diff --git a/src/complete.cpp b/src/complete.cpp index a863e5e3a..ae9681b61 100644 --- a/src/complete.cpp +++ b/src/complete.cpp @@ -1406,6 +1406,14 @@ void completer_t::complete_custom(const wcstring &cmd, const wcstring &cmdline, } } +static bool expand_command_token(const operation_context_t &ctx, wcstring &cmd_tok) { + // TODO: we give up if the first token expands to more than one argument. We could handle + // that case by propagating arguments. + // Also we could expand wildcards. + return expand_one(cmd_tok, {expand_flag::skip_cmdsubst, expand_flag::skip_wildcards}, ctx, + nullptr); +} + // Invoke command-specific completions given by \p arg_data. // Then, for each target wrapped by the given command, update the command // line with that target and invoke this recursively. @@ -1445,6 +1453,7 @@ void completer_t::walk_wrap_chain(const wcstring &cmd, const wcstring &cmdline, } else { wrapped_command_offset_in_wt = tok->offset; wrapped_command = std::move(tok_src); + expand_command_token(ctx, wrapped_command); break; } } @@ -1670,15 +1679,15 @@ void completer_t::perform_for_commandline(wcstring cmdline) { source_range_t command_range = {static_cast(cmd_tok.offset), static_cast(cmd_tok.length)}; - wcstring unesc_command; + wcstring exp_command = cmd_tok.get_source(cmdline); bool unescaped = - unescape_string(cmd_tok.get_source(cmdline), &unesc_command, UNESCAPE_DEFAULT) && + expand_command_token(ctx, exp_command) && unescape_string(previous_argument, &arg_data.previous_argument, UNESCAPE_DEFAULT) && unescape_string(current_argument, &arg_data.current_argument, UNESCAPE_INCOMPLETE); if (unescaped) { // Have to walk over the command and its entire wrap chain. If any command // disables do_file, then they all do. - walk_wrap_chain(unesc_command, cmdline, command_range, &arg_data); + walk_wrap_chain(exp_command, cmdline, command_range, &arg_data); do_file = arg_data.do_file; // If we're autosuggesting, and the token is empty, don't do file suggestions. @@ -1689,7 +1698,7 @@ void completer_t::perform_for_commandline(wcstring cmdline) { // Hack. If we're cd, handle it specially (issue #1059, others). handle_as_special_cd = - (unesc_command == L"cd") || arg_data.visited_wrapped_commands.count(L"cd"); + (exp_command == L"cd") || arg_data.visited_wrapped_commands.count(L"cd"); } // Maybe apply variable assignments. diff --git a/tests/checks/complete.fish b/tests/checks/complete.fish index f3991cca2..50076af04 100644 --- a/tests/checks/complete.fish +++ b/tests/checks/complete.fish @@ -410,6 +410,12 @@ complete -p $PWD/command-not-in-path -xa relative-path complete -C './command-not-in-path ' # CHECK: relative-path +# Expand variables and tildes in command. +complete -C '$PWD/command-not-in-path ' +# CHECK: relative-path +HOME=$PWD complete -C '~/command-not-in-path ' +# CHECK: relative-path + # Non-canonical command path mkdir -p subdir : >subdir/command-in-subdir @@ -427,3 +433,24 @@ end cd - rm -r $dir + +# Expand variables and tildes in command. +complete cat -xa +pet +set -l path_to_cat (command -v cat) +complete -C '$path_to_cat ' +# CHECK: +pet +HOME=$path_to_cat/.. complete -C '~/cat ' +# CHECK: +pet + +# Do not expand command substitutions. +complete -C '(echo cat) ' | string match +pet +# Give up if we expand to multiple arguments (we'd need to handle the arguments). +complete -C '{cat,arg1,arg2} ' | string match +pet +# Don't expand wildcards though we could. +complete -C '$path_to_cat* ' | string match +pet + +# Also expand wrap targets. +function crookshanks --wraps '$path_to_cat' +end +complete -C 'crookshanks ' +# CHECK: +pet