1use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
7
8const MAX_HISTORY: usize = 10;
10
11#[derive(Debug, Clone)]
13pub struct RecordedEvent {
14 pub event: KeyEvent,
16 pub description: String,
18 pub normalized: Option<String>,
22}
23
24impl RecordedEvent {
25 fn new(event: KeyEvent) -> Self {
26 let description = format_key_event(&event);
27 let (norm_code, norm_mods) =
28 crate::input::keybindings::normalize_key(event.code, event.modifiers);
29 let normalized = if norm_code == event.code && norm_mods == event.modifiers {
30 None
31 } else {
32 let norm_event = KeyEvent::new(norm_code, norm_mods);
33 Some(format_key_event(&norm_event))
34 };
35 Self {
36 event,
37 description,
38 normalized,
39 }
40 }
41}
42
43fn format_key_event(event: &KeyEvent) -> String {
45 let mut parts = Vec::new();
46
47 if event.modifiers.contains(KeyModifiers::CONTROL) {
49 parts.push("Ctrl");
50 }
51 if event.modifiers.contains(KeyModifiers::ALT) {
52 parts.push("Alt");
53 }
54 if event.modifiers.contains(KeyModifiers::SHIFT) {
55 parts.push("Shift");
56 }
57 if event.modifiers.contains(KeyModifiers::SUPER) {
58 parts.push("Super");
59 }
60 if event.modifiers.contains(KeyModifiers::HYPER) {
61 parts.push("Hyper");
62 }
63 if event.modifiers.contains(KeyModifiers::META) {
64 parts.push("Meta");
65 }
66
67 let key_str = match event.code {
69 KeyCode::Backspace => "Backspace".to_string(),
70 KeyCode::Enter => "Enter".to_string(),
71 KeyCode::Left => "Left".to_string(),
72 KeyCode::Right => "Right".to_string(),
73 KeyCode::Up => "Up".to_string(),
74 KeyCode::Down => "Down".to_string(),
75 KeyCode::Home => "Home".to_string(),
76 KeyCode::End => "End".to_string(),
77 KeyCode::PageUp => "PageUp".to_string(),
78 KeyCode::PageDown => "PageDown".to_string(),
79 KeyCode::Tab => "Tab".to_string(),
80 KeyCode::BackTab => "BackTab".to_string(),
81 KeyCode::Delete => "Delete".to_string(),
82 KeyCode::Insert => "Insert".to_string(),
83 KeyCode::F(n) => format!("F{}", n),
84 KeyCode::Char(c) => {
85 if c == ' ' {
86 "Space".to_string()
87 } else if c.is_control() {
88 format!("0x{:02x}", c as u8)
89 } else {
90 format!("'{}'", c)
91 }
92 }
93 KeyCode::Null => "Null".to_string(),
94 KeyCode::Esc => "Esc".to_string(),
95 KeyCode::CapsLock => "CapsLock".to_string(),
96 KeyCode::ScrollLock => "ScrollLock".to_string(),
97 KeyCode::NumLock => "NumLock".to_string(),
98 KeyCode::PrintScreen => "PrintScreen".to_string(),
99 KeyCode::Pause => "Pause".to_string(),
100 KeyCode::Menu => "Menu".to_string(),
101 KeyCode::KeypadBegin => "KeypadBegin".to_string(),
102 KeyCode::Modifier(m) => format!("Modifier({:?})", m),
103 KeyCode::Media(m) => format!("Media({:?})", m),
104 };
105
106 parts.push(&key_str);
107
108 if parts.len() > 1 {
110 parts.join("+")
111 } else {
112 key_str
113 }
114}
115
116#[derive(Debug)]
118pub struct EventDebug {
119 pub history: Vec<RecordedEvent>,
121 pub active: bool,
123}
124
125impl EventDebug {
126 pub fn new() -> Self {
128 Self {
129 history: Vec::new(),
130 active: true,
131 }
132 }
133
134 pub fn record_event(&mut self, event: KeyEvent) {
136 if event.modifiers == KeyModifiers::NONE {
138 match event.code {
139 KeyCode::Char('q') | KeyCode::Esc => {
140 self.active = false;
141 return;
142 }
143 KeyCode::Char('c') => {
144 self.history.clear();
146 return;
147 }
148 _ => {}
149 }
150 }
151
152 let recorded = RecordedEvent::new(event);
154 self.history.insert(0, recorded);
155
156 if self.history.len() > MAX_HISTORY {
158 self.history.truncate(MAX_HISTORY);
159 }
160 }
161
162 pub fn should_close(&self) -> bool {
164 !self.active
165 }
166
167 pub fn last_event_details(&self) -> Option<String> {
169 self.history.first().map(|e| {
170 format!(
171 "code={:?}, modifiers={:?} (bits=0x{:02x}), kind={:?}, state={:?}",
172 e.event.code,
173 e.event.modifiers,
174 e.event.modifiers.bits(),
175 e.event.kind,
176 e.event.state
177 )
178 })
179 }
180}
181
182impl Default for EventDebug {
183 fn default() -> Self {
184 Self::new()
185 }
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191
192 #[test]
193 fn test_event_debug_creation() {
194 let debug = EventDebug::new();
195 assert!(debug.active);
196 assert!(debug.history.is_empty());
197 }
198
199 #[test]
200 fn test_record_event() {
201 let mut debug = EventDebug::new();
202 let event = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::CONTROL);
203 debug.record_event(event);
204
205 assert_eq!(debug.history.len(), 1);
206 assert_eq!(debug.history[0].description, "Ctrl+'a'");
207 }
208
209 #[test]
210 fn test_close_with_q() {
211 let mut debug = EventDebug::new();
212 let event = KeyEvent::new(KeyCode::Char('q'), KeyModifiers::NONE);
213 debug.record_event(event);
214
215 assert!(debug.should_close());
216 }
217
218 #[test]
219 fn test_close_with_esc() {
220 let mut debug = EventDebug::new();
221 let event = KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE);
222 debug.record_event(event);
223
224 assert!(debug.should_close());
225 }
226
227 #[test]
228 fn test_clear_with_c() {
229 let mut debug = EventDebug::new();
230
231 debug.record_event(KeyEvent::new(KeyCode::Char('a'), KeyModifiers::NONE));
233 debug.record_event(KeyEvent::new(KeyCode::Char('b'), KeyModifiers::NONE));
234 assert_eq!(debug.history.len(), 2);
235
236 debug.record_event(KeyEvent::new(KeyCode::Char('c'), KeyModifiers::NONE));
238 assert!(debug.history.is_empty());
239 assert!(debug.active); }
241
242 #[test]
243 fn test_max_history() {
244 let mut debug = EventDebug::new();
245
246 for i in 0..15 {
248 debug.record_event(KeyEvent::new(
249 KeyCode::Char((b'a' + i) as char),
250 KeyModifiers::NONE,
251 ));
252 }
253
254 assert_eq!(debug.history.len(), MAX_HISTORY);
255 }
256
257 #[test]
258 fn test_format_modifiers() {
259 let mut debug = EventDebug::new();
260
261 let event = KeyEvent::new(KeyCode::Home, KeyModifiers::CONTROL | KeyModifiers::SHIFT);
263 debug.record_event(event);
264
265 assert_eq!(debug.history[0].description, "Ctrl+Shift+Home");
266 }
267}