1mod binding;
2mod collection;
3mod context;
4
5pub use binding::*;
6pub use collection::*;
7pub use context::*;
8
9use crate::{Action, AsKeystroke, Keystroke, Unbind, is_no_action, is_unbind};
10use collections::{HashSet, TypeIdHashMap};
11use smallvec::SmallVec;
12
13#[derive(Copy, Clone, Eq, PartialEq, Default)]
18pub struct KeymapVersion(usize);
19
20#[derive(Default)]
52pub struct Keymap {
53 bindings: Vec<KeyBinding>,
54 binding_indices_by_action_id: TypeIdHashMap<SmallVec<[usize; 3]>>,
55 disabled_binding_indices: Vec<usize>,
56 version: KeymapVersion,
57}
58
59#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
63pub struct BindingIndex(usize);
64
65fn disabled_binding_matches_context(disabled_binding: &KeyBinding, binding: &KeyBinding) -> bool {
66 match (
67 &disabled_binding.context_predicate,
68 &binding.context_predicate,
69 ) {
70 (None, _) => true,
71 (Some(_), None) => false,
72 (Some(disabled_predicate), Some(predicate)) => disabled_predicate.is_superset(predicate),
73 }
74}
75
76fn binding_is_unbound(disabled_binding: &KeyBinding, binding: &KeyBinding) -> bool {
77 disabled_binding.keystrokes == binding.keystrokes
78 && disabled_binding
79 .action()
80 .as_any()
81 .downcast_ref::<Unbind>()
82 .is_some_and(|unbind| unbind.0.as_ref() == binding.action.name())
83}
84
85impl Keymap {
86 pub fn new(bindings: Vec<KeyBinding>) -> Self {
88 let mut this = Self::default();
89 this.add_bindings(bindings);
90 this
91 }
92
93 pub fn version(&self) -> KeymapVersion {
95 self.version
96 }
97
98 pub fn add_bindings<T: IntoIterator<Item = KeyBinding>>(&mut self, bindings: T) {
103 for binding in bindings {
104 let action_id = binding.action().as_any().type_id();
105 if is_no_action(&*binding.action) || is_unbind(&*binding.action) {
106 self.disabled_binding_indices.push(self.bindings.len());
107 } else {
108 self.binding_indices_by_action_id
109 .entry(action_id)
110 .or_default()
111 .push(self.bindings.len());
112 }
113 self.bindings.push(binding);
114 }
115
116 self.version.0 += 1;
117 }
118
119 pub fn clear(&mut self) {
121 self.bindings.clear();
122 self.binding_indices_by_action_id.clear();
123 self.disabled_binding_indices.clear();
124 self.version.0 += 1;
125 }
126
127 pub fn bindings(&self) -> impl DoubleEndedIterator<Item = &KeyBinding> + ExactSizeIterator {
131 self.bindings.iter()
132 }
133
134 pub fn bindings_for_action<'a>(
142 &'a self,
143 action: &'a dyn Action,
144 ) -> impl 'a + DoubleEndedIterator<Item = &'a KeyBinding> {
145 let action_id = action.type_id();
146 let binding_indices = self
147 .binding_indices_by_action_id
148 .get(&action_id)
149 .map_or(&[] as _, SmallVec::as_slice)
150 .iter();
151
152 binding_indices.filter_map(|ix| {
153 let binding = &self.bindings[*ix];
154 if !binding.action().partial_eq(action) {
155 return None;
156 }
157
158 for disabled_ix in &self.disabled_binding_indices {
159 if disabled_ix > ix {
160 let disabled_binding = &self.bindings[*disabled_ix];
161 if disabled_binding.keystrokes != binding.keystrokes {
162 continue;
163 }
164
165 if is_no_action(&*disabled_binding.action) {
166 let source = binding.meta.map_or(0, |meta| meta.0);
169 let removal_source = disabled_binding.meta.map_or(0, |meta| meta.0);
170 if source >= removal_source
171 && disabled_binding_matches_context(disabled_binding, binding)
172 {
173 return None;
174 }
175 } else if is_unbind(&*disabled_binding.action)
176 && disabled_binding_matches_context(disabled_binding, binding)
177 && binding_is_unbound(disabled_binding, binding)
178 {
179 return None;
180 }
181 }
182 }
183
184 Some(binding)
185 })
186 }
187
188 pub fn all_bindings_for_input(&self, input: &[Keystroke]) -> Vec<KeyBinding> {
194 self.bindings()
195 .rev()
196 .filter(|binding| {
197 binding
198 .match_keystrokes(input)
199 .is_some_and(|pending| !pending)
200 })
201 .cloned()
202 .collect()
203 }
204
205 pub fn bindings_for_input(
250 &self,
251 input: &[impl AsKeystroke],
252 context_stack: &[KeyContext],
253 ) -> (SmallVec<[KeyBinding; 1]>, bool) {
254 let (bindings, pending) = self.resolve_bindings_for_input(
255 input,
256 context_stack,
257 self.bindings().enumerate().rev(),
258 );
259 (
260 bindings
261 .into_iter()
262 .map(|(_, binding)| binding.clone())
263 .collect(),
264 pending,
265 )
266 }
267
268 fn resolve_bindings_for_input<'a>(
277 &self,
278 input: &[impl AsKeystroke],
279 context_stack: &[KeyContext],
280 keymap_bindings: impl IntoIterator<Item = (usize, &'a KeyBinding)>,
281 ) -> (SmallVec<[(BindingIndex, &'a KeyBinding); 1]>, bool) {
282 let mut matched_bindings = SmallVec::<[(usize, BindingIndex, &KeyBinding); 1]>::new();
283 let mut pending_bindings = SmallVec::<[(BindingIndex, &KeyBinding); 1]>::new();
284
285 for (ix, binding) in keymap_bindings {
286 let Some(depth) = self.binding_enabled(binding, context_stack) else {
287 continue;
288 };
289 let Some(pending) = binding.match_keystrokes(input) else {
290 continue;
291 };
292
293 if !pending {
294 matched_bindings.push((depth, BindingIndex(ix), binding));
295 } else {
296 pending_bindings.push((BindingIndex(ix), binding));
297 }
298 }
299
300 matched_bindings.sort_by(|(depth_a, ix_a, _), (depth_b, ix_b, _)| {
301 depth_b.cmp(depth_a).then(ix_b.cmp(ix_a))
302 });
303
304 let mut bindings: SmallVec<[_; 1]> = SmallVec::new();
305 let mut first_binding_index = None;
306 let mut unbound_bindings: Vec<&KeyBinding> = Vec::new();
307 let mut no_action_meta: Option<u32> = None;
312
313 for (_, ix, binding) in matched_bindings {
314 let meta = binding.meta.map_or(0, |meta| meta.0);
315 if is_no_action(&*binding.action) {
316 no_action_meta = Some(no_action_meta.map_or(meta, |existing| existing.min(meta)));
317 continue;
318 }
319
320 if no_action_meta.is_some_and(|no_action_meta| meta >= no_action_meta) {
321 continue;
322 }
323
324 if is_unbind(&*binding.action) {
325 unbound_bindings.push(binding);
326 continue;
327 }
328
329 if unbound_bindings
330 .iter()
331 .any(|disabled_binding| binding_is_unbound(disabled_binding, binding))
332 {
333 continue;
334 }
335
336 bindings.push((ix, binding));
337 first_binding_index.get_or_insert(ix);
338 }
339
340 let mut pending = HashSet::default();
341 for (ix, binding) in pending_bindings.into_iter().rev() {
342 if let Some(binding_ix) = first_binding_index
343 && binding_ix > ix
344 {
345 continue;
346 }
347 if is_no_action(&*binding.action) || is_unbind(&*binding.action) {
348 pending.remove(&&binding.keystrokes);
349 continue;
350 }
351 pending.insert(&binding.keystrokes);
352 }
353
354 (bindings, !pending.is_empty())
355 }
356 fn binding_enabled(&self, binding: &KeyBinding, contexts: &[KeyContext]) -> Option<usize> {
359 if let Some(predicate) = &binding.context_predicate {
360 predicate.depth_of(contexts)
361 } else {
362 Some(contexts.len())
363 }
364 }
365
366 pub fn possible_next_bindings_for_input(
372 &self,
373 input: &[Keystroke],
374 context_stack: &[KeyContext],
375 ) -> Vec<KeyBinding> {
376 let bindings_extending_input = self
378 .bindings()
379 .enumerate()
380 .rev()
381 .filter(|(_, binding)| {
382 binding
383 .match_keystrokes(input)
384 .is_some_and(|pending| pending)
385 })
386 .collect::<Vec<_>>();
387
388 let mut full_input = SmallVec::<[&Keystroke; 4]>::new();
389 let mut candidates = bindings_extending_input
390 .iter()
391 .filter(|(_, binding)| !is_no_action(&*binding.action) && !is_unbind(&*binding.action))
392 .filter_map(|&(ix, binding)| {
393 let depth = self.binding_enabled(binding, context_stack)?;
394 full_input.clear();
398 full_input.extend(input.iter());
399 full_input.extend(
400 binding.keystrokes[input.len()..]
401 .iter()
402 .map(AsKeystroke::as_keystroke),
403 );
404 let (dispatched, _) = self.resolve_bindings_for_input(
405 &full_input,
406 context_stack,
407 bindings_extending_input.iter().copied(),
408 );
409 dispatched
410 .iter()
411 .any(|(dispatched_ix, _)| *dispatched_ix == BindingIndex(ix))
412 .then_some((depth, ix, binding))
413 })
414 .collect::<Vec<_>>();
415 candidates.sort_unstable_by(|(depth_a, ix_a, _), (depth_b, ix_b, _)| {
416 depth_b.cmp(depth_a).then(ix_b.cmp(ix_a))
417 });
418
419 candidates
420 .into_iter()
421 .map(|(_, _, binding)| binding.clone())
422 .collect()
423 }
424}
425
426#[cfg(test)]
427mod tests {
428 use super::*;
429 use crate as gpui;
430 use gpui::{NoAction, Unbind};
431
432 actions!(
433 test_only,
434 [ActionAlpha, ActionBeta, ActionGamma, ActionDelta,]
435 );
436
437 #[test]
438 fn test_keymap() {
439 let bindings = [
440 KeyBinding::new("ctrl-a", ActionAlpha {}, None),
441 KeyBinding::new("ctrl-a", ActionBeta {}, Some("pane")),
442 KeyBinding::new("ctrl-a", ActionGamma {}, Some("editor && mode==full")),
443 ];
444
445 let mut keymap = Keymap::default();
446 keymap.add_bindings(bindings.clone());
447
448 assert_eq!(keymap.binding_enabled(&bindings[0], &[]), Some(0));
450 assert_eq!(
451 keymap.binding_enabled(&bindings[0], &[KeyContext::parse("terminal").unwrap()]),
452 Some(1)
453 );
454
455 assert_eq!(
457 keymap.binding_enabled(&bindings[1], &[KeyContext::parse("barf x=y").unwrap()]),
458 None
459 );
460 assert_eq!(
461 keymap.binding_enabled(&bindings[1], &[KeyContext::parse("pane x=y").unwrap()]),
462 Some(1)
463 );
464
465 assert_eq!(
466 keymap.binding_enabled(&bindings[2], &[KeyContext::parse("editor").unwrap()]),
467 None
468 );
469 assert_eq!(
470 keymap.binding_enabled(
471 &bindings[2],
472 &[KeyContext::parse("editor mode=full").unwrap()]
473 ),
474 Some(1)
475 );
476 }
477
478 #[test]
479 fn test_depth_precedence() {
480 let bindings = [
481 KeyBinding::new("ctrl-a", ActionBeta {}, Some("pane")),
482 KeyBinding::new("ctrl-a", ActionGamma {}, Some("editor")),
483 ];
484
485 let mut keymap = Keymap::default();
486 keymap.add_bindings(bindings);
487
488 let (result, pending) = keymap.bindings_for_input(
489 &[Keystroke::parse("ctrl-a").unwrap()],
490 &[
491 KeyContext::parse("pane").unwrap(),
492 KeyContext::parse("editor").unwrap(),
493 ],
494 );
495
496 assert!(!pending);
497 assert_eq!(result.len(), 2);
498 assert!(result[0].action.partial_eq(&ActionGamma {}));
499 assert!(result[1].action.partial_eq(&ActionBeta {}));
500 }
501
502 #[test]
503 fn test_keymap_disabled() {
504 let bindings = [
505 KeyBinding::new("ctrl-a", ActionAlpha {}, Some("editor")),
506 KeyBinding::new("ctrl-b", ActionAlpha {}, Some("editor")),
507 KeyBinding::new("ctrl-a", NoAction {}, Some("editor && mode==full")),
508 KeyBinding::new("ctrl-b", NoAction {}, None),
509 ];
510
511 let mut keymap = Keymap::default();
512 keymap.add_bindings(bindings);
513
514 assert!(
516 keymap
517 .bindings_for_input(
518 &[Keystroke::parse("ctrl-a").unwrap()],
519 &[KeyContext::parse("barf").unwrap()],
520 )
521 .0
522 .is_empty()
523 );
524 assert!(
525 !keymap
526 .bindings_for_input(
527 &[Keystroke::parse("ctrl-a").unwrap()],
528 &[KeyContext::parse("editor").unwrap()],
529 )
530 .0
531 .is_empty()
532 );
533
534 assert!(
536 keymap
537 .bindings_for_input(
538 &[Keystroke::parse("ctrl-a").unwrap()],
539 &[KeyContext::parse("editor mode=full").unwrap()],
540 )
541 .0
542 .is_empty()
543 );
544
545 assert!(
547 keymap
548 .bindings_for_input(
549 &[Keystroke::parse("ctrl-b").unwrap()],
550 &[KeyContext::parse("barf").unwrap()],
551 )
552 .0
553 .is_empty()
554 );
555 }
556
557 #[test]
558 fn test_multiple_keystroke_binding_disabled() {
560 let bindings = [
561 KeyBinding::new("space w w", ActionAlpha {}, Some("workspace")),
562 KeyBinding::new("space w w", NoAction {}, Some("editor")),
563 ];
564
565 let mut keymap = Keymap::default();
566 keymap.add_bindings(bindings);
567
568 let space = || Keystroke::parse("space").unwrap();
569 let w = || Keystroke::parse("w").unwrap();
570
571 let space_w = [space(), w()];
572 let space_w_w = [space(), w(), w()];
573
574 let workspace_context = || [KeyContext::parse("workspace").unwrap()];
575
576 let editor_workspace_context = || {
577 [
578 KeyContext::parse("workspace").unwrap(),
579 KeyContext::parse("editor").unwrap(),
580 ]
581 };
582
583 let space_workspace = keymap.bindings_for_input(&[space()], &workspace_context());
585 assert!(space_workspace.0.is_empty());
586 assert!(space_workspace.1);
587
588 let space_editor = keymap.bindings_for_input(&[space()], &editor_workspace_context());
589 assert!(space_editor.0.is_empty());
590 assert!(!space_editor.1);
591
592 let space_w_workspace = keymap.bindings_for_input(&space_w, &workspace_context());
594 assert!(space_w_workspace.0.is_empty());
595 assert!(space_w_workspace.1);
596
597 let space_w_editor = keymap.bindings_for_input(&space_w, &editor_workspace_context());
598 assert!(space_w_editor.0.is_empty());
599 assert!(!space_w_editor.1);
600
601 let space_w_w_workspace = keymap.bindings_for_input(&space_w_w, &workspace_context());
603 assert!(!space_w_w_workspace.0.is_empty());
604 assert!(!space_w_w_workspace.1);
605
606 let space_w_w_editor = keymap.bindings_for_input(&space_w_w, &editor_workspace_context());
607 assert!(space_w_w_editor.0.is_empty());
608 assert!(!space_w_w_editor.1);
609
610 let bindings = [
613 KeyBinding::new("space w w", ActionAlpha {}, Some("workspace")),
614 KeyBinding::new("space w w", NoAction {}, Some("editor")),
615 KeyBinding::new("space w x", ActionAlpha {}, Some("editor")),
616 ];
617 let mut keymap = Keymap::default();
618 keymap.add_bindings(bindings);
619
620 let space_editor = keymap.bindings_for_input(&[space()], &editor_workspace_context());
621 assert!(space_editor.0.is_empty());
622 assert!(space_editor.1);
623
624 let bindings = [
627 KeyBinding::new("space w w", ActionAlpha {}, Some("workspace")),
628 KeyBinding::new("space w x", ActionAlpha {}, Some("editor")),
629 KeyBinding::new("space w w", NoAction {}, Some("editor")),
630 ];
631 let mut keymap = Keymap::default();
632 keymap.add_bindings(bindings);
633
634 let space_editor = keymap.bindings_for_input(&[space()], &editor_workspace_context());
635 assert!(space_editor.0.is_empty());
636 assert!(space_editor.1);
637
638 let bindings = [
641 KeyBinding::new("space w w", ActionAlpha {}, Some("workspace")),
642 KeyBinding::new("space w x", ActionAlpha {}, Some("workspace")),
643 KeyBinding::new("space w w", NoAction {}, Some("editor")),
644 ];
645 let mut keymap = Keymap::default();
646 keymap.add_bindings(bindings);
647
648 let space_editor = keymap.bindings_for_input(&[space()], &editor_workspace_context());
649 assert!(space_editor.0.is_empty());
650 assert!(space_editor.1);
651 }
652
653 #[test]
654 fn test_override_multikey() {
655 let bindings = [
656 KeyBinding::new("ctrl-w left", ActionAlpha {}, Some("editor")),
657 KeyBinding::new("ctrl-w", NoAction {}, Some("editor")),
658 ];
659
660 let mut keymap = Keymap::default();
661 keymap.add_bindings(bindings);
662
663 let (result, pending) = keymap.bindings_for_input(
665 &[Keystroke::parse("ctrl-w").unwrap()],
666 &[KeyContext::parse("editor").unwrap()],
667 );
668 assert!(result.is_empty());
669 assert!(pending);
670
671 let bindings = [
672 KeyBinding::new("ctrl-w left", ActionAlpha {}, Some("editor")),
673 KeyBinding::new("ctrl-w", ActionBeta {}, Some("editor")),
674 ];
675
676 let mut keymap = Keymap::default();
677 keymap.add_bindings(bindings);
678
679 let (result, pending) = keymap.bindings_for_input(
681 &[Keystroke::parse("ctrl-w").unwrap()],
682 &[KeyContext::parse("editor").unwrap()],
683 );
684 assert_eq!(result.len(), 1);
685 assert!(!pending);
686 }
687
688 #[test]
689 fn test_simple_disable() {
690 let bindings = [
691 KeyBinding::new("ctrl-x", ActionAlpha {}, Some("editor")),
692 KeyBinding::new("ctrl-x", NoAction {}, Some("editor")),
693 ];
694
695 let mut keymap = Keymap::default();
696 keymap.add_bindings(bindings);
697
698 let (result, pending) = keymap.bindings_for_input(
700 &[Keystroke::parse("ctrl-x").unwrap()],
701 &[KeyContext::parse("editor").unwrap()],
702 );
703 assert!(result.is_empty());
704 assert!(!pending);
705 }
706
707 #[test]
708 fn test_disable_weaker_sources_only() {
709 const USER: KeyBindingMetaIndex = KeyBindingMetaIndex(0);
710 const VIM: KeyBindingMetaIndex = KeyBindingMetaIndex(1);
711 const BASE: KeyBindingMetaIndex = KeyBindingMetaIndex(2);
712 const DEFAULT: KeyBindingMetaIndex = KeyBindingMetaIndex(3);
713
714 let editor_context = || [KeyContext::parse("editor").unwrap()];
715 let ctrl_x = || [Keystroke::parse("ctrl-x").unwrap()];
716
717 let mut keymap = Keymap::default();
719 keymap.add_bindings([
720 KeyBinding::new("ctrl-x", ActionAlpha {}, Some("editor")).with_meta(DEFAULT),
721 KeyBinding::new("ctrl-x", NoAction {}, Some("editor")).with_meta(BASE),
722 ]);
723 let (result, _) = keymap.bindings_for_input(&ctrl_x(), &editor_context());
724 assert!(result.is_empty());
725
726 let mut keymap = Keymap::default();
728 keymap.add_bindings([
729 KeyBinding::new("ctrl-x", NoAction {}, Some("editor")).with_meta(DEFAULT),
730 KeyBinding::new("ctrl-x", NoAction {}, Some("editor")).with_meta(BASE),
731 KeyBinding::new("ctrl-x", ActionBeta {}, None).with_meta(USER),
732 ]);
733 let (result, _) = keymap.bindings_for_input(&ctrl_x(), &editor_context());
734 assert_eq!(result.len(), 1);
735 assert!(result[0].action.partial_eq(&ActionBeta {}));
736
737 let mut keymap = Keymap::default();
740 keymap.add_bindings([
741 KeyBinding::new("ctrl-x", NoAction {}, Some("editor")).with_meta(BASE),
742 KeyBinding::new("ctrl-x", ActionBeta {}, Some("workspace")).with_meta(USER),
743 ]);
744 let (result, _) = keymap.bindings_for_input(
745 &ctrl_x(),
746 &[
747 KeyContext::parse("workspace").unwrap(),
748 KeyContext::parse("editor").unwrap(),
749 ],
750 );
751 assert_eq!(result.len(), 1);
752 assert!(result[0].action.partial_eq(&ActionBeta {}));
753
754 let mut keymap = Keymap::default();
757 keymap.add_bindings([
758 KeyBinding::new("ctrl-x", ActionAlpha {}, Some("editor")).with_meta(DEFAULT),
759 KeyBinding::new("ctrl-x", NoAction {}, Some("editor")).with_meta(BASE),
760 KeyBinding::new("ctrl-x", ActionGamma {}, Some("editor")).with_meta(VIM),
761 ]);
762 let (result, _) = keymap.bindings_for_input(&ctrl_x(), &editor_context());
763 assert_eq!(result.len(), 1);
764 assert!(result[0].action.partial_eq(&ActionGamma {}));
765
766 let mut keymap = Keymap::default();
767 keymap.add_bindings([
768 KeyBinding::new("ctrl-x", ActionAlpha {}, Some("editor")).with_meta(DEFAULT),
769 KeyBinding::new("ctrl-x", ActionGamma {}, Some("editor")).with_meta(VIM),
770 KeyBinding::new("ctrl-x", NoAction {}, Some("editor")).with_meta(USER),
771 ]);
772 let (result, _) = keymap.bindings_for_input(&ctrl_x(), &editor_context());
773 assert!(result.is_empty());
774 }
775
776 #[test]
777 fn test_bindings_for_action_disable_weaker_sources_only() {
778 for (source, expected) in [
781 (None, 1),
782 (Some(KeyBindingMetaIndex(0)), 1),
783 (Some(KeyBindingMetaIndex(1)), 1),
784 (Some(KeyBindingMetaIndex(2)), 0),
785 (Some(KeyBindingMetaIndex(3)), 0),
786 ] {
787 let mut binding = KeyBinding::new("ctrl-x", ActionAlpha {}, Some("editor"));
788 if let Some(source) = source {
789 binding.set_meta(source);
790 }
791 let mut keymap = Keymap::default();
792 keymap.add_bindings([
793 binding,
794 KeyBinding::new("ctrl-x", NoAction {}, Some("editor"))
795 .with_meta(KeyBindingMetaIndex(2)),
796 ]);
797
798 let (candidates, _) = keymap.bindings_for_input(
799 &[Keystroke::parse("ctrl-x").unwrap()],
800 &[KeyContext::parse("editor").unwrap()],
801 );
802 assert_eq!(candidates.len(), expected, "{source:?}");
803 assert_eq!(
804 keymap.bindings_for_action(&ActionAlpha {}).count(),
805 expected,
806 "{source:?}"
807 );
808 }
809 }
810
811 #[test]
812 fn test_fail_to_disable() {
813 let bindings = [
815 KeyBinding::new("ctrl-x", ActionAlpha {}, Some("editor")),
816 KeyBinding::new("ctrl-x", NoAction {}, Some("workspace")),
817 ];
818
819 let mut keymap = Keymap::default();
820 keymap.add_bindings(bindings);
821
822 let (result, pending) = keymap.bindings_for_input(
824 &[Keystroke::parse("ctrl-x").unwrap()],
825 &[
826 KeyContext::parse("workspace").unwrap(),
827 KeyContext::parse("editor").unwrap(),
828 ],
829 );
830 assert_eq!(result.len(), 1);
831 assert!(!pending);
832 }
833
834 #[test]
835 fn test_disable_deeper() {
836 let bindings = [
837 KeyBinding::new("ctrl-x", ActionAlpha {}, Some("workspace")),
838 KeyBinding::new("ctrl-x", NoAction {}, Some("editor")),
839 ];
840
841 let mut keymap = Keymap::default();
842 keymap.add_bindings(bindings);
843
844 let (result, pending) = keymap.bindings_for_input(
846 &[Keystroke::parse("ctrl-x").unwrap()],
847 &[
848 KeyContext::parse("workspace").unwrap(),
849 KeyContext::parse("editor").unwrap(),
850 ],
851 );
852 assert_eq!(result.len(), 0);
853 assert!(!pending);
854 }
855
856 #[test]
857 fn test_pending_match_enabled() {
858 let bindings = [
859 KeyBinding::new("ctrl-x", ActionBeta, Some("vim_mode == normal")),
860 KeyBinding::new("ctrl-x 0", ActionAlpha, Some("Workspace")),
861 ];
862 let mut keymap = Keymap::default();
863 keymap.add_bindings(bindings);
864
865 let matched = keymap.bindings_for_input(
866 &[Keystroke::parse("ctrl-x")].map(Result::unwrap),
867 &[
868 KeyContext::parse("Workspace"),
869 KeyContext::parse("Pane"),
870 KeyContext::parse("Editor vim_mode=normal"),
871 ]
872 .map(Result::unwrap),
873 );
874 assert_eq!(matched.0.len(), 1);
875 assert!(matched.0[0].action.partial_eq(&ActionBeta));
876 assert!(matched.1);
877 }
878
879 #[test]
880 fn test_pending_match_enabled_extended() {
881 let bindings = [
882 KeyBinding::new("ctrl-x", ActionBeta, Some("vim_mode == normal")),
883 KeyBinding::new("ctrl-x 0", NoAction, Some("Workspace")),
884 ];
885 let mut keymap = Keymap::default();
886 keymap.add_bindings(bindings);
887
888 let matched = keymap.bindings_for_input(
889 &[Keystroke::parse("ctrl-x")].map(Result::unwrap),
890 &[
891 KeyContext::parse("Workspace"),
892 KeyContext::parse("Pane"),
893 KeyContext::parse("Editor vim_mode=normal"),
894 ]
895 .map(Result::unwrap),
896 );
897 assert_eq!(matched.0.len(), 1);
898 assert!(matched.0[0].action.partial_eq(&ActionBeta));
899 assert!(!matched.1);
900 let bindings = [
901 KeyBinding::new("ctrl-x", ActionBeta, Some("Workspace")),
902 KeyBinding::new("ctrl-x 0", NoAction, Some("vim_mode == normal")),
903 ];
904 let mut keymap = Keymap::default();
905 keymap.add_bindings(bindings);
906
907 let matched = keymap.bindings_for_input(
908 &[Keystroke::parse("ctrl-x")].map(Result::unwrap),
909 &[
910 KeyContext::parse("Workspace"),
911 KeyContext::parse("Pane"),
912 KeyContext::parse("Editor vim_mode=normal"),
913 ]
914 .map(Result::unwrap),
915 );
916 assert_eq!(matched.0.len(), 1);
917 assert!(matched.0[0].action.partial_eq(&ActionBeta));
918 assert!(!matched.1);
919 }
920
921 #[test]
922 fn test_overriding_prefix() {
923 let bindings = [
924 KeyBinding::new("ctrl-x 0", ActionAlpha, Some("Workspace")),
925 KeyBinding::new("ctrl-x", ActionBeta, Some("vim_mode == normal")),
926 ];
927 let mut keymap = Keymap::default();
928 keymap.add_bindings(bindings);
929
930 let matched = keymap.bindings_for_input(
931 &[Keystroke::parse("ctrl-x")].map(Result::unwrap),
932 &[
933 KeyContext::parse("Workspace"),
934 KeyContext::parse("Pane"),
935 KeyContext::parse("Editor vim_mode=normal"),
936 ]
937 .map(Result::unwrap),
938 );
939 assert_eq!(matched.0.len(), 1);
940 assert!(matched.0[0].action.partial_eq(&ActionBeta));
941 assert!(!matched.1);
942 }
943
944 #[test]
945 fn test_context_precedence_with_same_source() {
946 let bindings = [
949 KeyBinding::new("cmd-r", ActionAlpha {}, Some("Workspace")),
950 KeyBinding::new("cmd-r", ActionBeta {}, Some("Editor")),
951 ];
952
953 let mut keymap = Keymap::default();
954 keymap.add_bindings(bindings);
955
956 let (result, _) = keymap.bindings_for_input(
958 &[Keystroke::parse("cmd-r").unwrap()],
959 &[
960 KeyContext::parse("Workspace").unwrap(),
961 KeyContext::parse("Editor").unwrap(),
962 ],
963 );
964
965 assert_eq!(result.len(), 2);
967 assert!(result[0].action.partial_eq(&ActionBeta {})); assert!(result[1].action.partial_eq(&ActionAlpha {})); }
970
971 #[test]
972 fn test_bindings_for_action() {
973 let bindings = [
974 KeyBinding::new("ctrl-a", ActionAlpha {}, Some("pane")),
975 KeyBinding::new("ctrl-b", ActionBeta {}, Some("editor && mode == full")),
976 KeyBinding::new("ctrl-c", ActionGamma {}, Some("workspace")),
977 KeyBinding::new("ctrl-a", NoAction {}, Some("pane && active")),
978 KeyBinding::new("ctrl-b", NoAction {}, Some("editor")),
979 ];
980
981 let mut keymap = Keymap::default();
982 keymap.add_bindings(bindings);
983
984 assert_bindings(&keymap, &ActionAlpha {}, &["ctrl-a"]);
985 assert_bindings(&keymap, &ActionBeta {}, &[]);
986 assert_bindings(&keymap, &ActionGamma {}, &["ctrl-c"]);
987
988 #[track_caller]
989 fn assert_bindings(keymap: &Keymap, action: &dyn Action, expected: &[&str]) {
990 let actual = keymap
991 .bindings_for_action(action)
992 .map(|binding| binding.keystrokes[0].inner().unparse())
993 .collect::<Vec<_>>();
994 assert_eq!(actual, expected, "{:?}", action);
995 }
996 }
997
998 #[test]
999 fn test_targeted_unbind_ignores_target_context() {
1000 let bindings = [
1001 KeyBinding::new("tab", ActionAlpha {}, Some("Editor")),
1002 KeyBinding::new("tab", ActionBeta {}, Some("Editor && showing_completions")),
1003 KeyBinding::new(
1004 "tab",
1005 Unbind("test_only::ActionAlpha".into()),
1006 Some("Editor && edit_prediction"),
1007 ),
1008 ];
1009
1010 let mut keymap = Keymap::default();
1011 keymap.add_bindings(bindings);
1012
1013 let (result, pending) = keymap.bindings_for_input(
1014 &[Keystroke::parse("tab").unwrap()],
1015 &[KeyContext::parse("Editor showing_completions edit_prediction").unwrap()],
1016 );
1017
1018 assert!(!pending);
1019 assert_eq!(result.len(), 1);
1020 assert!(result[0].action.partial_eq(&ActionBeta {}));
1021 }
1022
1023 #[test]
1024 fn test_bindings_for_action_keeps_binding_for_narrower_targeted_unbind() {
1025 let bindings = [
1026 KeyBinding::new("tab", ActionAlpha {}, Some("Editor")),
1027 KeyBinding::new(
1028 "tab",
1029 Unbind("test_only::ActionAlpha".into()),
1030 Some("Editor && edit_prediction"),
1031 ),
1032 KeyBinding::new("tab", ActionBeta {}, Some("Editor && showing_completions")),
1033 ];
1034
1035 let mut keymap = Keymap::default();
1036 keymap.add_bindings(bindings);
1037
1038 assert_bindings(&keymap, &ActionAlpha {}, &["tab"]);
1039 assert_bindings(&keymap, &ActionBeta {}, &["tab"]);
1040
1041 #[track_caller]
1042 fn assert_bindings(keymap: &Keymap, action: &dyn Action, expected: &[&str]) {
1043 let actual = keymap
1044 .bindings_for_action(action)
1045 .map(|binding| binding.keystrokes[0].inner().unparse())
1046 .collect::<Vec<_>>();
1047 assert_eq!(actual, expected, "{:?}", action);
1048 }
1049 }
1050
1051 #[test]
1052 fn test_bindings_for_action_removes_binding_for_broader_targeted_unbind() {
1053 let bindings = [
1054 KeyBinding::new("tab", ActionAlpha {}, Some("Editor && edit_prediction")),
1055 KeyBinding::new(
1056 "tab",
1057 Unbind("test_only::ActionAlpha".into()),
1058 Some("Editor"),
1059 ),
1060 ];
1061
1062 let mut keymap = Keymap::default();
1063 keymap.add_bindings(bindings);
1064
1065 assert!(keymap.bindings_for_action(&ActionAlpha {}).next().is_none());
1066 }
1067
1068 #[test]
1069 fn test_source_precedence_sorting() {
1070 let mut keymap = Keymap::default();
1073
1074 let mut default_binding = KeyBinding::new("cmd-r", ActionAlpha {}, Some("Editor"));
1076 default_binding.set_meta(KeyBindingMetaIndex(3)); keymap.add_bindings([default_binding]);
1078
1079 let mut user_binding = KeyBinding::new("cmd-r", ActionBeta {}, Some("Editor"));
1081 user_binding.set_meta(KeyBindingMetaIndex(0)); keymap.add_bindings([user_binding]);
1083
1084 let (result, _) = keymap.bindings_for_input(
1086 &[Keystroke::parse("cmd-r").unwrap()],
1087 &[KeyContext::parse("Editor").unwrap()],
1088 );
1089
1090 assert_eq!(result.len(), 2);
1092 assert!(result[0].action.partial_eq(&ActionBeta {}));
1093 assert!(result[1].action.partial_eq(&ActionAlpha {}));
1094 }
1095
1096 #[test]
1097 fn test_possible_next_bindings_exclude_null_bindings() {
1098 let keymap = Keymap::new(vec![
1099 KeyBinding::new("ctrl-k a", ActionAlpha {}, Some("Editor")),
1100 KeyBinding::new("ctrl-k b", ActionBeta {}, Some("Editor")),
1101 KeyBinding::new("ctrl-k a", NoAction {}, Some("Editor")),
1102 ]);
1103
1104 let bindings = keymap.possible_next_bindings_for_input(
1105 &[Keystroke::parse("ctrl-k").unwrap()],
1106 &[KeyContext::parse("Editor").unwrap()],
1107 );
1108 assert_eq!(
1109 describe_bindings(&bindings),
1110 vec![("ctrl-k b".to_string(), "test_only::ActionBeta")]
1111 );
1112 }
1113
1114 #[test]
1115 fn test_possible_next_bindings_exclude_unbound_bindings() {
1116 let keymap = Keymap::new(vec![
1117 KeyBinding::new("ctrl-k a", ActionAlpha {}, Some("Editor")),
1118 KeyBinding::new("ctrl-k b", ActionBeta {}, Some("Editor")),
1119 KeyBinding::new(
1120 "ctrl-k a",
1121 Unbind("test_only::ActionAlpha".into()),
1122 Some("Editor"),
1123 ),
1124 ]);
1125
1126 let bindings = keymap.possible_next_bindings_for_input(
1127 &[Keystroke::parse("ctrl-k").unwrap()],
1128 &[KeyContext::parse("Editor").unwrap()],
1129 );
1130 assert_eq!(
1131 describe_bindings(&bindings),
1132 vec![("ctrl-k b".to_string(), "test_only::ActionBeta")]
1133 );
1134 }
1135
1136 #[test]
1137 fn test_possible_next_bindings_keep_user_binding_over_base_keymap_null() {
1138 const USER: KeyBindingMetaIndex = KeyBindingMetaIndex(0);
1139 const BASE: KeyBindingMetaIndex = KeyBindingMetaIndex(2);
1140 const DEFAULT: KeyBindingMetaIndex = KeyBindingMetaIndex(3);
1141
1142 let mut keymap = Keymap::default();
1143 keymap.add_bindings([
1144 KeyBinding::new("ctrl-k a", ActionAlpha {}, Some("Editor")).with_meta(DEFAULT),
1145 KeyBinding::new("ctrl-k a", NoAction {}, Some("Editor")).with_meta(BASE),
1146 KeyBinding::new("ctrl-k a", ActionBeta {}, Some("Editor")).with_meta(USER),
1147 ]);
1148
1149 let bindings = keymap.possible_next_bindings_for_input(
1150 &[Keystroke::parse("ctrl-k").unwrap()],
1151 &[KeyContext::parse("Editor").unwrap()],
1152 );
1153 assert_eq!(
1154 describe_bindings(&bindings),
1155 vec![("ctrl-k a".to_string(), "test_only::ActionBeta")]
1156 );
1157 }
1158
1159 #[test]
1160 fn test_possible_next_bindings_apply_disabling_by_context() {
1161 let keymap = Keymap::new(vec![
1162 KeyBinding::new("ctrl-k a", ActionAlpha {}, Some("Workspace")),
1163 KeyBinding::new("ctrl-k a", ActionBeta {}, Some("Editor")),
1164 KeyBinding::new("ctrl-k b", ActionGamma {}, Some("Editor")),
1165 KeyBinding::new(
1167 "ctrl-k a",
1168 Unbind("test_only::ActionBeta".into()),
1169 Some("Editor"),
1170 ),
1171 KeyBinding::new("ctrl-k b", NoAction {}, Some("Terminal")),
1173 ]);
1174
1175 let bindings = keymap.possible_next_bindings_for_input(
1176 &[Keystroke::parse("ctrl-k").unwrap()],
1177 &[
1178 KeyContext::parse("Workspace").unwrap(),
1179 KeyContext::parse("Editor").unwrap(),
1180 ],
1181 );
1182 assert_eq!(
1183 describe_bindings(&bindings),
1184 vec![
1185 ("ctrl-k b".to_string(), "test_only::ActionGamma"),
1186 ("ctrl-k a".to_string(), "test_only::ActionAlpha"),
1187 ]
1188 );
1189 }
1190
1191 #[test]
1192 fn test_possible_next_bindings_exclude_longer_chord_disabled_in_deeper_context() {
1193 let keymap = Keymap::new(vec![
1194 KeyBinding::new("space w w", ActionAlpha {}, Some("Workspace")),
1195 KeyBinding::new("space w x", ActionBeta {}, Some("Workspace")),
1196 KeyBinding::new("space w w", NoAction {}, Some("AgentPanel")),
1197 ]);
1198 let context_stack = [
1199 KeyContext::parse("Workspace").unwrap(),
1200 KeyContext::parse("AgentPanel").unwrap(),
1201 ];
1202
1203 for input in ["space", "space w"] {
1204 let input = input
1205 .split(' ')
1206 .map(|keystroke| Keystroke::parse(keystroke).unwrap())
1207 .collect::<Vec<_>>();
1208 let bindings = keymap.possible_next_bindings_for_input(&input, &context_stack);
1209 assert_eq!(
1210 describe_bindings(&bindings),
1211 vec![("space w x".to_string(), "test_only::ActionBeta")]
1212 );
1213 }
1214 }
1215
1216 #[test]
1217 fn test_possible_next_bindings_exclude_disabled_bindings_with_typed_input() {
1218 let typed_input = [Keystroke::parse("alt-s->ß").unwrap()];
1221 let keymap = Keymap::new(vec![
1222 KeyBinding::new("ß a", ActionAlpha {}, None),
1223 KeyBinding::new("alt-s a", NoAction {}, None),
1224 ]);
1225 assert!(
1226 keymap
1227 .possible_next_bindings_for_input(&typed_input, &[])
1228 .is_empty()
1229 );
1230
1231 let typed_then_a = [
1233 Keystroke::parse("alt-s->ß").unwrap(),
1234 Keystroke::parse("a").unwrap(),
1235 ];
1236 let (dispatched, _) = keymap.bindings_for_input(&typed_then_a, &[]);
1237 assert!(dispatched.is_empty());
1238 }
1239
1240 fn describe_bindings(bindings: &[KeyBinding]) -> Vec<(String, &'static str)> {
1241 bindings
1242 .iter()
1243 .map(|binding| {
1244 (
1245 binding
1246 .keystrokes()
1247 .iter()
1248 .map(|keystroke| keystroke.inner().unparse())
1249 .collect::<Vec<_>>()
1250 .join(" "),
1251 binding.action().name(),
1252 )
1253 })
1254 .collect()
1255 }
1256}