diff --git a/doc/pages/commands.asciidoc b/doc/pages/commands.asciidoc index 46cf5c2b..4c7a71f3 100644 --- a/doc/pages/commands.asciidoc +++ b/doc/pages/commands.asciidoc @@ -129,6 +129,12 @@ command *q!* has to be used). Aliases are mentionned below each commands. *unmap* []:: unbind a key combination (See <>) +*declare-user-mode* :: + declare a new user keymap mode within the context of a scope + +*enter-user-mode* :: + enable keymap mode for next key + == Hooks *hook* [-group ] :: diff --git a/src/commands.cc b/src/commands.cc index 8e58a7e6..974f1eed 100644 --- a/src/commands.cc +++ b/src/commands.cc @@ -15,6 +15,7 @@ #include "highlighter.hh" #include "highlighters.hh" #include "insert_completer.hh" +#include "normal.hh" #include "option_manager.hh" #include "option_types.hh" #include "parameters_parser.hh" @@ -1099,7 +1100,7 @@ const CommandDesc echo_cmd = { } }; -KeymapMode parse_keymap_mode(const String& str) +KeymapMode parse_keymap_mode(const String& str, const KeymapManager::UserModeList& user_modes) { if (prefix_match("normal", str)) return KeymapMode::Normal; if (prefix_match("insert", str)) return KeymapMode::Insert; @@ -1110,7 +1111,12 @@ KeymapMode parse_keymap_mode(const String& str) if (prefix_match("user", str)) return KeymapMode::User; if (prefix_match("object", str)) return KeymapMode::Object; - throw runtime_error(format("unknown keymap mode '{}'", str)); + auto it = find(user_modes, str); + if (it == user_modes.end()) + throw runtime_error(format("unknown keymap mode '{}'", str)); + + char offset = static_cast(KeymapMode::FirstUserMode); + return (KeymapMode)(std::distance(user_modes.begin(), it) + offset); } const CommandDesc debug_cmd = { @@ -1189,7 +1195,7 @@ const CommandDesc debug_cmd = { write_to_debug_buffer("Mappings:"); for (auto& mode : modes) { - KeymapMode m = parse_keymap_mode(mode); + KeymapMode m = parse_keymap_mode(mode, keymaps.user_modes()); for (auto& key : keymaps.get_mapped_keys(m)) write_to_debug_buffer(format(" * {} {}: {}", mode, key_to_str(key), @@ -1427,11 +1433,13 @@ auto map_key_completer = if (token_to_complete == 0) return { 0_byte, params[0].length(), complete(params[0], pos_in_token, scopes) }; + if (token_to_complete == 1) { - constexpr const char* modes[] = { "normal", "insert", "menu", "prompt", "goto", "view", "user", "object" }; + static constexpr auto modes = { "normal", "insert", "menu", "prompt", "goto", "view", "user", "object" }; + auto& user_modes = get_scope(params[0], context).keymaps().user_modes(); return { 0_byte, params[1].length(), - complete(params[1], pos_in_token, modes) }; + complete(params[1], pos_in_token, concatenated(modes, user_modes) | gather>()) }; } return {}; }; @@ -1459,7 +1467,7 @@ const CommandDesc map_key_cmd = { [](const ParametersParser& parser, Context& context, const ShellContext&) { KeymapManager& keymaps = get_scope(parser[0], context).keymaps(); - KeymapMode keymap_mode = parse_keymap_mode(parser[1]); + KeymapMode keymap_mode = parse_keymap_mode(parser[1], keymaps.user_modes()); KeyList key = parse_keys(parser[2]); if (key.size() != 1) @@ -1492,7 +1500,7 @@ const CommandDesc unmap_key_cmd = { [](const ParametersParser& parser, Context& context, const ShellContext&) { KeymapManager& keymaps = get_scope(parser[0], context).keymaps(); - KeymapMode keymap_mode = parse_keymap_mode(parser[1]); + KeymapMode keymap_mode = parse_keymap_mode(parser[1], keymaps.user_modes()); KeyList key = parse_keys(parser[2]); if (key.size() != 1) @@ -2113,6 +2121,64 @@ const CommandDesc fail_cmd = { } }; +const CommandDesc declare_user_mode_cmd = { + "declare-user-mode", + nullptr, + "declare-user-mode : add a new user keymap mode in given ", + ParameterDesc{ {}, ParameterDesc::Flags::None, 2, 2 }, + CommandFlags::None, + CommandHelper{}, + make_completer(complete_scope), + [](const ParametersParser& parser, Context& context, const ShellContext&) + { + KeymapManager& keymaps = get_scope(parser[0], context).keymaps(); + keymaps.add_user_mode(std::move(parser[1])); + } +}; + +const CommandDesc enter_user_mode_cmd = { + "enter-user-mode", + nullptr, + "enter-user-mode : enable keymap mode for next key", + ParameterDesc{ {}, ParameterDesc::Flags::None, 2, 2 }, + CommandFlags::None, + CommandHelper{}, + [](const Context& context, CompletionFlags flags, + CommandParameters params, size_t token_to_complete, + ByteCount pos_in_token) -> Completions + { + if (token_to_complete == 0) + return { 0_byte, params[0].length(), + complete(params[0], pos_in_token, scopes) }; + if (token_to_complete == 1) + { + KeymapManager& keymaps = get_scope(params[0], context).keymaps(); + return { 0_byte, params[1].length(), + complete(params[1], pos_in_token, keymaps.user_modes()) }; + } + return {}; + }, + [](const ParametersParser& parser, Context& context, const ShellContext&) + { + KeymapManager& keymaps = get_scope(parser[0], context).keymaps(); + KeymapMode mode = parse_keymap_mode(parser[1], keymaps.user_modes()); + on_next_key_with_autoinfo(context, mode, + [mode](Key key, Context& context) mutable { + if (not context.keymaps().is_mapped(key, mode)) + return; + + auto& mapping = context.keymaps().get_mapping(key, mode); + ScopedSetBool disable_keymaps(context.keymaps_disabled()); + + InputHandler::ScopedForceNormal force_normal{context.input_handler(), {}}; + + ScopedEdition edition(context); + for (auto& key : mapping.keys) + context.input_handler().handle_key(key); + }, parser[1], build_autoinfo_for_mapping(context, mode, {})); + } +}; + } void register_commands() @@ -2175,6 +2241,8 @@ void register_commands() register_command(change_directory_cmd); register_command(rename_session_cmd); register_command(fail_cmd); + register_command(declare_user_mode_cmd); + register_command(enter_user_mode_cmd); } } diff --git a/src/keymap_manager.cc b/src/keymap_manager.cc index 859f420b..8c359082 100644 --- a/src/keymap_manager.cc +++ b/src/keymap_manager.cc @@ -2,6 +2,8 @@ #include "array_view.hh" #include "assert.hh" +#include "exception.hh" +#include "string_utils.hh" #include @@ -49,4 +51,19 @@ KeymapManager::KeyList KeymapManager::get_mapped_keys(KeymapMode mode) const return res; } +void KeymapManager::add_user_mode(const String user_mode_name) +{ + auto modes = {"normal", "insert", "prompt", "menu", "goto", "view", "user", "object"}; + if (contains(modes, user_mode_name)) + throw runtime_error(format("'{}' is already a regular mode", user_mode_name)); + + if (contains(m_user_modes, user_mode_name)) + throw runtime_error(format("user mode '{}' already defined", user_mode_name)); + + if (contains_that(user_mode_name, [](char c){ return not isalnum(c); })) + throw runtime_error(format("invalid mode name: '{}'", user_mode_name)); + + m_user_modes.push_back(user_mode_name); +} + } diff --git a/src/keymap_manager.hh b/src/keymap_manager.hh index 746c02c5..3c8d52ee 100644 --- a/src/keymap_manager.hh +++ b/src/keymap_manager.hh @@ -22,6 +22,7 @@ enum class KeymapMode : char View, User, Object, + FirstUserMode, }; class KeymapManager @@ -43,6 +44,10 @@ public: }; const KeymapInfo& get_mapping(Key key, KeymapMode mode) const; + using UserModeList = Vector; + const UserModeList& user_modes() const { return m_user_modes; } + void add_user_mode(const String user_mode_name); + private: KeymapManager() : m_parent(nullptr) {} @@ -52,6 +57,8 @@ private: KeymapManager* m_parent; using KeyAndMode = std::pair; HashMap m_mapping; + + UserModeList m_user_modes; }; } diff --git a/src/normal.cc b/src/normal.cc index 1b83ec8b..16270c18 100644 --- a/src/normal.cc +++ b/src/normal.cc @@ -127,12 +127,6 @@ void repeat_last_select(Context& context, NormalParams) context.repeat_last_select(); } -struct KeyInfo -{ - ConstArrayView keys; - StringView docstring; -}; - String build_autoinfo_for_mapping(Context& context, KeymapMode mode, ConstArrayView built_ins) { diff --git a/src/normal.hh b/src/normal.hh index 9802f642..9b6693cc 100644 --- a/src/normal.hh +++ b/src/normal.hh @@ -1,8 +1,10 @@ #ifndef normal_hh_INCLUDED #define normal_hh_INCLUDED +#include "context.hh" #include "optional.hh" #include "keys.hh" +#include "keymap_manager.hh" #include "string.hh" namespace Kakoune @@ -24,6 +26,15 @@ struct NormalCmd Optional get_normal_command(Key key); +struct KeyInfo +{ + ConstArrayView keys; + StringView docstring; +}; + +String build_autoinfo_for_mapping(Context& context, KeymapMode mode, + ConstArrayView built_ins); + } #endif // normal_hh_INCLUDED diff --git a/test/normal/user-modes/cmd b/test/normal/user-modes/cmd new file mode 100644 index 00000000..455c6ded --- /dev/null +++ b/test/normal/user-modes/cmd @@ -0,0 +1,4 @@ +:declare-user-mode global foo +:map global foo f 'wchello from foo' +:enter-user-mode global foo +f diff --git a/test/normal/user-modes/in b/test/normal/user-modes/in new file mode 100644 index 00000000..5716ca59 --- /dev/null +++ b/test/normal/user-modes/in @@ -0,0 +1 @@ +bar diff --git a/test/normal/user-modes/out b/test/normal/user-modes/out new file mode 100644 index 00000000..196f02f0 --- /dev/null +++ b/test/normal/user-modes/out @@ -0,0 +1 @@ +hello from foo