From 47329260da20a87077764a7c7d7e9c91c0b61b2c Mon Sep 17 00:00:00 2001 From: Johannes Altmanninger Date: Thu, 21 Jul 2022 16:25:15 +0200 Subject: [PATCH 1/3] Move input completer when constructing PromptCompleterAdapter I think we usually do this when passing completers. --- src/commands.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands.cc b/src/commands.cc index 5783b0b8..fc717814 100644 --- a/src/commands.cc +++ b/src/commands.cc @@ -306,7 +306,7 @@ private: template struct PromptCompleterAdapter { - PromptCompleterAdapter(Completer completer) : m_completer{completer} {} + PromptCompleterAdapter(Completer completer) : m_completer{std::move(completer)} {} Completions operator()(const Context& context, CompletionFlags flags, StringView prefix, ByteCount cursor_pos) From aa8f29eff11272112b153de95e83978ca6884fac Mon Sep 17 00:00:00 2001 From: Johannes Altmanninger Date: Mon, 18 Jul 2022 23:29:17 +0200 Subject: [PATCH 2/3] Extract function for parsing completion switches Both "define-command" and "prompt" use the same logic, so share it. This will make it easy to implement "prompt -menu". This reveals a problem with PromptCompleterAdapter: when converting it to std::function and then to bool, it always evaluates to true because it has an operator(). However, it should evaluate to false if the adaptee holds no valid function (e.g. is a default-constructed std::function). Otherwise we try to call a non-existant function. Tweak PromptCompleterAdapter to work for empty inputs. --- src/commands.cc | 72 ++++++++++++++++++------------------------------- 1 file changed, 26 insertions(+), 46 deletions(-) diff --git a/src/commands.cc b/src/commands.cc index fc717814..490c6309 100644 --- a/src/commands.cc +++ b/src/commands.cc @@ -14,6 +14,7 @@ #include "hash_map.hh" #include "highlighter.hh" #include "highlighters.hh" +#include "input_handler.hh" #include "insert_completer.hh" #include "normal.hh" #include "option_manager.hh" @@ -308,10 +309,14 @@ struct PromptCompleterAdapter { PromptCompleterAdapter(Completer completer) : m_completer{std::move(completer)} {} - Completions operator()(const Context& context, CompletionFlags flags, - StringView prefix, ByteCount cursor_pos) + operator PromptCompleter() && { - return m_completer(context, flags, {String{String::NoCopy{}, prefix}}, 0, cursor_pos); + if (not m_completer) + return {}; + return [completer=std::move(m_completer)](const Context& context, CompletionFlags flags, + StringView prefix, ByteCount cursor_pos) { + return completer(context, flags, {String{String::NoCopy{}, prefix}}, 0, cursor_pos); + }; } private: @@ -1235,6 +1240,22 @@ CommandCompleter make_command_completer(StringView type, StringView param, Compl throw runtime_error(format("invalid command completion type '{}'", type)); } +static CommandCompleter parse_completion_switch(const ParametersParser& parser, Completions::Flags completions_flags) { + for (StringView completion_switch : {"file-completion", "client-completion", "buffer-completion", + "shell-script-completion", "shell-script-candidates", + "command-completion", "shell-completion"}) + { + if (auto param = parser.get_switch(completion_switch)) + { + constexpr StringView suffix = "-completion"; + if (completion_switch.ends_with(suffix)) + completion_switch = completion_switch.substr(0, completion_switch.length() - suffix.length()); + return make_command_completer(completion_switch, *param, completions_flags); + } + } + return {}; +} + void define_command(const ParametersParser& parser, Context& context, const ShellContext&) { const String& cmd_name = parser[0]; @@ -1286,20 +1307,7 @@ void define_command(const ParametersParser& parser, Context& context, const Shel }; } - CommandCompleter completer; - for (StringView completion_switch : {"file-completion", "client-completion", "buffer-completion", - "shell-script-completion", "shell-script-candidates", - "command-completion", "shell-completion"}) - { - if (auto param = parser.get_switch(completion_switch)) - { - constexpr StringView suffix = "-completion"; - if (completion_switch.ends_with(suffix)) - completion_switch = completion_switch.substr(0, completion_switch.length() - suffix.length()); - completer = make_command_completer(completion_switch, *param, completions_flags); - break; - } - } + CommandCompleter completer = parse_completion_switch(parser, completions_flags); auto docstring = trim_indent(parser.get_switch("docstring").value_or(StringView{})); cm.register_command(cmd_name, cmd, docstring, desc, flags, CommandHelper{}, std::move(completer)); @@ -2169,35 +2177,7 @@ const CommandDesc prompt_cmd = { const String& command = parser[1]; auto initstr = parser.get_switch("init").value_or(StringView{}); - PromptCompleter completer; - if (parser.get_switch("file-completion")) - completer = [](const Context& context, CompletionFlags, - StringView prefix, ByteCount cursor_pos) -> Completions { - auto& ignored_files = context.options()["ignored_files"].get(); - return { 0_byte, cursor_pos, - complete_filename(prefix, ignored_files, cursor_pos, - FilenameFlags::Expand) }; - }; - else if (parser.get_switch("client-completion")) - completer = [](const Context& context, CompletionFlags, - StringView prefix, ByteCount cursor_pos) -> Completions { - return { 0_byte, cursor_pos, - ClientManager::instance().complete_client_name(prefix, cursor_pos) }; - }; - else if (parser.get_switch("buffer-completion")) - completer = complete_buffer_name; - else if (parser.get_switch("command-completion")) - completer = [](const Context& context, CompletionFlags flags, - StringView prefix, ByteCount cursor_pos) -> Completions { - return CommandManager::instance().complete( - context, flags, prefix, cursor_pos); - }; - else if (parser.get_switch("shell-completion")) - completer = shell_complete; - else if (auto shell_script = parser.get_switch("shell-script-completion")) - completer = PromptCompleterAdapter{ShellScriptCompleter{shell_script->str()}}; - else if (auto shell_script = parser.get_switch("shell-script-candidates")) - completer = PromptCompleterAdapter{ShellCandidatesCompleter{shell_script->str()}}; + PromptCompleterAdapter completer = parse_completion_switch(parser, Completions::Flags::None); const auto flags = parser.get_switch("password") ? PromptFlags::Password : PromptFlags::None; From 6e4b0d88599d43c59c2f035c2bdef97316469a9f Mon Sep 17 00:00:00 2001 From: Johannes Altmanninger Date: Mon, 18 Jul 2022 21:35:38 +0200 Subject: [PATCH 3/3] Support "prompt -menu" to mark completions as authoritative This gives the "prompt" command the same "-menu" switch as "complete-command" and "define-command". I don't think anyone has asked for it but it seems like a good idea? --- src/commands.cc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/commands.cc b/src/commands.cc index 490c6309..c455803b 100644 --- a/src/commands.cc +++ b/src/commands.cc @@ -2158,6 +2158,7 @@ const CommandDesc prompt_cmd = { ParameterDesc{ { { "init", { true, "set initial prompt content" } }, { "password", { false, "Do not display entered text and clear reg after command" } }, + { "menu", { false, "treat completions as the only valid inputs" } }, { "file-completion", { false, "use file completion for prompt" } }, { "client-completion", { false, "use client completion for prompt" } }, { "buffer-completion", { false, "use buffer completion for prompt" } }, @@ -2177,7 +2178,9 @@ const CommandDesc prompt_cmd = { const String& command = parser[1]; auto initstr = parser.get_switch("init").value_or(StringView{}); - PromptCompleterAdapter completer = parse_completion_switch(parser, Completions::Flags::None); + const Completions::Flags completions_flags = parser.get_switch("menu") ? + Completions::Flags::Menu : Completions::Flags::None; + PromptCompleterAdapter completer = parse_completion_switch(parser, completions_flags); const auto flags = parser.get_switch("password") ? PromptFlags::Password : PromptFlags::None;