diff --git a/src/command_manager.cc b/src/command_manager.cc index e4d52d76..64b12a32 100644 --- a/src/command_manager.cc +++ b/src/command_manager.cc @@ -726,7 +726,7 @@ Completions CommandManager::complete(const Context& context, context, flags, params, tokens.size() - 2, cursor_pos_in_token), start); - if (not completions.quoted and token.type == Token::Type::Raw) + if (not (completions.flags & Completions::Flags::Quoted) and token.type == Token::Type::Raw) { for (auto& c : completions.candidates) c = (not c.empty() and contains("%'\"", c[0]) ? "\\" : "") + escape(c, "; \t", '\\'); diff --git a/src/commands.cc b/src/commands.cc index e368dca8..ad3ed547 100644 --- a/src/commands.cc +++ b/src/commands.cc @@ -102,12 +102,30 @@ make_completer(Completers&&... completers) return {std::forward(completers)...}; } +template +auto add_flags(Completer&& completer, Completions::Flags completions_flags) +{ + return [=](const Context& context, CompletionFlags flags, const String& prefix, ByteCount cursor_pos) { + Completions res = completer(context, flags, prefix, cursor_pos); + res.flags |= completions_flags; + return res; + }; +} + +template +auto menu(Completer&& completer) +{ + return add_flags(std::forward(completer), Completions::Flags::Menu); +} + +template auto filename_completer = make_completer( [](const Context& context, CompletionFlags flags, const String& prefix, ByteCount cursor_pos) { return Completions{ 0_byte, cursor_pos, complete_filename(prefix, context.options()["ignored_files"].get(), - cursor_pos, FilenameFlags::Expand) }; }); + cursor_pos, FilenameFlags::Expand), + menu ? Completions::Flags::Menu : Completions::Flags::None}; }); template static Completions complete_buffer_name(const Context& context, CompletionFlags flags, @@ -165,9 +183,6 @@ auto make_single_word_completer(std::function func) return { 0_byte, cursor_pos, complete(prefix, cursor_pos, candidate) }; }); } -auto buffer_completer = make_completer(complete_buffer_name); -auto other_buffer_completer = make_completer(complete_buffer_name); - const ParameterDesc no_params{ {}, ParameterDesc::Flags::None, 0, 0 }; const ParameterDesc single_param{ {}, ParameterDesc::Flags::None, 1, 1 }; const ParameterDesc single_optional_param{ {}, ParameterDesc::Flags::None, 0, 1 }; @@ -190,7 +205,9 @@ static Completions complete_command_name(const Context& context, CompletionFlags struct ShellScriptCompleter { - ShellScriptCompleter(String shell_script) : m_shell_script{std::move(shell_script)} {} + ShellScriptCompleter(String shell_script, + Completions::Flags flags = Completions::Flags::None) + : m_shell_script{std::move(shell_script)}, m_flags(flags) {} Completions operator()(const Context& context, CompletionFlags flags, CommandParameters params, size_t token_to_complete, @@ -211,15 +228,18 @@ struct ShellScriptCompleter for (auto&& candidate : output | split('\n')) candidates.push_back(candidate.str()); - return {0_byte, pos_in_token, std::move(candidates)}; + return {0_byte, pos_in_token, std::move(candidates), m_flags}; } private: String m_shell_script; + Completions::Flags m_flags; }; struct ShellCandidatesCompleter { - ShellCandidatesCompleter(String shell_script) : m_shell_script{std::move(shell_script)} {} + ShellCandidatesCompleter(String shell_script, + Completions::Flags flags = Completions::Flags::None) + : m_shell_script{std::move(shell_script)}, m_flags(flags) {} Completions operator()(const Context& context, CompletionFlags flags, CommandParameters params, size_t token_to_complete, @@ -263,13 +283,14 @@ struct ShellCandidatesCompleter return true; }); - return Completions{ 0_byte, pos_in_token, std::move(res) }; + return Completions{0_byte, pos_in_token, std::move(res), m_flags}; } private: String m_shell_script; Vector, MemoryDomain::Completion> m_candidates; int m_token = -1; + Completions::Flags m_flags; }; template @@ -421,7 +442,7 @@ const CommandDesc edit_cmd = { edit_params, CommandFlags::None, CommandHelper{}, - filename_completer, + filename_completer, edit }; @@ -433,7 +454,7 @@ const CommandDesc force_edit_cmd = { edit_params, CommandFlags::None, CommandHelper{}, - filename_completer, + filename_completer, edit }; @@ -480,7 +501,7 @@ const CommandDesc write_cmd = { write_params, CommandFlags::None, CommandHelper{}, - filename_completer, + filename_completer, write_buffer, }; @@ -492,7 +513,7 @@ const CommandDesc force_write_cmd = { write_params, CommandFlags::None, CommandHelper{}, - filename_completer, + filename_completer, write_buffer, }; @@ -688,7 +709,7 @@ const CommandDesc buffer_cmd = { single_param, CommandFlags::None, CommandHelper{}, - other_buffer_completer, + make_completer(menu(complete_buffer_name)), [](const ParametersParser& parser, Context& context, const ShellContext&) { Buffer& buffer = BufferManager::instance().get_buffer(parser[0]); @@ -775,7 +796,7 @@ const CommandDesc delete_buffer_cmd = { single_optional_param, CommandFlags::None, CommandHelper{}, - buffer_completer, + make_completer(menu(complete_buffer_name)), delete_buffer }; @@ -787,7 +808,7 @@ const CommandDesc force_delete_buffer_cmd = { single_optional_param, CommandFlags::None, CommandHelper{}, - buffer_completer, + make_completer(menu(complete_buffer_name)), delete_buffer }; @@ -1037,6 +1058,9 @@ void define_command(const ParametersParser& parser, Context& context, const Shel if (parser.get_switch("hidden")) flags = CommandFlags::Hidden; + const Completions::Flags completions_flags = parser.get_switch("menu") ? + Completions::Flags::Menu : Completions::Flags::None; + const String& commands = parser[1]; CommandFunc cmd; ParameterDesc desc; @@ -1073,45 +1097,48 @@ void define_command(const ParametersParser& parser, Context& context, const Shel CommandCompleter completer; if (parser.get_switch("file-completion")) { - completer = [](const Context& context, CompletionFlags flags, + completer = [=](const Context& context, CompletionFlags flags, CommandParameters params, size_t token_to_complete, ByteCount pos_in_token) { const String& prefix = params[token_to_complete]; auto& ignored_files = context.options()["ignored_files"].get(); - return Completions{ 0_byte, pos_in_token, - complete_filename(prefix, ignored_files, - pos_in_token, FilenameFlags::Expand) }; + return Completions{0_byte, pos_in_token, + complete_filename(prefix, ignored_files, + pos_in_token, FilenameFlags::Expand), + completions_flags}; }; } else if (parser.get_switch("client-completion")) { - completer = [](const Context& context, CompletionFlags flags, + completer = [=](const Context& context, CompletionFlags flags, CommandParameters params, size_t token_to_complete, ByteCount pos_in_token) { const String& prefix = params[token_to_complete]; auto& cm = ClientManager::instance(); - return Completions{ 0_byte, pos_in_token, - cm.complete_client_name(prefix, pos_in_token) }; + return Completions{0_byte, pos_in_token, + cm.complete_client_name(prefix, pos_in_token), + completions_flags}; }; } else if (parser.get_switch("buffer-completion")) { - completer = [](const Context& context, CompletionFlags flags, + completer = [=](const Context& context, CompletionFlags flags, CommandParameters params, size_t token_to_complete, ByteCount pos_in_token) { - return complete_buffer_name(context, flags, params[token_to_complete], pos_in_token); + return add_flags(complete_buffer_name, completions_flags)( + context, flags, params[token_to_complete], pos_in_token); }; } else if (auto shell_script = parser.get_switch("shell-script-completion")) { - completer = ShellScriptCompleter{shell_script->str()}; + completer = ShellScriptCompleter{shell_script->str(), completions_flags}; } else if (auto shell_script = parser.get_switch("shell-script-candidates")) { - completer = ShellCandidatesCompleter{shell_script->str()}; + completer = ShellCandidatesCompleter{shell_script->str(), completions_flags}; } else if (parser.get_switch("command-completion")) { @@ -1125,11 +1152,12 @@ void define_command(const ParametersParser& parser, Context& context, const Shel } else if (parser.get_switch("shell-completion")) { - completer = [](const Context& context, CompletionFlags flags, - CommandParameters params, - size_t token_to_complete, ByteCount pos_in_token) + completer = [=](const Context& context, CompletionFlags flags, + CommandParameters params, + size_t token_to_complete, ByteCount pos_in_token) { - return shell_complete(context, flags, params[token_to_complete], pos_in_token); + return add_flags(shell_complete, completions_flags)( + context, flags, params[token_to_complete], pos_in_token); }; } @@ -1148,6 +1176,7 @@ const CommandDesc define_command_cmd = { { "override", { false, "allow overriding an existing command" } }, { "hidden", { false, "do not display the command in completion candidates" } }, { "docstring", { true, "define the documentation string for command" } }, + { "menu", { false, "treat completions as the only valid inputs" } }, { "file-completion", { false, "complete parameters using filename completion" } }, { "client-completion", { false, "complete parameters using client name completion" } }, { "buffer-completion", { false, "complete parameters using buffer name completion" } }, @@ -1368,7 +1397,7 @@ const CommandDesc source_cmd = { ParameterDesc{ {}, ParameterDesc::Flags::None, 1, (size_t)-1 }, CommandFlags::None, CommandHelper{}, - filename_completer, + filename_completer, [](const ParametersParser& parser, Context& context, const ShellContext&) { const DebugFlags debug_flags = context.options()["debug"].get(); @@ -1447,7 +1476,9 @@ const CommandDesc set_option_cmd = { GlobalScope::instance().option_registry().option_exists(params[start + 1])) { OptionManager& options = get_scope(params[start], context).options(); - return { 0_byte, params[start + 2].length(), { options[params[start + 1]].get_as_string(Quoting::Kakoune) }, true }; + return {0_byte, params[start + 2].length(), + {options[params[start + 1]].get_as_string(Quoting::Kakoune)}, + Completions::Flags::Quoted}; } return Completions{}; }, diff --git a/src/completion.hh b/src/completion.hh index 1cfd1f22..370aec1b 100644 --- a/src/completion.hh +++ b/src/completion.hh @@ -18,10 +18,19 @@ using CandidateList = Vector; struct Completions { + enum class Flags + { + None = 0, + Quoted = 0b1, + Menu = 0b10 + }; + + constexpr friend bool with_bit_ops(Meta::Type) { return true; } + CandidateList candidates; ByteCount start; ByteCount end; - bool quoted = false; + Flags flags = Flags::None; Completions() : start(0), end(0) {} @@ -29,8 +38,8 @@ struct Completions Completions(ByteCount start, ByteCount end) : start(start), end(end) {} - Completions(ByteCount start, ByteCount end, CandidateList candidates, bool quoted = false) - : candidates(std::move(candidates)), start(start), end(end), quoted{quoted} {} + Completions(ByteCount start, ByteCount end, CandidateList candidates, Flags flags = Flags::None) + : candidates(std::move(candidates)), start(start), end(end), flags{flags} {} }; enum class CompletionFlags @@ -53,8 +62,8 @@ Completions shell_complete(const Context& context, CompletionFlags, inline Completions offset_pos(Completions completion, ByteCount offset) { - return { completion.start + offset, completion.end + offset, - std::move(completion.candidates), completion.quoted }; + return {completion.start + offset, completion.end + offset, + std::move(completion.candidates), completion.flags}; } template diff --git a/src/input_handler.cc b/src/input_handler.cc index 275bc1f0..58b8d0d2 100644 --- a/src/input_handler.cc +++ b/src/input_handler.cc @@ -780,6 +780,15 @@ public: if (key == Key::Return) { + if ((m_completions.flags & Completions::Flags::Menu) and + m_current_completion == -1 and + not m_completions.candidates.empty()) + { + const String& completion = m_completions.candidates.front(); + m_line_editor.insert_from(line.char_count_to(m_completions.start), + completion); + } + if (not context().history_disabled()) history_push(history, line); context().print_status(DisplayLine{}); @@ -1001,6 +1010,7 @@ private: const String& line = m_line_editor.line(); m_completions = m_completer(context(), flags, line, line.byte_count_to(m_line_editor.cursor_pos())); + const bool menu = (bool)(m_completions.flags & Completions::Flags::Menu); if (context().has_client()) { if (m_completions.candidates.empty()) @@ -1013,8 +1023,11 @@ private: const auto menu_style = (m_flags & PromptFlags::Search) ? MenuStyle::Search : MenuStyle::Prompt; context().client().menu_show(items, {}, menu_style); + if (menu) + context().client().menu_select(0); + auto prefix = line.substr(m_completions.start, m_completions.end - m_completions.start); - if (not contains(m_completions.candidates, prefix)) + if (not menu and not contains(m_completions.candidates, prefix)) { m_current_completion = m_completions.candidates.size(); m_completions.candidates.push_back(prefix.str());