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