1use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
4
5#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
8pub struct Key {
9 pub code: KeyCode,
10 pub mods: KeyModifiers,
11}
12
13impl Key {
14 pub fn new(code: KeyCode, mods: KeyModifiers) -> Self {
15 Self { code, mods }.normalized()
16 }
17
18 fn normalized(mut self) -> Self {
19 if self.code == KeyCode::BackTab {
20 self.code = KeyCode::Tab;
21 self.mods.insert(KeyModifiers::SHIFT);
22 }
23 if let KeyCode::Char(c) = self.code {
24 if self.mods.contains(KeyModifiers::SHIFT) && c.is_alphabetic() {
25 self.code = KeyCode::Char(c.to_ascii_uppercase());
26 }
27 if c != ' ' {
32 self.mods.remove(KeyModifiers::SHIFT);
33 }
34 }
35 self
36 }
37
38 pub fn parse(s: &str) -> Result<Self, String> {
41 let mut mods = KeyModifiers::NONE;
42 let parts: Vec<&str> = s.split('+').collect();
43 let (mod_parts, key_part) = match parts.split_last() {
44 Some((last, rest)) if !last.is_empty() => (rest, *last),
45 _ => return Err(format!("invalid key: {s:?}")),
46 };
47 for part in mod_parts {
48 match part.to_ascii_lowercase().as_str() {
49 "ctrl" | "control" => mods |= KeyModifiers::CONTROL,
50 "alt" | "meta" => mods |= KeyModifiers::ALT,
51 "shift" => mods |= KeyModifiers::SHIFT,
52 other => return Err(format!("unknown modifier: {other:?}")),
53 }
54 }
55 let code = parse_key_name(key_part)?;
56 Ok(Self::new(code, mods))
57 }
58
59 pub fn from_event(ev: KeyEvent) -> Self {
61 Self::new(ev.code, ev.modifiers)
62 }
63}
64
65fn parse_key_name(name: &str) -> Result<KeyCode, String> {
66 let mut chars = name.chars();
67 if let (Some(c), None) = (chars.next(), chars.next()) {
68 return Ok(KeyCode::Char(c));
69 }
70 let code = match name.to_ascii_lowercase().as_str() {
71 "enter" | "return" => KeyCode::Enter,
72 "tab" => KeyCode::Tab,
73 "esc" | "escape" => KeyCode::Esc,
74 "space" => KeyCode::Char(' '),
75 "up" | "up-arrow" => KeyCode::Up,
76 "down" | "down-arrow" => KeyCode::Down,
77 "left" | "left-arrow" => KeyCode::Left,
78 "right" | "right-arrow" => KeyCode::Right,
79 "home" => KeyCode::Home,
80 "end" => KeyCode::End,
81 "pageup" | "page-up" => KeyCode::PageUp,
82 "pagedown" | "page-down" => KeyCode::PageDown,
83 "backspace" => KeyCode::Backspace,
84 "delete" | "del" => KeyCode::Delete,
85 "insert" => KeyCode::Insert,
86 f if f.starts_with('f') => {
87 let n: u8 = f[1..]
88 .parse()
89 .map_err(|_| format!("unknown key: {name:?}"))?;
90 if (1..=24).contains(&n) {
91 KeyCode::F(n)
92 } else {
93 return Err(format!("unknown key: {name:?}"));
94 }
95 }
96 _ => return Err(format!("unknown key: {name:?}")),
97 };
98 Ok(code)
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 fn key(code: KeyCode, mods: KeyModifiers) -> Key {
106 Key::new(code, mods)
107 }
108
109 #[test]
110 fn parses_bare_char() {
111 assert_eq!(
112 Key::parse("j").unwrap(),
113 key(KeyCode::Char('j'), KeyModifiers::NONE)
114 );
115 }
116
117 #[test]
118 fn parses_ctrl_char() {
119 assert_eq!(
120 Key::parse("ctrl+e").unwrap(),
121 key(KeyCode::Char('e'), KeyModifiers::CONTROL)
122 );
123 }
124
125 #[test]
126 fn parses_alt_char() {
127 assert_eq!(
128 Key::parse("alt+s").unwrap(),
129 key(KeyCode::Char('s'), KeyModifiers::ALT)
130 );
131 }
132
133 #[test]
134 fn shift_letter_normalizes_to_uppercase_char() {
135 assert_eq!(Key::parse("shift+j").unwrap(), Key::parse("J").unwrap());
137 assert_eq!(
138 Key::parse("J").unwrap(),
139 key(KeyCode::Char('J'), KeyModifiers::NONE)
140 );
141 }
142
143 #[test]
144 fn parses_named_keys() {
145 assert_eq!(
146 Key::parse("enter").unwrap(),
147 key(KeyCode::Enter, KeyModifiers::NONE)
148 );
149 assert_eq!(
150 Key::parse("ctrl+enter").unwrap(),
151 key(KeyCode::Enter, KeyModifiers::CONTROL)
152 );
153 assert_eq!(
154 Key::parse("alt+enter").unwrap(),
155 key(KeyCode::Enter, KeyModifiers::ALT)
156 );
157 assert_eq!(
158 Key::parse("tab").unwrap(),
159 key(KeyCode::Tab, KeyModifiers::NONE)
160 );
161 assert_eq!(
162 Key::parse("shift+right").unwrap(),
163 key(KeyCode::Right, KeyModifiers::SHIFT)
164 );
165 assert_eq!(
166 Key::parse("esc").unwrap(),
167 key(KeyCode::Esc, KeyModifiers::NONE)
168 );
169 assert_eq!(
170 Key::parse("space").unwrap(),
171 key(KeyCode::Char(' '), KeyModifiers::NONE)
172 );
173 }
174
175 #[test]
176 fn shift_space_stays_distinct_from_space() {
177 assert_ne!(
180 Key::parse("shift+space").unwrap(),
181 Key::parse("space").unwrap()
182 );
183 assert_eq!(
184 Key::parse("shift+space").unwrap(),
185 key(KeyCode::Char(' '), KeyModifiers::SHIFT)
186 );
187 let ev = KeyEvent::new(KeyCode::Char(' '), KeyModifiers::SHIFT);
188 assert_eq!(Key::from_event(ev), Key::parse("shift+space").unwrap());
189 }
190
191 #[test]
192 fn rejects_unknown_keys_and_modifiers() {
193 assert!(Key::parse("bogus").is_err());
194 assert!(Key::parse("hyper+j").is_err());
195 assert!(Key::parse("").is_err());
196 }
197
198 #[test]
199 fn event_normalization_matches_parse() {
200 let ev = KeyEvent::new(KeyCode::Char('J'), KeyModifiers::SHIFT);
202 assert_eq!(Key::from_event(ev), Key::parse("J").unwrap());
203
204 let ev = KeyEvent::new(KeyCode::Char('e'), KeyModifiers::CONTROL);
205 assert_eq!(Key::from_event(ev), Key::parse("ctrl+e").unwrap());
206
207 let ev = KeyEvent::new(KeyCode::Right, KeyModifiers::SHIFT);
209 assert_eq!(Key::from_event(ev), Key::parse("shift+right").unwrap());
210
211 let ev = KeyEvent::new(KeyCode::BackTab, KeyModifiers::SHIFT);
213 assert_eq!(Key::from_event(ev), Key::parse("shift+tab").unwrap());
214 }
215}