about to refactor the line buffer
This commit is contained in:
@@ -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;
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
|
||||||
@@ -19,7 +19,13 @@ pub enum MotionKind {
|
|||||||
To(usize),
|
To(usize),
|
||||||
Backward(usize),
|
Backward(usize),
|
||||||
Range(Range<usize>),
|
Range(Range<usize>),
|
||||||
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 {
|
impl MotionKind {
|
||||||
@@ -244,6 +250,7 @@ pub struct LineBuf {
|
|||||||
buffer: TermCharBuf,
|
buffer: TermCharBuf,
|
||||||
cursor: usize,
|
cursor: usize,
|
||||||
clamp_cursor: bool,
|
clamp_cursor: bool,
|
||||||
|
first_line_offset: usize,
|
||||||
merge_edit: bool,
|
merge_edit: bool,
|
||||||
undo_stack: Vec<Edit>,
|
undo_stack: Vec<Edit>,
|
||||||
redo_stack: Vec<Edit>,
|
redo_stack: Vec<Edit>,
|
||||||
@@ -261,6 +268,9 @@ impl LineBuf {
|
|||||||
}
|
}
|
||||||
self
|
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) {
|
pub fn set_cursor_clamp(&mut self, yn: bool) {
|
||||||
self.clamp_cursor = yn
|
self.clamp_cursor = yn
|
||||||
}
|
}
|
||||||
@@ -790,10 +800,30 @@ impl LineBuf {
|
|||||||
pub fn validate_range(&self, range: &Range<usize>) -> bool {
|
pub fn validate_range(&self, range: &Range<usize>) -> bool {
|
||||||
range.end < self.buffer.len()
|
range.end < self.buffer.len()
|
||||||
}
|
}
|
||||||
pub fn cur_line_range(&self) -> RangeInclusive<usize> {
|
pub fn lines_from_cursor(&self, offset: isize) -> RangeInclusive<usize> {
|
||||||
let cursor = self.cursor();
|
let mut start;
|
||||||
let mut line_start = self.backward_until(cursor, |c| c == &TermChar::Newline, false);
|
let mut end;
|
||||||
let mut line_end = self.forward_until(cursor, |c| c == &TermChar::Newline, true);
|
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<usize> {
|
||||||
|
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) {
|
if self.get_char(line_start.saturating_sub(1)).is_none_or(|c| c != &TermChar::Newline) {
|
||||||
line_start = 0;
|
line_start = 0;
|
||||||
}
|
}
|
||||||
@@ -804,6 +834,25 @@ impl LineBuf {
|
|||||||
|
|
||||||
line_start..=self.num_or_len(line_end + 1)
|
line_start..=self.num_or_len(line_end + 1)
|
||||||
}
|
}
|
||||||
|
pub fn cur_line_range(&self) -> RangeInclusive<usize> {
|
||||||
|
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
|
/// Clamp a number to the length of the buffer
|
||||||
pub fn num_or_len_minus_one(&self, num: usize) -> usize {
|
pub fn num_or_len_minus_one(&self, num: usize) -> usize {
|
||||||
num.min(self.buffer.len().saturating_sub(1))
|
num.min(self.buffer.len().saturating_sub(1))
|
||||||
@@ -902,7 +951,18 @@ impl LineBuf {
|
|||||||
}
|
}
|
||||||
Motion::BackwardChar => MotionKind::Backward(1),
|
Motion::BackwardChar => MotionKind::Backward(1),
|
||||||
Motion::ForwardChar => MotionKind::Forward(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::LineDown => todo!(),
|
||||||
Motion::WholeBuffer => MotionKind::Range(0..self.buffer.len().saturating_sub(1)),
|
Motion::WholeBuffer => MotionKind::Range(0..self.buffer.len().saturating_sub(1)),
|
||||||
Motion::BeginningOfBuffer => MotionKind::To(0),
|
Motion::BeginningOfBuffer => MotionKind::To(0),
|
||||||
@@ -915,6 +975,9 @@ impl LineBuf {
|
|||||||
match verb {
|
match verb {
|
||||||
Verb::Change |
|
Verb::Change |
|
||||||
Verb::Delete => {
|
Verb::Delete => {
|
||||||
|
if self.buffer.is_empty() {
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
let deleted;
|
let deleted;
|
||||||
match motion {
|
match motion {
|
||||||
MotionKind::Forward(n) => {
|
MotionKind::Forward(n) => {
|
||||||
@@ -942,9 +1005,12 @@ impl LineBuf {
|
|||||||
register.write_to_register(deleted);
|
register.write_to_register(deleted);
|
||||||
}
|
}
|
||||||
Verb::DeleteChar(anchor) => {
|
Verb::DeleteChar(anchor) => {
|
||||||
|
if self.buffer.is_empty() {
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
match anchor {
|
match anchor {
|
||||||
Anchor::After => {
|
Anchor::After => {
|
||||||
let pos = self.num_or_len(self.cursor() + 1);
|
let pos = self.cursor();
|
||||||
self.buffer.remove(pos);
|
self.buffer.remove(pos);
|
||||||
}
|
}
|
||||||
Anchor::Before => {
|
Anchor::Before => {
|
||||||
@@ -989,6 +1055,7 @@ impl LineBuf {
|
|||||||
.collect::<TermCharBuf>();
|
.collect::<TermCharBuf>();
|
||||||
self.apply_motion(MotionKind::To(r.start));
|
self.apply_motion(MotionKind::To(r.start));
|
||||||
}
|
}
|
||||||
|
MotionKind::ToScreenPos(pos) => todo!(),
|
||||||
MotionKind::Null => return Ok(())
|
MotionKind::Null => return Ok(())
|
||||||
}
|
}
|
||||||
register.write_to_register(yanked);
|
register.write_to_register(yanked);
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ impl FernVi {
|
|||||||
pub fn new(prompt: Option<String>) -> Self {
|
pub fn new(prompt: Option<String>) -> Self {
|
||||||
let prompt = prompt.unwrap_or("$ ".styled(Style::Green | Style::Bold));
|
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");
|
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 {
|
Self {
|
||||||
term: Terminal::new(),
|
term: Terminal::new(),
|
||||||
line,
|
line,
|
||||||
@@ -39,6 +38,12 @@ impl FernVi {
|
|||||||
last_movement: None,
|
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) {
|
pub fn clear_line(&self) {
|
||||||
let prompt_lines = self.prompt.lines().count();
|
let prompt_lines = self.prompt.lines().count();
|
||||||
let last_line_len = strip_ansi_codes_and_escapes(self.prompt.lines().last().unwrap_or_default()).width();
|
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());
|
self.term.write(&self.mode.cursor_style());
|
||||||
}
|
}
|
||||||
pub fn readline(&mut self) -> ShResult<String> {
|
pub fn readline(&mut self) -> ShResult<String> {
|
||||||
|
self.line.set_first_line_offset(self.calculate_prompt_offset());
|
||||||
let dims = self.term.get_dimensions()?;
|
let dims = self.term.get_dimensions()?;
|
||||||
self.line.update_term_dims(dims.0, dims.1);
|
self.line.update_term_dims(dims.0, dims.1);
|
||||||
self.print_buf(false);
|
self.print_buf(false);
|
||||||
|
|||||||
Reference in New Issue
Block a user