1mod binding;
2mod context;
3
4pub use binding::*;
5pub use context::*;
6
7use crate::{Action, AsKeystroke, Keystroke};
8use collections::{HashMap, HashSet};
9use smallvec::SmallVec;
10use std::any::TypeId;
11
12/// An opaque identifier of which version of the keymap is currently active.
13/// The keymap's version is changed whenever bindings are added or removed.
14#[derive(Copy, Clone, Eq, PartialEq, Default)]
15pub struct KeymapVersion(usize);
16
17/// A collection of key bindings for the user's application.
18#[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/// Index of a binding within a keymap.
27#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
28pub struct BindingIndex(usize);
29
30impl Keymap {
31 /// Create a new keymap with the given bindings.
32 pub fn new(bindings: Vec<KeyBinding>) -> Self {
33 let mut this = Self::default();
34 this.add_bindings(bindings);
35 this
36 }
37
38 /// Get the current version of the keymap.
39 pub fn version(&self) -> KeymapVersion {
40 self.version
41 }
42
43 /// Add more bindings to the keymap.
44 pub fn add_bindings<T: IntoIterator<Item = KeyBinding>>(&mut self, bindings: T) {
45 for binding in bindings {
46 if let Some(action) = binding.action.as_deref() {
47 let action_id = action.type_id();
48 self.binding_indices_by_action_id
49 .entry(action_id)
50 .or_default()
51 .push(self.bindings.len());
52 } else {
53 self.no_action_binding_indices.push(self.bindings.len());
54 }
55 self.bindings.push(binding);
56 }
57
58 self.version.0 += 1;
59 }
60
61 /// Reset this keymap to its initial state.
62 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 /// Iterate over all bindings, in the order they were added.
70 pub fn bindings(&self) -> impl DoubleEndedIterator<Item = &KeyBinding> + ExactSizeIterator {
71 self.bindings.iter()
72 }
73
74 /// Iterate over all bindings for the given action, in the order they were added. For display,
75 /// the last binding should take precedence.
76 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
90 .action()
91 .is_none_or(|action| !action.partial_eq(action))
92 {
93 return None;
94 }
95
96 for null_ix in &self.no_action_binding_indices {
97 if null_ix > ix {
98 let null_binding = &self.bindings[*null_ix];
99 if null_binding.keystrokes == binding.keystrokes {
100 let null_binding_matches =
101 match (&null_binding.context_predicate, &binding.context_predicate) {
102 (None, _) => true,
103 (Some(_), None) => false,
104 (Some(null_predicate), Some(predicate)) => {
105 null_predicate.is_superset(predicate)
106 }
107 };
108 if null_binding_matches {
109 return None;
110 }
111 }
112 }
113 }
114
115 Some(binding)
116 })
117 }
118
119 /// Returns all bindings that might match the input without checking context. The bindings
120 /// returned in precedence order (reverse of the order they were added to the keymap).
121 pub fn all_bindings_for_input(&self, input: &[Keystroke]) -> Vec<KeyBinding> {
122 self.bindings()
123 .rev()
124 .filter_map(|binding| {
125 binding.match_keystrokes(input).filter(|pending| !pending)?;
126 Some(binding.clone())
127 })
128 .collect()
129 }
130
131 /// Returns a list of bindings that match the given input, and a boolean indicating whether or
132 /// not more bindings might match if the input was longer. Bindings are returned in precedence
133 /// order (higher precedence first, reverse of the order they were added to the keymap).
134 ///
135 /// Precedence is defined by the depth in the tree (matches on the Editor take precedence over
136 /// matches on the Pane, then the Workspace, etc.). Bindings with no context are treated as the
137 /// same as the deepest context.
138 ///
139 /// In the case of multiple bindings at the same depth, the ones added to the keymap later take
140 /// precedence. User bindings are added after built-in bindings so that they take precedence.
141 ///
142 /// If a user has disabled a binding with `"x": null` it will not be returned. Disabled bindings
143 /// are evaluated with the same precedence rules so you can disable a rule in a given context
144 /// only.
145 pub fn bindings_for_input(
146 &self,
147 input: &[impl AsKeystroke],
148 context_stack: &[KeyContext],
149 ) -> (SmallVec<[KeyBinding; 1]>, bool) {
150 let mut matched_bindings = SmallVec::<[(usize, BindingIndex, &KeyBinding); 1]>::new();
151 let mut pending_bindings = SmallVec::<[(BindingIndex, &KeyBinding); 1]>::new();
152
153 for (ix, binding) in self.bindings().enumerate().rev() {
154 let Some(depth) = self.binding_enabled(binding, context_stack) else {
155 continue;
156 };
157 let Some(pending) = binding.match_keystrokes(input) else {
158 continue;
159 };
160
161 if !pending {
162 matched_bindings.push((depth, BindingIndex(ix), binding));
163 } else {
164 pending_bindings.push((BindingIndex(ix), binding));
165 }
166 }
167
168 matched_bindings.sort_by(|(depth_a, ix_a, _), (depth_b, ix_b, _)| {
169 depth_b.cmp(depth_a).then(ix_b.cmp(ix_a))
170 });
171
172 let mut bindings: SmallVec<[_; 1]> = SmallVec::new();
173 let mut first_binding_index = None;
174
175 for (_, ix, binding) in matched_bindings {
176 if binding.action.is_none() {
177 // Only break if this is a user-defined NoAction binding
178 // This allows user keymaps to override base keymap NoAction bindings
179 if let Some(meta) = binding.meta {
180 if meta.0 == 0 {
181 break;
182 }
183 } else {
184 // If no meta is set, assume it's a user binding for safety
185 break;
186 }
187 // For non-user NoAction bindings, continue searching for user overrides
188 continue;
189 }
190 bindings.push(binding.clone());
191 first_binding_index.get_or_insert(ix);
192 }
193
194 let mut pending = HashSet::default();
195 for (ix, binding) in pending_bindings.into_iter().rev() {
196 if let Some(binding_ix) = first_binding_index
197 && binding_ix > ix
198 {
199 continue;
200 }
201 if binding.action.is_none() {
202 pending.remove(&&binding.keystrokes);
203 continue;
204 }
205 pending.insert(&binding.keystrokes);
206 }
207
208 (bindings, !pending.is_empty())
209 }
210
211 /// Check if the given binding is enabled, given a certain key context.
212 /// Returns the deepest depth at which the binding matches, or [`None`] if it doesn't match.
213 fn binding_enabled(&self, binding: &KeyBinding, contexts: &[KeyContext]) -> Option<usize> {
214 if let Some(predicate) = &binding.context_predicate {
215 predicate.depth_of(contexts)
216 } else {
217 Some(contexts.len())
218 }
219 }
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225 use crate as gpui;
226
227 actions!(
228 test_only,
229 [ActionAlpha, ActionBeta, ActionGamma, ActionDelta,]
230 );
231
232 #[test]
233 fn test_keymap() {
234 let bindings = [
235 KeyBinding::new("ctrl-a", ActionAlpha {}, None),
236 KeyBinding::new("ctrl-a", ActionBeta {}, Some("pane")),
237 KeyBinding::new("ctrl-a", ActionGamma {}, Some("editor && mode==full")),
238 ];
239
240 let mut keymap = Keymap::default();
241 keymap.add_bindings(bindings.clone());
242
243 // global bindings are enabled in all contexts
244 assert_eq!(keymap.binding_enabled(&bindings[0], &[]), Some(0));
245 assert_eq!(
246 keymap.binding_enabled(&bindings[0], &[KeyContext::parse("terminal").unwrap()]),
247 Some(1)
248 );
249
250 // contextual bindings are enabled in contexts that match their predicate
251 assert_eq!(
252 keymap.binding_enabled(&bindings[1], &[KeyContext::parse("barf x=y").unwrap()]),
253 None
254 );
255 assert_eq!(
256 keymap.binding_enabled(&bindings[1], &[KeyContext::parse("pane x=y").unwrap()]),
257 Some(1)
258 );
259
260 assert_eq!(
261 keymap.binding_enabled(&bindings[2], &[KeyContext::parse("editor").unwrap()]),
262 None
263 );
264 assert_eq!(
265 keymap.binding_enabled(
266 &bindings[2],
267 &[KeyContext::parse("editor mode=full").unwrap()]
268 ),
269 Some(1)
270 );
271 }
272
273 #[test]
274 fn test_depth_precedence() {
275 let bindings = [
276 KeyBinding::new("ctrl-a", ActionBeta {}, Some("pane")),
277 KeyBinding::new("ctrl-a", ActionGamma {}, Some("editor")),
278 ];
279
280 let mut keymap = Keymap::default();
281 keymap.add_bindings(bindings);
282
283 let (result, pending) = keymap.bindings_for_input(
284 &[Keystroke::parse("ctrl-a").unwrap()],
285 &[
286 KeyContext::parse("pane").unwrap(),
287 KeyContext::parse("editor").unwrap(),
288 ],
289 );
290
291 assert!(!pending);
292 assert_eq!(result.len(), 2);
293 assert!(
294 result[0]
295 .action
296 .as_deref()
297 .is_some_and(|it| it.partial_eq(&ActionGamma {}))
298 );
299 assert!(
300 result[1]
301 .action
302 .as_deref()
303 .is_some_and(|it| it.partial_eq(&ActionBeta {}))
304 );
305 }
306
307 #[test]
308 fn test_keymap_disabled() {
309 let bindings = [
310 KeyBinding::new("ctrl-a", ActionAlpha {}, Some("editor")),
311 KeyBinding::new("ctrl-b", ActionAlpha {}, Some("editor")),
312 KeyBinding::new_no_action("ctrl-a", Some("editor && mode==full")),
313 KeyBinding::new_no_action("ctrl-b", None),
314 ];
315
316 let mut keymap = Keymap::default();
317 keymap.add_bindings(bindings);
318
319 // binding is only enabled in a specific context
320 assert!(
321 keymap
322 .bindings_for_input(
323 &[Keystroke::parse("ctrl-a").unwrap()],
324 &[KeyContext::parse("barf").unwrap()],
325 )
326 .0
327 .is_empty()
328 );
329 assert!(
330 !keymap
331 .bindings_for_input(
332 &[Keystroke::parse("ctrl-a").unwrap()],
333 &[KeyContext::parse("editor").unwrap()],
334 )
335 .0
336 .is_empty()
337 );
338
339 // binding is disabled in a more specific context
340 assert!(
341 keymap
342 .bindings_for_input(
343 &[Keystroke::parse("ctrl-a").unwrap()],
344 &[KeyContext::parse("editor mode=full").unwrap()],
345 )
346 .0
347 .is_empty()
348 );
349
350 // binding is globally disabled
351 assert!(
352 keymap
353 .bindings_for_input(
354 &[Keystroke::parse("ctrl-b").unwrap()],
355 &[KeyContext::parse("barf").unwrap()],
356 )
357 .0
358 .is_empty()
359 );
360 }
361
362 #[test]
363 /// Tests for https://github.com/zed-industries/zed/issues/30259
364 fn test_multiple_keystroke_binding_disabled() {
365 let bindings = [
366 KeyBinding::new("space w w", ActionAlpha {}, Some("workspace")),
367 KeyBinding::new_no_action("space w w", Some("editor")),
368 ];
369
370 let mut keymap = Keymap::default();
371 keymap.add_bindings(bindings);
372
373 let space = || Keystroke::parse("space").unwrap();
374 let w = || Keystroke::parse("w").unwrap();
375
376 let space_w = [space(), w()];
377 let space_w_w = [space(), w(), w()];
378
379 let workspace_context = || [KeyContext::parse("workspace").unwrap()];
380
381 let editor_workspace_context = || {
382 [
383 KeyContext::parse("workspace").unwrap(),
384 KeyContext::parse("editor").unwrap(),
385 ]
386 };
387
388 // Ensure `space` results in pending input on the workspace, but not editor
389 let space_workspace = keymap.bindings_for_input(&[space()], &workspace_context());
390 assert!(space_workspace.0.is_empty());
391 assert!(space_workspace.1);
392
393 let space_editor = keymap.bindings_for_input(&[space()], &editor_workspace_context());
394 assert!(space_editor.0.is_empty());
395 assert!(!space_editor.1);
396
397 // Ensure `space w` results in pending input on the workspace, but not editor
398 let space_w_workspace = keymap.bindings_for_input(&space_w, &workspace_context());
399 assert!(space_w_workspace.0.is_empty());
400 assert!(space_w_workspace.1);
401
402 let space_w_editor = keymap.bindings_for_input(&space_w, &editor_workspace_context());
403 assert!(space_w_editor.0.is_empty());
404 assert!(!space_w_editor.1);
405
406 // Ensure `space w w` results in the binding in the workspace, but not in the editor
407 let space_w_w_workspace = keymap.bindings_for_input(&space_w_w, &workspace_context());
408 assert!(!space_w_w_workspace.0.is_empty());
409 assert!(!space_w_w_workspace.1);
410
411 let space_w_w_editor = keymap.bindings_for_input(&space_w_w, &editor_workspace_context());
412 assert!(space_w_w_editor.0.is_empty());
413 assert!(!space_w_w_editor.1);
414
415 // Now test what happens if we have another binding defined AFTER the NoAction
416 // that should result in pending
417 let bindings = [
418 KeyBinding::new("space w w", ActionAlpha {}, Some("workspace")),
419 KeyBinding::new_no_action("space w w", Some("editor")),
420 KeyBinding::new("space w x", ActionAlpha {}, Some("editor")),
421 ];
422 let mut keymap = Keymap::default();
423 keymap.add_bindings(bindings);
424
425 let space_editor = keymap.bindings_for_input(&[space()], &editor_workspace_context());
426 assert!(space_editor.0.is_empty());
427 assert!(space_editor.1);
428
429 // Now test what happens if we have another binding defined BEFORE the NoAction
430 // that should result in pending
431 let bindings = [
432 KeyBinding::new("space w w", ActionAlpha {}, Some("workspace")),
433 KeyBinding::new("space w x", ActionAlpha {}, Some("editor")),
434 KeyBinding::new_no_action("space w w", Some("editor")),
435 ];
436 let mut keymap = Keymap::default();
437 keymap.add_bindings(bindings);
438
439 let space_editor = keymap.bindings_for_input(&[space()], &editor_workspace_context());
440 assert!(space_editor.0.is_empty());
441 assert!(space_editor.1);
442
443 // Now test what happens if we have another binding defined at a higher context
444 // that should result in pending
445 let bindings = [
446 KeyBinding::new("space w w", ActionAlpha {}, Some("workspace")),
447 KeyBinding::new("space w x", ActionAlpha {}, Some("workspace")),
448 KeyBinding::new_no_action("space w w", Some("editor")),
449 ];
450 let mut keymap = Keymap::default();
451 keymap.add_bindings(bindings);
452
453 let space_editor = keymap.bindings_for_input(&[space()], &editor_workspace_context());
454 assert!(space_editor.0.is_empty());
455 assert!(space_editor.1);
456 }
457
458 #[test]
459 fn test_override_multikey() {
460 let bindings = [
461 KeyBinding::new("ctrl-w left", ActionAlpha {}, Some("editor")),
462 KeyBinding::new_no_action("ctrl-w", Some("editor")),
463 ];
464
465 let mut keymap = Keymap::default();
466 keymap.add_bindings(bindings);
467
468 // Ensure `space` results in pending input on the workspace, but not editor
469 let (result, pending) = keymap.bindings_for_input(
470 &[Keystroke::parse("ctrl-w").unwrap()],
471 &[KeyContext::parse("editor").unwrap()],
472 );
473 assert!(result.is_empty());
474 assert!(pending);
475
476 let bindings = [
477 KeyBinding::new("ctrl-w left", ActionAlpha {}, Some("editor")),
478 KeyBinding::new("ctrl-w", ActionBeta {}, Some("editor")),
479 ];
480
481 let mut keymap = Keymap::default();
482 keymap.add_bindings(bindings);
483
484 // Ensure `space` results in pending input on the workspace, but not editor
485 let (result, pending) = keymap.bindings_for_input(
486 &[Keystroke::parse("ctrl-w").unwrap()],
487 &[KeyContext::parse("editor").unwrap()],
488 );
489 assert_eq!(result.len(), 1);
490 assert!(!pending);
491 }
492
493 #[test]
494 fn test_simple_disable() {
495 let bindings = [
496 KeyBinding::new("ctrl-x", ActionAlpha {}, Some("editor")),
497 KeyBinding::new_no_action("ctrl-x", Some("editor")),
498 ];
499
500 let mut keymap = Keymap::default();
501 keymap.add_bindings(bindings);
502
503 // Ensure `space` results in pending input on the workspace, but not editor
504 let (result, pending) = keymap.bindings_for_input(
505 &[Keystroke::parse("ctrl-x").unwrap()],
506 &[KeyContext::parse("editor").unwrap()],
507 );
508 assert!(result.is_empty());
509 assert!(!pending);
510 }
511
512 #[test]
513 fn test_fail_to_disable() {
514 // disabled at the wrong level
515 let bindings = [
516 KeyBinding::new("ctrl-x", ActionAlpha {}, Some("editor")),
517 KeyBinding::new_no_action("ctrl-x", Some("workspace")),
518 ];
519
520 let mut keymap = Keymap::default();
521 keymap.add_bindings(bindings);
522
523 // Ensure `space` results in pending input on the workspace, but not editor
524 let (result, pending) = keymap.bindings_for_input(
525 &[Keystroke::parse("ctrl-x").unwrap()],
526 &[
527 KeyContext::parse("workspace").unwrap(),
528 KeyContext::parse("editor").unwrap(),
529 ],
530 );
531 assert_eq!(result.len(), 1);
532 assert!(!pending);
533 }
534
535 #[test]
536 fn test_disable_deeper() {
537 let bindings = [
538 KeyBinding::new("ctrl-x", ActionAlpha {}, Some("workspace")),
539 KeyBinding::new_no_action("ctrl-x", Some("editor")),
540 ];
541
542 let mut keymap = Keymap::default();
543 keymap.add_bindings(bindings);
544
545 // Ensure `space` results in pending input on the workspace, but not editor
546 let (result, pending) = keymap.bindings_for_input(
547 &[Keystroke::parse("ctrl-x").unwrap()],
548 &[
549 KeyContext::parse("workspace").unwrap(),
550 KeyContext::parse("editor").unwrap(),
551 ],
552 );
553 assert_eq!(result.len(), 0);
554 assert!(!pending);
555 }
556
557 #[test]
558 fn test_pending_match_enabled() {
559 let bindings = [
560 KeyBinding::new("ctrl-x", ActionBeta, Some("vim_mode == normal")),
561 KeyBinding::new("ctrl-x 0", ActionAlpha, Some("Workspace")),
562 ];
563 let mut keymap = Keymap::default();
564 keymap.add_bindings(bindings);
565
566 let matched = keymap.bindings_for_input(
567 &[Keystroke::parse("ctrl-x")].map(Result::unwrap),
568 &[
569 KeyContext::parse("Workspace"),
570 KeyContext::parse("Pane"),
571 KeyContext::parse("Editor vim_mode=normal"),
572 ]
573 .map(Result::unwrap),
574 );
575 assert_eq!(matched.0.len(), 1);
576 assert!(
577 matched.0[0]
578 .action
579 .as_deref()
580 .is_some_and(|it| it.partial_eq(&ActionBeta))
581 );
582 assert!(matched.1);
583 }
584
585 #[test]
586 fn test_pending_match_enabled_extended() {
587 let bindings = [
588 KeyBinding::new("ctrl-x", ActionBeta, Some("vim_mode == normal")),
589 KeyBinding::new_no_action("ctrl-x 0", Some("Workspace")),
590 ];
591 let mut keymap = Keymap::default();
592 keymap.add_bindings(bindings);
593
594 let matched = keymap.bindings_for_input(
595 &[Keystroke::parse("ctrl-x")].map(Result::unwrap),
596 &[
597 KeyContext::parse("Workspace"),
598 KeyContext::parse("Pane"),
599 KeyContext::parse("Editor vim_mode=normal"),
600 ]
601 .map(Result::unwrap),
602 );
603 assert_eq!(matched.0.len(), 1);
604 assert!(
605 matched.0[0]
606 .action
607 .as_deref()
608 .is_some_and(|it| it.partial_eq(&ActionBeta))
609 );
610 assert!(!matched.1);
611 let bindings = [
612 KeyBinding::new("ctrl-x", ActionBeta, Some("Workspace")),
613 KeyBinding::new_no_action("ctrl-x 0", Some("vim_mode == normal")),
614 ];
615 let mut keymap = Keymap::default();
616 keymap.add_bindings(bindings);
617
618 let matched = keymap.bindings_for_input(
619 &[Keystroke::parse("ctrl-x")].map(Result::unwrap),
620 &[
621 KeyContext::parse("Workspace"),
622 KeyContext::parse("Pane"),
623 KeyContext::parse("Editor vim_mode=normal"),
624 ]
625 .map(Result::unwrap),
626 );
627 assert_eq!(matched.0.len(), 1);
628 assert!(
629 matched.0[0]
630 .action
631 .as_deref()
632 .is_some_and(|it| it.partial_eq(&ActionBeta))
633 );
634 assert!(!matched.1);
635 }
636
637 #[test]
638 fn test_overriding_prefix() {
639 let bindings = [
640 KeyBinding::new("ctrl-x 0", ActionAlpha, Some("Workspace")),
641 KeyBinding::new("ctrl-x", ActionBeta, Some("vim_mode == normal")),
642 ];
643 let mut keymap = Keymap::default();
644 keymap.add_bindings(bindings);
645
646 let matched = keymap.bindings_for_input(
647 &[Keystroke::parse("ctrl-x")].map(Result::unwrap),
648 &[
649 KeyContext::parse("Workspace"),
650 KeyContext::parse("Pane"),
651 KeyContext::parse("Editor vim_mode=normal"),
652 ]
653 .map(Result::unwrap),
654 );
655 assert_eq!(matched.0.len(), 1);
656 assert!(
657 matched.0[0]
658 .action
659 .as_deref()
660 .is_some_and(|it| it.partial_eq(&ActionBeta))
661 );
662 assert!(!matched.1);
663 }
664
665 #[test]
666 fn test_context_precedence_with_same_source() {
667 // Test case: User has both Workspace and Editor bindings for the same key
668 // Editor binding should take precedence over Workspace binding
669 let bindings = [
670 KeyBinding::new("cmd-r", ActionAlpha {}, Some("Workspace")),
671 KeyBinding::new("cmd-r", ActionBeta {}, Some("Editor")),
672 ];
673
674 let mut keymap = Keymap::default();
675 keymap.add_bindings(bindings);
676
677 // Test with context stack: [Workspace, Editor] (Editor is deeper)
678 let (result, _) = keymap.bindings_for_input(
679 &[Keystroke::parse("cmd-r").unwrap()],
680 &[
681 KeyContext::parse("Workspace").unwrap(),
682 KeyContext::parse("Editor").unwrap(),
683 ],
684 );
685
686 // Both bindings should be returned, but Editor binding should be first (highest precedence)
687 assert_eq!(result.len(), 2);
688 assert!(
689 result[0]
690 .action
691 .as_deref()
692 .is_some_and(|it| it.partial_eq(&ActionBeta {}))
693 ); // Editor binding first
694 assert!(
695 result[1]
696 .action
697 .as_deref()
698 .is_some_and(|it| it.partial_eq(&ActionAlpha {}))
699 ); // Workspace binding second
700 }
701
702 #[test]
703 fn test_bindings_for_action() {
704 let bindings = [
705 KeyBinding::new("ctrl-a", ActionAlpha {}, Some("pane")),
706 KeyBinding::new("ctrl-b", ActionBeta {}, Some("editor && mode == full")),
707 KeyBinding::new("ctrl-c", ActionGamma {}, Some("workspace")),
708 KeyBinding::new_no_action("ctrl-a", Some("pane && active")),
709 KeyBinding::new_no_action("ctrl-b", Some("editor")),
710 ];
711
712 let mut keymap = Keymap::default();
713 keymap.add_bindings(bindings);
714
715 assert_bindings(&keymap, &ActionAlpha {}, &["ctrl-a"]);
716 assert_bindings(&keymap, &ActionBeta {}, &[]);
717 assert_bindings(&keymap, &ActionGamma {}, &["ctrl-c"]);
718
719 #[track_caller]
720 fn assert_bindings(keymap: &Keymap, action: &dyn Action, expected: &[&str]) {
721 let actual = keymap
722 .bindings_for_action(action)
723 .map(|binding| binding.keystrokes[0].inner().unparse())
724 .collect::<Vec<_>>();
725 assert_eq!(actual, expected, "{:?}", action);
726 }
727 }
728
729 #[test]
730 fn test_source_precedence_sorting() {
731 // KeybindSource precedence: User (0) > Vim (1) > Base (2) > Default (3)
732 // Test that user keymaps take precedence over default keymaps at the same context depth
733 let mut keymap = Keymap::default();
734
735 // Add a default keymap binding first
736 let mut default_binding = KeyBinding::new("cmd-r", ActionAlpha {}, Some("Editor"));
737 default_binding.set_meta(KeyBindingMetaIndex(3)); // Default source
738 keymap.add_bindings([default_binding]);
739
740 // Add a user keymap binding
741 let mut user_binding = KeyBinding::new("cmd-r", ActionBeta {}, Some("Editor"));
742 user_binding.set_meta(KeyBindingMetaIndex(0)); // User source
743 keymap.add_bindings([user_binding]);
744
745 // Test with Editor context stack
746 let (result, _) = keymap.bindings_for_input(
747 &[Keystroke::parse("cmd-r").unwrap()],
748 &[KeyContext::parse("Editor").unwrap()],
749 );
750
751 // User binding should take precedence over default binding
752 assert_eq!(result.len(), 2);
753 assert!(
754 result[0]
755 .action
756 .as_deref()
757 .is_some_and(|it| it.partial_eq(&ActionBeta {}))
758 );
759 assert!(
760 result[1]
761 .action
762 .as_deref()
763 .is_some_and(|it| it.partial_eq(&ActionAlpha {}))
764 );
765 }
766}