1mod binding;
2mod context;
3
4pub use binding::*;
5pub use context::*;
6
7use crate::{Action, AsKeystroke, Keystroke, Unbind, is_no_action, is_unbind};
8use collections::{HashSet, TypeIdHashMap};
9use smallvec::SmallVec;
10
11#[derive(Copy, Clone, Eq, PartialEq, Default)]
14pub struct KeymapVersion(usize);
15
16#[derive(Default)]
18pub struct Keymap {
19 bindings: Vec<KeyBinding>,
20 binding_indices_by_action_id: TypeIdHashMap<SmallVec<[usize; 3]>>,
21 disabled_binding_indices: Vec<usize>,
22 version: KeymapVersion,
23}
24
25#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
27pub struct BindingIndex(usize);
28
29fn disabled_binding_matches_context(disabled_binding: &KeyBinding, binding: &KeyBinding) -> bool {
30 match (
31 &disabled_binding.context_predicate,
32 &binding.context_predicate,
33 ) {
34 (None, _) => true,
35 (Some(_), None) => false,
36 (Some(disabled_predicate), Some(predicate)) => disabled_predicate.is_superset(predicate),
37 }
38}
39
40fn binding_is_unbound(disabled_binding: &KeyBinding, binding: &KeyBinding) -> bool {
41 disabled_binding.keystrokes == binding.keystrokes
42 && disabled_binding
43 .action()
44 .as_any()
45 .downcast_ref::<Unbind>()
46 .is_some_and(|unbind| unbind.0.as_ref() == binding.action.name())
47}
48
49impl Keymap {
50 pub fn new(bindings: Vec<KeyBinding>) -> Self {
52 let mut this = Self::default();
53 this.add_bindings(bindings);
54 this
55 }
56
57 pub fn version(&self) -> KeymapVersion {
59 self.version
60 }
61
62 pub fn add_bindings<T: IntoIterator<Item = KeyBinding>>(&mut self, bindings: T) {
64 for binding in bindings {
65 let action_id = binding.action().as_any().type_id();
66 if is_no_action(&*binding.action) || is_unbind(&*binding.action) {
67 self.disabled_binding_indices.push(self.bindings.len());
68 } else {
69 self.binding_indices_by_action_id
70 .entry(action_id)
71 .or_default()
72 .push(self.bindings.len());
73 }
74 self.bindings.push(binding);
75 }
76
77 self.version.0 += 1;
78 }
79
80 pub fn clear(&mut self) {
82 self.bindings.clear();
83 self.binding_indices_by_action_id.clear();
84 self.disabled_binding_indices.clear();
85 self.version.0 += 1;
86 }
87
88 pub fn bindings(&self) -> impl DoubleEndedIterator<Item = &KeyBinding> + ExactSizeIterator {
90 self.bindings.iter()
91 }
92
93 pub fn bindings_for_action<'a>(
96 &'a self,
97 action: &'a dyn Action,
98 ) -> impl 'a + DoubleEndedIterator<Item = &'a KeyBinding> {
99 let action_id = action.type_id();
100 let binding_indices = self
101 .binding_indices_by_action_id
102 .get(&action_id)
103 .map_or(&[] as _, SmallVec::as_slice)
104 .iter();
105
106 binding_indices.filter_map(|ix| {
107 let binding = &self.bindings[*ix];
108 if !binding.action().partial_eq(action) {
109 return None;
110 }
111
112 for disabled_ix in &self.disabled_binding_indices {
113 if disabled_ix > ix {
114 let disabled_binding = &self.bindings[*disabled_ix];
115 if disabled_binding.keystrokes != binding.keystrokes {
116 continue;
117 }
118
119 if is_no_action(&*disabled_binding.action) {
120 if disabled_binding_matches_context(disabled_binding, binding) {
121 return None;
122 }
123 } else if is_unbind(&*disabled_binding.action)
124 && disabled_binding_matches_context(disabled_binding, binding)
125 && binding_is_unbound(disabled_binding, binding)
126 {
127 return None;
128 }
129 }
130 }
131
132 Some(binding)
133 })
134 }
135
136 pub fn all_bindings_for_input(&self, input: &[Keystroke]) -> Vec<KeyBinding> {
139 self.bindings()
140 .rev()
141 .filter(|binding| {
142 binding
143 .match_keystrokes(input)
144 .is_some_and(|pending| !pending)
145 })
146 .cloned()
147 .collect()
148 }
149
150 pub fn bindings_for_input(
166 &self,
167 input: &[impl AsKeystroke],
168 context_stack: &[KeyContext],
169 ) -> (SmallVec<[KeyBinding; 1]>, bool) {
170 let (bindings, pending) = self.resolve_bindings_for_input(
171 input,
172 context_stack,
173 self.bindings().enumerate().rev(),
174 );
175 (
176 bindings
177 .into_iter()
178 .map(|(_, binding)| binding.clone())
179 .collect(),
180 pending,
181 )
182 }
183
184 fn resolve_bindings_for_input<'a>(
193 &self,
194 input: &[impl AsKeystroke],
195 context_stack: &[KeyContext],
196 keymap_bindings: impl IntoIterator<Item = (usize, &'a KeyBinding)>,
197 ) -> (SmallVec<[(BindingIndex, &'a KeyBinding); 1]>, bool) {
198 let mut matched_bindings = SmallVec::<[(usize, BindingIndex, &KeyBinding); 1]>::new();
199 let mut pending_bindings = SmallVec::<[(BindingIndex, &KeyBinding); 1]>::new();
200
201 for (ix, binding) in keymap_bindings {
202 let Some(depth) = self.binding_enabled(binding, context_stack) else {
203 continue;
204 };
205 let Some(pending) = binding.match_keystrokes(input) else {
206 continue;
207 };
208
209 if !pending {
210 matched_bindings.push((depth, BindingIndex(ix), binding));
211 } else {
212 pending_bindings.push((BindingIndex(ix), binding));
213 }
214 }
215
216 matched_bindings.sort_by(|(depth_a, ix_a, _), (depth_b, ix_b, _)| {
217 depth_b.cmp(depth_a).then(ix_b.cmp(ix_a))
218 });
219
220 let mut bindings: SmallVec<[_; 1]> = SmallVec::new();
221 let mut first_binding_index = None;
222 let mut unbound_bindings: Vec<&KeyBinding> = Vec::new();
223 let mut no_action_meta: Option<u32> = None;
228
229 for (_, ix, binding) in matched_bindings {
230 let meta = binding.meta.map_or(0, |meta| meta.0);
231 if is_no_action(&*binding.action) {
232 no_action_meta = Some(no_action_meta.map_or(meta, |existing| existing.min(meta)));
233 continue;
234 }
235
236 if no_action_meta.is_some_and(|no_action_meta| meta >= no_action_meta) {
237 continue;
238 }
239
240 if is_unbind(&*binding.action) {
241 unbound_bindings.push(binding);
242 continue;
243 }
244
245 if unbound_bindings
246 .iter()
247 .any(|disabled_binding| binding_is_unbound(disabled_binding, binding))
248 {
249 continue;
250 }
251
252 bindings.push((ix, binding));
253 first_binding_index.get_or_insert(ix);
254 }
255
256 let mut pending = HashSet::default();
257 for (ix, binding) in pending_bindings.into_iter().rev() {
258 if let Some(binding_ix) = first_binding_index
259 && binding_ix > ix
260 {
261 continue;
262 }
263 if is_no_action(&*binding.action) || is_unbind(&*binding.action) {
264 pending.remove(&&binding.keystrokes);
265 continue;
266 }
267 pending.insert(&binding.keystrokes);
268 }
269
270 (bindings, !pending.is_empty())
271 }
272 fn binding_enabled(&self, binding: &KeyBinding, contexts: &[KeyContext]) -> Option<usize> {
275 if let Some(predicate) = &binding.context_predicate {
276 predicate.depth_of(contexts)
277 } else {
278 Some(contexts.len())
279 }
280 }
281
282 pub fn possible_next_bindings_for_input(
286 &self,
287 input: &[Keystroke],
288 context_stack: &[KeyContext],
289 ) -> Vec<KeyBinding> {
290 let bindings_extending_input = self
292 .bindings()
293 .enumerate()
294 .rev()
295 .filter(|(_, binding)| {
296 binding
297 .match_keystrokes(input)
298 .is_some_and(|pending| pending)
299 })
300 .collect::<Vec<_>>();
301
302 let mut full_input = SmallVec::<[&Keystroke; 4]>::new();
303 let mut candidates = bindings_extending_input
304 .iter()
305 .filter(|(_, binding)| !is_no_action(&*binding.action) && !is_unbind(&*binding.action))
306 .filter_map(|&(ix, binding)| {
307 let depth = self.binding_enabled(binding, context_stack)?;
308 full_input.clear();
312 full_input.extend(input.iter());
313 full_input.extend(
314 binding.keystrokes[input.len()..]
315 .iter()
316 .map(AsKeystroke::as_keystroke),
317 );
318 let (dispatched, _) = self.resolve_bindings_for_input(
319 &full_input,
320 context_stack,
321 bindings_extending_input.iter().copied(),
322 );
323 dispatched
324 .iter()
325 .any(|(dispatched_ix, _)| *dispatched_ix == BindingIndex(ix))
326 .then_some((depth, ix, binding))
327 })
328 .collect::<Vec<_>>();
329 candidates.sort_unstable_by(|(depth_a, ix_a, _), (depth_b, ix_b, _)| {
330 depth_b.cmp(depth_a).then(ix_b.cmp(ix_a))
331 });
332
333 candidates
334 .into_iter()
335 .map(|(_, _, binding)| binding.clone())
336 .collect()
337 }
338}
339
340#[cfg(test)]
341mod tests {
342 use super::*;
343 use crate as gpui;
344 use gpui::{NoAction, Unbind};
345
346 actions!(
347 test_only,
348 [ActionAlpha, ActionBeta, ActionGamma, ActionDelta,]
349 );
350
351 #[test]
352 fn test_keymap() {
353 let bindings = [
354 KeyBinding::new("ctrl-a", ActionAlpha {}, None),
355 KeyBinding::new("ctrl-a", ActionBeta {}, Some("pane")),
356 KeyBinding::new("ctrl-a", ActionGamma {}, Some("editor && mode==full")),
357 ];
358
359 let mut keymap = Keymap::default();
360 keymap.add_bindings(bindings.clone());
361
362 assert_eq!(keymap.binding_enabled(&bindings[0], &[]), Some(0));
364 assert_eq!(
365 keymap.binding_enabled(&bindings[0], &[KeyContext::parse("terminal").unwrap()]),
366 Some(1)
367 );
368
369 assert_eq!(
371 keymap.binding_enabled(&bindings[1], &[KeyContext::parse("barf x=y").unwrap()]),
372 None
373 );
374 assert_eq!(
375 keymap.binding_enabled(&bindings[1], &[KeyContext::parse("pane x=y").unwrap()]),
376 Some(1)
377 );
378
379 assert_eq!(
380 keymap.binding_enabled(&bindings[2], &[KeyContext::parse("editor").unwrap()]),
381 None
382 );
383 assert_eq!(
384 keymap.binding_enabled(
385 &bindings[2],
386 &[KeyContext::parse("editor mode=full").unwrap()]
387 ),
388 Some(1)
389 );
390 }
391
392 #[test]
393 fn test_depth_precedence() {
394 let bindings = [
395 KeyBinding::new("ctrl-a", ActionBeta {}, Some("pane")),
396 KeyBinding::new("ctrl-a", ActionGamma {}, Some("editor")),
397 ];
398
399 let mut keymap = Keymap::default();
400 keymap.add_bindings(bindings);
401
402 let (result, pending) = keymap.bindings_for_input(
403 &[Keystroke::parse("ctrl-a").unwrap()],
404 &[
405 KeyContext::parse("pane").unwrap(),
406 KeyContext::parse("editor").unwrap(),
407 ],
408 );
409
410 assert!(!pending);
411 assert_eq!(result.len(), 2);
412 assert!(result[0].action.partial_eq(&ActionGamma {}));
413 assert!(result[1].action.partial_eq(&ActionBeta {}));
414 }
415
416 #[test]
417 fn test_keymap_disabled() {
418 let bindings = [
419 KeyBinding::new("ctrl-a", ActionAlpha {}, Some("editor")),
420 KeyBinding::new("ctrl-b", ActionAlpha {}, Some("editor")),
421 KeyBinding::new("ctrl-a", NoAction {}, Some("editor && mode==full")),
422 KeyBinding::new("ctrl-b", NoAction {}, None),
423 ];
424
425 let mut keymap = Keymap::default();
426 keymap.add_bindings(bindings);
427
428 assert!(
430 keymap
431 .bindings_for_input(
432 &[Keystroke::parse("ctrl-a").unwrap()],
433 &[KeyContext::parse("barf").unwrap()],
434 )
435 .0
436 .is_empty()
437 );
438 assert!(
439 !keymap
440 .bindings_for_input(
441 &[Keystroke::parse("ctrl-a").unwrap()],
442 &[KeyContext::parse("editor").unwrap()],
443 )
444 .0
445 .is_empty()
446 );
447
448 assert!(
450 keymap
451 .bindings_for_input(
452 &[Keystroke::parse("ctrl-a").unwrap()],
453 &[KeyContext::parse("editor mode=full").unwrap()],
454 )
455 .0
456 .is_empty()
457 );
458
459 assert!(
461 keymap
462 .bindings_for_input(
463 &[Keystroke::parse("ctrl-b").unwrap()],
464 &[KeyContext::parse("barf").unwrap()],
465 )
466 .0
467 .is_empty()
468 );
469 }
470
471 #[test]
472 fn test_multiple_keystroke_binding_disabled() {
474 let bindings = [
475 KeyBinding::new("space w w", ActionAlpha {}, Some("workspace")),
476 KeyBinding::new("space w w", NoAction {}, Some("editor")),
477 ];
478
479 let mut keymap = Keymap::default();
480 keymap.add_bindings(bindings);
481
482 let space = || Keystroke::parse("space").unwrap();
483 let w = || Keystroke::parse("w").unwrap();
484
485 let space_w = [space(), w()];
486 let space_w_w = [space(), w(), w()];
487
488 let workspace_context = || [KeyContext::parse("workspace").unwrap()];
489
490 let editor_workspace_context = || {
491 [
492 KeyContext::parse("workspace").unwrap(),
493 KeyContext::parse("editor").unwrap(),
494 ]
495 };
496
497 let space_workspace = keymap.bindings_for_input(&[space()], &workspace_context());
499 assert!(space_workspace.0.is_empty());
500 assert!(space_workspace.1);
501
502 let space_editor = keymap.bindings_for_input(&[space()], &editor_workspace_context());
503 assert!(space_editor.0.is_empty());
504 assert!(!space_editor.1);
505
506 let space_w_workspace = keymap.bindings_for_input(&space_w, &workspace_context());
508 assert!(space_w_workspace.0.is_empty());
509 assert!(space_w_workspace.1);
510
511 let space_w_editor = keymap.bindings_for_input(&space_w, &editor_workspace_context());
512 assert!(space_w_editor.0.is_empty());
513 assert!(!space_w_editor.1);
514
515 let space_w_w_workspace = keymap.bindings_for_input(&space_w_w, &workspace_context());
517 assert!(!space_w_w_workspace.0.is_empty());
518 assert!(!space_w_w_workspace.1);
519
520 let space_w_w_editor = keymap.bindings_for_input(&space_w_w, &editor_workspace_context());
521 assert!(space_w_w_editor.0.is_empty());
522 assert!(!space_w_w_editor.1);
523
524 let bindings = [
527 KeyBinding::new("space w w", ActionAlpha {}, Some("workspace")),
528 KeyBinding::new("space w w", NoAction {}, Some("editor")),
529 KeyBinding::new("space w x", ActionAlpha {}, Some("editor")),
530 ];
531 let mut keymap = Keymap::default();
532 keymap.add_bindings(bindings);
533
534 let space_editor = keymap.bindings_for_input(&[space()], &editor_workspace_context());
535 assert!(space_editor.0.is_empty());
536 assert!(space_editor.1);
537
538 let bindings = [
541 KeyBinding::new("space w w", ActionAlpha {}, Some("workspace")),
542 KeyBinding::new("space w x", ActionAlpha {}, Some("editor")),
543 KeyBinding::new("space w w", NoAction {}, Some("editor")),
544 ];
545 let mut keymap = Keymap::default();
546 keymap.add_bindings(bindings);
547
548 let space_editor = keymap.bindings_for_input(&[space()], &editor_workspace_context());
549 assert!(space_editor.0.is_empty());
550 assert!(space_editor.1);
551
552 let bindings = [
555 KeyBinding::new("space w w", ActionAlpha {}, Some("workspace")),
556 KeyBinding::new("space w x", ActionAlpha {}, Some("workspace")),
557 KeyBinding::new("space w w", NoAction {}, Some("editor")),
558 ];
559 let mut keymap = Keymap::default();
560 keymap.add_bindings(bindings);
561
562 let space_editor = keymap.bindings_for_input(&[space()], &editor_workspace_context());
563 assert!(space_editor.0.is_empty());
564 assert!(space_editor.1);
565 }
566
567 #[test]
568 fn test_override_multikey() {
569 let bindings = [
570 KeyBinding::new("ctrl-w left", ActionAlpha {}, Some("editor")),
571 KeyBinding::new("ctrl-w", 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-w").unwrap()],
580 &[KeyContext::parse("editor").unwrap()],
581 );
582 assert!(result.is_empty());
583 assert!(pending);
584
585 let bindings = [
586 KeyBinding::new("ctrl-w left", ActionAlpha {}, Some("editor")),
587 KeyBinding::new("ctrl-w", ActionBeta {}, Some("editor")),
588 ];
589
590 let mut keymap = Keymap::default();
591 keymap.add_bindings(bindings);
592
593 let (result, pending) = keymap.bindings_for_input(
595 &[Keystroke::parse("ctrl-w").unwrap()],
596 &[KeyContext::parse("editor").unwrap()],
597 );
598 assert_eq!(result.len(), 1);
599 assert!(!pending);
600 }
601
602 #[test]
603 fn test_simple_disable() {
604 let bindings = [
605 KeyBinding::new("ctrl-x", ActionAlpha {}, Some("editor")),
606 KeyBinding::new("ctrl-x", NoAction {}, Some("editor")),
607 ];
608
609 let mut keymap = Keymap::default();
610 keymap.add_bindings(bindings);
611
612 let (result, pending) = keymap.bindings_for_input(
614 &[Keystroke::parse("ctrl-x").unwrap()],
615 &[KeyContext::parse("editor").unwrap()],
616 );
617 assert!(result.is_empty());
618 assert!(!pending);
619 }
620
621 #[test]
622 fn test_disable_weaker_sources_only() {
623 const USER: KeyBindingMetaIndex = KeyBindingMetaIndex(0);
624 const VIM: KeyBindingMetaIndex = KeyBindingMetaIndex(1);
625 const BASE: KeyBindingMetaIndex = KeyBindingMetaIndex(2);
626 const DEFAULT: KeyBindingMetaIndex = KeyBindingMetaIndex(3);
627
628 let editor_context = || [KeyContext::parse("editor").unwrap()];
629 let ctrl_x = || [Keystroke::parse("ctrl-x").unwrap()];
630
631 let mut keymap = Keymap::default();
633 keymap.add_bindings([
634 KeyBinding::new("ctrl-x", ActionAlpha {}, Some("editor")).with_meta(DEFAULT),
635 KeyBinding::new("ctrl-x", NoAction {}, Some("editor")).with_meta(BASE),
636 ]);
637 let (result, _) = keymap.bindings_for_input(&ctrl_x(), &editor_context());
638 assert!(result.is_empty());
639
640 let mut keymap = Keymap::default();
642 keymap.add_bindings([
643 KeyBinding::new("ctrl-x", NoAction {}, Some("editor")).with_meta(DEFAULT),
644 KeyBinding::new("ctrl-x", NoAction {}, Some("editor")).with_meta(BASE),
645 KeyBinding::new("ctrl-x", ActionBeta {}, None).with_meta(USER),
646 ]);
647 let (result, _) = keymap.bindings_for_input(&ctrl_x(), &editor_context());
648 assert_eq!(result.len(), 1);
649 assert!(result[0].action.partial_eq(&ActionBeta {}));
650
651 let mut keymap = Keymap::default();
654 keymap.add_bindings([
655 KeyBinding::new("ctrl-x", NoAction {}, Some("editor")).with_meta(BASE),
656 KeyBinding::new("ctrl-x", ActionBeta {}, Some("workspace")).with_meta(USER),
657 ]);
658 let (result, _) = keymap.bindings_for_input(
659 &ctrl_x(),
660 &[
661 KeyContext::parse("workspace").unwrap(),
662 KeyContext::parse("editor").unwrap(),
663 ],
664 );
665 assert_eq!(result.len(), 1);
666 assert!(result[0].action.partial_eq(&ActionBeta {}));
667
668 let mut keymap = Keymap::default();
671 keymap.add_bindings([
672 KeyBinding::new("ctrl-x", ActionAlpha {}, Some("editor")).with_meta(DEFAULT),
673 KeyBinding::new("ctrl-x", NoAction {}, Some("editor")).with_meta(BASE),
674 KeyBinding::new("ctrl-x", ActionGamma {}, Some("editor")).with_meta(VIM),
675 ]);
676 let (result, _) = keymap.bindings_for_input(&ctrl_x(), &editor_context());
677 assert_eq!(result.len(), 1);
678 assert!(result[0].action.partial_eq(&ActionGamma {}));
679
680 let mut keymap = Keymap::default();
681 keymap.add_bindings([
682 KeyBinding::new("ctrl-x", ActionAlpha {}, Some("editor")).with_meta(DEFAULT),
683 KeyBinding::new("ctrl-x", ActionGamma {}, Some("editor")).with_meta(VIM),
684 KeyBinding::new("ctrl-x", NoAction {}, Some("editor")).with_meta(USER),
685 ]);
686 let (result, _) = keymap.bindings_for_input(&ctrl_x(), &editor_context());
687 assert!(result.is_empty());
688 }
689
690 #[test]
691 fn test_fail_to_disable() {
692 let bindings = [
694 KeyBinding::new("ctrl-x", ActionAlpha {}, Some("editor")),
695 KeyBinding::new("ctrl-x", NoAction {}, Some("workspace")),
696 ];
697
698 let mut keymap = Keymap::default();
699 keymap.add_bindings(bindings);
700
701 let (result, pending) = keymap.bindings_for_input(
703 &[Keystroke::parse("ctrl-x").unwrap()],
704 &[
705 KeyContext::parse("workspace").unwrap(),
706 KeyContext::parse("editor").unwrap(),
707 ],
708 );
709 assert_eq!(result.len(), 1);
710 assert!(!pending);
711 }
712
713 #[test]
714 fn test_disable_deeper() {
715 let bindings = [
716 KeyBinding::new("ctrl-x", ActionAlpha {}, Some("workspace")),
717 KeyBinding::new("ctrl-x", NoAction {}, Some("editor")),
718 ];
719
720 let mut keymap = Keymap::default();
721 keymap.add_bindings(bindings);
722
723 let (result, pending) = keymap.bindings_for_input(
725 &[Keystroke::parse("ctrl-x").unwrap()],
726 &[
727 KeyContext::parse("workspace").unwrap(),
728 KeyContext::parse("editor").unwrap(),
729 ],
730 );
731 assert_eq!(result.len(), 0);
732 assert!(!pending);
733 }
734
735 #[test]
736 fn test_pending_match_enabled() {
737 let bindings = [
738 KeyBinding::new("ctrl-x", ActionBeta, Some("vim_mode == normal")),
739 KeyBinding::new("ctrl-x 0", ActionAlpha, Some("Workspace")),
740 ];
741 let mut keymap = Keymap::default();
742 keymap.add_bindings(bindings);
743
744 let matched = keymap.bindings_for_input(
745 &[Keystroke::parse("ctrl-x")].map(Result::unwrap),
746 &[
747 KeyContext::parse("Workspace"),
748 KeyContext::parse("Pane"),
749 KeyContext::parse("Editor vim_mode=normal"),
750 ]
751 .map(Result::unwrap),
752 );
753 assert_eq!(matched.0.len(), 1);
754 assert!(matched.0[0].action.partial_eq(&ActionBeta));
755 assert!(matched.1);
756 }
757
758 #[test]
759 fn test_pending_match_enabled_extended() {
760 let bindings = [
761 KeyBinding::new("ctrl-x", ActionBeta, Some("vim_mode == normal")),
762 KeyBinding::new("ctrl-x 0", NoAction, Some("Workspace")),
763 ];
764 let mut keymap = Keymap::default();
765 keymap.add_bindings(bindings);
766
767 let matched = keymap.bindings_for_input(
768 &[Keystroke::parse("ctrl-x")].map(Result::unwrap),
769 &[
770 KeyContext::parse("Workspace"),
771 KeyContext::parse("Pane"),
772 KeyContext::parse("Editor vim_mode=normal"),
773 ]
774 .map(Result::unwrap),
775 );
776 assert_eq!(matched.0.len(), 1);
777 assert!(matched.0[0].action.partial_eq(&ActionBeta));
778 assert!(!matched.1);
779 let bindings = [
780 KeyBinding::new("ctrl-x", ActionBeta, Some("Workspace")),
781 KeyBinding::new("ctrl-x 0", NoAction, Some("vim_mode == normal")),
782 ];
783 let mut keymap = Keymap::default();
784 keymap.add_bindings(bindings);
785
786 let matched = keymap.bindings_for_input(
787 &[Keystroke::parse("ctrl-x")].map(Result::unwrap),
788 &[
789 KeyContext::parse("Workspace"),
790 KeyContext::parse("Pane"),
791 KeyContext::parse("Editor vim_mode=normal"),
792 ]
793 .map(Result::unwrap),
794 );
795 assert_eq!(matched.0.len(), 1);
796 assert!(matched.0[0].action.partial_eq(&ActionBeta));
797 assert!(!matched.1);
798 }
799
800 #[test]
801 fn test_overriding_prefix() {
802 let bindings = [
803 KeyBinding::new("ctrl-x 0", ActionAlpha, Some("Workspace")),
804 KeyBinding::new("ctrl-x", ActionBeta, Some("vim_mode == normal")),
805 ];
806 let mut keymap = Keymap::default();
807 keymap.add_bindings(bindings);
808
809 let matched = keymap.bindings_for_input(
810 &[Keystroke::parse("ctrl-x")].map(Result::unwrap),
811 &[
812 KeyContext::parse("Workspace"),
813 KeyContext::parse("Pane"),
814 KeyContext::parse("Editor vim_mode=normal"),
815 ]
816 .map(Result::unwrap),
817 );
818 assert_eq!(matched.0.len(), 1);
819 assert!(matched.0[0].action.partial_eq(&ActionBeta));
820 assert!(!matched.1);
821 }
822
823 #[test]
824 fn test_context_precedence_with_same_source() {
825 let bindings = [
828 KeyBinding::new("cmd-r", ActionAlpha {}, Some("Workspace")),
829 KeyBinding::new("cmd-r", ActionBeta {}, Some("Editor")),
830 ];
831
832 let mut keymap = Keymap::default();
833 keymap.add_bindings(bindings);
834
835 let (result, _) = keymap.bindings_for_input(
837 &[Keystroke::parse("cmd-r").unwrap()],
838 &[
839 KeyContext::parse("Workspace").unwrap(),
840 KeyContext::parse("Editor").unwrap(),
841 ],
842 );
843
844 assert_eq!(result.len(), 2);
846 assert!(result[0].action.partial_eq(&ActionBeta {})); assert!(result[1].action.partial_eq(&ActionAlpha {})); }
849
850 #[test]
851 fn test_bindings_for_action() {
852 let bindings = [
853 KeyBinding::new("ctrl-a", ActionAlpha {}, Some("pane")),
854 KeyBinding::new("ctrl-b", ActionBeta {}, Some("editor && mode == full")),
855 KeyBinding::new("ctrl-c", ActionGamma {}, Some("workspace")),
856 KeyBinding::new("ctrl-a", NoAction {}, Some("pane && active")),
857 KeyBinding::new("ctrl-b", NoAction {}, Some("editor")),
858 ];
859
860 let mut keymap = Keymap::default();
861 keymap.add_bindings(bindings);
862
863 assert_bindings(&keymap, &ActionAlpha {}, &["ctrl-a"]);
864 assert_bindings(&keymap, &ActionBeta {}, &[]);
865 assert_bindings(&keymap, &ActionGamma {}, &["ctrl-c"]);
866
867 #[track_caller]
868 fn assert_bindings(keymap: &Keymap, action: &dyn Action, expected: &[&str]) {
869 let actual = keymap
870 .bindings_for_action(action)
871 .map(|binding| binding.keystrokes[0].inner().unparse())
872 .collect::<Vec<_>>();
873 assert_eq!(actual, expected, "{:?}", action);
874 }
875 }
876
877 #[test]
878 fn test_targeted_unbind_ignores_target_context() {
879 let bindings = [
880 KeyBinding::new("tab", ActionAlpha {}, Some("Editor")),
881 KeyBinding::new("tab", ActionBeta {}, Some("Editor && showing_completions")),
882 KeyBinding::new(
883 "tab",
884 Unbind("test_only::ActionAlpha".into()),
885 Some("Editor && edit_prediction"),
886 ),
887 ];
888
889 let mut keymap = Keymap::default();
890 keymap.add_bindings(bindings);
891
892 let (result, pending) = keymap.bindings_for_input(
893 &[Keystroke::parse("tab").unwrap()],
894 &[KeyContext::parse("Editor showing_completions edit_prediction").unwrap()],
895 );
896
897 assert!(!pending);
898 assert_eq!(result.len(), 1);
899 assert!(result[0].action.partial_eq(&ActionBeta {}));
900 }
901
902 #[test]
903 fn test_bindings_for_action_keeps_binding_for_narrower_targeted_unbind() {
904 let bindings = [
905 KeyBinding::new("tab", ActionAlpha {}, Some("Editor")),
906 KeyBinding::new(
907 "tab",
908 Unbind("test_only::ActionAlpha".into()),
909 Some("Editor && edit_prediction"),
910 ),
911 KeyBinding::new("tab", ActionBeta {}, Some("Editor && showing_completions")),
912 ];
913
914 let mut keymap = Keymap::default();
915 keymap.add_bindings(bindings);
916
917 assert_bindings(&keymap, &ActionAlpha {}, &["tab"]);
918 assert_bindings(&keymap, &ActionBeta {}, &["tab"]);
919
920 #[track_caller]
921 fn assert_bindings(keymap: &Keymap, action: &dyn Action, expected: &[&str]) {
922 let actual = keymap
923 .bindings_for_action(action)
924 .map(|binding| binding.keystrokes[0].inner().unparse())
925 .collect::<Vec<_>>();
926 assert_eq!(actual, expected, "{:?}", action);
927 }
928 }
929
930 #[test]
931 fn test_bindings_for_action_removes_binding_for_broader_targeted_unbind() {
932 let bindings = [
933 KeyBinding::new("tab", ActionAlpha {}, Some("Editor && edit_prediction")),
934 KeyBinding::new(
935 "tab",
936 Unbind("test_only::ActionAlpha".into()),
937 Some("Editor"),
938 ),
939 ];
940
941 let mut keymap = Keymap::default();
942 keymap.add_bindings(bindings);
943
944 assert!(keymap.bindings_for_action(&ActionAlpha {}).next().is_none());
945 }
946
947 #[test]
948 fn test_source_precedence_sorting() {
949 let mut keymap = Keymap::default();
952
953 let mut default_binding = KeyBinding::new("cmd-r", ActionAlpha {}, Some("Editor"));
955 default_binding.set_meta(KeyBindingMetaIndex(3)); keymap.add_bindings([default_binding]);
957
958 let mut user_binding = KeyBinding::new("cmd-r", ActionBeta {}, Some("Editor"));
960 user_binding.set_meta(KeyBindingMetaIndex(0)); keymap.add_bindings([user_binding]);
962
963 let (result, _) = keymap.bindings_for_input(
965 &[Keystroke::parse("cmd-r").unwrap()],
966 &[KeyContext::parse("Editor").unwrap()],
967 );
968
969 assert_eq!(result.len(), 2);
971 assert!(result[0].action.partial_eq(&ActionBeta {}));
972 assert!(result[1].action.partial_eq(&ActionAlpha {}));
973 }
974
975 #[test]
976 fn test_possible_next_bindings_exclude_null_bindings() {
977 let keymap = Keymap::new(vec![
978 KeyBinding::new("ctrl-k a", ActionAlpha {}, Some("Editor")),
979 KeyBinding::new("ctrl-k b", ActionBeta {}, Some("Editor")),
980 KeyBinding::new("ctrl-k a", NoAction {}, Some("Editor")),
981 ]);
982
983 let bindings = keymap.possible_next_bindings_for_input(
984 &[Keystroke::parse("ctrl-k").unwrap()],
985 &[KeyContext::parse("Editor").unwrap()],
986 );
987 assert_eq!(
988 describe_bindings(&bindings),
989 vec![("ctrl-k b".to_string(), "test_only::ActionBeta")]
990 );
991 }
992
993 #[test]
994 fn test_possible_next_bindings_exclude_unbound_bindings() {
995 let keymap = Keymap::new(vec![
996 KeyBinding::new("ctrl-k a", ActionAlpha {}, Some("Editor")),
997 KeyBinding::new("ctrl-k b", ActionBeta {}, Some("Editor")),
998 KeyBinding::new(
999 "ctrl-k a",
1000 Unbind("test_only::ActionAlpha".into()),
1001 Some("Editor"),
1002 ),
1003 ]);
1004
1005 let bindings = keymap.possible_next_bindings_for_input(
1006 &[Keystroke::parse("ctrl-k").unwrap()],
1007 &[KeyContext::parse("Editor").unwrap()],
1008 );
1009 assert_eq!(
1010 describe_bindings(&bindings),
1011 vec![("ctrl-k b".to_string(), "test_only::ActionBeta")]
1012 );
1013 }
1014
1015 #[test]
1016 fn test_possible_next_bindings_keep_user_binding_over_base_keymap_null() {
1017 const USER: KeyBindingMetaIndex = KeyBindingMetaIndex(0);
1018 const BASE: KeyBindingMetaIndex = KeyBindingMetaIndex(2);
1019 const DEFAULT: KeyBindingMetaIndex = KeyBindingMetaIndex(3);
1020
1021 let mut keymap = Keymap::default();
1022 keymap.add_bindings([
1023 KeyBinding::new("ctrl-k a", ActionAlpha {}, Some("Editor")).with_meta(DEFAULT),
1024 KeyBinding::new("ctrl-k a", NoAction {}, Some("Editor")).with_meta(BASE),
1025 KeyBinding::new("ctrl-k a", ActionBeta {}, Some("Editor")).with_meta(USER),
1026 ]);
1027
1028 let bindings = keymap.possible_next_bindings_for_input(
1029 &[Keystroke::parse("ctrl-k").unwrap()],
1030 &[KeyContext::parse("Editor").unwrap()],
1031 );
1032 assert_eq!(
1033 describe_bindings(&bindings),
1034 vec![("ctrl-k a".to_string(), "test_only::ActionBeta")]
1035 );
1036 }
1037
1038 #[test]
1039 fn test_possible_next_bindings_apply_disabling_by_context() {
1040 let keymap = Keymap::new(vec![
1041 KeyBinding::new("ctrl-k a", ActionAlpha {}, Some("Workspace")),
1042 KeyBinding::new("ctrl-k a", ActionBeta {}, Some("Editor")),
1043 KeyBinding::new("ctrl-k b", ActionGamma {}, Some("Editor")),
1044 KeyBinding::new(
1046 "ctrl-k a",
1047 Unbind("test_only::ActionBeta".into()),
1048 Some("Editor"),
1049 ),
1050 KeyBinding::new("ctrl-k b", NoAction {}, Some("Terminal")),
1052 ]);
1053
1054 let bindings = keymap.possible_next_bindings_for_input(
1055 &[Keystroke::parse("ctrl-k").unwrap()],
1056 &[
1057 KeyContext::parse("Workspace").unwrap(),
1058 KeyContext::parse("Editor").unwrap(),
1059 ],
1060 );
1061 assert_eq!(
1062 describe_bindings(&bindings),
1063 vec![
1064 ("ctrl-k b".to_string(), "test_only::ActionGamma"),
1065 ("ctrl-k a".to_string(), "test_only::ActionAlpha"),
1066 ]
1067 );
1068 }
1069
1070 #[test]
1071 fn test_possible_next_bindings_exclude_longer_chord_disabled_in_deeper_context() {
1072 let keymap = Keymap::new(vec![
1073 KeyBinding::new("space w w", ActionAlpha {}, Some("Workspace")),
1074 KeyBinding::new("space w x", ActionBeta {}, Some("Workspace")),
1075 KeyBinding::new("space w w", NoAction {}, Some("AgentPanel")),
1076 ]);
1077 let context_stack = [
1078 KeyContext::parse("Workspace").unwrap(),
1079 KeyContext::parse("AgentPanel").unwrap(),
1080 ];
1081
1082 for input in ["space", "space w"] {
1083 let input = input
1084 .split(' ')
1085 .map(|keystroke| Keystroke::parse(keystroke).unwrap())
1086 .collect::<Vec<_>>();
1087 let bindings = keymap.possible_next_bindings_for_input(&input, &context_stack);
1088 assert_eq!(
1089 describe_bindings(&bindings),
1090 vec![("space w x".to_string(), "test_only::ActionBeta")]
1091 );
1092 }
1093 }
1094
1095 #[test]
1096 fn test_possible_next_bindings_exclude_disabled_bindings_with_typed_input() {
1097 let typed_input = [Keystroke::parse("alt-s->ß").unwrap()];
1100 let keymap = Keymap::new(vec![
1101 KeyBinding::new("ß a", ActionAlpha {}, None),
1102 KeyBinding::new("alt-s a", NoAction {}, None),
1103 ]);
1104 assert!(
1105 keymap
1106 .possible_next_bindings_for_input(&typed_input, &[])
1107 .is_empty()
1108 );
1109
1110 let typed_then_a = [
1112 Keystroke::parse("alt-s->ß").unwrap(),
1113 Keystroke::parse("a").unwrap(),
1114 ];
1115 let (dispatched, _) = keymap.bindings_for_input(&typed_then_a, &[]);
1116 assert!(dispatched.is_empty());
1117 }
1118
1119 fn describe_bindings(bindings: &[KeyBinding]) -> Vec<(String, &'static str)> {
1120 bindings
1121 .iter()
1122 .map(|binding| {
1123 (
1124 binding
1125 .keystrokes()
1126 .iter()
1127 .map(|keystroke| keystroke.inner().unparse())
1128 .collect::<Vec<_>>()
1129 .join(" "),
1130 binding.action().name(),
1131 )
1132 })
1133 .collect()
1134 }
1135}