use std::io::Read; use crate::cursor::Direction; pub enum KeyboardInput { Eof, Key(u8), CtrlA, CtrlB, CtrlC, CtrlE, CtrlD, CtrlL, CtrlR, Arrow(Direction), CtrlArrow(Direction), DeleteLeft, DeleteRight, CtrlDeleteRight, Home, End, } fn read1() -> Option { let mut buf = [0]; match std::io::stdin().lock().read_exact(&mut buf) { Ok(_) => Some(buf[0]), Err(_) => None, } } fn byte_to_dir(b: u8) -> Option { use Direction::*; match b { b'A' => Some(Up), b'B' => Some(Down), b'C' => Some(Right), b'D' => Some(Left), _ => None, } } fn read_escape(debug: bool) -> KeyboardInput { use Direction::*; use KeyboardInput::*; let mut seq = vec![match read1() { Some(x) => x, None => return Eof, }]; if seq[0] == b'[' { // still more while { let last = seq[seq.len() - 1]; !(0x40..=0x7E).contains(&last) || seq.len() == 1 } { seq.push(match read1() { Some(x) => x, None => return Eof, }); } if debug { println!("escape: {}", seq.escape_ascii()); } match seq[1] { b'3' => { if seq.len() > 2 && seq[2] == b'~' { DeleteRight } else { todo!("unhandled: {}", seq.escape_ascii()); } } b'H' => Home, b'F' => End, b'd' => CtrlDeleteRight, // Ctrl Arrow b'1' => { if seq[1..].starts_with(b"1;5") { if seq.len() == 4 { todo!("idk what this is."); } match seq[4] { b'A' => CtrlArrow(Up), b'B' => CtrlArrow(Down), b'C' => CtrlArrow(Right), b'D' => CtrlArrow(Left), _ => todo!("unhandled {}", seq.escape_ascii()), } } else { todo!("unhandled {}", seq[1..].escape_ascii()) } } x => { if let Some(dir) = byte_to_dir(x) { Arrow(dir) } else { todo!("escape characters {}", seq[1..].escape_ascii()) } } } } else { if debug { println!("escape: {}", seq.escape_ascii()); } match seq[0] { b'd' => CtrlDeleteRight, x => todo!("unhandled escape code: ESC {x}"), } } } pub fn read(debug: bool) -> KeyboardInput { use KeyboardInput::*; let Some(x) = read1() else { return KeyboardInput::Eof; }; match x { 1 => CtrlA, 2 => CtrlB, 3 => CtrlC, 4 => CtrlD, 8 | 127 => DeleteLeft, 12 => CtrlL, 18 => CtrlR, 27 => read_escape(debug), b'\r' => Key(x), x if !x.is_ascii_control() => Key(x), x => todo!("unimplemented control code: {x}"), } }