Skip to main content

gpui/platform/
keystroke.rs

1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3use std::{
4    error::Error,
5    fmt::{Display, Write},
6};
7
8use crate::{OperatingSystem, 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    /// Parses a static keystroke with explicit, platform-independent modifiers.
116    ///
117    /// Syntax: `[ctrl-][alt-][shift-][cmd-][fn-]key[->key_char]`.
118    /// The `key_char` suffix is used for generating test events.
119    /// Use [`Self::parse_with_os`] for dynamic input or the `secondary-` alias.
120    pub fn parse(source: &'static str) -> std::result::Result<Self, InvalidKeystrokeError> {
121        Self::parse_internal(source, None)
122    }
123
124    /// Parses a keystroke, resolving `secondary-` using the supplied host OS.
125    pub fn parse_with_os(
126        source: &str,
127        os: OperatingSystem,
128    ) -> std::result::Result<Self, InvalidKeystrokeError> {
129        Self::parse_internal(source, Some(os))
130    }
131
132    fn parse_internal(
133        source: &str,
134        os: Option<OperatingSystem>,
135    ) -> std::result::Result<Self, InvalidKeystrokeError> {
136        let mut modifiers = Modifiers::none();
137        let mut key = None;
138        let mut key_char = None;
139
140        let mut components = source.split('-').peekable();
141        while let Some(component) = components.next() {
142            if component.eq_ignore_ascii_case("ctrl") {
143                modifiers.control = true;
144                continue;
145            }
146            if component.eq_ignore_ascii_case("alt") {
147                modifiers.alt = true;
148                continue;
149            }
150            if component.eq_ignore_ascii_case("shift") {
151                modifiers.shift = true;
152                continue;
153            }
154            if component.eq_ignore_ascii_case("fn") {
155                modifiers.function = true;
156                continue;
157            }
158            if component.eq_ignore_ascii_case("secondary") {
159                let os = os.ok_or_else(|| InvalidKeystrokeError {
160                    keystroke: source.to_owned(),
161                })?;
162                if os == OperatingSystem::Mac {
163                    modifiers.platform = true;
164                } else {
165                    modifiers.control = true;
166                };
167                continue;
168            }
169
170            let is_platform = component.eq_ignore_ascii_case("cmd")
171                || component.eq_ignore_ascii_case("super")
172                || component.eq_ignore_ascii_case("win");
173
174            if is_platform {
175                modifiers.platform = true;
176                continue;
177            }
178
179            let mut key_str = component.to_string();
180
181            if let Some(next) = components.peek() {
182                if next.is_empty() && source.ends_with('-') {
183                    key = Some(String::from("-"));
184                    break;
185                } else if next.len() > 1 && next.starts_with('>') {
186                    key = Some(key_str);
187                    key_char = Some(String::from(&next[1..]));
188                    components.next();
189                } else {
190                    return Err(InvalidKeystrokeError {
191                        keystroke: source.to_owned(),
192                    });
193                }
194                continue;
195            }
196
197            if component.len() == 1 && component.as_bytes()[0].is_ascii_uppercase() {
198                // Convert to shift + lowercase char
199                modifiers.shift = true;
200                key_str.make_ascii_lowercase();
201            } else {
202                // convert ascii chars to lowercase so that named keys like "tab" and "enter"
203                // are accepted case insensitively and stored how we expect so they are matched properly
204                key_str.make_ascii_lowercase()
205            }
206            key = Some(key_str);
207        }
208
209        // Allow for the user to specify a keystroke modifier as the key itself
210        // This sets the `key` to the modifier, and disables the modifier
211        key = key.or_else(|| {
212            use std::mem;
213            // std::mem::take clears bool incase its true
214            if mem::take(&mut modifiers.shift) {
215                Some("shift".to_string())
216            } else if mem::take(&mut modifiers.control) {
217                Some("control".to_string())
218            } else if mem::take(&mut modifiers.alt) {
219                Some("alt".to_string())
220            } else if mem::take(&mut modifiers.platform) {
221                Some("platform".to_string())
222            } else if mem::take(&mut modifiers.function) {
223                Some("function".to_string())
224            } else {
225                None
226            }
227        });
228
229        let key = key.ok_or_else(|| InvalidKeystrokeError {
230            keystroke: source.to_owned(),
231        })?;
232
233        Ok(Keystroke {
234            modifiers,
235            key,
236            key_char,
237        })
238    }
239
240    /// Serializes this keystroke with the supplied host OS's modifier spelling.
241    pub fn unparse(&self, os: OperatingSystem) -> String {
242        unparse(&self.modifiers, &self.key, os)
243    }
244
245    /// Formats this keystroke using the supplied host OS's key labels.
246    pub fn display(&self, os: OperatingSystem) -> KeystrokeDisplay<'_> {
247        KeystrokeDisplay {
248            modifiers: &self.modifiers,
249            key: &self.key,
250            os,
251        }
252    }
253
254    /// Returns true if this keystroke left
255    /// the ime system in an incomplete state.
256    pub fn is_ime_in_progress(&self) -> bool {
257        self.key_char.is_none()
258            && (is_printable_key(&self.key) || self.key.is_empty())
259            && !(self.modifiers.platform
260                || self.modifiers.control
261                || self.modifiers.function
262                || self.modifiers.alt)
263    }
264
265    /// Returns a new keystroke with the key_char filled.
266    /// This is used for dispatch_keystroke where we want users to
267    /// be able to simulate typing "space", etc.
268    pub fn with_simulated_ime(mut self) -> Self {
269        if self.key_char.is_none()
270            && !self.modifiers.platform
271            && !self.modifiers.control
272            && !self.modifiers.function
273            && !self.modifiers.alt
274        {
275            self.key_char = match self.key.as_str() {
276                "space" => Some(" ".into()),
277                "tab" => Some("\t".into()),
278                "enter" => Some("\n".into()),
279                key if !is_printable_key(key) || key.is_empty() => None,
280                key => {
281                    if self.modifiers.shift {
282                        Some(key.to_uppercase())
283                    } else {
284                        Some(key.into())
285                    }
286                }
287            }
288        }
289        self
290    }
291}
292
293impl KeybindingKeystroke {
294    /// Formats this binding using the supplied host OS's key labels.
295    pub fn display(&self, os: OperatingSystem) -> KeystrokeDisplay<'_> {
296        KeystrokeDisplay {
297            modifiers: self.modifiers(),
298            key: self.key(),
299            os,
300        }
301    }
302
303    #[cfg(target_os = "windows")]
304    #[expect(missing_docs)]
305    pub fn new(inner: Keystroke, display_modifiers: Modifiers, display_key: String) -> Self {
306        KeybindingKeystroke {
307            inner,
308            display_modifiers,
309            display_key,
310        }
311    }
312
313    /// Create a new keybinding keystroke from the given keystroke using the given keyboard mapper.
314    pub fn new_with_mapper(
315        inner: Keystroke,
316        use_key_equivalents: bool,
317        keyboard_mapper: &dyn PlatformKeyboardMapper,
318    ) -> Self {
319        keyboard_mapper.map_key_equivalent(inner, use_key_equivalents)
320    }
321
322    /// Create a new keybinding keystroke from the given keystroke, without any platform-specific mapping.
323    pub fn from_keystroke(keystroke: Keystroke) -> Self {
324        #[cfg(target_os = "windows")]
325        {
326            let key = keystroke.key.clone();
327            let modifiers = keystroke.modifiers;
328            KeybindingKeystroke {
329                inner: keystroke,
330                display_modifiers: modifiers,
331                display_key: key,
332            }
333        }
334        #[cfg(not(target_os = "windows"))]
335        {
336            KeybindingKeystroke { inner: keystroke }
337        }
338    }
339
340    /// Returns the GPUI representation of the keystroke.
341    pub fn inner(&self) -> &Keystroke {
342        &self.inner
343    }
344
345    /// Returns the modifiers.
346    ///
347    /// Platform-specific behavior:
348    /// - On macOS and Linux, this modifiers is the same as `inner.modifiers`, which is the GPUI representation of the keystroke.
349    /// - On Windows, this modifiers is the display modifiers, for example, a `ctrl-@` keystroke will have `inner.modifiers` as
350    /// `Modifiers::control()` and `display_modifiers` as `Modifiers::control_shift()`.
351    pub fn modifiers(&self) -> &Modifiers {
352        #[cfg(target_os = "windows")]
353        {
354            &self.display_modifiers
355        }
356        #[cfg(not(target_os = "windows"))]
357        {
358            &self.inner.modifiers
359        }
360    }
361
362    /// Returns the key.
363    ///
364    /// Platform-specific behavior:
365    /// - On macOS and Linux, this key is the same as `inner.key`, which is the GPUI representation of the keystroke.
366    /// - On Windows, this key is the display key, for example, a `ctrl-@` keystroke will have `inner.key` as `@` and `display_key` as `2`.
367    pub fn key(&self) -> &str {
368        #[cfg(target_os = "windows")]
369        {
370            &self.display_key
371        }
372        #[cfg(not(target_os = "windows"))]
373        {
374            &self.inner.key
375        }
376    }
377
378    /// Sets the modifiers. On Windows this modifies both `inner.modifiers` and `display_modifiers`.
379    pub fn set_modifiers(&mut self, modifiers: Modifiers) {
380        self.inner.modifiers = modifiers;
381        #[cfg(target_os = "windows")]
382        {
383            self.display_modifiers = modifiers;
384        }
385    }
386
387    /// Sets the key. On Windows this modifies both `inner.key` and `display_key`.
388    pub fn set_key(&mut self, key: String) {
389        #[cfg(target_os = "windows")]
390        {
391            self.display_key = key.clone();
392        }
393        self.inner.key = key;
394    }
395
396    /// Serializes this binding with the supplied host OS's modifier spelling.
397    pub fn unparse(&self, os: OperatingSystem) -> String {
398        #[cfg(target_os = "windows")]
399        {
400            unparse(&self.display_modifiers, &self.display_key, os)
401        }
402        #[cfg(not(target_os = "windows"))]
403        {
404            unparse(&self.inner.modifiers, &self.inner.key, os)
405        }
406    }
407
408    /// Removes the key_char
409    pub fn remove_key_char(&mut self) {
410        self.inner.key_char = None;
411    }
412}
413
414fn is_printable_key(key: &str) -> bool {
415    !matches!(
416        key,
417        "f1" | "f2"
418            | "f3"
419            | "f4"
420            | "f5"
421            | "f6"
422            | "f7"
423            | "f8"
424            | "f9"
425            | "f10"
426            | "f11"
427            | "f12"
428            | "f13"
429            | "f14"
430            | "f15"
431            | "f16"
432            | "f17"
433            | "f18"
434            | "f19"
435            | "f20"
436            | "f21"
437            | "f22"
438            | "f23"
439            | "f24"
440            | "f25"
441            | "f26"
442            | "f27"
443            | "f28"
444            | "f29"
445            | "f30"
446            | "f31"
447            | "f32"
448            | "f33"
449            | "f34"
450            | "f35"
451            | "backspace"
452            | "delete"
453            | "left"
454            | "right"
455            | "up"
456            | "down"
457            | "pageup"
458            | "pagedown"
459            | "insert"
460            | "home"
461            | "end"
462            | "back"
463            | "forward"
464            | "escape"
465    )
466}
467
468/// A keystroke formatted with an explicit host operating system.
469pub struct KeystrokeDisplay<'a> {
470    modifiers: &'a Modifiers,
471    key: &'a str,
472    os: OperatingSystem,
473}
474
475impl std::fmt::Display for KeystrokeDisplay<'_> {
476    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
477        display_modifiers(self.modifiers, self.os, f)?;
478        display_key(self.key, self.os, f)
479    }
480}
481
482/// The state of the modifier keys at some point in time
483#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Serialize, Deserialize, Hash, JsonSchema)]
484pub struct Modifiers {
485    /// The control key
486    #[serde(default)]
487    pub control: bool,
488
489    /// The alt key
490    /// Sometimes also known as the 'meta' key
491    #[serde(default)]
492    pub alt: bool,
493
494    /// The shift key
495    #[serde(default)]
496    pub shift: bool,
497
498    /// The command key, on macos
499    /// the windows key, on windows
500    /// the super key, on linux
501    #[serde(default)]
502    pub platform: bool,
503
504    /// The function key
505    #[serde(default)]
506    pub function: bool,
507}
508
509impl Modifiers {
510    /// Returns whether any modifier key is pressed.
511    pub fn modified(&self) -> bool {
512        self.control || self.alt || self.shift || self.platform || self.function
513    }
514
515    /// Whether the semantically 'secondary' modifier key is pressed.
516    ///
517    /// On macOS, this is the command key.
518    /// On Linux and Windows, this is the control key.
519    pub fn secondary(&self, cx: &crate::App) -> bool {
520        if cx.platform_info().is_mac() {
521            self.platform
522        } else {
523            self.control
524        }
525    }
526
527    /// Returns how many modifier keys are pressed.
528    pub fn number_of_modifiers(&self) -> u8 {
529        self.control as u8
530            + self.alt as u8
531            + self.shift as u8
532            + self.platform as u8
533            + self.function as u8
534    }
535
536    /// Returns [`Modifiers`] with no modifiers.
537    pub fn none() -> Modifiers {
538        Default::default()
539    }
540
541    /// Returns [`Modifiers`] with just the command key.
542    pub fn command() -> Modifiers {
543        Modifiers {
544            platform: true,
545            ..Default::default()
546        }
547    }
548
549    /// Returns [`Modifiers`] with just the secondary key pressed.
550    pub fn secondary_key(cx: &crate::App) -> Modifiers {
551        if cx.platform_info().is_mac() {
552            Modifiers {
553                platform: true,
554                ..Default::default()
555            }
556        } else {
557            Modifiers {
558                control: true,
559                ..Default::default()
560            }
561        }
562    }
563
564    /// Returns [`Modifiers`] with just the windows key.
565    pub fn windows() -> Modifiers {
566        Modifiers {
567            platform: true,
568            ..Default::default()
569        }
570    }
571
572    /// Returns [`Modifiers`] with just the super key.
573    pub fn super_key() -> Modifiers {
574        Modifiers {
575            platform: true,
576            ..Default::default()
577        }
578    }
579
580    /// Returns [`Modifiers`] with just control.
581    pub fn control() -> Modifiers {
582        Modifiers {
583            control: true,
584            ..Default::default()
585        }
586    }
587
588    /// Returns [`Modifiers`] with just alt.
589    pub fn alt() -> Modifiers {
590        Modifiers {
591            alt: true,
592            ..Default::default()
593        }
594    }
595
596    /// Returns [`Modifiers`] with just shift.
597    pub fn shift() -> Modifiers {
598        Modifiers {
599            shift: true,
600            ..Default::default()
601        }
602    }
603
604    /// Returns [`Modifiers`] with just function.
605    pub fn function() -> Modifiers {
606        Modifiers {
607            function: true,
608            ..Default::default()
609        }
610    }
611
612    /// Returns [`Modifiers`] with command + shift.
613    pub fn command_shift() -> Modifiers {
614        Modifiers {
615            shift: true,
616            platform: true,
617            ..Default::default()
618        }
619    }
620
621    /// Returns [`Modifiers`] with command + shift.
622    pub fn control_shift() -> Modifiers {
623        Modifiers {
624            shift: true,
625            control: true,
626            ..Default::default()
627        }
628    }
629
630    /// Checks if this [`Modifiers`] is a subset of another [`Modifiers`].
631    pub fn is_subset_of(&self, other: &Modifiers) -> bool {
632        (*other & *self) == *self
633    }
634}
635
636impl std::ops::BitOr for Modifiers {
637    type Output = Self;
638
639    fn bitor(mut self, other: Self) -> Self::Output {
640        self |= other;
641        self
642    }
643}
644
645impl std::ops::BitOrAssign for Modifiers {
646    fn bitor_assign(&mut self, other: Self) {
647        self.control |= other.control;
648        self.alt |= other.alt;
649        self.shift |= other.shift;
650        self.platform |= other.platform;
651        self.function |= other.function;
652    }
653}
654
655impl std::ops::BitXor for Modifiers {
656    type Output = Self;
657    fn bitxor(mut self, rhs: Self) -> Self::Output {
658        self ^= rhs;
659        self
660    }
661}
662
663impl std::ops::BitXorAssign for Modifiers {
664    fn bitxor_assign(&mut self, other: Self) {
665        self.control ^= other.control;
666        self.alt ^= other.alt;
667        self.shift ^= other.shift;
668        self.platform ^= other.platform;
669        self.function ^= other.function;
670    }
671}
672
673impl std::ops::BitAnd for Modifiers {
674    type Output = Self;
675    fn bitand(mut self, rhs: Self) -> Self::Output {
676        self &= rhs;
677        self
678    }
679}
680
681impl std::ops::BitAndAssign for Modifiers {
682    fn bitand_assign(&mut self, other: Self) {
683        self.control &= other.control;
684        self.alt &= other.alt;
685        self.shift &= other.shift;
686        self.platform &= other.platform;
687        self.function &= other.function;
688    }
689}
690
691/// The state of the capslock key at some point in time
692#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Serialize, Deserialize, Hash, JsonSchema)]
693pub struct Capslock {
694    /// The capslock key is on
695    #[serde(default)]
696    pub on: bool,
697}
698
699impl AsKeystroke for Keystroke {
700    fn as_keystroke(&self) -> &Keystroke {
701        self
702    }
703}
704
705impl AsKeystroke for KeybindingKeystroke {
706    fn as_keystroke(&self) -> &Keystroke {
707        &self.inner
708    }
709}
710
711impl<T: AsKeystroke + ?Sized> AsKeystroke for &T {
712    fn as_keystroke(&self) -> &Keystroke {
713        (**self).as_keystroke()
714    }
715}
716
717fn display_modifiers(
718    modifiers: &Modifiers,
719    os: OperatingSystem,
720    f: &mut std::fmt::Formatter<'_>,
721) -> std::fmt::Result {
722    if modifiers.control {
723        f.write_str(if os == OperatingSystem::Mac {
724            "^"
725        } else {
726            "ctrl-"
727        })?;
728    }
729    if modifiers.alt {
730        f.write_str(if os == OperatingSystem::Mac {
731            "⌥"
732        } else {
733            "alt-"
734        })?;
735    }
736    if modifiers.platform {
737        f.write_str(match os {
738            OperatingSystem::Mac => "⌘",
739            OperatingSystem::Windows => "⊞",
740            OperatingSystem::Linux | OperatingSystem::FreeBsd => "❖",
741            OperatingSystem::Ios | OperatingSystem::Android | OperatingSystem::Unknown => "cmd-",
742        })?;
743    }
744    if modifiers.shift {
745        f.write_str(if os == OperatingSystem::Mac {
746            "⇧"
747        } else {
748            "shift-"
749        })?;
750    }
751    Ok(())
752}
753
754fn display_key(
755    key: &str,
756    os: OperatingSystem,
757    f: &mut std::fmt::Formatter<'_>,
758) -> std::fmt::Result {
759    let key = match key {
760        "backspace" if os == OperatingSystem::Mac => '⌫',
761        "up" if os == OperatingSystem::Mac => '↑',
762        "down" if os == OperatingSystem::Mac => '↓',
763        "left" if os == OperatingSystem::Mac => '←',
764        "right" if os == OperatingSystem::Mac => '→',
765        "tab" if os == OperatingSystem::Mac => '⇥',
766        "escape" if os == OperatingSystem::Mac => '⎋',
767        "shift" if os == OperatingSystem::Mac => '⇧',
768        "control" if os == OperatingSystem::Mac => '⌃',
769        "alt" if os == OperatingSystem::Mac => '⌥',
770        "platform" if os == OperatingSystem::Mac => '⌘',
771
772        key if key.len() == 1 => key.chars().next().unwrap().to_ascii_uppercase(),
773        key => return f.write_str(key),
774    };
775    f.write_char(key)
776}
777
778#[inline]
779fn unparse(modifiers: &Modifiers, key: &str, os: OperatingSystem) -> String {
780    let mut result = String::new();
781    if modifiers.function {
782        result.push_str("fn-");
783    }
784    if modifiers.control {
785        result.push_str("ctrl-");
786    }
787    if modifiers.alt {
788        result.push_str("alt-");
789    }
790    if modifiers.platform {
791        result.push_str(match os {
792            OperatingSystem::Windows => "win-",
793            OperatingSystem::Linux
794            | OperatingSystem::FreeBsd
795            | OperatingSystem::Android
796            | OperatingSystem::Unknown => "super-",
797            OperatingSystem::Mac | OperatingSystem::Ios => "cmd-",
798        });
799    }
800    if modifiers.shift {
801        result.push_str("shift-");
802    }
803    result.push_str(&key);
804    result
805}