keymap.rs

  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}