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