From 45b7a16cae2a9a6217f661bd310dfdde08a6e499 Mon Sep 17 00:00:00 2001 From: Kyler Clay Date: Fri, 23 May 2025 10:27:11 -0400 Subject: [PATCH] about to refactor the line buffer --- src/prompt/readline/linebuf.rs | 83 ++++++++++++++++++++++++++++++---- src/prompt/readline/mod.rs | 8 +++- 2 files changed, 82 insertions(+), 9 deletions(-) diff --git a/src/prompt/readline/linebuf.rs b/src/prompt/readline/linebuf.rs index 2bdf644..0fae256 100644 --- a/src/prompt/readline/linebuf.rs +++ b/src/prompt/readline/linebuf.rs @@ -1,4 +1,4 @@ -use std::{fmt::Display, ops::{Deref, DerefMut, Range, RangeBounds, RangeInclusive}, sync::Arc}; +use std::{cmp::Ordering, fmt::Display, ops::{Deref, DerefMut, Range, RangeBounds, RangeInclusive}, sync::Arc}; use unicode_width::UnicodeWidthStr; @@ -19,7 +19,13 @@ pub enum MotionKind { To(usize), Backward(usize), Range(Range), - Null + Line(isize), // positive = up line, negative = down line + ToLine(usize), + Null, + + /// Absolute position based on display width of characters + /// Factors in the length of the prompt, and skips newlines + ToScreenPos(usize), } impl MotionKind { @@ -244,6 +250,7 @@ pub struct LineBuf { buffer: TermCharBuf, cursor: usize, clamp_cursor: bool, + first_line_offset: usize, merge_edit: bool, undo_stack: Vec, redo_stack: Vec, @@ -261,6 +268,9 @@ impl LineBuf { } self } + pub fn set_first_line_offset(&mut self, offset: usize) { + self.first_line_offset = offset + } pub fn set_cursor_clamp(&mut self, yn: bool) { self.clamp_cursor = yn } @@ -790,10 +800,30 @@ impl LineBuf { pub fn validate_range(&self, range: &Range) -> bool { range.end < self.buffer.len() } - pub fn cur_line_range(&self) -> RangeInclusive { - let cursor = self.cursor(); - let mut line_start = self.backward_until(cursor, |c| c == &TermChar::Newline, false); - let mut line_end = self.forward_until(cursor, |c| c == &TermChar::Newline, true); + pub fn lines_from_cursor(&self, offset: isize) -> RangeInclusive { + let mut start; + let mut end; + match offset.cmp(0) { + Ordering::Equal => { + return self.cur_line_range() + } + Ordering::Greater => { + let this_line = self.cur_line_range(); + start = *this_line.start(); + end = *this_line.end(); + for _ in 0..offset { + let next_ln = self.line_range_from_pos(self.num_or_len(end + 1)); + end = *this_line.end(); + } + } + Ordering::Less => { + } + } + start..=end + } + pub fn line_range_from_pos(&self, pos: usize) -> RangeInclusive { + let mut line_start = self.backward_until(pos, |c| c == &TermChar::Newline, false); + let mut line_end = self.forward_until(pos, |c| c == &TermChar::Newline, true); if self.get_char(line_start.saturating_sub(1)).is_none_or(|c| c != &TermChar::Newline) { line_start = 0; } @@ -804,6 +834,25 @@ impl LineBuf { line_start..=self.num_or_len(line_end + 1) } + pub fn cur_line_range(&self) -> RangeInclusive { + let cursor = self.cursor(); + self.line_range_from_pos(cursor) + } + pub fn on_first_line(&self) -> bool { + let cursor = self.cursor(); + let ln_start = self.backward_until(cursor, |c| c.matches("\n"), true); + !self.get_char(ln_start).is_some_and(|c| c.matches("\n")) + } + pub fn on_last_line(&self) -> bool { + let cursor = self.cursor(); + let ln_end = self.forward_until(cursor, |c| c.matches("\n"), true); + !self.get_char(ln_end).is_some_and(|c| c.matches("\n")) + } + pub fn cur_line_col(&self) -> usize { + let cursor = self.cursor(); + let ln_span = self.cur_line_range(); + cursor.saturating_sub(*ln_span.start()) + } /// Clamp a number to the length of the buffer pub fn num_or_len_minus_one(&self, num: usize) -> usize { num.min(self.buffer.len().saturating_sub(1)) @@ -902,7 +951,18 @@ impl LineBuf { } Motion::BackwardChar => MotionKind::Backward(1), Motion::ForwardChar => MotionKind::Forward(1), - Motion::LineUp => todo!(), + Motion::LineUp => { + if self.on_first_line() { + return MotionKind::Null // TODO: implement history scrolling here + } + let col = self.cur_line_col(); + let cursor = self.cursor(); + let mut ln_start = self.backward_until(cursor, |c| c.matches("\n"), true); + let ln_end = ln_start.saturating_sub(1); + ln_start = self.backward_until(ln_end, |c| c.matches("\n"), true); + let new_pos = (ln_start + col).min(ln_end); + MotionKind::To(new_pos) + } Motion::LineDown => todo!(), Motion::WholeBuffer => MotionKind::Range(0..self.buffer.len().saturating_sub(1)), Motion::BeginningOfBuffer => MotionKind::To(0), @@ -915,6 +975,9 @@ impl LineBuf { match verb { Verb::Change | Verb::Delete => { + if self.buffer.is_empty() { + return Ok(()) + } let deleted; match motion { MotionKind::Forward(n) => { @@ -942,9 +1005,12 @@ impl LineBuf { register.write_to_register(deleted); } Verb::DeleteChar(anchor) => { + if self.buffer.is_empty() { + return Ok(()) + } match anchor { Anchor::After => { - let pos = self.num_or_len(self.cursor() + 1); + let pos = self.cursor(); self.buffer.remove(pos); } Anchor::Before => { @@ -989,6 +1055,7 @@ impl LineBuf { .collect::(); self.apply_motion(MotionKind::To(r.start)); } + MotionKind::ToScreenPos(pos) => todo!(), MotionKind::Null => return Ok(()) } register.write_to_register(yanked); diff --git a/src/prompt/readline/mod.rs b/src/prompt/readline/mod.rs index 35a11b1..320ecfa 100644 --- a/src/prompt/readline/mod.rs +++ b/src/prompt/readline/mod.rs @@ -29,7 +29,6 @@ impl FernVi { pub fn new(prompt: Option) -> Self { let prompt = prompt.unwrap_or("$ ".styled(Style::Green | Style::Bold)); let line = LineBuf::new().with_initial("The quick brown fox jumps over the lazy dog");//\nThe quick brown fox jumps over the lazy dog\nThe quick brown fox jumps over the lazy dog\n"); - Self { term: Terminal::new(), line, @@ -39,6 +38,12 @@ impl FernVi { last_movement: None, } } + pub fn calculate_prompt_offset(&self) -> usize { + if self.prompt.ends_with('\n') { + return 0 + } + strip_ansi_codes_and_escapes(self.prompt.lines().last().unwrap_or_default()).width() + } pub fn clear_line(&self) { let prompt_lines = self.prompt.lines().count(); let last_line_len = strip_ansi_codes_and_escapes(self.prompt.lines().last().unwrap_or_default()).width(); @@ -98,6 +103,7 @@ impl FernVi { self.term.write(&self.mode.cursor_style()); } pub fn readline(&mut self) -> ShResult { + self.line.set_first_line_offset(self.calculate_prompt_offset()); let dims = self.term.get_dimensions()?; self.line.update_term_dims(dims.0, dims.1); self.print_buf(false);