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