diff --git a/doc_src/cmds/complete.rst b/doc_src/cmds/complete.rst index 0326d54f9..7faf5ecde 100644 --- a/doc_src/cmds/complete.rst +++ b/doc_src/cmds/complete.rst @@ -61,7 +61,7 @@ The following options are available: Causes the specified command to inherit completions from *WRAPPED_COMMAND* (see below for details). **-n** or **--condition** *CONDITION* - This completion should only be used if the *CONDITION* (a shell command) returns 0. This makes it possible to specify completions that should only be used in some cases. + This completion should only be used if the *CONDITION* (a shell command) returns 0. This makes it possible to specify completions that should only be used in some cases. If multiple conditions are specified, fish will try them in the order they are specified until one fails or all succeeded. **-C** or **--do-complete** *STRING* Makes ``complete`` try to find all possible completions for the specified string. If there is no *STRING*, the current commandline is used instead. diff --git a/src/builtins/complete.cpp b/src/builtins/complete.cpp index f27eb8272..f219b39ac 100644 --- a/src/builtins/complete.cpp +++ b/src/builtins/complete.cpp @@ -32,7 +32,7 @@ /// Silly function. static void builtin_complete_add2(const wchar_t *cmd, bool cmd_is_path, const wchar_t *short_opt, const wcstring_list_t &gnu_opts, const wcstring_list_t &old_opts, - completion_mode_t result_mode, const wchar_t *condition, + completion_mode_t result_mode, const wcstring_list_t &condition, const wchar_t *comp, const wchar_t *desc, int flags) { for (const wchar_t *s = short_opt; *s; s++) { complete_add(cmd, cmd_is_path, wcstring{*s}, option_type_short, result_mode, condition, @@ -59,7 +59,7 @@ static void builtin_complete_add2(const wchar_t *cmd, bool cmd_is_path, const wc static void builtin_complete_add(const wcstring_list_t &cmds, const wcstring_list_t &paths, const wchar_t *short_opt, const wcstring_list_t &gnu_opt, const wcstring_list_t &old_opt, completion_mode_t result_mode, - const wchar_t *condition, const wchar_t *comp, const wchar_t *desc, + const wcstring_list_t &condition, const wchar_t *comp, const wchar_t *desc, int flags) { for (const wcstring &cmd : cmds) { builtin_complete_add2(cmd.c_str(), false /* not path */, short_opt, gnu_opt, old_opt, @@ -135,7 +135,8 @@ maybe_t builtin_complete(parser_t &parser, io_streams_t &streams, const wch int remove = 0; wcstring short_opt; wcstring_list_t gnu_opt, old_opt, subcommand; - const wchar_t *comp = L"", *desc = L"", *condition = L""; + const wchar_t *comp = L"", *desc = L""; + wcstring_list_t condition; bool do_complete = false; bool have_do_complete_param = false; wcstring do_complete_param; @@ -268,8 +269,8 @@ maybe_t builtin_complete(parser_t &parser, io_streams_t &streams, const wch break; } case 'n': { - condition = w.woptarg; - assert(condition); + condition.push_back(w.woptarg); + assert(w.woptarg); break; } case 'w': { @@ -333,12 +334,11 @@ maybe_t builtin_complete(parser_t &parser, io_streams_t &streams, const wch } } - if (condition && *condition) { - const wcstring condition_string = condition; + for (const auto &condition_string : condition) { parse_error_list_t errors; if (parse_util_detect_errors(condition_string, &errors)) { streams.err.append_format(L"%ls: Condition '%ls' contained a syntax error", cmd, - condition); + condition_string.c_str()); for (const auto &error : errors) { streams.err.append_format(L"\n%ls: ", cmd); streams.err.append(error.describe(condition_string, parser.is_interactive())); @@ -428,7 +428,7 @@ maybe_t builtin_complete(parser_t &parser, io_streams_t &streams, const wch parser.libdata().builtin_complete_current_commandline = false; } } else if (path.empty() && gnu_opt.empty() && short_opt.empty() && old_opt.empty() && !remove && - !*comp && !*desc && !*condition && wrap_targets.empty() && !result_mode.no_files && + !*comp && !*desc && condition.empty() && wrap_targets.empty() && !result_mode.no_files && !result_mode.force_files && !result_mode.requires_param) { // No arguments that would add or remove anything specified, so we print the definitions of // all matching completions. diff --git a/src/complete.cpp b/src/complete.cpp index 584296d55..0689d5547 100644 --- a/src/complete.cpp +++ b/src/complete.cpp @@ -98,8 +98,8 @@ struct complete_entry_opt_t { wcstring comp; // Description of the completion. wcstring desc; - // Condition under which to use the option. - wcstring condition; + // Conditions under which to use the option. + wcstring_list_t condition; // Determines how completions should be performed on the argument after the switch. completion_mode_t result_mode; // Completion flags. @@ -404,6 +404,7 @@ class completer_t { bool complete_variable(const wcstring &str, size_t start_offset); bool condition_test(const wcstring &condition); + bool condition_test(const wcstring_list_t &conditions); void complete_strings(const wcstring &wc_escaped, const description_func_t &desc_func, const completion_list_t &possible_comp, complete_flags_t flags); @@ -499,6 +500,13 @@ bool completer_t::condition_test(const wcstring &condition) { return test_res; } +bool completer_t::condition_test(const wcstring_list_t &conditions) { + for (const auto &c : conditions) { + if (!condition_test(c)) return false; + } + return true; +} + /// Locate the specified entry. Create it if it doesn't exist. Must be called while locked. static completion_entry_t &complete_get_exact_entry(completion_entry_set_t &completion_set, const wcstring &cmd, bool cmd_is_path) { @@ -1725,7 +1733,7 @@ void append_completion(completion_list_t *completions, wcstring comp, wcstring d void complete_add(const wchar_t *cmd, bool cmd_is_path, const wcstring &option, complete_option_type_t option_type, completion_mode_t result_mode, - const wchar_t *condition, const wchar_t *comp, const wchar_t *desc, + wcstring_list_t condition, const wchar_t *comp, const wchar_t *desc, complete_flags_t flags) { assert(cmd && "Null command"); // option should be empty iff the option type is arguments only. @@ -1742,7 +1750,7 @@ void complete_add(const wchar_t *cmd, bool cmd_is_path, const wcstring &option, opt.result_mode = result_mode; if (comp) opt.comp = comp; - if (condition) opt.condition = condition; + opt.condition = std::move(condition); if (desc) opt.desc = desc; opt.flags = flags; @@ -1841,7 +1849,9 @@ static wcstring completion2string(const complete_entry_opt_t &o, const wcstring append_switch(out, L'd', C_(o.desc)); append_switch(out, L'a', o.comp); - append_switch(out, L'n', o.condition); + for (const auto &c : o.condition) { + append_switch(out, L'n', c); + } out.append(L"\n"); return out; } diff --git a/src/complete.h b/src/complete.h index e29bb7088..a5546a19a 100644 --- a/src/complete.h +++ b/src/complete.h @@ -230,7 +230,7 @@ void completions_sort_and_prioritize(completion_list_t *comps, /// \param flags A set of completion flags void complete_add(const wchar_t *cmd, bool cmd_is_path, const wcstring &option, complete_option_type_t option_type, completion_mode_t result_mode, - const wchar_t *condition, const wchar_t *comp, const wchar_t *desc, int flags); + wcstring_list_t condition, const wchar_t *comp, const wchar_t *desc, int flags); /// Remove a previously defined completion. void complete_remove(const wcstring &cmd, bool cmd_is_path, const wcstring &option, diff --git a/src/fish_tests.cpp b/src/fish_tests.cpp index 325e86eaf..9c803c970 100644 --- a/src/fish_tests.cpp +++ b/src/fish_tests.cpp @@ -3345,7 +3345,7 @@ static void test_complete() { // Trailing spaces (#1261). completion_mode_t no_files{}; no_files.no_files = true; - complete_add(L"foobarbaz", false, wcstring(), option_type_args_only, no_files, nullptr, L"qux", + complete_add(L"foobarbaz", false, wcstring(), option_type_args_only, no_files, {}, L"qux", nullptr, COMPLETE_AUTO_SPACE); completions = do_complete(L"foobarbaz ", {}); do_test(completions.size() == 1); diff --git a/tests/checks/complete.fish b/tests/checks/complete.fish index 626da1d55..749519ea5 100644 --- a/tests/checks/complete.fish +++ b/tests/checks/complete.fish @@ -466,3 +466,36 @@ complete -C"a=1 b=2 cmd_with_fancy_completion 1 " complete -c thing -x -F # CHECKERR: complete: invalid option combination, '--exclusive' and '--force-files' +# Multiple conditions +complete -f -c shot +complete -fc shot -n 'test (count (commandline -opc) -eq 1' -n 'test (commandline -opc)[-1] = shot' -a 'through' +# CHECKERR: complete: Condition 'test (count (commandline -opc) -eq 1' contained a syntax error +# CHECKERR: complete: Unexpected end of string, expecting ')' +# CHECKERR: test (count (commandline -opc) -eq 1 +# CHECKERR: ^ +complete -fc shot -n 'test (count (commandline -opc)) -eq 1' -n 'test (commandline -opc)[-1] = shot' -a 'through' +complete -fc shot -n 'test (count (commandline -opc)) -eq 2' -n 'test (commandline -opc)[-1] = through' -a 'the' +complete -fc shot -n 'test (count (commandline -opc)) -eq 3' -n 'test (commandline -opc)[-1] = the' -a 'heart' +complete -fc shot -n 'test (count (commandline -opc)) -eq 4' -n 'test (commandline -opc)[-1] = heart' -a 'and' +complete -fc shot -n 'test (count (commandline -opc)) -eq 5' -n 'test (commandline -opc)[-1] = and' -a "you\'re" +complete -fc shot -n 'test (count (commandline -opc)) -eq 6' -n 'test (commandline -opc)[-1] = "you\'re"' -a 'to' +complete -fc shot -n 'test (count (commandline -opc)) -eq 7' -n 'test (commandline -opc)[-1] = to' -a 'blame' + +complete -C"shot " +# CHECK: through +complete -C"shot through " +# CHECK: the + +# See that conditions after a failing one aren't executed. +set -g oops 0 +complete -fc oooops +complete -fc oooops -n true -n true -n true -n 'false' -n 'set -g oops 1' -a oops +complete -C'oooops ' +echo $oops +# CHECK: 0 + +complete -fc oooops -n 'true' -n 'set -g oops 1' -a oops +complete -C'oooops ' +# CHECK: oops +echo $oops +# CHECK: 1