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