on master help sign in
Add a sticky mode for keymaps (#635)
183dcceGokul Soumya
helix-term/src/keymap.rs+84−29
@@ -4,6 +4,7 @@ use helix_core::hashmap;
4use helix_view::{document::Mode, info::Info, input::KeyEvent};
4use helix_view::{document::Mode, info::Info, input::KeyEvent};
5use serde::Deserialize;
5use serde::Deserialize;
6use std::{
6use std::{
7+ borrow::Cow,
7 collections::HashMap,
8 collections::HashMap,
8 ops::{Deref, DerefMut},
9 ops::{Deref, DerefMut},
9};
10};
@@ -47,13 +48,13 @@ macro_rules! keymap {
47 };
48 };
48
49
49 (@trie
50 (@trie
50− { $label:literal $($($key:literal)|+ => $value:tt,)+ }
51+ { $label:literal $(sticky=$sticky:literal)? $($($key:literal)|+ => $value:tt,)+ }
51 ) => {
52 ) => {
52− keymap!({ $label $($($key)|+ => $value,)+ })
53+ keymap!({ $label $(sticky=$sticky)? $($($key)|+ => $value,)+ })
53 };
54 };
54
55
55 (
56 (
56− { $label:literal $($($key:literal)|+ => $value:tt,)+ }
57+ { $label:literal $(sticky=$sticky:literal)? $($($key:literal)|+ => $value:tt,)+ }
57 ) => {
58 ) => {
58 // modified from the hashmap! macro
59 // modified from the hashmap! macro
59 {
60 {
@@ -70,7 +71,9 @@ macro_rules! keymap {
70 _order.push(_key);
71 _order.push(_key);
71 )+
72 )+
72 )*
73 )*
73− $crate::keymap::KeyTrie::Node($crate::keymap::KeyTrieNode::new($label, _map, _order))
74+ let mut _node = $crate::keymap::KeyTrieNode::new($label, _map, _order);
75+ $( _node.is_sticky = $sticky; )?
76+ $crate::keymap::KeyTrie::Node(_node)
74 }
77 }
75 };
78 };
76}
79}
@@ -84,6 +87,8 @@ pub struct KeyTrieNode {
84 map: HashMap<KeyEvent, KeyTrie>,
87 map: HashMap<KeyEvent, KeyTrie>,
85 #[serde(skip)]
88 #[serde(skip)]
86 order: Vec<KeyEvent>,
89 order: Vec<KeyEvent>,
90+ #[serde(skip)]
91+ pub is_sticky: bool,
87}
92}
88
93
89impl KeyTrieNode {
94impl KeyTrieNode {
@@ -92,6 +97,7 @@ impl KeyTrieNode {
92 name: name.to_string(),
97 name: name.to_string(),
93 map,
98 map,
94 order,
99 order,
100+ is_sticky: false,
95 }
101 }
96 }
102 }
97
103
@@ -119,12 +125,10 @@ impl KeyTrieNode {
119 }
125 }
120 }
126 }
121 }
127 }
122−}
123
128
124−impl From<KeyTrieNode> for Info {
129+ pub fn infobox(&self) -> Info {
125− fn from(node: KeyTrieNode) -> Self {
126− let mut body: Vec<(&str, Vec<KeyEvent>)> = Vec::with_capacity(node.len());
130+ let mut body: Vec<(&str, Vec<KeyEvent>)> = Vec::with_capacity(self.len());
127− for (&key, trie) in node.iter() {
131+ for (&key, trie) in self.iter() {
128 let desc = match trie {
132 let desc = match trie {
129 KeyTrie::Leaf(cmd) => cmd.doc(),
133 KeyTrie::Leaf(cmd) => cmd.doc(),
130 KeyTrie::Node(n) => n.name(),
134 KeyTrie::Node(n) => n.name(),
@@ -136,16 +140,16 @@ impl From<KeyTrieNode> for Info {
136 }
140 }
137 }
141 }
138 body.sort_unstable_by_key(|(_, keys)| {
142 body.sort_unstable_by_key(|(_, keys)| {
139− node.order.iter().position(|&k| k == keys[0]).unwrap()
143+ self.order.iter().position(|&k| k == keys[0]).unwrap()
140 });
144 });
141− let prefix = format!("{} ", node.name());
145+ let prefix = format!("{} ", self.name());
142 if body.iter().all(|(desc, _)| desc.starts_with(&prefix)) {
146 if body.iter().all(|(desc, _)| desc.starts_with(&prefix)) {
143 body = body
147 body = body
144 .into_iter()
148 .into_iter()
145 .map(|(desc, keys)| (desc.strip_prefix(&prefix).unwrap(), keys))
149 .map(|(desc, keys)| (desc.strip_prefix(&prefix).unwrap(), keys))
146 .collect();
150 .collect();
147 }
151 }
148− Info::new(node.name(), body)
152+ Info::new(self.name(), body)
149 }
153 }
150}
154}
151
155
@@ -218,7 +222,7 @@ impl KeyTrie {
218}
222}
219
223
220#[derive(Debug, Clone, PartialEq)]
224#[derive(Debug, Clone, PartialEq)]
221−pub enum KeymapResult {
225+pub enum KeymapResultKind {
222 /// Needs more keys to execute a command. Contains valid keys for next keystroke.
226 /// Needs more keys to execute a command. Contains valid keys for next keystroke.
223 Pending(KeyTrieNode),
227 Pending(KeyTrieNode),
224 Matched(Command),
228 Matched(Command),
@@ -229,14 +233,31 @@ pub enum KeymapResult {
229 Cancelled(Vec<KeyEvent>),
233 Cancelled(Vec<KeyEvent>),
230}
234}
231
235
236+/// Returned after looking up a key in [`Keymap`]. The `sticky` field has a
237+/// reference to the sticky node if one is currently active.
238+pub struct KeymapResult<'a> {
239+ pub kind: KeymapResultKind,
240+ pub sticky: Option<&'a KeyTrieNode>,
241+}
242+
243+impl<'a> KeymapResult<'a> {
244+ pub fn new(kind: KeymapResultKind, sticky: Option<&'a KeyTrieNode>) -> Self {
245+ Self { kind, sticky }
246+ }
247+}
248+
232#[derive(Debug, Clone, PartialEq, Deserialize)]
249#[derive(Debug, Clone, PartialEq, Deserialize)]
233pub struct Keymap {
250pub struct Keymap {
234 /// Always a Node
251 /// Always a Node
235 #[serde(flatten)]
252 #[serde(flatten)]
236 root: KeyTrie,
253 root: KeyTrie,
237− /// Stores pending keys waiting for the next key
254+ /// Stores pending keys waiting for the next key. This is relative to a
255+ /// sticky node if one is in use.
238 #[serde(skip)]
256 #[serde(skip)]
239 state: Vec<KeyEvent>,
257 state: Vec<KeyEvent>,
258+ /// Stores the sticky node if one is activated.
259+ #[serde(skip)]
260+ sticky: Option<KeyTrieNode>,
240}
261}
241
262
242impl Keymap {
263impl Keymap {
@@ -244,6 +265,7 @@ impl Keymap {
244 Keymap {
265 Keymap {
245 root,
266 root,
246 state: Vec::new(),
267 state: Vec::new(),
268+ sticky: None,
247 }
269 }
248 }
270 }
249
271
@@ -251,27 +273,60 @@ impl Keymap {
251 &self.root
273 &self.root
252 }
274 }
253
275
276+ pub fn sticky(&self) -> Option<&KeyTrieNode> {
277+ self.sticky.as_ref()
278+ }
279+
254 /// Returns list of keys waiting to be disambiguated.
280 /// Returns list of keys waiting to be disambiguated.
255 pub fn pending(&self) -> &[KeyEvent] {
281 pub fn pending(&self) -> &[KeyEvent] {
256 &self.state
282 &self.state
257 }
283 }
258
284
259− /// Lookup `key` in the keymap to try and find a command to execute
285+ /// Lookup `key` in the keymap to try and find a command to execute. Escape
286+ /// key cancels pending keystrokes. If there are no pending keystrokes but a
287+ /// sticky node is in use, it will be cleared.
260 pub fn get(&mut self, key: KeyEvent) -> KeymapResult {
288 pub fn get(&mut self, key: KeyEvent) -> KeymapResult {
289+ if let key!(Esc) = key {
290+ if self.state.is_empty() {
291+ self.sticky = None;
292+ }
293+ return KeymapResult::new(
294+ KeymapResultKind::Cancelled(self.state.drain(..).collect()),
295+ self.sticky(),
296+ );
297+ }
298+
261− let &first = self.state.get(0).unwrap_or(&key);
299+ let first = self.state.get(0).unwrap_or(&key);
262− let trie = match self.root.search(&[first]) {
300+ let trie_node = match self.sticky {
301+ Some(ref trie) => Cow::Owned(KeyTrie::Node(trie.clone())),
302+ None => Cow::Borrowed(&self.root),
303+ };
304+
305+ let trie = match trie_node.search(&[*first]) {
263− Some(&KeyTrie::Leaf(cmd)) => return KeymapResult::Matched(cmd),
306+ Some(&KeyTrie::Leaf(cmd)) => {
307+ return KeymapResult::new(KeymapResultKind::Matched(cmd), self.sticky())
308+ }
264− None => return KeymapResult::NotFound,
309+ None => return KeymapResult::new(KeymapResultKind::NotFound, self.sticky()),
265 Some(t) => t,
310 Some(t) => t,
266 };
311 };
312+
267 self.state.push(key);
313 self.state.push(key);
268 match trie.search(&self.state[1..]) {
314 match trie.search(&self.state[1..]) {
269− Some(&KeyTrie::Node(ref map)) => KeymapResult::Pending(map.clone()),
315+ Some(&KeyTrie::Node(ref map)) => {
316+ if map.is_sticky {
317+ self.state.clear();
318+ self.sticky = Some(map.clone());
319+ }
320+ KeymapResult::new(KeymapResultKind::Pending(map.clone()), self.sticky())
321+ }
270− Some(&KeyTrie::Leaf(command)) => {
322+ Some(&KeyTrie::Leaf(cmd)) => {
271 self.state.clear();
323 self.state.clear();
272− KeymapResult::Matched(command)
324+ return KeymapResult::new(KeymapResultKind::Matched(cmd), self.sticky());
273 }
325 }
326+ None => KeymapResult::new(
274− None => KeymapResult::Cancelled(self.state.drain(..).collect()),
327+ KeymapResultKind::Cancelled(self.state.drain(..).collect()),
328+ self.sticky(),
329+ ),
275 }
330 }
276 }
331 }
277
332
@@ -602,19 +657,19 @@ fn merge_partial_keys() {
602
657
603 let keymap = merged_config.keys.0.get_mut(&Mode::Normal).unwrap();
658 let keymap = merged_config.keys.0.get_mut(&Mode::Normal).unwrap();
604 assert_eq!(
659 assert_eq!(
605− keymap.get(key!('i')),
660+ keymap.get(key!('i')).kind,
606− KeymapResult::Matched(Command::normal_mode),
661+ KeymapResultKind::Matched(Command::normal_mode),
607 "Leaf should replace leaf"
662 "Leaf should replace leaf"
608 );
663 );
609 assert_eq!(
664 assert_eq!(
610− keymap.get(key!('无')),
665+ keymap.get(key!('无')).kind,
611− KeymapResult::Matched(Command::insert_mode),
666+ KeymapResultKind::Matched(Command::insert_mode),
612 "New leaf should be present in merged keymap"
667 "New leaf should be present in merged keymap"
613 );
668 );
614 // Assumes that z is a node in the default keymap
669 // Assumes that z is a node in the default keymap
615 assert_eq!(
670 assert_eq!(
616− keymap.get(key!('z')),
671+ keymap.get(key!('z')).kind,
617− KeymapResult::Matched(Command::jump_backward),
672+ KeymapResultKind::Matched(Command::jump_backward),
618 "Leaf should replace node"
673 "Leaf should replace node"
619 );
674 );
620 // Assumes that `g` is a node in default keymap
675 // Assumes that `g` is a node in default keymap
helix-term/src/ui/editor.rs+20−16
@@ -2,7 +2,7 @@ use crate::{
2 commands,
2 commands,
3 compositor::{Component, Context, EventResult},
3 compositor::{Component, Context, EventResult},
4 key,
4 key,
5− keymap::{KeymapResult, Keymaps},
5+ keymap::{KeymapResult, KeymapResultKind, Keymaps},
6 ui::{Completion, ProgressSpinners},
6 ui::{Completion, ProgressSpinners},
7};
7};
8
8
@@ -638,7 +638,7 @@ impl EditorView {
638
638
639 /// Handle events by looking them up in `self.keymaps`. Returns None
639 /// Handle events by looking them up in `self.keymaps`. Returns None
640 /// if event was handled (a command was executed or a subkeymap was
640 /// if event was handled (a command was executed or a subkeymap was
641− /// activated). Only KeymapResult::{NotFound, Cancelled} is returned
641+ /// activated). Only KeymapResultKind::{NotFound, Cancelled} is returned
642 /// otherwise.
642 /// otherwise.
643 fn handle_keymap_event(
643 fn handle_keymap_event(
644 &mut self,
644 &mut self,
@@ -647,29 +647,32 @@ impl EditorView {
647 event: KeyEvent,
647 event: KeyEvent,
648 ) -> Option<KeymapResult> {
648 ) -> Option<KeymapResult> {
649 self.autoinfo = None;
649 self.autoinfo = None;
650− match self.keymaps.get_mut(&mode).unwrap().get(event) {
650+ let key_result = self.keymaps.get_mut(&mode).unwrap().get(event);
651+ self.autoinfo = key_result.sticky.map(|node| node.infobox());
652+
653+ match &key_result.kind {
651− KeymapResult::Matched(command) => command.execute(cxt),
654+ KeymapResultKind::Matched(command) => command.execute(cxt),
652− KeymapResult::Pending(node) => self.autoinfo = Some(node.into()),
655+ KeymapResultKind::Pending(node) => self.autoinfo = Some(node.infobox()),
653− k @ KeymapResult::NotFound | k @ KeymapResult::Cancelled(_) => return Some(k),
656+ KeymapResultKind::NotFound | KeymapResultKind::Cancelled(_) => return Some(key_result),
654 }
657 }
655 None
658 None
656 }
659 }
657
660
658 fn insert_mode(&mut self, cx: &mut commands::Context, event: KeyEvent) {
661 fn insert_mode(&mut self, cx: &mut commands::Context, event: KeyEvent) {
659 if let Some(keyresult) = self.handle_keymap_event(Mode::Insert, cx, event) {
662 if let Some(keyresult) = self.handle_keymap_event(Mode::Insert, cx, event) {
660− match keyresult {
663+ match keyresult.kind {
661− KeymapResult::NotFound => {
664+ KeymapResultKind::NotFound => {
662 if let Some(ch) = event.char() {
665 if let Some(ch) = event.char() {
663 commands::insert::insert_char(cx, ch)
666 commands::insert::insert_char(cx, ch)
664 }
667 }
665 }
668 }
666− KeymapResult::Cancelled(pending) => {
669+ KeymapResultKind::Cancelled(pending) => {
667 for ev in pending {
670 for ev in pending {
668 match ev.char() {
671 match ev.char() {
669 Some(ch) => commands::insert::insert_char(cx, ch),
672 Some(ch) => commands::insert::insert_char(cx, ch),
670 None => {
673 None => {
671− if let KeymapResult::Matched(command) =
674+ if let KeymapResultKind::Matched(command) =
672− self.keymaps.get_mut(&Mode::Insert).unwrap().get(ev)
675+ self.keymaps.get_mut(&Mode::Insert).unwrap().get(ev).kind
673 {
676 {
674 command.execute(cx);
677 command.execute(cx);
675 }
678 }
@@ -976,11 +979,12 @@ impl Component for EditorView {
976 // how we entered insert mode is important, and we should track that so
979 // how we entered insert mode is important, and we should track that so
977 // we can repeat the side effect.
980 // we can repeat the side effect.
978
981
982+ self.last_insert.0 =
979− self.last_insert.0 = match self.keymaps.get_mut(&mode).unwrap().get(key) {
983+ match self.keymaps.get_mut(&mode).unwrap().get(key).kind {
980− KeymapResult::Matched(command) => command,
984+ KeymapResultKind::Matched(command) => command,
981− // FIXME: insert mode can only be entered through single KeyCodes
985+ // FIXME: insert mode can only be entered through single KeyCodes
982− _ => unimplemented!(),
986+ _ => unimplemented!(),
983− };
987+ };
984 self.last_insert.1.clear();
988 self.last_insert.1.clear();
985 }
989 }
986 (Mode::Insert, Mode::Normal) => {
990 (Mode::Insert, Mode::Normal) => {