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