From 2d68b250253eb07f8f796a6fe5f421efc90b70e1 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Sat, 6 Feb 2016 14:39:47 -0800 Subject: [PATCH] Early work towards moving the cd special autosuggestion into completions This will simplify some code and make the cd autosuggestion smarter --- src/complete.cpp | 23 +++++---- src/expand.cpp | 129 ++++++++++++++++++++++++++++++++-------------- src/expand.h | 5 +- src/highlight.cpp | 52 ++----------------- src/path.cpp | 48 +++++++++++++++++ src/path.h | 3 ++ 6 files changed, 162 insertions(+), 98 deletions(-) diff --git a/src/complete.cpp b/src/complete.cpp index 5aa253056..790971f8a 100644 --- a/src/complete.cpp +++ b/src/complete.cpp @@ -362,7 +362,9 @@ public: const wcstring &str, bool use_switches); - void complete_param_expand(const wcstring &str, bool do_file, bool directories_only = false); + void complete_param_expand(const wcstring &str, bool do_file, bool handle_as_special_cd = false); + + void complete_special_cd(const wcstring &str); void complete_cmd(const wcstring &str, bool use_function, @@ -1335,17 +1337,19 @@ bool completer_t::complete_param(const wcstring &scmd_orig, const wcstring &spop } /** - Perform file completion on the specified string + Perform generic (not command-specific) expansions on the specified string */ -void completer_t::complete_param_expand(const wcstring &str, bool do_file, bool directories_only) +void completer_t::complete_param_expand(const wcstring &str, bool do_file, bool handle_as_special_cd) { expand_flags_t flags = EXPAND_SKIP_CMDSUBST | EXPAND_FOR_COMPLETIONS | this->expand_flags(); if (! do_file) flags |= EXPAND_SKIP_WILDCARDS; - if (directories_only && do_file) - flags |= DIRECTORIES_ONLY; + if (handle_as_special_cd && do_file) + { + flags |= DIRECTORIES_ONLY | EXPAND_SPECIAL_CD; + } /* Squelch file descriptions per issue 254 */ if (this->type() == COMPLETE_AUTOSUGGEST || do_file) @@ -1767,13 +1771,14 @@ void complete(const wcstring &cmd_with_subcmds, std::vector &comps in_redirection = (redirection != NULL); } - bool do_file = false, directories_only = false; + bool do_file = false, handle_as_special_cd = false; if (in_redirection) { do_file = true; } else { + /* Try completing as an argument */ wcstring current_command_unescape, previous_argument_unescape, current_argument_unescape; if (unescape_string(current_command, ¤t_command_unescape, UNESCAPE_DEFAULT) && unescape_string(previous_argument, &previous_argument_unescape, UNESCAPE_DEFAULT) && @@ -1813,8 +1818,8 @@ void complete(const wcstring &cmd_with_subcmds, std::vector &comps if (completer.empty()) do_file = true; - /* Hack. If we're cd, do directories only (#1059) */ - directories_only = (current_command_unescape == L"cd"); + /* Hack. If we're cd, handle it specially (#1059, others) */ + handle_as_special_cd = (current_command_unescape == L"cd"); /* And if we're autosuggesting, and the token is empty, don't do file suggestions */ if ((flags & COMPLETION_REQUEST_AUTOSUGGESTION) && current_argument_unescape.empty()) @@ -1824,7 +1829,7 @@ void complete(const wcstring &cmd_with_subcmds, std::vector &comps } /* This function wants the unescaped string */ - completer.complete_param_expand(current_token, do_file, directories_only); + completer.complete_param_expand(current_token, do_file, handle_as_special_cd); } } } diff --git a/src/expand.cpp b/src/expand.cpp index dde7ddac2..3be1c679e 100644 --- a/src/expand.cpp +++ b/src/expand.cpp @@ -39,6 +39,7 @@ parameter expansion. #include "env.h" #include "proc.h" #include "parser.h" +#include "path.h" #include "expand.h" #include "wildcard.h" #include "exec.h" @@ -1776,6 +1777,29 @@ static expand_error_t expand_stage_home_and_pid(const wcstring &input, std::vect } return EXPAND_OK; } +#if 0 +if (string_prefixes_string(L"./", path)) +{ + /* Ignore the CDPATH in this case; just use the working directory */ + directories.push_back(working_directory); +} +else +{ + /* Get the CDPATH */ + env_var_t cdpath = env_get_string(L"CDPATH"); + if (cdpath.missing_or_empty()) + cdpath = L"."; + + /* Tokenize it into 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)); + } +} +#endif static expand_error_t expand_stage_wildcards(const wcstring &input, std::vector *out, expand_flags_t flags, parse_error_list_t *errors) { @@ -1792,55 +1816,82 @@ static expand_error_t expand_stage_wildcards(const wcstring &input, std::vector< else if (((flags & EXPAND_FOR_COMPLETIONS) && (!(flags & EXPAND_SKIP_WILDCARDS))) || has_wildcard) { - /* We either have a wildcard, or we don't have a wildcard but we're doing completion expansion (so we want to get the completion of a file path) */ - wcstring start, rest; + /* We either have a wildcard, or we don't have a wildcard but we're doing completion expansion (so we want to get the completion of a file path). Note that if EXPAND_SKIP_WILDCARDS is set, we stomped wildcards in remove_internal_separator above, so there actually aren't any. + + So we're going to treat this input as a file path. Compute the base path. This may be literal if we start with / or ./, otherwise it may be CDPATH if the special flag is set. + */ - if (next[0] == L'/') + wcstring_list_t base_dirs; + wcstring path_remainder; + if (! (flags & EXPAND_SPECIAL_CD)) { - start = L"/"; - rest = next.substr(1); - } - else - { - start = L""; - rest = next; - } - - std::vector expanded; - int wc_res = wildcard_expand_string(rest, start, flags, &expanded); - if (flags & EXPAND_FOR_COMPLETIONS) - { - out->insert(out->end(), expanded.begin(), expanded.end()); - } - else - { - switch (wc_res) + /* Common case */ + if (string_prefixes_string(L"/", next)) { - case 0: + base_dirs.push_back(L"/"); + path_remainder = next.substr(1); + } + else + { + base_dirs.push_back(L""); + path_remainder = next; + } + } + else + { + /* Ignore the CDPATH if we start with ./ or / */ + if (string_prefixes_string(L"./", next)) + { + base_dirs.push_back(L""); + path_remainder = next; + } + else if (string_prefixes_string(L"/", next)) + { + base_dirs.push_back(L"/"); + path_remainder = next.substr(1); + } + else + { + /* Get the CDPATH and cwd. Perhaps these should be passed in. */ + const wcstring working_directory = env_get_pwd_slash(); + env_var_t cdpath = env_get_string(L"CDPATH"); + if (cdpath.missing_or_empty()) + cdpath = L"."; + + /* Tokenize it into directories */ + wcstokenizer tokenizer(cdpath, ARRAY_SEP_STR); + wcstring next_path; + while (tokenizer.next(next_path)) { - result = EXPAND_WILDCARD_NO_MATCH; - break; - } - - case 1: - { - result = EXPAND_WILDCARD_MATCH; - std::sort(expanded.begin(), expanded.end(), completion_t::is_naturally_less_than); - out->insert(out->end(), expanded.begin(), expanded.end()); - break; - } - - case -1: - { - result = EXPAND_ERROR; - break; + /* Ensure that we use the working directory for relative cdpaths like "." */ + base_dirs.push_back(path_apply_working_directory(next_path, working_directory)); } } } + + result = EXPAND_WILDCARD_NO_MATCH; + std::vector expanded; + for (size_t base_dir_idx = 0; base_dir_idx < base_dirs.size(); base_dir_idx++) + { + int local_wc_res = wildcard_expand_string(path_remainder, base_dirs.at(base_dir_idx), flags, &expanded); + if (local_wc_res > 0) + { + // Something matched,so overall we matched + result = EXPAND_WILDCARD_MATCH; + } + else if (local_wc_res < 0) + { + // Cancellation + result = EXPAND_ERROR; + break; + } + } + + out->insert(out->end(), expanded.begin(), expanded.end()); } else { - /* Can't yet justify this check */ + /* Can't fully justify this check. I think it's that SKIP_WILDCARDS is used when completing to mean don't do file expansions, so if we're not doing file expansions, just drop this completion on the floor. */ if (!(flags & EXPAND_FOR_COMPLETIONS)) { append_completion(out, next); diff --git a/src/expand.h b/src/expand.h index e81f7143e..2c526358f 100644 --- a/src/expand.h +++ b/src/expand.h @@ -59,7 +59,10 @@ enum EXPAND_FUZZY_MATCH = 1 << 9, /** Disallow directory abbreviations like /u/l/b for /usr/local/bin. Only applicable if EXPAND_FUZZY_MATCH is set. */ - EXPAND_NO_FUZZY_DIRECTORIES = 1 << 10 + EXPAND_NO_FUZZY_DIRECTORIES = 1 << 10, + + /** Do expansions specifically to support cd (CDPATH, etc) */ + EXPAND_SPECIAL_CD = 1 << 11 }; typedef int expand_flags_t; diff --git a/src/highlight.cpp b/src/highlight.cpp index 3c70f68c3..d2427d7cf 100644 --- a/src/highlight.cpp +++ b/src/highlight.cpp @@ -65,52 +65,6 @@ static const wchar_t * const highlight_var[] = }; -/* If the given path looks like it's relative to the working directory, then prepend that working directory. This operates on unescaped paths only (so a ~ means a literal ~) */ -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 HOME_DIRECTORY: - 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; - } -} - /* Determine if the filesystem containing the given fd is case insensitive. */ typedef std::map case_sensitivity_cache_t; bool fs_is_case_insensitive(const wcstring &path, int fd, case_sensitivity_cache_t &case_sensitivity_cache) @@ -261,7 +215,7 @@ bool is_potential_path(const wcstring &potential_path_fragment, const wcstring_l { const wcstring &wd = directories.at(wd_idx); - const wcstring abs_path = apply_working_directory(clean_potential_path_fragment, wd); + const wcstring abs_path = path_apply_working_directory(clean_potential_path_fragment, wd); /* Skip this if it's empty or we've already checked it */ if (abs_path.empty() || checked_paths.count(abs_path)) @@ -399,7 +353,7 @@ static bool is_potential_cd_path(const wcstring &path, const wcstring &working_d 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)); + directories.push_back(path_apply_working_directory(next_path, working_directory)); } } @@ -1226,7 +1180,7 @@ void highlighter_t::color_redirection(const parse_node_t &redirection_node) else { /* Ok, we successfully expanded our target. Now verify that it works with this redirection. We will probably need it as a path (but not in the case of fd redirections). Note that the target is now unescaped. */ - const wcstring target_path = apply_working_directory(target, this->working_directory); + const wcstring target_path = path_apply_working_directory(target, this->working_directory); switch (redirect_type) { case TOK_REDIRECT_FD: diff --git a/src/path.cpp b/src/path.cpp index b8cfd5165..a85b662f3 100644 --- a/src/path.cpp +++ b/src/path.cpp @@ -240,6 +240,54 @@ bool path_can_be_implicit_cd(const wcstring &path, wcstring *out_path, const wch return result; } +/* If the given path looks like it's relative to the working directory, then prepend that working directory. This operates on unescaped paths only (so a ~ means a literal ~) */ +wcstring path_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 HOME_DIRECTORY: + 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; + } +} + + + static wcstring path_create_config() { bool done = false; diff --git a/src/path.h b/src/path.h index 973e8a3ae..eec776a6e 100644 --- a/src/path.h +++ b/src/path.h @@ -96,4 +96,7 @@ bool path_is_valid(const wcstring &path, const wcstring &working_directory); /** Returns whether the two paths refer to the same file */ bool paths_are_same_file(const wcstring &path1, const wcstring &path2); +/* If the given path looks like it's relative to the working directory, then prepend that working directory. This operates on unescaped paths only (so a ~ means a literal ~) */ +wcstring path_apply_working_directory(const wcstring &path, const wcstring &working_directory); + #endif