diff options
author | ridiculousfish <corydoras@ridiculousfish.com> | 2012-05-07 17:31:24 -0700 |
---|---|---|
committer | ridiculousfish <corydoras@ridiculousfish.com> | 2012-05-07 17:31:24 -0700 |
commit | 0c79bb6e7c679552faba6cb333592eb1da26db35 (patch) | |
tree | 6eb08236422a8f31862f1d898d8fddcf08eb0b3c /highlight.cpp | |
parent | 1a264ab7c20cbd0e20eef3e3d83fedb7b69c163d (diff) |
Factor is_potential_path to properly handle CDPATH
This will let us color cd commands better
Diffstat (limited to 'highlight.cpp')
-rw-r--r-- | highlight.cpp | 412 |
1 files changed, 189 insertions, 223 deletions
diff --git a/highlight.cpp b/highlight.cpp index f7eb0b1f..41897c24 100644 --- a/highlight.cpp +++ b/highlight.cpp @@ -64,22 +64,54 @@ static const wchar_t * const highlight_var[] = L"fish_color_autosuggestion" }; -/** - Tests if the specified string is the prefix of any valid path in the system. - - \require_dir Whether the valid path must be a directory - \out_path If non-null, the path on output - \return zero it this is not a valid prefix, non-zero otherwise -*/ -// PCA DOES_IO -static bool is_potential_path( const wcstring &cpath, wcstring *out_path = NULL, bool require_dir = false ) +/* If the given path looks like it's relative to the working directory, then prepend that working directory. */ +static wcstring apply_working_directory(const wcstring &path, const wcstring &working_directory) { + if (path.empty() || working_directory.empty()) + return path; + + /* We're going to make sure that if we want to prepend the wd, that the string has no leading / */ + bool prepend_wd; + switch (path.at(0)) { + case L'/': + case L'~': + prepend_wd = false; + break; + default: + prepend_wd = true; + break; + } + + if (! prepend_wd) { + /* No need to prepend the wd, so just return the path we were given */ + return path; + } else { + /* Remove up to one ./ */ + wcstring path_component = path; + if (string_prefixes_string(L"./", path_component)) { + path_component.erase(0, 2); + } + + /* Removing leading /s */ + while (string_prefixes_string(L"/", path_component)) { + path_component.erase(0, 1); + } + + /* Construct and return a new path */ + wcstring new_path = working_directory; + append_path_component(new_path, path_component); + return new_path; + } +} + +/* Tests whether the specified string cpath is the prefix of anything we could cd to. directories is a list of possible parent directories (typically either the working directory, or the cdpath). This does I/O! */ +static bool is_potential_path( const wcstring &cpath, const wcstring_list_t &directories, bool require_dir = false, wcstring *out_path = NULL) { ASSERT_IS_BACKGROUND_THREAD(); const wchar_t *unescaped, *in; - wcstring cleaned_path; + wcstring clean_path; int has_magic = 0; - bool res = false; + bool result = false; wcstring path(cpath); expand_tilde(path); @@ -115,7 +147,7 @@ static bool is_potential_path( const wcstring &cpath, wcstring *out_path = NULL, default: { - cleaned_path += *in; + clean_path.append(in, 1); break; } @@ -123,62 +155,98 @@ static bool is_potential_path( const wcstring &cpath, wcstring *out_path = NULL, } - if( !has_magic && ! cleaned_path.empty() ) + if( ! has_magic && ! clean_path.empty() ) { - bool must_be_full_dir = cleaned_path[cleaned_path.length()-1] == L'/'; - DIR *dir; - if( must_be_full_dir ) - { - dir = wopendir( cleaned_path ); - if( dir ) - { - res = true; - if (out_path) - *out_path = cleaned_path; - closedir( dir ); - } - } - else - { - wcstring dir_name = wdirname(cleaned_path); - wcstring base_name = wbasename(cleaned_path); + /* Don't test the same path multiple times, which can happen if the path is absolute and the CDPATH contains multiple entries */ + std::set<wcstring> checked_paths; + + for (size_t wd_idx = 0; wd_idx < directories.size() && ! result; wd_idx++) { + const wcstring &wd = directories.at(wd_idx); + + const wcstring abs_path = apply_working_directory(clean_path, wd); + + /* Skip this if it's empty or we've already checked it */ + if (abs_path.empty() || checked_paths.count(abs_path)) + continue; + checked_paths.insert(abs_path); - if( dir_name == L"/" && base_name == L"/" ) + /* If we end with a slash, then it must be a directory */ + bool must_be_full_dir = abs_path.at(abs_path.size()-1) == L'/'; + if (must_be_full_dir) { - res = true; - if (out_path) - *out_path = cleaned_path; + struct stat buf; + if (0 == wstat(abs_path, &buf) && S_ISDIR(buf.st_mode)) { + result = true; + /* Return the path suffix, not the whole absolute path */ + if (out_path) + *out_path = clean_path; + } } - else if( (dir = wopendir( dir_name)) ) + else { - wcstring ent; - bool is_dir; - while (wreaddir_resolving(dir, dir_name, ent, &is_dir)) + DIR *dir = NULL; + + /* We do not end with a slash; it does not have to be a directory */ + const wcstring dir_name = wdirname(abs_path); + const wcstring base_name = wbasename(abs_path); + if (dir_name == L"/" && base_name == L"/") { - if (string_prefixes_string(base_name, ent) && (! require_dir || is_dir)) + result = true; + if (out_path) + *out_path = clean_path; + } + else if ((dir = wopendir(dir_name))) { + /* We opened the dir_name; look for a string where the base name prefixes it */ + wcstring ent; + + // Don't ask for the is_dir value unless we care, because it can cause extra filesystem acces */ + bool is_dir = false; + while (wreaddir_resolving(dir, dir_name, ent, require_dir ? &is_dir : NULL)) { - res = true; - if (out_path) { - out_path->assign(dir_name); - out_path->push_back(L'/'); - out_path->append(ent); - path_make_canonical(*out_path); - /* We actually do want a trailing / for directories, since it makes autosuggestion a bit nicer */ - if (is_dir) + /* TODO: support doing the right thing on case-insensitive filesystems like HFS+ */ + if (string_prefixes_string(base_name, ent) && (! require_dir || is_dir)) + { + result = true; + if (out_path) { + out_path->assign(dir_name); out_path->push_back(L'/'); + out_path->append(ent); + path_make_canonical(*out_path); + /* We actually do want a trailing / for directories, since it makes autosuggestion a bit nicer */ + if (is_dir) + out_path->push_back(L'/'); + } + break; } - break; } + closedir(dir); } - - closedir( dir ); } } - } - - return res; - + return result; +} + + +/* Given a string, return whether it prefixes a path that we could cd into. Return that path in out_path */ +static bool is_potential_cd_path(const wcstring &path, const wcstring &working_directory, wcstring *out_path) { + /* Get the CDPATH */ + env_var_t cdpath = env_get_string(L"CDPATH"); + if (cdpath.missing_or_empty()) + cdpath = L"."; + + /* Tokenize it into directories */ + wcstring_list_t directories; + wcstokenizer tokenizer(cdpath, ARRAY_SEP_STR); + wcstring next_path; + while (tokenizer.next(next_path)) + { + /* Ensure that we use the working directory for relative cdpaths like "." */ + directories.push_back(apply_working_directory(next_path, working_directory)); + } + + /* Call is_potential_path */ + return is_potential_path(path, directories, true /* require_dir */, out_path); } rgb_color_t highlight_get_color( int highlight, bool is_background ) @@ -235,7 +303,6 @@ rgb_color_t highlight_get_color( int highlight, bool is_background ) */ static void highlight_param( const wcstring &buffstr, std::vector<int> &colors, int pos, wcstring_list_t *error ) { - return; const wchar_t * const buff = buffstr.c_str(); enum {e_unquoted, e_single_quoted, e_double_quoted} mode = e_unquoted; size_t in_pos, len = buffstr.size(); @@ -543,13 +610,18 @@ class autosuggest_parsed_command_t { /* Arguments to the command */ wcstring_list_t arguments; + /* Position in the string of the start of the last argument */ + int last_arg_pos; + autosuggest_parsed_command_t(const wcstring &str) { if (str.empty()) return; wcstring cmd; wcstring_list_t args; - bool had_cmd = false, recognized_cmd = false; + int arg_pos = -1; + + bool had_cmd = false; tokenizer tok; for (tok_init( &tok, str.c_str(), TOK_SQUASH_ERRORS); tok_has_next(&tok); tok_next(&tok)) { @@ -563,6 +635,7 @@ class autosuggest_parsed_command_t { { /* Parameter to the command */ args.push_back(tok_last(&tok)); + arg_pos = tok_get_pos(&tok); } else { @@ -576,7 +649,7 @@ class autosuggest_parsed_command_t { else { bool is_subcommand = false; - int mark = tok_get_pos( &tok ); + int mark = tok_get_pos(&tok); if (parser_keywords_is_subcommand(cmd)) { @@ -631,6 +704,7 @@ class autosuggest_parsed_command_t { had_cmd = false; cmd.empty(); args.empty(); + arg_pos = -1; break; } @@ -648,182 +722,71 @@ class autosuggest_parsed_command_t { if (had_cmd) { this->command.swap(cmd); this->arguments.swap(args); + this->last_arg_pos = arg_pos; } - } }; -/* Attempts to suggest a completion for a command we handle specially, like 'cd'. Returns true if we recognized the command (even if we couldn't think of a suggestion for it) */ -bool autosuggest_suggest_special(const wcstring &str, const wcstring &working_directory, wcstring &outString) { +bool autosuggest_suggest_special(const wcstring &str, const wcstring &working_directory, wcstring &outSuggestion) { if (str.empty()) return false; + + ASSERT_IS_BACKGROUND_THREAD(); - wcstring cmd; - bool had_cmd = false, recognized_cmd = false; - wcstring suggestion; + /* Parse the string */ + const autosuggest_parsed_command_t parsed(str); - tokenizer tok; - for( tok_init( &tok, str.c_str(), TOK_SQUASH_ERRORS ); - tok_has_next( &tok ); - tok_next( &tok ) ) - { - int last_type = tok_last_type( &tok ); - - switch( last_type ) - { - case TOK_STRING: - { - if( had_cmd ) - { - recognized_cmd = (cmd == L"cd"); - if( recognized_cmd ) - { - wcstring dir = tok_last( &tok ); - wcstring suggested_path; - - if (is_potential_path(dir, &suggested_path, true /* require directory */)) { - - /* suggested_path needs to actually have dir as a prefix (perhaps with different case). Handle stuff like ./ */ - bool wants_dot_slash = string_prefixes_string(L"./", dir); - bool has_dot_slash = string_prefixes_string(L"./", suggested_path); - - if (wants_dot_slash && ! has_dot_slash) { - suggested_path.insert(0, L"./"); - } else if (! wants_dot_slash && has_dot_slash) { - suggested_path.erase(0, 2); - } - - bool wants_tilde = string_prefixes_string(L"~", dir); - bool has_tilde = string_prefixes_string(L"~", suggested_path); - if (wants_tilde && ! has_tilde) { - // The input string has a tilde, the output string does not - // Extract the tilde part, expand it, see if the expansion prefixes the suggestion - // If so, replace it with the tilde part - size_t slash_idx = dir.find(L'/'); - const wcstring tilde_part(dir, 0, slash_idx); //note that slash_idx is npos this will return everything - - // Expand the tilde - wcstring expanded_tilde = tilde_part; - expand_tilde(expanded_tilde); - - // Replace it - if (string_prefixes_string(expanded_tilde, suggested_path)) { - suggested_path.replace(0, expanded_tilde.size(), tilde_part); - } - } - - suggestion = str; - suggestion.erase(tok_get_pos(&tok)); - suggestion.append(suggested_path); - } - } - } - else - { - /* - Command. First check that the command actually exists. - */ - cmd = tok_last( &tok ); - bool expanded = expand_one(cmd, EXPAND_SKIP_CMDSUBST | EXPAND_SKIP_VARIABLES); - if (! expanded || has_expand_reserved(cmd.c_str())) - { - - } - else - { - int is_subcommand = 0; - int mark = tok_get_pos( &tok ); - - if( parser_keywords_is_subcommand( cmd ) ) - { - - int sw; - - if( cmd == L"builtin") - { - } - else if( cmd == L"command") - { - } - - tok_next( &tok ); - - sw = parser_keywords_is_switch( tok_last( &tok ) ); - - if( !parser_keywords_is_block( cmd ) && - sw == ARG_SWITCH ) - { + bool result = false; + if (parsed.command == L"cd" && ! parsed.arguments.empty()) { + /* We can possibly handle this specially */ + wcstring dir = parsed.arguments.back(); + wcstring suggested_path; + + /* We always return true because we recognized the command. This prevents us from falling back to dumber algorithms; for example we won't suggest a non-directory for the cd command. */ + result = true; + outSuggestion.clear(); + + if (is_potential_cd_path(dir, working_directory, &suggested_path)) { - } - else - { - if( sw == ARG_SKIP ) - { - mark = tok_get_pos( &tok ); - } - - is_subcommand = 1; - } - tok_set_pos( &tok, mark ); - } - - if( !is_subcommand ) - { - had_cmd = true; - } - } - - } - break; - } - - case TOK_REDIRECT_NOCLOB: - case TOK_REDIRECT_OUT: - case TOK_REDIRECT_IN: - case TOK_REDIRECT_APPEND: - case TOK_REDIRECT_FD: - { - if( !had_cmd ) - { - break; - } - tok_next( &tok ); - break; - } - - case TOK_PIPE: - case TOK_BACKGROUND: - { - had_cmd = false; - break; - } - - case TOK_END: - { - had_cmd = false; - break; - } - - case TOK_COMMENT: - { - break; - } - - case TOK_ERROR: - default: - { - break; - } - } - } - tok_destroy( &tok ); - - if (recognized_cmd) { - outString.swap(suggestion); + /* suggested_path needs to actually have dir as a prefix (perhaps with different case). Handle stuff like ./ */ + bool wants_dot_slash = string_prefixes_string(L"./", dir); + bool has_dot_slash = string_prefixes_string(L"./", suggested_path); + + if (wants_dot_slash && ! has_dot_slash) { + suggested_path.insert(0, L"./"); + } else if (! wants_dot_slash && has_dot_slash) { + suggested_path.erase(0, 2); + } + + bool wants_tilde = string_prefixes_string(L"~", dir); + bool has_tilde = string_prefixes_string(L"~", suggested_path); + if (wants_tilde && ! has_tilde) { + // The input string has a tilde, the output string does not + // Extract the tilde part, expand it, see if the expansion prefixes the suggestion + // If so, replace it with the tilde part + size_t slash_idx = dir.find(L'/'); + const wcstring tilde_part(dir, 0, slash_idx); //note that slash_idx is npos this will return everything + + // Expand the tilde + wcstring expanded_tilde = tilde_part; + expand_tilde(expanded_tilde); + + // Replace it + if (string_prefixes_string(expanded_tilde, suggested_path)) { + suggested_path.replace(0, expanded_tilde.size(), tilde_part); + } + } + + /* Success */ + outSuggestion = str; + outSuggestion.erase(parsed.last_arg_pos); + outSuggestion.append(suggested_path); + } + } else { + /* Either an error or some other command, so we don't handle it specially */ } - - return recognized_cmd; + return result; } bool autosuggest_special_validate_from_history(const wcstring &str, const wcstring &working_directory, bool *outSuggestionOK) { @@ -1308,6 +1271,9 @@ void highlight_shell( const wcstring &buff, std::vector<int> &color, int pos, wc color.at(i) = last_val; } + /* Do something sucky and get the current working directory on this background thread. This should really be passed in. Note this needs to be a vector (of one directory). */ + const wcstring_list_t working_directories(1, get_working_directory()); + /* Color potentially valid paths in a special path color if they are the current token. @@ -1322,7 +1288,7 @@ void highlight_shell( const wcstring &buff, std::vector<int> &color, int pos, wc if( tok_begin && tok_end ) { const wcstring token(tok_begin, tok_end-tok_begin); - if( is_potential_path( token ) ) + if (is_potential_path(token, working_directories)) { for( ptrdiff_t i=tok_begin-cbuff; i < (tok_end-cbuff); i++ ) { |