1mod binding;
2mod context;
3
4pub use binding::*;
5pub use context::*;
6
7use crate::{Action, AsKeystroke, Keystroke, is_no_action};
8use collections::{HashMap, HashSet};
9use smallvec::SmallVec;
10use std::any::TypeId;
11
12#[derive(Copy, Clone, Eq, PartialEq, Default)]
15pub struct KeymapVersion(usize);
16
17#[derive(Default)]
19pub struct Keymap {
20 bindings: Vec<KeyBinding>,
21 binding_indices_by_action_id: HashMap<TypeId, SmallVec<[usize; 3]>>,
22 no_action_binding_indices: Vec<usize>,
23 version: KeymapVersion,
24}
25
26#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
28pub struct BindingIndex(usize);
29
30impl Keymap {
31 pub fn new(bindings: Vec<KeyBinding>) -> Self {
33 let mut this = Self::default();
34 this.add_bindings(bindings);
35 this
36 }
37
38 pub fn version(&self) -> KeymapVersion {
40 self.version
41 }
42
43 pub fn add_bindings<T: IntoIterator<Item = KeyBinding>>(&mut self, bindings: T) {
45 for binding in bindings {
46 let action_id = binding.action().as_any().type_id();
47 if is_no_action(&*binding.action) {
48 self.no_action_binding_indices.push(self.bindings.len());
49 } else {
50 self.binding_indices_by_action_id
51 .entry(action_id)
52 .or_default()
53 .push(self.bindings.len());
54 }
55 self.bindings.push(binding);
56 }
57
58 self.version.0 += 1;
59 }
60
61 pub fn clear(&mut self) {
63 self.bindings.clear();
64 self.binding_indices_by_action_id.clear();
65 self.no_action_binding_indices.clear();
66 self.version.0 += 1;
67 }
68
69 pub fn bindings(&self) -> impl DoubleEndedIterator<Item = &KeyBinding> + ExactSizeIterator {
71 self.bindings.iter()
72 }
73
74 pub fn bindings_for_action<'a>(
77 &'a self,
78 action: &'a dyn Action,
79 ) -> impl 'a + DoubleEndedIterator<Item = &'a KeyBinding> {
80 let action_id = action.type_id();
81 let binding_indices = self
82 .binding_indices_by_action_id
83 .get(&action_id)
84 .map_or(&[] as _, SmallVec::as_slice)
85 .iter();
86
87 binding_indices.filter_map(|ix| {
88 let binding = &self.bindings[*ix];
89 if !binding.action().partial_eq(action) {
90 return None;
91 }
92
93 for null_ix in &self.no_action_binding_indices {
94 if null_ix > ix {
95 let null_binding = &self.bindings[*null_ix];
96 if null_binding.keystrokes == binding.keystrokes {
97 let null_binding_matches =
98 match (&null_binding.context_predicate, &binding.context_predicate) {
99 (None, _) => true,
100 (Some(_), None) => false,
101 (Some(null_predicate), Some(predicate)) => {
102 null_predicate.is_superset(predicate)
103 }
104 };
105 if null_binding_matches {
106 return None;
107 }
108 }
109 }
110 }
111
112 Some(binding)
113 })
114 }
115
116 pub fn all_bindings_for_input(&self, input: &[Keystroke]) -> Vec<KeyBinding> {
119 self.bindings()
120 .rev()
121 .filter_map(|binding| {
122 binding.match_keystrokes(input).filter(|pending| !pending)?;
123 Some(binding.clone())
124 })
125 .collect()
126 }
127
128 pub fn bindings_for_input(
143 &self,
144 input: &[impl AsKeystroke],
145 context_stack: &[KeyContext],
146 ) -> (SmallVec<[KeyBinding; 1]>, bool) {
147 let (bindings, pending) = self.resolve_bindings_for_input(
148 input,
149 context_stack,
150 self.bindings().enumerate().rev(),
151 );
152 (
153 bindings
154 .into_iter()
155 .map(|(_, binding)| binding.clone())
156 .collect(),
157 pending,
158 )
159 }
160
161 fn resolve_bindings_for_input<'a>(
170 &self,
171 input: &[impl AsKeystroke],
172 context_stack: &[KeyContext],
173 keymap_bindings: impl IntoIterator<Item = (usize, &'a KeyBinding)>,
174 ) -> (SmallVec<[(BindingIndex, &'a KeyBinding); 1]>, bool) {
175 let mut matched_bindings = SmallVec::<[(usize, BindingIndex, &KeyBinding); 1]>::new();
176 let mut pending_bindings = SmallVec::<[(BindingIndex, &KeyBinding); 1]>::new();
177
178 for (ix, binding) in keymap_bindings {
179 let Some(depth) = self.binding_enabled(binding, context_stack) else {
180 continue;
181 };
182 let Some(pending) = binding.match_keystrokes(input) else {
183 continue;
184 };
185
186 if !pending {
187 matched_bindings.push((depth, BindingIndex(ix), binding));
188 } else {
189 pending_bindings.push((BindingIndex(ix), binding));
190 }
191 }
192
193 matched_bindings.sort_by(|(depth_a, ix_a, _), (depth_b, ix_b, _)| {
194 depth_b.cmp(depth_a).then(ix_b.cmp(ix_a))
195 });
196
197 let mut bindings: SmallVec<[_; 1]> = SmallVec::new();
198 let mut first_binding_index = None;
199
200 for (_, ix, binding) in matched_bindings {
201 if is_no_action(&*binding.action) {
202 if let Some(meta) = binding.meta {
205 if meta.is_user() {
206 break;
207 }
208 } else {
209 break;
211 }
212 continue;
214 }
215 bindings.push((ix, binding));
216 first_binding_index.get_or_insert(ix);
217 }
218
219 let mut pending = HashSet::default();
220 for (ix, binding) in pending_bindings.into_iter().rev() {
221 if let Some(binding_ix) = first_binding_index
222 && binding_ix > ix
223 {
224 continue;
225 }
226 if is_no_action(&*binding.action) {
227 pending.remove(&&binding.keystrokes);
228 continue;
229 }
230 pending.insert(&binding.keystrokes);
231 }
232
233 (bindings, !pending.is_empty())
234 }
235
236 pub fn possible_next_bindings_for_input(
242 &self,
243 input: &[Keystroke],
244 context_stack: &[KeyContext],
245 ) -> Vec<KeyBinding> {
246 let bindings_extending_input = self
248 .bindings()
249 .enumerate()
250 .rev()
251 .filter(|(_, binding)| {
252 binding
253 .match_keystrokes(input)
254 .is_some_and(|pending| pending)
255 })
256 .collect::<Vec<_>>();
257
258 let mut full_input = SmallVec::<[&Keystroke; 4]>::new();
259 let mut candidates = bindings_extending_input
260 .iter()
261 .filter(|(_, binding)| !is_no_action(&*binding.action))
262 .filter_map(|&(ix, binding)| {
263 let depth = self.binding_enabled(binding, context_stack)?;
264 full_input.clear();
268 full_input.extend(input.iter());
269 full_input.extend(
270 binding.keystrokes[input.len()..]
271 .iter()
272 .map(AsKeystroke::as_keystroke),
273 );
274 let (dispatched, _) = self.resolve_bindings_for_input(
275 &full_input,
276 context_stack,
277 bindings_extending_input.iter().copied(),
278 );
279 dispatched
280 .iter()
281 .any(|(dispatched_ix, _)| *dispatched_ix == BindingIndex(ix))
282 .then_some((depth, ix, binding))
283 })
284 .collect::<Vec<_>>();
285 candidates.sort_unstable_by(|(depth_a, ix_a, _), (depth_b, ix_b, _)| {
286 depth_b.cmp(depth_a).then(ix_b.cmp(ix_a))
287 });
288
289 candidates
290 .into_iter()
291 .map(|(_, _, binding)| binding.clone())
292 .collect()
293 }
294 fn binding_enabled(&self, binding: &KeyBinding, contexts: &[KeyContext]) -> Option<usize> {
297 if let Some(predicate) = &binding.context_predicate {
298 predicate.depth_of(contexts)
299 } else {
300 Some(contexts.len())
301 }
302 }
303}
304
305#[cfg(test)]
306mod tests {
307 use super::*;
308 use crate as gpui;
309 use gpui::NoAction;
310
311 actions!(
312 test_only,
313 [ActionAlpha, ActionBeta, ActionGamma, ActionDelta,]
314 );
315
316 #[test]
317 fn test_keymap() {
318 let bindings = [
319 KeyBinding::new("ctrl-a", ActionAlpha {}, None),
320 KeyBinding::new("ctrl-a", ActionBeta {}, Some("pane")),
321 KeyBinding::new("ctrl-a", ActionGamma {}, Some("editor && mode==full")),
322 ];
323
324 let mut keymap = Keymap::default();
325 keymap.add_bindings(bindings.clone());
326
327 assert_eq!(keymap.binding_enabled(&bindings[0], &[]), Some(0));
329 assert_eq!(
330 keymap.binding_enabled(&bindings[0], &[KeyContext::parse("terminal").unwrap()]),
331 Some(1)
332 );
333
334 assert_eq!(
336 keymap.binding_enabled(&bindings[1], &[KeyContext::parse("barf x=y").unwrap()]),
337 None
338 );
339 assert_eq!(
340 keymap.binding_enabled(&bindings[1], &[KeyContext::parse("pane x=y").unwrap()]),
341 Some(1)
342 );
343
344 assert_eq!(
345 keymap.binding_enabled(&bindings[2], &[KeyContext::parse("editor").unwrap()]),
346 None
347 );
348 assert_eq!(
349 keymap.binding_enabled(
350 &bindings[2],
351 &[KeyContext::parse("editor mode=full").unwrap()]
352 ),
353 Some(1)
354 );
355 }
356
357 #[test]
358 fn test_depth_precedence() {
359 let bindings = [
360 KeyBinding::new("ctrl-a", ActionBeta {}, Some("pane")),
361 KeyBinding::new("ctrl-a", ActionGamma {}, Some("editor")),
362 ];
363
364 let mut keymap = Keymap::default();
365 keymap.add_bindings(bindings);
366
367 let (result, pending) = keymap.bindings_for_input(
368 &[Keystroke::parse("ctrl-a").unwrap()],
369 &[
370 KeyContext::parse("pane").unwrap(),
371 KeyContext::parse("editor").unwrap(),
372 ],
373 );
374
375 assert!(!pending);
376 assert_eq!(result.len(), 2);
377 assert!(result[0].action.partial_eq(&ActionGamma {}));
378 assert!(result[1].action.partial_eq(&ActionBeta {}));
379 }
380
381 #[test]
382 fn test_keymap_disabled() {
383 let bindings = [
384 KeyBinding::new("ctrl-a", ActionAlpha {}, Some("editor")),
385 KeyBinding::new("ctrl-b", ActionAlpha {}, Some("editor")),
386 KeyBinding::new("ctrl-a", NoAction {}, Some("editor && mode==full")),
387 KeyBinding::new("ctrl-b", NoAction {}, None),
388 ];
389
390 let mut keymap = Keymap::default();
391 keymap.add_bindings(bindings);
392
393 assert!(
395 keymap
396 .bindings_for_input(
397 &[Keystroke::parse("ctrl-a").unwrap()],
398 &[KeyContext::parse("barf").unwrap()],
399 )
400 .0
401 .is_empty()
402 );
403 assert!(
404 !keymap
405 .bindings_for_input(
406 &[Keystroke::parse("ctrl-a").unwrap()],
407 &[KeyContext::parse("editor").unwrap()],
408 )
409 .0
410 .is_empty()
411 );
412
413 assert!(
415 keymap
416 .bindings_for_input(
417 &[Keystroke::parse("ctrl-a").unwrap()],
418 &[KeyContext::parse("editor mode=full").unwrap()],
419 )
420 .0
421 .is_empty()
422 );
423
424 assert!(
426 keymap
427 .bindings_for_input(
428 &[Keystroke::parse("ctrl-b").unwrap()],
429 &[KeyContext::parse("barf").unwrap()],
430 )
431 .0
432 .is_empty()
433 );
434 }
435
436 #[test]
437 fn test_multiple_keystroke_binding_disabled() {
439 let bindings = [
440 KeyBinding::new("space w w", ActionAlpha {}, Some("workspace")),
441 KeyBinding::new("space w w", NoAction {}, Some("editor")),
442 ];
443
444 let mut keymap = Keymap::default();
445 keymap.add_bindings(bindings);
446
447 let space = || Keystroke::parse("space").unwrap();
448 let w = || Keystroke::parse("w").unwrap();
449
450 let space_w = [space(), w()];
451 let space_w_w = [space(), w(), w()];
452
453 let workspace_context = || [KeyContext::parse("workspace").unwrap()];
454
455 let editor_workspace_context = || {
456 [
457 KeyContext::parse("workspace").unwrap(),
458 KeyContext::parse("editor").unwrap(),
459 ]
460 };
461
462 let space_workspace = keymap.bindings_for_input(&[space()], &workspace_context());
464 assert!(space_workspace.0.is_empty());
465 assert!(space_workspace.1);
466
467 let space_editor = keymap.bindings_for_input(&[space()], &editor_workspace_context());
468 assert!(space_editor.0.is_empty());
469 assert!(!space_editor.1);
470
471 let space_w_workspace = keymap.bindings_for_input(&space_w, &workspace_context());
473 assert!(space_w_workspace.0.is_empty());
474 assert!(space_w_workspace.1);
475
476 let space_w_editor = keymap.bindings_for_input(&space_w, &editor_workspace_context());
477 assert!(space_w_editor.0.is_empty());
478 assert!(!space_w_editor.1);
479
480 let space_w_w_workspace = keymap.bindings_for_input(&space_w_w, &workspace_context());
482 assert!(!space_w_w_workspace.0.is_empty());
483 assert!(!space_w_w_workspace.1);
484
485 let space_w_w_editor = keymap.bindings_for_input(&space_w_w, &editor_workspace_context());
486 assert!(space_w_w_editor.0.is_empty());
487 assert!(!space_w_w_editor.1);
488
489 let bindings = [
492 KeyBinding::new("space w w", ActionAlpha {}, Some("workspace")),
493 KeyBinding::new("space w w", NoAction {}, Some("editor")),
494 KeyBinding::new("space w x", ActionAlpha {}, Some("editor")),
495 ];
496 let mut keymap = Keymap::default();
497 keymap.add_bindings(bindings);
498
499 let space_editor = keymap.bindings_for_input(&[space()], &editor_workspace_context());
500 assert!(space_editor.0.is_empty());
501 assert!(space_editor.1);
502
503 let bindings = [
506 KeyBinding::new("space w w", ActionAlpha {}, Some("workspace")),
507 KeyBinding::new("space w x", ActionAlpha {}, Some("editor")),
508 KeyBinding::new("space w w", NoAction {}, Some("editor")),
509 ];
510 let mut keymap = Keymap::default();
511 keymap.add_bindings(bindings);
512
513 let space_editor = keymap.bindings_for_input(&[space()], &editor_workspace_context());
514 assert!(space_editor.0.is_empty());
515 assert!(space_editor.1);
516
517 let bindings = [
520 KeyBinding::new("space w w", ActionAlpha {}, Some("workspace")),
521 KeyBinding::new("space w x", ActionAlpha {}, Some("workspace")),
522 KeyBinding::new("space w w", NoAction {}, Some("editor")),
523 ];
524 let mut keymap = Keymap::default();
525 keymap.add_bindings(bindings);
526
527 let space_editor = keymap.bindings_for_input(&[space()], &editor_workspace_context());
528 assert!(space_editor.0.is_empty());
529 assert!(space_editor.1);
530 }
531
532 #[test]
533 fn test_override_multikey() {
534 let bindings = [
535 KeyBinding::new("ctrl-w left", ActionAlpha {}, Some("editor")),
536 KeyBinding::new("ctrl-w", NoAction {}, Some("editor")),
537 ];
538
539 let mut keymap = Keymap::default();
540 keymap.add_bindings(bindings);
541
542 let (result, pending) = keymap.bindings_for_input(
544 &[Keystroke::parse("ctrl-w").unwrap()],
545 &[KeyContext::parse("editor").unwrap()],
546 );
547 assert!(result.is_empty());
548 assert!(pending);
549
550 let bindings = [
551 KeyBinding::new("ctrl-w left", ActionAlpha {}, Some("editor")),
552 KeyBinding::new("ctrl-w", ActionBeta {}, Some("editor")),
553 ];
554
555 let mut keymap = Keymap::default();
556 keymap.add_bindings(bindings);
557
558 let (result, pending) = keymap.bindings_for_input(
560 &[Keystroke::parse("ctrl-w").unwrap()],
561 &[KeyContext::parse("editor").unwrap()],
562 );
563 assert_eq!(result.len(), 1);
564 assert!(!pending);
565 }
566
567 #[test]
568 fn test_simple_disable() {
569 let bindings = [
570 KeyBinding::new("ctrl-x", ActionAlpha {}, Some("editor")),
571 KeyBinding::new("ctrl-x", NoAction {}, Some("editor")),
572 ];
573
574 let mut keymap = Keymap::default();
575 keymap.add_bindings(bindings);
576
577 let (result, pending) = keymap.bindings_for_input(
579 &[Keystroke::parse("ctrl-x").unwrap()],
580 &[KeyContext::parse("editor").unwrap()],
581 );
582 assert!(result.is_empty());
583 assert!(!pending);
584 }
585
586 #[test]
587 fn test_fail_to_disable() {
588 let bindings = [
590 KeyBinding::new("ctrl-x", ActionAlpha {}, Some("editor")),
591 KeyBinding::new("ctrl-x", NoAction {}, Some("workspace")),
592 ];
593
594 let mut keymap = Keymap::default();
595 keymap.add_bindings(bindings);
596
597 let (result, pending) = keymap.bindings_for_input(
599 &[Keystroke::parse("ctrl-x").unwrap()],
600 &[
601 KeyContext::parse("workspace").unwrap(),
602 KeyContext::parse("editor").unwrap(),
603 ],
604 );
605 assert_eq!(result.len(), 1);
606 assert!(!pending);
607 }
608
609 #[test]
610 fn test_disable_deeper() {
611 let bindings = [
612 KeyBinding::new("ctrl-x", ActionAlpha {}, Some("workspace")),
613 KeyBinding::new("ctrl-x", NoAction {}, Some("editor")),
614 ];
615
616 let mut keymap = Keymap::default();
617 keymap.add_bindings(bindings);
618
619 let (result, pending) = keymap.bindings_for_input(
621 &[Keystroke::parse("ctrl-x").unwrap()],
622 &[
623 KeyContext::parse("workspace").unwrap(),
624 KeyContext::parse("editor").unwrap(),
625 ],
626 );
627 assert_eq!(result.len(), 0);
628 assert!(!pending);
629 }
630
631 #[test]
632 fn test_pending_match_enabled() {
633 let bindings = [
634 KeyBinding::new("ctrl-x", ActionBeta, Some("vim_mode == normal")),
635 KeyBinding::new("ctrl-x 0", ActionAlpha, Some("Workspace")),
636 ];
637 let mut keymap = Keymap::default();
638 keymap.add_bindings(bindings);
639
640 let matched = keymap.bindings_for_input(
641 &[Keystroke::parse("ctrl-x")].map(Result::unwrap),
642 &[
643 KeyContext::parse("Workspace"),
644 KeyContext::parse("Pane"),
645 KeyContext::parse("Editor vim_mode=normal"),
646 ]
647 .map(Result::unwrap),
648 );
649 assert_eq!(matched.0.len(), 1);
650 assert!(matched.0[0].action.partial_eq(&ActionBeta));
651 assert!(matched.1);
652 }
653
654 #[test]
655 fn test_pending_match_enabled_extended() {
656 let bindings = [
657 KeyBinding::new("ctrl-x", ActionBeta, Some("vim_mode == normal")),
658 KeyBinding::new("ctrl-x 0", NoAction, Some("Workspace")),
659 ];
660 let mut keymap = Keymap::default();
661 keymap.add_bindings(bindings);
662
663 let matched = keymap.bindings_for_input(
664 &[Keystroke::parse("ctrl-x")].map(Result::unwrap),
665 &[
666 KeyContext::parse("Workspace"),
667 KeyContext::parse("Pane"),
668 KeyContext::parse("Editor vim_mode=normal"),
669 ]
670 .map(Result::unwrap),
671 );
672 assert_eq!(matched.0.len(), 1);
673 assert!(matched.0[0].action.partial_eq(&ActionBeta));
674 assert!(!matched.1);
675 let bindings = [
676 KeyBinding::new("ctrl-x", ActionBeta, Some("Workspace")),
677 KeyBinding::new("ctrl-x 0", NoAction, Some("vim_mode == normal")),
678 ];
679 let mut keymap = Keymap::default();
680 keymap.add_bindings(bindings);
681
682 let matched = keymap.bindings_for_input(
683 &[Keystroke::parse("ctrl-x")].map(Result::unwrap),
684 &[
685 KeyContext::parse("Workspace"),
686 KeyContext::parse("Pane"),
687 KeyContext::parse("Editor vim_mode=normal"),
688 ]
689 .map(Result::unwrap),
690 );
691 assert_eq!(matched.0.len(), 1);
692 assert!(matched.0[0].action.partial_eq(&ActionBeta));
693 assert!(!matched.1);
694 }
695
696 #[test]
697 fn test_overriding_prefix() {
698 let bindings = [
699 KeyBinding::new("ctrl-x 0", ActionAlpha, Some("Workspace")),
700 KeyBinding::new("ctrl-x", ActionBeta, Some("vim_mode == normal")),
701 ];
702 let mut keymap = Keymap::default();
703 keymap.add_bindings(bindings);
704
705 let matched = keymap.bindings_for_input(
706 &[Keystroke::parse("ctrl-x")].map(Result::unwrap),
707 &[
708 KeyContext::parse("Workspace"),
709 KeyContext::parse("Pane"),
710 KeyContext::parse("Editor vim_mode=normal"),
711 ]
712 .map(Result::unwrap),
713 );
714 assert_eq!(matched.0.len(), 1);
715 assert!(matched.0[0].action.partial_eq(&ActionBeta));
716 assert!(!matched.1);
717 }
718
719 #[test]
720 fn test_context_precedence_with_same_source() {
721 let bindings = [
724 KeyBinding::new("cmd-r", ActionAlpha {}, Some("Workspace")),
725 KeyBinding::new("cmd-r", ActionBeta {}, Some("Editor")),
726 ];
727
728 let mut keymap = Keymap::default();
729 keymap.add_bindings(bindings);
730
731 let (result, _) = keymap.bindings_for_input(
733 &[Keystroke::parse("cmd-r").unwrap()],
734 &[
735 KeyContext::parse("Workspace").unwrap(),
736 KeyContext::parse("Editor").unwrap(),
737 ],
738 );
739
740 assert_eq!(result.len(), 2);
742 assert!(result[0].action.partial_eq(&ActionBeta {})); assert!(result[1].action.partial_eq(&ActionAlpha {})); }
745
746 #[test]
747 fn test_bindings_for_action() {
748 let bindings = [
749 KeyBinding::new("ctrl-a", ActionAlpha {}, Some("pane")),
750 KeyBinding::new("ctrl-b", ActionBeta {}, Some("editor && mode == full")),
751 KeyBinding::new("ctrl-c", ActionGamma {}, Some("workspace")),
752 KeyBinding::new("ctrl-a", NoAction {}, Some("pane && active")),
753 KeyBinding::new("ctrl-b", NoAction {}, Some("editor")),
754 ];
755
756 let mut keymap = Keymap::default();
757 keymap.add_bindings(bindings);
758
759 assert_bindings(&keymap, &ActionAlpha {}, &["ctrl-a"]);
760 assert_bindings(&keymap, &ActionBeta {}, &[]);
761 assert_bindings(&keymap, &ActionGamma {}, &["ctrl-c"]);
762
763 #[track_caller]
764 fn assert_bindings(keymap: &Keymap, action: &dyn Action, expected: &[&str]) {
765 let actual = keymap
766 .bindings_for_action(action)
767 .map(|binding| binding.keystrokes[0].inner().unparse())
768 .collect::<Vec<_>>();
769 assert_eq!(actual, expected, "{:?}", action);
770 }
771 }
772
773 #[test]
774 fn test_source_precedence_sorting() {
775 let mut keymap = Keymap::default();
778
779 let mut default_binding = KeyBinding::new("cmd-r", ActionAlpha {}, Some("Editor"));
781 default_binding.set_meta(KeyBindingMetaIndex(3)); keymap.add_bindings([default_binding]);
783
784 let mut user_binding = KeyBinding::new("cmd-r", ActionBeta {}, Some("Editor"));
786 user_binding.set_meta(KeyBindingMetaIndex(0)); keymap.add_bindings([user_binding]);
788
789 let (result, _) = keymap.bindings_for_input(
791 &[Keystroke::parse("cmd-r").unwrap()],
792 &[KeyContext::parse("Editor").unwrap()],
793 );
794
795 assert_eq!(result.len(), 2);
797 assert!(result[0].action.partial_eq(&ActionBeta {}));
798 assert!(result[1].action.partial_eq(&ActionAlpha {}));
799 }
800
801 #[test]
802 fn test_possible_next_bindings_exclude_null_bindings() {
803 let keymap = Keymap::new(vec![
804 KeyBinding::new("ctrl-k a", ActionAlpha {}, Some("Editor")),
805 KeyBinding::new("ctrl-k b", ActionBeta {}, Some("Editor")),
806 KeyBinding::new("ctrl-k a", NoAction {}, Some("Editor")),
807 ]);
808
809 let bindings = keymap.possible_next_bindings_for_input(
810 &[Keystroke::parse("ctrl-k").unwrap()],
811 &[KeyContext::parse("Editor").unwrap()],
812 );
813 assert_eq!(
814 describe_bindings(&bindings),
815 vec![("ctrl-k b".to_string(), "test_only::ActionBeta")]
816 );
817 }
818
819 #[test]
820 fn test_possible_next_bindings_keep_user_binding_over_base_keymap_null() {
821 const USER: KeyBindingMetaIndex = KeyBindingMetaIndex(0);
822 const BASE: KeyBindingMetaIndex = KeyBindingMetaIndex(2);
823 const DEFAULT: KeyBindingMetaIndex = KeyBindingMetaIndex(3);
824
825 let mut keymap = Keymap::default();
826 keymap.add_bindings([
827 KeyBinding::new("ctrl-k a", ActionAlpha {}, Some("Editor")).with_meta(DEFAULT),
828 KeyBinding::new("ctrl-k a", NoAction {}, Some("Editor")).with_meta(BASE),
829 KeyBinding::new("ctrl-k a", ActionBeta {}, Some("Editor")).with_meta(USER),
830 ]);
831
832 let context = [KeyContext::parse("Editor").unwrap()];
833 let bindings = keymap
834 .possible_next_bindings_for_input(&[Keystroke::parse("ctrl-k").unwrap()], &context);
835 let expected = vec![
838 ("ctrl-k a".to_string(), "test_only::ActionBeta"),
839 ("ctrl-k a".to_string(), "test_only::ActionAlpha"),
840 ];
841 assert_eq!(describe_bindings(&bindings), expected);
842
843 let (dispatched, _) = keymap.bindings_for_input(
844 &[
845 Keystroke::parse("ctrl-k").unwrap(),
846 Keystroke::parse("a").unwrap(),
847 ],
848 &context,
849 );
850 assert_eq!(describe_bindings(&dispatched), expected);
851 }
852
853 #[test]
854 fn test_possible_next_bindings_apply_disabling_by_context() {
855 let keymap = Keymap::new(vec![
856 KeyBinding::new("ctrl-k a", ActionAlpha {}, Some("Workspace")),
857 KeyBinding::new("ctrl-k a", ActionBeta {}, Some("Editor")),
858 KeyBinding::new("ctrl-k b", ActionGamma {}, Some("Editor")),
859 KeyBinding::new("ctrl-k a", NoAction {}, Some("Editor")),
862 KeyBinding::new("ctrl-k b", NoAction {}, Some("Terminal")),
864 ]);
865
866 let bindings = keymap.possible_next_bindings_for_input(
867 &[Keystroke::parse("ctrl-k").unwrap()],
868 &[
869 KeyContext::parse("Workspace").unwrap(),
870 KeyContext::parse("Editor").unwrap(),
871 ],
872 );
873 assert_eq!(
874 describe_bindings(&bindings),
875 vec![("ctrl-k b".to_string(), "test_only::ActionGamma")]
876 );
877 }
878
879 #[test]
880 fn test_possible_next_bindings_exclude_longer_chord_disabled_in_deeper_context() {
881 let keymap = Keymap::new(vec![
882 KeyBinding::new("space w w", ActionAlpha {}, Some("Workspace")),
883 KeyBinding::new("space w x", ActionBeta {}, Some("Workspace")),
884 KeyBinding::new("space w w", NoAction {}, Some("AgentPanel")),
885 ]);
886 let context_stack = [
887 KeyContext::parse("Workspace").unwrap(),
888 KeyContext::parse("AgentPanel").unwrap(),
889 ];
890
891 for input in ["space", "space w"] {
892 let input = input
893 .split(' ')
894 .map(|keystroke| Keystroke::parse(keystroke).unwrap())
895 .collect::<Vec<_>>();
896 let bindings = keymap.possible_next_bindings_for_input(&input, &context_stack);
897 assert_eq!(
898 describe_bindings(&bindings),
899 vec![("space w x".to_string(), "test_only::ActionBeta")]
900 );
901 }
902 }
903
904 #[test]
905 fn test_possible_next_bindings_exclude_disabled_bindings_with_typed_input() {
906 let typed_input = [Keystroke::parse("alt-s->ß").unwrap()];
909 let keymap = Keymap::new(vec![
910 KeyBinding::new("ß a", ActionAlpha {}, None),
911 KeyBinding::new("alt-s a", NoAction {}, None),
912 ]);
913 assert!(
914 keymap
915 .possible_next_bindings_for_input(&typed_input, &[])
916 .is_empty()
917 );
918
919 let typed_then_a = [
921 Keystroke::parse("alt-s->ß").unwrap(),
922 Keystroke::parse("a").unwrap(),
923 ];
924 let (dispatched, _) = keymap.bindings_for_input(&typed_then_a, &[]);
925 assert!(dispatched.is_empty());
926 }
927
928 fn describe_bindings(bindings: &[KeyBinding]) -> Vec<(String, &'static str)> {
929 bindings
930 .iter()
931 .map(|binding| {
932 (
933 binding
934 .keystrokes()
935 .iter()
936 .map(|keystroke| keystroke.inner().unparse())
937 .collect::<Vec<_>>()
938 .join(" "),
939 binding.action().name(),
940 )
941 })
942 .collect()
943 }
944}