1use crate::{Action, KeyBinding, KeyBindingContextPredicate, KeyContext, Keystroke, NoAction};
2use collections::HashSet;
3use smallvec::SmallVec;
4use std::{
5 any::{Any, TypeId},
6 collections::HashMap,
7};
8
9#[derive(Copy, Clone, Eq, PartialEq, Default)]
10pub struct KeymapVersion(usize);
11
12#[derive(Default)]
13pub struct Keymap {
14 bindings: Vec<KeyBinding>,
15 binding_indices_by_action_id: HashMap<TypeId, SmallVec<[usize; 3]>>,
16 disabled_keystrokes:
17 HashMap<SmallVec<[Keystroke; 2]>, HashSet<Option<KeyBindingContextPredicate>>>,
18 version: KeymapVersion,
19}
20
21impl Keymap {
22 pub fn new(bindings: Vec<KeyBinding>) -> Self {
23 let mut this = Self::default();
24 this.add_bindings(bindings);
25 this
26 }
27
28 pub fn version(&self) -> KeymapVersion {
29 self.version
30 }
31
32 pub fn add_bindings<T: IntoIterator<Item = KeyBinding>>(&mut self, bindings: T) {
33 let no_action_id = (NoAction {}).type_id();
34
35 for binding in bindings {
36 let action_id = binding.action().as_any().type_id();
37 if action_id == no_action_id {
38 self.disabled_keystrokes
39 .entry(binding.keystrokes)
40 .or_default()
41 .insert(binding.context_predicate);
42 } else {
43 self.binding_indices_by_action_id
44 .entry(action_id)
45 .or_default()
46 .push(self.bindings.len());
47 self.bindings.push(binding);
48 }
49 }
50
51 self.version.0 += 1;
52 }
53
54 pub fn clear(&mut self) {
55 self.bindings.clear();
56 self.binding_indices_by_action_id.clear();
57 self.disabled_keystrokes.clear();
58 self.version.0 += 1;
59 }
60
61 /// Iterate over all bindings, in the order they were added.
62 pub fn bindings(&self) -> impl Iterator<Item = &KeyBinding> + DoubleEndedIterator {
63 self.bindings.iter()
64 }
65
66 /// Iterate over all bindings for the given action, in the order they were added.
67 pub fn bindings_for_action<'a>(
68 &'a self,
69 action: &'a dyn Action,
70 ) -> impl 'a + Iterator<Item = &'a KeyBinding> + DoubleEndedIterator {
71 let action_id = action.type_id();
72 self.binding_indices_by_action_id
73 .get(&action_id)
74 .map_or(&[] as _, SmallVec::as_slice)
75 .iter()
76 .map(|ix| &self.bindings[*ix])
77 .filter(move |binding| binding.action().partial_eq(action))
78 }
79
80 pub fn binding_enabled(&self, binding: &KeyBinding, context: &[KeyContext]) -> bool {
81 // If binding has a context predicate, it must match the current context,
82 if let Some(predicate) = &binding.context_predicate {
83 if !predicate.eval(context) {
84 return false;
85 }
86 }
87
88 if let Some(disabled_predicates) = self.disabled_keystrokes.get(&binding.keystrokes) {
89 for disabled_predicate in disabled_predicates {
90 match disabled_predicate {
91 // The binding must not be globally disabled.
92 None => return false,
93
94 // The binding must not be disabled in the current context.
95 Some(predicate) => {
96 if predicate.eval(context) {
97 return false;
98 }
99 }
100 }
101 }
102 }
103
104 true
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111 use crate as gpui;
112 use gpui::actions;
113
114 actions!(
115 keymap_test,
116 [ActionAlpha, ActionBeta, ActionGamma, ActionDelta,]
117 );
118
119 #[test]
120 fn test_keymap() {
121 let bindings = [
122 KeyBinding::new("ctrl-a", ActionAlpha {}, None),
123 KeyBinding::new("ctrl-a", ActionBeta {}, Some("pane")),
124 KeyBinding::new("ctrl-a", ActionGamma {}, Some("editor && mode==full")),
125 ];
126
127 let mut keymap = Keymap::default();
128 keymap.add_bindings(bindings.clone());
129
130 // global bindings are enabled in all contexts
131 assert!(keymap.binding_enabled(&bindings[0], &[]));
132 assert!(keymap.binding_enabled(&bindings[0], &[KeyContext::parse("terminal").unwrap()]));
133
134 // contextual bindings are enabled in contexts that match their predicate
135 assert!(!keymap.binding_enabled(&bindings[1], &[KeyContext::parse("barf x=y").unwrap()]));
136 assert!(keymap.binding_enabled(&bindings[1], &[KeyContext::parse("pane x=y").unwrap()]));
137
138 assert!(!keymap.binding_enabled(&bindings[2], &[KeyContext::parse("editor").unwrap()]));
139 assert!(keymap.binding_enabled(
140 &bindings[2],
141 &[KeyContext::parse("editor mode=full").unwrap()]
142 ));
143 }
144
145 #[test]
146 fn test_keymap_disabled() {
147 let bindings = [
148 KeyBinding::new("ctrl-a", ActionAlpha {}, Some("editor")),
149 KeyBinding::new("ctrl-b", ActionAlpha {}, Some("editor")),
150 KeyBinding::new("ctrl-a", NoAction {}, Some("editor && mode==full")),
151 KeyBinding::new("ctrl-b", NoAction {}, None),
152 ];
153
154 let mut keymap = Keymap::default();
155 keymap.add_bindings(bindings.clone());
156
157 // binding is only enabled in a specific context
158 assert!(!keymap.binding_enabled(&bindings[0], &[KeyContext::parse("barf").unwrap()]));
159 assert!(keymap.binding_enabled(&bindings[0], &[KeyContext::parse("editor").unwrap()]));
160
161 // binding is disabled in a more specific context
162 assert!(!keymap.binding_enabled(
163 &bindings[0],
164 &[KeyContext::parse("editor mode=full").unwrap()]
165 ));
166
167 // binding is globally disabled
168 assert!(!keymap.binding_enabled(&bindings[1], &[KeyContext::parse("barf").unwrap()]));
169 }
170}