From 031de6d28ca7279989d431c2e0225803c9389887 Mon Sep 17 00:00:00 2001 From: Johannes Altmanninger Date: Tue, 19 Jul 2022 12:58:14 +0200 Subject: [PATCH] Use menu behavior for completing builtins where appropriate This allows to select completions without pressing Tab. There are two different obvious ways to add the menu bit. 1. When creating the "Completions" object, pass the Completions::Flags::Menu parameter. 2. If there is a completer function like "complete_scope", wrap it, e.g. "menu(complete_scope)". I have settled on always using 2 if there is a completer function and 1 otherwise. The advantage of 2 over 1 is that it allows to use the completer function in a context where we don't want the menu behavior (e.g. "complete-command"). --- Now the only* completion type where we usually don't use menu behavior is file completion. Unfortunately, menu behavior has poor interaction with directories' trailing slashes. Consider this (contrived) example: define-command ls -docstring "list directory contents" -params .. %{ echo -- %sh{ls "$@"} } complete-command -menu ls file Run ":ls kakoun". The prompt expands to ":ls kakoune/" before executing. Next, run ":". This recalls ":ls kakoune/" and immediately selects the first completion, so on validation, the command will be ":ls kakoune/colors/", which is weird. [*] Also, expansions like %val{bufname} also don't use menu behavior. It wouldn't add value since validation doesn't add a closing delimiter. I have an experimental patch that adds closing delimiters automatically but I'm not sure if that's the right direction. --- src/commands.cc | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/commands.cc b/src/commands.cc index a854ea33d..132aa34ea 100644 --- a/src/commands.cc +++ b/src/commands.cc @@ -972,7 +972,7 @@ const CommandDesc arrange_buffers_cmd = { CommandHelper{}, [](const Context& context, CompletionFlags flags, CommandParameters params, size_t, ByteCount cursor_pos) { - return complete_buffer_name(context, flags, params.back(), cursor_pos); + return menu(complete_buffer_name)(context, flags, params.back(), cursor_pos); }, [](const ParametersParser& parser, Context&, const ShellContext&) { @@ -1094,7 +1094,7 @@ const CommandDesc add_hook_cmd = { }, CommandFlags::None, CommandHelper{}, - make_completer(menu(complete_scope),complete_hooks, complete_nothing, + make_completer(menu(complete_scope), menu(complete_hooks), complete_nothing, [](const Context& context, CompletionFlags flags, StringView prefix, ByteCount cursor_pos) { return CommandManager::instance().complete( @@ -1138,7 +1138,8 @@ const CommandDesc remove_hook_cmd = { { if (auto scope = get_scope_ifp(params[0], context)) return { 0_byte, params[0].length(), - scope->hooks().complete_hook_group(params[1], pos_in_token) }; + scope->hooks().complete_hook_group(params[1], pos_in_token), + Completions::Flags::Menu }; } return {}; }, @@ -1478,7 +1479,7 @@ const CommandDesc debug_cmd = { StringView prefix, ByteCount cursor_pos) -> Completions { auto c = {"info", "buffers", "options", "memory", "shared-strings", "profile-hash-maps", "faces", "mappings", "regex", "registers"}; - return { 0_byte, cursor_pos, complete(prefix, cursor_pos, c) }; + return { 0_byte, cursor_pos, complete(prefix, cursor_pos, c), Completions::Flags::Menu }; }), [](const ParametersParser& parser, Context& context, const ShellContext&) { @@ -1682,7 +1683,8 @@ const CommandDesc set_option_cmd = { return menu(complete_scope_including_current)(context, flags, params[0], pos_in_token); else if (token_to_complete == 1) return { 0_byte, params[1].length(), - GlobalScope::instance().option_registry().complete_option_name(params[1], pos_in_token) }; + GlobalScope::instance().option_registry().complete_option_name(params[1], pos_in_token), + Completions::Flags::Menu }; else if (token_to_complete == 2 and params[2].empty() and GlobalScope::instance().option_registry().option_exists(params[1])) { @@ -1718,7 +1720,8 @@ Completions complete_option(const Context& context, CompletionFlags flags, return menu(complete_scope_no_global)(context, flags, params[0], pos_in_token); else if (token_to_complete == 1) return { 0_byte, params[1].length(), - GlobalScope::instance().option_registry().complete_option_name(params[1], pos_in_token) }; + GlobalScope::instance().option_registry().complete_option_name(params[1], pos_in_token), + Completions::Flags::Menu }; return Completions{}; } @@ -1786,7 +1789,7 @@ const CommandDesc declare_option_cmd = { [](const Context& context, CompletionFlags flags, StringView prefix, ByteCount cursor_pos) -> Completions { auto c = {"int", "bool", "str", "regex", "int-list", "str-list", "completions", "line-specs", "range-specs", "str-to-str-map"}; - return { 0_byte, cursor_pos, complete(prefix, cursor_pos, c) }; + return { 0_byte, cursor_pos, complete(prefix, cursor_pos, c), Completions::Flags::Menu }; }), [](const ParametersParser& parser, Context& context, const ShellContext&) { @@ -1839,7 +1842,8 @@ static Completions map_key_completer(const Context& context, CompletionFlags fla { auto& user_modes = get_scope(params[0], context).keymaps().user_modes(); return { 0_byte, params[1].length(), - complete(params[1], pos_in_token, concatenated(modes, user_modes)) }; + complete(params[1], pos_in_token, concatenated(modes, user_modes)), + Completions::Flags::Menu }; } if (unmap and token_to_complete == 2) { @@ -1850,7 +1854,8 @@ static Completions map_key_completer(const Context& context, CompletionFlags fla return { 0_byte, params[2].length(), complete(params[2], pos_in_token, keys | transform([](Key k) { return key_to_str(k); }) - | gather>()) }; + | gather>()), + Completions::Flags::Menu }; } return {}; } @@ -2465,7 +2470,7 @@ const CommandDesc unset_face_cmd = { double_params, CommandFlags::None, face_doc_helper, - make_completer(menu(complete_scope), complete_face), + make_completer(menu(complete_scope), menu(complete_face)), [](const ParametersParser& parser, Context& context, const ShellContext&) { get_scope(parser[0], context).faces().remove_face(parser[1]); @@ -2653,7 +2658,8 @@ const CommandDesc enter_user_mode_cmd = { if (token_to_complete == 0) { return { 0_byte, params[0].length(), - complete(params[0], pos_in_token, context.keymaps().user_modes()) }; + complete(params[0], pos_in_token, context.keymaps().user_modes()), + Completions::Flags::Menu }; } return {}; }, @@ -2698,10 +2704,10 @@ const CommandDesc require_module_cmd = { single_param, CommandFlags::None, CommandHelper{}, - make_completer( + make_completer(menu( [](const Context&, CompletionFlags, StringView prefix, ByteCount cursor_pos) { return CommandManager::instance().complete_module_name(prefix.substr(0, cursor_pos)); - }), + })), [](const ParametersParser& parser, Context& context, const ShellContext&) { CommandManager::instance().load_module(parser[0], context);