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
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#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Hash)]
70pub enum MediaKeyCode {
71 Play,
73 Pause,
75 PlayPause,
77 Reverse,
79 Stop,
81 FastForward,
83 Rewind,
85 TrackNext,
87 TrackPrevious,
89 Record,
91 LowerVolume,
93 RaiseVolume,
95 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#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Hash)]
147pub enum ModifierKeyCode {
148 LeftShift,
150 LeftControl,
152 LeftAlt,
154 LeftSuper,
156 LeftHyper,
158 LeftMeta,
160 RightShift,
162 RightControl,
164 RightAlt,
166 RightSuper,
168 RightHyper,
170 RightMeta,
172 IsoLevel3Shift,
174 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#[allow(clippy::doc_markdown)]
228#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Hash)]
229pub enum KeyCode {
230 Backspace,
232 Enter,
234 Left,
236 Right,
238 Up,
240 Down,
242 Home,
244 End,
246 PageUp,
248 PageDown,
250 Tab,
252 Delete,
254 Insert,
256 F(u8),
260 Char(char),
264 Null,
266 Esc,
268 CapsLock,
270 ScrollLock,
272 NumLock,
274 PrintScreen,
276 Pause,
278 Menu,
280 KeypadBegin,
282 Media(MediaKeyCode),
284 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
361pub(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"; 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"; }
419
420#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
423pub struct KeyEvent {
424 pub code: KeyCode,
425 pub modifiers: KeyModifiers,
426 }
428
429impl KeyEvent {
430 pub fn new(code: KeyCode, modifiers: KeyModifiers) -> Self {
431 Self { code, modifiers }
432 }
433
434 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 _ 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 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 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 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}