aboutsummaryrefslogtreecommitdiff
path: root/helix-term/src/keymap.rs
diff options
context:
space:
mode:
Diffstat (limited to 'helix-term/src/keymap.rs')
-rw-r--r--helix-term/src/keymap.rs228
1 files changed, 150 insertions, 78 deletions
diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs
index e344457c..35dbce2f 100644
--- a/helix-term/src/keymap.rs
+++ b/helix-term/src/keymap.rs
@@ -5,20 +5,20 @@ use helix_view::{document::Mode, info::Info, input::KeyEvent};
use serde::Deserialize;
use std::{
borrow::Cow,
- collections::HashMap,
+ collections::{BTreeSet, HashMap},
ops::{Deref, DerefMut},
};
#[macro_export]
macro_rules! key {
($key:ident) => {
- KeyEvent {
+ ::helix_view::input::KeyEvent {
code: ::helix_view::keyboard::KeyCode::$key,
modifiers: ::helix_view::keyboard::KeyModifiers::NONE,
}
};
($($ch:tt)*) => {
- KeyEvent {
+ ::helix_view::input::KeyEvent {
code: ::helix_view::keyboard::KeyCode::Char($($ch)*),
modifiers: ::helix_view::keyboard::KeyModifiers::NONE,
}
@@ -78,19 +78,30 @@ macro_rules! keymap {
};
}
-#[derive(Debug, Clone, Deserialize)]
+#[derive(Debug, Clone)]
pub struct KeyTrieNode {
/// A label for keys coming under this node, like "Goto mode"
- #[serde(skip)]
name: String,
- #[serde(flatten)]
map: HashMap<KeyEvent, KeyTrie>,
- #[serde(skip)]
order: Vec<KeyEvent>,
- #[serde(skip)]
pub is_sticky: bool,
}
+impl<'de> Deserialize<'de> for KeyTrieNode {
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: serde::Deserializer<'de>,
+ {
+ let map = HashMap::<KeyEvent, KeyTrie>::deserialize(deserializer)?;
+ let order = map.keys().copied().collect::<Vec<_>>(); // NOTE: map.keys() has arbitrary order
+ Ok(Self {
+ map,
+ order,
+ ..Default::default()
+ })
+ }
+}
+
impl KeyTrieNode {
pub fn new(name: &str, map: HashMap<KeyEvent, KeyTrie>, order: Vec<KeyEvent>) -> Self {
Self {
@@ -118,7 +129,6 @@ impl KeyTrieNode {
}
self.map.insert(key, trie);
}
-
for &key in self.map.keys() {
if !self.order.contains(&key) {
self.order.push(key);
@@ -127,20 +137,29 @@ impl KeyTrieNode {
}
pub fn infobox(&self) -> Info {
- let mut body: Vec<(&str, Vec<KeyEvent>)> = 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::Leaf(cmd) => cmd.doc(),
+ KeyTrie::Leaf(cmd) => {
+ if cmd.name() == "no_op" {
+ continue;
+ }
+ cmd.doc()
+ }
KeyTrie::Node(n) => n.name(),
};
match body.iter().position(|(d, _)| d == &desc) {
- // FIXME: multiple keys are ordered randomly (use BTreeSet)
- Some(pos) => body[pos].1.push(key),
- None => body.push((desc, vec![key])),
+ Some(pos) => {
+ body[pos].1.insert(key);
+ }
+ None => body.push((desc, BTreeSet::from([key]))),
}
}
body.sort_unstable_by_key(|(_, keys)| {
- self.order.iter().position(|&k| k == keys[0]).unwrap()
+ 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)) {
@@ -151,6 +170,11 @@ impl KeyTrieNode {
}
Info::new(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 {
@@ -235,6 +259,7 @@ pub enum KeymapResultKind {
/// Returned after looking up a key in [`Keymap`]. The `sticky` field has a
/// reference to the sticky node if one is currently active.
+#[derive(Debug)]
pub struct KeymapResult<'a> {
pub kind: KeymapResultKind,
pub sticky: Option<&'a KeyTrieNode>,
@@ -395,6 +420,7 @@ impl Default for Keymaps {
"F" => find_prev_char,
"r" => replace,
"R" => replace_with_yanked,
+ "A-." => repeat_last_motion,
"~" => switch_case,
"`" => switch_to_lowercase,
@@ -427,6 +453,8 @@ impl Default for Keymaps {
"m" => goto_window_middle,
"b" => goto_window_bottom,
"a" => goto_last_accessed_file,
+ "n" => goto_next_buffer,
+ "p" => goto_previous_buffer,
},
":" => command_mode,
@@ -476,10 +504,9 @@ impl Default for Keymaps {
},
"/" => search,
- // ? for search_reverse
+ "?" => rsearch,
"n" => search_next,
- "N" => extend_search_next,
- // N for search_prev
+ "N" => search_prev,
"*" => search_selection,
"u" => undo,
@@ -520,9 +547,13 @@ impl Default for Keymaps {
"C-w" => { "Window"
"C-w" | "w" => rotate_view,
- "C-h" | "h" => hsplit,
+ "C-s" | "s" => hsplit,
"C-v" | "v" => vsplit,
"C-q" | "q" => wclose,
+ "C-h" | "h" | "left" => jump_view_left,
+ "C-j" | "j" | "down" => jump_view_down,
+ "C-k" | "k" | "up" => jump_view_up,
+ "C-l" | "l" | "right" => jump_view_right,
},
// move under <space>c
@@ -621,6 +652,9 @@ impl Default for Keymaps {
"B" => extend_prev_long_word_start,
"E" => extend_next_long_word_end,
+ "n" => extend_search_next,
+ "N" => extend_search_prev,
+
"t" => extend_till_char,
"f" => extend_next_char,
"T" => extend_till_prev_char,
@@ -669,63 +703,101 @@ pub fn merge_keys(mut config: Config) -> Config {
config
}
-#[test]
-fn merge_partial_keys() {
- let config = Config {
- keys: Keymaps(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 keymap = merged_config.keys.0.get_mut(&Mode::Normal).unwrap();
- assert_eq!(
- keymap.get(key!('i')).kind,
- KeymapResultKind::Matched(Command::normal_mode),
- "Leaf should replace leaf"
- );
- assert_eq!(
- keymap.get(key!('无')).kind,
- KeymapResultKind::Matched(Command::insert_mode),
- "New leaf should be present in merged keymap"
- );
- // Assumes that z is a node in the default keymap
- assert_eq!(
- keymap.get(key!('z')).kind,
- KeymapResultKind::Matched(Command::jump_backward),
- "Leaf should replace node"
- );
- // Assumes that `g` is a node in default keymap
- assert_eq!(
- keymap.root().search(&[key!('g'), key!('$')]).unwrap(),
- &KeyTrie::Leaf(Command::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(Command::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(Command::goto_last_line),
- "Old leaves in subnode should be present in merged node"
- );
-
- assert!(merged_config.keys.0.get(&Mode::Normal).unwrap().len() > 1);
- assert!(merged_config.keys.0.get(&Mode::Insert).unwrap().len() > 0);
+#[cfg(test)]
+mod tests {
+ use super::*;
+ #[test]
+ fn merge_partial_keys() {
+ let config = Config {
+ keys: Keymaps(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 keymap = merged_config.keys.0.get_mut(&Mode::Normal).unwrap();
+ assert_eq!(
+ keymap.get(key!('i')).kind,
+ KeymapResultKind::Matched(Command::normal_mode),
+ "Leaf should replace leaf"
+ );
+ assert_eq!(
+ keymap.get(key!('无')).kind,
+ KeymapResultKind::Matched(Command::insert_mode),
+ "New leaf should be present in merged keymap"
+ );
+ // Assumes that z is a node in the default keymap
+ assert_eq!(
+ keymap.get(key!('z')).kind,
+ KeymapResultKind::Matched(Command::jump_backward),
+ "Leaf should replace node"
+ );
+ // Assumes that `g` is a node in default keymap
+ assert_eq!(
+ keymap.root().search(&[key!('g'), key!('$')]).unwrap(),
+ &KeyTrie::Leaf(Command::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(Command::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(Command::goto_last_line),
+ "Old leaves in subnode should be present in merged node"
+ );
+
+ assert!(merged_config.keys.0.get(&Mode::Normal).unwrap().len() > 1);
+ assert!(merged_config.keys.0.get(&Mode::Insert).unwrap().len() > 0);
+ }
+
+ #[test]
+ fn order_should_be_set() {
+ let config = Config {
+ keys: Keymaps(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.0.get_mut(&Mode::Normal).unwrap();
+ // Make sure mapping works
+ assert_eq!(
+ keymap
+ .root()
+ .search(&[key!(' '), key!('s'), key!('v')])
+ .unwrap(),
+ &KeyTrie::Leaf(Command::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().is_empty())
+ }
}