keystroke.rs

  1use schemars::JsonSchema;
  2use serde::{Deserialize, Serialize};
  3use std::{
  4    error::Error,
  5    fmt::{Display, Write},
  6};
  7
  8/// A keystroke and associated metadata generated by the platform
  9#[derive(Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
 10pub struct Keystroke {
 11    /// the state of the modifier keys at the time the keystroke was generated
 12    pub modifiers: Modifiers,
 13
 14    /// key is the character printed on the key that was pressed
 15    /// e.g. for option-s, key is "s"
 16    pub key: String,
 17
 18    /// key_char is the character that could have been typed when
 19    /// this binding was pressed.
 20    /// e.g. for s this is "s", for option-s "รŸ", and cmd-s None
 21    pub key_char: Option<String>,
 22}
 23
 24/// Error type for `Keystroke::parse`. This is used instead of `anyhow::Error` so that Zed can use
 25/// markdown to display it.
 26#[derive(Debug)]
 27pub struct InvalidKeystrokeError {
 28    /// The invalid keystroke.
 29    pub keystroke: String,
 30}
 31
 32impl Error for InvalidKeystrokeError {}
 33
 34impl Display for InvalidKeystrokeError {
 35    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 36        write!(
 37            f,
 38            "Invalid keystroke \"{}\". {}",
 39            self.keystroke, KEYSTROKE_PARSE_EXPECTED_MESSAGE
 40        )
 41    }
 42}
 43
 44/// Sentence explaining what keystroke parser expects, starting with "Expected ..."
 45pub const KEYSTROKE_PARSE_EXPECTED_MESSAGE: &str = "Expected a sequence of modifiers \
 46    (`ctrl`, `alt`, `shift`, `fn`, `cmd`, `super`, or `win`) \
 47    followed by a key, separated by `-`.";
 48
 49impl Keystroke {
 50    /// When matching a key we cannot know whether the user intended to type
 51    /// the key_char or the key itself. On some non-US keyboards keys we use in our
 52    /// bindings are behind option (for example `$` is typed `alt-รง` on a Czech keyboard),
 53    /// and on some keyboards the IME handler converts a sequence of keys into a
 54    /// specific character (for example `"` is typed as `" space` on a brazilian keyboard).
 55    ///
 56    /// This method assumes that `self` was typed and `target' is in the keymap, and checks
 57    /// both possibilities for self against the target.
 58    pub(crate) fn should_match(&self, target: &Keystroke) -> bool {
 59        if let Some(key_char) = self
 60            .key_char
 61            .as_ref()
 62            .filter(|key_char| key_char != &&self.key)
 63        {
 64            let ime_modifiers = Modifiers {
 65                control: self.modifiers.control,
 66                platform: self.modifiers.platform,
 67                ..Default::default()
 68            };
 69
 70            if &target.key == key_char && target.modifiers == ime_modifiers {
 71                return true;
 72            }
 73        }
 74
 75        target.modifiers == self.modifiers && target.key == self.key
 76    }
 77
 78    /// key syntax is:
 79    /// [secondary-][ctrl-][alt-][shift-][cmd-][fn-]key[->key_char]
 80    /// key_char syntax is only used for generating test events,
 81    /// secondary means "cmd" on macOS and "ctrl" on other platforms
 82    /// when matching a key with an key_char set will be matched without it.
 83    pub fn parse(source: &str) -> std::result::Result<Self, InvalidKeystrokeError> {
 84        let mut control = false;
 85        let mut alt = false;
 86        let mut shift = false;
 87        let mut platform = false;
 88        let mut function = false;
 89        let mut key = None;
 90        let mut key_char = None;
 91
 92        let mut components = source.split('-').peekable();
 93        while let Some(component) = components.next() {
 94            match component {
 95                "ctrl" => control = true,
 96                "alt" => alt = true,
 97                "shift" => shift = true,
 98                "fn" => function = true,
 99                "secondary" => {
100                    if cfg!(target_os = "macos") {
101                        platform = true
102                    } else {
103                        control = true
104                    };
105                }
106                "cmd" | "super" | "win" => platform = true,
107                _ => {
108                    if let Some(next) = components.peek() {
109                        if next.is_empty() && source.ends_with('-') {
110                            key = Some(String::from("-"));
111                            break;
112                        } else if next.len() > 1 && next.starts_with('>') {
113                            key = Some(String::from(component));
114                            key_char = Some(String::from(&next[1..]));
115                            components.next();
116                        } else {
117                            return Err(InvalidKeystrokeError {
118                                keystroke: source.to_owned(),
119                            });
120                        }
121                    } else {
122                        key = Some(String::from(component));
123                    }
124                }
125            }
126        }
127
128        // Allow for the user to specify a keystroke modifier as the key itself
129        // This sets the `key` to the modifier, and disables the modifier
130        if key.is_none() {
131            if shift {
132                key = Some("shift".to_string());
133                shift = false;
134            } else if control {
135                key = Some("control".to_string());
136                control = false;
137            } else if alt {
138                key = Some("alt".to_string());
139                alt = false;
140            } else if platform {
141                key = Some("platform".to_string());
142                platform = false;
143            } else if function {
144                key = Some("function".to_string());
145                function = false;
146            }
147        }
148
149        let key = key.ok_or_else(|| InvalidKeystrokeError {
150            keystroke: source.to_owned(),
151        })?;
152
153        Ok(Keystroke {
154            modifiers: Modifiers {
155                control,
156                alt,
157                shift,
158                platform,
159                function,
160            },
161            key,
162            key_char: key_char,
163        })
164    }
165
166    /// Produces a representation of this key that Parse can understand.
167    pub fn unparse(&self) -> String {
168        let mut str = String::new();
169        if self.modifiers.function {
170            str.push_str("fn-");
171        }
172        if self.modifiers.control {
173            str.push_str("ctrl-");
174        }
175        if self.modifiers.alt {
176            str.push_str("alt-");
177        }
178        if self.modifiers.platform {
179            #[cfg(target_os = "macos")]
180            str.push_str("cmd-");
181
182            #[cfg(any(target_os = "linux", target_os = "freebsd"))]
183            str.push_str("super-");
184
185            #[cfg(target_os = "windows")]
186            str.push_str("win-");
187        }
188        if self.modifiers.shift {
189            str.push_str("shift-");
190        }
191        str.push_str(&self.key);
192        str
193    }
194
195    /// Returns true if this keystroke left
196    /// the ime system in an incomplete state.
197    pub fn is_ime_in_progress(&self) -> bool {
198        self.key_char.is_none()
199            && (is_printable_key(&self.key) || self.key.is_empty())
200            && !(self.modifiers.platform
201                || self.modifiers.control
202                || self.modifiers.function
203                || self.modifiers.alt)
204    }
205
206    /// Returns a new keystroke with the key_char filled.
207    /// This is used for dispatch_keystroke where we want users to
208    /// be able to simulate typing "space", etc.
209    pub fn with_simulated_ime(mut self) -> Self {
210        if self.key_char.is_none()
211            && !self.modifiers.platform
212            && !self.modifiers.control
213            && !self.modifiers.function
214            && !self.modifiers.alt
215        {
216            self.key_char = match self.key.as_str() {
217                "space" => Some(" ".into()),
218                "tab" => Some("\t".into()),
219                "enter" => Some("\n".into()),
220                key if !is_printable_key(key) || key.is_empty() => None,
221                key => {
222                    if self.modifiers.shift {
223                        Some(key.to_uppercase())
224                    } else {
225                        Some(key.into())
226                    }
227                }
228            }
229        }
230        self
231    }
232}
233
234fn is_printable_key(key: &str) -> bool {
235    !matches!(
236        key,
237        "f1" | "f2"
238            | "f3"
239            | "f4"
240            | "f5"
241            | "f6"
242            | "f7"
243            | "f8"
244            | "f9"
245            | "f10"
246            | "f11"
247            | "f12"
248            | "f13"
249            | "f14"
250            | "f15"
251            | "f16"
252            | "f17"
253            | "f18"
254            | "f19"
255            | "f20"
256            | "f21"
257            | "f22"
258            | "f23"
259            | "f24"
260            | "f25"
261            | "f26"
262            | "f27"
263            | "f28"
264            | "f29"
265            | "f30"
266            | "f31"
267            | "f32"
268            | "f33"
269            | "f34"
270            | "f35"
271            | "backspace"
272            | "delete"
273            | "left"
274            | "right"
275            | "up"
276            | "down"
277            | "pageup"
278            | "pagedown"
279            | "insert"
280            | "home"
281            | "end"
282            | "back"
283            | "forward"
284            | "escape"
285    )
286}
287
288impl std::fmt::Display for Keystroke {
289    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
290        if self.modifiers.control {
291            f.write_char('^')?;
292        }
293        if self.modifiers.alt {
294            f.write_char('โŒฅ')?;
295        }
296        if self.modifiers.platform {
297            #[cfg(target_os = "macos")]
298            f.write_char('โŒ˜')?;
299
300            #[cfg(any(target_os = "linux", target_os = "freebsd"))]
301            f.write_char('โ–')?;
302
303            #[cfg(target_os = "windows")]
304            f.write_char('โŠž')?;
305        }
306        if self.modifiers.shift {
307            f.write_char('โ‡ง')?;
308        }
309        let key = match self.key.as_str() {
310            "backspace" => 'โŒซ',
311            "up" => 'โ†‘',
312            "down" => 'โ†“',
313            "left" => 'โ†',
314            "right" => 'โ†’',
315            "tab" => 'โ‡ฅ',
316            "escape" => 'โŽ‹',
317            "shift" => 'โ‡ง',
318            "control" => 'โŒƒ',
319            "alt" => 'โŒฅ',
320            "platform" => 'โŒ˜',
321            key => {
322                if key.len() == 1 {
323                    key.chars().next().unwrap().to_ascii_uppercase()
324                } else {
325                    return f.write_str(key);
326                }
327            }
328        };
329        f.write_char(key)
330    }
331}
332
333/// The state of the modifier keys at some point in time
334#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Serialize, Deserialize, Hash, JsonSchema)]
335pub struct Modifiers {
336    /// The control key
337    #[serde(default)]
338    pub control: bool,
339
340    /// The alt key
341    /// Sometimes also known as the 'meta' key
342    #[serde(default)]
343    pub alt: bool,
344
345    /// The shift key
346    #[serde(default)]
347    pub shift: bool,
348
349    /// The command key, on macos
350    /// the windows key, on windows
351    /// the super key, on linux
352    #[serde(default)]
353    pub platform: bool,
354
355    /// The function key
356    #[serde(default)]
357    pub function: bool,
358}
359
360impl Modifiers {
361    /// Returns whether any modifier key is pressed.
362    pub fn modified(&self) -> bool {
363        self.control || self.alt || self.shift || self.platform || self.function
364    }
365
366    /// Whether the semantically 'secondary' modifier key is pressed.
367    ///
368    /// On macOS, this is the command key.
369    /// On Linux and Windows, this is the control key.
370    pub fn secondary(&self) -> bool {
371        #[cfg(target_os = "macos")]
372        {
373            self.platform
374        }
375
376        #[cfg(not(target_os = "macos"))]
377        {
378            self.control
379        }
380    }
381
382    /// Returns how many modifier keys are pressed.
383    pub fn number_of_modifiers(&self) -> u8 {
384        self.control as u8
385            + self.alt as u8
386            + self.shift as u8
387            + self.platform as u8
388            + self.function as u8
389    }
390
391    /// Returns [`Modifiers`] with no modifiers.
392    pub fn none() -> Modifiers {
393        Default::default()
394    }
395
396    /// Returns [`Modifiers`] with just the command key.
397    pub fn command() -> Modifiers {
398        Modifiers {
399            platform: true,
400            ..Default::default()
401        }
402    }
403
404    /// A Returns [`Modifiers`] with just the secondary key pressed.
405    pub fn secondary_key() -> Modifiers {
406        #[cfg(target_os = "macos")]
407        {
408            Modifiers {
409                platform: true,
410                ..Default::default()
411            }
412        }
413
414        #[cfg(not(target_os = "macos"))]
415        {
416            Modifiers {
417                control: true,
418                ..Default::default()
419            }
420        }
421    }
422
423    /// Returns [`Modifiers`] with just the windows key.
424    pub fn windows() -> Modifiers {
425        Modifiers {
426            platform: true,
427            ..Default::default()
428        }
429    }
430
431    /// Returns [`Modifiers`] with just the super key.
432    pub fn super_key() -> Modifiers {
433        Modifiers {
434            platform: true,
435            ..Default::default()
436        }
437    }
438
439    /// Returns [`Modifiers`] with just control.
440    pub fn control() -> Modifiers {
441        Modifiers {
442            control: true,
443            ..Default::default()
444        }
445    }
446
447    /// Returns [`Modifiers`] with just alt.
448    pub fn alt() -> Modifiers {
449        Modifiers {
450            alt: true,
451            ..Default::default()
452        }
453    }
454
455    /// Returns [`Modifiers`] with just shift.
456    pub fn shift() -> Modifiers {
457        Modifiers {
458            shift: true,
459            ..Default::default()
460        }
461    }
462
463    /// Returns [`Modifiers`] with command + shift.
464    pub fn command_shift() -> Modifiers {
465        Modifiers {
466            shift: true,
467            platform: true,
468            ..Default::default()
469        }
470    }
471
472    /// Returns [`Modifiers`] with command + shift.
473    pub fn control_shift() -> Modifiers {
474        Modifiers {
475            shift: true,
476            control: true,
477            ..Default::default()
478        }
479    }
480
481    /// Checks if this [`Modifiers`] is a subset of another [`Modifiers`].
482    pub fn is_subset_of(&self, other: &Modifiers) -> bool {
483        (other.control || !self.control)
484            && (other.alt || !self.alt)
485            && (other.shift || !self.shift)
486            && (other.platform || !self.platform)
487            && (other.function || !self.function)
488    }
489}