diff options
Diffstat (limited to 'src/main.rs')
| -rw-r--r-- | src/main.rs | 157 |
1 files changed, 98 insertions, 59 deletions
diff --git a/src/main.rs b/src/main.rs index 6251ace..a4fe9ae 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,14 +2,22 @@ mod rope; mod term; use rope::Rope; -use std::ffi::{OsStr, OsString}; -use std::io::{stdout, stdin, Write, Read}; use std::error::Error; +use std::ffi::{OsStr, OsString}; +use std::io::{stdin, stdout, Read, Write}; use std::path::Path; use term::Key; +#[derive(Debug, Clone, Copy)] +enum Mode { + Insert, + Backspace, + Normal, +} + struct State { buf: Rope, + mode: Mode, row_start: usize, cursor_row: usize, cursor_col: usize, @@ -25,25 +33,35 @@ fn parse_status_report(buf: &[u8]) -> Result<(usize, usize), Box<dyn Error>> { break; } } - if semicolon < 2 || semicolon == buf.len()-1 { + if semicolon < 2 || semicolon == buf.len() - 1 { 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..buf.len()-1])?; + let col_str = std::str::from_utf8(&buf[semicolon + 1..buf.len() - 1])?; let row = row_str.parse::<usize>()?; let col = col_str.parse::<usize>()?; // Yuck... 1 indexing - return Ok((row-1, col-1)); + return Ok((row - 1, col - 1)); } impl State { fn cursor_pos(&mut self) -> Result<(usize, usize), Box<dyn Error>> { - write!(stdout(), "\x1b[6n").map_err(|err| format!("cursor position: status report: {}", err))?; - stdout().flush().map_err(|err| format!("cursor position: status report: {}", err))?; + write!(stdout(), "\x1b[6n") + .map_err(|err| format!("cursor position: status report: {}", err))?; + stdout() + .flush() + .map_err(|err| format!("cursor position: status report: {}", err))?; let mut buf = vec![0; 20]; - let n = stdin().read(&mut buf).map_err(|err| format!("cursor position: read status report: {}", err))?; + let n = stdin() + .read(&mut buf) + .map_err(|err| format!("cursor position: read status report: {}", err))?; buf.truncate(n); - let pos = parse_status_report(&buf).map_err(|_| format!("cursor position: invalid response: {}", String::from_utf8_lossy(&buf)))?; + let pos = parse_status_report(&buf).map_err(|_| { + format!( + "cursor position: invalid response: {}", + String::from_utf8_lossy(&buf) + ) + })?; return Ok(pos); } @@ -52,18 +70,17 @@ impl State { let mut stdout = stdout().lock(); write!(stdout, "\x1b[H\x1b[J")?; - for i in self.row_start..self.row_start+usize::from(size.ws_row) { + for i in self.row_start..self.row_start + usize::from(size.ws_row) { if i > 0 { write!(stdout, "\r\n")?; } if i > self.buf.lines() { break; } - let line = self.buf.line(i); - let max_len = line.char(usize::from(size.ws_col-1)); - line.slice(0, max_len).print(&mut stdout)?; - if max_len < line.len() { - write!(stdout, "\x1b[30m\x1b[47m>\x1b[m")?; + self.buf.line(i).print(&mut stdout)?; + let (_, col) = self.cursor_pos()?; + if col == usize::from(size.ws_col - 1) { + write!(stdout, "\x1b[{}G\x1b[30m\x1b[47m>\x1b[m ", size.ws_col - 1)?; } } return Ok(()); @@ -73,42 +90,31 @@ impl State { let term_size = term::size()?; let mut stdout = stdout().lock(); - write!(stdout, "\x1b[{}H\x1b[K", self.cursor_row+1)?; - let mut line = self.buf.line(self.row_start+self.cursor_row); - let max_len = line.char(usize::from(term_size.ws_col-1)); - let mut truncated = false; - if max_len < line.len() { - line = line.slice(0, max_len); - truncated = true; - } + write!(stdout, "\x1b[{}H\x1b[K", self.cursor_row + 1)?; + let line = self.buf.line(self.row_start + self.cursor_row); line.print(&mut stdout)?; let (_, col) = self.cursor_pos()?; self.line_cols = col; + let mut truncated = false; + if col == usize::from(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[30m\x1b[47m>\x1b[m\x1b[{}G", self.cursor_col+1)?; - } - return Ok(()); - } - - let offset_guess = line.char(self.cursor_col); - write!(stdout, "\x1b[G")?; - line.slice(0, offset_guess).print(&mut stdout)?; - let (_, col) = self.cursor_pos()?; - if col == self.cursor_col { - // One codepoint per column, like God intended. - self.line_offset = offset_guess; - line.slice(offset_guess, line.len()).print(&mut stdout)?; - if truncated { - write!(stdout, "\x1b[30m\x1b[47m>\x1b[m")?; + write!( + stdout, + "\x1b[{}G\x1b[30m\x1b[47m>\x1b[m \x1b[{}G", + term_size.ws_col - 1, + self.line_cols + 1, + )?; } - write!(stdout, "\x1b[{}G", self.cursor_col+1)?; return Ok(()); } - let mut lo = offset_guess; + let mut lo = 0; let mut hi = line.len(); // Binary search :D while hi > lo { @@ -117,18 +123,22 @@ impl State { line.slice(0, x).print(&mut stdout)?; let (_, col) = self.cursor_pos()?; if col <= self.cursor_col { - lo = line.ceil_char_boundary(x+1); + lo = line.ceil_char_boundary(x + 1); } else { hi = x; } } - self.line_offset = line.floor_char_boundary(lo-1); + self.line_offset = line.floor_char_boundary(lo - 1); write!(stdout, "\x1b[G")?; line.print(&mut stdout)?; if truncated { - write!(stdout, "\x1b[30m\x1b[47m>\x1b[m")?; + write!( + stdout, + "\x1b[{}G\x1b[30m\x1b[47m>\x1b[m ", + term_size.ws_col - 1 + )?; } - write!(stdout, "\x1b[{}G", self.cursor_col+1)?; + write!(stdout, "\x1b[{}G", self.cursor_col + 1)?; return Ok(()); } @@ -147,7 +157,7 @@ impl State { } fn down(&mut self) -> Result<(), Box<dyn Error>> { - if self.row_start+self.cursor_row == self.buf.lines() { + if self.row_start + self.cursor_row == self.buf.lines() { return Ok(()); } let size = term::size()?; @@ -179,10 +189,6 @@ impl State { } fn right(&mut self) -> Result<(), Box<dyn Error>> { - let size = term::size()?; - if self.cursor_col >= self.line_cols && self.cursor_row == usize::from(size.ws_row-1) { - return Ok(()); - } if self.cursor_col >= self.line_cols { self.down()?; self.cursor_col = 0; @@ -192,15 +198,30 @@ impl State { self.repaint_line_full()?; return Ok(()); } -} + fn set_mode(&mut self, mode: Mode) { + match (self.mode, mode) { + (Mode::Insert, Mode::Insert) => (), + (Mode::Insert, _) => { + if let Ok((_, col)) = self.cursor_pos() { + self.cursor_col = col; + } + let _ = self.repaint_line_full(); + } + _ => (), + } + 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 _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 mut state = State{ + let mut state = State { buf: r, + mode: Mode::Normal, row_start: 0, cursor_row: 0, cursor_col: 0, @@ -212,38 +233,48 @@ fn edit(file: &OsStr) -> Result<(), Box<dyn Error>> { let _ = stdout().flush(); loop { - let c = term::read_key(&mut stdin()).map_err(|err| format!("read key: {}", err))?; + let c = term::read_key().map_err(|err| format!("read key: {}", err))?; match c { Key::CtrlQ => { break; } Key::CtrlS => { + state.set_mode(Mode::Normal); let _ = state.buf.save(Path::new(file)); } Key::Up => { + state.set_mode(Mode::Normal); let _ = state.up(); let _ = stdout().flush(); } Key::Down => { + state.set_mode(Mode::Normal); let _ = state.down(); let _ = stdout().flush(); } Key::Left => { + state.set_mode(Mode::Normal); let _ = state.left(); let _ = stdout().flush(); } Key::Right => { + state.set_mode(Mode::Normal); let _ = state.right(); let _ = stdout().flush(); } Key::Backspace => { + state.set_mode(Mode::Backspace); if state.row_start == 0 && state.cursor_row == 0 && state.cursor_col == 0 { continue; } - let offset = state.buf.line_idx(state.row_start+state.cursor_row)+state.line_offset; + let offset = + state.buf.line_idx(state.row_start + state.cursor_row) + state.line_offset; let need_refresh_all = state.cursor_col == 0; let _ = state.left(); - state.buf = state.buf.slice(0, state.buf.floor_char_boundary(offset-1)).concat(&state.buf.slice(offset, state.buf.len())); + state.buf = state + .buf + .slice(0, state.buf.floor_char_boundary(offset - 1)) + .concat(&state.buf.slice(offset, state.buf.len())); if need_refresh_all { let _ = state.repaint_all(); } @@ -251,7 +282,11 @@ fn edit(file: &OsStr) -> Result<(), Box<dyn Error>> { let _ = stdout().flush(); } Key::Char(b'\r') => { - state.buf = state.buf.insert(state.buf.line_idx(state.row_start+state.cursor_row)+state.line_offset, b'\n'); + state.set_mode(Mode::Insert); + state.buf = state.buf.insert( + state.buf.line_idx(state.row_start + state.cursor_row) + state.line_offset, + b'\n', + ); if state.cursor_row == usize::from(size.ws_row - 1) { state.row_start += 1; } else { @@ -263,10 +298,14 @@ fn edit(file: &OsStr) -> Result<(), Box<dyn Error>> { let _ = stdout().flush(); } Key::Char(c) => { + state.set_mode(Mode::Insert); state.cursor_col = std::cmp::min(state.cursor_col, state.line_cols); - state.buf = state.buf.insert(state.buf.line_idx(state.row_start+state.cursor_row)+state.line_offset, c); - state.cursor_col += 1; - let _ = state.repaint_line_full(); + state.buf = state.buf.insert( + state.buf.line_idx(state.row_start + state.cursor_row) + state.line_offset, + c, + ); + state.line_offset += 1; + let _ = stdout().write_all(&vec![c]); let _ = stdout().flush(); } } |
