From a8e237f0f9750c17e0e90848c44c67ee41abf16c Mon Sep 17 00:00:00 2001 From: Fabian Homborg Date: Wed, 9 Sep 2020 18:03:39 +0200 Subject: [PATCH] Let `complete` show completions for one command if just given `-c` Currently only `complete` will list completions, and it will list all of them. That's a bit ridiculous, especially since `complete -c foo` just does nothing. So just make `complete -c foo` list all the completions for `foo`. --- doc_src/cmds/complete.rst | 11 ++++-- src/builtin_complete.cpp | 46 ++++++++++++++++------ src/complete.cpp | 80 +++++++++++++++++++++----------------- src/complete.h | 2 +- tests/checks/complete.fish | 5 +++ 5 files changed, 92 insertions(+), 52 deletions(-) diff --git a/doc_src/cmds/complete.rst b/doc_src/cmds/complete.rst index 0b81af23d..1821bd57f 100644 --- a/doc_src/cmds/complete.rst +++ b/doc_src/cmds/complete.rst @@ -98,9 +98,10 @@ The ``-w`` or ``--wraps`` options causes the specified command to inherit comple When erasing completions, it is possible to either erase all completions for a specific command by specifying ``complete -c COMMAND -e``, or by specifying a specific completion option to delete by specifying either a long, short or old style option. +When ``complete`` is called without anything that would define or erase completions, it shows matching completions instead. So ``complete`` without any arguments shows all loaded completions, ``complete -c foo`` shows all loaded completions for ``foo``. Since completions are :ref:`autoloaded `, you will have to trigger them first. -Example -------- +Examples +-------- The short style option ``-o`` for the ``gcc`` command requires that a file follows it. This can be done using writing: @@ -151,5 +152,9 @@ To implement an alias, use the ``-w`` or ``--wraps`` option: complete -c hub -w git -Now hub inherits all of the completions from git. Note this can also be specified in a function declaration. +Now hub inherits all of the completions from git. Note this can also be specified in a function declaration (``function thing -w otherthing``). +:: + complete -c git + +Show all completions for ``git``. diff --git a/src/builtin_complete.cpp b/src/builtin_complete.cpp index 302dd89df..f4455b7da 100644 --- a/src/builtin_complete.cpp +++ b/src/builtin_complete.cpp @@ -108,6 +108,19 @@ static void builtin_complete_remove(const wcstring_list_t &cmds, const wcstring_ } } +static void builtin_complete_print(const wcstring cmd, io_streams_t &streams, parser_t &parser) { + const wcstring repr = complete_print(cmd); + + // colorize if interactive + if (!streams.out_is_redirected && isatty(STDOUT_FILENO)) { + std::vector colors; + highlight_shell(repr, colors, parser.context()); + streams.out.append(str2wcstring(colorize(repr, colors, parser.vars()))); + } else { + streams.out.append(repr); + } +} + /// The complete builtin. Used for specifying programmable tab-completions. Calls the functions in // complete.cpp for any heavy lifting. maybe_t builtin_complete(parser_t &parser, io_streams_t &streams, wchar_t **argv) { @@ -118,7 +131,7 @@ maybe_t builtin_complete(parser_t &parser, io_streams_t &streams, wchar_t * completion_mode_t result_mode{}; int remove = 0; wcstring short_opt; - wcstring_list_t gnu_opt, old_opt; + wcstring_list_t gnu_opt, old_opt, subcommand; const wchar_t *comp = L"", *desc = L"", *condition = L""; bool do_complete = false; bool have_do_complete_param = false; @@ -139,6 +152,7 @@ maybe_t builtin_complete(parser_t &parser, io_streams_t &streams, wchar_t * {L"short-option", required_argument, nullptr, 's'}, {L"long-option", required_argument, nullptr, 'l'}, {L"old-option", required_argument, nullptr, 'o'}, + {L"subcommand", required_argument, nullptr, 'S'}, {L"description", required_argument, nullptr, 'd'}, {L"arguments", required_argument, nullptr, 'a'}, {L"erase", no_argument, nullptr, 'e'}, @@ -225,6 +239,14 @@ maybe_t builtin_complete(parser_t &parser, io_streams_t &streams, wchar_t * } break; } + case 'S': { + subcommand.push_back(w.woptarg); + if (w.woptarg[0] == '\0') { + streams.err.append_format(_(L"%ls: -S requires a non-empty string\n"), cmd); + return STATUS_INVALID_ARGS; + } + break; + } case 'a': { comp = w.woptarg; break; @@ -381,18 +403,18 @@ maybe_t builtin_complete(parser_t &parser, io_streams_t &streams, wchar_t * parser.libdata().builtin_complete_recursion_level--; parser.libdata().builtin_complete_current_commandline = false; } - } else if (cmd_to_complete.empty() && path.empty()) { - // No arguments specified, meaning we print the definitions of all specified completions - // to stdout. - const wcstring repr = complete_print(); - - // colorize if interactive - if (!streams.out_is_redirected && isatty(STDOUT_FILENO)) { - std::vector colors; - highlight_shell(repr, colors, parser.context()); - streams.out.append(str2wcstring(colorize(repr, colors, parser.vars()))); + } else if (path.empty() && gnu_opt.empty() + && short_opt.empty() && old_opt.empty() + && !remove && !*comp && !*desc && !*condition + && 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. + if (cmd_to_complete.empty()) { + builtin_complete_print(L"", streams, parser); } else { - streams.out.append(repr); + for (auto& cmd : cmd_to_complete) { + builtin_complete_print(cmd, streams, parser); + } } } else { int flags = COMPLETE_AUTO_SPACE; diff --git a/src/complete.cpp b/src/complete.cpp index 7cd1854a1..35d2ae78d 100644 --- a/src/complete.cpp +++ b/src/complete.cpp @@ -1666,8 +1666,48 @@ static void append_switch(wcstring &out, const wcstring &opt) { append_format(out, L" --%ls", opt.c_str()); } +wcstring completion2string(const complete_entry_opt_t &o, wcstring cmd, bool is_path) { + wcstring out; + out.append(L"complete"); + + if (o.flags & COMPLETE_DONT_SORT) append_switch(out, L'k'); + + if (o.result_mode.no_files && o.result_mode.requires_param) { + append_switch(out, L"exclusive"); + } else if (o.result_mode.no_files) { + append_switch(out, L"no-files"); + } else if (o.result_mode.force_files) { + append_switch(out, L"force-files"); + } else if (o.result_mode.requires_param) { + append_switch(out, L"requires-param"); + } + + append_switch(out, is_path ? L'p' : L'c', cmd); + + switch (o.type) { + case option_type_args_only: { + break; + } + case option_type_short: { + append_switch(out, L's', wcstring(1, o.option.at(0))); + break; + } + case option_type_single_long: + case option_type_double_long: { + append_switch(out, o.type == option_type_single_long ? L'o' : L'l', o.option); + break; + } + } + + append_switch(out, L'd', C_(o.desc)); + append_switch(out, L'a', o.comp); + append_switch(out, L'n', o.condition); + out.append(L"\n"); + return out; +} + /// Use by the bare `complete`, loaded completions are printed out as commands -wcstring complete_print() { +wcstring complete_print(wcstring cmd) { wcstring out; out.reserve(40); // just a guess auto completion_set = s_completion_set.acquire(); @@ -1679,43 +1719,10 @@ wcstring complete_print() { sort(all_completions.begin(), all_completions.end(), compare_completions_by_order); for (const completion_entry_t &e : all_completions) { + if (!cmd.empty() && e.cmd != cmd) continue; const option_list_t &options = e.get_options(); for (const complete_entry_opt_t &o : options) { - out.append(L"complete"); - - if (o.flags & COMPLETE_DONT_SORT) append_switch(out, L'k'); - - if (o.result_mode.no_files && o.result_mode.requires_param) { - append_switch(out, L"exclusive"); - } else if (o.result_mode.no_files) { - append_switch(out, L"no-files"); - } else if (o.result_mode.force_files) { - append_switch(out, L"force-files"); - } else if (o.result_mode.requires_param) { - append_switch(out, L"requires-param"); - } - - append_switch(out, e.cmd_is_path ? L'p' : L'c', e.cmd); - - switch (o.type) { - case option_type_args_only: { - break; - } - case option_type_short: { - append_switch(out, L's', wcstring(1, o.option.at(0))); - break; - } - case option_type_single_long: - case option_type_double_long: { - append_switch(out, o.type == option_type_single_long ? L'o' : L'l', o.option); - break; - } - } - - append_switch(out, L'd', C_(o.desc)); - append_switch(out, L'a', o.comp); - append_switch(out, L'n', o.condition); - out.append(L"\n"); + out.append(completion2string(o, e.cmd, e.cmd_is_path)); } } @@ -1723,6 +1730,7 @@ wcstring complete_print() { auto locked_wrappers = wrapper_map.acquire(); for (const auto &entry : *locked_wrappers) { const wcstring &src = entry.first; + if (!cmd.empty() && src != cmd) continue; for (const wcstring &target : entry.second) { out.append(L"complete"); append_switch(out, L'c', src); diff --git a/src/complete.h b/src/complete.h index d7849eeba..31a3fb654 100644 --- a/src/complete.h +++ b/src/complete.h @@ -180,7 +180,7 @@ completion_list_t complete(const wcstring &cmd, completion_request_flags_t flags const operation_context_t &ctx); /// Return a list of all current completions. -wcstring complete_print(); +wcstring complete_print(wcstring cmd=L""); /// Tests if the specified option is defined for the specified command. int complete_is_valid_option(const wcstring &str, const wcstring &opt, diff --git a/tests/checks/complete.fish b/tests/checks/complete.fish index ab6bac291..bee026421 100644 --- a/tests/checks/complete.fish +++ b/tests/checks/complete.fish @@ -353,3 +353,8 @@ begin rm -rf $parened_path end + +# This should only list the completions for `banana` +complete -c banana -a '1 2 3' +complete -c banana +#CHECK: complete -c banana -a '1 2 3'