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    pub(crate) fn new(inner: Keystroke, display_modifiers: Modifiers, display_key: String) -> Self {
269        KeybindingKeystroke {
270            inner,
271            display_modifiers,
272            display_key,
273        }
274    }
275
276    /// Create a new keybinding keystroke from the given keystroke using the given keyboard mapper.
277    pub fn new_with_mapper(
278        inner: Keystroke,
279        use_key_equivalents: bool,
280        keyboard_mapper: &dyn PlatformKeyboardMapper,
281    ) -> Self {
282        keyboard_mapper.map_key_equivalent(inner, use_key_equivalents)
283    }
284
285    /// Create a new keybinding keystroke from the given keystroke, without any platform-specific mapping.
286    pub const fn from_keystroke(keystroke: Keystroke) -> Self {
287        #[cfg(target_os = "windows")]
288        {
289            let key = keystroke.key.clone();
290            let modifiers = keystroke.modifiers;
291            KeybindingKeystroke {
292                inner: keystroke,
293                display_modifiers: modifiers,
294                display_key: key,
295            }
296        }
297        #[cfg(not(target_os = "windows"))]
298        {
299            KeybindingKeystroke { inner: keystroke }
300        }
301    }
302
303    /// Returns the GPUI representation of the keystroke.
304    pub const fn inner(&self) -> &Keystroke {
305        &self.inner
306    }
307
308    /// Returns the modifiers.
309    ///
310    /// Platform-specific behavior:
311    /// - On macOS and Linux, this modifiers is the same as `inner.modifiers`, which is the GPUI representation of the keystroke.
312    /// - On Windows, this modifiers is the display modifiers, for example, a `ctrl-@` keystroke will have `inner.modifiers` as
313    /// `Modifiers::control()` and `display_modifiers` as `Modifiers::control_shift()`.
314    pub const fn modifiers(&self) -> &Modifiers {
315        #[cfg(target_os = "windows")]
316        {
317            &self.display_modifiers
318        }
319        #[cfg(not(target_os = "windows"))]
320        {
321            &self.inner.modifiers
322        }
323    }
324
325    /// Returns the key.
326    ///
327    /// Platform-specific behavior:
328    /// - On macOS and Linux, this key is the same as `inner.key`, which is the GPUI representation of the keystroke.
329    /// - On Windows, this key is the display key, for example, a `ctrl-@` keystroke will have `inner.key` as `@` and `display_key` as `2`.
330    pub fn key(&self) -> &str {
331        #[cfg(target_os = "windows")]
332        {
333            &self.display_key
334        }
335        #[cfg(not(target_os = "windows"))]
336        {
337            &self.inner.key
338        }
339    }
340
341    /// Sets the modifiers. On Windows this modifies both `inner.modifiers` and `display_modifiers`.
342    pub const fn set_modifiers(&mut self, modifiers: Modifiers) {
343        self.inner.modifiers = modifiers;
344        #[cfg(target_os = "windows")]
345        {
346            self.display_modifiers = modifiers;
347        }
348    }
349
350    /// Sets the key. On Windows this modifies both `inner.key` and `display_key`.
351    pub fn set_key(&mut self, key: String) {
352        #[cfg(target_os = "windows")]
353        {
354            self.display_key = key.clone();
355        }
356        self.inner.key = key;
357    }
358
359    /// Produces a representation of this key that Parse can understand.
360    pub fn unparse(&self) -> String {
361        #[cfg(target_os = "windows")]
362        {
363            unparse(&self.display_modifiers, &self.display_key)
364        }
365        #[cfg(not(target_os = "windows"))]
366        {
367            unparse(&self.inner.modifiers, &self.inner.key)
368        }
369    }
370
371    /// Removes the key_char
372    pub fn remove_key_char(&mut self) {
373        self.inner.key_char = None;
374    }
375}
376
377fn is_printable_key(key: &str) -> bool {
378    !matches!(
379        key,
380        "f1" | "f2"
381            | "f3"
382            | "f4"
383            | "f5"
384            | "f6"
385            | "f7"
386            | "f8"
387            | "f9"
388            | "f10"
389            | "f11"
390            | "f12"
391            | "f13"
392            | "f14"
393            | "f15"
394            | "f16"
395            | "f17"
396            | "f18"
397            | "f19"
398            | "f20"
399            | "f21"
400            | "f22"
401            | "f23"
402            | "f24"
403            | "f25"
404            | "f26"
405            | "f27"
406            | "f28"
407            | "f29"
408            | "f30"
409            | "f31"
410            | "f32"
411            | "f33"
412            | "f34"
413            | "f35"
414            | "backspace"
415            | "delete"
416            | "left"
417            | "right"
418            | "up"
419            | "down"
420            | "pageup"
421            | "pagedown"
422            | "insert"
423            | "home"
424            | "end"
425            | "back"
426            | "forward"
427            | "escape"
428    )
429}
430
431impl std::fmt::Display for Keystroke {
432    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
433        display_modifiers(&self.modifiers, f)?;
434        display_key(&self.key, f)
435    }
436}
437
438impl std::fmt::Display for KeybindingKeystroke {
439    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
440        display_modifiers(self.modifiers(), f)?;
441        display_key(self.key(), f)
442    }
443}
444
445/// The state of the modifier keys at some point in time
446#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Serialize, Deserialize, Hash, JsonSchema)]
447pub struct Modifiers {
448    /// The control key
449    #[serde(default)]
450    pub control: bool,
451
452    /// The alt key
453    /// Sometimes also known as the 'meta' key
454    #[serde(default)]
455    pub alt: bool,
456
457    /// The shift key
458    #[serde(default)]
459    pub shift: bool,
460
461    /// The command key, on macos
462    /// the windows key, on windows
463    /// the super key, on linux
464    #[serde(default)]
465    pub platform: bool,
466
467    /// The function key
468    #[serde(default)]
469    pub function: bool,
470}
471
472impl Modifiers {
473    /// Returns whether any modifier key is pressed.
474    pub const fn modified(&self) -> bool {
475        self.control || self.alt || self.shift || self.platform || self.function
476    }
477
478    /// Whether the semantically 'secondary' modifier key is pressed.
479    ///
480    /// On macOS, this is the command key.
481    /// On Linux and Windows, this is the control key.
482    pub const fn secondary(&self) -> bool {
483        #[cfg(target_os = "macos")]
484        {
485            self.platform
486        }
487
488        #[cfg(not(target_os = "macos"))]
489        {
490            self.control
491        }
492    }
493
494    /// Returns how many modifier keys are pressed.
495    pub const fn number_of_modifiers(&self) -> u8 {
496        self.control as u8
497            + self.alt as u8
498            + self.shift as u8
499            + self.platform as u8
500            + self.function as u8
501    }
502
503    /// Returns [`Modifiers`] with no modifiers.
504    pub fn none() -> Modifiers {
505        Default::default()
506    }
507
508    /// Returns [`Modifiers`] with just the command key.
509    pub fn command() -> Modifiers {
510        Modifiers {
511            platform: true,
512            ..Default::default()
513        }
514    }
515
516    /// A Returns [`Modifiers`] with just the secondary key pressed.
517    pub fn secondary_key() -> Modifiers {
518        #[cfg(target_os = "macos")]
519        {
520            Modifiers {
521                platform: true,
522                ..Default::default()
523            }
524        }
525
526        #[cfg(not(target_os = "macos"))]
527        {
528            Modifiers {
529                control: true,
530                ..Default::default()
531            }
532        }
533    }
534
535    /// Returns [`Modifiers`] with just the windows key.
536    pub fn windows() -> Modifiers {
537        Modifiers {
538            platform: true,
539            ..Default::default()
540        }
541    }
542
543    /// Returns [`Modifiers`] with just the super key.
544    pub fn super_key() -> Modifiers {
545        Modifiers {
546            platform: true,
547            ..Default::default()
548        }
549    }
550
551    /// Returns [`Modifiers`] with just control.
552    pub fn control() -> Modifiers {
553        Modifiers {
554            control: true,
555            ..Default::default()
556        }
557    }
558
559    /// Returns [`Modifiers`] with just alt.
560    pub fn alt() -> Modifiers {
561        Modifiers {
562            alt: true,
563            ..Default::default()
564        }
565    }
566
567    /// Returns [`Modifiers`] with just shift.
568    pub fn shift() -> Modifiers {
569        Modifiers {
570            shift: true,
571            ..Default::default()
572        }
573    }
574
575    /// Returns [`Modifiers`] with command + shift.
576    pub fn command_shift() -> Modifiers {
577        Modifiers {
578            shift: true,
579            platform: true,
580            ..Default::default()
581        }
582    }
583
584    /// Returns [`Modifiers`] with command + shift.
585    pub fn control_shift() -> Modifiers {
586        Modifiers {
587            shift: true,
588            control: true,
589            ..Default::default()
590        }
591    }
592
593    /// Checks if this [`Modifiers`] is a subset of another [`Modifiers`].
594    pub fn is_subset_of(&self, other: &Modifiers) -> bool {
595        (*other & *self) == *self
596    }
597}
598
599impl std::ops::BitOr for Modifiers {
600    type Output = Self;
601
602    fn bitor(mut self, other: Self) -> Self::Output {
603        self |= other;
604        self
605    }
606}
607
608impl std::ops::BitOrAssign for Modifiers {
609    fn bitor_assign(&mut self, other: Self) {
610        self.control |= other.control;
611        self.alt |= other.alt;
612        self.shift |= other.shift;
613        self.platform |= other.platform;
614        self.function |= other.function;
615    }
616}
617
618impl std::ops::BitXor for Modifiers {
619    type Output = Self;
620    fn bitxor(mut self, rhs: Self) -> Self::Output {
621        self ^= rhs;
622        self
623    }
624}
625
626impl std::ops::BitXorAssign for Modifiers {
627    fn bitxor_assign(&mut self, other: Self) {
628        self.control ^= other.control;
629        self.alt ^= other.alt;
630        self.shift ^= other.shift;
631        self.platform ^= other.platform;
632        self.function ^= other.function;
633    }
634}
635
636impl std::ops::BitAnd for Modifiers {
637    type Output = Self;
638    fn bitand(mut self, rhs: Self) -> Self::Output {
639        self &= rhs;
640        self
641    }
642}
643
644impl std::ops::BitAndAssign for Modifiers {
645    fn bitand_assign(&mut self, other: Self) {
646        self.control &= other.control;
647        self.alt &= other.alt;
648        self.shift &= other.shift;
649        self.platform &= other.platform;
650        self.function &= other.function;
651    }
652}
653
654/// The state of the capslock key at some point in time
655#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Serialize, Deserialize, Hash, JsonSchema)]
656pub struct Capslock {
657    /// The capslock key is on
658    #[serde(default)]
659    pub on: bool,
660}
661
662impl AsKeystroke for Keystroke {
663    fn as_keystroke(&self) -> &Keystroke {
664        self
665    }
666}
667
668impl AsKeystroke for KeybindingKeystroke {
669    fn as_keystroke(&self) -> &Keystroke {
670        &self.inner
671    }
672}
673
674fn display_modifiers(modifiers: &Modifiers, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
675    if modifiers.control {
676        #[cfg(target_os = "macos")]
677        f.write_char('^')?;
678
679        #[cfg(not(target_os = "macos"))]
680        write!(f, "ctrl-")?;
681    }
682    if modifiers.alt {
683        #[cfg(target_os = "macos")]
684        f.write_char('โฅ')?;
685
686        #[cfg(not(target_os = "macos"))]
687        write!(f, "alt-")?;
688    }
689    if modifiers.platform {
690        #[cfg(target_os = "macos")]
691        f.write_char('โ')?;
692
693        #[cfg(any(target_os = "linux", target_os = "freebsd"))]
694        f.write_char('โ')?;
695
696        #[cfg(target_os = "windows")]
697        f.write_char('โ')?;
698    }
699    if modifiers.shift {
700        #[cfg(target_os = "macos")]
701        f.write_char('โง')?;
702
703        #[cfg(not(target_os = "macos"))]
704        write!(f, "shift-")?;
705    }
706    Ok(())
707}
708
709fn display_key(key: &str, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
710    let key = match key {
711        #[cfg(target_os = "macos")]
712        "backspace" => 'โซ',
713        #[cfg(target_os = "macos")]
714        "up" => 'โ',
715        #[cfg(target_os = "macos")]
716        "down" => 'โ',
717        #[cfg(target_os = "macos")]
718        "left" => 'โ',
719        #[cfg(target_os = "macos")]
720        "right" => 'โ',
721        #[cfg(target_os = "macos")]
722        "tab" => 'โฅ',
723        #[cfg(target_os = "macos")]
724        "escape" => 'โ',
725        #[cfg(target_os = "macos")]
726        "shift" => 'โง',
727        #[cfg(target_os = "macos")]
728        "control" => 'โ',
729        #[cfg(target_os = "macos")]
730        "alt" => 'โฅ',
731        #[cfg(target_os = "macos")]
732        "platform" => 'โ',
733
734        key if key.len() == 1 => key.chars().next().unwrap().to_ascii_uppercase(),
735        key => return f.write_str(key),
736    };
737    f.write_char(key)
738}
739
740#[inline]
741fn unparse(modifiers: &Modifiers, key: &str) -> String {
742    let mut result = String::new();
743    if modifiers.function {
744        result.push_str("fn-");
745    }
746    if modifiers.control {
747        result.push_str("ctrl-");
748    }
749    if modifiers.alt {
750        result.push_str("alt-");
751    }
752    if modifiers.platform {
753        #[cfg(target_os = "macos")]
754        result.push_str("cmd-");
755
756        #[cfg(any(target_os = "linux", target_os = "freebsd"))]
757        result.push_str("super-");
758
759        #[cfg(target_os = "windows")]
760        result.push_str("win-");
761    }
762    if modifiers.shift {
763        result.push_str("shift-");
764    }
765    result.push_str(&key);
766    result
767}