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 /src/main.rs | |
| parent | Support backspace in xterm. (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.
Diffstat (limited to 'src/main.rs')
| -rw-r--r-- | src/main.rs | 778 |
1 files changed, 453 insertions, 325 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); } } |
