diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/main.rs | 194 | ||||
| -rw-r--r-- | src/rope.rs | 86 |
2 files changed, 212 insertions, 68 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(); } } } diff --git a/src/rope.rs b/src/rope.rs index ca057bd..ea37e6c 100644 --- a/src/rope.rs +++ b/src/rope.rs @@ -76,6 +76,9 @@ impl Rope { } pub fn print(&self, out: &mut dyn Write) -> Result<(), std::io::Error> { + if self.len() == 0 { + return Ok(()); + } // TODO: escape unprintable characters match self { Rope(Node::Leaf(l)) => { @@ -209,9 +212,9 @@ impl Rope { return Ok(()); } - fn slice(&self, start: usize, end: usize) -> Rope { - if start >= self.len() { - panic!("Index {} out of range 0..{}", start, self.len()); + pub fn slice(&self, start: usize, end: usize) -> Rope { + if start > self.len() { + panic!("Index {} out of range 0..{}", start, self.len()+1); } if end > self.len() { panic!("Index {} out of range 0..{}", end, self.len()); @@ -223,8 +226,8 @@ impl Rope { Rope(Node::Leaf(l)) => { return Rope(Node::Leaf(Leaf{ buf: l.buf.clone(), - start: u8::try_from(start).expect("buffer too long"), - end: u8::try_from(end).expect("buffer too long"), + start: l.start + u8::try_from(start).expect("buffer too long"), + end: l.start + u8::try_from(end).expect("buffer too long"), })); } Rope(Node::Branch(b)) => { @@ -234,11 +237,11 @@ impl Rope { } let mut right = Rope::leaf(Vec::new()); if end > b.left.len() { - let mut left_start = 0; + let mut right_start = 0; if start > b.left.len() { - left_start = start - b.left.len(); + right_start = start - b.left.len(); } - right = b.right.slice(left_start, end - b.left.len()); + right = b.right.slice(right_start, end - b.left.len()); } return left.concat(&right); } @@ -256,4 +259,71 @@ impl Rope { } return self.slice(start, end-1); } + + pub fn char(&self, n: usize) -> Result<usize, usize> { + match self { + Rope(Node::Leaf(l)) => { + let mut count = 0; + for (i, &b) in l.bytes().iter().enumerate() { + if b & 0xc0 == 0x80 { + continue; + } + if count == n { + return Ok(i); + } + count += 1; + } + return Err(count); + } + Rope(Node::Branch(b)) => { + match b.left.char(n) { + Ok(i) => { + return Ok(i); + } + Err(len) => { + match b.right.char(n - len) { + Ok(i) => { + return Ok(b.left.len() + i); + } + Err(right_len) => { + return Err(len + right_len); + } + } + } + } + } + } + } + + fn is_char_boundary(&self, index: usize) -> bool { + match self { + Rope(Node::Leaf(l)) => { + return l.bytes()[index] & 0xc0 != 0x80; + } + Rope(Node::Branch(b)) => { + if index < b.left.len() { + return b.left.is_char_boundary(index); + } + return b.right.is_char_boundary(index - b.left.len()); + } + } + } + + 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; + } + } + return self.len(); + } } |
