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