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