diff options
| author | Rose Hogenson <rosehogenson@posteo.net> | 2024-01-07 14:14:58 -0800 |
|---|---|---|
| committer | Rose Hogenson <rosehogenson@posteo.net> | 2024-01-07 14:14:58 -0800 |
| commit | 95c8e4763eddb2336d6ae7acdb752da94be43f5b (patch) | |
| tree | 08770b419b7097b65d8c395e3e9f508130daea3e /src/term.rs | |
| parent | 6f085692209f06c3427c475322786be06e45160f (diff) | |
| download | editor-95c8e4763eddb2336d6ae7acdb752da94be43f5b.tar.zst | |
Support pasting text into the editor.
The editor only repaints when there is a small delay in the input, which
only happens when the user is typing. Otherwise the bytes go straight
into the buffer.
Diffstat (limited to 'src/term.rs')
| -rw-r--r-- | src/term.rs | 92 |
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); +} |
