aboutsummaryrefslogtreecommitdiffstats
path: root/src/term.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/term.rs')
-rw-r--r--src/term.rs163
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))
}