From 8aaeaa3187aa6cda3f2b264d474f15b4486c44b5 Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Fri, 14 Feb 2014 02:21:06 +0000 Subject: [PATCH] Declare commands in a CommandDesc structure --- src/commands.cc | 1294 +++++++++++++++++++++++++++-------------------- 1 file changed, 736 insertions(+), 558 deletions(-) diff --git a/src/commands.cc b/src/commands.cc index e39bac81..addc67ee 100644 --- a/src/commands.cc +++ b/src/commands.cc @@ -84,13 +84,42 @@ Buffer* open_fifo(const String& name , const String& filename, Context& context) return buffer; } -static const ParameterDesc edit_params{ - SwitchMap{ { "scratch", { false, "create a scratch buffer, not linked to a file" } }, - { "fifo", { true, "create a buffer reading its content from a named fifo" } } }, - ParameterDesc::Flags::None, 1, 3 +const PerArgumentCommandCompleter filename_completer({ + [](const Context& context, CompletionFlags flags, const String& prefix, ByteCount cursor_pos) + { return Completions{ 0_byte, prefix.length(), + complete_filename(prefix, + context.options()["ignored_files"].get(), + cursor_pos) }; } +}); + +const PerArgumentCommandCompleter buffer_completer({ + [](const Context& context, CompletionFlags flags, const String& prefix, ByteCount cursor_pos) + { return Completions{ 0_byte, prefix.length(), + BufferManager::instance().complete_buffername(prefix, cursor_pos) }; } +}); + +const ParameterDesc no_params{ + SwitchMap{}, ParameterDesc::Flags::None, 0, 0 }; -static const char* edit_desc = "edit : open the given filename in a buffer"; +const ParameterDesc single_name_param{ + SwitchMap{}, ParameterDesc::Flags::None, 1, 1 +}; + +const ParameterDesc single_optional_name_param{ + SwitchMap{}, ParameterDesc::Flags::None, 0, 1 +}; + +struct CommandDesc +{ + const char* name; + const char* alias; + const char* docstring; + ParameterDesc params; + CommandFlags flags; + CommandCompleter completer; + void (*func)(const ParametersParser&, Context&); +}; template void edit(const ParametersParser& parser, Context& context) @@ -134,12 +163,31 @@ void edit(const ParametersParser& parser, Context& context) } } -static const ParameterDesc write_params{ - SwitchMap{}, - ParameterDesc::Flags::None, 0, 1 +ParameterDesc edit_params{ + SwitchMap{ { "scratch", { false, "create a scratch buffer, not linked to a file" } }, + { "fifo", { true, "create a buffer reading its content from a named fifo" } } }, + ParameterDesc::Flags::None, 1, 3 }; -static const char* write_desc = "write [filename]: write the current buffer to it's file or to [filename] if specified"; +const CommandDesc edit_cmd = { + "edit", + "e", + "edit : open the given filename in a buffer", + edit_params, + CommandFlags::None, + filename_completer, + edit +}; + +const CommandDesc force_edit_cmd = { + "edit!", + "e!", + "edit! : open the given filename in a buffer, force reload if needed", + edit_params, + CommandFlags::None, + filename_completer, + edit +}; void write_buffer(const ParametersParser& parser, Context& context) { @@ -154,23 +202,32 @@ void write_buffer(const ParametersParser& parser, Context& context) write_buffer_to_file(buffer, filename); } -static const ParameterDesc no_params{ - SwitchMap{}, - ParameterDesc::Flags::None, 0, 0 +const CommandDesc write_cmd = { + "write", + "w", + "write [filename]: write the current buffer to it's file or to [filename] if specified", + single_optional_name_param, + CommandFlags::None, + filename_completer, + write_buffer, }; -static const char* write_all_desc = "write all buffers that are associated to a file"; - -void write_all_buffers(const ParametersParser& parser, Context& context) -{ - for (auto& buffer : BufferManager::instance()) +const CommandDesc writeall_cmd = { + "writeall", + "wa", + "write all buffers that are associated to a file", + no_params, + CommandFlags::None, + CommandCompleter{}, + [](const ParametersParser& parser, Context& context) { - if ((buffer->flags() & Buffer::Flags::File) and buffer->is_modified()) - write_buffer_to_file(*buffer, buffer->name()); + for (auto& buffer : BufferManager::instance()) + { + if ((buffer->flags() & Buffer::Flags::File) and buffer->is_modified()) + write_buffer_to_file(*buffer, buffer->name()); + } } -} - -static const char* quit_desc = "quit current client, and the kakoune session if the client is the last (if not running in daemon mode)"; +}; template void quit(const ParametersParser& parser, Context& context) @@ -200,34 +257,74 @@ void quit(const ParametersParser& parser, Context& context) throw client_removed{}; } -static const char* write_and_quit_desc = "write current buffer and quit current client"; +const CommandDesc quit_cmd = { + "quit", + "q", + "quit current client, and the kakoune session if the client is the last (if not running in daemon mode)", + no_params, + CommandFlags::None, + CommandCompleter{}, + quit +}; -template -void write_and_quit(const ParametersParser& parser, Context& context) -{ - write_buffer(parser, context); - quit(ParametersParser{memoryview{}, no_params}, context); -} +const CommandDesc force_quit_cmd = { + "quit!", + "q!", + "quit current client, and the kakoune session if the client is the last (if not running in daemon mode)\n" + "force quit even if the client is the last and some buffers are not saved.", + no_params, + CommandFlags::None, + CommandCompleter{}, + quit +}; -static const ParameterDesc single_name_params{ SwitchMap{}, ParameterDesc::Flags::None, 1, 1 }; - -static const char* show_buffer_desc = "buffer : set buffer to edit in current client"; - -void show_buffer(const ParametersParser& parser, Context& context) -{ - Buffer& buffer = BufferManager::instance().get_buffer(parser[0]); - BufferManager::instance().set_last_used_buffer(buffer); - - if (&buffer != &context.buffer()) +const CommandDesc write_quit_cmd = { + "wq", + nullptr, + "write current buffer and quit current client", + no_params, + CommandFlags::None, + CommandCompleter{}, + [](const ParametersParser& parser, Context& context) { - context.push_jump(); - context.change_buffer(buffer); + write_buffer(parser, context); + quit(ParametersParser{memoryview{}, no_params}, context); } -} +}; -static const ParameterDesc single_opt_name_params{ SwitchMap{}, ParameterDesc::Flags::None, 0, 1 }; +const CommandDesc force_write_quit_cmd = { + "wq!", + nullptr, + "write current buffer and quit current client, even if other buffers are not saved", + no_params, + CommandFlags::None, + CommandCompleter{}, + [](const ParametersParser& parser, Context& context) + { + write_buffer(parser, context); + quit(ParametersParser{memoryview{}, no_params}, context); + } +}; -static const char* delete_buffer_desc = "delbuf [name]: delete the current buffer or the buffer named if given"; +const CommandDesc buffer_cmd = { + "buffer", + "b", + "buffer : set buffer to edit in current client", + single_name_param, + CommandFlags::None, + buffer_completer, + [](const ParametersParser& parser, Context& context) + { + Buffer& buffer = BufferManager::instance().get_buffer(parser[0]); + BufferManager::instance().set_last_used_buffer(buffer); + + if (&buffer != &context.buffer()) + { + context.push_jump(); + context.change_buffer(buffer); + } + } +}; template void delete_buffer(const ParametersParser& parser, Context& context) @@ -243,76 +340,150 @@ void delete_buffer(const ParametersParser& parser, Context& context) manager.delete_buffer(buffer); } -static const char* set_buffer_name_desc = "namebuf : change current buffer name"; - -void set_buffer_name(const ParametersParser& parser, Context& context) -{ - if (not context.buffer().set_name(parser[0])) - throw runtime_error("unable to change buffer name to " + parser[0]); -} - -static const char* define_highlighter_desc = "defhl : define a new reusable highlighter"; - -void define_highlighter(const ParametersParser& parser, Context& context) -{ - const String& name = parser[0]; - DefinedHighlighters::instance().append({name, HighlighterGroup{}}); -} - -static const ParameterDesc add_highlighter_params{ - SwitchMap{ { "group", { true, "add highlighter to named group" } }, - { "def-group", { true, "add highlighter to reusable defined group" } } }, - ParameterDesc::Flags::SwitchesOnlyAtStart, 1 +const CommandDesc delbuf_cmd = { + "delbuf", + "db", + "delbuf [name]: delete the current buffer or the buffer named if given", + single_optional_name_param, + CommandFlags::None, + buffer_completer, + delete_buffer }; -static const char* add_highlighter_desc = "addhl ...: add an highlighter to current window"; +const CommandDesc force_delbuf_cmd = { + "delbuf!", + "db!", + "delbuf! [name]: delete the current buffer or the buffer named if given, even if the buffer is unsaved", + single_optional_name_param, + CommandFlags::None, + buffer_completer, + delete_buffer +}; -void add_highlighter(const ParametersParser& parser, Context& context) +const CommandDesc namebuf_cmd = { + "namebuf", + nullptr, + "namebuf : change current buffer name", + single_name_param, + CommandFlags::None, + CommandCompleter{}, + [](const ParametersParser& parser, Context& context) + { + if (not context.buffer().set_name(parser[0])) + throw runtime_error("unable to change buffer name to " + parser[0]); + } +}; + +const CommandDesc define_highlighter_cmd = { + "defhl", + "dh", + "defhl : define a new reusable highlighter", + single_name_param, + CommandFlags::None, + CommandCompleter{}, + [](const ParametersParser& parser, Context& context) + { + const String& name = parser[0]; + DefinedHighlighters::instance().append({name, HighlighterGroup{}}); + } +}; + +template +CommandCompleter group_rm_completer(GetRootGroup get_root_group) { - HighlighterRegistry& registry = HighlighterRegistry::instance(); + return [=](const Context& context, CompletionFlags flags, + CommandParameters params, size_t token_to_complete, + ByteCount pos_in_token) -> Completions { + auto& root_group = get_root_group(context); + const String& arg = params[token_to_complete]; + if (token_to_complete == 1 and params[0] == "-group") + return { 0_byte, arg.length(), root_group.complete_group_id(arg, pos_in_token) }; + else if (token_to_complete == 2 and params[0] == "-group") + return { 0_byte, arg.length(), root_group.get_group(params[1], '/').complete_id(arg, pos_in_token) }; + return { 0_byte, arg.length(), root_group.complete_id(arg, pos_in_token) }; + }; +} - auto begin = parser.begin(); - const String& name = *begin; - std::vector highlighter_params; - for (++begin; begin != parser.end(); ++begin) - highlighter_params.push_back(*begin); +template +CommandCompleter group_add_completer(GetRootGroup get_root_group) +{ + return [=](const Context& context, CompletionFlags flags, + CommandParameters params, size_t token_to_complete, + ByteCount pos_in_token) -> Completions { + auto& root_group = get_root_group(context); + const String& arg = params[token_to_complete]; + if (token_to_complete == 1 and params[0] == "-group") + return { 0_byte, arg.length(), root_group.complete_group_id(arg, pos_in_token) }; + else if (token_to_complete == 0 or (token_to_complete == 2 and params[0] == "-group")) + return { 0_byte, arg.length(), FactoryRegistry::instance().complete_name(arg, pos_in_token) }; + return Completions{}; + }; +} - if (parser.has_option("group") and parser.has_option("def-group")) - throw runtime_error("-group and -def-group cannot be specified together"); +HighlighterGroup& get_highlighters(const Context& c) { return c.window().highlighters(); } - HighlighterGroup* group = nullptr; +const CommandDesc add_highlighter_cmd = { + "addhl", + "ah", + "addhl ...: add an highlighter to current window", + ParameterDesc{ + SwitchMap{ { "group", { true, "add highlighter to named group" } }, + { "def-group", { true, "add highlighter to reusable defined group" } } }, + ParameterDesc::Flags::SwitchesOnlyAtStart, 1 + }, + CommandFlags::None, + group_add_completer(get_highlighters), + [](const ParametersParser& parser, Context& context) + { + HighlighterRegistry& registry = HighlighterRegistry::instance(); - if (parser.has_option("def-group")) - group = &DefinedHighlighters::instance().get_group(parser.option_value("def-group"), '/'); - else + auto begin = parser.begin(); + const String& name = *begin; + std::vector highlighter_params; + for (++begin; begin != parser.end(); ++begin) + highlighter_params.push_back(*begin); + + if (parser.has_option("group") and parser.has_option("def-group")) + throw runtime_error("-group and -def-group cannot be specified together"); + + HighlighterGroup* group = nullptr; + + if (parser.has_option("def-group")) + group = &DefinedHighlighters::instance().get_group(parser.option_value("def-group"), '/'); + else + { + HighlighterGroup& window_hl = context.window().highlighters(); + group = parser.has_option("group") ? + &window_hl.get_group(parser.option_value("group"), '/') + : &window_hl; + } + + group->append(registry[name](highlighter_params)); + } +}; + +const CommandDesc rm_highlighter_cmd = { + "rmhl", + "rh", + "rmhl : remove highlighter from current window", + ParameterDesc{ + SwitchMap{ { "group", { true, "remove highlighter from given group" } } }, + ParameterDesc::Flags::None, 1, 1 + }, + CommandFlags::None, + group_rm_completer(get_highlighters), + [](const ParametersParser& parser, Context& context) { HighlighterGroup& window_hl = context.window().highlighters(); - group = parser.has_option("group") ? - &window_hl.get_group(parser.option_value("group"), '/') - : &window_hl; + HighlighterGroup& group = parser.has_option("group") ? + window_hl.get_group(parser.option_value("group"), '/') + : window_hl; + + group.remove(parser[0]); } - - group->append(registry[name](highlighter_params)); -} - -static const ParameterDesc rm_highlighter_params{ - SwitchMap{ { "group", { true, "remove highlighter from given group" } } }, - ParameterDesc::Flags::None, 1, 1 }; -static const char* rm_highlighter_desc = "rmhl : remove highlighter from current window"; - -void rm_highlighter(const ParametersParser& parser, Context& context) -{ - HighlighterGroup& window_hl = context.window().highlighters(); - HighlighterGroup& group = parser.has_option("group") ? - window_hl.get_group(parser.option_value("group"), '/') - : window_hl; - - group.remove(parser[0]); -} - -static HookManager& get_hook_manager(const String& scope, Context& context) +HookManager& get_hook_manager(const String& scope, Context& context) { if (prefix_match("global", scope)) return GlobalHooks::instance(); @@ -323,36 +494,66 @@ static HookManager& get_hook_manager(const String& scope, Context& context) throw runtime_error("error: no such hook container " + scope); } -static const ParameterDesc add_hook_params{ - SwitchMap{ { "id", { true, "set hook id" } } }, ParameterDesc::Flags::None, 4, 4 -}; - -static const char* add_hook_desc = "hook : add to be executed on hook in context"; - -void add_hook(const ParametersParser& parser, Context& context) +CandidateList complete_scope(const String& prefix) { - // copy so that the lambda gets a copy as well - Regex regex(parser[2].begin(), parser[2].end()); - String command = parser[3]; - auto hook_func = [=](const String& param, Context& context) { - if (boost::regex_match(param.begin(), param.end(), regex)) - CommandManager::instance().execute(command, context, {}, - { { "hook_param", param } }); - }; - String id = parser.has_option("id") ? parser.option_value("id") : ""; - get_hook_manager(parser[0], context).add_hook(parser[1], id, hook_func); + CandidateList res; + for (auto scope : { "global", "buffer", "window" }) + { + if (prefix_match(scope, prefix)) + res.emplace_back(scope); + } + return res; } -static const ParameterDesc rm_hooks_params{ - SwitchMap{}, ParameterDesc::Flags::None, 2, 2 +const CommandDesc add_hook_cmd = { + "hook", + nullptr, + "hook : add to be executed on hook in context", + ParameterDesc{ + SwitchMap{ { "id", { true, "set hook id" } } }, + ParameterDesc::Flags::None, 4, 4 + }, + CommandFlags::None, + [](const Context& context, CompletionFlags flags, + CommandParameters params, size_t token_to_complete, ByteCount pos_in_token) + { + if (token_to_complete == 0) + return Completions{ 0_byte, params[0].length(), + complete_scope(params[0].substr(0_byte, pos_in_token)) }; + else if (token_to_complete == 3) + { + auto& cm = CommandManager::instance(); + return cm.complete(context, flags, params[3], pos_in_token); + } + return Completions{}; + }, + [](const ParametersParser& parser, Context& context) + { + // copy so that the lambda gets a copy as well + Regex regex(parser[2].begin(), parser[2].end()); + String command = parser[3]; + auto hook_func = [=](const String& param, Context& context) { + if (boost::regex_match(param.begin(), param.end(), regex)) + CommandManager::instance().execute(command, context, {}, + { { "hook_param", param } }); + }; + String id = parser.has_option("id") ? parser.option_value("id") : ""; + get_hook_manager(parser[0], context).add_hook(parser[1], id, hook_func); + } }; -static const char* rm_hooks_desc = "rmhooks : remove all hooks that whose id is "; - -void rm_hooks(const ParametersParser& parser, Context& context) -{ - get_hook_manager(parser[0], context).remove_hooks(parser[1]); -} +const CommandDesc rm_hook_cmd = { + "rmhooks", + nullptr, + "rmhooks : remove all hooks that whose id is ", + ParameterDesc{ SwitchMap{}, ParameterDesc::Flags::None, 2, 2 }, + CommandFlags::None, + CommandCompleter{}, + [](const ParametersParser& parser, Context& context) + { + get_hook_manager(parser[0], context).remove_hooks(parser[1]); + } +}; EnvVarMap params_to_env_var_map(const ParametersParser& parser) { @@ -374,19 +575,6 @@ std::vector params_to_shell(const ParametersParser& parser) return vars; } -static const ParameterDesc define_command_params{ - SwitchMap{ { "env-params", { false, "pass parameters as env variables param0..paramN" } }, - { "shell-params", { false, "pass parameters to each shell escape as $0..$N" } }, - { "allow-override", { false, "allow overriding existing command" } }, - { "file-completion", { false, "complete parameters using filename completion" } }, - { "hidden", { false, "do not display the command as completion candidate" } }, - { "shell-completion", { true, "complete the parameters using the given shell-script" } } }, - ParameterDesc::Flags::None, - 2, 2 -}; - -static const char* define_command_desc = "def : define a command named corresponding to "; - void define_command(const ParametersParser& parser, Context& context) { auto begin = parser.begin(); @@ -460,63 +648,87 @@ void define_command(const ParametersParser& parser, Context& context) CommandManager::instance().register_command(cmd_name, cmd, "", desc, flags, completer); } -static const ParameterDesc echo_message_params{ - SwitchMap{ { "color", { true, "set message color" } } }, - ParameterDesc::Flags::SwitchesOnlyAtStart +const CommandDesc define_command_cmd = { + "def", + nullptr, + "def : define a command named corresponding to ", + ParameterDesc{ + SwitchMap{ { "env-params", { false, "pass parameters as env variables param0..paramN" } }, + { "shell-params", { false, "pass parameters to each shell escape as $0..$N" } }, + { "allow-override", { false, "allow overriding existing command" } }, + { "file-completion", { false, "complete parameters using filename completion" } }, + { "hidden", { false, "do not display the command as completion candidate" } }, + { "shell-completion", { true, "complete the parameters using the given shell-script" } } }, + ParameterDesc::Flags::None, + 2, 2 + }, + CommandFlags::None, + CommandCompleter{}, + define_command }; -static const char* echo_message_desc = "echo ...: display given parameters in the status line"; - -void echo_message(const ParametersParser& parser, Context& context) -{ - String message; - for (auto& param : parser) - message += param + " "; - ColorPair color = get_color(parser.has_option("color") ? - parser.option_value("color") : "StatusLine"); - context.print_status({ std::move(message), color } ); -} - -static const ParameterDesc write_debug_message_params{ - SwitchMap{}, - ParameterDesc::Flags::SwitchesOnlyAtStart -}; - -static const char* write_debug_message_desc = "debug ...: write given parameters in the debug buffer"; - -void write_debug_message(const ParametersParser& parser, Context&) -{ - String message; - for (auto& param : parser) - message += param + " "; - write_debug(message); -} - -static const ParameterDesc exec_commands_in_file_params{ - SwitchMap{}, - ParameterDesc::Flags::None, - 1, 1 -}; - -static const char* exec_commands_in_file_desc = "source : execute commands contained in "; - -void exec_commands_in_file(const ParametersParser& parser, - Context& context) -{ - String file_content = read_file(parse_filename(parser[0])); - try +const CommandDesc echo_cmd = { + "echo", + nullptr, + "echo ...: display given parameters in the status line", + ParameterDesc{ + SwitchMap{ { "color", { true, "set message color" } } }, + ParameterDesc::Flags::SwitchesOnlyAtStart + }, + CommandFlags::None, + CommandCompleter{}, + [](const ParametersParser& parser, Context& context) { - CommandManager::instance().execute(file_content, context); + String message; + for (auto& param : parser) + message += param + " "; + ColorPair color = get_color(parser.has_option("color") ? + parser.option_value("color") : "StatusLine"); + context.print_status({ std::move(message), color } ); } - catch (Kakoune::runtime_error& err) - { - write_debug("error while executing commands in file '" + parser[0] - + "'\n " + err.what()); - throw; - } -} +}; -static OptionManager& get_options(const String& scope, const Context& context) + +const CommandDesc debug_cmd = { + "debug", + nullptr, + "debug ...: write given parameters in the debug buffer", + ParameterDesc{ SwitchMap{}, ParameterDesc::Flags::SwitchesOnlyAtStart }, + CommandFlags::None, + CommandCompleter{}, + [](const ParametersParser& parser, Context&) + { + String message; + for (auto& param : parser) + message += param + " "; + write_debug(message); + } +}; + +const CommandDesc source_cmd = { + "source", + nullptr, + "source : execute commands contained in ", + single_name_param, + CommandFlags::None, + filename_completer, + [](const ParametersParser& parser, Context& context) + { + String file_content = read_file(parse_filename(parser[0])); + try + { + CommandManager::instance().execute(file_content, context); + } + catch (Kakoune::runtime_error& err) + { + write_debug("error while executing commands in file '" + parser[0] + + "'\n " + err.what()); + throw; + } + } +}; + +OptionManager& get_options(const String& scope, const Context& context) { if (prefix_match("global", scope)) return GlobalOptions::instance(); @@ -529,72 +741,92 @@ static OptionManager& get_options(const String& scope, const Context& context) throw runtime_error("error: no such option container " + scope); } - -static const ParameterDesc set_option_params{ - SwitchMap{ { "add", { false, "add to option rather than replacing it" } } }, - ParameterDesc::Flags::SwitchesOnlyAtStart, - 3, 3 +const CommandDesc set_option_cmd = { + "set", + nullptr, + "set : set option in to ", + ParameterDesc{ + SwitchMap{ { "add", { false, "add to option rather than replacing it" } } }, + ParameterDesc::Flags::SwitchesOnlyAtStart, + 3, 3 + }, + CommandFlags::None, + [](const Context& context, CompletionFlags, + CommandParameters params, size_t token_to_complete, + ByteCount pos_in_token) -> Completions + { + if (token_to_complete == 0) + return { 0_byte, params[0].length(), + complete_scope(params[0].substr(0_byte, pos_in_token)) }; + else if (token_to_complete == 1) + { + OptionManager& options = get_options(params[0], context); + return { 0_byte, params[1].length(), + options.complete_option_name(params[1], pos_in_token) }; + } + return Completions{}; + }, + [](const ParametersParser& parser, Context& context) + { + Option& opt = get_options(parser[0], context).get_local_option(parser[1]); + if (parser.has_option("add")) + opt.add_from_string(parser[2]); + else + opt.set_from_string(parser[2]); + } }; -static const char* set_option_desc = "set : set option in to "; +const CommandDesc declare_option_cmd = { + "decl", + nullptr, + "decl [value]: declare option of type , with initial value if given\n" + "Available types:\n" + " int: integer\n" + " bool: boolean (true/false or yes/no)\n" + " str: character string\n" + " regex: regular expression\n" + " int-list: list of integers\n" + " str-list: list of character strings\n" + " line-flag-list: list of line flags\n", + ParameterDesc{ + SwitchMap{ { "hidden", { false, "do not display option name when completing" } } }, + ParameterDesc::Flags::SwitchesOnlyAtStart, + 2, 3 + }, + CommandFlags::None, + CommandCompleter{}, + [](const ParametersParser& parser, Context& context) + { + Option* opt = nullptr; -void set_option(const ParametersParser& parser, Context& context) -{ - Option& opt = get_options(parser[0], context).get_local_option(parser[1]); - if (parser.has_option("add")) - opt.add_from_string(parser[2]); - else - opt.set_from_string(parser[2]); -} + Option::Flags flags = Option::Flags::None; + if (parser.has_option("hidden")) + flags = Option::Flags::Hidden; -static const ParameterDesc declare_option_params{ - SwitchMap{ { "hidden", { false, "do not display option name when completing" } } }, - ParameterDesc::Flags::SwitchesOnlyAtStart, - 2, 3 + GlobalOptions& opts = GlobalOptions::instance(); + + if (parser[0] == "int") + opt = &opts.declare_option(parser[1], 0, flags); + if (parser[0] == "bool") + opt = &opts.declare_option(parser[1], 0, flags); + else if (parser[0] == "str") + opt = &opts.declare_option(parser[1], "", flags); + else if (parser[0] == "regex") + opt = &opts.declare_option(parser[1], Regex{}, flags); + else if (parser[0] == "int-list") + opt = &opts.declare_option>(parser[1], {}, flags); + else if (parser[0] == "str-list") + opt = &opts.declare_option>(parser[1], {}, flags); + else if (parser[0] == "line-flag-list") + opt = &opts.declare_option>(parser[1], {}, flags); + else + throw runtime_error("unknown type " + parser[0]); + + if (parser.positional_count() == 3) + opt->set_from_string(parser[2]); + } }; -static const char* declare_option_desc = -"decl [value]: declare option of type , with initial value if given\n" -"Available types:\n" -" int: integer\n" -" bool: boolean (true/false or yes/no)\n" -" str: character string\n" -" regex: regular expression\n" -" int-list: list of integers\n" -" str-list: list of character strings\n" -" line-flag-list: list of line flags\n"; - -void declare_option(const ParametersParser& parser, Context& context) -{ - Option* opt = nullptr; - - Option::Flags flags = Option::Flags::None; - if (parser.has_option("hidden")) - flags = Option::Flags::Hidden; - - GlobalOptions& opts = GlobalOptions::instance(); - - if (parser[0] == "int") - opt = &opts.declare_option(parser[1], 0, flags); - if (parser[0] == "bool") - opt = &opts.declare_option(parser[1], 0, flags); - else if (parser[0] == "str") - opt = &opts.declare_option(parser[1], "", flags); - else if (parser[0] == "regex") - opt = &opts.declare_option(parser[1], Regex{}, flags); - else if (parser[0] == "int-list") - opt = &opts.declare_option>(parser[1], {}, flags); - else if (parser[0] == "str-list") - opt = &opts.declare_option>(parser[1], {}, flags); - else if (parser[0] == "line-flag-list") - opt = &opts.declare_option>(parser[1], {}, flags); - else - throw runtime_error("unknown type " + parser[0]); - - if (parser.positional_count() == 3) - opt->set_from_string(parser[2]); -} - KeymapManager& get_keymap_manager(const String& scope, Context& context) { @@ -616,31 +848,32 @@ KeymapMode parse_keymap_mode(const String& str) throw runtime_error("unknown keymap mode '" + str + "'"); } -static const ParameterDesc map_key_params{ - SwitchMap{}, ParameterDesc::Flags::None, 4, 4 +const CommandDesc map_key_cmd = { + "map", + nullptr, + "map : map to in given mode.\n" + "Valid modes:\n" + " normal\n" + " insert\n" + " menu\n" + " prompt\n", + ParameterDesc{ SwitchMap{}, ParameterDesc::Flags::None, 4, 4 }, + CommandFlags::None, + CommandCompleter{}, + [](const ParametersParser& parser, Context& context) + { + KeymapManager& keymaps = get_keymap_manager(parser[0], context); + KeymapMode keymap_mode = parse_keymap_mode(parser[1]); + + KeyList key = parse_keys(parser[2]); + if (key.size() != 1) + throw runtime_error("only a single key can be mapped"); + + KeyList mapping = parse_keys(parser[3]); + keymaps.map_key(key[0], keymap_mode, std::move(mapping)); + } }; -static const char* map_key_desc = -"map : map to in given mode.\n" -"Valid modes:\n" -" normal\n" -" insert\n" -" menu\n" -" prompt\n"; - -void map_key(const ParametersParser& parser, Context& context) -{ - KeymapManager& keymaps = get_keymap_manager(parser[0], context); - KeymapMode keymap_mode = parse_keymap_mode(parser[1]); - - KeyList key = parse_keys(parser[2]); - if (key.size() != 1) - throw runtime_error("only a single key can be mapped"); - - KeyList mapping = parse_keys(parser[3]); - keymaps.map_key(key[0], keymap_mode, std::move(mapping)); -} - const ParameterDesc context_wrap_params = { SwitchMap{ { "client", { true, "run in given client context" } }, { "try-client", { true, "run in given client context if it exists, or else in the current one" } }, @@ -697,219 +930,218 @@ void context_wrap(const ParametersParser& parser, Context& context, Func func) real_context->window().forget_timestamp(); } -static const char* exec_string_desc = "exec : execute given keys as if entered by user"; - -void exec_string(const ParametersParser& parser, Context& context) -{ - context_wrap(parser, context, [](const ParametersParser& parser, Context& context) { - KeyList keys; - for (auto& param : parser) - { - KeyList param_keys = parse_keys(param); - keys.insert(keys.end(), param_keys.begin(), param_keys.end()); - } - exec_keys(keys, context); - }); -} - -static const char* eval_string_desc = "eval : execute commands as if entered by user"; - -void eval_string(const ParametersParser& parser, Context& context) -{ - context_wrap(parser, context, [](const ParametersParser& parser, Context& context) { - String command; - for (auto& param : parser) - command += param + " "; - CommandManager::instance().execute(command, context); - }); -} - -static const ParameterDesc menu_params{ - SwitchMap{ { "auto-single", { false, "instantly validate if only one item is available" } }, - { "select-cmds", { false, "each item specify an additional command to run when selected" } } } -}; - -static const char* menu_desc = "menu ...: display a menu and execute commands for the selected item"; - - -void menu(const ParametersParser& parser, Context& context) -{ - const bool with_select_cmds = parser.has_option("select-cmds"); - const size_t modulo = with_select_cmds ? 3 : 2; - - const size_t count = parser.positional_count(); - if (count == 0 or (count % modulo) != 0) - throw wrong_argument_count(); - - if (count == modulo and parser.has_option("auto-single")) +const CommandDesc exec_string_cmd = { + "exec", + nullptr, + "exec : execute given keys as if entered by user", + context_wrap_params, + CommandFlags::None, + CommandCompleter{}, + [](const ParametersParser& parser, Context& context) { - CommandManager::instance().execute(parser[1], context); - return; - } - - std::vector choices; - std::vector commands; - std::vector select_cmds; - for (int i = 0; i < count; i += modulo) - { - choices.push_back(parser[i]); - commands.push_back(parser[i+1]); - if (with_select_cmds) - select_cmds.push_back(parser[i+2]); - } - - context.input_handler().menu(choices, - [=](int choice, MenuEvent event, Context& context) { - if (event == MenuEvent::Validate and choice >= 0 and choice < commands.size()) - CommandManager::instance().execute(commands[choice], context); - if (event == MenuEvent::Select and choice >= 0 and choice < select_cmds.size()) - CommandManager::instance().execute(select_cmds[choice], context); + context_wrap(parser, context, [](const ParametersParser& parser, Context& context) { + KeyList keys; + for (auto& param : parser) + { + KeyList param_keys = parse_keys(param); + keys.insert(keys.end(), param_keys.begin(), param_keys.end()); + } + exec_keys(keys, context); }); -} - -static const ParameterDesc info_params{ - SwitchMap{ { "anchor", { true, "set info anchoring (left, right, or cursor)" } }, - { "title", { true, "set info title" } } }, - ParameterDesc::Flags::None, 0, 1 + } }; -static const char* info_desc = "info ...: display an info box with the params as content"; - -void info(const ParametersParser& parser, Context& context) -{ - context.ui().info_hide(); - if (parser.positional_count() > 0) +const CommandDesc eval_string_cmd = { + "eval", + nullptr, + "eval : execute commands as if entered by user", + context_wrap_params, + CommandFlags::None, + CommandCompleter{}, + [](const ParametersParser& parser, Context& context) { - MenuStyle style = MenuStyle::Prompt; - DisplayCoord pos = context.ui().dimensions(); - pos.column -= 1; - if (parser.has_option("anchor")) + context_wrap(parser, context, [](const ParametersParser& parser, Context& context) { + String command; + for (auto& param : parser) + command += param + " "; + CommandManager::instance().execute(command, context); + }); + } +}; + +const CommandDesc menu_cmd = { + "menu", + nullptr, + "menu ...: display a menu and execute commands for the selected item", + ParameterDesc{ + SwitchMap{ { "auto-single", { false, "instantly validate if only one item is available" } }, + { "select-cmds", { false, "each item specify an additional command to run when selected" } } } + }, + CommandFlags::None, + CommandCompleter{}, + [](const ParametersParser& parser, Context& context) + { + const bool with_select_cmds = parser.has_option("select-cmds"); + const size_t modulo = with_select_cmds ? 3 : 2; + + const size_t count = parser.positional_count(); + if (count == 0 or (count % modulo) != 0) + throw wrong_argument_count(); + + if (count == modulo and parser.has_option("auto-single")) { - style = MenuStyle::Inline; - const auto& sel = context.selections().main(); - auto it = sel.cursor(); - String anchor = parser.option_value("anchor"); - if (anchor == "left") - it = sel.min(); - else if (anchor == "right") - it = sel.max(); - else if (anchor != "cursor") - throw runtime_error("anchor param must be one of [left, right, cursor]"); - pos = context.window().display_position(it); + CommandManager::instance().execute(parser[1], context); + return; } - const String& title = parser.has_option("title") ? parser.option_value("title") : ""; - context.ui().info_show(title, parser[0], pos, get_color("Information"), style); - } -} -static const ParameterDesc try_catch_params{ - SwitchMap{}, ParameterDesc::Flags::None, 1, 3 + std::vector choices; + std::vector commands; + std::vector select_cmds; + for (int i = 0; i < count; i += modulo) + { + choices.push_back(parser[i]); + commands.push_back(parser[i+1]); + if (with_select_cmds) + select_cmds.push_back(parser[i+2]); + } + + context.input_handler().menu(choices, + [=](int choice, MenuEvent event, Context& context) { + if (event == MenuEvent::Validate and choice >= 0 and choice < commands.size()) + CommandManager::instance().execute(commands[choice], context); + if (event == MenuEvent::Select and choice >= 0 and choice < select_cmds.size()) + CommandManager::instance().execute(select_cmds[choice], context); + }); + } }; -static const char* try_catch_desc = "try [catch ]: execute command in current context, and if an error is raised, execute if specified.\nThe error is not propagated further."; - -void try_catch(const ParametersParser& parser, Context& context) -{ - if (parser.positional_count() == 2) - throw wrong_argument_count(); - - const bool do_catch = parser.positional_count() == 3; - if (do_catch and parser[1] != "catch") - throw runtime_error("usage: try [catch ]"); - - CommandManager& command_manager = CommandManager::instance(); - try +const CommandDesc info_cmd = { + "info", + nullptr, + "info ...: display an info box with the params as content", + ParameterDesc{ + SwitchMap{ { "anchor", { true, "set info anchoring (left, right, or cursor)" } }, + { "title", { true, "set info title" } } }, + ParameterDesc::Flags::None, 0, 1 + }, + CommandFlags::None, + CommandCompleter{}, + [](const ParametersParser& parser, Context& context) { - command_manager.execute(parser[0], context); + context.ui().info_hide(); + if (parser.positional_count() > 0) + { + MenuStyle style = MenuStyle::Prompt; + DisplayCoord pos = context.ui().dimensions(); + pos.column -= 1; + if (parser.has_option("anchor")) + { + style = MenuStyle::Inline; + const auto& sel = context.selections().main(); + auto it = sel.cursor(); + String anchor = parser.option_value("anchor"); + if (anchor == "left") + it = sel.min(); + else if (anchor == "right") + it = sel.max(); + else if (anchor != "cursor") + throw runtime_error("anchor param must be one of [left, right, cursor]"); + pos = context.window().display_position(it); + } + const String& title = parser.has_option("title") ? parser.option_value("title") : ""; + context.ui().info_show(title, parser[0], pos, get_color("Information"), style); + } } - catch (Kakoune::runtime_error& e) +}; + +const CommandDesc try_catch_cmd = { + "try", + nullptr, + "try [catch ]: execute command in current context.\n" + "if an error is raised and is specified, execute it.\n" + "The error is not propagated further.", + ParameterDesc{ SwitchMap{}, ParameterDesc::Flags::None, 1, 3 }, + CommandFlags::None, + CommandCompleter{}, + [](const ParametersParser& parser, Context& context) { - if (do_catch) - command_manager.execute(parser[2], context); + if (parser.positional_count() == 2) + throw wrong_argument_count(); + + const bool do_catch = parser.positional_count() == 3; + if (do_catch and parser[1] != "catch") + throw runtime_error("usage: try [catch ]"); + + CommandManager& command_manager = CommandManager::instance(); + try + { + command_manager.execute(parser[0], context); + } + catch (Kakoune::runtime_error& e) + { + if (do_catch) + command_manager.execute(parser[2], context); + } } -} - -static const ParameterDesc define_color_alias_params{ - SwitchMap{}, ParameterDesc::Flags::None, 2, 2 }; -static const char* define_color_alias_desc = "colalias : set to refer to color (which can be an alias itself)"; - -void define_color_alias(const ParametersParser& parser, Context& context) -{ - ColorRegistry::instance().register_alias(parser[0], parser[1], true); -} - -static const ParameterDesc set_client_name_params{ - SwitchMap{}, ParameterDesc::Flags::None, 1, 1 +const CommandDesc define_color_alias_cmd = { + "colalias", + "ca", + "colalias : set to refer to color (which can be an alias itself)", + ParameterDesc{ SwitchMap{}, ParameterDesc::Flags::None, 2, 2 }, + CommandFlags::None, + CommandCompleter{}, + [](const ParametersParser& parser, Context& context) + { + ColorRegistry::instance().register_alias(parser[0], parser[1], true); + } }; -static const char* set_client_name_desc = "nameclient : set current client name to "; - -void set_client_name(const ParametersParser& parser, Context& context) -{ - if (ClientManager::instance().validate_client_name(parser[0])) - context.set_name(parser[0]); - else if (context.name() != parser[0]) - throw runtime_error("client name '" + parser[0] + "' is not unique"); -} - -static const ParameterDesc set_register_params{ - SwitchMap{}, ParameterDesc::Flags::None, 2, 2 +const CommandDesc set_client_name_cmd = { + "nameclient", + "nc", + "nameclient : set current client name to ", + single_name_param, + CommandFlags::None, + CommandCompleter{}, + [](const ParametersParser& parser, Context& context) + { + if (ClientManager::instance().validate_client_name(parser[0])) + context.set_name(parser[0]); + else if (context.name() != parser[0]) + throw runtime_error("client name '" + parser[0] + "' is not unique"); + } }; -static const char* set_register_desc = "reg : set register to "; - -void set_register(const ParametersParser& parser, Context& context) -{ - if (parser[0].length() != 1) - throw runtime_error("register names are single character"); - RegisterManager::instance()[parser[0][0]] = memoryview(parser[1]); -} - -static const ParameterDesc change_working_directory_params{ - SwitchMap{}, ParameterDesc::Flags::None, 1, 1 +const CommandDesc set_register_cmd = { + "reg", + nullptr, + "reg : set register to ", + ParameterDesc{ SwitchMap{}, ParameterDesc::Flags::None, 2, 2 }, + CommandFlags::None, + CommandCompleter{}, + [](const ParametersParser& parser, Context& context) + { + if (parser[0].length() != 1) + throw runtime_error("register names are single character"); + RegisterManager::instance()[parser[0][0]] = memoryview(parser[1]); + } }; -static const char* change_working_directory_desc = "cd : change server working directory to "; - -void change_working_directory(const ParametersParser& parser, Context&) -{ - if (chdir(parse_filename(parser[0]).c_str()) != 0) - throw runtime_error("cannot change to directory " + parser[0]); -} - -template -CommandCompleter group_rm_completer(GetRootGroup get_root_group) -{ - return [=](const Context& context, CompletionFlags flags, - CommandParameters params, size_t token_to_complete, - ByteCount pos_in_token) -> Completions { - auto& root_group = get_root_group(context); - const String& arg = params[token_to_complete]; - if (token_to_complete == 1 and params[0] == "-group") - return { 0_byte, arg.length(), root_group.complete_group_id(arg, pos_in_token) }; - else if (token_to_complete == 2 and params[0] == "-group") - return { 0_byte, arg.length(), root_group.get_group(params[1], '/').complete_id(arg, pos_in_token) }; - return { 0_byte, arg.length(), root_group.complete_id(arg, pos_in_token) }; - }; -} - -template -CommandCompleter group_add_completer(GetRootGroup get_root_group) -{ - return [=](const Context& context, CompletionFlags flags, - CommandParameters params, size_t token_to_complete, - ByteCount pos_in_token) -> Completions { - auto& root_group = get_root_group(context); - const String& arg = params[token_to_complete]; - if (token_to_complete == 1 and params[0] == "-group") - return { 0_byte, arg.length(), root_group.complete_group_id(arg, pos_in_token) }; - else if (token_to_complete == 0 or (token_to_complete == 2 and params[0] == "-group")) - return { 0_byte, arg.length(), FactoryRegistry::instance().complete_name(arg, pos_in_token) }; - return Completions{}; - }; -} +const CommandDesc change_working_directory_cmd = { + "cd", + nullptr, + "cd : change server working directory to ", + single_name_param, + CommandFlags::None, + filename_completer, + [](const ParametersParser& parser, Context&) + { + if (chdir(parse_filename(parser[0]).c_str()) != 0) + throw runtime_error("cannot change to directory " + parser[0]); + } +}; class RegisterRestorer { @@ -942,106 +1174,52 @@ void exec_keys(const KeyList& keys, Context& context) context.input_handler().handle_key(key); } -CandidateList complete_scope(const String& prefix) +static void register_command(CommandManager& cm, const CommandDesc& c) { - CandidateList res; - for (auto scope : { "global", "buffer", "window" }) - { - if (prefix_match(scope, prefix)) - res.emplace_back(scope); - } - return res; + if (c.alias) + cm.register_commands({ c.name, c.alias }, c.func, c.docstring, c.params, c.flags, c.completer); + else + cm.register_command(c.name, c.func, c.docstring, c.params, c.flags, c.completer); } void register_commands() { CommandManager& cm = CommandManager::instance(); - cm.register_commands({"nop"}, [](const ParametersParser&, Context&){}, "do nothing", {}); + cm.register_command("nop", [](const ParametersParser&, Context&){}, "do nothing", {}); - PerArgumentCommandCompleter filename_completer({ - [](const Context& context, CompletionFlags flags, const String& prefix, ByteCount cursor_pos) - { return Completions{ 0_byte, prefix.length(), - complete_filename(prefix, - context.options()["ignored_files"].get(), - cursor_pos) }; } - }); - cm.register_commands({ "edit", "e" }, edit, edit_desc, edit_params, CommandFlags::None, filename_completer); - cm.register_commands({ "edit!", "e!" }, edit, edit_desc, edit_params, CommandFlags::None, filename_completer); - cm.register_commands({ "write", "w" }, write_buffer, write_desc, write_params, CommandFlags::None, filename_completer); - cm.register_commands({ "writeall", "wa" }, write_all_buffers, write_all_desc, no_params); - cm.register_commands({ "quit", "q" }, quit, quit_desc, no_params); - cm.register_commands({ "quit!", "q!" }, quit, quit_desc, no_params); - cm.register_command("wq", write_and_quit, write_and_quit_desc, no_params); - cm.register_command("wq!", write_and_quit, write_and_quit_desc, no_params); - - PerArgumentCommandCompleter buffer_completer({ - [](const Context& context, CompletionFlags flags, const String& prefix, ByteCount cursor_pos) - { return Completions{ 0_byte, prefix.length(), - BufferManager::instance().complete_buffername(prefix, cursor_pos) }; } - }); - cm.register_commands({ "buffer", "b" }, show_buffer, show_buffer_desc, single_name_params, CommandFlags::None, buffer_completer); - cm.register_commands({ "delbuf", "db" }, delete_buffer, delete_buffer_desc, single_opt_name_params, CommandFlags::None, buffer_completer); - cm.register_commands({ "delbuf!", "db!" }, delete_buffer, delete_buffer_desc, single_opt_name_params, CommandFlags::None, buffer_completer); - cm.register_commands({ "namebuf", "nb" }, set_buffer_name, set_buffer_name_desc, single_name_params); - - auto get_highlighters = [](const Context& c) -> HighlighterGroup& { return c.window().highlighters(); }; - cm.register_commands({ "addhl", "ah" }, add_highlighter, add_highlighter_desc, add_highlighter_params, CommandFlags::None, group_add_completer(get_highlighters)); - cm.register_commands({ "rmhl", "rh" }, rm_highlighter, rm_highlighter_desc, rm_highlighter_params, CommandFlags::None, group_rm_completer(get_highlighters)); - cm.register_commands({ "defhl", "dh" }, define_highlighter, define_highlighter_desc, single_name_params); - - cm.register_command("hook", add_hook, add_hook_desc, add_hook_params, CommandFlags::None, - [](const Context& context, CompletionFlags flags, - CommandParameters params, size_t token_to_complete, ByteCount pos_in_token) - { - if (token_to_complete == 0) - return Completions{ 0_byte, params[0].length(), - complete_scope(params[0].substr(0_byte, pos_in_token)) }; - else if (token_to_complete == 3) - { - auto& cm = CommandManager::instance(); - return cm.complete(context, flags, params[3], pos_in_token); - } - return Completions{}; - }); - cm.register_command("rmhooks", rm_hooks, rm_hooks_desc, rm_hooks_params); - - cm.register_command("source", exec_commands_in_file, exec_commands_in_file_desc, exec_commands_in_file_params, CommandFlags::None, filename_completer); - - cm.register_command("exec", exec_string, exec_string_desc, context_wrap_params); - cm.register_command("eval", eval_string, eval_string_desc, context_wrap_params); - cm.register_command("menu", menu, menu_desc, menu_params); - cm.register_command("info", info, info_desc, info_params); - cm.register_command("try", try_catch, try_catch_desc, try_catch_params); - cm.register_command("reg", set_register, set_register_desc, set_register_params); - - cm.register_command("def", define_command, define_command_desc, define_command_params); - cm.register_command("decl", declare_option, declare_option_desc, declare_option_params); - - cm.register_command("echo", echo_message, echo_message_desc, echo_message_params); - cm.register_command("debug", write_debug_message, write_debug_message_desc, write_debug_message_params); - - cm.register_command("set", set_option, set_option_desc, set_option_params, CommandFlags::None, - [](const Context& context, CompletionFlags, - CommandParameters params, size_t token_to_complete, - ByteCount pos_in_token) -> Completions - { - if (token_to_complete == 0) - return { 0_byte, params[0].length(), - complete_scope(params[0].substr(0_byte, pos_in_token)) }; - else if (token_to_complete == 1) - { - OptionManager& options = get_options(params[0], context); - return { 0_byte, params[1].length(), - options.complete_option_name(params[1], pos_in_token) }; - } - return Completions{}; - } ); - - cm.register_commands({ "colalias", "ca" }, define_color_alias, define_color_alias_desc, define_color_alias_params); - cm.register_commands({ "nameclient", "nc" }, set_client_name, set_client_name_desc, set_client_name_params); - - cm.register_command("cd", change_working_directory, change_working_directory_desc, change_working_directory_params, CommandFlags::None, filename_completer); - cm.register_command("map", map_key, map_key_desc, map_key_params); + register_command(cm, edit_cmd); + register_command(cm, force_edit_cmd); + register_command(cm, write_cmd); + register_command(cm, writeall_cmd); + register_command(cm, quit_cmd); + register_command(cm, force_quit_cmd); + register_command(cm, write_quit_cmd); + register_command(cm, force_write_quit_cmd); + register_command(cm, buffer_cmd); + register_command(cm, delbuf_cmd); + register_command(cm, force_delbuf_cmd); + register_command(cm, namebuf_cmd); + register_command(cm, define_highlighter_cmd); + register_command(cm, add_highlighter_cmd); + register_command(cm, rm_highlighter_cmd); + register_command(cm, add_hook_cmd); + register_command(cm, rm_hook_cmd); + register_command(cm, define_command_cmd); + register_command(cm, echo_cmd); + register_command(cm, debug_cmd); + register_command(cm, source_cmd); + register_command(cm, set_option_cmd); + register_command(cm, declare_option_cmd); + register_command(cm, map_key_cmd); + register_command(cm, exec_string_cmd); + register_command(cm, eval_string_cmd); + register_command(cm, menu_cmd); + register_command(cm, info_cmd); + register_command(cm, try_catch_cmd); + register_command(cm, define_color_alias_cmd); + register_command(cm, set_client_name_cmd); + register_command(cm, set_register_cmd); + register_command(cm, change_working_directory_cmd); } }