matcher.rs

  1use crate::{Action, KeyContext, Keymap, KeymapVersion, Keystroke};
  2use parking_lot::Mutex;
  3use std::sync::Arc;
  4
  5pub(crate) struct KeystrokeMatcher {
  6    pending_keystrokes: Vec<Keystroke>,
  7    keymap: Arc<Mutex<Keymap>>,
  8    keymap_version: KeymapVersion,
  9}
 10
 11impl KeystrokeMatcher {
 12    pub fn new(keymap: Arc<Mutex<Keymap>>) -> Self {
 13        let keymap_version = keymap.lock().version();
 14        Self {
 15            pending_keystrokes: Vec::new(),
 16            keymap_version,
 17            keymap,
 18        }
 19    }
 20
 21    pub fn clear_pending(&mut self) {
 22        self.pending_keystrokes.clear();
 23    }
 24
 25    pub fn has_pending_keystrokes(&self) -> bool {
 26        !self.pending_keystrokes.is_empty()
 27    }
 28
 29    /// Pushes a keystroke onto the matcher.
 30    /// The result of the new keystroke is returned:
 31    /// - KeyMatch::None =>
 32    ///         No match is valid for this key given any pending keystrokes.
 33    /// - KeyMatch::Pending =>
 34    ///         There exist bindings which are still waiting for more keys.
 35    /// - KeyMatch::Complete(matches) =>
 36    ///         One or more bindings have received the necessary key presses.
 37    ///         Bindings added later will take precedence over earlier bindings.
 38    pub(crate) fn match_keystroke(
 39        &mut self,
 40        keystroke: &Keystroke,
 41        context_stack: &[KeyContext],
 42    ) -> KeyMatch {
 43        let keymap = self.keymap.lock();
 44        // Clear pending keystrokes if the keymap has changed since the last matched keystroke.
 45        if keymap.version() != self.keymap_version {
 46            self.keymap_version = keymap.version();
 47            self.pending_keystrokes.clear();
 48        }
 49
 50        let mut pending_key = None;
 51        let mut found_actions = Vec::new();
 52
 53        for binding in keymap.bindings().rev() {
 54            if !keymap.binding_enabled(binding, context_stack) {
 55                continue;
 56            }
 57
 58            for candidate in keystroke.match_candidates() {
 59                self.pending_keystrokes.push(candidate.clone());
 60                match binding.match_keystrokes(&self.pending_keystrokes) {
 61                    KeyMatch::Some(mut actions) => {
 62                        found_actions.append(&mut actions);
 63                    }
 64                    KeyMatch::Pending => {
 65                        pending_key.get_or_insert(candidate);
 66                    }
 67                    KeyMatch::None => {}
 68                }
 69                self.pending_keystrokes.pop();
 70            }
 71        }
 72
 73        if !found_actions.is_empty() {
 74            self.pending_keystrokes.clear();
 75            return KeyMatch::Some(found_actions);
 76        } else if let Some(pending_key) = pending_key {
 77            self.pending_keystrokes.push(pending_key);
 78            KeyMatch::Pending
 79        } else {
 80            self.pending_keystrokes.clear();
 81            KeyMatch::None
 82        }
 83    }
 84}
 85
 86/// The result of matching a keystroke against a given keybinding.
 87/// - KeyMatch::None => No match is valid for this key given any pending keystrokes.
 88/// - KeyMatch::Pending => There exist bindings that is still waiting for more keys.
 89/// - KeyMatch::Some(matches) => One or more bindings have received the necessary key presses.
 90#[derive(Debug)]
 91pub enum KeyMatch {
 92    None,
 93    Pending,
 94    Some(Vec<Box<dyn Action>>),
 95}
 96
 97impl KeyMatch {
 98    /// Returns true if the match is complete.
 99    pub fn is_some(&self) -> bool {
100        matches!(self, KeyMatch::Some(_))
101    }
102
103    /// Get the matches if the match is complete.
104    pub fn matches(self) -> Option<Vec<Box<dyn Action>>> {
105        match self {
106            KeyMatch::Some(matches) => Some(matches),
107            _ => None,
108        }
109    }
110}
111
112impl PartialEq for KeyMatch {
113    fn eq(&self, other: &Self) -> bool {
114        match (self, other) {
115            (KeyMatch::None, KeyMatch::None) => true,
116            (KeyMatch::Pending, KeyMatch::Pending) => true,
117            (KeyMatch::Some(a), KeyMatch::Some(b)) => {
118                if a.len() != b.len() {
119                    return false;
120                }
121
122                for (a, b) in a.iter().zip(b.iter()) {
123                    if !a.partial_eq(b.as_ref()) {
124                        return false;
125                    }
126                }
127
128                true
129            }
130            _ => false,
131        }
132    }
133}
134
135#[cfg(test)]
136mod tests {
137
138    use serde_derive::Deserialize;
139
140    use super::*;
141    use crate::{self as gpui, KeyBindingContextPredicate, Modifiers};
142    use crate::{actions, KeyBinding};
143
144    #[test]
145    fn test_keymap_and_view_ordering() {
146        actions!(test, [EditorAction, ProjectPanelAction]);
147
148        let mut editor = KeyContext::default();
149        editor.add("Editor");
150
151        let mut project_panel = KeyContext::default();
152        project_panel.add("ProjectPanel");
153
154        // Editor 'deeper' in than project panel
155        let dispatch_path = vec![project_panel, editor];
156
157        // But editor actions 'higher' up in keymap
158        let keymap = Keymap::new(vec![
159            KeyBinding::new("left", EditorAction, Some("Editor")),
160            KeyBinding::new("left", ProjectPanelAction, Some("ProjectPanel")),
161        ]);
162
163        let mut matcher = KeystrokeMatcher::new(Arc::new(Mutex::new(keymap)));
164
165        let matches = matcher
166            .match_keystroke(&Keystroke::parse("left").unwrap(), &dispatch_path)
167            .matches()
168            .unwrap();
169
170        assert!(matches[0].partial_eq(&EditorAction));
171        assert!(matches.get(1).is_none());
172    }
173
174    #[test]
175    fn test_multi_keystroke_match() {
176        actions!(test, [B, AB, C, D, DA, E, EF]);
177
178        let mut context1 = KeyContext::default();
179        context1.add("1");
180
181        let mut context2 = KeyContext::default();
182        context2.add("2");
183
184        let dispatch_path = vec![context2, context1];
185
186        let keymap = Keymap::new(vec![
187            KeyBinding::new("a b", AB, Some("1")),
188            KeyBinding::new("b", B, Some("2")),
189            KeyBinding::new("c", C, Some("2")),
190            KeyBinding::new("d", D, Some("1")),
191            KeyBinding::new("d", D, Some("2")),
192            KeyBinding::new("d a", DA, Some("2")),
193        ]);
194
195        let mut matcher = KeystrokeMatcher::new(Arc::new(Mutex::new(keymap)));
196
197        // Binding with pending prefix always takes precedence
198        assert_eq!(
199            matcher.match_keystroke(&Keystroke::parse("a").unwrap(), &dispatch_path),
200            KeyMatch::Pending,
201        );
202        // B alone doesn't match because a was pending, so AB is returned instead
203        assert_eq!(
204            matcher.match_keystroke(&Keystroke::parse("b").unwrap(), &dispatch_path),
205            KeyMatch::Some(vec![Box::new(AB)]),
206        );
207        assert!(!matcher.has_pending_keystrokes());
208
209        // Without an a prefix, B is dispatched like expected
210        assert_eq!(
211            matcher.match_keystroke(&Keystroke::parse("b").unwrap(), &dispatch_path[0..1]),
212            KeyMatch::Some(vec![Box::new(B)]),
213        );
214        assert!(!matcher.has_pending_keystrokes());
215
216        // If a is prefixed, C will not be dispatched because there
217        // was a pending binding for it
218        assert_eq!(
219            matcher.match_keystroke(&Keystroke::parse("a").unwrap(), &dispatch_path),
220            KeyMatch::Pending,
221        );
222        assert_eq!(
223            matcher.match_keystroke(&Keystroke::parse("c").unwrap(), &dispatch_path),
224            KeyMatch::None,
225        );
226        assert!(!matcher.has_pending_keystrokes());
227
228        // If a single keystroke matches multiple bindings in the tree
229        // only one of them is returned.
230        assert_eq!(
231            matcher.match_keystroke(&Keystroke::parse("d").unwrap(), &dispatch_path),
232            KeyMatch::Some(vec![Box::new(D)]),
233        );
234    }
235
236    #[test]
237    fn test_keystroke_parsing() {
238        assert_eq!(
239            Keystroke::parse("ctrl-p").unwrap(),
240            Keystroke {
241                key: "p".into(),
242                modifiers: Modifiers {
243                    control: true,
244                    alt: false,
245                    shift: false,
246                    command: false,
247                    function: false,
248                },
249                ime_key: None,
250            }
251        );
252
253        assert_eq!(
254            Keystroke::parse("alt-shift-down").unwrap(),
255            Keystroke {
256                key: "down".into(),
257                modifiers: Modifiers {
258                    control: false,
259                    alt: true,
260                    shift: true,
261                    command: false,
262                    function: false,
263                },
264                ime_key: None,
265            }
266        );
267
268        assert_eq!(
269            Keystroke::parse("shift-cmd--").unwrap(),
270            Keystroke {
271                key: "-".into(),
272                modifiers: Modifiers {
273                    control: false,
274                    alt: false,
275                    shift: true,
276                    command: true,
277                    function: false,
278                },
279                ime_key: None,
280            }
281        );
282    }
283
284    #[test]
285    fn test_context_predicate_parsing() {
286        use KeyBindingContextPredicate::*;
287
288        assert_eq!(
289            KeyBindingContextPredicate::parse("a && (b == c || d != e)").unwrap(),
290            And(
291                Box::new(Identifier("a".into())),
292                Box::new(Or(
293                    Box::new(Equal("b".into(), "c".into())),
294                    Box::new(NotEqual("d".into(), "e".into())),
295                ))
296            )
297        );
298
299        assert_eq!(
300            KeyBindingContextPredicate::parse("!a").unwrap(),
301            Not(Box::new(Identifier("a".into())),)
302        );
303    }
304
305    #[test]
306    fn test_context_predicate_eval() {
307        let predicate = KeyBindingContextPredicate::parse("a && b || c == d").unwrap();
308
309        let mut context = KeyContext::default();
310        context.add("a");
311        assert!(!predicate.eval(&[context]));
312
313        let mut context = KeyContext::default();
314        context.add("a");
315        context.add("b");
316        assert!(predicate.eval(&[context]));
317
318        let mut context = KeyContext::default();
319        context.add("a");
320        context.set("c", "x");
321        assert!(!predicate.eval(&[context]));
322
323        let mut context = KeyContext::default();
324        context.add("a");
325        context.set("c", "d");
326        assert!(predicate.eval(&[context]));
327
328        let predicate = KeyBindingContextPredicate::parse("!a").unwrap();
329        assert!(predicate.eval(&[KeyContext::default()]));
330    }
331
332    #[test]
333    fn test_context_child_predicate_eval() {
334        let predicate = KeyBindingContextPredicate::parse("a && b > c").unwrap();
335        let contexts = [
336            context_set(&["a", "b"]),
337            context_set(&["c", "d"]), // match this context
338            context_set(&["e", "f"]),
339        ];
340
341        assert!(!predicate.eval(&contexts[..=0]));
342        assert!(predicate.eval(&contexts[..=1]));
343        assert!(!predicate.eval(&contexts[..=2]));
344
345        let predicate = KeyBindingContextPredicate::parse("a && b > c && !d > e").unwrap();
346        let contexts = [
347            context_set(&["a", "b"]),
348            context_set(&["c", "d"]),
349            context_set(&["e"]),
350            context_set(&["a", "b"]),
351            context_set(&["c"]),
352            context_set(&["e"]), // only match this context
353            context_set(&["f"]),
354        ];
355
356        assert!(!predicate.eval(&contexts[..=0]));
357        assert!(!predicate.eval(&contexts[..=1]));
358        assert!(!predicate.eval(&contexts[..=2]));
359        assert!(!predicate.eval(&contexts[..=3]));
360        assert!(!predicate.eval(&contexts[..=4]));
361        assert!(predicate.eval(&contexts[..=5]));
362        assert!(!predicate.eval(&contexts[..=6]));
363
364        fn context_set(names: &[&str]) -> KeyContext {
365            let mut keymap = KeyContext::default();
366            names.iter().for_each(|name| keymap.add(name.to_string()));
367            keymap
368        }
369    }
370
371    #[test]
372    fn test_matcher() {
373        #[derive(Clone, Deserialize, PartialEq, Eq, Debug)]
374        pub struct A(pub String);
375        impl_actions!(test, [A]);
376        actions!(test, [B, Ab, Dollar, Quote, Ess, Backtick]);
377
378        #[derive(Clone, Debug, Eq, PartialEq)]
379        struct ActionArg {
380            a: &'static str,
381        }
382
383        let keymap = Keymap::new(vec![
384            KeyBinding::new("a", A("x".to_string()), Some("a")),
385            KeyBinding::new("b", B, Some("a")),
386            KeyBinding::new("a b", Ab, Some("a || b")),
387            KeyBinding::new("$", Dollar, Some("a")),
388            KeyBinding::new("\"", Quote, Some("a")),
389            KeyBinding::new("alt-s", Ess, Some("a")),
390            KeyBinding::new("ctrl-`", Backtick, Some("a")),
391        ]);
392
393        let mut context_a = KeyContext::default();
394        context_a.add("a");
395
396        let mut context_b = KeyContext::default();
397        context_b.add("b");
398
399        let mut matcher = KeystrokeMatcher::new(Arc::new(Mutex::new(keymap)));
400
401        // Basic match
402        assert_eq!(
403            matcher.match_keystroke(&Keystroke::parse("a").unwrap(), &[context_a.clone()]),
404            KeyMatch::Some(vec![Box::new(A("x".to_string()))])
405        );
406        matcher.clear_pending();
407
408        // Multi-keystroke match
409        assert_eq!(
410            matcher.match_keystroke(&Keystroke::parse("a").unwrap(), &[context_b.clone()]),
411            KeyMatch::Pending
412        );
413        assert_eq!(
414            matcher.match_keystroke(&Keystroke::parse("b").unwrap(), &[context_b.clone()]),
415            KeyMatch::Some(vec![Box::new(Ab)])
416        );
417        matcher.clear_pending();
418
419        // Failed matches don't interfere with matching subsequent keys
420        assert_eq!(
421            matcher.match_keystroke(&Keystroke::parse("x").unwrap(), &[context_a.clone()]),
422            KeyMatch::None
423        );
424        assert_eq!(
425            matcher.match_keystroke(&Keystroke::parse("a").unwrap(), &[context_a.clone()]),
426            KeyMatch::Some(vec![Box::new(A("x".to_string()))])
427        );
428        matcher.clear_pending();
429
430        let mut context_c = KeyContext::default();
431        context_c.add("c");
432
433        assert_eq!(
434            matcher.match_keystroke(
435                &Keystroke::parse("a").unwrap(),
436                &[context_c.clone(), context_b.clone()]
437            ),
438            KeyMatch::Pending
439        );
440        assert_eq!(
441            matcher.match_keystroke(&Keystroke::parse("b").unwrap(), &[context_b.clone()]),
442            KeyMatch::Some(vec![Box::new(Ab)])
443        );
444
445        // handle Czech $ (option + 4 key)
446        assert_eq!(
447            matcher.match_keystroke(&Keystroke::parse("alt-รง->$").unwrap(), &[context_a.clone()]),
448            KeyMatch::Some(vec![Box::new(Dollar)])
449        );
450
451        // handle Brazilian quote (quote key then space key)
452        assert_eq!(
453            matcher.match_keystroke(
454                &Keystroke::parse("space->\"").unwrap(),
455                &[context_a.clone()]
456            ),
457            KeyMatch::Some(vec![Box::new(Quote)])
458        );
459
460        // handle ctrl+` on a brazilian keyboard
461        assert_eq!(
462            matcher.match_keystroke(&Keystroke::parse("ctrl-->`").unwrap(), &[context_a.clone()]),
463            KeyMatch::Some(vec![Box::new(Backtick)])
464        );
465
466        // handle alt-s on a US keyboard
467        assert_eq!(
468            matcher.match_keystroke(&Keystroke::parse("alt-s->รŸ").unwrap(), &[context_a.clone()]),
469            KeyMatch::Some(vec![Box::new(Ess)])
470        );
471    }
472}