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