1mod binding;
2mod context;
3
4pub use binding::*;
5pub use context::*;
6
7use crate::{is_no_action, Action, Keystroke};
8use collections::HashMap;
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
26impl Keymap {
27 /// Create a new keymap with the given bindings.
28 pub fn new(bindings: Vec<KeyBinding>) -> Self {
29 let mut this = Self::default();
30 this.add_bindings(bindings);
31 this
32 }
33
34 /// Get the current version of the keymap.
35 pub fn version(&self) -> KeymapVersion {
36 self.version
37 }
38
39 /// Add more bindings to the keymap.
40 pub fn add_bindings<T: IntoIterator<Item = KeyBinding>>(&mut self, bindings: T) {
41 for binding in bindings {
42 let action_id = binding.action().as_any().type_id();
43 if is_no_action(&*binding.action) {
44 self.no_action_binding_indices.push(self.bindings.len());
45 } else {
46 self.binding_indices_by_action_id
47 .entry(action_id)
48 .or_default()
49 .push(self.bindings.len());
50 }
51 self.bindings.push(binding);
52 }
53
54 self.version.0 += 1;
55 }
56
57 /// Reset this keymap to its initial state.
58 pub fn clear(&mut self) {
59 self.bindings.clear();
60 self.binding_indices_by_action_id.clear();
61 self.no_action_binding_indices.clear();
62 self.version.0 += 1;
63 }
64
65 /// Iterate over all bindings, in the order they were added.
66 pub fn bindings(&self) -> impl DoubleEndedIterator<Item = &KeyBinding> {
67 self.bindings.iter()
68 }
69
70 /// Iterate over all bindings for the given action, in the order they were added.
71 pub fn bindings_for_action<'a>(
72 &'a self,
73 action: &'a dyn Action,
74 ) -> impl 'a + DoubleEndedIterator<Item = &'a KeyBinding> {
75 let action_id = action.type_id();
76 let binding_indices = self
77 .binding_indices_by_action_id
78 .get(&action_id)
79 .map_or(&[] as _, SmallVec::as_slice)
80 .iter();
81
82 binding_indices.filter_map(|ix| {
83 let binding = &self.bindings[*ix];
84 if !binding.action().partial_eq(action) {
85 return None;
86 }
87
88 for null_ix in &self.no_action_binding_indices {
89 if null_ix > ix {
90 let null_binding = &self.bindings[*null_ix];
91 if null_binding.keystrokes == binding.keystrokes {
92 let null_binding_matches =
93 match (&null_binding.context_predicate, &binding.context_predicate) {
94 (None, _) => true,
95 (Some(_), None) => false,
96 (Some(null_predicate), Some(predicate)) => {
97 null_predicate.is_superset(predicate)
98 }
99 };
100 if null_binding_matches {
101 return None;
102 }
103 }
104 }
105 }
106
107 Some(binding)
108 })
109 }
110
111 /// all bindings for input returns all bindings that might match the input
112 /// (without checking context)
113 pub fn all_bindings_for_input(&self, input: &[Keystroke]) -> Vec<KeyBinding> {
114 self.bindings()
115 .rev()
116 .filter_map(|binding| {
117 binding.match_keystrokes(input).filter(|pending| !pending)?;
118 Some(binding.clone())
119 })
120 .collect()
121 }
122
123 /// bindings_for_input returns a list of bindings that match the given input,
124 /// and a boolean indicating whether or not more bindings might match if
125 /// the input was longer.
126 ///
127 /// Precedence is defined by the depth in the tree (matches on the Editor take
128 /// precedence over matches on the Pane, then the Workspace, etc.). Bindings with
129 /// no context are treated as the same as the deepest context.
130 ///
131 /// In the case of multiple bindings at the same depth, the ones defined later in the
132 /// keymap take precedence (so user bindings take precedence over built-in bindings).
133 ///
134 /// If a user has disabled a binding with `"x": null` it will not be returned. Disabled
135 /// bindings are evaluated with the same precedence rules so you can disable a rule in
136 /// a given context only.
137 ///
138 /// In the case of multi-key bindings, the
139 pub fn bindings_for_input(
140 &self,
141 input: &[Keystroke],
142 context_stack: &[KeyContext],
143 ) -> (SmallVec<[KeyBinding; 1]>, bool) {
144 let possibilities = self.bindings().rev().filter_map(|binding| {
145 binding
146 .match_keystrokes(input)
147 .map(|pending| (binding, pending))
148 });
149
150 let mut bindings: SmallVec<[(KeyBinding, usize); 1]> = SmallVec::new();
151 let mut is_pending = None;
152
153 'outer: for (binding, pending) in possibilities {
154 for depth in (0..=context_stack.len()).rev() {
155 if self.binding_enabled(binding, &context_stack[0..depth]) {
156 if is_pending.is_none() {
157 is_pending = Some(pending);
158 }
159 if !pending {
160 bindings.push((binding.clone(), depth));
161 continue 'outer;
162 }
163 }
164 }
165 }
166 bindings.sort_by(|a, b| a.1.cmp(&b.1).reverse());
167 let bindings = bindings
168 .into_iter()
169 .map_while(|(binding, _)| {
170 if is_no_action(&*binding.action) {
171 None
172 } else {
173 Some(binding)
174 }
175 })
176 .collect();
177
178 (bindings, is_pending.unwrap_or_default())
179 }
180
181 /// Check if the given binding is enabled, given a certain key context.
182 fn binding_enabled(&self, binding: &KeyBinding, context: &[KeyContext]) -> bool {
183 // If binding has a context predicate, it must match the current context,
184 if let Some(predicate) = &binding.context_predicate {
185 if !predicate.eval(context) {
186 return false;
187 }
188 }
189
190 true
191 }
192}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197 use crate as gpui;
198 use gpui::{actions, NoAction};
199
200 actions!(
201 keymap_test,
202 [ActionAlpha, ActionBeta, ActionGamma, ActionDelta,]
203 );
204
205 #[test]
206 fn test_keymap() {
207 let bindings = [
208 KeyBinding::new("ctrl-a", ActionAlpha {}, None),
209 KeyBinding::new("ctrl-a", ActionBeta {}, Some("pane")),
210 KeyBinding::new("ctrl-a", ActionGamma {}, Some("editor && mode==full")),
211 ];
212
213 let mut keymap = Keymap::default();
214 keymap.add_bindings(bindings.clone());
215
216 // global bindings are enabled in all contexts
217 assert!(keymap.binding_enabled(&bindings[0], &[]));
218 assert!(keymap.binding_enabled(&bindings[0], &[KeyContext::parse("terminal").unwrap()]));
219
220 // contextual bindings are enabled in contexts that match their predicate
221 assert!(!keymap.binding_enabled(&bindings[1], &[KeyContext::parse("barf x=y").unwrap()]));
222 assert!(keymap.binding_enabled(&bindings[1], &[KeyContext::parse("pane x=y").unwrap()]));
223
224 assert!(!keymap.binding_enabled(&bindings[2], &[KeyContext::parse("editor").unwrap()]));
225 assert!(keymap.binding_enabled(
226 &bindings[2],
227 &[KeyContext::parse("editor mode=full").unwrap()]
228 ));
229 }
230
231 #[test]
232 fn test_keymap_disabled() {
233 let bindings = [
234 KeyBinding::new("ctrl-a", ActionAlpha {}, Some("editor")),
235 KeyBinding::new("ctrl-b", ActionAlpha {}, Some("editor")),
236 KeyBinding::new("ctrl-a", NoAction {}, Some("editor && mode==full")),
237 KeyBinding::new("ctrl-b", NoAction {}, None),
238 ];
239
240 let mut keymap = Keymap::default();
241 keymap.add_bindings(bindings.clone());
242
243 // binding is only enabled in a specific context
244 assert!(keymap
245 .bindings_for_input(
246 &[Keystroke::parse("ctrl-a").unwrap()],
247 &[KeyContext::parse("barf").unwrap()],
248 )
249 .0
250 .is_empty());
251 assert!(!keymap
252 .bindings_for_input(
253 &[Keystroke::parse("ctrl-a").unwrap()],
254 &[KeyContext::parse("editor").unwrap()],
255 )
256 .0
257 .is_empty());
258
259 // binding is disabled in a more specific context
260 assert!(keymap
261 .bindings_for_input(
262 &[Keystroke::parse("ctrl-a").unwrap()],
263 &[KeyContext::parse("editor mode=full").unwrap()],
264 )
265 .0
266 .is_empty());
267
268 // binding is globally disabled
269 assert!(keymap
270 .bindings_for_input(
271 &[Keystroke::parse("ctrl-b").unwrap()],
272 &[KeyContext::parse("barf").unwrap()],
273 )
274 .0
275 .is_empty());
276 }
277
278 #[test]
279 fn test_bindings_for_action() {
280 let bindings = [
281 KeyBinding::new("ctrl-a", ActionAlpha {}, Some("pane")),
282 KeyBinding::new("ctrl-b", ActionBeta {}, Some("editor && mode == full")),
283 KeyBinding::new("ctrl-c", ActionGamma {}, Some("workspace")),
284 KeyBinding::new("ctrl-a", NoAction {}, Some("pane && active")),
285 KeyBinding::new("ctrl-b", NoAction {}, Some("editor")),
286 ];
287
288 let mut keymap = Keymap::default();
289 keymap.add_bindings(bindings.clone());
290
291 assert_bindings(&keymap, &ActionAlpha {}, &["ctrl-a"]);
292 assert_bindings(&keymap, &ActionBeta {}, &[]);
293 assert_bindings(&keymap, &ActionGamma {}, &["ctrl-c"]);
294
295 #[track_caller]
296 fn assert_bindings(keymap: &Keymap, action: &dyn Action, expected: &[&str]) {
297 let actual = keymap
298 .bindings_for_action(action)
299 .map(|binding| binding.keystrokes[0].unparse())
300 .collect::<Vec<_>>();
301 assert_eq!(actual, expected, "{:?}", action);
302 }
303 }
304}