use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; /// Key modifiers #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub struct Modifiers { pub ctrl: bool, pub alt: bool, pub shift: bool, } impl From for Modifiers { fn from(m: KeyModifiers) -> Self { Self { ctrl: m.contains(KeyModifiers::CONTROL), alt: m.contains(KeyModifiers::ALT), shift: m.contains(KeyModifiers::SHIFT), } } } /// Abstracted key input #[derive(Debug, Clone, PartialEq, Eq)] pub enum Key { Char(char), Backspace, Delete, Enter, Tab, BackTab, Escape, Up, Down, Left, Right, Home, End, PageUp, PageDown, F(u8), Null, } impl Key { pub fn from_crossterm(event: KeyEvent) -> (Self, Modifiers) { let modifiers = Modifiers::from(event.modifiers); let key = match event.code { KeyCode::Char(c) => { // Some terminals report Enter/newline as Char('\r') or Char('\n') // during paste or raw key input. Normalize those to Enter so // multiline paste preserves line breaks. if c == '\r' || c == '\n' { return (Key::Enter, modifiers); } // If shift is pressed and character is lowercase alphabetic, // uppercase it. This handles terminals that don't support // REPORT_ALTERNATE_KEYS properly (which would report 'A' directly). let c = if modifiers.shift && c.is_ascii_lowercase() { c.to_ascii_uppercase() } else { c }; Key::Char(c) } KeyCode::Backspace => Key::Backspace, KeyCode::Delete => Key::Delete, KeyCode::Enter => Key::Enter, KeyCode::Tab => Key::Tab, KeyCode::BackTab => Key::BackTab, KeyCode::Esc => Key::Escape, KeyCode::Up => Key::Up, KeyCode::Down => Key::Down, KeyCode::Left => Key::Left, KeyCode::Right => Key::Right, KeyCode::Home => Key::Home, KeyCode::End => Key::End, KeyCode::PageUp => Key::PageUp, KeyCode::PageDown => Key::PageDown, KeyCode::F(n) => Key::F(n), KeyCode::Null => Key::Null, _ => Key::Null, }; (key, modifiers) } } #[cfg(test)] mod tests { use super::*; use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; #[test] fn char_carriage_return_maps_to_enter() { let (key, mods) = Key::from_crossterm(KeyEvent::new( KeyCode::Char('\r'), KeyModifiers::NONE, )); assert_eq!(key, Key::Enter); assert_eq!(mods, Modifiers::default()); } #[test] fn char_newline_maps_to_enter() { let (key, mods) = Key::from_crossterm(KeyEvent::new( KeyCode::Char('\n'), KeyModifiers::SHIFT, )); assert_eq!(key, Key::Enter); assert_eq!( mods, Modifiers { ctrl: false, alt: false, shift: true, } ); } }