From 719512b308f1d5165037775cb314094f1bb870ad Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Sat, 11 Nov 2023 18:45:27 +1100 Subject: [PATCH] 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. --- src/command_manager.cc | 98 +++++++++++++++++++++--------------------- src/command_manager.hh | 30 ++++++++----- src/commands.cc | 21 ++------- src/completion.hh | 1 - src/normal.cc | 8 ++-- 5 files changed, 76 insertions(+), 82 deletions(-) diff --git a/src/command_manager.cc b/src/command_manager.cc index c3eb141e..f7d094bb 100644 --- a/src/command_manager.cc +++ b/src/command_manager.cc @@ -719,10 +719,34 @@ static Completions complete_expand(const Context& context, CompletionFlags flags return {}; } -Completions CommandManager::complete(const Context& context, - CompletionFlags flags, - StringView command_line, - ByteCount cursor_pos) + +static Completions 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; +} + +Completions CommandManager::Completer::operator()( + const Context& context, CompletionFlags flags, + StringView command_line, ByteCount cursor_pos) { auto prefix = command_line.substr(0_byte, cursor_pos); CommandParser parser{prefix}; @@ -754,29 +778,6 @@ Completions CommandManager::complete(const Context& context, if (token.terminated) // do not complete past explicit token close 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 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 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) { case Token::Type::RegisterExpand: @@ -800,17 +803,16 @@ Completions CommandManager::complete(const Context& context, case Token::Type::RawQuoted: { StringView command_name = tokens.front().content; - if (command_name != m_last_complete_command) - { - 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()) + auto command_it = commands.find(resolve_alias(context, command_name)); + if (command_it == commands.end()) return Completions{}; 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>(); ParametersParser parser{raw_params, command.param_desc, true}; @@ -841,13 +843,13 @@ Completions CommandManager::complete(const Context& context, break; } - if (not command.completer) + if (not m_command_completer) return Completions{}; Vector params{parser.begin(), parser.end()}; 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: 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{}; } -Completions CommandManager::complete(const Context& context, - CompletionFlags flags, - CommandParameters params, - size_t token_to_complete, - ByteCount pos_in_token) +Completions CommandManager::NestedCompleter::operator()( + const Context& context, CompletionFlags flags, CommandParameters params, + size_t token_to_complete, ByteCount pos_in_token) { StringView prefix = params[token_to_complete].substr(0, pos_in_token); if (token_to_complete == 0) - return complete_command_name(context, prefix); + return CommandManager::instance().complete_command_name(context, prefix); StringView command_name = params[0]; + auto& commands = CommandManager::instance().m_commands; if (command_name != m_last_complete_command) { 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 (it != m_commands.end() and it->value.completer) - ? it->value.completer(context, flags, params.subrange(1), token_to_complete-1, pos_in_token) + return m_command_completer + ? m_command_completer(context, flags, params.subrange(1), token_to_complete-1, pos_in_token) : Completions{}; } diff --git a/src/command_manager.hh b/src/command_manager.hh index 8804b5b2..878ca129 100644 --- a/src/command_manager.hh +++ b/src/command_manager.hh @@ -93,13 +93,6 @@ public: 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 command_info(const Context& context, StringView command_line) const; @@ -116,8 +109,6 @@ public: 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; void register_module(String module_name, String commands); @@ -127,6 +118,26 @@ public: 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: struct Command { @@ -139,7 +150,6 @@ private: }; using CommandMap = HashMap; CommandMap m_commands; - String m_last_complete_command; int m_command_depth = 0; struct Module diff --git a/src/commands.cc b/src/commands.cc index 94c61241..92e11e1b 100644 --- a/src/commands.cc +++ b/src/commands.cc @@ -80,7 +80,7 @@ struct PerArgumentCommandCompleter : PerArgumentCommandCompl Completions operator()(const Context& context, CompletionFlags flags, CommandParameters params, size_t token_to_complete, - ByteCount pos_in_token) const + ByteCount pos_in_token) { if (token_to_complete == 0) { @@ -284,9 +284,6 @@ struct ShellCandidatesCompleter CommandParameters params, size_t token_to_complete, ByteCount pos_in_token) { - if (flags & CompletionFlags::Start) - m_token = -1; - if (m_token != token_to_complete) { ShellContext shell_context{ @@ -1140,11 +1137,7 @@ const CommandDesc add_hook_cmd = { }, CommandFlags::None, CommandHelper{}, - make_completer(menu(complete_scope), menu(complete_hooks), complete_nothing, - [](const Context& context, CompletionFlags flags, - StringView prefix, ByteCount cursor_pos) - { return CommandManager::instance().complete( - context, flags, prefix, cursor_pos); }), + make_completer(menu(complete_scope), menu(complete_hooks), complete_nothing, CommandManager::Completer{}), [](const ParametersParser& parser, Context& context, const ShellContext&) { auto descs = enum_desc(Meta::Type{}); @@ -1270,15 +1263,7 @@ CommandCompleter make_command_completer(StringView type, StringView param, Compl return ShellCandidatesCompleter{param.str(), completions_flags}; } else if (type == "command") - { - 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); - }; - } + return CommandManager::NestedCompleter{}; else if (type == "shell") { return [=](const Context& context, CompletionFlags flags, diff --git a/src/completion.hh b/src/completion.hh index d3aeca2b..e9321d5a 100644 --- a/src/completion.hh +++ b/src/completion.hh @@ -47,7 +47,6 @@ enum class CompletionFlags { None = 0, Fast = 1 << 0, - Start = 1 << 2, }; constexpr bool with_bit_ops(Meta::Type) { return true; } diff --git a/src/normal.cc b/src/normal.cc index ebe86ebc..e70d0680 100644 --- a/src/normal.cc +++ b/src/normal.cc @@ -471,17 +471,15 @@ void command(const Context& context, EnvVarMap env_vars, char reg = 0) if (not CommandManager::has_instance()) throw runtime_error{"commands are not supported"}; - CommandManager::instance().clear_last_complete_command(); - String default_command = context.main_sel_register_value(reg ? reg : ':').str(); context.input_handler().prompt( ":", {}, default_command, context.faces()["Prompt"], PromptFlags::DropHistoryEntriesWithBlankPrefix, ':', - [](const Context& context, CompletionFlags flags, - StringView cmd_line, ByteCount pos) { - return CommandManager::instance().complete(context, flags, cmd_line, pos); + [completer=CommandManager::Completer{}](const Context& context, CompletionFlags flags, + StringView cmd_line, ByteCount pos) mutable { + return completer(context, flags, cmd_line, pos); }, [env_vars = std::move(env_vars), default_command](StringView cmdline, PromptEvent event, Context& context) { if (context.has_client())