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<ret>".  The prompt expands to ":ls kakoune/"
before executing. Next, run ":<c-p>".  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.
This commit is contained in:
Johannes Altmanninger 2022-07-19 12:58:14 +02:00
parent 0b5dcf062f
commit 031de6d28c

View File

@ -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<false>(context, flags, params.back(), cursor_pos);
return menu(complete_buffer_name<false>)(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<Vector<String>>()) };
| gather<Vector<String>>()),
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);