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        eprintln!("PROBLEM AREA");
213        // If a is prefixed, C will not be dispatched because there
214        // was a pending binding for it
215        assert_eq!(
216            matcher.match_keystroke(&Keystroke::parse("a").unwrap(), &dispatch_path),
217            KeyMatch::Pending,
218        );
219        assert_eq!(
220            matcher.match_keystroke(&Keystroke::parse("c").unwrap(), &dispatch_path),
221            KeyMatch::None,
222        );
223        assert!(!matcher.has_pending_keystrokes());
224
225        // If a single keystroke matches multiple bindings in the tree
226        // only one of them is returned.
227        assert_eq!(
228            matcher.match_keystroke(&Keystroke::parse("d").unwrap(), &dispatch_path),
229            KeyMatch::Some(vec![Box::new(D)]),
230        );
231    }
232
233    #[test]
234    fn test_keystroke_parsing() {
235        assert_eq!(
236            Keystroke::parse("ctrl-p").unwrap(),
237            Keystroke {
238                key: "p".into(),
239                modifiers: Modifiers {
240                    control: true,
241                    alt: false,
242                    shift: false,
243                    command: false,
244                    function: false,
245                },
246                ime_key: None,
247            }
248        );
249
250        assert_eq!(
251            Keystroke::parse("alt-shift-down").unwrap(),
252            Keystroke {
253                key: "down".into(),
254                modifiers: Modifiers {
255                    control: false,
256                    alt: true,
257                    shift: true,
258                    command: false,
259                    function: false,
260                },
261                ime_key: None,
262            }
263        );
264
265        assert_eq!(
266            Keystroke::parse("shift-cmd--").unwrap(),
267            Keystroke {
268                key: "-".into(),
269                modifiers: Modifiers {
270                    control: false,
271                    alt: false,
272                    shift: true,
273                    command: true,
274                    function: false,
275                },
276                ime_key: None,
277            }
278        );
279    }
280
281    #[test]
282    fn test_context_predicate_parsing() {
283        use KeyBindingContextPredicate::*;
284
285        assert_eq!(
286            KeyBindingContextPredicate::parse("a && (b == c || d != e)").unwrap(),
287            And(
288                Box::new(Identifier("a".into())),
289                Box::new(Or(
290                    Box::new(Equal("b".into(), "c".into())),
291                    Box::new(NotEqual("d".into(), "e".into())),
292                ))
293            )
294        );
295
296        assert_eq!(
297            KeyBindingContextPredicate::parse("!a").unwrap(),
298            Not(Box::new(Identifier("a".into())),)
299        );
300    }
301
302    #[test]
303    fn test_context_predicate_eval() {
304        let predicate = KeyBindingContextPredicate::parse("a && b || c == d").unwrap();
305
306        let mut context = KeyContext::default();
307        context.add("a");
308        assert!(!predicate.eval(&[context]));
309
310        let mut context = KeyContext::default();
311        context.add("a");
312        context.add("b");
313        assert!(predicate.eval(&[context]));
314
315        let mut context = KeyContext::default();
316        context.add("a");
317        context.set("c", "x");
318        assert!(!predicate.eval(&[context]));
319
320        let mut context = KeyContext::default();
321        context.add("a");
322        context.set("c", "d");
323        assert!(predicate.eval(&[context]));
324
325        let predicate = KeyBindingContextPredicate::parse("!a").unwrap();
326        assert!(predicate.eval(&[KeyContext::default()]));
327    }
328
329    #[test]
330    fn test_context_child_predicate_eval() {
331        let predicate = KeyBindingContextPredicate::parse("a && b > c").unwrap();
332        let contexts = [
333            context_set(&["a", "b"]),
334            context_set(&["c", "d"]), // match this context
335            context_set(&["e", "f"]),
336        ];
337
338        assert!(!predicate.eval(&contexts[..=0]));
339        assert!(predicate.eval(&contexts[..=1]));
340        assert!(!predicate.eval(&contexts[..=2]));
341
342        let predicate = KeyBindingContextPredicate::parse("a && b > c && !d > e").unwrap();
343        let contexts = [
344            context_set(&["a", "b"]),
345            context_set(&["c", "d"]),
346            context_set(&["e"]),
347            context_set(&["a", "b"]),
348            context_set(&["c"]),
349            context_set(&["e"]), // only match this context
350            context_set(&["f"]),
351        ];
352
353        assert!(!predicate.eval(&contexts[..=0]));
354        assert!(!predicate.eval(&contexts[..=1]));
355        assert!(!predicate.eval(&contexts[..=2]));
356        assert!(!predicate.eval(&contexts[..=3]));
357        assert!(!predicate.eval(&contexts[..=4]));
358        assert!(predicate.eval(&contexts[..=5]));
359        assert!(!predicate.eval(&contexts[..=6]));
360
361        fn context_set(names: &[&str]) -> KeyContext {
362            let mut keymap = KeyContext::default();
363            names.iter().for_each(|name| keymap.add(name.to_string()));
364            keymap
365        }
366    }
367
368    #[test]
369    fn test_matcher() {
370        #[derive(Clone, Deserialize, PartialEq, Eq, Debug)]
371        pub struct A(pub String);
372        impl_actions!(test, [A]);
373        actions!(test, [B, Ab, Dollar, Quote, Ess, Backtick]);
374
375        #[derive(Clone, Debug, Eq, PartialEq)]
376        struct ActionArg {
377            a: &'static str,
378        }
379
380        let keymap = Keymap::new(vec![
381            KeyBinding::new("a", A("x".to_string()), Some("a")),
382            KeyBinding::new("b", B, Some("a")),
383            KeyBinding::new("a b", Ab, Some("a || b")),
384            KeyBinding::new("$", Dollar, Some("a")),
385            KeyBinding::new("\"", Quote, Some("a")),
386            KeyBinding::new("alt-s", Ess, Some("a")),
387            KeyBinding::new("ctrl-`", Backtick, Some("a")),
388        ]);
389
390        let mut context_a = KeyContext::default();
391        context_a.add("a");
392
393        let mut context_b = KeyContext::default();
394        context_b.add("b");
395
396        let mut matcher = KeystrokeMatcher::new(Arc::new(Mutex::new(keymap)));
397
398        // Basic match
399        assert_eq!(
400            matcher.match_keystroke(&Keystroke::parse("a").unwrap(), &[context_a.clone()]),
401            KeyMatch::Some(vec![Box::new(A("x".to_string()))])
402        );
403        matcher.clear_pending();
404
405        // Multi-keystroke match
406        assert_eq!(
407            matcher.match_keystroke(&Keystroke::parse("a").unwrap(), &[context_b.clone()]),
408            KeyMatch::Pending
409        );
410        assert_eq!(
411            matcher.match_keystroke(&Keystroke::parse("b").unwrap(), &[context_b.clone()]),
412            KeyMatch::Some(vec![Box::new(Ab)])
413        );
414        matcher.clear_pending();
415
416        // Failed matches don't interfere with matching subsequent keys
417        assert_eq!(
418            matcher.match_keystroke(&Keystroke::parse("x").unwrap(), &[context_a.clone()]),
419            KeyMatch::None
420        );
421        assert_eq!(
422            matcher.match_keystroke(&Keystroke::parse("a").unwrap(), &[context_a.clone()]),
423            KeyMatch::Some(vec![Box::new(A("x".to_string()))])
424        );
425        matcher.clear_pending();
426
427        let mut context_c = KeyContext::default();
428        context_c.add("c");
429
430        assert_eq!(
431            matcher.match_keystroke(
432                &Keystroke::parse("a").unwrap(),
433                &[context_c.clone(), context_b.clone()]
434            ),
435            KeyMatch::Pending
436        );
437        assert_eq!(
438            matcher.match_keystroke(&Keystroke::parse("b").unwrap(), &[context_b.clone()]),
439            KeyMatch::Some(vec![Box::new(Ab)])
440        );
441
442        // handle Czech $ (option + 4 key)
443        assert_eq!(
444            matcher.match_keystroke(&Keystroke::parse("alt-รง->$").unwrap(), &[context_a.clone()]),
445            KeyMatch::Some(vec![Box::new(Dollar)])
446        );
447
448        // handle Brazillian quote (quote key then space key)
449        assert_eq!(
450            matcher.match_keystroke(
451                &Keystroke::parse("space->\"").unwrap(),
452                &[context_a.clone()]
453            ),
454            KeyMatch::Some(vec![Box::new(Quote)])
455        );
456
457        // handle ctrl+` on a brazillian keyboard
458        assert_eq!(
459            matcher.match_keystroke(&Keystroke::parse("ctrl-->`").unwrap(), &[context_a.clone()]),
460            KeyMatch::Some(vec![Box::new(Backtick)])
461        );
462
463        // handle alt-s on a US keyboard
464        assert_eq!(
465            matcher.match_keystroke(&Keystroke::parse("alt-s->รŸ").unwrap(), &[context_a.clone()]),
466            KeyMatch::Some(vec![Box::new(Ess)])
467        );
468    }
469}