1pub use crate::commands::MappableCommand;
2use crate::config::Config;
3use helix_core::hashmap;
4use helix_view::{document::Mode, info::Info, input::KeyEvent};
8 collections::{BTreeSet, HashMap},
9 ops::{Deref, DerefMut},
15 ::helix_view::input::KeyEvent {
16 code: ::helix_view::keyboard::KeyCode::$key,
17 modifiers: ::helix_view::keyboard::KeyModifiers::NONE,
21 ::helix_view::input::KeyEvent {
22 code: ::helix_view::keyboard::KeyCode::Char($($ch)*),
23 modifiers: ::helix_view::keyboard::KeyModifiers::NONE,
31 ::helix_view::input::KeyEvent {
32 code: ::helix_view::keyboard::KeyCode::$key,
33 modifiers: ::helix_view::keyboard::KeyModifiers::SHIFT,
37 ::helix_view::input::KeyEvent {
38 code: ::helix_view::keyboard::KeyCode::Char($($ch)*),
39 modifiers: ::helix_view::keyboard::KeyModifiers::SHIFT,
47 ::helix_view::input::KeyEvent {
48 code: ::helix_view::keyboard::KeyCode::$key,
49 modifiers: ::helix_view::keyboard::KeyModifiers::CONTROL,
53 ::helix_view::input::KeyEvent {
54 code: ::helix_view::keyboard::KeyCode::Char($($ch)*),
55 modifiers: ::helix_view::keyboard::KeyModifiers::CONTROL,
63 ::helix_view::input::KeyEvent {
64 code: ::helix_view::keyboard::KeyCode::$key,
65 modifiers: ::helix_view::keyboard::KeyModifiers::ALT,
69 ::helix_view::input::KeyEvent {
70 code: ::helix_view::keyboard::KeyCode::Char($($ch)*),
71 modifiers: ::helix_view::keyboard::KeyModifiers::ALT,
76/// Macro for defining the root of a `Keymap` object. Example:
79/// # use helix_core::hashmap;
80/// # use helix_term::keymap;
81/// # use helix_term::keymap::Keymap;
82/// let normal_mode = keymap!({ "Normal mode"
83/// "i" => insert_mode,
85/// "g" => goto_file_start,
86/// "e" => goto_file_end,
88/// "j" | "down" => move_line_down,
90/// let keymap = Keymap::new(normal_mode);
94 (@trie $cmd:ident) => {
95 $crate::keymap::KeyTrie::Leaf($crate::commands::MappableCommand::$cmd)
99 { $label:literal $(sticky=$sticky:literal)? $($($key:literal)|+ => $value:tt,)+ }
101 keymap!({ $label $(sticky=$sticky)? $($($key)|+ => $value,)+ })
104 (@trie [$($cmd:ident),* $(,)?]) => {
105 $crate::keymap::KeyTrie::Sequence(vec![$($crate::commands::Command::$cmd),*])
109 { $label:literal $(sticky=$sticky:literal)? $($($key:literal)|+ => $value:tt,)+ }
111 // modified from the hashmap! macro
113 let _cap = hashmap!(@count $($($key),+),*);
114 let mut _map = ::std::collections::HashMap::with_capacity(_cap);
115 let mut _order = ::std::vec::Vec::with_capacity(_cap);
118 let _key = $key.parse::<::helix_view::input::KeyEvent>().unwrap();
119 let _duplicate = _map.insert(
121 keymap!(@trie $value)
123 assert!(_duplicate.is_none(), "Duplicate key found: {:?}", _duplicate.unwrap());
127 let mut _node = $crate::keymap::KeyTrieNode::new($label, _map, _order);
128 $( _node.is_sticky = $sticky; )?
129 $crate::keymap::KeyTrie::Node(_node)
134#[derive(Debug, Clone)]
135pub struct KeyTrieNode {
136 /// A label for keys coming under this node, like "Goto mode"
138 map: HashMap<KeyEvent, KeyTrie>,
139 order: Vec<KeyEvent>,
143impl<'de> Deserialize<'de> for KeyTrieNode {
144 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
146 D: serde::Deserializer<'de>,
148 let map = HashMap::<KeyEvent, KeyTrie>::deserialize(deserializer)?;
149 let order = map.keys().copied().collect::<Vec<_>>(); // NOTE: map.keys() has arbitrary order
159 pub fn new(name: &str, map: HashMap<KeyEvent, KeyTrie>, order: Vec<KeyEvent>) -> Self {
161 name: name.to_string(),
168 pub fn name(&self) -> &str {
172 /// Merge another Node in. Leaves and subnodes from the other node replace
173 /// corresponding keyevent in self, except when both other and self have
174 /// subnodes for same key. In that case the merge is recursive.
175 pub fn merge(&mut self, mut other: Self) {
176 for (key, trie) in std::mem::take(&mut other.map) {
177 if let Some(KeyTrie::Node(node)) = self.map.get_mut(&key) {
178 if let KeyTrie::Node(other_node) = trie {
179 node.merge(other_node);
183 self.map.insert(key, trie);
185 for &key in self.map.keys() {
186 if !self.order.contains(&key) {
187 self.order.push(key);
192 pub fn infobox(&self) -> Info {
193 let mut body: Vec<(&str, BTreeSet<KeyEvent>)> = Vec::with_capacity(self.len());
194 for (&key, trie) in self.iter() {
195 let desc = match trie {
196 KeyTrie::Leaf(cmd) => {
197 if cmd.name() == "no_op" {
202 KeyTrie::Node(n) => n.name(),
203 KeyTrie::Sequence(_) => "[Multiple commands]",
205 match body.iter().position(|(d, _)| d == &desc) {
207 body[pos].1.insert(key);
209 None => body.push((desc, BTreeSet::from([key]))),
212 body.sort_unstable_by_key(|(_, keys)| {
215 .position(|&k| k == *keys.iter().next().unwrap())
218 let prefix = format!("{} ", self.name());
219 if body.iter().all(|(desc, _)| desc.starts_with(&prefix)) {
222 .map(|(desc, keys)| (desc.strip_prefix(&prefix).unwrap(), keys))
225 Info::new(self.name(), body)
228 /// Get a reference to the key trie node's order.
229 pub fn order(&self) -> &[KeyEvent] {
230 self.order.as_slice()
234impl Default for KeyTrieNode {
235 fn default() -> Self {
236 Self::new("", HashMap::new(), Vec::new())
240impl PartialEq for KeyTrieNode {
241 fn eq(&self, other: &Self) -> bool {
242 self.map == other.map
246impl Deref for KeyTrieNode {
247 type Target = HashMap<KeyEvent, KeyTrie>;
249 fn deref(&self) -> &Self::Target {
254impl DerefMut for KeyTrieNode {
255 fn deref_mut(&mut self) -> &mut Self::Target {
260#[derive(Debug, Clone, PartialEq, Deserialize)]
263 Leaf(MappableCommand),
264 Sequence(Vec<MappableCommand>),
269 pub fn node(&self) -> Option<&KeyTrieNode> {
271 KeyTrie::Node(ref node) => Some(node),
272 KeyTrie::Leaf(_) | KeyTrie::Sequence(_) => None,
276 pub fn node_mut(&mut self) -> Option<&mut KeyTrieNode> {
278 KeyTrie::Node(ref mut node) => Some(node),
279 KeyTrie::Leaf(_) | KeyTrie::Sequence(_) => None,
283 /// Merge another KeyTrie in, assuming that this KeyTrie and the other
284 /// are both Nodes. Panics otherwise.
285 pub fn merge_nodes(&mut self, mut other: Self) {
286 let node = std::mem::take(other.node_mut().unwrap());
287 self.node_mut().unwrap().merge(node);
290 pub fn search(&self, keys: &[KeyEvent]) -> Option<&KeyTrie> {
294 KeyTrie::Node(map) => map.get(key),
295 // leaf encountered while keys left to process
296 KeyTrie::Leaf(_) | KeyTrie::Sequence(_) => None,
303#[derive(Debug, Clone, PartialEq)]
304pub enum KeymapResultKind {
305 /// Needs more keys to execute a command. Contains valid keys for next keystroke.
306 Pending(KeyTrieNode),
307 Matched(MappableCommand),
308 /// Matched a sequence of commands to execute.
309 MatchedSequence(Vec<MappableCommand>),
310 /// Key was not found in the root keymap
312 /// Key is invalid in combination with previous keys. Contains keys leading upto
313 /// and including current (invalid) key.
314 Cancelled(Vec<KeyEvent>),
317/// Returned after looking up a key in [`Keymap`]. The `sticky` field has a
318/// reference to the sticky node if one is currently active.
320pub struct KeymapResult<'a> {
321 pub kind: KeymapResultKind,
322 pub sticky: Option<&'a KeyTrieNode>,
325impl<'a> KeymapResult<'a> {
326 pub fn new(kind: KeymapResultKind, sticky: Option<&'a KeyTrieNode>) -> Self {
327 Self { kind, sticky }
331#[derive(Debug, Clone, PartialEq, Deserialize)]
336 /// Stores pending keys waiting for the next key. This is relative to a
337 /// sticky node if one is in use.
339 state: Vec<KeyEvent>,
340 /// Stores the sticky node if one is activated.
342 sticky: Option<KeyTrieNode>,
346 pub fn new(root: KeyTrie) -> Self {
354 pub fn root(&self) -> &KeyTrie {
358 pub fn sticky(&self) -> Option<&KeyTrieNode> {
362 /// Returns list of keys waiting to be disambiguated.
363 pub fn pending(&self) -> &[KeyEvent] {
367 /// Lookup `key` in the keymap to try and find a command to execute. Escape
368 /// key cancels pending keystrokes. If there are no pending keystrokes but a
369 /// sticky node is in use, it will be cleared.
370 pub fn get(&mut self, key: KeyEvent) -> KeymapResult {
371 if key!(Esc) == key {
372 if !self.state.is_empty() {
373 return KeymapResult::new(
374 // Note that Esc is not included here
375 KeymapResultKind::Cancelled(self.state.drain(..).collect()),
382 let first = self.state.get(0).unwrap_or(&key);
383 let trie_node = match self.sticky {
384 Some(ref trie) => Cow::Owned(KeyTrie::Node(trie.clone())),
385 None => Cow::Borrowed(&self.root),
388 let trie = match trie_node.search(&[*first]) {
389 Some(KeyTrie::Leaf(ref cmd)) => {
390 return KeymapResult::new(KeymapResultKind::Matched(cmd.clone()), self.sticky())
392 Some(KeyTrie::Sequence(ref cmds)) => {
393 return KeymapResult::new(
394 KeymapResultKind::MatchedSequence(cmds.clone()),
398 None => return KeymapResult::new(KeymapResultKind::NotFound, self.sticky()),
402 self.state.push(key);
403 match trie.search(&self.state[1..]) {
404 Some(&KeyTrie::Node(ref map)) => {
407 self.sticky = Some(map.clone());
409 KeymapResult::new(KeymapResultKind::Pending(map.clone()), self.sticky())
411 Some(&KeyTrie::Leaf(ref cmd)) => {
413 return KeymapResult::new(KeymapResultKind::Matched(cmd.clone()), self.sticky());
415 Some(&KeyTrie::Sequence(ref cmds)) => {
418 KeymapResultKind::MatchedSequence(cmds.clone()),
422 None => KeymapResult::new(
423 KeymapResultKind::Cancelled(self.state.drain(..).collect()),
429 pub fn merge(&mut self, other: Self) {
430 self.root.merge_nodes(other.root);
434impl Deref for Keymap {
435 type Target = KeyTrieNode;
437 fn deref(&self) -> &Self::Target {
438 self.root.node().unwrap()
442impl Default for Keymap {
443 fn default() -> Self {
444 Self::new(KeyTrie::Node(KeyTrieNode::default()))
448#[derive(Debug, Clone, PartialEq, Deserialize)]
450pub struct Keymaps(pub HashMap<Mode, Keymap>);
453 /// Returns list of keys waiting to be disambiguated in current mode.
454 pub fn pending(&self) -> &[KeyEvent] {
457 .find_map(|keymap| match keymap.pending().is_empty() {
459 false => Some(keymap.pending()),
465impl Deref for Keymaps {
466 type Target = HashMap<Mode, Keymap>;
468 fn deref(&self) -> &Self::Target {
473impl DerefMut for Keymaps {
474 fn deref_mut(&mut self) -> &mut Self::Target {
479impl Default for Keymaps {
480 fn default() -> Self {
481 let normal = keymap!({ "Normal mode"
482 "h" | "left" => move_char_left,
483 "j" | "down" => move_line_down,
484 "k" | "up" => move_line_up,
485 "l" | "right" => move_char_right,
487 "t" => find_till_char,
488 "f" => find_next_char,
489 "T" => till_prev_char,
490 "F" => find_prev_char,
492 "R" => replace_with_yanked,
493 "A-." => repeat_last_motion,
496 "`" => switch_to_lowercase,
497 "A-`" => switch_to_uppercase,
499 "home" => goto_line_start,
500 "end" => goto_line_end,
502 "w" => move_next_word_start,
503 "b" => move_prev_word_start,
504 "e" => move_next_word_end,
506 "W" => move_next_long_word_start,
507 "B" => move_prev_long_word_start,
508 "E" => move_next_long_word_end,
513 "g" => goto_file_start,
514 "e" => goto_last_line,
516 "h" => goto_line_start,
517 "l" => goto_line_end,
518 "s" => goto_first_nonwhitespace,
519 "d" => goto_definition,
520 "y" => goto_type_definition,
521 "r" => goto_reference,
522 "i" => goto_implementation,
523 "t" => goto_window_top,
524 "c" => goto_window_center,
525 "b" => goto_window_bottom,
526 "a" => goto_last_accessed_file,
527 "m" => goto_last_modified_file,
528 "n" => goto_next_buffer,
529 "p" => goto_previous_buffer,
530 "." => goto_last_modification,
535 "I" => prepend_to_line,
537 "A" => append_to_line,
541 "d" => delete_selection,
542 "A-d" => delete_selection_noyank,
543 "c" => change_selection,
544 "A-c" => change_selection_noyank,
546 "C" => copy_selection_on_next_line,
547 "A-C" => copy_selection_on_prev_line,
551 "A-s" => split_selection_on_newline,
552 "S" => split_selection,
553 ";" => collapse_selection,
554 "A-;" => flip_selections,
555 "A-k" => expand_selection,
556 "A-j" => shrink_selection,
557 "A-h" => select_prev_sibling,
558 "A-l" => select_next_sibling,
562 "X" => extend_to_line_bounds,
563 // crop_to_whole_line
566 "m" => match_brackets,
568 "r" => surround_replace,
569 "d" => surround_delete,
570 "a" => select_textobject_around,
571 "i" => select_textobject_inner,
573 "[" => { "Left bracket"
574 "d" => goto_prev_diag,
575 "D" => goto_first_diag,
576 "space" => add_newline_above,
578 "]" => { "Right bracket"
579 "d" => goto_next_diag,
580 "D" => goto_last_diag,
581 "space" => add_newline_below,
588 "*" => search_selection,
606 "=" => format_selections,
607 "J" => join_selections,
608 "K" => keep_selections,
609 "A-K" => remove_selections,
611 "," => keep_primary_selection,
612 "A-," => remove_primary_selection,
614 // "q" => record_macro,
615 // "Q" => replay_macro,
617 "&" => align_selections,
618 "_" => trim_selections,
620 "(" => rotate_selections_backward,
621 ")" => rotate_selections_forward,
622 "A-(" => rotate_selection_contents_backward,
623 "A-)" => rotate_selection_contents_forward,
625 "A-:" => ensure_selections_forward,
627 "esc" => normal_mode,
628 "C-b" | "pageup" => page_up,
629 "C-f" | "pagedown" => page_down,
630 "C-u" => half_page_up,
631 "C-d" => half_page_down,
634 "C-w" | "w" => rotate_view,
635 "C-s" | "s" => hsplit,
636 "C-v" | "v" => vsplit,
637 "f" => goto_file_hsplit,
638 "F" => goto_file_vsplit,
639 "C-q" | "q" => wclose,
640 "C-o" | "o" => wonly,
641 "C-h" | "h" | "left" => jump_view_left,
642 "C-j" | "j" | "down" => jump_view_down,
643 "C-k" | "k" | "up" => jump_view_up,
644 "C-l" | "l" | "right" => jump_view_right,
647 // move under <space>c
648 "C-c" => toggle_comments,
650 // z family for save/restore/combine from/to sels from register
652 "tab" => jump_forward, // tab == <C-i>
653 "C-o" => jump_backward,
654 "C-s" => save_selection,
658 "b" => buffer_picker,
659 "s" => symbol_picker,
660 "S" => workspace_symbol_picker,
664 "C-w" | "w" => rotate_view,
665 "C-s" | "s" => hsplit,
666 "C-v" | "v" => vsplit,
667 "f" => goto_file_hsplit,
668 "F" => goto_file_vsplit,
669 "C-q" | "q" => wclose,
670 "C-o" | "o" => wonly,
671 "C-h" | "h" | "left" => jump_view_left,
672 "C-j" | "j" | "down" => jump_view_down,
673 "C-k" | "k" | "up" => jump_view_up,
674 "C-l" | "l" | "right" => jump_view_right,
676 "y" => yank_joined_to_clipboard,
677 "Y" => yank_main_selection_to_clipboard,
678 "p" => paste_clipboard_after,
679 "P" => paste_clipboard_before,
680 "R" => replace_selections_with_clipboard,
681 "/" => global_search,
683 "r" => rename_symbol,
686 "z" | "c" => align_view_center,
687 "t" => align_view_top,
688 "b" => align_view_bottom,
689 "m" => align_view_middle,
690 "k" | "up" => scroll_up,
691 "j" | "down" => scroll_down,
692 "C-b" | "pageup" => page_up,
693 "C-f" | "pagedown" => page_down,
694 "C-u" => half_page_up,
695 "C-d" => half_page_down,
697 "Z" => { "View" sticky=true
698 "z" | "c" => align_view_center,
699 "t" => align_view_top,
700 "b" => align_view_bottom,
701 "m" => align_view_middle,
702 "k" | "up" => scroll_up,
703 "j" | "down" => scroll_down,
704 "C-b" | "pageup" => page_up,
705 "C-f" | "pagedown" => page_down,
706 "C-u" => half_page_up,
707 "C-d" => half_page_down,
710 "\"" => select_register,
712 "A-|" => shell_pipe_to,
713 "!" => shell_insert_output,
714 "A-!" => shell_append_output,
715 "$" => shell_keep_pipe,
721 let mut select = normal.clone();
722 select.merge_nodes(keymap!({ "Select mode"
723 "h" | "left" => extend_char_left,
724 "j" | "down" => extend_line_down,
725 "k" | "up" => extend_line_up,
726 "l" | "right" => extend_char_right,
728 "w" => extend_next_word_start,
729 "b" => extend_prev_word_start,
730 "e" => extend_next_word_end,
731 "W" => extend_next_long_word_start,
732 "B" => extend_prev_long_word_start,
733 "E" => extend_next_long_word_end,
735 "n" => extend_search_next,
736 "N" => extend_search_prev,
738 "t" => extend_till_char,
739 "f" => extend_next_char,
740 "T" => extend_till_prev_char,
741 "F" => extend_prev_char,
743 "home" => extend_to_line_start,
744 "end" => extend_to_line_end,
745 "esc" => exit_select_mode,
749 let insert = keymap!({ "Insert mode"
750 "esc" => normal_mode,
752 "backspace" => delete_char_backward,
753 "C-h" => delete_char_backward,
754 "del" => delete_char_forward,
755 "C-d" => delete_char_forward,
756 "ret" => insert_newline,
757 "C-j" => insert_newline,
759 "C-w" => delete_word_backward,
760 "A-backspace" => delete_word_backward,
761 "A-d" => delete_word_forward,
763 "left" => move_char_left,
764 "C-b" => move_char_left,
765 "down" => move_line_down,
766 "C-n" => move_line_down,
767 "up" => move_line_up,
768 "C-p" => move_line_up,
769 "right" => move_char_right,
770 "C-f" => move_char_right,
771 "A-b" => move_prev_word_end,
772 "A-left" => move_prev_word_end,
773 "A-f" => move_next_word_start,
774 "A-right" => move_next_word_start,
775 "A-<" => goto_file_start,
776 "A->" => goto_file_end,
778 "pagedown" => page_down,
779 "home" => goto_line_start,
780 "C-a" => goto_line_start,
781 "end" => goto_line_end_newline,
782 "C-e" => goto_line_end_newline,
784 "C-k" => kill_to_line_end,
785 "C-u" => kill_to_line_start,
788 "C-r" => insert_register,
791 Mode::Normal => Keymap::new(normal),
792 Mode::Select => Keymap::new(select),
793 Mode::Insert => Keymap::new(insert),
798/// Merge default config keys with user overwritten keys for custom user config.
799pub fn merge_keys(mut config: Config) -> Config {
800 let mut delta = std::mem::take(&mut config.keys);
801 for (mode, keys) in &mut *config.keys {
802 keys.merge(delta.remove(mode).unwrap_or_default())
813 fn duplicate_keys_should_panic() {
814 keymap!({ "Normal mode"
816 "i" => goto_definition,
821 fn check_duplicate_keys_in_default_keymap() {
822 // will panic on duplicate keys, assumes that `Keymaps` uses keymap! macro
827 fn merge_partial_keys() {
828 let config = Config {
829 keys: Keymaps(hashmap! {
830 Mode::Normal => Keymap::new(
831 keymap!({ "Normal mode"
834 "z" => jump_backward,
835 "g" => { "Merge into goto mode"
836 "$" => goto_line_end,
837 "g" => delete_char_forward,
844 let mut merged_config = merge_keys(config.clone());
845 assert_ne!(config, merged_config);
847 let keymap = merged_config.keys.0.get_mut(&Mode::Normal).unwrap();
849 keymap.get(key!('i')).kind,
850 KeymapResultKind::Matched(MappableCommand::normal_mode),
851 "Leaf should replace leaf"
854 keymap.get(key!('无')).kind,
855 KeymapResultKind::Matched(MappableCommand::insert_mode),
856 "New leaf should be present in merged keymap"
858 // Assumes that z is a node in the default keymap
860 keymap.get(key!('z')).kind,
861 KeymapResultKind::Matched(MappableCommand::jump_backward),
862 "Leaf should replace node"
864 // Assumes that `g` is a node in default keymap
866 keymap.root().search(&[key!('g'), key!('$')]).unwrap(),
867 &KeyTrie::Leaf(MappableCommand::goto_line_end),
868 "Leaf should be present in merged subnode"
870 // Assumes that `gg` is in default keymap
872 keymap.root().search(&[key!('g'), key!('g')]).unwrap(),
873 &KeyTrie::Leaf(MappableCommand::delete_char_forward),
874 "Leaf should replace old leaf in merged subnode"
876 // Assumes that `ge` is in default keymap
878 keymap.root().search(&[key!('g'), key!('e')]).unwrap(),
879 &KeyTrie::Leaf(MappableCommand::goto_last_line),
880 "Old leaves in subnode should be present in merged node"
883 assert!(merged_config.keys.0.get(&Mode::Normal).unwrap().len() > 1);
884 assert!(merged_config.keys.0.get(&Mode::Insert).unwrap().len() > 0);
888 fn order_should_be_set() {
889 let config = Config {
890 keys: Keymaps(hashmap! {
891 Mode::Normal => Keymap::new(
892 keymap!({ "Normal mode"
904 let mut merged_config = merge_keys(config.clone());
905 assert_ne!(config, merged_config);
906 let keymap = merged_config.keys.0.get_mut(&Mode::Normal).unwrap();
907 // Make sure mapping works
911 .search(&[key!(' '), key!('s'), key!('v')])
913 &KeyTrie::Leaf(MappableCommand::vsplit),
914 "Leaf should be present in merged subnode"
916 // Make sure an order was set during merge
917 let node = keymap.root().search(&[crate::key!(' ')]).unwrap();
918 assert!(!node.node().unwrap().order().is_empty())
922 fn aliased_modes_are_same_in_default_keymap() {
923 let keymaps = Keymaps::default();
924 let root = keymaps.get(&Mode::Normal).unwrap().root();
926 root.search(&[key!(' '), key!('w')]).unwrap(),
927 root.search(&["C-w".parse::<KeyEvent>().unwrap()]).unwrap(),
928 "Mismatch for window mode on `Space-w` and `Ctrl-w`"
931 root.search(&[key!('z')]).unwrap(),
932 root.search(&[key!('Z')]).unwrap(),
933 "Mismatch for view mode on `z` and `Z`"
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list