mirror of
https://github.com/fish-shell/fish-shell
synced 2024-11-10 07:04:29 +00:00
Allow complete to have multiple conditions
This makes it so `complete -c foo -n test1 -n test2` registers *both* conditions, and when it comes time to check the candidate, tries both, in that order. If any fails it stops, if all succeed the completion is offered. The reason for this is that it helps with caching - we have a condition cache, but conditions like ```fish test (count (commandline -opc)) -ge 2; and contains -- (commandline -opc)[2] length test (count (commandline -opc)) -ge 2; and contains -- (commandline -opc)[2] sub ``` defeats it pretty easily, because the cache only looks at the entire script as a string - it can't tell that the first `test` is the same in both. So this means we separate it into ```fish complete -f -c string -n "test (count (commandline -opc)) -ge 2; and contains -- (commandline -opc)[2] length" -s V -l visible -d "Use the visible width, excluding escape sequences" +complete -f -c string -n "test (count (commandline -opc)) -ge 2" -n "contains -- (commandline -opc)[2] length" -s V -l visible -d "Use the visible width, excluding escape sequences" ``` which allows the `test` to be cached. In tests, this improves performance for the string completions by 30% by reducing all the redundant `test` calls. The `git` completions can also greatly benefit from this.
This commit is contained in:
parent
5a610f60d7
commit
64b34c8cda
6 changed files with 60 additions and 17 deletions
|
@ -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.
|
||||
|
|
|
@ -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<int> 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<int> 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<int> 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<int> 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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue