use libc::{termios, winsize}; use std::error::Error; use std::io::{stdout, BufRead, BufReader, Read, Stdin, Write}; const ESC: u8 = 27; pub fn size() -> Result> { unsafe { let mut size = std::mem::zeroed(); if libc::ioctl(1, libc::TIOCGWINSZ, &mut size) < 0 { return Err(Box::from("ioctl failed")); } Ok(size) } } pub struct RawHandle { old_attr: termios, } impl Drop for RawHandle { fn drop(&mut self) { unsafe { libc::tcsetattr(1, libc::TCSANOW, &self.old_attr); } let _ = write!(stdout(), "\x1b[?7h\x1b[2J"); let _ = stdout().flush(); } } pub fn raw_mode() -> Result> { unsafe { let mut attr = std::mem::zeroed(); if libc::tcgetattr(1, &mut attr) < 0 { return Err(Box::from("tcgetattr failed")); } let mut raw = std::mem::zeroed(); libc::cfmakeraw(&mut raw); raw.c_cc[libc::VTIME] = 1; raw.c_cc[libc::VMIN] = 0; if libc::tcsetattr(1, libc::TCSANOW, &raw) < 0 { return Err(Box::from("tcsetattr failed")); } write!(stdout(), "\x1b[?7l")?; stdout().flush()?; Ok(RawHandle { old_attr: attr }) } } pub enum Key { Timeout, Up, Down, Left, Right, Home, End, PgDn, PgUp, Byte(u8), } pub fn read_key(stdin: &mut BufReader) -> Result> { loop { let mut buf = vec![0; 1]; let n = stdin.read(&mut buf)?; if n == 0 { return Ok(Key::Timeout); } if buf[0] != ESC { return Ok(Key::Byte(buf[0])); } let buf = stdin.fill_buf()?; if buf.is_empty() { continue; } if buf[0] != b'[' || buf.len() == 1 { stdin.consume(1); continue; } // An escape sequence usually starts with [, then has one or two numbers separated by // semicolon, and ends with some terminating character. To try and munch the whole // sequence, skip over any numbers and semicolon here. let mut n = 1; while let b'0'..=b'9' | b';' = buf[n] { n += 1; if n == buf.len() - 1 { break; } } // Skip the terminating character. n += 1; let seq = buf[1..n].to_vec(); stdin.consume(n); let Ok(s) = String::from_utf8(seq) else { continue; }; match s.as_str() { "A" => { return Ok(Key::Up); } "B" => { return Ok(Key::Down); } "C" => { return Ok(Key::Right); } "D" => { return Ok(Key::Left); } "H" | "1~" => { return Ok(Key::Home); } "F" | "8~" => { return Ok(Key::End); } "5~" => { return Ok(Key::PgUp); } "6~" => { return Ok(Key::PgDn); } _ => { continue; } } } } // cursor_pos *WILL* fail if the user is typing on the keyboard. Be sure to handle // errors appropriately. pub fn cursor_pos(stdin: &mut BufReader) -> Result<(u16, u16), Box> { if !stdin.buffer().is_empty() { // If there is any buffered input, the below call to fill_buf will just return the buffered // data and not actually read the terminal response. return Err(Box::from("interrupted")); } write!(stdout(), "\x1b[6n")?; stdout().flush()?; let buf = stdin.fill_buf()?; let mut semicolon = buf.len(); let mut r = buf.len(); for (i, &c) in buf.iter().enumerate() { if c == b';' { semicolon = i; } if c == b'R' { r = i; break; } } if buf.len() < 6 || buf[0] != ESC || buf[1] != b'[' || semicolon < 2 || semicolon + 1 > r || r == buf.len() { return Err(Box::from("invalid response")); } let row = std::str::from_utf8(&buf[2..semicolon])?.parse::()?; let col = std::str::from_utf8(&buf[semicolon + 1..r])?.parse::()?; stdin.consume(r + 1); Ok((row - 1, col - 1)) }