1use anyhow::anyhow;
3use bitflags::bitflags;
4use serde::Serialize;
5
6bitflags! {
7 #[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
20impl From<KeyModifiers> for crossterm::event::KeyModifiers {
21 fn from(key_modifiers: KeyModifiers) -> Self {
22 use crossterm::event::KeyModifiers as CKeyModifiers;
23
24 let mut result = CKeyModifiers::NONE;
25
26 if key_modifiers.contains(KeyModifiers::SHIFT) {
27 result.insert(CKeyModifiers::SHIFT);
28 }
29 if key_modifiers.contains(KeyModifiers::CONTROL) {
30 result.insert(CKeyModifiers::CONTROL);
31 }
32 if key_modifiers.contains(KeyModifiers::ALT) {
33 result.insert(CKeyModifiers::ALT);
34 }
35 if key_modifiers.contains(KeyModifiers::SUPER) {
36 result.insert(CKeyModifiers::SUPER);
37 }
38
39 result
40 }
41}
42
43impl From<crossterm::event::KeyModifiers> for KeyModifiers {
44 fn from(val: crossterm::event::KeyModifiers) -> Self {
45 use crossterm::event::KeyModifiers as CKeyModifiers;
46
47 let mut result = KeyModifiers::NONE;
48
49 if val.contains(CKeyModifiers::SHIFT) {
50 result.insert(KeyModifiers::SHIFT);
51 }
52 if val.contains(CKeyModifiers::CONTROL) {
53 result.insert(KeyModifiers::CONTROL);
54 }
55 if val.contains(CKeyModifiers::ALT) {
56 result.insert(KeyModifiers::ALT);
57 }
58 if val.contains(CKeyModifiers::SUPER) {
59 result.insert(KeyModifiers::SUPER);
60 }
61
62 result
63 }
64}
65
66#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Hash)]
68pub enum MediaKeyCode {
69 Play,
71 Pause,
73 PlayPause,
75 Reverse,
77 Stop,
79 FastForward,
81 Rewind,
83 TrackNext,
85 TrackPrevious,
87 Record,
89 LowerVolume,
91 RaiseVolume,
93 MuteVolume,
95}
96
97impl From<MediaKeyCode> for crossterm::event::MediaKeyCode {
98 fn from(media_key_code: MediaKeyCode) -> Self {
99 use crossterm::event::MediaKeyCode as CMediaKeyCode;
100
101 match media_key_code {
102 MediaKeyCode::Play => CMediaKeyCode::Play,
103 MediaKeyCode::Pause => CMediaKeyCode::Pause,
104 MediaKeyCode::PlayPause => CMediaKeyCode::PlayPause,
105 MediaKeyCode::Reverse => CMediaKeyCode::Reverse,
106 MediaKeyCode::Stop => CMediaKeyCode::Stop,
107 MediaKeyCode::FastForward => CMediaKeyCode::FastForward,
108 MediaKeyCode::Rewind => CMediaKeyCode::Rewind,
109 MediaKeyCode::TrackNext => CMediaKeyCode::TrackNext,
110 MediaKeyCode::TrackPrevious => CMediaKeyCode::TrackPrevious,
111 MediaKeyCode::Record => CMediaKeyCode::Record,
112 MediaKeyCode::LowerVolume => CMediaKeyCode::LowerVolume,
113 MediaKeyCode::RaiseVolume => CMediaKeyCode::RaiseVolume,
114 MediaKeyCode::MuteVolume => CMediaKeyCode::MuteVolume,
115 }
116 }
117}
118
119impl From<crossterm::event::MediaKeyCode> for MediaKeyCode {
120 fn from(val: crossterm::event::MediaKeyCode) -> Self {
121 use crossterm::event::MediaKeyCode as CMediaKeyCode;
122
123 match val {
124 CMediaKeyCode::Play => MediaKeyCode::Play,
125 CMediaKeyCode::Pause => MediaKeyCode::Pause,
126 CMediaKeyCode::PlayPause => MediaKeyCode::PlayPause,
127 CMediaKeyCode::Reverse => MediaKeyCode::Reverse,
128 CMediaKeyCode::Stop => MediaKeyCode::Stop,
129 CMediaKeyCode::FastForward => MediaKeyCode::FastForward,
130 CMediaKeyCode::Rewind => MediaKeyCode::Rewind,
131 CMediaKeyCode::TrackNext => MediaKeyCode::TrackNext,
132 CMediaKeyCode::TrackPrevious => MediaKeyCode::TrackPrevious,
133 CMediaKeyCode::Record => MediaKeyCode::Record,
134 CMediaKeyCode::LowerVolume => MediaKeyCode::LowerVolume,
135 CMediaKeyCode::RaiseVolume => MediaKeyCode::RaiseVolume,
136 CMediaKeyCode::MuteVolume => MediaKeyCode::MuteVolume,
137 }
138 }
139}
140
141#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Hash)]
143pub enum ModifierKeyCode {
144 LeftShift,
146 LeftControl,
148 LeftAlt,
150 LeftSuper,
152 LeftHyper,
154 LeftMeta,
156 RightShift,
158 RightControl,
160 RightAlt,
162 RightSuper,
164 RightHyper,
166 RightMeta,
168 IsoLevel3Shift,
170 IsoLevel5Shift,
172}
173
174impl From<ModifierKeyCode> for crossterm::event::ModifierKeyCode {
175 fn from(modifier_key_code: ModifierKeyCode) -> Self {
176 use crossterm::event::ModifierKeyCode as CModifierKeyCode;
177
178 match modifier_key_code {
179 ModifierKeyCode::LeftShift => CModifierKeyCode::LeftShift,
180 ModifierKeyCode::LeftControl => CModifierKeyCode::LeftControl,
181 ModifierKeyCode::LeftAlt => CModifierKeyCode::LeftAlt,
182 ModifierKeyCode::LeftSuper => CModifierKeyCode::LeftSuper,
183 ModifierKeyCode::LeftHyper => CModifierKeyCode::LeftHyper,
184 ModifierKeyCode::LeftMeta => CModifierKeyCode::LeftMeta,
185 ModifierKeyCode::RightShift => CModifierKeyCode::RightShift,
186 ModifierKeyCode::RightControl => CModifierKeyCode::RightControl,
187 ModifierKeyCode::RightAlt => CModifierKeyCode::RightAlt,
188 ModifierKeyCode::RightSuper => CModifierKeyCode::RightSuper,
189 ModifierKeyCode::RightHyper => CModifierKeyCode::RightHyper,
190 ModifierKeyCode::RightMeta => CModifierKeyCode::RightMeta,
191 ModifierKeyCode::IsoLevel3Shift => CModifierKeyCode::IsoLevel3Shift,
192 ModifierKeyCode::IsoLevel5Shift => CModifierKeyCode::IsoLevel5Shift,
193 }
194 }
195}
196
197impl From<crossterm::event::ModifierKeyCode> for ModifierKeyCode {
198 fn from(val: crossterm::event::ModifierKeyCode) -> Self {
199 use crossterm::event::ModifierKeyCode as CModifierKeyCode;
200
201 match val {
202 CModifierKeyCode::LeftShift => ModifierKeyCode::LeftShift,
203 CModifierKeyCode::LeftControl => ModifierKeyCode::LeftControl,
204 CModifierKeyCode::LeftAlt => ModifierKeyCode::LeftAlt,
205 CModifierKeyCode::LeftSuper => ModifierKeyCode::LeftSuper,
206 CModifierKeyCode::LeftHyper => ModifierKeyCode::LeftHyper,
207 CModifierKeyCode::LeftMeta => ModifierKeyCode::LeftMeta,
208 CModifierKeyCode::RightShift => ModifierKeyCode::RightShift,
209 CModifierKeyCode::RightControl => ModifierKeyCode::RightControl,
210 CModifierKeyCode::RightAlt => ModifierKeyCode::RightAlt,
211 CModifierKeyCode::RightSuper => ModifierKeyCode::RightSuper,
212 CModifierKeyCode::RightHyper => ModifierKeyCode::RightHyper,
213 CModifierKeyCode::RightMeta => ModifierKeyCode::RightMeta,
214 CModifierKeyCode::IsoLevel3Shift => ModifierKeyCode::IsoLevel3Shift,
215 CModifierKeyCode::IsoLevel5Shift => ModifierKeyCode::IsoLevel5Shift,
216 }
217 }
218}
219
220#[allow(clippy::doc_markdown)]
222#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Hash)]
223pub enum KeyCode {
224 Backspace,
226 Enter,
228 Left,
230 Right,
232 Up,
234 Down,
236 Home,
238 End,
240 PageUp,
242 PageDown,
244 Tab,
246 Delete,
248 Insert,
250 F(u8),
254 Char(char),
258 Null,
260 Esc,
262 CapsLock,
264 ScrollLock,
266 NumLock,
268 PrintScreen,
270 Pause,
272 Menu,
274 KeypadBegin,
276 Media(MediaKeyCode),
278 Modifier(ModifierKeyCode),
280}
281
282impl From<KeyCode> for crossterm::event::KeyCode {
283 fn from(key_code: KeyCode) -> Self {
284 use crossterm::event::KeyCode as CKeyCode;
285
286 match key_code {
287 KeyCode::Backspace => CKeyCode::Backspace,
288 KeyCode::Enter => CKeyCode::Enter,
289 KeyCode::Left => CKeyCode::Left,
290 KeyCode::Right => CKeyCode::Right,
291 KeyCode::Up => CKeyCode::Up,
292 KeyCode::Down => CKeyCode::Down,
293 KeyCode::Home => CKeyCode::Home,
294 KeyCode::End => CKeyCode::End,
295 KeyCode::PageUp => CKeyCode::PageUp,
296 KeyCode::PageDown => CKeyCode::PageDown,
297 KeyCode::Tab => CKeyCode::Tab,
298 KeyCode::Delete => CKeyCode::Delete,
299 KeyCode::Insert => CKeyCode::Insert,
300 KeyCode::F(f_number) => CKeyCode::F(f_number),
301 KeyCode::Char(character) => CKeyCode::Char(character),
302 KeyCode::Null => CKeyCode::Null,
303 KeyCode::Esc => CKeyCode::Esc,
304 KeyCode::CapsLock => CKeyCode::CapsLock,
305 KeyCode::ScrollLock => CKeyCode::ScrollLock,
306 KeyCode::NumLock => CKeyCode::NumLock,
307 KeyCode::PrintScreen => CKeyCode::PrintScreen,
308 KeyCode::Pause => CKeyCode::Pause,
309 KeyCode::Menu => CKeyCode::Menu,
310 KeyCode::KeypadBegin => CKeyCode::KeypadBegin,
311 KeyCode::Media(media_key_code) => CKeyCode::Media(media_key_code.into()),
312 KeyCode::Modifier(modifier_key_code) => CKeyCode::Modifier(modifier_key_code.into()),
313 }
314 }
315}
316
317impl From<crossterm::event::KeyCode> for KeyCode {
318 fn from(val: crossterm::event::KeyCode) -> Self {
319 use crossterm::event::KeyCode as CKeyCode;
320
321 match val {
322 CKeyCode::Backspace => KeyCode::Backspace,
323 CKeyCode::Enter => KeyCode::Enter,
324 CKeyCode::Left => KeyCode::Left,
325 CKeyCode::Right => KeyCode::Right,
326 CKeyCode::Up => KeyCode::Up,
327 CKeyCode::Down => KeyCode::Down,
328 CKeyCode::Home => KeyCode::Home,
329 CKeyCode::End => KeyCode::End,
330 CKeyCode::PageUp => KeyCode::PageUp,
331 CKeyCode::PageDown => KeyCode::PageDown,
332 CKeyCode::Tab => KeyCode::Tab,
333 CKeyCode::BackTab => unreachable!("BackTab should have been handled on KeyEvent level"),
334 CKeyCode::Delete => KeyCode::Delete,
335 CKeyCode::Insert => KeyCode::Insert,
336 CKeyCode::F(f_number) => KeyCode::F(f_number),
337 CKeyCode::Char(character) => KeyCode::Char(character),
338 CKeyCode::Null => KeyCode::Null,
339 CKeyCode::Esc => KeyCode::Esc,
340 CKeyCode::CapsLock => KeyCode::CapsLock,
341 CKeyCode::ScrollLock => KeyCode::ScrollLock,
342 CKeyCode::NumLock => KeyCode::NumLock,
343 CKeyCode::PrintScreen => KeyCode::PrintScreen,
344 CKeyCode::Pause => KeyCode::Pause,
345 CKeyCode::Menu => KeyCode::Menu,
346 CKeyCode::KeypadBegin => KeyCode::KeypadBegin,
347 CKeyCode::Media(media_key_code) => KeyCode::Media(media_key_code.into()),
348 CKeyCode::Modifier(modifier_key_code) => KeyCode::Modifier(modifier_key_code.into()),
349 }
350 }
351}
352
353pub(crate) mod keys {
354 pub(crate) const BACKSPACE: &str = "backspace";
355 pub(crate) const ENTER: &str = "ret";
356 pub(crate) const ENTER2: &str = "enter";
357 pub(crate) const LEFT: &str = "left";
358 pub(crate) const RIGHT: &str = "right";
359 pub(crate) const UP: &str = "up";
360 pub(crate) const DOWN: &str = "down";
361 pub(crate) const HOME: &str = "home";
362 pub(crate) const END: &str = "end";
363 pub(crate) const PAGEUP: &str = "pageup";
364 pub(crate) const PAGEDOWN: &str = "pagedown";
365 pub(crate) const TAB: &str = "tab";
366 pub(crate) const DELETE: &str = "del";
367 pub(crate) const INSERT: &str = "ins";
368 pub(crate) const NULL: &str = "null";
369 pub(crate) const ESC: &str = "esc";
370 pub(crate) const SPACE: &str = "space";
371 pub(crate) const MINUS: &str = "minus"; pub(crate) const LESS_THAN: &str = "lt"; pub(crate) const GREATER_THAN: &str = "gt"; pub(crate) const CAPS_LOCK: &str = "capslock"; pub(crate) const SCROLL_LOCK: &str = "scrolllock"; pub(crate) const NUM_LOCK: &str = "numlock"; pub(crate) const PRINT_SCREEN: &str = "printscreen"; pub(crate) const PAUSE: &str = "pause"; pub(crate) const MENU: &str = "menu"; pub(crate) const KEYPAD_BEGIN: &str = "keypadbegin"; pub(crate) const PLAY: &str = "play"; pub(crate) const PAUSE_MEDIA: &str = "pausemedia"; pub(crate) const PLAY_PAUSE: &str = "playpause"; pub(crate) const REVERSE: &str = "reverse"; pub(crate) const STOP: &str = "stop"; pub(crate) const FAST_FORWARD: &str = "fastforward"; pub(crate) const REWIND: &str = "rewind"; pub(crate) const TRACK_NEXT: &str = "tracknext"; pub(crate) const TRACK_PREVIOUS: &str = "trackprevious"; pub(crate) const RECORD: &str = "record"; pub(crate) const LOWER_VOLUME: &str = "lowervolume"; pub(crate) const RAISE_VOLUME: &str = "raisevolume"; pub(crate) const MUTE_VOLUME: &str = "mutevolume"; pub(crate) const LEFT_SHIFT: &str = "leftshift"; pub(crate) const LEFT_CONTROL: &str = "leftcontrol"; pub(crate) const LEFT_ALT: &str = "leftalt"; pub(crate) const LEFT_SUPER: &str = "leftsuper"; pub(crate) const LEFT_HYPER: &str = "lefthyper"; pub(crate) const LEFT_META: &str = "leftmeta"; pub(crate) const RIGHT_SHIFT: &str = "rightshift"; pub(crate) const RIGHT_CONTROL: &str = "rightcontrol"; pub(crate) const RIGHT_ALT: &str = "rightalt"; pub(crate) const RIGHT_SUPER: &str = "rightsuper"; pub(crate) const RIGHT_HYPER: &str = "righthyper"; pub(crate) const RIGHT_META: &str = "rightmeta"; pub(crate) const ISO_LEVEL_3_SHIFT: &str = "isolevel3shift"; pub(crate) const ISO_LEVEL_5_SHIFT: &str = "isolevel5shift"; }
409
410#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
413pub struct KeyEvent {
414 pub code: KeyCode,
415 pub modifiers: KeyModifiers,
416}
417
418impl KeyEvent {
419 pub fn new(code: KeyCode, modifiers: KeyModifiers) -> Self {
420 Self { code, modifiers }
421 }
422
423 pub fn canonicalize(&mut self) {
428 if let KeyCode::Char(_) = self.code {
429 self.modifiers.remove(KeyModifiers::SHIFT);
430 }
431 }
432}
433
434impl Serialize for KeyEvent {
435 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
436 where
437 S: serde::Serializer,
438 {
439 serializer.serialize_str(&self.to_string())
440 }
441}
442
443const MODIFIERS: [(KeyModifiers, &str); 3] = [
444 (KeyModifiers::SHIFT, "S-"),
445 (KeyModifiers::CONTROL, "C-"),
446 (KeyModifiers::ALT, "A-"),
447];
448
449impl std::fmt::Display for KeyEvent {
450 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
451 let mut result = String::new();
452
453 for (modifier, str) in MODIFIERS {
454 if self.modifiers.contains(modifier) {
455 result.push_str(str);
456 }
457 }
458
459 match self.code {
460 KeyCode::Backspace => result.push_str(keys::BACKSPACE),
461 KeyCode::Enter => result.push_str(keys::ENTER2),
462 KeyCode::Left => result.push_str(keys::LEFT),
463 KeyCode::Right => result.push_str(keys::RIGHT),
464 KeyCode::Up => result.push_str(keys::UP),
465 KeyCode::Down => result.push_str(keys::DOWN),
466 KeyCode::Home => result.push_str(keys::HOME),
467 KeyCode::End => result.push_str(keys::END),
468 KeyCode::PageUp => result.push_str(keys::PAGEUP),
469 KeyCode::PageDown => result.push_str(keys::PAGEDOWN),
470 KeyCode::Tab => result.push_str(keys::TAB),
471 KeyCode::Delete => result.push_str(keys::DELETE),
472 KeyCode::Insert => result.push_str(keys::INSERT),
473 KeyCode::Null => result.push_str(keys::NULL),
474 KeyCode::Esc => result.push_str(keys::ESC),
475 KeyCode::CapsLock => result.push_str(keys::CAPS_LOCK),
476 KeyCode::ScrollLock => result.push_str(keys::SCROLL_LOCK),
477 KeyCode::NumLock => result.push_str(keys::NUM_LOCK),
478 KeyCode::PrintScreen => result.push_str(keys::PRINT_SCREEN),
479 KeyCode::Pause => result.push_str(keys::PAUSE),
480 KeyCode::Menu => result.push_str(keys::MENU),
481 KeyCode::KeypadBegin => result.push_str(keys::KEYPAD_BEGIN),
482 KeyCode::Media(media) => match media {
483 MediaKeyCode::Play => result.push_str(keys::PLAY),
484 MediaKeyCode::Pause => result.push_str(keys::PAUSE_MEDIA),
485 MediaKeyCode::PlayPause => result.push_str(keys::PLAY_PAUSE),
486 MediaKeyCode::Reverse => result.push_str(keys::REVERSE),
487 MediaKeyCode::Stop => result.push_str(keys::STOP),
488 MediaKeyCode::FastForward => result.push_str(keys::FAST_FORWARD),
489 MediaKeyCode::Rewind => result.push_str(keys::REWIND),
490 MediaKeyCode::TrackNext => result.push_str(keys::TRACK_NEXT),
491 MediaKeyCode::TrackPrevious => result.push_str(keys::TRACK_PREVIOUS),
492 MediaKeyCode::Record => result.push_str(keys::RECORD),
493 MediaKeyCode::LowerVolume => result.push_str(keys::LOWER_VOLUME),
494 MediaKeyCode::RaiseVolume => result.push_str(keys::RAISE_VOLUME),
495 MediaKeyCode::MuteVolume => result.push_str(keys::MUTE_VOLUME),
496 },
497 KeyCode::Modifier(modifier) => match modifier {
498 ModifierKeyCode::LeftShift => result.push_str(keys::LEFT_SHIFT),
499 ModifierKeyCode::LeftControl => result.push_str(keys::LEFT_CONTROL),
500 ModifierKeyCode::LeftAlt => result.push_str(keys::LEFT_ALT),
501 ModifierKeyCode::LeftSuper => result.push_str(keys::LEFT_SUPER),
502 ModifierKeyCode::LeftHyper => result.push_str(keys::LEFT_HYPER),
503 ModifierKeyCode::LeftMeta => result.push_str(keys::LEFT_META),
504 ModifierKeyCode::RightShift => result.push_str(keys::RIGHT_SHIFT),
505 ModifierKeyCode::RightControl => result.push_str(keys::RIGHT_CONTROL),
506 ModifierKeyCode::RightAlt => result.push_str(keys::RIGHT_ALT),
507 ModifierKeyCode::RightSuper => result.push_str(keys::RIGHT_SUPER),
508 ModifierKeyCode::RightHyper => result.push_str(keys::RIGHT_HYPER),
509 ModifierKeyCode::RightMeta => result.push_str(keys::RIGHT_META),
510 ModifierKeyCode::IsoLevel3Shift => result.push_str(keys::ISO_LEVEL_3_SHIFT),
511 ModifierKeyCode::IsoLevel5Shift => result.push_str(keys::ISO_LEVEL_5_SHIFT),
512 },
513 KeyCode::Char(' ') => result.push_str(keys::SPACE),
514 KeyCode::Char('<') => result.push_str(keys::LESS_THAN),
515 KeyCode::Char('>') => result.push_str(keys::GREATER_THAN),
516 KeyCode::Char('-') => result.push_str(keys::MINUS),
517 KeyCode::Char(c) => result.push(c),
518 KeyCode::F(n) => {
519 use std::fmt::Write;
520 write!(&mut result, "F{n}").unwrap();
521 }
522 }
523
524 write!(f, "{result}")
525 }
526}
527
528impl std::str::FromStr for KeyEvent {
529 type Err = anyhow::Error;
530
531 #[allow(clippy::too_many_lines)]
532 fn from_str(s: &str) -> Result<Self, Self::Err> {
533 let mut tokens: Vec<_> = s.split('-').collect();
534 let mut code = match tokens.pop().ok_or_else(|| anyhow!("Missing key code"))? {
535 keys::BACKSPACE => KeyCode::Backspace,
536 keys::ENTER | keys::ENTER2 => KeyCode::Enter,
537 keys::LEFT => KeyCode::Left,
538 keys::RIGHT => KeyCode::Right,
539 keys::UP => KeyCode::Up,
540 keys::DOWN => KeyCode::Down,
541 keys::HOME => KeyCode::Home,
542 keys::END => KeyCode::End,
543 keys::PAGEUP => KeyCode::PageUp,
544 keys::PAGEDOWN => KeyCode::PageDown,
545 keys::TAB => KeyCode::Tab,
546 keys::DELETE => KeyCode::Delete,
547 keys::INSERT => KeyCode::Insert,
548 keys::NULL => KeyCode::Null,
549 keys::ESC => KeyCode::Esc,
550 keys::SPACE => KeyCode::Char(' '),
551 keys::MINUS => KeyCode::Char('-'),
552 keys::LESS_THAN => KeyCode::Char('<'),
553 keys::GREATER_THAN => KeyCode::Char('>'),
554 keys::CAPS_LOCK => KeyCode::CapsLock,
555 keys::SCROLL_LOCK => KeyCode::ScrollLock,
556 keys::NUM_LOCK => KeyCode::NumLock,
557 keys::PRINT_SCREEN => KeyCode::PrintScreen,
558 keys::PAUSE => KeyCode::Pause,
559 keys::MENU => KeyCode::Menu,
560 keys::KEYPAD_BEGIN => KeyCode::KeypadBegin,
561 keys::PLAY => KeyCode::Media(MediaKeyCode::Play),
562 keys::PAUSE_MEDIA => KeyCode::Media(MediaKeyCode::Pause),
563 keys::PLAY_PAUSE => KeyCode::Media(MediaKeyCode::PlayPause),
564 keys::STOP => KeyCode::Media(MediaKeyCode::Stop),
565 keys::REVERSE => KeyCode::Media(MediaKeyCode::Reverse),
566 keys::FAST_FORWARD => KeyCode::Media(MediaKeyCode::FastForward),
567 keys::REWIND => KeyCode::Media(MediaKeyCode::Rewind),
568 keys::TRACK_NEXT => KeyCode::Media(MediaKeyCode::TrackNext),
569 keys::TRACK_PREVIOUS => KeyCode::Media(MediaKeyCode::TrackPrevious),
570 keys::RECORD => KeyCode::Media(MediaKeyCode::Record),
571 keys::LOWER_VOLUME => KeyCode::Media(MediaKeyCode::LowerVolume),
572 keys::RAISE_VOLUME => KeyCode::Media(MediaKeyCode::RaiseVolume),
573 keys::MUTE_VOLUME => KeyCode::Media(MediaKeyCode::MuteVolume),
574 keys::LEFT_SHIFT => KeyCode::Modifier(ModifierKeyCode::LeftShift),
575 keys::LEFT_CONTROL => KeyCode::Modifier(ModifierKeyCode::LeftControl),
576 keys::LEFT_ALT => KeyCode::Modifier(ModifierKeyCode::LeftAlt),
577 keys::LEFT_SUPER => KeyCode::Modifier(ModifierKeyCode::LeftSuper),
578 keys::LEFT_HYPER => KeyCode::Modifier(ModifierKeyCode::LeftHyper),
579 keys::LEFT_META => KeyCode::Modifier(ModifierKeyCode::LeftMeta),
580 keys::RIGHT_SHIFT => KeyCode::Modifier(ModifierKeyCode::RightShift),
581 keys::RIGHT_CONTROL => KeyCode::Modifier(ModifierKeyCode::RightControl),
582 keys::RIGHT_ALT => KeyCode::Modifier(ModifierKeyCode::RightAlt),
583 keys::RIGHT_SUPER => KeyCode::Modifier(ModifierKeyCode::RightSuper),
584 keys::RIGHT_HYPER => KeyCode::Modifier(ModifierKeyCode::RightHyper),
585 keys::RIGHT_META => KeyCode::Modifier(ModifierKeyCode::RightMeta),
586 keys::ISO_LEVEL_3_SHIFT => KeyCode::Modifier(ModifierKeyCode::IsoLevel3Shift),
587 keys::ISO_LEVEL_5_SHIFT => KeyCode::Modifier(ModifierKeyCode::IsoLevel5Shift),
588 single if single.chars().count() == 1 => KeyCode::Char(single.chars().next().unwrap()),
589 function if function.len() > 1 && function.starts_with('F') => {
590 let function: String = function.chars().skip(1).collect();
591 let function = str::parse::<u8>(&function)?;
592 (function > 0 && function < 25)
593 .then_some(KeyCode::F(function))
594 .ok_or_else(|| anyhow!("Invalid function key '{function}'"))?
595 }
596 _ if s.ends_with('-') && tokens.last().is_some_and(|t| t.is_empty()) => {
600 if s == "-" {
601 return Ok(KeyEvent {
602 code: KeyCode::Char('-'),
603 modifiers: KeyModifiers::empty(),
604 });
605 }
606 let suggestion = format!("{}-{}", s.trim_end_matches('-'), keys::MINUS);
607 return Err(anyhow!(
608 "Key '-' cannot be used with modifiers, use '{suggestion}' instead",
609 ));
610 }
611 invalid => return Err(anyhow!("Invalid key code '{invalid}'")),
612 };
613
614 let mut modifiers = KeyModifiers::empty();
615 for token in tokens {
616 let flag = match token {
617 "S" => KeyModifiers::SHIFT,
618 "A" | "M" => KeyModifiers::ALT,
619 "C" => KeyModifiers::CONTROL,
620 "Meta" | "Cmd" | "Win" => KeyModifiers::SUPER,
621 _ => return Err(anyhow!("Invalid key modifier '{token}-'")),
622 };
623
624 if modifiers.contains(flag) {
625 return Err(anyhow!("Repeated key modifier '{token}-'"));
626 }
627 modifiers.insert(flag);
628 }
629
630 match code {
633 KeyCode::Char(ch)
634 if ch.is_ascii_lowercase() && modifiers.contains(KeyModifiers::SHIFT) =>
635 {
636 code = KeyCode::Char(ch.to_ascii_uppercase());
637 modifiers.remove(KeyModifiers::SHIFT);
638 }
639 _ => (),
640 }
641
642 Ok(KeyEvent { code, modifiers })
643 }
644}
645
646impl From<crossterm::event::KeyEvent> for KeyEvent {
647 fn from(
648 crossterm::event::KeyEvent {
649 code, modifiers, ..
650 }: crossterm::event::KeyEvent,
651 ) -> Self {
652 if code == crossterm::event::KeyCode::BackTab {
653 let mut modifiers: KeyModifiers = modifiers.into();
655 modifiers.insert(KeyModifiers::SHIFT);
656 Self {
657 code: KeyCode::Tab,
658 modifiers,
659 }
660 } else {
661 Self {
662 code: code.into(),
663 modifiers: modifiers.into(),
664 }
665 }
666 }
667}
668
669impl From<KeyEvent> for crossterm::event::KeyEvent {
670 fn from(KeyEvent { code, modifiers }: KeyEvent) -> Self {
671 if code == KeyCode::Tab && modifiers.contains(KeyModifiers::SHIFT) {
672 let mut modifiers = modifiers;
674 modifiers.remove(KeyModifiers::SHIFT);
675 crossterm::event::KeyEvent {
676 code: crossterm::event::KeyCode::BackTab,
677 modifiers: modifiers.into(),
678 kind: crossterm::event::KeyEventKind::Press,
679 state: crossterm::event::KeyEventState::NONE,
680 }
681 } else {
682 crossterm::event::KeyEvent {
683 code: code.into(),
684 modifiers: modifiers.into(),
685 kind: crossterm::event::KeyEventKind::Press,
686 state: crossterm::event::KeyEventState::NONE,
687 }
688 }
689 }
690}