keymap.rs

  1mod binding;
  2mod context;
  3mod matcher;
  4
  5pub use binding::*;
  6pub use context::*;
  7pub(crate) use matcher::*;
  8
  9use crate::{Action, Keystroke, NoAction};
 10use collections::HashSet;
 11use smallvec::SmallVec;
 12use std::{
 13    any::{Any, TypeId},
 14    collections::HashMap,
 15};
 16
 17/// An opaque identifier of which version of the keymap is currently active.
 18/// The keymap's version is changed whenever bindings are added or removed.
 19#[derive(Copy, Clone, Eq, PartialEq, Default)]
 20pub struct KeymapVersion(usize);
 21
 22/// A collection of key bindings for the user's application.
 23#[derive(Default)]
 24pub struct Keymap {
 25    bindings: Vec<KeyBinding>,
 26    binding_indices_by_action_id: HashMap<TypeId, SmallVec<[usize; 3]>>,
 27    disabled_keystrokes:
 28        HashMap<SmallVec<[Keystroke; 2]>, HashSet<Option<KeyBindingContextPredicate>>>,
 29    version: KeymapVersion,
 30}
 31
 32impl Keymap {
 33    /// Create a new keymap with the given bindings.
 34    pub fn new(bindings: Vec<KeyBinding>) -> Self {
 35        let mut this = Self::default();
 36        this.add_bindings(bindings);
 37        this
 38    }
 39
 40    /// Get the current version of the keymap.
 41    pub fn version(&self) -> KeymapVersion {
 42        self.version
 43    }
 44
 45    /// Add more bindings to the keymap.
 46    pub fn add_bindings<T: IntoIterator<Item = KeyBinding>>(&mut self, bindings: T) {
 47        let no_action_id = (NoAction {}).type_id();
 48
 49        for binding in bindings {
 50            let action_id = binding.action().as_any().type_id();
 51            if action_id == no_action_id {
 52                self.disabled_keystrokes
 53                    .entry(binding.keystrokes)
 54                    .or_default()
 55                    .insert(binding.context_predicate);
 56            } else {
 57                self.binding_indices_by_action_id
 58                    .entry(action_id)
 59                    .or_default()
 60                    .push(self.bindings.len());
 61                self.bindings.push(binding);
 62            }
 63        }
 64
 65        self.version.0 += 1;
 66    }
 67
 68    /// Reset this keymap to its initial state.
 69    pub fn clear(&mut self) {
 70        self.bindings.clear();
 71        self.binding_indices_by_action_id.clear();
 72        self.disabled_keystrokes.clear();
 73        self.version.0 += 1;
 74    }
 75
 76    /// Iterate over all bindings, in the order they were added.
 77    pub fn bindings(&self) -> impl Iterator<Item = &KeyBinding> + DoubleEndedIterator {
 78        self.bindings.iter()
 79    }
 80
 81    /// Iterate over all bindings for the given action, in the order they were added.
 82    pub fn bindings_for_action<'a>(
 83        &'a self,
 84        action: &'a dyn Action,
 85    ) -> impl 'a + Iterator<Item = &'a KeyBinding> + DoubleEndedIterator {
 86        let action_id = action.type_id();
 87        self.binding_indices_by_action_id
 88            .get(&action_id)
 89            .map_or(&[] as _, SmallVec::as_slice)
 90            .iter()
 91            .map(|ix| &self.bindings[*ix])
 92            .filter(move |binding| binding.action().partial_eq(action))
 93    }
 94
 95    /// Check if the given binding is enabled, given a certain key context.
 96    pub fn binding_enabled(&self, binding: &KeyBinding, context: &[KeyContext]) -> bool {
 97        // If binding has a context predicate, it must match the current context,
 98        if let Some(predicate) = &binding.context_predicate {
 99            if !predicate.eval(context) {
100                return false;
101            }
102        }
103
104        if let Some(disabled_predicates) = self.disabled_keystrokes.get(&binding.keystrokes) {
105            for disabled_predicate in disabled_predicates {
106                match disabled_predicate {
107                    // The binding must not be globally disabled.
108                    None => return false,
109
110                    // The binding must not be disabled in the current context.
111                    Some(predicate) => {
112                        if predicate.eval(context) {
113                            return false;
114                        }
115                    }
116                }
117            }
118        }
119
120        true
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127    use crate as gpui;
128    use gpui::actions;
129
130    actions!(
131        keymap_test,
132        [ActionAlpha, ActionBeta, ActionGamma, ActionDelta,]
133    );
134
135    #[test]
136    fn test_keymap() {
137        let bindings = [
138            KeyBinding::new("ctrl-a", ActionAlpha {}, None),
139            KeyBinding::new("ctrl-a", ActionBeta {}, Some("pane")),
140            KeyBinding::new("ctrl-a", ActionGamma {}, Some("editor && mode==full")),
141        ];
142
143        let mut keymap = Keymap::default();
144        keymap.add_bindings(bindings.clone());
145
146        // global bindings are enabled in all contexts
147        assert!(keymap.binding_enabled(&bindings[0], &[]));
148        assert!(keymap.binding_enabled(&bindings[0], &[KeyContext::parse("terminal").unwrap()]));
149
150        // contextual bindings are enabled in contexts that match their predicate
151        assert!(!keymap.binding_enabled(&bindings[1], &[KeyContext::parse("barf x=y").unwrap()]));
152        assert!(keymap.binding_enabled(&bindings[1], &[KeyContext::parse("pane x=y").unwrap()]));
153
154        assert!(!keymap.binding_enabled(&bindings[2], &[KeyContext::parse("editor").unwrap()]));
155        assert!(keymap.binding_enabled(
156            &bindings[2],
157            &[KeyContext::parse("editor mode=full").unwrap()]
158        ));
159    }
160
161    #[test]
162    fn test_keymap_disabled() {
163        let bindings = [
164            KeyBinding::new("ctrl-a", ActionAlpha {}, Some("editor")),
165            KeyBinding::new("ctrl-b", ActionAlpha {}, Some("editor")),
166            KeyBinding::new("ctrl-a", NoAction {}, Some("editor && mode==full")),
167            KeyBinding::new("ctrl-b", NoAction {}, None),
168        ];
169
170        let mut keymap = Keymap::default();
171        keymap.add_bindings(bindings.clone());
172
173        // binding is only enabled in a specific context
174        assert!(!keymap.binding_enabled(&bindings[0], &[KeyContext::parse("barf").unwrap()]));
175        assert!(keymap.binding_enabled(&bindings[0], &[KeyContext::parse("editor").unwrap()]));
176
177        // binding is disabled in a more specific context
178        assert!(!keymap.binding_enabled(
179            &bindings[0],
180            &[KeyContext::parse("editor mode=full").unwrap()]
181        ));
182
183        // binding is globally disabled
184        assert!(!keymap.binding_enabled(&bindings[1], &[KeyContext::parse("barf").unwrap()]));
185    }
186}