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}