keymap.rs

  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}