aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/main.rs118
-rw-r--r--src/term.rs216
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))
}