diff --git a/src/commands.cc b/src/commands.cc index 41ceefe9..33c6bdd7 100644 --- a/src/commands.cc +++ b/src/commands.cc @@ -1404,7 +1404,10 @@ const CommandDesc map_key_cmd = { " view\n" " user\n" " object\n", - ParameterDesc{{}, ParameterDesc::Flags::None, 4, 4}, + ParameterDesc{ + { { "docstring", { true, "specify mapping description" } } }, + ParameterDesc::Flags::None, 4, 4 + }, CommandFlags::None, CommandHelper{}, map_key_completer, @@ -1418,7 +1421,8 @@ const CommandDesc map_key_cmd = { 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)); + keymaps.map_key(key[0], keymap_mode, std::move(mapping), + parser.get_switch("docstring").value_or("").str()); } }; @@ -1451,8 +1455,8 @@ const CommandDesc unmap_key_cmd = { if (keymaps.is_mapped(key[0], keymap_mode) and (parser.positional_count() < 4 or - (keymaps.get_mapping(key[0], keymap_mode) == - ConstArrayView{parse_keys(parser[3])}))) + (keymaps.get_mapping(key[0], keymap_mode).keys == + parse_keys(parser[3])))) keymaps.unmap_key(key[0], keymap_mode); } }; diff --git a/src/input_handler.cc b/src/input_handler.cc index 3c72e66d..30851fdb 100644 --- a/src/input_handler.cc +++ b/src/input_handler.cc @@ -1453,7 +1453,7 @@ void InputHandler::handle_key(Key key) not m_context.keymaps_disabled()) { ScopedSetBool disable_history{context().history_disabled()}; - for (auto& k : keymaps.get_mapping(key, keymap_mode)) + for (auto& k : keymaps.get_mapping(key, keymap_mode).keys) current_mode().handle_key(k); } else diff --git a/src/keymap_manager.cc b/src/keymap_manager.cc index 42308538..ba6d16ed 100644 --- a/src/keymap_manager.cc +++ b/src/keymap_manager.cc @@ -3,12 +3,15 @@ #include "array_view.hh" #include "assert.hh" +#include + namespace Kakoune { -void KeymapManager::map_key(Key key, KeymapMode mode, KeyList mapping) +void KeymapManager::map_key(Key key, KeymapMode mode, + KeyList mapping, String docstring) { - m_mapping[{key, mode}] = std::move(mapping); + m_mapping[{key, mode}] = {std::move(mapping), std::move(docstring)}; } void KeymapManager::unmap_key(Key key, KeymapMode mode) @@ -23,13 +26,29 @@ bool KeymapManager::is_mapped(Key key, KeymapMode mode) const (m_parent and m_parent->is_mapped(key, mode)); } -ConstArrayView KeymapManager::get_mapping(Key key, KeymapMode mode) const +const KeymapManager::KeyMapInfo& +KeymapManager::get_mapping(Key key, KeymapMode mode) const { auto it = m_mapping.find({key, mode}); if (it != m_mapping.end()) - return { it->second }; + return it->second; kak_assert(m_parent); return m_parent->get_mapping(key, mode); } +KeymapManager::KeyList KeymapManager::get_mapped_keys(KeymapMode mode) const +{ + KeyList res; + if (m_parent) + res = m_parent->get_mapped_keys(mode); + for (auto& map : m_mapping) + { + if (map.first.second == mode) + res.emplace_back(map.first.first); + } + std::sort(res.begin(), res.end()); + res.erase(std::unique(res.begin(), res.end()), res.end()); + return res; +} + } diff --git a/src/keymap_manager.hh b/src/keymap_manager.hh index 3eeb0ebb..f2782ecb 100644 --- a/src/keymap_manager.hh +++ b/src/keymap_manager.hh @@ -4,6 +4,7 @@ #include "array_view.hh" #include "keys.hh" #include "hash.hh" +#include "string.hh" #include "unordered_map.hh" #include "vector.hh" @@ -29,11 +30,19 @@ public: KeymapManager(KeymapManager& parent) : m_parent(&parent) {} using KeyList = Vector; - void map_key(Key key, KeymapMode mode, KeyList mapping); + void map_key(Key key, KeymapMode mode, KeyList mapping, String docstring); void unmap_key(Key key, KeymapMode mode); bool is_mapped(Key key, KeymapMode mode) const; - ConstArrayView get_mapping(Key key, KeymapMode mode) const; + KeyList get_mapped_keys(KeymapMode mode) const; + + struct KeyMapInfo + { + KeyList keys; + String docstring; + }; + const KeyMapInfo& get_mapping(Key key, KeymapMode mode) const; + private: KeymapManager() : m_parent(nullptr) {} @@ -41,10 +50,8 @@ private: friend class Scope; KeymapManager* m_parent; - using KeyAndMode = std::pair; - using Keymap = UnorderedMap; - Keymap m_mapping; + UnorderedMap m_mapping; }; } diff --git a/src/normal.cc b/src/normal.cc index 3f816b02..9eabc20c 100644 --- a/src/normal.cc +++ b/src/normal.cc @@ -110,6 +110,49 @@ 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) +{ + auto& keymaps = context.keymaps(); + + Vector> descs; + for (auto& built_in : built_ins) + { + String keys = join(built_in.keys | + filter([&](Key k){ return not keymaps.is_mapped(k, mode); }) | + transform([](Key k) { return key_to_str(k); }), + ',', false); + if (not keys.empty()) + descs.emplace_back(std::move(keys), built_in.docstring); + } + + for (auto& key : keymaps.get_mapped_keys(mode)) + descs.emplace_back(key_to_str(key), + keymaps.get_mapping(key, mode).docstring); + + auto max_len = 0_col; + for (auto& desc : descs) + { + auto len = desc.first.column_length(); + if (len > max_len) + max_len = len; + } + + String res; + for (auto& desc : descs) + res += format("{}:{}{}\n", + desc.first, + String{' ', max_len - desc.first.column_length() + 1}, + desc.second); + return res; +} + template void goto_commands(Context& context, NormalParams params) { @@ -233,18 +276,19 @@ void goto_commands(Context& context, NormalParams params) } } }, "goto", - "g,k: buffer top \n" - "l: line end \n" - "h: line begin \n" - "i: line non blank start\n" - "j: buffer bottom \n" - "e: buffer end \n" - "t: window top \n" - "b: window bottom \n" - "c: window center \n" - "a: last buffer \n" - "f: file \n" - ".: last buffer change \n"); + build_autoinfo_for_mapping(context, KeymapMode::Goto, + {{{'g','k'},"buffer top"}, + {{'l'}, "line end"}, + {{'h'}, "line begin"}, + {{'i'}, "line non blank start"}, + {{'j'}, "buffer bottom"}, + {{'e'}, "buffer end"}, + {{'t'}, "window top"}, + {{'b'}, "window bottom"}, + {{'c'}, "window center"}, + {{'a'}, "last buffer"}, + {{'f'}, "file"}, + {{'.'}, "last buffer change"}})); } } @@ -296,14 +340,15 @@ void view_commands(Context& context, NormalParams params) break; } }, "view", - "v,c: center cursor (vertically)\n" - "m: center cursor (horizontally)\n" - "t: cursor on top \n" - "b: cursor on bottom\n" - "h: scroll left \n" - "j: scroll down \n" - "k: scroll up \n" - "l: scroll right \n"); + build_autoinfo_for_mapping(context, KeymapMode::View, + {{{'v','c'}, "center cursor (vertically)"}, + {{'m'}, "center cursor (horizontally)"}, + {{'t'}, "cursor on top"}, + {{'b'}, "cursor on bottom"}, + {{'h'}, "scroll left"}, + {{'j'}, "scroll down"}, + {{'k'}, "scroll up"}, + {{'l'}, "scroll right"}})); } void replace_with_char(Context& context, NormalParams) @@ -1085,22 +1130,23 @@ void select_object(Context& context, NormalParams params) utf8cp, utf8cp, count, flags)); } }, get_title(), - "b,(,): parenthesis block\n" - "B,{,}: braces block \n" - "r,[,]: brackets block \n" - "a,<,>: angle block \n" - "\",Q: double quote string\n" - "',q: single quote string\n" - "`,g: grave quote string \n" - "w: word \n" - "W: WORD \n" - "s: sentence \n" - "p: paragraph \n" - "␣: whitespaces \n" - "i: indent \n" - "u: argument \n" - "n: number \n" - ":: custom object desc \n"); + build_autoinfo_for_mapping(context, KeymapMode::Object, + {{{'b','(',')'}, "parenthesis block"}, + {{'B','{','}'}, "braces block"}, + {{'r','[',']'}, "brackets block"}, + {{'a','<','>'}, "angle block"}, + {{'"','Q'}, "double quote string"}, + {{'\'','q'}, "single quote string"}, + {{'`','g'}, "grave quote string"}, + {{'w'}, "word"}, + {{'W'}, "WORD"}, + {{'s'}, "sentence"}, + {{'p'}, "paragraph"}, + {{' '}, "whitespaces"}, + {{'i'}, "indent"}, + {{'u'}, "argument"}, + {{'n'}, "number"}, + {{':'}, "custom object desc"}})); } template @@ -1588,15 +1634,16 @@ void exec_user_mappings(Context& context, NormalParams params) if (not context.keymaps().is_mapped(key, KeymapMode::User)) return; - auto mapping = context.keymaps().get_mapping(key, KeymapMode::User); + auto& mapping = context.keymaps().get_mapping(key, KeymapMode::User); ScopedSetBool disable_keymaps(context.keymaps_disabled()); InputHandler::ScopedForceNormal force_normal{context.input_handler(), params}; ScopedEdition edition(context); - for (auto& key : mapping) + for (auto& key : mapping.keys) context.input_handler().handle_key(key); - }, "user mapping", "enter user key"); + }, "user mapping", + build_autoinfo_for_mapping(context, KeymapMode::User, {})); } template