aboutsummaryrefslogtreecommitdiffstats
path: root/src/term.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/term.rs')
-rw-r--r--src/term.rs92
1 files changed, 77 insertions, 15 deletions
diff --git a/src/term.rs b/src/term.rs
index 988786d..daef715 100644
--- a/src/term.rs
+++ b/src/term.rs
@@ -1,6 +1,13 @@
use libc::{termios, winsize};
use std::error::Error;
-use std::io::{stdin, stdout, Read, Write};
+use std::io::{stdout, BufRead, 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 ESC: u8 = 27;
pub fn size() -> Result<winsize, Box<dyn Error>> {
unsafe {
@@ -41,6 +48,8 @@ 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;
}
}
@@ -51,7 +60,7 @@ pub struct RawHandle {
impl Drop for RawHandle {
fn drop(&mut self) {
- let _ = write!(stdout(), "\x1b[?7h");
+ let _ = write!(stdout(), "\x1b[?7h\x1b[H\x1b[J");
let _ = stdout().flush();
let _ = set_attr(&self.old);
}
@@ -76,19 +85,16 @@ pub enum Key {
Right,
Backspace,
Char(u8),
+ Timeout,
}
-pub fn read_key() -> Result<Key, Box<dyn Error>> {
- 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 ESC: u8 = 27;
-
+pub fn read_key(stdin: &mut dyn BufRead) -> Result<Key, Box<dyn Error>> {
loop {
let mut buf = vec![0; 1];
- stdin().read_exact(&mut buf)?;
+ let n = stdin.read(&mut buf)?;
+ if n == 0 {
+ return Ok(Key::Timeout);
+ }
if buf[0] == CTRL_Q {
return Ok(Key::CtrlQ);
}
@@ -108,13 +114,34 @@ pub fn read_key() -> Result<Key, Box<dyn Error>> {
return Ok(Key::Char(buf[0]));
}
// Try to handle an escape sequence.
- let mut buf = vec![0; 2];
- stdin().read_exact(&mut buf)?;
+ let buf = stdin.fill_buf()?;
+ if buf.len() == 0 {
+ // Just an escape. Ignore it I guess.
+ continue;
+ }
if buf[0] != b'[' {
// Unknown escape sequence, just read another key.
+ 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;
+ }
+ _ => (),
+ }
+ }
+ let mut escape_sequence = Vec::new();
+ escape_sequence.extend_from_slice(&buf[1..n]);
+ stdin.consume(n);
+ if escape_sequence.len() < 1 {
continue;
}
- match buf[1] {
+ match escape_sequence[0] {
b'A' => {
return Ok(Key::Up);
}
@@ -128,9 +155,44 @@ pub fn read_key() -> Result<Key, Box<dyn Error>> {
return Ok(Key::Left);
}
_ => {
- // Unknown key, just ignore it.
+ // Unknown escape, just skip it.
continue;
}
}
}
}
+
+fn read_status_report(stdin: &mut dyn BufRead) -> Result<(u16, u16), Box<dyn Error>> {
+ 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 + 1 > r || r == buf.len() {
+ return Err(Box::from(format!(
+ "invalid response: {:?}",
+ String::from_utf8_lossy(&buf[0..r])
+ )));
+ }
+ 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>()?;
+ 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);
+}