progress on linebuf refactor
This commit is contained in:
@@ -1223,16 +1223,13 @@ pub fn unescape_str(raw: &str) -> String {
|
||||
result.push(markers::SNG_QUOTE);
|
||||
while let Some(q_ch) = chars.next() {
|
||||
match q_ch {
|
||||
'\\' => {
|
||||
match chars.peek() {
|
||||
Some(&'\\') |
|
||||
Some(&'\'') => {
|
||||
'\\' => match chars.peek() {
|
||||
Some(&'\\') | Some(&'\'') => {
|
||||
let ch = chars.next().unwrap();
|
||||
result.push(ch);
|
||||
}
|
||||
_ => result.push(q_ch),
|
||||
}
|
||||
}
|
||||
},
|
||||
'\'' => {
|
||||
result.push(markers::SNG_QUOTE);
|
||||
break;
|
||||
|
||||
@@ -29,6 +29,50 @@ use crate::{
|
||||
},
|
||||
};
|
||||
|
||||
/// Compat shim: replaces the old ClampedUsize type that was removed in the linebuf refactor.
|
||||
/// A simple wrapper around usize with wrapping arithmetic and a max bound.
|
||||
#[derive(Clone, Default, Debug)]
|
||||
pub struct ClampedUsize {
|
||||
val: usize,
|
||||
max: usize,
|
||||
wrap: bool,
|
||||
}
|
||||
|
||||
impl ClampedUsize {
|
||||
pub fn new(val: usize, max: usize, wrap: bool) -> Self {
|
||||
Self { val, max, wrap }
|
||||
}
|
||||
pub fn get(&self) -> usize {
|
||||
self.val
|
||||
}
|
||||
pub fn set_max(&mut self, max: usize) {
|
||||
self.max = max;
|
||||
if self.val >= self.max && self.max > 0 {
|
||||
self.val = self.max - 1;
|
||||
}
|
||||
}
|
||||
pub fn wrap_add(&mut self, n: usize) {
|
||||
if self.max == 0 {
|
||||
return;
|
||||
}
|
||||
if self.wrap {
|
||||
self.val = (self.val + n) % self.max;
|
||||
} else {
|
||||
self.val = (self.val + n).min(self.max.saturating_sub(1));
|
||||
}
|
||||
}
|
||||
pub fn wrap_sub(&mut self, n: usize) {
|
||||
if self.max == 0 {
|
||||
return;
|
||||
}
|
||||
if self.wrap {
|
||||
self.val = (self.val + self.max - (n % self.max)) % self.max;
|
||||
} else {
|
||||
self.val = self.val.saturating_sub(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Candidate(pub String);
|
||||
|
||||
@@ -325,8 +369,6 @@ fn complete_filename(start: &str) -> Vec<Candidate> {
|
||||
let file_name = entry.file_name();
|
||||
let file_str: Candidate = file_name.to_string_lossy().to_string().into();
|
||||
|
||||
|
||||
|
||||
// Skip hidden files unless explicitly requested
|
||||
if !prefix.starts_with('.') && file_str.0.starts_with('.') {
|
||||
continue;
|
||||
@@ -569,7 +611,12 @@ impl CompSpec for BashCompSpec {
|
||||
candidates.extend(complete_signals(&expanded));
|
||||
}
|
||||
if let Some(words) = &self.wordlist {
|
||||
candidates.extend(words.iter().map(Candidate::from).filter(|w| w.is_match(&expanded)));
|
||||
candidates.extend(
|
||||
words
|
||||
.iter()
|
||||
.map(Candidate::from)
|
||||
.filter(|w| w.is_match(&expanded)),
|
||||
);
|
||||
}
|
||||
if self.function.is_some() {
|
||||
candidates.extend(self.exec_comp_func(ctx)?);
|
||||
@@ -645,7 +692,7 @@ impl CompResult {
|
||||
Self::NoMatch
|
||||
} else if candidates.len() == 1 {
|
||||
Self::Single {
|
||||
result: candidates.remove(0)
|
||||
result: candidates.remove(0),
|
||||
}
|
||||
} else {
|
||||
Self::Many { candidates }
|
||||
@@ -828,22 +875,22 @@ impl QueryEditor {
|
||||
.cursor
|
||||
.ret_sub(self.available_width.saturating_sub(1));
|
||||
}
|
||||
let max_offset = self.linebuf
|
||||
let max_offset = self
|
||||
.linebuf
|
||||
.count_graphemes()
|
||||
.saturating_sub(self.available_width);
|
||||
self.scroll_offset = self.scroll_offset.min(max_offset);
|
||||
}
|
||||
pub fn get_window(&mut self) -> String {
|
||||
self.linebuf.update_graphemes();
|
||||
let buf_len = self.linebuf.grapheme_indices().len();
|
||||
let buf_len = self.linebuf.count_graphemes();
|
||||
if buf_len <= self.available_width {
|
||||
return self.linebuf.as_str().to_string();
|
||||
return self.linebuf.joined();
|
||||
}
|
||||
let start = self
|
||||
.scroll_offset
|
||||
.min(buf_len.saturating_sub(self.available_width));
|
||||
let end = (start + self.available_width).min(buf_len);
|
||||
self.linebuf.slice(start..end).unwrap_or("").to_string()
|
||||
self.linebuf.slice(start..end).unwrap_or_default()
|
||||
}
|
||||
pub fn handle_key(&mut self, key: K) -> ShResult<()> {
|
||||
let Some(cmd) = self.mode.handle_key(key) else {
|
||||
@@ -1028,7 +1075,7 @@ impl FuzzySelector {
|
||||
.into_iter()
|
||||
.filter_map(|c| {
|
||||
let mut sc = ScoredCandidate::new(c.to_string());
|
||||
let score = sc.fuzzy_score(self.query.linebuf.as_str());
|
||||
let score = sc.fuzzy_score(&self.query.linebuf.joined());
|
||||
if score > i32::MIN { Some(sc) } else { None }
|
||||
})
|
||||
.collect();
|
||||
@@ -1319,12 +1366,18 @@ impl Completer for FuzzyCompleter {
|
||||
basename,
|
||||
)
|
||||
} else {
|
||||
(self.completer.original_input[..start].to_string(), selected.clone())
|
||||
(
|
||||
self.completer.original_input[..start].to_string(),
|
||||
selected.clone(),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
start += slice.width();
|
||||
let completion = selected.strip_prefix(slice).unwrap_or(&selected);
|
||||
(self.completer.original_input[..start].to_string(), completion.to_string())
|
||||
(
|
||||
self.completer.original_input[..start].to_string(),
|
||||
completion.to_string(),
|
||||
)
|
||||
};
|
||||
let escaped = escape_str(&completion, false);
|
||||
let ret = format!(
|
||||
@@ -1435,7 +1488,10 @@ impl Completer for SimpleCompleter {
|
||||
}
|
||||
|
||||
fn selected_candidate(&self) -> Option<String> {
|
||||
self.candidates.get(self.selected_idx).map(|c| c.to_string())
|
||||
self
|
||||
.candidates
|
||||
.get(self.selected_idx)
|
||||
.map(|c| c.to_string())
|
||||
}
|
||||
|
||||
fn token_span(&self) -> (usize, usize) {
|
||||
@@ -1591,16 +1647,20 @@ impl SimpleCompleter {
|
||||
let prefix_end = start + last_sep + 1;
|
||||
let trailing_slash = selected.ends_with('/');
|
||||
let trimmed = selected.trim_end_matches('/');
|
||||
let mut basename = trimmed.rsplit('/').next().unwrap_or(selected.as_str()).to_string();
|
||||
let mut basename = trimmed
|
||||
.rsplit('/')
|
||||
.next()
|
||||
.unwrap_or(selected.as_str())
|
||||
.to_string();
|
||||
if trailing_slash {
|
||||
basename.push('/');
|
||||
}
|
||||
(
|
||||
self.original_input[..prefix_end].to_string(),
|
||||
basename,
|
||||
)
|
||||
(self.original_input[..prefix_end].to_string(), basename)
|
||||
} else {
|
||||
(self.original_input[..start].to_string(), selected.to_string())
|
||||
(
|
||||
self.original_input[..start].to_string(),
|
||||
selected.to_string(),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
start += slice.width();
|
||||
@@ -1608,12 +1668,7 @@ impl SimpleCompleter {
|
||||
(self.original_input[..start].to_string(), completion)
|
||||
};
|
||||
let escaped = escape_str(&completion, false);
|
||||
format!(
|
||||
"{}{}{}",
|
||||
prefix,
|
||||
escaped,
|
||||
&self.original_input[end..]
|
||||
)
|
||||
format!("{}{}{}", prefix, escaped, &self.original_input[end..])
|
||||
}
|
||||
|
||||
pub fn build_comp_ctx(&self, tks: &[Tk], line: &str, cursor_pos: usize) -> ShResult<CompContext> {
|
||||
@@ -2180,7 +2235,7 @@ mod tests {
|
||||
vi.feed_bytes(b"echo hello\t");
|
||||
let _ = vi.process_input();
|
||||
|
||||
let line = vi.editor.as_str().to_string();
|
||||
let line = vi.editor.joined();
|
||||
assert!(
|
||||
line.contains("hello\\ world.txt"),
|
||||
"expected escaped space in completion: {line:?}"
|
||||
@@ -2202,7 +2257,7 @@ mod tests {
|
||||
vi.feed_bytes(b"echo my\\ \t");
|
||||
let _ = vi.process_input();
|
||||
|
||||
let line = vi.editor.as_str().to_string();
|
||||
let line = vi.editor.joined();
|
||||
// The user's "my\ " should be preserved, not double-escaped to "my\\\ "
|
||||
assert!(
|
||||
!line.contains("my\\\\ "),
|
||||
@@ -2231,7 +2286,7 @@ mod tests {
|
||||
vi.feed_bytes(b"echo unique_shed_test\t");
|
||||
let _ = vi.process_input();
|
||||
|
||||
let line = vi.editor.as_str().to_string();
|
||||
let line = vi.editor.joined();
|
||||
assert!(
|
||||
line.contains("unique_shed_test_file.txt"),
|
||||
"expected completion in line: {line:?}"
|
||||
@@ -2251,7 +2306,7 @@ mod tests {
|
||||
vi.feed_bytes(b"cd mysub\t");
|
||||
let _ = vi.process_input();
|
||||
|
||||
let line = vi.editor.as_str().to_string();
|
||||
let line = vi.editor.joined();
|
||||
assert!(
|
||||
line.contains("mysubdir/"),
|
||||
"expected dir completion with trailing slash: {line:?}"
|
||||
@@ -2272,7 +2327,7 @@ mod tests {
|
||||
vi.feed_bytes(b"cmd --opt=eqf\t");
|
||||
let _ = vi.process_input();
|
||||
|
||||
let line = vi.editor.as_str().to_string();
|
||||
let line = vi.editor.joined();
|
||||
assert!(
|
||||
line.contains("--opt=eqfile.txt"),
|
||||
"expected completion after '=': {line:?}"
|
||||
|
||||
@@ -414,7 +414,12 @@ impl History {
|
||||
}
|
||||
|
||||
pub fn get_hint(&self) -> Option<String> {
|
||||
if self.at_pending() && self.pending.as_ref().is_some_and(|p| !p.buffer.is_empty()) {
|
||||
if self.at_pending()
|
||||
&& self
|
||||
.pending
|
||||
.as_ref()
|
||||
.is_some_and(|p| !p.joined().is_empty())
|
||||
{
|
||||
let entry = self.hint_entry()?;
|
||||
Some(entry.command().to_string())
|
||||
} else {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -341,9 +341,11 @@ impl ShedVi {
|
||||
|
||||
pub fn with_initial(mut self, initial: &str) -> Self {
|
||||
self.editor = LineBuf::new().with_initial(initial, 0);
|
||||
self
|
||||
.history
|
||||
.update_pending_cmd((self.editor.as_str(), self.editor.cursor.get()));
|
||||
{
|
||||
let s = self.editor.joined();
|
||||
let c = self.editor.cursor.get();
|
||||
self.history.update_pending_cmd((&s, c));
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
@@ -436,7 +438,7 @@ impl ShedVi {
|
||||
if self.mode.report_mode() == ModeReport::Normal {
|
||||
return Ok(true);
|
||||
}
|
||||
let input = Arc::new(self.editor.buffer.clone());
|
||||
let input = Arc::new(self.editor.joined());
|
||||
self.editor.calc_indent_level();
|
||||
let lex_result1 =
|
||||
LexStream::new(Arc::clone(&input), LexFlags::LEX_UNFINISHED).collect::<ShResult<Vec<_>>>();
|
||||
@@ -484,7 +486,7 @@ impl ShedVi {
|
||||
|
||||
self
|
||||
.history
|
||||
.update_pending_cmd((self.editor.as_str(), self.editor.cursor.get()));
|
||||
.update_pending_cmd((&self.editor.joined(), self.editor.cursor.get()));
|
||||
self.editor.set_hint(None);
|
||||
{
|
||||
let mut writer = std::mem::take(&mut self.writer);
|
||||
@@ -554,7 +556,7 @@ impl ShedVi {
|
||||
}
|
||||
self
|
||||
.history
|
||||
.update_pending_cmd((self.editor.as_str(), self.editor.cursor.get()));
|
||||
.update_pending_cmd((&self.editor.joined(), self.editor.cursor.get()));
|
||||
let hint = self.history.get_hint();
|
||||
self.editor.set_hint(hint);
|
||||
self.completer.clear(&mut self.writer)?;
|
||||
@@ -669,14 +671,15 @@ impl ShedVi {
|
||||
}
|
||||
self
|
||||
.history
|
||||
.update_pending_cmd((self.editor.as_str(), self.editor.cursor.get()));
|
||||
.update_pending_cmd((&self.editor.joined(), self.editor.cursor.get()));
|
||||
self.needs_redraw = true;
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if let KeyEvent(KeyCode::Tab, mod_keys) = key {
|
||||
if self.mode.report_mode() != ModeReport::Ex
|
||||
&& self.editor.attempt_history_expansion(&self.history) {
|
||||
&& self.editor.attempt_history_expansion(&self.history)
|
||||
{
|
||||
// If history expansion occurred, don't attempt completion yet
|
||||
// allow the user to see the expanded command and accept or edit it before completing
|
||||
return Ok(None);
|
||||
@@ -686,7 +689,7 @@ impl ShedVi {
|
||||
ModKeys::SHIFT => -1,
|
||||
_ => 1,
|
||||
};
|
||||
let line = self.focused_editor().as_str().to_string();
|
||||
let line = self.focused_editor().joined();
|
||||
let cursor_pos = self.focused_editor().cursor_byte_pos();
|
||||
|
||||
match self.completer.complete(line, cursor_pos, direction) {
|
||||
@@ -719,7 +722,7 @@ impl ShedVi {
|
||||
}
|
||||
self
|
||||
.history
|
||||
.update_pending_cmd((self.editor.as_str(), self.editor.cursor.get()));
|
||||
.update_pending_cmd((&self.editor.joined(), self.editor.cursor.get()));
|
||||
let hint = self.history.get_hint();
|
||||
self.editor.set_hint(hint);
|
||||
write_vars(|v| {
|
||||
@@ -776,7 +779,7 @@ impl ShedVi {
|
||||
} else if let KeyEvent(KeyCode::Char('R'), ModKeys::CTRL) = key
|
||||
&& matches!(self.mode.report_mode(), ModeReport::Insert | ModeReport::Ex)
|
||||
{
|
||||
let initial = self.focused_editor().as_str().to_string();
|
||||
let initial = self.focused_editor().joined();
|
||||
match self.focused_history().start_search(&initial) {
|
||||
Some(entry) => {
|
||||
let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnHistorySelect));
|
||||
@@ -788,7 +791,7 @@ impl ShedVi {
|
||||
self.focused_editor().move_cursor_to_end();
|
||||
self
|
||||
.history
|
||||
.update_pending_cmd((self.editor.as_str(), self.editor.cursor.get()));
|
||||
.update_pending_cmd((&self.editor.joined(), self.editor.cursor.get()));
|
||||
self.editor.set_hint(None);
|
||||
}
|
||||
None => {
|
||||
@@ -847,8 +850,6 @@ impl ShedVi {
|
||||
let Some(mut cmd) = cmd else {
|
||||
return Ok(None);
|
||||
};
|
||||
cmd.alter_line_motion_if_no_verb();
|
||||
|
||||
if self.should_grab_history(&cmd) {
|
||||
self.scroll_history(cmd);
|
||||
self.needs_redraw = true;
|
||||
@@ -875,7 +876,7 @@ impl ShedVi {
|
||||
}
|
||||
|
||||
if cmd.verb().is_some_and(|v| v.1 == Verb::EndOfFile) {
|
||||
if self.focused_editor().buffer.is_empty() {
|
||||
if self.focused_editor().joined().is_empty() {
|
||||
return Ok(Some(ReadlineEvent::Eof));
|
||||
} else {
|
||||
*self.focused_editor() = LineBuf::new();
|
||||
@@ -890,17 +891,15 @@ impl ShedVi {
|
||||
let has_edit_verb = cmd.verb().is_some_and(|v| v.1.is_edit());
|
||||
let is_shell_cmd = cmd.verb().is_some_and(|v| matches!(v.1, Verb::ShellCmd(_)));
|
||||
let is_ex_cmd = cmd.flags.contains(CmdFlags::IS_EX_CMD);
|
||||
log::debug!("is_ex_cmd: {is_ex_cmd}");
|
||||
if is_shell_cmd {
|
||||
self.old_layout = None;
|
||||
}
|
||||
if is_ex_cmd {
|
||||
self.ex_history.push(cmd.raw_seq.clone());
|
||||
self.ex_history.reset();
|
||||
log::debug!("ex_history: {:?}", self.ex_history.entries());
|
||||
}
|
||||
|
||||
let before = self.editor.buffer.clone();
|
||||
let before = self.editor.joined();
|
||||
|
||||
self.exec_cmd(cmd, false)?;
|
||||
|
||||
@@ -909,12 +908,12 @@ impl ShedVi {
|
||||
self.handle_key(key)?;
|
||||
}
|
||||
}
|
||||
let after = self.editor.as_str();
|
||||
let after = self.editor.joined();
|
||||
|
||||
if before != after {
|
||||
self
|
||||
.history
|
||||
.update_pending_cmd((self.editor.as_str(), self.editor.cursor.get()));
|
||||
.update_pending_cmd((&self.editor.joined(), self.editor.cursor.get()));
|
||||
} else if before == after && has_edit_verb {
|
||||
self.writer.send_bell().ok();
|
||||
}
|
||||
@@ -929,7 +928,7 @@ impl ShedVi {
|
||||
pub fn get_layout(&mut self, line: &str) -> Layout {
|
||||
let to_cursor = self.editor.slice_to_cursor().unwrap_or_default();
|
||||
let (cols, _) = get_win_size(self.tty);
|
||||
Layout::from_parts(cols, self.prompt.get_ps1(), to_cursor, line)
|
||||
Layout::from_parts(cols, self.prompt.get_ps1(), &to_cursor, line)
|
||||
}
|
||||
pub fn scroll_history(&mut self, cmd: ViCmd) {
|
||||
/*
|
||||
@@ -941,8 +940,8 @@ impl ShedVi {
|
||||
let count = &cmd.motion().unwrap().0;
|
||||
let motion = &cmd.motion().unwrap().1;
|
||||
let count = match motion {
|
||||
Motion::LineUpCharwise => -(*count as isize),
|
||||
Motion::LineDownCharwise => *count as isize,
|
||||
Motion::LineUp => -(*count as isize),
|
||||
Motion::LineDown => *count as isize,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let entry = self.history.scroll(count);
|
||||
@@ -985,12 +984,12 @@ impl ShedVi {
|
||||
cmd.verb().is_none()
|
||||
&& (cmd
|
||||
.motion()
|
||||
.is_some_and(|m| matches!(m, MotionCmd(_, Motion::LineUpCharwise)))
|
||||
.is_some_and(|m| matches!(m, MotionCmd(_, Motion::LineUp)))
|
||||
&& self.editor.start_of_line() == 0)
|
||||
|| (cmd
|
||||
.motion()
|
||||
.is_some_and(|m| matches!(m, MotionCmd(_, Motion::LineDownCharwise)))
|
||||
&& self.editor.end_of_line() == self.editor.cursor_max())
|
||||
.is_some_and(|m| matches!(m, MotionCmd(_, Motion::LineDown)))
|
||||
&& self.editor.on_last_line())
|
||||
}
|
||||
|
||||
pub fn line_text(&mut self) -> String {
|
||||
@@ -1004,7 +1003,8 @@ impl ShedVi {
|
||||
self.highlighter.expand_control_chars();
|
||||
self.highlighter.highlight();
|
||||
let highlighted = self.highlighter.take();
|
||||
format!("{highlighted}{hint}")
|
||||
let res = format!("{highlighted}{hint}");
|
||||
res
|
||||
}
|
||||
|
||||
pub fn print_line(&mut self, final_draw: bool) -> ShResult<()> {
|
||||
@@ -1172,7 +1172,6 @@ impl ShedVi {
|
||||
}
|
||||
|
||||
fn exec_mode_transition(&mut self, cmd: ViCmd, from_replay: bool) -> ShResult<()> {
|
||||
let mut select_mode = None;
|
||||
let mut is_insert_mode = false;
|
||||
let count = cmd.verb_count();
|
||||
|
||||
@@ -1210,9 +1209,7 @@ impl ShedVi {
|
||||
|
||||
Verb::VisualModeSelectLast => {
|
||||
if self.mode.report_mode() != ModeReport::Visual {
|
||||
self
|
||||
.editor
|
||||
.start_selecting(SelectMode::Char(SelectAnchor::End));
|
||||
self.editor.start_char_select();
|
||||
}
|
||||
let mut mode: Box<dyn ViMode> = Box::new(ViVisual::new());
|
||||
self.swap_mode(&mut mode);
|
||||
@@ -1220,11 +1217,11 @@ impl ShedVi {
|
||||
return self.editor.exec_cmd(cmd);
|
||||
}
|
||||
Verb::VisualMode => {
|
||||
select_mode = Some(SelectMode::Char(SelectAnchor::End));
|
||||
self.editor.start_char_select();
|
||||
Box::new(ViVisual::new())
|
||||
}
|
||||
Verb::VisualModeLine => {
|
||||
select_mode = Some(SelectMode::Line(SelectAnchor::End));
|
||||
self.editor.start_line_select();
|
||||
Box::new(ViVisual::new())
|
||||
}
|
||||
|
||||
@@ -1232,6 +1229,8 @@ impl ShedVi {
|
||||
}
|
||||
};
|
||||
|
||||
// The mode we just created swaps places with our current mode
|
||||
// After this line, 'mode' contains our previous mode.
|
||||
self.swap_mode(&mut mode);
|
||||
|
||||
if matches!(
|
||||
@@ -1259,11 +1258,6 @@ impl ShedVi {
|
||||
self.editor.set_cursor_clamp(self.mode.clamp_cursor());
|
||||
self.editor.exec_cmd(cmd)?;
|
||||
|
||||
if let Some(sel_mode) = select_mode {
|
||||
self.editor.start_selecting(sel_mode);
|
||||
} else {
|
||||
self.editor.stop_selecting();
|
||||
}
|
||||
if is_insert_mode {
|
||||
self.editor.mark_insert_mode_start_pos();
|
||||
} else {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use std::{fmt::Display, sync::Mutex};
|
||||
|
||||
use crate::readline::linebuf::Line;
|
||||
|
||||
pub static REGISTERS: Mutex<Registers> = Mutex::new(Registers::new());
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -41,8 +43,9 @@ pub fn append_register(ch: Option<char>, buf: RegisterContent) {
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub enum RegisterContent {
|
||||
Span(String),
|
||||
Line(String),
|
||||
Span(Vec<Line>),
|
||||
Line(Vec<Line>),
|
||||
Block(Vec<Line>),
|
||||
#[default]
|
||||
Empty,
|
||||
}
|
||||
@@ -50,8 +53,11 @@ pub enum RegisterContent {
|
||||
impl Display for RegisterContent {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Span(s) => write!(f, "{}", s),
|
||||
Self::Line(s) => write!(f, "{}", s),
|
||||
Self::Block(s) |
|
||||
Self::Line(s) |
|
||||
Self::Span(s) => {
|
||||
write!(f, "{}", s.iter().map(|l| l.to_string()).collect::<Vec<_>>().join("\n"))
|
||||
}
|
||||
Self::Empty => write!(f, ""),
|
||||
}
|
||||
}
|
||||
@@ -59,16 +65,13 @@ impl Display for RegisterContent {
|
||||
|
||||
impl RegisterContent {
|
||||
pub fn clear(&mut self) {
|
||||
match self {
|
||||
Self::Span(s) => s.clear(),
|
||||
Self::Line(s) => s.clear(),
|
||||
Self::Empty => {}
|
||||
}
|
||||
*self = Self::Empty
|
||||
}
|
||||
pub fn len(&self) -> usize {
|
||||
match self {
|
||||
Self::Span(s) => s.len(),
|
||||
Self::Line(s) => s.len(),
|
||||
Self::Span(s) |
|
||||
Self::Line(s) |
|
||||
Self::Block(s) => s.len(),
|
||||
Self::Empty => 0,
|
||||
}
|
||||
}
|
||||
@@ -76,24 +79,21 @@ impl RegisterContent {
|
||||
match self {
|
||||
Self::Span(s) => s.is_empty(),
|
||||
Self::Line(s) => s.is_empty(),
|
||||
Self::Block(s) => s.is_empty(),
|
||||
Self::Empty => true,
|
||||
}
|
||||
}
|
||||
pub fn is_block(&self) -> bool {
|
||||
matches!(self, Self::Block(_))
|
||||
}
|
||||
pub fn is_line(&self) -> bool {
|
||||
matches!(self, Self::Line(_))
|
||||
}
|
||||
pub fn is_span(&self) -> bool {
|
||||
matches!(self, Self::Span(_))
|
||||
}
|
||||
pub fn as_str(&self) -> &str {
|
||||
match self {
|
||||
Self::Span(s) => s,
|
||||
Self::Line(s) => s,
|
||||
Self::Empty => "",
|
||||
}
|
||||
}
|
||||
pub fn char_count(&self) -> usize {
|
||||
self.as_str().chars().count()
|
||||
self.to_string().chars().count()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,7 +238,7 @@ pub struct Register {
|
||||
impl Register {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
content: RegisterContent::Span(String::new()),
|
||||
content: RegisterContent::Empty,
|
||||
}
|
||||
}
|
||||
pub fn content(&self) -> &RegisterContent {
|
||||
@@ -247,13 +247,16 @@ impl Register {
|
||||
pub fn write(&mut self, buf: RegisterContent) {
|
||||
self.content = buf
|
||||
}
|
||||
pub fn append(&mut self, buf: RegisterContent) {
|
||||
pub fn append(&mut self, mut buf: RegisterContent) {
|
||||
match buf {
|
||||
RegisterContent::Empty => {}
|
||||
RegisterContent::Span(ref s) | RegisterContent::Line(ref s) => match &mut self.content {
|
||||
RegisterContent::Span(ref mut s) |
|
||||
RegisterContent::Block(ref mut s) |
|
||||
RegisterContent::Line(ref mut s) => match &mut self.content {
|
||||
RegisterContent::Empty => self.content = buf,
|
||||
RegisterContent::Span(existing) => existing.push_str(s),
|
||||
RegisterContent::Line(existing) => existing.push_str(s),
|
||||
RegisterContent::Span(existing) |
|
||||
RegisterContent::Line(existing) |
|
||||
RegisterContent::Block(existing) => existing.append(s),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,9 +70,11 @@ pub fn get_win_size(fd: RawFd) -> (Col, Row) {
|
||||
}
|
||||
|
||||
fn enumerate_lines(s: &str, left_pad: usize, show_numbers: bool) -> String {
|
||||
let total_lines = s.lines().count();
|
||||
let lines: Vec<&str> = s.split('\n').collect();
|
||||
let total_lines = lines.len();
|
||||
let max_num_len = total_lines.to_string().len();
|
||||
s.lines()
|
||||
lines
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.fold(String::new(), |mut acc, (i, ln)| {
|
||||
if i == 0 {
|
||||
|
||||
@@ -23,7 +23,7 @@ macro_rules! vi_test {
|
||||
|
||||
vi.feed_bytes($op.as_bytes());
|
||||
vi.process_input().unwrap();
|
||||
assert_eq!(vi.editor.as_str(), $expected_text);
|
||||
assert_eq!(vi.editor.joined(), $expected_text);
|
||||
assert_eq!(vi.editor.cursor.get(), $expected_cursor);
|
||||
}
|
||||
)*
|
||||
@@ -512,7 +512,7 @@ fn vi_auto_indent() {
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
vi.editor.as_str(),
|
||||
vi.editor.joined(),
|
||||
"func() {\n\tcase foo in\n\t\tbar)\n\t\t\twhile true; do\n\t\t\t\techo foo \\\n\t\t\t\t\tbar \\\n\t\t\t\t\tbiz \\\n\t\t\t\t\tbazz\n\t\t\t\tbreak\n\t\t\tdone\n\t\t;;\n\tesac\n}"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -158,12 +158,10 @@ impl ViCmd {
|
||||
}) && self.motion.is_none()
|
||||
}
|
||||
pub fn is_line_motion(&self) -> bool {
|
||||
self.motion.as_ref().is_some_and(|m| {
|
||||
matches!(
|
||||
m.1,
|
||||
Motion::LineUp | Motion::LineDown
|
||||
)
|
||||
})
|
||||
self
|
||||
.motion
|
||||
.as_ref()
|
||||
.is_some_and(|m| matches!(m.1, Motion::LineUp | Motion::LineDown))
|
||||
}
|
||||
/// If a ViCmd has a linewise motion, but no verb, we change it to charwise
|
||||
pub fn is_mode_transition(&self) -> bool {
|
||||
@@ -380,10 +378,7 @@ impl Motion {
|
||||
)
|
||||
}
|
||||
pub fn is_linewise(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Self::WholeLine | Self::LineUp | Self::LineDown
|
||||
)
|
||||
matches!(self, Self::WholeLine | Self::LineUp | Self::LineDown)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,6 @@ impl ExEditor {
|
||||
history,
|
||||
..Default::default()
|
||||
};
|
||||
new.buf.update_graphemes();
|
||||
new
|
||||
}
|
||||
pub fn clear(&mut self) {
|
||||
@@ -56,19 +55,19 @@ impl ExEditor {
|
||||
cmd.verb().is_none()
|
||||
&& (cmd
|
||||
.motion()
|
||||
.is_some_and(|m| matches!(m, MotionCmd(_, Motion::LineUpCharwise)))
|
||||
.is_some_and(|m| matches!(m, MotionCmd(_, Motion::LineUp)))
|
||||
&& self.buf.start_of_line() == 0)
|
||||
|| (cmd
|
||||
.motion()
|
||||
.is_some_and(|m| matches!(m, MotionCmd(_, Motion::LineDownCharwise)))
|
||||
&& self.buf.end_of_line() == self.buf.cursor_max())
|
||||
.is_some_and(|m| matches!(m, MotionCmd(_, Motion::LineDown)))
|
||||
&& self.buf.on_last_line())
|
||||
}
|
||||
pub fn scroll_history(&mut self, cmd: ViCmd) {
|
||||
let count = &cmd.motion().unwrap().0;
|
||||
let motion = &cmd.motion().unwrap().1;
|
||||
let count = match motion {
|
||||
Motion::LineUpCharwise => -(*count as isize),
|
||||
Motion::LineDownCharwise => *count as isize,
|
||||
Motion::LineUp => -(*count as isize),
|
||||
Motion::LineDown => *count as isize,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let entry = self.history.scroll(count);
|
||||
@@ -88,7 +87,6 @@ impl ExEditor {
|
||||
let Some(mut cmd) = self.mode.handle_key(key) else {
|
||||
return Ok(());
|
||||
};
|
||||
cmd.alter_line_motion_if_no_verb();
|
||||
log::debug!("ExEditor got cmd: {:?}", cmd);
|
||||
if self.should_grab_history(&cmd) {
|
||||
log::debug!("Grabbing history for cmd: {:?}", cmd);
|
||||
@@ -118,11 +116,11 @@ impl ViMode for ViEx {
|
||||
use crate::readline::keys::{KeyCode as C, KeyEvent as E, ModKeys as M};
|
||||
match key {
|
||||
E(C::Char('\r'), M::NONE) | E(C::Enter, M::NONE) => {
|
||||
let input = self.pending_cmd.buf.as_str();
|
||||
match parse_ex_cmd(input) {
|
||||
let input = self.pending_cmd.buf.joined();
|
||||
match parse_ex_cmd(&input) {
|
||||
Ok(cmd) => Ok(cmd),
|
||||
Err(e) => {
|
||||
let msg = e.unwrap_or(format!("Not an editor command: {}", input));
|
||||
let msg = e.unwrap_or(format!("Not an editor command: {}", &input));
|
||||
write_meta(|m| m.post_system_message(msg.clone()));
|
||||
Err(ShErr::simple(ShErrKind::ParseErr, msg))
|
||||
}
|
||||
@@ -167,7 +165,7 @@ impl ViMode for ViEx {
|
||||
}
|
||||
|
||||
fn pending_seq(&self) -> Option<String> {
|
||||
Some(self.pending_cmd.buf.as_str().to_string())
|
||||
Some(self.pending_cmd.buf.joined())
|
||||
}
|
||||
|
||||
fn pending_cursor(&self) -> Option<usize> {
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::str::Chars;
|
||||
|
||||
use super::{CmdReplay, CmdState, ModeReport, ViMode, common_cmds};
|
||||
use crate::readline::keys::{KeyCode as K, KeyEvent as E, ModKeys as M};
|
||||
use crate::readline::linebuf::Grapheme;
|
||||
use crate::readline::vicmd::{
|
||||
Anchor, Bound, CmdFlags, Dest, Direction, Motion, MotionCmd, RegisterName, TextObj, To, Verb,
|
||||
VerbCmd, ViCmd, Word,
|
||||
@@ -197,7 +198,7 @@ impl ViNormal {
|
||||
return Some(ViCmd {
|
||||
register,
|
||||
verb: Some(VerbCmd(count, Verb::Change)),
|
||||
motion: Some(MotionCmd(1, Motion::WholeLineExclusive)),
|
||||
motion: Some(MotionCmd(1, Motion::WholeLine)),
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: self.flags(),
|
||||
});
|
||||
@@ -411,10 +412,10 @@ impl ViNormal {
|
||||
| ('~', Some(VerbCmd(_, Verb::ToggleCaseRange)))
|
||||
| ('>', Some(VerbCmd(_, Verb::Indent)))
|
||||
| ('<', Some(VerbCmd(_, Verb::Dedent))) => {
|
||||
break 'motion_parse Some(MotionCmd(count, Motion::WholeLineInclusive));
|
||||
break 'motion_parse Some(MotionCmd(count, Motion::WholeLine));
|
||||
}
|
||||
('c', Some(VerbCmd(_, Verb::Change))) => {
|
||||
break 'motion_parse Some(MotionCmd(count, Motion::WholeLineExclusive));
|
||||
break 'motion_parse Some(MotionCmd(count, Motion::WholeLine));
|
||||
}
|
||||
('W', Some(VerbCmd(_, Verb::Change))) => {
|
||||
// Same with 'W'
|
||||
@@ -535,7 +536,7 @@ impl ViNormal {
|
||||
|
||||
break 'motion_parse Some(MotionCmd(
|
||||
count,
|
||||
Motion::CharSearch(Direction::Forward, Dest::On, *ch),
|
||||
Motion::CharSearch(Direction::Forward, Dest::On, Grapheme::from(*ch)),
|
||||
));
|
||||
}
|
||||
'F' => {
|
||||
@@ -545,7 +546,7 @@ impl ViNormal {
|
||||
|
||||
break 'motion_parse Some(MotionCmd(
|
||||
count,
|
||||
Motion::CharSearch(Direction::Backward, Dest::On, *ch),
|
||||
Motion::CharSearch(Direction::Backward, Dest::On, Grapheme::from(*ch)),
|
||||
));
|
||||
}
|
||||
't' => {
|
||||
@@ -555,7 +556,7 @@ impl ViNormal {
|
||||
|
||||
break 'motion_parse Some(MotionCmd(
|
||||
count,
|
||||
Motion::CharSearch(Direction::Forward, Dest::Before, *ch),
|
||||
Motion::CharSearch(Direction::Forward, Dest::Before, Grapheme::from(*ch)),
|
||||
));
|
||||
}
|
||||
'T' => {
|
||||
@@ -565,7 +566,7 @@ impl ViNormal {
|
||||
|
||||
break 'motion_parse Some(MotionCmd(
|
||||
count,
|
||||
Motion::CharSearch(Direction::Backward, Dest::Before, *ch),
|
||||
Motion::CharSearch(Direction::Backward, Dest::Before, Grapheme::from(*ch)),
|
||||
));
|
||||
}
|
||||
';' => {
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::str::Chars;
|
||||
|
||||
use super::{CmdReplay, CmdState, ModeReport, ViMode, common_cmds};
|
||||
use crate::readline::keys::{KeyCode as K, KeyEvent as E, ModKeys as M};
|
||||
use crate::readline::linebuf::Grapheme;
|
||||
use crate::readline::vicmd::{
|
||||
Anchor, Bound, CmdFlags, Dest, Direction, Motion, MotionCmd, RegisterName, TextObj, To, Verb,
|
||||
VerbCmd, ViCmd, Word,
|
||||
@@ -146,7 +147,7 @@ impl ViVisual {
|
||||
return Some(ViCmd {
|
||||
register,
|
||||
verb: Some(VerbCmd(1, Verb::Delete)),
|
||||
motion: Some(MotionCmd(1, Motion::WholeLineInclusive)),
|
||||
motion: Some(MotionCmd(1, Motion::WholeLine)),
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: CmdFlags::empty(),
|
||||
});
|
||||
@@ -155,7 +156,7 @@ impl ViVisual {
|
||||
return Some(ViCmd {
|
||||
register,
|
||||
verb: Some(VerbCmd(1, Verb::Yank)),
|
||||
motion: Some(MotionCmd(1, Motion::WholeLineInclusive)),
|
||||
motion: Some(MotionCmd(1, Motion::WholeLine)),
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: CmdFlags::empty(),
|
||||
});
|
||||
@@ -164,7 +165,7 @@ impl ViVisual {
|
||||
return Some(ViCmd {
|
||||
register,
|
||||
verb: Some(VerbCmd(1, Verb::Delete)),
|
||||
motion: Some(MotionCmd(1, Motion::WholeLineInclusive)),
|
||||
motion: Some(MotionCmd(1, Motion::WholeLine)),
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: CmdFlags::empty(),
|
||||
});
|
||||
@@ -173,7 +174,7 @@ impl ViVisual {
|
||||
return Some(ViCmd {
|
||||
register,
|
||||
verb: Some(VerbCmd(1, Verb::Change)),
|
||||
motion: Some(MotionCmd(1, Motion::WholeLineExclusive)),
|
||||
motion: Some(MotionCmd(1, Motion::WholeLine)),
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: CmdFlags::empty(),
|
||||
});
|
||||
@@ -182,7 +183,7 @@ impl ViVisual {
|
||||
return Some(ViCmd {
|
||||
register,
|
||||
verb: Some(VerbCmd(1, Verb::Indent)),
|
||||
motion: Some(MotionCmd(1, Motion::WholeLineInclusive)),
|
||||
motion: Some(MotionCmd(1, Motion::WholeLine)),
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: CmdFlags::empty(),
|
||||
});
|
||||
@@ -191,7 +192,7 @@ impl ViVisual {
|
||||
return Some(ViCmd {
|
||||
register,
|
||||
verb: Some(VerbCmd(1, Verb::Dedent)),
|
||||
motion: Some(MotionCmd(1, Motion::WholeLineInclusive)),
|
||||
motion: Some(MotionCmd(1, Motion::WholeLine)),
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: CmdFlags::empty(),
|
||||
});
|
||||
@@ -200,7 +201,7 @@ impl ViVisual {
|
||||
return Some(ViCmd {
|
||||
register,
|
||||
verb: Some(VerbCmd(1, Verb::Equalize)),
|
||||
motion: Some(MotionCmd(1, Motion::WholeLineInclusive)),
|
||||
motion: Some(MotionCmd(1, Motion::WholeLine)),
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: CmdFlags::empty(),
|
||||
});
|
||||
@@ -344,10 +345,10 @@ impl ViVisual {
|
||||
| ('=', Some(VerbCmd(_, Verb::Equalize)))
|
||||
| ('>', Some(VerbCmd(_, Verb::Indent)))
|
||||
| ('<', Some(VerbCmd(_, Verb::Dedent))) => {
|
||||
break 'motion_parse Some(MotionCmd(count, Motion::WholeLineInclusive));
|
||||
break 'motion_parse Some(MotionCmd(count, Motion::WholeLine));
|
||||
}
|
||||
('c', Some(VerbCmd(_, Verb::Change))) => {
|
||||
break 'motion_parse Some(MotionCmd(count, Motion::WholeLineExclusive));
|
||||
break 'motion_parse Some(MotionCmd(count, Motion::WholeLine));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -425,7 +426,7 @@ impl ViVisual {
|
||||
|
||||
break 'motion_parse Some(MotionCmd(
|
||||
count,
|
||||
Motion::CharSearch(Direction::Forward, Dest::On, *ch),
|
||||
Motion::CharSearch(Direction::Forward, Dest::On, Grapheme::from(*ch)),
|
||||
));
|
||||
}
|
||||
'F' => {
|
||||
@@ -435,7 +436,7 @@ impl ViVisual {
|
||||
|
||||
break 'motion_parse Some(MotionCmd(
|
||||
count,
|
||||
Motion::CharSearch(Direction::Backward, Dest::On, *ch),
|
||||
Motion::CharSearch(Direction::Backward, Dest::On, Grapheme::from(*ch)),
|
||||
));
|
||||
}
|
||||
't' => {
|
||||
@@ -445,7 +446,7 @@ impl ViVisual {
|
||||
|
||||
break 'motion_parse Some(MotionCmd(
|
||||
count,
|
||||
Motion::CharSearch(Direction::Forward, Dest::Before, *ch),
|
||||
Motion::CharSearch(Direction::Forward, Dest::Before, Grapheme::from(*ch)),
|
||||
));
|
||||
}
|
||||
'T' => {
|
||||
@@ -455,7 +456,7 @@ impl ViVisual {
|
||||
|
||||
break 'motion_parse Some(MotionCmd(
|
||||
count,
|
||||
Motion::CharSearch(Direction::Backward, Dest::Before, *ch),
|
||||
Motion::CharSearch(Direction::Backward, Dest::Before, Grapheme::from(*ch)),
|
||||
));
|
||||
}
|
||||
';' => {
|
||||
|
||||
Reference in New Issue
Block a user