From 2c09da50be80dbaf484682689bfd1a685d601573 Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Fri, 25 Oct 2013 00:01:17 +0100 Subject: [PATCH] Add key mapping support --- src/buffer.cc | 3 ++- src/buffer.hh | 6 ++++- src/client.cc | 23 +++++++++++++++++-- src/commands.cc | 37 ++++++++++++++++++++++++++++++ src/context.cc | 9 ++++++++ src/context.hh | 2 ++ src/keymap_manager.cc | 45 +++++++++++++++++++++++++++++++++++++ src/keymap_manager.hh | 52 +++++++++++++++++++++++++++++++++++++++++++ src/main.cc | 2 ++ src/window.cc | 3 ++- src/window.hh | 4 ++++ 11 files changed, 181 insertions(+), 5 deletions(-) create mode 100644 src/keymap_manager.cc create mode 100644 src/keymap_manager.hh diff --git a/src/buffer.cc b/src/buffer.cc index 368d530e..249064bb 100644 --- a/src/buffer.cc +++ b/src/buffer.cc @@ -20,7 +20,8 @@ Buffer::Buffer(String name, Flags flags, std::vector lines, m_timestamp(0), m_fs_timestamp(fs_timestamp), m_hooks(GlobalHooks::instance()), - m_options(GlobalOptions::instance()) + m_options(GlobalOptions::instance()), + m_keymaps(GlobalKeymaps::instance()) { BufferManager::instance().register_buffer(*this); diff --git a/src/buffer.hh b/src/buffer.hh index 4b67ce48..8727d217 100644 --- a/src/buffer.hh +++ b/src/buffer.hh @@ -1,9 +1,10 @@ #ifndef buffer_hh_INCLUDED #define buffer_hh_INCLUDED -#include "hook_manager.hh" #include "line_and_column.hh" +#include "hook_manager.hh" #include "option_manager.hh" +#include "keymap_manager.hh" #include "string.hh" #include "units.hh" @@ -160,6 +161,8 @@ public: const OptionManager& options() const { return m_options; } HookManager& hooks() { return m_hooks; } const HookManager& hooks() const { return m_hooks; } + KeymapManager& keymaps() { return m_keymaps; } + const KeymapManager& keymaps() const { return m_keymaps; } std::unordered_set& change_listeners() const { return m_change_listeners; } @@ -212,6 +215,7 @@ private: OptionManager m_options; HookManager m_hooks; + KeymapManager m_keymaps; friend constexpr Flags operator|(Flags lhs, Flags rhs) { diff --git a/src/client.cc b/src/client.cc index c81eab56..3b9827d6 100644 --- a/src/client.cc +++ b/src/client.cc @@ -33,6 +33,8 @@ public: virtual String description() const = 0; + virtual KeymapMode keymap_mode() const = 0; + using Insertion = Client::Insertion; Insertion& last_insert() { return m_client.m_last_insert; } @@ -91,6 +93,8 @@ public: (m_count != 0 ? " sel; param=" + to_string(m_count) : " sel"); } + KeymapMode keymap_mode() const override { return KeymapMode::Normal; } + private: int m_count = 0; Timer m_idle_timer; @@ -270,6 +274,7 @@ public: return "menu"; } + KeymapMode keymap_mode() const override { return KeymapMode::Menu; } private: MenuCallback m_callback; @@ -472,6 +477,7 @@ public: return "prompt"; } + KeymapMode keymap_mode() const override { return KeymapMode::Prompt; } private: void display() const @@ -513,6 +519,8 @@ public: return "enter key"; } + KeymapMode keymap_mode() const override { return KeymapMode::None; } + private: KeyCallback m_callback; }; @@ -933,6 +941,9 @@ public: { return "insert"; } + + KeymapMode keymap_mode() const override { return KeymapMode::Insert; } + private: enum class Mode { Default, Complete, InsertReg }; Mode m_mode = Mode::Default; @@ -1010,7 +1021,7 @@ void Client::on_next_key(KeyCallback callback) change_input_mode(new InputModes::NextKey(*this, callback)); } -bool is_valid(Key key) +static bool is_valid(Key key) { return key != Key::Invalid and key.key <= 0x10FFFF; } @@ -1031,7 +1042,15 @@ void Client::handle_key(Key key) { const bool was_recording = is_recording(); - m_mode->on_key(key); + auto keymap_mode = m_mode->keymap_mode(); + KeymapManager& keymaps = m_context.keymaps(); + if (keymaps.is_mapped(key, keymap_mode)) + { + for (auto& k : keymaps.get_mapping(key, keymap_mode)) + m_mode->on_key(k); + } + else + m_mode->on_key(key); // do not record the key that made us enter or leave recording mode. if (was_recording and is_recording()) diff --git a/src/commands.cc b/src/commands.cc index eeb33e1a..4b015c16 100644 --- a/src/commands.cc +++ b/src/commands.cc @@ -530,6 +530,42 @@ void declare_option(CommandParameters params, Context& context) opt->set_from_string(params[2]); } + +KeymapManager& get_keymap_manager(const String& scope, Context& context) +{ + if (prefix_match("global", scope)) + return GlobalKeymaps::instance(); + else if (prefix_match("buffer", scope)) + return context.buffer().keymaps(); + else if (prefix_match("window", scope)) + return context.window().keymaps(); + throw runtime_error("error: no such keymap container " + scope); +} + +KeymapMode parse_keymap_mode(const String& str) +{ + if (prefix_match("normal", str)) return KeymapMode::Normal; + if (prefix_match("insert", str)) return KeymapMode::Insert; + if (prefix_match("menu", str)) return KeymapMode::Menu; + if (prefix_match("prompt", str)) return KeymapMode::Prompt; + throw runtime_error("unknown keymap mode '" + str + "'"); +} + +void map_key(CommandParameters params, Context& context) +{ + ParametersParser parser(params, {}, ParametersParser::Flags::None, 4, 4); + + KeymapManager& keymaps = get_keymap_manager(params[0], context); + KeymapMode keymap_mode = parse_keymap_mode(params[1]); + + KeyList key = parse_keys(params[2]); + if (key.size() != 1) + throw runtime_error("only a single key can be mapped"); + + KeyList mapping = parse_keys(params[3]); + keymaps.map_key(key[0], keymap_mode, std::move(mapping)); +} + class DraftUI : public UserInterface { public: @@ -857,5 +893,6 @@ void register_commands() cm.register_commands({"nc", "nameclient"}, set_client_name); cm.register_command("cd", change_working_directory, filename_completer); + cm.register_command("map", map_key); } } diff --git a/src/context.cc b/src/context.cc index 513bd6cb..49a64f7d 100644 --- a/src/context.cc +++ b/src/context.cc @@ -75,6 +75,15 @@ HookManager& Context::hooks() const return GlobalHooks::instance(); } +KeymapManager& Context::keymaps() const +{ + if (has_window()) + return window().keymaps(); + if (has_buffer()) + return buffer().keymaps(); + return GlobalKeymaps::instance(); +} + void Context::print_status(DisplayLine status) const { if (has_client()) diff --git a/src/context.hh b/src/context.hh index 07e4ffa1..d65e9d4e 100644 --- a/src/context.hh +++ b/src/context.hh @@ -12,6 +12,7 @@ class Buffer; class Client; class UserInterface; class DisplayLine; +class KeymapManager; // A Context is used to access non singleton objects for various services // in commands. @@ -48,6 +49,7 @@ struct Context OptionManager& options() const; HookManager& hooks() const; + KeymapManager& keymaps() const; void print_status(DisplayLine status) const; diff --git a/src/keymap_manager.cc b/src/keymap_manager.cc new file mode 100644 index 00000000..71f936fd --- /dev/null +++ b/src/keymap_manager.cc @@ -0,0 +1,45 @@ +#include "keymap_manager.hh" + +namespace std +{ + +template<> struct hash +{ + size_t operator()(Kakoune::KeymapMode val) const + { + return hash{}((int)val); + } +}; + +} + +namespace Kakoune +{ + +void KeymapManager::map_key(Key key, KeymapMode mode, std::vector mapping) +{ + m_mapping[{key, mode}] = mapping; +} + +void KeymapManager::unmap_key(Key key, KeymapMode mode) +{ + m_mapping.erase({key, mode}); +} + + +bool KeymapManager::is_mapped(Key key, KeymapMode mode) const +{ + return m_mapping.find({key, mode}) != m_mapping.end() or + (m_parent and m_parent->is_mapped(key, mode)); +} + +memoryview KeymapManager::get_mapping(Key key, KeymapMode mode) const +{ + auto it = m_mapping.find({key, mode}); + if (it != m_mapping.end()) + return { it->second }; + kak_assert(m_parent); + return m_parent->get_mapping(key, mode); +} + +} diff --git a/src/keymap_manager.hh b/src/keymap_manager.hh new file mode 100644 index 00000000..c9887a6b --- /dev/null +++ b/src/keymap_manager.hh @@ -0,0 +1,52 @@ +#ifndef keymap_manager_hh_INCLUDED +#define keymap_manager_hh_INCLUDED + +#include "idvaluemap.hh" +#include "keys.hh" +#include "utils.hh" + +#include + +namespace Kakoune +{ + +enum class KeymapMode : int +{ + None, + Normal, + Insert, + Prompt, + Menu +}; + +class KeymapManager +{ +public: + KeymapManager(KeymapManager& parent) : m_parent(&parent) {} + + void map_key(Key key, KeymapMode mode, std::vector mapping); + void unmap_key(Key key, KeymapMode mode); + + bool is_mapped(Key key, KeymapMode mode) const; + memoryview get_mapping(Key key, KeymapMode mode) const; +private: + KeymapManager() + : m_parent(nullptr) {} + // the only one allowed to construct a root map manager + friend class GlobalKeymaps; + + KeymapManager* m_parent; + + using Keymap = std::unordered_map, std::vector>; + Keymap m_mapping; +}; + +class GlobalKeymaps : public KeymapManager, + public Singleton +{ +}; + +} + +#endif // keymap_manager_hh_INCLUDED + diff --git a/src/main.cc b/src/main.cc index cd078d6c..40829135 100644 --- a/src/main.cc +++ b/src/main.cc @@ -14,6 +14,7 @@ #include "hook_manager.hh" #include "ncurses.hh" #include "option_manager.hh" +#include "keymap_manager.hh" #include "parameters_parser.hh" #include "register_manager.hh" #include "remote.hh" @@ -264,6 +265,7 @@ int kakoune(memoryview params) EventManager event_manager; GlobalOptions global_options; GlobalHooks global_hooks; + GlobalKeymaps global_keymaps; ShellManager shell_manager; CommandManager command_manager; BufferManager buffer_manager; diff --git a/src/window.cc b/src/window.cc index c7ec5fff..2fdec0f8 100644 --- a/src/window.cc +++ b/src/window.cc @@ -19,7 +19,8 @@ void expand_unprintable(const Window& window, DisplayBuffer& display_buffer); Window::Window(Buffer& buffer) : Editor(buffer), m_hooks(buffer.hooks()), - m_options(buffer.options()) + m_options(buffer.options()), + m_keymaps(buffer.keymaps()) { Context hook_context{*this}; m_hooks.run_hook("WinCreate", buffer.name(), hook_context); diff --git a/src/window.hh b/src/window.hh index 534d18b3..49bbe39e 100644 --- a/src/window.hh +++ b/src/window.hh @@ -8,6 +8,7 @@ #include "highlighter.hh" #include "hook_manager.hh" #include "option_manager.hh" +#include "keymap_manager.hh" namespace Kakoune { @@ -46,6 +47,8 @@ public: const OptionManager& options() const { return m_options; } HookManager& hooks() { return m_hooks; } const HookManager& hooks() const { return m_hooks; } + KeymapManager& keymaps() { return m_keymaps; } + const KeymapManager& keymaps() const { return m_keymaps; } size_t timestamp() const { return m_timestamp; } void forget_timestamp() { m_timestamp = -1; } @@ -65,6 +68,7 @@ private: HookManager m_hooks; OptionManager m_options; + KeymapManager m_keymaps; HighlighterGroup m_highlighters; HighlighterGroup m_builtin_highlighters;