1use crate::{Action, KeyContext, Keymap, KeymapVersion, Keystroke};
2use parking_lot::Mutex;
3use std::sync::Arc;
4
5pub struct KeystrokeMatcher {
6 pending_keystrokes: Vec<Keystroke>,
7 keymap: Arc<Mutex<Keymap>>,
8 keymap_version: KeymapVersion,
9}
10
11impl KeystrokeMatcher {
12 pub fn new(keymap: Arc<Mutex<Keymap>>) -> Self {
13 let keymap_version = keymap.lock().version();
14 Self {
15 pending_keystrokes: Vec::new(),
16 keymap_version,
17 keymap,
18 }
19 }
20
21 pub fn clear_pending(&mut self) {
22 self.pending_keystrokes.clear();
23 }
24
25 pub fn has_pending_keystrokes(&self) -> bool {
26 !self.pending_keystrokes.is_empty()
27 }
28
29 /// Pushes a keystroke onto the matcher.
30 /// The result of the new keystroke is returned:
31 /// KeyMatch::None =>
32 /// No match is valid for this key given any pending keystrokes.
33 /// KeyMatch::Pending =>
34 /// There exist bindings which are still waiting for more keys.
35 /// KeyMatch::Complete(matches) =>
36 /// One or more bindings have received the necessary key presses.
37 /// Bindings added later will take precedence over earlier bindings.
38 pub fn match_keystroke(
39 &mut self,
40 keystroke: &Keystroke,
41 context_stack: &[KeyContext],
42 ) -> KeyMatch {
43 let keymap = self.keymap.lock();
44 // Clear pending keystrokes if the keymap has changed since the last matched keystroke.
45 if keymap.version() != self.keymap_version {
46 self.keymap_version = keymap.version();
47 self.pending_keystrokes.clear();
48 }
49
50 let mut pending_key = None;
51 let mut found_actions = Vec::new();
52
53 for binding in keymap.bindings().rev() {
54 if !keymap.binding_enabled(binding, context_stack) {
55 continue;
56 }
57
58 for candidate in keystroke.match_candidates() {
59 self.pending_keystrokes.push(candidate.clone());
60 match binding.match_keystrokes(&self.pending_keystrokes) {
61 KeyMatch::Some(mut actions) => {
62 found_actions.append(&mut actions);
63 }
64 KeyMatch::Pending => {
65 pending_key.get_or_insert(candidate);
66 }
67 KeyMatch::None => {}
68 }
69 self.pending_keystrokes.pop();
70 }
71 }
72
73 if !found_actions.is_empty() {
74 self.pending_keystrokes.clear();
75 return KeyMatch::Some(found_actions);
76 }
77
78 if let Some(pending_key) = pending_key {
79 self.pending_keystrokes.push(pending_key);
80 }
81
82 if self.pending_keystrokes.is_empty() {
83 KeyMatch::None
84 } else {
85 KeyMatch::Pending
86 }
87 }
88}
89
90#[derive(Debug)]
91pub enum KeyMatch {
92 None,
93 Pending,
94 Some(Vec<Box<dyn Action>>),
95}
96
97impl KeyMatch {
98 pub fn is_some(&self) -> bool {
99 matches!(self, KeyMatch::Some(_))
100 }
101}
102
103// #[cfg(test)]
104// mod tests {
105// use anyhow::Result;
106// use serde::Deserialize;
107
108// use crate::{actions, impl_actions, keymap_matcher::ActionContext};
109
110// use super::*;
111
112// #[test]
113// fn test_keymap_and_view_ordering() -> Result<()> {
114// actions!(test, [EditorAction, ProjectPanelAction]);
115
116// let mut editor = ActionContext::default();
117// editor.add_identifier("Editor");
118
119// let mut project_panel = ActionContext::default();
120// project_panel.add_identifier("ProjectPanel");
121
122// // Editor 'deeper' in than project panel
123// let dispatch_path = vec![(2, editor), (1, project_panel)];
124
125// // But editor actions 'higher' up in keymap
126// let keymap = Keymap::new(vec![
127// Binding::new("left", EditorAction, Some("Editor")),
128// Binding::new("left", ProjectPanelAction, Some("ProjectPanel")),
129// ]);
130
131// let mut matcher = KeymapMatcher::new(keymap);
132
133// assert_eq!(
134// matcher.match_keystroke(Keystroke::parse("left")?, dispatch_path.clone()),
135// KeyMatch::Matches(vec![
136// (2, Box::new(EditorAction)),
137// (1, Box::new(ProjectPanelAction)),
138// ]),
139// );
140
141// Ok(())
142// }
143
144// #[test]
145// fn test_push_keystroke() -> Result<()> {
146// actions!(test, [B, AB, C, D, DA, E, EF]);
147
148// let mut context1 = ActionContext::default();
149// context1.add_identifier("1");
150
151// let mut context2 = ActionContext::default();
152// context2.add_identifier("2");
153
154// let dispatch_path = vec![(2, context2), (1, context1)];
155
156// let keymap = Keymap::new(vec![
157// Binding::new("a b", AB, Some("1")),
158// Binding::new("b", B, Some("2")),
159// Binding::new("c", C, Some("2")),
160// Binding::new("d", D, Some("1")),
161// Binding::new("d", D, Some("2")),
162// Binding::new("d a", DA, Some("2")),
163// ]);
164
165// let mut matcher = KeymapMatcher::new(keymap);
166
167// // Binding with pending prefix always takes precedence
168// assert_eq!(
169// matcher.match_keystroke(Keystroke::parse("a")?, dispatch_path.clone()),
170// KeyMatch::Pending,
171// );
172// // B alone doesn't match because a was pending, so AB is returned instead
173// assert_eq!(
174// matcher.match_keystroke(Keystroke::parse("b")?, dispatch_path.clone()),
175// KeyMatch::Matches(vec![(1, Box::new(AB))]),
176// );
177// assert!(!matcher.has_pending_keystrokes());
178
179// // Without an a prefix, B is dispatched like expected
180// assert_eq!(
181// matcher.match_keystroke(Keystroke::parse("b")?, dispatch_path.clone()),
182// KeyMatch::Matches(vec![(2, Box::new(B))]),
183// );
184// assert!(!matcher.has_pending_keystrokes());
185
186// // If a is prefixed, C will not be dispatched because there
187// // was a pending binding for it
188// assert_eq!(
189// matcher.match_keystroke(Keystroke::parse("a")?, dispatch_path.clone()),
190// KeyMatch::Pending,
191// );
192// assert_eq!(
193// matcher.match_keystroke(Keystroke::parse("c")?, dispatch_path.clone()),
194// KeyMatch::None,
195// );
196// assert!(!matcher.has_pending_keystrokes());
197
198// // If a single keystroke matches multiple bindings in the tree
199// // all of them are returned so that we can fallback if the action
200// // handler decides to propagate the action
201// assert_eq!(
202// matcher.match_keystroke(Keystroke::parse("d")?, dispatch_path.clone()),
203// KeyMatch::Matches(vec![(2, Box::new(D)), (1, Box::new(D))]),
204// );
205
206// // If none of the d action handlers consume the binding, a pending
207// // binding may then be used
208// assert_eq!(
209// matcher.match_keystroke(Keystroke::parse("a")?, dispatch_path.clone()),
210// KeyMatch::Matches(vec![(2, Box::new(DA))]),
211// );
212// assert!(!matcher.has_pending_keystrokes());
213
214// Ok(())
215// }
216
217// #[test]
218// fn test_keystroke_parsing() -> Result<()> {
219// assert_eq!(
220// Keystroke::parse("ctrl-p")?,
221// Keystroke {
222// key: "p".into(),
223// ctrl: true,
224// alt: false,
225// shift: false,
226// cmd: false,
227// function: false,
228// ime_key: None,
229// }
230// );
231
232// assert_eq!(
233// Keystroke::parse("alt-shift-down")?,
234// Keystroke {
235// key: "down".into(),
236// ctrl: false,
237// alt: true,
238// shift: true,
239// cmd: false,
240// function: false,
241// ime_key: None,
242// }
243// );
244
245// assert_eq!(
246// Keystroke::parse("shift-cmd--")?,
247// Keystroke {
248// key: "-".into(),
249// ctrl: false,
250// alt: false,
251// shift: true,
252// cmd: true,
253// function: false,
254// ime_key: None,
255// }
256// );
257
258// Ok(())
259// }
260
261// #[test]
262// fn test_context_predicate_parsing() -> Result<()> {
263// use KeymapContextPredicate::*;
264
265// assert_eq!(
266// KeymapContextPredicate::parse("a && (b == c || d != e)")?,
267// And(
268// Box::new(Identifier("a".into())),
269// Box::new(Or(
270// Box::new(Equal("b".into(), "c".into())),
271// Box::new(NotEqual("d".into(), "e".into())),
272// ))
273// )
274// );
275
276// assert_eq!(
277// KeymapContextPredicate::parse("!a")?,
278// Not(Box::new(Identifier("a".into())),)
279// );
280
281// Ok(())
282// }
283
284// #[test]
285// fn test_context_predicate_eval() {
286// let predicate = KeymapContextPredicate::parse("a && b || c == d").unwrap();
287
288// let mut context = ActionContext::default();
289// context.add_identifier("a");
290// assert!(!predicate.eval(&[context]));
291
292// let mut context = ActionContext::default();
293// context.add_identifier("a");
294// context.add_identifier("b");
295// assert!(predicate.eval(&[context]));
296
297// let mut context = ActionContext::default();
298// context.add_identifier("a");
299// context.add_key("c", "x");
300// assert!(!predicate.eval(&[context]));
301
302// let mut context = ActionContext::default();
303// context.add_identifier("a");
304// context.add_key("c", "d");
305// assert!(predicate.eval(&[context]));
306
307// let predicate = KeymapContextPredicate::parse("!a").unwrap();
308// assert!(predicate.eval(&[ActionContext::default()]));
309// }
310
311// #[test]
312// fn test_context_child_predicate_eval() {
313// let predicate = KeymapContextPredicate::parse("a && b > c").unwrap();
314// let contexts = [
315// context_set(&["e", "f"]),
316// context_set(&["c", "d"]), // match this context
317// context_set(&["a", "b"]),
318// ];
319
320// assert!(!predicate.eval(&contexts[0..]));
321// assert!(predicate.eval(&contexts[1..]));
322// assert!(!predicate.eval(&contexts[2..]));
323
324// let predicate = KeymapContextPredicate::parse("a && b > c && !d > e").unwrap();
325// let contexts = [
326// context_set(&["f"]),
327// context_set(&["e"]), // only match this context
328// context_set(&["c"]),
329// context_set(&["a", "b"]),
330// context_set(&["e"]),
331// context_set(&["c", "d"]),
332// context_set(&["a", "b"]),
333// ];
334
335// assert!(!predicate.eval(&contexts[0..]));
336// assert!(predicate.eval(&contexts[1..]));
337// assert!(!predicate.eval(&contexts[2..]));
338// assert!(!predicate.eval(&contexts[3..]));
339// assert!(!predicate.eval(&contexts[4..]));
340// assert!(!predicate.eval(&contexts[5..]));
341// assert!(!predicate.eval(&contexts[6..]));
342
343// fn context_set(names: &[&str]) -> ActionContext {
344// let mut keymap = ActionContext::new();
345// names
346// .iter()
347// .for_each(|name| keymap.add_identifier(name.to_string()));
348// keymap
349// }
350// }
351
352// #[test]
353// fn test_matcher() -> Result<()> {
354// #[derive(Clone, Deserialize, PartialEq, Eq, Debug)]
355// pub struct A(pub String);
356// impl_actions!(test, [A]);
357// actions!(test, [B, Ab, Dollar, Quote, Ess, Backtick]);
358
359// #[derive(Clone, Debug, Eq, PartialEq)]
360// struct ActionArg {
361// a: &'static str,
362// }
363
364// let keymap = Keymap::new(vec![
365// Binding::new("a", A("x".to_string()), Some("a")),
366// Binding::new("b", B, Some("a")),
367// Binding::new("a b", Ab, Some("a || b")),
368// Binding::new("$", Dollar, Some("a")),
369// Binding::new("\"", Quote, Some("a")),
370// Binding::new("alt-s", Ess, Some("a")),
371// Binding::new("ctrl-`", Backtick, Some("a")),
372// ]);
373
374// let mut context_a = ActionContext::default();
375// context_a.add_identifier("a");
376
377// let mut context_b = ActionContext::default();
378// context_b.add_identifier("b");
379
380// let mut matcher = KeymapMatcher::new(keymap);
381
382// // Basic match
383// assert_eq!(
384// matcher.match_keystroke(Keystroke::parse("a")?, vec![(1, context_a.clone())]),
385// KeyMatch::Matches(vec![(1, Box::new(A("x".to_string())))])
386// );
387// matcher.clear_pending();
388
389// // Multi-keystroke match
390// assert_eq!(
391// matcher.match_keystroke(Keystroke::parse("a")?, vec![(1, context_b.clone())]),
392// KeyMatch::Pending
393// );
394// assert_eq!(
395// matcher.match_keystroke(Keystroke::parse("b")?, vec![(1, context_b.clone())]),
396// KeyMatch::Matches(vec![(1, Box::new(Ab))])
397// );
398// matcher.clear_pending();
399
400// // Failed matches don't interfere with matching subsequent keys
401// assert_eq!(
402// matcher.match_keystroke(Keystroke::parse("x")?, vec![(1, context_a.clone())]),
403// KeyMatch::None
404// );
405// assert_eq!(
406// matcher.match_keystroke(Keystroke::parse("a")?, vec![(1, context_a.clone())]),
407// KeyMatch::Matches(vec![(1, Box::new(A("x".to_string())))])
408// );
409// matcher.clear_pending();
410
411// // Pending keystrokes are cleared when the context changes
412// assert_eq!(
413// matcher.match_keystroke(Keystroke::parse("a")?, vec![(1, context_b.clone())]),
414// KeyMatch::Pending
415// );
416// assert_eq!(
417// matcher.match_keystroke(Keystroke::parse("b")?, vec![(1, context_a.clone())]),
418// KeyMatch::None
419// );
420// matcher.clear_pending();
421
422// let mut context_c = ActionContext::default();
423// context_c.add_identifier("c");
424
425// // Pending keystrokes are maintained per-view
426// assert_eq!(
427// matcher.match_keystroke(
428// Keystroke::parse("a")?,
429// vec![(1, context_b.clone()), (2, context_c.clone())]
430// ),
431// KeyMatch::Pending
432// );
433// assert_eq!(
434// matcher.match_keystroke(Keystroke::parse("b")?, vec![(1, context_b.clone())]),
435// KeyMatch::Matches(vec![(1, Box::new(Ab))])
436// );
437
438// // handle Czech $ (option + 4 key)
439// assert_eq!(
440// matcher.match_keystroke(Keystroke::parse("alt-รง->$")?, vec![(1, context_a.clone())]),
441// KeyMatch::Matches(vec![(1, Box::new(Dollar))])
442// );
443
444// // handle Brazillian quote (quote key then space key)
445// assert_eq!(
446// matcher.match_keystroke(Keystroke::parse("space->\"")?, vec![(1, context_a.clone())]),
447// KeyMatch::Matches(vec![(1, Box::new(Quote))])
448// );
449
450// // handle ctrl+` on a brazillian keyboard
451// assert_eq!(
452// matcher.match_keystroke(Keystroke::parse("ctrl-->`")?, vec![(1, context_a.clone())]),
453// KeyMatch::Matches(vec![(1, Box::new(Backtick))])
454// );
455
456// // handle alt-s on a US keyboard
457// assert_eq!(
458// matcher.match_keystroke(Keystroke::parse("alt-s->ร")?, vec![(1, context_a.clone())]),
459// KeyMatch::Matches(vec![(1, Box::new(Ess))])
460// );
461
462// Ok(())
463// }
464// }