//! Input event handling, currently backed by crossterm. use anyhow::{anyhow, Error}; use serde::de::{self, Deserialize, Deserializer}; use std::fmt; use crate::keyboard::{KeyCode, KeyModifiers}; /// Represents a key event. // We use a newtype here because we want to customize Deserialize and Display. #[derive(Debug, PartialEq, Eq, PartialOrd, Clone, Copy, Hash)] pub struct KeyEvent { pub code: KeyCode, pub modifiers: KeyModifiers, } impl fmt::Display for KeyEvent { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result { f.write_fmt(format_args!( "{}{}{}", if self.modifiers.contains(KeyModifiers::SHIFT) { "S-" } else { "" }, if self.modifiers.contains(KeyModifiers::ALT) { "A-" } else { "" }, if self.modifiers.contains(KeyModifiers::CONTROL) { "C-" } else { "" }, ))?; match self.code { KeyCode::Backspace => f.write_str("backspace")?, KeyCode::Enter => f.write_str("ret")?, KeyCode::Left => f.write_str("left")?, KeyCode::Right => f.write_str("right")?, KeyCode::Up => f.write_str("up")?, KeyCode::Down => f.write_str("down")?, KeyCode::Home => f.write_str("home")?, KeyCode::End => f.write_str("end")?, KeyCode::PageUp => f.write_str("pageup")?, KeyCode::PageDown => f.write_str("pagedown")?, KeyCode::Tab => f.write_str("tab")?, KeyCode::BackTab => f.write_str("backtab")?, KeyCode::Delete => f.write_str("del")?, KeyCode::Insert => f.write_str("ins")?, KeyCode::Null => f.write_str("null")?, KeyCode::Esc => f.write_str("esc")?, KeyCode::Char('<') => f.write_str("lt")?, KeyCode::Char('>') => f.write_str("gt")?, KeyCode::Char('+') => f.write_str("plus")?, KeyCode::Char('-') => f.write_str("minus")?, KeyCode::Char(';') => f.write_str("semicolon")?, KeyCode::Char('%') => f.write_str("percent")?, KeyCode::F(i) => f.write_fmt(format_args!("F{}", i))?, KeyCode::Char(c) => f.write_fmt(format_args!("{}", c))?, }; Ok(()) } } impl std::str::FromStr for KeyEvent { type Err = Error; fn from_str(s: &str) -> Result { let mut tokens: Vec<_> = s.split('-').collect(); let code = match tokens.pop().ok_or_else(|| anyhow!("Missing key code"))? { "backspace" => KeyCode::Backspace, "space" => KeyCode::Char(' '), "ret" => KeyCode::Enter, "lt" => KeyCode::Char('<'), "gt" => KeyCode::Char('>'), "plus" => KeyCode::Char('+'), "minus" => KeyCode::Char('-'), "semicolon" => KeyCode::Char(';'), "percent" => KeyCode::Char('%'), "left" => KeyCode::Left, "right" => KeyCode::Right, "up" => KeyCode::Down, "home" => KeyCode::Home, "end" => KeyCode::End, "pageup" => KeyCode::PageUp, "pagedown" => KeyCode::PageDown, "tab" => KeyCode::Tab, "backtab" => KeyCode::BackTab, "del" => KeyCode::Delete, "ins" => KeyCode::Insert, "null" => KeyCode::Null, "esc" => KeyCode::Esc, single if single.len() == 1 => KeyCode::Char(single.chars().next().unwrap()), function if function.len() > 1 && function.starts_with('F') => { let function: String = function.chars().skip(1).collect(); let function = str::parse::(&function)?; (function > 0 && function < 13) .then(|| KeyCode::F(function)) .ok_or_else(|| anyhow!("Invalid function key '{}'", function))? } invalid => return Err(anyhow!("Invalid key code '{}'", invalid)), }; let mut modifiers = KeyModifiers::empty(); for token in tokens { let flag = match token { "S" => KeyModifiers::SHIFT, "A" => KeyModifiers::ALT, "C" => KeyModifiers::CONTROL, _ => return Err(anyhow!("Invalid key modifier '{}-'", token)), }; if modifiers.contains(flag) { return Err(anyhow!("Repeated key modifier '{}-'", token)); } modifiers.insert(flag); } Ok(KeyEvent { code, modifiers }) } } impl<'de> Deserialize<'de> for KeyEvent { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { let s = String::deserialize(deserializer)?; s.parse().map_err(de::Error::custom) } } #[cfg(feature = "term")] impl From for KeyEvent { fn from( crossterm::event::KeyEvent { code, modifiers }: crossterm::event::KeyEvent, ) -> KeyEvent { KeyEvent { code: code.into(), modifiers: modifiers.into(), } } } #[cfg(test)] mod test { use super::*; #[test] fn parsing_unmodified_keys() { assert_eq!( str::parse::("backspace").unwrap(), KeyEvent { code: KeyCode::Backspace, modifiers: KeyModifiers::NONE } ); assert_eq!( str::parse::("left").unwrap(), KeyEvent { code: KeyCode::Left, modifiers: KeyModifiers::NONE } ); assert_eq!( str::parse::(",").unwrap(), KeyEvent { code: KeyCode::Char(','), modifiers: KeyModifiers::NONE } ); assert_eq!( str::parse::("w").unwrap(), KeyEvent { code: KeyCode::Char('w'), modifiers: KeyModifiers::NONE } ); assert_eq!( str::parse::("F12").unwrap(), KeyEvent { code: KeyCode::F(12), modifiers: KeyModifiers::NONE } ); } #[test] fn parsing_modified_keys() { assert_eq!( str::parse::("S-minus").unwrap(), KeyEvent { code: KeyCode::Char('-'), modifiers: KeyModifiers::SHIFT } ); assert_eq!( str::parse::("C-A-S-F12").unwrap(), KeyEvent { code: KeyCode::F(12), modifiers: KeyModifiers::SHIFT | KeyModifiers::CONTROL | KeyModifiers::ALT } ); assert_eq!( str::parse::("S-C-2").unwrap(), KeyEvent { code: KeyCode::Char('2'), modifiers: KeyModifiers::SHIFT | KeyModifiers::CONTROL } ); } #[test] fn parsing_nonsensical_keys_fails() { assert!(str::parse::("F13").is_err()); assert!(str::parse::("F0").is_err()); assert!(str::parse::("aaa").is_err()); assert!(str::parse::("S-S-a").is_err()); assert!(str::parse::("C-A-S-C-1").is_err()); assert!(str::parse::("FU").is_err()); assert!(str::parse::("123").is_err()); assert!(str::parse::("S--").is_err()); } }