aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRose Hogenson <rosehogenson@posteo.net>2024-01-11 00:20:11 -0800
committerRose Hogenson <rosehogenson@posteo.net>2024-01-11 00:20:11 -0800
commitdb087d43659f0760a02d6c32dc7dc0baf2f914f0 (patch)
treee514935b6c9dc7c9e835a64671aa0c6a80493595
parent57f527f2a1d9bc4ed8c862b014867634803831ba (diff)
downloadeditor-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.rs778
-rw-r--r--src/rope.rs559
-rw-r--r--src/term.rs163
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))
}