Skip to main content

open_gpui/
keymap.rs

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