1use std::ops::{Deref, DerefMut};
7
8use ratatui::crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
9use rustc_hash::FxHashMap;
10use serde::{
11 de::{Deserializer, Error as DeError},
12 Deserialize,
13};
14
15use crate::event::UserEvent;
16
17const DEFAULT_KEY_BIND: &str = include_str!("../assets/default-keybind.toml");
18
19#[derive(Debug, Default, Clone, PartialEq, Eq)]
20pub struct KeyBind(FxHashMap<KeyEvent, UserEvent>);
21
22impl Deref for KeyBind {
23 type Target = FxHashMap<KeyEvent, UserEvent>;
24
25 fn deref(&self) -> &Self::Target {
26 &self.0
27 }
28}
29
30impl DerefMut for KeyBind {
31 fn deref_mut(&mut self) -> &mut Self::Target {
32 &mut self.0
33 }
34}
35
36impl KeyBind {
37 pub fn new(custom_keybind_patch: Option<KeyBind>) -> Self {
38 let mut keybind: KeyBind =
39 toml::from_str(DEFAULT_KEY_BIND).expect("default key bind should be correct");
40
41 if let Some(mut custom_keybind_patch) = custom_keybind_patch {
42 for (key_event, user_event) in custom_keybind_patch.drain() {
43 keybind.insert(key_event, user_event);
44 }
45 }
46
47 keybind
48 }
49
50 pub fn keys_for_event(&self, user_event: UserEvent) -> Vec<String> {
51 let mut key_events: Vec<KeyEvent> = self
52 .iter()
53 .filter(|(_, ue)| **ue == user_event)
54 .map(|(ke, _)| *ke)
55 .collect();
56 key_events.sort_by(|a, b| a.partial_cmp(b).unwrap()); key_events.into_iter().map(key_event_to_string).collect()
58 }
59
60 pub fn user_command_view_toggle_event_numbers(&self) -> Vec<usize> {
61 let mut numbers: Vec<usize> = self
62 .values()
63 .filter_map(|ue| {
64 if let UserEvent::UserCommandViewToggle(n) = ue {
65 Some(*n)
66 } else {
67 None
68 }
69 })
70 .collect();
71 numbers.sort_unstable();
72 numbers
73 }
74}
75
76impl<'de> Deserialize<'de> for KeyBind {
77 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
78 where
79 D: Deserializer<'de>,
80 {
81 let parsed_map = FxHashMap::<UserEvent, Vec<String>>::deserialize(deserializer)?;
82 let mut key_map = FxHashMap::<KeyEvent, UserEvent>::default();
83 for (user_event, key_events) in parsed_map {
84 for key_event_str in key_events {
85 let key_event = match parse_key_event(&key_event_str) {
86 Ok(e) => e,
87 Err(s) => {
88 let msg = format!("{key_event_str:?} is not a valid key event: {s:}");
89 return Err(DeError::custom(msg));
90 }
91 };
92 if let Some(conflict_user_event) = key_map.insert(key_event, user_event) {
93 let msg = format!(
94 "{key_event:?} map to multiple events: {user_event:?}, {conflict_user_event:?}"
95 );
96 return Err(DeError::custom(msg));
97 }
98 }
99 }
100
101 Ok(KeyBind(key_map))
102 }
103}
104
105fn parse_key_event(raw: &str) -> Result<KeyEvent, String> {
106 let raw_lower = raw.to_ascii_lowercase().replace(' ', "");
107 let (remaining, modifiers) = extract_modifiers(&raw_lower);
108 parse_key_code_with_modifiers(remaining, modifiers)
109}
110
111fn extract_modifiers(raw: &str) -> (&str, KeyModifiers) {
112 let mut modifiers = KeyModifiers::empty();
113 let mut current = raw;
114
115 loop {
116 match current {
117 rest if rest.starts_with("ctrl-") => {
118 modifiers.insert(KeyModifiers::CONTROL);
119 current = &rest[5..];
120 }
121 rest if rest.starts_with("alt-") => {
122 modifiers.insert(KeyModifiers::ALT);
123 current = &rest[4..];
124 }
125 rest if rest.starts_with("shift-") => {
126 modifiers.insert(KeyModifiers::SHIFT);
127 current = &rest[6..];
128 }
129 _ => break, };
131 }
132
133 (current, modifiers)
134}
135
136fn parse_key_code_with_modifiers(
137 raw: &str,
138 mut modifiers: KeyModifiers,
139) -> Result<KeyEvent, String> {
140 let c = match raw {
141 "esc" => KeyCode::Esc,
142 "enter" => KeyCode::Enter,
143 "left" => KeyCode::Left,
144 "right" => KeyCode::Right,
145 "up" => KeyCode::Up,
146 "down" => KeyCode::Down,
147 "home" => KeyCode::Home,
148 "end" => KeyCode::End,
149 "pageup" => KeyCode::PageUp,
150 "pagedown" => KeyCode::PageDown,
151 "backtab" => {
152 modifiers.insert(KeyModifiers::SHIFT);
153 KeyCode::BackTab
154 }
155 "backspace" => KeyCode::Backspace,
156 "delete" => KeyCode::Delete,
157 "insert" => KeyCode::Insert,
158 "f1" => KeyCode::F(1),
159 "f2" => KeyCode::F(2),
160 "f3" => KeyCode::F(3),
161 "f4" => KeyCode::F(4),
162 "f5" => KeyCode::F(5),
163 "f6" => KeyCode::F(6),
164 "f7" => KeyCode::F(7),
165 "f8" => KeyCode::F(8),
166 "f9" => KeyCode::F(9),
167 "f10" => KeyCode::F(10),
168 "f11" => KeyCode::F(11),
169 "f12" => KeyCode::F(12),
170 "space" => KeyCode::Char(' '),
171 "hyphen" => KeyCode::Char('-'),
172 "minus" => KeyCode::Char('-'),
173 "tab" => KeyCode::Tab,
174 c if c.len() == 1 => {
175 let mut c = c.chars().next().unwrap();
176 if modifiers.contains(KeyModifiers::SHIFT) {
177 c = c.to_ascii_uppercase();
178 }
179 KeyCode::Char(c)
180 }
181 _ => return Err(format!("Unable to parse {raw}")),
182 };
183 Ok(KeyEvent::new(c, modifiers))
184}
185
186fn key_event_to_string(key_event: KeyEvent) -> String {
187 if let KeyCode::Char(c) = key_event.code {
188 if key_event.modifiers == KeyModifiers::SHIFT {
189 return c.to_ascii_uppercase().into();
190 }
191 }
192
193 let char;
194 let key_code = match key_event.code {
195 KeyCode::Backspace => "Backspace",
196 KeyCode::Enter => "Enter",
197 KeyCode::Left => "Left",
198 KeyCode::Right => "Right",
199 KeyCode::Up => "Up",
200 KeyCode::Down => "Down",
201 KeyCode::Home => "Home",
202 KeyCode::End => "End",
203 KeyCode::PageUp => "PageUp",
204 KeyCode::PageDown => "PageDown",
205 KeyCode::Tab => "Tab",
206 KeyCode::BackTab => "BackTab",
207 KeyCode::Delete => "Delete",
208 KeyCode::Insert => "Insert",
209 KeyCode::F(n) => {
210 char = format!("F{n}");
211 &char
212 }
213 KeyCode::Char(' ') => "Space",
214 KeyCode::Char(c) => {
215 char = c.to_string();
216 &char
217 }
218 KeyCode::Esc => "Esc",
219 KeyCode::Null => "",
220 KeyCode::CapsLock => "",
221 KeyCode::Menu => "",
222 KeyCode::ScrollLock => "",
223 KeyCode::Media(_) => "",
224 KeyCode::NumLock => "",
225 KeyCode::PrintScreen => "",
226 KeyCode::Pause => "",
227 KeyCode::KeypadBegin => "",
228 KeyCode::Modifier(_) => "",
229 };
230
231 let mut modifiers = Vec::with_capacity(3);
232
233 if key_event.modifiers.intersects(KeyModifiers::CONTROL) {
234 modifiers.push("Ctrl");
235 }
236
237 if key_event.modifiers.intersects(KeyModifiers::SHIFT) {
238 modifiers.push("Shift");
239 }
240
241 if key_event.modifiers.intersects(KeyModifiers::ALT) {
242 modifiers.push("Alt");
243 }
244
245 let mut key = modifiers.join("-");
246
247 if !key.is_empty() {
248 key.push('-');
249 }
250 key.push_str(key_code);
251
252 key
253}
254
255#[cfg(test)]
256mod tests {
257 use super::*;
258
259 #[rustfmt::skip]
260 #[test]
261 fn test_deserialize_keybind() {
262 let toml = r#"
263 navigate_up = ["k"]
264 navigate_down = ["j", "down"]
265 navigate_left = ["ctrl-h", "shift-h", "alt-h"]
266 navigate_right = ["ctrl-shift-l", "alt-shift-ctrl-l"]
267 quit = ["esc", "f12"]
268 user_command_view_toggle_1 = ["d"]
269 user_command_view_toggle_10 = ["e"]
270 "#;
271
272 let expected = KeyBind(
273 [
274 (
275 KeyEvent::new(KeyCode::Char('k'), KeyModifiers::empty()),
276 UserEvent::NavigateUp,
277 ),
278 (
279 KeyEvent::new(KeyCode::Char('j'), KeyModifiers::empty()),
280 UserEvent::NavigateDown,
281 ),
282 (
283 KeyEvent::new(KeyCode::Down, KeyModifiers::empty()),
284 UserEvent::NavigateDown,
285 ),
286 (
287 KeyEvent::new(KeyCode::Char('h'), KeyModifiers::CONTROL),
288 UserEvent::NavigateLeft,
289 ),
290 (
291 KeyEvent::new(KeyCode::Char('h'), KeyModifiers::SHIFT),
292 UserEvent::NavigateLeft,
293 ),
294 (
295 KeyEvent::new(KeyCode::Char('h'), KeyModifiers::ALT),
296 UserEvent::NavigateLeft,
297 ),
298 (
299 KeyEvent::new(KeyCode::Char('l'), KeyModifiers::CONTROL | KeyModifiers::SHIFT),
300 UserEvent::NavigateRight,
301 ),
302 (
303 KeyEvent::new(KeyCode::Char('l'), KeyModifiers::CONTROL | KeyModifiers::SHIFT | KeyModifiers::ALT),
304 UserEvent::NavigateRight,
305 ),
306 (
307 KeyEvent::new(KeyCode::Esc, KeyModifiers::empty()),
308 UserEvent::Quit,
309 ),
310 (
311 KeyEvent::new(KeyCode::F(12), KeyModifiers::empty()),
312 UserEvent::Quit,
313 ),
314 (
315 KeyEvent::new(KeyCode::Char('d'), KeyModifiers::empty()),
316 UserEvent::UserCommandViewToggle(1),
317 ),
318 (
319 KeyEvent::new(KeyCode::Char('e'), KeyModifiers::empty()),
320 UserEvent::UserCommandViewToggle(10),
321 ),
322 ]
323 .into_iter()
324 .collect(),
325 );
326
327 let actual: KeyBind = toml::from_str(toml).unwrap();
328
329 assert_eq!(actual, expected);
330 }
331
332 #[rustfmt::skip]
333 #[test]
334 fn test_key_event_to_string() {
335 let key_event = KeyEvent::new(KeyCode::Char('k'), KeyModifiers::empty());
336 assert_eq!(key_event_to_string(key_event), "k");
337
338 let key_event = KeyEvent::new(KeyCode::Char('j'), KeyModifiers::empty());
339 assert_eq!(key_event_to_string(key_event), "j");
340
341 let key_event = KeyEvent::new(KeyCode::Down, KeyModifiers::empty());
342 assert_eq!(key_event_to_string(key_event), "Down");
343
344 let key_event = KeyEvent::new(KeyCode::Char('h'), KeyModifiers::CONTROL);
345 assert_eq!(key_event_to_string(key_event), "Ctrl-h");
346
347 let key_event = KeyEvent::new(KeyCode::Char('h'), KeyModifiers::SHIFT);
348 assert_eq!(key_event_to_string(key_event), "H");
349
350 let key_event = KeyEvent::new(KeyCode::Char('H'), KeyModifiers::SHIFT);
351 assert_eq!(key_event_to_string(key_event), "H");
352
353 let key_event = KeyEvent::new(KeyCode::Left, KeyModifiers::SHIFT);
354 assert_eq!(key_event_to_string(key_event), "Shift-Left");
355
356 let key_event = KeyEvent::new(KeyCode::Char('h'), KeyModifiers::ALT);
357 assert_eq!(key_event_to_string(key_event), "Alt-h");
358
359 let key_event = KeyEvent::new(KeyCode::Char('l'), KeyModifiers::CONTROL | KeyModifiers::SHIFT);
360 assert_eq!(key_event_to_string(key_event), "Ctrl-Shift-l");
361
362 let key_event = KeyEvent::new(KeyCode::Char('l'), KeyModifiers::CONTROL | KeyModifiers::SHIFT | KeyModifiers::ALT);
363 assert_eq!(key_event_to_string(key_event), "Ctrl-Shift-Alt-l");
364
365 let key_event = KeyEvent::new(KeyCode::Esc, KeyModifiers::empty());
366 assert_eq!(key_event_to_string(key_event), "Esc");
367
368 let key_event = KeyEvent::new(KeyCode::F(12), KeyModifiers::empty());
369 assert_eq!(key_event_to_string(key_event), "F12");
370 }
371
372 #[test]
373 fn test_default_keybind_loads() {
374 let keybind = KeyBind::new(None);
377
378 assert!(keybind.values().any(|e| *e == UserEvent::Quit), "quit binding missing");
380 assert!(keybind.values().any(|e| *e == UserEvent::NavigateDown), "navigate_down binding missing");
381 assert!(keybind.values().any(|e| *e == UserEvent::NavigateUp), "navigate_up binding missing");
382 assert!(keybind.values().any(|e| *e == UserEvent::Confirm), "confirm binding missing");
383 assert!(keybind.values().any(|e| *e == UserEvent::Cancel), "cancel binding missing");
384 }
385}