diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/main.rs | 118 | ||||
| -rw-r--r-- | src/term.rs | 216 |
2 files changed, 186 insertions, 148 deletions
diff --git a/src/main.rs b/src/main.rs index 9c30520..b40d9b1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,9 +5,10 @@ use rope::Rope; use std::error::Error; use std::ffi::{OsStr, OsString}; use std::fs::File; -use std::io::{stderr, stdout, BufReader, Stdin, Write}; +use std::io::{stderr, stdout, Write}; use std::path::{Path, PathBuf}; use term::Key; +use term::Reader; fn open(file: &Path) -> Result<Rope, std::io::Error> { let mut f = File::open(file)?; @@ -15,11 +16,7 @@ fn open(file: &Path) -> Result<Rope, std::io::Error> { Ok(r) } -fn line_offset( - stdin: &mut BufReader<Stdin>, - line: &Rope, - col: u16, -) -> Result<usize, Box<dyn Error>> { +fn line_offset(stdin: &mut Reader, line: &Rope, col: u16) -> Result<usize, Box<dyn Error>> { let mut n = 0; for i in 0.. { if i == line.len() { @@ -49,7 +46,7 @@ fn line_offset( } write!(stdout(), "\x1b[G")?; line.slice(0, h).print(&mut stdout())?; - let (_, c) = term::cursor_pos(stdin)?; + let (_, c) = stdin.cursor_pos()?; if c <= col { lo = h + 1; if lo < line.len() { @@ -64,14 +61,14 @@ fn line_offset( } if lo == 0 { // You might think this case is impossible (I did), but terminals can do a lot of - // weird things. - return Ok(0); + // weird things and I'm trying to make this robust. + return Err(Box::from("incorrect cursor_pos")); } Ok(line.floor_char_boundary(lo - 1)) } fn floor_grapheme_cluster( - stdin: &mut BufReader<Stdin>, + stdin: &mut Reader, line: &Rope, offset: usize, ) -> Result<usize, Box<dyn Error>> { @@ -82,11 +79,11 @@ fn floor_grapheme_cluster( write!(stdout(), "\x1b[G")?; line.slice(0, line.ceil_char_boundary(offset + 1)) .print(&mut stdout())?; - let (_, target_col) = term::cursor_pos(stdin)?; + let (_, target_col) = stdin.cursor_pos()?; while offset > 0 { write!(stdout(), "\x1b[G")?; line.slice(0, offset).print(&mut stdout())?; - let (_, col) = term::cursor_pos(stdin)?; + let (_, col) = stdin.cursor_pos()?; if col < target_col { return Ok(offset); } @@ -96,7 +93,7 @@ fn floor_grapheme_cluster( } fn ceil_grapheme_cluster( - stdin: &mut BufReader<Stdin>, + stdin: &mut Reader, line: &Rope, offset: usize, ) -> Result<usize, Box<dyn Error>> { @@ -106,7 +103,7 @@ fn ceil_grapheme_cluster( } write!(stdout(), "\x1b[G")?; line.slice(0, offset).print(&mut stdout())?; - let (_, target_col) = term::cursor_pos(stdin)?; + let (_, target_col) = stdin.cursor_pos()?; loop { if offset >= line.len() { return Ok(line.len()); @@ -114,7 +111,7 @@ fn ceil_grapheme_cluster( let old_offset = offset; offset = line.ceil_char_boundary(old_offset + 1); line.slice(old_offset, offset).print(&mut stdout())?; - let (_, col) = term::cursor_pos(stdin)?; + let (_, col) = stdin.cursor_pos()?; if col > target_col { return Ok(line.floor_char_boundary(offset - 1)); } @@ -139,6 +136,7 @@ struct State { cursor_row: u16, cursor_col: u16, + stdin: Reader, path: PathBuf, inserting: bool, need_repaint_screen: bool, @@ -150,12 +148,12 @@ impl State { self.buf.line(self.row_start + usize::from(self.cursor_row)) } - fn update_cursor_col(&mut self, stdin: &mut BufReader<Stdin>) -> Result<(), Box<dyn Error>> { + fn update_cursor_col(&mut self) -> Result<(), Box<dyn Error>> { let size = term::size()?; let line = self.line(); write!(stdout(), "\x1b[{}H", self.cursor_row + 1)?; line.slice(0, self.line_offset).print(&mut stdout())?; - let (_, col) = term::cursor_pos(stdin)?; + let (_, col) = self.stdin.cursor_pos()?; if col == size.ws_col - 1 { self.cursor_col = col - 1; write!( @@ -237,7 +235,7 @@ impl State { self.history.push(self.snapshot()); } - fn keypress(&mut self, stdin: &mut BufReader<Stdin>, key: Key) -> Result<bool, Box<dyn Error>> { + fn keypress(&mut self, key: Key) -> Result<bool, Box<dyn Error>> { const CTRL_Q: u8 = b'Q' - b'@'; const CTRL_S: u8 = b'S' - b'@'; const CTRL_Y: u8 = b'Y' - b'@'; @@ -260,7 +258,7 @@ impl State { self.repaint_screen()?; self.need_repaint_screen = false; } - self.update_cursor_col(stdin)?; + self.update_cursor_col()?; self.need_repaint_line = false; stdout().flush()?; } @@ -272,17 +270,17 @@ impl State { .buf .line(self.row_start + usize::from(self.cursor_row) - 1); if self.cursor_row == 0 { - self.line_offset = line_offset(stdin, &prev_line, self.cursor_col)?; + self.line_offset = line_offset(&mut self.stdin, &prev_line, self.cursor_col)?; self.row_start -= 1; self.repaint_screen()?; - self.update_cursor_col(stdin)?; + self.update_cursor_col()?; stdout().flush()?; return Ok(true); } write!(stdout(), "\x1b[{}H", self.cursor_row)?; - self.line_offset = line_offset(stdin, &prev_line, self.cursor_col)?; + self.line_offset = line_offset(&mut self.stdin, &prev_line, self.cursor_col)?; self.cursor_row -= 1; - self.update_cursor_col(stdin)?; + self.update_cursor_col()?; stdout().flush()?; } Key::Down => { @@ -295,23 +293,24 @@ impl State { .line(self.row_start + usize::from(self.cursor_row) + 1); if self.cursor_row < size.ws_row - 1 { write!(stdout(), "\x1b[{}H", self.cursor_row + 2)?; - self.line_offset = line_offset(stdin, &next_line, self.cursor_col)?; + self.line_offset = line_offset(&mut self.stdin, &next_line, self.cursor_col)?; self.cursor_row += 1; - self.update_cursor_col(stdin)?; + self.update_cursor_col()?; stdout().flush()?; return Ok(true); } - self.line_offset = line_offset(stdin, &next_line, self.cursor_col)?; + self.line_offset = line_offset(&mut self.stdin, &next_line, self.cursor_col)?; self.row_start += 1; self.repaint_screen()?; - self.update_cursor_col(stdin)?; + self.update_cursor_col()?; stdout().flush()?; } Key::Left => { if self.line_offset > 0 { + let line = self.line(); self.line_offset = - floor_grapheme_cluster(stdin, &self.line(), self.line_offset - 1)?; - self.update_cursor_col(stdin)?; + floor_grapheme_cluster(&mut self.stdin, &line, self.line_offset - 1)?; + self.update_cursor_col()?; stdout().flush()?; return Ok(true); } @@ -319,14 +318,14 @@ impl State { let size = term::size()?; write!(stdout(), "\x1b[{}H", self.cursor_row)?; self.line_offset = line_offset( - stdin, + &mut self.stdin, &self .buf .line(self.row_start + usize::from(self.cursor_row) - 1), size.ws_col - 2, )?; self.cursor_row -= 1; - self.update_cursor_col(stdin)?; + self.update_cursor_col()?; stdout().flush()?; return Ok(true); } @@ -335,7 +334,7 @@ impl State { } let size = term::size()?; self.line_offset = line_offset( - stdin, + &mut self.stdin, &self .buf .line(self.row_start + usize::from(self.cursor_row) - 1), @@ -343,15 +342,16 @@ impl State { )?; self.row_start -= 1; self.repaint_screen()?; - self.update_cursor_col(stdin)?; + self.update_cursor_col()?; stdout().flush()?; } Key::Right => { let size = term::size()?; let line = self.line(); if self.cursor_col < size.ws_col - 2 && self.line_offset < line.len() { - self.line_offset = ceil_grapheme_cluster(stdin, &line, self.line_offset + 1)?; - self.update_cursor_col(stdin)?; + self.line_offset = + ceil_grapheme_cluster(&mut self.stdin, &line, self.line_offset + 1)?; + self.update_cursor_col()?; stdout().flush()?; return Ok(true); } @@ -381,8 +381,9 @@ impl State { } Key::End => { let size = term::size()?; - self.line_offset = line_offset(stdin, &self.line(), size.ws_col - 2)?; - self.update_cursor_col(stdin)?; + let line = self.line(); + self.line_offset = line_offset(&mut self.stdin, &line, size.ws_col - 2)?; + self.update_cursor_col()?; stdout().flush()?; } Key::PgUp => { @@ -400,13 +401,13 @@ impl State { start = self.row_start + 2 - usize::from(size.ws_row); }; self.line_offset = line_offset( - stdin, + &mut self.stdin, &self.buf.line(start + usize::from(self.cursor_row)), self.cursor_col, )?; self.row_start = start; self.repaint_screen()?; - self.update_cursor_col(stdin)?; + self.update_cursor_col()?; stdout().flush()?; } Key::PgDn => { @@ -415,7 +416,7 @@ impl State { self.cursor_row = u16::try_from(self.buf.lines() - self.row_start) .expect("self.buf.lines() - self.row_start < size.ws_row"); self.line_offset = self.line().len(); - self.update_cursor_col(stdin)?; + self.update_cursor_col()?; stdout().flush()?; return Ok(true); } @@ -425,23 +426,26 @@ impl State { } if start + usize::from(self.cursor_row) <= self.buf.lines() { self.line_offset = line_offset( - stdin, + &mut self.stdin, &self.buf.line(start + usize::from(self.cursor_row)), self.cursor_col, )?; self.row_start = start; self.repaint_screen()?; - self.update_cursor_col(stdin)?; + self.update_cursor_col()?; stdout().flush()?; return Ok(true); } - self.line_offset = - line_offset(stdin, &self.buf.line(self.buf.lines()), self.cursor_col)?; + self.line_offset = line_offset( + &mut self.stdin, + &self.buf.line(self.buf.lines()), + self.cursor_col, + )?; self.row_start = start; self.cursor_row = u16::try_from(self.buf.lines() - start) .expect("start + self.cursor_row > self.buf.lines()"); self.repaint_screen()?; - self.update_cursor_col(stdin)?; + self.update_cursor_col()?; stdout().flush()?; } Key::Byte(CTRL_Q) => { @@ -463,7 +467,7 @@ impl State { } self.load(); self.repaint_screen()?; - self.update_cursor_col(stdin)?; + self.update_cursor_col()?; stdout().flush()?; } Key::Byte(CTRL_Y) => { @@ -476,7 +480,7 @@ impl State { self.n = Some(n + 1); self.load(); self.repaint_screen()?; - self.update_cursor_col(stdin)?; + self.update_cursor_col()?; stdout().flush()?; } Key::Byte(BACKSPACE) | Key::Byte(OTHER_BACKSPACE) => { @@ -500,21 +504,21 @@ impl State { .slice(0, offset - 1) .concat(&self.buf.slice(offset, self.buf.len())); self.repaint_screen()?; - self.update_cursor_col(stdin)?; + self.update_cursor_col()?; stdout().flush()?; return Ok(true); } let line = self.line(); - self.line_offset = match floor_grapheme_cluster(stdin, &line, self.line_offset - 1) - { - Ok(x) => x, - Err(_) => line.floor_char_boundary(self.line_offset - 1), - }; + self.line_offset = + match floor_grapheme_cluster(&mut self.stdin, &line, self.line_offset - 1) { + Ok(x) => x, + Err(_) => line.floor_char_boundary(self.line_offset - 1), + }; self.buf = self .buf .slice(0, line_start + self.line_offset) .concat(&self.buf.slice(offset, self.buf.len())); - self.update_cursor_col(stdin)?; + self.update_cursor_col()?; stdout().flush()?; } Key::Byte(b'\r') | Key::Byte(b'\n') => { @@ -575,7 +579,6 @@ fn edit(file: &OsStr) -> Result<(), String> { return Err(format!("enter raw mode: {}", err)); } }; - let mut stdin = BufReader::new(std::io::stdin()); let mut state = State { history: vec![Snapshot { buf: r.clone(), @@ -592,6 +595,7 @@ fn edit(file: &OsStr) -> Result<(), String> { cursor_col: 0, line_offset: 0, + stdin: Reader::new(std::io::stdin()), path: file, inserting: false, need_repaint_line: false, @@ -607,10 +611,10 @@ fn edit(file: &OsStr) -> Result<(), String> { return Err(format!("cannot write to screen: {}", err)); } loop { - let Ok(key) = term::read_key(&mut stdin) else { + let Ok(key) = state.stdin.read_key() else { return Err(String::from("no input")); }; - let Ok(ok) = state.keypress(&mut stdin, key) else { + let Ok(ok) = state.keypress(key) else { state.need_repaint_screen = true; let _ = stdout().flush(); continue; diff --git a/src/term.rs b/src/term.rs index bf06538..6b892bf 100644 --- a/src/term.rs +++ b/src/term.rs @@ -1,6 +1,6 @@ use libc::{termios, winsize}; use std::error::Error; -use std::io::{stdout, BufRead, BufReader, Read, Stdin, Write}; +use std::io::{stdout, BufRead, BufReader, Stdin, Write}; const ESC: u8 = 27; @@ -47,6 +47,32 @@ pub fn raw_mode() -> Result<RawHandle, Box<dyn Error>> { } } +fn parse_status_report(buf: &[u8]) -> Result<(u16, u16), Box<dyn Error>> { + 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() - 1 + { + return Err(Box::from("invalid response")); + } + let row = std::str::from_utf8(&buf[2..semicolon])?.parse::<u16>()?; + let col = std::str::from_utf8(&buf[semicolon + 1..r])?.parse::<u16>()?; + Ok((row - 1, col - 1)) +} + pub enum Key { Timeout, Up, @@ -60,108 +86,116 @@ pub enum Key { Byte(u8), } -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] != 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; - } - } +pub struct Reader { + stdin: BufReader<Stdin>, + // expected_status_reports counts the number of unread status reports expected to come in + // on stdin. + expected_status_reports: i64, +} - // Skip the terminating character. - n += 1; +impl Reader { + pub fn new(stdin: Stdin) -> Reader { + Reader { + stdin: BufReader::new(stdin), + expected_status_reports: 0, + } + } - 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); + pub fn read_key(&mut self) -> Result<Key, Box<dyn Error>> { + loop { + let buf = self.stdin.fill_buf()?; + if buf.is_empty() { + // No buffered input, so let's give up on any status reports we were expecting. + self.expected_status_reports = 0; + return Ok(Key::Timeout); } - "B" => { - return Ok(Key::Down); + if buf[0] != ESC { + let b = buf[0]; + self.stdin.consume(1); + return Ok(Key::Byte(b)); } - "C" => { - return Ok(Key::Right); - } - "D" => { - return Ok(Key::Left); - } - "H" | "1~" => { - return Ok(Key::Home); - } - "F" | "8~" => { - return Ok(Key::End); + if buf.len() < 3 || buf[1] != b'[' { + // Unknown escape sequence, eat the escape and one more byte. + self.stdin.consume(2); + continue; } - "5~" => { - return Ok(Key::PgUp); + // 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 = 2; + while let b'0'..=b'9' | b';' = buf[n] { + n += 1; + if n == buf.len() - 1 { + break; + } } - "6~" => { - return Ok(Key::PgDn); + + // Skip the terminating character. + n += 1; + + let seq = buf[..n].to_vec(); + self.stdin.consume(n); + + if seq[seq.len() - 1] == b'R' && parse_status_report(&seq).is_ok() { + if self.expected_status_reports > 0 { + self.expected_status_reports -= 1; + } + continue; } - _ => { + + let Ok(s) = String::from_utf8(seq) else { continue; + }; + match &s[2..] { + "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<Stdin>) -> Result<(u16, u16), Box<dyn Error>> { - 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; + // cursor_pos *WILL* fail if the user is typing on the keyboard. Be sure to handle + // errors appropriately. + pub fn cursor_pos(&mut self) -> Result<(u16, u16), Box<dyn Error>> { + if !self.stdin.buffer().is_empty() || self.expected_status_reports > 0 { + // 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. Also abort if we are + // expecting any status reports from previous failed calls to cursor_pos, since we + // would read an old response and return an incorrect answer. + return Err(Box::from("interrupted")); } - 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")); + write!(stdout(), "\x1b[6n")?; + self.expected_status_reports += 1; + stdout().flush()?; + let buf = self.stdin.fill_buf()?; + let (row, col) = parse_status_report(buf)?; + let n = buf.len(); + self.stdin.consume(n); + self.expected_status_reports -= 1; + Ok((row, col)) } - 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); - Ok((row - 1, col - 1)) } |
