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    /// TODO:
277    pub fn into_keybinding_keystroke(self) -> KeybindingKeystroke {
278        let (key, modifiers) = temp_keyboard_mapper(self.key.clone(), self.modifiers);
279        KeybindingKeystroke {
280            inner: self,
281            modifiers,
282            key,
283        }
284    }
285}
286
287impl KeybindingKeystroke {
288    /// TODO:
289    pub fn parse(source: &str) -> std::result::Result<Self, InvalidKeystrokeError> {
290        let keystroke = Keystroke::parse(source)?;
291        let Keystroke {
292            mut modifiers, key, ..
293        } = keystroke.clone();
294        let (key, modifiers) = temp_keyboard_mapper(key, modifiers);
295        Ok(KeybindingKeystroke {
296            inner: keystroke,
297            modifiers,
298            key,
299        })
300    }
301
302    /// TODO:
303    pub fn to_string(&self) -> String {
304        let keystroke = Keystroke {
305            modifiers: self.modifiers,
306            key: self.key.clone(),
307            key_char: None,
308        };
309        keystroke.to_string()
310    }
311}
312
313fn temp_keyboard_mapper(key: String, mut modifiers: Modifiers) -> (String, Modifiers) {
314    match key.as_str() {
315        "~" => {
316            modifiers.shift = true;
317            ("`".to_string(), modifiers)
318        }
319        "!" => {
320            modifiers.shift = true;
321            ("1".to_string(), modifiers)
322        }
323        "@" => {
324            modifiers.shift = true;
325            ("2".to_string(), modifiers)
326        }
327        "#" => {
328            modifiers.shift = true;
329            ("3".to_string(), modifiers)
330        }
331        "$" => {
332            modifiers.shift = true;
333            ("4".to_string(), modifiers)
334        }
335        "%" => {
336            modifiers.shift = true;
337            ("5".to_string(), modifiers)
338        }
339        "^" => {
340            modifiers.shift = true;
341            ("6".to_string(), modifiers)
342        }
343        "&" => {
344            modifiers.shift = true;
345            ("7".to_string(), modifiers)
346        }
347        "*" => {
348            modifiers.shift = true;
349            ("8".to_string(), modifiers)
350        }
351        "(" => {
352            modifiers.shift = true;
353            ("9".to_string(), modifiers)
354        }
355        ")" => {
356            modifiers.shift = true;
357            ("0".to_string(), modifiers)
358        }
359        "_" => {
360            modifiers.shift = true;
361            ("-".to_string(), modifiers)
362        }
363        "+" => {
364            modifiers.shift = true;
365            ("=".to_string(), modifiers)
366        }
367        "{" => {
368            modifiers.shift = true;
369            ("[".to_string(), modifiers)
370        }
371        "}" => {
372            modifiers.shift = true;
373            ("]".to_string(), modifiers)
374        }
375        "|" => {
376            modifiers.shift = true;
377            ("\\".to_string(), modifiers)
378        }
379        ":" => {
380            modifiers.shift = true;
381            (";".to_string(), modifiers)
382        }
383        "\"" => {
384            modifiers.shift = true;
385            ("'".to_string(), modifiers)
386        }
387        "<" => {
388            modifiers.shift = true;
389            (",".to_string(), modifiers)
390        }
391        ">" => {
392            modifiers.shift = true;
393            (">".to_string(), modifiers)
394        }
395        "?" => {
396            modifiers.shift = true;
397            ("/".to_string(), modifiers)
398        }
399        _ => (key, modifiers),
400    }
401}
402
403fn is_printable_key(key: &str) -> bool {
404    !matches!(
405        key,
406        "f1" | "f2"
407            | "f3"
408            | "f4"
409            | "f5"
410            | "f6"
411            | "f7"
412            | "f8"
413            | "f9"
414            | "f10"
415            | "f11"
416            | "f12"
417            | "f13"
418            | "f14"
419            | "f15"
420            | "f16"
421            | "f17"
422            | "f18"
423            | "f19"
424            | "f20"
425            | "f21"
426            | "f22"
427            | "f23"
428            | "f24"
429            | "f25"
430            | "f26"
431            | "f27"
432            | "f28"
433            | "f29"
434            | "f30"
435            | "f31"
436            | "f32"
437            | "f33"
438            | "f34"
439            | "f35"
440            | "backspace"
441            | "delete"
442            | "left"
443            | "right"
444            | "up"
445            | "down"
446            | "pageup"
447            | "pagedown"
448            | "insert"
449            | "home"
450            | "end"
451            | "back"
452            | "forward"
453            | "escape"
454    )
455}
456
457impl std::fmt::Display for Keystroke {
458    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
459        if self.modifiers.control {
460            #[cfg(target_os = "macos")]
461            f.write_char('^')?;
462
463            #[cfg(not(target_os = "macos"))]
464            write!(f, "ctrl-")?;
465        }
466        if self.modifiers.alt {
467            #[cfg(target_os = "macos")]
468            f.write_char('โŒฅ')?;
469
470            #[cfg(not(target_os = "macos"))]
471            write!(f, "alt-")?;
472        }
473        if self.modifiers.platform {
474            #[cfg(target_os = "macos")]
475            f.write_char('โŒ˜')?;
476
477            #[cfg(any(target_os = "linux", target_os = "freebsd"))]
478            f.write_char('โ–')?;
479
480            #[cfg(target_os = "windows")]
481            f.write_char('โŠž')?;
482        }
483        if self.modifiers.shift {
484            #[cfg(target_os = "macos")]
485            f.write_char('โ‡ง')?;
486
487            #[cfg(not(target_os = "macos"))]
488            write!(f, "shift-")?;
489        }
490        let key = match self.key.as_str() {
491            #[cfg(target_os = "macos")]
492            "backspace" => 'โŒซ',
493            #[cfg(target_os = "macos")]
494            "up" => 'โ†‘',
495            #[cfg(target_os = "macos")]
496            "down" => 'โ†“',
497            #[cfg(target_os = "macos")]
498            "left" => 'โ†',
499            #[cfg(target_os = "macos")]
500            "right" => 'โ†’',
501            #[cfg(target_os = "macos")]
502            "tab" => 'โ‡ฅ',
503            #[cfg(target_os = "macos")]
504            "escape" => 'โŽ‹',
505            #[cfg(target_os = "macos")]
506            "shift" => 'โ‡ง',
507            #[cfg(target_os = "macos")]
508            "control" => 'โŒƒ',
509            #[cfg(target_os = "macos")]
510            "alt" => 'โŒฅ',
511            #[cfg(target_os = "macos")]
512            "platform" => 'โŒ˜',
513
514            key if key.len() == 1 => key.chars().next().unwrap().to_ascii_uppercase(),
515            key => return f.write_str(key),
516        };
517        f.write_char(key)
518    }
519}
520
521/// The state of the modifier keys at some point in time
522#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Serialize, Deserialize, Hash, JsonSchema)]
523pub struct Modifiers {
524    /// The control key
525    #[serde(default)]
526    pub control: bool,
527
528    /// The alt key
529    /// Sometimes also known as the 'meta' key
530    #[serde(default)]
531    pub alt: bool,
532
533    /// The shift key
534    #[serde(default)]
535    pub shift: bool,
536
537    /// The command key, on macos
538    /// the windows key, on windows
539    /// the super key, on linux
540    #[serde(default)]
541    pub platform: bool,
542
543    /// The function key
544    #[serde(default)]
545    pub function: bool,
546}
547
548impl Modifiers {
549    /// Returns whether any modifier key is pressed.
550    pub fn modified(&self) -> bool {
551        self.control || self.alt || self.shift || self.platform || self.function
552    }
553
554    /// Whether the semantically 'secondary' modifier key is pressed.
555    ///
556    /// On macOS, this is the command key.
557    /// On Linux and Windows, this is the control key.
558    pub fn secondary(&self) -> bool {
559        #[cfg(target_os = "macos")]
560        {
561            self.platform
562        }
563
564        #[cfg(not(target_os = "macos"))]
565        {
566            self.control
567        }
568    }
569
570    /// Returns how many modifier keys are pressed.
571    pub fn number_of_modifiers(&self) -> u8 {
572        self.control as u8
573            + self.alt as u8
574            + self.shift as u8
575            + self.platform as u8
576            + self.function as u8
577    }
578
579    /// Returns [`Modifiers`] with no modifiers.
580    pub fn none() -> Modifiers {
581        Default::default()
582    }
583
584    /// Returns [`Modifiers`] with just the command key.
585    pub fn command() -> Modifiers {
586        Modifiers {
587            platform: true,
588            ..Default::default()
589        }
590    }
591
592    /// A Returns [`Modifiers`] with just the secondary key pressed.
593    pub fn secondary_key() -> Modifiers {
594        #[cfg(target_os = "macos")]
595        {
596            Modifiers {
597                platform: true,
598                ..Default::default()
599            }
600        }
601
602        #[cfg(not(target_os = "macos"))]
603        {
604            Modifiers {
605                control: true,
606                ..Default::default()
607            }
608        }
609    }
610
611    /// Returns [`Modifiers`] with just the windows key.
612    pub fn windows() -> Modifiers {
613        Modifiers {
614            platform: true,
615            ..Default::default()
616        }
617    }
618
619    /// Returns [`Modifiers`] with just the super key.
620    pub fn super_key() -> Modifiers {
621        Modifiers {
622            platform: true,
623            ..Default::default()
624        }
625    }
626
627    /// Returns [`Modifiers`] with just control.
628    pub fn control() -> Modifiers {
629        Modifiers {
630            control: true,
631            ..Default::default()
632        }
633    }
634
635    /// Returns [`Modifiers`] with just alt.
636    pub fn alt() -> Modifiers {
637        Modifiers {
638            alt: true,
639            ..Default::default()
640        }
641    }
642
643    /// Returns [`Modifiers`] with just shift.
644    pub fn shift() -> Modifiers {
645        Modifiers {
646            shift: true,
647            ..Default::default()
648        }
649    }
650
651    /// Returns [`Modifiers`] with command + shift.
652    pub fn command_shift() -> Modifiers {
653        Modifiers {
654            shift: true,
655            platform: true,
656            ..Default::default()
657        }
658    }
659
660    /// Returns [`Modifiers`] with command + shift.
661    pub fn control_shift() -> Modifiers {
662        Modifiers {
663            shift: true,
664            control: true,
665            ..Default::default()
666        }
667    }
668
669    /// Checks if this [`Modifiers`] is a subset of another [`Modifiers`].
670    pub fn is_subset_of(&self, other: &Modifiers) -> bool {
671        (other.control || !self.control)
672            && (other.alt || !self.alt)
673            && (other.shift || !self.shift)
674            && (other.platform || !self.platform)
675            && (other.function || !self.function)
676    }
677}
678
679/// The state of the capslock key at some point in time
680#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Serialize, Deserialize, Hash, JsonSchema)]
681pub struct Capslock {
682    /// The capslock key is on
683    #[serde(default)]
684    pub on: bool,
685}