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}