keymap_matcher.rs

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