diff --git a/src/builtin_complete.cpp b/src/builtin_complete.cpp index 54df50511..67365caff 100644 --- a/src/builtin_complete.cpp +++ b/src/builtin_complete.cpp @@ -329,7 +329,8 @@ int builtin_complete(parser_t &parser, io_streams_t &streams, wchar_t **argv) { recursion_level++; std::vector comp; - complete(do_complete_param, &comp, completion_request_t::fuzzy_match, parser.vars()); + complete(do_complete_param, &comp, completion_request_t::fuzzy_match, parser.vars(), + parser.shared()); for (size_t i = 0; i < comp.size(); i++) { const completion_t &next = comp.at(i); diff --git a/src/complete.cpp b/src/complete.cpp index 3f3d97828..48ce17ea2 100644 --- a/src/complete.cpp +++ b/src/complete.cpp @@ -296,13 +296,17 @@ class completer_t { /// Environment inside which we are completing. const environment_t &vars; + /// The parser used for condition testing and subshell expansion. + /// If null, these features are disabled. + std::shared_ptr parser; + /// The command to complete. const wcstring cmd; /// Flags associated with the completion request. const completion_request_flags_t flags; - /// The output cmopletions. + /// The output completions. std::vector completions; /// Table of completions conditions that have already been tested and the corresponding test @@ -371,8 +375,9 @@ class completer_t { void mark_completions_duplicating_arguments(const wcstring &prefix, const arg_list_t &args); public: - completer_t(const environment_t &vars, wcstring c, completion_request_flags_t f) - : vars(vars), cmd(std::move(c)), flags(f) {} + completer_t(const environment_t &vars, const std::shared_ptr &parser, wcstring c, + completion_request_flags_t f) + : vars(vars), parser(parser), cmd(std::move(c)), flags(f) {} void perform(); @@ -398,21 +403,16 @@ bool completer_t::condition_test(const wcstring &condition) { // std::fwprintf( stderr, L"No condition specified\n" ); return true; } - - if (this->type() == COMPLETE_AUTOSUGGEST) { - // Autosuggestion can't support conditions. + if (!parser) { return false; } ASSERT_IS_MAIN_THREAD(); - bool test_res; condition_cache_t::iterator cached_entry = condition_cache.find(condition); if (cached_entry == condition_cache.end()) { // Compute new value and reinsert it. - // TODO: rationalize this principal_parser. - test_res = (0 == exec_subshell(condition, parser_t::principal_parser(), - false /* don't apply exit status */)); + test_res = (0 == exec_subshell(condition, *parser, false /* don't apply exit status */)); condition_cache[condition] = test_res; } else { // Use the old value. @@ -559,6 +559,7 @@ void completer_t::complete_strings(const wcstring &wc_escaped, const description /// for the executable. void completer_t::complete_cmd_desc(const wcstring &str) { ASSERT_IS_MAIN_THREAD(); + if (!parser) return; wcstring cmd; size_t pos = str.find_last_of(L'/'); @@ -596,9 +597,7 @@ void completer_t::complete_cmd_desc(const wcstring &str) { // search if we know the location of the whatis database. This can take some time on slower // systems with a large set of manuals, but it should be ok since apropos is only called once. wcstring_list_t list; - // TODO: justify this use of parser_t::principal_parser. - if (exec_subshell(lookup_cmd, parser_t::principal_parser(), list, - false /* don't apply exit status */) != -1) { + if (exec_subshell(lookup_cmd, *parser, list, false /* don't apply exit status */) != -1) { std::unordered_map lookup; lookup.reserve(list.size()); @@ -1582,14 +1581,15 @@ void completer_t::perform() { } void complete(const wcstring &cmd_with_subcmds, std::vector *out_comps, - completion_request_flags_t flags, const environment_t &vars) { + completion_request_flags_t flags, const environment_t &vars, + const std::shared_ptr &parser) { // Determine the innermost subcommand. const wchar_t *cmdsubst_begin, *cmdsubst_end; parse_util_cmdsubst_extent(cmd_with_subcmds.c_str(), cmd_with_subcmds.size(), &cmdsubst_begin, &cmdsubst_end); assert(cmdsubst_begin != NULL && cmdsubst_end != NULL && cmdsubst_end >= cmdsubst_begin); wcstring cmd = wcstring(cmdsubst_begin, cmdsubst_end - cmdsubst_begin); - completer_t completer(vars, std::move(cmd), flags); + completer_t completer(vars, parser, std::move(cmd), flags); completer.perform(); *out_comps = completer.acquire_completions(); } diff --git a/src/complete.h b/src/complete.h index c4532fc7e..397904f11 100644 --- a/src/complete.h +++ b/src/complete.h @@ -170,8 +170,10 @@ void complete_remove(const wcstring &cmd, bool cmd_is_path, const wcstring &opti void complete_remove_all(const wcstring &cmd, bool cmd_is_path); /// Find all completions of the command cmd, insert them into out. +class parser_t; void complete(const wcstring &cmd, std::vector *out_comps, - completion_request_flags_t flags, const environment_t &vars); + completion_request_flags_t flags, const environment_t &vars, + const std::shared_ptr &parser); /// Return a list of all current completions. wcstring complete_print(); diff --git a/src/fish_tests.cpp b/src/fish_tests.cpp index 1df50f1df..3ac77c940 100644 --- a/src/fish_tests.cpp +++ b/src/fish_tests.cpp @@ -2492,8 +2492,10 @@ static void test_complete() { }; test_complete_vars_t vars; + auto parser = parser_t::principal_parser().shared(); + completion_list_t completions; - complete(L"$", &completions, {}, vars); + complete(L"$", &completions, {}, vars, parser); completions_sort_and_prioritize(&completions); do_test(completions.size() == 6); do_test(completions.at(0).completion == L"Bar1"); @@ -2504,7 +2506,7 @@ static void test_complete() { do_test(completions.at(5).completion == L"Foo3"); completions.clear(); - complete(L"$F", &completions, {}, vars); + complete(L"$F", &completions, {}, vars, parser); completions_sort_and_prioritize(&completions); do_test(completions.size() == 3); do_test(completions.at(0).completion == L"oo1"); @@ -2512,12 +2514,12 @@ static void test_complete() { do_test(completions.at(2).completion == L"oo3"); completions.clear(); - complete(L"$1", &completions, {}, vars); + complete(L"$1", &completions, {}, vars, parser); completions_sort_and_prioritize(&completions); do_test(completions.empty()); completions.clear(); - complete(L"$1", &completions, completion_request_t::fuzzy_match, vars); + complete(L"$1", &completions, completion_request_t::fuzzy_match, vars, parser); completions_sort_and_prioritize(&completions); do_test(completions.size() == 2); do_test(completions.at(0).completion == L"$Bar1"); @@ -2529,23 +2531,23 @@ static void test_complete() { if (system("chmod 700 'test/complete_test/testfile'")) err(L"chmod failed"); completions.clear(); - complete(L"echo (test/complete_test/testfil", &completions, {}, vars); + complete(L"echo (test/complete_test/testfil", &completions, {}, vars, parser); do_test(completions.size() == 1); do_test(completions.at(0).completion == L"e"); completions.clear(); - complete(L"echo (ls test/complete_test/testfil", &completions, {}, vars); + complete(L"echo (ls test/complete_test/testfil", &completions, {}, vars, parser); do_test(completions.size() == 1); do_test(completions.at(0).completion == L"e"); completions.clear(); - complete(L"echo (command ls test/complete_test/testfil", &completions, {}, vars); + complete(L"echo (command ls test/complete_test/testfil", &completions, {}, vars, parser); do_test(completions.size() == 1); do_test(completions.at(0).completion == L"e"); // Completing after spaces - see #2447 completions.clear(); - complete(L"echo (ls test/complete_test/has\\ ", &completions, {}, vars); + complete(L"echo (ls test/complete_test/has\\ ", &completions, {}, vars, parser); do_test(completions.size() == 1); do_test(completions.at(0).completion == L"space"); @@ -2558,23 +2560,23 @@ static void test_complete() { // Complete a function name. completions.clear(); - complete(L"echo (scuttlebut", &completions, {}, vars); + complete(L"echo (scuttlebut", &completions, {}, vars, parser); 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, {}, vars); + complete(L"echo (command scuttlebut", &completions, {}, vars, parser); do_test(completions.size() == 0); // Not with the builtin prefix. completions.clear(); - complete(L"echo (builtin scuttlebut", &completions, {}, vars); + complete(L"echo (builtin scuttlebut", &completions, {}, vars, parser); do_test(completions.size() == 0); // Not after a redirection. completions.clear(); - complete(L"echo hi > scuttlebut", &completions, {}, vars); + complete(L"echo hi > scuttlebut", &completions, {}, vars, parser); do_test(completions.size() == 0); // Trailing spaces (#1261). @@ -2583,81 +2585,82 @@ static void test_complete() { 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, {}, vars); + complete(L"foobarbaz ", &completions, {}, vars, parser); 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, {}, vars); + complete(L"echo '$Foo", &completions, {}, vars, parser); do_test(completions.empty()); completions.clear(); - complete(L"echo \\$Foo", &completions, {}, vars); + complete(L"echo \\$Foo", &completions, {}, vars, parser); do_test(completions.empty()); // File completions. completions.clear(); - complete(L"cat test/complete_test/te", &completions, {}, vars); + complete(L"cat test/complete_test/te", &completions, {}, vars, parser); do_test(completions.size() == 1); do_test(completions.at(0).completion == L"stfile"); completions.clear(); - complete(L"echo sup > test/complete_test/te", &completions, {}, vars); + complete(L"echo sup > test/complete_test/te", &completions, {}, vars, parser); do_test(completions.size() == 1); do_test(completions.at(0).completion == L"stfile"); completions.clear(); - complete(L"echo sup > test/complete_test/te", &completions, {}, vars); + complete(L"echo sup > test/complete_test/te", &completions, {}, vars, parser); do_test(completions.size() == 1); do_test(completions.at(0).completion == L"stfile"); if (!pushd("test/complete_test")) return; - complete(L"cat te", &completions, {}, vars); + complete(L"cat te", &completions, {}, vars, parser); do_test(completions.size() == 1); do_test(completions.at(0).completion == L"stfile"); do_test(!(completions.at(0).flags & COMPLETE_REPLACES_TOKEN)); do_test(!(completions.at(0).flags & COMPLETE_DUPLICATES_ARGUMENT)); completions.clear(); - complete(L"cat testfile te", &completions, {}, vars); + complete(L"cat testfile te", &completions, {}, vars, parser); do_test(completions.size() == 1); do_test(completions.at(0).completion == L"stfile"); do_test(completions.at(0).flags & COMPLETE_DUPLICATES_ARGUMENT); completions.clear(); - complete(L"cat testfile TE", &completions, {}, vars); + complete(L"cat testfile TE", &completions, {}, vars, parser); do_test(completions.size() == 1); do_test(completions.at(0).completion == L"testfile"); do_test(completions.at(0).flags & COMPLETE_REPLACES_TOKEN); do_test(completions.at(0).flags & COMPLETE_DUPLICATES_ARGUMENT); completions.clear(); - complete(L"something --abc=te", &completions, {}, vars); + complete(L"something --abc=te", &completions, {}, vars, parser); do_test(completions.size() == 1); do_test(completions.at(0).completion == L"stfile"); completions.clear(); - complete(L"something -abc=te", &completions, {}, vars); + complete(L"something -abc=te", &completions, {}, vars, parser); do_test(completions.size() == 1); do_test(completions.at(0).completion == L"stfile"); completions.clear(); - complete(L"something abc=te", &completions, {}, vars); + complete(L"something abc=te", &completions, {}, vars, parser); do_test(completions.size() == 1); do_test(completions.at(0).completion == L"stfile"); completions.clear(); - complete(L"something abc=stfile", &completions, {}, vars); + complete(L"something abc=stfile", &completions, {}, vars, parser); do_test(completions.size() == 0); completions.clear(); - complete(L"something abc=stfile", &completions, completion_request_t::fuzzy_match, vars); + complete(L"something abc=stfile", &completions, completion_request_t::fuzzy_match, vars, + parser); do_test(completions.size() == 1); do_test(completions.at(0).completion == L"abc=testfile"); // Zero escapes can cause problems. See issue #1631. completions.clear(); - complete(L"cat foo\\0", &completions, {}, vars); + complete(L"cat foo\\0", &completions, {}, vars, parser); do_test(completions.empty()); completions.clear(); - complete(L"cat foo\\0bar", &completions, {}, vars); + complete(L"cat foo\\0bar", &completions, {}, vars, parser); do_test(completions.empty()); completions.clear(); - complete(L"cat \\0", &completions, {}, vars); + complete(L"cat \\0", &completions, {}, vars, parser); do_test(completions.empty()); completions.clear(); - complete(L"cat te\\0", &completions, {}, vars); + complete(L"cat te\\0", &completions, {}, vars, parser); do_test(completions.empty()); popd(); @@ -2669,7 +2672,7 @@ static void test_complete() { fd.name = L"testabbrsonetwothreefour"; function_add(fd, parser_t::principal_parser()); int ret = pvars.set_one(L"_fish_abbr_testabbrsonetwothreezero", ENV_LOCAL, L"expansion"); - complete(L"testabbrsonetwothree", &completions, {}, pvars); + complete(L"testabbrsonetwothree", &completions, {}, pvars, parser); do_test(ret == 0); do_test(completions.size() == 2); do_test(completions.at(0).completion == L"four"); @@ -2749,7 +2752,7 @@ static void test_completion_insertions() { static void perform_one_autosuggestion_cd_test(const wcstring &command, const wcstring &expected, const environment_t &vars, long line) { std::vector comps; - complete(command, &comps, completion_request_t::autosuggestion, vars); + complete(command, &comps, completion_request_t::autosuggestion, vars, nullptr); bool expects_error = (expected == L""); @@ -2785,7 +2788,7 @@ static void perform_one_autosuggestion_cd_test(const wcstring &command, const wc static void perform_one_completion_cd_test(const wcstring &command, const wcstring &expected, const environment_t &vars, long line) { std::vector comps; - complete(command, &comps, {}, vars); + complete(command, &comps, {}, vars, nullptr); bool expects_error = (expected == L""); @@ -2925,7 +2928,7 @@ static void test_autosuggest_suggest_special() { static void perform_one_autosuggestion_should_ignore_test(const wcstring &command, long line) { completion_list_t comps; - complete(command, &comps, completion_request_t::autosuggestion, null_environment_t{}); + complete(command, &comps, completion_request_t::autosuggestion, null_environment_t{}, nullptr); do_test(comps.empty()); if (!comps.empty()) { const wcstring &suggestion = comps.front().completion; diff --git a/src/parser.cpp b/src/parser.cpp index 364791bb1..c440b7828 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -344,6 +344,8 @@ wcstring parser_t::stack_trace() const { return trace; } +std::shared_ptr parser_t::shared() { return shared_from_this(); } + void parser_t::stack_trace_internal(size_t block_idx, wcstring *buff) const { // Check if we should end the recursion. if (block_idx >= this->block_count()) return; diff --git a/src/parser.h b/src/parser.h index 7cb075b3c..858f5bf7d 100644 --- a/src/parser.h +++ b/src/parser.h @@ -328,6 +328,9 @@ class parser_t : public std::enable_shared_from_this { /// Return a string representing the current stack trace. wcstring stack_trace() const; + /// \return a shared pointer reference to this parser. + std::shared_ptr shared(); + ~parser_t(); }; diff --git a/src/reader.cpp b/src/reader.cpp index 38ede99b8..473cbac54 100644 --- a/src/reader.cpp +++ b/src/reader.cpp @@ -1327,7 +1327,7 @@ static std::function get_autosuggestion_performer // Try normal completions. completion_request_flags_t complete_flags = completion_request_t::autosuggestion; std::vector completions; - complete(search_string, &completions, complete_flags, *vars); + complete(search_string, &completions, complete_flags, *vars, nullptr); completions_sort_and_prioritize(&completions, complete_flags); if (!completions.empty()) { const completion_t &comp = completions.at(0); @@ -2585,7 +2585,9 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat // std::fwprintf(stderr, L"Complete (%ls)\n", buffcpy.c_str()); completion_request_flags_t complete_flags = {completion_request_t::descriptions, completion_request_t::fuzzy_match}; - complete_func(buffcpy, &rls.comp, complete_flags, vars); + // TODO: eliminate this principal_parser. + complete_func(buffcpy, &rls.comp, complete_flags, vars, + parser_t::principal_parser().shared()); // Munge our completions. completions_sort_and_prioritize(&rls.comp); diff --git a/src/reader.h b/src/reader.h index a8faf08bf..a6c1bbffb 100644 --- a/src/reader.h +++ b/src/reader.h @@ -157,7 +157,8 @@ void reader_pop(); /// Specify function to use for finding possible tab completions. typedef void (*complete_function_t)(const wcstring &, std::vector *, - completion_request_flags_t, const environment_t &); + completion_request_flags_t, const environment_t &, + const std::shared_ptr &parser); void reader_set_complete_function(complete_function_t); /// The type of a highlight function.