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