gpui/keymap/binding.rs
1use std::rc::Rc;
2
3use crate::{
4 Action, AsKeystroke, DummyKeyboardMapper, InvalidKeystrokeError, KeyBindingContextPredicate,
5 KeybindingKeystroke, Keystroke, PlatformKeyboardMapper, SharedString,
6};
7use smallvec::SmallVec;
8
9/// A keybinding: a keystroke sequence, the action it runs, and the key context it applies in.
10///
11/// You usually create one with [`KeyBinding::new`] and register it with
12/// [`App::bind_keys`](crate::App::bind_keys). Its accessors let you inspect a binding, for
13/// example to show its shortcut in a menu:
14///
15/// ```standalone_crate
16/// use gpui::{Action, KeyBinding, actions};
17///
18/// actions!(binding_docs, [Save]);
19///
20/// let binding = KeyBinding::new("cmd-k cmd-s", Save, Some("Editor"));
21/// assert_eq!(binding.keystrokes().len(), 2);
22/// assert_eq!(binding.action().name(), "binding_docs::Save");
23/// assert!(binding.predicate().is_some());
24/// ```
25///
26/// See [keyboard input](crate::_documentation::concepts::input_dispatch) for how bindings are
27/// matched against the focus path.
28pub struct KeyBinding {
29 pub(crate) action: Box<dyn Action>,
30 pub(crate) keystrokes: SmallVec<[KeybindingKeystroke; 2]>,
31 pub(crate) context_predicate: Option<Rc<KeyBindingContextPredicate>>,
32 pub(crate) meta: Option<KeyBindingMetaIndex>,
33 /// The json input string used when building the keybinding, if any
34 pub(crate) action_input: Option<SharedString>,
35}
36
37impl Clone for KeyBinding {
38 fn clone(&self) -> Self {
39 KeyBinding {
40 action: self.action.boxed_clone(),
41 keystrokes: self.keystrokes.clone(),
42 context_predicate: self.context_predicate.clone(),
43 meta: self.meta,
44 action_input: self.action_input.clone(),
45 }
46 }
47}
48
49impl KeyBinding {
50 /// Creates a binding that runs `action` when `keystrokes` is typed in `context`.
51 ///
52 /// `keystrokes` is a whitespace-separated sequence of
53 /// [keystrokes](Keystroke::parse), such as `"cmd-s"` or `"ctrl-k ctrl-s"`.
54 /// `context` is a [key context predicate](KeyBindingContextPredicate::parse),
55 /// such as `"Editor"` or `"Editor && mode == insert"`. Pass `None` to make the binding apply
56 /// everywhere, which counts as the deepest context for precedence.
57 ///
58 /// Bindings made here use the keys as written. They never go through the platform's keyboard
59 /// mapping, so use this for bindings in code.
60 ///
61 /// # Panics
62 ///
63 /// Panics if any keystroke or the context fails to parse. Use [`KeyBinding::load`] to get an
64 /// error instead.
65 ///
66 /// # Examples
67 ///
68 /// ```standalone_crate
69 /// use gpui::{KeyBinding, actions};
70 ///
71 /// actions!(binding_new_docs, [Save]);
72 ///
73 /// let binding = KeyBinding::new("secondary-s", Save, Some("Editor"));
74 /// assert_eq!(binding.keystrokes().len(), 1);
75 /// ```
76 pub fn new<A: Action>(keystrokes: &str, action: A, context: Option<&str>) -> Self {
77 let context_predicate =
78 context.map(|context| KeyBindingContextPredicate::parse(context).unwrap().into());
79 Self::load(
80 keystrokes,
81 Box::new(action),
82 context_predicate,
83 false,
84 None,
85 &DummyKeyboardMapper,
86 )
87 .unwrap()
88 }
89
90 /// Creates a binding from the parts that a keymap file provides.
91 ///
92 /// `keystrokes` is split on spaces and each part is parsed with [`Keystroke::parse`]. Each
93 /// keystroke is then passed to `keyboard_mapper`, and `use_key_equivalents` asks it for the
94 /// layout-dependent mapping. On macOS, that replaces a key with the character the current
95 /// layout types for it. For bindings from a keymap file, `action_input` is the JSON the action
96 /// was built from. Pass `None` when there is none.
97 ///
98 /// # Errors
99 ///
100 /// Returns [`InvalidKeystrokeError`] if any keystroke fails to parse. The context is already
101 /// parsed, so this function doesn't check it.
102 pub fn load(
103 keystrokes: &str,
104 action: Box<dyn Action>,
105 context_predicate: Option<Rc<KeyBindingContextPredicate>>,
106 use_key_equivalents: bool,
107 action_input: Option<SharedString>,
108 keyboard_mapper: &dyn PlatformKeyboardMapper,
109 ) -> std::result::Result<Self, InvalidKeystrokeError> {
110 let keystrokes: SmallVec<[KeybindingKeystroke; 2]> = keystrokes
111 .split_whitespace()
112 .map(|source| {
113 let keystroke = Keystroke::parse(source)?;
114 Ok(KeybindingKeystroke::new_with_mapper(
115 keystroke,
116 use_key_equivalents,
117 keyboard_mapper,
118 ))
119 })
120 .collect::<std::result::Result<_, _>>()?;
121
122 Ok(Self {
123 keystrokes,
124 action,
125 context_predicate,
126 meta: None,
127 action_input,
128 })
129 }
130
131 /// Sets the source metadata for this binding and returns it.
132 ///
133 /// See [`KeyBindingMetaIndex`] for how the keymap uses this. A binding without metadata
134 /// counts as the strongest source.
135 pub fn with_meta(mut self, meta: KeyBindingMetaIndex) -> Self {
136 self.meta = Some(meta);
137 self
138 }
139
140 /// Sets the source metadata for this binding. See [`KeyBinding::with_meta`].
141 pub fn set_meta(&mut self, meta: KeyBindingMetaIndex) {
142 self.meta = Some(meta);
143 }
144
145 /// Returns how the typed `keystrokes` relate to this binding's keystroke sequence.
146 ///
147 /// Returns `None` if they don't match. Returns `Some(true)` if they match the start of the
148 /// sequence and more keystrokes are needed, and `Some(false)` if they match all of it. The
149 /// keymap uses this to decide whether to wait for the next key.
150 ///
151 /// # Examples
152 ///
153 /// ```standalone_crate
154 /// use gpui::{Keystroke, KeyBinding, actions};
155 ///
156 /// actions!(match_docs, [Save]);
157 ///
158 /// let binding = KeyBinding::new("ctrl-k ctrl-s", Save, None);
159 /// let ctrl_k = [Keystroke::parse("ctrl-k").unwrap()];
160 /// let ctrl_k_ctrl_s = [
161 /// Keystroke::parse("ctrl-k").unwrap(),
162 /// Keystroke::parse("ctrl-s").unwrap(),
163 /// ];
164 ///
165 /// assert_eq!(binding.match_keystrokes(&ctrl_k), Some(true));
166 /// assert_eq!(binding.match_keystrokes(&ctrl_k_ctrl_s), Some(false));
167 /// assert_eq!(binding.match_keystrokes(&[Keystroke::parse("ctrl-x").unwrap()]), None);
168 /// ```
169 pub fn match_keystrokes(&self, typed: &[impl AsKeystroke]) -> Option<bool> {
170 if self.keystrokes.len() < typed.len() {
171 return None;
172 }
173
174 for (target, typed) in self.keystrokes.iter().zip(typed.iter()) {
175 if !typed.as_keystroke().should_match(target) {
176 return None;
177 }
178 }
179
180 Some(self.keystrokes.len() > typed.len())
181 }
182
183 /// Returns the keystrokes that trigger this binding, in order. A binding has one keystroke
184 /// for a single key, and more for a sequence such as `ctrl-k ctrl-s`.
185 pub fn keystrokes(&self) -> &[KeybindingKeystroke] {
186 self.keystrokes.as_slice()
187 }
188
189 /// Returns the action this binding runs.
190 pub fn action(&self) -> &dyn Action {
191 self.action.as_ref()
192 }
193
194 /// Returns the key context predicate this binding applies in, or `None` if it applies in
195 /// every context.
196 pub fn predicate(&self) -> Option<Rc<KeyBindingContextPredicate>> {
197 self.context_predicate.as_ref().map(|rc| rc.clone())
198 }
199
200 /// Returns the source metadata set with [`KeyBinding::with_meta`], if any.
201 pub fn meta(&self) -> Option<KeyBindingMetaIndex> {
202 self.meta
203 }
204
205 /// Returns the JSON input the action was built from, if the binding came from a keymap file.
206 pub fn action_input(&self) -> Option<SharedString> {
207 self.action_input.clone()
208 }
209}
210
211impl std::fmt::Debug for KeyBinding {
212 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
213 f.debug_struct("KeyBinding")
214 .field("keystrokes", &self.keystrokes)
215 .field("context_predicate", &self.context_predicate)
216 .field("action", &self.action.name())
217 .finish()
218 }
219}
220
221/// An index into your own store of metadata about where a binding came from, such as the keymap
222/// file or the built-in defaults. Lower indexes are stronger sources.
223///
224/// The keymap uses the index when a [`NoAction`](crate::NoAction) disables a lower-precedence
225/// binding for the same keystrokes. The `NoAction` disables bindings from its own source or a
226/// weaker one (a higher index), but not bindings from a stronger source. A binding without an
227/// index counts as index `0`.
228#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
229pub struct KeyBindingMetaIndex(pub u32);