aboutsummaryrefslogtreecommitdiffstats
path: root/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.rs')
-rw-r--r--src/main.rs194
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();
}
}
}