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.rs147
1 files changed, 110 insertions, 37 deletions
diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs
index a0f2be94..77bb187c 100644
--- a/helix-term/src/keymap.rs
+++ b/helix-term/src/keymap.rs
@@ -4,6 +4,7 @@ use helix_core::hashmap;
use helix_view::{document::Mode, info::Info, input::KeyEvent};
use serde::Deserialize;
use std::{
+ borrow::Cow,
collections::HashMap,
ops::{Deref, DerefMut},
};
@@ -47,13 +48,13 @@ macro_rules! keymap {
};
(@trie
- { $label:literal $($($key:literal)|+ => $value:tt,)+ }
+ { $label:literal $(sticky=$sticky:literal)? $($($key:literal)|+ => $value:tt,)+ }
) => {
- keymap!({ $label $($($key)|+ => $value,)+ })
+ keymap!({ $label $(sticky=$sticky)? $($($key)|+ => $value,)+ })
};
(
- { $label:literal $($($key:literal)|+ => $value:tt,)+ }
+ { $label:literal $(sticky=$sticky:literal)? $($($key:literal)|+ => $value:tt,)+ }
) => {
// modified from the hashmap! macro
{
@@ -70,7 +71,9 @@ macro_rules! keymap {
_order.push(_key);
)+
)*
- $crate::keymap::KeyTrie::Node($crate::keymap::KeyTrieNode::new($label, _map, _order))
+ let mut _node = $crate::keymap::KeyTrieNode::new($label, _map, _order);
+ $( _node.is_sticky = $sticky; )?
+ $crate::keymap::KeyTrie::Node(_node)
}
};
}
@@ -84,6 +87,8 @@ pub struct KeyTrieNode {
map: HashMap<KeyEvent, KeyTrie>,
#[serde(skip)]
order: Vec<KeyEvent>,
+ #[serde(skip)]
+ pub is_sticky: bool,
}
impl KeyTrieNode {
@@ -92,6 +97,7 @@ impl KeyTrieNode {
name: name.to_string(),
map,
order,
+ is_sticky: false,
}
}
@@ -119,12 +125,10 @@ impl KeyTrieNode {
}
}
}
-}
-impl From<KeyTrieNode> for Info {
- fn from(node: KeyTrieNode) -> Self {
- let mut body: Vec<(&str, Vec<KeyEvent>)> = Vec::with_capacity(node.len());
- for (&key, trie) in node.iter() {
+ pub fn infobox(&self) -> Info {
+ let mut body: Vec<(&str, Vec<KeyEvent>)> = Vec::with_capacity(self.len());
+ for (&key, trie) in self.iter() {
let desc = match trie {
KeyTrie::Leaf(cmd) => cmd.doc(),
KeyTrie::Node(n) => n.name(),
@@ -136,16 +140,16 @@ impl From<KeyTrieNode> for Info {
}
}
body.sort_unstable_by_key(|(_, keys)| {
- node.order.iter().position(|&k| k == keys[0]).unwrap()
+ self.order.iter().position(|&k| k == keys[0]).unwrap()
});
- let prefix = format!("{} ", node.name());
+ 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::new(node.name(), body)
+ Info::new(self.name(), body)
}
}
@@ -218,7 +222,7 @@ impl KeyTrie {
}
#[derive(Debug, Clone, PartialEq)]
-pub enum KeymapResult {
+pub enum KeymapResultKind {
/// Needs more keys to execute a command. Contains valid keys for next keystroke.
Pending(KeyTrieNode),
Matched(Command),
@@ -229,14 +233,31 @@ pub enum KeymapResult {
Cancelled(Vec<KeyEvent>),
}
+/// Returned after looking up a key in [`Keymap`]. The `sticky` field has a
+/// reference to the sticky node if one is currently active.
+pub struct KeymapResult<'a> {
+ pub kind: KeymapResultKind,
+ pub sticky: Option<&'a KeyTrieNode>,
+}
+
+impl<'a> KeymapResult<'a> {
+ pub fn new(kind: KeymapResultKind, sticky: Option<&'a KeyTrieNode>) -> Self {
+ Self { kind, sticky }
+ }
+}
+
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct Keymap {
/// Always a Node
#[serde(flatten)]
root: KeyTrie,
- /// Stores pending keys waiting for the next key
+ /// Stores pending keys waiting for the next key. This is relative to a
+ /// sticky node if one is in use.
#[serde(skip)]
state: Vec<KeyEvent>,
+ /// Stores the sticky node if one is activated.
+ #[serde(skip)]
+ sticky: Option<KeyTrieNode>,
}
impl Keymap {
@@ -244,6 +265,7 @@ impl Keymap {
Keymap {
root,
state: Vec::new(),
+ sticky: None,
}
}
@@ -251,27 +273,61 @@ impl Keymap {
&self.root
}
+ pub fn sticky(&self) -> Option<&KeyTrieNode> {
+ self.sticky.as_ref()
+ }
+
/// Returns list of keys waiting to be disambiguated.
pub fn pending(&self) -> &[KeyEvent] {
&self.state
}
- /// Lookup `key` in the keymap to try and find a command to execute
+ /// 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, key: KeyEvent) -> KeymapResult {
- let &first = self.state.get(0).unwrap_or(&key);
- let trie = match self.root.search(&[first]) {
- Some(&KeyTrie::Leaf(cmd)) => return KeymapResult::Matched(cmd),
- None => return KeymapResult::NotFound,
+ if let key!(Esc) = key {
+ if !self.state.is_empty() {
+ return KeymapResult::new(
+ // Note that Esc is not included here
+ KeymapResultKind::Cancelled(self.state.drain(..).collect()),
+ self.sticky(),
+ );
+ }
+ 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(&self.root),
+ };
+
+ let trie = match trie_node.search(&[*first]) {
+ Some(&KeyTrie::Leaf(cmd)) => {
+ return KeymapResult::new(KeymapResultKind::Matched(cmd), self.sticky())
+ }
+ None => return KeymapResult::new(KeymapResultKind::NotFound, self.sticky()),
Some(t) => t,
};
+
self.state.push(key);
match trie.search(&self.state[1..]) {
- Some(&KeyTrie::Node(ref map)) => KeymapResult::Pending(map.clone()),
- Some(&KeyTrie::Leaf(command)) => {
+ Some(&KeyTrie::Node(ref map)) => {
+ if map.is_sticky {
+ self.state.clear();
+ self.sticky = Some(map.clone());
+ }
+ KeymapResult::new(KeymapResultKind::Pending(map.clone()), self.sticky())
+ }
+ Some(&KeyTrie::Leaf(cmd)) => {
self.state.clear();
- KeymapResult::Matched(command)
+ return KeymapResult::new(KeymapResultKind::Matched(cmd), self.sticky());
}
- None => KeymapResult::Cancelled(self.state.drain(..).collect()),
+ None => KeymapResult::new(
+ KeymapResultKind::Cancelled(self.state.drain(..).collect()),
+ self.sticky(),
+ ),
}
}
@@ -380,7 +436,6 @@ impl Default for Keymaps {
"A" => append_to_line,
"o" => open_below,
"O" => open_above,
- // [<space> ]<space> equivalents too (add blank new line, no edit)
"d" => delete_selection,
// TODO: also delete without yanking
@@ -440,12 +495,11 @@ impl Default for Keymaps {
"<" => unindent,
"=" => format_selections,
"J" => join_selections,
- // TODO: conflicts hover/doc
"K" => keep_selections,
// TODO: and another method for inverse
- // TODO: clashes with space mode
- "space" => keep_primary_selection,
+ "," => keep_primary_selection,
+ "A-," => remove_primary_selection,
// "q" => record_macro,
// "Q" => replay_macro,
@@ -473,7 +527,6 @@ impl Default for Keymaps {
// move under <space>c
"C-c" => toggle_comments,
- "K" => hover,
// z family for save/restore/combine from/to sels from register
@@ -516,7 +569,8 @@ impl Default for Keymaps {
"p" => paste_clipboard_after,
"P" => paste_clipboard_before,
"R" => replace_selections_with_clipboard,
- "space" => keep_primary_selection,
+ "/" => global_search,
+ "k" => hover,
},
"z" => { "View"
"z" | "c" => align_view_center,
@@ -525,6 +579,22 @@ impl Default for Keymaps {
"m" => align_view_middle,
"k" => scroll_up,
"j" => scroll_down,
+ "b" => page_up,
+ "f" => page_down,
+ "u" => half_page_up,
+ "d" => half_page_down,
+ },
+ "Z" => { "View" sticky=true
+ "z" | "c" => align_view_center,
+ "t" => align_view_top,
+ "b" => align_view_bottom,
+ "m" => align_view_middle,
+ "k" => scroll_up,
+ "j" => scroll_down,
+ "b" => page_up,
+ "f" => page_down,
+ "u" => half_page_up,
+ "d" => half_page_down,
},
"\"" => select_register,
@@ -545,14 +615,17 @@ impl Default for Keymaps {
"w" => extend_next_word_start,
"b" => extend_prev_word_start,
"e" => extend_next_word_end,
+ "W" => extend_next_long_word_start,
+ "B" => extend_prev_long_word_start,
+ "E" => extend_next_long_word_end,
"t" => extend_till_char,
"f" => extend_next_char,
"T" => extend_till_prev_char,
"F" => extend_prev_char,
- "home" => goto_line_start,
- "end" => goto_line_end,
+ "home" => extend_to_line_start,
+ "end" => extend_to_line_end,
"esc" => exit_select_mode,
"v" => normal_mode,
@@ -617,19 +690,19 @@ fn merge_partial_keys() {
let keymap = merged_config.keys.0.get_mut(&Mode::Normal).unwrap();
assert_eq!(
- keymap.get(key!('i')),
- KeymapResult::Matched(Command::normal_mode),
+ keymap.get(key!('i')).kind,
+ KeymapResultKind::Matched(Command::normal_mode),
"Leaf should replace leaf"
);
assert_eq!(
- keymap.get(key!('无')),
- KeymapResult::Matched(Command::insert_mode),
+ 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')),
- KeymapResult::Matched(Command::jump_backward),
+ keymap.get(key!('z')).kind,
+ KeymapResultKind::Matched(Command::jump_backward),
"Leaf should replace node"
);
// Assumes that `g` is a node in default keymap