diff options
Diffstat (limited to 'helix-term/src/keymap.rs')
-rw-r--r-- | helix-term/src/keymap.rs | 228 |
1 files changed, 150 insertions, 78 deletions
diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index e344457c..35dbce2f 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -5,20 +5,20 @@ use helix_view::{document::Mode, info::Info, input::KeyEvent}; use serde::Deserialize; use std::{ borrow::Cow, - collections::HashMap, + collections::{BTreeSet, HashMap}, ops::{Deref, DerefMut}, }; #[macro_export] macro_rules! key { ($key:ident) => { - KeyEvent { + ::helix_view::input::KeyEvent { code: ::helix_view::keyboard::KeyCode::$key, modifiers: ::helix_view::keyboard::KeyModifiers::NONE, } }; ($($ch:tt)*) => { - KeyEvent { + ::helix_view::input::KeyEvent { code: ::helix_view::keyboard::KeyCode::Char($($ch)*), modifiers: ::helix_view::keyboard::KeyModifiers::NONE, } @@ -78,19 +78,30 @@ macro_rules! keymap { }; } -#[derive(Debug, Clone, Deserialize)] +#[derive(Debug, Clone)] pub struct KeyTrieNode { /// A label for keys coming under this node, like "Goto mode" - #[serde(skip)] name: String, - #[serde(flatten)] map: HashMap<KeyEvent, KeyTrie>, - #[serde(skip)] order: Vec<KeyEvent>, - #[serde(skip)] pub is_sticky: bool, } +impl<'de> Deserialize<'de> for KeyTrieNode { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: serde::Deserializer<'de>, + { + let map = HashMap::<KeyEvent, KeyTrie>::deserialize(deserializer)?; + let order = map.keys().copied().collect::<Vec<_>>(); // NOTE: map.keys() has arbitrary order + Ok(Self { + map, + order, + ..Default::default() + }) + } +} + impl KeyTrieNode { pub fn new(name: &str, map: HashMap<KeyEvent, KeyTrie>, order: Vec<KeyEvent>) -> Self { Self { @@ -118,7 +129,6 @@ impl KeyTrieNode { } self.map.insert(key, trie); } - for &key in self.map.keys() { if !self.order.contains(&key) { self.order.push(key); @@ -127,20 +137,29 @@ impl KeyTrieNode { } pub fn infobox(&self) -> Info { - let mut body: Vec<(&str, Vec<KeyEvent>)> = Vec::with_capacity(self.len()); + let mut body: Vec<(&str, BTreeSet<KeyEvent>)> = Vec::with_capacity(self.len()); for (&key, trie) in self.iter() { let desc = match trie { - KeyTrie::Leaf(cmd) => cmd.doc(), + KeyTrie::Leaf(cmd) => { + if cmd.name() == "no_op" { + continue; + } + cmd.doc() + } KeyTrie::Node(n) => n.name(), }; match body.iter().position(|(d, _)| d == &desc) { - // FIXME: multiple keys are ordered randomly (use BTreeSet) - Some(pos) => body[pos].1.push(key), - None => body.push((desc, vec![key])), + Some(pos) => { + body[pos].1.insert(key); + } + None => body.push((desc, BTreeSet::from([key]))), } } body.sort_unstable_by_key(|(_, keys)| { - self.order.iter().position(|&k| k == keys[0]).unwrap() + self.order + .iter() + .position(|&k| k == *keys.iter().next().unwrap()) + .unwrap() }); let prefix = format!("{} ", self.name()); if body.iter().all(|(desc, _)| desc.starts_with(&prefix)) { @@ -151,6 +170,11 @@ impl KeyTrieNode { } Info::new(self.name(), body) } + + /// Get a reference to the key trie node's order. + pub fn order(&self) -> &[KeyEvent] { + self.order.as_slice() + } } impl Default for KeyTrieNode { @@ -235,6 +259,7 @@ pub enum KeymapResultKind { /// Returned after looking up a key in [`Keymap`]. The `sticky` field has a /// reference to the sticky node if one is currently active. +#[derive(Debug)] pub struct KeymapResult<'a> { pub kind: KeymapResultKind, pub sticky: Option<&'a KeyTrieNode>, @@ -395,6 +420,7 @@ impl Default for Keymaps { "F" => find_prev_char, "r" => replace, "R" => replace_with_yanked, + "A-." => repeat_last_motion, "~" => switch_case, "`" => switch_to_lowercase, @@ -427,6 +453,8 @@ impl Default for Keymaps { "m" => goto_window_middle, "b" => goto_window_bottom, "a" => goto_last_accessed_file, + "n" => goto_next_buffer, + "p" => goto_previous_buffer, }, ":" => command_mode, @@ -476,10 +504,9 @@ impl Default for Keymaps { }, "/" => search, - // ? for search_reverse + "?" => rsearch, "n" => search_next, - "N" => extend_search_next, - // N for search_prev + "N" => search_prev, "*" => search_selection, "u" => undo, @@ -520,9 +547,13 @@ impl Default for Keymaps { "C-w" => { "Window" "C-w" | "w" => rotate_view, - "C-h" | "h" => hsplit, + "C-s" | "s" => hsplit, "C-v" | "v" => vsplit, "C-q" | "q" => wclose, + "C-h" | "h" | "left" => jump_view_left, + "C-j" | "j" | "down" => jump_view_down, + "C-k" | "k" | "up" => jump_view_up, + "C-l" | "l" | "right" => jump_view_right, }, // move under <space>c @@ -621,6 +652,9 @@ impl Default for Keymaps { "B" => extend_prev_long_word_start, "E" => extend_next_long_word_end, + "n" => extend_search_next, + "N" => extend_search_prev, + "t" => extend_till_char, "f" => extend_next_char, "T" => extend_till_prev_char, @@ -669,63 +703,101 @@ pub fn merge_keys(mut config: Config) -> Config { config } -#[test] -fn merge_partial_keys() { - let config = Config { - keys: Keymaps(hashmap! { - Mode::Normal => Keymap::new( - keymap!({ "Normal mode" - "i" => normal_mode, - "无" => insert_mode, - "z" => jump_backward, - "g" => { "Merge into goto mode" - "$" => goto_line_end, - "g" => delete_char_forward, - }, - }) - ) - }), - ..Default::default() - }; - let mut merged_config = merge_keys(config.clone()); - assert_ne!(config, merged_config); - - let keymap = merged_config.keys.0.get_mut(&Mode::Normal).unwrap(); - assert_eq!( - keymap.get(key!('i')).kind, - KeymapResultKind::Matched(Command::normal_mode), - "Leaf should replace leaf" - ); - assert_eq!( - keymap.get(key!('无')).kind, - KeymapResultKind::Matched(Command::insert_mode), - "New leaf should be present in merged keymap" - ); - // Assumes that z is a node in the default keymap - assert_eq!( - keymap.get(key!('z')).kind, - KeymapResultKind::Matched(Command::jump_backward), - "Leaf should replace node" - ); - // Assumes that `g` is a node in default keymap - assert_eq!( - keymap.root().search(&[key!('g'), key!('$')]).unwrap(), - &KeyTrie::Leaf(Command::goto_line_end), - "Leaf should be present in merged subnode" - ); - // Assumes that `gg` is in default keymap - assert_eq!( - keymap.root().search(&[key!('g'), key!('g')]).unwrap(), - &KeyTrie::Leaf(Command::delete_char_forward), - "Leaf should replace old leaf in merged subnode" - ); - // Assumes that `ge` is in default keymap - assert_eq!( - keymap.root().search(&[key!('g'), key!('e')]).unwrap(), - &KeyTrie::Leaf(Command::goto_last_line), - "Old leaves in subnode should be present in merged node" - ); - - assert!(merged_config.keys.0.get(&Mode::Normal).unwrap().len() > 1); - assert!(merged_config.keys.0.get(&Mode::Insert).unwrap().len() > 0); +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn merge_partial_keys() { + let config = Config { + keys: Keymaps(hashmap! { + Mode::Normal => Keymap::new( + keymap!({ "Normal mode" + "i" => normal_mode, + "无" => insert_mode, + "z" => jump_backward, + "g" => { "Merge into goto mode" + "$" => goto_line_end, + "g" => delete_char_forward, + }, + }) + ) + }), + ..Default::default() + }; + let mut merged_config = merge_keys(config.clone()); + assert_ne!(config, merged_config); + + let keymap = merged_config.keys.0.get_mut(&Mode::Normal).unwrap(); + assert_eq!( + keymap.get(key!('i')).kind, + KeymapResultKind::Matched(Command::normal_mode), + "Leaf should replace leaf" + ); + assert_eq!( + keymap.get(key!('无')).kind, + KeymapResultKind::Matched(Command::insert_mode), + "New leaf should be present in merged keymap" + ); + // Assumes that z is a node in the default keymap + assert_eq!( + keymap.get(key!('z')).kind, + KeymapResultKind::Matched(Command::jump_backward), + "Leaf should replace node" + ); + // Assumes that `g` is a node in default keymap + assert_eq!( + keymap.root().search(&[key!('g'), key!('$')]).unwrap(), + &KeyTrie::Leaf(Command::goto_line_end), + "Leaf should be present in merged subnode" + ); + // Assumes that `gg` is in default keymap + assert_eq!( + keymap.root().search(&[key!('g'), key!('g')]).unwrap(), + &KeyTrie::Leaf(Command::delete_char_forward), + "Leaf should replace old leaf in merged subnode" + ); + // Assumes that `ge` is in default keymap + assert_eq!( + keymap.root().search(&[key!('g'), key!('e')]).unwrap(), + &KeyTrie::Leaf(Command::goto_last_line), + "Old leaves in subnode should be present in merged node" + ); + + assert!(merged_config.keys.0.get(&Mode::Normal).unwrap().len() > 1); + assert!(merged_config.keys.0.get(&Mode::Insert).unwrap().len() > 0); + } + + #[test] + fn order_should_be_set() { + let config = Config { + keys: Keymaps(hashmap! { + Mode::Normal => Keymap::new( + keymap!({ "Normal mode" + "space" => { "" + "s" => { "" + "v" => vsplit, + "c" => hsplit, + }, + }, + }) + ) + }), + ..Default::default() + }; + let mut merged_config = merge_keys(config.clone()); + assert_ne!(config, merged_config); + let keymap = merged_config.keys.0.get_mut(&Mode::Normal).unwrap(); + // Make sure mapping works + assert_eq!( + keymap + .root() + .search(&[key!(' '), key!('s'), key!('v')]) + .unwrap(), + &KeyTrie::Leaf(Command::vsplit), + "Leaf should be present in merged subnode" + ); + // Make sure an order was set during merge + let node = keymap.root().search(&[crate::key!(' ')]).unwrap(); + assert!(!node.node().unwrap().order().is_empty()) + } } |