1use crate::core::constants::DOUBLE_ESC_THRESHOLD;
2use crate::core::prelude::*;
3use crossterm::event::KeyModifiers;
4use std::sync::{LazyLock, Mutex};
5
6#[derive(Debug, Clone, PartialEq)]
7pub enum KeyAction {
8 MoveLeft,
9 MoveRight,
10 MoveToStart,
11 MoveToEnd,
12 InsertChar(char),
13 Backspace,
14 Delete,
15 Submit,
16 Cancel,
17 Quit,
18 ClearLine,
19 CopySelection,
20 PasteBuffer,
21 NoAction,
22 ScrollUp,
23 ScrollDown,
24 PageUp,
25 PageDown,
26}
27
28static LAST_ESC_PRESS: LazyLock<Mutex<Option<Instant>>> = LazyLock::new(|| Mutex::new(None));
29static ESCAPE_SEQUENCE_BUFFER: LazyLock<Mutex<Vec<char>>> =
30 LazyLock::new(|| Mutex::new(Vec::new()));
31
32pub struct KeyboardManager {
33 double_press_threshold: Duration,
34 sequence_timeout: Duration,
35 last_key_time: Instant,
36}
37
38impl KeyboardManager {
39 pub fn new() -> Self {
40 Self {
41 double_press_threshold: Duration::from_millis(DOUBLE_ESC_THRESHOLD),
42 sequence_timeout: Duration::from_millis(100),
43 last_key_time: Instant::now(),
44 }
45 }
46
47 fn is_safe_char(&mut self, c: char) -> bool {
49 if matches!(c, '\x00'..='\x08' | '\x0B'..='\x0C' | '\x0E'..='\x1F' | '\x7F') {
51 return false;
52 }
53
54 if !c.is_ascii() && !c.is_alphabetic() && !"äöüßÄÖÜ€".contains(c) {
56 return false;
57 }
58
59 !self.detect_terminal_sequence(c)
61 }
62
63 fn detect_terminal_sequence(&mut self, c: char) -> bool {
64 let now = Instant::now();
65
66 if now.duration_since(self.last_key_time) > self.sequence_timeout {
68 if let Ok(mut buffer) = ESCAPE_SEQUENCE_BUFFER.lock() {
69 buffer.clear();
70 }
71 }
72 self.last_key_time = now;
73
74 if let Ok(mut buffer) = ESCAPE_SEQUENCE_BUFFER.lock() {
76 buffer.push(c);
77 let sequence: String = buffer.iter().collect();
78
79 let is_dangerous = sequence.to_lowercase().contains("tmux")
81 || (sequence.len() > 3
82 && sequence.chars().all(|ch| ch.is_ascii_digit() || ch == ';'))
83 || sequence.contains("///")
84 || sequence.contains(";;;");
85
86 if is_dangerous {
87 buffer.clear();
88 }
89 if buffer.len() > 20 {
90 buffer.drain(0..10);
91 }
92
93 is_dangerous
94 } else {
95 false
96 }
97 }
98
99 pub fn get_action(&mut self, key: &KeyEvent) -> KeyAction {
100 if key.code == KeyCode::Esc {
102 return self.handle_escape();
103 }
104
105 if let KeyCode::Char(c) = key.code {
107 if !self.is_safe_char(c) {
108 return KeyAction::NoAction;
109 }
110 }
111
112 if key.modifiers.contains(KeyModifiers::SHIFT) {
114 match key.code {
115 KeyCode::Up => return KeyAction::ScrollUp,
116 KeyCode::Down => return KeyAction::ScrollDown,
117 _ => {}
118 }
119 }
120
121 match (key.code, key.modifiers) {
123 (KeyCode::Left, KeyModifiers::NONE) => KeyAction::MoveLeft,
125 (KeyCode::Right, KeyModifiers::NONE) => KeyAction::MoveRight,
126 (KeyCode::Home, KeyModifiers::NONE) => KeyAction::MoveToStart,
127 (KeyCode::End, KeyModifiers::NONE) => KeyAction::MoveToEnd,
128 (KeyCode::Enter, KeyModifiers::NONE) => KeyAction::Submit,
129
130 (KeyCode::PageUp, KeyModifiers::NONE) => KeyAction::PageUp,
132 (KeyCode::PageDown, KeyModifiers::NONE) => KeyAction::PageDown,
133
134 (KeyCode::Backspace, KeyModifiers::NONE) => KeyAction::Backspace,
136 (KeyCode::Delete, KeyModifiers::NONE) => KeyAction::Delete,
137
138 (KeyCode::Char(c), mods) => self.handle_char_with_modifiers(c, mods),
140
141 (KeyCode::Left, mods) if self.is_move_modifier(mods) => KeyAction::MoveToStart,
143 (KeyCode::Right, mods) if self.is_move_modifier(mods) => KeyAction::MoveToEnd,
144
145 (KeyCode::Backspace, mods) if self.is_clear_modifier(mods) => KeyAction::ClearLine,
147
148 _ => KeyAction::NoAction,
149 }
150 }
151
152 fn handle_escape(&self) -> KeyAction {
153 let now = Instant::now();
154 let mut last_press = LAST_ESC_PRESS.lock().unwrap_or_else(|p| p.into_inner());
155
156 if let Some(prev_press) = *last_press {
157 if now.duration_since(prev_press) <= self.double_press_threshold {
158 *last_press = None;
159 return KeyAction::Quit;
160 }
161 }
162
163 *last_press = Some(now);
164 KeyAction::NoAction
165 }
166
167 fn handle_char_with_modifiers(&self, c: char, mods: KeyModifiers) -> KeyAction {
168 if mods.is_empty() || mods == KeyModifiers::SHIFT {
170 return if c.is_ascii_control() && c != '\t' {
171 KeyAction::NoAction
172 } else {
173 KeyAction::InsertChar(c)
174 };
175 }
176
177 match c {
179 'c' if self.is_copy_modifier(mods) => KeyAction::CopySelection,
180 'v' if self.is_paste_modifier(mods) => KeyAction::PasteBuffer,
181 'x' if self.is_cut_modifier(mods) => KeyAction::ClearLine,
182 'a' if self.is_select_modifier(mods) => KeyAction::MoveToStart,
183 'e' if self.is_end_modifier(mods) => KeyAction::MoveToEnd,
184 'u' if self.is_clear_modifier(mods) => KeyAction::ClearLine,
185 _ => KeyAction::NoAction,
186 }
187 }
188
189 fn is_copy_modifier(&self, mods: KeyModifiers) -> bool {
191 mods.contains(KeyModifiers::SUPER)
192 || mods.contains(KeyModifiers::CONTROL)
193 || mods.contains(KeyModifiers::ALT)
194 }
195
196 fn is_paste_modifier(&self, mods: KeyModifiers) -> bool {
197 self.is_copy_modifier(mods)
198 }
199 fn is_cut_modifier(&self, mods: KeyModifiers) -> bool {
200 self.is_copy_modifier(mods)
201 }
202 fn is_select_modifier(&self, mods: KeyModifiers) -> bool {
203 self.is_copy_modifier(mods)
204 }
205 fn is_end_modifier(&self, mods: KeyModifiers) -> bool {
206 mods.contains(KeyModifiers::CONTROL) || mods.contains(KeyModifiers::ALT)
207 }
208 fn is_clear_modifier(&self, mods: KeyModifiers) -> bool {
209 self.is_copy_modifier(mods)
210 }
211 fn is_move_modifier(&self, mods: KeyModifiers) -> bool {
212 self.is_copy_modifier(mods)
213 }
214}
215
216impl Default for KeyboardManager {
217 fn default() -> Self {
218 Self::new()
219 }
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225 use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
226
227 #[test]
228 fn test_escape_sequence_filtering() {
229 let mut manager = KeyboardManager::new();
230
231 let ctrl_char = KeyEvent::new(KeyCode::Char('\x1B'), KeyModifiers::NONE);
233 assert_eq!(manager.get_action(&ctrl_char), KeyAction::NoAction);
234
235 let normal_char = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::NONE);
237 assert_eq!(manager.get_action(&normal_char), KeyAction::InsertChar('a'));
238 }
239
240 #[test]
241 fn test_platform_shortcuts() {
242 let mut manager = KeyboardManager::new();
243
244 let cmd_c = KeyEvent::new(KeyCode::Char('c'), KeyModifiers::SUPER);
246 assert_eq!(manager.get_action(&cmd_c), KeyAction::CopySelection);
247
248 let ctrl_c = KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL);
250 assert_eq!(manager.get_action(&ctrl_c), KeyAction::CopySelection);
251
252 let alt_c = KeyEvent::new(KeyCode::Char('c'), KeyModifiers::ALT);
254 assert_eq!(manager.get_action(&alt_c), KeyAction::CopySelection);
255 }
256
257 #[test]
258 fn test_scroll_actions() {
259 let mut manager = KeyboardManager::new();
260
261 let shift_up = KeyEvent::new(KeyCode::Up, KeyModifiers::SHIFT);
262 assert_eq!(manager.get_action(&shift_up), KeyAction::ScrollUp);
263
264 let shift_down = KeyEvent::new(KeyCode::Down, KeyModifiers::SHIFT);
265 assert_eq!(manager.get_action(&shift_down), KeyAction::ScrollDown);
266 }
267
268 #[test]
269 fn test_double_escape() {
270 let mut manager = KeyboardManager::new();
271 let esc_key = KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE);
272
273 assert_eq!(manager.get_action(&esc_key), KeyAction::NoAction);
275
276 }
279}