diff --git a/doc/pages/keys.asciidoc b/doc/pages/keys.asciidoc index 6bcb3449d..a4ffcd735 100644 --- a/doc/pages/keys.asciidoc +++ b/doc/pages/keys.asciidoc @@ -843,7 +843,9 @@ The following keys are recognized by this mode to help with editing These keys are used to cancel long-running operations, either inside Kakoune or outside it. Because they are intended as a safety mechanism when something goes wrong, these keys are handled very early on in -Kakoune's input processing, and therefore cannot be remapped in any mode. +Kakoune's input processing, so they can only be remapped by changing +the `interrupt_key` and `cancel_key` options (see +<>). **:: Stop any external processes. If you ever see Kakoune display a message diff --git a/doc/pages/options.asciidoc b/doc/pages/options.asciidoc index 4f69cbfb7..54ec95e52 100644 --- a/doc/pages/options.asciidoc +++ b/doc/pages/options.asciidoc @@ -79,6 +79,10 @@ are exclusively available to built-in options. as a string but the set commands will complain if the entered text is not a valid regex +*key*:: + a single keypress using the same syntax as `map` (see + <>) + *coord*:: a line, column pair (separated by a comma) Cannot be used with `declare-option` @@ -312,6 +316,15 @@ are exclusively available to built-in options. *debug* `flags(hooks|shell|profile|keys|commands)`:: dump various debug information in the '\*debug*' buffer +*interrupt_key* `key`:: + _default_ + + key used to interrupt any running external processes + +*cancel_key* `key`:: + _default_ + + key used to cancel long-running Kakoune operations and clear the input + buffer + *idle_timeout* `int`:: _default_ 50 + timeout, in milliseconds, with no user input that will trigger the diff --git a/src/client.cc b/src/client.cc index 6e5d082ac..679c252e3 100644 --- a/src/client.cc +++ b/src/client.cc @@ -47,13 +47,14 @@ Client::Client(std::unique_ptr&& ui, m_ui->set_ui_options(m_window->options()["ui_options"].get()); m_ui->set_on_key([this](Key key) { kak_assert(key != Key::Invalid); - if (key == ctrl('c')) + auto& opts = context().options(); + if (key == opts["interrupt_key"].get()) { auto prev_handler = set_signal_handler(SIGINT, SIG_IGN); killpg(getpgrp(), SIGINT); set_signal_handler(SIGINT, prev_handler); } - else if (key == ctrl('g')) + else if (key == opts["cancel_key"].get()) { m_pending_keys.clear(); print_status({"operation cancelled", context().faces()["Error"]}); diff --git a/src/commands.cc b/src/commands.cc index 0febf160a..2427f7bb8 100644 --- a/src/commands.cc +++ b/src/commands.cc @@ -1869,6 +1869,7 @@ const CommandDesc declare_option_cmd = { " bool: boolean (true/false or yes/no)\n" " str: character string\n" " regex: regular expression\n" + " key: keystroke specifier\n" " int-list: list of integers\n" " str-list: list of character strings\n" " completions: list of completion candidates\n" @@ -1885,7 +1886,7 @@ const CommandDesc declare_option_cmd = { make_completer( [](const Context& context, CompletionFlags flags, StringView prefix, ByteCount cursor_pos) -> Completions { - auto c = {"int", "bool", "str", "regex", "int-list", "str-list", "completions", "line-specs", "range-specs", "str-to-str-map"}; + auto c = {"int", "bool", "str", "regex", "key", "int-list", "str-list", "completions", "line-specs", "range-specs", "str-to-str-map"}; return { 0_byte, cursor_pos, complete(prefix, cursor_pos, c), Completions::Flags::Menu }; }), [](const ParametersParser& parser, Context& context, const ShellContext&) @@ -1908,6 +1909,8 @@ const CommandDesc declare_option_cmd = { opt = ®.declare_option(parser[1], docstring, "", flags); else if (parser[0] == "regex") opt = ®.declare_option(parser[1], docstring, Regex{}, flags); + else if (parser[0] == "key") + opt = ®.declare_option(parser[1], docstring, Key(Key::Invalid), flags); else if (parser[0] == "int-list") opt = ®.declare_option>(parser[1], docstring, {}, flags); else if (parser[0] == "str-list") diff --git a/src/keys.cc b/src/keys.cc index 5ba441817..767beb6fa 100644 --- a/src/keys.cc +++ b/src/keys.cc @@ -223,6 +223,20 @@ String to_string(Key key) return res; } +String option_to_string(const Key& key) +{ + return to_string(key); +} + +Key option_from_string(Meta::Type, StringView str) +{ + auto keys = parse_keys(str); + if (keys.size() != 1) + throw runtime_error(format("'{}' is not a single key", str)); + + return keys.front(); +} + UnitTest test_keys{[]() { KeyList keys{ diff --git a/src/keys.hh b/src/keys.hh index ccafe336d..e95c858d7 100644 --- a/src/keys.hh +++ b/src/keys.hh @@ -94,6 +94,8 @@ struct Key static Modifiers to_modifier(MouseButton button) { return Key::Modifiers{((int)button << 6) & (int)Modifiers::MouseButtonMask}; } Optional codepoint() const; + + static constexpr const char* option_type_name = "key"; }; constexpr bool with_bit_ops(Meta::Type) { return true; } @@ -107,6 +109,8 @@ KeyList parse_keys(StringView str); String to_string(Key key); StringView to_string(Key::MouseButton button); Key::MouseButton str_to_button(StringView str); +String option_to_string(const Key& key); +Key option_from_string(Meta::Type, StringView str); constexpr Key shift(Key key) { diff --git a/src/main.cc b/src/main.cc index 0c63403bd..e506f578a 100644 --- a/src/main.cc +++ b/src/main.cc @@ -616,6 +616,8 @@ void register_options() "set of pair of characters to be considered as matching pairs", { '(', ')', '{', '}', '[', ']', '<', '>' }); reg.declare_option("startup_info_version", "version up to which startup info changes should be hidden", 0); + reg.declare_option("cancel_key", "key used to cancel long-running operations", ctrl('g')); + reg.declare_option("interrupt_key", "key used to stop external processes", ctrl('c')); } static Client* local_client = nullptr;