keystroke.rs

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