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