diff options
| author | Rose Hogenson <rosehogenson@posteo.net> | 2024-01-11 00:20:11 -0800 |
|---|---|---|
| committer | Rose Hogenson <rosehogenson@posteo.net> | 2024-01-11 00:20:11 -0800 |
| commit | db087d43659f0760a02d6c32dc7dc0baf2f914f0 (patch) | |
| tree | e514935b6c9dc7c9e835a64671aa0c6a80493595 | |
| parent | 57f527f2a1d9bc4ed8c862b014867634803831ba (diff) | |
| download | editor-db087d43659f0760a02d6c32dc7dc0baf2f914f0.tar.zst | |
Rewrite the entire thing.
They say you can always do a better job on the rewrite, and to a certain
extent that was true here. I kept a lot of it the same though, since
there was a lot I liked from the original design. The main improvements
are in efficiency and code clarity.
| -rw-r--r-- | src/main.rs | 778 | ||||
| -rw-r--r-- | src/rope.rs | 559 | ||||
| -rw-r--r-- | src/term.rs | 163 |
3 files changed, 754 insertions, 746 deletions
diff --git a/src/main.rs b/src/main.rs index 8e0cff8..7c672e4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,403 +4,531 @@ mod term; use rope::Rope; use std::error::Error; use std::ffi::{OsStr, OsString}; -use std::io::{stdout, BufReader, Stdin, Write}; -use std::ops::{Deref, DerefMut}; -use std::path::Path; +use std::fs::File; +use std::io::{stderr, stdout, BufReader, Stdin, Write}; +use std::path::{Path, PathBuf}; use term::Key; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum Mode { - Insert, - Paste, - MultilinePaste, - Normal, +fn open(file: &Path) -> Result<Rope, std::io::Error> { + let mut f = File::open(file)?; + let r = Rope::read(&mut f)?; + Ok(r) } -#[derive(Debug, Clone)] -struct State { +fn line_offset( + stdin: &mut BufReader<Stdin>, + line: &Rope, + col: u16, +) -> Result<usize, Box<dyn Error>> { + // Binary search :D + let mut lo = 0; + let mut hi = line.len() + 1; + while lo < hi { + let mut h = lo + (hi - lo) / 2; + if h < line.len() { + h = line.floor_char_boundary(h); + } + write!(stdout(), "\x1b[G")?; + line.slice(0, h).print(&mut stdout())?; + let (_, c) = term::cursor_pos(stdin)?; + if c <= col { + lo = h + 1; + if lo < line.len() { + lo = line.ceil_char_boundary(lo); + } + } else { + hi = h; + } + } + if lo == line.len() + 1 { + return Ok(line.len()); + } + if lo == 0 { + // You might think this case is impossible (I did), but terminals can do a lot of + // weird things. + return Ok(0); + } + Ok(line.floor_char_boundary(lo - 1)) +} + +fn floor_grapheme_cluster( + stdin: &mut BufReader<Stdin>, + line: &Rope, + offset: usize, +) -> Result<usize, Box<dyn Error>> { + if offset == 0 || offset == line.len() { + return Ok(offset); + } + let mut offset = line.floor_char_boundary(offset); + write!(stdout(), "\x1b[G")?; + line.slice(0, line.ceil_char_boundary(offset + 1)) + .print(&mut stdout())?; + let (_, target_col) = term::cursor_pos(stdin)?; + while offset > 0 { + write!(stdout(), "\x1b[G")?; + line.slice(0, offset).print(&mut stdout())?; + let (_, col) = term::cursor_pos(stdin)?; + if col < target_col { + return Ok(offset); + } + offset = line.floor_char_boundary(offset - 1); + } + Ok(0) +} + +fn ceil_grapheme_cluster( + stdin: &mut BufReader<Stdin>, + line: &Rope, + offset: usize, +) -> Result<usize, Box<dyn Error>> { + let mut offset = line.ceil_char_boundary(offset); + write!(stdout(), "\x1b[G")?; + line.slice(0, offset).print(&mut stdout())?; + let (_, target_col) = term::cursor_pos(stdin)?; + loop { + if offset >= line.len() { + return Ok(line.len()); + } + 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)?; + if col > target_col { + return Ok(line.floor_char_boundary(offset - 1)); + } + } +} + +struct Snapshot { buf: Rope, - row_start: usize, line_offset: usize, + row_start: usize, cursor_row: u16, cursor_col: u16, - line_cols: u16, - mode: Mode, } -#[derive(Debug)] -struct StateHistory { - stdin: BufReader<Stdin>, +struct State { + history: Vec<Snapshot>, + n: Option<usize>, - i: usize, - history: Vec<State>, - curr: State, -} + buf: Rope, + line_offset: usize, + row_start: usize, + cursor_row: u16, + cursor_col: u16, -impl Deref for StateHistory { - type Target = State; + path: PathBuf, + inserting: bool, + need_repaint_screen: bool, + need_repaint_line: bool, +} - fn deref(&self) -> &Self::Target { - return &self.curr; +impl State { + fn line(&self) -> Rope { + self.buf.line(self.row_start + usize::from(self.cursor_row)) } -} -impl DerefMut for StateHistory { - fn deref_mut(&mut self) -> &mut Self::Target { - return &mut self.curr; + fn update_cursor_col(&mut self, stdin: &mut BufReader<Stdin>) -> Result<(), Box<dyn Error>> { + let size = term::size()?; + let line = self.line(); + write!(stdout(), "\x1b[{}H\x1b[K", self.cursor_row + 1)?; + line.slice(0, self.line_offset).print(&mut stdout())?; + if self.line_offset == line.len() { + write!(stdout(), "\x1b[C\x1b[D\x1b[K")?; + return Ok(()); + } + let (_, col) = term::cursor_pos(stdin)?; + if col == size.ws_col - 1 { + self.cursor_col = col - 1; + write!(stdout(), "\x1b[D\x1b[K")?; + return Ok(()); + } + self.cursor_col = col; + line.slice(self.line_offset, line.len()) + .print(&mut stdout())?; + write!( + stdout(), + "\x1b[C\x1b[D\x1b[K\x1b[{};{}H", + self.cursor_row + 1, + self.cursor_col + 1 + )?; + Ok(()) } -} -impl StateHistory { - fn repaint_all(&mut self) -> Result<(), Box<dyn Error>> { + fn repaint_screen(&self) -> Result<(), Box<dyn Error>> { let size = term::size()?; - let mut stdout = stdout().lock(); - write!(stdout, "\x1b[H\x1b[J")?; + write!(stdout(), "\x1b[H\x1b[J")?; for i in self.row_start..self.row_start + usize::from(size.ws_row) { if i > self.row_start { - write!(stdout, "\r\n")?; + write!(stdout(), "\r\n")?; } if i > self.buf.lines() { break; } - self.buf.line(i).print(&mut stdout)?; - if let Ok((_, col)) = term::cursor_pos(&mut self.stdin) { - if col == size.ws_col - 1 { - let _ = write!(stdout, "\x1b[{}G\x1b[30m\x1b[47m>\x1b[m ", size.ws_col - 1); - } - } + let line = self.buf.line(i); + line.print(&mut stdout())?; + write!(stdout(), "\x1b[C\x1b[D\x1b[K")?; } - return Ok(()); + Ok(()) } - fn repaint_line_full(&mut self) -> Result<(), Box<dyn Error>> { - let term_size = term::size()?; - let mut stdout = stdout().lock(); - - write!(stdout, "\x1b[{}H\x1b[K", self.cursor_row + 1)?; - let line = self.buf.line(self.row_start + usize::from(self.cursor_row)); - - line.print(&mut stdout)?; - let (_, col) = match term::cursor_pos(&mut self.stdin) { - Ok(x) => x, - Err(err) => { - let _ = write!(stdout, "\x1b[{}G", self.cursor_col + 1); - return Err(err); - } - }; - self.line_cols = col; - let mut truncated = false; - if col == term_size.ws_col - 1 { - truncated = true; - self.line_cols = col - 1; - } - if self.cursor_col >= self.line_cols { - self.line_offset = line.len(); - if truncated { - write!( - stdout, - "\x1b[{}G\x1b[30m\x1b[47m>\x1b[m \x1b[{}G", - term_size.ws_col - 1, - self.line_cols + 1, - )?; - } - return Ok(()); + fn snapshot(&self) -> Snapshot { + Snapshot { + buf: self.buf.clone(), + line_offset: self.line_offset, + row_start: self.row_start, + cursor_row: self.cursor_row, + cursor_col: self.cursor_col, } - - let mut lo = 0; - let mut hi = line.len(); - // Binary search :D - while hi > lo { - let x = line.floor_char_boundary(lo + (hi - lo) / 2); - write!(stdout, "\x1b[G")?; - line.slice(0, x).print(&mut stdout)?; - let (_, col) = match term::cursor_pos(&mut self.stdin) { - Ok(x) => x, - Err(err) => { - let _ = write!(stdout, "\x1b[{}G", self.cursor_col + 1); - return Err(err); - } - }; - if col <= self.cursor_col { - lo = line.ceil_char_boundary(x + 1); - } else { - hi = x; - } - } - self.line_offset = line.floor_char_boundary(lo - 1); - write!(stdout, "\x1b[G")?; - line.print(&mut stdout)?; - if truncated { - write!( - stdout, - "\x1b[{}G\x1b[30m\x1b[47m>\x1b[m ", - term_size.ws_col - 1 - )?; - } - write!(stdout, "\x1b[{}G", self.cursor_col + 1)?; - return Ok(()); } - fn up(&mut self) -> Result<(), Box<dyn Error>> { - if self.cursor_row == 0 && self.row_start == 0 { - return Ok(()); - } - if self.cursor_row == 0 { - self.row_start -= 1; - self.repaint_all()?; - } else { - self.cursor_row -= 1; - } - self.repaint_line_full()?; - return Ok(()); + fn load(&mut self) { + let Some(n) = self.n else { + return; + }; + let snapshot = &self.history[n]; + self.buf = snapshot.buf.clone(); + self.line_offset = snapshot.line_offset; + self.row_start = snapshot.row_start; + self.cursor_row = snapshot.cursor_row; + self.cursor_col = snapshot.cursor_col; } - fn down(&mut self) -> Result<(), Box<dyn Error>> { - if self.row_start + usize::from(self.cursor_row) == self.buf.lines() { - return Ok(()); + fn insert(&mut self, inserting: bool) { + self.inserting = true; + if inserting { + return; } - let size = term::size()?; - if self.cursor_row == size.ws_row - 1 { - self.row_start += 1; - self.repaint_all()?; - } else { - self.cursor_row += 1; + if let Some(n) = self.n { + self.history.truncate(n); + self.n = None; } - self.repaint_line_full()?; - return Ok(()); + self.history.push(self.snapshot()); } - fn left(&mut self) -> Result<(), Box<dyn Error>> { - if self.cursor_col > self.line_cols { - self.cursor_col = self.line_cols; - } - if self.cursor_col == 0 && self.cursor_row == 0 && self.row_start == 0 { - return Ok(()); - } - if self.cursor_col == 0 { - self.up()?; - self.cursor_col = self.line_cols; - } else { - self.cursor_col -= 1; - } - self.repaint_line_full()?; - return Ok(()); - } + fn keypress(&mut self, stdin: &mut BufReader<Stdin>, 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'@'; + const CTRL_Z: u8 = b'Z' - b'@'; + const BACKSPACE: u8 = b'H' - b'@'; + const OTHER_BACKSPACE: u8 = 127; - fn right(&mut self) -> Result<(), Box<dyn Error>> { - if self.cursor_col >= self.line_cols - && self.row_start + usize::from(self.cursor_row) == self.buf.lines() - { - return Ok(()); - } - if self.cursor_col >= self.line_cols { - self.down()?; - self.cursor_col = 0; - } else { - self.cursor_col += 1; - } - self.repaint_line_full()?; - return Ok(()); - } + let inserting = self.inserting; + self.inserting = false; - fn set_mode(&mut self, mode: Mode) { - match (self.mode, mode) { - (Mode::Normal, Mode::Normal) => (), - (Mode::Normal, _) => { - self.i += 1; - self.history.truncate(self.i); - self.history.push(self.curr.clone()); - } - (Mode::Paste, Mode::Paste) => (), - (Mode::Paste, _) => { - let Ok((_, col)) = term::cursor_pos(&mut self.stdin) else { - return; - }; - self.cursor_col = col; - let _ = self.repaint_line_full(); - let _ = stdout().flush(); - } - (Mode::MultilinePaste, Mode::MultilinePaste) => (), - (Mode::MultilinePaste, Mode::Paste) => { - return; - } - (Mode::MultilinePaste, _) => { - let Ok((_, col)) = term::cursor_pos(&mut self.stdin) else { - return; - }; - self.cursor_col = col; - let _ = self.repaint_all(); - let _ = self.repaint_line_full(); - let _ = stdout().flush(); - } - _ => (), - } - self.mode = mode; - } -} - -fn edit(file: &OsStr) -> Result<(), Box<dyn Error>> { - let _raw_handle = - term::raw().map_err(|err| format!("cannot put terminal in raw mode: {}", err))?; - - let r = Rope::open(Path::new(file))?; - let size = term::size()?; - let initial_state = State { - buf: r, - mode: Mode::Normal, - row_start: 0, - cursor_row: 0, - cursor_col: 0, - line_offset: 0, - line_cols: 0, - }; - let mut state = StateHistory { - stdin: BufReader::new(std::io::stdin()), - i: 0, - history: vec![initial_state.clone()], - curr: initial_state, - }; - let _ = state.repaint_all(); - let _ = write!(stdout(), "\x1b[H"); - let _ = stdout().flush(); - - loop { - let c = term::read_key(&mut state.stdin)?; - match c { + match key { Key::Timeout => { - if state.mode != Mode::Paste && state.mode != Mode::MultilinePaste { - continue; + if inserting { + self.inserting = true; } - state.set_mode(Mode::Insert); - } - Key::CtrlQ => { - break; - } - Key::CtrlS => { - state.set_mode(Mode::Normal); - let _ = state.buf.save(Path::new(file)); - } - Key::CtrlZ => { - state.set_mode(Mode::Normal); - if state.i == state.history.len() - 1 { - state.history.push(state.curr.clone()); - } else if state.i > 0 { - state.i -= 1; + if !self.need_repaint_screen && !self.need_repaint_line { + return Ok(true); } - state.curr = state.history[state.i].clone(); - let _ = state.repaint_all(); - let _ = state.repaint_line_full(); - let _ = stdout().flush(); - } - Key::CtrlY => { - state.set_mode(Mode::Normal); - if state.i == state.history.len() - 1 { - continue; + if self.need_repaint_screen { + self.repaint_screen()?; + self.need_repaint_screen = false; } - state.i += 1; - state.curr = state.history[state.i].clone(); - let _ = state.repaint_all(); - let _ = state.repaint_line_full(); - let _ = stdout().flush(); + self.update_cursor_col(stdin)?; + self.need_repaint_line = false; + stdout().flush()?; } Key::Up => { - state.set_mode(Mode::Normal); - let _ = state.up(); - let _ = stdout().flush(); + if self.cursor_row == 0 && self.row_start == 0 { + return Ok(true); + } + let prev_line = self + .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.row_start -= 1; + self.repaint_screen()?; + self.update_cursor_col(stdin)?; + stdout().flush()?; + return Ok(true); + } + write!(stdout(), "\x1b[{}H", self.cursor_row)?; + self.line_offset = line_offset(stdin, &prev_line, self.cursor_col)?; + self.cursor_row -= 1; + self.update_cursor_col(stdin)?; + stdout().flush()?; } Key::Down => { - state.set_mode(Mode::Normal); - let _ = state.down(); - let _ = stdout().flush(); + if self.row_start + usize::from(self.cursor_row) >= self.buf.lines() { + return Ok(true); + } + let size = term::size()?; + let next_line = self + .buf + .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.cursor_row += 1; + self.update_cursor_col(stdin)?; + stdout().flush()?; + return Ok(true); + } + self.line_offset = line_offset(stdin, &next_line, self.cursor_col)?; + self.row_start += 1; + self.repaint_screen()?; + self.update_cursor_col(stdin)?; + stdout().flush()?; } Key::Left => { - state.set_mode(Mode::Normal); - let _ = state.left(); - let _ = stdout().flush(); + if self.line_offset > 0 { + self.line_offset = + floor_grapheme_cluster(stdin, &self.line(), self.line_offset - 1)?; + self.update_cursor_col(stdin)?; + stdout().flush()?; + return Ok(true); + } + if self.cursor_row > 0 { + let size = term::size()?; + write!(stdout(), "\x1b[{}H", self.cursor_row)?; + self.line_offset = line_offset( + 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)?; + stdout().flush()?; + return Ok(true); + } + if self.row_start == 0 { + return Ok(true); + } + let size = term::size()?; + self.line_offset = line_offset( + stdin, + &self + .buf + .line(self.row_start + usize::from(self.cursor_row) - 1), + size.ws_col - 2, + )?; + self.row_start -= 1; + self.repaint_screen()?; + self.update_cursor_col(stdin)?; + stdout().flush()?; } Key::Right => { - state.set_mode(Mode::Normal); - let _ = state.right(); - let _ = stdout().flush(); + 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)?; + stdout().flush()?; + return Ok(true); + } + if self.row_start + usize::from(self.cursor_row) >= self.buf.lines() { + return Ok(true); + } + if self.cursor_row < size.ws_row - 1 { + self.cursor_row += 1; + self.line_offset = 0; + self.cursor_col = 0; + write!(stdout(), "\x1b[{}H", self.cursor_row + 1)?; + stdout().flush()?; + return Ok(true); + } + self.row_start += 1; + self.line_offset = 0; + self.cursor_col = 0; + self.repaint_screen()?; + write!(stdout(), "\x1b[{}H", self.cursor_row + 1)?; + stdout().flush()?; + } + Key::Byte(CTRL_Q) => { + return Ok(false); } - Key::Backspace => { - state.set_mode(Mode::Insert); - if state.row_start == 0 && state.cursor_row == 0 && state.line_offset == 0 { - continue; + Key::Byte(CTRL_S) => { + let mut f = File::create(&self.path)?; + self.buf.print(&mut f)?; + f.sync_all()?; + } + Key::Byte(CTRL_Z) => { + if let Some(n) = self.n { + if n > 0 { + self.n = Some(n - 1); + } + } else { + self.n = Some(self.history.len() - 1); + self.history.push(self.snapshot()); + } + self.load(); + self.repaint_screen()?; + self.update_cursor_col(stdin)?; + stdout().flush()?; + } + Key::Byte(CTRL_Y) => { + let Some(n) = self.n else { + return Ok(true); + }; + if n == self.history.len() - 1 { + return Ok(true); + } + self.n = Some(n + 1); + self.load(); + self.repaint_screen()?; + self.update_cursor_col(stdin)?; + stdout().flush()?; + } + Key::Byte(BACKSPACE) | Key::Byte(OTHER_BACKSPACE) => { + self.insert(inserting); + if self.line_offset == 0 && self.cursor_row == 0 && self.row_start == 0 { + return Ok(true); } - let line_start = state + let line_start = self .buf - .line_idx(state.row_start + usize::from(state.cursor_row)); - let gap_end = line_start + state.line_offset; - let gap_start = state.buf.floor_char_boundary(gap_end - 1); - if state.line_offset == 0 { - let _ = state.left(); - } else { - let _ = write!(stdout(), "\x1b[G"); - let _ = state.buf.slice(line_start, gap_start).print(&mut stdout()); - match term::cursor_pos(&mut state.stdin) { - Ok((_, col)) => { - state.cursor_col = col; - } - Err(_) => { - state.cursor_col -= 1; - } + .line_start(self.row_start + usize::from(self.cursor_row)); + let offset = line_start + self.line_offset; + if self.line_offset == 0 { + if self.cursor_row == 0 { + self.row_start -= 1; + } else { + self.cursor_row -= 1; } + self.line_offset = self.line().len(); + self.buf = self + .buf + .slice(0, offset - 1) + .concat(&self.buf.slice(offset, self.buf.len())); + self.repaint_screen()?; + self.update_cursor_col(stdin)?; + stdout().flush()?; + return Ok(true); } - state.buf = state + 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.buf = self .buf - .slice(0, gap_start) - .concat(&state.buf.slice(gap_end, state.buf.len())); - let _ = state.repaint_all(); - let _ = state.repaint_line_full(); - let _ = stdout().flush(); + .slice(0, line_start + self.line_offset) + .concat(&self.buf.slice(offset, self.buf.len())); + self.update_cursor_col(stdin)?; + stdout().flush()?; } - Key::Char(b'\r') => { - state.set_mode(Mode::MultilinePaste); - state.buf = state.buf.insert( - state - .buf - .line_idx(state.row_start + usize::from(state.cursor_row)) - + state.line_offset, - b'\n', - ); - if state.cursor_row == size.ws_row - 1 { - state.row_start += 1; + Key::Byte(b'\r') | Key::Byte(b'\n') => { + self.insert(inserting); + let size = term::size()?; + let offset = self + .buf + .line_start(self.row_start + usize::from(self.cursor_row)) + + self.line_offset; + self.buf = self + .buf + .slice(0, offset) + .concat(&Rope::new(&[b'\n'])) + .concat(&self.buf.slice(offset, self.buf.len())); + if self.cursor_row < size.ws_row - 1 { + self.cursor_row += 1; } else { - state.cursor_row += 1; + self.row_start += 1; } - state.cursor_col = 0; - state.line_offset = 0; - let _ = write!(stdout(), "\r\n"); - let _ = stdout().flush(); + self.line_offset = 0; + self.cursor_col = 0; + self.need_repaint_screen = true; + write!(stdout(), "\r\n")?; + stdout().flush()?; } - Key::Char(c) => { - state.set_mode(Mode::Paste); - state.buf = state.buf.insert( - state - .buf - .line_idx(state.row_start + usize::from(state.cursor_row)) - + state.line_offset, - c, - ); - state.line_offset += 1; - let _ = stdout().write_all(&vec![c]); - let _ = stdout().flush(); + Key::Byte(c) => { + self.insert(inserting); + let offset = self + .buf + .line_start(self.row_start + usize::from(self.cursor_row)) + + self.line_offset; + self.buf = self + .buf + .slice(0, offset) + .concat(&Rope::new(&[c])) + .concat(&self.buf.slice(offset, self.buf.len())); + self.line_offset += 1; + self.need_repaint_line = true; + stdout().write_all(&[c])?; + stdout().flush()?; } } + Ok(true) + } +} + +fn edit(file: &OsStr) -> Result<(), String> { + let file = PathBuf::from(file); + let r = match open(&file) { + Ok(r) => r, + Err(err) => { + return Err(format!("open file {}: {}", file.display(), err)); + } + }; + let _raw_mode = match term::raw_mode() { + Ok(x) => x, + Err(err) => { + 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(), + row_start: 0, + cursor_row: 0, + cursor_col: 0, + line_offset: 0, + }], + n: None, + + buf: r, + row_start: 0, + cursor_row: 0, + cursor_col: 0, + line_offset: 0, + + path: file, + inserting: false, + need_repaint_line: false, + need_repaint_screen: false, + }; + if let Err(err) = state.repaint_screen() { + return Err(format!("cannot write to screen: {}", err)); + } + if let Err(err) = write!(stdout(), "\x1b[H") { + return Err(format!("cannot write to screen: {}", err)); + } + if let Err(err) = stdout().flush() { + return Err(format!("cannot write to screen: {}", err)); + } + loop { + let Ok(key) = term::read_key(&mut stdin) else { + return Err(String::from("no input")); + }; + let Ok(ok) = state.keypress(&mut stdin, key) else { + state.need_repaint_screen = true; + continue; + }; + // I miss "do while" so much... + if !ok { + break; + } } - return Ok(()); + Ok(()) } fn main() { let args: Vec<OsString> = std::env::args_os().collect(); if args.len() != 2 { - println!("Usage: edit <filename>"); + let _ = writeln!(stderr(), "Usage: edit <filename>"); std::process::exit(1); } if let Err(err) = edit(&args[1]) { - println!("FAIL: {}", err); + let _ = writeln!(stderr(), "FAIL: {}", err); std::process::exit(1); } } diff --git a/src/rope.rs b/src/rope.rs index 60a3fe8..31c3296 100644 --- a/src/rope.rs +++ b/src/rope.rs @@ -1,66 +1,40 @@ -use std::error::Error; -use std::fs::File; -use std::io::{ErrorKind, Read, Write}; -use std::path::Path; +use std::io::{Read, Write}; use std::rc::Rc; -const MAX_NODE_SIZE: usize = 255; +const MAX_NODE_SIZE: usize = 1024; + +enum Insert { + Node(Rope), + Split(Rope, Rope), +} #[derive(Debug, Clone)] -struct Leaf { - buf: Rc<[u8]>, - start: u8, - end: u8, +pub struct Leaf { + unsafe_buf: Option<Rc<[u8]>>, + start: u16, + end: u16, } impl Leaf { fn bytes(&self) -> &[u8] { - return &self.buf[usize::from(self.start)..usize::from(self.end)]; + match &self.unsafe_buf { + None => &[], + Some(buf) => &buf[usize::from(self.start)..usize::from(self.end)], + } } } #[derive(Debug, Clone)] -struct Branch { +pub struct Branch { + unsafe_children: [Rope; 3], len: usize, - // Number of newline characters under this branch. lines: usize, - height: usize, - child1: Rope, - child2: Rope, - child3: Option<Rope>, + n_children: u8, } impl Branch { - fn children(&self) -> Children { - return Children { b: self, i: 0 }; - } -} - -#[derive(Debug, Clone, Copy)] -struct Children<'a> { - b: &'a Branch, - i: u8, -} - -impl<'a> Iterator for Children<'a> { - type Item = Rope; - - fn next(&mut self) -> Option<Self::Item> { - let c = match self.i { - 0 => self.b.child1.clone(), - 1 => self.b.child2.clone(), - 2 => match &self.b.child3 { - Some(child3) => child3.clone(), - None => { - return None; - } - }, - _ => { - return None; - } - }; - self.i += 1; - return Some(c); + fn children(&self) -> &[Rope] { + &self.unsafe_children[..usize::from(self.n_children)] } } @@ -73,345 +47,312 @@ enum Node { #[derive(Debug, Clone)] pub struct Rope(Node); -fn leaf(buf: &[u8]) -> Rope { - let len = buf.len(); - return Rope(Node::Leaf(Leaf { - buf: Rc::from(buf), - start: 0, - end: u8::try_from(len).expect("buffer too long"), - })); -} - -// The two and three constructors maintain the size of the tree. -fn two(l: &Rope, r: &Rope) -> Rope { - if l.height() != r.height() { - panic!("Joining trees of different height"); - } - return Rope(Node::Branch(Rc::new(Branch { - len: l.len() + r.len(), - lines: l.lines() + r.lines(), - height: l.height() + 1, - child1: l.clone(), - child2: r.clone(), - child3: None, - }))); -} - -fn three(a: &Rope, b: &Rope, c: &Rope) -> Rope { - if a.height() != b.height() || b.height() != c.height() { - panic!("Joining trees of different height"); +impl Rope { + pub fn len(&self) -> usize { + match self { + Rope(Node::Leaf(l)) => l.bytes().len(), + Rope(Node::Branch(b)) => b.len, + } } - return Rope(Node::Branch(Rc::new(Branch { - len: a.len() + b.len() + c.len(), - lines: a.lines() + b.lines() + c.lines(), - height: a.height() + 1, - child1: a.clone(), - child2: b.clone(), - child3: Some(c.clone()), - }))); -} -#[derive(Debug)] -enum Insert { - Node(Rope), - Split(Rope, Rope), -} - -impl Rope { pub fn lines(&self) -> usize { match self { Rope(Node::Leaf(l)) => { - let mut count = 0; - for &c in l.bytes().iter() { - if c == b'\n' { - count += 1; + let mut lines = 0; + for &b in l.bytes().iter() { + if b == b'\n' { + lines += 1; } } - return count; - } - Rope(Node::Branch(b)) => { - return b.lines; + lines } + Rope(Node::Branch(b)) => b.lines, } } - pub fn len(&self) -> usize { - match self { - Rope(Node::Leaf(l)) => { - return usize::from(l.end - l.start); + fn leaf(buf: &[u8]) -> Rope { + if buf.len() > MAX_NODE_SIZE { + panic!("buffer too long for leaf!"); + } + if buf.is_empty() { + return Rope(Node::Leaf(Leaf { + unsafe_buf: None, + start: 0, + end: 0, + })); + } + Rope(Node::Leaf(Leaf { + unsafe_buf: Some(Rc::from(buf)), + start: 0, + end: u16::try_from(buf.len()).expect("I just checked the length, it should be ok"), + })) + } + + fn branch(children: &[Rope]) -> Rope { + let mut len = 0; + let mut lines = 0; + for c in children.iter() { + len += c.len(); + lines += c.lines(); + } + let mut child_array = [Rope::leaf(&[]), Rope::leaf(&[]), Rope::leaf(&[])]; + for (i, c) in children.iter().enumerate() { + child_array[i] = c.clone(); + } + Rope(Node::Branch(Rc::new(Branch { + unsafe_children: child_array, + len, + lines, + n_children: u8::try_from(children.len()) + .expect("there should only ever be 2 or 3 children"), + }))) + } + + fn concat_height(&self, self_height: usize, other: &Rope, other_height: usize) -> Insert { + if let (Rope(Node::Leaf(l)), Rope(Node::Leaf(r))) = (self, other) { + if self.len() + other.len() <= MAX_NODE_SIZE { + let mut buf = vec![0; self.len() + other.len()]; + buf[..self.len()].copy_from_slice(l.bytes()); + buf[self.len()..].copy_from_slice(r.bytes()); + return Insert::Node(Rope::leaf(&buf)); } - Rope(Node::Branch(b)) => { - return b.len; + if self.len() < MAX_NODE_SIZE / 2 || other.len() < MAX_NODE_SIZE / 2 { + let mut buf = vec![0; self.len() + other.len()]; + buf[..self.len()].copy_from_slice(l.bytes()); + buf[self.len()..].copy_from_slice(r.bytes()); + return Insert::Split( + Rope::leaf(&buf[..buf.len() / 2]), + Rope::leaf(&buf[buf.len() / 2..]), + ); + } + return Insert::Split(self.clone(), other.clone()); + } + if self_height == other_height { + return Insert::Split(self.clone(), other.clone()); + } + if self_height > other_height { + let Rope(Node::Branch(b)) = self else { + panic!("self_height is at least 1, so it's a branch"); + }; + match b.children()[b.children().len() - 1].concat_height( + self_height - 1, + other, + other_height, + ) { + Insert::Node(new_child) => { + let mut new_children = b.children().to_vec(); + new_children[b.children().len() - 1] = new_child; + return Insert::Node(Rope::branch(&new_children)); + } + Insert::Split(child1, child2) => { + if b.children().len() == 2 { + return Insert::Node(Rope::branch(&[ + b.children()[0].clone(), + child1, + child2, + ])); + } + return Insert::Split( + Rope::branch(&[b.children()[0].clone(), b.children()[1].clone()]), + Rope::branch(&[child1, child2]), + ); + } + } + } + let Rope(Node::Branch(b)) = other else { + panic!("other_height is at least 1, so it's a branch"); + }; + match self.concat_height(self_height, &b.children()[0], other_height - 1) { + Insert::Node(new_child) => { + let mut new_children = b.children().to_vec(); + new_children[0] = new_child; + Insert::Node(Rope::branch(&new_children)) + } + Insert::Split(child1, child2) => { + if b.children().len() == 2 { + return Insert::Node(Rope::branch(&[child1, child2, b.children()[1].clone()])); + } + Insert::Split( + Rope::branch(&[child1, child2]), + Rope::branch(&[b.children()[1].clone(), b.children()[2].clone()]), + ) } } } - pub fn height(&self) -> usize { + fn height(&self) -> usize { match self { Rope(Node::Leaf(_)) => 0, - Rope(Node::Branch(b)) => b.height, + Rope(Node::Branch(b)) => 1 + b.children()[0].height(), } } - pub fn print(&self, out: &mut dyn Write) -> Result<(), std::io::Error> { + pub fn concat(&self, other: &Rope) -> Rope { if self.len() == 0 { - return Ok(()); + return other.clone(); + } + if other.len() == 0 { + return self.clone(); } - // TODO: escape unprintable characters + match self.concat_height(self.height(), other, other.height()) { + Insert::Node(r) => r, + Insert::Split(l, r) => Rope::branch(&[l, r]), + } + } + + pub fn read(r: &mut dyn Read) -> Result<Rope, std::io::Error> { + let mut buf = vec![0; MAX_NODE_SIZE]; + let mut rope = Rope::leaf(&[]); + loop { + let n = r.read(&mut buf)?; + if n == 0 { + break; + } + rope = rope.concat(&Rope::leaf(&buf[..n])); + } + Ok(rope) + } + + pub fn new(buf: &[u8]) -> Rope { + let mut buf = buf; + Rope::read(&mut buf).expect("reading from a slice should never fail") + } + + fn is_char_boundary(&self, i: usize) -> bool { match self { Rope(Node::Leaf(l)) => { - out.write_all(l.bytes())?; - return Ok(()); + return i < l.bytes().len() && l.bytes()[i] & 0xc0 != 0x80; } Rope(Node::Branch(b)) => { - for child in b.children() { - child.print(out)?; + let mut i = i; + for c in b.children().iter() { + if i < c.len() { + return c.is_char_boundary(i); + } + i -= c.len(); } - return Ok(()); + false } } } - pub fn line_idx(&self, n: usize) -> usize { - if n > self.lines() { - panic!("Index {} out of range 0..{}", n, self.lines() + 1); + pub fn floor_char_boundary(&self, i: usize) -> usize { + for i in (0..i + 1).rev() { + if self.is_char_boundary(i) { + return i; + } } - if n == 0 { - return 0; + 0 + } + + pub fn ceil_char_boundary(&self, i: usize) -> usize { + for i in i..self.len() { + if self.is_char_boundary(i) { + return i; + } } + self.len() + } + + fn line_end(&self, n: usize) -> usize { match self { Rope(Node::Leaf(l)) => { - let mut nl_count = 0; + let mut count = 0; for (i, &c) in l.bytes().iter().enumerate() { if c != b'\n' { continue; } - nl_count += 1; - if nl_count == n { - return i + 1; + if count == n { + return i; } + count += 1; } - panic!("unreachable"); } Rope(Node::Branch(b)) => { - let mut offset = 0; let mut n = n; - for child in b.children() { - let child_lines = child.lines(); - if n <= child_lines { - return offset + child.line_idx(n); + let mut offset = 0; + for c in b.children().iter() { + if n < c.lines() { + return offset + c.line_end(n); } - offset += child.len(); - n -= child_lines; + n -= c.lines(); + offset += c.len(); } - panic!("unreachable"); } } + self.len() } - fn try_concat(&self, other: &Rope) -> Insert { - if self.len() == 0 { - return Insert::Node(other.clone()); - } - if other.len() == 0 { - return Insert::Node(self.clone()); - } - if let (Rope(Node::Leaf(l)), Rope(Node::Leaf(r))) = (self, other) { - if self.len() <= MAX_NODE_SIZE - other.len() { - let mut buf = Vec::new(); - buf.extend_from_slice(&l.bytes()); - buf.extend_from_slice(&r.bytes()); - return Insert::Node(leaf(&buf)); - } - if self.len() < MAX_NODE_SIZE / 2 || other.len() < MAX_NODE_SIZE / 2 { - let mut buf = Vec::new(); - buf.extend_from_slice(&l.bytes()); - buf.extend_from_slice(&r.bytes()); - return Insert::Split(leaf(&buf[..buf.len() / 2]), leaf(&buf[buf.len() / 2..])); - } - } - if self.height() == other.height() { - return Insert::Split(self.clone(), other.clone()); - } - if self.height() > other.height() { - let Rope(Node::Branch(selfb)) = self else { - panic!("self should have height at least one, which means it's a branch"); - }; - match &selfb.child3 { - Some(child3) => match child3.try_concat(other) { - Insert::Node(new_child) => { - Insert::Node(three(&selfb.child1, &selfb.child2, &new_child)) - } - Insert::Split(child1, child2) => { - Insert::Split(two(&selfb.child1, &selfb.child2), two(&child1, &child2)) - } - }, - None => match selfb.child2.try_concat(other) { - Insert::Node(new_child) => Insert::Node(two(&selfb.child1, &new_child)), - Insert::Split(child1, child2) => { - Insert::Node(three(&selfb.child1, &child1, &child2)) - } - }, - } - } else { - let Rope(Node::Branch(otherb)) = other else { - panic!("other should have height at least one, which means it's a branch"); - }; - match &otherb.child3 { - Some(child3) => match self.try_concat(&otherb.child1) { - Insert::Node(new_child) => { - Insert::Node(three(&new_child, &otherb.child2, &child3)) - } - Insert::Split(child1, child2) => { - Insert::Split(two(&child1, &child2), two(&otherb.child2, &child3)) - } - }, - None => match self.try_concat(&otherb.child1) { - Insert::Node(new_child) => Insert::Node(two(&new_child, &otherb.child2)), - Insert::Split(child1, child2) => { - Insert::Node(three(&child1, &child2, &otherb.child2)) - } - }, - } + pub fn line_start(&self, n: usize) -> usize { + if n == 0 { + return 0; } + self.line_end(n - 1) + 1 } - pub fn concat(&self, other: &Rope) -> Rope { - match self.try_concat(other) { - Insert::Node(r) => r, - Insert::Split(l, r) => two(&l, &r), - } + pub fn line(&self, n: usize) -> Rope { + self.slice(self.line_start(n), self.line_end(n)).clone() } - pub fn insert(&self, pos: usize, c: u8) -> Rope { - let res = self - .slice(0, pos) - .concat(&leaf(&vec![c])) - .concat(&self.slice(pos, self.len())); - return res; + pub fn print(&self, w: &mut dyn Write) -> Result<(), std::io::Error> { + match self { + Rope(Node::Leaf(l)) => { + w.write_all(l.bytes())?; + Ok(()) + } + Rope(Node::Branch(b)) => { + for c in b.children().iter() { + c.print(w)?; + } + Ok(()) + } + } } pub fn slice(&self, start: usize, end: usize) -> Rope { if start > self.len() { - panic!("Index {} out of range 0..{}", start, self.len() + 1); + panic!( + "Slice start index {} out of range 0..={}", + start, + self.len() + ); } if end > self.len() { - panic!("Index {} out of range 0..{}", end, self.len()); + panic!("Slice end index {} out of range 0..={}", end, self.len()); } if start > end { - panic!( - "Slice start index {} is greater than end index {}", - start, end - ); + panic!("Slice start index {} greater than end index {}", start, end); + } + if start == 0 && end == self.len() { + return self.clone(); } match self { Rope(Node::Leaf(l)) => { - return Rope(Node::Leaf(Leaf { - buf: l.buf.clone(), - start: l.start + u8::try_from(start).expect("buffer too long"), - end: l.start + u8::try_from(end).expect("buffer too long"), - })); + Rope(Node::Leaf(Leaf{ + unsafe_buf: l.unsafe_buf.clone(), + start: l.start+u16::try_from(start).expect("index.start should be less than MAX_NODE_SIZE since self is a leaf"), + end: l.start+u16::try_from(end).expect("index.end should be less than or equal to MAX_NODE_SIZE since self is a leaf"), + })) } Rope(Node::Branch(b)) => { + let mut s = Rope::new(&[]); let mut start = start; let mut end = end; - let mut slice = leaf(&Vec::new()); - for child in b.children() { - if start < child.len() { - slice = slice.concat(&child.slice(start, std::cmp::min(end, child.len()))); - if end < child.len() { - break; - } - start = 0; - } else { - start -= child.len(); - } - end -= child.len(); - } - return slice; - } - } - } - - pub fn open(path: &Path) -> Result<Rope, Box<dyn Error>> { - let mut f = - File::open(path).map_err(|err| format!("open file {}: {}", path.display(), err))?; - let mut rope = leaf(&Vec::new()); - loop { - let mut buf = vec![0; MAX_NODE_SIZE]; - let n = match f.read(&mut buf) { - Ok(n) => n, - Err(err) => { - if err.kind() == ErrorKind::Interrupted { + for c in b.children().iter() { + if start >= c.len() { + start -= c.len(); + end -= c.len(); continue; } - return Err(Box::from(format!("read file {}: {}", path.display(), err))); - } - }; - if n == 0 { - break; - } - buf.truncate(n); - rope = rope.concat(&leaf(&buf)); - } - return Ok(rope); - } - - pub fn save(&self, path: &Path) -> Result<(), Box<dyn Error>> { - let mut f = File::create(path) - .map_err(|err| format!("save: create file {}: {}", path.display(), err))?; - self.print(&mut f) - .map_err(|err| format!("save: write file {}: {}", path.display(), err))?; - f.sync_all() - .map_err(|err| format!("save: write file {}: {}", path.display(), err))?; - return Ok(()); - } - - pub fn line(&self, n: usize) -> Rope { - if n > self.lines() { - panic!("Index {} out of range 0..{}", n, self.lines() + 1); - } - let start = self.line_idx(n); - let mut end = self.len(); - if n < self.lines() { - end = self.line_idx(n + 1) - 1; - } - return self.slice(start, end); - } - - fn is_char_boundary(&self, index: usize) -> bool { - match self { - Rope(Node::Leaf(l)) => { - return l.bytes()[index] & 0xc0 != 0x80; - } - Rope(Node::Branch(b)) => { - let mut index = index; - for child in b.children() { - if index < child.len() { - return child.is_char_boundary(index); + s = s.concat(&c.slice(start, std::cmp::min(end, c.len()))); + if end <= c.len() { + break; } - index -= child.len(); + start = 0; + end -= c.len(); } - return false; - } - } - } - - pub fn floor_char_boundary(&self, index: usize) -> usize { - for i in (0..index + 1).rev() { - if self.is_char_boundary(i) { - return i; - } - } - panic!("I'm not valid UTF-8: {:?}", self); - } - - pub fn ceil_char_boundary(&self, index: usize) -> usize { - for i in index..self.len() { - if self.is_char_boundary(i) { - return i; + s } } - return self.len(); } } 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)) } |
