1mod binding;
2mod keymap;
3mod keymap_context;
4mod keystroke;
5
6use std::{any::TypeId, fmt::Debug};
7
8use collections::HashMap;
9use smallvec::SmallVec;
10
11use crate::{Action, NoAction};
12
13pub use binding::{Binding, BindingMatchResult};
14pub use keymap::Keymap;
15pub use keymap_context::{KeymapContext, KeymapContextPredicate};
16pub use keystroke::Keystroke;
17
18pub struct KeymapMatcher {
19 pub contexts: Vec<KeymapContext>,
20 pending_views: HashMap<usize, KeymapContext>,
21 pending_keystrokes: Vec<Keystroke>,
22 keymap: Keymap,
23}
24
25impl KeymapMatcher {
26 pub fn new(keymap: Keymap) -> Self {
27 Self {
28 contexts: Vec::new(),
29 pending_views: Default::default(),
30 pending_keystrokes: Vec::new(),
31 keymap,
32 }
33 }
34
35 pub fn set_keymap(&mut self, keymap: Keymap) {
36 self.clear_pending();
37 self.keymap = keymap;
38 }
39
40 pub fn add_bindings<T: IntoIterator<Item = Binding>>(&mut self, bindings: T) {
41 self.clear_pending();
42 self.keymap.add_bindings(bindings);
43 }
44
45 pub fn clear_bindings(&mut self) {
46 self.clear_pending();
47 self.keymap.clear();
48 }
49
50 pub fn bindings_for_action(&self, action_id: TypeId) -> impl Iterator<Item = &Binding> {
51 self.keymap.bindings_for_action(action_id)
52 }
53
54 pub fn clear_pending(&mut self) {
55 self.pending_keystrokes.clear();
56 self.pending_views.clear();
57 }
58
59 pub fn has_pending_keystrokes(&self) -> bool {
60 !self.pending_keystrokes.is_empty()
61 }
62
63 /// Pushes a keystroke onto the matcher.
64 /// The result of the new keystroke is returned:
65 /// MatchResult::None =>
66 /// No match is valid for this key given any pending keystrokes.
67 /// MatchResult::Pending =>
68 /// There exist bindings which are still waiting for more keys.
69 /// MatchResult::Complete(matches) =>
70 /// 1 or more bindings have received the necessary key presses.
71 /// The order of the matched actions is by position of the matching first,
72 // and order in the keymap second.
73 pub fn push_keystroke(
74 &mut self,
75 keystroke: Keystroke,
76 mut dispatch_path: Vec<(usize, KeymapContext)>,
77 ) -> MatchResult {
78 // Collect matched bindings into an ordered list using the position in the matching binding first,
79 // and then the order the binding matched in the view tree second.
80 // The key is the reverse position of the binding in the bindings list so that later bindings
81 // match before earlier ones in the user's config
82 let mut matched_bindings: Vec<(usize, Box<dyn Action>)> = Default::default();
83 let no_action_id = (NoAction {}).id();
84
85 let first_keystroke = self.pending_keystrokes.is_empty();
86 let mut pending_key = None;
87 let mut previous_keystrokes = self.pending_keystrokes.clone();
88
89 self.contexts.clear();
90 self.contexts
91 .extend(dispatch_path.iter_mut().map(|e| std::mem::take(&mut e.1)));
92
93 // Find the bindings which map the pending keystrokes and current context
94 for (i, (view_id, _)) in dispatch_path.iter().enumerate() {
95 // Don't require pending view entry if there are no pending keystrokes
96 if !first_keystroke && !self.pending_views.contains_key(view_id) {
97 continue;
98 }
99
100 // If there is a previous view context, invalidate that view if it
101 // has changed
102 if let Some(previous_view_context) = self.pending_views.remove(view_id) {
103 if previous_view_context != self.contexts[i] {
104 continue;
105 }
106 }
107
108 for binding in self.keymap.bindings().iter().rev() {
109 for possibility in keystroke.match_possibilities() {
110 previous_keystrokes.push(possibility.clone());
111 match binding.match_keys_and_context(&previous_keystrokes, &self.contexts[i..])
112 {
113 BindingMatchResult::Complete(action) => {
114 if action.id() != no_action_id {
115 matched_bindings.push((*view_id, action));
116 }
117 }
118 BindingMatchResult::Partial => {
119 if pending_key == None || pending_key == Some(possibility.clone()) {
120 self.pending_views
121 .insert(*view_id, self.contexts[i].clone());
122 pending_key = Some(possibility)
123 }
124 }
125 _ => {}
126 }
127 previous_keystrokes.pop();
128 }
129 }
130 }
131
132 if pending_key.is_some() {
133 self.pending_keystrokes.push(pending_key.unwrap());
134 } else {
135 self.clear_pending();
136 }
137
138 if !matched_bindings.is_empty() {
139 // Collect the sorted matched bindings into the final vec for ease of use
140 // Matched bindings are in order by precedence
141 MatchResult::Matches(matched_bindings)
142 } else if !self.pending_keystrokes.is_empty() {
143 MatchResult::Pending
144 } else {
145 MatchResult::None
146 }
147 }
148
149 pub fn keystrokes_for_action(
150 &self,
151 action: &dyn Action,
152 contexts: &[KeymapContext],
153 ) -> Option<SmallVec<[Keystroke; 2]>> {
154 self.keymap
155 .bindings()
156 .iter()
157 .rev()
158 .find_map(|binding| binding.keystrokes_for_action(action, contexts))
159 }
160}
161
162impl Default for KeymapMatcher {
163 fn default() -> Self {
164 Self::new(Keymap::default())
165 }
166}
167
168pub enum MatchResult {
169 None,
170 Pending,
171 Matches(Vec<(usize, Box<dyn Action>)>),
172}
173
174impl Debug for MatchResult {
175 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
176 match self {
177 MatchResult::None => f.debug_struct("MatchResult::None").finish(),
178 MatchResult::Pending => f.debug_struct("MatchResult::Pending").finish(),
179 MatchResult::Matches(matches) => f
180 .debug_list()
181 .entries(
182 matches
183 .iter()
184 .map(|(view_id, action)| format!("{view_id}, {}", action.name())),
185 )
186 .finish(),
187 }
188 }
189}
190
191impl PartialEq for MatchResult {
192 fn eq(&self, other: &Self) -> bool {
193 match (self, other) {
194 (MatchResult::None, MatchResult::None) => true,
195 (MatchResult::Pending, MatchResult::Pending) => true,
196 (MatchResult::Matches(matches), MatchResult::Matches(other_matches)) => {
197 matches.len() == other_matches.len()
198 && matches.iter().zip(other_matches.iter()).all(
199 |((view_id, action), (other_view_id, other_action))| {
200 view_id == other_view_id && action.eq(other_action.as_ref())
201 },
202 )
203 }
204 _ => false,
205 }
206 }
207}
208
209impl Eq for MatchResult {}
210
211impl Clone for MatchResult {
212 fn clone(&self) -> Self {
213 match self {
214 MatchResult::None => MatchResult::None,
215 MatchResult::Pending => MatchResult::Pending,
216 MatchResult::Matches(matches) => MatchResult::Matches(
217 matches
218 .iter()
219 .map(|(view_id, action)| (*view_id, Action::boxed_clone(action.as_ref())))
220 .collect(),
221 ),
222 }
223 }
224}
225
226#[cfg(test)]
227mod tests {
228 use anyhow::Result;
229 use serde::Deserialize;
230
231 use crate::{actions, impl_actions, keymap_matcher::KeymapContext};
232
233 use super::*;
234
235 #[test]
236 fn test_keymap_and_view_ordering() -> Result<()> {
237 actions!(test, [EditorAction, ProjectPanelAction]);
238
239 let mut editor = KeymapContext::default();
240 editor.add_identifier("Editor");
241
242 let mut project_panel = KeymapContext::default();
243 project_panel.add_identifier("ProjectPanel");
244
245 // Editor 'deeper' in than project panel
246 let dispatch_path = vec![(2, editor), (1, project_panel)];
247
248 // But editor actions 'higher' up in keymap
249 let keymap = Keymap::new(vec![
250 Binding::new("left", EditorAction, Some("Editor")),
251 Binding::new("left", ProjectPanelAction, Some("ProjectPanel")),
252 ]);
253
254 let mut matcher = KeymapMatcher::new(keymap);
255
256 assert_eq!(
257 matcher.push_keystroke(Keystroke::parse("left")?, dispatch_path.clone()),
258 MatchResult::Matches(vec![
259 (2, Box::new(EditorAction)),
260 (1, Box::new(ProjectPanelAction)),
261 ]),
262 );
263
264 Ok(())
265 }
266
267 #[test]
268 fn test_push_keystroke() -> Result<()> {
269 actions!(test, [B, AB, C, D, DA, E, EF]);
270
271 let mut context1 = KeymapContext::default();
272 context1.add_identifier("1");
273
274 let mut context2 = KeymapContext::default();
275 context2.add_identifier("2");
276
277 let dispatch_path = vec![(2, context2), (1, context1)];
278
279 let keymap = Keymap::new(vec![
280 Binding::new("a b", AB, Some("1")),
281 Binding::new("b", B, Some("2")),
282 Binding::new("c", C, Some("2")),
283 Binding::new("d", D, Some("1")),
284 Binding::new("d", D, Some("2")),
285 Binding::new("d a", DA, Some("2")),
286 ]);
287
288 let mut matcher = KeymapMatcher::new(keymap);
289
290 // Binding with pending prefix always takes precedence
291 assert_eq!(
292 matcher.push_keystroke(Keystroke::parse("a")?, dispatch_path.clone()),
293 MatchResult::Pending,
294 );
295 // B alone doesn't match because a was pending, so AB is returned instead
296 assert_eq!(
297 matcher.push_keystroke(Keystroke::parse("b")?, dispatch_path.clone()),
298 MatchResult::Matches(vec![(1, Box::new(AB))]),
299 );
300 assert!(!matcher.has_pending_keystrokes());
301
302 // Without an a prefix, B is dispatched like expected
303 assert_eq!(
304 matcher.push_keystroke(Keystroke::parse("b")?, dispatch_path.clone()),
305 MatchResult::Matches(vec![(2, Box::new(B))]),
306 );
307 assert!(!matcher.has_pending_keystrokes());
308
309 // If a is prefixed, C will not be dispatched because there
310 // was a pending binding for it
311 assert_eq!(
312 matcher.push_keystroke(Keystroke::parse("a")?, dispatch_path.clone()),
313 MatchResult::Pending,
314 );
315 assert_eq!(
316 matcher.push_keystroke(Keystroke::parse("c")?, dispatch_path.clone()),
317 MatchResult::None,
318 );
319 assert!(!matcher.has_pending_keystrokes());
320
321 // If a single keystroke matches multiple bindings in the tree
322 // all of them are returned so that we can fallback if the action
323 // handler decides to propagate the action
324 assert_eq!(
325 matcher.push_keystroke(Keystroke::parse("d")?, dispatch_path.clone()),
326 MatchResult::Matches(vec![(2, Box::new(D)), (1, Box::new(D))]),
327 );
328
329 // If none of the d action handlers consume the binding, a pending
330 // binding may then be used
331 assert_eq!(
332 matcher.push_keystroke(Keystroke::parse("a")?, dispatch_path.clone()),
333 MatchResult::Matches(vec![(2, Box::new(DA))]),
334 );
335 assert!(!matcher.has_pending_keystrokes());
336
337 Ok(())
338 }
339
340 #[test]
341 fn test_keystroke_parsing() -> Result<()> {
342 assert_eq!(
343 Keystroke::parse("ctrl-p")?,
344 Keystroke {
345 key: "p".into(),
346 ctrl: true,
347 alt: false,
348 shift: false,
349 cmd: false,
350 function: false,
351 ime_key: None,
352 }
353 );
354
355 assert_eq!(
356 Keystroke::parse("alt-shift-down")?,
357 Keystroke {
358 key: "down".into(),
359 ctrl: false,
360 alt: true,
361 shift: true,
362 cmd: false,
363 function: false,
364 ime_key: None,
365 }
366 );
367
368 assert_eq!(
369 Keystroke::parse("shift-cmd--")?,
370 Keystroke {
371 key: "-".into(),
372 ctrl: false,
373 alt: false,
374 shift: true,
375 cmd: true,
376 function: false,
377 ime_key: None,
378 }
379 );
380
381 Ok(())
382 }
383
384 #[test]
385 fn test_context_predicate_parsing() -> Result<()> {
386 use KeymapContextPredicate::*;
387
388 assert_eq!(
389 KeymapContextPredicate::parse("a && (b == c || d != e)")?,
390 And(
391 Box::new(Identifier("a".into())),
392 Box::new(Or(
393 Box::new(Equal("b".into(), "c".into())),
394 Box::new(NotEqual("d".into(), "e".into())),
395 ))
396 )
397 );
398
399 assert_eq!(
400 KeymapContextPredicate::parse("!a")?,
401 Not(Box::new(Identifier("a".into())),)
402 );
403
404 Ok(())
405 }
406
407 #[test]
408 fn test_context_predicate_eval() {
409 let predicate = KeymapContextPredicate::parse("a && b || c == d").unwrap();
410
411 let mut context = KeymapContext::default();
412 context.add_identifier("a");
413 assert!(!predicate.eval(&[context]));
414
415 let mut context = KeymapContext::default();
416 context.add_identifier("a");
417 context.add_identifier("b");
418 assert!(predicate.eval(&[context]));
419
420 let mut context = KeymapContext::default();
421 context.add_identifier("a");
422 context.add_key("c", "x");
423 assert!(!predicate.eval(&[context]));
424
425 let mut context = KeymapContext::default();
426 context.add_identifier("a");
427 context.add_key("c", "d");
428 assert!(predicate.eval(&[context]));
429
430 let predicate = KeymapContextPredicate::parse("!a").unwrap();
431 assert!(predicate.eval(&[KeymapContext::default()]));
432 }
433
434 #[test]
435 fn test_context_child_predicate_eval() {
436 let predicate = KeymapContextPredicate::parse("a && b > c").unwrap();
437 let contexts = [
438 context_set(&["e", "f"]),
439 context_set(&["c", "d"]), // match this context
440 context_set(&["a", "b"]),
441 ];
442
443 assert!(!predicate.eval(&contexts[0..]));
444 assert!(predicate.eval(&contexts[1..]));
445 assert!(!predicate.eval(&contexts[2..]));
446
447 let predicate = KeymapContextPredicate::parse("a && b > c && !d > e").unwrap();
448 let contexts = [
449 context_set(&["f"]),
450 context_set(&["e"]), // only match this context
451 context_set(&["c"]),
452 context_set(&["a", "b"]),
453 context_set(&["e"]),
454 context_set(&["c", "d"]),
455 context_set(&["a", "b"]),
456 ];
457
458 assert!(!predicate.eval(&contexts[0..]));
459 assert!(predicate.eval(&contexts[1..]));
460 assert!(!predicate.eval(&contexts[2..]));
461 assert!(!predicate.eval(&contexts[3..]));
462 assert!(!predicate.eval(&contexts[4..]));
463 assert!(!predicate.eval(&contexts[5..]));
464 assert!(!predicate.eval(&contexts[6..]));
465
466 fn context_set(names: &[&str]) -> KeymapContext {
467 let mut keymap = KeymapContext::new();
468 names
469 .iter()
470 .for_each(|name| keymap.add_identifier(name.to_string()));
471 keymap
472 }
473 }
474
475 #[test]
476 fn test_matcher() -> Result<()> {
477 #[derive(Clone, Deserialize, PartialEq, Eq, Debug)]
478 pub struct A(pub String);
479 impl_actions!(test, [A]);
480 actions!(test, [B, Ab, Dollar, Quote, Ess, Backtick]);
481
482 #[derive(Clone, Debug, Eq, PartialEq)]
483 struct ActionArg {
484 a: &'static str,
485 }
486
487 let keymap = Keymap::new(vec![
488 Binding::new("a", A("x".to_string()), Some("a")),
489 Binding::new("b", B, Some("a")),
490 Binding::new("a b", Ab, Some("a || b")),
491 Binding::new("$", Dollar, Some("a")),
492 Binding::new("\"", Quote, Some("a")),
493 Binding::new("alt-s", Ess, Some("a")),
494 Binding::new("ctrl-`", Backtick, Some("a")),
495 ]);
496
497 let mut context_a = KeymapContext::default();
498 context_a.add_identifier("a");
499
500 let mut context_b = KeymapContext::default();
501 context_b.add_identifier("b");
502
503 let mut matcher = KeymapMatcher::new(keymap);
504
505 // Basic match
506 assert_eq!(
507 matcher.push_keystroke(Keystroke::parse("a")?, vec![(1, context_a.clone())]),
508 MatchResult::Matches(vec![(1, Box::new(A("x".to_string())))])
509 );
510 matcher.clear_pending();
511
512 // Multi-keystroke match
513 assert_eq!(
514 matcher.push_keystroke(Keystroke::parse("a")?, vec![(1, context_b.clone())]),
515 MatchResult::Pending
516 );
517 assert_eq!(
518 matcher.push_keystroke(Keystroke::parse("b")?, vec![(1, context_b.clone())]),
519 MatchResult::Matches(vec![(1, Box::new(Ab))])
520 );
521 matcher.clear_pending();
522
523 // Failed matches don't interfere with matching subsequent keys
524 assert_eq!(
525 matcher.push_keystroke(Keystroke::parse("x")?, vec![(1, context_a.clone())]),
526 MatchResult::None
527 );
528 assert_eq!(
529 matcher.push_keystroke(Keystroke::parse("a")?, vec![(1, context_a.clone())]),
530 MatchResult::Matches(vec![(1, Box::new(A("x".to_string())))])
531 );
532 matcher.clear_pending();
533
534 // Pending keystrokes are cleared when the context changes
535 assert_eq!(
536 matcher.push_keystroke(Keystroke::parse("a")?, vec![(1, context_b.clone())]),
537 MatchResult::Pending
538 );
539 assert_eq!(
540 matcher.push_keystroke(Keystroke::parse("b")?, vec![(1, context_a.clone())]),
541 MatchResult::None
542 );
543 matcher.clear_pending();
544
545 let mut context_c = KeymapContext::default();
546 context_c.add_identifier("c");
547
548 // Pending keystrokes are maintained per-view
549 assert_eq!(
550 matcher.push_keystroke(
551 Keystroke::parse("a")?,
552 vec![(1, context_b.clone()), (2, context_c.clone())]
553 ),
554 MatchResult::Pending
555 );
556 assert_eq!(
557 matcher.push_keystroke(Keystroke::parse("b")?, vec![(1, context_b.clone())]),
558 MatchResult::Matches(vec![(1, Box::new(Ab))])
559 );
560
561 // handle Czech $ (option + 4 key)
562 assert_eq!(
563 matcher.push_keystroke(Keystroke::parse("alt-รง->$")?, vec![(1, context_a.clone())]),
564 MatchResult::Matches(vec![(1, Box::new(Dollar))])
565 );
566
567 // handle Brazillian quote (quote key then space key)
568 assert_eq!(
569 matcher.push_keystroke(Keystroke::parse("space->\"")?, vec![(1, context_a.clone())]),
570 MatchResult::Matches(vec![(1, Box::new(Quote))])
571 );
572
573 // handle ctrl+` on a brazillian keyboard
574 assert_eq!(
575 matcher.push_keystroke(Keystroke::parse("ctrl-->`")?, vec![(1, context_a.clone())]),
576 MatchResult::Matches(vec![(1, Box::new(Backtick))])
577 );
578
579 // handle alt-s on a US keyboard
580 assert_eq!(
581 matcher.push_keystroke(Keystroke::parse("alt-s->ร")?, vec![(1, context_a.clone())]),
582 MatchResult::Matches(vec![(1, Box::new(Ess))])
583 );
584
585 Ok(())
586 }
587}