Skip to main content

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);