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}