diff options
Diffstat (limited to 'src/term.rs')
| -rw-r--r-- | src/term.rs | 163 |
1 files changed, 51 insertions, 112 deletions
diff --git a/src/term.rs b/src/term.rs index 673bf2e..33d9d7c 100644 --- a/src/term.rs +++ b/src/term.rs @@ -1,135 +1,82 @@ use libc::{termios, winsize}; use std::error::Error; -use std::io::{stdout, BufRead, Write}; +use std::io::{stdout, BufRead, BufReader, Read, Stdin, Write}; -const CTRL_Q: u8 = 17; -const CTRL_S: u8 = 19; -const CTRL_Y: u8 = 25; -const CTRL_Z: u8 = 26; -const BACKSPACE: u8 = 127; -const OTHER_BACKSPACE: u8 = 8; const ESC: u8 = 27; pub fn size() -> Result<winsize, Box<dyn Error>> { unsafe { - let mut window: winsize = std::mem::zeroed(); - let status = libc::ioctl(1, libc::TIOCGWINSZ, &mut window); - if status < 0 { - return Err(Box::from(format!( - "terminal size: ioctl failed with code {}", - status - ))); + let mut size = std::mem::zeroed(); + if libc::ioctl(1, libc::TIOCGWINSZ, &mut size) < 0 { + return Err(Box::from("ioctl failed")); } - return Ok(window); - } -} - -fn get_attr() -> Result<termios, Box<dyn Error>> { - unsafe { - let mut attr: termios = std::mem::zeroed(); - let status = libc::tcgetattr(1, &mut attr); - if status < 0 { - return Err(Box::from(format!("tcgetattr failed with code {}", status))); - } - return Ok(attr); - } -} - -fn set_attr(attrs: &termios) -> Result<(), Box<dyn Error>> { - unsafe { - let status = libc::tcsetattr(1, libc::TCSANOW, attrs); - if status < 0 { - return Err(Box::from(format!("set attributes: code {}", status))); - } - return Ok(()); - } -} - -fn make_raw() -> termios { - unsafe { - let mut attr: termios = std::mem::zeroed(); - libc::cfmakeraw(&mut attr); - attr.c_cc[libc::VMIN] = 0; // Return zero bytes on timeout. - attr.c_cc[libc::VTIME] = 1; // 100 ms timeout. - return attr; + Ok(size) } } pub struct RawHandle { - old: termios, + old_attr: termios, } impl Drop for RawHandle { fn drop(&mut self) { - let _ = write!(stdout(), "\x1b[?7h\x1b[H\x1b[J"); + unsafe { + libc::tcsetattr(1, libc::TCSANOW, &self.old_attr); + } + let _ = write!(stdout(), "\x1b[?7h\x1b[2J"); let _ = stdout().flush(); - let _ = set_attr(&self.old); } } -pub fn raw() -> Result<RawHandle, Box<dyn Error>> { - write!(stdout(), "\x1b[?7l")?; - stdout().flush()?; - let old = get_attr()?; - set_attr(&make_raw())?; - return Ok(RawHandle { old: old }); +pub fn raw_mode() -> Result<RawHandle, Box<dyn Error>> { + 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 }) + } } -#[derive(Debug)] pub enum Key { - CtrlQ, - CtrlS, - CtrlZ, - CtrlY, + Timeout, Up, Down, Left, Right, - Backspace, - Char(u8), - Timeout, + Byte(u8), } -pub fn read_key(stdin: &mut dyn BufRead) -> Result<Key, Box<dyn Error>> { +pub fn read_key(stdin: &mut BufReader<Stdin>) -> Result<Key, Box<dyn Error>> { loop { let mut buf = vec![0; 1]; let n = stdin.read(&mut buf)?; if n == 0 { return Ok(Key::Timeout); } - if buf[0] == CTRL_Q { - return Ok(Key::CtrlQ); - } - if buf[0] == CTRL_S { - return Ok(Key::CtrlS); - } - if buf[0] == CTRL_Y { - return Ok(Key::CtrlY); - } - if buf[0] == CTRL_Z { - return Ok(Key::CtrlZ); - } - if buf[0] == BACKSPACE || buf[0] == OTHER_BACKSPACE { - return Ok(Key::Backspace); - } if buf[0] != ESC { - return Ok(Key::Char(buf[0])); + return Ok(Key::Byte(buf[0])); } - // Try to handle an escape sequence. let buf = stdin.fill_buf()?; - if buf.len() == 0 { - // Just an escape. Ignore it I guess. + if buf.is_empty() { continue; } - if buf[0] != b'[' { - // Unknown escape sequence, just read another key. + if buf[0] != b'[' || buf.len() == 1 { stdin.consume(1); continue; } let mut n = buf.len(); for (i, &c) in buf.iter().enumerate() { match c { - // Escape sequences usually end with a letter. b'A'..=b'Z' | b'a'..=b'z' => { n = i + 1; break; @@ -137,13 +84,9 @@ pub fn read_key(stdin: &mut dyn BufRead) -> Result<Key, Box<dyn Error>> { _ => (), } } - let mut escape_sequence = Vec::new(); - escape_sequence.extend_from_slice(&buf[1..n]); + let c = buf[1]; stdin.consume(n); - if escape_sequence.len() < 1 { - continue; - } - match escape_sequence[0] { + match c { b'A' => { return Ok(Key::Up); } @@ -157,14 +100,17 @@ pub fn read_key(stdin: &mut dyn BufRead) -> Result<Key, Box<dyn Error>> { return Ok(Key::Left); } _ => { - // Unknown escape, just skip it. continue; } } } } -fn read_status_report(stdin: &mut dyn BufRead) -> Result<(u16, u16), Box<dyn Error>> { +// 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<Stdin>) -> Result<(u16, u16), Box<dyn Error>> { + write!(stdout(), "\x1b[6n")?; + stdout().flush()?; let buf = stdin.fill_buf()?; let mut semicolon = buf.len(); let mut r = buf.len(); @@ -177,24 +123,17 @@ fn read_status_report(stdin: &mut dyn BufRead) -> Result<(u16, u16), Box<dyn Err break; } } - if buf.len() < 6 || buf[0] != ESC || buf[1] != b'[' || semicolon + 1 > r || r == buf.len() { - return Err(Box::from(format!( - "invalid response: {:?}", - String::from_utf8_lossy(&buf[0..r]) - ))); + 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_str = std::str::from_utf8(&buf[2..semicolon])?; - let col_str = std::str::from_utf8(&buf[semicolon + 1..r])?; - let row = row_str.parse::<u16>()?; - let col = col_str.parse::<u16>()?; + let row = std::str::from_utf8(&buf[2..semicolon])?.parse::<u16>()?; + let col = std::str::from_utf8(&buf[semicolon + 1..r])?.parse::<u16>()?; stdin.consume(r + 1); - // Yuck... 1 indexing - return Ok((row - 1, col - 1)); -} - -pub fn cursor_pos(stdin: &mut dyn BufRead) -> Result<(u16, u16), Box<dyn Error>> { - write!(stdout(), "\x1b[6n")?; - stdout().flush()?; - let pos = read_status_report(stdin)?; - return Ok(pos); + Ok((row - 1, col - 1)) } |
