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}