From 95c8e4763eddb2336d6ae7acdb752da94be43f5b Mon Sep 17 00:00:00 2001 From: Rose Hogenson Date: Sun, 7 Jan 2024 14:14:58 -0800 Subject: Support pasting text into the editor. The editor only repaints when there is a small delay in the input, which only happens when the user is typing. Otherwise the bytes go straight into the buffer. --- src/main.rs | 187 ++++++++++++++++++++++++++++++------------------------------ 1 file changed, 94 insertions(+), 93 deletions(-) (limited to 'src/main.rs') diff --git a/src/main.rs b/src/main.rs index f0a38d8..8999771 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,69 +4,53 @@ mod term; use rope::Rope; use std::error::Error; use std::ffi::{OsStr, OsString}; -use std::io::{stdin, stdout, Read, Write}; +use std::io::{stdout, BufReader, Stdin, Write}; use std::ops::{Deref, DerefMut}; use std::path::Path; use term::Key; -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] enum Mode { Insert, + Paste, Normal, } #[derive(Debug, Clone)] struct State { buf: Rope, - mode: Mode, row_start: usize, - cursor_row: usize, - cursor_col: usize, line_offset: usize, - line_cols: usize, + cursor_row: u16, + cursor_col: u16, + line_cols: u16, + mode: Mode, } -fn parse_status_report(buf: &[u8]) -> Result<(usize, usize), Box> { - let mut semicolon = 0; - for (i, &c) in buf.iter().enumerate() { - if c == b';' { - semicolon = i; - break; - } - } - if semicolon < 2 || semicolon == buf.len() - 1 { - return Err(Box::from("invalid response")); +#[derive(Debug)] +struct StateHistory { + stdin: BufReader, + + i: usize, + history: Vec, +} + +impl Deref for StateHistory { + type Target = State; + + fn deref(&self) -> &Self::Target { + return &self.history[self.i]; } - let row_str = std::str::from_utf8(&buf[2..semicolon])?; - let col_str = std::str::from_utf8(&buf[semicolon + 1..buf.len() - 1])?; - let row = row_str.parse::()?; - let col = col_str.parse::()?; - // Yuck... 1 indexing - return Ok((row - 1, col - 1)); } -fn cursor_pos() -> Result<(usize, usize), Box> { - 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))?; - buf.truncate(n); - let pos = parse_status_report(&buf).map_err(|_| { - format!( - "cursor position: invalid response: {}", - String::from_utf8_lossy(&buf) - ) - })?; - return Ok(pos); +impl DerefMut for StateHistory { + fn deref_mut(&mut self) -> &mut Self::Target { + return &mut self.history[self.i]; + } } -impl State { - fn repaint_all(&self) -> Result<(), Box> { +impl StateHistory { + fn repaint_all(&mut self) -> Result<(), Box> { let size = term::size()?; let mut stdout = stdout().lock(); @@ -79,9 +63,10 @@ impl State { break; } self.buf.line(i).print(&mut stdout)?; - let (_, col) = cursor_pos()?; - if col == usize::from(size.ws_col - 1) { - write!(stdout, "\x1b[{}G\x1b[30m\x1b[47m>\x1b[m ", size.ws_col - 1)?; + 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); + } } } return Ok(()); @@ -92,13 +77,19 @@ impl State { let mut stdout = stdout().lock(); write!(stdout, "\x1b[{}H\x1b[K", self.cursor_row + 1)?; - let line = self.buf.line(self.row_start + self.cursor_row); + let line = self.buf.line(self.row_start + usize::from(self.cursor_row)); line.print(&mut stdout)?; - let (_, col) = cursor_pos()?; + 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 == usize::from(term_size.ws_col - 1) { + if col == term_size.ws_col - 1 { truncated = true; self.line_cols = col - 1; } @@ -122,7 +113,13 @@ impl State { let x = line.floor_char_boundary(lo + (hi - lo) / 2); write!(stdout, "\x1b[G")?; line.slice(0, x).print(&mut stdout)?; - let (_, col) = cursor_pos()?; + 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 { @@ -158,11 +155,11 @@ impl State { } fn down(&mut self) -> Result<(), Box> { - if self.row_start + self.cursor_row == self.buf.lines() { + if self.row_start + usize::from(self.cursor_row) == self.buf.lines() { return Ok(()); } let size = term::size()?; - if self.cursor_row == usize::from(size.ws_row - 1) { + if self.cursor_row == size.ws_row - 1 { self.row_start += 1; self.repaint_all()?; } else { @@ -190,7 +187,8 @@ impl State { } fn right(&mut self) -> Result<(), Box> { - if self.cursor_col >= self.line_cols && self.row_start + self.cursor_row == self.buf.lines() + if self.cursor_col >= self.line_cols + && self.row_start + usize::from(self.cursor_row) == self.buf.lines() { return Ok(()); } @@ -203,49 +201,32 @@ impl State { self.repaint_line_full()?; return Ok(()); } -} - -#[derive(Debug)] -struct StateHistory { - i: usize, - history: Vec, -} -impl StateHistory { fn set_mode(&mut self, mode: Mode) { match (self.mode, mode) { - (Mode::Insert, Mode::Normal) => { - if let Ok((_, col)) = cursor_pos() { - self.cursor_col = col; - } - let _ = self.repaint_line_full(); - } - (Mode::Normal, Mode::Insert) => { + (Mode::Normal, Mode::Normal) => (), + (Mode::Normal, _) => { let curr = self.clone(); self.i += 1; self.history.truncate(self.i); self.history.push(curr); } + (Mode::Paste, Mode::Paste) => (), + (Mode::Paste, _) => { + 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; } } -impl Deref for StateHistory { - type Target = State; - - fn deref(&self) -> &Self::Target { - return &self.history[self.i]; - } -} - -impl DerefMut for StateHistory { - fn deref_mut(&mut self) -> &mut Self::Target { - return &mut self.history[self.i]; - } -} - fn edit(file: &OsStr) -> Result<(), Box> { let _raw_handle = term::raw().map_err(|err| format!("cannot put terminal in raw mode: {}", err))?; @@ -263,14 +244,21 @@ fn edit(file: &OsStr) -> Result<(), Box> { line_offset: 0, line_cols: 0, }], + stdin: BufReader::new(std::io::stdin()), }; let _ = state.repaint_all(); let _ = write!(stdout(), "\x1b[H"); let _ = stdout().flush(); loop { - let c = term::read_key().map_err(|err| format!("read key: {}", err))?; + let c = term::read_key(&mut state.stdin)?; match c { + Key::Timeout => { + if state.mode != Mode::Paste { + continue; + } + state.set_mode(Mode::Insert); + } Key::CtrlQ => { break; } @@ -323,7 +311,9 @@ fn edit(file: &OsStr) -> Result<(), Box> { if state.row_start == 0 && state.cursor_row == 0 && state.line_offset == 0 { continue; } - let line_start = state.buf.line_idx(state.row_start + state.cursor_row); + let line_start = state + .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 { @@ -331,8 +321,14 @@ fn edit(file: &OsStr) -> Result<(), Box> { } else { let _ = write!(stdout(), "\x1b[G"); let _ = state.buf.slice(line_start, gap_start).print(&mut stdout()); - let (_, col) = cursor_pos()?; - state.cursor_col = col; + match term::cursor_pos(&mut state.stdin) { + Ok((_, col)) => { + state.cursor_col = col; + } + Err(_) => { + state.cursor_col -= 1; + } + } } state.buf = state .buf @@ -343,26 +339,31 @@ fn edit(file: &OsStr) -> Result<(), Box> { let _ = stdout().flush(); } Key::Char(b'\r') => { - state.set_mode(Mode::Insert); + state.set_mode(Mode::Paste); state.buf = state.buf.insert( - state.buf.line_idx(state.row_start + state.cursor_row) + state.line_offset, + state + .buf + .line_idx(state.row_start + usize::from(state.cursor_row)) + + state.line_offset, b'\n', ); - if state.cursor_row == usize::from(size.ws_row - 1) { + if state.cursor_row == size.ws_row - 1 { state.row_start += 1; } else { state.cursor_row += 1; } state.cursor_col = 0; - let _ = state.repaint_all(); - let _ = state.repaint_line_full(); + state.line_offset = 0; + let _ = write!(stdout(), "\r\n"); let _ = stdout().flush(); } Key::Char(c) => { - state.set_mode(Mode::Insert); - state.cursor_col = std::cmp::min(state.cursor_col, state.line_cols); + state.set_mode(Mode::Paste); state.buf = state.buf.insert( - state.buf.line_idx(state.row_start + state.cursor_row) + state.line_offset, + state + .buf + .line_idx(state.row_start + usize::from(state.cursor_row)) + + state.line_offset, c, ); state.line_offset += 1; -- cgit v1.3.1