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) = Keymap::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, 0 for globally
246    /// matching bindings or None if it doesn't match.
247    pub fn binding_enabled(binding: &KeyBinding, contexts: &[KeyContext]) -> Option<usize> {
248        if let Some(predicate) = &binding.context_predicate {
249            predicate.depth_of(contexts)
250        } else {
251            Some(0)
252        }
253    }
254
255    /// Find the bindings that can follow the current input sequence.
256    pub fn possible_next_bindings_for_input(
257        &self,
258        input: &[Keystroke],
259        context_stack: &[KeyContext],
260    ) -> Vec<KeyBinding> {
261        let mut bindings = self
262            .bindings()
263            .enumerate()
264            .rev()
265            .filter_map(|(ix, binding)| {
266                let depth = Keymap::binding_enabled(binding, context_stack)?;
267                let pending = binding.match_keystrokes(input);
268                match pending {
269                    None => None,
270                    Some(is_pending) => {
271                        if !is_pending
272                            || is_no_action(&*binding.action)
273                            || is_unbind(&*binding.action)
274                        {
275                            return None;
276                        }
277                        Some((depth, BindingIndex(ix), binding))
278                    }
279                }
280            })
281            .collect::<Vec<_>>();
282
283        bindings.sort_by(|(depth_a, ix_a, _), (depth_b, ix_b, _)| {
284            depth_b.cmp(depth_a).then(ix_b.cmp(ix_a))
285        });
286
287        bindings
288            .into_iter()
289            .map(|(_, _, binding)| binding.clone())
290            .collect::<Vec<_>>()
291    }
292}
293
294#[cfg(test)]
295mod tests {
296    use super::*;
297    use crate as gpui;
298    use gpui::{NoAction, Unbind};
299
300    actions!(
301        test_only,
302        [ActionAlpha, ActionBeta, ActionGamma, ActionDelta,]
303    );
304
305    #[test]
306    fn test_keymap() {
307        let bindings = [
308            KeyBinding::new("ctrl-a", ActionAlpha {}, None),
309            KeyBinding::new("ctrl-a", ActionBeta {}, Some("pane")),
310            KeyBinding::new("ctrl-a", ActionGamma {}, Some("editor && mode==full")),
311        ];
312
313        let mut keymap = Keymap::default();
314        keymap.add_bindings(bindings.clone());
315
316        // global bindings are enabled in all contexts
317        assert_eq!(Keymap::binding_enabled(&bindings[0], &[]), Some(0));
318        assert_eq!(
319            Keymap::binding_enabled(&bindings[0], &[KeyContext::parse("terminal").unwrap()]),
320            Some(0)
321        );
322
323        // contextual bindings are enabled in contexts that match their predicate
324        assert_eq!(
325            Keymap::binding_enabled(&bindings[1], &[KeyContext::parse("barf x=y").unwrap()]),
326            None
327        );
328        assert_eq!(
329            Keymap::binding_enabled(&bindings[1], &[KeyContext::parse("pane x=y").unwrap()]),
330            Some(1)
331        );
332
333        assert_eq!(
334            Keymap::binding_enabled(&bindings[2], &[KeyContext::parse("editor").unwrap()]),
335            None
336        );
337        assert_eq!(
338            Keymap::binding_enabled(
339                &bindings[2],
340                &[KeyContext::parse("editor mode=full").unwrap()]
341            ),
342            Some(1)
343        );
344    }
345
346    #[test]
347    fn test_depth_precedence() {
348        let bindings = [
349            KeyBinding::new("ctrl-a", ActionBeta {}, Some("pane")),
350            KeyBinding::new("ctrl-a", ActionGamma {}, Some("editor")),
351        ];
352
353        let mut keymap = Keymap::default();
354        keymap.add_bindings(bindings);
355
356        let (result, pending) = keymap.bindings_for_input(
357            &[Keystroke::parse("ctrl-a").unwrap()],
358            &[
359                KeyContext::parse("pane").unwrap(),
360                KeyContext::parse("editor").unwrap(),
361            ],
362        );
363
364        assert!(!pending);
365        assert_eq!(result.len(), 2);
366        assert!(result[0].action.partial_eq(&ActionGamma {}));
367        assert!(result[1].action.partial_eq(&ActionBeta {}));
368    }
369
370    #[test]
371    fn test_keymap_disabled() {
372        let bindings = [
373            KeyBinding::new("ctrl-a", ActionAlpha {}, Some("editor")),
374            KeyBinding::new("ctrl-b", ActionAlpha {}, Some("editor")),
375            KeyBinding::new("ctrl-a", NoAction {}, Some("editor && mode==full")),
376            KeyBinding::new("ctrl-b", NoAction {}, None),
377        ];
378
379        let mut keymap = Keymap::default();
380        keymap.add_bindings(bindings);
381
382        // binding is only enabled in a specific context
383        assert!(
384            keymap
385                .bindings_for_input(
386                    &[Keystroke::parse("ctrl-a").unwrap()],
387                    &[KeyContext::parse("barf").unwrap()],
388                )
389                .0
390                .is_empty()
391        );
392        assert!(
393            !keymap
394                .bindings_for_input(
395                    &[Keystroke::parse("ctrl-a").unwrap()],
396                    &[KeyContext::parse("editor").unwrap()],
397                )
398                .0
399                .is_empty()
400        );
401
402        // binding is disabled in a more specific context
403        assert!(
404            keymap
405                .bindings_for_input(
406                    &[Keystroke::parse("ctrl-a").unwrap()],
407                    &[KeyContext::parse("editor mode=full").unwrap()],
408                )
409                .0
410                .is_empty()
411        );
412
413        // binding is globally disabled
414        assert!(
415            keymap
416                .bindings_for_input(
417                    &[Keystroke::parse("ctrl-b").unwrap()],
418                    &[KeyContext::parse("barf").unwrap()],
419                )
420                .0
421                .is_empty()
422        );
423    }
424
425    #[test]
426    /// Tests for https://github.com/zed-industries/zed/issues/30259
427    fn test_multiple_keystroke_binding_disabled() {
428        let bindings = [
429            KeyBinding::new("space w w", ActionAlpha {}, Some("workspace")),
430            KeyBinding::new("space w w", NoAction {}, Some("editor")),
431        ];
432
433        let mut keymap = Keymap::default();
434        keymap.add_bindings(bindings);
435
436        let space = || Keystroke::parse("space").unwrap();
437        let w = || Keystroke::parse("w").unwrap();
438
439        let space_w = [space(), w()];
440        let space_w_w = [space(), w(), w()];
441
442        let workspace_context = || [KeyContext::parse("workspace").unwrap()];
443
444        let editor_workspace_context = || {
445            [
446                KeyContext::parse("workspace").unwrap(),
447                KeyContext::parse("editor").unwrap(),
448            ]
449        };
450
451        // Ensure `space` results in pending input on the workspace, but not editor
452        let space_workspace = keymap.bindings_for_input(&[space()], &workspace_context());
453        assert!(space_workspace.0.is_empty());
454        assert!(space_workspace.1);
455
456        let space_editor = keymap.bindings_for_input(&[space()], &editor_workspace_context());
457        assert!(space_editor.0.is_empty());
458        assert!(!space_editor.1);
459
460        // Ensure `space w` results in pending input on the workspace, but not editor
461        let space_w_workspace = keymap.bindings_for_input(&space_w, &workspace_context());
462        assert!(space_w_workspace.0.is_empty());
463        assert!(space_w_workspace.1);
464
465        let space_w_editor = keymap.bindings_for_input(&space_w, &editor_workspace_context());
466        assert!(space_w_editor.0.is_empty());
467        assert!(!space_w_editor.1);
468
469        // Ensure `space w w` results in the binding in the workspace, but not in the editor
470        let space_w_w_workspace = keymap.bindings_for_input(&space_w_w, &workspace_context());
471        assert!(!space_w_w_workspace.0.is_empty());
472        assert!(!space_w_w_workspace.1);
473
474        let space_w_w_editor = keymap.bindings_for_input(&space_w_w, &editor_workspace_context());
475        assert!(space_w_w_editor.0.is_empty());
476        assert!(!space_w_w_editor.1);
477
478        // Now test what happens if we have another binding defined AFTER the NoAction
479        // that should result in pending
480        let bindings = [
481            KeyBinding::new("space w w", ActionAlpha {}, Some("workspace")),
482            KeyBinding::new("space w w", NoAction {}, Some("editor")),
483            KeyBinding::new("space w x", ActionAlpha {}, Some("editor")),
484        ];
485        let mut keymap = Keymap::default();
486        keymap.add_bindings(bindings);
487
488        let space_editor = keymap.bindings_for_input(&[space()], &editor_workspace_context());
489        assert!(space_editor.0.is_empty());
490        assert!(space_editor.1);
491
492        // Now test what happens if we have another binding defined BEFORE the NoAction
493        // that should result in pending
494        let bindings = [
495            KeyBinding::new("space w w", ActionAlpha {}, Some("workspace")),
496            KeyBinding::new("space w x", ActionAlpha {}, Some("editor")),
497            KeyBinding::new("space w w", NoAction {}, Some("editor")),
498        ];
499        let mut keymap = Keymap::default();
500        keymap.add_bindings(bindings);
501
502        let space_editor = keymap.bindings_for_input(&[space()], &editor_workspace_context());
503        assert!(space_editor.0.is_empty());
504        assert!(space_editor.1);
505
506        // Now test what happens if we have another binding defined at a higher context
507        // that should result in pending
508        let bindings = [
509            KeyBinding::new("space w w", ActionAlpha {}, Some("workspace")),
510            KeyBinding::new("space w x", ActionAlpha {}, Some("workspace")),
511            KeyBinding::new("space w w", NoAction {}, Some("editor")),
512        ];
513        let mut keymap = Keymap::default();
514        keymap.add_bindings(bindings);
515
516        let space_editor = keymap.bindings_for_input(&[space()], &editor_workspace_context());
517        assert!(space_editor.0.is_empty());
518        assert!(space_editor.1);
519    }
520
521    #[test]
522    fn test_override_multikey() {
523        let bindings = [
524            KeyBinding::new("ctrl-w left", ActionAlpha {}, Some("editor")),
525            KeyBinding::new("ctrl-w", NoAction {}, Some("editor")),
526        ];
527
528        let mut keymap = Keymap::default();
529        keymap.add_bindings(bindings);
530
531        // Ensure `space` results in pending input on the workspace, but not editor
532        let (result, pending) = keymap.bindings_for_input(
533            &[Keystroke::parse("ctrl-w").unwrap()],
534            &[KeyContext::parse("editor").unwrap()],
535        );
536        assert!(result.is_empty());
537        assert!(pending);
538
539        let bindings = [
540            KeyBinding::new("ctrl-w left", ActionAlpha {}, Some("editor")),
541            KeyBinding::new("ctrl-w", ActionBeta {}, Some("editor")),
542        ];
543
544        let mut keymap = Keymap::default();
545        keymap.add_bindings(bindings);
546
547        // Ensure `space` results in pending input on the workspace, but not editor
548        let (result, pending) = keymap.bindings_for_input(
549            &[Keystroke::parse("ctrl-w").unwrap()],
550            &[KeyContext::parse("editor").unwrap()],
551        );
552        assert_eq!(result.len(), 1);
553        assert!(!pending);
554    }
555
556    #[test]
557    fn test_simple_disable() {
558        let bindings = [
559            KeyBinding::new("ctrl-x", ActionAlpha {}, Some("editor")),
560            KeyBinding::new("ctrl-x", NoAction {}, Some("editor")),
561        ];
562
563        let mut keymap = Keymap::default();
564        keymap.add_bindings(bindings);
565
566        // Ensure `space` results in pending input on the workspace, but not editor
567        let (result, pending) = keymap.bindings_for_input(
568            &[Keystroke::parse("ctrl-x").unwrap()],
569            &[KeyContext::parse("editor").unwrap()],
570        );
571        assert!(result.is_empty());
572        assert!(!pending);
573    }
574
575    #[test]
576    fn test_fail_to_disable() {
577        // disabled at the wrong level
578        let bindings = [
579            KeyBinding::new("ctrl-x", ActionAlpha {}, Some("editor")),
580            KeyBinding::new("ctrl-x", NoAction {}, Some("workspace")),
581        ];
582
583        let mut keymap = Keymap::default();
584        keymap.add_bindings(bindings);
585
586        // Ensure `space` results in pending input on the workspace, but not editor
587        let (result, pending) = keymap.bindings_for_input(
588            &[Keystroke::parse("ctrl-x").unwrap()],
589            &[
590                KeyContext::parse("workspace").unwrap(),
591                KeyContext::parse("editor").unwrap(),
592            ],
593        );
594        assert_eq!(result.len(), 1);
595        assert!(!pending);
596    }
597
598    #[test]
599    fn test_disable_deeper() {
600        let bindings = [
601            KeyBinding::new("ctrl-x", ActionAlpha {}, Some("workspace")),
602            KeyBinding::new("ctrl-x", NoAction {}, Some("editor")),
603        ];
604
605        let mut keymap = Keymap::default();
606        keymap.add_bindings(bindings);
607
608        // Ensure `space` results in pending input on the workspace, but not editor
609        let (result, pending) = keymap.bindings_for_input(
610            &[Keystroke::parse("ctrl-x").unwrap()],
611            &[
612                KeyContext::parse("workspace").unwrap(),
613                KeyContext::parse("editor").unwrap(),
614            ],
615        );
616        assert_eq!(result.len(), 0);
617        assert!(!pending);
618    }
619
620    #[test]
621    fn test_pending_match_enabled() {
622        let bindings = [
623            KeyBinding::new("ctrl-x", ActionBeta, Some("vim_mode == normal")),
624            KeyBinding::new("ctrl-x 0", ActionAlpha, Some("Workspace")),
625        ];
626        let mut keymap = Keymap::default();
627        keymap.add_bindings(bindings);
628
629        let matched = keymap.bindings_for_input(
630            &[Keystroke::parse("ctrl-x")].map(Result::unwrap),
631            &[
632                KeyContext::parse("Workspace"),
633                KeyContext::parse("Pane"),
634                KeyContext::parse("Editor vim_mode=normal"),
635            ]
636            .map(Result::unwrap),
637        );
638        assert_eq!(matched.0.len(), 1);
639        assert!(matched.0[0].action.partial_eq(&ActionBeta));
640        assert!(matched.1);
641    }
642
643    #[test]
644    fn test_pending_match_enabled_extended() {
645        let bindings = [
646            KeyBinding::new("ctrl-x", ActionBeta, Some("vim_mode == normal")),
647            KeyBinding::new("ctrl-x 0", NoAction, Some("Workspace")),
648        ];
649        let mut keymap = Keymap::default();
650        keymap.add_bindings(bindings);
651
652        let matched = keymap.bindings_for_input(
653            &[Keystroke::parse("ctrl-x")].map(Result::unwrap),
654            &[
655                KeyContext::parse("Workspace"),
656                KeyContext::parse("Pane"),
657                KeyContext::parse("Editor vim_mode=normal"),
658            ]
659            .map(Result::unwrap),
660        );
661        assert_eq!(matched.0.len(), 1);
662        assert!(matched.0[0].action.partial_eq(&ActionBeta));
663        assert!(!matched.1);
664        let bindings = [
665            KeyBinding::new("ctrl-x", ActionBeta, Some("Workspace")),
666            KeyBinding::new("ctrl-x 0", NoAction, Some("vim_mode == normal")),
667        ];
668        let mut keymap = Keymap::default();
669        keymap.add_bindings(bindings);
670
671        let matched = keymap.bindings_for_input(
672            &[Keystroke::parse("ctrl-x")].map(Result::unwrap),
673            &[
674                KeyContext::parse("Workspace"),
675                KeyContext::parse("Pane"),
676                KeyContext::parse("Editor vim_mode=normal"),
677            ]
678            .map(Result::unwrap),
679        );
680        assert_eq!(matched.0.len(), 1);
681        assert!(matched.0[0].action.partial_eq(&ActionBeta));
682        assert!(!matched.1);
683    }
684
685    #[test]
686    fn test_overriding_prefix() {
687        let bindings = [
688            KeyBinding::new("ctrl-x 0", ActionAlpha, Some("Workspace")),
689            KeyBinding::new("ctrl-x", ActionBeta, Some("vim_mode == normal")),
690        ];
691        let mut keymap = Keymap::default();
692        keymap.add_bindings(bindings);
693
694        let matched = keymap.bindings_for_input(
695            &[Keystroke::parse("ctrl-x")].map(Result::unwrap),
696            &[
697                KeyContext::parse("Workspace"),
698                KeyContext::parse("Pane"),
699                KeyContext::parse("Editor vim_mode=normal"),
700            ]
701            .map(Result::unwrap),
702        );
703        assert_eq!(matched.0.len(), 1);
704        assert!(matched.0[0].action.partial_eq(&ActionBeta));
705        assert!(!matched.1);
706    }
707
708    #[test]
709    fn test_context_precedence_with_same_source() {
710        // Test case: User has both Workspace and Editor bindings for the same key
711        // Editor binding should take precedence over Workspace binding
712        let bindings = [
713            KeyBinding::new("cmd-r", ActionAlpha {}, Some("Workspace")),
714            KeyBinding::new("cmd-r", ActionBeta {}, Some("Editor")),
715        ];
716
717        let mut keymap = Keymap::default();
718        keymap.add_bindings(bindings);
719
720        // Test with context stack: [Workspace, Editor] (Editor is deeper)
721        let (result, _) = keymap.bindings_for_input(
722            &[Keystroke::parse("cmd-r").unwrap()],
723            &[
724                KeyContext::parse("Workspace").unwrap(),
725                KeyContext::parse("Editor").unwrap(),
726            ],
727        );
728
729        // Both bindings should be returned, but Editor binding should be first (highest precedence)
730        assert_eq!(result.len(), 2);
731        assert!(result[0].action.partial_eq(&ActionBeta {})); // Editor binding first
732        assert!(result[1].action.partial_eq(&ActionAlpha {})); // Workspace binding second
733    }
734
735    #[test]
736    fn test_bindings_for_action() {
737        let bindings = [
738            KeyBinding::new("ctrl-a", ActionAlpha {}, Some("pane")),
739            KeyBinding::new("ctrl-b", ActionBeta {}, Some("editor && mode == full")),
740            KeyBinding::new("ctrl-c", ActionGamma {}, Some("workspace")),
741            KeyBinding::new("ctrl-a", NoAction {}, Some("pane && active")),
742            KeyBinding::new("ctrl-b", NoAction {}, Some("editor")),
743        ];
744
745        let mut keymap = Keymap::default();
746        keymap.add_bindings(bindings);
747
748        assert_bindings(&keymap, &ActionAlpha {}, &["ctrl-a"]);
749        assert_bindings(&keymap, &ActionBeta {}, &[]);
750        assert_bindings(&keymap, &ActionGamma {}, &["ctrl-c"]);
751
752        #[track_caller]
753        fn assert_bindings(keymap: &Keymap, action: &dyn Action, expected: &[&str]) {
754            let actual = keymap
755                .bindings_for_action(action)
756                .map(|binding| binding.keystrokes[0].inner().unparse())
757                .collect::<Vec<_>>();
758            assert_eq!(actual, expected, "{:?}", action);
759        }
760    }
761
762    #[test]
763    fn test_targeted_unbind_ignores_target_context() {
764        let bindings = [
765            KeyBinding::new("tab", ActionAlpha {}, Some("Editor")),
766            KeyBinding::new("tab", ActionBeta {}, Some("Editor && showing_completions")),
767            KeyBinding::new(
768                "tab",
769                Unbind("test_only::ActionAlpha".into()),
770                Some("Editor && edit_prediction"),
771            ),
772        ];
773
774        let mut keymap = Keymap::default();
775        keymap.add_bindings(bindings);
776
777        let (result, pending) = keymap.bindings_for_input(
778            &[Keystroke::parse("tab").unwrap()],
779            &[KeyContext::parse("Editor showing_completions edit_prediction").unwrap()],
780        );
781
782        assert!(!pending);
783        assert_eq!(result.len(), 1);
784        assert!(result[0].action.partial_eq(&ActionBeta {}));
785    }
786
787    #[test]
788    fn test_bindings_for_action_keeps_binding_for_narrower_targeted_unbind() {
789        let bindings = [
790            KeyBinding::new("tab", ActionAlpha {}, Some("Editor")),
791            KeyBinding::new(
792                "tab",
793                Unbind("test_only::ActionAlpha".into()),
794                Some("Editor && edit_prediction"),
795            ),
796            KeyBinding::new("tab", ActionBeta {}, Some("Editor && showing_completions")),
797        ];
798
799        let mut keymap = Keymap::default();
800        keymap.add_bindings(bindings);
801
802        assert_bindings(&keymap, &ActionAlpha {}, &["tab"]);
803        assert_bindings(&keymap, &ActionBeta {}, &["tab"]);
804
805        #[track_caller]
806        fn assert_bindings(keymap: &Keymap, action: &dyn Action, expected: &[&str]) {
807            let actual = keymap
808                .bindings_for_action(action)
809                .map(|binding| binding.keystrokes[0].inner().unparse())
810                .collect::<Vec<_>>();
811            assert_eq!(actual, expected, "{:?}", action);
812        }
813    }
814
815    #[test]
816    fn test_bindings_for_action_removes_binding_for_broader_targeted_unbind() {
817        let bindings = [
818            KeyBinding::new("tab", ActionAlpha {}, Some("Editor && edit_prediction")),
819            KeyBinding::new(
820                "tab",
821                Unbind("test_only::ActionAlpha".into()),
822                Some("Editor"),
823            ),
824        ];
825
826        let mut keymap = Keymap::default();
827        keymap.add_bindings(bindings);
828
829        assert!(keymap.bindings_for_action(&ActionAlpha {}).next().is_none());
830    }
831
832    #[test]
833    fn test_source_precedence_sorting() {
834        // KeybindSource precedence: User (0) > Vim (1) > Base (2) > Default (3)
835        // Test that user keymaps take precedence over default keymaps at the same context depth
836        let mut keymap = Keymap::default();
837
838        // Add a default keymap binding first
839        let mut default_binding = KeyBinding::new("cmd-r", ActionAlpha {}, Some("Editor"));
840        default_binding.set_meta(KeyBindingMetaIndex(3)); // Default source
841        keymap.add_bindings([default_binding]);
842
843        // Add a user keymap binding
844        let mut user_binding = KeyBinding::new("cmd-r", ActionBeta {}, Some("Editor"));
845        user_binding.set_meta(KeyBindingMetaIndex(0)); // User source
846        keymap.add_bindings([user_binding]);
847
848        // Test with Editor context stack
849        let (result, _) = keymap.bindings_for_input(
850            &[Keystroke::parse("cmd-r").unwrap()],
851            &[KeyContext::parse("Editor").unwrap()],
852        );
853
854        // User binding should take precedence over default binding
855        assert_eq!(result.len(), 2);
856        assert!(result[0].action.partial_eq(&ActionBeta {}));
857        assert!(result[1].action.partial_eq(&ActionAlpha {}));
858    }
859
860    #[test]
861    fn test_binding_enabled_order() {
862        let specific = KeyBinding::new("cmd-r", ActionBeta {}, Some("Editor"));
863        let global = KeyBinding::new("cmd-r", ActionAlpha {}, None);
864
865        let parent_context = KeyContext::try_from("Editor").unwrap();
866        let child_context = KeyContext::try_from("Editand").unwrap();
867
868        let nested_context = vec![parent_context, child_context];
869        let exactly_matching_context = &nested_context[..1];
870
871        assert!(
872            Keymap::binding_enabled(&specific, exactly_matching_context)
873                > Keymap::binding_enabled(&global, exactly_matching_context)
874        );
875        assert!(
876            Keymap::binding_enabled(&specific, &nested_context)
877                > Keymap::binding_enabled(&global, &nested_context)
878        );
879    }
880}