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