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