keymap_matcher.rs

  1mod binding;
  2mod keymap;
  3mod keymap_context;
  4mod keystroke;
  5
  6use std::{any::TypeId, fmt::Debug};
  7
  8use collections::HashMap;
  9use smallvec::SmallVec;
 10
 11use crate::{Action, NoAction};
 12
 13pub use binding::{Binding, BindingMatchResult};
 14pub use keymap::Keymap;
 15pub use keymap_context::{KeymapContext, KeymapContextPredicate};
 16pub use keystroke::Keystroke;
 17
 18pub struct KeymapMatcher {
 19    pub contexts: Vec<KeymapContext>,
 20    pending_views: HashMap<usize, KeymapContext>,
 21    pending_keystrokes: Vec<Keystroke>,
 22    keymap: Keymap,
 23}
 24
 25impl KeymapMatcher {
 26    pub fn new(keymap: Keymap) -> Self {
 27        Self {
 28            contexts: Vec::new(),
 29            pending_views: Default::default(),
 30            pending_keystrokes: Vec::new(),
 31            keymap,
 32        }
 33    }
 34
 35    pub fn set_keymap(&mut self, keymap: Keymap) {
 36        self.clear_pending();
 37        self.keymap = keymap;
 38    }
 39
 40    pub fn add_bindings<T: IntoIterator<Item = Binding>>(&mut self, bindings: T) {
 41        self.clear_pending();
 42        self.keymap.add_bindings(bindings);
 43    }
 44
 45    pub fn clear_bindings(&mut self) {
 46        self.clear_pending();
 47        self.keymap.clear();
 48    }
 49
 50    pub fn bindings_for_action(&self, action_id: TypeId) -> impl Iterator<Item = &Binding> {
 51        self.keymap.bindings_for_action(action_id)
 52    }
 53
 54    pub fn clear_pending(&mut self) {
 55        self.pending_keystrokes.clear();
 56        self.pending_views.clear();
 57    }
 58
 59    pub fn has_pending_keystrokes(&self) -> bool {
 60        !self.pending_keystrokes.is_empty()
 61    }
 62
 63    /// Pushes a keystroke onto the matcher.
 64    /// The result of the new keystroke is returned:
 65    ///     MatchResult::None =>
 66    ///         No match is valid for this key given any pending keystrokes.
 67    ///     MatchResult::Pending =>
 68    ///         There exist bindings which are still waiting for more keys.
 69    ///     MatchResult::Complete(matches) =>
 70    ///         1 or more bindings have received the necessary key presses.
 71    ///         The order of the matched actions is by position of the matching first,
 72    //          and order in the keymap second.
 73    pub fn push_keystroke(
 74        &mut self,
 75        keystroke: Keystroke,
 76        mut dispatch_path: Vec<(usize, KeymapContext)>,
 77    ) -> MatchResult {
 78        // Collect matched bindings into an ordered list using the position in the matching binding first,
 79        // and then the order the binding matched in the view tree second.
 80        // The key is the reverse position of the binding in the bindings list so that later bindings
 81        // match before earlier ones in the user's config
 82        let mut matched_bindings: Vec<(usize, Box<dyn Action>)> = Default::default();
 83        let no_action_id = (NoAction {}).id();
 84
 85        let first_keystroke = self.pending_keystrokes.is_empty();
 86        let mut pending_key = None;
 87        let mut previous_keystrokes = self.pending_keystrokes.clone();
 88
 89        self.contexts.clear();
 90        self.contexts
 91            .extend(dispatch_path.iter_mut().map(|e| std::mem::take(&mut e.1)));
 92
 93        // Find the bindings which map the pending keystrokes and current context
 94        for (i, (view_id, _)) in dispatch_path.iter().enumerate() {
 95            // Don't require pending view entry if there are no pending keystrokes
 96            if !first_keystroke && !self.pending_views.contains_key(view_id) {
 97                continue;
 98            }
 99
100            // If there is a previous view context, invalidate that view if it
101            // has changed
102            if let Some(previous_view_context) = self.pending_views.remove(view_id) {
103                if previous_view_context != self.contexts[i] {
104                    continue;
105                }
106            }
107
108            for binding in self.keymap.bindings().iter().rev() {
109                for possibility in keystroke.match_possibilities() {
110                    previous_keystrokes.push(possibility.clone());
111                    match binding.match_keys_and_context(&previous_keystrokes, &self.contexts[i..])
112                    {
113                        BindingMatchResult::Complete(action) => {
114                            if action.id() != no_action_id {
115                                matched_bindings.push((*view_id, action));
116                            }
117                        }
118                        BindingMatchResult::Partial => {
119                            if pending_key == None || pending_key == Some(possibility.clone()) {
120                                self.pending_views
121                                    .insert(*view_id, self.contexts[i].clone());
122                                pending_key = Some(possibility)
123                            }
124                        }
125                        _ => {}
126                    }
127                    previous_keystrokes.pop();
128                }
129            }
130        }
131
132        if pending_key.is_some() {
133            self.pending_keystrokes.push(pending_key.unwrap());
134        } else {
135            self.clear_pending();
136        }
137
138        if !matched_bindings.is_empty() {
139            // Collect the sorted matched bindings into the final vec for ease of use
140            // Matched bindings are in order by precedence
141            MatchResult::Matches(matched_bindings)
142        } else if !self.pending_keystrokes.is_empty() {
143            MatchResult::Pending
144        } else {
145            MatchResult::None
146        }
147    }
148
149    pub fn keystrokes_for_action(
150        &self,
151        action: &dyn Action,
152        contexts: &[KeymapContext],
153    ) -> Option<SmallVec<[Keystroke; 2]>> {
154        self.keymap
155            .bindings()
156            .iter()
157            .rev()
158            .find_map(|binding| binding.keystrokes_for_action(action, contexts))
159    }
160}
161
162impl Default for KeymapMatcher {
163    fn default() -> Self {
164        Self::new(Keymap::default())
165    }
166}
167
168pub enum MatchResult {
169    None,
170    Pending,
171    Matches(Vec<(usize, Box<dyn Action>)>),
172}
173
174impl Debug for MatchResult {
175    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
176        match self {
177            MatchResult::None => f.debug_struct("MatchResult::None").finish(),
178            MatchResult::Pending => f.debug_struct("MatchResult::Pending").finish(),
179            MatchResult::Matches(matches) => f
180                .debug_list()
181                .entries(
182                    matches
183                        .iter()
184                        .map(|(view_id, action)| format!("{view_id}, {}", action.name())),
185                )
186                .finish(),
187        }
188    }
189}
190
191impl PartialEq for MatchResult {
192    fn eq(&self, other: &Self) -> bool {
193        match (self, other) {
194            (MatchResult::None, MatchResult::None) => true,
195            (MatchResult::Pending, MatchResult::Pending) => true,
196            (MatchResult::Matches(matches), MatchResult::Matches(other_matches)) => {
197                matches.len() == other_matches.len()
198                    && matches.iter().zip(other_matches.iter()).all(
199                        |((view_id, action), (other_view_id, other_action))| {
200                            view_id == other_view_id && action.eq(other_action.as_ref())
201                        },
202                    )
203            }
204            _ => false,
205        }
206    }
207}
208
209impl Eq for MatchResult {}
210
211impl Clone for MatchResult {
212    fn clone(&self) -> Self {
213        match self {
214            MatchResult::None => MatchResult::None,
215            MatchResult::Pending => MatchResult::Pending,
216            MatchResult::Matches(matches) => MatchResult::Matches(
217                matches
218                    .iter()
219                    .map(|(view_id, action)| (*view_id, Action::boxed_clone(action.as_ref())))
220                    .collect(),
221            ),
222        }
223    }
224}
225
226#[cfg(test)]
227mod tests {
228    use anyhow::Result;
229    use serde::Deserialize;
230
231    use crate::{actions, impl_actions, keymap_matcher::KeymapContext};
232
233    use super::*;
234
235    #[test]
236    fn test_keymap_and_view_ordering() -> Result<()> {
237        actions!(test, [EditorAction, ProjectPanelAction]);
238
239        let mut editor = KeymapContext::default();
240        editor.add_identifier("Editor");
241
242        let mut project_panel = KeymapContext::default();
243        project_panel.add_identifier("ProjectPanel");
244
245        // Editor 'deeper' in than project panel
246        let dispatch_path = vec![(2, editor), (1, project_panel)];
247
248        // But editor actions 'higher' up in keymap
249        let keymap = Keymap::new(vec![
250            Binding::new("left", EditorAction, Some("Editor")),
251            Binding::new("left", ProjectPanelAction, Some("ProjectPanel")),
252        ]);
253
254        let mut matcher = KeymapMatcher::new(keymap);
255
256        assert_eq!(
257            matcher.push_keystroke(Keystroke::parse("left")?, dispatch_path.clone()),
258            MatchResult::Matches(vec![
259                (2, Box::new(EditorAction)),
260                (1, Box::new(ProjectPanelAction)),
261            ]),
262        );
263
264        Ok(())
265    }
266
267    #[test]
268    fn test_push_keystroke() -> Result<()> {
269        actions!(test, [B, AB, C, D, DA, E, EF]);
270
271        let mut context1 = KeymapContext::default();
272        context1.add_identifier("1");
273
274        let mut context2 = KeymapContext::default();
275        context2.add_identifier("2");
276
277        let dispatch_path = vec![(2, context2), (1, context1)];
278
279        let keymap = Keymap::new(vec![
280            Binding::new("a b", AB, Some("1")),
281            Binding::new("b", B, Some("2")),
282            Binding::new("c", C, Some("2")),
283            Binding::new("d", D, Some("1")),
284            Binding::new("d", D, Some("2")),
285            Binding::new("d a", DA, Some("2")),
286        ]);
287
288        let mut matcher = KeymapMatcher::new(keymap);
289
290        // Binding with pending prefix always takes precedence
291        assert_eq!(
292            matcher.push_keystroke(Keystroke::parse("a")?, dispatch_path.clone()),
293            MatchResult::Pending,
294        );
295        // B alone doesn't match because a was pending, so AB is returned instead
296        assert_eq!(
297            matcher.push_keystroke(Keystroke::parse("b")?, dispatch_path.clone()),
298            MatchResult::Matches(vec![(1, Box::new(AB))]),
299        );
300        assert!(!matcher.has_pending_keystrokes());
301
302        // Without an a prefix, B is dispatched like expected
303        assert_eq!(
304            matcher.push_keystroke(Keystroke::parse("b")?, dispatch_path.clone()),
305            MatchResult::Matches(vec![(2, Box::new(B))]),
306        );
307        assert!(!matcher.has_pending_keystrokes());
308
309        // If a is prefixed, C will not be dispatched because there
310        // was a pending binding for it
311        assert_eq!(
312            matcher.push_keystroke(Keystroke::parse("a")?, dispatch_path.clone()),
313            MatchResult::Pending,
314        );
315        assert_eq!(
316            matcher.push_keystroke(Keystroke::parse("c")?, dispatch_path.clone()),
317            MatchResult::None,
318        );
319        assert!(!matcher.has_pending_keystrokes());
320
321        // If a single keystroke matches multiple bindings in the tree
322        // all of them are returned so that we can fallback if the action
323        // handler decides to propagate the action
324        assert_eq!(
325            matcher.push_keystroke(Keystroke::parse("d")?, dispatch_path.clone()),
326            MatchResult::Matches(vec![(2, Box::new(D)), (1, Box::new(D))]),
327        );
328
329        // If none of the d action handlers consume the binding, a pending
330        // binding may then be used
331        assert_eq!(
332            matcher.push_keystroke(Keystroke::parse("a")?, dispatch_path.clone()),
333            MatchResult::Matches(vec![(2, Box::new(DA))]),
334        );
335        assert!(!matcher.has_pending_keystrokes());
336
337        Ok(())
338    }
339
340    #[test]
341    fn test_keystroke_parsing() -> Result<()> {
342        assert_eq!(
343            Keystroke::parse("ctrl-p")?,
344            Keystroke {
345                key: "p".into(),
346                ctrl: true,
347                alt: false,
348                shift: false,
349                cmd: false,
350                function: false,
351                ime_key: None,
352            }
353        );
354
355        assert_eq!(
356            Keystroke::parse("alt-shift-down")?,
357            Keystroke {
358                key: "down".into(),
359                ctrl: false,
360                alt: true,
361                shift: true,
362                cmd: false,
363                function: false,
364                ime_key: None,
365            }
366        );
367
368        assert_eq!(
369            Keystroke::parse("shift-cmd--")?,
370            Keystroke {
371                key: "-".into(),
372                ctrl: false,
373                alt: false,
374                shift: true,
375                cmd: true,
376                function: false,
377                ime_key: None,
378            }
379        );
380
381        Ok(())
382    }
383
384    #[test]
385    fn test_context_predicate_parsing() -> Result<()> {
386        use KeymapContextPredicate::*;
387
388        assert_eq!(
389            KeymapContextPredicate::parse("a && (b == c || d != e)")?,
390            And(
391                Box::new(Identifier("a".into())),
392                Box::new(Or(
393                    Box::new(Equal("b".into(), "c".into())),
394                    Box::new(NotEqual("d".into(), "e".into())),
395                ))
396            )
397        );
398
399        assert_eq!(
400            KeymapContextPredicate::parse("!a")?,
401            Not(Box::new(Identifier("a".into())),)
402        );
403
404        Ok(())
405    }
406
407    #[test]
408    fn test_context_predicate_eval() {
409        let predicate = KeymapContextPredicate::parse("a && b || c == d").unwrap();
410
411        let mut context = KeymapContext::default();
412        context.add_identifier("a");
413        assert!(!predicate.eval(&[context]));
414
415        let mut context = KeymapContext::default();
416        context.add_identifier("a");
417        context.add_identifier("b");
418        assert!(predicate.eval(&[context]));
419
420        let mut context = KeymapContext::default();
421        context.add_identifier("a");
422        context.add_key("c", "x");
423        assert!(!predicate.eval(&[context]));
424
425        let mut context = KeymapContext::default();
426        context.add_identifier("a");
427        context.add_key("c", "d");
428        assert!(predicate.eval(&[context]));
429
430        let predicate = KeymapContextPredicate::parse("!a").unwrap();
431        assert!(predicate.eval(&[KeymapContext::default()]));
432    }
433
434    #[test]
435    fn test_context_child_predicate_eval() {
436        let predicate = KeymapContextPredicate::parse("a && b > c").unwrap();
437        let contexts = [
438            context_set(&["e", "f"]),
439            context_set(&["c", "d"]), // match this context
440            context_set(&["a", "b"]),
441        ];
442
443        assert!(!predicate.eval(&contexts[0..]));
444        assert!(predicate.eval(&contexts[1..]));
445        assert!(!predicate.eval(&contexts[2..]));
446
447        let predicate = KeymapContextPredicate::parse("a && b > c && !d > e").unwrap();
448        let contexts = [
449            context_set(&["f"]),
450            context_set(&["e"]), // only match this context
451            context_set(&["c"]),
452            context_set(&["a", "b"]),
453            context_set(&["e"]),
454            context_set(&["c", "d"]),
455            context_set(&["a", "b"]),
456        ];
457
458        assert!(!predicate.eval(&contexts[0..]));
459        assert!(predicate.eval(&contexts[1..]));
460        assert!(!predicate.eval(&contexts[2..]));
461        assert!(!predicate.eval(&contexts[3..]));
462        assert!(!predicate.eval(&contexts[4..]));
463        assert!(!predicate.eval(&contexts[5..]));
464        assert!(!predicate.eval(&contexts[6..]));
465
466        fn context_set(names: &[&str]) -> KeymapContext {
467            let mut keymap = KeymapContext::new();
468            names
469                .iter()
470                .for_each(|name| keymap.add_identifier(name.to_string()));
471            keymap
472        }
473    }
474
475    #[test]
476    fn test_matcher() -> Result<()> {
477        #[derive(Clone, Deserialize, PartialEq, Eq, Debug)]
478        pub struct A(pub String);
479        impl_actions!(test, [A]);
480        actions!(test, [B, Ab, Dollar, Quote, Ess, Backtick]);
481
482        #[derive(Clone, Debug, Eq, PartialEq)]
483        struct ActionArg {
484            a: &'static str,
485        }
486
487        let keymap = Keymap::new(vec![
488            Binding::new("a", A("x".to_string()), Some("a")),
489            Binding::new("b", B, Some("a")),
490            Binding::new("a b", Ab, Some("a || b")),
491            Binding::new("$", Dollar, Some("a")),
492            Binding::new("\"", Quote, Some("a")),
493            Binding::new("alt-s", Ess, Some("a")),
494            Binding::new("ctrl-`", Backtick, Some("a")),
495        ]);
496
497        let mut context_a = KeymapContext::default();
498        context_a.add_identifier("a");
499
500        let mut context_b = KeymapContext::default();
501        context_b.add_identifier("b");
502
503        let mut matcher = KeymapMatcher::new(keymap);
504
505        // Basic match
506        assert_eq!(
507            matcher.push_keystroke(Keystroke::parse("a")?, vec![(1, context_a.clone())]),
508            MatchResult::Matches(vec![(1, Box::new(A("x".to_string())))])
509        );
510        matcher.clear_pending();
511
512        // Multi-keystroke match
513        assert_eq!(
514            matcher.push_keystroke(Keystroke::parse("a")?, vec![(1, context_b.clone())]),
515            MatchResult::Pending
516        );
517        assert_eq!(
518            matcher.push_keystroke(Keystroke::parse("b")?, vec![(1, context_b.clone())]),
519            MatchResult::Matches(vec![(1, Box::new(Ab))])
520        );
521        matcher.clear_pending();
522
523        // Failed matches don't interfere with matching subsequent keys
524        assert_eq!(
525            matcher.push_keystroke(Keystroke::parse("x")?, vec![(1, context_a.clone())]),
526            MatchResult::None
527        );
528        assert_eq!(
529            matcher.push_keystroke(Keystroke::parse("a")?, vec![(1, context_a.clone())]),
530            MatchResult::Matches(vec![(1, Box::new(A("x".to_string())))])
531        );
532        matcher.clear_pending();
533
534        // Pending keystrokes are cleared when the context changes
535        assert_eq!(
536            matcher.push_keystroke(Keystroke::parse("a")?, vec![(1, context_b.clone())]),
537            MatchResult::Pending
538        );
539        assert_eq!(
540            matcher.push_keystroke(Keystroke::parse("b")?, vec![(1, context_a.clone())]),
541            MatchResult::None
542        );
543        matcher.clear_pending();
544
545        let mut context_c = KeymapContext::default();
546        context_c.add_identifier("c");
547
548        // Pending keystrokes are maintained per-view
549        assert_eq!(
550            matcher.push_keystroke(
551                Keystroke::parse("a")?,
552                vec![(1, context_b.clone()), (2, context_c.clone())]
553            ),
554            MatchResult::Pending
555        );
556        assert_eq!(
557            matcher.push_keystroke(Keystroke::parse("b")?, vec![(1, context_b.clone())]),
558            MatchResult::Matches(vec![(1, Box::new(Ab))])
559        );
560
561        // handle Czech $ (option + 4 key)
562        assert_eq!(
563            matcher.push_keystroke(Keystroke::parse("alt-รง->$")?, vec![(1, context_a.clone())]),
564            MatchResult::Matches(vec![(1, Box::new(Dollar))])
565        );
566
567        // handle Brazillian quote (quote key then space key)
568        assert_eq!(
569            matcher.push_keystroke(Keystroke::parse("space->\"")?, vec![(1, context_a.clone())]),
570            MatchResult::Matches(vec![(1, Box::new(Quote))])
571        );
572
573        // handle ctrl+` on a brazillian keyboard
574        assert_eq!(
575            matcher.push_keystroke(Keystroke::parse("ctrl-->`")?, vec![(1, context_a.clone())]),
576            MatchResult::Matches(vec![(1, Box::new(Backtick))])
577        );
578
579        // handle alt-s on a US keyboard
580        assert_eq!(
581            matcher.push_keystroke(Keystroke::parse("alt-s->รŸ")?, vec![(1, context_a.clone())]),
582            MatchResult::Matches(vec![(1, Box::new(Ess))])
583        );
584
585        Ok(())
586    }
587}