1use collections::HashSet;
2use smallvec::SmallVec;
3use std::{any::TypeId, collections::HashMap};
4
5use crate::{Action, NoAction};
6
7use super::{Binding, KeymapContextPredicate, Keystroke};
8
9#[derive(Default)]
10pub struct Keymap {
11 bindings: Vec<Binding>,
12 binding_indices_by_action_id: HashMap<TypeId, SmallVec<[usize; 3]>>,
13 disabled_keystrokes: HashMap<SmallVec<[Keystroke; 2]>, HashSet<Option<KeymapContextPredicate>>>,
14}
15
16impl Keymap {
17 #[cfg(test)]
18 pub(super) fn new(bindings: Vec<Binding>) -> Self {
19 let mut this = Self::default();
20 this.add_bindings(bindings);
21 this
22 }
23
24 pub(crate) fn bindings_for_action(
25 &self,
26 action_id: TypeId,
27 ) -> impl Iterator<Item = &'_ Binding> {
28 self.binding_indices_by_action_id
29 .get(&action_id)
30 .map(SmallVec::as_slice)
31 .unwrap_or(&[])
32 .iter()
33 .map(|ix| &self.bindings[*ix])
34 .filter(|binding| !self.binding_disabled(binding))
35 }
36
37 pub(crate) fn add_bindings<T: IntoIterator<Item = Binding>>(&mut self, bindings: T) {
38 let no_action_id = (NoAction {}).id();
39 let mut new_bindings = Vec::new();
40 let mut has_new_disabled_keystrokes = false;
41 for binding in bindings {
42 if binding.action().id() == no_action_id {
43 has_new_disabled_keystrokes |= self
44 .disabled_keystrokes
45 .entry(binding.keystrokes)
46 .or_default()
47 .insert(binding.context_predicate);
48 } else {
49 new_bindings.push(binding);
50 }
51 }
52
53 if has_new_disabled_keystrokes {
54 self.binding_indices_by_action_id.retain(|_, indices| {
55 indices.retain(|ix| {
56 let binding = &self.bindings[*ix];
57 match self.disabled_keystrokes.get(&binding.keystrokes) {
58 Some(disabled_predicates) => {
59 !disabled_predicates.contains(&binding.context_predicate)
60 }
61 None => true,
62 }
63 });
64 !indices.is_empty()
65 });
66 }
67
68 for new_binding in new_bindings {
69 if !self.binding_disabled(&new_binding) {
70 self.binding_indices_by_action_id
71 .entry(new_binding.action().id())
72 .or_default()
73 .push(self.bindings.len());
74 self.bindings.push(new_binding);
75 }
76 }
77 }
78
79 pub(crate) fn clear(&mut self) {
80 self.bindings.clear();
81 self.binding_indices_by_action_id.clear();
82 self.disabled_keystrokes.clear();
83 }
84
85 pub fn bindings(&self) -> Vec<&Binding> {
86 self.bindings
87 .iter()
88 .filter(|binding| !self.binding_disabled(binding))
89 .collect()
90 }
91
92 fn binding_disabled(&self, binding: &Binding) -> bool {
93 match self.disabled_keystrokes.get(&binding.keystrokes) {
94 Some(disabled_predicates) => disabled_predicates.contains(&binding.context_predicate),
95 None => false,
96 }
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use crate::actions;
103
104 use super::*;
105
106 actions!(
107 keymap_test,
108 [Present1, Present2, Present3, Duplicate, Missing]
109 );
110
111 #[test]
112 fn regular_keymap() {
113 let present_1 = Binding::new("ctrl-q", Present1 {}, None);
114 let present_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
115 let present_3 = Binding::new("ctrl-e", Present3 {}, Some("editor"));
116 let keystroke_duplicate_to_1 = Binding::new("ctrl-q", Duplicate {}, None);
117 let full_duplicate_to_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
118 let missing = Binding::new("ctrl-r", Missing {}, None);
119 let all_bindings = [
120 &present_1,
121 &present_2,
122 &present_3,
123 &keystroke_duplicate_to_1,
124 &full_duplicate_to_2,
125 &missing,
126 ];
127
128 let mut keymap = Keymap::default();
129 assert_absent(&keymap, &all_bindings);
130 assert!(keymap.bindings().is_empty());
131
132 keymap.add_bindings([present_1.clone(), present_2.clone(), present_3.clone()]);
133 assert_absent(&keymap, &[&keystroke_duplicate_to_1, &missing]);
134 assert_present(
135 &keymap,
136 &[(&present_1, "q"), (&present_2, "w"), (&present_3, "e")],
137 );
138
139 keymap.add_bindings([
140 keystroke_duplicate_to_1.clone(),
141 full_duplicate_to_2.clone(),
142 ]);
143 assert_absent(&keymap, &[&missing]);
144 assert!(
145 !keymap.binding_disabled(&keystroke_duplicate_to_1),
146 "Duplicate binding 1 was added and should not be disabled"
147 );
148 assert!(
149 !keymap.binding_disabled(&full_duplicate_to_2),
150 "Duplicate binding 2 was added and should not be disabled"
151 );
152
153 assert_eq!(
154 keymap
155 .bindings_for_action(keystroke_duplicate_to_1.action().id())
156 .map(|binding| &binding.keystrokes)
157 .flatten()
158 .collect::<Vec<_>>(),
159 vec![&Keystroke {
160 ctrl: true,
161 alt: false,
162 shift: false,
163 cmd: false,
164 function: false,
165 key: "q".to_string()
166 }],
167 "{keystroke_duplicate_to_1:?} should have the expected keystroke in the keymap"
168 );
169 assert_eq!(
170 keymap
171 .bindings_for_action(full_duplicate_to_2.action().id())
172 .map(|binding| &binding.keystrokes)
173 .flatten()
174 .collect::<Vec<_>>(),
175 vec![
176 &Keystroke {
177 ctrl: true,
178 alt: false,
179 shift: false,
180 cmd: false,
181 function: false,
182 key: "w".to_string()
183 },
184 &Keystroke {
185 ctrl: true,
186 alt: false,
187 shift: false,
188 cmd: false,
189 function: false,
190 key: "w".to_string()
191 }
192 ],
193 "{full_duplicate_to_2:?} should have a duplicated keystroke in the keymap"
194 );
195
196 let updated_bindings = keymap.bindings();
197 let expected_updated_bindings = vec![
198 &present_1,
199 &present_2,
200 &present_3,
201 &keystroke_duplicate_to_1,
202 &full_duplicate_to_2,
203 ];
204 assert_eq!(
205 updated_bindings.len(),
206 expected_updated_bindings.len(),
207 "Unexpected updated keymap bindings {updated_bindings:?}"
208 );
209 for (i, expected) in expected_updated_bindings.iter().enumerate() {
210 let keymap_binding = &updated_bindings[i];
211 assert_eq!(
212 keymap_binding.context_predicate, expected.context_predicate,
213 "Unexpected context predicate for keymap {i} element: {keymap_binding:?}"
214 );
215 assert_eq!(
216 keymap_binding.keystrokes, expected.keystrokes,
217 "Unexpected keystrokes for keymap {i} element: {keymap_binding:?}"
218 );
219 }
220
221 keymap.clear();
222 assert_absent(&keymap, &all_bindings);
223 assert!(keymap.bindings().is_empty());
224 }
225
226 #[test]
227 fn keymap_with_ignored() {
228 let present_1 = Binding::new("ctrl-q", Present1 {}, None);
229 let present_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
230 let present_3 = Binding::new("ctrl-e", Present3 {}, Some("editor"));
231 let keystroke_duplicate_to_1 = Binding::new("ctrl-q", Duplicate {}, None);
232 let full_duplicate_to_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
233 let ignored_1 = Binding::new("ctrl-q", NoAction {}, None);
234 let ignored_2 = Binding::new("ctrl-w", NoAction {}, Some("pane"));
235 let ignored_3_with_other_context =
236 Binding::new("ctrl-e", NoAction {}, Some("other_context"));
237
238 let mut keymap = Keymap::default();
239
240 keymap.add_bindings([
241 ignored_1.clone(),
242 ignored_2.clone(),
243 ignored_3_with_other_context.clone(),
244 ]);
245 assert_absent(&keymap, &[&present_3]);
246 assert_disabled(
247 &keymap,
248 &[
249 &present_1,
250 &present_2,
251 &ignored_1,
252 &ignored_2,
253 &ignored_3_with_other_context,
254 ],
255 );
256 assert!(keymap.bindings().is_empty());
257 keymap.clear();
258
259 keymap.add_bindings([
260 present_1.clone(),
261 present_2.clone(),
262 present_3.clone(),
263 ignored_1.clone(),
264 ignored_2.clone(),
265 ignored_3_with_other_context.clone(),
266 ]);
267 assert_present(&keymap, &[(&present_3, "e")]);
268 assert_disabled(
269 &keymap,
270 &[
271 &present_1,
272 &present_2,
273 &ignored_1,
274 &ignored_2,
275 &ignored_3_with_other_context,
276 ],
277 );
278 keymap.clear();
279
280 keymap.add_bindings([
281 present_1.clone(),
282 present_2.clone(),
283 present_3.clone(),
284 ignored_1.clone(),
285 ]);
286 assert_present(&keymap, &[(&present_2, "w"), (&present_3, "e")]);
287 assert_disabled(&keymap, &[&present_1, &ignored_1]);
288 assert_absent(&keymap, &[&ignored_2, &ignored_3_with_other_context]);
289 keymap.clear();
290
291 keymap.add_bindings([
292 present_1.clone(),
293 present_2.clone(),
294 present_3.clone(),
295 keystroke_duplicate_to_1.clone(),
296 full_duplicate_to_2.clone(),
297 ignored_1.clone(),
298 ignored_2.clone(),
299 ignored_3_with_other_context.clone(),
300 ]);
301 assert_present(&keymap, &[(&present_3, "e")]);
302 assert_disabled(
303 &keymap,
304 &[
305 &present_1,
306 &present_2,
307 &keystroke_duplicate_to_1,
308 &full_duplicate_to_2,
309 &ignored_1,
310 &ignored_2,
311 &ignored_3_with_other_context,
312 ],
313 );
314 keymap.clear();
315 }
316
317 #[track_caller]
318 fn assert_present(keymap: &Keymap, expected_bindings: &[(&Binding, &str)]) {
319 let keymap_bindings = keymap.bindings();
320 assert_eq!(
321 expected_bindings.len(),
322 keymap_bindings.len(),
323 "Unexpected keymap bindings {keymap_bindings:?}"
324 );
325 for (i, (expected, expected_key)) in expected_bindings.iter().enumerate() {
326 assert!(
327 !keymap.binding_disabled(expected),
328 "{expected:?} should not be disabled as it was added into keymap for element {i}"
329 );
330 assert_eq!(
331 keymap
332 .bindings_for_action(expected.action().id())
333 .map(|binding| &binding.keystrokes)
334 .flatten()
335 .collect::<Vec<_>>(),
336 vec![&Keystroke {
337 ctrl: true,
338 alt: false,
339 shift: false,
340 cmd: false,
341 function: false,
342 key: expected_key.to_string()
343 }],
344 "{expected:?} should have the expected keystroke with key '{expected_key}' in the keymap for element {i}"
345 );
346
347 let keymap_binding = &keymap_bindings[i];
348 assert_eq!(
349 keymap_binding.context_predicate, expected.context_predicate,
350 "Unexpected context predicate for keymap {i} element: {keymap_binding:?}"
351 );
352 assert_eq!(
353 keymap_binding.keystrokes, expected.keystrokes,
354 "Unexpected keystrokes for keymap {i} element: {keymap_binding:?}"
355 );
356 }
357 }
358
359 #[track_caller]
360 fn assert_absent(keymap: &Keymap, bindings: &[&Binding]) {
361 for binding in bindings.iter() {
362 assert!(
363 !keymap.binding_disabled(binding),
364 "{binding:?} should not be disabled in the keymap where was not added"
365 );
366 assert_eq!(
367 keymap.bindings_for_action(binding.action().id()).count(),
368 0,
369 "{binding:?} should have no actions in the keymap where was not added"
370 );
371 }
372 }
373
374 #[track_caller]
375 fn assert_disabled(keymap: &Keymap, bindings: &[&Binding]) {
376 for binding in bindings.iter() {
377 assert!(
378 keymap.binding_disabled(binding),
379 "{binding:?} should be disabled in the keymap"
380 );
381 assert_eq!(
382 keymap.bindings_for_action(binding.action().id()).count(),
383 0,
384 "{binding:?} should have no actions in the keymap where it was disabled"
385 );
386 }
387 }
388}