Rust · 3300 bytes Raw Blame History
1 use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
2
3 /// Key modifiers
4 #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
5 pub struct Modifiers {
6 pub ctrl: bool,
7 pub alt: bool,
8 pub shift: bool,
9 }
10
11 impl From<KeyModifiers> for Modifiers {
12 fn from(m: KeyModifiers) -> Self {
13 Self {
14 ctrl: m.contains(KeyModifiers::CONTROL),
15 alt: m.contains(KeyModifiers::ALT),
16 shift: m.contains(KeyModifiers::SHIFT),
17 }
18 }
19 }
20
21 /// Abstracted key input
22 #[derive(Debug, Clone, PartialEq, Eq)]
23 pub enum Key {
24 Char(char),
25 Backspace,
26 Delete,
27 Enter,
28 Tab,
29 BackTab,
30 Escape,
31 Up,
32 Down,
33 Left,
34 Right,
35 Home,
36 End,
37 PageUp,
38 PageDown,
39 F(u8),
40 Null,
41 }
42
43 impl Key {
44 pub fn from_crossterm(event: KeyEvent) -> (Self, Modifiers) {
45 let modifiers = Modifiers::from(event.modifiers);
46 let key = match event.code {
47 KeyCode::Char(c) => {
48 // Some terminals report Enter/newline as Char('\r') or Char('\n')
49 // during paste or raw key input. Normalize those to Enter so
50 // multiline paste preserves line breaks.
51 if c == '\r' || c == '\n' {
52 return (Key::Enter, modifiers);
53 }
54
55 // If shift is pressed and character is lowercase alphabetic,
56 // uppercase it. This handles terminals that don't support
57 // REPORT_ALTERNATE_KEYS properly (which would report 'A' directly).
58 let c = if modifiers.shift && c.is_ascii_lowercase() {
59 c.to_ascii_uppercase()
60 } else {
61 c
62 };
63 Key::Char(c)
64 }
65 KeyCode::Backspace => Key::Backspace,
66 KeyCode::Delete => Key::Delete,
67 KeyCode::Enter => Key::Enter,
68 KeyCode::Tab => Key::Tab,
69 KeyCode::BackTab => Key::BackTab,
70 KeyCode::Esc => Key::Escape,
71 KeyCode::Up => Key::Up,
72 KeyCode::Down => Key::Down,
73 KeyCode::Left => Key::Left,
74 KeyCode::Right => Key::Right,
75 KeyCode::Home => Key::Home,
76 KeyCode::End => Key::End,
77 KeyCode::PageUp => Key::PageUp,
78 KeyCode::PageDown => Key::PageDown,
79 KeyCode::F(n) => Key::F(n),
80 KeyCode::Null => Key::Null,
81 _ => Key::Null,
82 };
83 (key, modifiers)
84 }
85 }
86
87 #[cfg(test)]
88 mod tests {
89 use super::*;
90 use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
91
92 #[test]
93 fn char_carriage_return_maps_to_enter() {
94 let (key, mods) = Key::from_crossterm(KeyEvent::new(
95 KeyCode::Char('\r'),
96 KeyModifiers::NONE,
97 ));
98 assert_eq!(key, Key::Enter);
99 assert_eq!(mods, Modifiers::default());
100 }
101
102 #[test]
103 fn char_newline_maps_to_enter() {
104 let (key, mods) = Key::from_crossterm(KeyEvent::new(
105 KeyCode::Char('\n'),
106 KeyModifiers::SHIFT,
107 ));
108 assert_eq!(key, Key::Enter);
109 assert_eq!(
110 mods,
111 Modifiers {
112 ctrl: false,
113 alt: false,
114 shift: true,
115 }
116 );
117 }
118 }
119