diff --git a/src/builtin_complete.cpp b/src/builtin_complete.cpp index 1101f4a5a..f7f3f256d 100644 --- a/src/builtin_complete.cpp +++ b/src/builtin_complete.cpp @@ -456,7 +456,7 @@ static int builtin_complete(parser_t &parser, io_streams_t &streams, wchar_t **a recursion_level++; std::vector comp; - complete(do_complete_param, &comp, COMPLETION_REQUEST_DEFAULT); + complete(do_complete_param, &comp, COMPLETION_REQUEST_DEFAULT, env_vars_snapshot_t::current()); for (size_t i=0; i< comp.size() ; i++) { diff --git a/src/complete.cpp b/src/complete.cpp index 266451c4d..0a173a79a 100644 --- a/src/complete.cpp +++ b/src/complete.cpp @@ -343,6 +343,7 @@ class completer_t const completion_request_flags_t flags; const wcstring initial_cmd; std::vector completions; + const env_vars_snapshot_t &vars; //transient, stack-allocated /** Table of completions conditions that have already been tested and the corresponding test results */ typedef std::map condition_cache_t; @@ -377,9 +378,10 @@ class completer_t public: - completer_t(const wcstring &c, completion_request_flags_t f) : + completer_t(const wcstring &c, completion_request_flags_t f, const env_vars_snapshot_t &evs) : flags(f), - initial_cmd(c) + initial_cmd(c), + vars(evs) { } @@ -879,7 +881,7 @@ void completer_t::complete_cmd(const wcstring &str_cmd, bool use_function, bool std::vector possible_comp; - env_var_t cdpath = env_get_string(L"CDPATH"); + env_var_t cdpath = this->vars.get(L"CDPATH"); if (cdpath.missing_or_empty()) cdpath = L"."; @@ -906,7 +908,7 @@ void completer_t::complete_cmd(const wcstring &str_cmd, bool use_function, bool if (use_command) { - const env_var_t path = env_get_string(L"PATH"); + const env_var_t path = this->vars.get(L"PATH"); if (!path.missing()) { wcstring base_path; @@ -1484,6 +1486,7 @@ bool completer_t::complete_variable(const wcstring &str, size_t start_offset) wcstring desc; if (this->wants_descriptions()) { + // Can't use this->vars here, it could be any variable env_var_t value_unescaped = env_get_string(env_name); if (value_unescaped.missing()) continue; @@ -1631,7 +1634,7 @@ bool completer_t::try_complete_user(const wcstring &str) return res; } -void complete(const wcstring &cmd_with_subcmds, std::vector *out_comps, completion_request_flags_t flags) +void complete(const wcstring &cmd_with_subcmds, std::vector *out_comps, completion_request_flags_t flags, const env_vars_snapshot_t &vars) { /* Determine the innermost subcommand */ const wchar_t *cmdsubst_begin, *cmdsubst_end; @@ -1640,7 +1643,7 @@ void complete(const wcstring &cmd_with_subcmds, std::vector *out_c const wcstring cmd = wcstring(cmdsubst_begin, cmdsubst_end - cmdsubst_begin); /* Make our completer */ - completer_t completer(cmd, flags); + completer_t completer(cmd, flags, vars); wcstring current_command; const size_t pos = cmd.size(); diff --git a/src/complete.h b/src/complete.h index 999b3c2ba..f46370abb 100644 --- a/src/complete.h +++ b/src/complete.h @@ -218,9 +218,11 @@ void complete_remove_all(const wcstring &cmd, bool cmd_is_path); /** Find all completions of the command cmd, insert them into out. */ +class env_vars_snapshot_t; void complete(const wcstring &cmd, std::vector *out_comps, - completion_request_flags_t flags); + completion_request_flags_t flags, + const env_vars_snapshot_t &vars); /** Return a list of all current completions. diff --git a/src/env.cpp b/src/env.cpp index d951036a9..f6a0104b2 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -1443,3 +1443,5 @@ env_var_t env_vars_snapshot_t::get(const wcstring &key) const } const wchar_t * const env_vars_snapshot_t::highlighting_keys[] = {L"PATH", L"CDPATH", L"fish_function_path", NULL}; + +const wchar_t * const env_vars_snapshot_t::completing_keys[] = {L"PATH", L"CDPATH", NULL}; diff --git a/src/env.h b/src/env.h index d7b4c23d9..bfea16aba 100644 --- a/src/env.h +++ b/src/env.h @@ -227,10 +227,13 @@ class env_vars_snapshot_t { std::map vars; bool is_current() const; - + + env_vars_snapshot_t(const env_vars_snapshot_t&); + void operator=(const env_vars_snapshot_t &); + public: env_vars_snapshot_t(const wchar_t * const * keys); - env_vars_snapshot_t(void); + env_vars_snapshot_t(); env_var_t get(const wcstring &key) const; @@ -239,6 +242,9 @@ public: // vars necessary for highlighting static const wchar_t * const highlighting_keys[]; + + // vars necessary for completion + static const wchar_t * const completing_keys[]; }; extern bool g_log_forks; diff --git a/src/expand.cpp b/src/expand.cpp index 3be1c679e..6f93ac359 100644 --- a/src/expand.cpp +++ b/src/expand.cpp @@ -1804,10 +1804,10 @@ else static expand_error_t expand_stage_wildcards(const wcstring &input, std::vector *out, expand_flags_t flags, parse_error_list_t *errors) { expand_error_t result = EXPAND_OK; - wcstring next = input; + wcstring path_to_expand = input; - remove_internal_separator(&next, (EXPAND_SKIP_WILDCARDS & flags) ? true : false); - const bool has_wildcard = wildcard_has(next, true /* internal, i.e. ANY_CHAR */); + remove_internal_separator(&path_to_expand, (EXPAND_SKIP_WILDCARDS & flags) ? true : false); + const bool has_wildcard = wildcard_has(path_to_expand, true /* internal, i.e. ANY_CHAR */); if (has_wildcard && (flags & EXECUTABLES_ONLY)) { @@ -1818,62 +1818,50 @@ static expand_error_t expand_stage_wildcards(const wcstring &input, std::vector< { /* 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. + So we're going to treat this input as a file path. Compute the "working directories", which may be CDPATH if the special flag is set. */ - wcstring_list_t base_dirs; - wcstring path_remainder; + const wcstring working_dir = env_get_pwd_slash(); + wcstring_list_t effective_working_dirs; if (! (flags & EXPAND_SPECIAL_CD)) { /* Common case */ - if (string_prefixes_string(L"/", next)) - { - base_dirs.push_back(L"/"); - path_remainder = next.substr(1); - } - else - { - base_dirs.push_back(L""); - path_remainder = next; - } + effective_working_dirs.push_back(working_dir); } else { /* Ignore the CDPATH if we start with ./ or / */ - if (string_prefixes_string(L"./", next)) + if (string_prefixes_string(L"./", path_to_expand)) { - base_dirs.push_back(L""); - path_remainder = next; + effective_working_dirs.push_back(working_dir); } - else if (string_prefixes_string(L"/", next)) + else if (string_prefixes_string(L"/", path_to_expand)) { - base_dirs.push_back(L"/"); - path_remainder = next.substr(1); + effective_working_dirs.push_back(working_dir); } 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)) + wcstring next_cd_path; + while (tokenizer.next(next_cd_path)) { /* Ensure that we use the working directory for relative cdpaths like "." */ - base_dirs.push_back(path_apply_working_directory(next_path, working_directory)); + effective_working_dirs.push_back(path_apply_working_directory(next_cd_path, working_dir)); } } } result = EXPAND_WILDCARD_NO_MATCH; std::vector expanded; - for (size_t base_dir_idx = 0; base_dir_idx < base_dirs.size(); base_dir_idx++) + for (size_t wd_idx = 0; wd_idx < effective_working_dirs.size(); wd_idx++) { - int local_wc_res = wildcard_expand_string(path_remainder, base_dirs.at(base_dir_idx), flags, &expanded); + int local_wc_res = wildcard_expand_string(path_to_expand, effective_working_dirs.at(wd_idx), flags, &expanded); if (local_wc_res > 0) { // Something matched,so overall we matched @@ -1894,7 +1882,7 @@ static expand_error_t expand_stage_wildcards(const wcstring &input, std::vector< /* 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); + append_completion(out, path_to_expand); } } return result; diff --git a/src/fish_tests.cpp b/src/fish_tests.cpp index 67e5dfa09..78246a8f3 100644 --- a/src/fish_tests.cpp +++ b/src/fish_tests.cpp @@ -1579,6 +1579,7 @@ static void test_expand() err(L"chdir failed"); return; } + env_set_pwd(); expand_test(L"b/xx", EXPAND_FOR_COMPLETIONS | EXPAND_FUZZY_MATCH, L"bax/xxx", L"baz/xxx", wnull, @@ -2092,20 +2093,22 @@ static void test_complete(void) const wcstring_list_t names(name_strs, name_strs + count); complete_set_variable_names(&names); + + const env_vars_snapshot_t &vars = env_vars_snapshot_t::current(); std::vector completions; - complete(L"$F", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"$F", &completions, COMPLETION_REQUEST_DEFAULT, vars); do_test(completions.size() == 3); do_test(completions.at(0).completion == L"oo1"); do_test(completions.at(1).completion == L"oo2"); do_test(completions.at(2).completion == L"oo3"); completions.clear(); - complete(L"$1", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"$1", &completions, COMPLETION_REQUEST_DEFAULT, vars); do_test(completions.empty()); completions.clear(); - complete(L"$1", &completions, COMPLETION_REQUEST_DEFAULT | COMPLETION_REQUEST_FUZZY_MATCH); + complete(L"$1", &completions, COMPLETION_REQUEST_DEFAULT | COMPLETION_REQUEST_FUZZY_MATCH, vars); do_test(completions.size() == 2); do_test(completions.at(0).completion == L"$Foo1"); do_test(completions.at(1).completion == L"$Bar1"); @@ -2115,17 +2118,17 @@ static void test_complete(void) if (system("chmod 700 '/tmp/complete_test/testfile'")) err(L"chmod failed"); completions.clear(); - complete(L"echo (/tmp/complete_test/testfil", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"echo (/tmp/complete_test/testfil", &completions, COMPLETION_REQUEST_DEFAULT, vars); do_test(completions.size() == 1); do_test(completions.at(0).completion == L"e"); completions.clear(); - complete(L"echo (ls /tmp/complete_test/testfil", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"echo (ls /tmp/complete_test/testfil", &completions, COMPLETION_REQUEST_DEFAULT, vars); do_test(completions.size() == 1); do_test(completions.at(0).completion == L"e"); completions.clear(); - complete(L"echo (command ls /tmp/complete_test/testfil", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"echo (command ls /tmp/complete_test/testfil", &completions, COMPLETION_REQUEST_DEFAULT, vars); do_test(completions.size() == 1); do_test(completions.at(0).completion == L"e"); @@ -2137,38 +2140,38 @@ static void test_complete(void) /* Complete a function name */ completions.clear(); - complete(L"echo (scuttlebut", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"echo (scuttlebut", &completions, COMPLETION_REQUEST_DEFAULT, vars); do_test(completions.size() == 1); do_test(completions.at(0).completion == L"t"); /* But not with the command prefix */ completions.clear(); - complete(L"echo (command scuttlebut", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"echo (command scuttlebut", &completions, COMPLETION_REQUEST_DEFAULT, vars); do_test(completions.size() == 0); /* Not with the builtin prefix */ completions.clear(); - complete(L"echo (builtin scuttlebut", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"echo (builtin scuttlebut", &completions, COMPLETION_REQUEST_DEFAULT, vars); do_test(completions.size() == 0); /* Not after a redirection */ completions.clear(); - complete(L"echo hi > scuttlebut", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"echo hi > scuttlebut", &completions, COMPLETION_REQUEST_DEFAULT, vars); do_test(completions.size() == 0); /* Trailing spaces (#1261) */ complete_add(L"foobarbaz", false, wcstring(), option_type_args_only, NO_FILES, NULL, L"qux", NULL, COMPLETE_AUTO_SPACE); completions.clear(); - complete(L"foobarbaz ", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"foobarbaz ", &completions, COMPLETION_REQUEST_DEFAULT, vars); do_test(completions.size() == 1); do_test(completions.at(0).completion == L"qux"); /* Don't complete variable names in single quotes (#1023) */ completions.clear(); - complete(L"echo '$Foo", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"echo '$Foo", &completions, COMPLETION_REQUEST_DEFAULT, vars); do_test(completions.empty()); completions.clear(); - complete(L"echo \\$Foo", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"echo \\$Foo", &completions, COMPLETION_REQUEST_DEFAULT, vars); do_test(completions.empty()); /* File completions */ @@ -2179,54 +2182,54 @@ static void test_complete(void) exit(-1); } if (chdir("/tmp/complete_test/")) err(L"chdir failed"); - complete(L"cat te", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"cat te", &completions, COMPLETION_REQUEST_DEFAULT, vars); do_test(completions.size() == 1); do_test(completions.at(0).completion == L"stfile"); completions.clear(); - complete(L"something --abc=te", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"something --abc=te", &completions, COMPLETION_REQUEST_DEFAULT, vars); do_test(completions.size() == 1); do_test(completions.at(0).completion == L"stfile"); completions.clear(); - complete(L"something -abc=te", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"something -abc=te", &completions, COMPLETION_REQUEST_DEFAULT, vars); do_test(completions.size() == 1); do_test(completions.at(0).completion == L"stfile"); completions.clear(); - complete(L"something abc=te", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"something abc=te", &completions, COMPLETION_REQUEST_DEFAULT, vars); do_test(completions.size() == 1); do_test(completions.at(0).completion == L"stfile"); completions.clear(); - complete(L"something abc=stfile", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"something abc=stfile", &completions, COMPLETION_REQUEST_DEFAULT, vars); do_test(completions.size() == 0); completions.clear(); - complete(L"something abc=stfile", &completions, COMPLETION_REQUEST_FUZZY_MATCH); + complete(L"something abc=stfile", &completions, COMPLETION_REQUEST_FUZZY_MATCH, vars); do_test(completions.size() == 1); do_test(completions.at(0).completion == L"abc=testfile"); completions.clear(); - complete(L"cat /tmp/complete_test/te", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"cat /tmp/complete_test/te", &completions, COMPLETION_REQUEST_DEFAULT, vars); do_test(completions.size() == 1); do_test(completions.at(0).completion == L"stfile"); completions.clear(); - complete(L"echo sup > /tmp/complete_test/te", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"echo sup > /tmp/complete_test/te", &completions, COMPLETION_REQUEST_DEFAULT, vars); do_test(completions.size() == 1); do_test(completions.at(0).completion == L"stfile"); completions.clear(); - complete(L"echo sup > /tmp/complete_test/te", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"echo sup > /tmp/complete_test/te", &completions, COMPLETION_REQUEST_DEFAULT, vars); do_test(completions.size() == 1); do_test(completions.at(0).completion == L"stfile"); completions.clear(); // Zero escapes can cause problems. See #1631 - complete(L"cat foo\\0", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"cat foo\\0", &completions, COMPLETION_REQUEST_DEFAULT, vars); do_test(completions.empty()); completions.clear(); - complete(L"cat foo\\0bar", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"cat foo\\0bar", &completions, COMPLETION_REQUEST_DEFAULT, vars); do_test(completions.empty()); completions.clear(); - complete(L"cat \\0", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"cat \\0", &completions, COMPLETION_REQUEST_DEFAULT, vars); do_test(completions.empty()); completions.clear(); - complete(L"cat te\\0", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"cat te\\0", &completions, COMPLETION_REQUEST_DEFAULT, vars); do_test(completions.empty()); completions.clear(); @@ -2299,10 +2302,10 @@ static void test_completion_insertions() TEST_1_COMPLETION(L"'foo^", L"bar", COMPLETE_REPLACES_TOKEN, false, L"bar ^"); } -static void perform_one_autosuggestion_special_test(const wcstring &command, const wcstring &wd, const wcstring &expected, long line) +static void perform_one_autosuggestion_special_test(const wcstring &command, const env_vars_snapshot_t &vars, const wcstring &expected, long line) { completion_t suggestion(L""); - bool success = autosuggest_suggest_special(command, wd, &suggestion); + bool success = autosuggest_suggest_special(command, vars, &suggestion); if (! success) { printf("line %ld: autosuggest_suggest_special() failed for command %ls\n", line, command.c_str()); @@ -2317,6 +2320,31 @@ static void perform_one_autosuggestion_special_test(const wcstring &command, con } } +static void perform_one_autosuggestion_cd_test(const wcstring &command, const env_vars_snapshot_t &vars, const wcstring &expected, long line) +{ + std::vector comps; + complete(command, &comps,COMPLETION_REQUEST_AUTOSUGGESTION, vars); + + if (comps.empty()) + { + printf("line %ld: autosuggest_suggest_special() failed for command %ls\n", line, command.c_str()); + do_test(! comps.empty()); + return; + } + + completions_sort_and_prioritize(&comps); + const completion_t &suggestion = comps.at(0); + + if (suggestion.completion != expected) + { + printf("line %ld: complete() for cd returned the wrong expected string for command %ls\n", line, command.c_str()); + printf(" actual: %ls\n", suggestion.completion.c_str()); + printf("expected: %ls\n", expected.c_str()); + do_test(suggestion.completion == expected); + } +} + + /* Testing test_autosuggest_suggest_special, in particular for properly handling quotes and backslashes */ static void test_autosuggest_suggest_special() { @@ -2330,81 +2358,182 @@ static void test_autosuggest_suggest_special() if (system("mkdir -p /tmp/autosuggest_test/start/unique2/unique3/multi4")) err(L"mkdir failed"); if (system("mkdir -p /tmp/autosuggest_test/start/unique2/unique3/multi42")) err(L"mkdir failed"); if (system("mkdir -p /tmp/autosuggest_test/start/unique2/.hiddenDir/moreStuff")) err(L"mkdir failed"); - + + char saved_wd[PATH_MAX] = {}; + if (NULL == getcwd(saved_wd, sizeof saved_wd)) err(L"getcwd failed"); + const wcstring wd = L"/tmp/autosuggest_test/"; + if (wchdir(wd)) err(L"chdir failed"); + env_set_pwd(); env_set(L"AUTOSUGGEST_TEST_LOC", wd.c_str(), ENV_LOCAL); - - perform_one_autosuggestion_special_test(L"cd /tmp/autosuggest_test/0", wd, L"cd /tmp/autosuggest_test/0foobar/", __LINE__); - perform_one_autosuggestion_special_test(L"cd \"/tmp/autosuggest_test/0", wd, L"cd \"/tmp/autosuggest_test/0foobar/\"", __LINE__); - perform_one_autosuggestion_special_test(L"cd '/tmp/autosuggest_test/0", wd, L"cd '/tmp/autosuggest_test/0foobar/'", __LINE__); - perform_one_autosuggestion_special_test(L"cd 0", wd, L"cd 0foobar/", __LINE__); - perform_one_autosuggestion_special_test(L"cd \"0", wd, L"cd \"0foobar/\"", __LINE__); - perform_one_autosuggestion_special_test(L"cd '0", wd, L"cd '0foobar/'", __LINE__); - - perform_one_autosuggestion_special_test(L"cd /tmp/autosuggest_test/1", wd, L"cd /tmp/autosuggest_test/1foo\\ bar/", __LINE__); - perform_one_autosuggestion_special_test(L"cd \"/tmp/autosuggest_test/1", wd, L"cd \"/tmp/autosuggest_test/1foo bar/\"", __LINE__); - perform_one_autosuggestion_special_test(L"cd '/tmp/autosuggest_test/1", wd, L"cd '/tmp/autosuggest_test/1foo bar/'", __LINE__); - perform_one_autosuggestion_special_test(L"cd 1", wd, L"cd 1foo\\ bar/", __LINE__); - perform_one_autosuggestion_special_test(L"cd \"1", wd, L"cd \"1foo bar/\"", __LINE__); - perform_one_autosuggestion_special_test(L"cd '1", wd, L"cd '1foo bar/'", __LINE__); - - perform_one_autosuggestion_special_test(L"cd /tmp/autosuggest_test/2", wd, L"cd /tmp/autosuggest_test/2foo\\ \\ bar/", __LINE__); - perform_one_autosuggestion_special_test(L"cd \"/tmp/autosuggest_test/2", wd, L"cd \"/tmp/autosuggest_test/2foo bar/\"", __LINE__); - perform_one_autosuggestion_special_test(L"cd '/tmp/autosuggest_test/2", wd, L"cd '/tmp/autosuggest_test/2foo bar/'", __LINE__); - perform_one_autosuggestion_special_test(L"cd 2", wd, L"cd 2foo\\ \\ bar/", __LINE__); - perform_one_autosuggestion_special_test(L"cd \"2", wd, L"cd \"2foo bar/\"", __LINE__); - perform_one_autosuggestion_special_test(L"cd '2", wd, L"cd '2foo bar/'", __LINE__); - - perform_one_autosuggestion_special_test(L"cd /tmp/autosuggest_test/3", wd, L"cd /tmp/autosuggest_test/3foo\\\\bar/", __LINE__); - perform_one_autosuggestion_special_test(L"cd \"/tmp/autosuggest_test/3", wd, L"cd \"/tmp/autosuggest_test/3foo\\bar/\"", __LINE__); - perform_one_autosuggestion_special_test(L"cd '/tmp/autosuggest_test/3", wd, L"cd '/tmp/autosuggest_test/3foo\\bar/'", __LINE__); - perform_one_autosuggestion_special_test(L"cd 3", wd, L"cd 3foo\\\\bar/", __LINE__); - perform_one_autosuggestion_special_test(L"cd \"3", wd, L"cd \"3foo\\bar/\"", __LINE__); - perform_one_autosuggestion_special_test(L"cd '3", wd, L"cd '3foo\\bar/'", __LINE__); - - perform_one_autosuggestion_special_test(L"cd /tmp/autosuggest_test/4", wd, L"cd /tmp/autosuggest_test/4foo\\'bar/", __LINE__); - perform_one_autosuggestion_special_test(L"cd \"/tmp/autosuggest_test/4", wd, L"cd \"/tmp/autosuggest_test/4foo'bar/\"", __LINE__); - perform_one_autosuggestion_special_test(L"cd '/tmp/autosuggest_test/4", wd, L"cd '/tmp/autosuggest_test/4foo\\'bar/'", __LINE__); - perform_one_autosuggestion_special_test(L"cd 4", wd, L"cd 4foo\\'bar/", __LINE__); - perform_one_autosuggestion_special_test(L"cd \"4", wd, L"cd \"4foo'bar/\"", __LINE__); - perform_one_autosuggestion_special_test(L"cd '4", wd, L"cd '4foo\\'bar/'", __LINE__); - - perform_one_autosuggestion_special_test(L"cd /tmp/autosuggest_test/5", wd, L"cd /tmp/autosuggest_test/5foo\\\"bar/", __LINE__); - perform_one_autosuggestion_special_test(L"cd \"/tmp/autosuggest_test/5", wd, L"cd \"/tmp/autosuggest_test/5foo\\\"bar/\"", __LINE__); - perform_one_autosuggestion_special_test(L"cd '/tmp/autosuggest_test/5", wd, L"cd '/tmp/autosuggest_test/5foo\"bar/'", __LINE__); - perform_one_autosuggestion_special_test(L"cd 5", wd, L"cd 5foo\\\"bar/", __LINE__); - perform_one_autosuggestion_special_test(L"cd \"5", wd, L"cd \"5foo\\\"bar/\"", __LINE__); - perform_one_autosuggestion_special_test(L"cd '5", wd, L"cd '5foo\"bar/'", __LINE__); - //perform_one_autosuggestion_special_test(L"cd $AUTOSUGGEST_TEST_LOC/0", wd, L"cd $AUTOSUGGEST_TEST_LOC/0foobar/", __LINE__); + const env_vars_snapshot_t &vars = env_vars_snapshot_t::current(); - perform_one_autosuggestion_special_test(L"cd ~/test_autosuggest_suggest_specia", wd, L"cd ~/test_autosuggest_suggest_special/", __LINE__); + perform_one_autosuggestion_cd_test(L"cd /tmp/autosuggest_test/0", vars, L"/tmp/autosuggest_test/0foobar/", __LINE__); + perform_one_autosuggestion_cd_test(L"cd \"/tmp/autosuggest_test/0", vars, L"/tmp/autosuggest_test/0foobar/", __LINE__); + perform_one_autosuggestion_cd_test(L"cd '/tmp/autosuggest_test/0", vars, L"cd '/tmp/autosuggest_test/0foobar/'", __LINE__); + perform_one_autosuggestion_cd_test(L"cd 0", vars, L"cd 0foobar/", __LINE__); + perform_one_autosuggestion_cd_test(L"cd \"0", vars, L"cd \"0foobar/\"", __LINE__); + perform_one_autosuggestion_cd_test(L"cd '0", vars, L"cd '0foobar/'", __LINE__); + + perform_one_autosuggestion_cd_test(L"cd /tmp/autosuggest_test/1", vars, L"cd /tmp/autosuggest_test/1foo\\ bar/", __LINE__); + perform_one_autosuggestion_cd_test(L"cd \"/tmp/autosuggest_test/1", vars, L"cd \"/tmp/autosuggest_test/1foo bar/\"", __LINE__); + perform_one_autosuggestion_cd_test(L"cd '/tmp/autosuggest_test/1", vars, L"cd '/tmp/autosuggest_test/1foo bar/'", __LINE__); + perform_one_autosuggestion_cd_test(L"cd 1", vars, L"cd 1foo\\ bar/", __LINE__); + perform_one_autosuggestion_cd_test(L"cd \"1", vars, L"cd \"1foo bar/\"", __LINE__); + perform_one_autosuggestion_cd_test(L"cd '1", vars, L"cd '1foo bar/'", __LINE__); + + perform_one_autosuggestion_cd_test(L"cd /tmp/autosuggest_test/2", vars, L"cd /tmp/autosuggest_test/2foo\\ \\ bar/", __LINE__); + perform_one_autosuggestion_cd_test(L"cd \"/tmp/autosuggest_test/2", vars, L"cd \"/tmp/autosuggest_test/2foo bar/\"", __LINE__); + perform_one_autosuggestion_cd_test(L"cd '/tmp/autosuggest_test/2", vars, L"cd '/tmp/autosuggest_test/2foo bar/'", __LINE__); + perform_one_autosuggestion_cd_test(L"cd 2", vars, L"cd 2foo\\ \\ bar/", __LINE__); + perform_one_autosuggestion_cd_test(L"cd \"2", vars, L"cd \"2foo bar/\"", __LINE__); + perform_one_autosuggestion_cd_test(L"cd '2", vars, L"cd '2foo bar/'", __LINE__); + + perform_one_autosuggestion_cd_test(L"cd /tmp/autosuggest_test/3", vars, L"cd /tmp/autosuggest_test/3foo\\\\bar/", __LINE__); + perform_one_autosuggestion_cd_test(L"cd \"/tmp/autosuggest_test/3", vars, L"cd \"/tmp/autosuggest_test/3foo\\bar/\"", __LINE__); + perform_one_autosuggestion_cd_test(L"cd '/tmp/autosuggest_test/3", vars, L"cd '/tmp/autosuggest_test/3foo\\bar/'", __LINE__); + perform_one_autosuggestion_cd_test(L"cd 3", vars, L"cd 3foo\\\\bar/", __LINE__); + perform_one_autosuggestion_cd_test(L"cd \"3", vars, L"cd \"3foo\\bar/\"", __LINE__); + perform_one_autosuggestion_cd_test(L"cd '3", vars, L"cd '3foo\\bar/'", __LINE__); + + perform_one_autosuggestion_cd_test(L"cd /tmp/autosuggest_test/4", vars, L"cd /tmp/autosuggest_test/4foo\\'bar/", __LINE__); + perform_one_autosuggestion_cd_test(L"cd \"/tmp/autosuggest_test/4", vars, L"cd \"/tmp/autosuggest_test/4foo'bar/\"", __LINE__); + perform_one_autosuggestion_cd_test(L"cd '/tmp/autosuggest_test/4", vars, L"cd '/tmp/autosuggest_test/4foo\\'bar/'", __LINE__); + perform_one_autosuggestion_cd_test(L"cd 4", vars, L"cd 4foo\\'bar/", __LINE__); + perform_one_autosuggestion_cd_test(L"cd \"4", vars, L"cd \"4foo'bar/\"", __LINE__); + perform_one_autosuggestion_cd_test(L"cd '4", vars, L"cd '4foo\\'bar/'", __LINE__); + + perform_one_autosuggestion_cd_test(L"cd /tmp/autosuggest_test/5", vars, L"cd /tmp/autosuggest_test/5foo\\\"bar/", __LINE__); + perform_one_autosuggestion_cd_test(L"cd \"/tmp/autosuggest_test/5", vars, L"cd \"/tmp/autosuggest_test/5foo\\\"bar/\"", __LINE__); + perform_one_autosuggestion_cd_test(L"cd '/tmp/autosuggest_test/5", vars, L"cd '/tmp/autosuggest_test/5foo\"bar/'", __LINE__); + perform_one_autosuggestion_cd_test(L"cd 5", vars, L"cd 5foo\\\"bar/", __LINE__); + perform_one_autosuggestion_cd_test(L"cd \"5", vars, L"cd \"5foo\\\"bar/\"", __LINE__); + perform_one_autosuggestion_cd_test(L"cd '5", vars, L"cd '5foo\"bar/'", __LINE__); - perform_one_autosuggestion_special_test(L"cd /tmp/autosuggest_test/start/", wd, L"cd /tmp/autosuggest_test/start/unique2/unique3/", __LINE__); + //perform_one_autosuggestion_cd_test(L"cd $AUTOSUGGEST_TEST_LOC/0", vars, L"cd $AUTOSUGGEST_TEST_LOC/0foobar/", __LINE__); + + perform_one_autosuggestion_cd_test(L"cd ~/test_autosuggest_suggest_specia", vars, L"cd ~/test_autosuggest_suggest_special/", __LINE__); + + perform_one_autosuggestion_cd_test(L"cd /tmp/autosuggest_test/start/", vars, L"cd /tmp/autosuggest_test/start/unique2/unique3/", __LINE__); // A single quote should defeat tilde expansion - perform_one_autosuggestion_special_test(L"cd '~/test_autosuggest_suggest_specia'", wd, L"", __LINE__); + perform_one_autosuggestion_cd_test(L"cd '~/test_autosuggest_suggest_specia'", vars, L"", __LINE__); // Don't crash on ~ (2696) // note this was wd dependent, hence why we set it - char saved_wd[PATH_MAX] = {}; - if (NULL == getcwd(saved_wd, sizeof saved_wd)) err(L"getcwd failed"); if (chdir("/tmp/autosuggest_test/")) err(L"chdir failed"); + env_set_pwd(); + if (system("mkdir -p '/tmp/autosuggest_test/~hahaha/path1/path2/'")) err(L"mkdir failed"); - perform_one_autosuggestion_special_test(L"cd ~haha", wd, L"cd ~hahaha/path1/path2/", __LINE__); + perform_one_autosuggestion_cd_test(L"cd ~haha", vars, L"cd ~hahaha/path1/path2/", __LINE__); if (chdir(saved_wd)) err(L"chdir failed"); + + env_set_pwd(); if (system("rm -Rf '/tmp/autosuggest_test/'")) err(L"rm failed"); if (system("rm -Rf ~/test_autosuggest_suggest_special/")) err(L"rm failed"); } +static void test_autosuggest_suggest_special2() +{ + if (system("mkdir -p '/tmp/autosuggest_test/0foobar'")) err(L"mkdir failed"); + if (system("mkdir -p '/tmp/autosuggest_test/1foo bar'")) err(L"mkdir failed"); + if (system("mkdir -p '/tmp/autosuggest_test/2foo bar'")) err(L"mkdir failed"); + if (system("mkdir -p '/tmp/autosuggest_test/3foo\\bar'")) err(L"mkdir failed"); + if (system("mkdir -p /tmp/autosuggest_test/4foo\\'bar")) err(L"mkdir failed"); //a path with a single quote + if (system("mkdir -p /tmp/autosuggest_test/5foo\\\"bar")) err(L"mkdir failed"); //a path with a double quote + if (system("mkdir -p ~/test_autosuggest_suggest_special/")) err(L"mkdir failed"); //make sure tilde is handled + if (system("mkdir -p /tmp/autosuggest_test/start/unique2/unique3/multi4")) err(L"mkdir failed"); + if (system("mkdir -p /tmp/autosuggest_test/start/unique2/unique3/multi42")) err(L"mkdir failed"); + if (system("mkdir -p /tmp/autosuggest_test/start/unique2/.hiddenDir/moreStuff")) err(L"mkdir failed"); + + char saved_wd[PATH_MAX] = {}; + if (NULL == getcwd(saved_wd, sizeof saved_wd)) err(L"getcwd failed"); + + const wcstring wd = L"/tmp/autosuggest_test/"; + if (wchdir(wd)) err(L"chdir failed"); + env_set_pwd(); + + env_set(L"AUTOSUGGEST_TEST_LOC", wd.c_str(), ENV_LOCAL); + + const env_vars_snapshot_t &vars = env_vars_snapshot_t::current(); + + perform_one_autosuggestion_special_test(L"cd /tmp/autosuggest_test/0", vars, L"cd /tmp/autosuggest_test/0foobar/", __LINE__); + perform_one_autosuggestion_special_test(L"cd \"/tmp/autosuggest_test/0", vars, L"cd \"/tmp/autosuggest_test/0foobar/\"", __LINE__); + perform_one_autosuggestion_special_test(L"cd '/tmp/autosuggest_test/0", vars, L"cd '/tmp/autosuggest_test/0foobar/'", __LINE__); + perform_one_autosuggestion_special_test(L"cd 0", vars, L"cd 0foobar/", __LINE__); + perform_one_autosuggestion_special_test(L"cd \"0", vars, L"cd \"0foobar/\"", __LINE__); + perform_one_autosuggestion_special_test(L"cd '0", vars, L"cd '0foobar/'", __LINE__); + + perform_one_autosuggestion_special_test(L"cd /tmp/autosuggest_test/1", vars, L"cd /tmp/autosuggest_test/1foo\\ bar/", __LINE__); + perform_one_autosuggestion_special_test(L"cd \"/tmp/autosuggest_test/1", vars, L"cd \"/tmp/autosuggest_test/1foo bar/\"", __LINE__); + perform_one_autosuggestion_special_test(L"cd '/tmp/autosuggest_test/1", vars, L"cd '/tmp/autosuggest_test/1foo bar/'", __LINE__); + perform_one_autosuggestion_special_test(L"cd 1", vars, L"cd 1foo\\ bar/", __LINE__); + perform_one_autosuggestion_special_test(L"cd \"1", vars, L"cd \"1foo bar/\"", __LINE__); + perform_one_autosuggestion_special_test(L"cd '1", vars, L"cd '1foo bar/'", __LINE__); + + perform_one_autosuggestion_special_test(L"cd /tmp/autosuggest_test/2", vars, L"cd /tmp/autosuggest_test/2foo\\ \\ bar/", __LINE__); + perform_one_autosuggestion_special_test(L"cd \"/tmp/autosuggest_test/2", vars, L"cd \"/tmp/autosuggest_test/2foo bar/\"", __LINE__); + perform_one_autosuggestion_special_test(L"cd '/tmp/autosuggest_test/2", vars, L"cd '/tmp/autosuggest_test/2foo bar/'", __LINE__); + perform_one_autosuggestion_special_test(L"cd 2", vars, L"cd 2foo\\ \\ bar/", __LINE__); + perform_one_autosuggestion_special_test(L"cd \"2", vars, L"cd \"2foo bar/\"", __LINE__); + perform_one_autosuggestion_special_test(L"cd '2", vars, L"cd '2foo bar/'", __LINE__); + + perform_one_autosuggestion_special_test(L"cd /tmp/autosuggest_test/3", vars, L"cd /tmp/autosuggest_test/3foo\\\\bar/", __LINE__); + perform_one_autosuggestion_special_test(L"cd \"/tmp/autosuggest_test/3", vars, L"cd \"/tmp/autosuggest_test/3foo\\bar/\"", __LINE__); + perform_one_autosuggestion_special_test(L"cd '/tmp/autosuggest_test/3", vars, L"cd '/tmp/autosuggest_test/3foo\\bar/'", __LINE__); + perform_one_autosuggestion_special_test(L"cd 3", vars, L"cd 3foo\\\\bar/", __LINE__); + perform_one_autosuggestion_special_test(L"cd \"3", vars, L"cd \"3foo\\bar/\"", __LINE__); + perform_one_autosuggestion_special_test(L"cd '3", vars, L"cd '3foo\\bar/'", __LINE__); + + perform_one_autosuggestion_special_test(L"cd /tmp/autosuggest_test/4", vars, L"cd /tmp/autosuggest_test/4foo\\'bar/", __LINE__); + perform_one_autosuggestion_special_test(L"cd \"/tmp/autosuggest_test/4", vars, L"cd \"/tmp/autosuggest_test/4foo'bar/\"", __LINE__); + perform_one_autosuggestion_special_test(L"cd '/tmp/autosuggest_test/4", vars, L"cd '/tmp/autosuggest_test/4foo\\'bar/'", __LINE__); + perform_one_autosuggestion_special_test(L"cd 4", vars, L"cd 4foo\\'bar/", __LINE__); + perform_one_autosuggestion_special_test(L"cd \"4", vars, L"cd \"4foo'bar/\"", __LINE__); + perform_one_autosuggestion_special_test(L"cd '4", vars, L"cd '4foo\\'bar/'", __LINE__); + + perform_one_autosuggestion_special_test(L"cd /tmp/autosuggest_test/5", vars, L"cd /tmp/autosuggest_test/5foo\\\"bar/", __LINE__); + perform_one_autosuggestion_special_test(L"cd \"/tmp/autosuggest_test/5", vars, L"cd \"/tmp/autosuggest_test/5foo\\\"bar/\"", __LINE__); + perform_one_autosuggestion_special_test(L"cd '/tmp/autosuggest_test/5", vars, L"cd '/tmp/autosuggest_test/5foo\"bar/'", __LINE__); + perform_one_autosuggestion_special_test(L"cd 5", vars, L"cd 5foo\\\"bar/", __LINE__); + perform_one_autosuggestion_special_test(L"cd \"5", vars, L"cd \"5foo\\\"bar/\"", __LINE__); + perform_one_autosuggestion_special_test(L"cd '5", vars, L"cd '5foo\"bar/'", __LINE__); + + //perform_one_autosuggestion_special_test(L"cd $AUTOSUGGEST_TEST_LOC/0", vars, L"cd $AUTOSUGGEST_TEST_LOC/0foobar/", __LINE__); + + perform_one_autosuggestion_special_test(L"cd ~/test_autosuggest_suggest_specia", vars, L"cd ~/test_autosuggest_suggest_special/", __LINE__); + + perform_one_autosuggestion_special_test(L"cd /tmp/autosuggest_test/start/", vars, L"cd /tmp/autosuggest_test/start/unique2/unique3/", __LINE__); + + + // A single quote should defeat tilde expansion + perform_one_autosuggestion_special_test(L"cd '~/test_autosuggest_suggest_specia'", vars, L"", __LINE__); + + // Don't crash on ~ (2696) + // note this was wd dependent, hence why we set it + if (chdir("/tmp/autosuggest_test/")) err(L"chdir failed"); + + env_set_pwd(); + + if (system("mkdir -p '/tmp/autosuggest_test/~hahaha/path1/path2/'")) err(L"mkdir failed"); + perform_one_autosuggestion_special_test(L"cd ~haha", vars, L"cd ~hahaha/path1/path2/", __LINE__); + if (chdir(saved_wd)) err(L"chdir failed"); + + env_set_pwd(); + + if (system("rm -Rf '/tmp/autosuggest_test/'")) err(L"rm failed"); + if (system("rm -Rf ~/test_autosuggest_suggest_special/")) err(L"rm failed"); +} + static void perform_one_autosuggestion_should_ignore_test(const wcstring &command, const wcstring &wd, long line) { completion_list_t comps; - complete(command, &comps, COMPLETION_REQUEST_AUTOSUGGESTION); + complete(command, &comps, COMPLETION_REQUEST_AUTOSUGGESTION, env_vars_snapshot_t::current()); do_test(comps.empty()); if (! comps.empty()) { @@ -2473,7 +2602,7 @@ void perf_complete() str[0]=c; reader_set_buffer(str, 0); - complete(str, &out, COMPLETION_REQUEST_DEFAULT); + complete(str, &out, COMPLETION_REQUEST_DEFAULT, env_vars_snapshot_t::current()); matches += out.size(); out.clear(); @@ -2493,7 +2622,7 @@ void perf_complete() reader_set_buffer(str, 0); - complete(str, &out, COMPLETION_REQUEST_DEFAULT); + complete(str, &out, COMPLETION_REQUEST_DEFAULT, env_vars_snapshot_t::current()); matches += out.size(); out.clear(); diff --git a/src/highlight.cpp b/src/highlight.cpp index 595631b78..0272c1faf 100644 --- a/src/highlight.cpp +++ b/src/highlight.cpp @@ -481,7 +481,7 @@ static bool autosuggest_parse_command(const wcstring &buff, wcstring *out_expand } /* We have to return an escaped string here */ -bool autosuggest_suggest_special(const wcstring &str, const wcstring &working_directory, completion_t *out_suggestion) +bool autosuggest_suggest_special(const wcstring &str, const env_vars_snapshot_t &vars, completion_t *out_suggestion) { if (str.empty()) return false; @@ -500,30 +500,32 @@ bool autosuggest_suggest_special(const wcstring &str, const wcstring &working_di /* 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; -#if 0 +#if 1 std::vector comps; - complete(str, &comps, COMPLETION_REQUEST_AUTOSUGGESTION); + complete(str, &comps, COMPLETION_REQUEST_AUTOSUGGESTION, vars); if (! comps.empty()) { *out_suggestion = comps.at(0); - } - - // Hackish to make tests pass - if (!(out_suggestion->flags & COMPLETE_REPLACES_TOKEN)) - { - out_suggestion->completion.insert(0, str); - out_suggestion->flags |= COMPLETE_REPLACES_TOKEN; - wcstring escaped_dir = last_arg_node.get_source(str); - wchar_t quote = L'\0'; - parse_util_get_parameter_info(escaped_dir, 0, "e, NULL, NULL); - if (quote != L'\0') out_suggestion->completion.push_back(quote); + // Hackish to make tests pass + if (!(out_suggestion->flags & COMPLETE_REPLACES_TOKEN)) + { + out_suggestion->completion.insert(0, str); + out_suggestion->flags |= COMPLETE_REPLACES_TOKEN; + + wcstring escaped_dir = last_arg_node.get_source(str); + wchar_t quote = L'\0'; + parse_util_get_parameter_info(escaped_dir, 0, "e, NULL, NULL); + + out_suggestion->completion = parse_util_escape_string_with_quote(out_suggestion->completion, quote); + + if (quote != L'\0') out_suggestion->completion.push_back(quote); + } } return result; #endif - /* We can possibly handle this specially */ const wcstring escaped_dir = last_arg_node.get_source(str); wcstring suggested_path; @@ -542,6 +544,12 @@ bool autosuggest_suggest_special(const wcstring &str, const wcstring &working_di /* Big hack to avoid expanding a tilde inside quotes */ path_flags_t path_flags = (quote == L'\0') ? PATH_EXPAND_TILDE : 0; + env_var_t working_directory = vars.get(L"PWD"); + if (working_directory.missing_or_empty()) + { + working_directory = L"."; + } + if (unescaped && is_potential_cd_path(unescaped_dir, working_directory, path_flags, &suggested_path)) { /* Note: this looks really wrong for strings that have an "unescapable" character in them, e.g. a \t, because parse_util_escape_string_with_quote will insert that character */ diff --git a/src/highlight.h b/src/highlight.h index e2395afcd..4bd0e4a03 100644 --- a/src/highlight.h +++ b/src/highlight.h @@ -116,7 +116,7 @@ bool autosuggest_validate_from_history(const history_item_t &item, file_detectio /** Given the command line contents 'str', return via reference a suggestion by specially recognizing the command. The suggestion is escaped. Returns true if we recognized the command (even if we couldn't think of a suggestion for it). */ class completion_t; -bool autosuggest_suggest_special(const wcstring &str, const wcstring &working_directory, completion_t *out_suggestion); +bool autosuggest_suggest_special(const wcstring &str, const env_vars_snapshot_t &vars, completion_t *out_suggestion); /* 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! diff --git a/src/reader.cpp b/src/reader.cpp index 39bfc0bb4..08f849304 100644 --- a/src/reader.cpp +++ b/src/reader.cpp @@ -1481,7 +1481,7 @@ struct autosuggestion_context_t /* Try handling a special command like cd */ completion_t special_suggestion(L""); - if (autosuggest_suggest_special(search_string, working_directory, &special_suggestion)) + if (autosuggest_suggest_special(search_string, this->vars, &special_suggestion)) { this->autosuggestion = special_suggestion.completion; return 1; @@ -1506,7 +1506,7 @@ struct autosuggestion_context_t /* Try normal completions */ std::vector completions; - complete(search_string, &completions, COMPLETION_REQUEST_AUTOSUGGESTION); + complete(search_string, &completions, COMPLETION_REQUEST_AUTOSUGGESTION, vars); completions_sort_and_prioritize(&completions); if (! completions.empty()) { @@ -3324,7 +3324,8 @@ const wchar_t *reader_readline(int nchars) const wcstring buffcpy = wcstring(cmdsub_begin, token_end); //fprintf(stderr, "Complete (%ls)\n", buffcpy.c_str()); - data->complete_func(buffcpy, &comp, COMPLETION_REQUEST_DEFAULT | COMPLETION_REQUEST_DESCRIPTIONS | COMPLETION_REQUEST_FUZZY_MATCH); + complete_flags_t complete_flags = COMPLETION_REQUEST_DEFAULT | COMPLETION_REQUEST_DESCRIPTIONS | COMPLETION_REQUEST_FUZZY_MATCH; + data->complete_func(buffcpy, &comp, complete_flags, env_vars_snapshot_t::current()); /* Munge our completions */ completions_sort_and_prioritize(&comp); diff --git a/src/reader.h b/src/reader.h index 73c19c2f8..3d9208a0a 100644 --- a/src/reader.h +++ b/src/reader.h @@ -223,7 +223,7 @@ void reader_pop(); - The command to be completed as a null terminated array of wchar_t - An array_list_t in which completions will be inserted. */ -typedef void (*complete_function_t)(const wcstring &, std::vector *, completion_request_flags_t); +typedef void (*complete_function_t)(const wcstring &, std::vector *, completion_request_flags_t, const env_vars_snapshot_t &); void reader_set_complete_function(complete_function_t); /** diff --git a/src/wildcard.cpp b/src/wildcard.cpp index 664eeb5b0..671a3dd05 100644 --- a/src/wildcard.cpp +++ b/src/wildcard.cpp @@ -644,7 +644,10 @@ static bool wildcard_test_flags_then_complete(const wcstring &filepath, class wildcard_expander_t { - /* The original string we are expanding */ + /* Prefix, i.e. effective working directory */ + const wcstring prefix; + + /* The original base we are expanding */ const wcstring original_base; /* Original wildcard we are expanding. */ @@ -736,15 +739,18 @@ class wildcard_expander_t } } - /* Helper to resolve an empty base directory */ - static DIR *open_dir(const wcstring &base_dir) + /* Helper to resolve using our prefix */ + DIR *open_dir(const wcstring &base_dir) const { - return wopendir(base_dir.empty() ? L"." : base_dir); + wcstring path = this->prefix; + append_path_component(path, base_dir); + return wopendir(path); } public: - wildcard_expander_t(const wcstring &orig_base, const wchar_t *orig_wc, expand_flags_t f, std::vector *r) : + wildcard_expander_t(const wcstring pref, const wcstring &orig_base, const wchar_t *orig_wc, expand_flags_t f, std::vector *r) : + prefix(pref), original_base(orig_base), original_wildcard(orig_wc), flags(f), @@ -1058,7 +1064,7 @@ void wildcard_expander_t::expand(const wcstring &base_dir, const wchar_t *wc) } -int wildcard_expand_string(const wcstring &wc, const wcstring &base_dir, expand_flags_t flags, std::vector *output) +int wildcard_expand_string(const wcstring &wc, const wcstring &working_directory, expand_flags_t flags, std::vector *output) { assert(output != NULL); /* Fuzzy matching only if we're doing completions */ @@ -1069,7 +1075,25 @@ int wildcard_expand_string(const wcstring &wc, const wcstring &base_dir, expand_ return 0; } - wildcard_expander_t expander(base_dir, wc.c_str(), flags, output); + /* Compute the prefix and base dir. The prefix is what we prepend for filesystem operations (i.e. the working directory), the base_dir is the part of the wildcard consumed thus far, which we also have to append. The difference is that the base_dir is returned as part of the expansion, and the prefix is not. + + Check for a leading slash. If we find one, we have an absolute path: the prefix is empty, the base dir is /, and the wildcard is the remainder. If we don't find one, the prefix is the working directory, there's base dir is empty. + */ + wcstring prefix, base_dir, effective_wc; + if (string_prefixes_string(L"/", wc)) + { + prefix = L""; + base_dir = L"/"; + effective_wc = wc.substr(1); + } + else + { + prefix = working_directory; + base_dir = L""; + effective_wc = wc; + } + + wildcard_expander_t expander(prefix, base_dir, effective_wc.c_str(), flags, output); expander.expand(base_dir, wc.c_str()); return expander.status_code(); } diff --git a/src/wildcard.h b/src/wildcard.h index 3820fc3e5..87ae147d7 100644 --- a/src/wildcard.h +++ b/src/wildcard.h @@ -58,14 +58,15 @@ enum wildcard_expand will continue the matching process. \param wc The wildcard string - \param base_dir The base directory of the filesystem to perform the match against + \param working_directory The working directory \param flags flags for the search. Can be any combination of EXPAND_FOR_COMPLETIONS and EXECUTABLES_ONLY \param out The list in which to put the output \return 1 if matches where found, 0 otherwise. Return -1 on abort (I.e. ^C was pressed). */ -int wildcard_expand_string(const wcstring &wc, const wcstring &base_dir, expand_flags_t flags, std::vector *out); +int wildcard_expand_string(const wcstring &wc, const wcstring &working_directory, expand_flags_t flags, std::vector *out); + /** Test whether the given wildcard matches the string. Does not perform any I/O.