From 671f297630d360cfa5d55dc69d4af98521a4b152 Mon Sep 17 00:00:00 2001 From: bb Date: Sun, 9 Oct 2022 17:33:16 +0200 Subject: [PATCH 001/105] Make stickiness configurable --- book/src/remapping.md | 2 ++ helix-term/src/keymap.rs | 22 ++++++++++++++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/book/src/remapping.md b/book/src/remapping.md index bd4ac7f81c86..37fcba2c7140 100644 --- a/book/src/remapping.md +++ b/book/src/remapping.md @@ -50,5 +50,7 @@ Control, Shift and Alt modifiers are encoded respectively with the prefixes Keys can be disabled by binding them to the `no_op` command. +Making a mode "sticky" can be achieved by adding `sticky = true` to the mapping. + Commands can be found at [Keymap](https://docs.helix-editor.com/keymap.html) Commands. > Commands can also be found in the source code at [`helix-term/src/commands.rs`](https://github.com/helix-editor/helix/blob/master/helix-term/src/commands.rs) at the invocation of `static_commands!` macro and the `TypableCommandList`. diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 088b3b6d2083..dbf8e3472dd9 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -200,11 +200,25 @@ impl<'de> serde::de::Visitor<'de> for KeyTrieVisitor { { 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); + let mut is_sticky = false; + while let Some(toml_key) = map.next_key::()? { + let maybe_key_event = toml_key.clone().try_into::(); + match (maybe_key_event, toml_key.as_str()) { + (Ok(key_event), _) => { + mapping.insert(key_event, map.next_value::()?); + order.push(key_event); + } + (Err(_), Some("sticky")) => { + is_sticky = map.next_value::()?; + } + (Err(err), _) => { + return Err(serde::de::Error::custom(err.to_string())); + } + } } - Ok(KeyTrie::Node(KeyTrieNode::new("", mapping, order))) + let mut trie_node = KeyTrieNode::new("", mapping, order); + trie_node.is_sticky = is_sticky; + Ok(KeyTrie::Node(trie_node)) } } From 09ea56b234312c68e6b9830b07a3f50ce6c43df0 Mon Sep 17 00:00:00 2001 From: Matthew Cheely Date: Fri, 23 Sep 2022 21:37:23 -0400 Subject: [PATCH 002/105] Add support for labels in custom menu keymaps --- helix-term/src/config.rs | 33 +++++++++++++++++++++++++++++++++ helix-term/src/keymap.rs | 17 +++++++++++++---- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index 4407a882f838..fbea75970582 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -94,6 +94,39 @@ mod tests { ); } + #[test] + fn parsing_menus() { + use crate::keymap; + use crate::keymap::Keymap; + use helix_core::hashmap; + use helix_view::document::Mode; + + let sample_keymaps = r#" + [keys.normal] + f = { f = "file_picker", c = "wclose" } + b = { label = "buffer", b = "buffer_picker", n = "goto_next_buffer" } + "#; + + assert_eq!( + toml::from_str::(sample_keymaps).unwrap(), + Config { + keys: hashmap! { + Mode::Normal => Keymap::new(keymap!({ "Normal mode" + "f" => { "" + "f" => file_picker, + "c" => wclose, + }, + "b" => { "buffer" + "b" => buffer_picker, + "n" => goto_next_buffer, + }, + })), + }, + ..Default::default() + } + ); + } + #[test] fn keys_resolve_to_correct_defaults() { // From serde default diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 4a131f0a5217..c2d6cecbf1d7 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -198,13 +198,22 @@ impl<'de> serde::de::Visitor<'de> for KeyTrieVisitor { where M: serde::de::MapAccess<'de>, { + let mut name = ""; 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); + + while let Some(key) = map.next_key::<&str>()? { + match key { + "label" => name = map.next_value::<&str>()?, + _ => { + let key_event = key.parse::().map_err(serde::de::Error::custom)?; + let key_trie = map.next_value::()?; + mapping.insert(key_event, key_trie); + order.push(key_event); + } + } } - Ok(KeyTrie::Node(KeyTrieNode::new("", mapping, order))) + Ok(KeyTrie::Node(KeyTrieNode::new(name, mapping, order))) } } From 608687da6008063db6721d57a74a1f88ccb0d2ae Mon Sep 17 00:00:00 2001 From: Matthew Cheely Date: Fri, 23 Sep 2022 23:03:56 -0400 Subject: [PATCH 003/105] Add support for labels on typable commands --- helix-term/src/config.rs | 38 ++++++++++++++++++++++++++++++++++++++ helix-term/src/keymap.rs | 28 +++++++++++++++++++++++++--- 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index fbea75970582..ac19cee106a9 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -127,6 +127,44 @@ mod tests { ); } + #[test] + fn parsing_typable_commands() { + use crate::keymap; + use crate::keymap::MappableCommand; + use helix_view::document::Mode; + use helix_view::input::KeyEvent; + use std::str::FromStr; + + let sample_keymaps = r#" + [keys.normal] + o = { label = "Edit Config", command = ":open ~/.config" } + c = ":buffer-close" + "#; + + let config = toml::from_str::(sample_keymaps).unwrap(); + + let tree = config.keys.get(&Mode::Normal).unwrap().root(); + + if let keymap::KeyTrie::Node(node) = tree { + let open_node = node.get(&KeyEvent::from_str("o").unwrap()).unwrap(); + + if let keymap::KeyTrie::Leaf(MappableCommand::Typable { doc, .. }) = open_node { + assert_eq!(doc, "Edit Config"); + } else { + panic!("Edit Config did not parse to typable command"); + } + + let close_node = node.get(&KeyEvent::from_str("c").unwrap()).unwrap(); + if let keymap::KeyTrie::Leaf(MappableCommand::Typable { doc, .. }) = close_node { + assert_eq!(doc, ":buffer-close []"); + } else { + panic!(":buffer-close command did not parse to typable command"); + } + } else { + panic!("Config did not parse to trie"); + } + } + #[test] fn keys_resolve_to_correct_defaults() { // From serde default diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index c2d6cecbf1d7..5dff983952e0 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -198,13 +198,15 @@ impl<'de> serde::de::Visitor<'de> for KeyTrieVisitor { where M: serde::de::MapAccess<'de>, { - let mut name = ""; + let mut label = ""; + let mut command = None; let mut mapping = HashMap::new(); let mut order = Vec::new(); while let Some(key) = map.next_key::<&str>()? { match key { - "label" => name = map.next_value::<&str>()?, + "label" => label = map.next_value::<&str>()?, + "command" => command = Some(map.next_value::()?), _ => { let key_event = key.parse::().map_err(serde::de::Error::custom)?; let key_trie = map.next_value::()?; @@ -213,7 +215,27 @@ impl<'de> serde::de::Visitor<'de> for KeyTrieVisitor { } } } - Ok(KeyTrie::Node(KeyTrieNode::new(name, mapping, order))) + + match command { + None => Ok(KeyTrie::Node(KeyTrieNode::new(label, mapping, order))), + Some(cmd) => { + if label.is_empty() { + Ok(KeyTrie::Leaf(cmd)) + } else { + match cmd { + MappableCommand::Typable { name, args, .. } => { + Ok(MappableCommand::Typable { + name, + args, + doc: label.to_string(), + }) + .map(KeyTrie::Leaf) + } + MappableCommand::Static { .. } => Ok(KeyTrie::Leaf(cmd)), + } + } + } + } } } From 091b3f5df094e60295bcfce134fe94ce43d64b67 Mon Sep 17 00:00:00 2001 From: Matthew Cheely Date: Sat, 24 Sep 2022 21:31:15 -0400 Subject: [PATCH 004/105] refactor keymap map visitor to reduce # of cases --- helix-term/src/keymap.rs | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 5dff983952e0..8a4fa8337fb5 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -219,20 +219,16 @@ impl<'de> serde::de::Visitor<'de> for KeyTrieVisitor { match command { None => Ok(KeyTrie::Node(KeyTrieNode::new(label, mapping, order))), Some(cmd) => { - if label.is_empty() { - Ok(KeyTrie::Leaf(cmd)) + let status = (cmd, label.is_empty()); + if let (MappableCommand::Typable { name, args, .. }, false) = status { + Ok(MappableCommand::Typable { + name, + args, + doc: label.to_string(), + }) + .map(KeyTrie::Leaf) } else { - match cmd { - MappableCommand::Typable { name, args, .. } => { - Ok(MappableCommand::Typable { - name, - args, - doc: label.to_string(), - }) - .map(KeyTrie::Leaf) - } - MappableCommand::Static { .. } => Ok(KeyTrie::Leaf(cmd)), - } + Ok(KeyTrie::Leaf(status.0)) } } } From 56d624ef5a8ebc3c48ff23a966ffe6b20ec19a9c Mon Sep 17 00:00:00 2001 From: Matthew Cheely Date: Tue, 4 Oct 2022 19:18:22 -0400 Subject: [PATCH 005/105] Simplify labelled command pattern match Co-authored-by: Michael Davis --- helix-term/src/keymap.rs | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 8a4fa8337fb5..82e1137fc4eb 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -218,19 +218,14 @@ impl<'de> serde::de::Visitor<'de> for KeyTrieVisitor { match command { None => Ok(KeyTrie::Node(KeyTrieNode::new(label, mapping, order))), - Some(cmd) => { - let status = (cmd, label.is_empty()); - if let (MappableCommand::Typable { name, args, .. }, false) = status { - Ok(MappableCommand::Typable { - name, - args, - doc: label.to_string(), - }) - .map(KeyTrie::Leaf) - } else { - Ok(KeyTrie::Leaf(status.0)) - } + Some(MappableCommand::Typable { name, args, .. }) if !label.is_empty() => { + Ok(KeyTrie::Leaf(MappableCommand::Typable { + name, + args, + doc: label.to_string(), + })) } + Some(command) => Ok(KeyTrie::Leaf(command)), } } } From 3642213127cacf0bcf67cabc2be920277c39eaa1 Mon Sep 17 00:00:00 2001 From: Matthew Cheely Date: Mon, 17 Oct 2022 20:15:27 -0400 Subject: [PATCH 006/105] Add some basic docs --- book/src/remapping.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/book/src/remapping.md b/book/src/remapping.md index e89c66113d1b..657ae509bc7d 100644 --- a/book/src/remapping.md +++ b/book/src/remapping.md @@ -19,6 +19,13 @@ w = "move_line_up" # Maps the 'w' key move_line_up g = { a = "code_action" } # Maps `ga` to show possible code actions "ret" = ["open_below", "normal_mode"] # Maps the enter key to open_below then re-enter normal mode +# You can create labeled sub-menus and provide friendly labels for typeable commands +[keys.normal.space.f] # Registering multiple mappings under a single entry creates a sub-menu (accesed by 'space', 'f' in this case) +label = "File" # The menu is called file and within it: +f = "file_picker" # 'f' opens the file picker +s = { label = "Save", command = ":write" } # 's' saves the current file +c = { label = "Edit Config", command = ":open ~/.config/helix/config.toml" } # 'c' opens the helix config file + [keys.insert] "A-x" = "normal_mode" # Maps Alt-X to enter normal mode j = { k = "normal_mode" } # Maps `jk` to exit insert mode From 780457293485a8ded512db622b7f09d8b6b18505 Mon Sep 17 00:00:00 2001 From: Matthew Cheely Date: Tue, 15 Nov 2022 18:42:36 -0500 Subject: [PATCH 007/105] fix typos in menu label docs --- book/src/remapping.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/book/src/remapping.md b/book/src/remapping.md index 657ae509bc7d..5676f96e12ee 100644 --- a/book/src/remapping.md +++ b/book/src/remapping.md @@ -20,8 +20,8 @@ g = { a = "code_action" } # Maps `ga` to show possible code actions "ret" = ["open_below", "normal_mode"] # Maps the enter key to open_below then re-enter normal mode # You can create labeled sub-menus and provide friendly labels for typeable commands -[keys.normal.space.f] # Registering multiple mappings under a single entry creates a sub-menu (accesed by 'space', 'f' in this case) -label = "File" # The menu is called file and within it: +[keys.normal.space.f] # Registering multiple mappings under a single entry creates a sub-menu (accessed by 'space', 'f' in this case) +label = "File" # The menu is called file and within it: f = "file_picker" # 'f' opens the file picker s = { label = "Save", command = ":write" } # 's' saves the current file c = { label = "Edit Config", command = ":open ~/.config/helix/config.toml" } # 'c' opens the helix config file From 0e6c4af38bad31c344d416f2b5592facccc4f26c Mon Sep 17 00:00:00 2001 From: Matthew Cheely Date: Tue, 15 Nov 2022 21:20:39 -0500 Subject: [PATCH 008/105] return errors for ambiguous and unsupported labels in menus --- helix-term/src/keymap.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 82e1137fc4eb..a17e7a0ad446 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -218,6 +218,12 @@ impl<'de> serde::de::Visitor<'de> for KeyTrieVisitor { match command { None => Ok(KeyTrie::Node(KeyTrieNode::new(label, mapping, order))), + Some(_command) if !order.is_empty() => { + Err(serde::de::Error::custom("ambiguous mapping: 'command' is only valid with 'label', but I found other keys")) + } + Some(MappableCommand::Static { .. }) if !label.is_empty() => { + Err(serde::de::Error::custom("custom labels are only available for typable commands (the ones starting with ':')")) + } Some(MappableCommand::Typable { name, args, .. }) if !label.is_empty() => { Ok(KeyTrie::Leaf(MappableCommand::Typable { name, From 6d50d7fd68a5e16acba42b7b910c4b68cbdb471f Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Sat, 17 Dec 2022 15:33:55 +0100 Subject: [PATCH 009/105] remove one-liner solely used for a test --- helix-term/src/keymap.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 4a131f0a5217..60b9ff1c31c8 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -112,10 +112,6 @@ impl KeyTrieNode { } Info::from_keymap(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 { @@ -541,7 +537,7 @@ mod tests { ); // Make sure an order was set during merge let node = keymap.root().search(&[crate::key!(' ')]).unwrap(); - assert!(!node.node().unwrap().order().is_empty()) + assert!(!node.node().unwrap().order.as_slice().is_empty()) } #[test] From 37f49f6838e95b23ee64d5b63c827c8cfd9b469a Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Sat, 17 Dec 2022 19:55:30 +0100 Subject: [PATCH 010/105] Inline helper fuction of singular reference --- helix-term/src/config.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index 4407a882f838..f7cac6a36d10 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -28,7 +28,7 @@ impl Default for Config { } #[derive(Debug)] -pub enum ConfigLoadError { +pub enum ConfigLoadError { BadConfig(TomlError), Error(IOError), } @@ -43,18 +43,15 @@ impl Display for ConfigLoadError { } impl Config { - pub fn load(config_path: PathBuf) -> Result { - match std::fs::read_to_string(config_path) { + // REFACTOR? code similar to config assignment in main.rs, + pub fn load_default() -> Result { + match std::fs::read_to_string(helix_loader::config_file()) { Ok(config) => toml::from_str(&config) .map(merge_keys) .map_err(ConfigLoadError::BadConfig), Err(err) => Err(ConfigLoadError::Error(err)), } } - - pub fn load_default() -> Result { - Config::load(helix_loader::config_file()) - } } #[cfg(test)] From c748f7cb1d88d9652b45d8edb82343fa0adb1073 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Sat, 17 Dec 2022 21:55:05 +0100 Subject: [PATCH 011/105] Remove order property of KeyTrie struct: Was not implemented correctly as the order was in the most cases assigned from the values of an HashMap, which does not guarantee order. This applied to when a keymap was both deserialized and merged. Info box body was then being sorted as a function of the fallacious `order` property, and by a method that yielded at worst a time complexity of at least n^2. Furthermore, the body contents were inerted at first by the order of the hash map keys, in which `order` itself was based on. A more reliable predifined sorting order is to be implemented. --- helix-term/src/keymap.rs | 29 ++++------------------------- helix-term/src/keymap/macros.rs | 4 +--- 2 files changed, 5 insertions(+), 28 deletions(-) diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 60b9ff1c31c8..03ac5ce18269 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -24,7 +24,6 @@ pub struct KeyTrieNode { /// A label for keys coming under this node, like "Goto mode" name: String, map: HashMap, - order: Vec, pub is_sticky: bool, } @@ -33,22 +32,18 @@ impl<'de> Deserialize<'de> for KeyTrieNode { 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, + map: HashMap::::deserialize(deserializer)?, ..Default::default() }) } } impl KeyTrieNode { - pub fn new(name: &str, map: HashMap, order: Vec) -> Self { + pub fn new(name: &str, map: HashMap) -> Self { Self { name: name.to_string(), map, - order, is_sticky: false, } } @@ -70,11 +65,6 @@ impl KeyTrieNode { } 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 { @@ -97,12 +87,6 @@ impl KeyTrieNode { None => body.push((desc, BTreeSet::from([key]))), } } - body.sort_unstable_by_key(|(_, keys)| { - 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)) { body = body @@ -116,7 +100,7 @@ impl KeyTrieNode { impl Default for KeyTrieNode { fn default() -> Self { - Self::new("", HashMap::new(), Vec::new()) + Self::new("", HashMap::new()) } } @@ -195,12 +179,10 @@ impl<'de> serde::de::Visitor<'de> for KeyTrieVisitor { 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))) + Ok(KeyTrie::Node(KeyTrieNode::new("", mapping))) } } @@ -535,9 +517,6 @@ mod tests { &KeyTrie::Leaf(MappableCommand::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.as_slice().is_empty()) } #[test] diff --git a/helix-term/src/keymap/macros.rs b/helix-term/src/keymap/macros.rs index c4a1bfbb3064..1cf4151c9a38 100644 --- a/helix-term/src/keymap/macros.rs +++ b/helix-term/src/keymap/macros.rs @@ -101,7 +101,6 @@ macro_rules! keymap { { let _cap = hashmap!(@count $($($key),+),*); let mut _map = ::std::collections::HashMap::with_capacity(_cap); - let mut _order = ::std::vec::Vec::with_capacity(_cap); $( $( let _key = $key.parse::<::helix_view::input::KeyEvent>().unwrap(); @@ -110,10 +109,9 @@ macro_rules! keymap { keymap!(@trie $value) ); assert!(_duplicate.is_none(), "Duplicate key found: {:?}", _duplicate.unwrap()); - _order.push(_key); )+ )* - let mut _node = $crate::keymap::KeyTrieNode::new($label, _map, _order); + let mut _node = $crate::keymap::KeyTrieNode::new($label, _map); $( _node.is_sticky = $sticky; )? $crate::keymap::KeyTrie::Node(_node) } From 2c7c4d943bb4bb52225af218a60bdb62573420d4 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Sat, 17 Dec 2022 22:57:28 +0100 Subject: [PATCH 012/105] Infobox: Remove superflous command descripition pruning: Exist under the wrong (possibly just outdated) assumption that command descriptions are written with their KeyTrie name prefixed (Space, View, Goto etc.). For examle: The command `file_picker` is assumed to have the description "Space Open file picker", which is not the case , nor for any other command description. --- helix-term/src/keymap.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 03ac5ce18269..938e781210f9 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -87,13 +87,7 @@ impl KeyTrieNode { None => body.push((desc, BTreeSet::from([key]))), } } - let prefix = format!("{} ", self.name()); - if body.iter().all(|(desc, _)| desc.starts_with(&prefix)) { - body = body - .into_iter() - .map(|(desc, keys)| (desc.strip_prefix(&prefix).unwrap(), keys)) - .collect(); - } + Info::from_keymap(self.name(), body) } } From 8d35e64eea2235aca513aafe73178bb5a55467d9 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Sat, 17 Dec 2022 23:42:10 +0100 Subject: [PATCH 013/105] Keymap infobox: Use Vec in place of BTree: BTree was being used to store a list of keyevents for a given command. This list was only iterated over twice to in the end be converted to a Vec. Better to just use a Vec from start given the use- case. Temporalily reverts #952. --- helix-term/src/keymap.rs | 8 ++++---- helix-view/src/info.rs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 938e781210f9..b575ee97e9ff 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -11,7 +11,7 @@ use helix_view::{document::Mode, info::Info, input::KeyEvent}; use serde::Deserialize; use std::{ borrow::Cow, - collections::{BTreeSet, HashMap}, + collections::HashMap, ops::{Deref, DerefMut}, sync::Arc, }; @@ -68,7 +68,7 @@ impl KeyTrieNode { } pub fn infobox(&self) -> Info { - let mut body: Vec<(&str, BTreeSet)> = Vec::with_capacity(self.len()); + let mut body: Vec<(&str, Vec)> = Vec::with_capacity(self.len()); for (&key, trie) in self.iter() { let desc = match trie { KeyTrie::Leaf(cmd) => { @@ -82,9 +82,9 @@ impl KeyTrieNode { }; match body.iter().position(|(d, _)| d == &desc) { Some(pos) => { - body[pos].1.insert(key); + body[pos].1.push(key); } - None => body.push((desc, BTreeSet::from([key]))), + None => body.push((desc, Vec::from([key]))), } } diff --git a/helix-view/src/info.rs b/helix-view/src/info.rs index 3080cf8e1b2f..26dc844d75b1 100644 --- a/helix-view/src/info.rs +++ b/helix-view/src/info.rs @@ -1,6 +1,6 @@ use crate::input::KeyEvent; use helix_core::{register::Registers, unicode::width::UnicodeWidthStr}; -use std::{collections::BTreeSet, fmt::Write}; +use std::fmt::Write; #[derive(Debug)] /// Info box used in editor. Rendering logic will be in other crate. @@ -55,7 +55,7 @@ impl Info { } } - pub fn from_keymap(title: &str, body: Vec<(&str, BTreeSet)>) -> Self { + pub fn from_keymap(title: &str, body: Vec<(&str, Vec)>) -> Self { let body: Vec<_> = body .into_iter() .map(|(desc, events)| { From f187f2d5942c541c5a605136223b50b5fa6a08cd Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Sat, 17 Dec 2022 23:53:07 +0100 Subject: [PATCH 014/105] Keymap infobox: Place in correct order from start: Infobox body was being filled with description then KeyEvent list to only later be iterated over for the purpose of flippingin this order. --- helix-term/src/keymap.rs | 8 ++++---- helix-view/src/info.rs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index b575ee97e9ff..1a91d997e862 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -68,7 +68,7 @@ impl KeyTrieNode { } pub fn infobox(&self) -> Info { - let mut body: Vec<(&str, Vec)> = Vec::with_capacity(self.len()); + let mut body: Vec<(Vec, &str)> = Vec::with_capacity(self.len()); for (&key, trie) in self.iter() { let desc = match trie { KeyTrie::Leaf(cmd) => { @@ -80,11 +80,11 @@ impl KeyTrieNode { KeyTrie::Node(n) => n.name(), KeyTrie::Sequence(_) => "[Multiple commands]", }; - match body.iter().position(|(d, _)| d == &desc) { + match body.iter().position(|(_, d)| d == &desc) { Some(pos) => { - body[pos].1.push(key); + body[pos].0.push(key); } - None => body.push((desc, Vec::from([key]))), + None => body.push((Vec::from([key]), desc)), } } diff --git a/helix-view/src/info.rs b/helix-view/src/info.rs index 26dc844d75b1..fea8f535cd59 100644 --- a/helix-view/src/info.rs +++ b/helix-view/src/info.rs @@ -55,10 +55,10 @@ impl Info { } } - pub fn from_keymap(title: &str, body: Vec<(&str, Vec)>) -> Self { + pub fn from_keymap(title: &str, body: Vec<(Vec, &str)>) -> Self { let body: Vec<_> = body .into_iter() - .map(|(desc, events)| { + .map(|(events, desc)| { let events = events.iter().map(ToString::to_string).collect::>(); (events.join(", "), desc) }) From 2a0e9d20bf003f75cc34677d9d8d610e9fbe4c51 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Sun, 18 Dec 2022 00:56:15 +0100 Subject: [PATCH 015/105] Keymap infobox: Format body from start Vec was being created as an intermediary into Info.rs for it to be converted into a comma separated string. This commit removes this intermediate step but also the single purpose from_keymap public function in Info.rs, as it is no longer needed. --- helix-term/src/keymap.rs | 22 ++++++++++------------ helix-view/src/info.rs | 13 ------------- 2 files changed, 10 insertions(+), 25 deletions(-) diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 1a91d997e862..7ff4f83764f9 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -68,27 +68,25 @@ impl KeyTrieNode { } pub fn infobox(&self) -> Info { - let mut body: Vec<(Vec, &str)> = Vec::with_capacity(self.len()); + let mut body: Vec<(String, &str)> = Vec::with_capacity(self.len()); for (&key, trie) in self.iter() { - let desc = match trie { - KeyTrie::Leaf(cmd) => { - if cmd.name() == "no_op" { + let description: &str = match trie { + KeyTrie::Leaf(command) => { + if command.name() == "no_op" { continue; } - cmd.doc() + command.doc() } - KeyTrie::Node(n) => n.name(), + KeyTrie::Node(key_trie) => key_trie.name(), KeyTrie::Sequence(_) => "[Multiple commands]", }; - match body.iter().position(|(_, d)| d == &desc) { - Some(pos) => { - body[pos].0.push(key); - } - None => body.push((Vec::from([key]), desc)), + match body.iter().position(|(_, existing_description)| &description == existing_description) { + Some(pos) => body[pos].0 += &format!(", {}", &key.to_string()), + None => body.push((key.to_string(), description)), } } - Info::from_keymap(self.name(), body) + Info::new(self.name(), &body) } } diff --git a/helix-view/src/info.rs b/helix-view/src/info.rs index fea8f535cd59..1503e855e362 100644 --- a/helix-view/src/info.rs +++ b/helix-view/src/info.rs @@ -1,4 +1,3 @@ -use crate::input::KeyEvent; use helix_core::{register::Registers, unicode::width::UnicodeWidthStr}; use std::fmt::Write; @@ -55,18 +54,6 @@ impl Info { } } - pub fn from_keymap(title: &str, body: Vec<(Vec, &str)>) -> Self { - let body: Vec<_> = body - .into_iter() - .map(|(events, desc)| { - let events = events.iter().map(ToString::to_string).collect::>(); - (events.join(", "), desc) - }) - .collect(); - - Self::new(title, &body) - } - pub fn from_registers(registers: &Registers) -> Self { let body: Vec<_> = registers .inner() From faec82729a50236f394b046b5e4c11453b247a99 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Mon, 26 Dec 2022 17:19:33 +0100 Subject: [PATCH 016/105] keymap.rs file splitting, cleanup and descriptive naming: * Descriptive naming and correct use of data structure terminology example translations: keymap.reverse_map -> keymap.command_list keytrienode -> keytrie (struct) search->traverse merge->merge_keytrie keytrie -> keytrienode (enum describing node variants) Leaf -> MappableCommand Sequence -> CommandSequence Node -> KeyTrie (A leaf is a node, and the sequence was also a node. So old naming made absolutely no sense whatsoever.) * Splitting parts of the keymap.rs file into {keytrienode, keytrie, keymaps, tests}.rs. (Should be self-explanatory by looking at the old keymap.rs.) * Removed KeytrieNode enum functions node(&self) -> Option<&KeyTrie> and node_mut(&mut self) -> Option<&mut KeyTrie> from KeyTrieNode as they served no purpose that could not be used from elsewhere. Was also a bit strange to return a "parent struct" from an enum "building block". * Removed getters that could be achieved by making fields public for now. (E.g .root(), .map(), .name()) * Removed keymap.merge() and keytrienode.merge_nodes() All merging could be handled by keytrie.merge() and keymaps.merge_with_default(), unnecessary to have a second unused system that does about the same thing. We also don't want functions that can cause panics as merge_nodes() could. * Initial simplification and formatting of command palette. Formatting is done during the creation of the command lists, removes the need for the creation and and traversal of an intermediary Vec>. Keyevent formatting changed from "(w)" to ["space>w>C-q"]. Clarifying how commands are triggered by moving through submenus/keytries. --- helix-term/src/application.rs | 2 +- helix-term/src/commands.rs | 36 +- helix-term/src/config.rs | 4 +- helix-term/src/keymap.rs | 591 +++------------------------ helix-term/src/keymap.rs:41:29 | 0 helix-term/src/keymap/default.rs | 12 +- helix-term/src/keymap/keymaps.rs | 121 ++++++ helix-term/src/keymap/keytrie.rs | 129 ++++++ helix-term/src/keymap/keytrienode.rs | 72 ++++ helix-term/src/keymap/macros.rs | 44 +- helix-term/src/keymap/tests.rs | 181 ++++++++ helix-term/src/lib.rs | 1 + helix-term/src/ui/editor.rs | 6 +- 13 files changed, 602 insertions(+), 597 deletions(-) create mode 100644 helix-term/src/keymap.rs:41:29 create mode 100644 helix-term/src/keymap/keymaps.rs create mode 100644 helix-term/src/keymap/keytrie.rs create mode 100644 helix-term/src/keymap/keytrienode.rs create mode 100644 helix-term/src/keymap/tests.rs diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index c0cbc2451483..42daaa64d44a 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -24,7 +24,7 @@ use crate::{ compositor::{Compositor, Event}, config::Config, job::Jobs, - keymap::Keymaps, + keymap::keymaps::Keymaps, ui::{self, overlay::overlayed}, }; diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index e773a2c789d8..7e892d8606f6 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -46,7 +46,7 @@ use crate::{ args, compositor::{self, Component, Compositor}, job::Callback, - keymap::ReverseKeymap, + keymap::CommandList, ui::{self, overlay::overlayed, FilePicker, Picker, Popup, Prompt, PromptEvent}, }; @@ -2450,30 +2450,21 @@ fn jumplist_picker(cx: &mut Context) { cx.push_layer(Box::new(overlayed(picker))); } -impl ui::menu::Item for MappableCommand { - type Data = ReverseKeymap; - fn format(&self, keymap: &Self::Data) -> Row { - let fmt_binding = |bindings: &Vec>| -> String { - bindings.iter().fold(String::new(), |mut acc, bind| { - if !acc.is_empty() { - acc.push(' '); - } - for key in bind { - acc.push_str(&key.key_sequence_format()); - } - acc - }) - }; + +impl ui::menu::Item for MappableCommand { + type Data = CommandList; + + fn label(&self, keymap: &Self::Data) -> Spans { match self { MappableCommand::Typable { doc, name, .. } => match keymap.get(name as &String) { - Some(bindings) => format!("{} ({}) [:{}]", doc, fmt_binding(bindings), name).into(), - None => format!("{} [:{}]", doc, name).into(), + Some(key_events) => format!("{} {:?} ':{}'", doc, key_events, name).into(), + None => format!("{} ':{}'", doc, name).into(), }, MappableCommand::Static { doc, name, .. } => match keymap.get(*name) { - Some(bindings) => format!("{} ({}) [{}]", doc, fmt_binding(bindings), name).into(), - None => format!("{} [{}]", doc, name).into(), + Some(key_events) => format!("{} {:?} '{}'", doc, key_events, name).into(), + None => format!("{} '{}'", doc, name).into(), }, } } @@ -2482,9 +2473,9 @@ impl ui::menu::Item for MappableCommand { pub fn command_palette(cx: &mut Context) { cx.callback = Some(Box::new( move |compositor: &mut Compositor, cx: &mut compositor::Context| { - let keymap = compositor.find::().unwrap().keymaps.map() + let keymap_command_lists = compositor.find::().unwrap().keymaps.load_keymaps() [&cx.editor.mode] - .reverse_map(); + .command_list(); let mut commands: Vec = MappableCommand::STATIC_COMMAND_LIST.into(); commands.extend(typed::TYPABLE_COMMAND_LIST.iter().map(|cmd| { @@ -2495,7 +2486,7 @@ pub fn command_palette(cx: &mut Context) { } })); - let picker = Picker::new(commands, keymap, move |cx, command, _action| { + let picker = Picker::new(commands, keymap_command_lists, move |cx, command, _action| { let mut ctx = Context { register: None, count: std::num::NonZeroUsize::new(1), @@ -2524,6 +2515,7 @@ pub fn command_palette(cx: &mut Context) { compositor.push(Box::new(overlayed(picker))); }, )); + } fn last_picker(cx: &mut Context) { diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index f7cac6a36d10..471b8c168a79 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -1,4 +1,4 @@ -use crate::keymap::{default::default, merge_keys, Keymap}; +use crate::keymap::{default::default, Keymap, keymaps::Keymaps}; use helix_view::document::Mode; use serde::Deserialize; use std::collections::HashMap; @@ -47,7 +47,7 @@ impl Config { pub fn load_default() -> Result { match std::fs::read_to_string(helix_loader::config_file()) { Ok(config) => toml::from_str(&config) - .map(merge_keys) + .map(Keymaps::merge_with_default) .map_err(ConfigLoadError::BadConfig), Err(err) => Err(ConfigLoadError::Error(err)), } diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 7ff4f83764f9..edfa41df2705 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -1,570 +1,83 @@ +pub mod keymaps; +pub mod keytrienode; pub mod default; pub mod macros; +pub mod keytrie; +pub mod tests; -pub use crate::commands::MappableCommand; -use crate::config::Config; -use arc_swap::{ - access::{DynAccess, DynGuard}, - ArcSwap, -}; -use helix_view::{document::Mode, info::Info, input::KeyEvent}; use serde::Deserialize; -use std::{ - borrow::Cow, - collections::HashMap, - ops::{Deref, DerefMut}, - sync::Arc, -}; - -use default::default; -use macros::key; - -#[derive(Debug, Clone)] -pub struct KeyTrieNode { - /// A label for keys coming under this node, like "Goto mode" - name: String, - map: HashMap, - pub is_sticky: bool, -} - -impl<'de> Deserialize<'de> for KeyTrieNode { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - Ok(Self { - map: HashMap::::deserialize(deserializer)?, - ..Default::default() - }) - } -} - -impl KeyTrieNode { - pub fn new(name: &str, map: HashMap) -> Self { - Self { - name: name.to_string(), - map, - is_sticky: false, - } - } - - pub fn name(&self) -> &str { - &self.name - } - - /// 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); - } - } - - pub fn infobox(&self) -> Info { - let mut body: Vec<(String, &str)> = Vec::with_capacity(self.len()); - for (&key, trie) in self.iter() { - let description: &str = match trie { - KeyTrie::Leaf(command) => { - if command.name() == "no_op" { - continue; - } - command.doc() - } - KeyTrie::Node(key_trie) => key_trie.name(), - KeyTrie::Sequence(_) => "[Multiple commands]", - }; - match body.iter().position(|(_, existing_description)| &description == existing_description) { - Some(pos) => body[pos].0 += &format!(", {}", &key.to_string()), - None => body.push((key.to_string(), description)), - } - } - - Info::new(self.name(), &body) - } -} - -impl Default for KeyTrieNode { - fn default() -> Self { - Self::new("", HashMap::new()) - } -} - -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 { - Leaf(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::Leaf) - .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::<&str>()? { - 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(); - while let Some((key, value)) = map.next_entry::()? { - mapping.insert(key, value); - } - Ok(KeyTrie::Node(KeyTrieNode::new("", mapping))) - } -} - -impl KeyTrie { - pub fn node(&self) -> Option<&KeyTrieNode> { - match *self { - KeyTrie::Node(ref node) => Some(node), - KeyTrie::Leaf(_) | KeyTrie::Sequence(_) => None, - } - } - - pub fn node_mut(&mut self) -> Option<&mut KeyTrieNode> { - match *self { - KeyTrie::Node(ref mut node) => Some(node), - KeyTrie::Leaf(_) | 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::Leaf(_) | KeyTrie::Sequence(_) => None, - }? - } - Some(trie) +use std::{collections::HashMap, ops::{Deref, DerefMut}}; +use crate::{ + commands::MappableCommand, + keymap::{ + keytrie::KeyTrie, + keytrienode::KeyTrieNode } -} - -#[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), -} +}; #[derive(Debug, Clone, PartialEq, Deserialize)] #[serde(transparent)] +/// KeyTrie starting point. pub struct Keymap { - /// Always a Node - root: KeyTrie, + pub root_node: KeyTrie } -/// A map of command names to keybinds that will execute the command. -pub type ReverseKeymap = HashMap>>; - +pub type CommandList = HashMap>; impl Keymap { - pub fn new(root: KeyTrie) -> Self { - Keymap { root } - } - - pub fn reverse_map(&self) -> ReverseKeymap { - // recursively visit all nodes in keymap - fn map_node(cmd_map: &mut ReverseKeymap, node: &KeyTrie, keys: &mut Vec) { + pub fn new(root_node: KeyTrie) -> Self { + Keymap { root_node } + } + + /// Returns a key-value list of all commands associated to a given Keymap. + /// Keys are the node names (see KeyTrieNode documentation) + /// Values are lists of stringified KeyEvents that triger the command. + /// Each element in the KeyEvent list is prefixed with prefixed the ancestor KeyEvents. + /// For example: Stringified KeyEvent element for the 'goto_next_window' command could be "space>w>w". + /// Ancestor KeyEvents are in this case "space" and "w". + pub fn command_list(&self) -> CommandList { + let mut list = HashMap::new(); + _command_list(&mut list, &KeyTrieNode::KeyTrie(self.root_node.clone()), &mut String::new()); + return list; + + fn _command_list(list: &mut CommandList, node: &KeyTrieNode, prefix: &mut String) { match node { - KeyTrie::Leaf(cmd) => match cmd { - MappableCommand::Typable { name, .. } => { - cmd_map.entry(name.into()).or_default().push(keys.clone()) + KeyTrieNode::KeyTrie(trie_node) => { + for (key_event, subtrie_node) in trie_node.deref() { + let mut temp_prefix: String = prefix.to_string(); + if &temp_prefix != "" { + temp_prefix.push_str(">"); + } + temp_prefix.push_str(&key_event.to_string()); + _command_list(list, subtrie_node, &mut temp_prefix); } - MappableCommand::Static { name, .. } => cmd_map - .entry(name.to_string()) - .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(_) => {} + KeyTrieNode::MappableCommand(mappable_command) => { + list.entry(mappable_command.name().to_string()).or_default().push(prefix.to_string()); + }, + KeyTrieNode::CommandSequence(_) => {} }; } - - let mut res = HashMap::new(); - map_node(&mut res, &self.root, &mut Vec::new()); - res - } - - pub fn root(&self) -> &KeyTrie { - &self.root - } - - pub fn merge(&mut self, other: Self) { - self.root.merge_nodes(other.root); - } -} - -impl Deref for Keymap { - type Target = KeyTrieNode; - - fn deref(&self) -> &Self::Target { - self.root.node().unwrap() } } impl Default for Keymap { fn default() -> Self { - Self::new(KeyTrie::Node(KeyTrieNode::default())) + Self::new(KeyTrie::default()) } } -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.root), - }; - - let trie = match trie_node.search(&[*first]) { - Some(KeyTrie::Leaf(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::Leaf(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())))) - } -} +/// Returns the Keymap root KeyTrie node. +impl Deref for Keymap { + type Target = KeyTrie; -/// Merge default config keys with user overwritten keys for custom user config. -pub fn merge_keys(mut config: Config) -> Config { - let mut delta = std::mem::replace(&mut config.keys, default()); - for (mode, keys) in &mut config.keys { - keys.merge(delta.remove(mode).unwrap_or_default()) + fn deref(&self) -> &Self::Target { + &self.root_node } - config } -#[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 config = Config { - keys: 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 mut keymap = Keymaps::new(Box::new(Constant(merged_config.keys.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_config.keys.get_mut(&Mode::Normal).unwrap(); - // Assumes that `g` is a node in default keymap - assert_eq!( - keymap.root().search(&[key!('g'), key!('$')]).unwrap(), - &KeyTrie::Leaf(MappableCommand::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(MappableCommand::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(MappableCommand::goto_last_line), - "Old leaves in subnode should be present in merged node" - ); - - assert!(merged_config.keys.get(&Mode::Normal).unwrap().len() > 1); - assert!(merged_config.keys.get(&Mode::Insert).unwrap().len() > 0); - } - - #[test] - fn order_should_be_set() { - let config = Config { - keys: 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.get_mut(&Mode::Normal).unwrap(); - // Make sure mapping works - assert_eq!( - keymap - .root() - .search(&[key!(' '), key!('s'), key!('v')]) - .unwrap(), - &KeyTrie::Leaf(MappableCommand::vsplit), - "Leaf should be present in merged subnode" - ); - } - - #[test] - fn aliased_modes_are_same_in_default_keymap() { - let keymaps = Keymaps::default().map(); - let root = keymaps.get(&Mode::Normal).unwrap().root(); - 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 = Keymap::new(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" - ) +/// Returns the Keymap root KeyTrie node. +impl DerefMut for Keymap { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.root_node } } diff --git a/helix-term/src/keymap.rs:41:29 b/helix-term/src/keymap.rs:41:29 new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/helix-term/src/keymap/default.rs b/helix-term/src/keymap/default.rs index ef93dee08a77..7ce2736021b4 100644 --- a/helix-term/src/keymap/default.rs +++ b/helix-term/src/keymap/default.rs @@ -1,11 +1,13 @@ use std::collections::HashMap; use super::macros::keymap; -use super::{Keymap, Mode}; +use super::Keymap; +use super::keytrie::KeyTrie; +use helix_view::document::Mode; use helix_core::hashmap; pub fn default() -> HashMap { - let normal = keymap!({ "Normal mode" + let normal: KeyTrie = keymap!({ "Normal mode" "h" | "left" => move_char_left, "j" | "down" => move_line_down, "k" | "up" => move_line_up, @@ -317,8 +319,8 @@ pub fn default() -> HashMap { "C-a" => increment, "C-x" => decrement, }); - let mut select = normal.clone(); - select.merge_nodes(keymap!({ "Select mode" + let mut select: KeyTrie = normal.clone(); + select.merge_keytrie(keymap!({ "Select mode" "h" | "left" => extend_char_left, "j" | "down" => extend_line_down, "k" | "up" => extend_line_up, @@ -345,7 +347,7 @@ pub fn default() -> HashMap { "v" => normal_mode, })); - let insert = keymap!({ "Insert mode" + let insert: KeyTrie = keymap!({ "Insert mode" "esc" => normal_mode, "C-s" => commit_undo_checkpoint, diff --git a/helix-term/src/keymap/keymaps.rs b/helix-term/src/keymap/keymaps.rs new file mode 100644 index 000000000000..fe03af6d48c6 --- /dev/null +++ b/helix-term/src/keymap/keymaps.rs @@ -0,0 +1,121 @@ +use std::{sync::Arc, collections::HashMap}; +use arc_swap::{access::{DynAccess, DynGuard}, ArcSwap}; +use helix_view::{document::Mode, input::KeyEvent}; +use crate::commands::MappableCommand; +use crate::config::Config; +use super::{macros::key, keytrienode::KeyTrieNode, default}; +use super::keytrie::KeyTrie; +use super::Keymap; +use super::default::default; + +#[derive(Debug, Clone, PartialEq)] +pub enum KeymapResult { + Pending(KeyTrie), + Matched(MappableCommand), + MatchedCommandSequence(Vec), + NotFound, + /// Contains pressed KeyEvents leading up to the cancellation. + Cancelled(Vec), +} + +pub struct Keymaps { + pub keymaps: Box>>, + /// Relative to a sticky node if Some. + pending_keys: Vec, + pub sticky_keytrie: Option, +} + +impl Keymaps { + pub fn new(keymaps: Box>>) -> Self { + Self { + keymaps, + pending_keys: Vec::new(), + sticky_keytrie: None, + } + } + + pub fn load_keymaps(&self) -> DynGuard> { + self.keymaps.load() + } + + /// Returns list of keys waiting to be disambiguated in current mode. + pub fn pending(&self) -> &[KeyEvent] { + &self.pending_keys + } + + pub fn sticky_keytrie(&self) -> Option<&KeyTrie> { + self.sticky_keytrie.as_ref() + } + + pub fn merge_with_default(mut config: Config) -> Config { + let mut delta = std::mem::replace(&mut config.keys, default::default()); + for (mode, keys) in &mut config.keys { + keys.merge_keytrie(delta.remove(mode).unwrap_or_default().root_node) + } + config + } + + /// Lookup `key` in the keymap to try and find a command to execute. + /// Escape key represents cancellation. + /// This means clearing pending keystrokes, or the sticky_keytrie if none were present. + pub fn get(&mut self, mode: Mode, key: KeyEvent) -> KeymapResult { + // TODO: remove the sticky part and look up manually + let keymaps = &*self.load_keymaps(); + let active_keymap = &keymaps[&mode]; + + if key == key!(Esc) { + if !self.pending_keys.is_empty() { + // NOTE: Esc is not included here + return KeymapResult::Cancelled(self.pending_keys.drain(..).collect()); + } + // TODO: Shouldn't we return here also? + self.sticky_keytrie = None; + } + + // Check if sticky keytrie is to be used. + let starting_keytrie = match self.sticky_keytrie { + None => &active_keymap.root_node, + Some(ref active_sticky_keytrie) => active_sticky_keytrie, + }; + + // TODO: why check either pending or regular key? + let first_key = self.pending_keys.get(0).unwrap_or(&key); + + let pending_keytrie: KeyTrie = match starting_keytrie.traverse(&[*first_key]) { + Some(KeyTrieNode::KeyTrie(sub_keytrie)) => sub_keytrie, + Some(KeyTrieNode::MappableCommand(cmd)) => { + return KeymapResult::Matched(cmd.clone()); + } + Some(KeyTrieNode::CommandSequence(cmds)) => { + return KeymapResult::MatchedCommandSequence(cmds.clone()); + } + None => return KeymapResult::NotFound, + }; + + self.pending_keys.push(key); + match pending_keytrie.traverse(&self.pending_keys[1..]) { + Some(KeyTrieNode::KeyTrie(map)) => { + if map.is_sticky { + self.pending_keys.clear(); + self.sticky_keytrie = Some(map.clone()); + } + KeymapResult::Pending(map.clone()) + } + Some(KeyTrieNode::MappableCommand(cmd)) => { + self.pending_keys.clear(); + KeymapResult::Matched(cmd.clone()) + } + Some(KeyTrieNode::CommandSequence(cmds)) => { + self.pending_keys.clear(); + KeymapResult::MatchedCommandSequence(cmds.clone()) + } + None => KeymapResult::Cancelled(self.pending_keys.drain(..).collect()), + } + } +} + +impl Default for Keymaps { + fn default() -> Self { + Self::new(Box::new(ArcSwap::new(Arc::new(default())))) + } +} diff --git a/helix-term/src/keymap/keytrie.rs b/helix-term/src/keymap/keytrie.rs new file mode 100644 index 000000000000..ae5ecb58f12d --- /dev/null +++ b/helix-term/src/keymap/keytrie.rs @@ -0,0 +1,129 @@ +use serde::Deserialize; +use std::{collections::HashMap, ops::{Deref, DerefMut}}; +use helix_view::{info::Info, input::KeyEvent}; +use super::keytrienode::KeyTrieNode; + +/// Edges of the trie are KeyEvents and the nodes are descrbibed by KeyTrieNode +#[derive(Debug, Clone)] +pub struct KeyTrie { + documentation: String, + children: HashMap, + pub is_sticky: bool, +} + +impl KeyTrie { + pub fn new(documentation: &str, children: HashMap) -> Self { + Self { + documentation: documentation.to_string(), + children, + is_sticky: false, + } + } + + // None symbolizes NotFound + pub fn traverse(&self, key_events: &[KeyEvent]) -> Option { + return _traverse(self, key_events, 0); + + fn _traverse(keytrie: &KeyTrie, key_events: &[KeyEvent], mut depth: usize) -> Option { + if depth == key_events.len() { + return Some(KeyTrieNode::KeyTrie(keytrie.clone())); + } + else if let Some(found_child) = keytrie.get(&key_events[depth]) { + match found_child { + KeyTrieNode::KeyTrie(sub_keytrie) => { + depth += 1; + return _traverse(sub_keytrie, key_events, depth) + }, + _ => return Some(found_child.clone()) + } + } + return None; + } + } + + pub fn merge_keytrie(&mut self, mut other_keytrie: Self) { + for (other_key_event, other_child_node) in std::mem::take(&mut other_keytrie.children) { + match other_child_node { + KeyTrieNode::KeyTrie(other_child_key_trie) => { + if let Some(KeyTrieNode::KeyTrie(self_clashing_child_key_trie)) = self.children.get_mut(&other_key_event) { + self_clashing_child_key_trie.merge_keytrie(other_child_key_trie); + } + else { + self.children.insert(other_key_event, KeyTrieNode::KeyTrie(other_child_key_trie)); + } + } + KeyTrieNode::MappableCommand(_) | KeyTrieNode::CommandSequence(_) => { + self.children.insert(other_key_event, other_child_node); + } + } + } + } + + /// Open an Info box for a given KeyTrie + /// Shows the children listed by possible KeyEvents + /// and thier associated documentation. + pub fn infobox(&self) -> Info { + let mut body: Vec<(String, &str)> = Vec::with_capacity(self.len()); + for (&key_event, key_trie) in self.iter() { + let documentation: &str = match key_trie { + KeyTrieNode::MappableCommand(command) => { + if command.name() == "no_op" { + continue; + } + command.doc() + }, + KeyTrieNode::KeyTrie(key_trie) => &key_trie.documentation, + // FIX: default to a join of all command names + // NOTE: Giving same documentation for all sequences will place all sequence keyvents together. + // Regardless if the command sequence is different. + KeyTrieNode::CommandSequence(_) => "[Multiple commands]", + }; + match body.iter().position(|(_, existing_documentation)| &documentation == existing_documentation) { + Some(position) => body[position].0 += &format!(", {}", &key_event.to_string()), + None => body.push((key_event.to_string(), documentation)), + } + } + + Info::new(&self.documentation, &body) + } +} + +impl Default for KeyTrie { + fn default() -> Self { + Self::new("", HashMap::new()) + } +} + +impl PartialEq for KeyTrie { + fn eq(&self, other: &Self) -> bool { + self.children == other.children + } +} + +/// Returns the children of the KeyTrie +impl Deref for KeyTrie { + type Target = HashMap; + + fn deref(&self) -> &Self::Target { + &self.children + } +} + +/// Returns the children of the KeyTrie +impl DerefMut for KeyTrie { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.children + } +} + +impl<'de> Deserialize<'de> for KeyTrie { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + Ok(Self { + children: HashMap::::deserialize(deserializer)?, + ..Default::default() + }) + } +} diff --git a/helix-term/src/keymap/keytrienode.rs b/helix-term/src/keymap/keytrienode.rs new file mode 100644 index 000000000000..a8a0ebba384d --- /dev/null +++ b/helix-term/src/keymap/keytrienode.rs @@ -0,0 +1,72 @@ +use std::collections::HashMap; +use super::MappableCommand; +use serde::Deserialize; +use serde::de::Visitor; +use super::keytrie::KeyTrie; +use helix_view::input::KeyEvent; + +/// Each variant includes a documentaion property. +/// For the MappableCommand and CommandSequence variants, the property is self explanatory. +/// For KeyTrie, the documentation is used for respective infobox titles, +/// or infobox KeyEvent descriptions that in themselves trigger the opening of another infobox. +#[derive(Debug, Clone, PartialEq)] +pub enum KeyTrieNode { + MappableCommand(MappableCommand), + CommandSequence(Vec), + KeyTrie(KeyTrie), +} + +impl<'de> Deserialize<'de> for KeyTrieNode { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_any(KeyTrieNodeVisitor) + } +} + +struct KeyTrieNodeVisitor; + +impl<'de> Visitor<'de> for KeyTrieNodeVisitor { + type Value = KeyTrieNode; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(formatter, "a KeyTrieNode") + } + + fn visit_str(self, command: &str) -> Result + where + E: serde::de::Error, + { + command + .parse::() + .map(KeyTrieNode::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::<&str>()? { + commands.push( + command + .parse::() + .map_err(serde::de::Error::custom)?, + ) + } + Ok(KeyTrieNode::CommandSequence(commands)) + } + + fn visit_map(self, mut map: M) -> Result + where + M: serde::de::MapAccess<'de>, + { + let mut sub_key_trie = HashMap::new(); + while let Some((key_event, key_trie_node)) = map.next_entry::()? { + sub_key_trie.insert(key_event, key_trie_node); + } + Ok(KeyTrieNode::KeyTrie(KeyTrie::new("", sub_key_trie))) + } +} \ No newline at end of file diff --git a/helix-term/src/keymap/macros.rs b/helix-term/src/keymap/macros.rs index 1cf4151c9a38..4d81d220ffb9 100644 --- a/helix-term/src/keymap/macros.rs +++ b/helix-term/src/keymap/macros.rs @@ -80,42 +80,36 @@ macro_rules! alt { /// ``` #[macro_export] macro_rules! keymap { - (@trie $cmd:ident) => { - $crate::keymap::KeyTrie::Leaf($crate::commands::MappableCommand::$cmd) - }; - - (@trie - { $label:literal $(sticky=$sticky:literal)? $($($key:literal)|+ => $value:tt,)+ } - ) => { - keymap!({ $label $(sticky=$sticky)? $($($key)|+ => $value,)+ }) - }; - - (@trie [$($cmd:ident),* $(,)?]) => { - $crate::keymap::KeyTrie::Sequence(vec![$($crate::commands::Command::$cmd),*]) - }; - - ( - { $label:literal $(sticky=$sticky:literal)? $($($key:literal)|+ => $value:tt,)+ } - ) => { + ({ $label:literal $(sticky=$sticky:literal)? $($($key:literal)|+ => $value:tt,)+ }) => { // modified from the hashmap! macro { let _cap = hashmap!(@count $($($key),+),*); - let mut _map = ::std::collections::HashMap::with_capacity(_cap); + let mut _map: ::std::collections::HashMap<::helix_view::input::KeyEvent, $crate::keymap::keytrienode::KeyTrieNode> = + ::std::collections::HashMap::with_capacity(_cap); $( $( let _key = $key.parse::<::helix_view::input::KeyEvent>().unwrap(); - let _duplicate = _map.insert( - _key, - keymap!(@trie $value) - ); - assert!(_duplicate.is_none(), "Duplicate key found: {:?}", _duplicate.unwrap()); + let _potential_duplicate = _map.insert(_key,keymap!(@trie $value)); + assert!(_potential_duplicate.is_none(), "Duplicate key found: {:?}", _potential_duplicate.unwrap()); )+ )* - let mut _node = $crate::keymap::KeyTrieNode::new($label, _map); + let mut _node = $crate::keymap::keytrie::KeyTrie::new($label, _map); $( _node.is_sticky = $sticky; )? - $crate::keymap::KeyTrie::Node(_node) + _node } }; + + (@trie {$label:literal $(sticky=$sticky:literal)? $($($key:literal)|+ => $value:tt,)+ }) => { + $crate::keymap::keytrienode::KeyTrieNode::KeyTrie(keymap!({ $label $(sticky=$sticky)? $($($key)|+ => $value,)+ })) + }; + + (@trie $cmd:ident) => { + $crate::keymap::keytrienode::KeyTrieNode::MappableCommand($crate::commands::MappableCommand::$cmd) + }; + + (@trie [$($cmd:ident),* $(,)?]) => { + $crate::keymap::keytrienode::KeyTrieNode::CommandSequence(vec![$($crate::commands::Command::$cmd),*]) + }; } pub use alt; diff --git a/helix-term/src/keymap/tests.rs b/helix-term/src/keymap/tests.rs new file mode 100644 index 000000000000..55420d1f5f14 --- /dev/null +++ b/helix-term/src/keymap/tests.rs @@ -0,0 +1,181 @@ +#[macro_use] +#[cfg(test)] +mod tests { + use arc_swap::access::Constant; + use helix_core::hashmap; + use helix_view::{document::Mode, input::KeyEvent}; + use crate::config::Config; + use crate::commands::MappableCommand; + use crate::keymap::*; + use keymaps::{Keymaps, KeymapResult}; + use macros::*; + + #[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 config = Config { + keys: 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 mut keymap = Keymaps::new(Box::new(Constant(merged_config.keys.clone()))); + assert_eq!( + keymap.get(Mode::Normal, key!('i')), + KeymapResult::Matched(MappableCommand::normal_mode), + "New mappable command should ovveride default." + ); + assert_eq!( + keymap.get(Mode::Normal, key!('无')), + KeymapResult::Matched(MappableCommand::insert_mode), + "New mappable command 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), + "New Mappable command should replace default sub keytrie." + ); + + let keymap = merged_config.keys.get_mut(&Mode::Normal).unwrap(); + // Assumes that `g` is a node in default keymap + assert_eq!( + keymap.root_node.traverse(&[key!('g'), key!('$')]).unwrap(), + KeyTrieNode::MappableCommand(MappableCommand::goto_line_end), + "Mappable command should be present in merged keytrie." + ); + // Assumes that `gg` is in default keymap + assert_eq!( + keymap.root_node.traverse(&[key!('g'), key!('g')]).unwrap(), + KeyTrieNode::MappableCommand(MappableCommand::delete_char_forward), + "Mappable command should replace default in merged keytrie." + ); + // Assumes that `ge` is in default keymap + assert_eq!( + keymap.root_node.traverse(&[key!('g'), key!('e')]).unwrap(), + KeyTrieNode::MappableCommand(MappableCommand::goto_last_line), + "Mappable commands from default keytrie should still be present in merged merged keytrie unless overridden." + ); + + assert!(merged_config.keys.get(&Mode::Normal).unwrap().len() > 1); + assert!(merged_config.keys.get(&Mode::Insert).unwrap().len() > 0); + } + + #[test] + fn order_should_be_set() { + let config = Config { + keys: 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_normal = merged_config.keys.get_mut(&Mode::Normal).unwrap(); + // Make sure mapping works + assert_eq!( + keymap_normal + .root_node + .traverse(&[key!(' '), key!('s'), key!('v')]) + .unwrap(), + KeyTrieNode::MappableCommand(MappableCommand::vsplit), + "Mappable command should be present in merged keytrie." + ); + } + + #[test] + fn aliased_modes_are_same_in_default_keymap() { + let keymaps = Keymaps::default().keymaps; + let root = keymaps.load().get(&Mode::Normal).unwrap().root_node.clone(); + assert_eq!( + root.traverse(&[key!(' '), key!('w')]).unwrap(), + root.traverse(&["C-w".parse::().unwrap()]).unwrap(), + "Mismatch for window mode on `Space-w` and `Ctrl-w`." + ); + assert_eq!( + root.traverse(&[key!('z')]).unwrap(), + root.traverse(&[key!('Z')]).unwrap(), + "Mismatch for view mode on `z` and `Z`." + ); + } + + #[test] + fn command_list() { + 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 = Keymap::new(normal_mode); + let mut command_list = keymap.command_list(); + + // 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 command_list.values_mut() { + v.sort() + } + + assert_eq!( + command_list, + HashMap::from([ + ( + "insert_mode".to_string(), + vec![key!('i').to_string()] + ), + ( + "goto_file_start".to_string(), + vec![format!("{}>{}", key!('g'), key!('g'))] + ), + ( + "goto_file_end".to_string(), + vec![format!("{}>{}", key!('g'), key!('e'))] + ), + ( + "move_line_down".to_string(), + vec![key!('j').to_string(), key!('k').to_string()] + ) + ]), + "Mismatch" + ) + } +} \ No newline at end of file diff --git a/helix-term/src/lib.rs b/helix-term/src/lib.rs index a945b20dedaf..58ae12c2e120 100644 --- a/helix-term/src/lib.rs +++ b/helix-term/src/lib.rs @@ -11,6 +11,7 @@ pub mod job; pub mod keymap; pub mod ui; pub use keymap::macros::*; +pub use keymap::tests; #[cfg(not(windows))] fn true_color() -> bool { diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index a0518964e2a7..bd5d99fbcbaa 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -3,7 +3,7 @@ use crate::{ compositor::{Component, Context, Event, EventResult}, job::{self, Callback}, key, - keymap::{KeymapResult, Keymaps}, + keymap::keymaps::{KeymapResult, Keymaps}, ui::{Completion, ProgressSpinners}, }; @@ -936,7 +936,7 @@ impl EditorView { let mut last_mode = mode; self.pseudo_pending.extend(self.keymaps.pending()); let key_result = self.keymaps.get(mode, event); - cxt.editor.autoinfo = self.keymaps.sticky().map(|node| node.infobox()); + cxt.editor.autoinfo = self.keymaps.sticky_keytrie().map(|node| node.infobox()); let mut execute_command = |command: &commands::MappableCommand| { command.execute(cxt); @@ -976,7 +976,7 @@ impl EditorView { execute_command(command); } KeymapResult::Pending(node) => cxt.editor.autoinfo = Some(node.infobox()), - KeymapResult::MatchedSequence(commands) => { + KeymapResult::MatchedCommandSequence(commands) => { for command in commands { execute_command(command); } From d5520e84c7d4ab1c8265dbcbb000ee35d0ebea7b Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Mon, 26 Dec 2022 19:08:52 +0100 Subject: [PATCH 017/105] Rename MappableCommand field doc to description: For it not to be confused with the upcoming ":help" feature. --- helix-term/src/commands.rs | 24 ++++++++++++------------ helix-term/src/keymap/keytrie.rs | 5 ++--- helix-term/src/main.rs | 2 +- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 7e892d8606f6..b4f64296852c 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -133,23 +133,23 @@ pub enum MappableCommand { Typable { name: String, args: Vec, - doc: String, + description: String, }, Static { name: &'static str, fun: fn(cx: &mut Context), - doc: &'static str, + description: &'static str, }, } macro_rules! static_commands { - ( $($name:ident, $doc:literal,)* ) => { + ( $($name:ident, $description:literal,)* ) => { $( #[allow(non_upper_case_globals)] pub const $name: Self = Self::Static { name: stringify!($name), fun: $name, - doc: $doc + description: $description }; )* @@ -162,7 +162,7 @@ macro_rules! static_commands { impl MappableCommand { pub fn execute(&self, cx: &mut Context) { match &self { - Self::Typable { name, args, doc: _ } => { + Self::Typable { name, args, description: _ } => { let args: Vec> = args.iter().map(Cow::from).collect(); if let Some(command) = typed::TYPABLE_COMMAND_MAP.get(name.as_str()) { let mut cx = compositor::Context { @@ -186,10 +186,10 @@ impl MappableCommand { } } - pub fn doc(&self) -> &str { + pub fn description(&self) -> &str { match &self { - Self::Typable { doc, .. } => doc, - Self::Static { doc, .. } => doc, + Self::Typable { description, .. } => description, + Self::Static { description, .. } => description, } } @@ -476,7 +476,7 @@ impl std::str::FromStr for MappableCommand { .get(name) .map(|cmd| MappableCommand::Typable { name: cmd.name.to_owned(), - doc: format!(":{} {:?}", cmd.name, args), + description: format!(":{} {:?}", cmd.name, args), args, }) .ok_or_else(|| anyhow!("No TypableCommand named '{}'", s)) @@ -2458,11 +2458,11 @@ impl ui::menu::Item for MappableCommand { fn label(&self, keymap: &Self::Data) -> Spans { match self { - MappableCommand::Typable { doc, name, .. } => match keymap.get(name as &String) { + MappableCommand::Typable { description: doc, name, .. } => match keymap.get(name as &String) { Some(key_events) => format!("{} {:?} ':{}'", doc, key_events, name).into(), None => format!("{} ':{}'", doc, name).into(), }, - MappableCommand::Static { doc, name, .. } => match keymap.get(*name) { + MappableCommand::Static { description: doc, name, .. } => match keymap.get(*name) { Some(key_events) => format!("{} {:?} '{}'", doc, key_events, name).into(), None => format!("{} '{}'", doc, name).into(), }, @@ -2481,7 +2481,7 @@ pub fn command_palette(cx: &mut Context) { commands.extend(typed::TYPABLE_COMMAND_LIST.iter().map(|cmd| { MappableCommand::Typable { name: cmd.name.to_owned(), - doc: cmd.doc.to_owned(), + description: cmd.doc.to_owned(), args: Vec::new(), } })); diff --git a/helix-term/src/keymap/keytrie.rs b/helix-term/src/keymap/keytrie.rs index ae5ecb58f12d..25ab0b196afe 100644 --- a/helix-term/src/keymap/keytrie.rs +++ b/helix-term/src/keymap/keytrie.rs @@ -60,8 +60,7 @@ impl KeyTrie { } /// Open an Info box for a given KeyTrie - /// Shows the children listed by possible KeyEvents - /// and thier associated documentation. + /// Shows the children as possible KeyEvents and thier associated description. pub fn infobox(&self) -> Info { let mut body: Vec<(String, &str)> = Vec::with_capacity(self.len()); for (&key_event, key_trie) in self.iter() { @@ -70,7 +69,7 @@ impl KeyTrie { if command.name() == "no_op" { continue; } - command.doc() + command.description() }, KeyTrieNode::KeyTrie(key_trie) => &key_trie.documentation, // FIX: default to a join of all command names diff --git a/helix-term/src/main.rs b/helix-term/src/main.rs index aac5c5379f37..bae827e30402 100644 --- a/helix-term/src/main.rs +++ b/helix-term/src/main.rs @@ -128,7 +128,7 @@ FLAGS: let config = match std::fs::read_to_string(helix_loader::config_file()) { Ok(config) => toml::from_str(&config) - .map(helix_term::keymap::merge_keys) + .map(helix_term::keymap::keymaps::Keymaps::merge_with_default) .unwrap_or_else(|err| { eprintln!("Bad config: {}", err); eprintln!("Press to continue with default config"); From aea10f70513f155135725853dbf984e2e9f7e8e2 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Mon, 26 Dec 2022 22:44:50 +0100 Subject: [PATCH 018/105] Initial sorting of keymap info window --- helix-term/src/keymap/keytrie.rs | 39 ++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/helix-term/src/keymap/keytrie.rs b/helix-term/src/keymap/keytrie.rs index 25ab0b196afe..8020b6caafbf 100644 --- a/helix-term/src/keymap/keytrie.rs +++ b/helix-term/src/keymap/keytrie.rs @@ -62,7 +62,7 @@ impl KeyTrie { /// Open an Info box for a given KeyTrie /// Shows the children as possible KeyEvents and thier associated description. pub fn infobox(&self) -> Info { - let mut body: Vec<(String, &str)> = Vec::with_capacity(self.len()); + let mut body: Vec<(Vec, &str)> = Vec::with_capacity(self.len()); for (&key_event, key_trie) in self.iter() { let documentation: &str = match key_trie { KeyTrieNode::MappableCommand(command) => { @@ -78,12 +78,43 @@ impl KeyTrie { KeyTrieNode::CommandSequence(_) => "[Multiple commands]", }; match body.iter().position(|(_, existing_documentation)| &documentation == existing_documentation) { - Some(position) => body[position].0 += &format!(", {}", &key_event.to_string()), - None => body.push((key_event.to_string(), documentation)), + Some(position) => body[position].0.push(key_event.to_string()), + None => { + let mut temp_vec: Vec = Vec::new(); + temp_vec.push(key_event.to_string()); + body.push((temp_vec, documentation)) + }, } } + // OPTIMIZATIONS? + // Change the children hashmap to an ordered datastructure? Like a regulap map maybe? + // Add sorted conditional? + // Add body as a keytrie field and reload it in keytrie merge? + + // Make the shortest keyevents appear first + let mut sorted_body = body + .iter() + .map(|(key_events, description)| { + let mut temp_key_events = key_events.clone(); + temp_key_events.sort_unstable_by(|a, b| a.len().cmp(&b.len())); + (temp_key_events, *description) + }) + .collect::, &str)>>(); + sorted_body.sort_unstable_by(|a, b| a.0[0].to_lowercase().cmp(&b.0[0].to_lowercase())); + + let stringified_key_events_body: Vec<(String, &str)> = sorted_body + .iter() + .map(|(key_events, description)| { + let key_events_string: String = key_events.iter().fold(String::new(), |mut acc, key_event| { + if acc.is_empty() { acc.push_str(key_event); } + else { acc.push_str(&format!(", {}", key_event)) } + acc + }); + (key_events_string, *description) + }) + .collect(); - Info::new(&self.documentation, &body) + Info::new(&self.documentation, &stringified_key_events_body) } } From 7f86d01407840a7e1d9a9fda62279af16c80df88 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Tue, 27 Dec 2022 12:08:12 +0100 Subject: [PATCH 019/105] Infobox: Consistently place lowercase equivalents first This, along with previous commit reverts regression in #952 --- helix-term/src/keymap/keytrie.rs | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/helix-term/src/keymap/keytrie.rs b/helix-term/src/keymap/keytrie.rs index 8020b6caafbf..e49b33d501b1 100644 --- a/helix-term/src/keymap/keytrie.rs +++ b/helix-term/src/keymap/keytrie.rs @@ -91,7 +91,7 @@ impl KeyTrie { // Add sorted conditional? // Add body as a keytrie field and reload it in keytrie merge? - // Make the shortest keyevents appear first + // Make the shortest keyevent appear first let mut sorted_body = body .iter() .map(|(key_events, description)| { @@ -102,6 +102,27 @@ impl KeyTrie { .collect::, &str)>>(); sorted_body.sort_unstable_by(|a, b| a.0[0].to_lowercase().cmp(&b.0[0].to_lowercase())); + // Consistently place lowercase before uppercase of the same letter. + if sorted_body.len() > 1 { + let mut x_index = 0; + let mut y_index = 1; + + while y_index < sorted_body.len() { + let x = &sorted_body[x_index].0[0]; + let y = &sorted_body[y_index].0[0]; + if x.to_lowercase() == y.to_lowercase() { + // Uppercase regarded as lower value. + if x < y { + let temp_holder = sorted_body[x_index].clone(); + sorted_body[x_index] = sorted_body[y_index].clone(); + sorted_body[y_index] = temp_holder; + } + } + x_index = y_index; + y_index += 1; + } + } + let stringified_key_events_body: Vec<(String, &str)> = sorted_body .iter() .map(|(key_events, description)| { From 7935f77a98afcfe913b98148cc6c219532a6e18d Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Tue, 27 Dec 2022 13:15:46 +0100 Subject: [PATCH 020/105] Remove infobox optimization suggestion comment: Sort duration calculations averaged to about 0.04 ms when opening the larger infoboxes. --- helix-term/src/keymap/keytrie.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/helix-term/src/keymap/keytrie.rs b/helix-term/src/keymap/keytrie.rs index e49b33d501b1..fa115770ed83 100644 --- a/helix-term/src/keymap/keytrie.rs +++ b/helix-term/src/keymap/keytrie.rs @@ -86,10 +86,6 @@ impl KeyTrie { }, } } - // OPTIMIZATIONS? - // Change the children hashmap to an ordered datastructure? Like a regulap map maybe? - // Add sorted conditional? - // Add body as a keytrie field and reload it in keytrie merge? // Make the shortest keyevent appear first let mut sorted_body = body @@ -101,7 +97,6 @@ impl KeyTrie { }) .collect::, &str)>>(); sorted_body.sort_unstable_by(|a, b| a.0[0].to_lowercase().cmp(&b.0[0].to_lowercase())); - // Consistently place lowercase before uppercase of the same letter. if sorted_body.len() > 1 { let mut x_index = 0; From 0482d097337220d78f496ce7e28810ca5b501bf4 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Tue, 27 Dec 2022 13:50:05 +0100 Subject: [PATCH 021/105] keymap testing touchups --- helix-term/src/keymap/keytrie.rs | 3 ++- helix-term/src/keymap/tests.rs | 5 ++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/helix-term/src/keymap/keytrie.rs b/helix-term/src/keymap/keytrie.rs index fa115770ed83..89c441e2974a 100644 --- a/helix-term/src/keymap/keytrie.rs +++ b/helix-term/src/keymap/keytrie.rs @@ -87,7 +87,8 @@ impl KeyTrie { } } - // Make the shortest keyevent appear first + // Shortest keyevent (as string) appears first, unless is a "C-" KeyEvent + // Those events will always be placed after the one letter KeyEvent let mut sorted_body = body .iter() .map(|(key_events, description)| { diff --git a/helix-term/src/keymap/tests.rs b/helix-term/src/keymap/tests.rs index 55420d1f5f14..557debb94cb1 100644 --- a/helix-term/src/keymap/tests.rs +++ b/helix-term/src/keymap/tests.rs @@ -89,7 +89,7 @@ mod tests { } #[test] - fn order_should_be_set() { + fn merges_with_default_keymap_config() { let config = Config { keys: hashmap! { Mode::Normal => Keymap::new( @@ -105,10 +105,9 @@ mod tests { }, ..Default::default() }; - let mut merged_config = merge_keys(config.clone()); + let mut merged_config = Keymaps::merge_with_default(config.clone()); assert_ne!(config, merged_config); let keymap_normal = merged_config.keys.get_mut(&Mode::Normal).unwrap(); - // Make sure mapping works assert_eq!( keymap_normal .root_node From 6b01af59ee6d8648857db40fe0b75984ec092a65 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Tue, 27 Dec 2022 14:33:32 +0100 Subject: [PATCH 022/105] Exclude config no_op bindings in command palette. --- helix-term/src/keymap.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index edfa41df2705..58433629f09b 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -52,7 +52,8 @@ impl Keymap { } }, KeyTrieNode::MappableCommand(mappable_command) => { - list.entry(mappable_command.name().to_string()).or_default().push(prefix.to_string()); + if mappable_command.name() == "no_op" { return } + list.entry(mappable_command.name().to_string()).or_default().push(prefix.to_string()); }, KeyTrieNode::CommandSequence(_) => {} }; From 7a8397c061e977001af135ba20f2aaa5b55e31a4 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Tue, 27 Dec 2022 20:22:59 +0100 Subject: [PATCH 023/105] Cleaner infobox join operation --- helix-term/src/keymap/keytrie.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helix-term/src/keymap/keytrie.rs b/helix-term/src/keymap/keytrie.rs index 89c441e2974a..227a0a5b7d9c 100644 --- a/helix-term/src/keymap/keytrie.rs +++ b/helix-term/src/keymap/keytrie.rs @@ -123,8 +123,8 @@ impl KeyTrie { .iter() .map(|(key_events, description)| { let key_events_string: String = key_events.iter().fold(String::new(), |mut acc, key_event| { - if acc.is_empty() { acc.push_str(key_event); } - else { acc.push_str(&format!(", {}", key_event)) } + if !acc.is_empty() { acc.push_str(", "); } + acc.push_str(key_event); acc }); (key_events_string, *description) From 546010bde7e2ae121b75ae3bd79f1452677a484e Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Wed, 28 Dec 2022 17:48:59 +0100 Subject: [PATCH 024/105] use statement cleanups --- helix-term/src/application.rs | 15 ++++++--------- helix-term/src/commands.rs | 28 +++++++++++----------------- helix-term/src/config.rs | 7 ++----- helix-term/src/keymap.rs | 9 +++++---- helix-term/src/keymap.rs:41:29 | 0 helix-term/src/keymap/default.rs | 7 ++----- helix-term/src/keymap/keymaps.rs | 12 ++++-------- helix-term/src/keymap/keytrie.rs | 6 +++--- helix-term/src/keymap/keytrienode.rs | 8 +++----- helix-term/src/keymap/tests.rs | 8 ++------ 10 files changed, 38 insertions(+), 62 deletions(-) delete mode 100644 helix-term/src/keymap.rs:41:29 diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 42daaa64d44a..b614f5395c12 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -1,5 +1,3 @@ -use arc_swap::{access::Map, ArcSwap}; -use futures_util::Stream; use helix_core::{ diagnostic::{DiagnosticTag, NumberOrString}, path::get_relative_path, @@ -15,9 +13,6 @@ use helix_view::{ tree::Layout, Align, Editor, }; -use serde_json::json; -use tui::backend::Backend; - use crate::{ args::Args, commands::apply_workspace_edit, @@ -27,16 +22,17 @@ use crate::{ keymap::keymaps::Keymaps, ui::{self, overlay::overlayed}, }; - -use log::{debug, error, warn}; use std::{ io::{stdin, stdout, Write}, sync::Arc, time::{Duration, Instant}, }; - +use arc_swap::{access::Map, ArcSwap}; +use futures_util::Stream; +use log::{debug, error, warn}; use anyhow::{Context, Error}; - +use serde_json::json; +use tui::backend::Backend; use crossterm::{ event::{ DisableBracketedPaste, DisableFocusChange, DisableMouseCapture, EnableBracketedPaste, @@ -45,6 +41,7 @@ use crossterm::{ execute, terminal, tty::IsTty, }; + #[cfg(not(windows))] use { signal_hook::{consts::signal, low_level}, diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index b4f64296852c..7255995186cf 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -3,11 +3,11 @@ pub(crate) mod lsp; pub(crate) mod typed; pub use dap::*; -use helix_vcs::Hunk; pub use lsp::*; use tui::widgets::Row; pub use typed::*; +use helix_vcs::Hunk; use helix_core::{ comment, coords_at_pos, encoding, find_first_non_whitespace_char, find_root, graphemes, history::UndoKind, @@ -15,7 +15,7 @@ use helix_core::{ indent::IndentStyle, line_ending::{get_line_ending_of_str, line_end_char_index, str_is_line_ending}, match_brackets, - movement::{self, Direction}, + movement::{self, Direction, Movement}, object, pos_at_coords, pos_at_visual_coords, regex::{self, Regex, RegexBuilder}, search::{self, CharMatcher}, @@ -36,33 +36,27 @@ use helix_view::{ view::View, Document, DocumentId, Editor, ViewId, }; - -use anyhow::{anyhow, bail, ensure, Context as _}; -use fuzzy_matcher::FuzzyMatcher; -use insert::*; -use movement::Movement; - use crate::{ + commands::insert::*, args, compositor::{self, Component, Compositor}, - job::Callback, + job::{Callback, self, Jobs}, keymap::CommandList, ui::{self, overlay::overlayed, FilePicker, Picker, Popup, Prompt, PromptEvent}, }; - -use crate::job::{self, Jobs}; -use futures_util::StreamExt; -use std::{collections::HashMap, fmt, future::Future}; -use std::{collections::HashSet, num::NonZeroUsize}; - use std::{ + collections::{HashMap, HashSet}, + num::NonZeroUsize, + future::Future, borrow::Cow, path::{Path, PathBuf}, + fmt, }; - +use anyhow::{anyhow, bail, ensure, Context as _}; +use fuzzy_matcher::FuzzyMatcher; +use futures_util::StreamExt; use once_cell::sync::Lazy; use serde::de::{self, Deserialize, Deserializer}; - use grep_regex::RegexMatcherBuilder; use grep_searcher::{sinks, BinaryDetection, SearcherBuilder}; use ignore::{DirEntry, WalkBuilder, WalkState}; diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index 471b8c168a79..d924bff79c90 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -1,11 +1,8 @@ use crate::keymap::{default::default, Keymap, keymaps::Keymaps}; use helix_view::document::Mode; -use serde::Deserialize; -use std::collections::HashMap; -use std::fmt::Display; -use std::io::Error as IOError; -use std::path::PathBuf; +use std::{fmt::Display, collections::HashMap, io::Error as IOError}; use toml::de::Error as TomlError; +use serde::Deserialize; #[derive(Debug, Clone, PartialEq, Deserialize)] #[serde(deny_unknown_fields)] diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 58433629f09b..f5cbcec212a5 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -1,12 +1,11 @@ pub mod keymaps; -pub mod keytrienode; pub mod default; pub mod macros; -pub mod keytrie; pub mod tests; -use serde::Deserialize; -use std::{collections::HashMap, ops::{Deref, DerefMut}}; +mod keytrie; +mod keytrienode; + use crate::{ commands::MappableCommand, keymap::{ @@ -14,6 +13,8 @@ use crate::{ keytrienode::KeyTrieNode } }; +use std::{collections::HashMap, ops::{Deref, DerefMut}}; +use serde::Deserialize; #[derive(Debug, Clone, PartialEq, Deserialize)] #[serde(transparent)] diff --git a/helix-term/src/keymap.rs:41:29 b/helix-term/src/keymap.rs:41:29 deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/helix-term/src/keymap/default.rs b/helix-term/src/keymap/default.rs index 7ce2736021b4..b74ae84c7b63 100644 --- a/helix-term/src/keymap/default.rs +++ b/helix-term/src/keymap/default.rs @@ -1,10 +1,7 @@ -use std::collections::HashMap; - -use super::macros::keymap; -use super::Keymap; -use super::keytrie::KeyTrie; +use super::{macros::keymap, Keymap, keytrie::KeyTrie}; use helix_view::document::Mode; use helix_core::hashmap; +use std::collections::HashMap; pub fn default() -> HashMap { let normal: KeyTrie = keymap!({ "Normal mode" diff --git a/helix-term/src/keymap/keymaps.rs b/helix-term/src/keymap/keymaps.rs index fe03af6d48c6..ec9ecbce8eb7 100644 --- a/helix-term/src/keymap/keymaps.rs +++ b/helix-term/src/keymap/keymaps.rs @@ -1,12 +1,8 @@ +use super::*; +use crate::{commands::MappableCommand, config::Config}; +use helix_view::{document::Mode, input::KeyEvent}; use std::{sync::Arc, collections::HashMap}; use arc_swap::{access::{DynAccess, DynGuard}, ArcSwap}; -use helix_view::{document::Mode, input::KeyEvent}; -use crate::commands::MappableCommand; -use crate::config::Config; -use super::{macros::key, keytrienode::KeyTrieNode, default}; -use super::keytrie::KeyTrie; -use super::Keymap; -use super::default::default; #[derive(Debug, Clone, PartialEq)] pub enum KeymapResult { @@ -116,6 +112,6 @@ impl Keymaps { impl Default for Keymaps { fn default() -> Self { - Self::new(Box::new(ArcSwap::new(Arc::new(default())))) + Self::new(Box::new(ArcSwap::new(Arc::new(default::default())))) } } diff --git a/helix-term/src/keymap/keytrie.rs b/helix-term/src/keymap/keytrie.rs index 227a0a5b7d9c..0706182db228 100644 --- a/helix-term/src/keymap/keytrie.rs +++ b/helix-term/src/keymap/keytrie.rs @@ -1,7 +1,7 @@ -use serde::Deserialize; -use std::{collections::HashMap, ops::{Deref, DerefMut}}; -use helix_view::{info::Info, input::KeyEvent}; use super::keytrienode::KeyTrieNode; +use helix_view::{info::Info, input::KeyEvent}; +use std::{collections::HashMap, ops::{Deref, DerefMut}, cmp::Ordering}; +use serde::Deserialize; /// Edges of the trie are KeyEvents and the nodes are descrbibed by KeyTrieNode #[derive(Debug, Clone)] diff --git a/helix-term/src/keymap/keytrienode.rs b/helix-term/src/keymap/keytrienode.rs index a8a0ebba384d..41b5a7cd3343 100644 --- a/helix-term/src/keymap/keytrienode.rs +++ b/helix-term/src/keymap/keytrienode.rs @@ -1,9 +1,7 @@ -use std::collections::HashMap; -use super::MappableCommand; -use serde::Deserialize; -use serde::de::Visitor; -use super::keytrie::KeyTrie; +use super::{MappableCommand, keytrie::KeyTrie}; use helix_view::input::KeyEvent; +use std::collections::HashMap; +use serde::{Deserialize, de::Visitor}; /// Each variant includes a documentaion property. /// For the MappableCommand and CommandSequence variants, the property is self explanatory. diff --git a/helix-term/src/keymap/tests.rs b/helix-term/src/keymap/tests.rs index 557debb94cb1..cc06e9992b30 100644 --- a/helix-term/src/keymap/tests.rs +++ b/helix-term/src/keymap/tests.rs @@ -1,14 +1,10 @@ #[macro_use] #[cfg(test)] mod tests { - use arc_swap::access::Constant; use helix_core::hashmap; use helix_view::{document::Mode, input::KeyEvent}; - use crate::config::Config; - use crate::commands::MappableCommand; - use crate::keymap::*; - use keymaps::{Keymaps, KeymapResult}; - use macros::*; + use crate::{config::Config, commands::MappableCommand, keymap::*}; + use arc_swap::access::Constant; #[test] #[should_panic] From 036ce4abea1fd6d4deab00e5fb4172f7f9f00d80 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Wed, 28 Dec 2022 18:44:26 +0100 Subject: [PATCH 025/105] Move Config related tests from keymap tests Config tests --- helix-term/src/config.rs | 127 +++++++++++++++++++++++++++++---- helix-term/src/keymap.rs | 5 +- helix-term/src/keymap/tests.rs | 97 +------------------------ 3 files changed, 118 insertions(+), 111 deletions(-) diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index d924bff79c90..21fbb60ea016 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -1,8 +1,8 @@ -use crate::keymap::{default::default, Keymap, keymaps::Keymaps}; +use crate::keymap::{default::default, keymaps::Keymaps, Keymap}; use helix_view::document::Mode; -use std::{fmt::Display, collections::HashMap, io::Error as IOError}; -use toml::de::Error as TomlError; use serde::Deserialize; +use std::{collections::HashMap, fmt::Display, io::Error as IOError}; +use toml::de::Error as TomlError; #[derive(Debug, Clone, PartialEq, Deserialize)] #[serde(deny_unknown_fields)] @@ -25,7 +25,7 @@ impl Default for Config { } #[derive(Debug)] -pub enum ConfigLoadError { +pub enum ConfigLoadError { BadConfig(TomlError), Error(IOError), } @@ -40,7 +40,7 @@ impl Display for ConfigLoadError { } impl Config { - // REFACTOR? code similar to config assignment in main.rs, + // REFACTOR? code similar to config assignment in main.rs, pub fn load_default() -> Result { match std::fs::read_to_string(helix_loader::config_file()) { Ok(config) => toml::from_str(&config) @@ -53,15 +53,23 @@ impl Config { #[cfg(test)] mod tests { - use super::*; + use crate::{ + commands::MappableCommand, + config::Config, + keymap::{ + default, + keymaps::{KeymapResult, Keymaps}, + keytrienode::KeyTrieNode, + macros::*, + Keymap, + }, + }; + use arc_swap::access::Constant; + use helix_core::hashmap; + use helix_view::document::Mode; #[test] fn parsing_keymaps_config_file() { - use crate::keymap; - use crate::keymap::Keymap; - use helix_core::hashmap; - use helix_view::document::Mode; - let sample_keymaps = r#" [keys.insert] y = "move_line_down" @@ -92,10 +100,103 @@ mod tests { fn keys_resolve_to_correct_defaults() { // From serde default let default_keys = toml::from_str::("").unwrap().keys; - assert_eq!(default_keys, default()); + assert_eq!(default_keys, default::default()); // From the Default trait let default_keys = Config::default().keys; - assert_eq!(default_keys, default()); + assert_eq!(default_keys, default::default()); + } + + #[test] + fn merge_partial_keys() { + let config = Config { + keys: 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 = Keymaps::merge_with_default(config.clone()); + assert_ne!(config, merged_config); + + let mut keymap = Keymaps::new(Box::new(Constant(merged_config.keys.clone()))); + assert_eq!( + keymap.get(Mode::Normal, key!('i')), + KeymapResult::Matched(MappableCommand::normal_mode), + "New mappable command should ovveride default." + ); + assert_eq!( + keymap.get(Mode::Normal, key!('无')), + KeymapResult::Matched(MappableCommand::insert_mode), + "New mappable command 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), + "New Mappable command should replace default sub keytrie." + ); + + let keymap = merged_config.keys.get_mut(&Mode::Normal).unwrap(); + // Assumes that `g` is a node in default keymap + assert_eq!( + keymap.root_node.traverse(&[key!('g'), key!('$')]).unwrap(), + KeyTrieNode::MappableCommand(MappableCommand::goto_line_end), + "Mappable command should be present in merged keytrie." + ); + // Assumes that `gg` is in default keymap + assert_eq!( + keymap.root_node.traverse(&[key!('g'), key!('g')]).unwrap(), + KeyTrieNode::MappableCommand(MappableCommand::delete_char_forward), + "Mappable command should replace default in merged keytrie." + ); + // Assumes that `ge` is in default keymap + assert_eq!( + keymap.root_node.traverse(&[key!('g'), key!('e')]).unwrap(), + KeyTrieNode::MappableCommand(MappableCommand::goto_last_line), + "Mappable commands from default keytrie should still be present in merged merged keytrie unless overridden." + ); + + assert!(merged_config.keys.get(&Mode::Normal).unwrap().len() > 1); + assert!(merged_config.keys.get(&Mode::Insert).unwrap().len() > 0); + } + + #[test] + fn merges_with_default_keymap_config() { + let config = Config { + keys: hashmap! { + Mode::Normal => Keymap::new( + keymap!({ "Normal mode" + "space" => { "" + "s" => { "" + "v" => vsplit, + "c" => hsplit, + }, + }, + }) + ) + }, + ..Default::default() + }; + let mut merged_config = Keymaps::merge_with_default(config.clone()); + assert_ne!(config, merged_config); + let keymap_normal = merged_config.keys.get_mut(&Mode::Normal).unwrap(); + assert_eq!( + keymap_normal + .root_node + .traverse(&[key!(' '), key!('s'), key!('v')]) + .unwrap(), + KeyTrieNode::MappableCommand(MappableCommand::vsplit), + "Mappable command should be present in merged keytrie." + ); } } diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index f5cbcec212a5..601046600b5e 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -2,9 +2,8 @@ pub mod keymaps; pub mod default; pub mod macros; pub mod tests; - -mod keytrie; -mod keytrienode; +pub mod keytrienode; +pub mod keytrie; use crate::{ commands::MappableCommand, diff --git a/helix-term/src/keymap/tests.rs b/helix-term/src/keymap/tests.rs index cc06e9992b30..157230d816d3 100644 --- a/helix-term/src/keymap/tests.rs +++ b/helix-term/src/keymap/tests.rs @@ -3,8 +3,8 @@ mod tests { use helix_core::hashmap; use helix_view::{document::Mode, input::KeyEvent}; - use crate::{config::Config, commands::MappableCommand, keymap::*}; - use arc_swap::access::Constant; + use crate::keymap::*; + use std::collections::HashMap; #[test] #[should_panic] @@ -21,99 +21,6 @@ mod tests { Keymaps::default(); } - #[test] - fn merge_partial_keys() { - let config = Config { - keys: 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 mut keymap = Keymaps::new(Box::new(Constant(merged_config.keys.clone()))); - assert_eq!( - keymap.get(Mode::Normal, key!('i')), - KeymapResult::Matched(MappableCommand::normal_mode), - "New mappable command should ovveride default." - ); - assert_eq!( - keymap.get(Mode::Normal, key!('无')), - KeymapResult::Matched(MappableCommand::insert_mode), - "New mappable command 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), - "New Mappable command should replace default sub keytrie." - ); - - let keymap = merged_config.keys.get_mut(&Mode::Normal).unwrap(); - // Assumes that `g` is a node in default keymap - assert_eq!( - keymap.root_node.traverse(&[key!('g'), key!('$')]).unwrap(), - KeyTrieNode::MappableCommand(MappableCommand::goto_line_end), - "Mappable command should be present in merged keytrie." - ); - // Assumes that `gg` is in default keymap - assert_eq!( - keymap.root_node.traverse(&[key!('g'), key!('g')]).unwrap(), - KeyTrieNode::MappableCommand(MappableCommand::delete_char_forward), - "Mappable command should replace default in merged keytrie." - ); - // Assumes that `ge` is in default keymap - assert_eq!( - keymap.root_node.traverse(&[key!('g'), key!('e')]).unwrap(), - KeyTrieNode::MappableCommand(MappableCommand::goto_last_line), - "Mappable commands from default keytrie should still be present in merged merged keytrie unless overridden." - ); - - assert!(merged_config.keys.get(&Mode::Normal).unwrap().len() > 1); - assert!(merged_config.keys.get(&Mode::Insert).unwrap().len() > 0); - } - - #[test] - fn merges_with_default_keymap_config() { - let config = Config { - keys: hashmap! { - Mode::Normal => Keymap::new( - keymap!({ "Normal mode" - "space" => { "" - "s" => { "" - "v" => vsplit, - "c" => hsplit, - }, - }, - }) - ) - }, - ..Default::default() - }; - let mut merged_config = Keymaps::merge_with_default(config.clone()); - assert_ne!(config, merged_config); - let keymap_normal = merged_config.keys.get_mut(&Mode::Normal).unwrap(); - assert_eq!( - keymap_normal - .root_node - .traverse(&[key!(' '), key!('s'), key!('v')]) - .unwrap(), - KeyTrieNode::MappableCommand(MappableCommand::vsplit), - "Mappable command should be present in merged keytrie." - ); - } - #[test] fn aliased_modes_are_same_in_default_keymap() { let keymaps = Keymaps::default().keymaps; From ac98d82cd738ca7e4448634f496adbcaae8fba33 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Wed, 28 Dec 2022 19:35:34 +0100 Subject: [PATCH 026/105] Config test cleanup * Elaborated on test descriptions * Removed duplicate unit test * Unified keytrie traversal in tests --- helix-term/src/config.rs | 84 ++++++++++++++-------------------------- 1 file changed, 28 insertions(+), 56 deletions(-) diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index 21fbb60ea016..40d98378da10 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -58,18 +58,17 @@ mod tests { config::Config, keymap::{ default, - keymaps::{KeymapResult, Keymaps}, + keymaps::Keymaps, keytrienode::KeyTrieNode, macros::*, Keymap, }, }; - use arc_swap::access::Constant; use helix_core::hashmap; use helix_view::document::Mode; #[test] - fn parsing_keymaps_config_file() { + fn parses_keymap_from_toml() { let sample_keymaps = r#" [keys.insert] y = "move_line_down" @@ -108,8 +107,8 @@ mod tests { } #[test] - fn merge_partial_keys() { - let config = Config { + fn user_config_merges_with_default() { + let user_config = Config { keys: hashmap! { Mode::Normal => Keymap::new( keymap!({ "Normal mode" @@ -125,78 +124,51 @@ mod tests { }, ..Default::default() }; - let mut merged_config = Keymaps::merge_with_default(config.clone()); - assert_ne!(config, merged_config); + let mut merged_config = Keymaps::merge_with_default(user_config.clone()); + assert_ne!( + user_config, + merged_config, + "Merged user keymap with default should differ from user keymap." + ); - let mut keymap = Keymaps::new(Box::new(Constant(merged_config.keys.clone()))); + let keymap_normal_root_key_trie = &merged_config.keys.get_mut(&Mode::Normal).unwrap().root_node; assert_eq!( - keymap.get(Mode::Normal, key!('i')), - KeymapResult::Matched(MappableCommand::normal_mode), - "New mappable command should ovveride default." + keymap_normal_root_key_trie.traverse(&[key!('i')]).unwrap(), + KeyTrieNode::MappableCommand(MappableCommand::normal_mode), + "User supplied mappable command should ovveride default mappable command bound to the same key event." ); assert_eq!( - keymap.get(Mode::Normal, key!('无')), - KeymapResult::Matched(MappableCommand::insert_mode), - "New mappable command should be present in merged keymap." + keymap_normal_root_key_trie.traverse(&[key!('无')]).unwrap(), + KeyTrieNode::MappableCommand(MappableCommand::insert_mode), + "User supplied mappable command of new key event 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), - "New Mappable command should replace default sub keytrie." + keymap_normal_root_key_trie.traverse(&[key!('z')]).unwrap(), + KeyTrieNode::MappableCommand(MappableCommand::jump_backward), + "User supplied mappable command should replace a sub keytrie from default keymap bound to the same key event." ); - - let keymap = merged_config.keys.get_mut(&Mode::Normal).unwrap(); - // Assumes that `g` is a node in default keymap + // Assumes that `g` is a sub key trie in default keymap assert_eq!( - keymap.root_node.traverse(&[key!('g'), key!('$')]).unwrap(), + keymap_normal_root_key_trie.traverse(&[key!('g'), key!('$')]).unwrap(), KeyTrieNode::MappableCommand(MappableCommand::goto_line_end), - "Mappable command should be present in merged keytrie." + "User supplied mappable command should be inserted under the correct sub keytrie." ); // Assumes that `gg` is in default keymap assert_eq!( - keymap.root_node.traverse(&[key!('g'), key!('g')]).unwrap(), + keymap_normal_root_key_trie.traverse(&[key!('g'), key!('g')]).unwrap(), KeyTrieNode::MappableCommand(MappableCommand::delete_char_forward), - "Mappable command should replace default in merged keytrie." + "User supplied mappable command should replace default even in sub keytries." ); // Assumes that `ge` is in default keymap assert_eq!( - keymap.root_node.traverse(&[key!('g'), key!('e')]).unwrap(), + keymap_normal_root_key_trie.traverse(&[key!('g'), key!('e')]).unwrap(), KeyTrieNode::MappableCommand(MappableCommand::goto_last_line), - "Mappable commands from default keytrie should still be present in merged merged keytrie unless overridden." + "Default mappable commands that aren't ovveridden should exist in merged keymap." ); + // Huh? assert!(merged_config.keys.get(&Mode::Normal).unwrap().len() > 1); assert!(merged_config.keys.get(&Mode::Insert).unwrap().len() > 0); } - - #[test] - fn merges_with_default_keymap_config() { - let config = Config { - keys: hashmap! { - Mode::Normal => Keymap::new( - keymap!({ "Normal mode" - "space" => { "" - "s" => { "" - "v" => vsplit, - "c" => hsplit, - }, - }, - }) - ) - }, - ..Default::default() - }; - let mut merged_config = Keymaps::merge_with_default(config.clone()); - assert_ne!(config, merged_config); - let keymap_normal = merged_config.keys.get_mut(&Mode::Normal).unwrap(); - assert_eq!( - keymap_normal - .root_node - .traverse(&[key!(' '), key!('s'), key!('v')]) - .unwrap(), - KeyTrieNode::MappableCommand(MappableCommand::vsplit), - "Mappable command should be present in merged keytrie." - ); - } } From 109228bd89ce7225a576ab8973a37d627c8ff906 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Thu, 29 Dec 2022 08:38:17 +0100 Subject: [PATCH 027/105] Load keymap config consistently * Moved config merging Keymaps to Config * Removed unused default EditorView function --- helix-term/src/config.rs | 18 +++++++++++++----- helix-term/src/keymap/keymaps.rs | 11 ++--------- helix-term/src/main.rs | 2 +- helix-term/src/ui/editor.rs | 6 ------ 4 files changed, 16 insertions(+), 21 deletions(-) diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index 40d98378da10..eae1da41fc94 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -1,4 +1,4 @@ -use crate::keymap::{default::default, keymaps::Keymaps, Keymap}; +use crate::keymap::{default, Keymap}; use helix_view::document::Mode; use serde::Deserialize; use std::{collections::HashMap, fmt::Display, io::Error as IOError}; @@ -8,7 +8,7 @@ use toml::de::Error as TomlError; #[serde(deny_unknown_fields)] pub struct Config { pub theme: Option, - #[serde(default = "default")] + #[serde(default = "default::default")] pub keys: HashMap, #[serde(default)] pub editor: helix_view::editor::Config, @@ -18,7 +18,7 @@ impl Default for Config { fn default() -> Config { Config { theme: None, - keys: default(), + keys: default::default(), editor: helix_view::editor::Config::default(), } } @@ -44,11 +44,19 @@ impl Config { pub fn load_default() -> Result { match std::fs::read_to_string(helix_loader::config_file()) { Ok(config) => toml::from_str(&config) - .map(Keymaps::merge_with_default) + .map(self::Config::merge_in_default_keymap) .map_err(ConfigLoadError::BadConfig), Err(err) => Err(ConfigLoadError::Error(err)), } } + + pub fn merge_in_default_keymap(mut config: Config) -> Config { + let mut delta = std::mem::replace(&mut config.keys, default::default()); + for (mode, keys) in &mut config.keys { + keys.merge_keytrie(delta.remove(mode).unwrap_or_default().root_node) + } + config + } } #[cfg(test)] @@ -124,7 +132,7 @@ mod tests { }, ..Default::default() }; - let mut merged_config = Keymaps::merge_with_default(user_config.clone()); + let mut merged_config = Keymaps::merge_in_default_keymap(user_config.clone()); assert_ne!( user_config, merged_config, diff --git a/helix-term/src/keymap/keymaps.rs b/helix-term/src/keymap/keymaps.rs index ec9ecbce8eb7..5d899f49c7a2 100644 --- a/helix-term/src/keymap/keymaps.rs +++ b/helix-term/src/keymap/keymaps.rs @@ -1,5 +1,5 @@ use super::*; -use crate::{commands::MappableCommand, config::Config}; +use crate::commands::MappableCommand; use helix_view::{document::Mode, input::KeyEvent}; use std::{sync::Arc, collections::HashMap}; use arc_swap::{access::{DynAccess, DynGuard}, ArcSwap}; @@ -43,14 +43,6 @@ impl Keymaps { self.sticky_keytrie.as_ref() } - pub fn merge_with_default(mut config: Config) -> Config { - let mut delta = std::mem::replace(&mut config.keys, default::default()); - for (mode, keys) in &mut config.keys { - keys.merge_keytrie(delta.remove(mode).unwrap_or_default().root_node) - } - config - } - /// Lookup `key` in the keymap to try and find a command to execute. /// Escape key represents cancellation. /// This means clearing pending keystrokes, or the sticky_keytrie if none were present. @@ -110,6 +102,7 @@ impl Keymaps { } } +// NOTE: Only used for testing purposes impl Default for Keymaps { fn default() -> Self { Self::new(Box::new(ArcSwap::new(Arc::new(default::default())))) diff --git a/helix-term/src/main.rs b/helix-term/src/main.rs index bae827e30402..f3cfd0800ed0 100644 --- a/helix-term/src/main.rs +++ b/helix-term/src/main.rs @@ -128,7 +128,7 @@ FLAGS: let config = match std::fs::read_to_string(helix_loader::config_file()) { Ok(config) => toml::from_str(&config) - .map(helix_term::keymap::keymaps::Keymaps::merge_with_default) + .map(Config::merge_in_default_keymap) .unwrap_or_else(|err| { eprintln!("Bad config: {}", err); eprintln!("Press to continue with default config"); diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index bd5d99fbcbaa..98da59c95097 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -47,12 +47,6 @@ pub enum InsertEvent { TriggerCompletion, } -impl Default for EditorView { - fn default() -> Self { - Self::new(Keymaps::default()) - } -} - impl EditorView { pub fn new(keymaps: Keymaps) -> Self { Self { From 527198a6ffc2886130d1b1b9d4d31b1b322fd4f7 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Thu, 29 Dec 2022 18:58:53 +0100 Subject: [PATCH 028/105] Make use of new self context in keymap config load --- helix-term/src/config.rs | 13 ++++++------- helix-term/src/main.rs | 2 +- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index eae1da41fc94..57fe28a84045 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -44,18 +44,18 @@ impl Config { pub fn load_default() -> Result { match std::fs::read_to_string(helix_loader::config_file()) { Ok(config) => toml::from_str(&config) - .map(self::Config::merge_in_default_keymap) + .map(|config: Config| config.merge_in_default_keymap()) .map_err(ConfigLoadError::BadConfig), Err(err) => Err(ConfigLoadError::Error(err)), } } - pub fn merge_in_default_keymap(mut config: Config) -> Config { - let mut delta = std::mem::replace(&mut config.keys, default::default()); - for (mode, keys) in &mut config.keys { + pub fn merge_in_default_keymap(mut self) -> Config { + let mut delta = std::mem::replace(&mut self.keys, default::default()); + for (mode, keys) in &mut self.keys { keys.merge_keytrie(delta.remove(mode).unwrap_or_default().root_node) } - config + self } } @@ -66,7 +66,6 @@ mod tests { config::Config, keymap::{ default, - keymaps::Keymaps, keytrienode::KeyTrieNode, macros::*, Keymap, @@ -132,7 +131,7 @@ mod tests { }, ..Default::default() }; - let mut merged_config = Keymaps::merge_in_default_keymap(user_config.clone()); + let mut merged_config = user_config.clone().merge_in_default_keymap(); assert_ne!( user_config, merged_config, diff --git a/helix-term/src/main.rs b/helix-term/src/main.rs index f3cfd0800ed0..67e4f4a0cd55 100644 --- a/helix-term/src/main.rs +++ b/helix-term/src/main.rs @@ -128,7 +128,7 @@ FLAGS: let config = match std::fs::read_to_string(helix_loader::config_file()) { Ok(config) => toml::from_str(&config) - .map(Config::merge_in_default_keymap) + .map(|config: Config| config.merge_in_default_keymap()) .unwrap_or_else(|err| { eprintln!("Bad config: {}", err); eprintln!("Press to continue with default config"); From 917ca0d52289db71f26b427656baa3df89e54e98 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Thu, 29 Dec 2022 21:27:37 +0100 Subject: [PATCH 029/105] Minor keymap module visibilyti cleanup --- helix-term/src/keymap.rs | 3 ++- helix-term/src/keymap/keymaps.rs | 1 - helix-term/src/lib.rs | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 601046600b5e..880b405f604e 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -1,9 +1,10 @@ pub mod keymaps; pub mod default; pub mod macros; -pub mod tests; +// NOTE: Only pub becuase of their use in macros pub mod keytrienode; pub mod keytrie; +mod tests; use crate::{ commands::MappableCommand, diff --git a/helix-term/src/keymap/keymaps.rs b/helix-term/src/keymap/keymaps.rs index 5d899f49c7a2..f824401b1833 100644 --- a/helix-term/src/keymap/keymaps.rs +++ b/helix-term/src/keymap/keymaps.rs @@ -102,7 +102,6 @@ impl Keymaps { } } -// NOTE: Only used for testing purposes impl Default for Keymaps { fn default() -> Self { Self::new(Box::new(ArcSwap::new(Arc::new(default::default())))) diff --git a/helix-term/src/lib.rs b/helix-term/src/lib.rs index 58ae12c2e120..fc8e934e1a7d 100644 --- a/helix-term/src/lib.rs +++ b/helix-term/src/lib.rs @@ -10,8 +10,6 @@ pub mod health; pub mod job; pub mod keymap; pub mod ui; -pub use keymap::macros::*; -pub use keymap::tests; #[cfg(not(windows))] fn true_color() -> bool { From 8b8fadb695733ce5961dec896091697cc4c9c985 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Thu, 29 Dec 2022 21:54:41 +0100 Subject: [PATCH 030/105] Renamed the keymap! macro to keytrie!: It produces, after all, a Keytrie. It would be a bit like saying: let engine = car!() --- helix-term/src/config.rs | 6 +++--- helix-term/src/keymap/default.rs | 10 +++++----- helix-term/src/keymap/macros.rs | 18 +++++++++++++----- helix-term/src/keymap/tests.rs | 4 ++-- 4 files changed, 23 insertions(+), 15 deletions(-) diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index 57fe28a84045..fc724d818ff0 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -89,11 +89,11 @@ mod tests { toml::from_str::(sample_keymaps).unwrap(), Config { keys: hashmap! { - Mode::Insert => Keymap::new(keymap!({ "Insert mode" + Mode::Insert => Keymap::new(keytrie!({ "Insert mode" "y" => move_line_down, "S-C-a" => delete_selection, })), - Mode::Normal => Keymap::new(keymap!({ "Normal mode" + Mode::Normal => Keymap::new(keytrie!({ "Normal mode" "A-F12" => move_next_word_end, })), }, @@ -118,7 +118,7 @@ mod tests { let user_config = Config { keys: hashmap! { Mode::Normal => Keymap::new( - keymap!({ "Normal mode" + keytrie!({ "Normal mode" "i" => normal_mode, "无" => insert_mode, "z" => jump_backward, diff --git a/helix-term/src/keymap/default.rs b/helix-term/src/keymap/default.rs index b74ae84c7b63..551cf1a79bed 100644 --- a/helix-term/src/keymap/default.rs +++ b/helix-term/src/keymap/default.rs @@ -1,10 +1,10 @@ -use super::{macros::keymap, Keymap, keytrie::KeyTrie}; +use super::{macros::keytrie, Keymap}; use helix_view::document::Mode; use helix_core::hashmap; use std::collections::HashMap; pub fn default() -> HashMap { - let normal: KeyTrie = keymap!({ "Normal mode" + let normal = keytrie!({ "Normal mode" "h" | "left" => move_char_left, "j" | "down" => move_line_down, "k" | "up" => move_line_up, @@ -316,8 +316,8 @@ pub fn default() -> HashMap { "C-a" => increment, "C-x" => decrement, }); - let mut select: KeyTrie = normal.clone(); - select.merge_keytrie(keymap!({ "Select mode" + let mut select = normal.clone(); + select.merge_keytrie(keytrie!({ "Select mode" "h" | "left" => extend_char_left, "j" | "down" => extend_line_down, "k" | "up" => extend_line_up, @@ -344,7 +344,7 @@ pub fn default() -> HashMap { "v" => normal_mode, })); - let insert: KeyTrie = keymap!({ "Insert mode" + let insert = keytrie!({ "Insert mode" "esc" => normal_mode, "C-s" => commit_undo_checkpoint, diff --git a/helix-term/src/keymap/macros.rs b/helix-term/src/keymap/macros.rs index 4d81d220ffb9..66fa78f7a34b 100644 --- a/helix-term/src/keymap/macros.rs +++ b/helix-term/src/keymap/macros.rs @@ -66,9 +66,8 @@ macro_rules! alt { /// /// ``` /// # use helix_core::hashmap; -/// # use helix_term::keymap; -/// # use helix_term::keymap::Keymap; -/// let normal_mode = keymap!({ "Normal mode" +/// # use helix_term::keymap::{Keymap, macros::keytrie}; +/// let normal_mode = keytrie!({ "Normal mode" /// "i" => insert_mode, /// "g" => { "Goto" /// "g" => goto_file_start, @@ -79,7 +78,7 @@ macro_rules! alt { /// let keymap = Keymap::new(normal_mode); /// ``` #[macro_export] -macro_rules! keymap { +macro_rules! keytrie { ({ $label:literal $(sticky=$sticky:literal)? $($($key:literal)|+ => $value:tt,)+ }) => { // modified from the hashmap! macro { @@ -88,8 +87,13 @@ macro_rules! keymap { ::std::collections::HashMap::with_capacity(_cap); $( $( +<<<<<<< HEAD let _key = $key.parse::<::helix_view::input::KeyEvent>().unwrap(); let _potential_duplicate = _map.insert(_key,keymap!(@trie $value)); +======= + let _key = $key.parse::().unwrap(); + let _potential_duplicate = _map.insert(_key,keytrie!(@trie $value)); +>>>>>>> 8cb9a917 (Renamed the keymap! macro to keytrie!:) assert!(_potential_duplicate.is_none(), "Duplicate key found: {:?}", _potential_duplicate.unwrap()); )+ )* @@ -100,7 +104,7 @@ macro_rules! keymap { }; (@trie {$label:literal $(sticky=$sticky:literal)? $($($key:literal)|+ => $value:tt,)+ }) => { - $crate::keymap::keytrienode::KeyTrieNode::KeyTrie(keymap!({ $label $(sticky=$sticky)? $($($key)|+ => $value,)+ })) + $crate::keymap::keytrienode::KeyTrieNode::KeyTrie(keytrie!({ $label $(sticky=$sticky)? $($($key)|+ => $value,)+ })) }; (@trie $cmd:ident) => { @@ -113,7 +117,11 @@ macro_rules! keymap { } pub use alt; +<<<<<<< HEAD pub use ctrl; pub use key; pub use keymap; pub use shift; +======= +pub use keytrie; +>>>>>>> 8cb9a917 (Renamed the keymap! macro to keytrie!:) diff --git a/helix-term/src/keymap/tests.rs b/helix-term/src/keymap/tests.rs index 157230d816d3..4570ac2e50eb 100644 --- a/helix-term/src/keymap/tests.rs +++ b/helix-term/src/keymap/tests.rs @@ -9,7 +9,7 @@ mod tests { #[test] #[should_panic] fn duplicate_keys_should_panic() { - keymap!({ "Normal mode" + keytrie!({ "Normal mode" "i" => normal_mode, "i" => goto_definition, }); @@ -39,7 +39,7 @@ mod tests { #[test] fn command_list() { - let normal_mode = keymap!({ "Normal mode" + let normal_mode = keytrie!({ "Normal mode" "i" => insert_mode, "g" => { "Goto" "g" => goto_file_start, From 7747777d68a4d199e2db6edf6bb704c5a6cbaa0f Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Thu, 19 Jan 2023 15:37:43 +0100 Subject: [PATCH 031/105] Fix failed cherry picks --- helix-term/src/commands.rs | 45 ++++++++++++++++++++++---------- helix-term/src/commands/typed.rs | 3 +-- helix-term/src/keymap.rs | 2 +- helix-term/src/keymap/keymaps.rs | 1 + helix-term/src/keymap/keytrie.rs | 2 +- helix-term/src/keymap/macros.rs | 9 ------- helix-term/src/keymap/tests.rs | 9 +++---- 7 files changed, 39 insertions(+), 32 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 7255995186cf..bfd7e475ef3c 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -4,7 +4,7 @@ pub(crate) mod typed; pub use dap::*; pub use lsp::*; -use tui::widgets::Row; +use tui::widgets::{Row, Cell}; pub use typed::*; use helix_vcs::Hunk; @@ -34,7 +34,7 @@ use helix_view::{ keyboard::KeyCode, tree, view::View, - Document, DocumentId, Editor, ViewId, + Document, DocumentId, Editor, ViewId, apply_transaction, }; use crate::{ commands::insert::*, @@ -2444,22 +2444,40 @@ fn jumplist_picker(cx: &mut Context) { cx.push_layer(Box::new(overlayed(picker))); } - - - +// NOTE: does not present aliases impl ui::menu::Item for MappableCommand { type Data = CommandList; - fn label(&self, keymap: &Self::Data) -> Spans { + fn format(&self, command_list: &Self::Data) -> Row { match self { - MappableCommand::Typable { description: doc, name, .. } => match keymap.get(name as &String) { - Some(key_events) => format!("{} {:?} ':{}'", doc, key_events, name).into(), - None => format!("{} ':{}'", doc, name).into(), - }, - MappableCommand::Static { description: doc, name, .. } => match keymap.get(*name) { - Some(key_events) => format!("{} {:?} '{}'", doc, key_events, name).into(), - None => format!("{} '{}'", doc, name).into(), + MappableCommand::Typable { description: doc, name, .. } => { + let mut row: Vec = vec![Cell::from(&*name.as_str()), Cell::from(""), Cell::from(&*doc.as_str())]; + match command_list.get(name as &String) { + Some(key_events) => { row[1] = Cell::from(format_key_events(key_events)); }, + None => {} + } + return Row::new(row); }, + MappableCommand::Static { description: doc, name, .. } => { + let mut row: Vec = vec![Cell::from(*name), Cell::from(""), Cell::from(*doc)]; + match command_list.get(*name) { + Some(key_events) => { row[1] = Cell::from(format_key_events(key_events)); }, + None => {} + } + return Row::new(row) + } + } + + // TODO: Generalize into a Vec Display implemention? + fn format_key_events(key_events: &Vec) -> String { + let mut result_string: String = String::new(); + for key_event in key_events { + if !result_string.is_empty() { + result_string.push_str(", "); + } + result_string.push_str(key_event); + } + result_string } } } @@ -2509,7 +2527,6 @@ pub fn command_palette(cx: &mut Context) { compositor.push(Box::new(overlayed(picker))); }, )); - } fn last_picker(cx: &mut Context) { diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index bd91df5ae62a..deb5f7bc88a8 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -1,8 +1,7 @@ use std::ops::Deref; -use crate::job::Job; - use super::*; +use crate::job::*; use helix_view::editor::{Action, CloseError, ConfigEvent}; use ui::completers::{self, Completer}; diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 880b405f604e..6b1b56a80f1a 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -26,7 +26,7 @@ pub struct Keymap { pub type CommandList = HashMap>; impl Keymap { pub fn new(root_node: KeyTrie) -> Self { - Keymap { root_node } + Self { root_node } } /// Returns a key-value list of all commands associated to a given Keymap. diff --git a/helix-term/src/keymap/keymaps.rs b/helix-term/src/keymap/keymaps.rs index f824401b1833..773b6213003f 100644 --- a/helix-term/src/keymap/keymaps.rs +++ b/helix-term/src/keymap/keymaps.rs @@ -1,4 +1,5 @@ use super::*; +use crate::keymap::macros::*; use crate::commands::MappableCommand; use helix_view::{document::Mode, input::KeyEvent}; use std::{sync::Arc, collections::HashMap}; diff --git a/helix-term/src/keymap/keytrie.rs b/helix-term/src/keymap/keytrie.rs index 0706182db228..cb7798971246 100644 --- a/helix-term/src/keymap/keytrie.rs +++ b/helix-term/src/keymap/keytrie.rs @@ -1,6 +1,6 @@ use super::keytrienode::KeyTrieNode; use helix_view::{info::Info, input::KeyEvent}; -use std::{collections::HashMap, ops::{Deref, DerefMut}, cmp::Ordering}; +use std::{collections::HashMap, ops::{Deref, DerefMut}}; use serde::Deserialize; /// Edges of the trie are KeyEvents and the nodes are descrbibed by KeyTrieNode diff --git a/helix-term/src/keymap/macros.rs b/helix-term/src/keymap/macros.rs index 66fa78f7a34b..24fc7de212d8 100644 --- a/helix-term/src/keymap/macros.rs +++ b/helix-term/src/keymap/macros.rs @@ -87,13 +87,8 @@ macro_rules! keytrie { ::std::collections::HashMap::with_capacity(_cap); $( $( -<<<<<<< HEAD let _key = $key.parse::<::helix_view::input::KeyEvent>().unwrap(); - let _potential_duplicate = _map.insert(_key,keymap!(@trie $value)); -======= - let _key = $key.parse::().unwrap(); let _potential_duplicate = _map.insert(_key,keytrie!(@trie $value)); ->>>>>>> 8cb9a917 (Renamed the keymap! macro to keytrie!:) assert!(_potential_duplicate.is_none(), "Duplicate key found: {:?}", _potential_duplicate.unwrap()); )+ )* @@ -117,11 +112,7 @@ macro_rules! keytrie { } pub use alt; -<<<<<<< HEAD pub use ctrl; pub use key; -pub use keymap; pub use shift; -======= pub use keytrie; ->>>>>>> 8cb9a917 (Renamed the keymap! macro to keytrie!:) diff --git a/helix-term/src/keymap/tests.rs b/helix-term/src/keymap/tests.rs index 4570ac2e50eb..a4cded6fb645 100644 --- a/helix-term/src/keymap/tests.rs +++ b/helix-term/src/keymap/tests.rs @@ -3,7 +3,7 @@ mod tests { use helix_core::hashmap; use helix_view::{document::Mode, input::KeyEvent}; - use crate::keymap::*; + use crate::keymap::{*, macros::*}; use std::collections::HashMap; #[test] @@ -17,14 +17,13 @@ mod tests { #[test] fn check_duplicate_keys_in_default_keymap() { - // will panic on duplicate keys, assumes that `Keymaps` uses keymap! macro - Keymaps::default(); + // will panic on duplicate keys, assumes that `Keymap` uses keymap! macro + Keymap::default(); } #[test] fn aliased_modes_are_same_in_default_keymap() { - let keymaps = Keymaps::default().keymaps; - let root = keymaps.load().get(&Mode::Normal).unwrap().root_node.clone(); + let root = Keymap::default().get(&Mode::Normal).unwrap().root_node.clone(); assert_eq!( root.traverse(&[key!(' '), key!('w')]).unwrap(), root.traverse(&["C-w".parse::().unwrap()]).unwrap(), From 342f794b22ae9c9ff09567ee014c8444f73716c5 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Thu, 29 Dec 2022 23:29:00 +0100 Subject: [PATCH 032/105] Removed keymap::Keymap: By the end of this refactroring it became clear that it was mostly acting as a useless wrapper for KeyTrie. --- helix-term/src/commands.rs | 21 ++++--- helix-term/src/config.rs | 22 ++++--- helix-term/src/keymap.rs | 85 ---------------------------- helix-term/src/keymap/default.rs | 10 ++-- helix-term/src/keymap/keymaps.rs | 48 ++++++++++++++-- helix-term/src/keymap/keytrienode.rs | 3 +- helix-term/src/keymap/macros.rs | 6 +- helix-term/src/keymap/tests.rs | 19 ++++++- 8 files changed, 91 insertions(+), 123 deletions(-) delete mode 100644 helix-term/src/keymap.rs diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index bfd7e475ef3c..55b4e2b3009a 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -7,6 +7,15 @@ pub use lsp::*; use tui::widgets::{Row, Cell}; pub use typed::*; +use crate::{ + commands::insert::*, + args, + keymap::keymaps::CommandList, + compositor::{self, Component, Compositor}, + job::{Callback, self, Jobs}, + ui::{self, overlay::overlayed, FilePicker, Picker, Popup, Prompt, PromptEvent, menu::{Cell, Row}}, +}; + use helix_vcs::Hunk; use helix_core::{ comment, coords_at_pos, encoding, find_first_non_whitespace_char, find_root, graphemes, @@ -36,14 +45,6 @@ use helix_view::{ view::View, Document, DocumentId, Editor, ViewId, apply_transaction, }; -use crate::{ - commands::insert::*, - args, - compositor::{self, Component, Compositor}, - job::{Callback, self, Jobs}, - keymap::CommandList, - ui::{self, overlay::overlayed, FilePicker, Picker, Popup, Prompt, PromptEvent}, -}; use std::{ collections::{HashMap, HashSet}, num::NonZeroUsize, @@ -2485,9 +2486,7 @@ impl ui::menu::Item for MappableCommand { pub fn command_palette(cx: &mut Context) { cx.callback = Some(Box::new( move |compositor: &mut Compositor, cx: &mut compositor::Context| { - let keymap_command_lists = compositor.find::().unwrap().keymaps.load_keymaps() - [&cx.editor.mode] - .command_list(); + let keymap_command_lists = compositor.find::().unwrap().keymaps.command_list(&cx.editor.mode); let mut commands: Vec = MappableCommand::STATIC_COMMAND_LIST.into(); commands.extend(typed::TYPABLE_COMMAND_LIST.iter().map(|cmd| { diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index fc724d818ff0..8f7649578c70 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -1,4 +1,4 @@ -use crate::keymap::{default, Keymap}; +use crate::keymap::{default, keytrie::KeyTrie}; use helix_view::document::Mode; use serde::Deserialize; use std::{collections::HashMap, fmt::Display, io::Error as IOError}; @@ -9,7 +9,7 @@ use toml::de::Error as TomlError; pub struct Config { pub theme: Option, #[serde(default = "default::default")] - pub keys: HashMap, + pub keys: HashMap, #[serde(default)] pub editor: helix_view::editor::Config, } @@ -53,7 +53,7 @@ impl Config { pub fn merge_in_default_keymap(mut self) -> Config { let mut delta = std::mem::replace(&mut self.keys, default::default()); for (mode, keys) in &mut self.keys { - keys.merge_keytrie(delta.remove(mode).unwrap_or_default().root_node) + keys.merge_keytrie(delta.remove(mode).unwrap_or_default()) } self } @@ -68,7 +68,6 @@ mod tests { default, keytrienode::KeyTrieNode, macros::*, - Keymap, }, }; use helix_core::hashmap; @@ -89,13 +88,13 @@ mod tests { toml::from_str::(sample_keymaps).unwrap(), Config { keys: hashmap! { - Mode::Insert => Keymap::new(keytrie!({ "Insert mode" + Mode::Insert => keytrie!({ "Insert mode" "y" => move_line_down, "S-C-a" => delete_selection, - })), - Mode::Normal => Keymap::new(keytrie!({ "Normal mode" + }), + Mode::Normal => keytrie!({ "Normal mode" "A-F12" => move_next_word_end, - })), + }), }, ..Default::default() } @@ -117,8 +116,7 @@ mod tests { fn user_config_merges_with_default() { let user_config = Config { keys: hashmap! { - Mode::Normal => Keymap::new( - keytrie!({ "Normal mode" + Mode::Normal => keytrie!({ "Normal mode" "i" => normal_mode, "无" => insert_mode, "z" => jump_backward, @@ -127,7 +125,7 @@ mod tests { "g" => delete_char_forward, }, }) - ) + }, ..Default::default() }; @@ -138,7 +136,7 @@ mod tests { "Merged user keymap with default should differ from user keymap." ); - let keymap_normal_root_key_trie = &merged_config.keys.get_mut(&Mode::Normal).unwrap().root_node; + let keymap_normal_root_key_trie = &merged_config.keys.get_mut(&Mode::Normal).unwrap(); assert_eq!( keymap_normal_root_key_trie.traverse(&[key!('i')]).unwrap(), KeyTrieNode::MappableCommand(MappableCommand::normal_mode), diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs deleted file mode 100644 index 6b1b56a80f1a..000000000000 --- a/helix-term/src/keymap.rs +++ /dev/null @@ -1,85 +0,0 @@ -pub mod keymaps; -pub mod default; -pub mod macros; -// NOTE: Only pub becuase of their use in macros -pub mod keytrienode; -pub mod keytrie; -mod tests; - -use crate::{ - commands::MappableCommand, - keymap::{ - keytrie::KeyTrie, - keytrienode::KeyTrieNode - } -}; -use std::{collections::HashMap, ops::{Deref, DerefMut}}; -use serde::Deserialize; - -#[derive(Debug, Clone, PartialEq, Deserialize)] -#[serde(transparent)] -/// KeyTrie starting point. -pub struct Keymap { - pub root_node: KeyTrie -} - -pub type CommandList = HashMap>; -impl Keymap { - pub fn new(root_node: KeyTrie) -> Self { - Self { root_node } - } - - /// Returns a key-value list of all commands associated to a given Keymap. - /// Keys are the node names (see KeyTrieNode documentation) - /// Values are lists of stringified KeyEvents that triger the command. - /// Each element in the KeyEvent list is prefixed with prefixed the ancestor KeyEvents. - /// For example: Stringified KeyEvent element for the 'goto_next_window' command could be "space>w>w". - /// Ancestor KeyEvents are in this case "space" and "w". - pub fn command_list(&self) -> CommandList { - let mut list = HashMap::new(); - _command_list(&mut list, &KeyTrieNode::KeyTrie(self.root_node.clone()), &mut String::new()); - return list; - - fn _command_list(list: &mut CommandList, node: &KeyTrieNode, prefix: &mut String) { - match node { - KeyTrieNode::KeyTrie(trie_node) => { - for (key_event, subtrie_node) in trie_node.deref() { - let mut temp_prefix: String = prefix.to_string(); - if &temp_prefix != "" { - temp_prefix.push_str(">"); - } - temp_prefix.push_str(&key_event.to_string()); - _command_list(list, subtrie_node, &mut temp_prefix); - } - }, - KeyTrieNode::MappableCommand(mappable_command) => { - if mappable_command.name() == "no_op" { return } - list.entry(mappable_command.name().to_string()).or_default().push(prefix.to_string()); - }, - KeyTrieNode::CommandSequence(_) => {} - }; - } - } -} - -impl Default for Keymap { - fn default() -> Self { - Self::new(KeyTrie::default()) - } -} - -/// Returns the Keymap root KeyTrie node. -impl Deref for Keymap { - type Target = KeyTrie; - - fn deref(&self) -> &Self::Target { - &self.root_node - } -} - -/// Returns the Keymap root KeyTrie node. -impl DerefMut for Keymap { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.root_node - } -} diff --git a/helix-term/src/keymap/default.rs b/helix-term/src/keymap/default.rs index 551cf1a79bed..5ee0706622e1 100644 --- a/helix-term/src/keymap/default.rs +++ b/helix-term/src/keymap/default.rs @@ -1,9 +1,9 @@ -use super::{macros::keytrie, Keymap}; +use super::{macros::keytrie, keytrie::KeyTrie}; use helix_view::document::Mode; use helix_core::hashmap; use std::collections::HashMap; -pub fn default() -> HashMap { +pub fn default() -> HashMap { let normal = keytrie!({ "Normal mode" "h" | "left" => move_char_left, "j" | "down" => move_line_down, @@ -370,8 +370,8 @@ pub fn default() -> HashMap { "end" => goto_line_end_newline, }); hashmap!( - Mode::Normal => Keymap::new(normal), - Mode::Select => Keymap::new(select), - Mode::Insert => Keymap::new(insert), + Mode::Normal => normal, + Mode::Select => select, + Mode::Insert => insert, ) } diff --git a/helix-term/src/keymap/keymaps.rs b/helix-term/src/keymap/keymaps.rs index 773b6213003f..f99cfe9bdebe 100644 --- a/helix-term/src/keymap/keymaps.rs +++ b/helix-term/src/keymap/keymaps.rs @@ -5,6 +5,8 @@ use helix_view::{document::Mode, input::KeyEvent}; use std::{sync::Arc, collections::HashMap}; use arc_swap::{access::{DynAccess, DynGuard}, ArcSwap}; +use std::ops::Deref; + #[derive(Debug, Clone, PartialEq)] pub enum KeymapResult { Pending(KeyTrie), @@ -16,14 +18,15 @@ pub enum KeymapResult { } pub struct Keymaps { - pub keymaps: Box>>, + pub keymaps: Box>>, /// Relative to a sticky node if Some. pending_keys: Vec, pub sticky_keytrie: Option, } +pub type CommandList = HashMap>; impl Keymaps { - pub fn new(keymaps: Box>>) -> Self { + pub fn new(keymaps: Box>>) -> Self { Self { keymaps, pending_keys: Vec::new(), @@ -31,7 +34,7 @@ impl Keymaps { } } - pub fn load_keymaps(&self) -> DynGuard> { + pub fn load_keymaps(&self) -> DynGuard> { self.keymaps.load() } @@ -63,7 +66,7 @@ impl Keymaps { // Check if sticky keytrie is to be used. let starting_keytrie = match self.sticky_keytrie { - None => &active_keymap.root_node, + None => &active_keymap, Some(ref active_sticky_keytrie) => active_sticky_keytrie, }; @@ -101,6 +104,43 @@ impl Keymaps { None => KeymapResult::Cancelled(self.pending_keys.drain(..).collect()), } } + + fn get_keytrie(&self, mode: &Mode) -> KeyTrie { + // HELP: Unsure how I should handle this Option + self.keymaps.load().get(mode).unwrap().clone() + } + + /// Returns a key-value list of all commands associated to a given Keymap. + /// Keys are the node names (see KeyTrieNode documentation) + /// Values are lists of stringified KeyEvents that triger the command. + /// Each element in the KeyEvent list is prefixed with prefixed the ancestor KeyEvents. + /// For example: Stringified KeyEvent element for the 'goto_next_window' command could be "space>w>w". + /// Ancestor KeyEvents are in this case "space" and "w". + pub fn command_list(&self, mode: &Mode) -> CommandList { + let mut list = HashMap::new(); + _command_list(&mut list, &KeyTrieNode::KeyTrie(self.get_keytrie(mode)), &mut String::new()); + return list; + + fn _command_list(list: &mut CommandList, node: &KeyTrieNode, prefix: &mut String) { + match node { + KeyTrieNode::KeyTrie(trie_node) => { + for (key_event, subtrie_node) in trie_node.deref() { + let mut temp_prefix: String = prefix.to_string(); + if &temp_prefix != "" { + temp_prefix.push_str(">"); + } + temp_prefix.push_str(&key_event.to_string()); + _command_list(list, subtrie_node, &mut temp_prefix); + } + }, + KeyTrieNode::MappableCommand(mappable_command) => { + if mappable_command.name() == "no_op" { return } + list.entry(mappable_command.name().to_string()).or_default().push(prefix.to_string()); + }, + KeyTrieNode::CommandSequence(_) => {} + }; + } + } } impl Default for Keymaps { diff --git a/helix-term/src/keymap/keytrienode.rs b/helix-term/src/keymap/keytrienode.rs index 41b5a7cd3343..433372377787 100644 --- a/helix-term/src/keymap/keytrienode.rs +++ b/helix-term/src/keymap/keytrienode.rs @@ -1,4 +1,5 @@ -use super::{MappableCommand, keytrie::KeyTrie}; +use super::keytrie::KeyTrie; +use crate::commands::MappableCommand; use helix_view::input::KeyEvent; use std::collections::HashMap; use serde::{Deserialize, de::Visitor}; diff --git a/helix-term/src/keymap/macros.rs b/helix-term/src/keymap/macros.rs index 24fc7de212d8..e17bad91d48d 100644 --- a/helix-term/src/keymap/macros.rs +++ b/helix-term/src/keymap/macros.rs @@ -62,11 +62,11 @@ macro_rules! alt { }; } -/// Macro for defining the root of a `Keymap` object. Example: +/// Macro for defining the root of a `KeyTrie` object. Example: /// /// ``` /// # use helix_core::hashmap; -/// # use helix_term::keymap::{Keymap, macros::keytrie}; +/// # use helix_term::keymap::{keytrie::KeyTrie, macros::keytrie}; /// let normal_mode = keytrie!({ "Normal mode" /// "i" => insert_mode, /// "g" => { "Goto" @@ -75,7 +75,7 @@ macro_rules! alt { /// }, /// "j" | "down" => move_line_down, /// }); -/// let keymap = Keymap::new(normal_mode); +/// let keymap = normal_mode; /// ``` #[macro_export] macro_rules! keytrie { diff --git a/helix-term/src/keymap/tests.rs b/helix-term/src/keymap/tests.rs index a4cded6fb645..6d566c91f7ad 100644 --- a/helix-term/src/keymap/tests.rs +++ b/helix-term/src/keymap/tests.rs @@ -3,8 +3,17 @@ mod tests { use helix_core::hashmap; use helix_view::{document::Mode, input::KeyEvent}; +<<<<<<< HEAD use crate::keymap::{*, macros::*}; use std::collections::HashMap; +======= + use crate::{ + keymap::macros::*, + keymap::keymaps::Keymaps, + }; + use std::{sync::Arc, collections::HashMap}; + use arc_swap::ArcSwap; +>>>>>>> adb17e18 (Removed keymap::Keymap:) #[test] #[should_panic] @@ -23,7 +32,12 @@ mod tests { #[test] fn aliased_modes_are_same_in_default_keymap() { +<<<<<<< HEAD let root = Keymap::default().get(&Mode::Normal).unwrap().root_node.clone(); +======= + let keymaps = Keymaps::default().keymaps; + let root = keymaps.load().get(&Mode::Normal).unwrap().clone(); +>>>>>>> adb17e18 (Removed keymap::Keymap:) assert_eq!( root.traverse(&[key!(' '), key!('w')]).unwrap(), root.traverse(&["C-w".parse::().unwrap()]).unwrap(), @@ -46,8 +60,9 @@ mod tests { }, "j" | "k" => move_line_down, }); - let keymap = Keymap::new(normal_mode); - let mut command_list = keymap.command_list(); + + let keymap = Keymaps::new(Box::new(ArcSwap::new(Arc::new(hashmap!(Mode::Normal => normal_mode))))); + let mut command_list = keymap.command_list(&Mode::Normal); // sort keybindings in order to have consistent tests // HashMaps can be compared but we can still get different ordering of bindings From 020c53af9739629d1be6cc7e8e0fc093b43660e3 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Fri, 30 Dec 2022 10:59:05 +0100 Subject: [PATCH 033/105] Switched keymap::keymaps::Keymaps to keymap::Keymap Root keymap module was empty as old Keymap could be removed. And there was no point anymore in differentiating Keymaps and Keymap. --- helix-term/src/application.rs | 4 +- helix-term/src/commands.rs | 5 +- helix-term/src/keymap.rs | 161 +++++++++++++++++++++++++++++++++ helix-term/src/keymap/tests.rs | 26 ++---- helix-term/src/ui/editor.rs | 22 ++--- 5 files changed, 182 insertions(+), 36 deletions(-) create mode 100644 helix-term/src/keymap.rs diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index b614f5395c12..81fa20951122 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -18,8 +18,8 @@ use crate::{ commands::apply_workspace_edit, compositor::{Compositor, Event}, config::Config, + keymap::Keymap, job::Jobs, - keymap::keymaps::Keymaps, ui::{self, overlay::overlayed}, }; use std::{ @@ -177,7 +177,7 @@ impl Application { let keys = Box::new(Map::new(Arc::clone(&config), |config: &Config| { &config.keys })); - let editor_view = Box::new(ui::EditorView::new(Keymaps::new(keys))); + let editor_view = Box::new(ui::EditorView::new(Keymap::new(keys))); compositor.push(editor_view); if args.load_tutor { diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 55b4e2b3009a..6d013456a29f 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -4,13 +4,12 @@ pub(crate) mod typed; pub use dap::*; pub use lsp::*; -use tui::widgets::{Row, Cell}; pub use typed::*; use crate::{ commands::insert::*, args, - keymap::keymaps::CommandList, + keymap::CommandList, compositor::{self, Component, Compositor}, job::{Callback, self, Jobs}, ui::{self, overlay::overlayed, FilePicker, Picker, Popup, Prompt, PromptEvent, menu::{Cell, Row}}, @@ -2486,7 +2485,7 @@ impl ui::menu::Item for MappableCommand { pub fn command_palette(cx: &mut Context) { cx.callback = Some(Box::new( move |compositor: &mut Compositor, cx: &mut compositor::Context| { - let keymap_command_lists = compositor.find::().unwrap().keymaps.command_list(&cx.editor.mode); + let keymap_command_lists = compositor.find::().unwrap().keymap.command_list(&cx.editor.mode); let mut commands: Vec = MappableCommand::STATIC_COMMAND_LIST.into(); commands.extend(typed::TYPABLE_COMMAND_LIST.iter().map(|cmd| { diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs new file mode 100644 index 000000000000..780b7e6531aa --- /dev/null +++ b/helix-term/src/keymap.rs @@ -0,0 +1,161 @@ +pub mod default; +pub mod macros; +// NOTE: Only pub becuase of their use in macros +pub mod keytrienode; +pub mod keytrie; +mod tests; + +use self::{ + keytrienode::KeyTrieNode, + keytrie::KeyTrie, + macros::key, +}; + +use crate::commands::MappableCommand; +use helix_view::{document::Mode, input::KeyEvent}; +use std::{sync::Arc, collections::HashMap}; +use arc_swap::{access::{DynAccess, DynGuard}, ArcSwap}; + +use std::ops::Deref; + +#[derive(Debug, Clone, PartialEq)] +pub enum KeymapResult { + Pending(KeyTrie), + Matched(MappableCommand), + MatchedCommandSequence(Vec), + NotFound, + /// Contains pressed KeyEvents leading up to the cancellation. + Cancelled(Vec), +} + +pub struct Keymap { + pub keytries: Box>>, + /// Relative to a sticky node if Some. + pending_keys: Vec, + pub sticky_keytrie: Option, +} + +pub type CommandList = HashMap>; +impl Keymap { + pub fn new(keymaps: Box>>) -> Self { + Self { + keytries: keymaps, + pending_keys: Vec::new(), + sticky_keytrie: None, + } + } + + pub fn load_keymaps(&self) -> DynGuard> { + self.keytries.load() + } + + /// Returns list of keys waiting to be disambiguated in current mode. + pub fn pending(&self) -> &[KeyEvent] { + &self.pending_keys + } + + pub fn sticky_keytrie(&self) -> Option<&KeyTrie> { + self.sticky_keytrie.as_ref() + } + + /// Lookup `key` in the keymap to try and find a command to execute. + /// Escape key represents cancellation. + /// This means clearing pending keystrokes, or the sticky_keytrie if none were present. + pub fn get(&mut self, mode: Mode, key: KeyEvent) -> KeymapResult { + // TODO: remove the sticky part and look up manually + let keymaps = &*self.load_keymaps(); + let active_keymap = &keymaps[&mode]; + + if key == key!(Esc) { + if !self.pending_keys.is_empty() { + // NOTE: Esc is not included here + return KeymapResult::Cancelled(self.pending_keys.drain(..).collect()); + } + // TODO: Shouldn't we return here also? + self.sticky_keytrie = None; + } + + // Check if sticky keytrie is to be used. + let starting_keytrie = match self.sticky_keytrie { + None => &active_keymap, + Some(ref active_sticky_keytrie) => active_sticky_keytrie, + }; + + // TODO: why check either pending or regular key? + let first_key = self.pending_keys.get(0).unwrap_or(&key); + + let pending_keytrie: KeyTrie = match starting_keytrie.traverse(&[*first_key]) { + Some(KeyTrieNode::KeyTrie(sub_keytrie)) => sub_keytrie, + Some(KeyTrieNode::MappableCommand(cmd)) => { + return KeymapResult::Matched(cmd.clone()); + } + Some(KeyTrieNode::CommandSequence(cmds)) => { + return KeymapResult::MatchedCommandSequence(cmds.clone()); + } + None => return KeymapResult::NotFound, + }; + + self.pending_keys.push(key); + match pending_keytrie.traverse(&self.pending_keys[1..]) { + Some(KeyTrieNode::KeyTrie(map)) => { + if map.is_sticky { + self.pending_keys.clear(); + self.sticky_keytrie = Some(map.clone()); + } + KeymapResult::Pending(map.clone()) + } + Some(KeyTrieNode::MappableCommand(cmd)) => { + self.pending_keys.clear(); + KeymapResult::Matched(cmd.clone()) + } + Some(KeyTrieNode::CommandSequence(cmds)) => { + self.pending_keys.clear(); + KeymapResult::MatchedCommandSequence(cmds.clone()) + } + None => KeymapResult::Cancelled(self.pending_keys.drain(..).collect()), + } + } + + fn get_keytrie(&self, mode: &Mode) -> KeyTrie { + // HELP: Unsure how I should handle this Option + self.keytries.load().get(mode).unwrap().clone() + } + + /// Returns a key-value list of all commands associated to a given Keymap. + /// Keys are the node names (see KeyTrieNode documentation) + /// Values are lists of stringified KeyEvents that triger the command. + /// Each element in the KeyEvent list is prefixed with prefixed the ancestor KeyEvents. + /// For example: Stringified KeyEvent element for the 'goto_next_window' command could be "space>w>w". + /// Ancestor KeyEvents are in this case "space" and "w". + pub fn command_list(&self, mode: &Mode) -> CommandList { + let mut list = HashMap::new(); + _command_list(&mut list, &KeyTrieNode::KeyTrie(self.get_keytrie(mode)), &mut String::new()); + return list; + + fn _command_list(list: &mut CommandList, node: &KeyTrieNode, prefix: &mut String) { + match node { + KeyTrieNode::KeyTrie(trie_node) => { + for (key_event, subtrie_node) in trie_node.deref() { + let mut temp_prefix: String = prefix.to_string(); + if &temp_prefix != "" { + temp_prefix.push_str(">"); + } + temp_prefix.push_str(&key_event.to_string()); + _command_list(list, subtrie_node, &mut temp_prefix); + } + }, + KeyTrieNode::MappableCommand(mappable_command) => { + if mappable_command.name() == "no_op" { return } + list.entry(mappable_command.name().to_string()).or_default().push(prefix.to_string()); + }, + KeyTrieNode::CommandSequence(_) => {} + }; + } + } +} + +impl Default for Keymap { + fn default() -> Self { + Self::new(Box::new(ArcSwap::new(Arc::new(default::default())))) + } +} diff --git a/helix-term/src/keymap/tests.rs b/helix-term/src/keymap/tests.rs index 6d566c91f7ad..07145bdf310a 100644 --- a/helix-term/src/keymap/tests.rs +++ b/helix-term/src/keymap/tests.rs @@ -3,17 +3,8 @@ mod tests { use helix_core::hashmap; use helix_view::{document::Mode, input::KeyEvent}; -<<<<<<< HEAD use crate::keymap::{*, macros::*}; use std::collections::HashMap; -======= - use crate::{ - keymap::macros::*, - keymap::keymaps::Keymaps, - }; - use std::{sync::Arc, collections::HashMap}; - use arc_swap::ArcSwap; ->>>>>>> adb17e18 (Removed keymap::Keymap:) #[test] #[should_panic] @@ -32,20 +23,15 @@ mod tests { #[test] fn aliased_modes_are_same_in_default_keymap() { -<<<<<<< HEAD - let root = Keymap::default().get(&Mode::Normal).unwrap().root_node.clone(); -======= - let keymaps = Keymaps::default().keymaps; - let root = keymaps.load().get(&Mode::Normal).unwrap().clone(); ->>>>>>> adb17e18 (Removed keymap::Keymap:) + let normal_mode_keytrie_root = Keymap::default().get_keytrie(&Mode::Normal); assert_eq!( - root.traverse(&[key!(' '), key!('w')]).unwrap(), - root.traverse(&["C-w".parse::().unwrap()]).unwrap(), + normal_mode_keytrie_root.traverse(&[key!(' '), key!('w')]).unwrap(), + normal_mode_keytrie_root.traverse(&["C-w".parse::().unwrap()]).unwrap(), "Mismatch for window mode on `Space-w` and `Ctrl-w`." ); assert_eq!( - root.traverse(&[key!('z')]).unwrap(), - root.traverse(&[key!('Z')]).unwrap(), + normal_mode_keytrie_root.traverse(&[key!('z')]).unwrap(), + normal_mode_keytrie_root.traverse(&[key!('Z')]).unwrap(), "Mismatch for view mode on `z` and `Z`." ); } @@ -61,7 +47,7 @@ mod tests { "j" | "k" => move_line_down, }); - let keymap = Keymaps::new(Box::new(ArcSwap::new(Arc::new(hashmap!(Mode::Normal => normal_mode))))); + let keymap = Keymap::new(Box::new(ArcSwap::new(Arc::new(hashmap!(Mode::Normal => normal_mode))))); let mut command_list = keymap.command_list(&Mode::Normal); // sort keybindings in order to have consistent tests diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 98da59c95097..a4f3f182d9c2 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -3,7 +3,7 @@ use crate::{ compositor::{Component, Context, Event, EventResult}, job::{self, Callback}, key, - keymap::keymaps::{KeymapResult, Keymaps}, + keymap::{KeymapResult, Keymap}, ui::{Completion, ProgressSpinners}, }; @@ -32,7 +32,7 @@ use super::lsp::SignatureHelp; use super::statusline; pub struct EditorView { - pub keymaps: Keymaps, + pub keymap: Keymap, on_next_key: Option>, pseudo_pending: Vec, last_insert: (commands::MappableCommand, Vec), @@ -48,9 +48,9 @@ pub enum InsertEvent { } impl EditorView { - pub fn new(keymaps: Keymaps) -> Self { + pub fn new(keymap: Keymap) -> Self { Self { - keymaps, + keymap, on_next_key: None, pseudo_pending: Vec::new(), last_insert: (commands::MappableCommand::normal_mode, Vec::new()), @@ -928,9 +928,9 @@ impl EditorView { event: KeyEvent, ) -> Option { let mut last_mode = mode; - self.pseudo_pending.extend(self.keymaps.pending()); - let key_result = self.keymaps.get(mode, event); - cxt.editor.autoinfo = self.keymaps.sticky_keytrie().map(|node| node.infobox()); + self.pseudo_pending.extend(self.keymap.pending()); + let key_result = self.keymap.get(mode, event); + cxt.editor.autoinfo = self.keymap.sticky_keytrie().map(|node| node.infobox()); let mut execute_command = |command: &commands::MappableCommand| { command.execute(cxt); @@ -994,7 +994,7 @@ impl EditorView { Some(ch) => commands::insert::insert_char(cx, ch), None => { if let KeymapResult::Matched(command) = - self.keymaps.get(Mode::Insert, ev) + self.keymap.get(Mode::Insert, ev) { command.execute(cx); } @@ -1016,7 +1016,7 @@ impl EditorView { std::num::NonZeroUsize::new(cxt.editor.count.map_or(i, |c| c.get() * 10 + i)); } // special handling for repeat operator - (key!('.'), _) if self.keymaps.pending().is_empty() => { + (key!('.'), _) if self.keymap.pending().is_empty() => { for _ in 0..cxt.editor.count.map_or(1, NonZeroUsize::into) { // first execute whatever put us into insert mode self.last_insert.0.execute(cxt); @@ -1063,7 +1063,7 @@ impl EditorView { cxt.register = cxt.editor.selected_register.take(); self.handle_keymap_event(mode, cxt, event); - if self.keymaps.pending().is_empty() { + if self.keymap.pending().is_empty() { cxt.editor.count = None } } @@ -1521,7 +1521,7 @@ impl Component for EditorView { if let Some(count) = cx.editor.count { disp.push_str(&count.to_string()) } - for key in self.keymaps.pending() { + for key in self.keymap.pending() { disp.push_str(&key.key_sequence_format()); } for key in &self.pseudo_pending { From bda06bcd32d6e24bcee219d7715b64566b4aa743 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Fri, 20 Jan 2023 17:34:59 +0100 Subject: [PATCH 034/105] Re-implemented pre-defined orders in keytrie * Fixed aliased_modes_are_same_in_default_keymap to check for order too. It failed to detect a bug in which the swap_view_* were ordered differently under space-w and c-w. --- helix-term/src/config.rs | 11 +- helix-term/src/keymap.rs | 9 +- helix-term/src/keymap/default.rs | 4 +- helix-term/src/keymap/keytrie.rs | 162 ++++++++++++++++----------- helix-term/src/keymap/keytrienode.rs | 27 ++++- helix-term/src/keymap/macros.rs | 38 ++++--- helix-term/src/keymap/tests.rs | 4 +- 7 files changed, 150 insertions(+), 105 deletions(-) diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index 8f7649578c70..3ed2fa52323b 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -103,11 +103,8 @@ mod tests { #[test] fn keys_resolve_to_correct_defaults() { - // From serde default - let default_keys = toml::from_str::("").unwrap().keys; - assert_eq!(default_keys, default::default()); - - // From the Default trait + let serde_default = toml::from_str::("").unwrap().keys; + assert_eq!(serde_default, default::default()); let default_keys = Config::default().keys; assert_eq!(default_keys, default::default()); } @@ -173,7 +170,7 @@ mod tests { ); // Huh? - assert!(merged_config.keys.get(&Mode::Normal).unwrap().len() > 1); - assert!(merged_config.keys.get(&Mode::Insert).unwrap().len() > 0); + assert!(merged_config.keys.get(&Mode::Normal).unwrap().get_children().len() > 1); + assert!(merged_config.keys.get(&Mode::Insert).unwrap().get_children().len() > 0); } } diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 780b7e6531aa..9bd04222e39f 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -16,8 +16,6 @@ use helix_view::{document::Mode, input::KeyEvent}; use std::{sync::Arc, collections::HashMap}; use arc_swap::{access::{DynAccess, DynGuard}, ArcSwap}; -use std::ops::Deref; - #[derive(Debug, Clone, PartialEq)] pub enum KeymapResult { Pending(KeyTrie), @@ -135,13 +133,14 @@ impl Keymap { fn _command_list(list: &mut CommandList, node: &KeyTrieNode, prefix: &mut String) { match node { KeyTrieNode::KeyTrie(trie_node) => { - for (key_event, subtrie_node) in trie_node.deref() { + for (key_event, index) in trie_node.get_child_order() { let mut temp_prefix: String = prefix.to_string(); if &temp_prefix != "" { - temp_prefix.push_str(">"); + temp_prefix.push_str("→"); } temp_prefix.push_str(&key_event.to_string()); - _command_list(list, subtrie_node, &mut temp_prefix); + _command_list(list, &trie_node.get_children()[*index], &mut temp_prefix); + } }, KeyTrieNode::MappableCommand(mappable_command) => { diff --git a/helix-term/src/keymap/default.rs b/helix-term/src/keymap/default.rs index 5ee0706622e1..3fffaa08c517 100644 --- a/helix-term/src/keymap/default.rs +++ b/helix-term/src/keymap/default.rs @@ -187,10 +187,10 @@ pub fn default() -> HashMap { "C-j" | "j" | "down" => jump_view_down, "C-k" | "k" | "up" => jump_view_up, "C-l" | "l" | "right" => jump_view_right, - "L" => swap_view_right, - "K" => swap_view_up, "H" => swap_view_left, "J" => swap_view_down, + "K" => swap_view_up, + "L" => swap_view_right, "n" => { "New split scratch buffer" "C-s" | "s" => hsplit_new, "C-v" | "v" => vsplit_new, diff --git a/helix-term/src/keymap/keytrie.rs b/helix-term/src/keymap/keytrie.rs index cb7798971246..8d97c7a8ca5b 100644 --- a/helix-term/src/keymap/keytrie.rs +++ b/helix-term/src/keymap/keytrie.rs @@ -1,25 +1,36 @@ use super::keytrienode::KeyTrieNode; use helix_view::{info::Info, input::KeyEvent}; -use std::{collections::HashMap, ops::{Deref, DerefMut}}; +use std::{collections::HashMap, ops::{Deref, DerefMut}, cmp::Ordering}; use serde::Deserialize; /// Edges of the trie are KeyEvents and the nodes are descrbibed by KeyTrieNode #[derive(Debug, Clone)] pub struct KeyTrie { documentation: String, - children: HashMap, + /// Used for pre-defined order in infoboxes, values represent the index of the key tries children. + child_order: HashMap, + children: Vec, pub is_sticky: bool, } impl KeyTrie { - pub fn new(documentation: &str, children: HashMap) -> Self { + pub fn new(documentation: &str, child_order: HashMap, children: Vec) -> Self { Self { documentation: documentation.to_string(), + child_order, children, is_sticky: false, } } + pub fn get_child_order(&self) -> &HashMap { + &self.child_order + } + + pub fn get_children(&self) -> &Vec { + &self.children + } + // None symbolizes NotFound pub fn traverse(&self, key_events: &[KeyEvent]) -> Option { return _traverse(self, key_events, 0); @@ -28,13 +39,13 @@ impl KeyTrie { if depth == key_events.len() { return Some(KeyTrieNode::KeyTrie(keytrie.clone())); } - else if let Some(found_child) = keytrie.get(&key_events[depth]) { - match found_child { + else if let Some(found_index) = keytrie.child_order.get(&key_events[depth]) { + match &keytrie.children[*found_index] { KeyTrieNode::KeyTrie(sub_keytrie) => { depth += 1; return _traverse(sub_keytrie, key_events, depth) }, - _ => return Some(found_child.clone()) + _found_child => return Some(_found_child.clone()) } } return None; @@ -42,84 +53,81 @@ impl KeyTrie { } pub fn merge_keytrie(&mut self, mut other_keytrie: Self) { - for (other_key_event, other_child_node) in std::mem::take(&mut other_keytrie.children) { - match other_child_node { - KeyTrieNode::KeyTrie(other_child_key_trie) => { - if let Some(KeyTrieNode::KeyTrie(self_clashing_child_key_trie)) = self.children.get_mut(&other_key_event) { - self_clashing_child_key_trie.merge_keytrie(other_child_key_trie); + for (other_key_event, other_index) in other_keytrie.get_child_order() { + let other_child_keytrie_node = &other_keytrie.get_children()[*other_index]; + match other_child_keytrie_node { + KeyTrieNode::KeyTrie(ref other_child_keytrie) => { + if let Some(self_index) = self.child_order.get(&other_key_event) { + if let KeyTrieNode::KeyTrie(ref mut self_clashing_child_key_trie) = self.children[*self_index] { + self_clashing_child_key_trie.merge_keytrie(other_child_keytrie.clone()); + } } else { - self.children.insert(other_key_event, KeyTrieNode::KeyTrie(other_child_key_trie)); + self.child_order.insert(*other_key_event, self.children.len()); + self.children.push(KeyTrieNode::KeyTrie(other_child_keytrie.clone())); } } KeyTrieNode::MappableCommand(_) | KeyTrieNode::CommandSequence(_) => { - self.children.insert(other_key_event, other_child_node); + if let Some(existing_index) = self.child_order.get(other_key_event) { + self.children[*existing_index] = other_child_keytrie_node.clone(); + } + else { + self.child_order.insert(*other_key_event, self.children.len()); + self.children.push(other_child_keytrie_node.clone()); + } } } } } - + /// Open an Info box for a given KeyTrie /// Shows the children as possible KeyEvents and thier associated description. pub fn infobox(&self) -> Info { - let mut body: Vec<(Vec, &str)> = Vec::with_capacity(self.len()); - for (&key_event, key_trie) in self.iter() { + let mut body: InfoBoxBody = Vec::with_capacity(self.children.len()); + let mut key_event_order = Vec::with_capacity(self.children.len()); + // child_order and children is of same length + unsafe { key_event_order.set_len(self.children.len()); } + for (key_event, index) in &self.child_order { + key_event_order[*index] = key_event.clone(); + } + + for (index, key_trie) in self.children.iter().enumerate() { let documentation: &str = match key_trie { - KeyTrieNode::MappableCommand(command) => { + KeyTrieNode::MappableCommand(ref command) => { if command.name() == "no_op" { continue; } command.description() }, - KeyTrieNode::KeyTrie(key_trie) => &key_trie.documentation, + KeyTrieNode::KeyTrie(ref key_trie) => &key_trie.documentation, // FIX: default to a join of all command names // NOTE: Giving same documentation for all sequences will place all sequence keyvents together. // Regardless if the command sequence is different. KeyTrieNode::CommandSequence(_) => "[Multiple commands]", }; + let key_event = key_event_order[index]; match body.iter().position(|(_, existing_documentation)| &documentation == existing_documentation) { Some(position) => body[position].0.push(key_event.to_string()), None => { - let mut temp_vec: Vec = Vec::new(); - temp_vec.push(key_event.to_string()); - body.push((temp_vec, documentation)) + body.push((vec![key_event.to_string()], documentation)) }, } } // Shortest keyevent (as string) appears first, unless is a "C-" KeyEvent // Those events will always be placed after the one letter KeyEvent - let mut sorted_body = body - .iter() - .map(|(key_events, description)| { - let mut temp_key_events = key_events.clone(); - temp_key_events.sort_unstable_by(|a, b| a.len().cmp(&b.len())); - (temp_key_events, *description) - }) - .collect::, &str)>>(); - sorted_body.sort_unstable_by(|a, b| a.0[0].to_lowercase().cmp(&b.0[0].to_lowercase())); - // Consistently place lowercase before uppercase of the same letter. - if sorted_body.len() > 1 { - let mut x_index = 0; - let mut y_index = 1; - - while y_index < sorted_body.len() { - let x = &sorted_body[x_index].0[0]; - let y = &sorted_body[y_index].0[0]; - if x.to_lowercase() == y.to_lowercase() { - // Uppercase regarded as lower value. - if x < y { - let temp_holder = sorted_body[x_index].clone(); - sorted_body[x_index] = sorted_body[y_index].clone(); - sorted_body[y_index] = temp_holder; - } + for (key_events, _) in body.iter_mut() { + key_events.sort_unstable_by(|a, b| { + if a.len() == 1 { return Ordering::Less } + if b.len() > a.len() && b.starts_with("C-") { + return Ordering::Greater } - x_index = y_index; - y_index += 1; - } + a.len().cmp(&b.len()) + }); } - let stringified_key_events_body: Vec<(String, &str)> = sorted_body + // TODO: conditional sort added here by calling infobox_sort(body) + let stringified_key_events_body: Vec<(String, &str)> = body .iter() .map(|(key_events, description)| { let key_events_string: String = key_events.iter().fold(String::new(), |mut acc, key_event| { @@ -137,7 +145,7 @@ impl KeyTrie { impl Default for KeyTrie { fn default() -> Self { - Self::new("", HashMap::new()) + Self::new("", HashMap::new(), Vec::new()) } } @@ -147,30 +155,50 @@ impl PartialEq for KeyTrie { } } -/// Returns the children of the KeyTrie -impl Deref for KeyTrie { - type Target = HashMap; - - fn deref(&self) -> &Self::Target { - &self.children - } -} - -/// Returns the children of the KeyTrie -impl DerefMut for KeyTrie { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.children - } -} - impl<'de> Deserialize<'de> for KeyTrie { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { + // NOTE: no assumption of pre-defined order in config + let child_collection = HashMap::::deserialize(deserializer)?; + let mut child_order = HashMap::::new(); + let mut children = Vec::new(); + for (key_event, keytrie_node) in child_collection { + child_order.insert(key_event, children.len()); + children.push(keytrie_node); + } + Ok(Self { - children: HashMap::::deserialize(deserializer)?, + child_order, + children, ..Default::default() }) } } + +type InfoBoxBody<'a> = Vec<(Vec, &'a str)>; +fn infobox_sort(mut body: InfoBoxBody) -> InfoBoxBody { + body.sort_unstable_by(|a, b| a.0[0].to_lowercase().cmp(&b.0[0].to_lowercase())); + // Consistently place lowercase before uppercase of the same letter. + if body.len() > 1 { + let mut x_index = 0; + let mut y_index = 1; + + while y_index < body.len() { + let x = &body[x_index].0[0]; + let y = &body[y_index].0[0]; + if x.to_lowercase() == y.to_lowercase() { + // Uppercase regarded as lower value. + if x < y { + let temp_holder = body[x_index].clone(); + body[x_index] = body[y_index].clone(); + body[y_index] = temp_holder; + } + } + x_index = y_index; + y_index += 1; + } + } + body +} diff --git a/helix-term/src/keymap/keytrienode.rs b/helix-term/src/keymap/keytrienode.rs index 433372377787..66a642ac902d 100644 --- a/helix-term/src/keymap/keytrienode.rs +++ b/helix-term/src/keymap/keytrienode.rs @@ -8,7 +8,7 @@ use serde::{Deserialize, de::Visitor}; /// For the MappableCommand and CommandSequence variants, the property is self explanatory. /// For KeyTrie, the documentation is used for respective infobox titles, /// or infobox KeyEvent descriptions that in themselves trigger the opening of another infobox. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub enum KeyTrieNode { MappableCommand(MappableCommand), CommandSequence(Vec), @@ -24,6 +24,23 @@ impl<'de> Deserialize<'de> for KeyTrieNode { } } +impl PartialEq for KeyTrieNode { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (KeyTrieNode::MappableCommand(_self), KeyTrieNode::MappableCommand(_other)) => { + _self == _other + }, + (KeyTrieNode::CommandSequence(_self), KeyTrieNode::CommandSequence(_other)) => { + _self == _other + }, + (KeyTrieNode::KeyTrie(_self), KeyTrieNode::KeyTrie(_other)) => { + _self.get_children() == _other.get_children() + }, + _ => false + } + } +} + struct KeyTrieNodeVisitor; impl<'de> Visitor<'de> for KeyTrieNodeVisitor { @@ -62,10 +79,12 @@ impl<'de> Visitor<'de> for KeyTrieNodeVisitor { where M: serde::de::MapAccess<'de>, { - let mut sub_key_trie = HashMap::new(); + let mut children = Vec::new(); + let mut child_order = HashMap::new(); while let Some((key_event, key_trie_node)) = map.next_entry::()? { - sub_key_trie.insert(key_event, key_trie_node); + child_order.insert(key_event, children.len()); + children.push(key_trie_node); } - Ok(KeyTrieNode::KeyTrie(KeyTrie::new("", sub_key_trie))) + Ok(KeyTrieNode::KeyTrie(KeyTrie::new("", child_order, children))) } } \ No newline at end of file diff --git a/helix-term/src/keymap/macros.rs b/helix-term/src/keymap/macros.rs index e17bad91d48d..b834c43e56aa 100644 --- a/helix-term/src/keymap/macros.rs +++ b/helix-term/src/keymap/macros.rs @@ -1,8 +1,8 @@ #[macro_export] macro_rules! key { - ($key:ident) => { + ($key_event:ident) => { ::helix_view::input::KeyEvent { - code: ::helix_view::keyboard::KeyCode::$key, + code: ::helix_view::keyboard::KeyCode::$key_event, modifiers: ::helix_view::keyboard::KeyModifiers::NONE, } }; @@ -16,9 +16,9 @@ macro_rules! key { #[macro_export] macro_rules! shift { - ($key:ident) => { + ($key_event:ident) => { ::helix_view::input::KeyEvent { - code: ::helix_view::keyboard::KeyCode::$key, + code: ::helix_view::keyboard::KeyCode::$key_event, modifiers: ::helix_view::keyboard::KeyModifiers::SHIFT, } }; @@ -32,9 +32,9 @@ macro_rules! shift { #[macro_export] macro_rules! ctrl { - ($key:ident) => { + ($key_event:ident) => { ::helix_view::input::KeyEvent { - code: ::helix_view::keyboard::KeyCode::$key, + code: ::helix_view::keyboard::KeyCode::$key_event, modifiers: ::helix_view::keyboard::KeyModifiers::CONTROL, } }; @@ -48,9 +48,9 @@ macro_rules! ctrl { #[macro_export] macro_rules! alt { - ($key:ident) => { + ($key_event:ident) => { ::helix_view::input::KeyEvent { - code: ::helix_view::keyboard::KeyCode::$key, + code: ::helix_view::keyboard::KeyCode::$key_event, modifiers: ::helix_view::keyboard::KeyModifiers::ALT, } }; @@ -79,27 +79,29 @@ macro_rules! alt { /// ``` #[macro_export] macro_rules! keytrie { - ({ $label:literal $(sticky=$sticky:literal)? $($($key:literal)|+ => $value:tt,)+ }) => { - // modified from the hashmap! macro + // Sub key_trie + ({ $label:literal $(sticky=$sticky:literal)? $($($key_event:literal)|+ => $value:tt,)+ }) => { { - let _cap = hashmap!(@count $($($key),+),*); - let mut _map: ::std::collections::HashMap<::helix_view::input::KeyEvent, $crate::keymap::keytrienode::KeyTrieNode> = - ::std::collections::HashMap::with_capacity(_cap); + let _cap = hashmap!(@count $($($key_event),+),*); + let mut _children: Vec<$crate::keymap::keytrienode::KeyTrieNode> = ::std::vec::Vec::new(); + let mut _child_order: ::std::collections::HashMap<::helix_view::input::KeyEvent, usize> = ::std::collections::HashMap::with_capacity(_cap); $( $( - let _key = $key.parse::<::helix_view::input::KeyEvent>().unwrap(); - let _potential_duplicate = _map.insert(_key,keytrie!(@trie $value)); + let _key_event = $key_event.parse::<::helix_view::input::KeyEvent>().unwrap(); + let _potential_duplicate = _child_order.insert(_key_event, _children.len()); assert!(_potential_duplicate.is_none(), "Duplicate key found: {:?}", _potential_duplicate.unwrap()); + _children.push(keytrie!(@trie $value)); )+ )* - let mut _node = $crate::keymap::keytrie::KeyTrie::new($label, _map); + + let mut _node = $crate::keymap::keytrie::KeyTrie::new($label, _child_order, _children); $( _node.is_sticky = $sticky; )? _node } }; - (@trie {$label:literal $(sticky=$sticky:literal)? $($($key:literal)|+ => $value:tt,)+ }) => { - $crate::keymap::keytrienode::KeyTrieNode::KeyTrie(keytrie!({ $label $(sticky=$sticky)? $($($key)|+ => $value,)+ })) + (@trie {$label:literal $(sticky=$sticky:literal)? $($($key_event:literal)|+ => $value:tt,)+ }) => { + $crate::keymap::keytrienode::KeyTrieNode::KeyTrie(keytrie!({ $label $(sticky=$sticky)? $($($key_event)|+ => $value,)+ })) }; (@trie $cmd:ident) => { diff --git a/helix-term/src/keymap/tests.rs b/helix-term/src/keymap/tests.rs index 07145bdf310a..4ab431c6e31d 100644 --- a/helix-term/src/keymap/tests.rs +++ b/helix-term/src/keymap/tests.rs @@ -66,11 +66,11 @@ mod tests { ), ( "goto_file_start".to_string(), - vec![format!("{}>{}", key!('g'), key!('g'))] + vec![format!("{}→{}", key!('g'), key!('g'))] ), ( "goto_file_end".to_string(), - vec![format!("{}>{}", key!('g'), key!('e'))] + vec![format!("{}→{}", key!('g'), key!('e'))] ), ( "move_line_down".to_string(), From 87fc5714039b5eaa47ee6d9c905b968d5ef28f7b Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Sat, 21 Jan 2023 16:20:05 +0100 Subject: [PATCH 035/105] Add sort_infobox editor config option --- book/src/configuration.md | 1 + helix-term/src/keymap/keytrie.rs | 7 ++++--- helix-term/src/ui/editor.rs | 5 +++-- helix-view/src/editor.rs | 3 +++ 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/book/src/configuration.md b/book/src/configuration.md index ab229f772a1c..9e9c07451c31 100644 --- a/book/src/configuration.md +++ b/book/src/configuration.md @@ -53,6 +53,7 @@ on unix operating systems. | `idle-timeout` | Time in milliseconds since last keypress before idle timers trigger. Used for autocompletion, set to 0 for instant. | `400` | | `completion-trigger-len` | The min-length of word under cursor to trigger autocompletion | `2` | | `auto-info` | Whether to display infoboxes | `true` | +| `sorted-infobox` | Sort infoboxes by key event category rather than by predefined command categories | `false` | | `true-color` | Set to `true` to override automatic detection of terminal truecolor support in the event of a false negative. | `false` | | `rulers` | List of column positions at which to display the rulers. Can be overridden by language specific `rulers` in `languages.toml` file. | `[]` | | `bufferline` | Renders a line at the top of the editor displaying open buffers. Can be `always`, `never` or `multiple` (only shown if more than one buffer is in use) | `never` | diff --git a/helix-term/src/keymap/keytrie.rs b/helix-term/src/keymap/keytrie.rs index 8d97c7a8ca5b..d2b9734f1cf2 100644 --- a/helix-term/src/keymap/keytrie.rs +++ b/helix-term/src/keymap/keytrie.rs @@ -82,7 +82,7 @@ impl KeyTrie { /// Open an Info box for a given KeyTrie /// Shows the children as possible KeyEvents and thier associated description. - pub fn infobox(&self) -> Info { + pub fn infobox(&self, alphabetical_sort: bool) -> Info { let mut body: InfoBoxBody = Vec::with_capacity(self.children.len()); let mut key_event_order = Vec::with_capacity(self.children.len()); // child_order and children is of same length @@ -126,7 +126,8 @@ impl KeyTrie { }); } - // TODO: conditional sort added here by calling infobox_sort(body) + if alphabetical_sort { body = alphabetially_sort_infobox(body); } + let stringified_key_events_body: Vec<(String, &str)> = body .iter() .map(|(key_events, description)| { @@ -178,7 +179,7 @@ impl<'de> Deserialize<'de> for KeyTrie { } type InfoBoxBody<'a> = Vec<(Vec, &'a str)>; -fn infobox_sort(mut body: InfoBoxBody) -> InfoBoxBody { +fn alphabetially_sort_infobox(mut body: InfoBoxBody) -> InfoBoxBody { body.sort_unstable_by(|a, b| a.0[0].to_lowercase().cmp(&b.0[0].to_lowercase())); // Consistently place lowercase before uppercase of the same letter. if body.len() > 1 { diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index a4f3f182d9c2..94ead3f0f0e6 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -930,7 +930,8 @@ impl EditorView { let mut last_mode = mode; self.pseudo_pending.extend(self.keymap.pending()); let key_result = self.keymap.get(mode, event); - cxt.editor.autoinfo = self.keymap.sticky_keytrie().map(|node| node.infobox()); + let sort_infobox = cxt.editor.config.load().sorted_infobox; + cxt.editor.autoinfo = self.keymap.sticky_keytrie().map(|node| node.infobox(sort_infobox)); let mut execute_command = |command: &commands::MappableCommand| { command.execute(cxt); @@ -969,7 +970,7 @@ impl EditorView { KeymapResult::Matched(command) => { execute_command(command); } - KeymapResult::Pending(node) => cxt.editor.autoinfo = Some(node.infobox()), + KeymapResult::Pending(node) => cxt.editor.autoinfo = Some(node.infobox(sort_infobox)), KeymapResult::MatchedCommandSequence(commands) => { for command in commands { execute_command(command); diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 9af8e4c34175..c54f8be94248 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -246,6 +246,8 @@ pub struct Config { pub completion_trigger_len: u8, /// Whether to display infoboxes. Defaults to true. pub auto_info: bool, + /// Sort infoboxes alphabetically rather than by predefined categories. Defaults to `false`. + pub sorted_infobox: bool, pub file_picker: FilePickerConfig, /// Configuration of the statusline elements pub statusline: StatusLineConfig, @@ -717,6 +719,7 @@ impl Default for Config { bufferline: BufferLine::default(), indent_guides: IndentGuidesConfig::default(), color_modes: false, + sorted_infobox: false, } } } From ecf5d61a75c10b602e22ad7fa0d899e1cc083a82 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Sun, 22 Jan 2023 16:45:50 +0100 Subject: [PATCH 036/105] Refine sorting behavior Sorts by modifier, then by KeyCode category, then by each KeyEvent. Single character alphas are placed before the rest (ex. "space") in KeyCode::Char. --- helix-term/Cargo.toml | 1 - helix-term/src/keymap/keytrie.rs | 126 +++++++++++++++++++++++++------ helix-view/src/keyboard.rs | 59 +++++---------- 3 files changed, 120 insertions(+), 66 deletions(-) diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml index 895a08828a98..fd009aa776ed 100644 --- a/helix-term/Cargo.toml +++ b/helix-term/Cargo.toml @@ -36,7 +36,6 @@ helix-loader = { version = "0.6", path = "../helix-loader" } anyhow = "1" once_cell = "1.17" - which = "4.2" tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] } diff --git a/helix-term/src/keymap/keytrie.rs b/helix-term/src/keymap/keytrie.rs index d2b9734f1cf2..7fe5df9a8610 100644 --- a/helix-term/src/keymap/keytrie.rs +++ b/helix-term/src/keymap/keytrie.rs @@ -80,9 +80,9 @@ impl KeyTrie { } } - /// Open an Info box for a given KeyTrie + /// Open an info box for a given KeyTrie /// Shows the children as possible KeyEvents and thier associated description. - pub fn infobox(&self, alphabetical_sort: bool) -> Info { + pub fn infobox(&self, sort_infobox: bool) -> Info { let mut body: InfoBoxBody = Vec::with_capacity(self.children.len()); let mut key_event_order = Vec::with_capacity(self.children.len()); // child_order and children is of same length @@ -114,6 +114,7 @@ impl KeyTrie { } } + // TODO: Add "A-" aknowledgement? // Shortest keyevent (as string) appears first, unless is a "C-" KeyEvent // Those events will always be placed after the one letter KeyEvent for (key_events, _) in body.iter_mut() { @@ -126,7 +127,7 @@ impl KeyTrie { }); } - if alphabetical_sort { body = alphabetially_sort_infobox(body); } + if sort_infobox { body = keyevent_sort_infobox(body); } let stringified_key_events_body: Vec<(String, &str)> = body .iter() @@ -178,28 +179,107 @@ impl<'de> Deserialize<'de> for KeyTrie { } } -type InfoBoxBody<'a> = Vec<(Vec, &'a str)>; -fn alphabetially_sort_infobox(mut body: InfoBoxBody) -> InfoBoxBody { - body.sort_unstable_by(|a, b| a.0[0].to_lowercase().cmp(&b.0[0].to_lowercase())); - // Consistently place lowercase before uppercase of the same letter. - if body.len() > 1 { - let mut x_index = 0; - let mut y_index = 1; - - while y_index < body.len() { - let x = &body[x_index].0[0]; - let y = &body[y_index].0[0]; - if x.to_lowercase() == y.to_lowercase() { - // Uppercase regarded as lower value. - if x < y { - let temp_holder = body[x_index].clone(); - body[x_index] = body[y_index].clone(); - body[y_index] = temp_holder; +// (KeyEvents, Description) +type InfoBoxRow<'a> = (Vec, &'a str); +type InfoBoxBody<'a> = Vec>; +/// Sorts by `ModifierKeyCode`, then by each `KeyCode` category, then by each `KeyEvent`. +/// KeyCode::Char sorting is special in that lower-case and upper-case equivalents are +/// placed together, and alphas are placed before the rest. +fn keyevent_sort_infobox(body: InfoBoxBody) -> InfoBoxBody { + use std::str::FromStr; + use std::collections::BTreeMap; + use helix_view::keyboard::{KeyCode, KeyModifiers, MediaKeyCode}; + + let mut category_holder: BTreeMap>> = BTreeMap::new(); + let mut sorted_body: InfoBoxBody = Vec::with_capacity(body.len()); + for infobox_row in body { + let first_keyevent = KeyEvent::from_str(infobox_row.0[0].as_str()).unwrap(); + if !category_holder.contains_key(&first_keyevent.modifiers) { + category_holder.insert(first_keyevent.modifiers.clone(), BTreeMap::new()); + } + + // HACK: inserting by variant not by variant value. + // KeyCode:: Char, F, and MediaKeys can have muiltiple values for the given variant + // Hence the use of mock Variant values + let keycode_category = match first_keyevent.code { + KeyCode::Char(_) => { + KeyCode::Char('a') + } + KeyCode::F(_) => { + KeyCode::F(0) + } + KeyCode::Media(_) => { + KeyCode::Media(MediaKeyCode::Play) + } + other_keycode => { + other_keycode + } + }; + + + let modifier_category = category_holder.get_mut(&first_keyevent.modifiers) + .expect("keycode category existence should be checked."); + if !modifier_category.contains_key(&keycode_category) { + modifier_category.insert(keycode_category.clone(), Vec::new()); + } + modifier_category.get_mut(&keycode_category) + .expect("key existence should be checked") + .push(infobox_row); + } + + for (_, keycode_categories) in category_holder { + for (keycode_category, mut infobox_rows) in keycode_categories { + if infobox_rows.len() > 1 { + match keycode_category { + KeyCode::Char(_) => { + infobox_rows.sort_unstable_by(|a, b| a.0[0].to_lowercase().cmp(&b.0[0].to_lowercase())); + + // Consistently place lowercase before uppercase of the same letter. + let mut x_index = 0; + let mut y_index = 1; + while y_index < infobox_rows.len() { + let x = &infobox_rows[x_index].0[0]; + let y = &infobox_rows[y_index].0[0]; + if x.to_lowercase() == y.to_lowercase() { + if x < y { + infobox_rows.swap(x_index, y_index); + } + } + x_index = y_index; + y_index += 1; + } + + // TEMP: until drain_filter becomes stable. Migth also be worth implementing + // FromIterator on InfoboxBody by then, last two for loops could then also be replaced by Iter::chain + let mut alphas = Vec::new(); + let mut misc = Vec::new(); + for infobox_row in infobox_rows { + if ('a'..='z') + .map(|char| char.to_string()) + .find(|alpha_char| *alpha_char == infobox_row.0[0].to_lowercase()) + .is_some() + { + alphas.push(infobox_row); + } + else { + misc.push(infobox_row); + } + } + infobox_rows = Vec::with_capacity(alphas.len() + misc.len()); + for alpha_row in alphas { + infobox_rows.push(alpha_row); + } + for misc_row in misc { + infobox_rows.push(misc_row); + } + }, + _ => { + infobox_rows.sort_unstable(); + } } } - x_index = y_index; - y_index += 1; + sorted_body.append(infobox_rows.as_mut()); } } - body + sorted_body } diff --git a/helix-view/src/keyboard.rs b/helix-view/src/keyboard.rs index cf673e113ce2..44813235b8c9 100644 --- a/helix-view/src/keyboard.rs +++ b/helix-view/src/keyboard.rs @@ -212,63 +212,38 @@ impl From for ModifierKeyCode { } /// Represents a key. +/// Variant order determines order in keymap infobox if sorted_infobox is set to true. #[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Hash)] pub enum KeyCode { - /// Backspace key. - Backspace, - /// Enter key. - Enter, - /// Left arrow key. - Left, - /// Right arrow key. - Right, - /// Up arrow key. + /// Character key. + /// Ex: `KeyCode::Char('c')` represents the `c` character. + Char(char), + /// Function key. + /// Ex: `KeyCode::F(1)` represents the F1 key. + F(u8), Up, - /// Down arrow key. Down, - /// Home key. + Left, + Right, + Enter, + Esc, + Tab, + Backspace, + Insert, + Delete, Home, - /// End key. End, - /// Page up key. PageUp, - /// Page down key. PageDown, - /// Tab key. - Tab, - /// Delete key. - Delete, - /// Insert key. - Insert, - /// F key. - /// - /// `KeyCode::F(1)` represents F1 key, etc. - F(u8), - /// A character. - /// - /// `KeyCode::Char('c')` represents `c` character, etc. - Char(char), - /// Null. Null, - /// Escape key. - Esc, - /// CapsLock key. CapsLock, - /// ScrollLock key. ScrollLock, - /// NumLock key. NumLock, - /// PrintScreen key. - PrintScreen, - /// Pause key. - Pause, - /// Menu key. Menu, - /// KeypadBegin key. + Pause, + PrintScreen, KeypadBegin, - /// A media key. Media(MediaKeyCode), - /// A modifier key. Modifier(ModifierKeyCode), } From 5e7f4a0972ce0debdaf7f1b075970e1f664a2bf6 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Sun, 22 Jan 2023 16:53:05 +0100 Subject: [PATCH 037/105] Removed warnings --- helix-term/src/keymap/keytrie.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helix-term/src/keymap/keytrie.rs b/helix-term/src/keymap/keytrie.rs index 7fe5df9a8610..04a2424dcec2 100644 --- a/helix-term/src/keymap/keytrie.rs +++ b/helix-term/src/keymap/keytrie.rs @@ -1,6 +1,6 @@ use super::keytrienode::KeyTrieNode; use helix_view::{info::Info, input::KeyEvent}; -use std::{collections::HashMap, ops::{Deref, DerefMut}, cmp::Ordering}; +use std::{collections::HashMap, cmp::Ordering}; use serde::Deserialize; /// Edges of the trie are KeyEvents and the nodes are descrbibed by KeyTrieNode @@ -52,7 +52,7 @@ impl KeyTrie { } } - pub fn merge_keytrie(&mut self, mut other_keytrie: Self) { + pub fn merge_keytrie(&mut self, other_keytrie: Self) { for (other_key_event, other_index) in other_keytrie.get_child_order() { let other_child_keytrie_node = &other_keytrie.get_children()[*other_index]; match other_child_keytrie_node { From c88a817da4e9934604e6074ed96b82240c98c2eb Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Sun, 22 Jan 2023 17:01:47 +0100 Subject: [PATCH 038/105] Use .join(", ") rather that own implementation --- helix-term/src/keymap/keytrie.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/helix-term/src/keymap/keytrie.rs b/helix-term/src/keymap/keytrie.rs index 04a2424dcec2..01c03ec9d1b1 100644 --- a/helix-term/src/keymap/keytrie.rs +++ b/helix-term/src/keymap/keytrie.rs @@ -80,6 +80,7 @@ impl KeyTrie { } } + // IMPROVEMENT: cache sorting and update cache only when config is updated /// Open an info box for a given KeyTrie /// Shows the children as possible KeyEvents and thier associated description. pub fn infobox(&self, sort_infobox: bool) -> Info { @@ -130,16 +131,9 @@ impl KeyTrie { if sort_infobox { body = keyevent_sort_infobox(body); } let stringified_key_events_body: Vec<(String, &str)> = body - .iter() - .map(|(key_events, description)| { - let key_events_string: String = key_events.iter().fold(String::new(), |mut acc, key_event| { - if !acc.is_empty() { acc.push_str(", "); } - acc.push_str(key_event); - acc - }); - (key_events_string, *description) - }) - .collect(); + .iter().map(|(key_events, description)| { + (key_events.join(", "), *description) + }).collect(); Info::new(&self.documentation, &stringified_key_events_body) } From 2f0fa30aef5dbd383388db249f50b2f7cd4d5e9e Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Sun, 22 Jan 2023 18:05:24 +0100 Subject: [PATCH 039/105] Fix apply_trasaction rebase fail --- helix-term/src/commands.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 6d013456a29f..7a79fd45b488 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -42,7 +42,7 @@ use helix_view::{ keyboard::KeyCode, tree, view::View, - Document, DocumentId, Editor, ViewId, apply_transaction, + Document, DocumentId, Editor, ViewId, }; use std::{ collections::{HashMap, HashSet}, From 1eaaf2df588c87fef22f4e421da32b3e8d06cb34 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Sun, 22 Jan 2023 18:43:26 +0100 Subject: [PATCH 040/105] Fix failing test --- helix-term/src/config.rs | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index 3ed2fa52323b..4838e2d79c26 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -77,28 +77,31 @@ mod tests { fn parses_keymap_from_toml() { let sample_keymaps = r#" [keys.insert] - y = "move_line_down" S-C-a = "delete_selection" + y = "move_line_down" [keys.normal] A-F12 = "move_next_word_end" "#; - assert_eq!( - toml::from_str::(sample_keymaps).unwrap(), - Config { - keys: hashmap! { - Mode::Insert => keytrie!({ "Insert mode" - "y" => move_line_down, - "S-C-a" => delete_selection, - }), - Mode::Normal => keytrie!({ "Normal mode" - "A-F12" => move_next_word_end, - }), - }, - ..Default::default() - } - ); + let config = Config { + keys: hashmap! { + Mode::Insert => keytrie!({ "Insert mode" + "S-C-a" => delete_selection, + "y" => move_line_down, + }), + Mode::Normal => keytrie!({ "Normal mode" + "A-F12" => move_next_word_end, + }), + }, + ..Default::default() + }; + for mode in config.keys.keys() { + assert_eq!( + config.keys.get(mode).unwrap().get_children(), + toml::from_str::(sample_keymaps).unwrap().keys.get(mode).unwrap().get_children() + ); + } } #[test] From ae6790c48d1c7672d121ba46393bf761e48a2d81 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Mon, 23 Jan 2023 17:28:18 +0100 Subject: [PATCH 041/105] Run cargo fmt --- helix-term/src/application.rs | 44 ++++----- helix-term/src/commands.rs | 127 +++++++++++++++---------- helix-term/src/config.rs | 50 +++++++--- helix-term/src/keymap.rs | 40 ++++---- helix-term/src/keymap/default.rs | 4 +- helix-term/src/keymap/keytrie.rs | 134 +++++++++++++++------------ helix-term/src/keymap/keytrienode.rs | 20 ++-- helix-term/src/keymap/macros.rs | 2 +- helix-term/src/keymap/tests.rs | 21 +++-- helix-term/src/ui/editor.rs | 7 +- 10 files changed, 266 insertions(+), 183 deletions(-) diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 81fa20951122..adaed7530bcf 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -1,3 +1,23 @@ +use crate::{ + args::Args, + commands::apply_workspace_edit, + compositor::{Compositor, Event}, + config::Config, + job::Jobs, + keymap::Keymap, + ui::{self, overlay::overlayed}, +}; +use anyhow::{Context, Error}; +use arc_swap::{access::Map, ArcSwap}; +use crossterm::{ + event::{ + DisableBracketedPaste, DisableFocusChange, DisableMouseCapture, EnableBracketedPaste, + EnableFocusChange, EnableMouseCapture, Event as CrosstermEvent, + }, + execute, terminal, + tty::IsTty, +}; +use futures_util::Stream; use helix_core::{ diagnostic::{DiagnosticTag, NumberOrString}, path::get_relative_path, @@ -13,34 +33,14 @@ use helix_view::{ tree::Layout, Align, Editor, }; -use crate::{ - args::Args, - commands::apply_workspace_edit, - compositor::{Compositor, Event}, - config::Config, - keymap::Keymap, - job::Jobs, - ui::{self, overlay::overlayed}, -}; +use log::{debug, error, warn}; +use serde_json::json; use std::{ io::{stdin, stdout, Write}, sync::Arc, time::{Duration, Instant}, }; -use arc_swap::{access::Map, ArcSwap}; -use futures_util::Stream; -use log::{debug, error, warn}; -use anyhow::{Context, Error}; -use serde_json::json; use tui::backend::Backend; -use crossterm::{ - event::{ - DisableBracketedPaste, DisableFocusChange, DisableMouseCapture, EnableBracketedPaste, - EnableFocusChange, EnableMouseCapture, Event as CrosstermEvent, - }, - execute, terminal, - tty::IsTty, -}; #[cfg(not(windows))] use { diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 7a79fd45b488..d9d784fc820b 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -7,15 +7,24 @@ pub use lsp::*; pub use typed::*; use crate::{ - commands::insert::*, args, - keymap::CommandList, + commands::insert::*, compositor::{self, Component, Compositor}, - job::{Callback, self, Jobs}, - ui::{self, overlay::overlayed, FilePicker, Picker, Popup, Prompt, PromptEvent, menu::{Cell, Row}}, + job::{self, Callback, Jobs}, + keymap::CommandList, + ui::{ + self, + menu::{Cell, Row}, + overlay::overlayed, + FilePicker, Picker, Popup, Prompt, PromptEvent, + }, }; -use helix_vcs::Hunk; +use anyhow::{anyhow, bail, ensure, Context as _}; +use futures_util::StreamExt; +use fuzzy_matcher::FuzzyMatcher; +use grep_regex::RegexMatcherBuilder; +use grep_searcher::{sinks, BinaryDetection, SearcherBuilder}; use helix_core::{ comment, coords_at_pos, encoding, find_first_non_whitespace_char, find_root, graphemes, history::UndoKind, @@ -33,6 +42,7 @@ use helix_core::{ visual_coords_at_pos, LineEnding, Position, Range, Rope, RopeGraphemes, RopeSlice, Selection, SmallVec, Tendril, Transaction, }; +use helix_vcs::Hunk; use helix_view::{ clipboard::ClipboardType, document::{FormatterError, Mode, SCRATCH_BUFFER_NAME}, @@ -44,22 +54,17 @@ use helix_view::{ view::View, Document, DocumentId, Editor, ViewId, }; +use ignore::{DirEntry, WalkBuilder, WalkState}; +use once_cell::sync::Lazy; +use serde::de::{self, Deserialize, Deserializer}; use std::{ + borrow::Cow, collections::{HashMap, HashSet}, - num::NonZeroUsize, + fmt, future::Future, - borrow::Cow, + num::NonZeroUsize, path::{Path, PathBuf}, - fmt, }; -use anyhow::{anyhow, bail, ensure, Context as _}; -use fuzzy_matcher::FuzzyMatcher; -use futures_util::StreamExt; -use once_cell::sync::Lazy; -use serde::de::{self, Deserialize, Deserializer}; -use grep_regex::RegexMatcherBuilder; -use grep_searcher::{sinks, BinaryDetection, SearcherBuilder}; -use ignore::{DirEntry, WalkBuilder, WalkState}; use tokio_stream::wrappers::UnboundedReceiverStream; pub struct Context<'a> { @@ -156,7 +161,11 @@ macro_rules! static_commands { impl MappableCommand { pub fn execute(&self, cx: &mut Context) { match &self { - Self::Typable { name, args, description: _ } => { + Self::Typable { + name, + args, + description: _, + } => { let args: Vec> = args.iter().map(Cow::from).collect(); if let Some(command) = typed::TYPABLE_COMMAND_MAP.get(name.as_str()) { let mut cx = compositor::Context { @@ -2450,22 +2459,38 @@ impl ui::menu::Item for MappableCommand { fn format(&self, command_list: &Self::Data) -> Row { match self { - MappableCommand::Typable { description: doc, name, .. } => { - let mut row: Vec = vec![Cell::from(&*name.as_str()), Cell::from(""), Cell::from(&*doc.as_str())]; + MappableCommand::Typable { + description: doc, + name, + .. + } => { + let mut row: Vec = vec![ + Cell::from(&*name.as_str()), + Cell::from(""), + Cell::from(&*doc.as_str()), + ]; match command_list.get(name as &String) { - Some(key_events) => { row[1] = Cell::from(format_key_events(key_events)); }, + Some(key_events) => { + row[1] = Cell::from(format_key_events(key_events)); + } None => {} } return Row::new(row); - }, - MappableCommand::Static { description: doc, name, .. } => { + } + MappableCommand::Static { + description: doc, + name, + .. + } => { let mut row: Vec = vec![Cell::from(*name), Cell::from(""), Cell::from(*doc)]; match command_list.get(*name) { - Some(key_events) => { row[1] = Cell::from(format_key_events(key_events)); }, + Some(key_events) => { + row[1] = Cell::from(format_key_events(key_events)); + } None => {} } - return Row::new(row) - } + return Row::new(row); + } } // TODO: Generalize into a Vec Display implemention? @@ -2485,7 +2510,11 @@ impl ui::menu::Item for MappableCommand { pub fn command_palette(cx: &mut Context) { cx.callback = Some(Box::new( move |compositor: &mut Compositor, cx: &mut compositor::Context| { - let keymap_command_lists = compositor.find::().unwrap().keymap.command_list(&cx.editor.mode); + let keymap_command_lists = compositor + .find::() + .unwrap() + .keymap + .command_list(&cx.editor.mode); let mut commands: Vec = MappableCommand::STATIC_COMMAND_LIST.into(); commands.extend(typed::TYPABLE_COMMAND_LIST.iter().map(|cmd| { @@ -2496,32 +2525,36 @@ pub fn command_palette(cx: &mut Context) { } })); - let picker = Picker::new(commands, keymap_command_lists, move |cx, command, _action| { - let mut ctx = Context { - register: None, - count: std::num::NonZeroUsize::new(1), - editor: cx.editor, - callback: None, - on_next_key_callback: None, - jobs: cx.jobs, - }; - let focus = view!(ctx.editor).id; + let picker = Picker::new( + commands, + keymap_command_lists, + move |cx, command, _action| { + let mut ctx = Context { + register: None, + count: std::num::NonZeroUsize::new(1), + editor: cx.editor, + callback: None, + on_next_key_callback: None, + jobs: cx.jobs, + }; + let focus = view!(ctx.editor).id; - command.execute(&mut ctx); + command.execute(&mut ctx); - if ctx.editor.tree.contains(focus) { - let config = ctx.editor.config(); - let mode = ctx.editor.mode(); - let view = view_mut!(ctx.editor, focus); - let doc = doc_mut!(ctx.editor, &view.doc); + if ctx.editor.tree.contains(focus) { + let config = ctx.editor.config(); + let mode = ctx.editor.mode(); + let view = view_mut!(ctx.editor, focus); + let doc = doc_mut!(ctx.editor, &view.doc); - view.ensure_cursor_in_view(doc, config.scrolloff); + view.ensure_cursor_in_view(doc, config.scrolloff); - if mode != Mode::Insert { - doc.append_changes_to_history(view); + if mode != Mode::Insert { + doc.append_changes_to_history(view); + } } - } - }); + }, + ); compositor.push(Box::new(overlayed(picker))); }, )); diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index 4838e2d79c26..296526aeb71e 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -64,11 +64,7 @@ mod tests { use crate::{ commands::MappableCommand, config::Config, - keymap::{ - default, - keytrienode::KeyTrieNode, - macros::*, - }, + keymap::{default, keytrienode::KeyTrieNode, macros::*}, }; use helix_core::hashmap; use helix_view::document::Mode; @@ -99,7 +95,12 @@ mod tests { for mode in config.keys.keys() { assert_eq!( config.keys.get(mode).unwrap().get_children(), - toml::from_str::(sample_keymaps).unwrap().keys.get(mode).unwrap().get_children() + toml::from_str::(sample_keymaps) + .unwrap() + .keys + .get(mode) + .unwrap() + .get_children() ); } } @@ -125,14 +126,13 @@ mod tests { "g" => delete_char_forward, }, }) - + }, ..Default::default() }; let mut merged_config = user_config.clone().merge_in_default_keymap(); assert_ne!( - user_config, - merged_config, + user_config, merged_config, "Merged user keymap with default should differ from user keymap." ); @@ -155,25 +155,47 @@ mod tests { ); // Assumes that `g` is a sub key trie in default keymap assert_eq!( - keymap_normal_root_key_trie.traverse(&[key!('g'), key!('$')]).unwrap(), + keymap_normal_root_key_trie + .traverse(&[key!('g'), key!('$')]) + .unwrap(), KeyTrieNode::MappableCommand(MappableCommand::goto_line_end), "User supplied mappable command should be inserted under the correct sub keytrie." ); // Assumes that `gg` is in default keymap assert_eq!( - keymap_normal_root_key_trie.traverse(&[key!('g'), key!('g')]).unwrap(), + keymap_normal_root_key_trie + .traverse(&[key!('g'), key!('g')]) + .unwrap(), KeyTrieNode::MappableCommand(MappableCommand::delete_char_forward), "User supplied mappable command should replace default even in sub keytries." ); // Assumes that `ge` is in default keymap assert_eq!( - keymap_normal_root_key_trie.traverse(&[key!('g'), key!('e')]).unwrap(), + keymap_normal_root_key_trie + .traverse(&[key!('g'), key!('e')]) + .unwrap(), KeyTrieNode::MappableCommand(MappableCommand::goto_last_line), "Default mappable commands that aren't ovveridden should exist in merged keymap." ); // Huh? - assert!(merged_config.keys.get(&Mode::Normal).unwrap().get_children().len() > 1); - assert!(merged_config.keys.get(&Mode::Insert).unwrap().get_children().len() > 0); + assert!( + merged_config + .keys + .get(&Mode::Normal) + .unwrap() + .get_children() + .len() + > 1 + ); + assert!( + merged_config + .keys + .get(&Mode::Insert) + .unwrap() + .get_children() + .len() + > 0 + ); } } diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 9bd04222e39f..c6b3f75b56c8 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -1,20 +1,19 @@ pub mod default; pub mod macros; // NOTE: Only pub becuase of their use in macros -pub mod keytrienode; pub mod keytrie; +pub mod keytrienode; mod tests; -use self::{ - keytrienode::KeyTrieNode, - keytrie::KeyTrie, - macros::key, -}; +use self::{keytrie::KeyTrie, keytrienode::KeyTrieNode, macros::key}; use crate::commands::MappableCommand; +use arc_swap::{ + access::{DynAccess, DynGuard}, + ArcSwap, +}; use helix_view::{document::Mode, input::KeyEvent}; -use std::{sync::Arc, collections::HashMap}; -use arc_swap::{access::{DynAccess, DynGuard}, ArcSwap}; +use std::{collections::HashMap, sync::Arc}; #[derive(Debug, Clone, PartialEq)] pub enum KeymapResult { @@ -57,7 +56,7 @@ impl Keymap { } /// Lookup `key` in the keymap to try and find a command to execute. - /// Escape key represents cancellation. + /// Escape key represents cancellation. /// This means clearing pending keystrokes, or the sticky_keytrie if none were present. pub fn get(&mut self, mode: Mode, key: KeyEvent) -> KeymapResult { // TODO: remove the sticky part and look up manually @@ -122,12 +121,16 @@ impl Keymap { /// Returns a key-value list of all commands associated to a given Keymap. /// Keys are the node names (see KeyTrieNode documentation) /// Values are lists of stringified KeyEvents that triger the command. - /// Each element in the KeyEvent list is prefixed with prefixed the ancestor KeyEvents. + /// Each element in the KeyEvent list is prefixed with prefixed the ancestor KeyEvents. /// For example: Stringified KeyEvent element for the 'goto_next_window' command could be "space>w>w". /// Ancestor KeyEvents are in this case "space" and "w". pub fn command_list(&self, mode: &Mode) -> CommandList { let mut list = HashMap::new(); - _command_list(&mut list, &KeyTrieNode::KeyTrie(self.get_keytrie(mode)), &mut String::new()); + _command_list( + &mut list, + &KeyTrieNode::KeyTrie(self.get_keytrie(mode)), + &mut String::new(), + ); return list; fn _command_list(list: &mut CommandList, node: &KeyTrieNode, prefix: &mut String) { @@ -135,18 +138,21 @@ impl Keymap { KeyTrieNode::KeyTrie(trie_node) => { for (key_event, index) in trie_node.get_child_order() { let mut temp_prefix: String = prefix.to_string(); - if &temp_prefix != "" { + if &temp_prefix != "" { temp_prefix.push_str("→"); } temp_prefix.push_str(&key_event.to_string()); _command_list(list, &trie_node.get_children()[*index], &mut temp_prefix); - } - }, + } KeyTrieNode::MappableCommand(mappable_command) => { - if mappable_command.name() == "no_op" { return } - list.entry(mappable_command.name().to_string()).or_default().push(prefix.to_string()); - }, + if mappable_command.name() == "no_op" { + return; + } + list.entry(mappable_command.name().to_string()) + .or_default() + .push(prefix.to_string()); + } KeyTrieNode::CommandSequence(_) => {} }; } diff --git a/helix-term/src/keymap/default.rs b/helix-term/src/keymap/default.rs index 3fffaa08c517..24d53b8565d0 100644 --- a/helix-term/src/keymap/default.rs +++ b/helix-term/src/keymap/default.rs @@ -1,6 +1,6 @@ -use super::{macros::keytrie, keytrie::KeyTrie}; -use helix_view::document::Mode; +use super::{keytrie::KeyTrie, macros::keytrie}; use helix_core::hashmap; +use helix_view::document::Mode; use std::collections::HashMap; pub fn default() -> HashMap { diff --git a/helix-term/src/keymap/keytrie.rs b/helix-term/src/keymap/keytrie.rs index 01c03ec9d1b1..d40002d09245 100644 --- a/helix-term/src/keymap/keytrie.rs +++ b/helix-term/src/keymap/keytrie.rs @@ -1,7 +1,7 @@ use super::keytrienode::KeyTrieNode; use helix_view::{info::Info, input::KeyEvent}; -use std::{collections::HashMap, cmp::Ordering}; use serde::Deserialize; +use std::{cmp::Ordering, collections::HashMap}; /// Edges of the trie are KeyEvents and the nodes are descrbibed by KeyTrieNode #[derive(Debug, Clone)] @@ -14,7 +14,11 @@ pub struct KeyTrie { } impl KeyTrie { - pub fn new(documentation: &str, child_order: HashMap, children: Vec) -> Self { + pub fn new( + documentation: &str, + child_order: HashMap, + children: Vec, + ) -> Self { Self { documentation: documentation.to_string(), child_order, @@ -35,17 +39,20 @@ impl KeyTrie { pub fn traverse(&self, key_events: &[KeyEvent]) -> Option { return _traverse(self, key_events, 0); - fn _traverse(keytrie: &KeyTrie, key_events: &[KeyEvent], mut depth: usize) -> Option { + fn _traverse( + keytrie: &KeyTrie, + key_events: &[KeyEvent], + mut depth: usize, + ) -> Option { if depth == key_events.len() { return Some(KeyTrieNode::KeyTrie(keytrie.clone())); - } - else if let Some(found_index) = keytrie.child_order.get(&key_events[depth]) { + } else if let Some(found_index) = keytrie.child_order.get(&key_events[depth]) { match &keytrie.children[*found_index] { KeyTrieNode::KeyTrie(sub_keytrie) => { depth += 1; - return _traverse(sub_keytrie, key_events, depth) - }, - _found_child => return Some(_found_child.clone()) + return _traverse(sub_keytrie, key_events, depth); + } + _found_child => return Some(_found_child.clone()), } } return None; @@ -58,28 +65,31 @@ impl KeyTrie { match other_child_keytrie_node { KeyTrieNode::KeyTrie(ref other_child_keytrie) => { if let Some(self_index) = self.child_order.get(&other_key_event) { - if let KeyTrieNode::KeyTrie(ref mut self_clashing_child_key_trie) = self.children[*self_index] { + if let KeyTrieNode::KeyTrie(ref mut self_clashing_child_key_trie) = + self.children[*self_index] + { self_clashing_child_key_trie.merge_keytrie(other_child_keytrie.clone()); } - } - else { - self.child_order.insert(*other_key_event, self.children.len()); - self.children.push(KeyTrieNode::KeyTrie(other_child_keytrie.clone())); + } else { + self.child_order + .insert(*other_key_event, self.children.len()); + self.children + .push(KeyTrieNode::KeyTrie(other_child_keytrie.clone())); } } KeyTrieNode::MappableCommand(_) | KeyTrieNode::CommandSequence(_) => { if let Some(existing_index) = self.child_order.get(other_key_event) { self.children[*existing_index] = other_child_keytrie_node.clone(); - } - else { - self.child_order.insert(*other_key_event, self.children.len()); + } else { + self.child_order + .insert(*other_key_event, self.children.len()); self.children.push(other_child_keytrie_node.clone()); } } } } } - + // IMPROVEMENT: cache sorting and update cache only when config is updated /// Open an info box for a given KeyTrie /// Shows the children as possible KeyEvents and thier associated description. @@ -87,7 +97,9 @@ impl KeyTrie { let mut body: InfoBoxBody = Vec::with_capacity(self.children.len()); let mut key_event_order = Vec::with_capacity(self.children.len()); // child_order and children is of same length - unsafe { key_event_order.set_len(self.children.len()); } + unsafe { + key_event_order.set_len(self.children.len()); + } for (key_event, index) in &self.child_order { key_event_order[*index] = key_event.clone(); } @@ -99,7 +111,7 @@ impl KeyTrie { continue; } command.description() - }, + } KeyTrieNode::KeyTrie(ref key_trie) => &key_trie.documentation, // FIX: default to a join of all command names // NOTE: Giving same documentation for all sequences will place all sequence keyvents together. @@ -107,11 +119,12 @@ impl KeyTrie { KeyTrieNode::CommandSequence(_) => "[Multiple commands]", }; let key_event = key_event_order[index]; - match body.iter().position(|(_, existing_documentation)| &documentation == existing_documentation) { - Some(position) => body[position].0.push(key_event.to_string()), - None => { - body.push((vec![key_event.to_string()], documentation)) - }, + match body + .iter() + .position(|(_, existing_documentation)| &documentation == existing_documentation) + { + Some(position) => body[position].0.push(key_event.to_string()), + None => body.push((vec![key_event.to_string()], documentation)), } } @@ -120,20 +133,24 @@ impl KeyTrie { // Those events will always be placed after the one letter KeyEvent for (key_events, _) in body.iter_mut() { key_events.sort_unstable_by(|a, b| { - if a.len() == 1 { return Ordering::Less } + if a.len() == 1 { + return Ordering::Less; + } if b.len() > a.len() && b.starts_with("C-") { - return Ordering::Greater + return Ordering::Greater; } a.len().cmp(&b.len()) }); } - if sort_infobox { body = keyevent_sort_infobox(body); } + if sort_infobox { + body = keyevent_sort_infobox(body); + } let stringified_key_events_body: Vec<(String, &str)> = body - .iter().map(|(key_events, description)| { - (key_events.join(", "), *description) - }).collect(); + .iter() + .map(|(key_events, description)| (key_events.join(", "), *description)) + .collect(); Info::new(&self.documentation, &stringified_key_events_body) } @@ -165,7 +182,7 @@ impl<'de> Deserialize<'de> for KeyTrie { children.push(keytrie_node); } - Ok(Self { + Ok(Self { child_order, children, ..Default::default() @@ -177,14 +194,15 @@ impl<'de> Deserialize<'de> for KeyTrie { type InfoBoxRow<'a> = (Vec, &'a str); type InfoBoxBody<'a> = Vec>; /// Sorts by `ModifierKeyCode`, then by each `KeyCode` category, then by each `KeyEvent`. -/// KeyCode::Char sorting is special in that lower-case and upper-case equivalents are +/// KeyCode::Char sorting is special in that lower-case and upper-case equivalents are /// placed together, and alphas are placed before the rest. fn keyevent_sort_infobox(body: InfoBoxBody) -> InfoBoxBody { - use std::str::FromStr; - use std::collections::BTreeMap; use helix_view::keyboard::{KeyCode, KeyModifiers, MediaKeyCode}; + use std::collections::BTreeMap; + use std::str::FromStr; - let mut category_holder: BTreeMap>> = BTreeMap::new(); + let mut category_holder: BTreeMap>> = + BTreeMap::new(); let mut sorted_body: InfoBoxBody = Vec::with_capacity(body.len()); for infobox_row in body { let first_keyevent = KeyEvent::from_str(infobox_row.0[0].as_str()).unwrap(); @@ -196,27 +214,20 @@ fn keyevent_sort_infobox(body: InfoBoxBody) -> InfoBoxBody { // KeyCode:: Char, F, and MediaKeys can have muiltiple values for the given variant // Hence the use of mock Variant values let keycode_category = match first_keyevent.code { - KeyCode::Char(_) => { - KeyCode::Char('a') - } - KeyCode::F(_) => { - KeyCode::F(0) - } - KeyCode::Media(_) => { - KeyCode::Media(MediaKeyCode::Play) - } - other_keycode => { - other_keycode - } + KeyCode::Char(_) => KeyCode::Char('a'), + KeyCode::F(_) => KeyCode::F(0), + KeyCode::Media(_) => KeyCode::Media(MediaKeyCode::Play), + other_keycode => other_keycode, }; - - let modifier_category = category_holder.get_mut(&first_keyevent.modifiers) + let modifier_category = category_holder + .get_mut(&first_keyevent.modifiers) .expect("keycode category existence should be checked."); if !modifier_category.contains_key(&keycode_category) { modifier_category.insert(keycode_category.clone(), Vec::new()); } - modifier_category.get_mut(&keycode_category) + modifier_category + .get_mut(&keycode_category) .expect("key existence should be checked") .push(infobox_row); } @@ -225,12 +236,14 @@ fn keyevent_sort_infobox(body: InfoBoxBody) -> InfoBoxBody { for (keycode_category, mut infobox_rows) in keycode_categories { if infobox_rows.len() > 1 { match keycode_category { - KeyCode::Char(_) => { - infobox_rows.sort_unstable_by(|a, b| a.0[0].to_lowercase().cmp(&b.0[0].to_lowercase())); + KeyCode::Char(_) => { + infobox_rows.sort_unstable_by(|a, b| { + a.0[0].to_lowercase().cmp(&b.0[0].to_lowercase()) + }); // Consistently place lowercase before uppercase of the same letter. let mut x_index = 0; - let mut y_index = 1; + let mut y_index = 1; while y_index < infobox_rows.len() { let x = &infobox_rows[x_index].0[0]; let y = &infobox_rows[y_index].0[0]; @@ -238,9 +251,9 @@ fn keyevent_sort_infobox(body: InfoBoxBody) -> InfoBoxBody { if x < y { infobox_rows.swap(x_index, y_index); } - } - x_index = y_index; - y_index += 1; + } + x_index = y_index; + y_index += 1; } // TEMP: until drain_filter becomes stable. Migth also be worth implementing @@ -254,19 +267,18 @@ fn keyevent_sort_infobox(body: InfoBoxBody) -> InfoBoxBody { .is_some() { alphas.push(infobox_row); - } - else { + } else { misc.push(infobox_row); } } infobox_rows = Vec::with_capacity(alphas.len() + misc.len()); for alpha_row in alphas { - infobox_rows.push(alpha_row); + infobox_rows.push(alpha_row); } for misc_row in misc { - infobox_rows.push(misc_row); + infobox_rows.push(misc_row); } - }, + } _ => { infobox_rows.sort_unstable(); } diff --git a/helix-term/src/keymap/keytrienode.rs b/helix-term/src/keymap/keytrienode.rs index 66a642ac902d..5511c49dd7c6 100644 --- a/helix-term/src/keymap/keytrienode.rs +++ b/helix-term/src/keymap/keytrienode.rs @@ -1,8 +1,8 @@ use super::keytrie::KeyTrie; use crate::commands::MappableCommand; use helix_view::input::KeyEvent; +use serde::{de::Visitor, Deserialize}; use std::collections::HashMap; -use serde::{Deserialize, de::Visitor}; /// Each variant includes a documentaion property. /// For the MappableCommand and CommandSequence variants, the property is self explanatory. @@ -29,14 +29,14 @@ impl PartialEq for KeyTrieNode { match (self, other) { (KeyTrieNode::MappableCommand(_self), KeyTrieNode::MappableCommand(_other)) => { _self == _other - }, + } (KeyTrieNode::CommandSequence(_self), KeyTrieNode::CommandSequence(_other)) => { - _self == _other - }, + _self == _other + } (KeyTrieNode::KeyTrie(_self), KeyTrieNode::KeyTrie(_other)) => { _self.get_children() == _other.get_children() - }, - _ => false + } + _ => false, } } } @@ -85,6 +85,10 @@ impl<'de> Visitor<'de> for KeyTrieNodeVisitor { child_order.insert(key_event, children.len()); children.push(key_trie_node); } - Ok(KeyTrieNode::KeyTrie(KeyTrie::new("", child_order, children))) + Ok(KeyTrieNode::KeyTrie(KeyTrie::new( + "", + child_order, + children, + ))) } -} \ No newline at end of file +} diff --git a/helix-term/src/keymap/macros.rs b/helix-term/src/keymap/macros.rs index b834c43e56aa..c8f52128e3e4 100644 --- a/helix-term/src/keymap/macros.rs +++ b/helix-term/src/keymap/macros.rs @@ -116,5 +116,5 @@ macro_rules! keytrie { pub use alt; pub use ctrl; pub use key; -pub use shift; pub use keytrie; +pub use shift; diff --git a/helix-term/src/keymap/tests.rs b/helix-term/src/keymap/tests.rs index 4ab431c6e31d..bc5e061892bb 100644 --- a/helix-term/src/keymap/tests.rs +++ b/helix-term/src/keymap/tests.rs @@ -1,9 +1,9 @@ #[macro_use] #[cfg(test)] mod tests { + use crate::keymap::{macros::*, *}; use helix_core::hashmap; use helix_view::{document::Mode, input::KeyEvent}; - use crate::keymap::{*, macros::*}; use std::collections::HashMap; #[test] @@ -25,8 +25,12 @@ mod tests { fn aliased_modes_are_same_in_default_keymap() { let normal_mode_keytrie_root = Keymap::default().get_keytrie(&Mode::Normal); assert_eq!( - normal_mode_keytrie_root.traverse(&[key!(' '), key!('w')]).unwrap(), - normal_mode_keytrie_root.traverse(&["C-w".parse::().unwrap()]).unwrap(), + normal_mode_keytrie_root + .traverse(&[key!(' '), key!('w')]) + .unwrap(), + normal_mode_keytrie_root + .traverse(&["C-w".parse::().unwrap()]) + .unwrap(), "Mismatch for window mode on `Space-w` and `Ctrl-w`." ); assert_eq!( @@ -47,7 +51,9 @@ mod tests { "j" | "k" => move_line_down, }); - let keymap = Keymap::new(Box::new(ArcSwap::new(Arc::new(hashmap!(Mode::Normal => normal_mode))))); + let keymap = Keymap::new(Box::new(ArcSwap::new(Arc::new( + hashmap!(Mode::Normal => normal_mode), + )))); let mut command_list = keymap.command_list(&Mode::Normal); // sort keybindings in order to have consistent tests @@ -60,10 +66,7 @@ mod tests { assert_eq!( command_list, HashMap::from([ - ( - "insert_mode".to_string(), - vec![key!('i').to_string()] - ), + ("insert_mode".to_string(), vec![key!('i').to_string()]), ( "goto_file_start".to_string(), vec![format!("{}→{}", key!('g'), key!('g'))] @@ -80,4 +83,4 @@ mod tests { "Mismatch" ) } -} \ No newline at end of file +} diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 94ead3f0f0e6..f4cfd51cf429 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -3,7 +3,7 @@ use crate::{ compositor::{Component, Context, Event, EventResult}, job::{self, Callback}, key, - keymap::{KeymapResult, Keymap}, + keymap::{Keymap, KeymapResult}, ui::{Completion, ProgressSpinners}, }; @@ -931,7 +931,10 @@ impl EditorView { self.pseudo_pending.extend(self.keymap.pending()); let key_result = self.keymap.get(mode, event); let sort_infobox = cxt.editor.config.load().sorted_infobox; - cxt.editor.autoinfo = self.keymap.sticky_keytrie().map(|node| node.infobox(sort_infobox)); + cxt.editor.autoinfo = self + .keymap + .sticky_keytrie() + .map(|node| node.infobox(sort_infobox)); let mut execute_command = |command: &commands::MappableCommand| { command.execute(cxt); From 44ad646d1568ac47d4668857eff2911ddecbe4ac Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Mon, 23 Jan 2023 17:46:17 +0100 Subject: [PATCH 042/105] Use description conistenly --- helix-term/src/keymap/keytrie.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/helix-term/src/keymap/keytrie.rs b/helix-term/src/keymap/keytrie.rs index d40002d09245..dc08695678d5 100644 --- a/helix-term/src/keymap/keytrie.rs +++ b/helix-term/src/keymap/keytrie.rs @@ -6,7 +6,7 @@ use std::{cmp::Ordering, collections::HashMap}; /// Edges of the trie are KeyEvents and the nodes are descrbibed by KeyTrieNode #[derive(Debug, Clone)] pub struct KeyTrie { - documentation: String, + description: String, /// Used for pre-defined order in infoboxes, values represent the index of the key tries children. child_order: HashMap, children: Vec, @@ -15,12 +15,12 @@ pub struct KeyTrie { impl KeyTrie { pub fn new( - documentation: &str, + description: &str, child_order: HashMap, children: Vec, ) -> Self { Self { - documentation: documentation.to_string(), + description: description.to_string(), child_order, children, is_sticky: false, @@ -105,26 +105,26 @@ impl KeyTrie { } for (index, key_trie) in self.children.iter().enumerate() { - let documentation: &str = match key_trie { + let description: &str = match key_trie { KeyTrieNode::MappableCommand(ref command) => { if command.name() == "no_op" { continue; } command.description() } - KeyTrieNode::KeyTrie(ref key_trie) => &key_trie.documentation, + KeyTrieNode::KeyTrie(ref key_trie) => &key_trie.description, // FIX: default to a join of all command names - // NOTE: Giving same documentation for all sequences will place all sequence keyvents together. + // NOTE: Giving same description for all sequences will place all sequence keyvents together. // Regardless if the command sequence is different. KeyTrieNode::CommandSequence(_) => "[Multiple commands]", }; let key_event = key_event_order[index]; match body .iter() - .position(|(_, existing_documentation)| &documentation == existing_documentation) + .position(|(_, existing_description)| &description == existing_description) { Some(position) => body[position].0.push(key_event.to_string()), - None => body.push((vec![key_event.to_string()], documentation)), + None => body.push((vec![key_event.to_string()], description)), } } @@ -152,7 +152,7 @@ impl KeyTrie { .map(|(key_events, description)| (key_events.join(", "), *description)) .collect(); - Info::new(&self.documentation, &stringified_key_events_body) + Info::new(&self.description, &stringified_key_events_body) } } From abf2f283ddce847754a91b22884d8945cd566415 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Mon, 23 Jan 2023 23:03:56 +0100 Subject: [PATCH 043/105] Fix undeterministic test failure --- helix-term/src/config.rs | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index 296526aeb71e..63c6b65d1a90 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -64,17 +64,18 @@ mod tests { use crate::{ commands::MappableCommand, config::Config, - keymap::{default, keytrienode::KeyTrieNode, macros::*}, + keymap::{default, keytrie::KeyTrie, keytrienode::KeyTrieNode, macros::*}, }; use helix_core::hashmap; - use helix_view::document::Mode; + use helix_view::{document::Mode, input::KeyEvent}; + use std::collections::BTreeMap; #[test] fn parses_keymap_from_toml() { let sample_keymaps = r#" [keys.insert] - S-C-a = "delete_selection" y = "move_line_down" + S-C-a = "delete_selection" [keys.normal] A-F12 = "move_next_word_end" @@ -83,8 +84,8 @@ mod tests { let config = Config { keys: hashmap! { Mode::Insert => keytrie!({ "Insert mode" - "S-C-a" => delete_selection, "y" => move_line_down, + "S-C-a" => delete_selection, }), Mode::Normal => keytrie!({ "Normal mode" "A-F12" => move_next_word_end, @@ -92,17 +93,30 @@ mod tests { }, ..Default::default() }; + for mode in config.keys.keys() { + // toml keymap config is placed into a hashmap, so order can not be presumed to be conserved + // hence the insertion into a BTreeMap assert_eq!( - config.keys.get(mode).unwrap().get_children(), - toml::from_str::(sample_keymaps) - .unwrap() - .keys - .get(mode) - .unwrap() - .get_children() + ordered_mapping(config.keys.get(mode).unwrap()), + ordered_mapping( + toml::from_str::(sample_keymaps) + .unwrap() + .keys + .get(mode) + .unwrap() + ) ); } + + fn ordered_mapping<'a>(keytrie: &'a KeyTrie) -> BTreeMap<&'a KeyEvent, KeyTrieNode> { + let children = keytrie.get_children(); + let mut ordered_keymap = BTreeMap::new(); + for (key_event, order) in keytrie.get_child_order() { + ordered_keymap.insert(key_event, children[*order].clone()); + } + ordered_keymap + } } #[test] From eccda6ec94b24e6f7dd5819725165a1b3bc0c2f9 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Tue, 24 Jan 2023 00:06:24 +0100 Subject: [PATCH 044/105] Implement clippy tips --- helix-term/src/keymap.rs | 16 ++++++++-------- helix-term/src/keymap/keytrie.rs | 22 ++++++++-------------- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index c6b3f75b56c8..96fa7cc6a589 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -74,7 +74,7 @@ impl Keymap { // Check if sticky keytrie is to be used. let starting_keytrie = match self.sticky_keytrie { - None => &active_keymap, + None => active_keymap, Some(ref active_sticky_keytrie) => active_sticky_keytrie, }; @@ -84,10 +84,10 @@ impl Keymap { let pending_keytrie: KeyTrie = match starting_keytrie.traverse(&[*first_key]) { Some(KeyTrieNode::KeyTrie(sub_keytrie)) => sub_keytrie, Some(KeyTrieNode::MappableCommand(cmd)) => { - return KeymapResult::Matched(cmd.clone()); + return KeymapResult::Matched(cmd); } Some(KeyTrieNode::CommandSequence(cmds)) => { - return KeymapResult::MatchedCommandSequence(cmds.clone()); + return KeymapResult::MatchedCommandSequence(cmds); } None => return KeymapResult::NotFound, }; @@ -99,15 +99,15 @@ impl Keymap { self.pending_keys.clear(); self.sticky_keytrie = Some(map.clone()); } - KeymapResult::Pending(map.clone()) + KeymapResult::Pending(map) } Some(KeyTrieNode::MappableCommand(cmd)) => { self.pending_keys.clear(); - KeymapResult::Matched(cmd.clone()) + KeymapResult::Matched(cmd) } Some(KeyTrieNode::CommandSequence(cmds)) => { self.pending_keys.clear(); - KeymapResult::MatchedCommandSequence(cmds.clone()) + KeymapResult::MatchedCommandSequence(cmds) } None => KeymapResult::Cancelled(self.pending_keys.drain(..).collect()), } @@ -138,8 +138,8 @@ impl Keymap { KeyTrieNode::KeyTrie(trie_node) => { for (key_event, index) in trie_node.get_child_order() { let mut temp_prefix: String = prefix.to_string(); - if &temp_prefix != "" { - temp_prefix.push_str("→"); + if !&temp_prefix.is_empty() { + temp_prefix.push('→'); } temp_prefix.push_str(&key_event.to_string()); _command_list(list, &trie_node.get_children()[*index], &mut temp_prefix); diff --git a/helix-term/src/keymap/keytrie.rs b/helix-term/src/keymap/keytrie.rs index dc08695678d5..6bf4ebf86c45 100644 --- a/helix-term/src/keymap/keytrie.rs +++ b/helix-term/src/keymap/keytrie.rs @@ -55,7 +55,7 @@ impl KeyTrie { _found_child => return Some(_found_child.clone()), } } - return None; + None } } @@ -64,7 +64,7 @@ impl KeyTrie { let other_child_keytrie_node = &other_keytrie.get_children()[*other_index]; match other_child_keytrie_node { KeyTrieNode::KeyTrie(ref other_child_keytrie) => { - if let Some(self_index) = self.child_order.get(&other_key_event) { + if let Some(self_index) = self.child_order.get(other_key_event) { if let KeyTrieNode::KeyTrie(ref mut self_clashing_child_key_trie) = self.children[*self_index] { @@ -97,11 +97,12 @@ impl KeyTrie { let mut body: InfoBoxBody = Vec::with_capacity(self.children.len()); let mut key_event_order = Vec::with_capacity(self.children.len()); // child_order and children is of same length + #[allow(clippy::uninit_vec)] unsafe { key_event_order.set_len(self.children.len()); } for (key_event, index) in &self.child_order { - key_event_order[*index] = key_event.clone(); + key_event_order[*index] = key_event; } for (index, key_trie) in self.children.iter().enumerate() { @@ -206,9 +207,7 @@ fn keyevent_sort_infobox(body: InfoBoxBody) -> InfoBoxBody { let mut sorted_body: InfoBoxBody = Vec::with_capacity(body.len()); for infobox_row in body { let first_keyevent = KeyEvent::from_str(infobox_row.0[0].as_str()).unwrap(); - if !category_holder.contains_key(&first_keyevent.modifiers) { - category_holder.insert(first_keyevent.modifiers.clone(), BTreeMap::new()); - } + category_holder.entry(first_keyevent.modifiers).or_insert_with(BTreeMap::new); // HACK: inserting by variant not by variant value. // KeyCode:: Char, F, and MediaKeys can have muiltiple values for the given variant @@ -223,9 +222,7 @@ fn keyevent_sort_infobox(body: InfoBoxBody) -> InfoBoxBody { let modifier_category = category_holder .get_mut(&first_keyevent.modifiers) .expect("keycode category existence should be checked."); - if !modifier_category.contains_key(&keycode_category) { - modifier_category.insert(keycode_category.clone(), Vec::new()); - } + modifier_category.entry(keycode_category).or_insert_with(Vec::new); modifier_category .get_mut(&keycode_category) .expect("key existence should be checked") @@ -247,10 +244,8 @@ fn keyevent_sort_infobox(body: InfoBoxBody) -> InfoBoxBody { while y_index < infobox_rows.len() { let x = &infobox_rows[x_index].0[0]; let y = &infobox_rows[y_index].0[0]; - if x.to_lowercase() == y.to_lowercase() { - if x < y { + if x.to_lowercase() == y.to_lowercase() && x < y{ infobox_rows.swap(x_index, y_index); - } } x_index = y_index; y_index += 1; @@ -263,8 +258,7 @@ fn keyevent_sort_infobox(body: InfoBoxBody) -> InfoBoxBody { for infobox_row in infobox_rows { if ('a'..='z') .map(|char| char.to_string()) - .find(|alpha_char| *alpha_char == infobox_row.0[0].to_lowercase()) - .is_some() + .any(|alpha_char| *alpha_char == infobox_row.0[0].to_lowercase()) { alphas.push(infobox_row); } else { From 2afcfcfc731172b04f482524f105d712d923cf0f Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Tue, 24 Jan 2023 00:14:25 +0100 Subject: [PATCH 045/105] Make cargo fmt happy --- helix-term/src/keymap/keytrie.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/helix-term/src/keymap/keytrie.rs b/helix-term/src/keymap/keytrie.rs index 6bf4ebf86c45..25ed9e8e2537 100644 --- a/helix-term/src/keymap/keytrie.rs +++ b/helix-term/src/keymap/keytrie.rs @@ -207,7 +207,9 @@ fn keyevent_sort_infobox(body: InfoBoxBody) -> InfoBoxBody { let mut sorted_body: InfoBoxBody = Vec::with_capacity(body.len()); for infobox_row in body { let first_keyevent = KeyEvent::from_str(infobox_row.0[0].as_str()).unwrap(); - category_holder.entry(first_keyevent.modifiers).or_insert_with(BTreeMap::new); + category_holder + .entry(first_keyevent.modifiers) + .or_insert_with(BTreeMap::new); // HACK: inserting by variant not by variant value. // KeyCode:: Char, F, and MediaKeys can have muiltiple values for the given variant @@ -222,7 +224,9 @@ fn keyevent_sort_infobox(body: InfoBoxBody) -> InfoBoxBody { let modifier_category = category_holder .get_mut(&first_keyevent.modifiers) .expect("keycode category existence should be checked."); - modifier_category.entry(keycode_category).or_insert_with(Vec::new); + modifier_category + .entry(keycode_category) + .or_insert_with(Vec::new); modifier_category .get_mut(&keycode_category) .expect("key existence should be checked") @@ -244,8 +248,8 @@ fn keyevent_sort_infobox(body: InfoBoxBody) -> InfoBoxBody { while y_index < infobox_rows.len() { let x = &infobox_rows[x_index].0[0]; let y = &infobox_rows[y_index].0[0]; - if x.to_lowercase() == y.to_lowercase() && x < y{ - infobox_rows.swap(x_index, y_index); + if x.to_lowercase() == y.to_lowercase() && x < y { + infobox_rows.swap(x_index, y_index); } x_index = y_index; y_index += 1; From c617fd15ae545d1e830ddb0fb29dcef5fc97e4c2 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Tue, 24 Jan 2023 11:32:12 +0100 Subject: [PATCH 046/105] Make updated cargo clippy happy --- helix-term/src/config.rs | 18 +--- helix-term/src/keymap.rs | 1 + helix-term/src/keymap/tests.rs | 154 ++++++++++++++++----------------- 3 files changed, 80 insertions(+), 93 deletions(-) diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index 63c6b65d1a90..01b7e3f419d1 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -109,7 +109,7 @@ mod tests { ); } - fn ordered_mapping<'a>(keytrie: &'a KeyTrie) -> BTreeMap<&'a KeyEvent, KeyTrieNode> { + fn ordered_mapping(keytrie: &KeyTrie) -> BTreeMap<&KeyEvent, KeyTrieNode> { let children = keytrie.get_children(); let mut ordered_keymap = BTreeMap::new(); for (key_event, order) in keytrie.get_child_order() { @@ -194,22 +194,10 @@ mod tests { // Huh? assert!( - merged_config - .keys - .get(&Mode::Normal) - .unwrap() - .get_children() - .len() - > 1 + merged_config.keys.get(&Mode::Normal).unwrap().get_children().len() > 1 ); assert!( - merged_config - .keys - .get(&Mode::Insert) - .unwrap() - .get_children() - .len() - > 0 + !merged_config.keys.get(&Mode::Insert).unwrap().get_children().is_empty() ); } } diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 96fa7cc6a589..2a77d0ca66cc 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -3,6 +3,7 @@ pub mod macros; // NOTE: Only pub becuase of their use in macros pub mod keytrie; pub mod keytrienode; +#[cfg(test)] mod tests; use self::{keytrie::KeyTrie, keytrienode::KeyTrieNode, macros::key}; diff --git a/helix-term/src/keymap/tests.rs b/helix-term/src/keymap/tests.rs index bc5e061892bb..32dbc305770f 100644 --- a/helix-term/src/keymap/tests.rs +++ b/helix-term/src/keymap/tests.rs @@ -1,86 +1,84 @@ -#[macro_use] -#[cfg(test)] -mod tests { - use crate::keymap::{macros::*, *}; - use helix_core::hashmap; - use helix_view::{document::Mode, input::KeyEvent}; - use std::collections::HashMap; +use std::{sync::Arc, collections::HashMap}; +use arc_swap::ArcSwap; +use helix_core::hashmap; +use helix_view::{document::Mode, input::KeyEvent}; +use crate::key; +use super::{Keymap, macros::keytrie}; - #[test] - #[should_panic] - fn duplicate_keys_should_panic() { - keytrie!({ "Normal mode" - "i" => normal_mode, - "i" => goto_definition, - }); - } - - #[test] - fn check_duplicate_keys_in_default_keymap() { - // will panic on duplicate keys, assumes that `Keymap` uses keymap! macro - Keymap::default(); - } +#[test] +#[should_panic] +fn duplicate_keys_should_panic() { + keytrie!({ "Normal mode" + "i" => normal_mode, + "i" => goto_definition, + }); +} - #[test] - fn aliased_modes_are_same_in_default_keymap() { - let normal_mode_keytrie_root = Keymap::default().get_keytrie(&Mode::Normal); - assert_eq!( - normal_mode_keytrie_root - .traverse(&[key!(' '), key!('w')]) - .unwrap(), - normal_mode_keytrie_root - .traverse(&["C-w".parse::().unwrap()]) - .unwrap(), - "Mismatch for window mode on `Space-w` and `Ctrl-w`." - ); - assert_eq!( - normal_mode_keytrie_root.traverse(&[key!('z')]).unwrap(), - normal_mode_keytrie_root.traverse(&[key!('Z')]).unwrap(), - "Mismatch for view mode on `z` and `Z`." - ); - } +#[test] +fn check_duplicate_keys_in_default_keymap() { + // will panic on duplicate keys, assumes that `Keymap` uses keymap! macro + Keymap::default(); +} - #[test] - fn command_list() { - let normal_mode = keytrie!({ "Normal mode" - "i" => insert_mode, - "g" => { "Goto" - "g" => goto_file_start, - "e" => goto_file_end, - }, - "j" | "k" => move_line_down, - }); +#[test] +fn aliased_modes_are_same_in_default_keymap() { + let normal_mode_keytrie_root = Keymap::default().get_keytrie(&Mode::Normal); + assert_eq!( + normal_mode_keytrie_root + .traverse(&[key!(' '), key!('w')]) + .unwrap(), + normal_mode_keytrie_root + .traverse(&["C-w".parse::().unwrap()]) + .unwrap(), + "Mismatch for window mode on `Space-w` and `Ctrl-w`." + ); + assert_eq!( + normal_mode_keytrie_root.traverse(&[key!('z')]).unwrap(), + normal_mode_keytrie_root.traverse(&[key!('Z')]).unwrap(), + "Mismatch for view mode on `z` and `Z`." + ); +} - let keymap = Keymap::new(Box::new(ArcSwap::new(Arc::new( - hashmap!(Mode::Normal => normal_mode), - )))); - let mut command_list = keymap.command_list(&Mode::Normal); +#[test] +fn command_list() { + let normal_mode = keytrie!({ "Normal mode" + "i" => insert_mode, + "g" => { "Goto" + "g" => goto_file_start, + "e" => goto_file_end, + }, + "j" | "k" => move_line_down, + }); - // 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 command_list.values_mut() { - v.sort() - } + let keymap = Keymap::new(Box::new(ArcSwap::new(Arc::new( + hashmap!(Mode::Normal => normal_mode), + )))); + let mut command_list = keymap.command_list(&Mode::Normal); - assert_eq!( - command_list, - HashMap::from([ - ("insert_mode".to_string(), vec![key!('i').to_string()]), - ( - "goto_file_start".to_string(), - vec![format!("{}→{}", key!('g'), key!('g'))] - ), - ( - "goto_file_end".to_string(), - vec![format!("{}→{}", key!('g'), key!('e'))] - ), - ( - "move_line_down".to_string(), - vec![key!('j').to_string(), key!('k').to_string()] - ) - ]), - "Mismatch" - ) + // 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 command_list.values_mut() { + v.sort() } + + assert_eq!( + command_list, + HashMap::from([ + ("insert_mode".to_string(), vec![key!('i').to_string()]), + ( + "goto_file_start".to_string(), + vec![format!("{}→{}", key!('g'), key!('g'))] + ), + ( + "goto_file_end".to_string(), + vec![format!("{}→{}", key!('g'), key!('e'))] + ), + ( + "move_line_down".to_string(), + vec![key!('j').to_string(), key!('k').to_string()] + ) + ]), + "Mismatch" + ) } From b4b82b43c0e10be861ef7492e40c2e0102269935 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Tue, 24 Jan 2023 12:15:32 +0100 Subject: [PATCH 047/105] Fight with cargo fmt round 3 --- helix-term/src/config.rs | 17 +++++++++++++---- helix-term/src/keymap/tests.rs | 6 +++--- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index 01b7e3f419d1..4c8bbe144d5a 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -194,10 +194,19 @@ mod tests { // Huh? assert!( - merged_config.keys.get(&Mode::Normal).unwrap().get_children().len() > 1 - ); - assert!( - !merged_config.keys.get(&Mode::Insert).unwrap().get_children().is_empty() + merged_config + .keys + .get(&Mode::Normal) + .unwrap() + .get_children() + .len() + > 1 ); + assert!(!merged_config + .keys + .get(&Mode::Insert) + .unwrap() + .get_children() + .is_empty()); } } diff --git a/helix-term/src/keymap/tests.rs b/helix-term/src/keymap/tests.rs index 32dbc305770f..a0c52de6f77c 100644 --- a/helix-term/src/keymap/tests.rs +++ b/helix-term/src/keymap/tests.rs @@ -1,9 +1,9 @@ -use std::{sync::Arc, collections::HashMap}; +use super::{macros::keytrie, Keymap}; +use crate::key; use arc_swap::ArcSwap; use helix_core::hashmap; use helix_view::{document::Mode, input::KeyEvent}; -use crate::key; -use super::{Keymap, macros::keytrie}; +use std::{collections::HashMap, sync::Arc}; #[test] #[should_panic] From c8dd563043c0309ba64c428ecdde1f8d4a3c7b4f Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Sat, 17 Dec 2022 15:33:55 +0100 Subject: [PATCH 048/105] remove one-liner solely used for a test --- helix-term/src/keymap.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 4a131f0a5217..60b9ff1c31c8 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -112,10 +112,6 @@ impl KeyTrieNode { } Info::from_keymap(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 { @@ -541,7 +537,7 @@ mod tests { ); // Make sure an order was set during merge let node = keymap.root().search(&[crate::key!(' ')]).unwrap(); - assert!(!node.node().unwrap().order().is_empty()) + assert!(!node.node().unwrap().order.as_slice().is_empty()) } #[test] From df49f601ca4876c98728ef2d78fcea864f867f04 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Sat, 17 Dec 2022 19:55:30 +0100 Subject: [PATCH 049/105] Inline helper fuction of singular reference --- helix-term/src/config.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index 4407a882f838..f7cac6a36d10 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -28,7 +28,7 @@ impl Default for Config { } #[derive(Debug)] -pub enum ConfigLoadError { +pub enum ConfigLoadError { BadConfig(TomlError), Error(IOError), } @@ -43,18 +43,15 @@ impl Display for ConfigLoadError { } impl Config { - pub fn load(config_path: PathBuf) -> Result { - match std::fs::read_to_string(config_path) { + // REFACTOR? code similar to config assignment in main.rs, + pub fn load_default() -> Result { + match std::fs::read_to_string(helix_loader::config_file()) { Ok(config) => toml::from_str(&config) .map(merge_keys) .map_err(ConfigLoadError::BadConfig), Err(err) => Err(ConfigLoadError::Error(err)), } } - - pub fn load_default() -> Result { - Config::load(helix_loader::config_file()) - } } #[cfg(test)] From 69ed13532be09b1686e91f17e7bf6820d877ed57 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Sat, 17 Dec 2022 21:55:05 +0100 Subject: [PATCH 050/105] Remove order property of KeyTrie struct: Was not implemented correctly as the order was in the most cases assigned from the values of an HashMap, which does not guarantee order. This applied to when a keymap was both deserialized and merged. Info box body was then being sorted as a function of the fallacious `order` property, and by a method that yielded at worst a time complexity of at least n^2. Furthermore, the body contents were inerted at first by the order of the hash map keys, in which `order` itself was based on. A more reliable predifined sorting order is to be implemented. --- helix-term/src/keymap.rs | 29 ++++------------------------- helix-term/src/keymap/macros.rs | 4 +--- 2 files changed, 5 insertions(+), 28 deletions(-) diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 60b9ff1c31c8..03ac5ce18269 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -24,7 +24,6 @@ pub struct KeyTrieNode { /// A label for keys coming under this node, like "Goto mode" name: String, map: HashMap, - order: Vec, pub is_sticky: bool, } @@ -33,22 +32,18 @@ impl<'de> Deserialize<'de> for KeyTrieNode { 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, + map: HashMap::::deserialize(deserializer)?, ..Default::default() }) } } impl KeyTrieNode { - pub fn new(name: &str, map: HashMap, order: Vec) -> Self { + pub fn new(name: &str, map: HashMap) -> Self { Self { name: name.to_string(), map, - order, is_sticky: false, } } @@ -70,11 +65,6 @@ impl KeyTrieNode { } 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 { @@ -97,12 +87,6 @@ impl KeyTrieNode { None => body.push((desc, BTreeSet::from([key]))), } } - body.sort_unstable_by_key(|(_, keys)| { - 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)) { body = body @@ -116,7 +100,7 @@ impl KeyTrieNode { impl Default for KeyTrieNode { fn default() -> Self { - Self::new("", HashMap::new(), Vec::new()) + Self::new("", HashMap::new()) } } @@ -195,12 +179,10 @@ impl<'de> serde::de::Visitor<'de> for KeyTrieVisitor { 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))) + Ok(KeyTrie::Node(KeyTrieNode::new("", mapping))) } } @@ -535,9 +517,6 @@ mod tests { &KeyTrie::Leaf(MappableCommand::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.as_slice().is_empty()) } #[test] diff --git a/helix-term/src/keymap/macros.rs b/helix-term/src/keymap/macros.rs index c4a1bfbb3064..1cf4151c9a38 100644 --- a/helix-term/src/keymap/macros.rs +++ b/helix-term/src/keymap/macros.rs @@ -101,7 +101,6 @@ macro_rules! keymap { { let _cap = hashmap!(@count $($($key),+),*); let mut _map = ::std::collections::HashMap::with_capacity(_cap); - let mut _order = ::std::vec::Vec::with_capacity(_cap); $( $( let _key = $key.parse::<::helix_view::input::KeyEvent>().unwrap(); @@ -110,10 +109,9 @@ macro_rules! keymap { keymap!(@trie $value) ); assert!(_duplicate.is_none(), "Duplicate key found: {:?}", _duplicate.unwrap()); - _order.push(_key); )+ )* - let mut _node = $crate::keymap::KeyTrieNode::new($label, _map, _order); + let mut _node = $crate::keymap::KeyTrieNode::new($label, _map); $( _node.is_sticky = $sticky; )? $crate::keymap::KeyTrie::Node(_node) } From 10151a9193a064d30389cade60c3e66936842230 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Sat, 17 Dec 2022 22:57:28 +0100 Subject: [PATCH 051/105] Infobox: Remove superflous command descripition pruning: Exist under the wrong (possibly just outdated) assumption that command descriptions are written with their KeyTrie name prefixed (Space, View, Goto etc.). For examle: The command `file_picker` is assumed to have the description "Space Open file picker", which is not the case , nor for any other command description. --- helix-term/src/keymap.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 03ac5ce18269..938e781210f9 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -87,13 +87,7 @@ impl KeyTrieNode { None => body.push((desc, BTreeSet::from([key]))), } } - let prefix = format!("{} ", self.name()); - if body.iter().all(|(desc, _)| desc.starts_with(&prefix)) { - body = body - .into_iter() - .map(|(desc, keys)| (desc.strip_prefix(&prefix).unwrap(), keys)) - .collect(); - } + Info::from_keymap(self.name(), body) } } From 465c0ef6aca196606eaccfb693e9c5407def932c Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Sat, 17 Dec 2022 23:42:10 +0100 Subject: [PATCH 052/105] Keymap infobox: Use Vec in place of BTree: BTree was being used to store a list of keyevents for a given command. This list was only iterated over twice to in the end be converted to a Vec. Better to just use a Vec from start given the use- case. Temporalily reverts #952. --- helix-term/src/keymap.rs | 8 ++++---- helix-view/src/info.rs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 938e781210f9..b575ee97e9ff 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -11,7 +11,7 @@ use helix_view::{document::Mode, info::Info, input::KeyEvent}; use serde::Deserialize; use std::{ borrow::Cow, - collections::{BTreeSet, HashMap}, + collections::HashMap, ops::{Deref, DerefMut}, sync::Arc, }; @@ -68,7 +68,7 @@ impl KeyTrieNode { } pub fn infobox(&self) -> Info { - let mut body: Vec<(&str, BTreeSet)> = Vec::with_capacity(self.len()); + let mut body: Vec<(&str, Vec)> = Vec::with_capacity(self.len()); for (&key, trie) in self.iter() { let desc = match trie { KeyTrie::Leaf(cmd) => { @@ -82,9 +82,9 @@ impl KeyTrieNode { }; match body.iter().position(|(d, _)| d == &desc) { Some(pos) => { - body[pos].1.insert(key); + body[pos].1.push(key); } - None => body.push((desc, BTreeSet::from([key]))), + None => body.push((desc, Vec::from([key]))), } } diff --git a/helix-view/src/info.rs b/helix-view/src/info.rs index 3080cf8e1b2f..26dc844d75b1 100644 --- a/helix-view/src/info.rs +++ b/helix-view/src/info.rs @@ -1,6 +1,6 @@ use crate::input::KeyEvent; use helix_core::{register::Registers, unicode::width::UnicodeWidthStr}; -use std::{collections::BTreeSet, fmt::Write}; +use std::fmt::Write; #[derive(Debug)] /// Info box used in editor. Rendering logic will be in other crate. @@ -55,7 +55,7 @@ impl Info { } } - pub fn from_keymap(title: &str, body: Vec<(&str, BTreeSet)>) -> Self { + pub fn from_keymap(title: &str, body: Vec<(&str, Vec)>) -> Self { let body: Vec<_> = body .into_iter() .map(|(desc, events)| { From 8768bf92f40518703ffcf4742b1cf907178bd28e Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Sat, 17 Dec 2022 23:53:07 +0100 Subject: [PATCH 053/105] Keymap infobox: Place in correct order from start: Infobox body was being filled with description then KeyEvent list to only later be iterated over for the purpose of flippingin this order. --- helix-term/src/keymap.rs | 8 ++++---- helix-view/src/info.rs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index b575ee97e9ff..1a91d997e862 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -68,7 +68,7 @@ impl KeyTrieNode { } pub fn infobox(&self) -> Info { - let mut body: Vec<(&str, Vec)> = Vec::with_capacity(self.len()); + let mut body: Vec<(Vec, &str)> = Vec::with_capacity(self.len()); for (&key, trie) in self.iter() { let desc = match trie { KeyTrie::Leaf(cmd) => { @@ -80,11 +80,11 @@ impl KeyTrieNode { KeyTrie::Node(n) => n.name(), KeyTrie::Sequence(_) => "[Multiple commands]", }; - match body.iter().position(|(d, _)| d == &desc) { + match body.iter().position(|(_, d)| d == &desc) { Some(pos) => { - body[pos].1.push(key); + body[pos].0.push(key); } - None => body.push((desc, Vec::from([key]))), + None => body.push((Vec::from([key]), desc)), } } diff --git a/helix-view/src/info.rs b/helix-view/src/info.rs index 26dc844d75b1..fea8f535cd59 100644 --- a/helix-view/src/info.rs +++ b/helix-view/src/info.rs @@ -55,10 +55,10 @@ impl Info { } } - pub fn from_keymap(title: &str, body: Vec<(&str, Vec)>) -> Self { + pub fn from_keymap(title: &str, body: Vec<(Vec, &str)>) -> Self { let body: Vec<_> = body .into_iter() - .map(|(desc, events)| { + .map(|(events, desc)| { let events = events.iter().map(ToString::to_string).collect::>(); (events.join(", "), desc) }) From b50050b0c91998394d744425d7b00c0a020ba84a Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Sun, 18 Dec 2022 00:56:15 +0100 Subject: [PATCH 054/105] Keymap infobox: Format body from start Vec was being created as an intermediary into Info.rs for it to be converted into a comma separated string. This commit removes this intermediate step but also the single purpose from_keymap public function in Info.rs, as it is no longer needed. --- helix-term/src/keymap.rs | 22 ++++++++++------------ helix-view/src/info.rs | 13 ------------- 2 files changed, 10 insertions(+), 25 deletions(-) diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 1a91d997e862..7ff4f83764f9 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -68,27 +68,25 @@ impl KeyTrieNode { } pub fn infobox(&self) -> Info { - let mut body: Vec<(Vec, &str)> = Vec::with_capacity(self.len()); + let mut body: Vec<(String, &str)> = Vec::with_capacity(self.len()); for (&key, trie) in self.iter() { - let desc = match trie { - KeyTrie::Leaf(cmd) => { - if cmd.name() == "no_op" { + let description: &str = match trie { + KeyTrie::Leaf(command) => { + if command.name() == "no_op" { continue; } - cmd.doc() + command.doc() } - KeyTrie::Node(n) => n.name(), + KeyTrie::Node(key_trie) => key_trie.name(), KeyTrie::Sequence(_) => "[Multiple commands]", }; - match body.iter().position(|(_, d)| d == &desc) { - Some(pos) => { - body[pos].0.push(key); - } - None => body.push((Vec::from([key]), desc)), + match body.iter().position(|(_, existing_description)| &description == existing_description) { + Some(pos) => body[pos].0 += &format!(", {}", &key.to_string()), + None => body.push((key.to_string(), description)), } } - Info::from_keymap(self.name(), body) + Info::new(self.name(), &body) } } diff --git a/helix-view/src/info.rs b/helix-view/src/info.rs index fea8f535cd59..1503e855e362 100644 --- a/helix-view/src/info.rs +++ b/helix-view/src/info.rs @@ -1,4 +1,3 @@ -use crate::input::KeyEvent; use helix_core::{register::Registers, unicode::width::UnicodeWidthStr}; use std::fmt::Write; @@ -55,18 +54,6 @@ impl Info { } } - pub fn from_keymap(title: &str, body: Vec<(Vec, &str)>) -> Self { - let body: Vec<_> = body - .into_iter() - .map(|(events, desc)| { - let events = events.iter().map(ToString::to_string).collect::>(); - (events.join(", "), desc) - }) - .collect(); - - Self::new(title, &body) - } - pub fn from_registers(registers: &Registers) -> Self { let body: Vec<_> = registers .inner() From eb50de1b9390a1b3d988a72bd9550e85b5ff9888 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Mon, 26 Dec 2022 17:19:33 +0100 Subject: [PATCH 055/105] keymap.rs file splitting, cleanup and descriptive naming: * Descriptive naming and correct use of data structure terminology example translations: keymap.reverse_map -> keymap.command_list keytrienode -> keytrie (struct) search->traverse merge->merge_keytrie keytrie -> keytrienode (enum describing node variants) Leaf -> MappableCommand Sequence -> CommandSequence Node -> KeyTrie (A leaf is a node, and the sequence was also a node. So old naming made absolutely no sense whatsoever.) * Splitting parts of the keymap.rs file into {keytrienode, keytrie, keymaps, tests}.rs. (Should be self-explanatory by looking at the old keymap.rs.) * Removed KeytrieNode enum functions node(&self) -> Option<&KeyTrie> and node_mut(&mut self) -> Option<&mut KeyTrie> from KeyTrieNode as they served no purpose that could not be used from elsewhere. Was also a bit strange to return a "parent struct" from an enum "building block". * Removed getters that could be achieved by making fields public for now. (E.g .root(), .map(), .name()) * Removed keymap.merge() and keytrienode.merge_nodes() All merging could be handled by keytrie.merge() and keymaps.merge_with_default(), unnecessary to have a second unused system that does about the same thing. We also don't want functions that can cause panics as merge_nodes() could. * Initial simplification and formatting of command palette. Formatting is done during the creation of the command lists, removes the need for the creation and and traversal of an intermediary Vec>. Keyevent formatting changed from "(w)" to ["space>w>C-q"]. Clarifying how commands are triggered by moving through submenus/keytries. --- helix-term/src/application.rs | 2 +- helix-term/src/commands.rs | 36 +- helix-term/src/config.rs | 4 +- helix-term/src/keymap.rs | 591 +++------------------------ helix-term/src/keymap.rs:41:29 | 0 helix-term/src/keymap/default.rs | 12 +- helix-term/src/keymap/keymaps.rs | 121 ++++++ helix-term/src/keymap/keytrie.rs | 129 ++++++ helix-term/src/keymap/keytrienode.rs | 72 ++++ helix-term/src/keymap/macros.rs | 44 +- helix-term/src/keymap/tests.rs | 181 ++++++++ helix-term/src/lib.rs | 1 + helix-term/src/ui/editor.rs | 6 +- 13 files changed, 602 insertions(+), 597 deletions(-) create mode 100644 helix-term/src/keymap.rs:41:29 create mode 100644 helix-term/src/keymap/keymaps.rs create mode 100644 helix-term/src/keymap/keytrie.rs create mode 100644 helix-term/src/keymap/keytrienode.rs create mode 100644 helix-term/src/keymap/tests.rs diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index c0cbc2451483..42daaa64d44a 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -24,7 +24,7 @@ use crate::{ compositor::{Compositor, Event}, config::Config, job::Jobs, - keymap::Keymaps, + keymap::keymaps::Keymaps, ui::{self, overlay::overlayed}, }; diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index e70914016d60..ef166737a38e 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -46,7 +46,7 @@ use crate::{ args, compositor::{self, Component, Compositor}, job::Callback, - keymap::ReverseKeymap, + keymap::CommandList, ui::{self, overlay::overlayed, FilePicker, Picker, Popup, Prompt, PromptEvent}, }; @@ -2454,30 +2454,21 @@ fn jumplist_picker(cx: &mut Context) { cx.push_layer(Box::new(overlayed(picker))); } -impl ui::menu::Item for MappableCommand { - type Data = ReverseKeymap; - fn format(&self, keymap: &Self::Data) -> Row { - let fmt_binding = |bindings: &Vec>| -> String { - bindings.iter().fold(String::new(), |mut acc, bind| { - if !acc.is_empty() { - acc.push(' '); - } - for key in bind { - acc.push_str(&key.key_sequence_format()); - } - acc - }) - }; + +impl ui::menu::Item for MappableCommand { + type Data = CommandList; + + fn label(&self, keymap: &Self::Data) -> Spans { match self { MappableCommand::Typable { doc, name, .. } => match keymap.get(name as &String) { - Some(bindings) => format!("{} ({}) [:{}]", doc, fmt_binding(bindings), name).into(), - None => format!("{} [:{}]", doc, name).into(), + Some(key_events) => format!("{} {:?} ':{}'", doc, key_events, name).into(), + None => format!("{} ':{}'", doc, name).into(), }, MappableCommand::Static { doc, name, .. } => match keymap.get(*name) { - Some(bindings) => format!("{} ({}) [{}]", doc, fmt_binding(bindings), name).into(), - None => format!("{} [{}]", doc, name).into(), + Some(key_events) => format!("{} {:?} '{}'", doc, key_events, name).into(), + None => format!("{} '{}'", doc, name).into(), }, } } @@ -2486,9 +2477,9 @@ impl ui::menu::Item for MappableCommand { pub fn command_palette(cx: &mut Context) { cx.callback = Some(Box::new( move |compositor: &mut Compositor, cx: &mut compositor::Context| { - let keymap = compositor.find::().unwrap().keymaps.map() + let keymap_command_lists = compositor.find::().unwrap().keymaps.load_keymaps() [&cx.editor.mode] - .reverse_map(); + .command_list(); let mut commands: Vec = MappableCommand::STATIC_COMMAND_LIST.into(); commands.extend(typed::TYPABLE_COMMAND_LIST.iter().map(|cmd| { @@ -2499,7 +2490,7 @@ pub fn command_palette(cx: &mut Context) { } })); - let picker = Picker::new(commands, keymap, move |cx, command, _action| { + let picker = Picker::new(commands, keymap_command_lists, move |cx, command, _action| { let mut ctx = Context { register: None, count: std::num::NonZeroUsize::new(1), @@ -2528,6 +2519,7 @@ pub fn command_palette(cx: &mut Context) { compositor.push(Box::new(overlayed(picker))); }, )); + } fn last_picker(cx: &mut Context) { diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index f7cac6a36d10..471b8c168a79 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -1,4 +1,4 @@ -use crate::keymap::{default::default, merge_keys, Keymap}; +use crate::keymap::{default::default, Keymap, keymaps::Keymaps}; use helix_view::document::Mode; use serde::Deserialize; use std::collections::HashMap; @@ -47,7 +47,7 @@ impl Config { pub fn load_default() -> Result { match std::fs::read_to_string(helix_loader::config_file()) { Ok(config) => toml::from_str(&config) - .map(merge_keys) + .map(Keymaps::merge_with_default) .map_err(ConfigLoadError::BadConfig), Err(err) => Err(ConfigLoadError::Error(err)), } diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 7ff4f83764f9..edfa41df2705 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -1,570 +1,83 @@ +pub mod keymaps; +pub mod keytrienode; pub mod default; pub mod macros; +pub mod keytrie; +pub mod tests; -pub use crate::commands::MappableCommand; -use crate::config::Config; -use arc_swap::{ - access::{DynAccess, DynGuard}, - ArcSwap, -}; -use helix_view::{document::Mode, info::Info, input::KeyEvent}; use serde::Deserialize; -use std::{ - borrow::Cow, - collections::HashMap, - ops::{Deref, DerefMut}, - sync::Arc, -}; - -use default::default; -use macros::key; - -#[derive(Debug, Clone)] -pub struct KeyTrieNode { - /// A label for keys coming under this node, like "Goto mode" - name: String, - map: HashMap, - pub is_sticky: bool, -} - -impl<'de> Deserialize<'de> for KeyTrieNode { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - Ok(Self { - map: HashMap::::deserialize(deserializer)?, - ..Default::default() - }) - } -} - -impl KeyTrieNode { - pub fn new(name: &str, map: HashMap) -> Self { - Self { - name: name.to_string(), - map, - is_sticky: false, - } - } - - pub fn name(&self) -> &str { - &self.name - } - - /// 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); - } - } - - pub fn infobox(&self) -> Info { - let mut body: Vec<(String, &str)> = Vec::with_capacity(self.len()); - for (&key, trie) in self.iter() { - let description: &str = match trie { - KeyTrie::Leaf(command) => { - if command.name() == "no_op" { - continue; - } - command.doc() - } - KeyTrie::Node(key_trie) => key_trie.name(), - KeyTrie::Sequence(_) => "[Multiple commands]", - }; - match body.iter().position(|(_, existing_description)| &description == existing_description) { - Some(pos) => body[pos].0 += &format!(", {}", &key.to_string()), - None => body.push((key.to_string(), description)), - } - } - - Info::new(self.name(), &body) - } -} - -impl Default for KeyTrieNode { - fn default() -> Self { - Self::new("", HashMap::new()) - } -} - -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 { - Leaf(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::Leaf) - .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::<&str>()? { - 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(); - while let Some((key, value)) = map.next_entry::()? { - mapping.insert(key, value); - } - Ok(KeyTrie::Node(KeyTrieNode::new("", mapping))) - } -} - -impl KeyTrie { - pub fn node(&self) -> Option<&KeyTrieNode> { - match *self { - KeyTrie::Node(ref node) => Some(node), - KeyTrie::Leaf(_) | KeyTrie::Sequence(_) => None, - } - } - - pub fn node_mut(&mut self) -> Option<&mut KeyTrieNode> { - match *self { - KeyTrie::Node(ref mut node) => Some(node), - KeyTrie::Leaf(_) | 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::Leaf(_) | KeyTrie::Sequence(_) => None, - }? - } - Some(trie) +use std::{collections::HashMap, ops::{Deref, DerefMut}}; +use crate::{ + commands::MappableCommand, + keymap::{ + keytrie::KeyTrie, + keytrienode::KeyTrieNode } -} - -#[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), -} +}; #[derive(Debug, Clone, PartialEq, Deserialize)] #[serde(transparent)] +/// KeyTrie starting point. pub struct Keymap { - /// Always a Node - root: KeyTrie, + pub root_node: KeyTrie } -/// A map of command names to keybinds that will execute the command. -pub type ReverseKeymap = HashMap>>; - +pub type CommandList = HashMap>; impl Keymap { - pub fn new(root: KeyTrie) -> Self { - Keymap { root } - } - - pub fn reverse_map(&self) -> ReverseKeymap { - // recursively visit all nodes in keymap - fn map_node(cmd_map: &mut ReverseKeymap, node: &KeyTrie, keys: &mut Vec) { + pub fn new(root_node: KeyTrie) -> Self { + Keymap { root_node } + } + + /// Returns a key-value list of all commands associated to a given Keymap. + /// Keys are the node names (see KeyTrieNode documentation) + /// Values are lists of stringified KeyEvents that triger the command. + /// Each element in the KeyEvent list is prefixed with prefixed the ancestor KeyEvents. + /// For example: Stringified KeyEvent element for the 'goto_next_window' command could be "space>w>w". + /// Ancestor KeyEvents are in this case "space" and "w". + pub fn command_list(&self) -> CommandList { + let mut list = HashMap::new(); + _command_list(&mut list, &KeyTrieNode::KeyTrie(self.root_node.clone()), &mut String::new()); + return list; + + fn _command_list(list: &mut CommandList, node: &KeyTrieNode, prefix: &mut String) { match node { - KeyTrie::Leaf(cmd) => match cmd { - MappableCommand::Typable { name, .. } => { - cmd_map.entry(name.into()).or_default().push(keys.clone()) + KeyTrieNode::KeyTrie(trie_node) => { + for (key_event, subtrie_node) in trie_node.deref() { + let mut temp_prefix: String = prefix.to_string(); + if &temp_prefix != "" { + temp_prefix.push_str(">"); + } + temp_prefix.push_str(&key_event.to_string()); + _command_list(list, subtrie_node, &mut temp_prefix); } - MappableCommand::Static { name, .. } => cmd_map - .entry(name.to_string()) - .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(_) => {} + KeyTrieNode::MappableCommand(mappable_command) => { + list.entry(mappable_command.name().to_string()).or_default().push(prefix.to_string()); + }, + KeyTrieNode::CommandSequence(_) => {} }; } - - let mut res = HashMap::new(); - map_node(&mut res, &self.root, &mut Vec::new()); - res - } - - pub fn root(&self) -> &KeyTrie { - &self.root - } - - pub fn merge(&mut self, other: Self) { - self.root.merge_nodes(other.root); - } -} - -impl Deref for Keymap { - type Target = KeyTrieNode; - - fn deref(&self) -> &Self::Target { - self.root.node().unwrap() } } impl Default for Keymap { fn default() -> Self { - Self::new(KeyTrie::Node(KeyTrieNode::default())) + Self::new(KeyTrie::default()) } } -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.root), - }; - - let trie = match trie_node.search(&[*first]) { - Some(KeyTrie::Leaf(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::Leaf(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())))) - } -} +/// Returns the Keymap root KeyTrie node. +impl Deref for Keymap { + type Target = KeyTrie; -/// Merge default config keys with user overwritten keys for custom user config. -pub fn merge_keys(mut config: Config) -> Config { - let mut delta = std::mem::replace(&mut config.keys, default()); - for (mode, keys) in &mut config.keys { - keys.merge(delta.remove(mode).unwrap_or_default()) + fn deref(&self) -> &Self::Target { + &self.root_node } - config } -#[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 config = Config { - keys: 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 mut keymap = Keymaps::new(Box::new(Constant(merged_config.keys.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_config.keys.get_mut(&Mode::Normal).unwrap(); - // Assumes that `g` is a node in default keymap - assert_eq!( - keymap.root().search(&[key!('g'), key!('$')]).unwrap(), - &KeyTrie::Leaf(MappableCommand::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(MappableCommand::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(MappableCommand::goto_last_line), - "Old leaves in subnode should be present in merged node" - ); - - assert!(merged_config.keys.get(&Mode::Normal).unwrap().len() > 1); - assert!(merged_config.keys.get(&Mode::Insert).unwrap().len() > 0); - } - - #[test] - fn order_should_be_set() { - let config = Config { - keys: 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.get_mut(&Mode::Normal).unwrap(); - // Make sure mapping works - assert_eq!( - keymap - .root() - .search(&[key!(' '), key!('s'), key!('v')]) - .unwrap(), - &KeyTrie::Leaf(MappableCommand::vsplit), - "Leaf should be present in merged subnode" - ); - } - - #[test] - fn aliased_modes_are_same_in_default_keymap() { - let keymaps = Keymaps::default().map(); - let root = keymaps.get(&Mode::Normal).unwrap().root(); - 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 = Keymap::new(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" - ) +/// Returns the Keymap root KeyTrie node. +impl DerefMut for Keymap { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.root_node } } diff --git a/helix-term/src/keymap.rs:41:29 b/helix-term/src/keymap.rs:41:29 new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/helix-term/src/keymap/default.rs b/helix-term/src/keymap/default.rs index ef93dee08a77..7ce2736021b4 100644 --- a/helix-term/src/keymap/default.rs +++ b/helix-term/src/keymap/default.rs @@ -1,11 +1,13 @@ use std::collections::HashMap; use super::macros::keymap; -use super::{Keymap, Mode}; +use super::Keymap; +use super::keytrie::KeyTrie; +use helix_view::document::Mode; use helix_core::hashmap; pub fn default() -> HashMap { - let normal = keymap!({ "Normal mode" + let normal: KeyTrie = keymap!({ "Normal mode" "h" | "left" => move_char_left, "j" | "down" => move_line_down, "k" | "up" => move_line_up, @@ -317,8 +319,8 @@ pub fn default() -> HashMap { "C-a" => increment, "C-x" => decrement, }); - let mut select = normal.clone(); - select.merge_nodes(keymap!({ "Select mode" + let mut select: KeyTrie = normal.clone(); + select.merge_keytrie(keymap!({ "Select mode" "h" | "left" => extend_char_left, "j" | "down" => extend_line_down, "k" | "up" => extend_line_up, @@ -345,7 +347,7 @@ pub fn default() -> HashMap { "v" => normal_mode, })); - let insert = keymap!({ "Insert mode" + let insert: KeyTrie = keymap!({ "Insert mode" "esc" => normal_mode, "C-s" => commit_undo_checkpoint, diff --git a/helix-term/src/keymap/keymaps.rs b/helix-term/src/keymap/keymaps.rs new file mode 100644 index 000000000000..fe03af6d48c6 --- /dev/null +++ b/helix-term/src/keymap/keymaps.rs @@ -0,0 +1,121 @@ +use std::{sync::Arc, collections::HashMap}; +use arc_swap::{access::{DynAccess, DynGuard}, ArcSwap}; +use helix_view::{document::Mode, input::KeyEvent}; +use crate::commands::MappableCommand; +use crate::config::Config; +use super::{macros::key, keytrienode::KeyTrieNode, default}; +use super::keytrie::KeyTrie; +use super::Keymap; +use super::default::default; + +#[derive(Debug, Clone, PartialEq)] +pub enum KeymapResult { + Pending(KeyTrie), + Matched(MappableCommand), + MatchedCommandSequence(Vec), + NotFound, + /// Contains pressed KeyEvents leading up to the cancellation. + Cancelled(Vec), +} + +pub struct Keymaps { + pub keymaps: Box>>, + /// Relative to a sticky node if Some. + pending_keys: Vec, + pub sticky_keytrie: Option, +} + +impl Keymaps { + pub fn new(keymaps: Box>>) -> Self { + Self { + keymaps, + pending_keys: Vec::new(), + sticky_keytrie: None, + } + } + + pub fn load_keymaps(&self) -> DynGuard> { + self.keymaps.load() + } + + /// Returns list of keys waiting to be disambiguated in current mode. + pub fn pending(&self) -> &[KeyEvent] { + &self.pending_keys + } + + pub fn sticky_keytrie(&self) -> Option<&KeyTrie> { + self.sticky_keytrie.as_ref() + } + + pub fn merge_with_default(mut config: Config) -> Config { + let mut delta = std::mem::replace(&mut config.keys, default::default()); + for (mode, keys) in &mut config.keys { + keys.merge_keytrie(delta.remove(mode).unwrap_or_default().root_node) + } + config + } + + /// Lookup `key` in the keymap to try and find a command to execute. + /// Escape key represents cancellation. + /// This means clearing pending keystrokes, or the sticky_keytrie if none were present. + pub fn get(&mut self, mode: Mode, key: KeyEvent) -> KeymapResult { + // TODO: remove the sticky part and look up manually + let keymaps = &*self.load_keymaps(); + let active_keymap = &keymaps[&mode]; + + if key == key!(Esc) { + if !self.pending_keys.is_empty() { + // NOTE: Esc is not included here + return KeymapResult::Cancelled(self.pending_keys.drain(..).collect()); + } + // TODO: Shouldn't we return here also? + self.sticky_keytrie = None; + } + + // Check if sticky keytrie is to be used. + let starting_keytrie = match self.sticky_keytrie { + None => &active_keymap.root_node, + Some(ref active_sticky_keytrie) => active_sticky_keytrie, + }; + + // TODO: why check either pending or regular key? + let first_key = self.pending_keys.get(0).unwrap_or(&key); + + let pending_keytrie: KeyTrie = match starting_keytrie.traverse(&[*first_key]) { + Some(KeyTrieNode::KeyTrie(sub_keytrie)) => sub_keytrie, + Some(KeyTrieNode::MappableCommand(cmd)) => { + return KeymapResult::Matched(cmd.clone()); + } + Some(KeyTrieNode::CommandSequence(cmds)) => { + return KeymapResult::MatchedCommandSequence(cmds.clone()); + } + None => return KeymapResult::NotFound, + }; + + self.pending_keys.push(key); + match pending_keytrie.traverse(&self.pending_keys[1..]) { + Some(KeyTrieNode::KeyTrie(map)) => { + if map.is_sticky { + self.pending_keys.clear(); + self.sticky_keytrie = Some(map.clone()); + } + KeymapResult::Pending(map.clone()) + } + Some(KeyTrieNode::MappableCommand(cmd)) => { + self.pending_keys.clear(); + KeymapResult::Matched(cmd.clone()) + } + Some(KeyTrieNode::CommandSequence(cmds)) => { + self.pending_keys.clear(); + KeymapResult::MatchedCommandSequence(cmds.clone()) + } + None => KeymapResult::Cancelled(self.pending_keys.drain(..).collect()), + } + } +} + +impl Default for Keymaps { + fn default() -> Self { + Self::new(Box::new(ArcSwap::new(Arc::new(default())))) + } +} diff --git a/helix-term/src/keymap/keytrie.rs b/helix-term/src/keymap/keytrie.rs new file mode 100644 index 000000000000..ae5ecb58f12d --- /dev/null +++ b/helix-term/src/keymap/keytrie.rs @@ -0,0 +1,129 @@ +use serde::Deserialize; +use std::{collections::HashMap, ops::{Deref, DerefMut}}; +use helix_view::{info::Info, input::KeyEvent}; +use super::keytrienode::KeyTrieNode; + +/// Edges of the trie are KeyEvents and the nodes are descrbibed by KeyTrieNode +#[derive(Debug, Clone)] +pub struct KeyTrie { + documentation: String, + children: HashMap, + pub is_sticky: bool, +} + +impl KeyTrie { + pub fn new(documentation: &str, children: HashMap) -> Self { + Self { + documentation: documentation.to_string(), + children, + is_sticky: false, + } + } + + // None symbolizes NotFound + pub fn traverse(&self, key_events: &[KeyEvent]) -> Option { + return _traverse(self, key_events, 0); + + fn _traverse(keytrie: &KeyTrie, key_events: &[KeyEvent], mut depth: usize) -> Option { + if depth == key_events.len() { + return Some(KeyTrieNode::KeyTrie(keytrie.clone())); + } + else if let Some(found_child) = keytrie.get(&key_events[depth]) { + match found_child { + KeyTrieNode::KeyTrie(sub_keytrie) => { + depth += 1; + return _traverse(sub_keytrie, key_events, depth) + }, + _ => return Some(found_child.clone()) + } + } + return None; + } + } + + pub fn merge_keytrie(&mut self, mut other_keytrie: Self) { + for (other_key_event, other_child_node) in std::mem::take(&mut other_keytrie.children) { + match other_child_node { + KeyTrieNode::KeyTrie(other_child_key_trie) => { + if let Some(KeyTrieNode::KeyTrie(self_clashing_child_key_trie)) = self.children.get_mut(&other_key_event) { + self_clashing_child_key_trie.merge_keytrie(other_child_key_trie); + } + else { + self.children.insert(other_key_event, KeyTrieNode::KeyTrie(other_child_key_trie)); + } + } + KeyTrieNode::MappableCommand(_) | KeyTrieNode::CommandSequence(_) => { + self.children.insert(other_key_event, other_child_node); + } + } + } + } + + /// Open an Info box for a given KeyTrie + /// Shows the children listed by possible KeyEvents + /// and thier associated documentation. + pub fn infobox(&self) -> Info { + let mut body: Vec<(String, &str)> = Vec::with_capacity(self.len()); + for (&key_event, key_trie) in self.iter() { + let documentation: &str = match key_trie { + KeyTrieNode::MappableCommand(command) => { + if command.name() == "no_op" { + continue; + } + command.doc() + }, + KeyTrieNode::KeyTrie(key_trie) => &key_trie.documentation, + // FIX: default to a join of all command names + // NOTE: Giving same documentation for all sequences will place all sequence keyvents together. + // Regardless if the command sequence is different. + KeyTrieNode::CommandSequence(_) => "[Multiple commands]", + }; + match body.iter().position(|(_, existing_documentation)| &documentation == existing_documentation) { + Some(position) => body[position].0 += &format!(", {}", &key_event.to_string()), + None => body.push((key_event.to_string(), documentation)), + } + } + + Info::new(&self.documentation, &body) + } +} + +impl Default for KeyTrie { + fn default() -> Self { + Self::new("", HashMap::new()) + } +} + +impl PartialEq for KeyTrie { + fn eq(&self, other: &Self) -> bool { + self.children == other.children + } +} + +/// Returns the children of the KeyTrie +impl Deref for KeyTrie { + type Target = HashMap; + + fn deref(&self) -> &Self::Target { + &self.children + } +} + +/// Returns the children of the KeyTrie +impl DerefMut for KeyTrie { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.children + } +} + +impl<'de> Deserialize<'de> for KeyTrie { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + Ok(Self { + children: HashMap::::deserialize(deserializer)?, + ..Default::default() + }) + } +} diff --git a/helix-term/src/keymap/keytrienode.rs b/helix-term/src/keymap/keytrienode.rs new file mode 100644 index 000000000000..a8a0ebba384d --- /dev/null +++ b/helix-term/src/keymap/keytrienode.rs @@ -0,0 +1,72 @@ +use std::collections::HashMap; +use super::MappableCommand; +use serde::Deserialize; +use serde::de::Visitor; +use super::keytrie::KeyTrie; +use helix_view::input::KeyEvent; + +/// Each variant includes a documentaion property. +/// For the MappableCommand and CommandSequence variants, the property is self explanatory. +/// For KeyTrie, the documentation is used for respective infobox titles, +/// or infobox KeyEvent descriptions that in themselves trigger the opening of another infobox. +#[derive(Debug, Clone, PartialEq)] +pub enum KeyTrieNode { + MappableCommand(MappableCommand), + CommandSequence(Vec), + KeyTrie(KeyTrie), +} + +impl<'de> Deserialize<'de> for KeyTrieNode { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_any(KeyTrieNodeVisitor) + } +} + +struct KeyTrieNodeVisitor; + +impl<'de> Visitor<'de> for KeyTrieNodeVisitor { + type Value = KeyTrieNode; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(formatter, "a KeyTrieNode") + } + + fn visit_str(self, command: &str) -> Result + where + E: serde::de::Error, + { + command + .parse::() + .map(KeyTrieNode::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::<&str>()? { + commands.push( + command + .parse::() + .map_err(serde::de::Error::custom)?, + ) + } + Ok(KeyTrieNode::CommandSequence(commands)) + } + + fn visit_map(self, mut map: M) -> Result + where + M: serde::de::MapAccess<'de>, + { + let mut sub_key_trie = HashMap::new(); + while let Some((key_event, key_trie_node)) = map.next_entry::()? { + sub_key_trie.insert(key_event, key_trie_node); + } + Ok(KeyTrieNode::KeyTrie(KeyTrie::new("", sub_key_trie))) + } +} \ No newline at end of file diff --git a/helix-term/src/keymap/macros.rs b/helix-term/src/keymap/macros.rs index 1cf4151c9a38..4d81d220ffb9 100644 --- a/helix-term/src/keymap/macros.rs +++ b/helix-term/src/keymap/macros.rs @@ -80,42 +80,36 @@ macro_rules! alt { /// ``` #[macro_export] macro_rules! keymap { - (@trie $cmd:ident) => { - $crate::keymap::KeyTrie::Leaf($crate::commands::MappableCommand::$cmd) - }; - - (@trie - { $label:literal $(sticky=$sticky:literal)? $($($key:literal)|+ => $value:tt,)+ } - ) => { - keymap!({ $label $(sticky=$sticky)? $($($key)|+ => $value,)+ }) - }; - - (@trie [$($cmd:ident),* $(,)?]) => { - $crate::keymap::KeyTrie::Sequence(vec![$($crate::commands::Command::$cmd),*]) - }; - - ( - { $label:literal $(sticky=$sticky:literal)? $($($key:literal)|+ => $value:tt,)+ } - ) => { + ({ $label:literal $(sticky=$sticky:literal)? $($($key:literal)|+ => $value:tt,)+ }) => { // modified from the hashmap! macro { let _cap = hashmap!(@count $($($key),+),*); - let mut _map = ::std::collections::HashMap::with_capacity(_cap); + let mut _map: ::std::collections::HashMap<::helix_view::input::KeyEvent, $crate::keymap::keytrienode::KeyTrieNode> = + ::std::collections::HashMap::with_capacity(_cap); $( $( let _key = $key.parse::<::helix_view::input::KeyEvent>().unwrap(); - let _duplicate = _map.insert( - _key, - keymap!(@trie $value) - ); - assert!(_duplicate.is_none(), "Duplicate key found: {:?}", _duplicate.unwrap()); + let _potential_duplicate = _map.insert(_key,keymap!(@trie $value)); + assert!(_potential_duplicate.is_none(), "Duplicate key found: {:?}", _potential_duplicate.unwrap()); )+ )* - let mut _node = $crate::keymap::KeyTrieNode::new($label, _map); + let mut _node = $crate::keymap::keytrie::KeyTrie::new($label, _map); $( _node.is_sticky = $sticky; )? - $crate::keymap::KeyTrie::Node(_node) + _node } }; + + (@trie {$label:literal $(sticky=$sticky:literal)? $($($key:literal)|+ => $value:tt,)+ }) => { + $crate::keymap::keytrienode::KeyTrieNode::KeyTrie(keymap!({ $label $(sticky=$sticky)? $($($key)|+ => $value,)+ })) + }; + + (@trie $cmd:ident) => { + $crate::keymap::keytrienode::KeyTrieNode::MappableCommand($crate::commands::MappableCommand::$cmd) + }; + + (@trie [$($cmd:ident),* $(,)?]) => { + $crate::keymap::keytrienode::KeyTrieNode::CommandSequence(vec![$($crate::commands::Command::$cmd),*]) + }; } pub use alt; diff --git a/helix-term/src/keymap/tests.rs b/helix-term/src/keymap/tests.rs new file mode 100644 index 000000000000..55420d1f5f14 --- /dev/null +++ b/helix-term/src/keymap/tests.rs @@ -0,0 +1,181 @@ +#[macro_use] +#[cfg(test)] +mod tests { + use arc_swap::access::Constant; + use helix_core::hashmap; + use helix_view::{document::Mode, input::KeyEvent}; + use crate::config::Config; + use crate::commands::MappableCommand; + use crate::keymap::*; + use keymaps::{Keymaps, KeymapResult}; + use macros::*; + + #[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 config = Config { + keys: 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 mut keymap = Keymaps::new(Box::new(Constant(merged_config.keys.clone()))); + assert_eq!( + keymap.get(Mode::Normal, key!('i')), + KeymapResult::Matched(MappableCommand::normal_mode), + "New mappable command should ovveride default." + ); + assert_eq!( + keymap.get(Mode::Normal, key!('无')), + KeymapResult::Matched(MappableCommand::insert_mode), + "New mappable command 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), + "New Mappable command should replace default sub keytrie." + ); + + let keymap = merged_config.keys.get_mut(&Mode::Normal).unwrap(); + // Assumes that `g` is a node in default keymap + assert_eq!( + keymap.root_node.traverse(&[key!('g'), key!('$')]).unwrap(), + KeyTrieNode::MappableCommand(MappableCommand::goto_line_end), + "Mappable command should be present in merged keytrie." + ); + // Assumes that `gg` is in default keymap + assert_eq!( + keymap.root_node.traverse(&[key!('g'), key!('g')]).unwrap(), + KeyTrieNode::MappableCommand(MappableCommand::delete_char_forward), + "Mappable command should replace default in merged keytrie." + ); + // Assumes that `ge` is in default keymap + assert_eq!( + keymap.root_node.traverse(&[key!('g'), key!('e')]).unwrap(), + KeyTrieNode::MappableCommand(MappableCommand::goto_last_line), + "Mappable commands from default keytrie should still be present in merged merged keytrie unless overridden." + ); + + assert!(merged_config.keys.get(&Mode::Normal).unwrap().len() > 1); + assert!(merged_config.keys.get(&Mode::Insert).unwrap().len() > 0); + } + + #[test] + fn order_should_be_set() { + let config = Config { + keys: 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_normal = merged_config.keys.get_mut(&Mode::Normal).unwrap(); + // Make sure mapping works + assert_eq!( + keymap_normal + .root_node + .traverse(&[key!(' '), key!('s'), key!('v')]) + .unwrap(), + KeyTrieNode::MappableCommand(MappableCommand::vsplit), + "Mappable command should be present in merged keytrie." + ); + } + + #[test] + fn aliased_modes_are_same_in_default_keymap() { + let keymaps = Keymaps::default().keymaps; + let root = keymaps.load().get(&Mode::Normal).unwrap().root_node.clone(); + assert_eq!( + root.traverse(&[key!(' '), key!('w')]).unwrap(), + root.traverse(&["C-w".parse::().unwrap()]).unwrap(), + "Mismatch for window mode on `Space-w` and `Ctrl-w`." + ); + assert_eq!( + root.traverse(&[key!('z')]).unwrap(), + root.traverse(&[key!('Z')]).unwrap(), + "Mismatch for view mode on `z` and `Z`." + ); + } + + #[test] + fn command_list() { + 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 = Keymap::new(normal_mode); + let mut command_list = keymap.command_list(); + + // 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 command_list.values_mut() { + v.sort() + } + + assert_eq!( + command_list, + HashMap::from([ + ( + "insert_mode".to_string(), + vec![key!('i').to_string()] + ), + ( + "goto_file_start".to_string(), + vec![format!("{}>{}", key!('g'), key!('g'))] + ), + ( + "goto_file_end".to_string(), + vec![format!("{}>{}", key!('g'), key!('e'))] + ), + ( + "move_line_down".to_string(), + vec![key!('j').to_string(), key!('k').to_string()] + ) + ]), + "Mismatch" + ) + } +} \ No newline at end of file diff --git a/helix-term/src/lib.rs b/helix-term/src/lib.rs index a945b20dedaf..58ae12c2e120 100644 --- a/helix-term/src/lib.rs +++ b/helix-term/src/lib.rs @@ -11,6 +11,7 @@ pub mod job; pub mod keymap; pub mod ui; pub use keymap::macros::*; +pub use keymap::tests; #[cfg(not(windows))] fn true_color() -> bool { diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index a0518964e2a7..bd5d99fbcbaa 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -3,7 +3,7 @@ use crate::{ compositor::{Component, Context, Event, EventResult}, job::{self, Callback}, key, - keymap::{KeymapResult, Keymaps}, + keymap::keymaps::{KeymapResult, Keymaps}, ui::{Completion, ProgressSpinners}, }; @@ -936,7 +936,7 @@ impl EditorView { let mut last_mode = mode; self.pseudo_pending.extend(self.keymaps.pending()); let key_result = self.keymaps.get(mode, event); - cxt.editor.autoinfo = self.keymaps.sticky().map(|node| node.infobox()); + cxt.editor.autoinfo = self.keymaps.sticky_keytrie().map(|node| node.infobox()); let mut execute_command = |command: &commands::MappableCommand| { command.execute(cxt); @@ -976,7 +976,7 @@ impl EditorView { execute_command(command); } KeymapResult::Pending(node) => cxt.editor.autoinfo = Some(node.infobox()), - KeymapResult::MatchedSequence(commands) => { + KeymapResult::MatchedCommandSequence(commands) => { for command in commands { execute_command(command); } From d74be02498f06e3d70cd73eeb337fb59653126fc Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Mon, 26 Dec 2022 19:08:52 +0100 Subject: [PATCH 056/105] Rename MappableCommand field doc to description: For it not to be confused with the upcoming ":help" feature. --- helix-term/src/commands.rs | 24 ++++++++++++------------ helix-term/src/keymap/keytrie.rs | 5 ++--- helix-term/src/main.rs | 2 +- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index ef166737a38e..7e2262b3702c 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -133,23 +133,23 @@ pub enum MappableCommand { Typable { name: String, args: Vec, - doc: String, + description: String, }, Static { name: &'static str, fun: fn(cx: &mut Context), - doc: &'static str, + description: &'static str, }, } macro_rules! static_commands { - ( $($name:ident, $doc:literal,)* ) => { + ( $($name:ident, $description:literal,)* ) => { $( #[allow(non_upper_case_globals)] pub const $name: Self = Self::Static { name: stringify!($name), fun: $name, - doc: $doc + description: $description }; )* @@ -162,7 +162,7 @@ macro_rules! static_commands { impl MappableCommand { pub fn execute(&self, cx: &mut Context) { match &self { - Self::Typable { name, args, doc: _ } => { + Self::Typable { name, args, description: _ } => { let args: Vec> = args.iter().map(Cow::from).collect(); if let Some(command) = typed::TYPABLE_COMMAND_MAP.get(name.as_str()) { let mut cx = compositor::Context { @@ -186,10 +186,10 @@ impl MappableCommand { } } - pub fn doc(&self) -> &str { + pub fn description(&self) -> &str { match &self { - Self::Typable { doc, .. } => doc, - Self::Static { doc, .. } => doc, + Self::Typable { description, .. } => description, + Self::Static { description, .. } => description, } } @@ -476,7 +476,7 @@ impl std::str::FromStr for MappableCommand { .get(name) .map(|cmd| MappableCommand::Typable { name: cmd.name.to_owned(), - doc: format!(":{} {:?}", cmd.name, args), + description: format!(":{} {:?}", cmd.name, args), args, }) .ok_or_else(|| anyhow!("No TypableCommand named '{}'", s)) @@ -2462,11 +2462,11 @@ impl ui::menu::Item for MappableCommand { fn label(&self, keymap: &Self::Data) -> Spans { match self { - MappableCommand::Typable { doc, name, .. } => match keymap.get(name as &String) { + MappableCommand::Typable { description: doc, name, .. } => match keymap.get(name as &String) { Some(key_events) => format!("{} {:?} ':{}'", doc, key_events, name).into(), None => format!("{} ':{}'", doc, name).into(), }, - MappableCommand::Static { doc, name, .. } => match keymap.get(*name) { + MappableCommand::Static { description: doc, name, .. } => match keymap.get(*name) { Some(key_events) => format!("{} {:?} '{}'", doc, key_events, name).into(), None => format!("{} '{}'", doc, name).into(), }, @@ -2485,7 +2485,7 @@ pub fn command_palette(cx: &mut Context) { commands.extend(typed::TYPABLE_COMMAND_LIST.iter().map(|cmd| { MappableCommand::Typable { name: cmd.name.to_owned(), - doc: cmd.doc.to_owned(), + description: cmd.doc.to_owned(), args: Vec::new(), } })); diff --git a/helix-term/src/keymap/keytrie.rs b/helix-term/src/keymap/keytrie.rs index ae5ecb58f12d..25ab0b196afe 100644 --- a/helix-term/src/keymap/keytrie.rs +++ b/helix-term/src/keymap/keytrie.rs @@ -60,8 +60,7 @@ impl KeyTrie { } /// Open an Info box for a given KeyTrie - /// Shows the children listed by possible KeyEvents - /// and thier associated documentation. + /// Shows the children as possible KeyEvents and thier associated description. pub fn infobox(&self) -> Info { let mut body: Vec<(String, &str)> = Vec::with_capacity(self.len()); for (&key_event, key_trie) in self.iter() { @@ -70,7 +69,7 @@ impl KeyTrie { if command.name() == "no_op" { continue; } - command.doc() + command.description() }, KeyTrieNode::KeyTrie(key_trie) => &key_trie.documentation, // FIX: default to a join of all command names diff --git a/helix-term/src/main.rs b/helix-term/src/main.rs index aac5c5379f37..bae827e30402 100644 --- a/helix-term/src/main.rs +++ b/helix-term/src/main.rs @@ -128,7 +128,7 @@ FLAGS: let config = match std::fs::read_to_string(helix_loader::config_file()) { Ok(config) => toml::from_str(&config) - .map(helix_term::keymap::merge_keys) + .map(helix_term::keymap::keymaps::Keymaps::merge_with_default) .unwrap_or_else(|err| { eprintln!("Bad config: {}", err); eprintln!("Press to continue with default config"); From 7192a4eddcc076266d09a1b7b58c39f93d2ef834 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Mon, 26 Dec 2022 22:44:50 +0100 Subject: [PATCH 057/105] Initial sorting of keymap info window --- helix-term/src/keymap/keytrie.rs | 39 ++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/helix-term/src/keymap/keytrie.rs b/helix-term/src/keymap/keytrie.rs index 25ab0b196afe..8020b6caafbf 100644 --- a/helix-term/src/keymap/keytrie.rs +++ b/helix-term/src/keymap/keytrie.rs @@ -62,7 +62,7 @@ impl KeyTrie { /// Open an Info box for a given KeyTrie /// Shows the children as possible KeyEvents and thier associated description. pub fn infobox(&self) -> Info { - let mut body: Vec<(String, &str)> = Vec::with_capacity(self.len()); + let mut body: Vec<(Vec, &str)> = Vec::with_capacity(self.len()); for (&key_event, key_trie) in self.iter() { let documentation: &str = match key_trie { KeyTrieNode::MappableCommand(command) => { @@ -78,12 +78,43 @@ impl KeyTrie { KeyTrieNode::CommandSequence(_) => "[Multiple commands]", }; match body.iter().position(|(_, existing_documentation)| &documentation == existing_documentation) { - Some(position) => body[position].0 += &format!(", {}", &key_event.to_string()), - None => body.push((key_event.to_string(), documentation)), + Some(position) => body[position].0.push(key_event.to_string()), + None => { + let mut temp_vec: Vec = Vec::new(); + temp_vec.push(key_event.to_string()); + body.push((temp_vec, documentation)) + }, } } + // OPTIMIZATIONS? + // Change the children hashmap to an ordered datastructure? Like a regulap map maybe? + // Add sorted conditional? + // Add body as a keytrie field and reload it in keytrie merge? + + // Make the shortest keyevents appear first + let mut sorted_body = body + .iter() + .map(|(key_events, description)| { + let mut temp_key_events = key_events.clone(); + temp_key_events.sort_unstable_by(|a, b| a.len().cmp(&b.len())); + (temp_key_events, *description) + }) + .collect::, &str)>>(); + sorted_body.sort_unstable_by(|a, b| a.0[0].to_lowercase().cmp(&b.0[0].to_lowercase())); + + let stringified_key_events_body: Vec<(String, &str)> = sorted_body + .iter() + .map(|(key_events, description)| { + let key_events_string: String = key_events.iter().fold(String::new(), |mut acc, key_event| { + if acc.is_empty() { acc.push_str(key_event); } + else { acc.push_str(&format!(", {}", key_event)) } + acc + }); + (key_events_string, *description) + }) + .collect(); - Info::new(&self.documentation, &body) + Info::new(&self.documentation, &stringified_key_events_body) } } From 26adc32afd639b77aff56a810c7db08c611512c1 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Tue, 27 Dec 2022 12:08:12 +0100 Subject: [PATCH 058/105] Infobox: Consistently place lowercase equivalents first This, along with previous commit reverts regression in #952 --- helix-term/src/keymap/keytrie.rs | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/helix-term/src/keymap/keytrie.rs b/helix-term/src/keymap/keytrie.rs index 8020b6caafbf..e49b33d501b1 100644 --- a/helix-term/src/keymap/keytrie.rs +++ b/helix-term/src/keymap/keytrie.rs @@ -91,7 +91,7 @@ impl KeyTrie { // Add sorted conditional? // Add body as a keytrie field and reload it in keytrie merge? - // Make the shortest keyevents appear first + // Make the shortest keyevent appear first let mut sorted_body = body .iter() .map(|(key_events, description)| { @@ -102,6 +102,27 @@ impl KeyTrie { .collect::, &str)>>(); sorted_body.sort_unstable_by(|a, b| a.0[0].to_lowercase().cmp(&b.0[0].to_lowercase())); + // Consistently place lowercase before uppercase of the same letter. + if sorted_body.len() > 1 { + let mut x_index = 0; + let mut y_index = 1; + + while y_index < sorted_body.len() { + let x = &sorted_body[x_index].0[0]; + let y = &sorted_body[y_index].0[0]; + if x.to_lowercase() == y.to_lowercase() { + // Uppercase regarded as lower value. + if x < y { + let temp_holder = sorted_body[x_index].clone(); + sorted_body[x_index] = sorted_body[y_index].clone(); + sorted_body[y_index] = temp_holder; + } + } + x_index = y_index; + y_index += 1; + } + } + let stringified_key_events_body: Vec<(String, &str)> = sorted_body .iter() .map(|(key_events, description)| { From 4fb58f72ea1a946a788d556d0fbb107103e21542 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Tue, 27 Dec 2022 13:15:46 +0100 Subject: [PATCH 059/105] Remove infobox optimization suggestion comment: Sort duration calculations averaged to about 0.04 ms when opening the larger infoboxes. --- helix-term/src/keymap/keytrie.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/helix-term/src/keymap/keytrie.rs b/helix-term/src/keymap/keytrie.rs index e49b33d501b1..fa115770ed83 100644 --- a/helix-term/src/keymap/keytrie.rs +++ b/helix-term/src/keymap/keytrie.rs @@ -86,10 +86,6 @@ impl KeyTrie { }, } } - // OPTIMIZATIONS? - // Change the children hashmap to an ordered datastructure? Like a regulap map maybe? - // Add sorted conditional? - // Add body as a keytrie field and reload it in keytrie merge? // Make the shortest keyevent appear first let mut sorted_body = body @@ -101,7 +97,6 @@ impl KeyTrie { }) .collect::, &str)>>(); sorted_body.sort_unstable_by(|a, b| a.0[0].to_lowercase().cmp(&b.0[0].to_lowercase())); - // Consistently place lowercase before uppercase of the same letter. if sorted_body.len() > 1 { let mut x_index = 0; From d33ff8bce5eb00001d819483f52ef6ae4dee8fb4 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Tue, 27 Dec 2022 13:50:05 +0100 Subject: [PATCH 060/105] keymap testing touchups --- helix-term/src/keymap/keytrie.rs | 3 ++- helix-term/src/keymap/tests.rs | 5 ++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/helix-term/src/keymap/keytrie.rs b/helix-term/src/keymap/keytrie.rs index fa115770ed83..89c441e2974a 100644 --- a/helix-term/src/keymap/keytrie.rs +++ b/helix-term/src/keymap/keytrie.rs @@ -87,7 +87,8 @@ impl KeyTrie { } } - // Make the shortest keyevent appear first + // Shortest keyevent (as string) appears first, unless is a "C-" KeyEvent + // Those events will always be placed after the one letter KeyEvent let mut sorted_body = body .iter() .map(|(key_events, description)| { diff --git a/helix-term/src/keymap/tests.rs b/helix-term/src/keymap/tests.rs index 55420d1f5f14..557debb94cb1 100644 --- a/helix-term/src/keymap/tests.rs +++ b/helix-term/src/keymap/tests.rs @@ -89,7 +89,7 @@ mod tests { } #[test] - fn order_should_be_set() { + fn merges_with_default_keymap_config() { let config = Config { keys: hashmap! { Mode::Normal => Keymap::new( @@ -105,10 +105,9 @@ mod tests { }, ..Default::default() }; - let mut merged_config = merge_keys(config.clone()); + let mut merged_config = Keymaps::merge_with_default(config.clone()); assert_ne!(config, merged_config); let keymap_normal = merged_config.keys.get_mut(&Mode::Normal).unwrap(); - // Make sure mapping works assert_eq!( keymap_normal .root_node From 958e8bc3e17f9f6f36dad7ec88d14081b49da9c7 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Tue, 27 Dec 2022 14:33:32 +0100 Subject: [PATCH 061/105] Exclude config no_op bindings in command palette. --- helix-term/src/keymap.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index edfa41df2705..58433629f09b 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -52,7 +52,8 @@ impl Keymap { } }, KeyTrieNode::MappableCommand(mappable_command) => { - list.entry(mappable_command.name().to_string()).or_default().push(prefix.to_string()); + if mappable_command.name() == "no_op" { return } + list.entry(mappable_command.name().to_string()).or_default().push(prefix.to_string()); }, KeyTrieNode::CommandSequence(_) => {} }; From d0f93ec7c5861dcb5835e13ad9b0fc2f5aa16ff3 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Tue, 27 Dec 2022 20:22:59 +0100 Subject: [PATCH 062/105] Cleaner infobox join operation --- helix-term/src/keymap/keytrie.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helix-term/src/keymap/keytrie.rs b/helix-term/src/keymap/keytrie.rs index 89c441e2974a..227a0a5b7d9c 100644 --- a/helix-term/src/keymap/keytrie.rs +++ b/helix-term/src/keymap/keytrie.rs @@ -123,8 +123,8 @@ impl KeyTrie { .iter() .map(|(key_events, description)| { let key_events_string: String = key_events.iter().fold(String::new(), |mut acc, key_event| { - if acc.is_empty() { acc.push_str(key_event); } - else { acc.push_str(&format!(", {}", key_event)) } + if !acc.is_empty() { acc.push_str(", "); } + acc.push_str(key_event); acc }); (key_events_string, *description) From 876885e8bdefa100dc1c3b5d1de1135adc275a94 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Wed, 28 Dec 2022 17:48:59 +0100 Subject: [PATCH 063/105] use statement cleanups --- helix-term/src/application.rs | 15 ++++++--------- helix-term/src/commands.rs | 28 +++++++++++----------------- helix-term/src/config.rs | 7 ++----- helix-term/src/keymap.rs | 9 +++++---- helix-term/src/keymap.rs:41:29 | 0 helix-term/src/keymap/default.rs | 7 ++----- helix-term/src/keymap/keymaps.rs | 12 ++++-------- helix-term/src/keymap/keytrie.rs | 6 +++--- helix-term/src/keymap/keytrienode.rs | 8 +++----- helix-term/src/keymap/tests.rs | 8 ++------ 10 files changed, 38 insertions(+), 62 deletions(-) delete mode 100644 helix-term/src/keymap.rs:41:29 diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 42daaa64d44a..b614f5395c12 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -1,5 +1,3 @@ -use arc_swap::{access::Map, ArcSwap}; -use futures_util::Stream; use helix_core::{ diagnostic::{DiagnosticTag, NumberOrString}, path::get_relative_path, @@ -15,9 +13,6 @@ use helix_view::{ tree::Layout, Align, Editor, }; -use serde_json::json; -use tui::backend::Backend; - use crate::{ args::Args, commands::apply_workspace_edit, @@ -27,16 +22,17 @@ use crate::{ keymap::keymaps::Keymaps, ui::{self, overlay::overlayed}, }; - -use log::{debug, error, warn}; use std::{ io::{stdin, stdout, Write}, sync::Arc, time::{Duration, Instant}, }; - +use arc_swap::{access::Map, ArcSwap}; +use futures_util::Stream; +use log::{debug, error, warn}; use anyhow::{Context, Error}; - +use serde_json::json; +use tui::backend::Backend; use crossterm::{ event::{ DisableBracketedPaste, DisableFocusChange, DisableMouseCapture, EnableBracketedPaste, @@ -45,6 +41,7 @@ use crossterm::{ execute, terminal, tty::IsTty, }; + #[cfg(not(windows))] use { signal_hook::{consts::signal, low_level}, diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 7e2262b3702c..e21356545d7e 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -3,11 +3,11 @@ pub(crate) mod lsp; pub(crate) mod typed; pub use dap::*; -use helix_vcs::Hunk; pub use lsp::*; use tui::widgets::Row; pub use typed::*; +use helix_vcs::Hunk; use helix_core::{ comment, coords_at_pos, encoding, find_first_non_whitespace_char, find_root, graphemes, history::UndoKind, @@ -15,7 +15,7 @@ use helix_core::{ indent::IndentStyle, line_ending::{get_line_ending_of_str, line_end_char_index, str_is_line_ending}, match_brackets, - movement::{self, Direction}, + movement::{self, Direction, Movement}, object, pos_at_coords, pos_at_visual_coords, regex::{self, Regex, RegexBuilder}, search::{self, CharMatcher}, @@ -36,33 +36,27 @@ use helix_view::{ view::View, Document, DocumentId, Editor, ViewId, }; - -use anyhow::{anyhow, bail, ensure, Context as _}; -use fuzzy_matcher::FuzzyMatcher; -use insert::*; -use movement::Movement; - use crate::{ + commands::insert::*, args, compositor::{self, Component, Compositor}, - job::Callback, + job::{Callback, self, Jobs}, keymap::CommandList, ui::{self, overlay::overlayed, FilePicker, Picker, Popup, Prompt, PromptEvent}, }; - -use crate::job::{self, Jobs}; -use futures_util::StreamExt; -use std::{collections::HashMap, fmt, future::Future}; -use std::{collections::HashSet, num::NonZeroUsize}; - use std::{ + collections::{HashMap, HashSet}, + num::NonZeroUsize, + future::Future, borrow::Cow, path::{Path, PathBuf}, + fmt, }; - +use anyhow::{anyhow, bail, ensure, Context as _}; +use fuzzy_matcher::FuzzyMatcher; +use futures_util::StreamExt; use once_cell::sync::Lazy; use serde::de::{self, Deserialize, Deserializer}; - use grep_regex::RegexMatcherBuilder; use grep_searcher::{sinks, BinaryDetection, SearcherBuilder}; use ignore::{DirEntry, WalkBuilder, WalkState}; diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index 471b8c168a79..d924bff79c90 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -1,11 +1,8 @@ use crate::keymap::{default::default, Keymap, keymaps::Keymaps}; use helix_view::document::Mode; -use serde::Deserialize; -use std::collections::HashMap; -use std::fmt::Display; -use std::io::Error as IOError; -use std::path::PathBuf; +use std::{fmt::Display, collections::HashMap, io::Error as IOError}; use toml::de::Error as TomlError; +use serde::Deserialize; #[derive(Debug, Clone, PartialEq, Deserialize)] #[serde(deny_unknown_fields)] diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 58433629f09b..f5cbcec212a5 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -1,12 +1,11 @@ pub mod keymaps; -pub mod keytrienode; pub mod default; pub mod macros; -pub mod keytrie; pub mod tests; -use serde::Deserialize; -use std::{collections::HashMap, ops::{Deref, DerefMut}}; +mod keytrie; +mod keytrienode; + use crate::{ commands::MappableCommand, keymap::{ @@ -14,6 +13,8 @@ use crate::{ keytrienode::KeyTrieNode } }; +use std::{collections::HashMap, ops::{Deref, DerefMut}}; +use serde::Deserialize; #[derive(Debug, Clone, PartialEq, Deserialize)] #[serde(transparent)] diff --git a/helix-term/src/keymap.rs:41:29 b/helix-term/src/keymap.rs:41:29 deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/helix-term/src/keymap/default.rs b/helix-term/src/keymap/default.rs index 7ce2736021b4..b74ae84c7b63 100644 --- a/helix-term/src/keymap/default.rs +++ b/helix-term/src/keymap/default.rs @@ -1,10 +1,7 @@ -use std::collections::HashMap; - -use super::macros::keymap; -use super::Keymap; -use super::keytrie::KeyTrie; +use super::{macros::keymap, Keymap, keytrie::KeyTrie}; use helix_view::document::Mode; use helix_core::hashmap; +use std::collections::HashMap; pub fn default() -> HashMap { let normal: KeyTrie = keymap!({ "Normal mode" diff --git a/helix-term/src/keymap/keymaps.rs b/helix-term/src/keymap/keymaps.rs index fe03af6d48c6..ec9ecbce8eb7 100644 --- a/helix-term/src/keymap/keymaps.rs +++ b/helix-term/src/keymap/keymaps.rs @@ -1,12 +1,8 @@ +use super::*; +use crate::{commands::MappableCommand, config::Config}; +use helix_view::{document::Mode, input::KeyEvent}; use std::{sync::Arc, collections::HashMap}; use arc_swap::{access::{DynAccess, DynGuard}, ArcSwap}; -use helix_view::{document::Mode, input::KeyEvent}; -use crate::commands::MappableCommand; -use crate::config::Config; -use super::{macros::key, keytrienode::KeyTrieNode, default}; -use super::keytrie::KeyTrie; -use super::Keymap; -use super::default::default; #[derive(Debug, Clone, PartialEq)] pub enum KeymapResult { @@ -116,6 +112,6 @@ impl Keymaps { impl Default for Keymaps { fn default() -> Self { - Self::new(Box::new(ArcSwap::new(Arc::new(default())))) + Self::new(Box::new(ArcSwap::new(Arc::new(default::default())))) } } diff --git a/helix-term/src/keymap/keytrie.rs b/helix-term/src/keymap/keytrie.rs index 227a0a5b7d9c..0706182db228 100644 --- a/helix-term/src/keymap/keytrie.rs +++ b/helix-term/src/keymap/keytrie.rs @@ -1,7 +1,7 @@ -use serde::Deserialize; -use std::{collections::HashMap, ops::{Deref, DerefMut}}; -use helix_view::{info::Info, input::KeyEvent}; use super::keytrienode::KeyTrieNode; +use helix_view::{info::Info, input::KeyEvent}; +use std::{collections::HashMap, ops::{Deref, DerefMut}, cmp::Ordering}; +use serde::Deserialize; /// Edges of the trie are KeyEvents and the nodes are descrbibed by KeyTrieNode #[derive(Debug, Clone)] diff --git a/helix-term/src/keymap/keytrienode.rs b/helix-term/src/keymap/keytrienode.rs index a8a0ebba384d..41b5a7cd3343 100644 --- a/helix-term/src/keymap/keytrienode.rs +++ b/helix-term/src/keymap/keytrienode.rs @@ -1,9 +1,7 @@ -use std::collections::HashMap; -use super::MappableCommand; -use serde::Deserialize; -use serde::de::Visitor; -use super::keytrie::KeyTrie; +use super::{MappableCommand, keytrie::KeyTrie}; use helix_view::input::KeyEvent; +use std::collections::HashMap; +use serde::{Deserialize, de::Visitor}; /// Each variant includes a documentaion property. /// For the MappableCommand and CommandSequence variants, the property is self explanatory. diff --git a/helix-term/src/keymap/tests.rs b/helix-term/src/keymap/tests.rs index 557debb94cb1..cc06e9992b30 100644 --- a/helix-term/src/keymap/tests.rs +++ b/helix-term/src/keymap/tests.rs @@ -1,14 +1,10 @@ #[macro_use] #[cfg(test)] mod tests { - use arc_swap::access::Constant; use helix_core::hashmap; use helix_view::{document::Mode, input::KeyEvent}; - use crate::config::Config; - use crate::commands::MappableCommand; - use crate::keymap::*; - use keymaps::{Keymaps, KeymapResult}; - use macros::*; + use crate::{config::Config, commands::MappableCommand, keymap::*}; + use arc_swap::access::Constant; #[test] #[should_panic] From 315fa89b52cdeda99a09ea771b7042ad80b1db90 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Wed, 28 Dec 2022 18:44:26 +0100 Subject: [PATCH 064/105] Move Config related tests from keymap tests Config tests --- helix-term/src/config.rs | 127 +++++++++++++++++++++++++++++---- helix-term/src/keymap.rs | 5 +- helix-term/src/keymap/tests.rs | 97 +------------------------ 3 files changed, 118 insertions(+), 111 deletions(-) diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index d924bff79c90..21fbb60ea016 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -1,8 +1,8 @@ -use crate::keymap::{default::default, Keymap, keymaps::Keymaps}; +use crate::keymap::{default::default, keymaps::Keymaps, Keymap}; use helix_view::document::Mode; -use std::{fmt::Display, collections::HashMap, io::Error as IOError}; -use toml::de::Error as TomlError; use serde::Deserialize; +use std::{collections::HashMap, fmt::Display, io::Error as IOError}; +use toml::de::Error as TomlError; #[derive(Debug, Clone, PartialEq, Deserialize)] #[serde(deny_unknown_fields)] @@ -25,7 +25,7 @@ impl Default for Config { } #[derive(Debug)] -pub enum ConfigLoadError { +pub enum ConfigLoadError { BadConfig(TomlError), Error(IOError), } @@ -40,7 +40,7 @@ impl Display for ConfigLoadError { } impl Config { - // REFACTOR? code similar to config assignment in main.rs, + // REFACTOR? code similar to config assignment in main.rs, pub fn load_default() -> Result { match std::fs::read_to_string(helix_loader::config_file()) { Ok(config) => toml::from_str(&config) @@ -53,15 +53,23 @@ impl Config { #[cfg(test)] mod tests { - use super::*; + use crate::{ + commands::MappableCommand, + config::Config, + keymap::{ + default, + keymaps::{KeymapResult, Keymaps}, + keytrienode::KeyTrieNode, + macros::*, + Keymap, + }, + }; + use arc_swap::access::Constant; + use helix_core::hashmap; + use helix_view::document::Mode; #[test] fn parsing_keymaps_config_file() { - use crate::keymap; - use crate::keymap::Keymap; - use helix_core::hashmap; - use helix_view::document::Mode; - let sample_keymaps = r#" [keys.insert] y = "move_line_down" @@ -92,10 +100,103 @@ mod tests { fn keys_resolve_to_correct_defaults() { // From serde default let default_keys = toml::from_str::("").unwrap().keys; - assert_eq!(default_keys, default()); + assert_eq!(default_keys, default::default()); // From the Default trait let default_keys = Config::default().keys; - assert_eq!(default_keys, default()); + assert_eq!(default_keys, default::default()); + } + + #[test] + fn merge_partial_keys() { + let config = Config { + keys: 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 = Keymaps::merge_with_default(config.clone()); + assert_ne!(config, merged_config); + + let mut keymap = Keymaps::new(Box::new(Constant(merged_config.keys.clone()))); + assert_eq!( + keymap.get(Mode::Normal, key!('i')), + KeymapResult::Matched(MappableCommand::normal_mode), + "New mappable command should ovveride default." + ); + assert_eq!( + keymap.get(Mode::Normal, key!('无')), + KeymapResult::Matched(MappableCommand::insert_mode), + "New mappable command 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), + "New Mappable command should replace default sub keytrie." + ); + + let keymap = merged_config.keys.get_mut(&Mode::Normal).unwrap(); + // Assumes that `g` is a node in default keymap + assert_eq!( + keymap.root_node.traverse(&[key!('g'), key!('$')]).unwrap(), + KeyTrieNode::MappableCommand(MappableCommand::goto_line_end), + "Mappable command should be present in merged keytrie." + ); + // Assumes that `gg` is in default keymap + assert_eq!( + keymap.root_node.traverse(&[key!('g'), key!('g')]).unwrap(), + KeyTrieNode::MappableCommand(MappableCommand::delete_char_forward), + "Mappable command should replace default in merged keytrie." + ); + // Assumes that `ge` is in default keymap + assert_eq!( + keymap.root_node.traverse(&[key!('g'), key!('e')]).unwrap(), + KeyTrieNode::MappableCommand(MappableCommand::goto_last_line), + "Mappable commands from default keytrie should still be present in merged merged keytrie unless overridden." + ); + + assert!(merged_config.keys.get(&Mode::Normal).unwrap().len() > 1); + assert!(merged_config.keys.get(&Mode::Insert).unwrap().len() > 0); + } + + #[test] + fn merges_with_default_keymap_config() { + let config = Config { + keys: hashmap! { + Mode::Normal => Keymap::new( + keymap!({ "Normal mode" + "space" => { "" + "s" => { "" + "v" => vsplit, + "c" => hsplit, + }, + }, + }) + ) + }, + ..Default::default() + }; + let mut merged_config = Keymaps::merge_with_default(config.clone()); + assert_ne!(config, merged_config); + let keymap_normal = merged_config.keys.get_mut(&Mode::Normal).unwrap(); + assert_eq!( + keymap_normal + .root_node + .traverse(&[key!(' '), key!('s'), key!('v')]) + .unwrap(), + KeyTrieNode::MappableCommand(MappableCommand::vsplit), + "Mappable command should be present in merged keytrie." + ); } } diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index f5cbcec212a5..601046600b5e 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -2,9 +2,8 @@ pub mod keymaps; pub mod default; pub mod macros; pub mod tests; - -mod keytrie; -mod keytrienode; +pub mod keytrienode; +pub mod keytrie; use crate::{ commands::MappableCommand, diff --git a/helix-term/src/keymap/tests.rs b/helix-term/src/keymap/tests.rs index cc06e9992b30..157230d816d3 100644 --- a/helix-term/src/keymap/tests.rs +++ b/helix-term/src/keymap/tests.rs @@ -3,8 +3,8 @@ mod tests { use helix_core::hashmap; use helix_view::{document::Mode, input::KeyEvent}; - use crate::{config::Config, commands::MappableCommand, keymap::*}; - use arc_swap::access::Constant; + use crate::keymap::*; + use std::collections::HashMap; #[test] #[should_panic] @@ -21,99 +21,6 @@ mod tests { Keymaps::default(); } - #[test] - fn merge_partial_keys() { - let config = Config { - keys: 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 mut keymap = Keymaps::new(Box::new(Constant(merged_config.keys.clone()))); - assert_eq!( - keymap.get(Mode::Normal, key!('i')), - KeymapResult::Matched(MappableCommand::normal_mode), - "New mappable command should ovveride default." - ); - assert_eq!( - keymap.get(Mode::Normal, key!('无')), - KeymapResult::Matched(MappableCommand::insert_mode), - "New mappable command 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), - "New Mappable command should replace default sub keytrie." - ); - - let keymap = merged_config.keys.get_mut(&Mode::Normal).unwrap(); - // Assumes that `g` is a node in default keymap - assert_eq!( - keymap.root_node.traverse(&[key!('g'), key!('$')]).unwrap(), - KeyTrieNode::MappableCommand(MappableCommand::goto_line_end), - "Mappable command should be present in merged keytrie." - ); - // Assumes that `gg` is in default keymap - assert_eq!( - keymap.root_node.traverse(&[key!('g'), key!('g')]).unwrap(), - KeyTrieNode::MappableCommand(MappableCommand::delete_char_forward), - "Mappable command should replace default in merged keytrie." - ); - // Assumes that `ge` is in default keymap - assert_eq!( - keymap.root_node.traverse(&[key!('g'), key!('e')]).unwrap(), - KeyTrieNode::MappableCommand(MappableCommand::goto_last_line), - "Mappable commands from default keytrie should still be present in merged merged keytrie unless overridden." - ); - - assert!(merged_config.keys.get(&Mode::Normal).unwrap().len() > 1); - assert!(merged_config.keys.get(&Mode::Insert).unwrap().len() > 0); - } - - #[test] - fn merges_with_default_keymap_config() { - let config = Config { - keys: hashmap! { - Mode::Normal => Keymap::new( - keymap!({ "Normal mode" - "space" => { "" - "s" => { "" - "v" => vsplit, - "c" => hsplit, - }, - }, - }) - ) - }, - ..Default::default() - }; - let mut merged_config = Keymaps::merge_with_default(config.clone()); - assert_ne!(config, merged_config); - let keymap_normal = merged_config.keys.get_mut(&Mode::Normal).unwrap(); - assert_eq!( - keymap_normal - .root_node - .traverse(&[key!(' '), key!('s'), key!('v')]) - .unwrap(), - KeyTrieNode::MappableCommand(MappableCommand::vsplit), - "Mappable command should be present in merged keytrie." - ); - } - #[test] fn aliased_modes_are_same_in_default_keymap() { let keymaps = Keymaps::default().keymaps; From fcf8e6b6692d629da585c4e4c1f141ec7053f056 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Wed, 28 Dec 2022 19:35:34 +0100 Subject: [PATCH 065/105] Config test cleanup * Elaborated on test descriptions * Removed duplicate unit test * Unified keytrie traversal in tests --- helix-term/src/config.rs | 84 ++++++++++++++-------------------------- 1 file changed, 28 insertions(+), 56 deletions(-) diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index 21fbb60ea016..40d98378da10 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -58,18 +58,17 @@ mod tests { config::Config, keymap::{ default, - keymaps::{KeymapResult, Keymaps}, + keymaps::Keymaps, keytrienode::KeyTrieNode, macros::*, Keymap, }, }; - use arc_swap::access::Constant; use helix_core::hashmap; use helix_view::document::Mode; #[test] - fn parsing_keymaps_config_file() { + fn parses_keymap_from_toml() { let sample_keymaps = r#" [keys.insert] y = "move_line_down" @@ -108,8 +107,8 @@ mod tests { } #[test] - fn merge_partial_keys() { - let config = Config { + fn user_config_merges_with_default() { + let user_config = Config { keys: hashmap! { Mode::Normal => Keymap::new( keymap!({ "Normal mode" @@ -125,78 +124,51 @@ mod tests { }, ..Default::default() }; - let mut merged_config = Keymaps::merge_with_default(config.clone()); - assert_ne!(config, merged_config); + let mut merged_config = Keymaps::merge_with_default(user_config.clone()); + assert_ne!( + user_config, + merged_config, + "Merged user keymap with default should differ from user keymap." + ); - let mut keymap = Keymaps::new(Box::new(Constant(merged_config.keys.clone()))); + let keymap_normal_root_key_trie = &merged_config.keys.get_mut(&Mode::Normal).unwrap().root_node; assert_eq!( - keymap.get(Mode::Normal, key!('i')), - KeymapResult::Matched(MappableCommand::normal_mode), - "New mappable command should ovveride default." + keymap_normal_root_key_trie.traverse(&[key!('i')]).unwrap(), + KeyTrieNode::MappableCommand(MappableCommand::normal_mode), + "User supplied mappable command should ovveride default mappable command bound to the same key event." ); assert_eq!( - keymap.get(Mode::Normal, key!('无')), - KeymapResult::Matched(MappableCommand::insert_mode), - "New mappable command should be present in merged keymap." + keymap_normal_root_key_trie.traverse(&[key!('无')]).unwrap(), + KeyTrieNode::MappableCommand(MappableCommand::insert_mode), + "User supplied mappable command of new key event 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), - "New Mappable command should replace default sub keytrie." + keymap_normal_root_key_trie.traverse(&[key!('z')]).unwrap(), + KeyTrieNode::MappableCommand(MappableCommand::jump_backward), + "User supplied mappable command should replace a sub keytrie from default keymap bound to the same key event." ); - - let keymap = merged_config.keys.get_mut(&Mode::Normal).unwrap(); - // Assumes that `g` is a node in default keymap + // Assumes that `g` is a sub key trie in default keymap assert_eq!( - keymap.root_node.traverse(&[key!('g'), key!('$')]).unwrap(), + keymap_normal_root_key_trie.traverse(&[key!('g'), key!('$')]).unwrap(), KeyTrieNode::MappableCommand(MappableCommand::goto_line_end), - "Mappable command should be present in merged keytrie." + "User supplied mappable command should be inserted under the correct sub keytrie." ); // Assumes that `gg` is in default keymap assert_eq!( - keymap.root_node.traverse(&[key!('g'), key!('g')]).unwrap(), + keymap_normal_root_key_trie.traverse(&[key!('g'), key!('g')]).unwrap(), KeyTrieNode::MappableCommand(MappableCommand::delete_char_forward), - "Mappable command should replace default in merged keytrie." + "User supplied mappable command should replace default even in sub keytries." ); // Assumes that `ge` is in default keymap assert_eq!( - keymap.root_node.traverse(&[key!('g'), key!('e')]).unwrap(), + keymap_normal_root_key_trie.traverse(&[key!('g'), key!('e')]).unwrap(), KeyTrieNode::MappableCommand(MappableCommand::goto_last_line), - "Mappable commands from default keytrie should still be present in merged merged keytrie unless overridden." + "Default mappable commands that aren't ovveridden should exist in merged keymap." ); + // Huh? assert!(merged_config.keys.get(&Mode::Normal).unwrap().len() > 1); assert!(merged_config.keys.get(&Mode::Insert).unwrap().len() > 0); } - - #[test] - fn merges_with_default_keymap_config() { - let config = Config { - keys: hashmap! { - Mode::Normal => Keymap::new( - keymap!({ "Normal mode" - "space" => { "" - "s" => { "" - "v" => vsplit, - "c" => hsplit, - }, - }, - }) - ) - }, - ..Default::default() - }; - let mut merged_config = Keymaps::merge_with_default(config.clone()); - assert_ne!(config, merged_config); - let keymap_normal = merged_config.keys.get_mut(&Mode::Normal).unwrap(); - assert_eq!( - keymap_normal - .root_node - .traverse(&[key!(' '), key!('s'), key!('v')]) - .unwrap(), - KeyTrieNode::MappableCommand(MappableCommand::vsplit), - "Mappable command should be present in merged keytrie." - ); - } } From e233a1bc1b0685612c9c1bf64d109c0c882ed278 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Thu, 29 Dec 2022 08:38:17 +0100 Subject: [PATCH 066/105] Load keymap config consistently * Moved config merging Keymaps to Config * Removed unused default EditorView function --- helix-term/src/config.rs | 18 +++++++++++++----- helix-term/src/keymap/keymaps.rs | 11 ++--------- helix-term/src/main.rs | 2 +- helix-term/src/ui/editor.rs | 6 ------ 4 files changed, 16 insertions(+), 21 deletions(-) diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index 40d98378da10..eae1da41fc94 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -1,4 +1,4 @@ -use crate::keymap::{default::default, keymaps::Keymaps, Keymap}; +use crate::keymap::{default, Keymap}; use helix_view::document::Mode; use serde::Deserialize; use std::{collections::HashMap, fmt::Display, io::Error as IOError}; @@ -8,7 +8,7 @@ use toml::de::Error as TomlError; #[serde(deny_unknown_fields)] pub struct Config { pub theme: Option, - #[serde(default = "default")] + #[serde(default = "default::default")] pub keys: HashMap, #[serde(default)] pub editor: helix_view::editor::Config, @@ -18,7 +18,7 @@ impl Default for Config { fn default() -> Config { Config { theme: None, - keys: default(), + keys: default::default(), editor: helix_view::editor::Config::default(), } } @@ -44,11 +44,19 @@ impl Config { pub fn load_default() -> Result { match std::fs::read_to_string(helix_loader::config_file()) { Ok(config) => toml::from_str(&config) - .map(Keymaps::merge_with_default) + .map(self::Config::merge_in_default_keymap) .map_err(ConfigLoadError::BadConfig), Err(err) => Err(ConfigLoadError::Error(err)), } } + + pub fn merge_in_default_keymap(mut config: Config) -> Config { + let mut delta = std::mem::replace(&mut config.keys, default::default()); + for (mode, keys) in &mut config.keys { + keys.merge_keytrie(delta.remove(mode).unwrap_or_default().root_node) + } + config + } } #[cfg(test)] @@ -124,7 +132,7 @@ mod tests { }, ..Default::default() }; - let mut merged_config = Keymaps::merge_with_default(user_config.clone()); + let mut merged_config = Keymaps::merge_in_default_keymap(user_config.clone()); assert_ne!( user_config, merged_config, diff --git a/helix-term/src/keymap/keymaps.rs b/helix-term/src/keymap/keymaps.rs index ec9ecbce8eb7..5d899f49c7a2 100644 --- a/helix-term/src/keymap/keymaps.rs +++ b/helix-term/src/keymap/keymaps.rs @@ -1,5 +1,5 @@ use super::*; -use crate::{commands::MappableCommand, config::Config}; +use crate::commands::MappableCommand; use helix_view::{document::Mode, input::KeyEvent}; use std::{sync::Arc, collections::HashMap}; use arc_swap::{access::{DynAccess, DynGuard}, ArcSwap}; @@ -43,14 +43,6 @@ impl Keymaps { self.sticky_keytrie.as_ref() } - pub fn merge_with_default(mut config: Config) -> Config { - let mut delta = std::mem::replace(&mut config.keys, default::default()); - for (mode, keys) in &mut config.keys { - keys.merge_keytrie(delta.remove(mode).unwrap_or_default().root_node) - } - config - } - /// Lookup `key` in the keymap to try and find a command to execute. /// Escape key represents cancellation. /// This means clearing pending keystrokes, or the sticky_keytrie if none were present. @@ -110,6 +102,7 @@ impl Keymaps { } } +// NOTE: Only used for testing purposes impl Default for Keymaps { fn default() -> Self { Self::new(Box::new(ArcSwap::new(Arc::new(default::default())))) diff --git a/helix-term/src/main.rs b/helix-term/src/main.rs index bae827e30402..f3cfd0800ed0 100644 --- a/helix-term/src/main.rs +++ b/helix-term/src/main.rs @@ -128,7 +128,7 @@ FLAGS: let config = match std::fs::read_to_string(helix_loader::config_file()) { Ok(config) => toml::from_str(&config) - .map(helix_term::keymap::keymaps::Keymaps::merge_with_default) + .map(Config::merge_in_default_keymap) .unwrap_or_else(|err| { eprintln!("Bad config: {}", err); eprintln!("Press to continue with default config"); diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index bd5d99fbcbaa..98da59c95097 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -47,12 +47,6 @@ pub enum InsertEvent { TriggerCompletion, } -impl Default for EditorView { - fn default() -> Self { - Self::new(Keymaps::default()) - } -} - impl EditorView { pub fn new(keymaps: Keymaps) -> Self { Self { From 4a188d2c03f6ea0ac2006ea422c25b123cb786cc Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Thu, 29 Dec 2022 18:58:53 +0100 Subject: [PATCH 067/105] Make use of new self context in keymap config load --- helix-term/src/config.rs | 13 ++++++------- helix-term/src/main.rs | 2 +- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index eae1da41fc94..57fe28a84045 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -44,18 +44,18 @@ impl Config { pub fn load_default() -> Result { match std::fs::read_to_string(helix_loader::config_file()) { Ok(config) => toml::from_str(&config) - .map(self::Config::merge_in_default_keymap) + .map(|config: Config| config.merge_in_default_keymap()) .map_err(ConfigLoadError::BadConfig), Err(err) => Err(ConfigLoadError::Error(err)), } } - pub fn merge_in_default_keymap(mut config: Config) -> Config { - let mut delta = std::mem::replace(&mut config.keys, default::default()); - for (mode, keys) in &mut config.keys { + pub fn merge_in_default_keymap(mut self) -> Config { + let mut delta = std::mem::replace(&mut self.keys, default::default()); + for (mode, keys) in &mut self.keys { keys.merge_keytrie(delta.remove(mode).unwrap_or_default().root_node) } - config + self } } @@ -66,7 +66,6 @@ mod tests { config::Config, keymap::{ default, - keymaps::Keymaps, keytrienode::KeyTrieNode, macros::*, Keymap, @@ -132,7 +131,7 @@ mod tests { }, ..Default::default() }; - let mut merged_config = Keymaps::merge_in_default_keymap(user_config.clone()); + let mut merged_config = user_config.clone().merge_in_default_keymap(); assert_ne!( user_config, merged_config, diff --git a/helix-term/src/main.rs b/helix-term/src/main.rs index f3cfd0800ed0..67e4f4a0cd55 100644 --- a/helix-term/src/main.rs +++ b/helix-term/src/main.rs @@ -128,7 +128,7 @@ FLAGS: let config = match std::fs::read_to_string(helix_loader::config_file()) { Ok(config) => toml::from_str(&config) - .map(Config::merge_in_default_keymap) + .map(|config: Config| config.merge_in_default_keymap()) .unwrap_or_else(|err| { eprintln!("Bad config: {}", err); eprintln!("Press to continue with default config"); From baf86401f8f6bb506a2fcc997120ce3870477149 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Thu, 29 Dec 2022 21:27:37 +0100 Subject: [PATCH 068/105] Minor keymap module visibilyti cleanup --- helix-term/src/keymap.rs | 3 ++- helix-term/src/keymap/keymaps.rs | 1 - helix-term/src/lib.rs | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 601046600b5e..880b405f604e 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -1,9 +1,10 @@ pub mod keymaps; pub mod default; pub mod macros; -pub mod tests; +// NOTE: Only pub becuase of their use in macros pub mod keytrienode; pub mod keytrie; +mod tests; use crate::{ commands::MappableCommand, diff --git a/helix-term/src/keymap/keymaps.rs b/helix-term/src/keymap/keymaps.rs index 5d899f49c7a2..f824401b1833 100644 --- a/helix-term/src/keymap/keymaps.rs +++ b/helix-term/src/keymap/keymaps.rs @@ -102,7 +102,6 @@ impl Keymaps { } } -// NOTE: Only used for testing purposes impl Default for Keymaps { fn default() -> Self { Self::new(Box::new(ArcSwap::new(Arc::new(default::default())))) diff --git a/helix-term/src/lib.rs b/helix-term/src/lib.rs index 58ae12c2e120..fc8e934e1a7d 100644 --- a/helix-term/src/lib.rs +++ b/helix-term/src/lib.rs @@ -10,8 +10,6 @@ pub mod health; pub mod job; pub mod keymap; pub mod ui; -pub use keymap::macros::*; -pub use keymap::tests; #[cfg(not(windows))] fn true_color() -> bool { From 3466209d30d33c1e41fe96827925522d53daeaaa Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Thu, 29 Dec 2022 21:54:41 +0100 Subject: [PATCH 069/105] Renamed the keymap! macro to keytrie!: It produces, after all, a Keytrie. It would be a bit like saying: let engine = car!() --- helix-term/src/config.rs | 6 +++--- helix-term/src/keymap/default.rs | 10 +++++----- helix-term/src/keymap/macros.rs | 18 +++++++++++++----- helix-term/src/keymap/tests.rs | 4 ++-- 4 files changed, 23 insertions(+), 15 deletions(-) diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index 57fe28a84045..fc724d818ff0 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -89,11 +89,11 @@ mod tests { toml::from_str::(sample_keymaps).unwrap(), Config { keys: hashmap! { - Mode::Insert => Keymap::new(keymap!({ "Insert mode" + Mode::Insert => Keymap::new(keytrie!({ "Insert mode" "y" => move_line_down, "S-C-a" => delete_selection, })), - Mode::Normal => Keymap::new(keymap!({ "Normal mode" + Mode::Normal => Keymap::new(keytrie!({ "Normal mode" "A-F12" => move_next_word_end, })), }, @@ -118,7 +118,7 @@ mod tests { let user_config = Config { keys: hashmap! { Mode::Normal => Keymap::new( - keymap!({ "Normal mode" + keytrie!({ "Normal mode" "i" => normal_mode, "无" => insert_mode, "z" => jump_backward, diff --git a/helix-term/src/keymap/default.rs b/helix-term/src/keymap/default.rs index b74ae84c7b63..551cf1a79bed 100644 --- a/helix-term/src/keymap/default.rs +++ b/helix-term/src/keymap/default.rs @@ -1,10 +1,10 @@ -use super::{macros::keymap, Keymap, keytrie::KeyTrie}; +use super::{macros::keytrie, Keymap}; use helix_view::document::Mode; use helix_core::hashmap; use std::collections::HashMap; pub fn default() -> HashMap { - let normal: KeyTrie = keymap!({ "Normal mode" + let normal = keytrie!({ "Normal mode" "h" | "left" => move_char_left, "j" | "down" => move_line_down, "k" | "up" => move_line_up, @@ -316,8 +316,8 @@ pub fn default() -> HashMap { "C-a" => increment, "C-x" => decrement, }); - let mut select: KeyTrie = normal.clone(); - select.merge_keytrie(keymap!({ "Select mode" + let mut select = normal.clone(); + select.merge_keytrie(keytrie!({ "Select mode" "h" | "left" => extend_char_left, "j" | "down" => extend_line_down, "k" | "up" => extend_line_up, @@ -344,7 +344,7 @@ pub fn default() -> HashMap { "v" => normal_mode, })); - let insert: KeyTrie = keymap!({ "Insert mode" + let insert = keytrie!({ "Insert mode" "esc" => normal_mode, "C-s" => commit_undo_checkpoint, diff --git a/helix-term/src/keymap/macros.rs b/helix-term/src/keymap/macros.rs index 4d81d220ffb9..66fa78f7a34b 100644 --- a/helix-term/src/keymap/macros.rs +++ b/helix-term/src/keymap/macros.rs @@ -66,9 +66,8 @@ macro_rules! alt { /// /// ``` /// # use helix_core::hashmap; -/// # use helix_term::keymap; -/// # use helix_term::keymap::Keymap; -/// let normal_mode = keymap!({ "Normal mode" +/// # use helix_term::keymap::{Keymap, macros::keytrie}; +/// let normal_mode = keytrie!({ "Normal mode" /// "i" => insert_mode, /// "g" => { "Goto" /// "g" => goto_file_start, @@ -79,7 +78,7 @@ macro_rules! alt { /// let keymap = Keymap::new(normal_mode); /// ``` #[macro_export] -macro_rules! keymap { +macro_rules! keytrie { ({ $label:literal $(sticky=$sticky:literal)? $($($key:literal)|+ => $value:tt,)+ }) => { // modified from the hashmap! macro { @@ -88,8 +87,13 @@ macro_rules! keymap { ::std::collections::HashMap::with_capacity(_cap); $( $( +<<<<<<< HEAD let _key = $key.parse::<::helix_view::input::KeyEvent>().unwrap(); let _potential_duplicate = _map.insert(_key,keymap!(@trie $value)); +======= + let _key = $key.parse::().unwrap(); + let _potential_duplicate = _map.insert(_key,keytrie!(@trie $value)); +>>>>>>> 8cb9a917 (Renamed the keymap! macro to keytrie!:) assert!(_potential_duplicate.is_none(), "Duplicate key found: {:?}", _potential_duplicate.unwrap()); )+ )* @@ -100,7 +104,7 @@ macro_rules! keymap { }; (@trie {$label:literal $(sticky=$sticky:literal)? $($($key:literal)|+ => $value:tt,)+ }) => { - $crate::keymap::keytrienode::KeyTrieNode::KeyTrie(keymap!({ $label $(sticky=$sticky)? $($($key)|+ => $value,)+ })) + $crate::keymap::keytrienode::KeyTrieNode::KeyTrie(keytrie!({ $label $(sticky=$sticky)? $($($key)|+ => $value,)+ })) }; (@trie $cmd:ident) => { @@ -113,7 +117,11 @@ macro_rules! keymap { } pub use alt; +<<<<<<< HEAD pub use ctrl; pub use key; pub use keymap; pub use shift; +======= +pub use keytrie; +>>>>>>> 8cb9a917 (Renamed the keymap! macro to keytrie!:) diff --git a/helix-term/src/keymap/tests.rs b/helix-term/src/keymap/tests.rs index 157230d816d3..4570ac2e50eb 100644 --- a/helix-term/src/keymap/tests.rs +++ b/helix-term/src/keymap/tests.rs @@ -9,7 +9,7 @@ mod tests { #[test] #[should_panic] fn duplicate_keys_should_panic() { - keymap!({ "Normal mode" + keytrie!({ "Normal mode" "i" => normal_mode, "i" => goto_definition, }); @@ -39,7 +39,7 @@ mod tests { #[test] fn command_list() { - let normal_mode = keymap!({ "Normal mode" + let normal_mode = keytrie!({ "Normal mode" "i" => insert_mode, "g" => { "Goto" "g" => goto_file_start, From ff2538031b32bb72ffaca678b3fee87877a59944 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Thu, 19 Jan 2023 15:37:43 +0100 Subject: [PATCH 070/105] Fix failed cherry picks --- helix-term/src/commands.rs | 45 ++++++++++++++++++++++---------- helix-term/src/commands/typed.rs | 3 +-- helix-term/src/keymap.rs | 2 +- helix-term/src/keymap/keymaps.rs | 1 + helix-term/src/keymap/keytrie.rs | 2 +- helix-term/src/keymap/macros.rs | 9 ------- helix-term/src/keymap/tests.rs | 9 +++---- 7 files changed, 39 insertions(+), 32 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index e21356545d7e..ce1a698d2b0f 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -4,7 +4,7 @@ pub(crate) mod typed; pub use dap::*; pub use lsp::*; -use tui::widgets::Row; +use tui::widgets::{Row, Cell}; pub use typed::*; use helix_vcs::Hunk; @@ -34,7 +34,7 @@ use helix_view::{ keyboard::KeyCode, tree, view::View, - Document, DocumentId, Editor, ViewId, + Document, DocumentId, Editor, ViewId, apply_transaction, }; use crate::{ commands::insert::*, @@ -2448,22 +2448,40 @@ fn jumplist_picker(cx: &mut Context) { cx.push_layer(Box::new(overlayed(picker))); } - - - +// NOTE: does not present aliases impl ui::menu::Item for MappableCommand { type Data = CommandList; - fn label(&self, keymap: &Self::Data) -> Spans { + fn format(&self, command_list: &Self::Data) -> Row { match self { - MappableCommand::Typable { description: doc, name, .. } => match keymap.get(name as &String) { - Some(key_events) => format!("{} {:?} ':{}'", doc, key_events, name).into(), - None => format!("{} ':{}'", doc, name).into(), - }, - MappableCommand::Static { description: doc, name, .. } => match keymap.get(*name) { - Some(key_events) => format!("{} {:?} '{}'", doc, key_events, name).into(), - None => format!("{} '{}'", doc, name).into(), + MappableCommand::Typable { description: doc, name, .. } => { + let mut row: Vec = vec![Cell::from(&*name.as_str()), Cell::from(""), Cell::from(&*doc.as_str())]; + match command_list.get(name as &String) { + Some(key_events) => { row[1] = Cell::from(format_key_events(key_events)); }, + None => {} + } + return Row::new(row); }, + MappableCommand::Static { description: doc, name, .. } => { + let mut row: Vec = vec![Cell::from(*name), Cell::from(""), Cell::from(*doc)]; + match command_list.get(*name) { + Some(key_events) => { row[1] = Cell::from(format_key_events(key_events)); }, + None => {} + } + return Row::new(row) + } + } + + // TODO: Generalize into a Vec Display implemention? + fn format_key_events(key_events: &Vec) -> String { + let mut result_string: String = String::new(); + for key_event in key_events { + if !result_string.is_empty() { + result_string.push_str(", "); + } + result_string.push_str(key_event); + } + result_string } } } @@ -2513,7 +2531,6 @@ pub fn command_palette(cx: &mut Context) { compositor.push(Box::new(overlayed(picker))); }, )); - } fn last_picker(cx: &mut Context) { diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index bd91df5ae62a..deb5f7bc88a8 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -1,8 +1,7 @@ use std::ops::Deref; -use crate::job::Job; - use super::*; +use crate::job::*; use helix_view::editor::{Action, CloseError, ConfigEvent}; use ui::completers::{self, Completer}; diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 880b405f604e..6b1b56a80f1a 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -26,7 +26,7 @@ pub struct Keymap { pub type CommandList = HashMap>; impl Keymap { pub fn new(root_node: KeyTrie) -> Self { - Keymap { root_node } + Self { root_node } } /// Returns a key-value list of all commands associated to a given Keymap. diff --git a/helix-term/src/keymap/keymaps.rs b/helix-term/src/keymap/keymaps.rs index f824401b1833..773b6213003f 100644 --- a/helix-term/src/keymap/keymaps.rs +++ b/helix-term/src/keymap/keymaps.rs @@ -1,4 +1,5 @@ use super::*; +use crate::keymap::macros::*; use crate::commands::MappableCommand; use helix_view::{document::Mode, input::KeyEvent}; use std::{sync::Arc, collections::HashMap}; diff --git a/helix-term/src/keymap/keytrie.rs b/helix-term/src/keymap/keytrie.rs index 0706182db228..cb7798971246 100644 --- a/helix-term/src/keymap/keytrie.rs +++ b/helix-term/src/keymap/keytrie.rs @@ -1,6 +1,6 @@ use super::keytrienode::KeyTrieNode; use helix_view::{info::Info, input::KeyEvent}; -use std::{collections::HashMap, ops::{Deref, DerefMut}, cmp::Ordering}; +use std::{collections::HashMap, ops::{Deref, DerefMut}}; use serde::Deserialize; /// Edges of the trie are KeyEvents and the nodes are descrbibed by KeyTrieNode diff --git a/helix-term/src/keymap/macros.rs b/helix-term/src/keymap/macros.rs index 66fa78f7a34b..24fc7de212d8 100644 --- a/helix-term/src/keymap/macros.rs +++ b/helix-term/src/keymap/macros.rs @@ -87,13 +87,8 @@ macro_rules! keytrie { ::std::collections::HashMap::with_capacity(_cap); $( $( -<<<<<<< HEAD let _key = $key.parse::<::helix_view::input::KeyEvent>().unwrap(); - let _potential_duplicate = _map.insert(_key,keymap!(@trie $value)); -======= - let _key = $key.parse::().unwrap(); let _potential_duplicate = _map.insert(_key,keytrie!(@trie $value)); ->>>>>>> 8cb9a917 (Renamed the keymap! macro to keytrie!:) assert!(_potential_duplicate.is_none(), "Duplicate key found: {:?}", _potential_duplicate.unwrap()); )+ )* @@ -117,11 +112,7 @@ macro_rules! keytrie { } pub use alt; -<<<<<<< HEAD pub use ctrl; pub use key; -pub use keymap; pub use shift; -======= pub use keytrie; ->>>>>>> 8cb9a917 (Renamed the keymap! macro to keytrie!:) diff --git a/helix-term/src/keymap/tests.rs b/helix-term/src/keymap/tests.rs index 4570ac2e50eb..a4cded6fb645 100644 --- a/helix-term/src/keymap/tests.rs +++ b/helix-term/src/keymap/tests.rs @@ -3,7 +3,7 @@ mod tests { use helix_core::hashmap; use helix_view::{document::Mode, input::KeyEvent}; - use crate::keymap::*; + use crate::keymap::{*, macros::*}; use std::collections::HashMap; #[test] @@ -17,14 +17,13 @@ mod tests { #[test] fn check_duplicate_keys_in_default_keymap() { - // will panic on duplicate keys, assumes that `Keymaps` uses keymap! macro - Keymaps::default(); + // will panic on duplicate keys, assumes that `Keymap` uses keymap! macro + Keymap::default(); } #[test] fn aliased_modes_are_same_in_default_keymap() { - let keymaps = Keymaps::default().keymaps; - let root = keymaps.load().get(&Mode::Normal).unwrap().root_node.clone(); + let root = Keymap::default().get(&Mode::Normal).unwrap().root_node.clone(); assert_eq!( root.traverse(&[key!(' '), key!('w')]).unwrap(), root.traverse(&["C-w".parse::().unwrap()]).unwrap(), From 5c6c0ed93969c4889f4d8bddd33746dceabfa33d Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Thu, 29 Dec 2022 23:29:00 +0100 Subject: [PATCH 071/105] Removed keymap::Keymap: By the end of this refactroring it became clear that it was mostly acting as a useless wrapper for KeyTrie. --- helix-term/src/commands.rs | 21 ++++--- helix-term/src/config.rs | 22 ++++--- helix-term/src/keymap.rs | 85 ---------------------------- helix-term/src/keymap/default.rs | 10 ++-- helix-term/src/keymap/keymaps.rs | 48 ++++++++++++++-- helix-term/src/keymap/keytrienode.rs | 3 +- helix-term/src/keymap/macros.rs | 6 +- helix-term/src/keymap/tests.rs | 19 ++++++- 8 files changed, 91 insertions(+), 123 deletions(-) delete mode 100644 helix-term/src/keymap.rs diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index ce1a698d2b0f..e03fa5bb230b 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -7,6 +7,15 @@ pub use lsp::*; use tui::widgets::{Row, Cell}; pub use typed::*; +use crate::{ + commands::insert::*, + args, + keymap::keymaps::CommandList, + compositor::{self, Component, Compositor}, + job::{Callback, self, Jobs}, + ui::{self, overlay::overlayed, FilePicker, Picker, Popup, Prompt, PromptEvent, menu::{Cell, Row}}, +}; + use helix_vcs::Hunk; use helix_core::{ comment, coords_at_pos, encoding, find_first_non_whitespace_char, find_root, graphemes, @@ -36,14 +45,6 @@ use helix_view::{ view::View, Document, DocumentId, Editor, ViewId, apply_transaction, }; -use crate::{ - commands::insert::*, - args, - compositor::{self, Component, Compositor}, - job::{Callback, self, Jobs}, - keymap::CommandList, - ui::{self, overlay::overlayed, FilePicker, Picker, Popup, Prompt, PromptEvent}, -}; use std::{ collections::{HashMap, HashSet}, num::NonZeroUsize, @@ -2489,9 +2490,7 @@ impl ui::menu::Item for MappableCommand { pub fn command_palette(cx: &mut Context) { cx.callback = Some(Box::new( move |compositor: &mut Compositor, cx: &mut compositor::Context| { - let keymap_command_lists = compositor.find::().unwrap().keymaps.load_keymaps() - [&cx.editor.mode] - .command_list(); + let keymap_command_lists = compositor.find::().unwrap().keymaps.command_list(&cx.editor.mode); let mut commands: Vec = MappableCommand::STATIC_COMMAND_LIST.into(); commands.extend(typed::TYPABLE_COMMAND_LIST.iter().map(|cmd| { diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index fc724d818ff0..8f7649578c70 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -1,4 +1,4 @@ -use crate::keymap::{default, Keymap}; +use crate::keymap::{default, keytrie::KeyTrie}; use helix_view::document::Mode; use serde::Deserialize; use std::{collections::HashMap, fmt::Display, io::Error as IOError}; @@ -9,7 +9,7 @@ use toml::de::Error as TomlError; pub struct Config { pub theme: Option, #[serde(default = "default::default")] - pub keys: HashMap, + pub keys: HashMap, #[serde(default)] pub editor: helix_view::editor::Config, } @@ -53,7 +53,7 @@ impl Config { pub fn merge_in_default_keymap(mut self) -> Config { let mut delta = std::mem::replace(&mut self.keys, default::default()); for (mode, keys) in &mut self.keys { - keys.merge_keytrie(delta.remove(mode).unwrap_or_default().root_node) + keys.merge_keytrie(delta.remove(mode).unwrap_or_default()) } self } @@ -68,7 +68,6 @@ mod tests { default, keytrienode::KeyTrieNode, macros::*, - Keymap, }, }; use helix_core::hashmap; @@ -89,13 +88,13 @@ mod tests { toml::from_str::(sample_keymaps).unwrap(), Config { keys: hashmap! { - Mode::Insert => Keymap::new(keytrie!({ "Insert mode" + Mode::Insert => keytrie!({ "Insert mode" "y" => move_line_down, "S-C-a" => delete_selection, - })), - Mode::Normal => Keymap::new(keytrie!({ "Normal mode" + }), + Mode::Normal => keytrie!({ "Normal mode" "A-F12" => move_next_word_end, - })), + }), }, ..Default::default() } @@ -117,8 +116,7 @@ mod tests { fn user_config_merges_with_default() { let user_config = Config { keys: hashmap! { - Mode::Normal => Keymap::new( - keytrie!({ "Normal mode" + Mode::Normal => keytrie!({ "Normal mode" "i" => normal_mode, "无" => insert_mode, "z" => jump_backward, @@ -127,7 +125,7 @@ mod tests { "g" => delete_char_forward, }, }) - ) + }, ..Default::default() }; @@ -138,7 +136,7 @@ mod tests { "Merged user keymap with default should differ from user keymap." ); - let keymap_normal_root_key_trie = &merged_config.keys.get_mut(&Mode::Normal).unwrap().root_node; + let keymap_normal_root_key_trie = &merged_config.keys.get_mut(&Mode::Normal).unwrap(); assert_eq!( keymap_normal_root_key_trie.traverse(&[key!('i')]).unwrap(), KeyTrieNode::MappableCommand(MappableCommand::normal_mode), diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs deleted file mode 100644 index 6b1b56a80f1a..000000000000 --- a/helix-term/src/keymap.rs +++ /dev/null @@ -1,85 +0,0 @@ -pub mod keymaps; -pub mod default; -pub mod macros; -// NOTE: Only pub becuase of their use in macros -pub mod keytrienode; -pub mod keytrie; -mod tests; - -use crate::{ - commands::MappableCommand, - keymap::{ - keytrie::KeyTrie, - keytrienode::KeyTrieNode - } -}; -use std::{collections::HashMap, ops::{Deref, DerefMut}}; -use serde::Deserialize; - -#[derive(Debug, Clone, PartialEq, Deserialize)] -#[serde(transparent)] -/// KeyTrie starting point. -pub struct Keymap { - pub root_node: KeyTrie -} - -pub type CommandList = HashMap>; -impl Keymap { - pub fn new(root_node: KeyTrie) -> Self { - Self { root_node } - } - - /// Returns a key-value list of all commands associated to a given Keymap. - /// Keys are the node names (see KeyTrieNode documentation) - /// Values are lists of stringified KeyEvents that triger the command. - /// Each element in the KeyEvent list is prefixed with prefixed the ancestor KeyEvents. - /// For example: Stringified KeyEvent element for the 'goto_next_window' command could be "space>w>w". - /// Ancestor KeyEvents are in this case "space" and "w". - pub fn command_list(&self) -> CommandList { - let mut list = HashMap::new(); - _command_list(&mut list, &KeyTrieNode::KeyTrie(self.root_node.clone()), &mut String::new()); - return list; - - fn _command_list(list: &mut CommandList, node: &KeyTrieNode, prefix: &mut String) { - match node { - KeyTrieNode::KeyTrie(trie_node) => { - for (key_event, subtrie_node) in trie_node.deref() { - let mut temp_prefix: String = prefix.to_string(); - if &temp_prefix != "" { - temp_prefix.push_str(">"); - } - temp_prefix.push_str(&key_event.to_string()); - _command_list(list, subtrie_node, &mut temp_prefix); - } - }, - KeyTrieNode::MappableCommand(mappable_command) => { - if mappable_command.name() == "no_op" { return } - list.entry(mappable_command.name().to_string()).or_default().push(prefix.to_string()); - }, - KeyTrieNode::CommandSequence(_) => {} - }; - } - } -} - -impl Default for Keymap { - fn default() -> Self { - Self::new(KeyTrie::default()) - } -} - -/// Returns the Keymap root KeyTrie node. -impl Deref for Keymap { - type Target = KeyTrie; - - fn deref(&self) -> &Self::Target { - &self.root_node - } -} - -/// Returns the Keymap root KeyTrie node. -impl DerefMut for Keymap { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.root_node - } -} diff --git a/helix-term/src/keymap/default.rs b/helix-term/src/keymap/default.rs index 551cf1a79bed..5ee0706622e1 100644 --- a/helix-term/src/keymap/default.rs +++ b/helix-term/src/keymap/default.rs @@ -1,9 +1,9 @@ -use super::{macros::keytrie, Keymap}; +use super::{macros::keytrie, keytrie::KeyTrie}; use helix_view::document::Mode; use helix_core::hashmap; use std::collections::HashMap; -pub fn default() -> HashMap { +pub fn default() -> HashMap { let normal = keytrie!({ "Normal mode" "h" | "left" => move_char_left, "j" | "down" => move_line_down, @@ -370,8 +370,8 @@ pub fn default() -> HashMap { "end" => goto_line_end_newline, }); hashmap!( - Mode::Normal => Keymap::new(normal), - Mode::Select => Keymap::new(select), - Mode::Insert => Keymap::new(insert), + Mode::Normal => normal, + Mode::Select => select, + Mode::Insert => insert, ) } diff --git a/helix-term/src/keymap/keymaps.rs b/helix-term/src/keymap/keymaps.rs index 773b6213003f..f99cfe9bdebe 100644 --- a/helix-term/src/keymap/keymaps.rs +++ b/helix-term/src/keymap/keymaps.rs @@ -5,6 +5,8 @@ use helix_view::{document::Mode, input::KeyEvent}; use std::{sync::Arc, collections::HashMap}; use arc_swap::{access::{DynAccess, DynGuard}, ArcSwap}; +use std::ops::Deref; + #[derive(Debug, Clone, PartialEq)] pub enum KeymapResult { Pending(KeyTrie), @@ -16,14 +18,15 @@ pub enum KeymapResult { } pub struct Keymaps { - pub keymaps: Box>>, + pub keymaps: Box>>, /// Relative to a sticky node if Some. pending_keys: Vec, pub sticky_keytrie: Option, } +pub type CommandList = HashMap>; impl Keymaps { - pub fn new(keymaps: Box>>) -> Self { + pub fn new(keymaps: Box>>) -> Self { Self { keymaps, pending_keys: Vec::new(), @@ -31,7 +34,7 @@ impl Keymaps { } } - pub fn load_keymaps(&self) -> DynGuard> { + pub fn load_keymaps(&self) -> DynGuard> { self.keymaps.load() } @@ -63,7 +66,7 @@ impl Keymaps { // Check if sticky keytrie is to be used. let starting_keytrie = match self.sticky_keytrie { - None => &active_keymap.root_node, + None => &active_keymap, Some(ref active_sticky_keytrie) => active_sticky_keytrie, }; @@ -101,6 +104,43 @@ impl Keymaps { None => KeymapResult::Cancelled(self.pending_keys.drain(..).collect()), } } + + fn get_keytrie(&self, mode: &Mode) -> KeyTrie { + // HELP: Unsure how I should handle this Option + self.keymaps.load().get(mode).unwrap().clone() + } + + /// Returns a key-value list of all commands associated to a given Keymap. + /// Keys are the node names (see KeyTrieNode documentation) + /// Values are lists of stringified KeyEvents that triger the command. + /// Each element in the KeyEvent list is prefixed with prefixed the ancestor KeyEvents. + /// For example: Stringified KeyEvent element for the 'goto_next_window' command could be "space>w>w". + /// Ancestor KeyEvents are in this case "space" and "w". + pub fn command_list(&self, mode: &Mode) -> CommandList { + let mut list = HashMap::new(); + _command_list(&mut list, &KeyTrieNode::KeyTrie(self.get_keytrie(mode)), &mut String::new()); + return list; + + fn _command_list(list: &mut CommandList, node: &KeyTrieNode, prefix: &mut String) { + match node { + KeyTrieNode::KeyTrie(trie_node) => { + for (key_event, subtrie_node) in trie_node.deref() { + let mut temp_prefix: String = prefix.to_string(); + if &temp_prefix != "" { + temp_prefix.push_str(">"); + } + temp_prefix.push_str(&key_event.to_string()); + _command_list(list, subtrie_node, &mut temp_prefix); + } + }, + KeyTrieNode::MappableCommand(mappable_command) => { + if mappable_command.name() == "no_op" { return } + list.entry(mappable_command.name().to_string()).or_default().push(prefix.to_string()); + }, + KeyTrieNode::CommandSequence(_) => {} + }; + } + } } impl Default for Keymaps { diff --git a/helix-term/src/keymap/keytrienode.rs b/helix-term/src/keymap/keytrienode.rs index 41b5a7cd3343..433372377787 100644 --- a/helix-term/src/keymap/keytrienode.rs +++ b/helix-term/src/keymap/keytrienode.rs @@ -1,4 +1,5 @@ -use super::{MappableCommand, keytrie::KeyTrie}; +use super::keytrie::KeyTrie; +use crate::commands::MappableCommand; use helix_view::input::KeyEvent; use std::collections::HashMap; use serde::{Deserialize, de::Visitor}; diff --git a/helix-term/src/keymap/macros.rs b/helix-term/src/keymap/macros.rs index 24fc7de212d8..e17bad91d48d 100644 --- a/helix-term/src/keymap/macros.rs +++ b/helix-term/src/keymap/macros.rs @@ -62,11 +62,11 @@ macro_rules! alt { }; } -/// Macro for defining the root of a `Keymap` object. Example: +/// Macro for defining the root of a `KeyTrie` object. Example: /// /// ``` /// # use helix_core::hashmap; -/// # use helix_term::keymap::{Keymap, macros::keytrie}; +/// # use helix_term::keymap::{keytrie::KeyTrie, macros::keytrie}; /// let normal_mode = keytrie!({ "Normal mode" /// "i" => insert_mode, /// "g" => { "Goto" @@ -75,7 +75,7 @@ macro_rules! alt { /// }, /// "j" | "down" => move_line_down, /// }); -/// let keymap = Keymap::new(normal_mode); +/// let keymap = normal_mode; /// ``` #[macro_export] macro_rules! keytrie { diff --git a/helix-term/src/keymap/tests.rs b/helix-term/src/keymap/tests.rs index a4cded6fb645..6d566c91f7ad 100644 --- a/helix-term/src/keymap/tests.rs +++ b/helix-term/src/keymap/tests.rs @@ -3,8 +3,17 @@ mod tests { use helix_core::hashmap; use helix_view::{document::Mode, input::KeyEvent}; +<<<<<<< HEAD use crate::keymap::{*, macros::*}; use std::collections::HashMap; +======= + use crate::{ + keymap::macros::*, + keymap::keymaps::Keymaps, + }; + use std::{sync::Arc, collections::HashMap}; + use arc_swap::ArcSwap; +>>>>>>> adb17e18 (Removed keymap::Keymap:) #[test] #[should_panic] @@ -23,7 +32,12 @@ mod tests { #[test] fn aliased_modes_are_same_in_default_keymap() { +<<<<<<< HEAD let root = Keymap::default().get(&Mode::Normal).unwrap().root_node.clone(); +======= + let keymaps = Keymaps::default().keymaps; + let root = keymaps.load().get(&Mode::Normal).unwrap().clone(); +>>>>>>> adb17e18 (Removed keymap::Keymap:) assert_eq!( root.traverse(&[key!(' '), key!('w')]).unwrap(), root.traverse(&["C-w".parse::().unwrap()]).unwrap(), @@ -46,8 +60,9 @@ mod tests { }, "j" | "k" => move_line_down, }); - let keymap = Keymap::new(normal_mode); - let mut command_list = keymap.command_list(); + + let keymap = Keymaps::new(Box::new(ArcSwap::new(Arc::new(hashmap!(Mode::Normal => normal_mode))))); + let mut command_list = keymap.command_list(&Mode::Normal); // sort keybindings in order to have consistent tests // HashMaps can be compared but we can still get different ordering of bindings From ff6b53f55b73bd95bbb58869188e54a229627891 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Fri, 30 Dec 2022 10:59:05 +0100 Subject: [PATCH 072/105] Switched keymap::keymaps::Keymaps to keymap::Keymap Root keymap module was empty as old Keymap could be removed. And there was no point anymore in differentiating Keymaps and Keymap. --- helix-term/src/application.rs | 4 +- helix-term/src/commands.rs | 5 +- helix-term/src/keymap.rs | 161 +++++++++++++++++++++++++++++++++ helix-term/src/keymap/tests.rs | 26 ++---- helix-term/src/ui/editor.rs | 22 ++--- 5 files changed, 182 insertions(+), 36 deletions(-) create mode 100644 helix-term/src/keymap.rs diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index b614f5395c12..81fa20951122 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -18,8 +18,8 @@ use crate::{ commands::apply_workspace_edit, compositor::{Compositor, Event}, config::Config, + keymap::Keymap, job::Jobs, - keymap::keymaps::Keymaps, ui::{self, overlay::overlayed}, }; use std::{ @@ -177,7 +177,7 @@ impl Application { let keys = Box::new(Map::new(Arc::clone(&config), |config: &Config| { &config.keys })); - let editor_view = Box::new(ui::EditorView::new(Keymaps::new(keys))); + let editor_view = Box::new(ui::EditorView::new(Keymap::new(keys))); compositor.push(editor_view); if args.load_tutor { diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index e03fa5bb230b..72dd5bdc5b1a 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -4,13 +4,12 @@ pub(crate) mod typed; pub use dap::*; pub use lsp::*; -use tui::widgets::{Row, Cell}; pub use typed::*; use crate::{ commands::insert::*, args, - keymap::keymaps::CommandList, + keymap::CommandList, compositor::{self, Component, Compositor}, job::{Callback, self, Jobs}, ui::{self, overlay::overlayed, FilePicker, Picker, Popup, Prompt, PromptEvent, menu::{Cell, Row}}, @@ -2490,7 +2489,7 @@ impl ui::menu::Item for MappableCommand { pub fn command_palette(cx: &mut Context) { cx.callback = Some(Box::new( move |compositor: &mut Compositor, cx: &mut compositor::Context| { - let keymap_command_lists = compositor.find::().unwrap().keymaps.command_list(&cx.editor.mode); + let keymap_command_lists = compositor.find::().unwrap().keymap.command_list(&cx.editor.mode); let mut commands: Vec = MappableCommand::STATIC_COMMAND_LIST.into(); commands.extend(typed::TYPABLE_COMMAND_LIST.iter().map(|cmd| { diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs new file mode 100644 index 000000000000..780b7e6531aa --- /dev/null +++ b/helix-term/src/keymap.rs @@ -0,0 +1,161 @@ +pub mod default; +pub mod macros; +// NOTE: Only pub becuase of their use in macros +pub mod keytrienode; +pub mod keytrie; +mod tests; + +use self::{ + keytrienode::KeyTrieNode, + keytrie::KeyTrie, + macros::key, +}; + +use crate::commands::MappableCommand; +use helix_view::{document::Mode, input::KeyEvent}; +use std::{sync::Arc, collections::HashMap}; +use arc_swap::{access::{DynAccess, DynGuard}, ArcSwap}; + +use std::ops::Deref; + +#[derive(Debug, Clone, PartialEq)] +pub enum KeymapResult { + Pending(KeyTrie), + Matched(MappableCommand), + MatchedCommandSequence(Vec), + NotFound, + /// Contains pressed KeyEvents leading up to the cancellation. + Cancelled(Vec), +} + +pub struct Keymap { + pub keytries: Box>>, + /// Relative to a sticky node if Some. + pending_keys: Vec, + pub sticky_keytrie: Option, +} + +pub type CommandList = HashMap>; +impl Keymap { + pub fn new(keymaps: Box>>) -> Self { + Self { + keytries: keymaps, + pending_keys: Vec::new(), + sticky_keytrie: None, + } + } + + pub fn load_keymaps(&self) -> DynGuard> { + self.keytries.load() + } + + /// Returns list of keys waiting to be disambiguated in current mode. + pub fn pending(&self) -> &[KeyEvent] { + &self.pending_keys + } + + pub fn sticky_keytrie(&self) -> Option<&KeyTrie> { + self.sticky_keytrie.as_ref() + } + + /// Lookup `key` in the keymap to try and find a command to execute. + /// Escape key represents cancellation. + /// This means clearing pending keystrokes, or the sticky_keytrie if none were present. + pub fn get(&mut self, mode: Mode, key: KeyEvent) -> KeymapResult { + // TODO: remove the sticky part and look up manually + let keymaps = &*self.load_keymaps(); + let active_keymap = &keymaps[&mode]; + + if key == key!(Esc) { + if !self.pending_keys.is_empty() { + // NOTE: Esc is not included here + return KeymapResult::Cancelled(self.pending_keys.drain(..).collect()); + } + // TODO: Shouldn't we return here also? + self.sticky_keytrie = None; + } + + // Check if sticky keytrie is to be used. + let starting_keytrie = match self.sticky_keytrie { + None => &active_keymap, + Some(ref active_sticky_keytrie) => active_sticky_keytrie, + }; + + // TODO: why check either pending or regular key? + let first_key = self.pending_keys.get(0).unwrap_or(&key); + + let pending_keytrie: KeyTrie = match starting_keytrie.traverse(&[*first_key]) { + Some(KeyTrieNode::KeyTrie(sub_keytrie)) => sub_keytrie, + Some(KeyTrieNode::MappableCommand(cmd)) => { + return KeymapResult::Matched(cmd.clone()); + } + Some(KeyTrieNode::CommandSequence(cmds)) => { + return KeymapResult::MatchedCommandSequence(cmds.clone()); + } + None => return KeymapResult::NotFound, + }; + + self.pending_keys.push(key); + match pending_keytrie.traverse(&self.pending_keys[1..]) { + Some(KeyTrieNode::KeyTrie(map)) => { + if map.is_sticky { + self.pending_keys.clear(); + self.sticky_keytrie = Some(map.clone()); + } + KeymapResult::Pending(map.clone()) + } + Some(KeyTrieNode::MappableCommand(cmd)) => { + self.pending_keys.clear(); + KeymapResult::Matched(cmd.clone()) + } + Some(KeyTrieNode::CommandSequence(cmds)) => { + self.pending_keys.clear(); + KeymapResult::MatchedCommandSequence(cmds.clone()) + } + None => KeymapResult::Cancelled(self.pending_keys.drain(..).collect()), + } + } + + fn get_keytrie(&self, mode: &Mode) -> KeyTrie { + // HELP: Unsure how I should handle this Option + self.keytries.load().get(mode).unwrap().clone() + } + + /// Returns a key-value list of all commands associated to a given Keymap. + /// Keys are the node names (see KeyTrieNode documentation) + /// Values are lists of stringified KeyEvents that triger the command. + /// Each element in the KeyEvent list is prefixed with prefixed the ancestor KeyEvents. + /// For example: Stringified KeyEvent element for the 'goto_next_window' command could be "space>w>w". + /// Ancestor KeyEvents are in this case "space" and "w". + pub fn command_list(&self, mode: &Mode) -> CommandList { + let mut list = HashMap::new(); + _command_list(&mut list, &KeyTrieNode::KeyTrie(self.get_keytrie(mode)), &mut String::new()); + return list; + + fn _command_list(list: &mut CommandList, node: &KeyTrieNode, prefix: &mut String) { + match node { + KeyTrieNode::KeyTrie(trie_node) => { + for (key_event, subtrie_node) in trie_node.deref() { + let mut temp_prefix: String = prefix.to_string(); + if &temp_prefix != "" { + temp_prefix.push_str(">"); + } + temp_prefix.push_str(&key_event.to_string()); + _command_list(list, subtrie_node, &mut temp_prefix); + } + }, + KeyTrieNode::MappableCommand(mappable_command) => { + if mappable_command.name() == "no_op" { return } + list.entry(mappable_command.name().to_string()).or_default().push(prefix.to_string()); + }, + KeyTrieNode::CommandSequence(_) => {} + }; + } + } +} + +impl Default for Keymap { + fn default() -> Self { + Self::new(Box::new(ArcSwap::new(Arc::new(default::default())))) + } +} diff --git a/helix-term/src/keymap/tests.rs b/helix-term/src/keymap/tests.rs index 6d566c91f7ad..07145bdf310a 100644 --- a/helix-term/src/keymap/tests.rs +++ b/helix-term/src/keymap/tests.rs @@ -3,17 +3,8 @@ mod tests { use helix_core::hashmap; use helix_view::{document::Mode, input::KeyEvent}; -<<<<<<< HEAD use crate::keymap::{*, macros::*}; use std::collections::HashMap; -======= - use crate::{ - keymap::macros::*, - keymap::keymaps::Keymaps, - }; - use std::{sync::Arc, collections::HashMap}; - use arc_swap::ArcSwap; ->>>>>>> adb17e18 (Removed keymap::Keymap:) #[test] #[should_panic] @@ -32,20 +23,15 @@ mod tests { #[test] fn aliased_modes_are_same_in_default_keymap() { -<<<<<<< HEAD - let root = Keymap::default().get(&Mode::Normal).unwrap().root_node.clone(); -======= - let keymaps = Keymaps::default().keymaps; - let root = keymaps.load().get(&Mode::Normal).unwrap().clone(); ->>>>>>> adb17e18 (Removed keymap::Keymap:) + let normal_mode_keytrie_root = Keymap::default().get_keytrie(&Mode::Normal); assert_eq!( - root.traverse(&[key!(' '), key!('w')]).unwrap(), - root.traverse(&["C-w".parse::().unwrap()]).unwrap(), + normal_mode_keytrie_root.traverse(&[key!(' '), key!('w')]).unwrap(), + normal_mode_keytrie_root.traverse(&["C-w".parse::().unwrap()]).unwrap(), "Mismatch for window mode on `Space-w` and `Ctrl-w`." ); assert_eq!( - root.traverse(&[key!('z')]).unwrap(), - root.traverse(&[key!('Z')]).unwrap(), + normal_mode_keytrie_root.traverse(&[key!('z')]).unwrap(), + normal_mode_keytrie_root.traverse(&[key!('Z')]).unwrap(), "Mismatch for view mode on `z` and `Z`." ); } @@ -61,7 +47,7 @@ mod tests { "j" | "k" => move_line_down, }); - let keymap = Keymaps::new(Box::new(ArcSwap::new(Arc::new(hashmap!(Mode::Normal => normal_mode))))); + let keymap = Keymap::new(Box::new(ArcSwap::new(Arc::new(hashmap!(Mode::Normal => normal_mode))))); let mut command_list = keymap.command_list(&Mode::Normal); // sort keybindings in order to have consistent tests diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 98da59c95097..a4f3f182d9c2 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -3,7 +3,7 @@ use crate::{ compositor::{Component, Context, Event, EventResult}, job::{self, Callback}, key, - keymap::keymaps::{KeymapResult, Keymaps}, + keymap::{KeymapResult, Keymap}, ui::{Completion, ProgressSpinners}, }; @@ -32,7 +32,7 @@ use super::lsp::SignatureHelp; use super::statusline; pub struct EditorView { - pub keymaps: Keymaps, + pub keymap: Keymap, on_next_key: Option>, pseudo_pending: Vec, last_insert: (commands::MappableCommand, Vec), @@ -48,9 +48,9 @@ pub enum InsertEvent { } impl EditorView { - pub fn new(keymaps: Keymaps) -> Self { + pub fn new(keymap: Keymap) -> Self { Self { - keymaps, + keymap, on_next_key: None, pseudo_pending: Vec::new(), last_insert: (commands::MappableCommand::normal_mode, Vec::new()), @@ -928,9 +928,9 @@ impl EditorView { event: KeyEvent, ) -> Option { let mut last_mode = mode; - self.pseudo_pending.extend(self.keymaps.pending()); - let key_result = self.keymaps.get(mode, event); - cxt.editor.autoinfo = self.keymaps.sticky_keytrie().map(|node| node.infobox()); + self.pseudo_pending.extend(self.keymap.pending()); + let key_result = self.keymap.get(mode, event); + cxt.editor.autoinfo = self.keymap.sticky_keytrie().map(|node| node.infobox()); let mut execute_command = |command: &commands::MappableCommand| { command.execute(cxt); @@ -994,7 +994,7 @@ impl EditorView { Some(ch) => commands::insert::insert_char(cx, ch), None => { if let KeymapResult::Matched(command) = - self.keymaps.get(Mode::Insert, ev) + self.keymap.get(Mode::Insert, ev) { command.execute(cx); } @@ -1016,7 +1016,7 @@ impl EditorView { std::num::NonZeroUsize::new(cxt.editor.count.map_or(i, |c| c.get() * 10 + i)); } // special handling for repeat operator - (key!('.'), _) if self.keymaps.pending().is_empty() => { + (key!('.'), _) if self.keymap.pending().is_empty() => { for _ in 0..cxt.editor.count.map_or(1, NonZeroUsize::into) { // first execute whatever put us into insert mode self.last_insert.0.execute(cxt); @@ -1063,7 +1063,7 @@ impl EditorView { cxt.register = cxt.editor.selected_register.take(); self.handle_keymap_event(mode, cxt, event); - if self.keymaps.pending().is_empty() { + if self.keymap.pending().is_empty() { cxt.editor.count = None } } @@ -1521,7 +1521,7 @@ impl Component for EditorView { if let Some(count) = cx.editor.count { disp.push_str(&count.to_string()) } - for key in self.keymaps.pending() { + for key in self.keymap.pending() { disp.push_str(&key.key_sequence_format()); } for key in &self.pseudo_pending { From aed2e90c8783363d94b95e901f24a6f927511264 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Fri, 20 Jan 2023 17:34:59 +0100 Subject: [PATCH 073/105] Re-implemented pre-defined orders in keytrie * Fixed aliased_modes_are_same_in_default_keymap to check for order too. It failed to detect a bug in which the swap_view_* were ordered differently under space-w and c-w. --- helix-term/src/config.rs | 11 +- helix-term/src/keymap.rs | 9 +- helix-term/src/keymap/default.rs | 4 +- helix-term/src/keymap/keytrie.rs | 162 ++++++++++++++++----------- helix-term/src/keymap/keytrienode.rs | 27 ++++- helix-term/src/keymap/macros.rs | 38 ++++--- helix-term/src/keymap/tests.rs | 4 +- 7 files changed, 150 insertions(+), 105 deletions(-) diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index 8f7649578c70..3ed2fa52323b 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -103,11 +103,8 @@ mod tests { #[test] fn keys_resolve_to_correct_defaults() { - // From serde default - let default_keys = toml::from_str::("").unwrap().keys; - assert_eq!(default_keys, default::default()); - - // From the Default trait + let serde_default = toml::from_str::("").unwrap().keys; + assert_eq!(serde_default, default::default()); let default_keys = Config::default().keys; assert_eq!(default_keys, default::default()); } @@ -173,7 +170,7 @@ mod tests { ); // Huh? - assert!(merged_config.keys.get(&Mode::Normal).unwrap().len() > 1); - assert!(merged_config.keys.get(&Mode::Insert).unwrap().len() > 0); + assert!(merged_config.keys.get(&Mode::Normal).unwrap().get_children().len() > 1); + assert!(merged_config.keys.get(&Mode::Insert).unwrap().get_children().len() > 0); } } diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 780b7e6531aa..9bd04222e39f 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -16,8 +16,6 @@ use helix_view::{document::Mode, input::KeyEvent}; use std::{sync::Arc, collections::HashMap}; use arc_swap::{access::{DynAccess, DynGuard}, ArcSwap}; -use std::ops::Deref; - #[derive(Debug, Clone, PartialEq)] pub enum KeymapResult { Pending(KeyTrie), @@ -135,13 +133,14 @@ impl Keymap { fn _command_list(list: &mut CommandList, node: &KeyTrieNode, prefix: &mut String) { match node { KeyTrieNode::KeyTrie(trie_node) => { - for (key_event, subtrie_node) in trie_node.deref() { + for (key_event, index) in trie_node.get_child_order() { let mut temp_prefix: String = prefix.to_string(); if &temp_prefix != "" { - temp_prefix.push_str(">"); + temp_prefix.push_str("→"); } temp_prefix.push_str(&key_event.to_string()); - _command_list(list, subtrie_node, &mut temp_prefix); + _command_list(list, &trie_node.get_children()[*index], &mut temp_prefix); + } }, KeyTrieNode::MappableCommand(mappable_command) => { diff --git a/helix-term/src/keymap/default.rs b/helix-term/src/keymap/default.rs index 5ee0706622e1..3fffaa08c517 100644 --- a/helix-term/src/keymap/default.rs +++ b/helix-term/src/keymap/default.rs @@ -187,10 +187,10 @@ pub fn default() -> HashMap { "C-j" | "j" | "down" => jump_view_down, "C-k" | "k" | "up" => jump_view_up, "C-l" | "l" | "right" => jump_view_right, - "L" => swap_view_right, - "K" => swap_view_up, "H" => swap_view_left, "J" => swap_view_down, + "K" => swap_view_up, + "L" => swap_view_right, "n" => { "New split scratch buffer" "C-s" | "s" => hsplit_new, "C-v" | "v" => vsplit_new, diff --git a/helix-term/src/keymap/keytrie.rs b/helix-term/src/keymap/keytrie.rs index cb7798971246..8d97c7a8ca5b 100644 --- a/helix-term/src/keymap/keytrie.rs +++ b/helix-term/src/keymap/keytrie.rs @@ -1,25 +1,36 @@ use super::keytrienode::KeyTrieNode; use helix_view::{info::Info, input::KeyEvent}; -use std::{collections::HashMap, ops::{Deref, DerefMut}}; +use std::{collections::HashMap, ops::{Deref, DerefMut}, cmp::Ordering}; use serde::Deserialize; /// Edges of the trie are KeyEvents and the nodes are descrbibed by KeyTrieNode #[derive(Debug, Clone)] pub struct KeyTrie { documentation: String, - children: HashMap, + /// Used for pre-defined order in infoboxes, values represent the index of the key tries children. + child_order: HashMap, + children: Vec, pub is_sticky: bool, } impl KeyTrie { - pub fn new(documentation: &str, children: HashMap) -> Self { + pub fn new(documentation: &str, child_order: HashMap, children: Vec) -> Self { Self { documentation: documentation.to_string(), + child_order, children, is_sticky: false, } } + pub fn get_child_order(&self) -> &HashMap { + &self.child_order + } + + pub fn get_children(&self) -> &Vec { + &self.children + } + // None symbolizes NotFound pub fn traverse(&self, key_events: &[KeyEvent]) -> Option { return _traverse(self, key_events, 0); @@ -28,13 +39,13 @@ impl KeyTrie { if depth == key_events.len() { return Some(KeyTrieNode::KeyTrie(keytrie.clone())); } - else if let Some(found_child) = keytrie.get(&key_events[depth]) { - match found_child { + else if let Some(found_index) = keytrie.child_order.get(&key_events[depth]) { + match &keytrie.children[*found_index] { KeyTrieNode::KeyTrie(sub_keytrie) => { depth += 1; return _traverse(sub_keytrie, key_events, depth) }, - _ => return Some(found_child.clone()) + _found_child => return Some(_found_child.clone()) } } return None; @@ -42,84 +53,81 @@ impl KeyTrie { } pub fn merge_keytrie(&mut self, mut other_keytrie: Self) { - for (other_key_event, other_child_node) in std::mem::take(&mut other_keytrie.children) { - match other_child_node { - KeyTrieNode::KeyTrie(other_child_key_trie) => { - if let Some(KeyTrieNode::KeyTrie(self_clashing_child_key_trie)) = self.children.get_mut(&other_key_event) { - self_clashing_child_key_trie.merge_keytrie(other_child_key_trie); + for (other_key_event, other_index) in other_keytrie.get_child_order() { + let other_child_keytrie_node = &other_keytrie.get_children()[*other_index]; + match other_child_keytrie_node { + KeyTrieNode::KeyTrie(ref other_child_keytrie) => { + if let Some(self_index) = self.child_order.get(&other_key_event) { + if let KeyTrieNode::KeyTrie(ref mut self_clashing_child_key_trie) = self.children[*self_index] { + self_clashing_child_key_trie.merge_keytrie(other_child_keytrie.clone()); + } } else { - self.children.insert(other_key_event, KeyTrieNode::KeyTrie(other_child_key_trie)); + self.child_order.insert(*other_key_event, self.children.len()); + self.children.push(KeyTrieNode::KeyTrie(other_child_keytrie.clone())); } } KeyTrieNode::MappableCommand(_) | KeyTrieNode::CommandSequence(_) => { - self.children.insert(other_key_event, other_child_node); + if let Some(existing_index) = self.child_order.get(other_key_event) { + self.children[*existing_index] = other_child_keytrie_node.clone(); + } + else { + self.child_order.insert(*other_key_event, self.children.len()); + self.children.push(other_child_keytrie_node.clone()); + } } } } } - + /// Open an Info box for a given KeyTrie /// Shows the children as possible KeyEvents and thier associated description. pub fn infobox(&self) -> Info { - let mut body: Vec<(Vec, &str)> = Vec::with_capacity(self.len()); - for (&key_event, key_trie) in self.iter() { + let mut body: InfoBoxBody = Vec::with_capacity(self.children.len()); + let mut key_event_order = Vec::with_capacity(self.children.len()); + // child_order and children is of same length + unsafe { key_event_order.set_len(self.children.len()); } + for (key_event, index) in &self.child_order { + key_event_order[*index] = key_event.clone(); + } + + for (index, key_trie) in self.children.iter().enumerate() { let documentation: &str = match key_trie { - KeyTrieNode::MappableCommand(command) => { + KeyTrieNode::MappableCommand(ref command) => { if command.name() == "no_op" { continue; } command.description() }, - KeyTrieNode::KeyTrie(key_trie) => &key_trie.documentation, + KeyTrieNode::KeyTrie(ref key_trie) => &key_trie.documentation, // FIX: default to a join of all command names // NOTE: Giving same documentation for all sequences will place all sequence keyvents together. // Regardless if the command sequence is different. KeyTrieNode::CommandSequence(_) => "[Multiple commands]", }; + let key_event = key_event_order[index]; match body.iter().position(|(_, existing_documentation)| &documentation == existing_documentation) { Some(position) => body[position].0.push(key_event.to_string()), None => { - let mut temp_vec: Vec = Vec::new(); - temp_vec.push(key_event.to_string()); - body.push((temp_vec, documentation)) + body.push((vec![key_event.to_string()], documentation)) }, } } // Shortest keyevent (as string) appears first, unless is a "C-" KeyEvent // Those events will always be placed after the one letter KeyEvent - let mut sorted_body = body - .iter() - .map(|(key_events, description)| { - let mut temp_key_events = key_events.clone(); - temp_key_events.sort_unstable_by(|a, b| a.len().cmp(&b.len())); - (temp_key_events, *description) - }) - .collect::, &str)>>(); - sorted_body.sort_unstable_by(|a, b| a.0[0].to_lowercase().cmp(&b.0[0].to_lowercase())); - // Consistently place lowercase before uppercase of the same letter. - if sorted_body.len() > 1 { - let mut x_index = 0; - let mut y_index = 1; - - while y_index < sorted_body.len() { - let x = &sorted_body[x_index].0[0]; - let y = &sorted_body[y_index].0[0]; - if x.to_lowercase() == y.to_lowercase() { - // Uppercase regarded as lower value. - if x < y { - let temp_holder = sorted_body[x_index].clone(); - sorted_body[x_index] = sorted_body[y_index].clone(); - sorted_body[y_index] = temp_holder; - } + for (key_events, _) in body.iter_mut() { + key_events.sort_unstable_by(|a, b| { + if a.len() == 1 { return Ordering::Less } + if b.len() > a.len() && b.starts_with("C-") { + return Ordering::Greater } - x_index = y_index; - y_index += 1; - } + a.len().cmp(&b.len()) + }); } - let stringified_key_events_body: Vec<(String, &str)> = sorted_body + // TODO: conditional sort added here by calling infobox_sort(body) + let stringified_key_events_body: Vec<(String, &str)> = body .iter() .map(|(key_events, description)| { let key_events_string: String = key_events.iter().fold(String::new(), |mut acc, key_event| { @@ -137,7 +145,7 @@ impl KeyTrie { impl Default for KeyTrie { fn default() -> Self { - Self::new("", HashMap::new()) + Self::new("", HashMap::new(), Vec::new()) } } @@ -147,30 +155,50 @@ impl PartialEq for KeyTrie { } } -/// Returns the children of the KeyTrie -impl Deref for KeyTrie { - type Target = HashMap; - - fn deref(&self) -> &Self::Target { - &self.children - } -} - -/// Returns the children of the KeyTrie -impl DerefMut for KeyTrie { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.children - } -} - impl<'de> Deserialize<'de> for KeyTrie { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { + // NOTE: no assumption of pre-defined order in config + let child_collection = HashMap::::deserialize(deserializer)?; + let mut child_order = HashMap::::new(); + let mut children = Vec::new(); + for (key_event, keytrie_node) in child_collection { + child_order.insert(key_event, children.len()); + children.push(keytrie_node); + } + Ok(Self { - children: HashMap::::deserialize(deserializer)?, + child_order, + children, ..Default::default() }) } } + +type InfoBoxBody<'a> = Vec<(Vec, &'a str)>; +fn infobox_sort(mut body: InfoBoxBody) -> InfoBoxBody { + body.sort_unstable_by(|a, b| a.0[0].to_lowercase().cmp(&b.0[0].to_lowercase())); + // Consistently place lowercase before uppercase of the same letter. + if body.len() > 1 { + let mut x_index = 0; + let mut y_index = 1; + + while y_index < body.len() { + let x = &body[x_index].0[0]; + let y = &body[y_index].0[0]; + if x.to_lowercase() == y.to_lowercase() { + // Uppercase regarded as lower value. + if x < y { + let temp_holder = body[x_index].clone(); + body[x_index] = body[y_index].clone(); + body[y_index] = temp_holder; + } + } + x_index = y_index; + y_index += 1; + } + } + body +} diff --git a/helix-term/src/keymap/keytrienode.rs b/helix-term/src/keymap/keytrienode.rs index 433372377787..66a642ac902d 100644 --- a/helix-term/src/keymap/keytrienode.rs +++ b/helix-term/src/keymap/keytrienode.rs @@ -8,7 +8,7 @@ use serde::{Deserialize, de::Visitor}; /// For the MappableCommand and CommandSequence variants, the property is self explanatory. /// For KeyTrie, the documentation is used for respective infobox titles, /// or infobox KeyEvent descriptions that in themselves trigger the opening of another infobox. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub enum KeyTrieNode { MappableCommand(MappableCommand), CommandSequence(Vec), @@ -24,6 +24,23 @@ impl<'de> Deserialize<'de> for KeyTrieNode { } } +impl PartialEq for KeyTrieNode { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (KeyTrieNode::MappableCommand(_self), KeyTrieNode::MappableCommand(_other)) => { + _self == _other + }, + (KeyTrieNode::CommandSequence(_self), KeyTrieNode::CommandSequence(_other)) => { + _self == _other + }, + (KeyTrieNode::KeyTrie(_self), KeyTrieNode::KeyTrie(_other)) => { + _self.get_children() == _other.get_children() + }, + _ => false + } + } +} + struct KeyTrieNodeVisitor; impl<'de> Visitor<'de> for KeyTrieNodeVisitor { @@ -62,10 +79,12 @@ impl<'de> Visitor<'de> for KeyTrieNodeVisitor { where M: serde::de::MapAccess<'de>, { - let mut sub_key_trie = HashMap::new(); + let mut children = Vec::new(); + let mut child_order = HashMap::new(); while let Some((key_event, key_trie_node)) = map.next_entry::()? { - sub_key_trie.insert(key_event, key_trie_node); + child_order.insert(key_event, children.len()); + children.push(key_trie_node); } - Ok(KeyTrieNode::KeyTrie(KeyTrie::new("", sub_key_trie))) + Ok(KeyTrieNode::KeyTrie(KeyTrie::new("", child_order, children))) } } \ No newline at end of file diff --git a/helix-term/src/keymap/macros.rs b/helix-term/src/keymap/macros.rs index e17bad91d48d..b834c43e56aa 100644 --- a/helix-term/src/keymap/macros.rs +++ b/helix-term/src/keymap/macros.rs @@ -1,8 +1,8 @@ #[macro_export] macro_rules! key { - ($key:ident) => { + ($key_event:ident) => { ::helix_view::input::KeyEvent { - code: ::helix_view::keyboard::KeyCode::$key, + code: ::helix_view::keyboard::KeyCode::$key_event, modifiers: ::helix_view::keyboard::KeyModifiers::NONE, } }; @@ -16,9 +16,9 @@ macro_rules! key { #[macro_export] macro_rules! shift { - ($key:ident) => { + ($key_event:ident) => { ::helix_view::input::KeyEvent { - code: ::helix_view::keyboard::KeyCode::$key, + code: ::helix_view::keyboard::KeyCode::$key_event, modifiers: ::helix_view::keyboard::KeyModifiers::SHIFT, } }; @@ -32,9 +32,9 @@ macro_rules! shift { #[macro_export] macro_rules! ctrl { - ($key:ident) => { + ($key_event:ident) => { ::helix_view::input::KeyEvent { - code: ::helix_view::keyboard::KeyCode::$key, + code: ::helix_view::keyboard::KeyCode::$key_event, modifiers: ::helix_view::keyboard::KeyModifiers::CONTROL, } }; @@ -48,9 +48,9 @@ macro_rules! ctrl { #[macro_export] macro_rules! alt { - ($key:ident) => { + ($key_event:ident) => { ::helix_view::input::KeyEvent { - code: ::helix_view::keyboard::KeyCode::$key, + code: ::helix_view::keyboard::KeyCode::$key_event, modifiers: ::helix_view::keyboard::KeyModifiers::ALT, } }; @@ -79,27 +79,29 @@ macro_rules! alt { /// ``` #[macro_export] macro_rules! keytrie { - ({ $label:literal $(sticky=$sticky:literal)? $($($key:literal)|+ => $value:tt,)+ }) => { - // modified from the hashmap! macro + // Sub key_trie + ({ $label:literal $(sticky=$sticky:literal)? $($($key_event:literal)|+ => $value:tt,)+ }) => { { - let _cap = hashmap!(@count $($($key),+),*); - let mut _map: ::std::collections::HashMap<::helix_view::input::KeyEvent, $crate::keymap::keytrienode::KeyTrieNode> = - ::std::collections::HashMap::with_capacity(_cap); + let _cap = hashmap!(@count $($($key_event),+),*); + let mut _children: Vec<$crate::keymap::keytrienode::KeyTrieNode> = ::std::vec::Vec::new(); + let mut _child_order: ::std::collections::HashMap<::helix_view::input::KeyEvent, usize> = ::std::collections::HashMap::with_capacity(_cap); $( $( - let _key = $key.parse::<::helix_view::input::KeyEvent>().unwrap(); - let _potential_duplicate = _map.insert(_key,keytrie!(@trie $value)); + let _key_event = $key_event.parse::<::helix_view::input::KeyEvent>().unwrap(); + let _potential_duplicate = _child_order.insert(_key_event, _children.len()); assert!(_potential_duplicate.is_none(), "Duplicate key found: {:?}", _potential_duplicate.unwrap()); + _children.push(keytrie!(@trie $value)); )+ )* - let mut _node = $crate::keymap::keytrie::KeyTrie::new($label, _map); + + let mut _node = $crate::keymap::keytrie::KeyTrie::new($label, _child_order, _children); $( _node.is_sticky = $sticky; )? _node } }; - (@trie {$label:literal $(sticky=$sticky:literal)? $($($key:literal)|+ => $value:tt,)+ }) => { - $crate::keymap::keytrienode::KeyTrieNode::KeyTrie(keytrie!({ $label $(sticky=$sticky)? $($($key)|+ => $value,)+ })) + (@trie {$label:literal $(sticky=$sticky:literal)? $($($key_event:literal)|+ => $value:tt,)+ }) => { + $crate::keymap::keytrienode::KeyTrieNode::KeyTrie(keytrie!({ $label $(sticky=$sticky)? $($($key_event)|+ => $value,)+ })) }; (@trie $cmd:ident) => { diff --git a/helix-term/src/keymap/tests.rs b/helix-term/src/keymap/tests.rs index 07145bdf310a..4ab431c6e31d 100644 --- a/helix-term/src/keymap/tests.rs +++ b/helix-term/src/keymap/tests.rs @@ -66,11 +66,11 @@ mod tests { ), ( "goto_file_start".to_string(), - vec![format!("{}>{}", key!('g'), key!('g'))] + vec![format!("{}→{}", key!('g'), key!('g'))] ), ( "goto_file_end".to_string(), - vec![format!("{}>{}", key!('g'), key!('e'))] + vec![format!("{}→{}", key!('g'), key!('e'))] ), ( "move_line_down".to_string(), From 5edce2e269c80e8931d09a8a74b755d405bacf40 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Sat, 21 Jan 2023 16:20:05 +0100 Subject: [PATCH 074/105] Add sort_infobox editor config option --- book/src/configuration.md | 1 + helix-term/src/keymap/keytrie.rs | 7 ++++--- helix-term/src/ui/editor.rs | 5 +++-- helix-view/src/editor.rs | 3 +++ 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/book/src/configuration.md b/book/src/configuration.md index ab229f772a1c..9e9c07451c31 100644 --- a/book/src/configuration.md +++ b/book/src/configuration.md @@ -53,6 +53,7 @@ on unix operating systems. | `idle-timeout` | Time in milliseconds since last keypress before idle timers trigger. Used for autocompletion, set to 0 for instant. | `400` | | `completion-trigger-len` | The min-length of word under cursor to trigger autocompletion | `2` | | `auto-info` | Whether to display infoboxes | `true` | +| `sorted-infobox` | Sort infoboxes by key event category rather than by predefined command categories | `false` | | `true-color` | Set to `true` to override automatic detection of terminal truecolor support in the event of a false negative. | `false` | | `rulers` | List of column positions at which to display the rulers. Can be overridden by language specific `rulers` in `languages.toml` file. | `[]` | | `bufferline` | Renders a line at the top of the editor displaying open buffers. Can be `always`, `never` or `multiple` (only shown if more than one buffer is in use) | `never` | diff --git a/helix-term/src/keymap/keytrie.rs b/helix-term/src/keymap/keytrie.rs index 8d97c7a8ca5b..d2b9734f1cf2 100644 --- a/helix-term/src/keymap/keytrie.rs +++ b/helix-term/src/keymap/keytrie.rs @@ -82,7 +82,7 @@ impl KeyTrie { /// Open an Info box for a given KeyTrie /// Shows the children as possible KeyEvents and thier associated description. - pub fn infobox(&self) -> Info { + pub fn infobox(&self, alphabetical_sort: bool) -> Info { let mut body: InfoBoxBody = Vec::with_capacity(self.children.len()); let mut key_event_order = Vec::with_capacity(self.children.len()); // child_order and children is of same length @@ -126,7 +126,8 @@ impl KeyTrie { }); } - // TODO: conditional sort added here by calling infobox_sort(body) + if alphabetical_sort { body = alphabetially_sort_infobox(body); } + let stringified_key_events_body: Vec<(String, &str)> = body .iter() .map(|(key_events, description)| { @@ -178,7 +179,7 @@ impl<'de> Deserialize<'de> for KeyTrie { } type InfoBoxBody<'a> = Vec<(Vec, &'a str)>; -fn infobox_sort(mut body: InfoBoxBody) -> InfoBoxBody { +fn alphabetially_sort_infobox(mut body: InfoBoxBody) -> InfoBoxBody { body.sort_unstable_by(|a, b| a.0[0].to_lowercase().cmp(&b.0[0].to_lowercase())); // Consistently place lowercase before uppercase of the same letter. if body.len() > 1 { diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index a4f3f182d9c2..94ead3f0f0e6 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -930,7 +930,8 @@ impl EditorView { let mut last_mode = mode; self.pseudo_pending.extend(self.keymap.pending()); let key_result = self.keymap.get(mode, event); - cxt.editor.autoinfo = self.keymap.sticky_keytrie().map(|node| node.infobox()); + let sort_infobox = cxt.editor.config.load().sorted_infobox; + cxt.editor.autoinfo = self.keymap.sticky_keytrie().map(|node| node.infobox(sort_infobox)); let mut execute_command = |command: &commands::MappableCommand| { command.execute(cxt); @@ -969,7 +970,7 @@ impl EditorView { KeymapResult::Matched(command) => { execute_command(command); } - KeymapResult::Pending(node) => cxt.editor.autoinfo = Some(node.infobox()), + KeymapResult::Pending(node) => cxt.editor.autoinfo = Some(node.infobox(sort_infobox)), KeymapResult::MatchedCommandSequence(commands) => { for command in commands { execute_command(command); diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index eef4a3f99e91..3e487d9b6189 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -246,6 +246,8 @@ pub struct Config { pub completion_trigger_len: u8, /// Whether to display infoboxes. Defaults to true. pub auto_info: bool, + /// Sort infoboxes alphabetically rather than by predefined categories. Defaults to `false`. + pub sorted_infobox: bool, pub file_picker: FilePickerConfig, /// Configuration of the statusline elements pub statusline: StatusLineConfig, @@ -717,6 +719,7 @@ impl Default for Config { bufferline: BufferLine::default(), indent_guides: IndentGuidesConfig::default(), color_modes: false, + sorted_infobox: false, } } } From 0b08e38a6021cce68b08286b4b0a345b4c55aaa5 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Sun, 22 Jan 2023 16:45:50 +0100 Subject: [PATCH 075/105] Refine sorting behavior Sorts by modifier, then by KeyCode category, then by each KeyEvent. Single character alphas are placed before the rest (ex. "space") in KeyCode::Char. --- helix-term/Cargo.toml | 1 - helix-term/src/keymap/keytrie.rs | 126 +++++++++++++++++++++++++------ helix-view/src/keyboard.rs | 59 +++++---------- 3 files changed, 120 insertions(+), 66 deletions(-) diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml index d4eebefb19e2..4efd8911cb0b 100644 --- a/helix-term/Cargo.toml +++ b/helix-term/Cargo.toml @@ -36,7 +36,6 @@ helix-loader = { version = "0.6", path = "../helix-loader" } anyhow = "1" once_cell = "1.17" - which = "4.4" tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] } diff --git a/helix-term/src/keymap/keytrie.rs b/helix-term/src/keymap/keytrie.rs index d2b9734f1cf2..7fe5df9a8610 100644 --- a/helix-term/src/keymap/keytrie.rs +++ b/helix-term/src/keymap/keytrie.rs @@ -80,9 +80,9 @@ impl KeyTrie { } } - /// Open an Info box for a given KeyTrie + /// Open an info box for a given KeyTrie /// Shows the children as possible KeyEvents and thier associated description. - pub fn infobox(&self, alphabetical_sort: bool) -> Info { + pub fn infobox(&self, sort_infobox: bool) -> Info { let mut body: InfoBoxBody = Vec::with_capacity(self.children.len()); let mut key_event_order = Vec::with_capacity(self.children.len()); // child_order and children is of same length @@ -114,6 +114,7 @@ impl KeyTrie { } } + // TODO: Add "A-" aknowledgement? // Shortest keyevent (as string) appears first, unless is a "C-" KeyEvent // Those events will always be placed after the one letter KeyEvent for (key_events, _) in body.iter_mut() { @@ -126,7 +127,7 @@ impl KeyTrie { }); } - if alphabetical_sort { body = alphabetially_sort_infobox(body); } + if sort_infobox { body = keyevent_sort_infobox(body); } let stringified_key_events_body: Vec<(String, &str)> = body .iter() @@ -178,28 +179,107 @@ impl<'de> Deserialize<'de> for KeyTrie { } } -type InfoBoxBody<'a> = Vec<(Vec, &'a str)>; -fn alphabetially_sort_infobox(mut body: InfoBoxBody) -> InfoBoxBody { - body.sort_unstable_by(|a, b| a.0[0].to_lowercase().cmp(&b.0[0].to_lowercase())); - // Consistently place lowercase before uppercase of the same letter. - if body.len() > 1 { - let mut x_index = 0; - let mut y_index = 1; - - while y_index < body.len() { - let x = &body[x_index].0[0]; - let y = &body[y_index].0[0]; - if x.to_lowercase() == y.to_lowercase() { - // Uppercase regarded as lower value. - if x < y { - let temp_holder = body[x_index].clone(); - body[x_index] = body[y_index].clone(); - body[y_index] = temp_holder; +// (KeyEvents, Description) +type InfoBoxRow<'a> = (Vec, &'a str); +type InfoBoxBody<'a> = Vec>; +/// Sorts by `ModifierKeyCode`, then by each `KeyCode` category, then by each `KeyEvent`. +/// KeyCode::Char sorting is special in that lower-case and upper-case equivalents are +/// placed together, and alphas are placed before the rest. +fn keyevent_sort_infobox(body: InfoBoxBody) -> InfoBoxBody { + use std::str::FromStr; + use std::collections::BTreeMap; + use helix_view::keyboard::{KeyCode, KeyModifiers, MediaKeyCode}; + + let mut category_holder: BTreeMap>> = BTreeMap::new(); + let mut sorted_body: InfoBoxBody = Vec::with_capacity(body.len()); + for infobox_row in body { + let first_keyevent = KeyEvent::from_str(infobox_row.0[0].as_str()).unwrap(); + if !category_holder.contains_key(&first_keyevent.modifiers) { + category_holder.insert(first_keyevent.modifiers.clone(), BTreeMap::new()); + } + + // HACK: inserting by variant not by variant value. + // KeyCode:: Char, F, and MediaKeys can have muiltiple values for the given variant + // Hence the use of mock Variant values + let keycode_category = match first_keyevent.code { + KeyCode::Char(_) => { + KeyCode::Char('a') + } + KeyCode::F(_) => { + KeyCode::F(0) + } + KeyCode::Media(_) => { + KeyCode::Media(MediaKeyCode::Play) + } + other_keycode => { + other_keycode + } + }; + + + let modifier_category = category_holder.get_mut(&first_keyevent.modifiers) + .expect("keycode category existence should be checked."); + if !modifier_category.contains_key(&keycode_category) { + modifier_category.insert(keycode_category.clone(), Vec::new()); + } + modifier_category.get_mut(&keycode_category) + .expect("key existence should be checked") + .push(infobox_row); + } + + for (_, keycode_categories) in category_holder { + for (keycode_category, mut infobox_rows) in keycode_categories { + if infobox_rows.len() > 1 { + match keycode_category { + KeyCode::Char(_) => { + infobox_rows.sort_unstable_by(|a, b| a.0[0].to_lowercase().cmp(&b.0[0].to_lowercase())); + + // Consistently place lowercase before uppercase of the same letter. + let mut x_index = 0; + let mut y_index = 1; + while y_index < infobox_rows.len() { + let x = &infobox_rows[x_index].0[0]; + let y = &infobox_rows[y_index].0[0]; + if x.to_lowercase() == y.to_lowercase() { + if x < y { + infobox_rows.swap(x_index, y_index); + } + } + x_index = y_index; + y_index += 1; + } + + // TEMP: until drain_filter becomes stable. Migth also be worth implementing + // FromIterator on InfoboxBody by then, last two for loops could then also be replaced by Iter::chain + let mut alphas = Vec::new(); + let mut misc = Vec::new(); + for infobox_row in infobox_rows { + if ('a'..='z') + .map(|char| char.to_string()) + .find(|alpha_char| *alpha_char == infobox_row.0[0].to_lowercase()) + .is_some() + { + alphas.push(infobox_row); + } + else { + misc.push(infobox_row); + } + } + infobox_rows = Vec::with_capacity(alphas.len() + misc.len()); + for alpha_row in alphas { + infobox_rows.push(alpha_row); + } + for misc_row in misc { + infobox_rows.push(misc_row); + } + }, + _ => { + infobox_rows.sort_unstable(); + } } } - x_index = y_index; - y_index += 1; + sorted_body.append(infobox_rows.as_mut()); } } - body + sorted_body } diff --git a/helix-view/src/keyboard.rs b/helix-view/src/keyboard.rs index 04a9922a99bb..20e592d36c28 100644 --- a/helix-view/src/keyboard.rs +++ b/helix-view/src/keyboard.rs @@ -211,63 +211,38 @@ impl From for ModifierKeyCode { } /// Represents a key. +/// Variant order determines order in keymap infobox if sorted_infobox is set to true. #[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Hash)] pub enum KeyCode { - /// Backspace key. - Backspace, - /// Enter key. - Enter, - /// Left arrow key. - Left, - /// Right arrow key. - Right, - /// Up arrow key. + /// Character key. + /// Ex: `KeyCode::Char('c')` represents the `c` character. + Char(char), + /// Function key. + /// Ex: `KeyCode::F(1)` represents the F1 key. + F(u8), Up, - /// Down arrow key. Down, - /// Home key. + Left, + Right, + Enter, + Esc, + Tab, + Backspace, + Insert, + Delete, Home, - /// End key. End, - /// Page up key. PageUp, - /// Page down key. PageDown, - /// Tab key. - Tab, - /// Delete key. - Delete, - /// Insert key. - Insert, - /// F key. - /// - /// `KeyCode::F(1)` represents F1 key, etc. - F(u8), - /// A character. - /// - /// `KeyCode::Char('c')` represents `c` character, etc. - Char(char), - /// Null. Null, - /// Escape key. - Esc, - /// CapsLock key. CapsLock, - /// ScrollLock key. ScrollLock, - /// NumLock key. NumLock, - /// PrintScreen key. - PrintScreen, - /// Pause key. - Pause, - /// Menu key. Menu, - /// KeypadBegin key. + Pause, + PrintScreen, KeypadBegin, - /// A media key. Media(MediaKeyCode), - /// A modifier key. Modifier(ModifierKeyCode), } From 5049e7c85fc6b492e1de8aba65d5851c6092717e Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Sun, 22 Jan 2023 16:53:05 +0100 Subject: [PATCH 076/105] Removed warnings --- helix-term/src/keymap/keytrie.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helix-term/src/keymap/keytrie.rs b/helix-term/src/keymap/keytrie.rs index 7fe5df9a8610..04a2424dcec2 100644 --- a/helix-term/src/keymap/keytrie.rs +++ b/helix-term/src/keymap/keytrie.rs @@ -1,6 +1,6 @@ use super::keytrienode::KeyTrieNode; use helix_view::{info::Info, input::KeyEvent}; -use std::{collections::HashMap, ops::{Deref, DerefMut}, cmp::Ordering}; +use std::{collections::HashMap, cmp::Ordering}; use serde::Deserialize; /// Edges of the trie are KeyEvents and the nodes are descrbibed by KeyTrieNode @@ -52,7 +52,7 @@ impl KeyTrie { } } - pub fn merge_keytrie(&mut self, mut other_keytrie: Self) { + pub fn merge_keytrie(&mut self, other_keytrie: Self) { for (other_key_event, other_index) in other_keytrie.get_child_order() { let other_child_keytrie_node = &other_keytrie.get_children()[*other_index]; match other_child_keytrie_node { From 4f758ee240d5c2cd373458aeb4032ae68f202c1e Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Sun, 22 Jan 2023 17:01:47 +0100 Subject: [PATCH 077/105] Use .join(", ") rather that own implementation --- helix-term/src/keymap/keytrie.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/helix-term/src/keymap/keytrie.rs b/helix-term/src/keymap/keytrie.rs index 04a2424dcec2..01c03ec9d1b1 100644 --- a/helix-term/src/keymap/keytrie.rs +++ b/helix-term/src/keymap/keytrie.rs @@ -80,6 +80,7 @@ impl KeyTrie { } } + // IMPROVEMENT: cache sorting and update cache only when config is updated /// Open an info box for a given KeyTrie /// Shows the children as possible KeyEvents and thier associated description. pub fn infobox(&self, sort_infobox: bool) -> Info { @@ -130,16 +131,9 @@ impl KeyTrie { if sort_infobox { body = keyevent_sort_infobox(body); } let stringified_key_events_body: Vec<(String, &str)> = body - .iter() - .map(|(key_events, description)| { - let key_events_string: String = key_events.iter().fold(String::new(), |mut acc, key_event| { - if !acc.is_empty() { acc.push_str(", "); } - acc.push_str(key_event); - acc - }); - (key_events_string, *description) - }) - .collect(); + .iter().map(|(key_events, description)| { + (key_events.join(", "), *description) + }).collect(); Info::new(&self.documentation, &stringified_key_events_body) } From c224bf339437b732bb40992f9004a6be2ef0cb1a Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Sun, 22 Jan 2023 18:05:24 +0100 Subject: [PATCH 078/105] Fix apply_trasaction rebase fail --- helix-term/src/commands.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 72dd5bdc5b1a..d12b5b98110e 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -42,7 +42,7 @@ use helix_view::{ keyboard::KeyCode, tree, view::View, - Document, DocumentId, Editor, ViewId, apply_transaction, + Document, DocumentId, Editor, ViewId, }; use std::{ collections::{HashMap, HashSet}, From 95a2b8784a77f6ca955987a3fa1508fb290e72ec Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Sun, 22 Jan 2023 18:43:26 +0100 Subject: [PATCH 079/105] Fix failing test --- helix-term/src/config.rs | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index 3ed2fa52323b..4838e2d79c26 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -77,28 +77,31 @@ mod tests { fn parses_keymap_from_toml() { let sample_keymaps = r#" [keys.insert] - y = "move_line_down" S-C-a = "delete_selection" + y = "move_line_down" [keys.normal] A-F12 = "move_next_word_end" "#; - assert_eq!( - toml::from_str::(sample_keymaps).unwrap(), - Config { - keys: hashmap! { - Mode::Insert => keytrie!({ "Insert mode" - "y" => move_line_down, - "S-C-a" => delete_selection, - }), - Mode::Normal => keytrie!({ "Normal mode" - "A-F12" => move_next_word_end, - }), - }, - ..Default::default() - } - ); + let config = Config { + keys: hashmap! { + Mode::Insert => keytrie!({ "Insert mode" + "S-C-a" => delete_selection, + "y" => move_line_down, + }), + Mode::Normal => keytrie!({ "Normal mode" + "A-F12" => move_next_word_end, + }), + }, + ..Default::default() + }; + for mode in config.keys.keys() { + assert_eq!( + config.keys.get(mode).unwrap().get_children(), + toml::from_str::(sample_keymaps).unwrap().keys.get(mode).unwrap().get_children() + ); + } } #[test] From 941624bdec8ba672c0cd9179679e0c2cc39af224 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Mon, 23 Jan 2023 17:28:18 +0100 Subject: [PATCH 080/105] Run cargo fmt --- helix-term/src/application.rs | 44 ++++----- helix-term/src/commands.rs | 127 +++++++++++++++---------- helix-term/src/config.rs | 50 +++++++--- helix-term/src/keymap.rs | 40 ++++---- helix-term/src/keymap/default.rs | 4 +- helix-term/src/keymap/keytrie.rs | 134 +++++++++++++++------------ helix-term/src/keymap/keytrienode.rs | 20 ++-- helix-term/src/keymap/macros.rs | 2 +- helix-term/src/keymap/tests.rs | 21 +++-- helix-term/src/ui/editor.rs | 7 +- 10 files changed, 266 insertions(+), 183 deletions(-) diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 81fa20951122..adaed7530bcf 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -1,3 +1,23 @@ +use crate::{ + args::Args, + commands::apply_workspace_edit, + compositor::{Compositor, Event}, + config::Config, + job::Jobs, + keymap::Keymap, + ui::{self, overlay::overlayed}, +}; +use anyhow::{Context, Error}; +use arc_swap::{access::Map, ArcSwap}; +use crossterm::{ + event::{ + DisableBracketedPaste, DisableFocusChange, DisableMouseCapture, EnableBracketedPaste, + EnableFocusChange, EnableMouseCapture, Event as CrosstermEvent, + }, + execute, terminal, + tty::IsTty, +}; +use futures_util::Stream; use helix_core::{ diagnostic::{DiagnosticTag, NumberOrString}, path::get_relative_path, @@ -13,34 +33,14 @@ use helix_view::{ tree::Layout, Align, Editor, }; -use crate::{ - args::Args, - commands::apply_workspace_edit, - compositor::{Compositor, Event}, - config::Config, - keymap::Keymap, - job::Jobs, - ui::{self, overlay::overlayed}, -}; +use log::{debug, error, warn}; +use serde_json::json; use std::{ io::{stdin, stdout, Write}, sync::Arc, time::{Duration, Instant}, }; -use arc_swap::{access::Map, ArcSwap}; -use futures_util::Stream; -use log::{debug, error, warn}; -use anyhow::{Context, Error}; -use serde_json::json; use tui::backend::Backend; -use crossterm::{ - event::{ - DisableBracketedPaste, DisableFocusChange, DisableMouseCapture, EnableBracketedPaste, - EnableFocusChange, EnableMouseCapture, Event as CrosstermEvent, - }, - execute, terminal, - tty::IsTty, -}; #[cfg(not(windows))] use { diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index d12b5b98110e..cb907479d04b 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -7,15 +7,24 @@ pub use lsp::*; pub use typed::*; use crate::{ - commands::insert::*, args, - keymap::CommandList, + commands::insert::*, compositor::{self, Component, Compositor}, - job::{Callback, self, Jobs}, - ui::{self, overlay::overlayed, FilePicker, Picker, Popup, Prompt, PromptEvent, menu::{Cell, Row}}, + job::{self, Callback, Jobs}, + keymap::CommandList, + ui::{ + self, + menu::{Cell, Row}, + overlay::overlayed, + FilePicker, Picker, Popup, Prompt, PromptEvent, + }, }; -use helix_vcs::Hunk; +use anyhow::{anyhow, bail, ensure, Context as _}; +use futures_util::StreamExt; +use fuzzy_matcher::FuzzyMatcher; +use grep_regex::RegexMatcherBuilder; +use grep_searcher::{sinks, BinaryDetection, SearcherBuilder}; use helix_core::{ comment, coords_at_pos, encoding, find_first_non_whitespace_char, find_root, graphemes, history::UndoKind, @@ -33,6 +42,7 @@ use helix_core::{ visual_coords_at_pos, LineEnding, Position, Range, Rope, RopeGraphemes, RopeSlice, Selection, SmallVec, Tendril, Transaction, }; +use helix_vcs::Hunk; use helix_view::{ clipboard::ClipboardType, document::{FormatterError, Mode, SCRATCH_BUFFER_NAME}, @@ -44,22 +54,17 @@ use helix_view::{ view::View, Document, DocumentId, Editor, ViewId, }; +use ignore::{DirEntry, WalkBuilder, WalkState}; +use once_cell::sync::Lazy; +use serde::de::{self, Deserialize, Deserializer}; use std::{ + borrow::Cow, collections::{HashMap, HashSet}, - num::NonZeroUsize, + fmt, future::Future, - borrow::Cow, + num::NonZeroUsize, path::{Path, PathBuf}, - fmt, }; -use anyhow::{anyhow, bail, ensure, Context as _}; -use fuzzy_matcher::FuzzyMatcher; -use futures_util::StreamExt; -use once_cell::sync::Lazy; -use serde::de::{self, Deserialize, Deserializer}; -use grep_regex::RegexMatcherBuilder; -use grep_searcher::{sinks, BinaryDetection, SearcherBuilder}; -use ignore::{DirEntry, WalkBuilder, WalkState}; use tokio_stream::wrappers::UnboundedReceiverStream; pub struct Context<'a> { @@ -156,7 +161,11 @@ macro_rules! static_commands { impl MappableCommand { pub fn execute(&self, cx: &mut Context) { match &self { - Self::Typable { name, args, description: _ } => { + Self::Typable { + name, + args, + description: _, + } => { let args: Vec> = args.iter().map(Cow::from).collect(); if let Some(command) = typed::TYPABLE_COMMAND_MAP.get(name.as_str()) { let mut cx = compositor::Context { @@ -2454,22 +2463,38 @@ impl ui::menu::Item for MappableCommand { fn format(&self, command_list: &Self::Data) -> Row { match self { - MappableCommand::Typable { description: doc, name, .. } => { - let mut row: Vec = vec![Cell::from(&*name.as_str()), Cell::from(""), Cell::from(&*doc.as_str())]; + MappableCommand::Typable { + description: doc, + name, + .. + } => { + let mut row: Vec = vec![ + Cell::from(&*name.as_str()), + Cell::from(""), + Cell::from(&*doc.as_str()), + ]; match command_list.get(name as &String) { - Some(key_events) => { row[1] = Cell::from(format_key_events(key_events)); }, + Some(key_events) => { + row[1] = Cell::from(format_key_events(key_events)); + } None => {} } return Row::new(row); - }, - MappableCommand::Static { description: doc, name, .. } => { + } + MappableCommand::Static { + description: doc, + name, + .. + } => { let mut row: Vec = vec![Cell::from(*name), Cell::from(""), Cell::from(*doc)]; match command_list.get(*name) { - Some(key_events) => { row[1] = Cell::from(format_key_events(key_events)); }, + Some(key_events) => { + row[1] = Cell::from(format_key_events(key_events)); + } None => {} } - return Row::new(row) - } + return Row::new(row); + } } // TODO: Generalize into a Vec Display implemention? @@ -2489,7 +2514,11 @@ impl ui::menu::Item for MappableCommand { pub fn command_palette(cx: &mut Context) { cx.callback = Some(Box::new( move |compositor: &mut Compositor, cx: &mut compositor::Context| { - let keymap_command_lists = compositor.find::().unwrap().keymap.command_list(&cx.editor.mode); + let keymap_command_lists = compositor + .find::() + .unwrap() + .keymap + .command_list(&cx.editor.mode); let mut commands: Vec = MappableCommand::STATIC_COMMAND_LIST.into(); commands.extend(typed::TYPABLE_COMMAND_LIST.iter().map(|cmd| { @@ -2500,32 +2529,36 @@ pub fn command_palette(cx: &mut Context) { } })); - let picker = Picker::new(commands, keymap_command_lists, move |cx, command, _action| { - let mut ctx = Context { - register: None, - count: std::num::NonZeroUsize::new(1), - editor: cx.editor, - callback: None, - on_next_key_callback: None, - jobs: cx.jobs, - }; - let focus = view!(ctx.editor).id; + let picker = Picker::new( + commands, + keymap_command_lists, + move |cx, command, _action| { + let mut ctx = Context { + register: None, + count: std::num::NonZeroUsize::new(1), + editor: cx.editor, + callback: None, + on_next_key_callback: None, + jobs: cx.jobs, + }; + let focus = view!(ctx.editor).id; - command.execute(&mut ctx); + command.execute(&mut ctx); - if ctx.editor.tree.contains(focus) { - let config = ctx.editor.config(); - let mode = ctx.editor.mode(); - let view = view_mut!(ctx.editor, focus); - let doc = doc_mut!(ctx.editor, &view.doc); + if ctx.editor.tree.contains(focus) { + let config = ctx.editor.config(); + let mode = ctx.editor.mode(); + let view = view_mut!(ctx.editor, focus); + let doc = doc_mut!(ctx.editor, &view.doc); - view.ensure_cursor_in_view(doc, config.scrolloff); + view.ensure_cursor_in_view(doc, config.scrolloff); - if mode != Mode::Insert { - doc.append_changes_to_history(view); + if mode != Mode::Insert { + doc.append_changes_to_history(view); + } } - } - }); + }, + ); compositor.push(Box::new(overlayed(picker))); }, )); diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index 4838e2d79c26..296526aeb71e 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -64,11 +64,7 @@ mod tests { use crate::{ commands::MappableCommand, config::Config, - keymap::{ - default, - keytrienode::KeyTrieNode, - macros::*, - }, + keymap::{default, keytrienode::KeyTrieNode, macros::*}, }; use helix_core::hashmap; use helix_view::document::Mode; @@ -99,7 +95,12 @@ mod tests { for mode in config.keys.keys() { assert_eq!( config.keys.get(mode).unwrap().get_children(), - toml::from_str::(sample_keymaps).unwrap().keys.get(mode).unwrap().get_children() + toml::from_str::(sample_keymaps) + .unwrap() + .keys + .get(mode) + .unwrap() + .get_children() ); } } @@ -125,14 +126,13 @@ mod tests { "g" => delete_char_forward, }, }) - + }, ..Default::default() }; let mut merged_config = user_config.clone().merge_in_default_keymap(); assert_ne!( - user_config, - merged_config, + user_config, merged_config, "Merged user keymap with default should differ from user keymap." ); @@ -155,25 +155,47 @@ mod tests { ); // Assumes that `g` is a sub key trie in default keymap assert_eq!( - keymap_normal_root_key_trie.traverse(&[key!('g'), key!('$')]).unwrap(), + keymap_normal_root_key_trie + .traverse(&[key!('g'), key!('$')]) + .unwrap(), KeyTrieNode::MappableCommand(MappableCommand::goto_line_end), "User supplied mappable command should be inserted under the correct sub keytrie." ); // Assumes that `gg` is in default keymap assert_eq!( - keymap_normal_root_key_trie.traverse(&[key!('g'), key!('g')]).unwrap(), + keymap_normal_root_key_trie + .traverse(&[key!('g'), key!('g')]) + .unwrap(), KeyTrieNode::MappableCommand(MappableCommand::delete_char_forward), "User supplied mappable command should replace default even in sub keytries." ); // Assumes that `ge` is in default keymap assert_eq!( - keymap_normal_root_key_trie.traverse(&[key!('g'), key!('e')]).unwrap(), + keymap_normal_root_key_trie + .traverse(&[key!('g'), key!('e')]) + .unwrap(), KeyTrieNode::MappableCommand(MappableCommand::goto_last_line), "Default mappable commands that aren't ovveridden should exist in merged keymap." ); // Huh? - assert!(merged_config.keys.get(&Mode::Normal).unwrap().get_children().len() > 1); - assert!(merged_config.keys.get(&Mode::Insert).unwrap().get_children().len() > 0); + assert!( + merged_config + .keys + .get(&Mode::Normal) + .unwrap() + .get_children() + .len() + > 1 + ); + assert!( + merged_config + .keys + .get(&Mode::Insert) + .unwrap() + .get_children() + .len() + > 0 + ); } } diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 9bd04222e39f..c6b3f75b56c8 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -1,20 +1,19 @@ pub mod default; pub mod macros; // NOTE: Only pub becuase of their use in macros -pub mod keytrienode; pub mod keytrie; +pub mod keytrienode; mod tests; -use self::{ - keytrienode::KeyTrieNode, - keytrie::KeyTrie, - macros::key, -}; +use self::{keytrie::KeyTrie, keytrienode::KeyTrieNode, macros::key}; use crate::commands::MappableCommand; +use arc_swap::{ + access::{DynAccess, DynGuard}, + ArcSwap, +}; use helix_view::{document::Mode, input::KeyEvent}; -use std::{sync::Arc, collections::HashMap}; -use arc_swap::{access::{DynAccess, DynGuard}, ArcSwap}; +use std::{collections::HashMap, sync::Arc}; #[derive(Debug, Clone, PartialEq)] pub enum KeymapResult { @@ -57,7 +56,7 @@ impl Keymap { } /// Lookup `key` in the keymap to try and find a command to execute. - /// Escape key represents cancellation. + /// Escape key represents cancellation. /// This means clearing pending keystrokes, or the sticky_keytrie if none were present. pub fn get(&mut self, mode: Mode, key: KeyEvent) -> KeymapResult { // TODO: remove the sticky part and look up manually @@ -122,12 +121,16 @@ impl Keymap { /// Returns a key-value list of all commands associated to a given Keymap. /// Keys are the node names (see KeyTrieNode documentation) /// Values are lists of stringified KeyEvents that triger the command. - /// Each element in the KeyEvent list is prefixed with prefixed the ancestor KeyEvents. + /// Each element in the KeyEvent list is prefixed with prefixed the ancestor KeyEvents. /// For example: Stringified KeyEvent element for the 'goto_next_window' command could be "space>w>w". /// Ancestor KeyEvents are in this case "space" and "w". pub fn command_list(&self, mode: &Mode) -> CommandList { let mut list = HashMap::new(); - _command_list(&mut list, &KeyTrieNode::KeyTrie(self.get_keytrie(mode)), &mut String::new()); + _command_list( + &mut list, + &KeyTrieNode::KeyTrie(self.get_keytrie(mode)), + &mut String::new(), + ); return list; fn _command_list(list: &mut CommandList, node: &KeyTrieNode, prefix: &mut String) { @@ -135,18 +138,21 @@ impl Keymap { KeyTrieNode::KeyTrie(trie_node) => { for (key_event, index) in trie_node.get_child_order() { let mut temp_prefix: String = prefix.to_string(); - if &temp_prefix != "" { + if &temp_prefix != "" { temp_prefix.push_str("→"); } temp_prefix.push_str(&key_event.to_string()); _command_list(list, &trie_node.get_children()[*index], &mut temp_prefix); - } - }, + } KeyTrieNode::MappableCommand(mappable_command) => { - if mappable_command.name() == "no_op" { return } - list.entry(mappable_command.name().to_string()).or_default().push(prefix.to_string()); - }, + if mappable_command.name() == "no_op" { + return; + } + list.entry(mappable_command.name().to_string()) + .or_default() + .push(prefix.to_string()); + } KeyTrieNode::CommandSequence(_) => {} }; } diff --git a/helix-term/src/keymap/default.rs b/helix-term/src/keymap/default.rs index 3fffaa08c517..24d53b8565d0 100644 --- a/helix-term/src/keymap/default.rs +++ b/helix-term/src/keymap/default.rs @@ -1,6 +1,6 @@ -use super::{macros::keytrie, keytrie::KeyTrie}; -use helix_view::document::Mode; +use super::{keytrie::KeyTrie, macros::keytrie}; use helix_core::hashmap; +use helix_view::document::Mode; use std::collections::HashMap; pub fn default() -> HashMap { diff --git a/helix-term/src/keymap/keytrie.rs b/helix-term/src/keymap/keytrie.rs index 01c03ec9d1b1..d40002d09245 100644 --- a/helix-term/src/keymap/keytrie.rs +++ b/helix-term/src/keymap/keytrie.rs @@ -1,7 +1,7 @@ use super::keytrienode::KeyTrieNode; use helix_view::{info::Info, input::KeyEvent}; -use std::{collections::HashMap, cmp::Ordering}; use serde::Deserialize; +use std::{cmp::Ordering, collections::HashMap}; /// Edges of the trie are KeyEvents and the nodes are descrbibed by KeyTrieNode #[derive(Debug, Clone)] @@ -14,7 +14,11 @@ pub struct KeyTrie { } impl KeyTrie { - pub fn new(documentation: &str, child_order: HashMap, children: Vec) -> Self { + pub fn new( + documentation: &str, + child_order: HashMap, + children: Vec, + ) -> Self { Self { documentation: documentation.to_string(), child_order, @@ -35,17 +39,20 @@ impl KeyTrie { pub fn traverse(&self, key_events: &[KeyEvent]) -> Option { return _traverse(self, key_events, 0); - fn _traverse(keytrie: &KeyTrie, key_events: &[KeyEvent], mut depth: usize) -> Option { + fn _traverse( + keytrie: &KeyTrie, + key_events: &[KeyEvent], + mut depth: usize, + ) -> Option { if depth == key_events.len() { return Some(KeyTrieNode::KeyTrie(keytrie.clone())); - } - else if let Some(found_index) = keytrie.child_order.get(&key_events[depth]) { + } else if let Some(found_index) = keytrie.child_order.get(&key_events[depth]) { match &keytrie.children[*found_index] { KeyTrieNode::KeyTrie(sub_keytrie) => { depth += 1; - return _traverse(sub_keytrie, key_events, depth) - }, - _found_child => return Some(_found_child.clone()) + return _traverse(sub_keytrie, key_events, depth); + } + _found_child => return Some(_found_child.clone()), } } return None; @@ -58,28 +65,31 @@ impl KeyTrie { match other_child_keytrie_node { KeyTrieNode::KeyTrie(ref other_child_keytrie) => { if let Some(self_index) = self.child_order.get(&other_key_event) { - if let KeyTrieNode::KeyTrie(ref mut self_clashing_child_key_trie) = self.children[*self_index] { + if let KeyTrieNode::KeyTrie(ref mut self_clashing_child_key_trie) = + self.children[*self_index] + { self_clashing_child_key_trie.merge_keytrie(other_child_keytrie.clone()); } - } - else { - self.child_order.insert(*other_key_event, self.children.len()); - self.children.push(KeyTrieNode::KeyTrie(other_child_keytrie.clone())); + } else { + self.child_order + .insert(*other_key_event, self.children.len()); + self.children + .push(KeyTrieNode::KeyTrie(other_child_keytrie.clone())); } } KeyTrieNode::MappableCommand(_) | KeyTrieNode::CommandSequence(_) => { if let Some(existing_index) = self.child_order.get(other_key_event) { self.children[*existing_index] = other_child_keytrie_node.clone(); - } - else { - self.child_order.insert(*other_key_event, self.children.len()); + } else { + self.child_order + .insert(*other_key_event, self.children.len()); self.children.push(other_child_keytrie_node.clone()); } } } } } - + // IMPROVEMENT: cache sorting and update cache only when config is updated /// Open an info box for a given KeyTrie /// Shows the children as possible KeyEvents and thier associated description. @@ -87,7 +97,9 @@ impl KeyTrie { let mut body: InfoBoxBody = Vec::with_capacity(self.children.len()); let mut key_event_order = Vec::with_capacity(self.children.len()); // child_order and children is of same length - unsafe { key_event_order.set_len(self.children.len()); } + unsafe { + key_event_order.set_len(self.children.len()); + } for (key_event, index) in &self.child_order { key_event_order[*index] = key_event.clone(); } @@ -99,7 +111,7 @@ impl KeyTrie { continue; } command.description() - }, + } KeyTrieNode::KeyTrie(ref key_trie) => &key_trie.documentation, // FIX: default to a join of all command names // NOTE: Giving same documentation for all sequences will place all sequence keyvents together. @@ -107,11 +119,12 @@ impl KeyTrie { KeyTrieNode::CommandSequence(_) => "[Multiple commands]", }; let key_event = key_event_order[index]; - match body.iter().position(|(_, existing_documentation)| &documentation == existing_documentation) { - Some(position) => body[position].0.push(key_event.to_string()), - None => { - body.push((vec![key_event.to_string()], documentation)) - }, + match body + .iter() + .position(|(_, existing_documentation)| &documentation == existing_documentation) + { + Some(position) => body[position].0.push(key_event.to_string()), + None => body.push((vec![key_event.to_string()], documentation)), } } @@ -120,20 +133,24 @@ impl KeyTrie { // Those events will always be placed after the one letter KeyEvent for (key_events, _) in body.iter_mut() { key_events.sort_unstable_by(|a, b| { - if a.len() == 1 { return Ordering::Less } + if a.len() == 1 { + return Ordering::Less; + } if b.len() > a.len() && b.starts_with("C-") { - return Ordering::Greater + return Ordering::Greater; } a.len().cmp(&b.len()) }); } - if sort_infobox { body = keyevent_sort_infobox(body); } + if sort_infobox { + body = keyevent_sort_infobox(body); + } let stringified_key_events_body: Vec<(String, &str)> = body - .iter().map(|(key_events, description)| { - (key_events.join(", "), *description) - }).collect(); + .iter() + .map(|(key_events, description)| (key_events.join(", "), *description)) + .collect(); Info::new(&self.documentation, &stringified_key_events_body) } @@ -165,7 +182,7 @@ impl<'de> Deserialize<'de> for KeyTrie { children.push(keytrie_node); } - Ok(Self { + Ok(Self { child_order, children, ..Default::default() @@ -177,14 +194,15 @@ impl<'de> Deserialize<'de> for KeyTrie { type InfoBoxRow<'a> = (Vec, &'a str); type InfoBoxBody<'a> = Vec>; /// Sorts by `ModifierKeyCode`, then by each `KeyCode` category, then by each `KeyEvent`. -/// KeyCode::Char sorting is special in that lower-case and upper-case equivalents are +/// KeyCode::Char sorting is special in that lower-case and upper-case equivalents are /// placed together, and alphas are placed before the rest. fn keyevent_sort_infobox(body: InfoBoxBody) -> InfoBoxBody { - use std::str::FromStr; - use std::collections::BTreeMap; use helix_view::keyboard::{KeyCode, KeyModifiers, MediaKeyCode}; + use std::collections::BTreeMap; + use std::str::FromStr; - let mut category_holder: BTreeMap>> = BTreeMap::new(); + let mut category_holder: BTreeMap>> = + BTreeMap::new(); let mut sorted_body: InfoBoxBody = Vec::with_capacity(body.len()); for infobox_row in body { let first_keyevent = KeyEvent::from_str(infobox_row.0[0].as_str()).unwrap(); @@ -196,27 +214,20 @@ fn keyevent_sort_infobox(body: InfoBoxBody) -> InfoBoxBody { // KeyCode:: Char, F, and MediaKeys can have muiltiple values for the given variant // Hence the use of mock Variant values let keycode_category = match first_keyevent.code { - KeyCode::Char(_) => { - KeyCode::Char('a') - } - KeyCode::F(_) => { - KeyCode::F(0) - } - KeyCode::Media(_) => { - KeyCode::Media(MediaKeyCode::Play) - } - other_keycode => { - other_keycode - } + KeyCode::Char(_) => KeyCode::Char('a'), + KeyCode::F(_) => KeyCode::F(0), + KeyCode::Media(_) => KeyCode::Media(MediaKeyCode::Play), + other_keycode => other_keycode, }; - - let modifier_category = category_holder.get_mut(&first_keyevent.modifiers) + let modifier_category = category_holder + .get_mut(&first_keyevent.modifiers) .expect("keycode category existence should be checked."); if !modifier_category.contains_key(&keycode_category) { modifier_category.insert(keycode_category.clone(), Vec::new()); } - modifier_category.get_mut(&keycode_category) + modifier_category + .get_mut(&keycode_category) .expect("key existence should be checked") .push(infobox_row); } @@ -225,12 +236,14 @@ fn keyevent_sort_infobox(body: InfoBoxBody) -> InfoBoxBody { for (keycode_category, mut infobox_rows) in keycode_categories { if infobox_rows.len() > 1 { match keycode_category { - KeyCode::Char(_) => { - infobox_rows.sort_unstable_by(|a, b| a.0[0].to_lowercase().cmp(&b.0[0].to_lowercase())); + KeyCode::Char(_) => { + infobox_rows.sort_unstable_by(|a, b| { + a.0[0].to_lowercase().cmp(&b.0[0].to_lowercase()) + }); // Consistently place lowercase before uppercase of the same letter. let mut x_index = 0; - let mut y_index = 1; + let mut y_index = 1; while y_index < infobox_rows.len() { let x = &infobox_rows[x_index].0[0]; let y = &infobox_rows[y_index].0[0]; @@ -238,9 +251,9 @@ fn keyevent_sort_infobox(body: InfoBoxBody) -> InfoBoxBody { if x < y { infobox_rows.swap(x_index, y_index); } - } - x_index = y_index; - y_index += 1; + } + x_index = y_index; + y_index += 1; } // TEMP: until drain_filter becomes stable. Migth also be worth implementing @@ -254,19 +267,18 @@ fn keyevent_sort_infobox(body: InfoBoxBody) -> InfoBoxBody { .is_some() { alphas.push(infobox_row); - } - else { + } else { misc.push(infobox_row); } } infobox_rows = Vec::with_capacity(alphas.len() + misc.len()); for alpha_row in alphas { - infobox_rows.push(alpha_row); + infobox_rows.push(alpha_row); } for misc_row in misc { - infobox_rows.push(misc_row); + infobox_rows.push(misc_row); } - }, + } _ => { infobox_rows.sort_unstable(); } diff --git a/helix-term/src/keymap/keytrienode.rs b/helix-term/src/keymap/keytrienode.rs index 66a642ac902d..5511c49dd7c6 100644 --- a/helix-term/src/keymap/keytrienode.rs +++ b/helix-term/src/keymap/keytrienode.rs @@ -1,8 +1,8 @@ use super::keytrie::KeyTrie; use crate::commands::MappableCommand; use helix_view::input::KeyEvent; +use serde::{de::Visitor, Deserialize}; use std::collections::HashMap; -use serde::{Deserialize, de::Visitor}; /// Each variant includes a documentaion property. /// For the MappableCommand and CommandSequence variants, the property is self explanatory. @@ -29,14 +29,14 @@ impl PartialEq for KeyTrieNode { match (self, other) { (KeyTrieNode::MappableCommand(_self), KeyTrieNode::MappableCommand(_other)) => { _self == _other - }, + } (KeyTrieNode::CommandSequence(_self), KeyTrieNode::CommandSequence(_other)) => { - _self == _other - }, + _self == _other + } (KeyTrieNode::KeyTrie(_self), KeyTrieNode::KeyTrie(_other)) => { _self.get_children() == _other.get_children() - }, - _ => false + } + _ => false, } } } @@ -85,6 +85,10 @@ impl<'de> Visitor<'de> for KeyTrieNodeVisitor { child_order.insert(key_event, children.len()); children.push(key_trie_node); } - Ok(KeyTrieNode::KeyTrie(KeyTrie::new("", child_order, children))) + Ok(KeyTrieNode::KeyTrie(KeyTrie::new( + "", + child_order, + children, + ))) } -} \ No newline at end of file +} diff --git a/helix-term/src/keymap/macros.rs b/helix-term/src/keymap/macros.rs index b834c43e56aa..c8f52128e3e4 100644 --- a/helix-term/src/keymap/macros.rs +++ b/helix-term/src/keymap/macros.rs @@ -116,5 +116,5 @@ macro_rules! keytrie { pub use alt; pub use ctrl; pub use key; -pub use shift; pub use keytrie; +pub use shift; diff --git a/helix-term/src/keymap/tests.rs b/helix-term/src/keymap/tests.rs index 4ab431c6e31d..bc5e061892bb 100644 --- a/helix-term/src/keymap/tests.rs +++ b/helix-term/src/keymap/tests.rs @@ -1,9 +1,9 @@ #[macro_use] #[cfg(test)] mod tests { + use crate::keymap::{macros::*, *}; use helix_core::hashmap; use helix_view::{document::Mode, input::KeyEvent}; - use crate::keymap::{*, macros::*}; use std::collections::HashMap; #[test] @@ -25,8 +25,12 @@ mod tests { fn aliased_modes_are_same_in_default_keymap() { let normal_mode_keytrie_root = Keymap::default().get_keytrie(&Mode::Normal); assert_eq!( - normal_mode_keytrie_root.traverse(&[key!(' '), key!('w')]).unwrap(), - normal_mode_keytrie_root.traverse(&["C-w".parse::().unwrap()]).unwrap(), + normal_mode_keytrie_root + .traverse(&[key!(' '), key!('w')]) + .unwrap(), + normal_mode_keytrie_root + .traverse(&["C-w".parse::().unwrap()]) + .unwrap(), "Mismatch for window mode on `Space-w` and `Ctrl-w`." ); assert_eq!( @@ -47,7 +51,9 @@ mod tests { "j" | "k" => move_line_down, }); - let keymap = Keymap::new(Box::new(ArcSwap::new(Arc::new(hashmap!(Mode::Normal => normal_mode))))); + let keymap = Keymap::new(Box::new(ArcSwap::new(Arc::new( + hashmap!(Mode::Normal => normal_mode), + )))); let mut command_list = keymap.command_list(&Mode::Normal); // sort keybindings in order to have consistent tests @@ -60,10 +66,7 @@ mod tests { assert_eq!( command_list, HashMap::from([ - ( - "insert_mode".to_string(), - vec![key!('i').to_string()] - ), + ("insert_mode".to_string(), vec![key!('i').to_string()]), ( "goto_file_start".to_string(), vec![format!("{}→{}", key!('g'), key!('g'))] @@ -80,4 +83,4 @@ mod tests { "Mismatch" ) } -} \ No newline at end of file +} diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 94ead3f0f0e6..f4cfd51cf429 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -3,7 +3,7 @@ use crate::{ compositor::{Component, Context, Event, EventResult}, job::{self, Callback}, key, - keymap::{KeymapResult, Keymap}, + keymap::{Keymap, KeymapResult}, ui::{Completion, ProgressSpinners}, }; @@ -931,7 +931,10 @@ impl EditorView { self.pseudo_pending.extend(self.keymap.pending()); let key_result = self.keymap.get(mode, event); let sort_infobox = cxt.editor.config.load().sorted_infobox; - cxt.editor.autoinfo = self.keymap.sticky_keytrie().map(|node| node.infobox(sort_infobox)); + cxt.editor.autoinfo = self + .keymap + .sticky_keytrie() + .map(|node| node.infobox(sort_infobox)); let mut execute_command = |command: &commands::MappableCommand| { command.execute(cxt); From 7735b348081e2e379e6e0617e9440af9955e2327 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Mon, 23 Jan 2023 17:46:17 +0100 Subject: [PATCH 081/105] Use description conistenly --- helix-term/src/keymap/keytrie.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/helix-term/src/keymap/keytrie.rs b/helix-term/src/keymap/keytrie.rs index d40002d09245..dc08695678d5 100644 --- a/helix-term/src/keymap/keytrie.rs +++ b/helix-term/src/keymap/keytrie.rs @@ -6,7 +6,7 @@ use std::{cmp::Ordering, collections::HashMap}; /// Edges of the trie are KeyEvents and the nodes are descrbibed by KeyTrieNode #[derive(Debug, Clone)] pub struct KeyTrie { - documentation: String, + description: String, /// Used for pre-defined order in infoboxes, values represent the index of the key tries children. child_order: HashMap, children: Vec, @@ -15,12 +15,12 @@ pub struct KeyTrie { impl KeyTrie { pub fn new( - documentation: &str, + description: &str, child_order: HashMap, children: Vec, ) -> Self { Self { - documentation: documentation.to_string(), + description: description.to_string(), child_order, children, is_sticky: false, @@ -105,26 +105,26 @@ impl KeyTrie { } for (index, key_trie) in self.children.iter().enumerate() { - let documentation: &str = match key_trie { + let description: &str = match key_trie { KeyTrieNode::MappableCommand(ref command) => { if command.name() == "no_op" { continue; } command.description() } - KeyTrieNode::KeyTrie(ref key_trie) => &key_trie.documentation, + KeyTrieNode::KeyTrie(ref key_trie) => &key_trie.description, // FIX: default to a join of all command names - // NOTE: Giving same documentation for all sequences will place all sequence keyvents together. + // NOTE: Giving same description for all sequences will place all sequence keyvents together. // Regardless if the command sequence is different. KeyTrieNode::CommandSequence(_) => "[Multiple commands]", }; let key_event = key_event_order[index]; match body .iter() - .position(|(_, existing_documentation)| &documentation == existing_documentation) + .position(|(_, existing_description)| &description == existing_description) { Some(position) => body[position].0.push(key_event.to_string()), - None => body.push((vec![key_event.to_string()], documentation)), + None => body.push((vec![key_event.to_string()], description)), } } @@ -152,7 +152,7 @@ impl KeyTrie { .map(|(key_events, description)| (key_events.join(", "), *description)) .collect(); - Info::new(&self.documentation, &stringified_key_events_body) + Info::new(&self.description, &stringified_key_events_body) } } From 8e1a3dad60f20fd2d6f1ef2ec8cae4dd521aa05e Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Mon, 23 Jan 2023 23:03:56 +0100 Subject: [PATCH 082/105] Fix undeterministic test failure --- helix-term/src/config.rs | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index 296526aeb71e..63c6b65d1a90 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -64,17 +64,18 @@ mod tests { use crate::{ commands::MappableCommand, config::Config, - keymap::{default, keytrienode::KeyTrieNode, macros::*}, + keymap::{default, keytrie::KeyTrie, keytrienode::KeyTrieNode, macros::*}, }; use helix_core::hashmap; - use helix_view::document::Mode; + use helix_view::{document::Mode, input::KeyEvent}; + use std::collections::BTreeMap; #[test] fn parses_keymap_from_toml() { let sample_keymaps = r#" [keys.insert] - S-C-a = "delete_selection" y = "move_line_down" + S-C-a = "delete_selection" [keys.normal] A-F12 = "move_next_word_end" @@ -83,8 +84,8 @@ mod tests { let config = Config { keys: hashmap! { Mode::Insert => keytrie!({ "Insert mode" - "S-C-a" => delete_selection, "y" => move_line_down, + "S-C-a" => delete_selection, }), Mode::Normal => keytrie!({ "Normal mode" "A-F12" => move_next_word_end, @@ -92,17 +93,30 @@ mod tests { }, ..Default::default() }; + for mode in config.keys.keys() { + // toml keymap config is placed into a hashmap, so order can not be presumed to be conserved + // hence the insertion into a BTreeMap assert_eq!( - config.keys.get(mode).unwrap().get_children(), - toml::from_str::(sample_keymaps) - .unwrap() - .keys - .get(mode) - .unwrap() - .get_children() + ordered_mapping(config.keys.get(mode).unwrap()), + ordered_mapping( + toml::from_str::(sample_keymaps) + .unwrap() + .keys + .get(mode) + .unwrap() + ) ); } + + fn ordered_mapping<'a>(keytrie: &'a KeyTrie) -> BTreeMap<&'a KeyEvent, KeyTrieNode> { + let children = keytrie.get_children(); + let mut ordered_keymap = BTreeMap::new(); + for (key_event, order) in keytrie.get_child_order() { + ordered_keymap.insert(key_event, children[*order].clone()); + } + ordered_keymap + } } #[test] From 16e2d74bbd25175f5cd707cde4d01f2250e27a08 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Tue, 24 Jan 2023 00:06:24 +0100 Subject: [PATCH 083/105] Implement clippy tips --- helix-term/src/keymap.rs | 16 ++++++++-------- helix-term/src/keymap/keytrie.rs | 22 ++++++++-------------- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index c6b3f75b56c8..96fa7cc6a589 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -74,7 +74,7 @@ impl Keymap { // Check if sticky keytrie is to be used. let starting_keytrie = match self.sticky_keytrie { - None => &active_keymap, + None => active_keymap, Some(ref active_sticky_keytrie) => active_sticky_keytrie, }; @@ -84,10 +84,10 @@ impl Keymap { let pending_keytrie: KeyTrie = match starting_keytrie.traverse(&[*first_key]) { Some(KeyTrieNode::KeyTrie(sub_keytrie)) => sub_keytrie, Some(KeyTrieNode::MappableCommand(cmd)) => { - return KeymapResult::Matched(cmd.clone()); + return KeymapResult::Matched(cmd); } Some(KeyTrieNode::CommandSequence(cmds)) => { - return KeymapResult::MatchedCommandSequence(cmds.clone()); + return KeymapResult::MatchedCommandSequence(cmds); } None => return KeymapResult::NotFound, }; @@ -99,15 +99,15 @@ impl Keymap { self.pending_keys.clear(); self.sticky_keytrie = Some(map.clone()); } - KeymapResult::Pending(map.clone()) + KeymapResult::Pending(map) } Some(KeyTrieNode::MappableCommand(cmd)) => { self.pending_keys.clear(); - KeymapResult::Matched(cmd.clone()) + KeymapResult::Matched(cmd) } Some(KeyTrieNode::CommandSequence(cmds)) => { self.pending_keys.clear(); - KeymapResult::MatchedCommandSequence(cmds.clone()) + KeymapResult::MatchedCommandSequence(cmds) } None => KeymapResult::Cancelled(self.pending_keys.drain(..).collect()), } @@ -138,8 +138,8 @@ impl Keymap { KeyTrieNode::KeyTrie(trie_node) => { for (key_event, index) in trie_node.get_child_order() { let mut temp_prefix: String = prefix.to_string(); - if &temp_prefix != "" { - temp_prefix.push_str("→"); + if !&temp_prefix.is_empty() { + temp_prefix.push('→'); } temp_prefix.push_str(&key_event.to_string()); _command_list(list, &trie_node.get_children()[*index], &mut temp_prefix); diff --git a/helix-term/src/keymap/keytrie.rs b/helix-term/src/keymap/keytrie.rs index dc08695678d5..6bf4ebf86c45 100644 --- a/helix-term/src/keymap/keytrie.rs +++ b/helix-term/src/keymap/keytrie.rs @@ -55,7 +55,7 @@ impl KeyTrie { _found_child => return Some(_found_child.clone()), } } - return None; + None } } @@ -64,7 +64,7 @@ impl KeyTrie { let other_child_keytrie_node = &other_keytrie.get_children()[*other_index]; match other_child_keytrie_node { KeyTrieNode::KeyTrie(ref other_child_keytrie) => { - if let Some(self_index) = self.child_order.get(&other_key_event) { + if let Some(self_index) = self.child_order.get(other_key_event) { if let KeyTrieNode::KeyTrie(ref mut self_clashing_child_key_trie) = self.children[*self_index] { @@ -97,11 +97,12 @@ impl KeyTrie { let mut body: InfoBoxBody = Vec::with_capacity(self.children.len()); let mut key_event_order = Vec::with_capacity(self.children.len()); // child_order and children is of same length + #[allow(clippy::uninit_vec)] unsafe { key_event_order.set_len(self.children.len()); } for (key_event, index) in &self.child_order { - key_event_order[*index] = key_event.clone(); + key_event_order[*index] = key_event; } for (index, key_trie) in self.children.iter().enumerate() { @@ -206,9 +207,7 @@ fn keyevent_sort_infobox(body: InfoBoxBody) -> InfoBoxBody { let mut sorted_body: InfoBoxBody = Vec::with_capacity(body.len()); for infobox_row in body { let first_keyevent = KeyEvent::from_str(infobox_row.0[0].as_str()).unwrap(); - if !category_holder.contains_key(&first_keyevent.modifiers) { - category_holder.insert(first_keyevent.modifiers.clone(), BTreeMap::new()); - } + category_holder.entry(first_keyevent.modifiers).or_insert_with(BTreeMap::new); // HACK: inserting by variant not by variant value. // KeyCode:: Char, F, and MediaKeys can have muiltiple values for the given variant @@ -223,9 +222,7 @@ fn keyevent_sort_infobox(body: InfoBoxBody) -> InfoBoxBody { let modifier_category = category_holder .get_mut(&first_keyevent.modifiers) .expect("keycode category existence should be checked."); - if !modifier_category.contains_key(&keycode_category) { - modifier_category.insert(keycode_category.clone(), Vec::new()); - } + modifier_category.entry(keycode_category).or_insert_with(Vec::new); modifier_category .get_mut(&keycode_category) .expect("key existence should be checked") @@ -247,10 +244,8 @@ fn keyevent_sort_infobox(body: InfoBoxBody) -> InfoBoxBody { while y_index < infobox_rows.len() { let x = &infobox_rows[x_index].0[0]; let y = &infobox_rows[y_index].0[0]; - if x.to_lowercase() == y.to_lowercase() { - if x < y { + if x.to_lowercase() == y.to_lowercase() && x < y{ infobox_rows.swap(x_index, y_index); - } } x_index = y_index; y_index += 1; @@ -263,8 +258,7 @@ fn keyevent_sort_infobox(body: InfoBoxBody) -> InfoBoxBody { for infobox_row in infobox_rows { if ('a'..='z') .map(|char| char.to_string()) - .find(|alpha_char| *alpha_char == infobox_row.0[0].to_lowercase()) - .is_some() + .any(|alpha_char| *alpha_char == infobox_row.0[0].to_lowercase()) { alphas.push(infobox_row); } else { From dc8e463be2c42c33863592f6caf4d7a436f759c6 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Tue, 24 Jan 2023 00:14:25 +0100 Subject: [PATCH 084/105] Make cargo fmt happy --- helix-term/src/keymap/keytrie.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/helix-term/src/keymap/keytrie.rs b/helix-term/src/keymap/keytrie.rs index 6bf4ebf86c45..25ed9e8e2537 100644 --- a/helix-term/src/keymap/keytrie.rs +++ b/helix-term/src/keymap/keytrie.rs @@ -207,7 +207,9 @@ fn keyevent_sort_infobox(body: InfoBoxBody) -> InfoBoxBody { let mut sorted_body: InfoBoxBody = Vec::with_capacity(body.len()); for infobox_row in body { let first_keyevent = KeyEvent::from_str(infobox_row.0[0].as_str()).unwrap(); - category_holder.entry(first_keyevent.modifiers).or_insert_with(BTreeMap::new); + category_holder + .entry(first_keyevent.modifiers) + .or_insert_with(BTreeMap::new); // HACK: inserting by variant not by variant value. // KeyCode:: Char, F, and MediaKeys can have muiltiple values for the given variant @@ -222,7 +224,9 @@ fn keyevent_sort_infobox(body: InfoBoxBody) -> InfoBoxBody { let modifier_category = category_holder .get_mut(&first_keyevent.modifiers) .expect("keycode category existence should be checked."); - modifier_category.entry(keycode_category).or_insert_with(Vec::new); + modifier_category + .entry(keycode_category) + .or_insert_with(Vec::new); modifier_category .get_mut(&keycode_category) .expect("key existence should be checked") @@ -244,8 +248,8 @@ fn keyevent_sort_infobox(body: InfoBoxBody) -> InfoBoxBody { while y_index < infobox_rows.len() { let x = &infobox_rows[x_index].0[0]; let y = &infobox_rows[y_index].0[0]; - if x.to_lowercase() == y.to_lowercase() && x < y{ - infobox_rows.swap(x_index, y_index); + if x.to_lowercase() == y.to_lowercase() && x < y { + infobox_rows.swap(x_index, y_index); } x_index = y_index; y_index += 1; From 4bfa470722859c5fbe3dcd3943a05855b6101907 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Tue, 24 Jan 2023 11:32:12 +0100 Subject: [PATCH 085/105] Make updated cargo clippy happy --- helix-term/src/config.rs | 18 +--- helix-term/src/keymap.rs | 1 + helix-term/src/keymap/tests.rs | 154 ++++++++++++++++----------------- 3 files changed, 80 insertions(+), 93 deletions(-) diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index 63c6b65d1a90..01b7e3f419d1 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -109,7 +109,7 @@ mod tests { ); } - fn ordered_mapping<'a>(keytrie: &'a KeyTrie) -> BTreeMap<&'a KeyEvent, KeyTrieNode> { + fn ordered_mapping(keytrie: &KeyTrie) -> BTreeMap<&KeyEvent, KeyTrieNode> { let children = keytrie.get_children(); let mut ordered_keymap = BTreeMap::new(); for (key_event, order) in keytrie.get_child_order() { @@ -194,22 +194,10 @@ mod tests { // Huh? assert!( - merged_config - .keys - .get(&Mode::Normal) - .unwrap() - .get_children() - .len() - > 1 + merged_config.keys.get(&Mode::Normal).unwrap().get_children().len() > 1 ); assert!( - merged_config - .keys - .get(&Mode::Insert) - .unwrap() - .get_children() - .len() - > 0 + !merged_config.keys.get(&Mode::Insert).unwrap().get_children().is_empty() ); } } diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 96fa7cc6a589..2a77d0ca66cc 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -3,6 +3,7 @@ pub mod macros; // NOTE: Only pub becuase of their use in macros pub mod keytrie; pub mod keytrienode; +#[cfg(test)] mod tests; use self::{keytrie::KeyTrie, keytrienode::KeyTrieNode, macros::key}; diff --git a/helix-term/src/keymap/tests.rs b/helix-term/src/keymap/tests.rs index bc5e061892bb..32dbc305770f 100644 --- a/helix-term/src/keymap/tests.rs +++ b/helix-term/src/keymap/tests.rs @@ -1,86 +1,84 @@ -#[macro_use] -#[cfg(test)] -mod tests { - use crate::keymap::{macros::*, *}; - use helix_core::hashmap; - use helix_view::{document::Mode, input::KeyEvent}; - use std::collections::HashMap; +use std::{sync::Arc, collections::HashMap}; +use arc_swap::ArcSwap; +use helix_core::hashmap; +use helix_view::{document::Mode, input::KeyEvent}; +use crate::key; +use super::{Keymap, macros::keytrie}; - #[test] - #[should_panic] - fn duplicate_keys_should_panic() { - keytrie!({ "Normal mode" - "i" => normal_mode, - "i" => goto_definition, - }); - } - - #[test] - fn check_duplicate_keys_in_default_keymap() { - // will panic on duplicate keys, assumes that `Keymap` uses keymap! macro - Keymap::default(); - } +#[test] +#[should_panic] +fn duplicate_keys_should_panic() { + keytrie!({ "Normal mode" + "i" => normal_mode, + "i" => goto_definition, + }); +} - #[test] - fn aliased_modes_are_same_in_default_keymap() { - let normal_mode_keytrie_root = Keymap::default().get_keytrie(&Mode::Normal); - assert_eq!( - normal_mode_keytrie_root - .traverse(&[key!(' '), key!('w')]) - .unwrap(), - normal_mode_keytrie_root - .traverse(&["C-w".parse::().unwrap()]) - .unwrap(), - "Mismatch for window mode on `Space-w` and `Ctrl-w`." - ); - assert_eq!( - normal_mode_keytrie_root.traverse(&[key!('z')]).unwrap(), - normal_mode_keytrie_root.traverse(&[key!('Z')]).unwrap(), - "Mismatch for view mode on `z` and `Z`." - ); - } +#[test] +fn check_duplicate_keys_in_default_keymap() { + // will panic on duplicate keys, assumes that `Keymap` uses keymap! macro + Keymap::default(); +} - #[test] - fn command_list() { - let normal_mode = keytrie!({ "Normal mode" - "i" => insert_mode, - "g" => { "Goto" - "g" => goto_file_start, - "e" => goto_file_end, - }, - "j" | "k" => move_line_down, - }); +#[test] +fn aliased_modes_are_same_in_default_keymap() { + let normal_mode_keytrie_root = Keymap::default().get_keytrie(&Mode::Normal); + assert_eq!( + normal_mode_keytrie_root + .traverse(&[key!(' '), key!('w')]) + .unwrap(), + normal_mode_keytrie_root + .traverse(&["C-w".parse::().unwrap()]) + .unwrap(), + "Mismatch for window mode on `Space-w` and `Ctrl-w`." + ); + assert_eq!( + normal_mode_keytrie_root.traverse(&[key!('z')]).unwrap(), + normal_mode_keytrie_root.traverse(&[key!('Z')]).unwrap(), + "Mismatch for view mode on `z` and `Z`." + ); +} - let keymap = Keymap::new(Box::new(ArcSwap::new(Arc::new( - hashmap!(Mode::Normal => normal_mode), - )))); - let mut command_list = keymap.command_list(&Mode::Normal); +#[test] +fn command_list() { + let normal_mode = keytrie!({ "Normal mode" + "i" => insert_mode, + "g" => { "Goto" + "g" => goto_file_start, + "e" => goto_file_end, + }, + "j" | "k" => move_line_down, + }); - // 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 command_list.values_mut() { - v.sort() - } + let keymap = Keymap::new(Box::new(ArcSwap::new(Arc::new( + hashmap!(Mode::Normal => normal_mode), + )))); + let mut command_list = keymap.command_list(&Mode::Normal); - assert_eq!( - command_list, - HashMap::from([ - ("insert_mode".to_string(), vec![key!('i').to_string()]), - ( - "goto_file_start".to_string(), - vec![format!("{}→{}", key!('g'), key!('g'))] - ), - ( - "goto_file_end".to_string(), - vec![format!("{}→{}", key!('g'), key!('e'))] - ), - ( - "move_line_down".to_string(), - vec![key!('j').to_string(), key!('k').to_string()] - ) - ]), - "Mismatch" - ) + // 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 command_list.values_mut() { + v.sort() } + + assert_eq!( + command_list, + HashMap::from([ + ("insert_mode".to_string(), vec![key!('i').to_string()]), + ( + "goto_file_start".to_string(), + vec![format!("{}→{}", key!('g'), key!('g'))] + ), + ( + "goto_file_end".to_string(), + vec![format!("{}→{}", key!('g'), key!('e'))] + ), + ( + "move_line_down".to_string(), + vec![key!('j').to_string(), key!('k').to_string()] + ) + ]), + "Mismatch" + ) } From 03088b2b67a518ef708dc5878b001c7e6cf2d782 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Tue, 24 Jan 2023 12:15:32 +0100 Subject: [PATCH 086/105] Fight with cargo fmt round 3 --- helix-term/src/config.rs | 17 +++++++++++++---- helix-term/src/keymap/tests.rs | 6 +++--- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index 01b7e3f419d1..4c8bbe144d5a 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -194,10 +194,19 @@ mod tests { // Huh? assert!( - merged_config.keys.get(&Mode::Normal).unwrap().get_children().len() > 1 - ); - assert!( - !merged_config.keys.get(&Mode::Insert).unwrap().get_children().is_empty() + merged_config + .keys + .get(&Mode::Normal) + .unwrap() + .get_children() + .len() + > 1 ); + assert!(!merged_config + .keys + .get(&Mode::Insert) + .unwrap() + .get_children() + .is_empty()); } } diff --git a/helix-term/src/keymap/tests.rs b/helix-term/src/keymap/tests.rs index 32dbc305770f..a0c52de6f77c 100644 --- a/helix-term/src/keymap/tests.rs +++ b/helix-term/src/keymap/tests.rs @@ -1,9 +1,9 @@ -use std::{sync::Arc, collections::HashMap}; +use super::{macros::keytrie, Keymap}; +use crate::key; use arc_swap::ArcSwap; use helix_core::hashmap; use helix_view::{document::Mode, input::KeyEvent}; -use crate::key; -use super::{Keymap, macros::keytrie}; +use std::{collections::HashMap, sync::Arc}; #[test] #[should_panic] From 13a9f3edaef2d58b96ef90b418a1e0c8ef967e69 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Wed, 25 Jan 2023 20:28:12 +0100 Subject: [PATCH 087/105] merge escaped keymap test --- helix-term/src/keymap.rs | 40 ---------------------------- helix-term/src/keymap/keytrienode.rs | 2 +- helix-term/src/keymap/tests.rs | 35 ++++++++++++++++++++++-- 3 files changed, 34 insertions(+), 43 deletions(-) diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 187cbb656b73..7f6fb234693c 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -17,7 +17,6 @@ use arc_swap::{ use helix_view::{document::Mode, input::KeyEvent}; use std::{collections::HashMap, sync::Arc}; - #[derive(Debug, Clone, PartialEq)] pub enum KeymapResult { Pending(KeyTrie), @@ -166,43 +165,4 @@ impl Default for Keymap { fn default() -> Self { Self::new(Box::new(ArcSwap::new(Arc::new(default::default())))) } - - #[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 = Keymap::new(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)); - } } diff --git a/helix-term/src/keymap/keytrienode.rs b/helix-term/src/keymap/keytrienode.rs index 5511c49dd7c6..18696b1eccb8 100644 --- a/helix-term/src/keymap/keytrienode.rs +++ b/helix-term/src/keymap/keytrienode.rs @@ -65,7 +65,7 @@ impl<'de> Visitor<'de> for KeyTrieNodeVisitor { S: serde::de::SeqAccess<'de>, { let mut commands = Vec::new(); - while let Some(command) = seq.next_element::<&str>()? { + while let Some(command) = seq.next_element::()? { commands.push( command .parse::() diff --git a/helix-term/src/keymap/tests.rs b/helix-term/src/keymap/tests.rs index a0c52de6f77c..18a008b28be1 100644 --- a/helix-term/src/keymap/tests.rs +++ b/helix-term/src/keymap/tests.rs @@ -1,5 +1,8 @@ -use super::{macros::keytrie, Keymap}; -use crate::key; +use crate::{ + commands::MappableCommand, + key, + keymap::{keytrie::KeyTrie, keytrienode::KeyTrieNode, macros::keytrie, Keymap}, +}; use arc_swap::ArcSwap; use helix_core::hashmap; use helix_view::{document::Mode, input::KeyEvent}; @@ -82,3 +85,31 @@ fn command_list() { "Mismatch" ) } + +#[test] +fn escaped_keymap() { + let parsed_keytrie: KeyTrie = toml::from_str( + r#" +"+" = [ + "select_all", + ":pipe sed -E 's/\\s+$//g'", +] + "#, + ) + .unwrap(); + + let command_sequence = KeyTrieNode::CommandSequence(vec![ + MappableCommand::select_all, + MappableCommand::Typable { + name: "pipe".to_string(), + args: vec![ + "sed".to_string(), + "-E".to_string(), + "'s/\\s+$//g'".to_string(), + ], + description: "".to_string(), + }, + ]); + + assert_eq!(parsed_keytrie.get_children()[0], command_sequence); +} From 8dd2169d81ef74bad187e1a8eead89a1d86ca4bb Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Wed, 25 Jan 2023 22:52:16 +0100 Subject: [PATCH 088/105] Initial alternative solution to #5203 --- helix-term/src/keymap/keytrie.rs | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/helix-term/src/keymap/keytrie.rs b/helix-term/src/keymap/keytrie.rs index 25ed9e8e2537..cbdd241d1b9d 100644 --- a/helix-term/src/keymap/keytrie.rs +++ b/helix-term/src/keymap/keytrie.rs @@ -106,18 +106,23 @@ impl KeyTrie { } for (index, key_trie) in self.children.iter().enumerate() { - let description: &str = match key_trie { + let description: String = match key_trie { KeyTrieNode::MappableCommand(ref command) => { if command.name() == "no_op" { continue; } - command.description() + command.description().to_string() } - KeyTrieNode::KeyTrie(ref key_trie) => &key_trie.description, // FIX: default to a join of all command names // NOTE: Giving same description for all sequences will place all sequence keyvents together. // Regardless if the command sequence is different. - KeyTrieNode::CommandSequence(_) => "[Multiple commands]", + KeyTrieNode::CommandSequence(ref command_sequence) => command_sequence + .iter() + .map(|command| command.name().to_string()) + .collect::>() + .join(" → ") + .clone(), + KeyTrieNode::KeyTrie(key_trie) => key_trie.description.clone(), }; let key_event = key_event_order[index]; match body @@ -148,10 +153,11 @@ impl KeyTrie { body = keyevent_sort_infobox(body); } - let stringified_key_events_body: Vec<(String, &str)> = body - .iter() - .map(|(key_events, description)| (key_events.join(", "), *description)) - .collect(); + // TODO: create InfoboxBody collect + let mut stringified_key_events_body = Vec::with_capacity(body.len()); + for (key_events, description) in body { + stringified_key_events_body.push((key_events.join(", "), description)); + } Info::new(&self.description, &stringified_key_events_body) } @@ -192,8 +198,8 @@ impl<'de> Deserialize<'de> for KeyTrie { } // (KeyEvents, Description) -type InfoBoxRow<'a> = (Vec, &'a str); -type InfoBoxBody<'a> = Vec>; +type InfoBoxRow = (Vec, String); +type InfoBoxBody = Vec; /// Sorts by `ModifierKeyCode`, then by each `KeyCode` category, then by each `KeyEvent`. /// KeyCode::Char sorting is special in that lower-case and upper-case equivalents are /// placed together, and alphas are placed before the rest. From b8e1ee46b1709765172070875ddce0a1385214ec Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Wed, 25 Jan 2023 23:06:38 +0100 Subject: [PATCH 089/105] Scrapped FromIterator idea Removes the ability to create the Vec using with_capacity if I'm not mistaken. --- helix-term/src/keymap/keytrie.rs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/helix-term/src/keymap/keytrie.rs b/helix-term/src/keymap/keytrie.rs index cbdd241d1b9d..9c7f484b1986 100644 --- a/helix-term/src/keymap/keytrie.rs +++ b/helix-term/src/keymap/keytrie.rs @@ -90,9 +90,9 @@ impl KeyTrie { } } - // IMPROVEMENT: cache sorting and update cache only when config is updated + // IMPROVEMENT: cache contents and update cache only when config is updated /// Open an info box for a given KeyTrie - /// Shows the children as possible KeyEvents and thier associated description. + /// Shows the children as possible KeyEvents with thier associated description. pub fn infobox(&self, sort_infobox: bool) -> Info { let mut body: InfoBoxBody = Vec::with_capacity(self.children.len()); let mut key_event_order = Vec::with_capacity(self.children.len()); @@ -113,9 +113,6 @@ impl KeyTrie { } command.description().to_string() } - // FIX: default to a join of all command names - // NOTE: Giving same description for all sequences will place all sequence keyvents together. - // Regardless if the command sequence is different. KeyTrieNode::CommandSequence(ref command_sequence) => command_sequence .iter() .map(|command| command.name().to_string()) @@ -153,7 +150,6 @@ impl KeyTrie { body = keyevent_sort_infobox(body); } - // TODO: create InfoboxBody collect let mut stringified_key_events_body = Vec::with_capacity(body.len()); for (key_events, description) in body { stringified_key_events_body.push((key_events.join(", "), description)); @@ -197,7 +193,7 @@ impl<'de> Deserialize<'de> for KeyTrie { } } -// (KeyEvents, Description) +/// (KeyEvents as strings, Description) type InfoBoxRow = (Vec, String); type InfoBoxBody = Vec; /// Sorts by `ModifierKeyCode`, then by each `KeyCode` category, then by each `KeyEvent`. @@ -261,8 +257,6 @@ fn keyevent_sort_infobox(body: InfoBoxBody) -> InfoBoxBody { y_index += 1; } - // TEMP: until drain_filter becomes stable. Migth also be worth implementing - // FromIterator on InfoboxBody by then, last two for loops could then also be replaced by Iter::chain let mut alphas = Vec::new(); let mut misc = Vec::new(); for infobox_row in infobox_rows { From 375238fa50d742fdffd2a28dec6652cfcf629293 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Wed, 25 Jan 2023 23:53:03 +0100 Subject: [PATCH 090/105] Infobox: Typaple commands w/ arguments formatting --- helix-term/src/commands.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index e6e9e778630e..1d3541f1f477 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -482,11 +482,12 @@ impl std::str::FromStr for MappableCommand { let args = typable_command .map(|s| s.to_owned()) .collect::>(); + typed::TYPABLE_COMMAND_MAP .get(name) .map(|cmd| MappableCommand::Typable { name: cmd.name.to_owned(), - description: format!(":{} {:?}", cmd.name, args), + description: format!(":{} {}", cmd.name, args.join(" ")), args, }) .ok_or_else(|| anyhow!("No TypableCommand named '{}'", s)) @@ -2475,14 +2476,12 @@ impl ui::menu::Item for MappableCommand { fn format(&self, command_list: &Self::Data) -> Row { match self { MappableCommand::Typable { - description: doc, - name, - .. + description, name, .. } => { let mut row: Vec = vec![ - Cell::from(&*name.as_str()), + Cell::from(name.as_str()), Cell::from(""), - Cell::from(&*doc.as_str()), + Cell::from(description.as_str()), ]; match command_list.get(name as &String) { Some(key_events) => { From b06eb3f4e2ab135dd710ca166eb0f6caa7fd3cf6 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Tue, 14 Feb 2023 00:37:11 +0100 Subject: [PATCH 091/105] fix clippy warnigs --- helix-term/src/config.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index 2ebd097f60b1..65ac4e037895 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -136,11 +136,9 @@ mod tests { let parsed_description = parsed_node.get_description().unwrap(); assert_eq!(parsed_description, "Edit Config"); - if let KeyTrieNode::MappableCommand(command) = parsed_node { - if let MappableCommand::Typable { name, .. } = command { - assert_eq!(name, "open".to_string()); - return; - } + if let KeyTrieNode::MappableCommand(MappableCommand::Typable { name, .. }) = parsed_node { + assert_eq!(name, "open".to_string()); + return; } panic!("KeyTrieNode::MappableCommand::Typable expected.") } @@ -190,7 +188,6 @@ mod tests { assert_eq!(parsed_description, "Buffer menu"); if let KeyTrieNode::KeyTrie(_) = parsed_node { - return; } else { panic!("KeyTrieNode::KeyTrie expected.") } From f44a34875c1d7aabf9169a976497d2847c3ee24f Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Tue, 14 Feb 2023 13:01:34 +0100 Subject: [PATCH 092/105] Sorted infobox: place A- before C- bindings --- helix-term/src/keymap/keytrie.rs | 3 +-- helix-view/src/keyboard.rs | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/helix-term/src/keymap/keytrie.rs b/helix-term/src/keymap/keytrie.rs index e6529fc9be50..7916caccefa9 100644 --- a/helix-term/src/keymap/keytrie.rs +++ b/helix-term/src/keymap/keytrie.rs @@ -143,7 +143,7 @@ impl KeyTrie { } // TODO: Add "A-" acknowledgement? - // Shortest keyevent (as string) appears first, unless is a "C-" KeyEvent + // Shortest keyevent (as string) appears first, unless it's a "C-" KeyEvent // Those events will always be placed after the one letter KeyEvent for (key_events, _) in body.iter_mut() { key_events.sort_unstable_by(|a, b| { @@ -189,7 +189,6 @@ impl<'de> Deserialize<'de> for KeyTrie { { // NOTE: no assumption of pre-defined order in config let child_collection = HashMap::::deserialize(deserializer)?; - // TODO: common pattern, generalize (found in keytrinode deserialize too) let mut child_order = HashMap::::new(); let mut children = Vec::new(); for (key_event, keytrie_node) in child_collection { diff --git a/helix-view/src/keyboard.rs b/helix-view/src/keyboard.rs index 20e592d36c28..858d7ecd1f51 100644 --- a/helix-view/src/keyboard.rs +++ b/helix-view/src/keyboard.rs @@ -4,8 +4,8 @@ bitflags! { /// Represents key modifiers (shift, control, alt). pub struct KeyModifiers: u8 { const SHIFT = 0b0000_0001; - const CONTROL = 0b0000_0010; - const ALT = 0b0000_0100; + const ALT = 0b0000_0010; + const CONTROL = 0b0000_0100; const NONE = 0b0000_0000; } } From b62d9a434928010a329b98b5ed77fc54b3ce5cc2 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Tue, 14 Feb 2023 13:06:39 +0100 Subject: [PATCH 093/105] keyboard.rs: remove reduntant comments --- helix-view/src/keyboard.rs | 33 --------------------------------- 1 file changed, 33 deletions(-) diff --git a/helix-view/src/keyboard.rs b/helix-view/src/keyboard.rs index 858d7ecd1f51..14d4c6c8b1fa 100644 --- a/helix-view/src/keyboard.rs +++ b/helix-view/src/keyboard.rs @@ -1,7 +1,6 @@ use bitflags::bitflags; bitflags! { - /// Represents key modifiers (shift, control, alt). pub struct KeyModifiers: u8 { const SHIFT = 0b0000_0001; const ALT = 0b0000_0010; @@ -55,31 +54,18 @@ impl From for KeyModifiers { /// Represents a media key (as part of [`KeyCode::Media`]). #[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Hash)] pub enum MediaKeyCode { - /// Play media key. Play, - /// Pause media key. Pause, - /// Play/Pause media key. PlayPause, - /// Reverse media key. Reverse, - /// Stop media key. Stop, - /// Fast-forward media key. FastForward, - /// Rewind media key. Rewind, - /// Next-track media key. TrackNext, - /// Previous-track media key. TrackPrevious, - /// Record media key. Record, - /// Lower-volume media key. LowerVolume, - /// Raise-volume media key. RaiseVolume, - /// Mute media key. MuteVolume, } @@ -132,33 +118,19 @@ impl From for MediaKeyCode { /// Represents a media key (as part of [`KeyCode::Modifier`]). #[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Hash)] pub enum ModifierKeyCode { - /// Left Shift key. LeftShift, - /// Left Control key. LeftControl, - /// Left Alt key. LeftAlt, - /// Left Super key. LeftSuper, - /// Left Hyper key. LeftHyper, - /// Left Meta key. LeftMeta, - /// Right Shift key. RightShift, - /// Right Control key. RightControl, - /// Right Alt key. RightAlt, - /// Right Super key. RightSuper, - /// Right Hyper key. RightHyper, - /// Right Meta key. RightMeta, - /// Iso Level3 Shift key. IsoLevel3Shift, - /// Iso Level5 Shift key. IsoLevel5Shift, } @@ -210,15 +182,10 @@ impl From for ModifierKeyCode { } } -/// Represents a key. /// Variant order determines order in keymap infobox if sorted_infobox is set to true. #[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Hash)] pub enum KeyCode { - /// Character key. - /// Ex: `KeyCode::Char('c')` represents the `c` character. Char(char), - /// Function key. - /// Ex: `KeyCode::F(1)` represents the F1 key. F(u8), Up, Down, From 72e1581c82d397cfd6f50bf36e58c7c1b5f3ae48 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Wed, 15 Feb 2023 10:26:13 +0100 Subject: [PATCH 094/105] Sticky can now overwrite pre-defined sticky. --- helix-term/src/keymap/keytrie.rs | 10 +++++++++- helix-term/src/keymap/keytrienode.rs | 3 +++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/helix-term/src/keymap/keytrie.rs b/helix-term/src/keymap/keytrie.rs index 7916caccefa9..15fad2e59546 100644 --- a/helix-term/src/keymap/keytrie.rs +++ b/helix-term/src/keymap/keytrie.rs @@ -11,6 +11,8 @@ pub struct KeyTrie { child_order: HashMap, children: Vec, pub is_sticky: bool, + /// Used to respect pre-defined stickyness. + pub explicitly_set_sticky: bool, } impl KeyTrie { @@ -24,6 +26,7 @@ impl KeyTrie { child_order, children, is_sticky: false, + explicitly_set_sticky: false } } @@ -63,11 +66,16 @@ impl KeyTrie { } } + /// Other takes precedent. pub fn merge_keytrie(&mut self, other_keytrie: Self) { + if other_keytrie.explicitly_set_sticky { + self.is_sticky = other_keytrie.is_sticky; + } + for (other_key_event, other_index) in other_keytrie.get_child_order() { let other_child_keytrie_node = &other_keytrie.get_children()[*other_index]; match other_child_keytrie_node { - KeyTrieNode::KeyTrie(ref other_child_keytrie) => { + KeyTrieNode::KeyTrie(other_child_keytrie) => { if let Some(self_index) = self.child_order.get(other_key_event) { if let KeyTrieNode::KeyTrie(ref mut self_clashing_child_key_trie) = self.children[*self_index] diff --git a/helix-term/src/keymap/keytrienode.rs b/helix-term/src/keymap/keytrienode.rs index cb5b533d4e42..9bfe00a8be8e 100644 --- a/helix-term/src/keymap/keytrienode.rs +++ b/helix-term/src/keymap/keytrienode.rs @@ -121,11 +121,13 @@ impl<'de> Visitor<'de> for KeyTrieNodeVisitor { let mut children = Vec::new(); let mut child_order = HashMap::new(); let mut keytrie_is_sticky = false; + let mut user_explicit_sticky = false; let mut next_key = Some(peeked_key); while let Some(ref peeked_key) = next_key { if peeked_key == "sticky" { keytrie_is_sticky = map.next_value::()?; + user_explicit_sticky = true; } else { let key_event = peeked_key @@ -144,6 +146,7 @@ impl<'de> Visitor<'de> for KeyTrieNodeVisitor { children, ); keytrie.is_sticky = keytrie_is_sticky; + keytrie.explicitly_set_sticky = user_explicit_sticky; Ok(KeyTrieNode::KeyTrie(keytrie)) }; From ad9bc2a9228f7c345efe7dfc8bd28296be0219e8 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Wed, 15 Feb 2023 10:28:09 +0100 Subject: [PATCH 095/105] cargo fmt --- helix-term/src/keymap/keytrie.rs | 4 ++-- helix-term/src/keymap/keytrienode.rs | 9 ++------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/helix-term/src/keymap/keytrie.rs b/helix-term/src/keymap/keytrie.rs index 15fad2e59546..9398e1edfe95 100644 --- a/helix-term/src/keymap/keytrie.rs +++ b/helix-term/src/keymap/keytrie.rs @@ -26,7 +26,7 @@ impl KeyTrie { child_order, children, is_sticky: false, - explicitly_set_sticky: false + explicitly_set_sticky: false, } } @@ -71,7 +71,7 @@ impl KeyTrie { if other_keytrie.explicitly_set_sticky { self.is_sticky = other_keytrie.is_sticky; } - + for (other_key_event, other_index) in other_keytrie.get_child_order() { let other_child_keytrie_node = &other_keytrie.get_children()[*other_index]; match other_child_keytrie_node { diff --git a/helix-term/src/keymap/keytrienode.rs b/helix-term/src/keymap/keytrienode.rs index 9bfe00a8be8e..2a911b935652 100644 --- a/helix-term/src/keymap/keytrienode.rs +++ b/helix-term/src/keymap/keytrienode.rs @@ -128,8 +128,7 @@ impl<'de> Visitor<'de> for KeyTrieNodeVisitor { if peeked_key == "sticky" { keytrie_is_sticky = map.next_value::()?; user_explicit_sticky = true; - } - else { + } else { let key_event = peeked_key .parse::() .map_err(serde::de::Error::custom)?; @@ -140,11 +139,7 @@ impl<'de> Visitor<'de> for KeyTrieNodeVisitor { next_key = map.next_key::()?; } - let mut keytrie = KeyTrie::new( - description, - child_order, - children, - ); + let mut keytrie = KeyTrie::new(description, child_order, children); keytrie.is_sticky = keytrie_is_sticky; keytrie.explicitly_set_sticky = user_explicit_sticky; Ok(KeyTrieNode::KeyTrie(keytrie)) From 34db3413f9faef6c49261149ed95d185de0a4933 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Wed, 15 Feb 2023 10:34:30 +0100 Subject: [PATCH 096/105] Clarify sticky in remapping.md --- book/src/remapping.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/book/src/remapping.md b/book/src/remapping.md index ad62928d8fd7..0d58fd719610 100644 --- a/book/src/remapping.md +++ b/book/src/remapping.md @@ -101,7 +101,8 @@ Ctrl, Shift and Alt modifiers are encoded respectively with the prefixes Keys can be disabled by binding them to the `no_op` command. -Making a mode "sticky" can be achieved by adding `sticky = true` to the mapping. +Making a mode "sticky" can be achieved by adding `sticky = true` to the mapping. +(Predefined sticky keytries can like wise be made unsticky with `sticky = false`.) Commands can be found at [Keymap](https://docs.helix-editor.com/keymap.html) Commands. > Commands can also be found in the source code at [`helix-term/src/commands.rs`](https://github.com/helix-editor/helix/blob/master/helix-term/src/commands.rs) at the invocation of `static_commands!` macro and the `TypableCommandList`. From 2d9cea80957f307f296e8e311bac327a96fe0766 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Wed, 15 Feb 2023 11:16:56 +0100 Subject: [PATCH 097/105] Add tests for user defined sticky --- helix-term/src/config.rs | 65 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index 65ac4e037895..f62bc1964556 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -119,6 +119,71 @@ mod tests { } } + #[test] + fn false_to_true_sticky_override() { + let sample_keymap = r#" + [keys.normal.space] + sticky = true + "#; + let space_keytrie: KeyTrieNode = toml::from_str::(sample_keymap) + .unwrap() + .merge_in_default_keymap() + .keys + .get(&Mode::Normal) + .unwrap() + .traverse(&[KeyEvent::from_str("space").unwrap()]) + .unwrap(); + if let KeyTrieNode::KeyTrie(_space_keytrie) = space_keytrie { + assert!(_space_keytrie.is_sticky) + } else { + panic!("KeyTrieNode::KeyTrie expected.") + } + } + + #[test] + fn true_to_undefined_remains_sticky() { + // NOTE: assumes Z binding is predefined as sticky. + let sample_keymap = r#" + [keys.normal.Z] + c = "no_op" + "#; + let sticky_keytrie: KeyTrieNode = toml::from_str::(sample_keymap) + .unwrap() + .merge_in_default_keymap() + .keys + .get(&Mode::Normal) + .unwrap() + .traverse(&[KeyEvent::from_str("Z").unwrap()]) + .unwrap(); + if let KeyTrieNode::KeyTrie(_sticky_keytrie) = sticky_keytrie { + assert!(_sticky_keytrie.is_sticky) + } else { + panic!("KeyTrieNode::KeyTrie expected.") + } + } + + #[test] + fn true_to_false_sticky_override() { + // NOTE: assumes Z binding is predefined as sticky. + let sample_keymap = r#" + [keys.normal.Z] + sticky = false + "#; + let sticky_keytrie: KeyTrieNode = toml::from_str::(sample_keymap) + .unwrap() + .merge_in_default_keymap() + .keys + .get(&Mode::Normal) + .unwrap() + .traverse(&[KeyEvent::from_str("Z").unwrap()]) + .unwrap(); + if let KeyTrieNode::KeyTrie(_sticky_keytrie) = sticky_keytrie { + assert!(!_sticky_keytrie.is_sticky) + } else { + panic!("KeyTrieNode::KeyTrie expected.") + } + } + #[test] fn parses_custom_typable_command_label_from_toml() { let sample_keymap = r#" From 68c66ace491ccec0226e020a72af8b76a4e58472 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Wed, 15 Feb 2023 12:13:50 +0100 Subject: [PATCH 098/105] config.rs: clean up tests --- helix-term/src/config.rs | 115 +++++++++++++-------------------------- 1 file changed, 37 insertions(+), 78 deletions(-) diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index f62bc1964556..da4281668373 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -125,41 +125,17 @@ mod tests { [keys.normal.space] sticky = true "#; - let space_keytrie: KeyTrieNode = toml::from_str::(sample_keymap) - .unwrap() - .merge_in_default_keymap() - .keys - .get(&Mode::Normal) - .unwrap() - .traverse(&[KeyEvent::from_str("space").unwrap()]) - .unwrap(); - if let KeyTrieNode::KeyTrie(_space_keytrie) = space_keytrie { - assert!(_space_keytrie.is_sticky) - } else { - panic!("KeyTrieNode::KeyTrie expected.") - } + assert!(_normal_mode_keytrie("space", sample_keymap).is_sticky) } #[test] fn true_to_undefined_remains_sticky() { // NOTE: assumes Z binding is predefined as sticky. let sample_keymap = r#" - [keys.normal.Z] - c = "no_op" - "#; - let sticky_keytrie: KeyTrieNode = toml::from_str::(sample_keymap) - .unwrap() - .merge_in_default_keymap() - .keys - .get(&Mode::Normal) - .unwrap() - .traverse(&[KeyEvent::from_str("Z").unwrap()]) - .unwrap(); - if let KeyTrieNode::KeyTrie(_sticky_keytrie) = sticky_keytrie { - assert!(_sticky_keytrie.is_sticky) - } else { - panic!("KeyTrieNode::KeyTrie expected.") - } + [keys.normal.Z] + c = "no_op" + "#; + assert!(_normal_mode_keytrie("Z", sample_keymap).is_sticky) } #[test] @@ -168,20 +144,8 @@ mod tests { let sample_keymap = r#" [keys.normal.Z] sticky = false - "#; - let sticky_keytrie: KeyTrieNode = toml::from_str::(sample_keymap) - .unwrap() - .merge_in_default_keymap() - .keys - .get(&Mode::Normal) - .unwrap() - .traverse(&[KeyEvent::from_str("Z").unwrap()]) - .unwrap(); - if let KeyTrieNode::KeyTrie(_sticky_keytrie) = sticky_keytrie { - assert!(!_sticky_keytrie.is_sticky) - } else { - panic!("KeyTrieNode::KeyTrie expected.") - } + "#; + assert!(!_normal_mode_keytrie("Z", sample_keymap).is_sticky) } #[test] @@ -190,14 +154,7 @@ mod tests { [keys.normal] A-k = { description = "Edit Config", exec = ":open ~/.config/helix/config.toml" } "#; - let parsed_node: KeyTrieNode = toml::from_str::(sample_keymap) - .unwrap() - .keys - .get(&Mode::Normal) - .unwrap() - .traverse(&[KeyEvent::from_str("A-k").unwrap()]) - .unwrap(); - + let parsed_node: KeyTrieNode = _normal_mode_keytrie_node("A-k", sample_keymap); let parsed_description = parsed_node.get_description().unwrap(); assert_eq!(parsed_description, "Edit Config"); @@ -215,14 +172,7 @@ mod tests { "C-r" = { "description" = "Sort selection", "exec" = ["split_selection_on_newline", ":sort", "collapse_selection", "keep_primary_selection"] } "#; - let parsed_node: KeyTrieNode = toml::from_str::(sample_keymap) - .unwrap() - .keys - .get(&Mode::Normal) - .unwrap() - .traverse(&[KeyEvent::from_str("C-r").unwrap()]) - .unwrap(); - + let parsed_node: KeyTrieNode = _normal_mode_keytrie_node("C-r", sample_keymap); let parsed_description = parsed_node.get_description().unwrap(); assert_eq!(parsed_description, "Sort selection"); @@ -237,25 +187,13 @@ mod tests { #[test] fn parses_custom_infobox_label_from_toml() { let sample_keymap = r#" - [keys.normal] - b = { description = "Buffer menu", b = "buffer_picker", n = "goto_next_buffer" } + [keys.normal.A-b] + description = "Buffer menu" + b = "buffer_picker" + n = "goto_next_buffer" "#; - - let parsed_node: KeyTrieNode = toml::from_str::(sample_keymap) - .unwrap() - .keys - .get(&Mode::Normal) - .unwrap() - .traverse(&[KeyEvent::from_str("b").unwrap()]) - .unwrap(); - - let parsed_description = parsed_node.get_description().unwrap(); - assert_eq!(parsed_description, "Buffer menu"); - - if let KeyTrieNode::KeyTrie(_) = parsed_node { - } else { - panic!("KeyTrieNode::KeyTrie expected.") - } + let parsed_node: KeyTrieNode = _normal_mode_keytrie_node("A-b", sample_keymap); + assert_eq!(parsed_node.get_description().unwrap(), "Buffer menu"); } #[test] @@ -293,7 +231,7 @@ mod tests { assert_eq!( keymap_normal_root_key_trie.traverse(&[key!('i')]).unwrap(), KeyTrieNode::MappableCommand(MappableCommand::normal_mode), - "User supplied mappable command should ovveride default mappable command bound to the same key event." + "User supplied mappable command should override default mappable command bound to the same key event." ); assert_eq!( keymap_normal_root_key_trie.traverse(&[key!('无')]).unwrap(), @@ -348,4 +286,25 @@ mod tests { .get_children() .is_empty()); } + + fn _normal_mode_keytrie(key_event_str: &str, sample_keymap: &str) -> KeyTrie { + if let KeyTrieNode::KeyTrie(_parsed_keytrie) = + _normal_mode_keytrie_node(key_event_str, sample_keymap) + { + _parsed_keytrie + } else { + panic!("KeyTrieNode::KeyTrie expected.") + } + } + + fn _normal_mode_keytrie_node(key_event_str: &str, sample_keymap: &str) -> KeyTrieNode { + toml::from_str::(sample_keymap) + .unwrap() + .merge_in_default_keymap() + .keys + .get(&Mode::Normal) + .unwrap() + .traverse(&[KeyEvent::from_str(key_event_str).unwrap()]) + .unwrap() + } } From 6a812153a49450493d99fc409f7b76cb6c48bb4c Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Wed, 15 Feb 2023 12:35:49 +0100 Subject: [PATCH 099/105] fix keytrie on default mappable command override bug I had introduced a bug in which it wasn't possible to override mappable commands with keytries. Test has been added to catch this miss. --- helix-term/src/config.rs | 13 +++++++++++-- helix-term/src/keymap/keytrie.rs | 2 ++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index da4281668373..2259c3485ef7 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -187,12 +187,12 @@ mod tests { #[test] fn parses_custom_infobox_label_from_toml() { let sample_keymap = r#" - [keys.normal.A-b] + [keys.normal.b] description = "Buffer menu" b = "buffer_picker" n = "goto_next_buffer" "#; - let parsed_node: KeyTrieNode = _normal_mode_keytrie_node("A-b", sample_keymap); + let parsed_node: KeyTrieNode = _normal_mode_keytrie_node("b", sample_keymap); assert_eq!(parsed_node.get_description().unwrap(), "Buffer menu"); } @@ -216,6 +216,9 @@ mod tests { "$" => goto_line_end, "g" => delete_char_forward, }, + "b" => { "Buffer menu" + "b" => buffer_picker, + }, }) }, @@ -268,6 +271,12 @@ mod tests { KeyTrieNode::MappableCommand(MappableCommand::goto_last_line), "Default mappable commands that aren't ovveridden should exist in merged keymap." ); + // Assumes that `b` is a MappableCommand in default keymap + assert_ne!( + keymap_normal_root_key_trie.traverse(&[key!('b')]).unwrap(), + KeyTrieNode::MappableCommand(MappableCommand::move_prev_word_start), + "Keytrie can override default mappable command." + ); // Huh? assert!( diff --git a/helix-term/src/keymap/keytrie.rs b/helix-term/src/keymap/keytrie.rs index 9398e1edfe95..fbca705cc40d 100644 --- a/helix-term/src/keymap/keytrie.rs +++ b/helix-term/src/keymap/keytrie.rs @@ -81,6 +81,8 @@ impl KeyTrie { self.children[*self_index] { self_clashing_child_key_trie.merge_keytrie(other_child_keytrie.clone()); + } else { + self.children[*self_index] = other_child_keytrie_node.clone(); } } else { self.child_order From 91d8d92cdb6b922f7b0d89d546afd7b5a0ed5771 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Wed, 15 Feb 2023 13:02:26 +0100 Subject: [PATCH 100/105] keytrie.rs: merge_keytrie() refactor --- helix-term/src/keymap/keytrie.rs | 36 +++++++++++--------------------- 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/helix-term/src/keymap/keytrie.rs b/helix-term/src/keymap/keytrie.rs index fbca705cc40d..0bd4f71185c4 100644 --- a/helix-term/src/keymap/keytrie.rs +++ b/helix-term/src/keymap/keytrie.rs @@ -74,32 +74,20 @@ impl KeyTrie { for (other_key_event, other_index) in other_keytrie.get_child_order() { let other_child_keytrie_node = &other_keytrie.get_children()[*other_index]; - match other_child_keytrie_node { - KeyTrieNode::KeyTrie(other_child_keytrie) => { - if let Some(self_index) = self.child_order.get(other_key_event) { - if let KeyTrieNode::KeyTrie(ref mut self_clashing_child_key_trie) = - self.children[*self_index] - { - self_clashing_child_key_trie.merge_keytrie(other_child_keytrie.clone()); - } else { - self.children[*self_index] = other_child_keytrie_node.clone(); - } - } else { - self.child_order - .insert(*other_key_event, self.children.len()); - self.children - .push(KeyTrieNode::KeyTrie(other_child_keytrie.clone())); - } - } - KeyTrieNode::MappableCommand(_) | KeyTrieNode::CommandSequence(_) => { - if let Some(existing_index) = self.child_order.get(other_key_event) { - self.children[*existing_index] = other_child_keytrie_node.clone(); - } else { - self.child_order - .insert(*other_key_event, self.children.len()); - self.children.push(other_child_keytrie_node.clone()); + if let Some(existing_index) = self.child_order.get(other_key_event) { + if let KeyTrieNode::KeyTrie(ref mut self_clashing_child_key_trie) = + self.children[*existing_index] + { + if let KeyTrieNode::KeyTrie(other_child_keytrie) = other_child_keytrie_node { + self_clashing_child_key_trie.merge_keytrie(other_child_keytrie.clone()); + continue; } } + self.children[*existing_index] = other_child_keytrie_node.clone(); + } else { + self.child_order + .insert(*other_key_event, self.children.len()); + self.children.push(other_child_keytrie_node.clone()); } } } From 159150b24a4b01c0a65479139c45fc115351d0ff Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Wed, 15 Feb 2023 13:17:22 +0100 Subject: [PATCH 101/105] replace custom join with vec::join --- helix-term/src/commands.rs | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 1d98bdee9041..4d9091e3e958 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -2605,7 +2605,7 @@ impl ui::menu::Item for MappableCommand { ]; match command_list.get(name as &String) { Some(key_events) => { - row[1] = Cell::from(format_key_events(key_events)); + row[1] = Cell::from(key_events.join(", ")); } None => {} } @@ -2619,25 +2619,13 @@ impl ui::menu::Item for MappableCommand { let mut row: Vec = vec![Cell::from(*name), Cell::from(""), Cell::from(*doc)]; match command_list.get(*name) { Some(key_events) => { - row[1] = Cell::from(format_key_events(key_events)); + row[1] = Cell::from(key_events.join(", ")); } None => {} } return Row::new(row); } } - - // TODO: Generalize into a Vec Display implemention? - fn format_key_events(key_events: &Vec) -> String { - let mut result_string: String = String::new(); - for key_event in key_events { - if !result_string.is_empty() { - result_string.push_str(", "); - } - result_string.push_str(key_event); - } - result_string - } } } From 683931a8cd6feff5e0b92c96aae2beade3c14a8a Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Wed, 15 Feb 2023 14:17:48 +0100 Subject: [PATCH 102/105] revert some cleanup of use statements ...in the hopes that it simplifies review. The changes should have probably be codified into `cargo fmt`. --- helix-term/Cargo.toml | 1 + helix-term/src/application.rs | 39 +++++++++++--------- helix-term/src/commands.rs | 63 +++++++++++++++++--------------- helix-term/src/commands/typed.rs | 3 +- 4 files changed, 58 insertions(+), 48 deletions(-) diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml index d4df572fdb13..603f37d39ab8 100644 --- a/helix-term/Cargo.toml +++ b/helix-term/Cargo.toml @@ -36,6 +36,7 @@ helix-loader = { version = "0.6", path = "../helix-loader" } anyhow = "1" once_cell = "1.17" + which = "4.4" tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] } diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 2ede96bad019..74052c8b7d69 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -1,3 +1,13 @@ +use arc_swap::{access::Map, ArcSwap}; +use futures_util::Stream; +use helix_core::{ + diagnostic::{DiagnosticTag, NumberOrString}, + path::get_relative_path, + pos_at_coords, syntax, Selection, +}; +use serde_json::json; +use tui::backend::Backend; + use crate::{ args::Args, commands::apply_workspace_edit, @@ -7,22 +17,7 @@ use crate::{ keymap::Keymap, ui::{self, overlay::overlayed}, }; -use anyhow::{Context, Error}; -use arc_swap::{access::Map, ArcSwap}; -use crossterm::{ - event::{ - DisableBracketedPaste, DisableFocusChange, DisableMouseCapture, EnableBracketedPaste, - EnableFocusChange, EnableMouseCapture, Event as CrosstermEvent, - }, - execute, terminal, - tty::IsTty, -}; -use futures_util::Stream; -use helix_core::{ - diagnostic::{DiagnosticTag, NumberOrString}, - path::get_relative_path, - pos_at_coords, syntax, Selection, -}; + use helix_lsp::{lsp, util::lsp_pos_to_pos, LspProgressMap}; use helix_view::{ align_view, @@ -34,14 +29,22 @@ use helix_view::{ Align, Editor, }; use log::{debug, error, warn}; -use serde_json::json; use std::{ io::{stdin, stdout, Write}, sync::Arc, time::{Duration, Instant}, }; -use tui::backend::Backend; +use anyhow::{Context, Error}; + +use crossterm::{ + event::{ + DisableBracketedPaste, DisableFocusChange, DisableMouseCapture, EnableBracketedPaste, + EnableFocusChange, EnableMouseCapture, Event as CrosstermEvent, + }, + execute, terminal, + tty::IsTty, +}; #[cfg(not(windows))] use { signal_hook::{consts::signal, low_level}, diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 4d9091e3e958..b5e876b21712 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -3,29 +3,10 @@ pub(crate) mod lsp; pub(crate) mod typed; pub use dap::*; +use helix_vcs::Hunk; pub use lsp::*; pub use typed::*; -use crate::{ - args, - commands::insert::*, - compositor::{self, Component, Compositor}, - filter_picker_entry, - job::{self, Callback, Jobs}, - keymap::CommandList, - ui::{ - self, - menu::{Cell, Row}, - overlay::overlayed, - FilePicker, Picker, Popup, Prompt, PromptEvent, - }, -}; - -use anyhow::{anyhow, bail, ensure, Context as _}; -use futures_util::StreamExt; -use fuzzy_matcher::FuzzyMatcher; -use grep_regex::RegexMatcherBuilder; -use grep_searcher::{sinks, BinaryDetection, SearcherBuilder}; use helix_core::{ char_idx_at_visual_offset, comment, doc_formatter::TextFormat, @@ -35,7 +16,7 @@ use helix_core::{ indent::IndentStyle, line_ending::{get_line_ending_of_str, line_end_char_index, str_is_line_ending}, match_brackets, - movement::{self, move_vertically_visual, Direction, Movement}, + movement::{self, move_vertically_visual, Direction}, object, pos_at_coords, regex::{self, Regex, RegexBuilder}, search::{self, CharMatcher}, @@ -47,7 +28,6 @@ use helix_core::{ visual_offset_from_block, LineEnding, Position, Range, Rope, RopeGraphemes, RopeSlice, Selection, SmallVec, Tendril, Transaction, }; -use helix_vcs::Hunk; use helix_view::{ clipboard::ClipboardType, document::{FormatterError, Mode, SCRATCH_BUFFER_NAME}, @@ -59,17 +39,42 @@ use helix_view::{ view::View, Document, DocumentId, Editor, ViewId, }; -use ignore::{DirEntry, WalkBuilder, WalkState}; -use once_cell::sync::Lazy; -use serde::de::{self, Deserialize, Deserializer}; + +use anyhow::{anyhow, bail, ensure, Context as _}; +use fuzzy_matcher::FuzzyMatcher; +use insert::*; +use movement::Movement; + +use crate::{ + args, + compositor::{self, Component, Compositor}, + filter_picker_entry, + job::Callback, + keymap::CommandList, + ui::{ + self, + menu::{Cell, Row}, + overlay::overlayed, + FilePicker, Picker, Popup, Prompt, PromptEvent, + }, +}; + +use crate::job::{self, Jobs}; +use futures_util::StreamExt; +use std::{collections::HashMap, fmt, future::Future}; +use std::{collections::HashSet, num::NonZeroUsize}; + use std::{ borrow::Cow, - collections::{HashMap, HashSet}, - fmt, - future::Future, - num::NonZeroUsize, path::{Path, PathBuf}, }; + +use once_cell::sync::Lazy; +use serde::de::{self, Deserialize, Deserializer}; + +use grep_regex::RegexMatcherBuilder; +use grep_searcher::{sinks, BinaryDetection, SearcherBuilder}; +use ignore::{DirEntry, WalkBuilder, WalkState}; use tokio_stream::wrappers::UnboundedReceiverStream; pub type OnKeyCallback = Box; diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 241d281808ac..740d978db9cc 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -1,9 +1,10 @@ use std::fmt::Write; use std::ops::Deref; -use super::*; use crate::job::*; +use super::*; + use helix_core::encoding; use helix_view::editor::{Action, CloseError, ConfigEvent}; use serde_json::Value; From e98afd29abc1119381452077e1dcfbfab9ae2613 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Wed, 15 Feb 2023 14:50:47 +0100 Subject: [PATCH 103/105] improve keymap deserialize error handlig Replace two `.expect()` with returning Err. Using the let else pattern for this. Only became stable in 1.65.0, hence the bump. --- helix-term/src/keymap/keytrienode.rs | 12 ++++++------ rust-toolchain.toml | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/helix-term/src/keymap/keytrienode.rs b/helix-term/src/keymap/keytrienode.rs index 2a911b935652..3605dde07c87 100644 --- a/helix-term/src/keymap/keytrienode.rs +++ b/helix-term/src/keymap/keytrienode.rs @@ -145,18 +145,18 @@ impl<'de> Visitor<'de> for KeyTrieNodeVisitor { Ok(KeyTrieNode::KeyTrie(keytrie)) }; - let first_key = map - .next_key::()? - .expect("Maps without keys are undefined keymap remapping behaviour."); + let Some(first_key) = map.next_key::()? else { + return Err(serde::de::Error::custom("Maps without keys are undefined keymap remapping behaviour.")) + }; if first_key != "description" { return into_keytrie(first_key, map, ""); } let description = map.next_value::()?; - let second_key = map - .next_key::()? - .expect("Associated key when a 'description' key is provided"); + let Some(second_key) = map.next_key::()? else { + return Err(serde::de::Error::custom("Associated key when a 'description' key is provided")) + }; if &second_key != "exec" { return into_keytrie(second_key, map, &description); diff --git a/rust-toolchain.toml b/rust-toolchain.toml index ace4f5f96e55..2abc5665236b 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "1.63.0" +channel = "1.65.0" components = ["rustfmt", "rust-src"] From d32682714b597c069f27d36382dc3bbb5fb62ca6 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Fri, 17 Feb 2023 00:17:53 +0100 Subject: [PATCH 104/105] fix clippy lints --- helix-term/src/commands.rs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index b5e876b21712..d8a07961ace0 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -2608,13 +2608,10 @@ impl ui::menu::Item for MappableCommand { Cell::from(""), Cell::from(description.as_str()), ]; - match command_list.get(name as &String) { - Some(key_events) => { - row[1] = Cell::from(key_events.join(", ")); - } - None => {} + if let Some(key_events) = command_list.get(name as &String) { + row[1] = Cell::from(key_events.join(", ")); } - return Row::new(row); + Row::new(row) } MappableCommand::Static { description: doc, @@ -2622,13 +2619,10 @@ impl ui::menu::Item for MappableCommand { .. } => { let mut row: Vec = vec![Cell::from(*name), Cell::from(""), Cell::from(*doc)]; - match command_list.get(*name) { - Some(key_events) => { - row[1] = Cell::from(key_events.join(", ")); - } - None => {} + if let Some(key_events) = command_list.get(*name) { + row[1] = Cell::from(key_events.join(", ")); } - return Row::new(row); + Row::new(row) } } } From a0d918a47a7fa7ddeaf07963e148ce4993bb9020 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Fri, 17 Feb 2023 01:18:33 +0100 Subject: [PATCH 105/105] custom description refinements * Allow override of predifinde infobox descriptions. * Make sure that custom description overrider on infoboxes can be done without additional remappings of the same keytrie. --- helix-term/src/config.rs | 24 +++++++++ helix-term/src/keymap/keytrie.rs | 7 +++ helix-term/src/keymap/keytrienode.rs | 80 +++++++++++++++------------- 3 files changed, 74 insertions(+), 37 deletions(-) diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index 2259c3485ef7..339634a98859 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -196,6 +196,30 @@ mod tests { assert_eq!(parsed_node.get_description().unwrap(), "Buffer menu"); } + #[test] + fn parses_custom_infobox_label_override_from_toml() { + let sample_keymap = r#" + [keys.normal.space] + description = "To the moon" + b = "buffer_picker" + "#; + let parsed_node: KeyTrieNode = _normal_mode_keytrie_node("space", sample_keymap); + assert_eq!(parsed_node.get_description().unwrap(), "To the moon"); + } + + #[test] + fn parses_empty_custom_infobox_label_override_from_toml() { + let sample_keymap = r#" + [keys.normal.space] + description = "To the moon" + "#; + let parsed_node: KeyTrie = _normal_mode_keytrie("space", sample_keymap); + assert!( + parsed_node.get_children().len() > 2, + "Empty custom label override does not override other defualt mappings in keytrie." + ); + } + #[test] fn keys_resolve_to_correct_defaults() { let serde_default = toml::from_str::("").unwrap().keys; diff --git a/helix-term/src/keymap/keytrie.rs b/helix-term/src/keymap/keytrie.rs index 0bd4f71185c4..1d495c754cdd 100644 --- a/helix-term/src/keymap/keytrie.rs +++ b/helix-term/src/keymap/keytrie.rs @@ -13,6 +13,8 @@ pub struct KeyTrie { pub is_sticky: bool, /// Used to respect pre-defined stickyness. pub explicitly_set_sticky: bool, + /// Used to override pre-defined descriptions. + pub explicitly_set_description: bool, } impl KeyTrie { @@ -27,6 +29,7 @@ impl KeyTrie { children, is_sticky: false, explicitly_set_sticky: false, + explicitly_set_description: false, } } @@ -72,6 +75,10 @@ impl KeyTrie { self.is_sticky = other_keytrie.is_sticky; } + if other_keytrie.explicitly_set_description { + self.description = other_keytrie.description.clone(); + } + for (other_key_event, other_index) in other_keytrie.get_child_order() { let other_child_keytrie_node = &other_keytrie.get_children()[*other_index]; if let Some(existing_index) = self.child_order.get(other_key_event) { diff --git a/helix-term/src/keymap/keytrienode.rs b/helix-term/src/keymap/keytrienode.rs index 3605dde07c87..fe58c3a40104 100644 --- a/helix-term/src/keymap/keytrienode.rs +++ b/helix-term/src/keymap/keytrienode.rs @@ -116,54 +116,55 @@ impl<'de> Visitor<'de> for KeyTrieNodeVisitor { where M: serde::de::MapAccess<'de>, { - let into_keytrie = - |peeked_key: String, mut map: M, description: &str| -> Result { - let mut children = Vec::new(); - let mut child_order = HashMap::new(); - let mut keytrie_is_sticky = false; - let mut user_explicit_sticky = false; - let mut next_key = Some(peeked_key); - - while let Some(ref peeked_key) = next_key { - if peeked_key == "sticky" { - keytrie_is_sticky = map.next_value::()?; - user_explicit_sticky = true; - } else { - let key_event = peeked_key - .parse::() - .map_err(serde::de::Error::custom)?; - let keytrie_node = map.next_value::()?; - child_order.insert(key_event, children.len()); - children.push(keytrie_node); - } - next_key = map.next_key::()?; + let into_keytrie = |peeked_key: String, + mut map: M, + description: &str, + user_explicit_description: bool| + -> Result { + let mut children = Vec::new(); + let mut child_order = HashMap::new(); + let mut keytrie_is_sticky = false; + let mut user_explicit_sticky = false; + let mut next_key = Some(peeked_key); + + while let Some(ref peeked_key) = next_key { + if peeked_key == "sticky" { + keytrie_is_sticky = map.next_value::()?; + user_explicit_sticky = true; + } else { + let key_event = peeked_key + .parse::() + .map_err(serde::de::Error::custom)?; + let keytrie_node = map.next_value::()?; + child_order.insert(key_event, children.len()); + children.push(keytrie_node); } + next_key = map.next_key::()?; + } - let mut keytrie = KeyTrie::new(description, child_order, children); - keytrie.is_sticky = keytrie_is_sticky; - keytrie.explicitly_set_sticky = user_explicit_sticky; - Ok(KeyTrieNode::KeyTrie(keytrie)) - }; + let mut keytrie = KeyTrie::new(description, child_order, children); + keytrie.is_sticky = keytrie_is_sticky; + keytrie.explicitly_set_sticky = user_explicit_sticky; + keytrie.explicitly_set_description = user_explicit_description; + Ok(KeyTrieNode::KeyTrie(keytrie)) + }; let Some(first_key) = map.next_key::()? else { return Err(serde::de::Error::custom("Maps without keys are undefined keymap remapping behaviour.")) }; if first_key != "description" { - return into_keytrie(first_key, map, ""); + return into_keytrie(first_key, map, "", false); } let description = map.next_value::()?; - let Some(second_key) = map.next_key::()? else { - return Err(serde::de::Error::custom("Associated key when a 'description' key is provided")) - }; - if &second_key != "exec" { - return into_keytrie(second_key, map, &description); - } - - let keytrie_node: KeyTrieNode = map.next_value::()?; - match keytrie_node { + if let Some(second_key) = map.next_key::()? { + if &second_key != "exec" { + return into_keytrie(second_key, map, &description, true); + } + let keytrie_node: KeyTrieNode = map.next_value::()?; + match keytrie_node { KeyTrieNode::KeyTrie(_) => Err(serde::de::Error::custom( "'exec' key reserved for command(s) only, omit when adding custom descriptions to nested remappings.", )), @@ -180,7 +181,7 @@ impl<'de> Visitor<'de> for KeyTrieNodeVisitor { Err(serde::de::Error::custom("Currently not possible to rename static commands, only typables. (Those that begin with a colon.) ")) } } - } + }, KeyTrieNode::CommandSequence(command_sequence) => { Ok(KeyTrieNode::CommandSequence(CommandSequence { description: Some(description), @@ -188,5 +189,10 @@ impl<'de> Visitor<'de> for KeyTrieNodeVisitor { })) } } + } else { + let mut keytrie_node = KeyTrie::new(&description, HashMap::new(), Vec::new()); + keytrie_node.explicitly_set_description = true; + Ok(KeyTrieNode::KeyTrie(keytrie_node)) + } } }