From 21169c77fd28ffe94a62c0af66d14256b950b1ac Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Sun, 25 Jun 2023 14:32:20 -0500 Subject: [PATCH] keymap: Allow looking up by Component ID Previously we only split key tries by editor Mode. This commit changes that to allow other "domain"s like Component IDs. We'll use the Component IDs later so that a Component like a Picker can look up keybindings for itself. The naming is a bit of a placeholder. The hashmap could just use `&'static str` and we could add a From for Mode. But I'd like the key to have some sort of formal name to it. --- helix-term/src/config.rs | 11 ++++--- helix-term/src/keymap.rs | 51 +++++++++++++++++++++----------- helix-term/src/keymap/default.rs | 10 +++---- helix-term/src/ui/editor.rs | 2 +- 4 files changed, 45 insertions(+), 29 deletions(-) diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index f37b03ec7..a7afead52 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -1,7 +1,6 @@ use crate::keymap; -use crate::keymap::{merge_keys, KeyTrie}; +use crate::keymap::{merge_keys, Domain, KeyTrie}; use helix_loader::merge_toml_values; -use helix_view::document::Mode; use serde::Deserialize; use std::collections::HashMap; use std::fmt::Display; @@ -12,7 +11,7 @@ use toml::de::Error as TomlError; #[derive(Debug, Clone, PartialEq)] pub struct Config { pub theme: Option, - pub keys: HashMap, + pub keys: HashMap, pub editor: helix_view::editor::Config, } @@ -20,7 +19,7 @@ pub struct Config { #[serde(deny_unknown_fields)] pub struct ConfigRaw { pub theme: Option, - pub keys: Option>, + pub keys: Option>, pub editor: Option, } @@ -154,11 +153,11 @@ mod tests { merge_keys( &mut keys, hashmap! { - Mode::Insert => keymap!({ "Insert mode" + Domain::Mode(Mode::Insert) => keymap!({ "Insert mode" "y" => move_line_down, "S-C-a" => delete_selection, }), - Mode::Normal => keymap!({ "Normal mode" + Domain::Mode(Mode::Normal) => keymap!({ "Normal mode" "A-F12" => move_next_word_end, }), }, diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 5a72a35a5..55c107f3a 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -272,8 +272,15 @@ pub enum KeymapResult { /// A map of command names to keybinds that will execute the command. pub type ReverseKeymap = HashMap>>; +// TODO name +#[derive(Eq, Hash, PartialEq, Clone, Debug)] +pub enum Domain { + Mode(Mode), + Component(&'static str), +} + pub struct Keymaps { - pub map: Box>>, + 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, @@ -282,7 +289,7 @@ pub struct Keymaps { } impl Keymaps { - pub fn new(map: Box>>) -> Self { + pub fn new(map: Box>>) -> Self { Self { map, state: Vec::new(), @@ -290,7 +297,7 @@ impl Keymaps { } } - pub fn map(&self) -> DynGuard> { + pub fn map(&self) -> DynGuard> { self.map.load() } @@ -303,14 +310,24 @@ impl Keymaps { self.sticky.as_ref() } + 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 @@ -365,7 +382,7 @@ impl Default for Keymaps { } /// Merge default config keys with user overwritten keys for custom user config. -pub fn merge_keys(dst: &mut HashMap, mut delta: HashMap) { +pub fn merge_keys(dst: &mut HashMap, mut delta: HashMap) { for (mode, keys) in dst { keys.merge_nodes( delta @@ -400,7 +417,7 @@ mod tests { #[test] fn merge_partial_keys() { let keymap = hashmap! { - Mode::Normal => keymap!({ "Normal mode" + Domain::Mode(Mode::Normal) => keymap!({ "Normal mode" "i" => normal_mode, "无" => insert_mode, "z" => jump_backward, @@ -416,23 +433,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(), @@ -454,7 +471,7 @@ mod tests { assert!( merged_keyamp - .get(&Mode::Normal) + .get(&Domain::Mode(Mode::Normal)) .and_then(|key_trie| key_trie.node()) .unwrap() .len() @@ -462,7 +479,7 @@ mod tests { ); assert!( merged_keyamp - .get(&Mode::Insert) + .get(&Domain::Mode(Mode::Insert)) .and_then(|key_trie| key_trie.node()) .unwrap() .len() @@ -473,7 +490,7 @@ mod tests { #[test] fn order_should_be_set() { let keymap = hashmap! { - Mode::Normal => keymap!({ "Normal mode" + Domain::Mode(Mode::Normal) => keymap!({ "Normal mode" "space" => { "" "s" => { "" "v" => vsplit, @@ -485,7 +502,7 @@ mod tests { 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(), @@ -500,7 +517,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::().unwrap()]).unwrap(), diff --git a/helix-term/src/keymap/default.rs b/helix-term/src/keymap/default.rs index c84c616c6..fd92c3615 100644 --- a/helix-term/src/keymap/default.rs +++ b/helix-term/src/keymap/default.rs @@ -1,10 +1,10 @@ use std::collections::HashMap; use super::macros::keymap; -use super::{KeyTrie, Mode}; +use super::{Domain, KeyTrie, Mode}; use helix_core::hashmap; -pub fn default() -> HashMap { +pub fn default() -> HashMap { let normal = keymap!({ "Normal mode" "h" | "left" => move_char_left, "j" | "down" => move_visual_line_down, @@ -380,8 +380,8 @@ pub fn default() -> HashMap { "end" => goto_line_end_newline, }); hashmap!( - Mode::Normal => normal, - Mode::Select => select, - Mode::Insert => insert, + Domain::Mode(Mode::Normal) => normal, + Domain::Mode(Mode::Select) => select, + Domain::Mode(Mode::Insert) => insert, ) } diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 515297c39..d1487bcb6 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -792,7 +792,7 @@ impl EditorView { ) -> Option { let mut last_mode = mode; self.pseudo_pending.extend(cxt.keymaps.pending()); - let key_result = cxt.keymaps.get(mode, event); + let key_result = cxt.keymaps.get_by_mode(mode, event); cxt.editor.autoinfo = cxt.keymaps.sticky().map(|node| node.infobox()); let mut execute_command = |command: &commands::MappableCommand| {