matcher.rs

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