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 and its associated metadata, from the keymap.
10pub struct KeyBinding {
11    pub(crate) action: Box<dyn Action>,
12    pub(crate) keystrokes: SmallVec<[KeybindingKeystroke; 2]>,
13    pub(crate) context_predicate: Option<Rc<KeyBindingContextPredicate>>,
14    pub(crate) meta: Option<KeyBindingMetaIndex>,
15    /// The json input string used when building the keybinding, if any
16    pub(crate) action_input: Option<SharedString>,
17}
18
19impl Clone for KeyBinding {
20    fn clone(&self) -> Self {
21        KeyBinding {
22            action: self.action.boxed_clone(),
23            keystrokes: self.keystrokes.clone(),
24            context_predicate: self.context_predicate.clone(),
25            meta: self.meta,
26            action_input: self.action_input.clone(),
27        }
28    }
29}
30
31impl KeyBinding {
32    /// Constructs a static keybinding with explicit modifiers. Panics on parse error.
33    ///
34    /// Use [`Self::load`] for dynamic input or to resolve `secondary-` using the host OS.
35    pub fn new<A: Action>(keystrokes: &'static str, action: A, context: Option<&str>) -> Self {
36        let context_predicate =
37            context.map(|context| KeyBindingContextPredicate::parse(context).unwrap().into());
38        Self::load_internal(
39            keystrokes,
40            Box::new(action),
41            context_predicate,
42            false,
43            None,
44            &DummyKeyboardMapper,
45            Keystroke::parse,
46        )
47        .unwrap()
48    }
49
50    /// Load a keybinding from the given raw data.
51    pub fn load(
52        keystrokes: &str,
53        action: Box<dyn Action>,
54        context_predicate: Option<Rc<KeyBindingContextPredicate>>,
55        use_key_equivalents: bool,
56        action_input: Option<SharedString>,
57        cx: &crate::App,
58    ) -> std::result::Result<Self, InvalidKeystrokeError> {
59        Self::load_internal(
60            keystrokes,
61            action,
62            context_predicate,
63            use_key_equivalents,
64            action_input,
65            cx.keyboard_mapper().as_ref(),
66            |source| Keystroke::parse_with_os(source, cx.platform_info().os),
67        )
68    }
69
70    fn load_internal<'a>(
71        keystrokes: &'a str,
72        action: Box<dyn Action>,
73        context_predicate: Option<Rc<KeyBindingContextPredicate>>,
74        use_key_equivalents: bool,
75        action_input: Option<SharedString>,
76        keyboard_mapper: &dyn PlatformKeyboardMapper,
77        parse_keystroke: impl Fn(&'a str) -> std::result::Result<Keystroke, InvalidKeystrokeError>,
78    ) -> std::result::Result<Self, InvalidKeystrokeError> {
79        let keystrokes: SmallVec<[KeybindingKeystroke; 2]> = keystrokes
80            .split_whitespace()
81            .map(|source| {
82                let keystroke = parse_keystroke(source)?;
83                Ok(KeybindingKeystroke::new_with_mapper(
84                    keystroke,
85                    use_key_equivalents,
86                    keyboard_mapper,
87                ))
88            })
89            .collect::<std::result::Result<_, _>>()?;
90
91        Ok(Self {
92            keystrokes,
93            action,
94            context_predicate,
95            meta: None,
96            action_input,
97        })
98    }
99
100    /// Set the metadata for this binding.
101    pub fn with_meta(mut self, meta: KeyBindingMetaIndex) -> Self {
102        self.meta = Some(meta);
103        self
104    }
105
106    /// Set the metadata for this binding.
107    pub fn set_meta(&mut self, meta: KeyBindingMetaIndex) {
108        self.meta = Some(meta);
109    }
110
111    /// Check if the given keystrokes match this binding.
112    pub fn match_keystrokes(&self, typed: &[impl AsKeystroke]) -> Option<bool> {
113        if self.keystrokes.len() < typed.len() {
114            return None;
115        }
116
117        for (target, typed) in self.keystrokes.iter().zip(typed.iter()) {
118            if !typed.as_keystroke().should_match(target) {
119                return None;
120            }
121        }
122
123        Some(self.keystrokes.len() > typed.len())
124    }
125
126    /// Get the keystrokes associated with this binding
127    pub fn keystrokes(&self) -> &[KeybindingKeystroke] {
128        self.keystrokes.as_slice()
129    }
130
131    /// Get the action associated with this binding
132    pub fn action(&self) -> &dyn Action {
133        self.action.as_ref()
134    }
135
136    /// Get the predicate used to match this binding
137    pub fn predicate(&self) -> Option<Rc<KeyBindingContextPredicate>> {
138        self.context_predicate.as_ref().map(|rc| rc.clone())
139    }
140
141    /// Get the metadata for this binding
142    pub fn meta(&self) -> Option<KeyBindingMetaIndex> {
143        self.meta
144    }
145
146    /// Get the action input associated with the action for this binding
147    pub fn action_input(&self) -> Option<SharedString> {
148        self.action_input.clone()
149    }
150}
151
152impl std::fmt::Debug for KeyBinding {
153    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
154        f.debug_struct("KeyBinding")
155            .field("keystrokes", &self.keystrokes)
156            .field("context_predicate", &self.context_predicate)
157            .field("action", &self.action.name())
158            .finish()
159    }
160}
161
162/// A unique identifier for retrieval of metadata associated with a key binding.
163/// Intended to be used as an index or key into a user-defined store of metadata
164/// associated with the binding, such as the source of the binding.
165#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
166pub struct KeyBindingMetaIndex(pub u32);