aboutsummaryrefslogtreecommitdiffstats
path: root/src/main.rs
diff options
context:
space:
mode:
authorRose Hogenson <rosehogenson@posteo.net>2024-01-11 00:20:11 -0800
committerRose Hogenson <rosehogenson@posteo.net>2024-01-11 00:20:11 -0800
commitdb087d43659f0760a02d6c32dc7dc0baf2f914f0 (patch)
treee514935b6c9dc7c9e835a64671aa0c6a80493595 /src/main.rs
parentSupport backspace in xterm. (diff)
downloadeditor-db087d43659f0760a02d6c32dc7dc0baf2f914f0.tar.zst
Rewrite the entire thing.
They say you can always do a better job on the rewrite, and to a certain extent that was true here. I kept a lot of it the same though, since there was a lot I liked from the original design. The main improvements are in efficiency and code clarity.
Diffstat (limited to 'src/main.rs')
-rw-r--r--src/main.rs778
1 files changed, 453 insertions, 325 deletions
diff --git a/src/main.rs b/src/main.rs
index 8e0cff8..7c672e4 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -4,403 +4,531 @@ mod term;
use rope::Rope;
use std::error::Error;
use std::ffi::{OsStr, OsString};
-use std::io::{stdout, BufReader, Stdin, Write};
-use std::ops::{Deref, DerefMut};
-use std::path::Path;
+use std::fs::File;
+use std::io::{stderr, stdout, BufReader, Stdin, Write};
+use std::path::{Path, PathBuf};
use term::Key;
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-enum Mode {
- Insert,
- Paste,
- MultilinePaste,
- Normal,
+fn open(file: &Path) -> Result<Rope, std::io::Error> {
+ let mut f = File::open(file)?;
+ let r = Rope::read(&mut f)?;
+ Ok(r)
}
-#[derive(Debug, Clone)]
-struct State {
+fn line_offset(
+ stdin: &mut BufReader<Stdin>,
+ line: &Rope,
+ col: u16,
+) -> Result<usize, Box<dyn Error>> {
+ // Binary search :D
+ let mut lo = 0;
+ let mut hi = line.len() + 1;
+ while lo < hi {
+ let mut h = lo + (hi - lo) / 2;
+ if h < line.len() {
+ h = line.floor_char_boundary(h);
+ }
+ write!(stdout(), "\x1b[G")?;
+ line.slice(0, h).print(&mut stdout())?;
+ let (_, c) = term::cursor_pos(stdin)?;
+ if c <= col {
+ lo = h + 1;
+ if lo < line.len() {
+ lo = line.ceil_char_boundary(lo);
+ }
+ } else {
+ hi = h;
+ }
+ }
+ if lo == line.len() + 1 {
+ return Ok(line.len());
+ }
+ if lo == 0 {
+ // You might think this case is impossible (I did), but terminals can do a lot of
+ // weird things.
+ return Ok(0);
+ }
+ Ok(line.floor_char_boundary(lo - 1))
+}
+
+fn floor_grapheme_cluster(
+ stdin: &mut BufReader<Stdin>,
+ line: &Rope,
+ offset: usize,
+) -> Result<usize, Box<dyn Error>> {
+ if offset == 0 || offset == line.len() {
+ return Ok(offset);
+ }
+ let mut offset = line.floor_char_boundary(offset);
+ write!(stdout(), "\x1b[G")?;
+ line.slice(0, line.ceil_char_boundary(offset + 1))
+ .print(&mut stdout())?;
+ let (_, target_col) = term::cursor_pos(stdin)?;
+ while offset > 0 {
+ write!(stdout(), "\x1b[G")?;
+ line.slice(0, offset).print(&mut stdout())?;
+ let (_, col) = term::cursor_pos(stdin)?;
+ if col < target_col {
+ return Ok(offset);
+ }
+ offset = line.floor_char_boundary(offset - 1);
+ }
+ Ok(0)
+}
+
+fn ceil_grapheme_cluster(
+ stdin: &mut BufReader<Stdin>,
+ line: &Rope,
+ offset: usize,
+) -> Result<usize, Box<dyn Error>> {
+ let mut offset = line.ceil_char_boundary(offset);
+ write!(stdout(), "\x1b[G")?;
+ line.slice(0, offset).print(&mut stdout())?;
+ let (_, target_col) = term::cursor_pos(stdin)?;
+ loop {
+ if offset >= line.len() {
+ return Ok(line.len());
+ }
+ let old_offset = offset;
+ offset = line.ceil_char_boundary(old_offset + 1);
+ line.slice(old_offset, offset).print(&mut stdout())?;
+ let (_, col) = term::cursor_pos(stdin)?;
+ if col > target_col {
+ return Ok(line.floor_char_boundary(offset - 1));
+ }
+ }
+}
+
+struct Snapshot {
buf: Rope,
- row_start: usize,
line_offset: usize,
+ row_start: usize,
cursor_row: u16,
cursor_col: u16,
- line_cols: u16,
- mode: Mode,
}
-#[derive(Debug)]
-struct StateHistory {
- stdin: BufReader<Stdin>,
+struct State {
+ history: Vec<Snapshot>,
+ n: Option<usize>,
- i: usize,
- history: Vec<State>,
- curr: State,
-}
+ buf: Rope,
+ line_offset: usize,
+ row_start: usize,
+ cursor_row: u16,
+ cursor_col: u16,
-impl Deref for StateHistory {
- type Target = State;
+ path: PathBuf,
+ inserting: bool,
+ need_repaint_screen: bool,
+ need_repaint_line: bool,
+}
- fn deref(&self) -> &Self::Target {
- return &self.curr;
+impl State {
+ fn line(&self) -> Rope {
+ self.buf.line(self.row_start + usize::from(self.cursor_row))
}
-}
-impl DerefMut for StateHistory {
- fn deref_mut(&mut self) -> &mut Self::Target {
- return &mut self.curr;
+ fn update_cursor_col(&mut self, stdin: &mut BufReader<Stdin>) -> Result<(), Box<dyn Error>> {
+ let size = term::size()?;
+ let line = self.line();
+ write!(stdout(), "\x1b[{}H\x1b[K", self.cursor_row + 1)?;
+ line.slice(0, self.line_offset).print(&mut stdout())?;
+ if self.line_offset == line.len() {
+ write!(stdout(), "\x1b[C\x1b[D\x1b[K")?;
+ return Ok(());
+ }
+ let (_, col) = term::cursor_pos(stdin)?;
+ if col == size.ws_col - 1 {
+ self.cursor_col = col - 1;
+ write!(stdout(), "\x1b[D\x1b[K")?;
+ return Ok(());
+ }
+ self.cursor_col = col;
+ line.slice(self.line_offset, line.len())
+ .print(&mut stdout())?;
+ write!(
+ stdout(),
+ "\x1b[C\x1b[D\x1b[K\x1b[{};{}H",
+ self.cursor_row + 1,
+ self.cursor_col + 1
+ )?;
+ Ok(())
}
-}
-impl StateHistory {
- fn repaint_all(&mut self) -> Result<(), Box<dyn Error>> {
+ fn repaint_screen(&self) -> Result<(), Box<dyn Error>> {
let size = term::size()?;
- let mut stdout = stdout().lock();
- write!(stdout, "\x1b[H\x1b[J")?;
+ write!(stdout(), "\x1b[H\x1b[J")?;
for i in self.row_start..self.row_start + usize::from(size.ws_row) {
if i > self.row_start {
- write!(stdout, "\r\n")?;
+ write!(stdout(), "\r\n")?;
}
if i > self.buf.lines() {
break;
}
- self.buf.line(i).print(&mut stdout)?;
- if let Ok((_, col)) = term::cursor_pos(&mut self.stdin) {
- if col == size.ws_col - 1 {
- let _ = write!(stdout, "\x1b[{}G\x1b[30m\x1b[47m>\x1b[m ", size.ws_col - 1);
- }
- }
+ let line = self.buf.line(i);
+ line.print(&mut stdout())?;
+ write!(stdout(), "\x1b[C\x1b[D\x1b[K")?;
}
- return Ok(());
+ Ok(())
}
- fn repaint_line_full(&mut self) -> Result<(), Box<dyn Error>> {
- let term_size = term::size()?;
- let mut stdout = stdout().lock();
-
- write!(stdout, "\x1b[{}H\x1b[K", self.cursor_row + 1)?;
- let line = self.buf.line(self.row_start + usize::from(self.cursor_row));
-
- line.print(&mut stdout)?;
- let (_, col) = match term::cursor_pos(&mut self.stdin) {
- Ok(x) => x,
- Err(err) => {
- let _ = write!(stdout, "\x1b[{}G", self.cursor_col + 1);
- return Err(err);
- }
- };
- self.line_cols = col;
- let mut truncated = false;
- if col == 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[{}G\x1b[30m\x1b[47m>\x1b[m \x1b[{}G",
- term_size.ws_col - 1,
- self.line_cols + 1,
- )?;
- }
- return Ok(());
+ fn snapshot(&self) -> Snapshot {
+ Snapshot {
+ buf: self.buf.clone(),
+ line_offset: self.line_offset,
+ row_start: self.row_start,
+ cursor_row: self.cursor_row,
+ cursor_col: self.cursor_col,
}
-
- let mut lo = 0;
- let mut hi = line.len();
- // Binary search :D
- while hi > lo {
- let x = line.floor_char_boundary(lo + (hi - lo) / 2);
- write!(stdout, "\x1b[G")?;
- line.slice(0, x).print(&mut stdout)?;
- let (_, col) = match term::cursor_pos(&mut self.stdin) {
- Ok(x) => x,
- Err(err) => {
- let _ = write!(stdout, "\x1b[{}G", self.cursor_col + 1);
- return Err(err);
- }
- };
- if col <= self.cursor_col {
- lo = line.ceil_char_boundary(x + 1);
- } else {
- hi = x;
- }
- }
- self.line_offset = line.floor_char_boundary(lo - 1);
- write!(stdout, "\x1b[G")?;
- line.print(&mut stdout)?;
- if truncated {
- write!(
- stdout,
- "\x1b[{}G\x1b[30m\x1b[47m>\x1b[m ",
- term_size.ws_col - 1
- )?;
- }
- write!(stdout, "\x1b[{}G", self.cursor_col + 1)?;
- return Ok(());
}
- fn up(&mut self) -> Result<(), Box<dyn Error>> {
- if self.cursor_row == 0 && self.row_start == 0 {
- return Ok(());
- }
- if self.cursor_row == 0 {
- self.row_start -= 1;
- self.repaint_all()?;
- } else {
- self.cursor_row -= 1;
- }
- self.repaint_line_full()?;
- return Ok(());
+ fn load(&mut self) {
+ let Some(n) = self.n else {
+ return;
+ };
+ let snapshot = &self.history[n];
+ self.buf = snapshot.buf.clone();
+ self.line_offset = snapshot.line_offset;
+ self.row_start = snapshot.row_start;
+ self.cursor_row = snapshot.cursor_row;
+ self.cursor_col = snapshot.cursor_col;
}
- fn down(&mut self) -> Result<(), Box<dyn Error>> {
- if self.row_start + usize::from(self.cursor_row) == self.buf.lines() {
- return Ok(());
+ fn insert(&mut self, inserting: bool) {
+ self.inserting = true;
+ if inserting {
+ return;
}
- let size = term::size()?;
- if self.cursor_row == size.ws_row - 1 {
- self.row_start += 1;
- self.repaint_all()?;
- } else {
- self.cursor_row += 1;
+ if let Some(n) = self.n {
+ self.history.truncate(n);
+ self.n = None;
}
- self.repaint_line_full()?;
- return Ok(());
+ self.history.push(self.snapshot());
}
- fn left(&mut self) -> Result<(), Box<dyn Error>> {
- if self.cursor_col > self.line_cols {
- self.cursor_col = self.line_cols;
- }
- if self.cursor_col == 0 && self.cursor_row == 0 && self.row_start == 0 {
- return Ok(());
- }
- if self.cursor_col == 0 {
- self.up()?;
- self.cursor_col = self.line_cols;
- } else {
- self.cursor_col -= 1;
- }
- self.repaint_line_full()?;
- return Ok(());
- }
+ fn keypress(&mut self, stdin: &mut BufReader<Stdin>, key: Key) -> Result<bool, Box<dyn Error>> {
+ const CTRL_Q: u8 = b'Q' - b'@';
+ const CTRL_S: u8 = b'S' - b'@';
+ const CTRL_Y: u8 = b'Y' - b'@';
+ const CTRL_Z: u8 = b'Z' - b'@';
+ const BACKSPACE: u8 = b'H' - b'@';
+ const OTHER_BACKSPACE: u8 = 127;
- fn right(&mut self) -> Result<(), Box<dyn Error>> {
- if self.cursor_col >= self.line_cols
- && self.row_start + usize::from(self.cursor_row) == self.buf.lines()
- {
- return Ok(());
- }
- if self.cursor_col >= self.line_cols {
- self.down()?;
- self.cursor_col = 0;
- } else {
- self.cursor_col += 1;
- }
- self.repaint_line_full()?;
- return Ok(());
- }
+ let inserting = self.inserting;
+ self.inserting = false;
- fn set_mode(&mut self, mode: Mode) {
- match (self.mode, mode) {
- (Mode::Normal, Mode::Normal) => (),
- (Mode::Normal, _) => {
- self.i += 1;
- self.history.truncate(self.i);
- self.history.push(self.curr.clone());
- }
- (Mode::Paste, Mode::Paste) => (),
- (Mode::Paste, _) => {
- let Ok((_, col)) = term::cursor_pos(&mut self.stdin) else {
- return;
- };
- self.cursor_col = col;
- let _ = self.repaint_line_full();
- let _ = stdout().flush();
- }
- (Mode::MultilinePaste, Mode::MultilinePaste) => (),
- (Mode::MultilinePaste, Mode::Paste) => {
- return;
- }
- (Mode::MultilinePaste, _) => {
- let Ok((_, col)) = term::cursor_pos(&mut self.stdin) else {
- return;
- };
- self.cursor_col = col;
- let _ = self.repaint_all();
- let _ = self.repaint_line_full();
- let _ = stdout().flush();
- }
- _ => (),
- }
- 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 r = Rope::open(Path::new(file))?;
- let size = term::size()?;
- let initial_state = State {
- buf: r,
- mode: Mode::Normal,
- row_start: 0,
- cursor_row: 0,
- cursor_col: 0,
- line_offset: 0,
- line_cols: 0,
- };
- let mut state = StateHistory {
- stdin: BufReader::new(std::io::stdin()),
- i: 0,
- history: vec![initial_state.clone()],
- curr: initial_state,
- };
- let _ = state.repaint_all();
- let _ = write!(stdout(), "\x1b[H");
- let _ = stdout().flush();
-
- loop {
- let c = term::read_key(&mut state.stdin)?;
- match c {
+ match key {
Key::Timeout => {
- if state.mode != Mode::Paste && state.mode != Mode::MultilinePaste {
- continue;
+ if inserting {
+ self.inserting = true;
}
- state.set_mode(Mode::Insert);
- }
- Key::CtrlQ => {
- break;
- }
- Key::CtrlS => {
- state.set_mode(Mode::Normal);
- let _ = state.buf.save(Path::new(file));
- }
- Key::CtrlZ => {
- state.set_mode(Mode::Normal);
- if state.i == state.history.len() - 1 {
- state.history.push(state.curr.clone());
- } else if state.i > 0 {
- state.i -= 1;
+ if !self.need_repaint_screen && !self.need_repaint_line {
+ return Ok(true);
}
- state.curr = state.history[state.i].clone();
- let _ = state.repaint_all();
- let _ = state.repaint_line_full();
- let _ = stdout().flush();
- }
- Key::CtrlY => {
- state.set_mode(Mode::Normal);
- if state.i == state.history.len() - 1 {
- continue;
+ if self.need_repaint_screen {
+ self.repaint_screen()?;
+ self.need_repaint_screen = false;
}
- state.i += 1;
- state.curr = state.history[state.i].clone();
- let _ = state.repaint_all();
- let _ = state.repaint_line_full();
- let _ = stdout().flush();
+ self.update_cursor_col(stdin)?;
+ self.need_repaint_line = false;
+ stdout().flush()?;
}
Key::Up => {
- state.set_mode(Mode::Normal);
- let _ = state.up();
- let _ = stdout().flush();
+ if self.cursor_row == 0 && self.row_start == 0 {
+ return Ok(true);
+ }
+ let prev_line = self
+ .buf
+ .line(self.row_start + usize::from(self.cursor_row) - 1);
+ if self.cursor_row == 0 {
+ self.line_offset = line_offset(stdin, &prev_line, self.cursor_col)?;
+ self.row_start -= 1;
+ self.repaint_screen()?;
+ self.update_cursor_col(stdin)?;
+ stdout().flush()?;
+ return Ok(true);
+ }
+ write!(stdout(), "\x1b[{}H", self.cursor_row)?;
+ self.line_offset = line_offset(stdin, &prev_line, self.cursor_col)?;
+ self.cursor_row -= 1;
+ self.update_cursor_col(stdin)?;
+ stdout().flush()?;
}
Key::Down => {
- state.set_mode(Mode::Normal);
- let _ = state.down();
- let _ = stdout().flush();
+ if self.row_start + usize::from(self.cursor_row) >= self.buf.lines() {
+ return Ok(true);
+ }
+ let size = term::size()?;
+ let next_line = self
+ .buf
+ .line(self.row_start + usize::from(self.cursor_row) + 1);
+ if self.cursor_row < size.ws_row - 1 {
+ write!(stdout(), "\x1b[{}H", self.cursor_row + 2)?;
+ self.line_offset = line_offset(stdin, &next_line, self.cursor_col)?;
+ self.cursor_row += 1;
+ self.update_cursor_col(stdin)?;
+ stdout().flush()?;
+ return Ok(true);
+ }
+ self.line_offset = line_offset(stdin, &next_line, self.cursor_col)?;
+ self.row_start += 1;
+ self.repaint_screen()?;
+ self.update_cursor_col(stdin)?;
+ stdout().flush()?;
}
Key::Left => {
- state.set_mode(Mode::Normal);
- let _ = state.left();
- let _ = stdout().flush();
+ if self.line_offset > 0 {
+ self.line_offset =
+ floor_grapheme_cluster(stdin, &self.line(), self.line_offset - 1)?;
+ self.update_cursor_col(stdin)?;
+ stdout().flush()?;
+ return Ok(true);
+ }
+ if self.cursor_row > 0 {
+ let size = term::size()?;
+ write!(stdout(), "\x1b[{}H", self.cursor_row)?;
+ self.line_offset = line_offset(
+ stdin,
+ &self
+ .buf
+ .line(self.row_start + usize::from(self.cursor_row) - 1),
+ size.ws_col - 2,
+ )?;
+ self.cursor_row -= 1;
+ self.update_cursor_col(stdin)?;
+ stdout().flush()?;
+ return Ok(true);
+ }
+ if self.row_start == 0 {
+ return Ok(true);
+ }
+ let size = term::size()?;
+ self.line_offset = line_offset(
+ stdin,
+ &self
+ .buf
+ .line(self.row_start + usize::from(self.cursor_row) - 1),
+ size.ws_col - 2,
+ )?;
+ self.row_start -= 1;
+ self.repaint_screen()?;
+ self.update_cursor_col(stdin)?;
+ stdout().flush()?;
}
Key::Right => {
- state.set_mode(Mode::Normal);
- let _ = state.right();
- let _ = stdout().flush();
+ let size = term::size()?;
+ let line = self.line();
+ if self.cursor_col < size.ws_col - 2 && self.line_offset < line.len() {
+ self.line_offset = ceil_grapheme_cluster(stdin, &line, self.line_offset + 1)?;
+ self.update_cursor_col(stdin)?;
+ stdout().flush()?;
+ return Ok(true);
+ }
+ if self.row_start + usize::from(self.cursor_row) >= self.buf.lines() {
+ return Ok(true);
+ }
+ if self.cursor_row < size.ws_row - 1 {
+ self.cursor_row += 1;
+ self.line_offset = 0;
+ self.cursor_col = 0;
+ write!(stdout(), "\x1b[{}H", self.cursor_row + 1)?;
+ stdout().flush()?;
+ return Ok(true);
+ }
+ self.row_start += 1;
+ self.line_offset = 0;
+ self.cursor_col = 0;
+ self.repaint_screen()?;
+ write!(stdout(), "\x1b[{}H", self.cursor_row + 1)?;
+ stdout().flush()?;
+ }
+ Key::Byte(CTRL_Q) => {
+ return Ok(false);
}
- Key::Backspace => {
- state.set_mode(Mode::Insert);
- if state.row_start == 0 && state.cursor_row == 0 && state.line_offset == 0 {
- continue;
+ Key::Byte(CTRL_S) => {
+ let mut f = File::create(&self.path)?;
+ self.buf.print(&mut f)?;
+ f.sync_all()?;
+ }
+ Key::Byte(CTRL_Z) => {
+ if let Some(n) = self.n {
+ if n > 0 {
+ self.n = Some(n - 1);
+ }
+ } else {
+ self.n = Some(self.history.len() - 1);
+ self.history.push(self.snapshot());
+ }
+ self.load();
+ self.repaint_screen()?;
+ self.update_cursor_col(stdin)?;
+ stdout().flush()?;
+ }
+ Key::Byte(CTRL_Y) => {
+ let Some(n) = self.n else {
+ return Ok(true);
+ };
+ if n == self.history.len() - 1 {
+ return Ok(true);
+ }
+ self.n = Some(n + 1);
+ self.load();
+ self.repaint_screen()?;
+ self.update_cursor_col(stdin)?;
+ stdout().flush()?;
+ }
+ Key::Byte(BACKSPACE) | Key::Byte(OTHER_BACKSPACE) => {
+ self.insert(inserting);
+ if self.line_offset == 0 && self.cursor_row == 0 && self.row_start == 0 {
+ return Ok(true);
}
- let line_start = state
+ let line_start = self
.buf
- .line_idx(state.row_start + usize::from(state.cursor_row));
- let gap_end = line_start + state.line_offset;
- let gap_start = state.buf.floor_char_boundary(gap_end - 1);
- if state.line_offset == 0 {
- let _ = state.left();
- } else {
- let _ = write!(stdout(), "\x1b[G");
- let _ = state.buf.slice(line_start, gap_start).print(&mut stdout());
- match term::cursor_pos(&mut state.stdin) {
- Ok((_, col)) => {
- state.cursor_col = col;
- }
- Err(_) => {
- state.cursor_col -= 1;
- }
+ .line_start(self.row_start + usize::from(self.cursor_row));
+ let offset = line_start + self.line_offset;
+ if self.line_offset == 0 {
+ if self.cursor_row == 0 {
+ self.row_start -= 1;
+ } else {
+ self.cursor_row -= 1;
}
+ self.line_offset = self.line().len();
+ self.buf = self
+ .buf
+ .slice(0, offset - 1)
+ .concat(&self.buf.slice(offset, self.buf.len()));
+ self.repaint_screen()?;
+ self.update_cursor_col(stdin)?;
+ stdout().flush()?;
+ return Ok(true);
}
- state.buf = state
+ let line = self.line();
+ self.line_offset = match floor_grapheme_cluster(stdin, &line, self.line_offset - 1)
+ {
+ Ok(x) => x,
+ Err(_) => line.floor_char_boundary(self.line_offset - 1),
+ };
+ self.buf = self
.buf
- .slice(0, gap_start)
- .concat(&state.buf.slice(gap_end, state.buf.len()));
- let _ = state.repaint_all();
- let _ = state.repaint_line_full();
- let _ = stdout().flush();
+ .slice(0, line_start + self.line_offset)
+ .concat(&self.buf.slice(offset, self.buf.len()));
+ self.update_cursor_col(stdin)?;
+ stdout().flush()?;
}
- Key::Char(b'\r') => {
- state.set_mode(Mode::MultilinePaste);
- state.buf = state.buf.insert(
- state
- .buf
- .line_idx(state.row_start + usize::from(state.cursor_row))
- + state.line_offset,
- b'\n',
- );
- if state.cursor_row == size.ws_row - 1 {
- state.row_start += 1;
+ Key::Byte(b'\r') | Key::Byte(b'\n') => {
+ self.insert(inserting);
+ let size = term::size()?;
+ let offset = self
+ .buf
+ .line_start(self.row_start + usize::from(self.cursor_row))
+ + self.line_offset;
+ self.buf = self
+ .buf
+ .slice(0, offset)
+ .concat(&Rope::new(&[b'\n']))
+ .concat(&self.buf.slice(offset, self.buf.len()));
+ if self.cursor_row < size.ws_row - 1 {
+ self.cursor_row += 1;
} else {
- state.cursor_row += 1;
+ self.row_start += 1;
}
- state.cursor_col = 0;
- state.line_offset = 0;
- let _ = write!(stdout(), "\r\n");
- let _ = stdout().flush();
+ self.line_offset = 0;
+ self.cursor_col = 0;
+ self.need_repaint_screen = true;
+ write!(stdout(), "\r\n")?;
+ stdout().flush()?;
}
- Key::Char(c) => {
- state.set_mode(Mode::Paste);
- state.buf = state.buf.insert(
- state
- .buf
- .line_idx(state.row_start + usize::from(state.cursor_row))
- + state.line_offset,
- c,
- );
- state.line_offset += 1;
- let _ = stdout().write_all(&vec![c]);
- let _ = stdout().flush();
+ Key::Byte(c) => {
+ self.insert(inserting);
+ let offset = self
+ .buf
+ .line_start(self.row_start + usize::from(self.cursor_row))
+ + self.line_offset;
+ self.buf = self
+ .buf
+ .slice(0, offset)
+ .concat(&Rope::new(&[c]))
+ .concat(&self.buf.slice(offset, self.buf.len()));
+ self.line_offset += 1;
+ self.need_repaint_line = true;
+ stdout().write_all(&[c])?;
+ stdout().flush()?;
}
}
+ Ok(true)
+ }
+}
+
+fn edit(file: &OsStr) -> Result<(), String> {
+ let file = PathBuf::from(file);
+ let r = match open(&file) {
+ Ok(r) => r,
+ Err(err) => {
+ return Err(format!("open file {}: {}", file.display(), err));
+ }
+ };
+ let _raw_mode = match term::raw_mode() {
+ Ok(x) => x,
+ Err(err) => {
+ return Err(format!("enter raw mode: {}", err));
+ }
+ };
+ let mut stdin = BufReader::new(std::io::stdin());
+ let mut state = State {
+ history: vec![Snapshot {
+ buf: r.clone(),
+ row_start: 0,
+ cursor_row: 0,
+ cursor_col: 0,
+ line_offset: 0,
+ }],
+ n: None,
+
+ buf: r,
+ row_start: 0,
+ cursor_row: 0,
+ cursor_col: 0,
+ line_offset: 0,
+
+ path: file,
+ inserting: false,
+ need_repaint_line: false,
+ need_repaint_screen: false,
+ };
+ if let Err(err) = state.repaint_screen() {
+ return Err(format!("cannot write to screen: {}", err));
+ }
+ if let Err(err) = write!(stdout(), "\x1b[H") {
+ return Err(format!("cannot write to screen: {}", err));
+ }
+ if let Err(err) = stdout().flush() {
+ return Err(format!("cannot write to screen: {}", err));
+ }
+ loop {
+ let Ok(key) = term::read_key(&mut stdin) else {
+ return Err(String::from("no input"));
+ };
+ let Ok(ok) = state.keypress(&mut stdin, key) else {
+ state.need_repaint_screen = true;
+ continue;
+ };
+ // I miss "do while" so much...
+ if !ok {
+ break;
+ }
}
- return Ok(());
+ Ok(())
}
fn main() {
let args: Vec<OsString> = std::env::args_os().collect();
if args.len() != 2 {
- println!("Usage: edit <filename>");
+ let _ = writeln!(stderr(), "Usage: edit <filename>");
std::process::exit(1);
}
if let Err(err) = edit(&args[1]) {
- println!("FAIL: {}", err);
+ let _ = writeln!(stderr(), "FAIL: {}", err);
std::process::exit(1);
}
}