Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-term/src/keymap.rs')
-rw-r--r--helix-term/src/keymap.rs343
1 files changed, 166 insertions, 177 deletions
diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs
index d8227b50..4a131f0a 100644
--- a/helix-term/src/keymap.rs
+++ b/helix-term/src/keymap.rs
@@ -2,6 +2,7 @@ pub mod default;
pub mod macros;
pub use crate::commands::MappableCommand;
+use crate::config::Config;
use arc_swap::{
access::{DynAccess, DynGuard},
ArcSwap,
@@ -15,10 +16,10 @@ use std::{
sync::Arc,
};
-pub use default::default;
+use default::default;
use macros::key;
-#[derive(Debug, Clone, Default)]
+#[derive(Debug, Clone)]
pub struct KeyTrieNode {
/// A label for keys coming under this node, like "Goto mode"
name: String,
@@ -52,6 +53,10 @@ impl KeyTrieNode {
}
}
+ 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.
@@ -73,40 +78,49 @@ impl KeyTrieNode {
}
pub fn infobox(&self) -> Info {
- let mut body: Vec<(BTreeSet<KeyEvent>, &str)> = Vec::with_capacity(self.len());
+ let mut body: Vec<(&str, BTreeSet<KeyEvent>)> = Vec::with_capacity(self.len());
for (&key, trie) in self.iter() {
let desc = match trie {
- KeyTrie::MappableCommand(cmd) => {
+ KeyTrie::Leaf(cmd) => {
if cmd.name() == "no_op" {
continue;
}
cmd.doc()
}
- KeyTrie::Node(n) => &n.name,
+ 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].0.insert(key);
+ body[pos].1.insert(key);
}
- None => body.push((BTreeSet::from([key]), desc)),
+ None => body.push((desc, BTreeSet::from([key]))),
}
}
- body.sort_unstable_by_key(|(keys, _)| {
+ 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
+ .into_iter()
+ .map(|(desc, keys)| (desc.strip_prefix(&prefix).unwrap(), keys))
+ .collect();
+ }
+ 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()
+ }
+}
- let body: Vec<_> = body
- .into_iter()
- .map(|(events, desc)| {
- let events = events.iter().map(ToString::to_string).collect::<Vec<_>>();
- (events.join(", "), desc)
- })
- .collect();
- Info::new(self.name.clone(), &body)
+impl Default for KeyTrieNode {
+ fn default() -> Self {
+ Self::new("", HashMap::new(), Vec::new())
}
}
@@ -132,7 +146,7 @@ impl DerefMut for KeyTrieNode {
#[derive(Debug, Clone, PartialEq)]
pub enum KeyTrie {
- MappableCommand(MappableCommand),
+ Leaf(MappableCommand),
Sequence(Vec<MappableCommand>),
Node(KeyTrieNode),
}
@@ -161,7 +175,7 @@ impl<'de> serde::de::Visitor<'de> for KeyTrieVisitor {
{
command
.parse::<MappableCommand>()
- .map(KeyTrie::MappableCommand)
+ .map(KeyTrie::Leaf)
.map_err(E::custom)
}
@@ -170,26 +184,13 @@ impl<'de> serde::de::Visitor<'de> for KeyTrieVisitor {
S: serde::de::SeqAccess<'de>,
{
let mut commands = Vec::new();
- while let Some(command) = seq.next_element::<String>()? {
+ while let Some(command) = seq.next_element::<&str>()? {
commands.push(
command
.parse::<MappableCommand>()
.map_err(serde::de::Error::custom)?,
)
}
-
- // Prevent macro keybindings from being used in command sequences.
- // This is meant to be a temporary restriction pending a larger
- // refactor of how command sequences are executed.
- if commands
- .iter()
- .any(|cmd| matches!(cmd, MappableCommand::Macro { .. }))
- {
- return Err(serde::de::Error::custom(
- "macro keybindings may not be used in command sequences",
- ));
- }
-
Ok(KeyTrie::Sequence(commands))
}
@@ -208,44 +209,17 @@ impl<'de> serde::de::Visitor<'de> for KeyTrieVisitor {
}
impl KeyTrie {
- pub fn reverse_map(&self) -> ReverseKeymap {
- // recursively visit all nodes in keymap
- fn map_node(cmd_map: &mut ReverseKeymap, node: &KeyTrie, keys: &mut Vec<KeyEvent>) {
- match node {
- KeyTrie::MappableCommand(MappableCommand::Macro { .. }) => {}
- KeyTrie::MappableCommand(cmd) => {
- let name = cmd.name();
- if name != "no_op" {
- cmd_map.entry(name.into()).or_default().push(keys.clone())
- }
- }
- KeyTrie::Node(next) => {
- for (key, trie) in &next.map {
- keys.push(*key);
- map_node(cmd_map, trie, keys);
- keys.pop();
- }
- }
- KeyTrie::Sequence(_) => {}
- };
- }
-
- let mut res = HashMap::new();
- map_node(&mut res, self, &mut Vec::new());
- res
- }
-
pub fn node(&self) -> Option<&KeyTrieNode> {
match *self {
KeyTrie::Node(ref node) => Some(node),
- KeyTrie::MappableCommand(_) | KeyTrie::Sequence(_) => None,
+ KeyTrie::Leaf(_) | KeyTrie::Sequence(_) => None,
}
}
pub fn node_mut(&mut self) -> Option<&mut KeyTrieNode> {
match *self {
KeyTrie::Node(ref mut node) => Some(node),
- KeyTrie::MappableCommand(_) | KeyTrie::Sequence(_) => None,
+ KeyTrie::Leaf(_) | KeyTrie::Sequence(_) => None,
}
}
@@ -262,7 +236,7 @@ impl KeyTrie {
trie = match trie {
KeyTrie::Node(map) => map.get(key),
// leaf encountered while keys left to process
- KeyTrie::MappableCommand(_) | KeyTrie::Sequence(_) => None,
+ KeyTrie::Leaf(_) | KeyTrie::Sequence(_) => None,
}?
}
Some(trie)
@@ -283,11 +257,75 @@ pub enum KeymapResult {
Cancelled(Vec<KeyEvent>),
}
+#[derive(Debug, Clone, PartialEq, Deserialize)]
+#[serde(transparent)]
+pub struct Keymap {
+ /// Always a Node
+ root: KeyTrie,
+}
+
/// A map of command names to keybinds that will execute the command.
pub type ReverseKeymap = HashMap<String, Vec<Vec<KeyEvent>>>;
+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<KeyEvent>) {
+ match node {
+ KeyTrie::Leaf(cmd) => match cmd {
+ MappableCommand::Typable { name, .. } => {
+ cmd_map.entry(name.into()).or_default().push(keys.clone())
+ }
+ 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(_) => {}
+ };
+ }
+
+ 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()))
+ }
+}
+
pub struct Keymaps {
- pub map: Box<dyn DynAccess<HashMap<Mode, KeyTrie>>>,
+ pub map: Box<dyn DynAccess<HashMap<Mode, Keymap>>>,
/// Stores pending keys waiting for the next key. This is relative to a
/// sticky node if one is in use.
state: Vec<KeyEvent>,
@@ -296,7 +334,7 @@ pub struct Keymaps {
}
impl Keymaps {
- pub fn new(map: Box<dyn DynAccess<HashMap<Mode, KeyTrie>>>) -> Self {
+ pub fn new(map: Box<dyn DynAccess<HashMap<Mode, Keymap>>>) -> Self {
Self {
map,
state: Vec::new(),
@@ -304,7 +342,7 @@ impl Keymaps {
}
}
- pub fn map(&self) -> DynGuard<HashMap<Mode, KeyTrie>> {
+ pub fn map(&self) -> DynGuard<HashMap<Mode, Keymap>> {
self.map.load()
}
@@ -317,15 +355,6 @@ impl Keymaps {
self.sticky.as_ref()
}
- pub fn contains_key(&self, mode: Mode, key: KeyEvent) -> bool {
- let keymaps = &*self.map();
- let keymap = &keymaps[&mode];
- keymap
- .search(self.pending())
- .and_then(KeyTrie::node)
- .is_some_and(|node| node.contains_key(&key))
- }
-
/// 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.
@@ -342,14 +371,14 @@ impl Keymaps {
self.sticky = None;
}
- let first = self.state.first().unwrap_or(&key);
+ let first = self.state.get(0).unwrap_or(&key);
let trie_node = match self.sticky {
Some(ref trie) => Cow::Owned(KeyTrie::Node(trie.clone())),
- None => Cow::Borrowed(keymap),
+ None => Cow::Borrowed(&keymap.root),
};
let trie = match trie_node.search(&[*first]) {
- Some(KeyTrie::MappableCommand(ref cmd)) => {
+ Some(KeyTrie::Leaf(ref cmd)) => {
return KeymapResult::Matched(cmd.clone());
}
Some(KeyTrie::Sequence(ref cmds)) => {
@@ -368,7 +397,7 @@ impl Keymaps {
}
KeymapResult::Pending(map.clone())
}
- Some(KeyTrie::MappableCommand(cmd)) => {
+ Some(KeyTrie::Leaf(cmd)) => {
self.state.clear();
KeymapResult::Matched(cmd.clone())
}
@@ -388,14 +417,12 @@ impl Default for Keymaps {
}
/// Merge default config keys with user overwritten keys for custom user config.
-pub fn merge_keys(dst: &mut HashMap<Mode, KeyTrie>, mut delta: HashMap<Mode, KeyTrie>) {
- for (mode, keys) in dst {
- keys.merge_nodes(
- delta
- .remove(mode)
- .unwrap_or_else(|| KeyTrie::Node(KeyTrieNode::default())),
- )
+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())
}
+ config
}
#[cfg(test)]
@@ -422,22 +449,26 @@ mod tests {
#[test]
fn merge_partial_keys() {
- let keymap = hashmap! {
- Mode::Normal => keymap!({ "Normal mode"
- "i" => normal_mode,
- "无" => insert_mode,
- "z" => jump_backward,
- "g" => { "Merge into goto mode"
- "$" => goto_line_end,
- "g" => delete_char_forward,
- },
- })
+ 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_keyamp = default();
- merge_keys(&mut merged_keyamp, keymap.clone());
- assert_ne!(keymap, merged_keyamp);
+ let mut merged_config = merge_keys(config.clone());
+ assert_ne!(config, merged_config);
- let mut keymap = Keymaps::new(Box::new(Constant(merged_keyamp.clone())));
+ 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),
@@ -455,75 +486,68 @@ mod tests {
"Leaf should replace node"
);
- let keymap = merged_keyamp.get_mut(&Mode::Normal).unwrap();
+ let keymap = merged_config.keys.get_mut(&Mode::Normal).unwrap();
// Assumes that `g` is a node in default keymap
assert_eq!(
- keymap.search(&[key!('g'), key!('$')]).unwrap(),
- &KeyTrie::MappableCommand(MappableCommand::goto_line_end),
+ 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.search(&[key!('g'), key!('g')]).unwrap(),
- &KeyTrie::MappableCommand(MappableCommand::delete_char_forward),
+ 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.search(&[key!('g'), key!('e')]).unwrap(),
- &KeyTrie::MappableCommand(MappableCommand::goto_last_line),
+ 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_keyamp
- .get(&Mode::Normal)
- .and_then(|key_trie| key_trie.node())
- .unwrap()
- .len()
- > 1
- );
- assert!(
- merged_keyamp
- .get(&Mode::Insert)
- .and_then(|key_trie| key_trie.node())
- .unwrap()
- .len()
- > 0
- );
+ 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 keymap = hashmap! {
- Mode::Normal => keymap!({ "Normal mode"
- "space" => { ""
- "s" => { ""
- "v" => vsplit,
- "c" => hsplit,
- },
- },
- })
+ let config = Config {
+ keys: hashmap! {
+ Mode::Normal => Keymap::new(
+ keymap!({ "Normal mode"
+ "space" => { ""
+ "s" => { ""
+ "v" => vsplit,
+ "c" => hsplit,
+ },
+ },
+ })
+ )
+ },
+ ..Default::default()
};
- let mut merged_keyamp = default();
- merge_keys(&mut merged_keyamp, keymap.clone());
- assert_ne!(keymap, merged_keyamp);
- let keymap = merged_keyamp.get_mut(&Mode::Normal).unwrap();
+ let 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.search(&[key!(' '), key!('s'), key!('v')]).unwrap(),
- &KeyTrie::MappableCommand(MappableCommand::vsplit),
+ keymap
+ .root()
+ .search(&[key!(' '), key!('s'), key!('v')])
+ .unwrap(),
+ &KeyTrie::Leaf(MappableCommand::vsplit),
"Leaf should be present in merged subnode"
);
// Make sure an order was set during merge
- let node = keymap.search(&[crate::key!(' ')]).unwrap();
- assert!(!node.node().unwrap().order.as_slice().is_empty())
+ let node = keymap.root().search(&[crate::key!(' ')]).unwrap();
+ assert!(!node.node().unwrap().order().is_empty())
}
#[test]
fn aliased_modes_are_same_in_default_keymap() {
let keymaps = Keymaps::default().map();
- let root = keymaps.get(&Mode::Normal).unwrap();
+ let root = keymaps.get(&Mode::Normal).unwrap().root();
assert_eq!(
root.search(&[key!(' '), key!('w')]).unwrap(),
root.search(&["C-w".parse::<KeyEvent>().unwrap()]).unwrap(),
@@ -546,7 +570,7 @@ mod tests {
},
"j" | "k" => move_line_down,
});
- let keymap = normal_mode;
+ let keymap = Keymap::new(normal_mode);
let mut reverse_map = keymap.reverse_map();
// sort keybindings in order to have consistent tests
@@ -576,39 +600,4 @@ mod tests {
"Mismatch"
)
}
-
- #[test]
- fn escaped_keymap() {
- use crate::commands::MappableCommand;
- use helix_view::input::{KeyCode, KeyEvent, KeyModifiers};
-
- let keys = r#"
-"+" = [
- "select_all",
- ":pipe sed -E 's/\\s+$//g'",
-]
- "#;
-
- let key = KeyEvent {
- code: KeyCode::Char('+'),
- modifiers: KeyModifiers::NONE,
- };
-
- let expectation = KeyTrie::Node(KeyTrieNode::new(
- "",
- hashmap! {
- key => KeyTrie::Sequence(vec!{
- MappableCommand::select_all,
- MappableCommand::Typable {
- name: "pipe".to_string(),
- args: "sed -E 's/\\s+$//g'".to_string(),
- doc: "".to_string(),
- },
- })
- },
- vec![key],
- ));
-
- assert_eq!(toml::from_str(keys), Ok(expectation));
- }
}