mirror of
https://github.com/fish-shell/fish-shell
synced 2025-01-14 14:03:58 +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).
|
Causes the specified command to inherit completions from *WRAPPED_COMMAND* (see below for details).
|
||||||
|
|
||||||
**-n** or **--condition** *CONDITION*
|
**-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*
|
**-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.
|
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.
|
/// Silly function.
|
||||||
static void builtin_complete_add2(const wchar_t *cmd, bool cmd_is_path, const wchar_t *short_opt,
|
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,
|
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) {
|
const wchar_t *comp, const wchar_t *desc, int flags) {
|
||||||
for (const wchar_t *s = short_opt; *s; s++) {
|
for (const wchar_t *s = short_opt; *s; s++) {
|
||||||
complete_add(cmd, cmd_is_path, wcstring{*s}, option_type_short, result_mode, condition,
|
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,
|
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 wchar_t *short_opt, const wcstring_list_t &gnu_opt,
|
||||||
const wcstring_list_t &old_opt, completion_mode_t result_mode,
|
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) {
|
int flags) {
|
||||||
for (const wcstring &cmd : cmds) {
|
for (const wcstring &cmd : cmds) {
|
||||||
builtin_complete_add2(cmd.c_str(), false /* not path */, short_opt, gnu_opt, old_opt,
|
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;
|
int remove = 0;
|
||||||
wcstring short_opt;
|
wcstring short_opt;
|
||||||
wcstring_list_t gnu_opt, old_opt, subcommand;
|
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 do_complete = false;
|
||||||
bool have_do_complete_param = false;
|
bool have_do_complete_param = false;
|
||||||
wcstring do_complete_param;
|
wcstring do_complete_param;
|
||||||
|
@ -268,8 +269,8 @@ maybe_t<int> builtin_complete(parser_t &parser, io_streams_t &streams, const wch
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'n': {
|
case 'n': {
|
||||||
condition = w.woptarg;
|
condition.push_back(w.woptarg);
|
||||||
assert(condition);
|
assert(w.woptarg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'w': {
|
case 'w': {
|
||||||
|
@ -333,12 +334,11 @@ maybe_t<int> builtin_complete(parser_t &parser, io_streams_t &streams, const wch
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (condition && *condition) {
|
for (const auto &condition_string : condition) {
|
||||||
const wcstring condition_string = condition;
|
|
||||||
parse_error_list_t errors;
|
parse_error_list_t errors;
|
||||||
if (parse_util_detect_errors(condition_string, &errors)) {
|
if (parse_util_detect_errors(condition_string, &errors)) {
|
||||||
streams.err.append_format(L"%ls: Condition '%ls' contained a syntax error", cmd,
|
streams.err.append_format(L"%ls: Condition '%ls' contained a syntax error", cmd,
|
||||||
condition);
|
condition_string.c_str());
|
||||||
for (const auto &error : errors) {
|
for (const auto &error : errors) {
|
||||||
streams.err.append_format(L"\n%ls: ", cmd);
|
streams.err.append_format(L"\n%ls: ", cmd);
|
||||||
streams.err.append(error.describe(condition_string, parser.is_interactive()));
|
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;
|
parser.libdata().builtin_complete_current_commandline = false;
|
||||||
}
|
}
|
||||||
} else if (path.empty() && gnu_opt.empty() && short_opt.empty() && old_opt.empty() && !remove &&
|
} 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) {
|
!result_mode.force_files && !result_mode.requires_param) {
|
||||||
// No arguments that would add or remove anything specified, so we print the definitions of
|
// No arguments that would add or remove anything specified, so we print the definitions of
|
||||||
// all matching completions.
|
// all matching completions.
|
||||||
|
|
|
@ -98,8 +98,8 @@ struct complete_entry_opt_t {
|
||||||
wcstring comp;
|
wcstring comp;
|
||||||
// Description of the completion.
|
// Description of the completion.
|
||||||
wcstring desc;
|
wcstring desc;
|
||||||
// Condition under which to use the option.
|
// Conditions under which to use the option.
|
||||||
wcstring condition;
|
wcstring_list_t condition;
|
||||||
// Determines how completions should be performed on the argument after the switch.
|
// Determines how completions should be performed on the argument after the switch.
|
||||||
completion_mode_t result_mode;
|
completion_mode_t result_mode;
|
||||||
// Completion flags.
|
// Completion flags.
|
||||||
|
@ -404,6 +404,7 @@ class completer_t {
|
||||||
bool complete_variable(const wcstring &str, size_t start_offset);
|
bool complete_variable(const wcstring &str, size_t start_offset);
|
||||||
|
|
||||||
bool condition_test(const wcstring &condition);
|
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,
|
void complete_strings(const wcstring &wc_escaped, const description_func_t &desc_func,
|
||||||
const completion_list_t &possible_comp, complete_flags_t flags);
|
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;
|
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.
|
/// 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,
|
static completion_entry_t &complete_get_exact_entry(completion_entry_set_t &completion_set,
|
||||||
const wcstring &cmd, bool cmd_is_path) {
|
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,
|
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,
|
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) {
|
complete_flags_t flags) {
|
||||||
assert(cmd && "Null command");
|
assert(cmd && "Null command");
|
||||||
// option should be empty iff the option type is arguments only.
|
// 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;
|
opt.result_mode = result_mode;
|
||||||
|
|
||||||
if (comp) opt.comp = comp;
|
if (comp) opt.comp = comp;
|
||||||
if (condition) opt.condition = condition;
|
opt.condition = std::move(condition);
|
||||||
if (desc) opt.desc = desc;
|
if (desc) opt.desc = desc;
|
||||||
opt.flags = flags;
|
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'd', C_(o.desc));
|
||||||
append_switch(out, L'a', o.comp);
|
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");
|
out.append(L"\n");
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
|
@ -230,7 +230,7 @@ void completions_sort_and_prioritize(completion_list_t *comps,
|
||||||
/// \param flags A set of completion flags
|
/// \param flags A set of completion flags
|
||||||
void complete_add(const wchar_t *cmd, bool cmd_is_path, const wcstring &option,
|
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,
|
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.
|
/// Remove a previously defined completion.
|
||||||
void complete_remove(const wcstring &cmd, bool cmd_is_path, const wcstring &option,
|
void complete_remove(const wcstring &cmd, bool cmd_is_path, const wcstring &option,
|
||||||
|
|
|
@ -3345,7 +3345,7 @@ static void test_complete() {
|
||||||
// Trailing spaces (#1261).
|
// Trailing spaces (#1261).
|
||||||
completion_mode_t no_files{};
|
completion_mode_t no_files{};
|
||||||
no_files.no_files = true;
|
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);
|
nullptr, COMPLETE_AUTO_SPACE);
|
||||||
completions = do_complete(L"foobarbaz ", {});
|
completions = do_complete(L"foobarbaz ", {});
|
||||||
do_test(completions.size() == 1);
|
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
|
complete -c thing -x -F
|
||||||
# CHECKERR: complete: invalid option combination, '--exclusive' and '--force-files'
|
# 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