use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
const MAX_HISTORY: usize = 10;
#[derive(Debug, Clone)]
pub struct RecordedEvent {
pub event: KeyEvent,
pub description: String,
}
impl RecordedEvent {
fn new(event: KeyEvent) -> Self {
let description = format_key_event(&event);
Self { event, description }
}
}
fn format_key_event(event: &KeyEvent) -> String {
let mut parts = Vec::new();
if event.modifiers.contains(KeyModifiers::CONTROL) {
parts.push("Ctrl");
}
if event.modifiers.contains(KeyModifiers::ALT) {
parts.push("Alt");
}
if event.modifiers.contains(KeyModifiers::SHIFT) {
parts.push("Shift");
}
if event.modifiers.contains(KeyModifiers::SUPER) {
parts.push("Super");
}
if event.modifiers.contains(KeyModifiers::HYPER) {
parts.push("Hyper");
}
if event.modifiers.contains(KeyModifiers::META) {
parts.push("Meta");
}
let key_str = match event.code {
KeyCode::Backspace => "Backspace".to_string(),
KeyCode::Enter => "Enter".to_string(),
KeyCode::Left => "Left".to_string(),
KeyCode::Right => "Right".to_string(),
KeyCode::Up => "Up".to_string(),
KeyCode::Down => "Down".to_string(),
KeyCode::Home => "Home".to_string(),
KeyCode::End => "End".to_string(),
KeyCode::PageUp => "PageUp".to_string(),
KeyCode::PageDown => "PageDown".to_string(),
KeyCode::Tab => "Tab".to_string(),
KeyCode::BackTab => "BackTab".to_string(),
KeyCode::Delete => "Delete".to_string(),
KeyCode::Insert => "Insert".to_string(),
KeyCode::F(n) => format!("F{}", n),
KeyCode::Char(c) => {
if c == ' ' {
"Space".to_string()
} else if c.is_control() {
format!("0x{:02x}", c as u8)
} else {
format!("'{}'", c)
}
}
KeyCode::Null => "Null".to_string(),
KeyCode::Esc => "Esc".to_string(),
KeyCode::CapsLock => "CapsLock".to_string(),
KeyCode::ScrollLock => "ScrollLock".to_string(),
KeyCode::NumLock => "NumLock".to_string(),
KeyCode::PrintScreen => "PrintScreen".to_string(),
KeyCode::Pause => "Pause".to_string(),
KeyCode::Menu => "Menu".to_string(),
KeyCode::KeypadBegin => "KeypadBegin".to_string(),
KeyCode::Modifier(m) => format!("Modifier({:?})", m),
KeyCode::Media(m) => format!("Media({:?})", m),
};
parts.push(&key_str);
if parts.len() > 1 {
parts.join("+")
} else {
key_str
}
}
#[derive(Debug)]
pub struct EventDebug {
pub history: Vec<RecordedEvent>,
pub active: bool,
}
impl EventDebug {
pub fn new() -> Self {
Self {
history: Vec::new(),
active: true,
}
}
pub fn record_event(&mut self, event: KeyEvent) {
if event.modifiers == KeyModifiers::NONE {
match event.code {
KeyCode::Char('q') | KeyCode::Esc => {
self.active = false;
return;
}
KeyCode::Char('c') => {
self.history.clear();
return;
}
_ => {}
}
}
let recorded = RecordedEvent::new(event);
self.history.insert(0, recorded);
if self.history.len() > MAX_HISTORY {
self.history.truncate(MAX_HISTORY);
}
}
pub fn should_close(&self) -> bool {
!self.active
}
pub fn last_event_details(&self) -> Option<String> {
self.history.first().map(|e| {
format!(
"code={:?}, modifiers={:?} (bits=0x{:02x}), kind={:?}, state={:?}",
e.event.code,
e.event.modifiers,
e.event.modifiers.bits(),
e.event.kind,
e.event.state
)
})
}
}
impl Default for EventDebug {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_event_debug_creation() {
let debug = EventDebug::new();
assert!(debug.active);
assert!(debug.history.is_empty());
}
#[test]
fn test_record_event() {
let mut debug = EventDebug::new();
let event = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::CONTROL);
debug.record_event(event);
assert_eq!(debug.history.len(), 1);
assert_eq!(debug.history[0].description, "Ctrl+'a'");
}
#[test]
fn test_close_with_q() {
let mut debug = EventDebug::new();
let event = KeyEvent::new(KeyCode::Char('q'), KeyModifiers::NONE);
debug.record_event(event);
assert!(debug.should_close());
}
#[test]
fn test_close_with_esc() {
let mut debug = EventDebug::new();
let event = KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE);
debug.record_event(event);
assert!(debug.should_close());
}
#[test]
fn test_clear_with_c() {
let mut debug = EventDebug::new();
debug.record_event(KeyEvent::new(KeyCode::Char('a'), KeyModifiers::NONE));
debug.record_event(KeyEvent::new(KeyCode::Char('b'), KeyModifiers::NONE));
assert_eq!(debug.history.len(), 2);
debug.record_event(KeyEvent::new(KeyCode::Char('c'), KeyModifiers::NONE));
assert!(debug.history.is_empty());
assert!(debug.active); }
#[test]
fn test_max_history() {
let mut debug = EventDebug::new();
for i in 0..15 {
debug.record_event(KeyEvent::new(
KeyCode::Char((b'a' + i) as char),
KeyModifiers::NONE,
));
}
assert_eq!(debug.history.len(), MAX_HISTORY);
}
#[test]
fn test_format_modifiers() {
let mut debug = EventDebug::new();
let event = KeyEvent::new(KeyCode::Home, KeyModifiers::CONTROL | KeyModifiers::SHIFT);
debug.record_event(event);
assert_eq!(debug.history[0].description, "Ctrl+Shift+Home");
}
}