1mod binding;
2mod context;
3
4pub use binding::*;
5pub use context::*;
6
7use crate::{Action, AsKeystroke, Keystroke, is_no_action};
8use collections::{HashMap, HashSet};
9use smallvec::SmallVec;
10use std::any::TypeId;
11
12#[derive(Copy, Clone, Eq, PartialEq, Default)]
15pub struct KeymapVersion(usize);
16
17#[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#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
28pub struct BindingIndex(usize);
29
30impl Keymap {
31 pub fn new(bindings: Vec<KeyBinding>) -> Self {
33 let mut this = Self::default();
34 this.add_bindings(bindings);
35 this
36 }
37
38 pub fn version(&self) -> KeymapVersion {
40 self.version
41 }
42
43 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 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 pub fn bindings(&self) -> impl DoubleEndedIterator<Item = &KeyBinding> + ExactSizeIterator {
71 self.bindings.iter()
72 }
73
74 pub fn bindings_for_action<'a>(
77 &'a self,
78 action: &'a dyn Action,
79 ) -> impl 'a + DoubleEndedIterator<Item = &'a KeyBinding> {
80 let action_id = action.type_id();
81 let binding_indices = self
82 .binding_indices_by_action_id
83 .get(&action_id)
84 .map_or(&[] as _, SmallVec::as_slice)
85 .iter();
86
87 binding_indices.filter_map(|ix| {
88 let binding = &self.bindings[*ix];
89 if !binding.action().partial_eq(action) {
90 return None;
91 }
92
93 for null_ix in &self.no_action_binding_indices {
94 if null_ix > ix {
95 let null_binding = &self.bindings[*null_ix];
96 if null_binding.keystrokes == binding.keystrokes {
97 let null_binding_matches =
98 match (&null_binding.context_predicate, &binding.context_predicate) {
99 (None, _) => true,
100 (Some(_), None) => false,
101 (Some(null_predicate), Some(predicate)) => {
102 null_predicate.is_superset(predicate)
103 }
104 };
105 if null_binding_matches {
106 return None;
107 }
108 }
109 }
110 }
111
112 Some(binding)
113 })
114 }
115
116 pub fn all_bindings_for_input(&self, input: &[Keystroke]) -> Vec<KeyBinding> {
119 self.bindings()
120 .rev()
121 .filter_map(|binding| {
122 binding.match_keystrokes(input).filter(|pending| !pending)?;
123 Some(binding.clone())
124 })
125 .collect()
126 }
127
128 pub fn bindings_for_input(
143 &self,
144 input: &[impl AsKeystroke],
145 context_stack: &[KeyContext],
146 ) -> (SmallVec<[KeyBinding; 1]>, bool) {
147 let mut matched_bindings = SmallVec::<[(usize, BindingIndex, &KeyBinding); 1]>::new();
148 let mut pending_bindings = SmallVec::<[(BindingIndex, &KeyBinding); 1]>::new();
149
150 for (ix, binding) in self.bindings().enumerate().rev() {
151 let Some(depth) = self.binding_enabled(binding, context_stack) else {
152 continue;
153 };
154 let Some(pending) = binding.match_keystrokes(input) else {
155 continue;
156 };
157
158 if !pending {
159 matched_bindings.push((depth, BindingIndex(ix), binding));
160 } else {
161 pending_bindings.push((BindingIndex(ix), binding));
162 }
163 }
164
165 matched_bindings.sort_by(|(depth_a, ix_a, _), (depth_b, ix_b, _)| {
166 depth_b.cmp(depth_a).then(ix_b.cmp(ix_a))
167 });
168
169 let mut bindings: SmallVec<[_; 1]> = SmallVec::new();
170 let mut first_binding_index = None;
171
172 for (_, ix, binding) in matched_bindings {
173 if is_no_action(&*binding.action) {
174 if let Some(meta) = binding.meta {
177 if meta.0 == 0 {
178 break;
179 }
180 } else {
181 break;
183 }
184 continue;
186 }
187 bindings.push(binding.clone());
188 first_binding_index.get_or_insert(ix);
189 }
190
191 let mut pending = HashSet::default();
192 for (ix, binding) in pending_bindings.into_iter().rev() {
193 if let Some(binding_ix) = first_binding_index
194 && binding_ix > ix
195 {
196 continue;
197 }
198 if is_no_action(&*binding.action) {
199 pending.remove(&&binding.keystrokes);
200 continue;
201 }
202 pending.insert(&binding.keystrokes);
203 }
204
205 (bindings, !pending.is_empty())
206 }
207 fn binding_enabled(&self, binding: &KeyBinding, contexts: &[KeyContext]) -> Option<usize> {
210 if let Some(predicate) = &binding.context_predicate {
211 predicate.depth_of(contexts)
212 } else {
213 Some(contexts.len())
214 }
215 }
216}
217
218#[cfg(test)]
219mod tests {
220 use super::*;
221 use crate as gpui;
222 use gpui::NoAction;
223
224 actions!(
225 test_only,
226 [ActionAlpha, ActionBeta, ActionGamma, ActionDelta,]
227 );
228
229 #[test]
230 fn test_keymap() {
231 let bindings = [
232 KeyBinding::new("ctrl-a", ActionAlpha {}, None),
233 KeyBinding::new("ctrl-a", ActionBeta {}, Some("pane")),
234 KeyBinding::new("ctrl-a", ActionGamma {}, Some("editor && mode==full")),
235 ];
236
237 let mut keymap = Keymap::default();
238 keymap.add_bindings(bindings.clone());
239
240 assert_eq!(keymap.binding_enabled(&bindings[0], &[]), Some(0));
242 assert_eq!(
243 keymap.binding_enabled(&bindings[0], &[KeyContext::parse("terminal").unwrap()]),
244 Some(1)
245 );
246
247 assert_eq!(
249 keymap.binding_enabled(&bindings[1], &[KeyContext::parse("barf x=y").unwrap()]),
250 None
251 );
252 assert_eq!(
253 keymap.binding_enabled(&bindings[1], &[KeyContext::parse("pane x=y").unwrap()]),
254 Some(1)
255 );
256
257 assert_eq!(
258 keymap.binding_enabled(&bindings[2], &[KeyContext::parse("editor").unwrap()]),
259 None
260 );
261 assert_eq!(
262 keymap.binding_enabled(
263 &bindings[2],
264 &[KeyContext::parse("editor mode=full").unwrap()]
265 ),
266 Some(1)
267 );
268 }
269
270 #[test]
271 fn test_depth_precedence() {
272 let bindings = [
273 KeyBinding::new("ctrl-a", ActionBeta {}, Some("pane")),
274 KeyBinding::new("ctrl-a", ActionGamma {}, Some("editor")),
275 ];
276
277 let mut keymap = Keymap::default();
278 keymap.add_bindings(bindings);
279
280 let (result, pending) = keymap.bindings_for_input(
281 &[Keystroke::parse("ctrl-a").unwrap()],
282 &[
283 KeyContext::parse("pane").unwrap(),
284 KeyContext::parse("editor").unwrap(),
285 ],
286 );
287
288 assert!(!pending);
289 assert_eq!(result.len(), 2);
290 assert!(result[0].action.partial_eq(&ActionGamma {}));
291 assert!(result[1].action.partial_eq(&ActionBeta {}));
292 }
293
294 #[test]
295 fn test_keymap_disabled() {
296 let bindings = [
297 KeyBinding::new("ctrl-a", ActionAlpha {}, Some("editor")),
298 KeyBinding::new("ctrl-b", ActionAlpha {}, Some("editor")),
299 KeyBinding::new("ctrl-a", NoAction {}, Some("editor && mode==full")),
300 KeyBinding::new("ctrl-b", NoAction {}, None),
301 ];
302
303 let mut keymap = Keymap::default();
304 keymap.add_bindings(bindings);
305
306 assert!(
308 keymap
309 .bindings_for_input(
310 &[Keystroke::parse("ctrl-a").unwrap()],
311 &[KeyContext::parse("barf").unwrap()],
312 )
313 .0
314 .is_empty()
315 );
316 assert!(
317 !keymap
318 .bindings_for_input(
319 &[Keystroke::parse("ctrl-a").unwrap()],
320 &[KeyContext::parse("editor").unwrap()],
321 )
322 .0
323 .is_empty()
324 );
325
326 assert!(
328 keymap
329 .bindings_for_input(
330 &[Keystroke::parse("ctrl-a").unwrap()],
331 &[KeyContext::parse("editor mode=full").unwrap()],
332 )
333 .0
334 .is_empty()
335 );
336
337 assert!(
339 keymap
340 .bindings_for_input(
341 &[Keystroke::parse("ctrl-b").unwrap()],
342 &[KeyContext::parse("barf").unwrap()],
343 )
344 .0
345 .is_empty()
346 );
347 }
348
349 #[test]
350 fn test_multiple_keystroke_binding_disabled() {
352 let bindings = [
353 KeyBinding::new("space w w", ActionAlpha {}, Some("workspace")),
354 KeyBinding::new("space w w", NoAction {}, Some("editor")),
355 ];
356
357 let mut keymap = Keymap::default();
358 keymap.add_bindings(bindings);
359
360 let space = || Keystroke::parse("space").unwrap();
361 let w = || Keystroke::parse("w").unwrap();
362
363 let space_w = [space(), w()];
364 let space_w_w = [space(), w(), w()];
365
366 let workspace_context = || [KeyContext::parse("workspace").unwrap()];
367
368 let editor_workspace_context = || {
369 [
370 KeyContext::parse("workspace").unwrap(),
371 KeyContext::parse("editor").unwrap(),
372 ]
373 };
374
375 let space_workspace = keymap.bindings_for_input(&[space()], &workspace_context());
377 assert!(space_workspace.0.is_empty());
378 assert!(space_workspace.1);
379
380 let space_editor = keymap.bindings_for_input(&[space()], &editor_workspace_context());
381 assert!(space_editor.0.is_empty());
382 assert!(!space_editor.1);
383
384 let space_w_workspace = keymap.bindings_for_input(&space_w, &workspace_context());
386 assert!(space_w_workspace.0.is_empty());
387 assert!(space_w_workspace.1);
388
389 let space_w_editor = keymap.bindings_for_input(&space_w, &editor_workspace_context());
390 assert!(space_w_editor.0.is_empty());
391 assert!(!space_w_editor.1);
392
393 let space_w_w_workspace = keymap.bindings_for_input(&space_w_w, &workspace_context());
395 assert!(!space_w_w_workspace.0.is_empty());
396 assert!(!space_w_w_workspace.1);
397
398 let space_w_w_editor = keymap.bindings_for_input(&space_w_w, &editor_workspace_context());
399 assert!(space_w_w_editor.0.is_empty());
400 assert!(!space_w_w_editor.1);
401
402 let bindings = [
405 KeyBinding::new("space w w", ActionAlpha {}, Some("workspace")),
406 KeyBinding::new("space w w", NoAction {}, Some("editor")),
407 KeyBinding::new("space w x", ActionAlpha {}, Some("editor")),
408 ];
409 let mut keymap = Keymap::default();
410 keymap.add_bindings(bindings);
411
412 let space_editor = keymap.bindings_for_input(&[space()], &editor_workspace_context());
413 assert!(space_editor.0.is_empty());
414 assert!(space_editor.1);
415
416 let bindings = [
419 KeyBinding::new("space w w", ActionAlpha {}, Some("workspace")),
420 KeyBinding::new("space w x", ActionAlpha {}, Some("editor")),
421 KeyBinding::new("space w w", NoAction {}, Some("editor")),
422 ];
423 let mut keymap = Keymap::default();
424 keymap.add_bindings(bindings);
425
426 let space_editor = keymap.bindings_for_input(&[space()], &editor_workspace_context());
427 assert!(space_editor.0.is_empty());
428 assert!(space_editor.1);
429
430 let bindings = [
433 KeyBinding::new("space w w", ActionAlpha {}, Some("workspace")),
434 KeyBinding::new("space w x", ActionAlpha {}, Some("workspace")),
435 KeyBinding::new("space w w", NoAction {}, Some("editor")),
436 ];
437 let mut keymap = Keymap::default();
438 keymap.add_bindings(bindings);
439
440 let space_editor = keymap.bindings_for_input(&[space()], &editor_workspace_context());
441 assert!(space_editor.0.is_empty());
442 assert!(space_editor.1);
443 }
444
445 #[test]
446 fn test_override_multikey() {
447 let bindings = [
448 KeyBinding::new("ctrl-w left", ActionAlpha {}, Some("editor")),
449 KeyBinding::new("ctrl-w", NoAction {}, Some("editor")),
450 ];
451
452 let mut keymap = Keymap::default();
453 keymap.add_bindings(bindings);
454
455 let (result, pending) = keymap.bindings_for_input(
457 &[Keystroke::parse("ctrl-w").unwrap()],
458 &[KeyContext::parse("editor").unwrap()],
459 );
460 assert!(result.is_empty());
461 assert!(pending);
462
463 let bindings = [
464 KeyBinding::new("ctrl-w left", ActionAlpha {}, Some("editor")),
465 KeyBinding::new("ctrl-w", ActionBeta {}, Some("editor")),
466 ];
467
468 let mut keymap = Keymap::default();
469 keymap.add_bindings(bindings);
470
471 let (result, pending) = keymap.bindings_for_input(
473 &[Keystroke::parse("ctrl-w").unwrap()],
474 &[KeyContext::parse("editor").unwrap()],
475 );
476 assert_eq!(result.len(), 1);
477 assert!(!pending);
478 }
479
480 #[test]
481 fn test_simple_disable() {
482 let bindings = [
483 KeyBinding::new("ctrl-x", ActionAlpha {}, Some("editor")),
484 KeyBinding::new("ctrl-x", NoAction {}, Some("editor")),
485 ];
486
487 let mut keymap = Keymap::default();
488 keymap.add_bindings(bindings);
489
490 let (result, pending) = keymap.bindings_for_input(
492 &[Keystroke::parse("ctrl-x").unwrap()],
493 &[KeyContext::parse("editor").unwrap()],
494 );
495 assert!(result.is_empty());
496 assert!(!pending);
497 }
498
499 #[test]
500 fn test_fail_to_disable() {
501 let bindings = [
503 KeyBinding::new("ctrl-x", ActionAlpha {}, Some("editor")),
504 KeyBinding::new("ctrl-x", NoAction {}, Some("workspace")),
505 ];
506
507 let mut keymap = Keymap::default();
508 keymap.add_bindings(bindings);
509
510 let (result, pending) = keymap.bindings_for_input(
512 &[Keystroke::parse("ctrl-x").unwrap()],
513 &[
514 KeyContext::parse("workspace").unwrap(),
515 KeyContext::parse("editor").unwrap(),
516 ],
517 );
518 assert_eq!(result.len(), 1);
519 assert!(!pending);
520 }
521
522 #[test]
523 fn test_disable_deeper() {
524 let bindings = [
525 KeyBinding::new("ctrl-x", ActionAlpha {}, Some("workspace")),
526 KeyBinding::new("ctrl-x", NoAction {}, Some("editor")),
527 ];
528
529 let mut keymap = Keymap::default();
530 keymap.add_bindings(bindings);
531
532 let (result, pending) = keymap.bindings_for_input(
534 &[Keystroke::parse("ctrl-x").unwrap()],
535 &[
536 KeyContext::parse("workspace").unwrap(),
537 KeyContext::parse("editor").unwrap(),
538 ],
539 );
540 assert_eq!(result.len(), 0);
541 assert!(!pending);
542 }
543
544 #[test]
545 fn test_pending_match_enabled() {
546 let bindings = [
547 KeyBinding::new("ctrl-x", ActionBeta, Some("vim_mode == normal")),
548 KeyBinding::new("ctrl-x 0", ActionAlpha, Some("Workspace")),
549 ];
550 let mut keymap = Keymap::default();
551 keymap.add_bindings(bindings);
552
553 let matched = keymap.bindings_for_input(
554 &[Keystroke::parse("ctrl-x")].map(Result::unwrap),
555 &[
556 KeyContext::parse("Workspace"),
557 KeyContext::parse("Pane"),
558 KeyContext::parse("Editor vim_mode=normal"),
559 ]
560 .map(Result::unwrap),
561 );
562 assert_eq!(matched.0.len(), 1);
563 assert!(matched.0[0].action.partial_eq(&ActionBeta));
564 assert!(matched.1);
565 }
566
567 #[test]
568 fn test_pending_match_enabled_extended() {
569 let bindings = [
570 KeyBinding::new("ctrl-x", ActionBeta, Some("vim_mode == normal")),
571 KeyBinding::new("ctrl-x 0", NoAction, Some("Workspace")),
572 ];
573 let mut keymap = Keymap::default();
574 keymap.add_bindings(bindings);
575
576 let matched = keymap.bindings_for_input(
577 &[Keystroke::parse("ctrl-x")].map(Result::unwrap),
578 &[
579 KeyContext::parse("Workspace"),
580 KeyContext::parse("Pane"),
581 KeyContext::parse("Editor vim_mode=normal"),
582 ]
583 .map(Result::unwrap),
584 );
585 assert_eq!(matched.0.len(), 1);
586 assert!(matched.0[0].action.partial_eq(&ActionBeta));
587 assert!(!matched.1);
588 let bindings = [
589 KeyBinding::new("ctrl-x", ActionBeta, Some("Workspace")),
590 KeyBinding::new("ctrl-x 0", NoAction, Some("vim_mode == normal")),
591 ];
592 let mut keymap = Keymap::default();
593 keymap.add_bindings(bindings);
594
595 let matched = keymap.bindings_for_input(
596 &[Keystroke::parse("ctrl-x")].map(Result::unwrap),
597 &[
598 KeyContext::parse("Workspace"),
599 KeyContext::parse("Pane"),
600 KeyContext::parse("Editor vim_mode=normal"),
601 ]
602 .map(Result::unwrap),
603 );
604 assert_eq!(matched.0.len(), 1);
605 assert!(matched.0[0].action.partial_eq(&ActionBeta));
606 assert!(!matched.1);
607 }
608
609 #[test]
610 fn test_overriding_prefix() {
611 let bindings = [
612 KeyBinding::new("ctrl-x 0", ActionAlpha, Some("Workspace")),
613 KeyBinding::new("ctrl-x", ActionBeta, Some("vim_mode == normal")),
614 ];
615 let mut keymap = Keymap::default();
616 keymap.add_bindings(bindings);
617
618 let matched = keymap.bindings_for_input(
619 &[Keystroke::parse("ctrl-x")].map(Result::unwrap),
620 &[
621 KeyContext::parse("Workspace"),
622 KeyContext::parse("Pane"),
623 KeyContext::parse("Editor vim_mode=normal"),
624 ]
625 .map(Result::unwrap),
626 );
627 assert_eq!(matched.0.len(), 1);
628 assert!(matched.0[0].action.partial_eq(&ActionBeta));
629 assert!(!matched.1);
630 }
631
632 #[test]
633 fn test_context_precedence_with_same_source() {
634 let bindings = [
637 KeyBinding::new("cmd-r", ActionAlpha {}, Some("Workspace")),
638 KeyBinding::new("cmd-r", ActionBeta {}, Some("Editor")),
639 ];
640
641 let mut keymap = Keymap::default();
642 keymap.add_bindings(bindings);
643
644 let (result, _) = keymap.bindings_for_input(
646 &[Keystroke::parse("cmd-r").unwrap()],
647 &[
648 KeyContext::parse("Workspace").unwrap(),
649 KeyContext::parse("Editor").unwrap(),
650 ],
651 );
652
653 assert_eq!(result.len(), 2);
655 assert!(result[0].action.partial_eq(&ActionBeta {})); assert!(result[1].action.partial_eq(&ActionAlpha {})); }
658
659 #[test]
660 fn test_bindings_for_action() {
661 let bindings = [
662 KeyBinding::new("ctrl-a", ActionAlpha {}, Some("pane")),
663 KeyBinding::new("ctrl-b", ActionBeta {}, Some("editor && mode == full")),
664 KeyBinding::new("ctrl-c", ActionGamma {}, Some("workspace")),
665 KeyBinding::new("ctrl-a", NoAction {}, Some("pane && active")),
666 KeyBinding::new("ctrl-b", NoAction {}, Some("editor")),
667 ];
668
669 let mut keymap = Keymap::default();
670 keymap.add_bindings(bindings);
671
672 assert_bindings(&keymap, &ActionAlpha {}, &["ctrl-a"]);
673 assert_bindings(&keymap, &ActionBeta {}, &[]);
674 assert_bindings(&keymap, &ActionGamma {}, &["ctrl-c"]);
675
676 #[track_caller]
677 fn assert_bindings(keymap: &Keymap, action: &dyn Action, expected: &[&str]) {
678 let actual = keymap
679 .bindings_for_action(action)
680 .map(|binding| binding.keystrokes[0].inner().unparse())
681 .collect::<Vec<_>>();
682 assert_eq!(actual, expected, "{:?}", action);
683 }
684 }
685
686 #[test]
687 fn test_source_precedence_sorting() {
688 let mut keymap = Keymap::default();
691
692 let mut default_binding = KeyBinding::new("cmd-r", ActionAlpha {}, Some("Editor"));
694 default_binding.set_meta(KeyBindingMetaIndex(3)); keymap.add_bindings([default_binding]);
696
697 let mut user_binding = KeyBinding::new("cmd-r", ActionBeta {}, Some("Editor"));
699 user_binding.set_meta(KeyBindingMetaIndex(0)); keymap.add_bindings([user_binding]);
701
702 let (result, _) = keymap.bindings_for_input(
704 &[Keystroke::parse("cmd-r").unwrap()],
705 &[KeyContext::parse("Editor").unwrap()],
706 );
707
708 assert_eq!(result.len(), 2);
710 assert!(result[0].action.partial_eq(&ActionBeta {}));
711 assert!(result[1].action.partial_eq(&ActionAlpha {}));
712 }
713}