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 ime_key: None,
167 }],
168 "{keystroke_duplicate_to_1:?} should have the expected keystroke in the keymap"
169 );
170 assert_eq!(
171 keymap
172 .bindings_for_action(full_duplicate_to_2.action().id())
173 .map(|binding| &binding.keystrokes)
174 .flatten()
175 .collect::<Vec<_>>(),
176 vec![
177 &Keystroke {
178 ctrl: true,
179 alt: false,
180 shift: false,
181 cmd: false,
182 function: false,
183 key: "w".to_string(),
184 ime_key: None,
185 },
186 &Keystroke {
187 ctrl: true,
188 alt: false,
189 shift: false,
190 cmd: false,
191 function: false,
192 key: "w".to_string(),
193 ime_key: None,
194 }
195 ],
196 "{full_duplicate_to_2:?} should have a duplicated keystroke in the keymap"
197 );
198
199 let updated_bindings = keymap.bindings();
200 let expected_updated_bindings = vec![
201 &present_1,
202 &present_2,
203 &present_3,
204 &keystroke_duplicate_to_1,
205 &full_duplicate_to_2,
206 ];
207 assert_eq!(
208 updated_bindings.len(),
209 expected_updated_bindings.len(),
210 "Unexpected updated keymap bindings {updated_bindings:?}"
211 );
212 for (i, expected) in expected_updated_bindings.iter().enumerate() {
213 let keymap_binding = &updated_bindings[i];
214 assert_eq!(
215 keymap_binding.context_predicate, expected.context_predicate,
216 "Unexpected context predicate for keymap {i} element: {keymap_binding:?}"
217 );
218 assert_eq!(
219 keymap_binding.keystrokes, expected.keystrokes,
220 "Unexpected keystrokes for keymap {i} element: {keymap_binding:?}"
221 );
222 }
223
224 keymap.clear();
225 assert_absent(&keymap, &all_bindings);
226 assert!(keymap.bindings().is_empty());
227 }
228
229 #[test]
230 fn keymap_with_ignored() {
231 let present_1 = Binding::new("ctrl-q", Present1 {}, None);
232 let present_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
233 let present_3 = Binding::new("ctrl-e", Present3 {}, Some("editor"));
234 let keystroke_duplicate_to_1 = Binding::new("ctrl-q", Duplicate {}, None);
235 let full_duplicate_to_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
236 let ignored_1 = Binding::new("ctrl-q", NoAction {}, None);
237 let ignored_2 = Binding::new("ctrl-w", NoAction {}, Some("pane"));
238 let ignored_3_with_other_context =
239 Binding::new("ctrl-e", NoAction {}, Some("other_context"));
240
241 let mut keymap = Keymap::default();
242
243 keymap.add_bindings([
244 ignored_1.clone(),
245 ignored_2.clone(),
246 ignored_3_with_other_context.clone(),
247 ]);
248 assert_absent(&keymap, &[&present_3]);
249 assert_disabled(
250 &keymap,
251 &[
252 &present_1,
253 &present_2,
254 &ignored_1,
255 &ignored_2,
256 &ignored_3_with_other_context,
257 ],
258 );
259 assert!(keymap.bindings().is_empty());
260 keymap.clear();
261
262 keymap.add_bindings([
263 present_1.clone(),
264 present_2.clone(),
265 present_3.clone(),
266 ignored_1.clone(),
267 ignored_2.clone(),
268 ignored_3_with_other_context.clone(),
269 ]);
270 assert_present(&keymap, &[(&present_3, "e")]);
271 assert_disabled(
272 &keymap,
273 &[
274 &present_1,
275 &present_2,
276 &ignored_1,
277 &ignored_2,
278 &ignored_3_with_other_context,
279 ],
280 );
281 keymap.clear();
282
283 keymap.add_bindings([
284 present_1.clone(),
285 present_2.clone(),
286 present_3.clone(),
287 ignored_1.clone(),
288 ]);
289 assert_present(&keymap, &[(&present_2, "w"), (&present_3, "e")]);
290 assert_disabled(&keymap, &[&present_1, &ignored_1]);
291 assert_absent(&keymap, &[&ignored_2, &ignored_3_with_other_context]);
292 keymap.clear();
293
294 keymap.add_bindings([
295 present_1.clone(),
296 present_2.clone(),
297 present_3.clone(),
298 keystroke_duplicate_to_1.clone(),
299 full_duplicate_to_2.clone(),
300 ignored_1.clone(),
301 ignored_2.clone(),
302 ignored_3_with_other_context.clone(),
303 ]);
304 assert_present(&keymap, &[(&present_3, "e")]);
305 assert_disabled(
306 &keymap,
307 &[
308 &present_1,
309 &present_2,
310 &keystroke_duplicate_to_1,
311 &full_duplicate_to_2,
312 &ignored_1,
313 &ignored_2,
314 &ignored_3_with_other_context,
315 ],
316 );
317 keymap.clear();
318 }
319
320 #[track_caller]
321 fn assert_present(keymap: &Keymap, expected_bindings: &[(&Binding, &str)]) {
322 let keymap_bindings = keymap.bindings();
323 assert_eq!(
324 expected_bindings.len(),
325 keymap_bindings.len(),
326 "Unexpected keymap bindings {keymap_bindings:?}"
327 );
328 for (i, (expected, expected_key)) in expected_bindings.iter().enumerate() {
329 assert!(
330 !keymap.binding_disabled(expected),
331 "{expected:?} should not be disabled as it was added into keymap for element {i}"
332 );
333 assert_eq!(
334 keymap
335 .bindings_for_action(expected.action().id())
336 .map(|binding| &binding.keystrokes)
337 .flatten()
338 .collect::<Vec<_>>(),
339 vec![&Keystroke {
340 ctrl: true,
341 alt: false,
342 shift: false,
343 cmd: false,
344 function: false,
345 key: expected_key.to_string(),
346 ime_key: None,
347 }],
348 "{expected:?} should have the expected keystroke with key '{expected_key}' in the keymap for element {i}"
349 );
350
351 let keymap_binding = &keymap_bindings[i];
352 assert_eq!(
353 keymap_binding.context_predicate, expected.context_predicate,
354 "Unexpected context predicate for keymap {i} element: {keymap_binding:?}"
355 );
356 assert_eq!(
357 keymap_binding.keystrokes, expected.keystrokes,
358 "Unexpected keystrokes for keymap {i} element: {keymap_binding:?}"
359 );
360 }
361 }
362
363 #[track_caller]
364 fn assert_absent(keymap: &Keymap, bindings: &[&Binding]) {
365 for binding in bindings.iter() {
366 assert!(
367 !keymap.binding_disabled(binding),
368 "{binding:?} should not be disabled in the keymap where was not added"
369 );
370 assert_eq!(
371 keymap.bindings_for_action(binding.action().id()).count(),
372 0,
373 "{binding:?} should have no actions in the keymap where was not added"
374 );
375 }
376 }
377
378 #[track_caller]
379 fn assert_disabled(keymap: &Keymap, bindings: &[&Binding]) {
380 for binding in bindings.iter() {
381 assert!(
382 keymap.binding_disabled(binding),
383 "{binding:?} should be disabled in the keymap"
384 );
385 assert_eq!(
386 keymap.bindings_for_action(binding.action().id()).count(),
387 0,
388 "{binding:?} should have no actions in the keymap where it was disabled"
389 );
390 }
391 }
392}