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.
This commit is contained in:
Johannes Altmanninger 2022-07-18 23:29:17 +02:00
parent 47329260da
commit aa8f29eff1

View File

@ -14,6 +14,7 @@
#include "hash_map.hh" #include "hash_map.hh"
#include "highlighter.hh" #include "highlighter.hh"
#include "highlighters.hh" #include "highlighters.hh"
#include "input_handler.hh"
#include "insert_completer.hh" #include "insert_completer.hh"
#include "normal.hh" #include "normal.hh"
#include "option_manager.hh" #include "option_manager.hh"
@ -308,10 +309,14 @@ struct PromptCompleterAdapter
{ {
PromptCompleterAdapter(Completer completer) : m_completer{std::move(completer)} {} PromptCompleterAdapter(Completer completer) : m_completer{std::move(completer)} {}
Completions operator()(const Context& context, CompletionFlags flags, operator PromptCompleter() &&
StringView prefix, ByteCount cursor_pos)
{ {
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: private:
@ -1235,6 +1240,22 @@ CommandCompleter make_command_completer(StringView type, StringView param, Compl
throw runtime_error(format("invalid command completion type '{}'", type)); 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&) void define_command(const ParametersParser& parser, Context& context, const ShellContext&)
{ {
const String& cmd_name = parser[0]; const String& cmd_name = parser[0];
@ -1286,20 +1307,7 @@ void define_command(const ParametersParser& parser, Context& context, const Shel
}; };
} }
CommandCompleter completer; CommandCompleter completer = parse_completion_switch(parser, 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());
completer = make_command_completer(completion_switch, *param, completions_flags);
break;
}
}
auto docstring = trim_indent(parser.get_switch("docstring").value_or(StringView{})); auto docstring = trim_indent(parser.get_switch("docstring").value_or(StringView{}));
cm.register_command(cmd_name, cmd, docstring, desc, flags, CommandHelper{}, std::move(completer)); 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]; const String& command = parser[1];
auto initstr = parser.get_switch("init").value_or(StringView{}); auto initstr = parser.get_switch("init").value_or(StringView{});
PromptCompleter completer; PromptCompleterAdapter completer = parse_completion_switch(parser, Completions::Flags::None);
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<Regex>();
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<false>;
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()}};
const auto flags = parser.get_switch("password") ? const auto flags = parser.get_switch("password") ?
PromptFlags::Password : PromptFlags::None; PromptFlags::Password : PromptFlags::None;