pub mod default; pub mod macros; pub use crate::commands::MappableCommand; use arc_swap::{ access::{DynAccess, DynGuard}, ArcSwap, }; use helix_view::{document::Mode, info::Info, input::KeyEvent}; use serde::Deserialize; use std::{ borrow::Cow, collections::{BTreeSet, HashMap}, ops::{Deref, DerefMut}, sync::Arc, }; pub use default::default; use macros::key; #[derive(Debug, Clone, Default)] pub struct KeyTrieNode { /// A label for keys coming under this node, like "Goto mode" name: String, map: HashMap, order: Vec, pub is_sticky: bool, } impl<'de> Deserialize<'de> for KeyTrieNode { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { let map = HashMap::::deserialize(deserializer)?; let order = map.keys().copied().collect::>(); // NOTE: map.keys() has arbitrary order Ok(Self { map, order, ..Default::default() }) } } impl KeyTrieNode { pub fn new(name: &str, map: HashMap, order: Vec) -> Self { Self { name: name.to_string(), map, order, is_sticky: false, } } /// Merge another Node in. Leaves and subnodes from the other node replace /// corresponding keyevent in self, except when both other and self have /// subnodes for same key. In that case the merge is recursive. pub fn merge(&mut self, mut other: Self) { for (key, trie) in std::mem::take(&mut other.map) { if let Some(KeyTrie::Node(node)) = self.map.get_mut(&key) { if let KeyTrie::Node(other_node) = trie { node.merge(other_node); continue; } } self.map.insert(key, trie); } for &key in self.map.keys() { if !self.order.contains(&key) { self.order.push(key); } } } pub fn infobox(&self) -> Info { let mut body: Vec<(BTreeSet, &str)> = Vec::with_capacity(self.len()); for (&key, trie) in self.iter() { let desc = match trie { KeyTrie::MappableCommand(cmd) => { if cmd.name() == "no_op" { continue; } cmd.doc() } KeyTrie::Node(n) => &n.name, KeyTrie::Sequence(_) => "[Multiple commands]", }; match body.iter().position(|(_, d)| d == &desc) { Some(pos) => { body[pos].0.insert(key); } None => body.push((BTreeSet::from([key]), desc)), } } body.sort_unstable_by_key(|(keys, _)| { self.order .iter() .position(|&k| k == *keys.iter().next().unwrap()) .unwrap() }); let body: Vec<_> = body .into_iter() .map(|(events, desc)| { let events = events.iter().map(ToString::to_string).collect::>(); (events.join(", "), desc) }) .collect(); Info::new(&self.name, &body) } } impl PartialEq for KeyTrieNode { fn eq(&self, other: &Self) -> bool { self.map == other.map } } impl Deref for KeyTrieNode { type Target = HashMap; fn deref(&self) -> &Self::Target { &self.map } } impl DerefMut for KeyTrieNode { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.map } } #[derive(Debug, Clone, PartialEq)] pub enum KeyTrie { MappableCommand(MappableCommand), Sequence(Vec), Node(KeyTrieNode), } impl<'de> Deserialize<'de> for KeyTrie { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { deserializer.deserialize_any(KeyTrieVisitor) } } struct KeyTrieVisitor; impl<'de> serde::de::Visitor<'de> for KeyTrieVisitor { type Value = KeyTrie; fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { write!(formatter, "a command, list of commands, or sub-keymap") } fn visit_str(self, command: &str) -> Result where E: serde::de::Error, { command .parse::() .map(KeyTrie::MappableCommand) .map_err(E::custom) } fn visit_seq(self, mut seq: S) -> Result where S: serde::de::SeqAccess<'de>, { let mut commands = Vec::new(); while let Some(command) = seq.next_element::()? { commands.push( command .parse::() .map_err(serde::de::Error::custom)?, ) } Ok(KeyTrie::Sequence(commands)) } fn visit_map(self, mut map: M) -> Result where M: serde::de::MapAccess<'de>, { let mut mapping = HashMap::new(); let mut order = Vec::new(); while let Some((key, value)) = map.next_entry::()? { mapping.insert(key, value); order.push(key); } Ok(KeyTrie::Node(KeyTrieNode::new("", mapping, order))) } } impl KeyTrie { pub fn reverse_map(&self) -> ReverseKeymap { // recursively visit all nodes in keymap fn map_node(cmd_map: &mut ReverseKeymap, node: &KeyTrie, keys: &mut Vec) { match node { KeyTrie::MappableCommand(cmd) => { let name = cmd.name(); if name != "no_op" { cmd_map.entry(name.into()).or_default().push(keys.clone()) } } KeyTrie::Node(next) => { for (key, trie) in &next.map { keys.push(*key); map_node(cmd_map, trie, keys); keys.pop(); } } KeyTrie::Sequence(_) => {} }; } let mut res = HashMap::new(); map_node(&mut res, self, &mut Vec::new()); res } pub fn node(&self) -> Option<&KeyTrieNode> { match *self { KeyTrie::Node(ref node) => Some(node), KeyTrie::MappableCommand(_) | KeyTrie::Sequence(_) => None, } } pub fn node_mut(&mut self) -> Option<&mut KeyTrieNode> { match *self { KeyTrie::Node(ref mut node) => Some(node), KeyTrie::MappableCommand(_) | KeyTrie::Sequence(_) => None, } } /// Merge another KeyTrie in, assuming that this KeyTrie and the other /// are both Nodes. Panics otherwise. pub fn merge_nodes(&mut self, mut other: Self) { let node = std::mem::take(other.node_mut().unwrap()); self.node_mut().unwrap().merge(node); } pub fn search(&self, keys: &[KeyEvent]) -> Option<&KeyTrie> { let mut trie = self; for key in keys { trie = match trie { KeyTrie::Node(map) => map.get(key), // leaf encountered while keys left to process KeyTrie::MappableCommand(_) | KeyTrie::Sequence(_) => None, }? } Some(trie) } } #[derive(Debug, Clone, PartialEq)] pub enum KeymapResult { /// Needs more keys to execute a command. Contains valid keys for next keystroke. Pending(KeyTrieNode), Matched(MappableCommand), /// Matched a sequence of commands to execute. MatchedSequence(Vec), /// Key was not found in the root keymap NotFound, /// Key is invalid in combination with previous keys. Contains keys leading upto /// and including current (invalid) key. Cancelled(Vec), } /// A map of command names to keybinds that will execute the command. pub type ReverseKeymap = HashMap>>; pub struct Keymaps { pub map: Box>>, /// Stores pending keys waiting for the next key. This is relative to a /// sticky node if one is in use. state: Vec, /// Stores the sticky node if one is activated. pub sticky: Option, } impl Keymaps { pub fn new(map: Box>>) -> Self { Self { map, state: Vec::new(), sticky: None, } } pub fn map(&self) -> DynGuard> { self.map.load() } /// Returns list of keys waiting to be disambiguated in current mode. pub fn pending(&self) -> &[KeyEvent] { &self.state } pub fn sticky(&self) -> Option<&KeyTrieNode> { self.sticky.as_ref() } /// Lookup `key` in the keymap to try and find a command to execute. Escape /// key cancels pending keystrokes. If there are no pending keystrokes but a /// sticky node is in use, it will be cleared. pub fn get(&mut self, mode: Mode, key: KeyEvent) -> KeymapResult { // TODO: remove the sticky part and look up manually let keymaps = &*self.map(); let keymap = &keymaps[&mode]; if key!(Esc) == key { if !self.state.is_empty() { // Note that Esc is not included here return KeymapResult::Cancelled(self.state.drain(..).collect()); } self.sticky = None; } let first = self.state.get(0).unwrap_or(&key); let trie_node = match self.sticky { Some(ref trie) => Cow::Owned(KeyTrie::Node(trie.clone())), None => Cow::Borrowed(keymap), }; let trie = match trie_node.search(&[*first]) { Some(KeyTrie::MappableCommand(ref cmd)) => { return KeymapResult::Matched(cmd.clone()); } Some(KeyTrie::Sequence(ref cmds)) => { return KeymapResult::MatchedSequence(cmds.clone()); } None => return KeymapResult::NotFound, Some(t) => t, }; self.state.push(key); match trie.search(&self.state[1..]) { Some(KeyTrie::Node(map)) => { if map.is_sticky { self.state.clear(); self.sticky = Some(map.clone()); } KeymapResult::Pending(map.clone()) } Some(KeyTrie::MappableCommand(cmd)) => { self.state.clear(); KeymapResult::Matched(cmd.clone()) } Some(KeyTrie::Sequence(cmds)) => { self.state.clear(); KeymapResult::MatchedSequence(cmds.clone()) } None => KeymapResult::Cancelled(self.state.drain(..).collect()), } } } impl Default for Keymaps { fn default() -> Self { Self::new(Box::new(ArcSwap::new(Arc::new(default())))) } } /// Merge default config keys with user overwritten keys for custom user config. pub fn merge_keys(dst: &mut HashMap, mut delta: HashMap) { for (mode, keys) in dst { keys.merge_nodes( delta .remove(mode) .unwrap_or_else(|| KeyTrie::Node(KeyTrieNode::default())), ) } } #[cfg(test)] mod tests { use super::macros::keymap; use super::*; use arc_swap::access::Constant; use helix_core::hashmap; #[test] #[should_panic] fn duplicate_keys_should_panic() { keymap!({ "Normal mode" "i" => normal_mode, "i" => goto_definition, }); } #[test] fn check_duplicate_keys_in_default_keymap() { // will panic on duplicate keys, assumes that `Keymaps` uses keymap! macro Keymaps::default(); } #[test] fn merge_partial_keys() { let keymap = hashmap! { Mode::Normal => keymap!({ "Normal mode" "i" => normal_mode, "无" => insert_mode, "z" => jump_backward, "g" => { "Merge into goto mode" "$" => goto_line_end, "g" => delete_char_forward, }, }) }; let mut merged_keyamp = default(); merge_keys(&mut merged_keyamp, keymap.clone()); assert_ne!(keymap, merged_keyamp); let mut keymap = Keymaps::new(Box::new(Constant(merged_keyamp.clone()))); assert_eq!( keymap.get(Mode::Normal, key!('i')), KeymapResult::Matched(MappableCommand::normal_mode), "Leaf should replace leaf" ); assert_eq!( keymap.get(Mode::Normal, key!('无')), KeymapResult::Matched(MappableCommand::insert_mode), "New leaf should be present in merged keymap" ); // Assumes that z is a node in the default keymap assert_eq!( keymap.get(Mode::Normal, key!('z')), KeymapResult::Matched(MappableCommand::jump_backward), "Leaf should replace node" ); let keymap = merged_keyamp.get_mut(&Mode::Normal).unwrap(); // Assumes that `g` is a node in default keymap assert_eq!( keymap.search(&[key!('g'), key!('$')]).unwrap(), &KeyTrie::MappableCommand(MappableCommand::goto_line_end), "Leaf should be present in merged subnode" ); // Assumes that `gg` is in default keymap assert_eq!( keymap.search(&[key!('g'), key!('g')]).unwrap(), &KeyTrie::MappableCommand(MappableCommand::delete_char_forward), "Leaf should replace old leaf in merged subnode" ); // Assumes that `ge` is in default keymap assert_eq!( keymap.search(&[key!('g'), key!('e')]).unwrap(), &KeyTrie::MappableCommand(MappableCommand::goto_last_line), "Old leaves in subnode should be present in merged node" ); assert!( merged_keyamp .get(&Mode::Normal) .and_then(|key_trie| key_trie.node()) .unwrap() .len() > 1 ); assert!( merged_keyamp .get(&Mode::Insert) .and_then(|key_trie| key_trie.node()) .unwrap() .len() > 0 ); } #[test] fn order_should_be_set() { let keymap = hashmap! { Mode::Normal => keymap!({ "Normal mode" "space" => { "" "s" => { "" "v" => vsplit, "c" => hsplit, }, }, }) }; let mut merged_keyamp = default(); merge_keys(&mut merged_keyamp, keymap.clone()); assert_ne!(keymap, merged_keyamp); let keymap = merged_keyamp.get_mut(&Mode::Normal).unwrap(); // Make sure mapping works assert_eq!( keymap.search(&[key!(' '), key!('s'), key!('v')]).unwrap(), &KeyTrie::MappableCommand(MappableCommand::vsplit), "Leaf should be present in merged subnode" ); // Make sure an order was set during merge let node = keymap.search(&[crate::key!(' ')]).unwrap(); assert!(!node.node().unwrap().order.as_slice().is_empty()) } #[test] fn aliased_modes_are_same_in_default_keymap() { let keymaps = Keymaps::default().map(); let root = keymaps.get(&Mode::Normal).unwrap(); assert_eq!( root.search(&[key!(' '), key!('w')]).unwrap(), root.search(&["C-w".parse::().unwrap()]).unwrap(), "Mismatch for window mode on `Space-w` and `Ctrl-w`" ); assert_eq!( root.search(&[key!('z')]).unwrap(), root.search(&[key!('Z')]).unwrap(), "Mismatch for view mode on `z` and `Z`" ); } #[test] fn reverse_map() { let normal_mode = keymap!({ "Normal mode" "i" => insert_mode, "g" => { "Goto" "g" => goto_file_start, "e" => goto_file_end, }, "j" | "k" => move_line_down, }); let keymap = normal_mode; let mut reverse_map = keymap.reverse_map(); // sort keybindings in order to have consistent tests // HashMaps can be compared but we can still get different ordering of bindings // for commands that have multiple bindings assigned for v in reverse_map.values_mut() { v.sort() } assert_eq!( reverse_map, HashMap::from([ ("insert_mode".to_string(), vec![vec![key!('i')]]), ( "goto_file_start".to_string(), vec![vec![key!('g'), key!('g')]] ), ( "goto_file_end".to_string(), vec![vec![key!('g'), key!('e')]] ), ( "move_line_down".to_string(), vec![vec![key!('j')], vec![key!('k')]] ), ]), "Mismatch" ) } #[test] fn escaped_keymap() { use crate::commands::MappableCommand; use helix_view::input::{KeyCode, KeyEvent, KeyModifiers}; let keys = r#" "+" = [ "select_all", ":pipe sed -E 's/\\s+$//g'", ] "#; let key = KeyEvent { code: KeyCode::Char('+'), modifiers: KeyModifiers::NONE, }; let expectation = KeyTrie::Node(KeyTrieNode::new( "", hashmap! { key => KeyTrie::Sequence(vec!{ MappableCommand::select_all, MappableCommand::Typable { name: "pipe".to_string(), args: vec!{ "sed".to_string(), "-E".to_string(), "'s/\\s+$//g'".to_string() }, doc: "".to_string(), }, }) }, vec![key], )); assert_eq!(toml::from_str(keys), Ok(expectation)); } }