1use crate::core::constants::DOUBLE_ESC_THRESHOLD;
6use crate::core::prelude::*;
7use crossterm::event::KeyModifiers;
8
9#[derive(Debug, Clone, PartialEq)]
10pub enum KeyAction {
11 MoveLeft,
12 MoveRight,
13 MoveToStart,
14 MoveToEnd,
15 InsertChar(char),
16 Backspace,
17 Delete,
18 Submit,
19 Cancel,
20 Quit,
21 ClearLine,
22 CopySelection,
23 PasteBuffer,
24 NoAction,
25 ScrollUp,
26 ScrollDown,
27 PageUp,
28 PageDown,
29}
30
31use lazy_static::lazy_static;
32use std::sync::Mutex;
33
34lazy_static! {
35 static ref LAST_ESC_PRESS: Mutex<Option<Instant>> = Mutex::new(None);
36}
37
38pub struct KeyboardManager {
39 double_press_threshold: Duration,
40}
41
42impl KeyboardManager {
43 pub fn new() -> Self {
44 Self {
45 double_press_threshold: Duration::from_millis(DOUBLE_ESC_THRESHOLD),
46 }
47 }
48
49 fn detect_broken_cmd_event(&self) -> bool {
50 false }
52
53 pub fn get_action(&mut self, key: &KeyEvent) -> KeyAction {
54 if key.modifiers.contains(KeyModifiers::SUPER) {
56 log::warn!(
57 "π RAW Cmd Event: code={:?}, modifiers={:?}, char={}",
58 key.code,
59 key.modifiers,
60 match key.code {
61 KeyCode::Char(c) => format!("'{}'", c),
62 _ => "none".to_string(),
63 }
64 );
65 }
66
67 if key.code == KeyCode::Esc {
69 let now = Instant::now();
70 let mut last_press = LAST_ESC_PRESS.lock().unwrap_or_else(|poisoned| {
71 log::warn!("Recovered from poisoned mutex");
72 poisoned.into_inner()
73 });
74
75 if let Some(prev_press) = *last_press {
76 if now.duration_since(prev_press) <= self.double_press_threshold {
77 *last_press = None;
78 log::info!("πͺ Double ESC detected - Quit requested");
79 return KeyAction::Quit;
80 }
81 }
82
83 *last_press = Some(now);
84 return KeyAction::NoAction;
85 }
86
87 match (key.code, key.modifiers) {
89 (KeyCode::Left, KeyModifiers::NONE) => KeyAction::MoveLeft,
91 (KeyCode::Right, KeyModifiers::NONE) => KeyAction::MoveRight,
92 (KeyCode::Home, KeyModifiers::NONE) => KeyAction::MoveToStart,
93 (KeyCode::End, KeyModifiers::NONE) => KeyAction::MoveToEnd,
94
95 (KeyCode::Enter, KeyModifiers::NONE) => KeyAction::Submit,
97
98 (KeyCode::Up, KeyModifiers::SHIFT) => KeyAction::ScrollUp,
100 (KeyCode::Down, KeyModifiers::SHIFT) => KeyAction::ScrollDown,
101 (KeyCode::PageUp, KeyModifiers::NONE) => KeyAction::PageUp,
102 (KeyCode::PageDown, KeyModifiers::NONE) => KeyAction::PageDown,
103
104 (KeyCode::Backspace, KeyModifiers::NONE) => KeyAction::Backspace,
106 (KeyCode::Delete, KeyModifiers::NONE) => KeyAction::Delete,
107
108 (KeyCode::Char('v'), KeyModifiers::NONE) if self.detect_broken_cmd_event() => {
110 log::warn!("π Detected broken Cmd+V event, treating as paste");
111 KeyAction::PasteBuffer
112 }
113 (KeyCode::Char('c'), KeyModifiers::NONE) if self.detect_broken_cmd_event() => {
114 log::warn!("π Detected broken Cmd+C event, treating as copy");
115 KeyAction::CopySelection
116 }
117 (KeyCode::Char('a'), KeyModifiers::NONE) if self.detect_broken_cmd_event() => {
118 log::warn!("π Detected broken Cmd+A event, treating as move to start");
119 KeyAction::MoveToStart
120 }
121
122 (KeyCode::Char('a'), KeyModifiers::SUPER) => {
124 log::debug!("π Cmd+A β Start");
125 KeyAction::MoveToStart
126 }
127 (KeyCode::Char('e'), KeyModifiers::SUPER) => {
128 log::debug!("π Cmd+E β End");
129 KeyAction::MoveToEnd
130 }
131 (KeyCode::Char('u'), KeyModifiers::SUPER) => {
132 log::debug!("π Cmd+U β Clear");
133 KeyAction::ClearLine
134 }
135 (KeyCode::Char('c'), KeyModifiers::SUPER) => {
136 log::debug!("π Cmd+C β Copy");
137 KeyAction::CopySelection
138 }
139 (KeyCode::Char('v'), KeyModifiers::SUPER) => {
140 log::debug!("π Cmd+V β Paste");
141 KeyAction::PasteBuffer
142 }
143
144 (KeyCode::Char('a'), KeyModifiers::ALT) => {
146 log::debug!("π Opt+A β Start");
147 KeyAction::MoveToStart
148 }
149 (KeyCode::Char('e'), KeyModifiers::ALT) => {
150 log::debug!("π Opt+E β End");
151 KeyAction::MoveToEnd
152 }
153 (KeyCode::Char('u'), KeyModifiers::ALT) => {
154 log::debug!("π Opt+U β Clear");
155 KeyAction::ClearLine
156 }
157 (KeyCode::Char('c'), KeyModifiers::ALT) => {
158 log::debug!("π Opt+C β Copy");
159 KeyAction::CopySelection
160 }
161 (KeyCode::Char('v'), KeyModifiers::ALT) => {
162 log::debug!("π Opt+V β Paste");
163 KeyAction::PasteBuffer
164 }
165
166 (KeyCode::Char('a'), KeyModifiers::CONTROL) => {
168 log::debug!("π₯οΈ Ctrl+A β Start");
169 KeyAction::MoveToStart
170 }
171 (KeyCode::Char('e'), KeyModifiers::CONTROL) => {
172 log::debug!("π₯οΈ Ctrl+E β End");
173 KeyAction::MoveToEnd
174 }
175 (KeyCode::Char('u'), KeyModifiers::CONTROL) => {
176 log::debug!("π₯οΈ Ctrl+U β Clear");
177 KeyAction::ClearLine
178 }
179 (KeyCode::Char('c'), KeyModifiers::CONTROL) => {
180 log::debug!("π₯οΈ Ctrl+C β Copy");
181 KeyAction::CopySelection
182 }
183 (KeyCode::Char('v'), KeyModifiers::CONTROL) => {
184 log::debug!("π₯οΈ Ctrl+V β Paste");
185 KeyAction::PasteBuffer
186 }
187
188 (KeyCode::Backspace, KeyModifiers::SUPER) => {
190 log::debug!("π Cmd+β« β Clear");
191 KeyAction::ClearLine
192 }
193 (KeyCode::Backspace, KeyModifiers::ALT) => {
194 log::debug!("π Opt+β« β Clear");
195 KeyAction::ClearLine
196 }
197 (KeyCode::Backspace, KeyModifiers::CONTROL) => {
198 log::debug!("π₯οΈ Ctrl+β« β Clear");
199 KeyAction::ClearLine
200 }
201
202 (KeyCode::Char('\\'), KeyModifiers::SUPER) => {
204 log::debug!("π Cmd+\\ β Clear");
205 KeyAction::ClearLine
206 }
207 (KeyCode::Char('\\'), KeyModifiers::ALT) => {
208 log::debug!("π Opt+\\ β Clear");
209 KeyAction::ClearLine
210 }
211
212 (KeyCode::Left, KeyModifiers::SUPER) => {
214 log::debug!("π Cmd+β β Start");
215 KeyAction::MoveToStart
216 }
217 (KeyCode::Right, KeyModifiers::SUPER) => {
218 log::debug!("π Cmd+β β End");
219 KeyAction::MoveToEnd
220 }
221 (KeyCode::Left, KeyModifiers::CONTROL) => {
222 log::debug!("π₯οΈ Ctrl+β β Start");
223 KeyAction::MoveToStart
224 }
225 (KeyCode::Right, KeyModifiers::CONTROL) => {
226 log::debug!("π₯οΈ Ctrl+β β End");
227 KeyAction::MoveToEnd
228 }
229 (KeyCode::Left, KeyModifiers::ALT) => {
230 log::debug!("π Opt+β β Start");
231 KeyAction::MoveToStart
232 }
233 (KeyCode::Right, KeyModifiers::ALT) => {
234 log::debug!("π Opt+β β End");
235 KeyAction::MoveToEnd
236 }
237
238 (KeyCode::Char(c), KeyModifiers::NONE) => KeyAction::InsertChar(c),
240 (KeyCode::Char(c), KeyModifiers::SHIFT) => KeyAction::InsertChar(c),
241
242 (code, modifiers) => {
244 log::debug!("β Unhandled key combination: {:?} + {:?}", code, modifiers);
245 KeyAction::NoAction
246 }
247 }
248 }
249}
250
251impl Default for KeyboardManager {
252 fn default() -> Self {
253 Self::new()
254 }
255}
256
257#[cfg(test)]
259mod tests {
260 use super::*;
261 use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
262
263 #[test]
264 fn test_shift_support() {
265 let mut manager = KeyboardManager::new();
266
267 let key_a = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::NONE);
269 assert_eq!(manager.get_action(&key_a), KeyAction::InsertChar('a'));
270
271 let key_a_upper = KeyEvent::new(KeyCode::Char('A'), KeyModifiers::SHIFT);
273 assert_eq!(manager.get_action(&key_a_upper), KeyAction::InsertChar('A'));
274
275 let key_exclamation = KeyEvent::new(KeyCode::Char('!'), KeyModifiers::SHIFT);
277 assert_eq!(
278 manager.get_action(&key_exclamation),
279 KeyAction::InsertChar('!')
280 );
281
282 let key_at = KeyEvent::new(KeyCode::Char('@'), KeyModifiers::SHIFT);
284 assert_eq!(manager.get_action(&key_at), KeyAction::InsertChar('@'));
285 }
286
287 #[test]
288 fn test_mac_specific_shortcuts() {
289 let mut manager = KeyboardManager::new();
290
291 let opt_a = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::ALT);
293 assert_eq!(manager.get_action(&opt_a), KeyAction::MoveToStart);
294
295 let opt_e = KeyEvent::new(KeyCode::Char('e'), KeyModifiers::ALT);
296 assert_eq!(manager.get_action(&opt_e), KeyAction::MoveToEnd);
297
298 let opt_u = KeyEvent::new(KeyCode::Char('u'), KeyModifiers::ALT);
299 assert_eq!(manager.get_action(&opt_u), KeyAction::ClearLine);
300
301 let opt_backspace = KeyEvent::new(KeyCode::Backspace, KeyModifiers::ALT);
303 assert_eq!(manager.get_action(&opt_backspace), KeyAction::ClearLine);
304
305 let cmd_backspace = KeyEvent::new(KeyCode::Backspace, KeyModifiers::SUPER);
306 assert_eq!(manager.get_action(&cmd_backspace), KeyAction::ClearLine);
307
308 let ctrl_backspace = KeyEvent::new(KeyCode::Backspace, KeyModifiers::CONTROL);
309 assert_eq!(manager.get_action(&ctrl_backspace), KeyAction::ClearLine);
310
311 let cmd_backslash = KeyEvent::new(KeyCode::Char('\\'), KeyModifiers::SUPER);
313 assert_eq!(manager.get_action(&cmd_backslash), KeyAction::ClearLine);
314
315 let opt_backslash = KeyEvent::new(KeyCode::Char('\\'), KeyModifiers::ALT);
316 assert_eq!(manager.get_action(&opt_backslash), KeyAction::ClearLine);
317
318 let ctrl_a = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::CONTROL);
320 assert_eq!(manager.get_action(&ctrl_a), KeyAction::MoveToStart);
321
322 let ctrl_e = KeyEvent::new(KeyCode::Char('e'), KeyModifiers::CONTROL);
323 assert_eq!(manager.get_action(&ctrl_e), KeyAction::MoveToEnd);
324
325 let cmd_left = KeyEvent::new(KeyCode::Left, KeyModifiers::SUPER);
327 assert_eq!(manager.get_action(&cmd_left), KeyAction::MoveToStart);
328
329 let opt_left = KeyEvent::new(KeyCode::Left, KeyModifiers::ALT);
330 assert_eq!(manager.get_action(&opt_left), KeyAction::MoveToStart);
331
332 let ctrl_left = KeyEvent::new(KeyCode::Left, KeyModifiers::CONTROL);
333 assert_eq!(manager.get_action(&ctrl_left), KeyAction::MoveToStart);
334 }
335
336 #[test]
337 fn test_special_characters() {
338 let mut manager = KeyboardManager::new();
339
340 let key_Γ€ = KeyEvent::new(KeyCode::Char('Γ€'), KeyModifiers::NONE);
342 assert_eq!(manager.get_action(&key_Γ€), KeyAction::InsertChar('Γ€'));
343
344 let key_ae_upper = KeyEvent::new(KeyCode::Char('Γ'), KeyModifiers::SHIFT);
345 assert_eq!(
346 manager.get_action(&key_ae_upper),
347 KeyAction::InsertChar('Γ')
348 );
349
350 let key_emoji = KeyEvent::new(KeyCode::Char('π'), KeyModifiers::NONE);
352 assert_eq!(manager.get_action(&key_emoji), KeyAction::InsertChar('π'));
353 }
354}