aboutsummaryrefslogtreecommitdiffstats
path: root/src/main.rs
diff options
context:
space:
mode:
authorRose Hogenson <rosehogenson@posteo.net>2024-01-06 22:05:37 -0800
committerRose Hogenson <rosehogenson@posteo.net>2024-01-06 22:05:37 -0800
commitbb5480126fb4417339000f183b0d6787c7cb2b82 (patch)
treea87ca6672d061b8f340b0ac40fee7f22f806b62e /src/main.rs
parentFix some bugs with long lines. (diff)
downloadeditor-bb5480126fb4417339000f183b0d6787c7cb2b82.tar.zst
Use a b-tree for the rope.
Now it's balanced :D
Diffstat (limited to 'src/main.rs')
-rw-r--r--src/main.rs157
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();
}
}