Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-term/src/keymap.rs')
| -rw-r--r-- | helix-term/src/keymap.rs | 136 |
1 files changed, 81 insertions, 55 deletions
diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index d8227b50..aae78933 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -106,7 +106,7 @@ impl KeyTrieNode { (events.join(", "), desc) }) .collect(); - Info::new(self.name.clone(), &body) + Info::new(&self.name, &body) } } @@ -177,19 +177,6 @@ impl<'de> serde::de::Visitor<'de> for KeyTrieVisitor { .map_err(serde::de::Error::custom)?, ) } - - // Prevent macro keybindings from being used in command sequences. - // This is meant to be a temporary restriction pending a larger - // refactor of how command sequences are executed. - if commands - .iter() - .any(|cmd| matches!(cmd, MappableCommand::Macro { .. })) - { - return Err(serde::de::Error::custom( - "macro keybindings may not be used in command sequences", - )); - } - Ok(KeyTrie::Sequence(commands)) } @@ -212,7 +199,6 @@ impl KeyTrie { // recursively visit all nodes in keymap fn map_node(cmd_map: &mut ReverseKeymap, node: &KeyTrie, keys: &mut Vec<KeyEvent>) { match node { - KeyTrie::MappableCommand(MappableCommand::Macro { .. }) => {} KeyTrie::MappableCommand(cmd) => { let name = cmd.name(); if name != "no_op" { @@ -286,8 +272,43 @@ pub enum KeymapResult { /// A map of command names to keybinds that will execute the command. pub type ReverseKeymap = HashMap<String, Vec<Vec<KeyEvent>>>; +// TODO name +#[derive(Eq, Hash, PartialEq, Clone, Debug)] +pub enum Domain { + Mode(Mode), + Component(&'static str), +} + +const REMAPPABLE_COMPONENTS: [&'static str; 3] = [ + crate::ui::DYNAMIC_PICKER_ID, + crate::ui::PICKER_ID, + // TODO: make it a constant + "buffer-picker", +]; + +impl<'de> Deserialize<'de> for Domain { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: serde::Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + if let Ok(mode) = s.parse::<Mode>() { + return Ok(Domain::Mode(mode)); + } else if let Some(name) = REMAPPABLE_COMPONENTS + .iter() + .find(|name| **name == s.as_str()) + { + Ok(Domain::Component(name)) + } else { + Err(serde::de::Error::custom(format!( + "Unknown keymap domain {s}. Expected a mode or component name" + ))) + } + } +} + pub struct Keymaps { - pub map: Box<dyn DynAccess<HashMap<Mode, KeyTrie>>>, + pub map: Box<dyn DynAccess<HashMap<Domain, KeyTrie>>>, /// Stores pending keys waiting for the next key. This is relative to a /// sticky node if one is in use. state: Vec<KeyEvent>, @@ -296,7 +317,7 @@ pub struct Keymaps { } impl Keymaps { - pub fn new(map: Box<dyn DynAccess<HashMap<Mode, KeyTrie>>>) -> Self { + pub fn new(map: Box<dyn DynAccess<HashMap<Domain, KeyTrie>>>) -> Self { Self { map, state: Vec::new(), @@ -304,7 +325,7 @@ impl Keymaps { } } - pub fn map(&self) -> DynGuard<HashMap<Mode, KeyTrie>> { + pub fn map(&self) -> DynGuard<HashMap<Domain, KeyTrie>> { self.map.load() } @@ -317,23 +338,24 @@ impl Keymaps { self.sticky.as_ref() } - pub fn contains_key(&self, mode: Mode, key: KeyEvent) -> bool { - let keymaps = &*self.map(); - let keymap = &keymaps[&mode]; - keymap - .search(self.pending()) - .and_then(KeyTrie::node) - .is_some_and(|node| node.contains_key(&key)) + pub fn get_by_mode(&mut self, mode: Mode, key: KeyEvent) -> KeymapResult { + self.get(Domain::Mode(mode), key) + } + + pub fn get_by_component_id(&mut self, id: &'static str, key: KeyEvent) -> KeymapResult { + self.get(Domain::Component(id), key) } /// 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 + fn get(&mut self, domain: Domain, key: KeyEvent) -> KeymapResult { let keymaps = &*self.map(); - let keymap = &keymaps[&mode]; + let Some(keymap) = keymaps.get(&domain) else { + return KeymapResult::NotFound; + }; + // TODO: remove the sticky part and look up manually if key!(Esc) == key { if !self.state.is_empty() { // Note that Esc is not included here @@ -342,7 +364,7 @@ impl Keymaps { self.sticky = None; } - let first = self.state.first().unwrap_or(&key); + 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), @@ -388,7 +410,7 @@ impl Default for Keymaps { } /// Merge default config keys with user overwritten keys for custom user config. -pub fn merge_keys(dst: &mut HashMap<Mode, KeyTrie>, mut delta: HashMap<Mode, KeyTrie>) { +pub fn merge_keys(dst: &mut HashMap<Domain, KeyTrie>, mut delta: HashMap<Domain, KeyTrie>) { for (mode, keys) in dst { keys.merge_nodes( delta @@ -423,15 +445,15 @@ mod tests { #[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, - }, - }) + Domain::Mode(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()); @@ -439,23 +461,23 @@ mod tests { let mut keymap = Keymaps::new(Box::new(Constant(merged_keyamp.clone()))); assert_eq!( - keymap.get(Mode::Normal, key!('i')), + keymap.get_by_mode(Mode::Normal, key!('i')), KeymapResult::Matched(MappableCommand::normal_mode), "Leaf should replace leaf" ); assert_eq!( - keymap.get(Mode::Normal, key!('无')), + keymap.get_by_mode(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')), + keymap.get_by_mode(Mode::Normal, key!('z')), KeymapResult::Matched(MappableCommand::jump_backward), "Leaf should replace node" ); - let keymap = merged_keyamp.get_mut(&Mode::Normal).unwrap(); + let keymap = merged_keyamp.get_mut(&Domain::Mode(Mode::Normal)).unwrap(); // Assumes that `g` is a node in default keymap assert_eq!( keymap.search(&[key!('g'), key!('$')]).unwrap(), @@ -477,7 +499,7 @@ mod tests { assert!( merged_keyamp - .get(&Mode::Normal) + .get(&Domain::Mode(Mode::Normal)) .and_then(|key_trie| key_trie.node()) .unwrap() .len() @@ -485,7 +507,7 @@ mod tests { ); assert!( merged_keyamp - .get(&Mode::Insert) + .get(&Domain::Mode(Mode::Insert)) .and_then(|key_trie| key_trie.node()) .unwrap() .len() @@ -496,19 +518,19 @@ mod tests { #[test] fn order_should_be_set() { let keymap = hashmap! { - Mode::Normal => keymap!({ "Normal mode" - "space" => { "" - "s" => { "" - "v" => vsplit, - "c" => hsplit, + Domain::Mode(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(); + let keymap = merged_keyamp.get_mut(&Domain::Mode(Mode::Normal)).unwrap(); // Make sure mapping works assert_eq!( keymap.search(&[key!(' '), key!('s'), key!('v')]).unwrap(), @@ -523,7 +545,7 @@ mod tests { #[test] fn aliased_modes_are_same_in_default_keymap() { let keymaps = Keymaps::default().map(); - let root = keymaps.get(&Mode::Normal).unwrap(); + let root = keymaps.get(&Domain::Mode(Mode::Normal)).unwrap(); assert_eq!( root.search(&[key!(' '), key!('w')]).unwrap(), root.search(&["C-w".parse::<KeyEvent>().unwrap()]).unwrap(), @@ -601,7 +623,11 @@ mod tests { MappableCommand::select_all, MappableCommand::Typable { name: "pipe".to_string(), - args: "sed -E 's/\\s+$//g'".to_string(), + args: vec!{ + "sed".to_string(), + "-E".to_string(), + "'s/\\s+$//g'".to_string() + }, doc: "".to_string(), }, }) |