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() -> Self {
38 toml::from_str(DEFAULT_KEY_BIND).expect("default key bind should be correct")
39 }
40
41 pub fn keys_for_event(&self, user_event: UserEvent) -> Vec<String> {
42 let mut key_events: Vec<KeyEvent> = self
43 .iter()
44 .filter(|(_, ue)| **ue == user_event)
45 .map(|(ke, _)| *ke)
46 .collect();
47 key_events.sort_by(|a, b| a.partial_cmp(b).unwrap());
48 key_events.into_iter().map(key_event_to_string).collect()
49 }
50}
51
52impl<'de> Deserialize<'de> for KeyBind {
53 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
54 where
55 D: Deserializer<'de>,
56 {
57 let parsed_map = FxHashMap::<UserEvent, Vec<String>>::deserialize(deserializer)?;
58 let mut key_map = FxHashMap::<KeyEvent, UserEvent>::default();
59 for (user_event, key_events) in parsed_map {
60 for key_event_str in key_events {
61 let key_event = match parse_key_event(&key_event_str) {
62 Ok(e) => e,
63 Err(s) => {
64 let msg = format!("{key_event_str:?} is not a valid key event: {s:}");
65 return Err(DeError::custom(msg));
66 }
67 };
68 if let Some(conflict_user_event) = key_map.insert(key_event, user_event) {
69 let msg = format!(
70 "{key_event:?} map to multiple events: {user_event:?}, {conflict_user_event:?}"
71 );
72 return Err(DeError::custom(msg));
73 }
74 }
75 }
76
77 Ok(KeyBind(key_map))
78 }
79}
80
81fn parse_key_event(raw: &str) -> Result<KeyEvent, String> {
82 let raw_lower = raw.to_ascii_lowercase().replace(' ', "");
83 let (remaining, modifiers) = extract_modifiers(&raw_lower);
84 parse_key_code_with_modifiers(remaining, modifiers)
85}
86
87fn extract_modifiers(raw: &str) -> (&str, KeyModifiers) {
88 let mut modifiers = KeyModifiers::empty();
89 let mut current = raw;
90
91 loop {
92 match current {
93 rest if rest.starts_with("ctrl-") => {
94 modifiers.insert(KeyModifiers::CONTROL);
95 current = &rest[5..];
96 }
97 rest if rest.starts_with("alt-") => {
98 modifiers.insert(KeyModifiers::ALT);
99 current = &rest[4..];
100 }
101 rest if rest.starts_with("shift-") => {
102 modifiers.insert(KeyModifiers::SHIFT);
103 current = &rest[6..];
104 }
105 _ => break,
106 };
107 }
108
109 (current, modifiers)
110}
111
112fn parse_key_code_with_modifiers(
113 raw: &str,
114 mut modifiers: KeyModifiers,
115) -> Result<KeyEvent, String> {
116 let c = match raw {
117 "esc" => KeyCode::Esc,
118 "enter" => KeyCode::Enter,
119 "left" => KeyCode::Left,
120 "right" => KeyCode::Right,
121 "up" => KeyCode::Up,
122 "down" => KeyCode::Down,
123 "home" => KeyCode::Home,
124 "end" => KeyCode::End,
125 "pageup" => KeyCode::PageUp,
126 "pagedown" => KeyCode::PageDown,
127 "backtab" => {
128 modifiers.insert(KeyModifiers::SHIFT);
129 KeyCode::BackTab
130 }
131 "backspace" => KeyCode::Backspace,
132 "delete" => KeyCode::Delete,
133 "insert" => KeyCode::Insert,
134 "f1" => KeyCode::F(1),
135 "f2" => KeyCode::F(2),
136 "f3" => KeyCode::F(3),
137 "f4" => KeyCode::F(4),
138 "f5" => KeyCode::F(5),
139 "f6" => KeyCode::F(6),
140 "f7" => KeyCode::F(7),
141 "f8" => KeyCode::F(8),
142 "f9" => KeyCode::F(9),
143 "f10" => KeyCode::F(10),
144 "f11" => KeyCode::F(11),
145 "f12" => KeyCode::F(12),
146 "space" => KeyCode::Char(' '),
147 "hyphen" => KeyCode::Char('-'),
148 "minus" => KeyCode::Char('-'),
149 "tab" => KeyCode::Tab,
150 c if c.len() == 1 => {
151 let mut c = c.chars().next().unwrap();
152 if modifiers.contains(KeyModifiers::SHIFT) {
153 c = c.to_ascii_uppercase();
154 }
155 KeyCode::Char(c)
156 }
157 _ => return Err(format!("Unable to parse {raw}")),
158 };
159 Ok(KeyEvent::new(c, modifiers))
160}
161
162fn key_event_to_string(key_event: KeyEvent) -> String {
163 if let KeyCode::Char(c) = key_event.code {
164 if key_event.modifiers == KeyModifiers::SHIFT {
165 return c.to_ascii_uppercase().into();
166 }
167 }
168
169 let char;
170 let key_code = match key_event.code {
171 KeyCode::Backspace => "Backspace",
172 KeyCode::Enter => "Enter",
173 KeyCode::Left => "Left",
174 KeyCode::Right => "Right",
175 KeyCode::Up => "Up",
176 KeyCode::Down => "Down",
177 KeyCode::Home => "Home",
178 KeyCode::End => "End",
179 KeyCode::PageUp => "PageUp",
180 KeyCode::PageDown => "PageDown",
181 KeyCode::Tab => "Tab",
182 KeyCode::BackTab => "BackTab",
183 KeyCode::Delete => "Delete",
184 KeyCode::Insert => "Insert",
185 KeyCode::F(n) => {
186 char = format!("F{n}");
187 &char
188 }
189 KeyCode::Char(' ') => "Space",
190 KeyCode::Char(c) => {
191 char = c.to_string();
192 &char
193 }
194 KeyCode::Esc => "Esc",
195 KeyCode::Null => "",
196 KeyCode::CapsLock => "",
197 KeyCode::Menu => "",
198 KeyCode::ScrollLock => "",
199 KeyCode::Media(_) => "",
200 KeyCode::NumLock => "",
201 KeyCode::PrintScreen => "",
202 KeyCode::Pause => "",
203 KeyCode::KeypadBegin => "",
204 KeyCode::Modifier(_) => "",
205 };
206
207 let mut modifiers_vec = Vec::with_capacity(3);
208
209 if key_event.modifiers.intersects(KeyModifiers::CONTROL) {
210 modifiers_vec.push("Ctrl");
211 }
212
213 if key_event.modifiers.intersects(KeyModifiers::SHIFT) {
214 modifiers_vec.push("Shift");
215 }
216
217 if key_event.modifiers.intersects(KeyModifiers::ALT) {
218 modifiers_vec.push("Alt");
219 }
220
221 let mut key = modifiers_vec.join("-");
222
223 if !key.is_empty() {
224 key.push('-');
225 }
226 key.push_str(key_code);
227
228 key
229}