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