use crate::*; use std::time::Duration; use std::thread::{spawn, JoinHandle}; use crossterm::event::{poll, read}; #[derive(Debug, Clone)] pub struct TuiIn(pub Arc, pub Event); impl Input for TuiIn { type Event = Event; type Handled = bool; fn event (&self) -> &Event { &self.1 } fn is_done (&self) -> bool { self.0.fetch_and(true, Relaxed) } fn done (&self) { self.0.store(true, Relaxed); } } impl TuiIn { /// Spawn the input thread. pub fn run_input + 'static> ( engine: &Arc>, state: &Arc>, timer: Duration ) -> JoinHandle<()> { let exited = engine.read().unwrap().exited.clone(); let state = state.clone(); spawn(move || loop { if exited.fetch_and(true, Relaxed) { break } if poll(timer).is_ok() { let event = read().unwrap(); match event { crossterm::event::Event::Key(KeyEvent { code: KeyCode::Char('c'), modifiers: KeyModifiers::CONTROL, kind: KeyEventKind::Press, state: KeyEventState::NONE }) => { exited.store(true, Relaxed); }, _ => { let exited = exited.clone(); if let Err(e) = state.write().unwrap().handle(&TuiIn(exited, event)) { panic!("{e}") } } } } }) } } impl AtomInput for TuiIn { fn matches_atom (&self, token: &str) -> bool { if let Some(event) = KeyMatcher::new(token).build() { &event == self.event() } else { false } } //fn get_event (item: &AtomItem>) -> Option { //match item { AtomItem::Sym(s) => KeyMatcher::new(s).build(), _ => None } //} } struct KeyMatcher { valid: bool, key: Option, mods: KeyModifiers, } impl KeyMatcher { fn new (token: impl AsRef) -> Self { let token = token.as_ref(); if token.len() < 2 { Self { valid: false, key: None, mods: KeyModifiers::NONE } } else if token.chars().next() != Some('@') { Self { valid: false, key: None, mods: KeyModifiers::NONE } } else { Self { valid: true, key: None, mods: KeyModifiers::NONE }.next(&token[1..]) } } fn next (mut self, token: &str) -> Self { let mut tokens = token.split('-').peekable(); while let Some(token) = tokens.next() { if tokens.peek().is_some() { match token { "ctrl" | "Ctrl" | "c" | "C" => self.mods |= KeyModifiers::CONTROL, "alt" | "Alt" | "m" | "M" => self.mods |= KeyModifiers::ALT, "shift" | "Shift" | "s" | "S" => { self.mods |= KeyModifiers::SHIFT; // + TODO normalize character case, BackTab, etc. }, _ => panic!("unknown modifier {token}"), } } else { self.key = if token.len() == 1 { Some(KeyCode::Char(token.chars().next().unwrap())) } else { Some(Self::named_key(token).unwrap_or_else(||panic!("unknown character {token}"))) } } } self } fn named_key (token: &str) -> Option { use KeyCode::*; Some(match token { "up" => Up, "down" => Down, "left" => Left, "right" => Right, "enter" | "return" => Enter, "delete" | "del" => Delete, "tab" => Tab, "space" => Char(' '), "comma" => Char(','), "period" => Char('.'), "plus" => Char('+'), "minus" | "dash" => Char('-'), "equal" | "equals" => Char('='), "underscore" => Char('_'), "backtick" => Char('`'), "lt" => Char('<'), "gt" => Char('>'), "openbracket" => Char('['), "closebracket" => Char(']'), _ => return None, }) } fn build (self) -> Option { if self.valid && self.key.is_some() { Some(Event::Key(KeyEvent::new(self.key.unwrap(), self.mods))) } else { None } } } #[cfg(test)] #[test] fn test_parse_key () { use KeyModifiers as Mods; let test = |x: &str, y|assert_eq!(KeyMatcher::new(x).build(), Some(Event::Key(y))); //test(":x", //KeyEvent::new(KeyCode::Char('x'), Mods::NONE)); //test(":ctrl-x", //KeyEvent::new(KeyCode::Char('x'), Mods::CONTROL)); //test(":alt-x", //KeyEvent::new(KeyCode::Char('x'), Mods::ALT)); //test(":shift-x", //KeyEvent::new(KeyCode::Char('x'), Mods::SHIFT)); //test(":ctrl-alt-shift-x", //KeyEvent::new(KeyCode::Char('x'), Mods::CONTROL | Mods::ALT | Mods::SHIFT )); }