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        let mut any_pending = false;
 79        // Collect matched bindings into an ordered list using the position in the matching binding first,
 80        // and then the order the binding matched in the view tree second.
 81        // The key is the reverse position of the binding in the bindings list so that later bindings
 82        // match before earlier ones in the user's config
 83        let mut matched_bindings: Vec<(usize, Box<dyn Action>)> = Default::default();
 84        let no_action_id = (NoAction {}).id();
 85
 86        let first_keystroke = self.pending_keystrokes.is_empty();
 87        self.pending_keystrokes.push(keystroke.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                match binding.match_keys_and_context(&self.pending_keystrokes, &self.contexts[i..])
110                {
111                    BindingMatchResult::Complete(action) => {
112                        if action.id() != no_action_id {
113                            matched_bindings.push((*view_id, action));
114                        }
115                    }
116                    BindingMatchResult::Partial => {
117                        self.pending_views
118                            .insert(*view_id, self.contexts[i].clone());
119                        any_pending = true;
120                    }
121                    _ => {}
122                }
123            }
124        }
125
126        if !any_pending {
127            self.clear_pending();
128        }
129
130        if !matched_bindings.is_empty() {
131            // Collect the sorted matched bindings into the final vec for ease of use
132            // Matched bindings are in order by precedence
133            MatchResult::Matches(matched_bindings)
134        } else if any_pending {
135            MatchResult::Pending
136        } else {
137            MatchResult::None
138        }
139    }
140
141    pub fn keystrokes_for_action(
142        &self,
143        action: &dyn Action,
144        contexts: &[KeymapContext],
145    ) -> Option<SmallVec<[Keystroke; 2]>> {
146        self.keymap
147            .bindings()
148            .iter()
149            .rev()
150            .find_map(|binding| binding.keystrokes_for_action(action, contexts))
151    }
152}
153
154impl Default for KeymapMatcher {
155    fn default() -> Self {
156        Self::new(Keymap::default())
157    }
158}
159
160pub enum MatchResult {
161    None,
162    Pending,
163    Matches(Vec<(usize, Box<dyn Action>)>),
164}
165
166impl Debug for MatchResult {
167    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
168        match self {
169            MatchResult::None => f.debug_struct("MatchResult::None").finish(),
170            MatchResult::Pending => f.debug_struct("MatchResult::Pending").finish(),
171            MatchResult::Matches(matches) => f
172                .debug_list()
173                .entries(
174                    matches
175                        .iter()
176                        .map(|(view_id, action)| format!("{view_id}, {}", action.name())),
177                )
178                .finish(),
179        }
180    }
181}
182
183impl PartialEq for MatchResult {
184    fn eq(&self, other: &Self) -> bool {
185        match (self, other) {
186            (MatchResult::None, MatchResult::None) => true,
187            (MatchResult::Pending, MatchResult::Pending) => true,
188            (MatchResult::Matches(matches), MatchResult::Matches(other_matches)) => {
189                matches.len() == other_matches.len()
190                    && matches.iter().zip(other_matches.iter()).all(
191                        |((view_id, action), (other_view_id, other_action))| {
192                            view_id == other_view_id && action.eq(other_action.as_ref())
193                        },
194                    )
195            }
196            _ => false,
197        }
198    }
199}
200
201impl Eq for MatchResult {}
202
203impl Clone for MatchResult {
204    fn clone(&self) -> Self {
205        match self {
206            MatchResult::None => MatchResult::None,
207            MatchResult::Pending => MatchResult::Pending,
208            MatchResult::Matches(matches) => MatchResult::Matches(
209                matches
210                    .iter()
211                    .map(|(view_id, action)| (*view_id, Action::boxed_clone(action.as_ref())))
212                    .collect(),
213            ),
214        }
215    }
216}
217
218#[cfg(test)]
219mod tests {
220    use anyhow::Result;
221    use serde::Deserialize;
222
223    use crate::{actions, impl_actions, keymap_matcher::KeymapContext};
224
225    use super::*;
226
227    #[test]
228    fn test_keymap_and_view_ordering() -> Result<()> {
229        actions!(test, [EditorAction, ProjectPanelAction]);
230
231        let mut editor = KeymapContext::default();
232        editor.add_identifier("Editor");
233
234        let mut project_panel = KeymapContext::default();
235        project_panel.add_identifier("ProjectPanel");
236
237        // Editor 'deeper' in than project panel
238        let dispatch_path = vec![(2, editor), (1, project_panel)];
239
240        // But editor actions 'higher' up in keymap
241        let keymap = Keymap::new(vec![
242            Binding::new("left", EditorAction, Some("Editor")),
243            Binding::new("left", ProjectPanelAction, Some("ProjectPanel")),
244        ]);
245
246        let mut matcher = KeymapMatcher::new(keymap);
247
248        assert_eq!(
249            matcher.push_keystroke(Keystroke::parse("left")?, dispatch_path.clone()),
250            MatchResult::Matches(vec![
251                (2, Box::new(EditorAction)),
252                (1, Box::new(ProjectPanelAction)),
253            ]),
254        );
255
256        Ok(())
257    }
258
259    #[test]
260    fn test_push_keystroke() -> Result<()> {
261        actions!(test, [B, AB, C, D, DA, E, EF]);
262
263        let mut context1 = KeymapContext::default();
264        context1.add_identifier("1");
265
266        let mut context2 = KeymapContext::default();
267        context2.add_identifier("2");
268
269        let dispatch_path = vec![(2, context2), (1, context1)];
270
271        let keymap = Keymap::new(vec![
272            Binding::new("a b", AB, Some("1")),
273            Binding::new("b", B, Some("2")),
274            Binding::new("c", C, Some("2")),
275            Binding::new("d", D, Some("1")),
276            Binding::new("d", D, Some("2")),
277            Binding::new("d a", DA, Some("2")),
278        ]);
279
280        let mut matcher = KeymapMatcher::new(keymap);
281
282        // Binding with pending prefix always takes precedence
283        assert_eq!(
284            matcher.push_keystroke(Keystroke::parse("a")?, dispatch_path.clone()),
285            MatchResult::Pending,
286        );
287        // B alone doesn't match because a was pending, so AB is returned instead
288        assert_eq!(
289            matcher.push_keystroke(Keystroke::parse("b")?, dispatch_path.clone()),
290            MatchResult::Matches(vec![(1, Box::new(AB))]),
291        );
292        assert!(!matcher.has_pending_keystrokes());
293
294        // Without an a prefix, B is dispatched like expected
295        assert_eq!(
296            matcher.push_keystroke(Keystroke::parse("b")?, dispatch_path.clone()),
297            MatchResult::Matches(vec![(2, Box::new(B))]),
298        );
299        assert!(!matcher.has_pending_keystrokes());
300
301        // If a is prefixed, C will not be dispatched because there
302        // was a pending binding for it
303        assert_eq!(
304            matcher.push_keystroke(Keystroke::parse("a")?, dispatch_path.clone()),
305            MatchResult::Pending,
306        );
307        assert_eq!(
308            matcher.push_keystroke(Keystroke::parse("c")?, dispatch_path.clone()),
309            MatchResult::None,
310        );
311        assert!(!matcher.has_pending_keystrokes());
312
313        // If a single keystroke matches multiple bindings in the tree
314        // all of them are returned so that we can fallback if the action
315        // handler decides to propagate the action
316        assert_eq!(
317            matcher.push_keystroke(Keystroke::parse("d")?, dispatch_path.clone()),
318            MatchResult::Matches(vec![(2, Box::new(D)), (1, Box::new(D))]),
319        );
320
321        // If none of the d action handlers consume the binding, a pending
322        // binding may then be used
323        assert_eq!(
324            matcher.push_keystroke(Keystroke::parse("a")?, dispatch_path.clone()),
325            MatchResult::Matches(vec![(2, Box::new(DA))]),
326        );
327        assert!(!matcher.has_pending_keystrokes());
328
329        Ok(())
330    }
331
332    #[test]
333    fn test_keystroke_parsing() -> Result<()> {
334        assert_eq!(
335            Keystroke::parse("ctrl-p")?,
336            Keystroke {
337                key: "p".into(),
338                ctrl: true,
339                alt: false,
340                shift: false,
341                cmd: false,
342                function: false,
343            }
344        );
345
346        assert_eq!(
347            Keystroke::parse("alt-shift-down")?,
348            Keystroke {
349                key: "down".into(),
350                ctrl: false,
351                alt: true,
352                shift: true,
353                cmd: false,
354                function: false,
355            }
356        );
357
358        assert_eq!(
359            Keystroke::parse("shift-cmd--")?,
360            Keystroke {
361                key: "-".into(),
362                ctrl: false,
363                alt: false,
364                shift: true,
365                cmd: true,
366                function: false,
367            }
368        );
369
370        Ok(())
371    }
372
373    #[test]
374    fn test_context_predicate_parsing() -> Result<()> {
375        use KeymapContextPredicate::*;
376
377        assert_eq!(
378            KeymapContextPredicate::parse("a && (b == c || d != e)")?,
379            And(
380                Box::new(Identifier("a".into())),
381                Box::new(Or(
382                    Box::new(Equal("b".into(), "c".into())),
383                    Box::new(NotEqual("d".into(), "e".into())),
384                ))
385            )
386        );
387
388        assert_eq!(
389            KeymapContextPredicate::parse("!a")?,
390            Not(Box::new(Identifier("a".into())),)
391        );
392
393        Ok(())
394    }
395
396    #[test]
397    fn test_context_predicate_eval() {
398        let predicate = KeymapContextPredicate::parse("a && b || c == d").unwrap();
399
400        let mut context = KeymapContext::default();
401        context.add_identifier("a");
402        assert!(!predicate.eval(&[context]));
403
404        let mut context = KeymapContext::default();
405        context.add_identifier("a");
406        context.add_identifier("b");
407        assert!(predicate.eval(&[context]));
408
409        let mut context = KeymapContext::default();
410        context.add_identifier("a");
411        context.add_key("c", "x");
412        assert!(!predicate.eval(&[context]));
413
414        let mut context = KeymapContext::default();
415        context.add_identifier("a");
416        context.add_key("c", "d");
417        assert!(predicate.eval(&[context]));
418
419        let predicate = KeymapContextPredicate::parse("!a").unwrap();
420        assert!(predicate.eval(&[KeymapContext::default()]));
421    }
422
423    #[test]
424    fn test_context_child_predicate_eval() {
425        let predicate = KeymapContextPredicate::parse("a && b > c").unwrap();
426        let contexts = [
427            context_set(&["e", "f"]),
428            context_set(&["c", "d"]), // match this context
429            context_set(&["a", "b"]),
430        ];
431
432        assert!(!predicate.eval(&contexts[0..]));
433        assert!(predicate.eval(&contexts[1..]));
434        assert!(!predicate.eval(&contexts[2..]));
435
436        let predicate = KeymapContextPredicate::parse("a && b > c && !d > e").unwrap();
437        let contexts = [
438            context_set(&["f"]),
439            context_set(&["e"]), // only match this context
440            context_set(&["c"]),
441            context_set(&["a", "b"]),
442            context_set(&["e"]),
443            context_set(&["c", "d"]),
444            context_set(&["a", "b"]),
445        ];
446
447        assert!(!predicate.eval(&contexts[0..]));
448        assert!(predicate.eval(&contexts[1..]));
449        assert!(!predicate.eval(&contexts[2..]));
450        assert!(!predicate.eval(&contexts[3..]));
451        assert!(!predicate.eval(&contexts[4..]));
452        assert!(!predicate.eval(&contexts[5..]));
453        assert!(!predicate.eval(&contexts[6..]));
454
455        fn context_set(names: &[&str]) -> KeymapContext {
456            let mut keymap = KeymapContext::new();
457            names
458                .iter()
459                .for_each(|name| keymap.add_identifier(name.to_string()));
460            keymap
461        }
462    }
463
464    #[test]
465    fn test_matcher() -> Result<()> {
466        #[derive(Clone, Deserialize, PartialEq, Eq, Debug)]
467        pub struct A(pub String);
468        impl_actions!(test, [A]);
469        actions!(test, [B, Ab]);
470
471        #[derive(Clone, Debug, Eq, PartialEq)]
472        struct ActionArg {
473            a: &'static str,
474        }
475
476        let keymap = Keymap::new(vec![
477            Binding::new("a", A("x".to_string()), Some("a")),
478            Binding::new("b", B, Some("a")),
479            Binding::new("a b", Ab, Some("a || b")),
480        ]);
481
482        let mut context_a = KeymapContext::default();
483        context_a.add_identifier("a");
484
485        let mut context_b = KeymapContext::default();
486        context_b.add_identifier("b");
487
488        let mut matcher = KeymapMatcher::new(keymap);
489
490        // Basic match
491        assert_eq!(
492            matcher.push_keystroke(Keystroke::parse("a")?, vec![(1, context_a.clone())]),
493            MatchResult::Matches(vec![(1, Box::new(A("x".to_string())))])
494        );
495        matcher.clear_pending();
496
497        // Multi-keystroke match
498        assert_eq!(
499            matcher.push_keystroke(Keystroke::parse("a")?, vec![(1, context_b.clone())]),
500            MatchResult::Pending
501        );
502        assert_eq!(
503            matcher.push_keystroke(Keystroke::parse("b")?, vec![(1, context_b.clone())]),
504            MatchResult::Matches(vec![(1, Box::new(Ab))])
505        );
506        matcher.clear_pending();
507
508        // Failed matches don't interfere with matching subsequent keys
509        assert_eq!(
510            matcher.push_keystroke(Keystroke::parse("x")?, vec![(1, context_a.clone())]),
511            MatchResult::None
512        );
513        assert_eq!(
514            matcher.push_keystroke(Keystroke::parse("a")?, vec![(1, context_a.clone())]),
515            MatchResult::Matches(vec![(1, Box::new(A("x".to_string())))])
516        );
517        matcher.clear_pending();
518
519        // Pending keystrokes are cleared when the context changes
520        assert_eq!(
521            matcher.push_keystroke(Keystroke::parse("a")?, vec![(1, context_b.clone())]),
522            MatchResult::Pending
523        );
524        assert_eq!(
525            matcher.push_keystroke(Keystroke::parse("b")?, vec![(1, context_a.clone())]),
526            MatchResult::None
527        );
528        matcher.clear_pending();
529
530        let mut context_c = KeymapContext::default();
531        context_c.add_identifier("c");
532
533        // Pending keystrokes are maintained per-view
534        assert_eq!(
535            matcher.push_keystroke(
536                Keystroke::parse("a")?,
537                vec![(1, context_b.clone()), (2, context_c.clone())]
538            ),
539            MatchResult::Pending
540        );
541        assert_eq!(
542            matcher.push_keystroke(Keystroke::parse("b")?, vec![(1, context_b.clone())]),
543            MatchResult::Matches(vec![(1, Box::new(Ab))])
544        );
545
546        Ok(())
547    }
548}