diff options
Diffstat (limited to 'src/main.rs')
| -rw-r--r-- | src/main.rs | 194 |
1 files changed, 134 insertions, 60 deletions
diff --git a/src/main.rs b/src/main.rs index 2b359f8..599ed36 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,13 +3,100 @@ mod term; use rope::Rope; use std::ffi::{OsStr, OsString}; -use std::io::{Stdout, Write}; +use std::io::{stdout, stdin, Write, Read}; use std::path::Path; use term::Key; -fn move_cursor(stdout: &mut Stdout, row: usize, col: usize) { - let _ = write!(stdout, "\x1b[{};{}H", row + 1, col + 1); - let _ = stdout.flush(); +struct State { + buf: Rope, + cursor_row: usize, + cursor_col: usize, + line_offset: usize, + line_cols: usize, +} + +impl State { + fn cursor_pos(&mut self) -> Result<(usize, usize), String> { + if let Err(err) = write!(stdout(), "\x1b[6n") { + return Err(format!("cursor position: status report: {}", err)); + } + if let Err(err) = stdout().flush() { + return Err(format!("cursor position: status report: {}", err)); + } + let mut buf = vec![0; 20]; + let n = match stdin().read(&mut buf) { + Ok(n) => n, + Err(err) => { + return Err(format!("cursor position: read status report: {}", err)); + } + }; + buf.truncate(n); + let mut semicolon = 0; + for (i, &c) in buf.iter().enumerate() { + if c == b';' { + semicolon = i; + break; + } + } + let row_str = match std::str::from_utf8(&buf[2..semicolon]) { + Ok(row_str) => row_str, + Err(_) => { + return Err(format!("cursor position: invalid response {}", String::from_utf8_lossy(&buf))); + } + }; + let col_str = match std::str::from_utf8(&buf[semicolon+1..buf.len()-1]) { + Ok(col_str) => col_str, + Err(_) => { + return Err(format!("cursor position: invalid response {}", String::from_utf8_lossy(&buf))); + } + }; + let row = match row_str.parse::<usize>() { + Ok(row) => row, + Err(_) => { + return Err(format!("cursor position: invalid response {}", String::from_utf8_lossy(&buf))); + } + }; + let col = match col_str.parse::<usize>() { + Ok(col) => col, + Err(_) => { + return Err(format!("cursor position: invalid response {}", String::from_utf8_lossy(&buf))); + } + }; + // Yuck... 1 indexing + return Ok((row-1, col-1)); + } + + fn repaint_line_full(&mut self) { + let _ = write!(stdout(), "\x1b[{}H", self.cursor_row+1); + let line = self.buf.line(self.cursor_row); + + let _ = line.print(&mut stdout()); + let (_, col) = self.cursor_pos().expect("asdf"); + self.line_cols = col; + if self.cursor_col >= self.line_cols { + self.line_offset = line.len(); + return; + } + + let mut lo = line.char(self.cursor_col).expect("at this point, cursor_col should have some valid offset"); + let mut hi = line.len(); + // Binary search :D + while hi > lo { + let x = line.floor_char_boundary(lo + (hi - lo) / 2); + let _ = write!(stdout(), "\x1b[G"); + let _ = line.slice(0, x).print(&mut stdout()); + let (_, col) = self.cursor_pos().expect("ouchie, I should really handle this I guess"); + if col <= self.cursor_col { + lo = line.ceil_char_boundary(x+1); + } else { + hi = x; + } + } + self.line_offset = line.floor_char_boundary(lo-1); + let _ = write!(stdout(), "\x1b[G\x1b[K"); + let _ = line.print(&mut stdout()); + let _ = write!(stdout(), "\x1b[{}G", self.cursor_col+1); + } } fn edit(file: &OsStr) -> Result<(), String> { @@ -20,7 +107,7 @@ fn edit(file: &OsStr) -> Result<(), String> { } }; - let mut r = match Rope::open(Path::new(file)) { + let r = match Rope::open(Path::new(file)) { Ok(r) => r, Err(err) => { return Err(format!("open file {}: {}", file.to_string_lossy(), err)); @@ -32,28 +119,34 @@ fn edit(file: &OsStr) -> Result<(), String> { return Err(String::from("cannot get terminal size")); } }; - let mut stdout = std::io::stdout(); - let _ = stdout.write(b"\x1b[J"); + let mut state = State{ + buf: r, + cursor_row: 0, + cursor_col: 0, + line_offset: 0, + line_cols: 0, + }; + let _ = write!(stdout(), "\x1b[J"); for i in 0..usize::from(size.ws_row) { if i > 0 { - let _ = stdout.write(b"\r\n"); + let _ = write!(stdout(), "\r\n"); } - if i > r.lines() { + if i > state.buf.lines() { break; } - let line = r.line(i); + let line = state.buf.line(i); // TODO: handle line-wrapping. - let _ = line.print(&mut stdout); + let _ = line.print(&mut stdout()); + if i == 0 { + let (_, col) = state.cursor_pos()?; + state.line_cols = col; + } } - let _ = stdout.write(b"\x1b[H"); - let _ = stdout.flush(); + let _ = write!(stdout(), "\x1b[H"); + let _ = stdout().flush(); - let mut cursor_row = 0; - let mut cursor_col = 0; - - let mut stdin = std::io::stdin(); loop { - let c = match term::read_key(&mut stdin) { + let c = match term::read_key(&mut stdin()) { Ok(c) => c, Err(err) => { return Err(err); @@ -64,70 +157,51 @@ fn edit(file: &OsStr) -> Result<(), String> { break; } Key::CtrlS => { - if let Err(err) = r.save(Path::new(file)) { + if let Err(err) = state.buf.save(Path::new(file)) { return Err(format!("write file {}: {}", file.to_string_lossy(), err)); } } Key::Up => { - if cursor_row == 0 { + if state.cursor_row == 0 { continue; } - cursor_row -= 1; + state.cursor_row -= 1; - let len = r - .line(cursor_row) - .len(); - if cursor_col > len { - move_cursor(&mut stdout, cursor_row, len); - } else { - move_cursor(&mut stdout, cursor_row, cursor_col); - } + state.repaint_line_full(); + let _ = stdout().flush(); } Key::Down => { - if cursor_row == usize::from(size.ws_row - 1) || cursor_row == r.lines() { + if state.cursor_row == usize::from(size.ws_row - 1) || state.cursor_row == state.buf.lines() { continue; } - cursor_row += 1; + state.cursor_row += 1; - let len = r - .line(cursor_row) - .len(); - if cursor_col > len { - move_cursor(&mut stdout, cursor_row, len); - } else { - move_cursor(&mut stdout, cursor_row, cursor_col); - } + state.repaint_line_full(); + let _ = stdout().flush(); } Key::Left => { - let len = r - .line(cursor_row) - .len(); - cursor_col = std::cmp::min(cursor_col, len); - if cursor_col == 0 { + state.cursor_col = std::cmp::min(state.cursor_col, state.line_cols); + if state.cursor_col == 0 { continue; } - cursor_col -= 1; - move_cursor(&mut stdout, cursor_row, cursor_col); + state.cursor_col -= 1; + state.repaint_line_full(); + let _ = stdout().flush(); } Key::Right => { - let len = r - .line(cursor_row) - .len(); - if cursor_col >= len { + if state.cursor_col >= state.line_cols { continue; } - cursor_col += 1; - move_cursor(&mut stdout, cursor_row, cursor_col); + state.cursor_col += 1; + state.repaint_line_full(); + let _ = stdout().flush(); } Key::Char(c) => { - cursor_col = std::cmp::min(cursor_col, r.line(cursor_row).len()); - r = r.insert(r.line_idx(cursor_row)+cursor_col, c); - move_cursor(&mut stdout, cursor_row, 0); - let _ = r - .line(cursor_row) - .print(&mut stdout); - cursor_col += 1; - move_cursor(&mut stdout, cursor_row, cursor_col); + state.cursor_col = std::cmp::min(state.cursor_col, state.line_cols); + state.buf = state.buf.insert(state.buf.line_idx(state.cursor_row)+state.line_offset, c); + state.cursor_col += 1; + state.repaint_line_full(); + let _ = stdout().flush(); } } } |
