Use a separate copy of the command completer for each completion

This make the completer lifetime tied to the Prompt mode and removes
the need for the Start flag. It also makes it possible to cleanup
on completer destruction.
This commit is contained in:
Maxime Coste 2023-11-11 18:45:27 +11:00
parent 79f3f5b046
commit 719512b308
5 changed files with 76 additions and 82 deletions

View File

@ -719,10 +719,34 @@ static Completions complete_expand(const Context& context, CompletionFlags flags
return {}; return {};
} }
Completions CommandManager::complete(const Context& context,
CompletionFlags flags, static Completions requote(Completions completions, Token::Type token_type)
StringView command_line, {
ByteCount cursor_pos) if (completions.flags & Completions::Flags::Quoted)
return completions;
if (token_type == Token::Type::Raw)
{
const bool at_token_start = completions.start == 0;
for (auto& candidate : completions.candidates)
{
const StringView to_escape = ";\n \t";
if ((at_token_start and candidate.substr(0_byte, 1_byte) == "%") or
any_of(candidate, [&](auto c) { return contains(to_escape, c); }))
candidate = at_token_start ? quote(candidate) : escape(candidate, to_escape, '\\');
}
}
else if (token_type == Token::Type::RawQuoted)
completions.flags |= Completions::Flags::Quoted;
else
kak_assert(false);
return completions;
}
Completions CommandManager::Completer::operator()(
const Context& context, CompletionFlags flags,
StringView command_line, ByteCount cursor_pos)
{ {
auto prefix = command_line.substr(0_byte, cursor_pos); auto prefix = command_line.substr(0_byte, cursor_pos);
CommandParser parser{prefix}; CommandParser parser{prefix};
@ -754,29 +778,6 @@ Completions CommandManager::complete(const Context& context,
if (token.terminated) // do not complete past explicit token close if (token.terminated) // do not complete past explicit token close
return Completions{}; return Completions{};
auto requote = [](Completions completions, Token::Type token_type) {
if (completions.flags & Completions::Flags::Quoted)
return completions;
if (token_type == Token::Type::Raw)
{
const bool at_token_start = completions.start == 0;
for (auto& candidate : completions.candidates)
{
const StringView to_escape = ";\n \t";
if ((at_token_start and candidate.substr(0_byte, 1_byte) == "%") or
any_of(candidate, [&](auto c) { return contains(to_escape, c); }))
candidate = at_token_start ? quote(candidate) : escape(candidate, to_escape, '\\');
}
}
else if (token_type == Token::Type::RawQuoted)
completions.flags |= Completions::Flags::Quoted;
else
kak_assert(false);
return completions;
};
const ByteCount start = token.pos; const ByteCount start = token.pos;
const ByteCount pos_in_token = cursor_pos - start; const ByteCount pos_in_token = cursor_pos - start;
@ -784,9 +785,11 @@ Completions CommandManager::complete(const Context& context,
if (tokens.size() == 1 and (token.type == Token::Type::Raw or if (tokens.size() == 1 and (token.type == Token::Type::Raw or
token.type == Token::Type::RawQuoted)) token.type == Token::Type::RawQuoted))
{ {
return offset_pos(requote(complete_command_name(context, prefix), token.type), start); return offset_pos(requote(CommandManager::instance().complete_command_name(context, prefix), token.type), start);
} }
auto& commands = CommandManager::instance().m_commands;
switch (token.type) switch (token.type)
{ {
case Token::Type::RegisterExpand: case Token::Type::RegisterExpand:
@ -800,17 +803,16 @@ Completions CommandManager::complete(const Context& context,
case Token::Type::RawQuoted: case Token::Type::RawQuoted:
{ {
StringView command_name = tokens.front().content; StringView command_name = tokens.front().content;
if (command_name != m_last_complete_command) auto command_it = commands.find(resolve_alias(context, command_name));
{ if (command_it == commands.end())
m_last_complete_command = command_name.str();
flags |= CompletionFlags::Start;
}
auto command_it = m_commands.find(resolve_alias(context, command_name));
if (command_it == m_commands.end())
return Completions{}; return Completions{};
auto& command = command_it->value; auto& command = command_it->value;
if (command_name != m_last_complete_command)
{
m_last_complete_command = command_name.str();
m_command_completer = command.completer;
}
auto raw_params = tokens | skip(1) | transform(&Token::content) | gather<Vector<String>>(); auto raw_params = tokens | skip(1) | transform(&Token::content) | gather<Vector<String>>();
ParametersParser parser{raw_params, command.param_desc, true}; ParametersParser parser{raw_params, command.param_desc, true};
@ -841,13 +843,13 @@ Completions CommandManager::complete(const Context& context,
break; break;
} }
if (not command.completer) if (not m_command_completer)
return Completions{}; return Completions{};
Vector<String> params{parser.begin(), parser.end()}; Vector<String> params{parser.begin(), parser.end()};
auto index = params.size() - 1; auto index = params.size() - 1;
return offset_pos(requote(command.completer(context, flags, params, index, pos_in_token), token.type), start); return offset_pos(requote(m_command_completer(context, flags, params, index, pos_in_token), token.type), start);
} }
case Token::Type::Expand: case Token::Type::Expand:
return complete_expand(context, flags, token.content, start, cursor_pos, pos_in_token); return complete_expand(context, flags, token.content, start, cursor_pos, pos_in_token);
@ -857,26 +859,26 @@ Completions CommandManager::complete(const Context& context,
return Completions{}; return Completions{};
} }
Completions CommandManager::complete(const Context& context, Completions CommandManager::NestedCompleter::operator()(
CompletionFlags flags, const Context& context, CompletionFlags flags, CommandParameters params,
CommandParameters params, size_t token_to_complete, ByteCount pos_in_token)
size_t token_to_complete,
ByteCount pos_in_token)
{ {
StringView prefix = params[token_to_complete].substr(0, pos_in_token); StringView prefix = params[token_to_complete].substr(0, pos_in_token);
if (token_to_complete == 0) if (token_to_complete == 0)
return complete_command_name(context, prefix); return CommandManager::instance().complete_command_name(context, prefix);
StringView command_name = params[0]; StringView command_name = params[0];
auto& commands = CommandManager::instance().m_commands;
if (command_name != m_last_complete_command) if (command_name != m_last_complete_command)
{ {
m_last_complete_command = command_name.str(); m_last_complete_command = command_name.str();
flags |= CompletionFlags::Start; auto it = commands.find(resolve_alias(context, command_name));
if (it != commands.end())
m_command_completer = it->value.completer;
} }
auto it = m_commands.find(resolve_alias(context, command_name)); return m_command_completer
return (it != m_commands.end() and it->value.completer) ? m_command_completer(context, flags, params.subrange(1), token_to_complete-1, pos_in_token)
? it->value.completer(context, flags, params.subrange(1), token_to_complete-1, pos_in_token)
: Completions{}; : Completions{};
} }

View File

@ -93,13 +93,6 @@ public:
const ShellContext& shell_context); const ShellContext& shell_context);
Completions complete(const Context& context, CompletionFlags flags,
StringView command_line, ByteCount cursor_pos);
Completions complete(const Context& context, CompletionFlags flags,
CommandParameters params,
size_t token_to_complete, ByteCount pos_in_token);
Optional<CommandInfo> command_info(const Context& context, Optional<CommandInfo> command_info(const Context& context,
StringView command_line) const; StringView command_line) const;
@ -116,8 +109,6 @@ public:
Completions complete_command_name(const Context& context, StringView query) const; Completions complete_command_name(const Context& context, StringView query) const;
void clear_last_complete_command() { m_last_complete_command = String{}; }
bool module_defined(StringView module_name) const; bool module_defined(StringView module_name) const;
void register_module(String module_name, String commands); void register_module(String module_name, String commands);
@ -127,6 +118,26 @@ public:
Completions complete_module_name(StringView query) const; Completions complete_module_name(StringView query) const;
struct Completer
{
Completions operator()(const Context& context, CompletionFlags flags,
StringView command_line, ByteCount cursor_pos);
private:
String m_last_complete_command;
CommandCompleter m_command_completer;
};
struct NestedCompleter
{
Completions operator()(const Context& context, CompletionFlags flags,
CommandParameters params, size_t token_to_complete, ByteCount pos_in_token);
private:
String m_last_complete_command;
CommandCompleter m_command_completer;
};
private: private:
struct Command struct Command
{ {
@ -139,7 +150,6 @@ private:
}; };
using CommandMap = HashMap<String, Command, MemoryDomain::Commands>; using CommandMap = HashMap<String, Command, MemoryDomain::Commands>;
CommandMap m_commands; CommandMap m_commands;
String m_last_complete_command;
int m_command_depth = 0; int m_command_depth = 0;
struct Module struct Module

View File

@ -80,7 +80,7 @@ struct PerArgumentCommandCompleter<Completer, Rest...> : PerArgumentCommandCompl
Completions operator()(const Context& context, CompletionFlags flags, Completions operator()(const Context& context, CompletionFlags flags,
CommandParameters params, size_t token_to_complete, CommandParameters params, size_t token_to_complete,
ByteCount pos_in_token) const ByteCount pos_in_token)
{ {
if (token_to_complete == 0) if (token_to_complete == 0)
{ {
@ -284,9 +284,6 @@ struct ShellCandidatesCompleter
CommandParameters params, size_t token_to_complete, CommandParameters params, size_t token_to_complete,
ByteCount pos_in_token) ByteCount pos_in_token)
{ {
if (flags & CompletionFlags::Start)
m_token = -1;
if (m_token != token_to_complete) if (m_token != token_to_complete)
{ {
ShellContext shell_context{ ShellContext shell_context{
@ -1140,11 +1137,7 @@ const CommandDesc add_hook_cmd = {
}, },
CommandFlags::None, CommandFlags::None,
CommandHelper{}, CommandHelper{},
make_completer(menu(complete_scope), menu(complete_hooks), complete_nothing, make_completer(menu(complete_scope), menu(complete_hooks), complete_nothing, CommandManager::Completer{}),
[](const Context& context, CompletionFlags flags,
StringView prefix, ByteCount cursor_pos)
{ return CommandManager::instance().complete(
context, flags, prefix, cursor_pos); }),
[](const ParametersParser& parser, Context& context, const ShellContext&) [](const ParametersParser& parser, Context& context, const ShellContext&)
{ {
auto descs = enum_desc(Meta::Type<Hook>{}); auto descs = enum_desc(Meta::Type<Hook>{});
@ -1270,15 +1263,7 @@ CommandCompleter make_command_completer(StringView type, StringView param, Compl
return ShellCandidatesCompleter{param.str(), completions_flags}; return ShellCandidatesCompleter{param.str(), completions_flags};
} }
else if (type == "command") else if (type == "command")
{ return CommandManager::NestedCompleter{};
return [](const Context& context, CompletionFlags flags,
CommandParameters params,
size_t token_to_complete, ByteCount pos_in_token)
{
return CommandManager::instance().complete(
context, flags, params, token_to_complete, pos_in_token);
};
}
else if (type == "shell") else if (type == "shell")
{ {
return [=](const Context& context, CompletionFlags flags, return [=](const Context& context, CompletionFlags flags,

View File

@ -47,7 +47,6 @@ enum class CompletionFlags
{ {
None = 0, None = 0,
Fast = 1 << 0, Fast = 1 << 0,
Start = 1 << 2,
}; };
constexpr bool with_bit_ops(Meta::Type<CompletionFlags>) { return true; } constexpr bool with_bit_ops(Meta::Type<CompletionFlags>) { return true; }

View File

@ -471,17 +471,15 @@ void command(const Context& context, EnvVarMap env_vars, char reg = 0)
if (not CommandManager::has_instance()) if (not CommandManager::has_instance())
throw runtime_error{"commands are not supported"}; throw runtime_error{"commands are not supported"};
CommandManager::instance().clear_last_complete_command();
String default_command = context.main_sel_register_value(reg ? reg : ':').str(); String default_command = context.main_sel_register_value(reg ? reg : ':').str();
context.input_handler().prompt( context.input_handler().prompt(
":", {}, default_command, ":", {}, default_command,
context.faces()["Prompt"], PromptFlags::DropHistoryEntriesWithBlankPrefix, context.faces()["Prompt"], PromptFlags::DropHistoryEntriesWithBlankPrefix,
':', ':',
[](const Context& context, CompletionFlags flags, [completer=CommandManager::Completer{}](const Context& context, CompletionFlags flags,
StringView cmd_line, ByteCount pos) { StringView cmd_line, ByteCount pos) mutable {
return CommandManager::instance().complete(context, flags, cmd_line, pos); return completer(context, flags, cmd_line, pos);
}, },
[env_vars = std::move(env_vars), default_command](StringView cmdline, PromptEvent event, Context& context) { [env_vars = std::move(env_vars), default_command](StringView cmdline, PromptEvent event, Context& context) {
if (context.has_client()) if (context.has_client())