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/// TODO:
 25#[derive(Debug, Clone, Eq, PartialEq)]
 26pub struct KeybindingKeystroke {
 27    /// TODO:
 28    pub inner: Keystroke,
 29    /// TODO:
 30    pub modifiers: Modifiers,
 31    /// TODO:
 32    pub key: String,
 33}
 34
 35/// Error type for `Keystroke::parse`. This is used instead of `anyhow::Error` so that Zed can use
 36/// markdown to display it.
 37#[derive(Debug)]
 38pub struct InvalidKeystrokeError {
 39    /// The invalid keystroke.
 40    pub keystroke: String,
 41}
 42
 43impl Error for InvalidKeystrokeError {}
 44
 45impl Display for InvalidKeystrokeError {
 46    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 47        write!(
 48            f,
 49            "Invalid keystroke \"{}\". {}",
 50            self.keystroke, KEYSTROKE_PARSE_EXPECTED_MESSAGE
 51        )
 52    }
 53}
 54
 55/// Sentence explaining what keystroke parser expects, starting with "Expected ..."
 56pub const KEYSTROKE_PARSE_EXPECTED_MESSAGE: &str = "Expected a sequence of modifiers \
 57    (`ctrl`, `alt`, `shift`, `fn`, `cmd`, `super`, or `win`) \
 58    followed by a key, separated by `-`.";
 59
 60impl Keystroke {
 61    /// When matching a key we cannot know whether the user intended to type
 62    /// the key_char or the key itself. On some non-US keyboards keys we use in our
 63    /// bindings are behind option (for example `$` is typed `alt-รง` on a Czech keyboard),
 64    /// and on some keyboards the IME handler converts a sequence of keys into a
 65    /// specific character (for example `"` is typed as `" space` on a brazilian keyboard).
 66    ///
 67    /// This method assumes that `self` was typed and `target' is in the keymap, and checks
 68    /// both possibilities for self against the target.
 69    pub fn should_match(&self, target: &KeybindingKeystroke) -> bool {
 70        #[cfg(not(target_os = "windows"))]
 71        if let Some(key_char) = self
 72            .key_char
 73            .as_ref()
 74            .filter(|key_char| key_char != &&self.key)
 75        {
 76            let ime_modifiers = Modifiers {
 77                control: self.modifiers.control,
 78                platform: self.modifiers.platform,
 79                ..Default::default()
 80            };
 81
 82            if &target.inner.key == key_char && target.inner.modifiers == ime_modifiers {
 83                return true;
 84            }
 85        }
 86
 87        #[cfg(target_os = "windows")]
 88        if let Some(key_char) = self
 89            .key_char
 90            .as_ref()
 91            .filter(|key_char| key_char != &&self.key)
 92        {
 93            // On Windows, if key_char is set, then the typed keystroke produced the key_char
 94            if &target.inner.key == key_char && target.inner.modifiers == Modifiers::none() {
 95                return true;
 96            }
 97        }
 98
 99        target.inner.modifiers == self.modifiers && target.inner.key == self.key
100    }
101
102    /// key syntax is:
103    /// [secondary-][ctrl-][alt-][shift-][cmd-][fn-]key[->key_char]
104    /// key_char syntax is only used for generating test events,
105    /// secondary means "cmd" on macOS and "ctrl" on other platforms
106    /// when matching a key with an key_char set will be matched without it.
107    pub fn parse(source: &str) -> std::result::Result<Self, InvalidKeystrokeError> {
108        let mut modifiers = Modifiers::none();
109        let mut key = None;
110        let mut key_char = None;
111
112        let mut components = source.split('-').peekable();
113        while let Some(component) = components.next() {
114            if component.eq_ignore_ascii_case("ctrl") {
115                modifiers.control = true;
116                continue;
117            }
118            if component.eq_ignore_ascii_case("alt") {
119                modifiers.alt = true;
120                continue;
121            }
122            if component.eq_ignore_ascii_case("shift") {
123                modifiers.shift = true;
124                continue;
125            }
126            if component.eq_ignore_ascii_case("fn") {
127                modifiers.function = true;
128                continue;
129            }
130            if component.eq_ignore_ascii_case("secondary") {
131                if cfg!(target_os = "macos") {
132                    modifiers.platform = true;
133                } else {
134                    modifiers.control = true;
135                };
136                continue;
137            }
138
139            let is_platform = component.eq_ignore_ascii_case("cmd")
140                || component.eq_ignore_ascii_case("super")
141                || component.eq_ignore_ascii_case("win");
142
143            if is_platform {
144                modifiers.platform = true;
145                continue;
146            }
147
148            let mut key_str = component.to_string();
149
150            if let Some(next) = components.peek() {
151                if next.is_empty() && source.ends_with('-') {
152                    key = Some(String::from("-"));
153                    break;
154                } else if next.len() > 1 && next.starts_with('>') {
155                    key = Some(key_str);
156                    key_char = Some(String::from(&next[1..]));
157                    components.next();
158                } else {
159                    return Err(InvalidKeystrokeError {
160                        keystroke: source.to_owned(),
161                    });
162                }
163                continue;
164            }
165
166            if component.len() == 1 && component.as_bytes()[0].is_ascii_uppercase() {
167                // Convert to shift + lowercase char
168                modifiers.shift = true;
169                key_str.make_ascii_lowercase();
170            } else {
171                // convert ascii chars to lowercase so that named keys like "tab" and "enter"
172                // are accepted case insensitively and stored how we expect so they are matched properly
173                key_str.make_ascii_lowercase()
174            }
175            key = Some(key_str);
176        }
177
178        // Allow for the user to specify a keystroke modifier as the key itself
179        // This sets the `key` to the modifier, and disables the modifier
180        key = key.or_else(|| {
181            use std::mem;
182            // std::mem::take clears bool incase its true
183            if mem::take(&mut modifiers.shift) {
184                Some("shift".to_string())
185            } else if mem::take(&mut modifiers.control) {
186                Some("control".to_string())
187            } else if mem::take(&mut modifiers.alt) {
188                Some("alt".to_string())
189            } else if mem::take(&mut modifiers.platform) {
190                Some("platform".to_string())
191            } else if mem::take(&mut modifiers.function) {
192                Some("function".to_string())
193            } else {
194                None
195            }
196        });
197
198        let key = key.ok_or_else(|| InvalidKeystrokeError {
199            keystroke: source.to_owned(),
200        })?;
201
202        Ok(Keystroke {
203            modifiers,
204            key,
205            key_char,
206        })
207    }
208
209    /// Produces a representation of this key that Parse can understand.
210    pub fn unparse(&self) -> String {
211        let mut str = String::new();
212        if self.modifiers.function {
213            str.push_str("fn-");
214        }
215        if self.modifiers.control {
216            str.push_str("ctrl-");
217        }
218        if self.modifiers.alt {
219            str.push_str("alt-");
220        }
221        if self.modifiers.platform {
222            #[cfg(target_os = "macos")]
223            str.push_str("cmd-");
224
225            #[cfg(any(target_os = "linux", target_os = "freebsd"))]
226            str.push_str("super-");
227
228            #[cfg(target_os = "windows")]
229            str.push_str("win-");
230        }
231        if self.modifiers.shift {
232            str.push_str("shift-");
233        }
234        str.push_str(&self.key);
235        str
236    }
237
238    /// Returns true if this keystroke left
239    /// the ime system in an incomplete state.
240    pub fn is_ime_in_progress(&self) -> bool {
241        self.key_char.is_none()
242            && (is_printable_key(&self.key) || self.key.is_empty())
243            && !(self.modifiers.platform
244                || self.modifiers.control
245                || self.modifiers.function
246                || self.modifiers.alt)
247    }
248
249    /// Returns a new keystroke with the key_char filled.
250    /// This is used for dispatch_keystroke where we want users to
251    /// be able to simulate typing "space", etc.
252    pub fn with_simulated_ime(mut self) -> Self {
253        if self.key_char.is_none()
254            && !self.modifiers.platform
255            && !self.modifiers.control
256            && !self.modifiers.function
257            && !self.modifiers.alt
258        {
259            self.key_char = match self.key.as_str() {
260                "space" => Some(" ".into()),
261                "tab" => Some("\t".into()),
262                "enter" => Some("\n".into()),
263                key if !is_printable_key(key) || key.is_empty() => None,
264                key => {
265                    if self.modifiers.shift {
266                        Some(key.to_uppercase())
267                    } else {
268                        Some(key.into())
269                    }
270                }
271            }
272        }
273        self
274    }
275}
276
277impl KeybindingKeystroke {
278    /// TODO:
279    pub fn parse(source: &str) -> std::result::Result<Self, InvalidKeystrokeError> {
280        let keystroke = Keystroke::parse(source)?;
281        let Keystroke {
282            mut modifiers, key, ..
283        } = keystroke.clone();
284        let (key, modifiers) = temp_keyboard_mapper(key, modifiers);
285        Ok(KeybindingKeystroke {
286            inner: keystroke,
287            modifiers,
288            key,
289        })
290    }
291
292    /// TODO:
293    pub fn to_string(&self) -> String {
294        let keystroke = Keystroke {
295            modifiers: self.modifiers,
296            key: self.key.clone(),
297            key_char: None,
298        };
299        keystroke.to_string()
300    }
301}
302
303fn temp_keyboard_mapper(key: String, mut modifiers: Modifiers) -> (String, Modifiers) {
304    match key.as_str() {
305        "~" => {
306            modifiers.shift = true;
307            ("`".to_string(), modifiers)
308        }
309        "!" => {
310            modifiers.shift = true;
311            ("1".to_string(), modifiers)
312        }
313        "@" => {
314            modifiers.shift = true;
315            ("2".to_string(), modifiers)
316        }
317        "#" => {
318            modifiers.shift = true;
319            ("3".to_string(), modifiers)
320        }
321        "$" => {
322            modifiers.shift = true;
323            ("4".to_string(), modifiers)
324        }
325        "%" => {
326            modifiers.shift = true;
327            ("5".to_string(), modifiers)
328        }
329        "^" => {
330            modifiers.shift = true;
331            ("6".to_string(), modifiers)
332        }
333        "&" => {
334            modifiers.shift = true;
335            ("7".to_string(), modifiers)
336        }
337        "*" => {
338            modifiers.shift = true;
339            ("8".to_string(), modifiers)
340        }
341        "(" => {
342            modifiers.shift = true;
343            ("9".to_string(), modifiers)
344        }
345        ")" => {
346            modifiers.shift = true;
347            ("0".to_string(), modifiers)
348        }
349        "_" => {
350            modifiers.shift = true;
351            ("-".to_string(), modifiers)
352        }
353        "+" => {
354            modifiers.shift = true;
355            ("=".to_string(), modifiers)
356        }
357        "{" => {
358            modifiers.shift = true;
359            ("[".to_string(), modifiers)
360        }
361        "}" => {
362            modifiers.shift = true;
363            ("]".to_string(), modifiers)
364        }
365        "|" => {
366            modifiers.shift = true;
367            ("\\".to_string(), modifiers)
368        }
369        ":" => {
370            modifiers.shift = true;
371            (";".to_string(), modifiers)
372        }
373        "\"" => {
374            modifiers.shift = true;
375            ("'".to_string(), modifiers)
376        }
377        "<" => {
378            modifiers.shift = true;
379            (",".to_string(), modifiers)
380        }
381        ">" => {
382            modifiers.shift = true;
383            (">".to_string(), modifiers)
384        }
385        "?" => {
386            modifiers.shift = true;
387            ("/".to_string(), modifiers)
388        }
389        _ => (key, modifiers),
390    }
391}
392
393fn is_printable_key(key: &str) -> bool {
394    !matches!(
395        key,
396        "f1" | "f2"
397            | "f3"
398            | "f4"
399            | "f5"
400            | "f6"
401            | "f7"
402            | "f8"
403            | "f9"
404            | "f10"
405            | "f11"
406            | "f12"
407            | "f13"
408            | "f14"
409            | "f15"
410            | "f16"
411            | "f17"
412            | "f18"
413            | "f19"
414            | "f20"
415            | "f21"
416            | "f22"
417            | "f23"
418            | "f24"
419            | "f25"
420            | "f26"
421            | "f27"
422            | "f28"
423            | "f29"
424            | "f30"
425            | "f31"
426            | "f32"
427            | "f33"
428            | "f34"
429            | "f35"
430            | "backspace"
431            | "delete"
432            | "left"
433            | "right"
434            | "up"
435            | "down"
436            | "pageup"
437            | "pagedown"
438            | "insert"
439            | "home"
440            | "end"
441            | "back"
442            | "forward"
443            | "escape"
444    )
445}
446
447impl std::fmt::Display for Keystroke {
448    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
449        if self.modifiers.control {
450            #[cfg(target_os = "macos")]
451            f.write_char('^')?;
452
453            #[cfg(not(target_os = "macos"))]
454            write!(f, "ctrl-")?;
455        }
456        if self.modifiers.alt {
457            #[cfg(target_os = "macos")]
458            f.write_char('โŒฅ')?;
459
460            #[cfg(not(target_os = "macos"))]
461            write!(f, "alt-")?;
462        }
463        if self.modifiers.platform {
464            #[cfg(target_os = "macos")]
465            f.write_char('โŒ˜')?;
466
467            #[cfg(any(target_os = "linux", target_os = "freebsd"))]
468            f.write_char('โ–')?;
469
470            #[cfg(target_os = "windows")]
471            f.write_char('โŠž')?;
472        }
473        if self.modifiers.shift {
474            #[cfg(target_os = "macos")]
475            f.write_char('โ‡ง')?;
476
477            #[cfg(not(target_os = "macos"))]
478            write!(f, "shift-")?;
479        }
480        let key = match self.key.as_str() {
481            #[cfg(target_os = "macos")]
482            "backspace" => 'โŒซ',
483            #[cfg(target_os = "macos")]
484            "up" => 'โ†‘',
485            #[cfg(target_os = "macos")]
486            "down" => 'โ†“',
487            #[cfg(target_os = "macos")]
488            "left" => 'โ†',
489            #[cfg(target_os = "macos")]
490            "right" => 'โ†’',
491            #[cfg(target_os = "macos")]
492            "tab" => 'โ‡ฅ',
493            #[cfg(target_os = "macos")]
494            "escape" => 'โŽ‹',
495            #[cfg(target_os = "macos")]
496            "shift" => 'โ‡ง',
497            #[cfg(target_os = "macos")]
498            "control" => 'โŒƒ',
499            #[cfg(target_os = "macos")]
500            "alt" => 'โŒฅ',
501            #[cfg(target_os = "macos")]
502            "platform" => 'โŒ˜',
503
504            key if key.len() == 1 => key.chars().next().unwrap().to_ascii_uppercase(),
505            key => return f.write_str(key),
506        };
507        f.write_char(key)
508    }
509}
510
511/// The state of the modifier keys at some point in time
512#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Serialize, Deserialize, Hash, JsonSchema)]
513pub struct Modifiers {
514    /// The control key
515    #[serde(default)]
516    pub control: bool,
517
518    /// The alt key
519    /// Sometimes also known as the 'meta' key
520    #[serde(default)]
521    pub alt: bool,
522
523    /// The shift key
524    #[serde(default)]
525    pub shift: bool,
526
527    /// The command key, on macos
528    /// the windows key, on windows
529    /// the super key, on linux
530    #[serde(default)]
531    pub platform: bool,
532
533    /// The function key
534    #[serde(default)]
535    pub function: bool,
536}
537
538impl Modifiers {
539    /// Returns whether any modifier key is pressed.
540    pub fn modified(&self) -> bool {
541        self.control || self.alt || self.shift || self.platform || self.function
542    }
543
544    /// Whether the semantically 'secondary' modifier key is pressed.
545    ///
546    /// On macOS, this is the command key.
547    /// On Linux and Windows, this is the control key.
548    pub fn secondary(&self) -> bool {
549        #[cfg(target_os = "macos")]
550        {
551            self.platform
552        }
553
554        #[cfg(not(target_os = "macos"))]
555        {
556            self.control
557        }
558    }
559
560    /// Returns how many modifier keys are pressed.
561    pub fn number_of_modifiers(&self) -> u8 {
562        self.control as u8
563            + self.alt as u8
564            + self.shift as u8
565            + self.platform as u8
566            + self.function as u8
567    }
568
569    /// Returns [`Modifiers`] with no modifiers.
570    pub fn none() -> Modifiers {
571        Default::default()
572    }
573
574    /// Returns [`Modifiers`] with just the command key.
575    pub fn command() -> Modifiers {
576        Modifiers {
577            platform: true,
578            ..Default::default()
579        }
580    }
581
582    /// A Returns [`Modifiers`] with just the secondary key pressed.
583    pub fn secondary_key() -> Modifiers {
584        #[cfg(target_os = "macos")]
585        {
586            Modifiers {
587                platform: true,
588                ..Default::default()
589            }
590        }
591
592        #[cfg(not(target_os = "macos"))]
593        {
594            Modifiers {
595                control: true,
596                ..Default::default()
597            }
598        }
599    }
600
601    /// Returns [`Modifiers`] with just the windows key.
602    pub fn windows() -> Modifiers {
603        Modifiers {
604            platform: true,
605            ..Default::default()
606        }
607    }
608
609    /// Returns [`Modifiers`] with just the super key.
610    pub fn super_key() -> Modifiers {
611        Modifiers {
612            platform: true,
613            ..Default::default()
614        }
615    }
616
617    /// Returns [`Modifiers`] with just control.
618    pub fn control() -> Modifiers {
619        Modifiers {
620            control: true,
621            ..Default::default()
622        }
623    }
624
625    /// Returns [`Modifiers`] with just alt.
626    pub fn alt() -> Modifiers {
627        Modifiers {
628            alt: true,
629            ..Default::default()
630        }
631    }
632
633    /// Returns [`Modifiers`] with just shift.
634    pub fn shift() -> Modifiers {
635        Modifiers {
636            shift: true,
637            ..Default::default()
638        }
639    }
640
641    /// Returns [`Modifiers`] with command + shift.
642    pub fn command_shift() -> Modifiers {
643        Modifiers {
644            shift: true,
645            platform: true,
646            ..Default::default()
647        }
648    }
649
650    /// Returns [`Modifiers`] with command + shift.
651    pub fn control_shift() -> Modifiers {
652        Modifiers {
653            shift: true,
654            control: true,
655            ..Default::default()
656        }
657    }
658
659    /// Checks if this [`Modifiers`] is a subset of another [`Modifiers`].
660    pub fn is_subset_of(&self, other: &Modifiers) -> bool {
661        (other.control || !self.control)
662            && (other.alt || !self.alt)
663            && (other.shift || !self.shift)
664            && (other.platform || !self.platform)
665            && (other.function || !self.function)
666    }
667}
668
669/// The state of the capslock key at some point in time
670#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Serialize, Deserialize, Hash, JsonSchema)]
671pub struct Capslock {
672    /// The capslock key is on
673    #[serde(default)]
674    pub on: bool,
675}