work started on refactoring LineBuf

This commit is contained in:
2026-03-18 15:34:12 -04:00
parent 782a3820da
commit 7c8a418f96
8 changed files with 662 additions and 3396 deletions

7
Cargo.lock generated
View File

@@ -577,6 +577,7 @@ dependencies = [
"regex", "regex",
"scopeguard", "scopeguard",
"serde_json", "serde_json",
"smallvec",
"tempfile", "tempfile",
"unicode-segmentation", "unicode-segmentation",
"unicode-width", "unicode-width",
@@ -584,6 +585,12 @@ dependencies = [
"yansi", "yansi",
] ]
[[package]]
name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]] [[package]]
name = "strsim" name = "strsim"
version = "0.11.1" version = "0.11.1"

View File

@@ -35,6 +35,7 @@ rand = "0.10.0"
regex = "1.11.1" regex = "1.11.1"
scopeguard = "1.2.0" scopeguard = "1.2.0"
serde_json = "1.0.149" serde_json = "1.0.149"
smallvec = { version = "1.15.1", features = ["write"] }
tempfile = "3.24.0" tempfile = "3.24.0"
unicode-segmentation = "1.12.0" unicode-segmentation = "1.12.0"
unicode-width = "0.2.0" unicode-width = "0.2.0"

View File

@@ -160,6 +160,12 @@ fn main() -> ExitCode {
on_exit_autocmds.exec(); on_exit_autocmds.exec();
write_jobs(|j| j.hang_up()); write_jobs(|j| j.hang_up());
let code = QUIT_CODE.load(Ordering::SeqCst) as u8;
if code == 0 && isatty(STDIN_FILENO).unwrap_or_default() {
write(borrow_fd(STDERR_FILENO), b"\nexit\n").ok();
}
ExitCode::from(QUIT_CODE.load(Ordering::SeqCst) as u8) ExitCode::from(QUIT_CODE.load(Ordering::SeqCst) as u8)
} }

View File

@@ -19,7 +19,7 @@ use crate::{
readline::{ readline::{
Marker, annotate_input_recursive, Marker, annotate_input_recursive,
keys::{KeyCode as C, KeyEvent as K, ModKeys as M}, keys::{KeyCode as C, KeyEvent as K, ModKeys as M},
linebuf::{ClampedUsize, LineBuf}, linebuf::LineBuf,
markers::{self, is_marker}, markers::{self, is_marker},
term::{LineWriter, TermWriter, calc_str_width, get_win_size}, term::{LineWriter, TermWriter, calc_str_width, get_win_size},
vimode::{ViInsert, ViMode}, vimode::{ViInsert, ViMode},
@@ -818,7 +818,6 @@ impl QueryEditor {
self.available_width = width; self.available_width = width;
} }
pub fn update_scroll_offset(&mut self) { pub fn update_scroll_offset(&mut self) {
self.linebuf.update_graphemes();
let cursor_pos = self.linebuf.cursor.get(); let cursor_pos = self.linebuf.cursor.get();
if cursor_pos < self.scroll_offset + 1 { if cursor_pos < self.scroll_offset + 1 {
self.scroll_offset = self.linebuf.cursor.ret_sub(1); self.scroll_offset = self.linebuf.cursor.ret_sub(1);
@@ -829,10 +828,8 @@ impl QueryEditor {
.cursor .cursor
.ret_sub(self.available_width.saturating_sub(1)); .ret_sub(self.available_width.saturating_sub(1));
} }
let max_offset = self let max_offset = self.linebuf
.linebuf .count_graphemes()
.grapheme_indices()
.len()
.saturating_sub(self.available_width); .saturating_sub(self.available_width);
self.scroll_offset = self.scroll_offset.min(max_offset); self.scroll_offset = self.scroll_offset.min(max_offset);
} }

File diff suppressed because it is too large Load Diff

View File

@@ -9,6 +9,7 @@ use vimode::{CmdReplay, ModeReport, ViInsert, ViMode, ViNormal, ViReplace, ViVis
use crate::builtin::keymap::{KeyMapFlags, KeyMapMatch}; use crate::builtin::keymap::{KeyMapFlags, KeyMapMatch};
use crate::expand::expand_prompt; use crate::expand::expand_prompt;
use crate::libsh::error::{ShErr, ShErrKind};
use crate::libsh::utils::AutoCmdVecUtils; use crate::libsh::utils::AutoCmdVecUtils;
use crate::parse::lex::{LexStream, QuoteState}; use crate::parse::lex::{LexStream, QuoteState};
use crate::readline::complete::{FuzzyCompleter, SelectorResponse}; use crate::readline::complete::{FuzzyCompleter, SelectorResponse};
@@ -882,6 +883,8 @@ impl ShedVi {
self.needs_redraw = true; self.needs_redraw = true;
return Ok(None); return Ok(None);
} }
} else if cmd.verb().is_some_and(|v| v.1 == Verb::Quit) {
return Ok(Some(ReadlineEvent::Eof));
} }
let has_edit_verb = cmd.verb().is_some_and(|v| v.1.is_edit()); let has_edit_verb = cmd.verb().is_some_and(|v| v.1.is_edit());

View File

@@ -2,7 +2,7 @@ use std::path::PathBuf;
use bitflags::bitflags; use bitflags::bitflags;
use crate::readline::vimode::ex::SubFlags; use crate::readline::{linebuf::Grapheme, vimode::ex::SubFlags};
use super::register::{RegisterContent, append_register, read_register, write_register}; use super::register::{RegisterContent, append_register, read_register, write_register};
@@ -161,23 +161,11 @@ impl ViCmd {
self.motion.as_ref().is_some_and(|m| { self.motion.as_ref().is_some_and(|m| {
matches!( matches!(
m.1, m.1,
Motion::LineUp | Motion::LineDown | Motion::LineUpCharwise | Motion::LineDownCharwise Motion::LineUp | Motion::LineDown
) )
}) })
} }
/// If a ViCmd has a linewise motion, but no verb, we change it to charwise /// If a ViCmd has a linewise motion, but no verb, we change it to charwise
pub fn alter_line_motion_if_no_verb(&mut self) {
if self.is_line_motion()
&& self.verb.is_none()
&& let Some(motion) = self.motion.as_mut()
{
match motion.1 {
Motion::LineUp => motion.1 = Motion::LineUpCharwise,
Motion::LineDown => motion.1 = Motion::LineDownCharwise,
_ => unreachable!(),
}
}
}
pub fn is_mode_transition(&self) -> bool { pub fn is_mode_transition(&self) -> bool {
self.verb.as_ref().is_some_and(|v| { self.verb.as_ref().is_some_and(|v| {
matches!( matches!(
@@ -261,6 +249,7 @@ pub enum Verb {
Read(ReadSrc), Read(ReadSrc),
Write(WriteDest), Write(WriteDest),
Edit(PathBuf), Edit(PathBuf),
Quit,
Substitute(String, String, SubFlags), Substitute(String, String, SubFlags),
RepeatSubstitute, RepeatSubstitute,
RepeatGlobal, RepeatGlobal,
@@ -326,23 +315,20 @@ impl Verb {
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]
pub enum Motion { pub enum Motion {
WholeLineInclusive, // whole line including the linebreak WholeLine,
WholeLineExclusive, // whole line excluding the linebreak
TextObj(TextObj), TextObj(TextObj),
EndOfLastWord, EndOfLastWord,
BeginningOfFirstWord, BeginningOfFirstWord,
BeginningOfLine, BeginningOfLine,
EndOfLine, EndOfLine,
WordMotion(To, Word, Direction), WordMotion(To, Word, Direction),
CharSearch(Direction, Dest, char), CharSearch(Direction, Dest, Grapheme),
BackwardChar, BackwardChar,
ForwardChar, ForwardChar,
BackwardCharForced, // These two variants can cross line boundaries BackwardCharForced, // These two variants can cross line boundaries
ForwardCharForced, ForwardCharForced,
LineUp, LineUp,
LineUpCharwise,
LineDown, LineDown,
LineDownCharwise,
WholeBuffer, WholeBuffer,
StartOfBuffer, StartOfBuffer,
EndOfBuffer, EndOfBuffer,
@@ -382,8 +368,6 @@ impl Motion {
&self, &self,
Self::BeginningOfLine Self::BeginningOfLine
| Self::BeginningOfFirstWord | Self::BeginningOfFirstWord
| Self::LineDownCharwise
| Self::LineUpCharwise
| Self::ToColumn | Self::ToColumn
| Self::TextObj(TextObj::Sentence(_)) | Self::TextObj(TextObj::Sentence(_))
| Self::TextObj(TextObj::Paragraph(_)) | Self::TextObj(TextObj::Paragraph(_))
@@ -398,7 +382,7 @@ impl Motion {
pub fn is_linewise(&self) -> bool { pub fn is_linewise(&self) -> bool {
matches!( matches!(
self, self,
Self::WholeLineInclusive | Self::WholeLineExclusive | Self::LineUp | Self::LineDown Self::WholeLine | Self::LineUp | Self::LineDown
) )
} }
} }

View File

@@ -280,6 +280,7 @@ fn parse_ex_command(chars: &mut Peekable<Chars<'_>>) -> Result<Option<Verb>, Opt
_ if "delete".starts_with(&cmd_name) => Ok(Some(Verb::Delete)), _ if "delete".starts_with(&cmd_name) => Ok(Some(Verb::Delete)),
_ if "yank".starts_with(&cmd_name) => Ok(Some(Verb::Yank)), _ if "yank".starts_with(&cmd_name) => Ok(Some(Verb::Yank)),
_ if "put".starts_with(&cmd_name) => Ok(Some(Verb::Put(Anchor::After))), _ if "put".starts_with(&cmd_name) => Ok(Some(Verb::Put(Anchor::After))),
_ if "quit".starts_with(&cmd_name) => Ok(Some(Verb::Quit)),
_ if "read".starts_with(&cmd_name) => parse_read(chars), _ if "read".starts_with(&cmd_name) => parse_read(chars),
_ if "write".starts_with(&cmd_name) => parse_write(chars), _ if "write".starts_with(&cmd_name) => parse_write(chars),
_ if "edit".starts_with(&cmd_name) => parse_edit(chars), _ if "edit".starts_with(&cmd_name) => parse_edit(chars),