Skip to main content

scooter_core/
keyboard.rs

1// This code is copied from Helix: https://github.com/helix-editor/helix/blob/d79cce4e/helix-view/src/keyboard.rs
2use anyhow::anyhow;
3use bitflags::bitflags;
4use serde::Serialize;
5
6bitflags! {
7    /// Represents key modifiers (shift, control, alt).
8    #[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Hash)]
9    pub struct KeyModifiers: u8 {
10        const SHIFT = 0b0000_0001;
11        const CONTROL = 0b0000_0010;
12        const ALT = 0b0000_0100;
13        const SUPER = 0b0000_1000;
14        const HYPER = 0b0001_0000;
15        const META = 0b0010_0000;
16        const NONE = 0b0000_0000;
17    }
18}
19
20#[cfg(feature = "term")]
21impl From<KeyModifiers> for crossterm::event::KeyModifiers {
22    fn from(key_modifiers: KeyModifiers) -> Self {
23        use crossterm::event::KeyModifiers as CKeyModifiers;
24
25        let mut result = CKeyModifiers::NONE;
26
27        if key_modifiers.contains(KeyModifiers::SHIFT) {
28            result.insert(CKeyModifiers::SHIFT);
29        }
30        if key_modifiers.contains(KeyModifiers::CONTROL) {
31            result.insert(CKeyModifiers::CONTROL);
32        }
33        if key_modifiers.contains(KeyModifiers::ALT) {
34            result.insert(CKeyModifiers::ALT);
35        }
36        if key_modifiers.contains(KeyModifiers::SUPER) {
37            result.insert(CKeyModifiers::SUPER);
38        }
39
40        result
41    }
42}
43
44#[cfg(feature = "term")]
45impl From<crossterm::event::KeyModifiers> for KeyModifiers {
46    fn from(val: crossterm::event::KeyModifiers) -> Self {
47        use crossterm::event::KeyModifiers as CKeyModifiers;
48
49        let mut result = KeyModifiers::NONE;
50
51        if val.contains(CKeyModifiers::SHIFT) {
52            result.insert(KeyModifiers::SHIFT);
53        }
54        if val.contains(CKeyModifiers::CONTROL) {
55            result.insert(KeyModifiers::CONTROL);
56        }
57        if val.contains(CKeyModifiers::ALT) {
58            result.insert(KeyModifiers::ALT);
59        }
60        if val.contains(CKeyModifiers::SUPER) {
61            result.insert(KeyModifiers::SUPER);
62        }
63
64        result
65    }
66}
67
68/// Represents a media key (as part of [`KeyCode::Media`]).
69#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Hash)]
70pub enum MediaKeyCode {
71    /// Play media key.
72    Play,
73    /// Pause media key.
74    Pause,
75    /// Play/Pause media key.
76    PlayPause,
77    /// Reverse media key.
78    Reverse,
79    /// Stop media key.
80    Stop,
81    /// Fast-forward media key.
82    FastForward,
83    /// Rewind media key.
84    Rewind,
85    /// Next-track media key.
86    TrackNext,
87    /// Previous-track media key.
88    TrackPrevious,
89    /// Record media key.
90    Record,
91    /// Lower-volume media key.
92    LowerVolume,
93    /// Raise-volume media key.
94    RaiseVolume,
95    /// Mute media key.
96    MuteVolume,
97}
98
99#[cfg(feature = "term")]
100impl From<MediaKeyCode> for crossterm::event::MediaKeyCode {
101    fn from(media_key_code: MediaKeyCode) -> Self {
102        use crossterm::event::MediaKeyCode as CMediaKeyCode;
103
104        match media_key_code {
105            MediaKeyCode::Play => CMediaKeyCode::Play,
106            MediaKeyCode::Pause => CMediaKeyCode::Pause,
107            MediaKeyCode::PlayPause => CMediaKeyCode::PlayPause,
108            MediaKeyCode::Reverse => CMediaKeyCode::Reverse,
109            MediaKeyCode::Stop => CMediaKeyCode::Stop,
110            MediaKeyCode::FastForward => CMediaKeyCode::FastForward,
111            MediaKeyCode::Rewind => CMediaKeyCode::Rewind,
112            MediaKeyCode::TrackNext => CMediaKeyCode::TrackNext,
113            MediaKeyCode::TrackPrevious => CMediaKeyCode::TrackPrevious,
114            MediaKeyCode::Record => CMediaKeyCode::Record,
115            MediaKeyCode::LowerVolume => CMediaKeyCode::LowerVolume,
116            MediaKeyCode::RaiseVolume => CMediaKeyCode::RaiseVolume,
117            MediaKeyCode::MuteVolume => CMediaKeyCode::MuteVolume,
118        }
119    }
120}
121
122#[cfg(feature = "term")]
123impl From<crossterm::event::MediaKeyCode> for MediaKeyCode {
124    fn from(val: crossterm::event::MediaKeyCode) -> Self {
125        use crossterm::event::MediaKeyCode as CMediaKeyCode;
126
127        match val {
128            CMediaKeyCode::Play => MediaKeyCode::Play,
129            CMediaKeyCode::Pause => MediaKeyCode::Pause,
130            CMediaKeyCode::PlayPause => MediaKeyCode::PlayPause,
131            CMediaKeyCode::Reverse => MediaKeyCode::Reverse,
132            CMediaKeyCode::Stop => MediaKeyCode::Stop,
133            CMediaKeyCode::FastForward => MediaKeyCode::FastForward,
134            CMediaKeyCode::Rewind => MediaKeyCode::Rewind,
135            CMediaKeyCode::TrackNext => MediaKeyCode::TrackNext,
136            CMediaKeyCode::TrackPrevious => MediaKeyCode::TrackPrevious,
137            CMediaKeyCode::Record => MediaKeyCode::Record,
138            CMediaKeyCode::LowerVolume => MediaKeyCode::LowerVolume,
139            CMediaKeyCode::RaiseVolume => MediaKeyCode::RaiseVolume,
140            CMediaKeyCode::MuteVolume => MediaKeyCode::MuteVolume,
141        }
142    }
143}
144
145/// Represents a media key (as part of [`KeyCode::Modifier`]).
146#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Hash)]
147pub enum ModifierKeyCode {
148    /// Left Shift key.
149    LeftShift,
150    /// Left Control key.
151    LeftControl,
152    /// Left Alt key.
153    LeftAlt,
154    /// Left Super key.
155    LeftSuper,
156    /// Left Hyper key.
157    LeftHyper,
158    /// Left Meta key.
159    LeftMeta,
160    /// Right Shift key.
161    RightShift,
162    /// Right Control key.
163    RightControl,
164    /// Right Alt key.
165    RightAlt,
166    /// Right Super key.
167    RightSuper,
168    /// Right Hyper key.
169    RightHyper,
170    /// Right Meta key.
171    RightMeta,
172    /// Iso Level3 Shift key.
173    IsoLevel3Shift,
174    /// Iso Level5 Shift key.
175    IsoLevel5Shift,
176}
177
178#[cfg(feature = "term")]
179impl From<ModifierKeyCode> for crossterm::event::ModifierKeyCode {
180    fn from(modifier_key_code: ModifierKeyCode) -> Self {
181        use crossterm::event::ModifierKeyCode as CModifierKeyCode;
182
183        match modifier_key_code {
184            ModifierKeyCode::LeftShift => CModifierKeyCode::LeftShift,
185            ModifierKeyCode::LeftControl => CModifierKeyCode::LeftControl,
186            ModifierKeyCode::LeftAlt => CModifierKeyCode::LeftAlt,
187            ModifierKeyCode::LeftSuper => CModifierKeyCode::LeftSuper,
188            ModifierKeyCode::LeftHyper => CModifierKeyCode::LeftHyper,
189            ModifierKeyCode::LeftMeta => CModifierKeyCode::LeftMeta,
190            ModifierKeyCode::RightShift => CModifierKeyCode::RightShift,
191            ModifierKeyCode::RightControl => CModifierKeyCode::RightControl,
192            ModifierKeyCode::RightAlt => CModifierKeyCode::RightAlt,
193            ModifierKeyCode::RightSuper => CModifierKeyCode::RightSuper,
194            ModifierKeyCode::RightHyper => CModifierKeyCode::RightHyper,
195            ModifierKeyCode::RightMeta => CModifierKeyCode::RightMeta,
196            ModifierKeyCode::IsoLevel3Shift => CModifierKeyCode::IsoLevel3Shift,
197            ModifierKeyCode::IsoLevel5Shift => CModifierKeyCode::IsoLevel5Shift,
198        }
199    }
200}
201
202#[cfg(feature = "term")]
203impl From<crossterm::event::ModifierKeyCode> for ModifierKeyCode {
204    fn from(val: crossterm::event::ModifierKeyCode) -> Self {
205        use crossterm::event::ModifierKeyCode as CModifierKeyCode;
206
207        match val {
208            CModifierKeyCode::LeftShift => ModifierKeyCode::LeftShift,
209            CModifierKeyCode::LeftControl => ModifierKeyCode::LeftControl,
210            CModifierKeyCode::LeftAlt => ModifierKeyCode::LeftAlt,
211            CModifierKeyCode::LeftSuper => ModifierKeyCode::LeftSuper,
212            CModifierKeyCode::LeftHyper => ModifierKeyCode::LeftHyper,
213            CModifierKeyCode::LeftMeta => ModifierKeyCode::LeftMeta,
214            CModifierKeyCode::RightShift => ModifierKeyCode::RightShift,
215            CModifierKeyCode::RightControl => ModifierKeyCode::RightControl,
216            CModifierKeyCode::RightAlt => ModifierKeyCode::RightAlt,
217            CModifierKeyCode::RightSuper => ModifierKeyCode::RightSuper,
218            CModifierKeyCode::RightHyper => ModifierKeyCode::RightHyper,
219            CModifierKeyCode::RightMeta => ModifierKeyCode::RightMeta,
220            CModifierKeyCode::IsoLevel3Shift => ModifierKeyCode::IsoLevel3Shift,
221            CModifierKeyCode::IsoLevel5Shift => ModifierKeyCode::IsoLevel5Shift,
222        }
223    }
224}
225
226/// Represents a key.
227#[allow(clippy::doc_markdown)]
228#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Hash)]
229pub enum KeyCode {
230    /// Backspace key.
231    Backspace,
232    /// Enter key.
233    Enter,
234    /// Left arrow key.
235    Left,
236    /// Right arrow key.
237    Right,
238    /// Up arrow key.
239    Up,
240    /// Down arrow key.
241    Down,
242    /// Home key.
243    Home,
244    /// End key.
245    End,
246    /// Page up key.
247    PageUp,
248    /// Page down key.
249    PageDown,
250    /// Tab key.
251    Tab,
252    /// Delete key.
253    Delete,
254    /// Insert key.
255    Insert,
256    /// F key.
257    ///
258    /// `KeyCode::F(1)` represents F1 key, etc.
259    F(u8),
260    /// A character.
261    ///
262    /// `KeyCode::Char('c')` represents `c` character, etc.
263    Char(char),
264    /// Null.
265    Null,
266    /// Escape key.
267    Esc,
268    /// CapsLock key.
269    CapsLock,
270    /// ScrollLock key.
271    ScrollLock,
272    /// NumLock key.
273    NumLock,
274    /// PrintScreen key.
275    PrintScreen,
276    /// Pause key.
277    Pause,
278    /// Menu key.
279    Menu,
280    /// KeypadBegin key.
281    KeypadBegin,
282    /// A media key.
283    Media(MediaKeyCode),
284    /// A modifier key.
285    Modifier(ModifierKeyCode),
286}
287
288#[cfg(feature = "term")]
289impl From<KeyCode> for crossterm::event::KeyCode {
290    fn from(key_code: KeyCode) -> Self {
291        use crossterm::event::KeyCode as CKeyCode;
292
293        match key_code {
294            KeyCode::Backspace => CKeyCode::Backspace,
295            KeyCode::Enter => CKeyCode::Enter,
296            KeyCode::Left => CKeyCode::Left,
297            KeyCode::Right => CKeyCode::Right,
298            KeyCode::Up => CKeyCode::Up,
299            KeyCode::Down => CKeyCode::Down,
300            KeyCode::Home => CKeyCode::Home,
301            KeyCode::End => CKeyCode::End,
302            KeyCode::PageUp => CKeyCode::PageUp,
303            KeyCode::PageDown => CKeyCode::PageDown,
304            KeyCode::Tab => CKeyCode::Tab,
305            KeyCode::Delete => CKeyCode::Delete,
306            KeyCode::Insert => CKeyCode::Insert,
307            KeyCode::F(f_number) => CKeyCode::F(f_number),
308            KeyCode::Char(character) => CKeyCode::Char(character),
309            KeyCode::Null => CKeyCode::Null,
310            KeyCode::Esc => CKeyCode::Esc,
311            KeyCode::CapsLock => CKeyCode::CapsLock,
312            KeyCode::ScrollLock => CKeyCode::ScrollLock,
313            KeyCode::NumLock => CKeyCode::NumLock,
314            KeyCode::PrintScreen => CKeyCode::PrintScreen,
315            KeyCode::Pause => CKeyCode::Pause,
316            KeyCode::Menu => CKeyCode::Menu,
317            KeyCode::KeypadBegin => CKeyCode::KeypadBegin,
318            KeyCode::Media(media_key_code) => CKeyCode::Media(media_key_code.into()),
319            KeyCode::Modifier(modifier_key_code) => CKeyCode::Modifier(modifier_key_code.into()),
320        }
321    }
322}
323
324#[cfg(feature = "term")]
325impl From<crossterm::event::KeyCode> for KeyCode {
326    fn from(val: crossterm::event::KeyCode) -> Self {
327        use crossterm::event::KeyCode as CKeyCode;
328
329        match val {
330            CKeyCode::Backspace => KeyCode::Backspace,
331            CKeyCode::Enter => KeyCode::Enter,
332            CKeyCode::Left => KeyCode::Left,
333            CKeyCode::Right => KeyCode::Right,
334            CKeyCode::Up => KeyCode::Up,
335            CKeyCode::Down => KeyCode::Down,
336            CKeyCode::Home => KeyCode::Home,
337            CKeyCode::End => KeyCode::End,
338            CKeyCode::PageUp => KeyCode::PageUp,
339            CKeyCode::PageDown => KeyCode::PageDown,
340            CKeyCode::Tab => KeyCode::Tab,
341            CKeyCode::BackTab => unreachable!("BackTab should have been handled on KeyEvent level"),
342            CKeyCode::Delete => KeyCode::Delete,
343            CKeyCode::Insert => KeyCode::Insert,
344            CKeyCode::F(f_number) => KeyCode::F(f_number),
345            CKeyCode::Char(character) => KeyCode::Char(character),
346            CKeyCode::Null => KeyCode::Null,
347            CKeyCode::Esc => KeyCode::Esc,
348            CKeyCode::CapsLock => KeyCode::CapsLock,
349            CKeyCode::ScrollLock => KeyCode::ScrollLock,
350            CKeyCode::NumLock => KeyCode::NumLock,
351            CKeyCode::PrintScreen => KeyCode::PrintScreen,
352            CKeyCode::Pause => KeyCode::Pause,
353            CKeyCode::Menu => KeyCode::Menu,
354            CKeyCode::KeypadBegin => KeyCode::KeypadBegin,
355            CKeyCode::Media(media_key_code) => KeyCode::Media(media_key_code.into()),
356            CKeyCode::Modifier(modifier_key_code) => KeyCode::Modifier(modifier_key_code.into()),
357        }
358    }
359}
360
361// This code is copied from Helix: https://github.com/helix-editor/helix/blob/d79cce4e/helix-view/src/input.rs
362
363pub(crate) mod keys {
364    pub(crate) const BACKSPACE: &str = "backspace";
365    pub(crate) const ENTER: &str = "ret";
366    pub(crate) const ENTER2: &str = "enter";
367    pub(crate) const LEFT: &str = "left";
368    pub(crate) const RIGHT: &str = "right";
369    pub(crate) const UP: &str = "up";
370    pub(crate) const DOWN: &str = "down";
371    pub(crate) const HOME: &str = "home";
372    pub(crate) const END: &str = "end";
373    pub(crate) const PAGEUP: &str = "pageup";
374    pub(crate) const PAGEDOWN: &str = "pagedown";
375    pub(crate) const TAB: &str = "tab";
376    pub(crate) const DELETE: &str = "del";
377    pub(crate) const INSERT: &str = "ins";
378    pub(crate) const NULL: &str = "null";
379    pub(crate) const ESC: &str = "esc";
380    pub(crate) const SPACE: &str = "space";
381    pub(crate) const MINUS: &str = "minus"; // DOCS: ignore
382    pub(crate) const LESS_THAN: &str = "lt"; // DOCS: ignore
383    pub(crate) const GREATER_THAN: &str = "gt"; // DOCS: ignore
384    pub(crate) const CAPS_LOCK: &str = "capslock"; // DOCS: ignore
385    pub(crate) const SCROLL_LOCK: &str = "scrolllock"; // DOCS: ignore
386    pub(crate) const NUM_LOCK: &str = "numlock"; // DOCS: ignore
387    pub(crate) const PRINT_SCREEN: &str = "printscreen"; // DOCS: ignore
388    pub(crate) const PAUSE: &str = "pause"; // DOCS: ignore
389    pub(crate) const MENU: &str = "menu"; // DOCS: ignore
390    pub(crate) const KEYPAD_BEGIN: &str = "keypadbegin"; // DOCS: ignore
391    pub(crate) const PLAY: &str = "play"; // DOCS: ignore
392    pub(crate) const PAUSE_MEDIA: &str = "pausemedia"; // DOCS: ignore
393    pub(crate) const PLAY_PAUSE: &str = "playpause"; // DOCS: ignore
394    pub(crate) const REVERSE: &str = "reverse"; // DOCS: ignore
395    pub(crate) const STOP: &str = "stop"; // DOCS: ignore
396    pub(crate) const FAST_FORWARD: &str = "fastforward"; // DOCS: ignore
397    pub(crate) const REWIND: &str = "rewind"; // DOCS: ignore
398    pub(crate) const TRACK_NEXT: &str = "tracknext"; // DOCS: ignore
399    pub(crate) const TRACK_PREVIOUS: &str = "trackprevious"; // DOCS: ignore
400    pub(crate) const RECORD: &str = "record"; // DOCS: ignore
401    pub(crate) const LOWER_VOLUME: &str = "lowervolume"; // DOCS: ignore
402    pub(crate) const RAISE_VOLUME: &str = "raisevolume"; // DOCS: ignore
403    pub(crate) const MUTE_VOLUME: &str = "mutevolume"; // DOCS: ignore
404    pub(crate) const LEFT_SHIFT: &str = "leftshift"; // DOCS: ignore
405    pub(crate) const LEFT_CONTROL: &str = "leftcontrol"; // DOCS: ignore
406    pub(crate) const LEFT_ALT: &str = "leftalt"; // DOCS: ignore
407    pub(crate) const LEFT_SUPER: &str = "leftsuper"; // DOCS: ignore
408    pub(crate) const LEFT_HYPER: &str = "lefthyper"; // DOCS: ignore
409    pub(crate) const LEFT_META: &str = "leftmeta"; // DOCS: ignore
410    pub(crate) const RIGHT_SHIFT: &str = "rightshift"; // DOCS: ignore
411    pub(crate) const RIGHT_CONTROL: &str = "rightcontrol"; // DOCS: ignore
412    pub(crate) const RIGHT_ALT: &str = "rightalt"; // DOCS: ignore
413    pub(crate) const RIGHT_SUPER: &str = "rightsuper"; // DOCS: ignore
414    pub(crate) const RIGHT_HYPER: &str = "righthyper"; // DOCS: ignore
415    pub(crate) const RIGHT_META: &str = "rightmeta"; // DOCS: ignore
416    pub(crate) const ISO_LEVEL_3_SHIFT: &str = "isolevel3shift"; // DOCS: ignore
417    pub(crate) const ISO_LEVEL_5_SHIFT: &str = "isolevel5shift"; // DOCS: ignore
418}
419
420/// Represents a key event.
421// We use a newtype here because we want to customize Deserialize and Display.
422#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
423pub struct KeyEvent {
424    pub code: KeyCode,
425    pub modifiers: KeyModifiers,
426    // TODO: crossterm now supports kind & state if terminal supports kitty's extended protocol
427}
428
429impl KeyEvent {
430    pub fn new(code: KeyCode, modifiers: KeyModifiers) -> Self {
431        Self { code, modifiers }
432    }
433
434    /// Canonicalize the key event by removing the SHIFT modifier from character keys.
435    /// This is necessary because terminals send uppercase characters with the SHIFT modifier,
436    /// so e.g. a press of `G` arrives as `S-G` from crossterm. However, we just want `G`. This
437    /// matches the `FromStr for KeyEvent` implementation used when parsing config.
438    pub fn canonicalize(&mut self) {
439        if let KeyCode::Char(_) = self.code {
440            self.modifiers.remove(KeyModifiers::SHIFT);
441        }
442    }
443}
444
445impl Serialize for KeyEvent {
446    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
447    where
448        S: serde::Serializer,
449    {
450        serializer.serialize_str(&self.to_string())
451    }
452}
453
454const MODIFIERS: [(KeyModifiers, &str); 3] = [
455    (KeyModifiers::SHIFT, "S-"),
456    (KeyModifiers::CONTROL, "C-"),
457    (KeyModifiers::ALT, "A-"),
458];
459
460impl std::fmt::Display for KeyEvent {
461    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
462        let mut result = String::new();
463
464        for (modifier, str) in MODIFIERS {
465            if self.modifiers.contains(modifier) {
466                result.push_str(str);
467            }
468        }
469
470        match self.code {
471            KeyCode::Backspace => result.push_str(keys::BACKSPACE),
472            KeyCode::Enter => result.push_str(keys::ENTER2),
473            KeyCode::Left => result.push_str(keys::LEFT),
474            KeyCode::Right => result.push_str(keys::RIGHT),
475            KeyCode::Up => result.push_str(keys::UP),
476            KeyCode::Down => result.push_str(keys::DOWN),
477            KeyCode::Home => result.push_str(keys::HOME),
478            KeyCode::End => result.push_str(keys::END),
479            KeyCode::PageUp => result.push_str(keys::PAGEUP),
480            KeyCode::PageDown => result.push_str(keys::PAGEDOWN),
481            KeyCode::Tab => result.push_str(keys::TAB),
482            KeyCode::Delete => result.push_str(keys::DELETE),
483            KeyCode::Insert => result.push_str(keys::INSERT),
484            KeyCode::Null => result.push_str(keys::NULL),
485            KeyCode::Esc => result.push_str(keys::ESC),
486            KeyCode::CapsLock => result.push_str(keys::CAPS_LOCK),
487            KeyCode::ScrollLock => result.push_str(keys::SCROLL_LOCK),
488            KeyCode::NumLock => result.push_str(keys::NUM_LOCK),
489            KeyCode::PrintScreen => result.push_str(keys::PRINT_SCREEN),
490            KeyCode::Pause => result.push_str(keys::PAUSE),
491            KeyCode::Menu => result.push_str(keys::MENU),
492            KeyCode::KeypadBegin => result.push_str(keys::KEYPAD_BEGIN),
493            KeyCode::Media(media) => {
494                use crate::keyboard::MediaKeyCode;
495                match media {
496                    MediaKeyCode::Play => result.push_str(keys::PLAY),
497                    MediaKeyCode::Pause => result.push_str(keys::PAUSE_MEDIA),
498                    MediaKeyCode::PlayPause => result.push_str(keys::PLAY_PAUSE),
499                    MediaKeyCode::Reverse => result.push_str(keys::REVERSE),
500                    MediaKeyCode::Stop => result.push_str(keys::STOP),
501                    MediaKeyCode::FastForward => result.push_str(keys::FAST_FORWARD),
502                    MediaKeyCode::Rewind => result.push_str(keys::REWIND),
503                    MediaKeyCode::TrackNext => result.push_str(keys::TRACK_NEXT),
504                    MediaKeyCode::TrackPrevious => result.push_str(keys::TRACK_PREVIOUS),
505                    MediaKeyCode::Record => result.push_str(keys::RECORD),
506                    MediaKeyCode::LowerVolume => result.push_str(keys::LOWER_VOLUME),
507                    MediaKeyCode::RaiseVolume => result.push_str(keys::RAISE_VOLUME),
508                    MediaKeyCode::MuteVolume => result.push_str(keys::MUTE_VOLUME),
509                }
510            }
511            KeyCode::Modifier(modifier) => {
512                use crate::keyboard::ModifierKeyCode;
513                match modifier {
514                    ModifierKeyCode::LeftShift => result.push_str(keys::LEFT_SHIFT),
515                    ModifierKeyCode::LeftControl => result.push_str(keys::LEFT_CONTROL),
516                    ModifierKeyCode::LeftAlt => result.push_str(keys::LEFT_ALT),
517                    ModifierKeyCode::LeftSuper => result.push_str(keys::LEFT_SUPER),
518                    ModifierKeyCode::LeftHyper => result.push_str(keys::LEFT_HYPER),
519                    ModifierKeyCode::LeftMeta => result.push_str(keys::LEFT_META),
520                    ModifierKeyCode::RightShift => result.push_str(keys::RIGHT_SHIFT),
521                    ModifierKeyCode::RightControl => result.push_str(keys::RIGHT_CONTROL),
522                    ModifierKeyCode::RightAlt => result.push_str(keys::RIGHT_ALT),
523                    ModifierKeyCode::RightSuper => result.push_str(keys::RIGHT_SUPER),
524                    ModifierKeyCode::RightHyper => result.push_str(keys::RIGHT_HYPER),
525                    ModifierKeyCode::RightMeta => result.push_str(keys::RIGHT_META),
526                    ModifierKeyCode::IsoLevel3Shift => result.push_str(keys::ISO_LEVEL_3_SHIFT),
527                    ModifierKeyCode::IsoLevel5Shift => result.push_str(keys::ISO_LEVEL_5_SHIFT),
528                }
529            }
530            KeyCode::Char(' ') => result.push_str(keys::SPACE),
531            KeyCode::Char('<') => result.push_str(keys::LESS_THAN),
532            KeyCode::Char('>') => result.push_str(keys::GREATER_THAN),
533            KeyCode::Char('-') => result.push_str(keys::MINUS),
534            KeyCode::Char(c) => result.push(c),
535            KeyCode::F(n) => {
536                use std::fmt::Write;
537                write!(&mut result, "F{n}").unwrap();
538            }
539        }
540
541        write!(f, "{result}")
542    }
543}
544
545impl std::str::FromStr for KeyEvent {
546    type Err = anyhow::Error;
547
548    #[allow(clippy::too_many_lines)]
549    fn from_str(s: &str) -> Result<Self, Self::Err> {
550        let mut tokens: Vec<_> = s.split('-').collect();
551        let mut code = match tokens.pop().ok_or_else(|| anyhow!("Missing key code"))? {
552            keys::BACKSPACE => KeyCode::Backspace,
553            keys::ENTER | keys::ENTER2 => KeyCode::Enter,
554            keys::LEFT => KeyCode::Left,
555            keys::RIGHT => KeyCode::Right,
556            keys::UP => KeyCode::Up,
557            keys::DOWN => KeyCode::Down,
558            keys::HOME => KeyCode::Home,
559            keys::END => KeyCode::End,
560            keys::PAGEUP => KeyCode::PageUp,
561            keys::PAGEDOWN => KeyCode::PageDown,
562            keys::TAB => KeyCode::Tab,
563            keys::DELETE => KeyCode::Delete,
564            keys::INSERT => KeyCode::Insert,
565            keys::NULL => KeyCode::Null,
566            keys::ESC => KeyCode::Esc,
567            keys::SPACE => KeyCode::Char(' '),
568            keys::MINUS => KeyCode::Char('-'),
569            keys::LESS_THAN => KeyCode::Char('<'),
570            keys::GREATER_THAN => KeyCode::Char('>'),
571            keys::CAPS_LOCK => KeyCode::CapsLock,
572            keys::SCROLL_LOCK => KeyCode::ScrollLock,
573            keys::NUM_LOCK => KeyCode::NumLock,
574            keys::PRINT_SCREEN => KeyCode::PrintScreen,
575            keys::PAUSE => KeyCode::Pause,
576            keys::MENU => KeyCode::Menu,
577            keys::KEYPAD_BEGIN => KeyCode::KeypadBegin,
578            keys::PLAY => KeyCode::Media(MediaKeyCode::Play),
579            keys::PAUSE_MEDIA => KeyCode::Media(MediaKeyCode::Pause),
580            keys::PLAY_PAUSE => KeyCode::Media(MediaKeyCode::PlayPause),
581            keys::STOP => KeyCode::Media(MediaKeyCode::Stop),
582            keys::REVERSE => KeyCode::Media(MediaKeyCode::Reverse),
583            keys::FAST_FORWARD => KeyCode::Media(MediaKeyCode::FastForward),
584            keys::REWIND => KeyCode::Media(MediaKeyCode::Rewind),
585            keys::TRACK_NEXT => KeyCode::Media(MediaKeyCode::TrackNext),
586            keys::TRACK_PREVIOUS => KeyCode::Media(MediaKeyCode::TrackPrevious),
587            keys::RECORD => KeyCode::Media(MediaKeyCode::Record),
588            keys::LOWER_VOLUME => KeyCode::Media(MediaKeyCode::LowerVolume),
589            keys::RAISE_VOLUME => KeyCode::Media(MediaKeyCode::RaiseVolume),
590            keys::MUTE_VOLUME => KeyCode::Media(MediaKeyCode::MuteVolume),
591            keys::LEFT_SHIFT => KeyCode::Modifier(ModifierKeyCode::LeftShift),
592            keys::LEFT_CONTROL => KeyCode::Modifier(ModifierKeyCode::LeftControl),
593            keys::LEFT_ALT => KeyCode::Modifier(ModifierKeyCode::LeftAlt),
594            keys::LEFT_SUPER => KeyCode::Modifier(ModifierKeyCode::LeftSuper),
595            keys::LEFT_HYPER => KeyCode::Modifier(ModifierKeyCode::LeftHyper),
596            keys::LEFT_META => KeyCode::Modifier(ModifierKeyCode::LeftMeta),
597            keys::RIGHT_SHIFT => KeyCode::Modifier(ModifierKeyCode::RightShift),
598            keys::RIGHT_CONTROL => KeyCode::Modifier(ModifierKeyCode::RightControl),
599            keys::RIGHT_ALT => KeyCode::Modifier(ModifierKeyCode::RightAlt),
600            keys::RIGHT_SUPER => KeyCode::Modifier(ModifierKeyCode::RightSuper),
601            keys::RIGHT_HYPER => KeyCode::Modifier(ModifierKeyCode::RightHyper),
602            keys::RIGHT_META => KeyCode::Modifier(ModifierKeyCode::RightMeta),
603            keys::ISO_LEVEL_3_SHIFT => KeyCode::Modifier(ModifierKeyCode::IsoLevel3Shift),
604            keys::ISO_LEVEL_5_SHIFT => KeyCode::Modifier(ModifierKeyCode::IsoLevel5Shift),
605            single if single.chars().count() == 1 => KeyCode::Char(single.chars().next().unwrap()),
606            function if function.len() > 1 && function.starts_with('F') => {
607                let function: String = function.chars().skip(1).collect();
608                let function = str::parse::<u8>(&function)?;
609                (function > 0 && function < 25)
610                    .then_some(KeyCode::F(function))
611                    .ok_or_else(|| anyhow!("Invalid function key '{function}'"))?
612            }
613            // Checking that the last token is empty ensures that this branch is only taken if
614            // `-` is used as a code. For example this branch will not be taken for `S-` (which is
615            // missing a code).
616            _ if s.ends_with('-') && tokens.last().is_some_and(|t| t.is_empty()) => {
617                if s == "-" {
618                    return Ok(KeyEvent {
619                        code: KeyCode::Char('-'),
620                        modifiers: KeyModifiers::empty(),
621                    });
622                } else {
623                    let suggestion = format!("{}-{}", s.trim_end_matches('-'), keys::MINUS);
624                    return Err(anyhow!(
625                        "Key '-' cannot be used with modifiers, use '{suggestion}' instead",
626                    ));
627                }
628            }
629            invalid => return Err(anyhow!("Invalid key code '{invalid}'")),
630        };
631
632        let mut modifiers = KeyModifiers::empty();
633        for token in tokens {
634            let flag = match token {
635                "S" => KeyModifiers::SHIFT,
636                "A" | "M" => KeyModifiers::ALT,
637                "C" => KeyModifiers::CONTROL,
638                "Meta" | "Cmd" | "Win" => KeyModifiers::SUPER,
639                _ => return Err(anyhow!("Invalid key modifier '{token}-'")),
640            };
641
642            if modifiers.contains(flag) {
643                return Err(anyhow!("Repeated key modifier '{token}-'"));
644            }
645            modifiers.insert(flag);
646        }
647
648        // Normalize character keys so that characters like C-S-r and C-R
649        // are represented by equal KeyEvents.
650        match code {
651            KeyCode::Char(ch)
652                if ch.is_ascii_lowercase() && modifiers.contains(KeyModifiers::SHIFT) =>
653            {
654                code = KeyCode::Char(ch.to_ascii_uppercase());
655                modifiers.remove(KeyModifiers::SHIFT);
656            }
657            _ => (),
658        }
659
660        Ok(KeyEvent { code, modifiers })
661    }
662}
663
664#[cfg(feature = "term")]
665impl From<crossterm::event::KeyEvent> for KeyEvent {
666    fn from(
667        crossterm::event::KeyEvent {
668            code, modifiers, ..
669        }: crossterm::event::KeyEvent,
670    ) -> Self {
671        if code == crossterm::event::KeyCode::BackTab {
672            // special case for BackTab -> Shift-Tab
673            let mut modifiers: KeyModifiers = modifiers.into();
674            modifiers.insert(KeyModifiers::SHIFT);
675            Self {
676                code: KeyCode::Tab,
677                modifiers,
678            }
679        } else {
680            Self {
681                code: code.into(),
682                modifiers: modifiers.into(),
683            }
684        }
685    }
686}
687
688#[cfg(feature = "term")]
689impl From<KeyEvent> for crossterm::event::KeyEvent {
690    fn from(KeyEvent { code, modifiers }: KeyEvent) -> Self {
691        if code == KeyCode::Tab && modifiers.contains(KeyModifiers::SHIFT) {
692            // special case for Shift-Tab -> BackTab
693            let mut modifiers = modifiers;
694            modifiers.remove(KeyModifiers::SHIFT);
695            crossterm::event::KeyEvent {
696                code: crossterm::event::KeyCode::BackTab,
697                modifiers: modifiers.into(),
698                kind: crossterm::event::KeyEventKind::Press,
699                state: crossterm::event::KeyEventState::NONE,
700            }
701        } else {
702            crossterm::event::KeyEvent {
703                code: code.into(),
704                modifiers: modifiers.into(),
705                kind: crossterm::event::KeyEventKind::Press,
706                state: crossterm::event::KeyEventState::NONE,
707            }
708        }
709    }
710}