progress on linebuf refactor

This commit is contained in:
2026-03-18 23:52:23 -04:00
parent 7c8a418f96
commit 4a82f29231
14 changed files with 2018 additions and 847 deletions

View File

@@ -1223,16 +1223,13 @@ pub fn unescape_str(raw: &str) -> String {
result.push(markers::SNG_QUOTE); result.push(markers::SNG_QUOTE);
while let Some(q_ch) = chars.next() { while let Some(q_ch) = chars.next() {
match q_ch { match q_ch {
'\\' => { '\\' => match chars.peek() {
match chars.peek() { Some(&'\\') | Some(&'\'') => {
Some(&'\\') | let ch = chars.next().unwrap();
Some(&'\'') => { result.push(ch);
let ch = chars.next().unwrap(); }
result.push(ch); _ => result.push(q_ch),
} },
_ => result.push(q_ch),
}
}
'\'' => { '\'' => {
result.push(markers::SNG_QUOTE); result.push(markers::SNG_QUOTE);
break; break;

View File

@@ -161,10 +161,10 @@ fn main() -> ExitCode {
write_jobs(|j| j.hang_up()); write_jobs(|j| j.hang_up());
let code = QUIT_CODE.load(Ordering::SeqCst) as u8; let code = QUIT_CODE.load(Ordering::SeqCst) as u8;
if code == 0 && isatty(STDIN_FILENO).unwrap_or_default() { if code == 0 && isatty(STDIN_FILENO).unwrap_or_default() {
write(borrow_fd(STDERR_FILENO), b"\nexit\n").ok(); 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

@@ -29,100 +29,144 @@ 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)] #[derive(Debug, Clone)]
pub struct Candidate(pub String); pub struct Candidate(pub String);
impl Eq for Candidate {} impl Eq for Candidate {}
impl PartialEq for Candidate { impl PartialEq for Candidate {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.0 == other.0 self.0 == other.0
} }
} }
impl PartialOrd for Candidate { impl PartialOrd for Candidate {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other)) Some(self.cmp(other))
} }
} }
impl Ord for Candidate { impl Ord for Candidate {
fn cmp(&self, other: &Self) -> std::cmp::Ordering { fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.0.cmp(&other.0) self.0.cmp(&other.0)
} }
} }
impl From<String> for Candidate { impl From<String> for Candidate {
fn from(value: String) -> Self { fn from(value: String) -> Self {
Self(value) Self(value)
} }
} }
impl From<&String> for Candidate { impl From<&String> for Candidate {
fn from(value: &String) -> Self { fn from(value: &String) -> Self {
Self(value.clone()) Self(value.clone())
} }
} }
impl From<&str> for Candidate { impl From<&str> for Candidate {
fn from(value: &str) -> Self { fn from(value: &str) -> Self {
Self(value.to_string()) Self(value.to_string())
} }
} }
impl Display for Candidate { impl Display for Candidate {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", &self.0) write!(f, "{}", &self.0)
} }
} }
impl AsRef<str> for Candidate { impl AsRef<str> for Candidate {
fn as_ref(&self) -> &str { fn as_ref(&self) -> &str {
&self.0 &self.0
} }
} }
impl std::ops::Deref for Candidate { impl std::ops::Deref for Candidate {
type Target = str; type Target = str;
fn deref(&self) -> &str { fn deref(&self) -> &str {
&self.0 &self.0
} }
} }
impl Candidate { impl Candidate {
pub fn is_match(&self, other: &str) -> bool { pub fn is_match(&self, other: &str) -> bool {
let ignore_case = read_shopts(|o| o.prompt.completion_ignore_case); let ignore_case = read_shopts(|o| o.prompt.completion_ignore_case);
if ignore_case { if ignore_case {
let other_lower = other.to_lowercase(); let other_lower = other.to_lowercase();
let self_lower = self.0.to_lowercase(); let self_lower = self.0.to_lowercase();
self_lower.starts_with(&other_lower) self_lower.starts_with(&other_lower)
} else { } else {
self.0.starts_with(other) self.0.starts_with(other)
} }
} }
pub fn as_str(&self) -> &str { pub fn as_str(&self) -> &str {
&self.0 &self.0
} }
pub fn as_bytes(&self) -> &[u8] { pub fn as_bytes(&self) -> &[u8] {
self.0.as_bytes() self.0.as_bytes()
} }
pub fn starts_with(&self, pat: char) -> bool { pub fn starts_with(&self, pat: char) -> bool {
self.0.starts_with(pat) self.0.starts_with(pat)
} }
pub fn strip_prefix(&self, prefix: &str) -> Option<String> { pub fn strip_prefix(&self, prefix: &str) -> Option<String> {
let ignore_case = read_shopts(|o| o.prompt.completion_ignore_case); let ignore_case = read_shopts(|o| o.prompt.completion_ignore_case);
if ignore_case { if ignore_case {
let old_len = self.0.len(); let old_len = self.0.len();
let prefix_lower = prefix.to_lowercase(); let prefix_lower = prefix.to_lowercase();
let self_lower = self.0.to_lowercase(); let self_lower = self.0.to_lowercase();
let stripped = self_lower.strip_prefix(&prefix_lower)?; let stripped = self_lower.strip_prefix(&prefix_lower)?;
let new_len = stripped.len(); let new_len = stripped.len();
let delta = old_len - new_len; let delta = old_len - new_len;
Some(self.0[delta..].to_string()) Some(self.0[delta..].to_string())
} else { } else {
self.0.strip_prefix(prefix).map(|s| s.to_string()) self.0.strip_prefix(prefix).map(|s| s.to_string())
} }
} }
} }
pub fn complete_signals(start: &str) -> Vec<Candidate> { pub fn complete_signals(start: &str) -> Vec<Candidate> {
@@ -133,7 +177,7 @@ pub fn complete_signals(start: &str) -> Vec<Candidate> {
.unwrap_or(s.as_ref()) .unwrap_or(s.as_ref())
.to_string() .to_string()
}) })
.map(Candidate::from) .map(Candidate::from)
.filter(|s| s.is_match(start)) .filter(|s| s.is_match(start))
.collect() .collect()
} }
@@ -141,8 +185,8 @@ pub fn complete_signals(start: &str) -> Vec<Candidate> {
pub fn complete_aliases(start: &str) -> Vec<Candidate> { pub fn complete_aliases(start: &str) -> Vec<Candidate> {
read_logic(|l| { read_logic(|l| {
l.aliases() l.aliases()
.keys() .keys()
.map(Candidate::from) .map(Candidate::from)
.filter(|a| a.is_match(start)) .filter(|a| a.is_match(start))
.collect() .collect()
}) })
@@ -155,7 +199,7 @@ pub fn complete_jobs(start: &str) -> Vec<Candidate> {
.iter() .iter()
.filter_map(|j| j.as_ref()) .filter_map(|j| j.as_ref())
.filter_map(|j| j.name()) .filter_map(|j| j.name())
.map(Candidate::from) .map(Candidate::from)
.filter(|name| name.is_match(prefix)) .filter(|name| name.is_match(prefix))
.map(|name| format!("%{name}").into()) .map(|name| format!("%{name}").into())
.collect() .collect()
@@ -179,7 +223,7 @@ pub fn complete_users(start: &str) -> Vec<Candidate> {
passwd passwd
.lines() .lines()
.filter_map(|line| line.split(':').next()) .filter_map(|line| line.split(':').next())
.map(Candidate::from) .map(Candidate::from)
.filter(|username| username.is_match(start)) .filter(|username| username.is_match(start))
.collect() .collect()
} }
@@ -199,7 +243,7 @@ pub fn complete_vars(start: &str) -> Vec<Candidate> {
.keys() .keys()
.filter(|k| k.starts_with(&var_name) && *k != &var_name) .filter(|k| k.starts_with(&var_name) && *k != &var_name)
.map(|k| format!("{prefix}{k}")) .map(|k| format!("{prefix}{k}"))
.map(Candidate::from) .map(Candidate::from)
.collect::<Vec<_>>() .collect::<Vec<_>>()
}) })
} }
@@ -271,7 +315,7 @@ fn complete_commands(start: &str) -> Vec<Candidate> {
let mut candidates: Vec<Candidate> = read_meta(|m| { let mut candidates: Vec<Candidate> = read_meta(|m| {
m.cached_cmds() m.cached_cmds()
.iter() .iter()
.map(Candidate::from) .map(Candidate::from)
.filter(|c| c.is_match(start)) .filter(|c| c.is_match(start))
.collect() .collect()
}); });
@@ -325,8 +369,6 @@ fn complete_filename(start: &str) -> Vec<Candidate> {
let file_name = entry.file_name(); let file_name = entry.file_name();
let file_str: Candidate = file_name.to_string_lossy().to_string().into(); let file_str: Candidate = file_name.to_string_lossy().to_string().into();
// Skip hidden files unless explicitly requested // Skip hidden files unless explicitly requested
if !prefix.starts_with('.') && file_str.0.starts_with('.') { if !prefix.starts_with('.') && file_str.0.starts_with('.') {
continue; continue;
@@ -528,11 +570,11 @@ impl BashCompSpec {
); );
exec_input(input, None, false, Some("comp_function".into()))?; exec_input(input, None, false, Some("comp_function".into()))?;
let comp_reply = read_vars(|v| v.get_arr_elems("COMPREPLY")) let comp_reply = read_vars(|v| v.get_arr_elems("COMPREPLY"))
.unwrap_or_default() .unwrap_or_default()
.into_iter() .into_iter()
.map(Candidate::from) .map(Candidate::from)
.collect(); .collect();
Ok(comp_reply) Ok(comp_reply)
} }
@@ -569,7 +611,12 @@ impl CompSpec for BashCompSpec {
candidates.extend(complete_signals(&expanded)); candidates.extend(complete_signals(&expanded));
} }
if let Some(words) = &self.wordlist { 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() { if self.function.is_some() {
candidates.extend(self.exec_comp_func(ctx)?); candidates.extend(self.exec_comp_func(ctx)?);
@@ -645,7 +692,7 @@ impl CompResult {
Self::NoMatch Self::NoMatch
} else if candidates.len() == 1 { } else if candidates.len() == 1 {
Self::Single { Self::Single {
result: candidates.remove(0) result: candidates.remove(0),
} }
} else { } else {
Self::Many { candidates } Self::Many { candidates }
@@ -828,22 +875,22 @@ impl QueryEditor {
.cursor .cursor
.ret_sub(self.available_width.saturating_sub(1)); .ret_sub(self.available_width.saturating_sub(1));
} }
let max_offset = self.linebuf let max_offset = self
.count_graphemes() .linebuf
.count_graphemes()
.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);
} }
pub fn get_window(&mut self) -> String { pub fn get_window(&mut self) -> String {
self.linebuf.update_graphemes(); let buf_len = self.linebuf.count_graphemes();
let buf_len = self.linebuf.grapheme_indices().len();
if buf_len <= self.available_width { if buf_len <= self.available_width {
return self.linebuf.as_str().to_string(); return self.linebuf.joined();
} }
let start = self let start = self
.scroll_offset .scroll_offset
.min(buf_len.saturating_sub(self.available_width)); .min(buf_len.saturating_sub(self.available_width));
let end = (start + self.available_width).min(buf_len); 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<()> { pub fn handle_key(&mut self, key: K) -> ShResult<()> {
let Some(cmd) = self.mode.handle_key(key) else { let Some(cmd) = self.mode.handle_key(key) else {
@@ -1028,7 +1075,7 @@ impl FuzzySelector {
.into_iter() .into_iter()
.filter_map(|c| { .filter_map(|c| {
let mut sc = ScoredCandidate::new(c.to_string()); 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 } if score > i32::MIN { Some(sc) } else { None }
}) })
.collect(); .collect();
@@ -1319,12 +1366,18 @@ impl Completer for FuzzyCompleter {
basename, basename,
) )
} else { } else {
(self.completer.original_input[..start].to_string(), selected.clone()) (
self.completer.original_input[..start].to_string(),
selected.clone(),
)
} }
} else { } else {
start += slice.width(); start += slice.width();
let completion = selected.strip_prefix(slice).unwrap_or(&selected); 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 escaped = escape_str(&completion, false);
let ret = format!( let ret = format!(
@@ -1435,7 +1488,10 @@ impl Completer for SimpleCompleter {
} }
fn selected_candidate(&self) -> Option<String> { 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) { fn token_span(&self) -> (usize, usize) {
@@ -1591,16 +1647,20 @@ impl SimpleCompleter {
let prefix_end = start + last_sep + 1; let prefix_end = start + last_sep + 1;
let trailing_slash = selected.ends_with('/'); let trailing_slash = selected.ends_with('/');
let trimmed = selected.trim_end_matches('/'); 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 { if trailing_slash {
basename.push('/'); basename.push('/');
} }
( (self.original_input[..prefix_end].to_string(), basename)
self.original_input[..prefix_end].to_string(),
basename,
)
} else { } else {
(self.original_input[..start].to_string(), selected.to_string()) (
self.original_input[..start].to_string(),
selected.to_string(),
)
} }
} else { } else {
start += slice.width(); start += slice.width();
@@ -1608,12 +1668,7 @@ impl SimpleCompleter {
(self.original_input[..start].to_string(), completion) (self.original_input[..start].to_string(), completion)
}; };
let escaped = escape_str(&completion, false); let escaped = escape_str(&completion, false);
format!( format!("{}{}{}", prefix, escaped, &self.original_input[end..])
"{}{}{}",
prefix,
escaped,
&self.original_input[end..]
)
} }
pub fn build_comp_ctx(&self, tks: &[Tk], line: &str, cursor_pos: usize) -> ShResult<CompContext> { 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"); vi.feed_bytes(b"echo hello\t");
let _ = vi.process_input(); let _ = vi.process_input();
let line = vi.editor.as_str().to_string(); let line = vi.editor.joined();
assert!( assert!(
line.contains("hello\\ world.txt"), line.contains("hello\\ world.txt"),
"expected escaped space in completion: {line:?}" "expected escaped space in completion: {line:?}"
@@ -2202,7 +2257,7 @@ mod tests {
vi.feed_bytes(b"echo my\\ \t"); vi.feed_bytes(b"echo my\\ \t");
let _ = vi.process_input(); 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\\\ " // The user's "my\ " should be preserved, not double-escaped to "my\\\ "
assert!( assert!(
!line.contains("my\\\\ "), !line.contains("my\\\\ "),
@@ -2231,7 +2286,7 @@ mod tests {
vi.feed_bytes(b"echo unique_shed_test\t"); vi.feed_bytes(b"echo unique_shed_test\t");
let _ = vi.process_input(); let _ = vi.process_input();
let line = vi.editor.as_str().to_string(); let line = vi.editor.joined();
assert!( assert!(
line.contains("unique_shed_test_file.txt"), line.contains("unique_shed_test_file.txt"),
"expected completion in line: {line:?}" "expected completion in line: {line:?}"
@@ -2251,7 +2306,7 @@ mod tests {
vi.feed_bytes(b"cd mysub\t"); vi.feed_bytes(b"cd mysub\t");
let _ = vi.process_input(); let _ = vi.process_input();
let line = vi.editor.as_str().to_string(); let line = vi.editor.joined();
assert!( assert!(
line.contains("mysubdir/"), line.contains("mysubdir/"),
"expected dir completion with trailing slash: {line:?}" "expected dir completion with trailing slash: {line:?}"
@@ -2272,7 +2327,7 @@ mod tests {
vi.feed_bytes(b"cmd --opt=eqf\t"); vi.feed_bytes(b"cmd --opt=eqf\t");
let _ = vi.process_input(); let _ = vi.process_input();
let line = vi.editor.as_str().to_string(); let line = vi.editor.joined();
assert!( assert!(
line.contains("--opt=eqfile.txt"), line.contains("--opt=eqfile.txt"),
"expected completion after '=': {line:?}" "expected completion after '=': {line:?}"

View File

@@ -414,7 +414,12 @@ impl History {
} }
pub fn get_hint(&self) -> Option<String> { 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()?; let entry = self.hint_entry()?;
Some(entry.command().to_string()) Some(entry.command().to_string())
} else { } else {

File diff suppressed because it is too large Load Diff

View File

@@ -341,23 +341,25 @@ impl ShedVi {
pub fn with_initial(mut self, initial: &str) -> Self { pub fn with_initial(mut self, initial: &str) -> Self {
self.editor = LineBuf::new().with_initial(initial, 0); self.editor = LineBuf::new().with_initial(initial, 0);
self {
.history let s = self.editor.joined();
.update_pending_cmd((self.editor.as_str(), self.editor.cursor.get())); let c = self.editor.cursor.get();
self.history.update_pending_cmd((&s, c));
}
self self
} }
/// A mutable reference to the currently focused editor /// A mutable reference to the currently focused editor
/// This includes the main LineBuf, and sub-editors for modes like Ex mode. /// This includes the main LineBuf, and sub-editors for modes like Ex mode.
pub fn focused_editor(&mut self) -> &mut LineBuf { pub fn focused_editor(&mut self) -> &mut LineBuf {
self.mode.editor().unwrap_or(&mut self.editor) self.mode.editor().unwrap_or(&mut self.editor)
} }
/// A mutable reference to the currently focused history, if any. /// A mutable reference to the currently focused history, if any.
/// This includes the main history struct, and history for sub-editors like Ex mode. /// This includes the main history struct, and history for sub-editors like Ex mode.
pub fn focused_history(&mut self) -> &mut History { pub fn focused_history(&mut self) -> &mut History {
self.mode.history().unwrap_or(&mut self.history) self.mode.history().unwrap_or(&mut self.history)
} }
/// Feed raw bytes from stdin into the reader's buffer /// Feed raw bytes from stdin into the reader's buffer
pub fn feed_bytes(&mut self, bytes: &[u8]) { pub fn feed_bytes(&mut self, bytes: &[u8]) {
@@ -436,7 +438,7 @@ impl ShedVi {
if self.mode.report_mode() == ModeReport::Normal { if self.mode.report_mode() == ModeReport::Normal {
return Ok(true); return Ok(true);
} }
let input = Arc::new(self.editor.buffer.clone()); let input = Arc::new(self.editor.joined());
self.editor.calc_indent_level(); self.editor.calc_indent_level();
let lex_result1 = let lex_result1 =
LexStream::new(Arc::clone(&input), LexFlags::LEX_UNFINISHED).collect::<ShResult<Vec<_>>>(); LexStream::new(Arc::clone(&input), LexFlags::LEX_UNFINISHED).collect::<ShResult<Vec<_>>>();
@@ -476,21 +478,21 @@ impl ShedVi {
SelectorResponse::Accept(cmd) => { SelectorResponse::Accept(cmd) => {
let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnHistorySelect)); let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnHistorySelect));
{ {
let editor = self.focused_editor(); let editor = self.focused_editor();
editor.set_buffer(cmd.to_string()); editor.set_buffer(cmd.to_string());
editor.move_cursor_to_end(); editor.move_cursor_to_end();
} }
self self
.history .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); self.editor.set_hint(None);
{ {
let mut writer = std::mem::take(&mut self.writer); let mut writer = std::mem::take(&mut self.writer);
self.focused_history().fuzzy_finder.clear(&mut writer)?; self.focused_history().fuzzy_finder.clear(&mut writer)?;
self.writer = writer; self.writer = writer;
} }
self.focused_history().fuzzy_finder.reset(); self.focused_history().fuzzy_finder.reset();
with_vars([("_HIST_ENTRY".into(), cmd.clone())], || { with_vars([("_HIST_ENTRY".into(), cmd.clone())], || {
@@ -514,11 +516,11 @@ impl ShedVi {
post_cmds.exec(); post_cmds.exec();
self.editor.set_hint(None); self.editor.set_hint(None);
{ {
let mut writer = std::mem::take(&mut self.writer); let mut writer = std::mem::take(&mut self.writer);
self.focused_history().fuzzy_finder.clear(&mut writer)?; self.focused_history().fuzzy_finder.clear(&mut writer)?;
self.writer = writer; self.writer = writer;
} }
write_vars(|v| { write_vars(|v| {
v.set_var( v.set_var(
"SHED_VI_MODE", "SHED_VI_MODE",
@@ -554,7 +556,7 @@ impl ShedVi {
} }
self self
.history .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(); let hint = self.history.get_hint();
self.editor.set_hint(hint); self.editor.set_hint(hint);
self.completer.clear(&mut self.writer)?; self.completer.clear(&mut self.writer)?;
@@ -669,14 +671,15 @@ impl ShedVi {
} }
self self
.history .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; self.needs_redraw = true;
return Ok(None); return Ok(None);
} }
if let KeyEvent(KeyCode::Tab, mod_keys) = key { if let KeyEvent(KeyCode::Tab, mod_keys) = key {
if self.mode.report_mode() != ModeReport::Ex 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 // If history expansion occurred, don't attempt completion yet
// allow the user to see the expanded command and accept or edit it before completing // allow the user to see the expanded command and accept or edit it before completing
return Ok(None); return Ok(None);
@@ -686,7 +689,7 @@ impl ShedVi {
ModKeys::SHIFT => -1, ModKeys::SHIFT => -1,
_ => 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(); let cursor_pos = self.focused_editor().cursor_byte_pos();
match self.completer.complete(line, cursor_pos, direction) { match self.completer.complete(line, cursor_pos, direction) {
@@ -719,7 +722,7 @@ impl ShedVi {
} }
self self
.history .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(); let hint = self.history.get_hint();
self.editor.set_hint(hint); self.editor.set_hint(hint);
write_vars(|v| { write_vars(|v| {
@@ -776,7 +779,7 @@ impl ShedVi {
} else if let KeyEvent(KeyCode::Char('R'), ModKeys::CTRL) = key } else if let KeyEvent(KeyCode::Char('R'), ModKeys::CTRL) = key
&& matches!(self.mode.report_mode(), ModeReport::Insert | ModeReport::Ex) && 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) { match self.focused_history().start_search(&initial) {
Some(entry) => { Some(entry) => {
let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnHistorySelect)); 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.focused_editor().move_cursor_to_end();
self self
.history .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); self.editor.set_hint(None);
} }
None => { None => {
@@ -847,8 +850,6 @@ impl ShedVi {
let Some(mut cmd) = cmd else { let Some(mut cmd) = cmd else {
return Ok(None); return Ok(None);
}; };
cmd.alter_line_motion_if_no_verb();
if self.should_grab_history(&cmd) { if self.should_grab_history(&cmd) {
self.scroll_history(cmd); self.scroll_history(cmd);
self.needs_redraw = true; self.needs_redraw = true;
@@ -875,7 +876,7 @@ impl ShedVi {
} }
if cmd.verb().is_some_and(|v| v.1 == Verb::EndOfFile) { 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)); return Ok(Some(ReadlineEvent::Eof));
} else { } else {
*self.focused_editor() = LineBuf::new(); *self.focused_editor() = LineBuf::new();
@@ -884,23 +885,21 @@ impl ShedVi {
return Ok(None); return Ok(None);
} }
} else if cmd.verb().is_some_and(|v| v.1 == Verb::Quit) { } else if cmd.verb().is_some_and(|v| v.1 == Verb::Quit) {
return Ok(Some(ReadlineEvent::Eof)); 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());
let is_shell_cmd = cmd.verb().is_some_and(|v| matches!(v.1, Verb::ShellCmd(_))); 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); let is_ex_cmd = cmd.flags.contains(CmdFlags::IS_EX_CMD);
log::debug!("is_ex_cmd: {is_ex_cmd}");
if is_shell_cmd { if is_shell_cmd {
self.old_layout = None; self.old_layout = None;
} }
if is_ex_cmd { if is_ex_cmd {
self.ex_history.push(cmd.raw_seq.clone()); self.ex_history.push(cmd.raw_seq.clone());
self.ex_history.reset(); 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)?; self.exec_cmd(cmd, false)?;
@@ -909,12 +908,12 @@ impl ShedVi {
self.handle_key(key)?; self.handle_key(key)?;
} }
} }
let after = self.editor.as_str(); let after = self.editor.joined();
if before != after { if before != after {
self self
.history .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 { } else if before == after && has_edit_verb {
self.writer.send_bell().ok(); self.writer.send_bell().ok();
} }
@@ -929,7 +928,7 @@ impl ShedVi {
pub fn get_layout(&mut self, line: &str) -> Layout { pub fn get_layout(&mut self, line: &str) -> Layout {
let to_cursor = self.editor.slice_to_cursor().unwrap_or_default(); let to_cursor = self.editor.slice_to_cursor().unwrap_or_default();
let (cols, _) = get_win_size(self.tty); 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) { pub fn scroll_history(&mut self, cmd: ViCmd) {
/* /*
@@ -941,8 +940,8 @@ impl ShedVi {
let count = &cmd.motion().unwrap().0; let count = &cmd.motion().unwrap().0;
let motion = &cmd.motion().unwrap().1; let motion = &cmd.motion().unwrap().1;
let count = match motion { let count = match motion {
Motion::LineUpCharwise => -(*count as isize), Motion::LineUp => -(*count as isize),
Motion::LineDownCharwise => *count as isize, Motion::LineDown => *count as isize,
_ => unreachable!(), _ => unreachable!(),
}; };
let entry = self.history.scroll(count); let entry = self.history.scroll(count);
@@ -985,12 +984,12 @@ impl ShedVi {
cmd.verb().is_none() cmd.verb().is_none()
&& (cmd && (cmd
.motion() .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) && self.editor.start_of_line() == 0)
|| (cmd || (cmd
.motion() .motion()
.is_some_and(|m| matches!(m, MotionCmd(_, Motion::LineDownCharwise))) .is_some_and(|m| matches!(m, MotionCmd(_, Motion::LineDown)))
&& self.editor.end_of_line() == self.editor.cursor_max()) && self.editor.on_last_line())
} }
pub fn line_text(&mut self) -> String { pub fn line_text(&mut self) -> String {
@@ -1004,7 +1003,8 @@ impl ShedVi {
self.highlighter.expand_control_chars(); self.highlighter.expand_control_chars();
self.highlighter.highlight(); self.highlighter.highlight();
let highlighted = self.highlighter.take(); let highlighted = self.highlighter.take();
format!("{highlighted}{hint}") let res = format!("{highlighted}{hint}");
res
} }
pub fn print_line(&mut self, final_draw: bool) -> ShResult<()> { pub fn print_line(&mut self, final_draw: bool) -> ShResult<()> {
@@ -1035,11 +1035,11 @@ impl ShedVi {
let one_line = new_layout.end.row == 0; let one_line = new_layout.end.row == 0;
self.completer.clear(&mut self.writer)?; self.completer.clear(&mut self.writer)?;
{ {
let mut writer = std::mem::take(&mut self.writer); let mut writer = std::mem::take(&mut self.writer);
self.focused_history().fuzzy_finder.clear(&mut writer)?; self.focused_history().fuzzy_finder.clear(&mut writer)?;
self.writer = writer; self.writer = writer;
} }
if let Some(layout) = self.old_layout.as_ref() { if let Some(layout) = self.old_layout.as_ref() {
self.writer.clear_rows(layout)?; self.writer.clear_rows(layout)?;
@@ -1136,11 +1136,11 @@ impl ShedVi {
.fuzzy_finder .fuzzy_finder
.set_prompt_line_context(preceding_width, new_layout.cursor.col); .set_prompt_line_context(preceding_width, new_layout.cursor.col);
{ {
let mut writer = std::mem::take(&mut self.writer); let mut writer = std::mem::take(&mut self.writer);
self.focused_history().fuzzy_finder.draw(&mut writer)?; self.focused_history().fuzzy_finder.draw(&mut writer)?;
self.writer = writer; self.writer = writer;
} }
self.old_layout = Some(new_layout); self.old_layout = Some(new_layout);
self.needs_redraw = false; self.needs_redraw = false;
@@ -1172,7 +1172,6 @@ impl ShedVi {
} }
fn exec_mode_transition(&mut self, cmd: ViCmd, from_replay: bool) -> ShResult<()> { fn exec_mode_transition(&mut self, cmd: ViCmd, from_replay: bool) -> ShResult<()> {
let mut select_mode = None;
let mut is_insert_mode = false; let mut is_insert_mode = false;
let count = cmd.verb_count(); let count = cmd.verb_count();
@@ -1210,9 +1209,7 @@ impl ShedVi {
Verb::VisualModeSelectLast => { Verb::VisualModeSelectLast => {
if self.mode.report_mode() != ModeReport::Visual { if self.mode.report_mode() != ModeReport::Visual {
self self.editor.start_char_select();
.editor
.start_selecting(SelectMode::Char(SelectAnchor::End));
} }
let mut mode: Box<dyn ViMode> = Box::new(ViVisual::new()); let mut mode: Box<dyn ViMode> = Box::new(ViVisual::new());
self.swap_mode(&mut mode); self.swap_mode(&mut mode);
@@ -1220,11 +1217,11 @@ impl ShedVi {
return self.editor.exec_cmd(cmd); return self.editor.exec_cmd(cmd);
} }
Verb::VisualMode => { Verb::VisualMode => {
select_mode = Some(SelectMode::Char(SelectAnchor::End)); self.editor.start_char_select();
Box::new(ViVisual::new()) Box::new(ViVisual::new())
} }
Verb::VisualModeLine => { Verb::VisualModeLine => {
select_mode = Some(SelectMode::Line(SelectAnchor::End)); self.editor.start_line_select();
Box::new(ViVisual::new()) 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); self.swap_mode(&mut mode);
if matches!( if matches!(
@@ -1259,11 +1258,6 @@ impl ShedVi {
self.editor.set_cursor_clamp(self.mode.clamp_cursor()); self.editor.set_cursor_clamp(self.mode.clamp_cursor());
self.editor.exec_cmd(cmd)?; 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 { if is_insert_mode {
self.editor.mark_insert_mode_start_pos(); self.editor.mark_insert_mode_start_pos();
} else { } else {

View File

@@ -1,5 +1,7 @@
use std::{fmt::Display, sync::Mutex}; use std::{fmt::Display, sync::Mutex};
use crate::readline::linebuf::Line;
pub static REGISTERS: Mutex<Registers> = Mutex::new(Registers::new()); pub static REGISTERS: Mutex<Registers> = Mutex::new(Registers::new());
#[cfg(test)] #[cfg(test)]
@@ -41,8 +43,9 @@ pub fn append_register(ch: Option<char>, buf: RegisterContent) {
#[derive(Default, Clone, Debug)] #[derive(Default, Clone, Debug)]
pub enum RegisterContent { pub enum RegisterContent {
Span(String), Span(Vec<Line>),
Line(String), Line(Vec<Line>),
Block(Vec<Line>),
#[default] #[default]
Empty, Empty,
} }
@@ -50,8 +53,11 @@ pub enum RegisterContent {
impl Display for RegisterContent { impl Display for RegisterContent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
Self::Span(s) => write!(f, "{}", s), Self::Block(s) |
Self::Line(s) => write!(f, "{}", s), Self::Line(s) |
Self::Span(s) => {
write!(f, "{}", s.iter().map(|l| l.to_string()).collect::<Vec<_>>().join("\n"))
}
Self::Empty => write!(f, ""), Self::Empty => write!(f, ""),
} }
} }
@@ -59,16 +65,13 @@ impl Display for RegisterContent {
impl RegisterContent { impl RegisterContent {
pub fn clear(&mut self) { pub fn clear(&mut self) {
match self { *self = Self::Empty
Self::Span(s) => s.clear(),
Self::Line(s) => s.clear(),
Self::Empty => {}
}
} }
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
match self { match self {
Self::Span(s) => s.len(), Self::Span(s) |
Self::Line(s) => s.len(), Self::Line(s) |
Self::Block(s) => s.len(),
Self::Empty => 0, Self::Empty => 0,
} }
} }
@@ -76,24 +79,21 @@ impl RegisterContent {
match self { match self {
Self::Span(s) => s.is_empty(), Self::Span(s) => s.is_empty(),
Self::Line(s) => s.is_empty(), Self::Line(s) => s.is_empty(),
Self::Block(s) => s.is_empty(),
Self::Empty => true, Self::Empty => true,
} }
} }
pub fn is_block(&self) -> bool {
matches!(self, Self::Block(_))
}
pub fn is_line(&self) -> bool { pub fn is_line(&self) -> bool {
matches!(self, Self::Line(_)) matches!(self, Self::Line(_))
} }
pub fn is_span(&self) -> bool { pub fn is_span(&self) -> bool {
matches!(self, Self::Span(_)) 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 { 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 { impl Register {
pub const fn new() -> Self { pub const fn new() -> Self {
Self { Self {
content: RegisterContent::Span(String::new()), content: RegisterContent::Empty,
} }
} }
pub fn content(&self) -> &RegisterContent { pub fn content(&self) -> &RegisterContent {
@@ -247,13 +247,16 @@ impl Register {
pub fn write(&mut self, buf: RegisterContent) { pub fn write(&mut self, buf: RegisterContent) {
self.content = buf self.content = buf
} }
pub fn append(&mut self, buf: RegisterContent) { pub fn append(&mut self, mut buf: RegisterContent) {
match buf { match buf {
RegisterContent::Empty => {} 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::Empty => self.content = buf,
RegisterContent::Span(existing) => existing.push_str(s), RegisterContent::Span(existing) |
RegisterContent::Line(existing) => existing.push_str(s), RegisterContent::Line(existing) |
RegisterContent::Block(existing) => existing.append(s),
}, },
} }
} }

View File

@@ -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 { 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(); let max_num_len = total_lines.to_string().len();
s.lines() lines
.into_iter()
.enumerate() .enumerate()
.fold(String::new(), |mut acc, (i, ln)| { .fold(String::new(), |mut acc, (i, ln)| {
if i == 0 { if i == 0 {

View File

@@ -23,7 +23,7 @@ macro_rules! vi_test {
vi.feed_bytes($op.as_bytes()); vi.feed_bytes($op.as_bytes());
vi.process_input().unwrap(); 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); assert_eq!(vi.editor.cursor.get(), $expected_cursor);
} }
)* )*
@@ -512,7 +512,7 @@ fn vi_auto_indent() {
} }
assert_eq!( 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}" "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}"
); );
} }

View File

@@ -158,12 +158,10 @@ impl ViCmd {
}) && self.motion.is_none() }) && self.motion.is_none()
} }
pub fn is_line_motion(&self) -> bool { pub fn is_line_motion(&self) -> bool {
self.motion.as_ref().is_some_and(|m| { self
matches!( .motion
m.1, .as_ref()
Motion::LineUp | Motion::LineDown .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 /// If a ViCmd has a linewise motion, but no verb, we change it to charwise
pub fn is_mode_transition(&self) -> bool { pub fn is_mode_transition(&self) -> bool {
@@ -249,7 +247,7 @@ pub enum Verb {
Read(ReadSrc), Read(ReadSrc),
Write(WriteDest), Write(WriteDest),
Edit(PathBuf), Edit(PathBuf),
Quit, Quit,
Substitute(String, String, SubFlags), Substitute(String, String, SubFlags),
RepeatSubstitute, RepeatSubstitute,
RepeatGlobal, RepeatGlobal,
@@ -296,9 +294,9 @@ impl Verb {
| Self::JoinLines | Self::JoinLines
| Self::InsertChar(_) | Self::InsertChar(_)
| Self::Insert(_) | Self::Insert(_)
| Self::Dedent | Self::Dedent
| Self::Indent | Self::Indent
| Self::Equalize | Self::Equalize
| Self::Rot13 | Self::Rot13
| Self::EndOfFile | Self::EndOfFile
| Self::IncrementNumber(_) | Self::IncrementNumber(_)
@@ -380,10 +378,7 @@ impl Motion {
) )
} }
pub fn is_linewise(&self) -> bool { pub fn is_linewise(&self) -> bool {
matches!( matches!(self, Self::WholeLine | Self::LineUp | Self::LineDown)
self,
Self::WholeLine | Self::LineUp | Self::LineDown
)
} }
} }

View File

@@ -46,7 +46,6 @@ impl ExEditor {
history, history,
..Default::default() ..Default::default()
}; };
new.buf.update_graphemes();
new new
} }
pub fn clear(&mut self) { pub fn clear(&mut self) {
@@ -56,19 +55,19 @@ impl ExEditor {
cmd.verb().is_none() cmd.verb().is_none()
&& (cmd && (cmd
.motion() .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) && self.buf.start_of_line() == 0)
|| (cmd || (cmd
.motion() .motion()
.is_some_and(|m| matches!(m, MotionCmd(_, Motion::LineDownCharwise))) .is_some_and(|m| matches!(m, MotionCmd(_, Motion::LineDown)))
&& self.buf.end_of_line() == self.buf.cursor_max()) && self.buf.on_last_line())
} }
pub fn scroll_history(&mut self, cmd: ViCmd) { pub fn scroll_history(&mut self, cmd: ViCmd) {
let count = &cmd.motion().unwrap().0; let count = &cmd.motion().unwrap().0;
let motion = &cmd.motion().unwrap().1; let motion = &cmd.motion().unwrap().1;
let count = match motion { let count = match motion {
Motion::LineUpCharwise => -(*count as isize), Motion::LineUp => -(*count as isize),
Motion::LineDownCharwise => *count as isize, Motion::LineDown => *count as isize,
_ => unreachable!(), _ => unreachable!(),
}; };
let entry = self.history.scroll(count); let entry = self.history.scroll(count);
@@ -88,7 +87,6 @@ impl ExEditor {
let Some(mut cmd) = self.mode.handle_key(key) else { let Some(mut cmd) = self.mode.handle_key(key) else {
return Ok(()); return Ok(());
}; };
cmd.alter_line_motion_if_no_verb();
log::debug!("ExEditor got cmd: {:?}", cmd); log::debug!("ExEditor got cmd: {:?}", cmd);
if self.should_grab_history(&cmd) { if self.should_grab_history(&cmd) {
log::debug!("Grabbing history for cmd: {:?}", 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}; use crate::readline::keys::{KeyCode as C, KeyEvent as E, ModKeys as M};
match key { match key {
E(C::Char('\r'), M::NONE) | E(C::Enter, M::NONE) => { E(C::Char('\r'), M::NONE) | E(C::Enter, M::NONE) => {
let input = self.pending_cmd.buf.as_str(); let input = self.pending_cmd.buf.joined();
match parse_ex_cmd(input) { match parse_ex_cmd(&input) {
Ok(cmd) => Ok(cmd), Ok(cmd) => Ok(cmd),
Err(e) => { 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())); write_meta(|m| m.post_system_message(msg.clone()));
Err(ShErr::simple(ShErrKind::ParseErr, msg)) Err(ShErr::simple(ShErrKind::ParseErr, msg))
} }
@@ -167,7 +165,7 @@ impl ViMode for ViEx {
} }
fn pending_seq(&self) -> Option<String> { 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> { fn pending_cursor(&self) -> Option<usize> {
@@ -280,7 +278,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 "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),

View File

@@ -3,6 +3,7 @@ use std::str::Chars;
use super::{CmdReplay, CmdState, ModeReport, ViMode, common_cmds}; use super::{CmdReplay, CmdState, ModeReport, ViMode, common_cmds};
use crate::readline::keys::{KeyCode as K, KeyEvent as E, ModKeys as M}; use crate::readline::keys::{KeyCode as K, KeyEvent as E, ModKeys as M};
use crate::readline::linebuf::Grapheme;
use crate::readline::vicmd::{ use crate::readline::vicmd::{
Anchor, Bound, CmdFlags, Dest, Direction, Motion, MotionCmd, RegisterName, TextObj, To, Verb, Anchor, Bound, CmdFlags, Dest, Direction, Motion, MotionCmd, RegisterName, TextObj, To, Verb,
VerbCmd, ViCmd, Word, VerbCmd, ViCmd, Word,
@@ -197,7 +198,7 @@ impl ViNormal {
return Some(ViCmd { return Some(ViCmd {
register, register,
verb: Some(VerbCmd(count, Verb::Change)), verb: Some(VerbCmd(count, Verb::Change)),
motion: Some(MotionCmd(1, Motion::WholeLineExclusive)), motion: Some(MotionCmd(1, Motion::WholeLine)),
raw_seq: self.take_cmd(), raw_seq: self.take_cmd(),
flags: self.flags(), flags: self.flags(),
}); });
@@ -411,10 +412,10 @@ impl ViNormal {
| ('~', Some(VerbCmd(_, Verb::ToggleCaseRange))) | ('~', Some(VerbCmd(_, Verb::ToggleCaseRange)))
| ('>', Some(VerbCmd(_, Verb::Indent))) | ('>', Some(VerbCmd(_, Verb::Indent)))
| ('<', Some(VerbCmd(_, Verb::Dedent))) => { | ('<', 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))) => { ('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))) => { ('W', Some(VerbCmd(_, Verb::Change))) => {
// Same with 'W' // Same with 'W'
@@ -535,7 +536,7 @@ impl ViNormal {
break 'motion_parse Some(MotionCmd( break 'motion_parse Some(MotionCmd(
count, count,
Motion::CharSearch(Direction::Forward, Dest::On, *ch), Motion::CharSearch(Direction::Forward, Dest::On, Grapheme::from(*ch)),
)); ));
} }
'F' => { 'F' => {
@@ -545,7 +546,7 @@ impl ViNormal {
break 'motion_parse Some(MotionCmd( break 'motion_parse Some(MotionCmd(
count, count,
Motion::CharSearch(Direction::Backward, Dest::On, *ch), Motion::CharSearch(Direction::Backward, Dest::On, Grapheme::from(*ch)),
)); ));
} }
't' => { 't' => {
@@ -555,7 +556,7 @@ impl ViNormal {
break 'motion_parse Some(MotionCmd( break 'motion_parse Some(MotionCmd(
count, count,
Motion::CharSearch(Direction::Forward, Dest::Before, *ch), Motion::CharSearch(Direction::Forward, Dest::Before, Grapheme::from(*ch)),
)); ));
} }
'T' => { 'T' => {
@@ -565,7 +566,7 @@ impl ViNormal {
break 'motion_parse Some(MotionCmd( break 'motion_parse Some(MotionCmd(
count, count,
Motion::CharSearch(Direction::Backward, Dest::Before, *ch), Motion::CharSearch(Direction::Backward, Dest::Before, Grapheme::from(*ch)),
)); ));
} }
';' => { ';' => {

View File

@@ -3,6 +3,7 @@ use std::str::Chars;
use super::{CmdReplay, CmdState, ModeReport, ViMode, common_cmds}; use super::{CmdReplay, CmdState, ModeReport, ViMode, common_cmds};
use crate::readline::keys::{KeyCode as K, KeyEvent as E, ModKeys as M}; use crate::readline::keys::{KeyCode as K, KeyEvent as E, ModKeys as M};
use crate::readline::linebuf::Grapheme;
use crate::readline::vicmd::{ use crate::readline::vicmd::{
Anchor, Bound, CmdFlags, Dest, Direction, Motion, MotionCmd, RegisterName, TextObj, To, Verb, Anchor, Bound, CmdFlags, Dest, Direction, Motion, MotionCmd, RegisterName, TextObj, To, Verb,
VerbCmd, ViCmd, Word, VerbCmd, ViCmd, Word,
@@ -146,7 +147,7 @@ impl ViVisual {
return Some(ViCmd { return Some(ViCmd {
register, register,
verb: Some(VerbCmd(1, Verb::Delete)), verb: Some(VerbCmd(1, Verb::Delete)),
motion: Some(MotionCmd(1, Motion::WholeLineInclusive)), motion: Some(MotionCmd(1, Motion::WholeLine)),
raw_seq: self.take_cmd(), raw_seq: self.take_cmd(),
flags: CmdFlags::empty(), flags: CmdFlags::empty(),
}); });
@@ -155,7 +156,7 @@ impl ViVisual {
return Some(ViCmd { return Some(ViCmd {
register, register,
verb: Some(VerbCmd(1, Verb::Yank)), verb: Some(VerbCmd(1, Verb::Yank)),
motion: Some(MotionCmd(1, Motion::WholeLineInclusive)), motion: Some(MotionCmd(1, Motion::WholeLine)),
raw_seq: self.take_cmd(), raw_seq: self.take_cmd(),
flags: CmdFlags::empty(), flags: CmdFlags::empty(),
}); });
@@ -164,7 +165,7 @@ impl ViVisual {
return Some(ViCmd { return Some(ViCmd {
register, register,
verb: Some(VerbCmd(1, Verb::Delete)), verb: Some(VerbCmd(1, Verb::Delete)),
motion: Some(MotionCmd(1, Motion::WholeLineInclusive)), motion: Some(MotionCmd(1, Motion::WholeLine)),
raw_seq: self.take_cmd(), raw_seq: self.take_cmd(),
flags: CmdFlags::empty(), flags: CmdFlags::empty(),
}); });
@@ -173,7 +174,7 @@ impl ViVisual {
return Some(ViCmd { return Some(ViCmd {
register, register,
verb: Some(VerbCmd(1, Verb::Change)), verb: Some(VerbCmd(1, Verb::Change)),
motion: Some(MotionCmd(1, Motion::WholeLineExclusive)), motion: Some(MotionCmd(1, Motion::WholeLine)),
raw_seq: self.take_cmd(), raw_seq: self.take_cmd(),
flags: CmdFlags::empty(), flags: CmdFlags::empty(),
}); });
@@ -182,7 +183,7 @@ impl ViVisual {
return Some(ViCmd { return Some(ViCmd {
register, register,
verb: Some(VerbCmd(1, Verb::Indent)), verb: Some(VerbCmd(1, Verb::Indent)),
motion: Some(MotionCmd(1, Motion::WholeLineInclusive)), motion: Some(MotionCmd(1, Motion::WholeLine)),
raw_seq: self.take_cmd(), raw_seq: self.take_cmd(),
flags: CmdFlags::empty(), flags: CmdFlags::empty(),
}); });
@@ -191,7 +192,7 @@ impl ViVisual {
return Some(ViCmd { return Some(ViCmd {
register, register,
verb: Some(VerbCmd(1, Verb::Dedent)), verb: Some(VerbCmd(1, Verb::Dedent)),
motion: Some(MotionCmd(1, Motion::WholeLineInclusive)), motion: Some(MotionCmd(1, Motion::WholeLine)),
raw_seq: self.take_cmd(), raw_seq: self.take_cmd(),
flags: CmdFlags::empty(), flags: CmdFlags::empty(),
}); });
@@ -200,7 +201,7 @@ impl ViVisual {
return Some(ViCmd { return Some(ViCmd {
register, register,
verb: Some(VerbCmd(1, Verb::Equalize)), verb: Some(VerbCmd(1, Verb::Equalize)),
motion: Some(MotionCmd(1, Motion::WholeLineInclusive)), motion: Some(MotionCmd(1, Motion::WholeLine)),
raw_seq: self.take_cmd(), raw_seq: self.take_cmd(),
flags: CmdFlags::empty(), flags: CmdFlags::empty(),
}); });
@@ -344,10 +345,10 @@ impl ViVisual {
| ('=', Some(VerbCmd(_, Verb::Equalize))) | ('=', Some(VerbCmd(_, Verb::Equalize)))
| ('>', Some(VerbCmd(_, Verb::Indent))) | ('>', Some(VerbCmd(_, Verb::Indent)))
| ('<', Some(VerbCmd(_, Verb::Dedent))) => { | ('<', 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))) => { ('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( break 'motion_parse Some(MotionCmd(
count, count,
Motion::CharSearch(Direction::Forward, Dest::On, *ch), Motion::CharSearch(Direction::Forward, Dest::On, Grapheme::from(*ch)),
)); ));
} }
'F' => { 'F' => {
@@ -435,7 +436,7 @@ impl ViVisual {
break 'motion_parse Some(MotionCmd( break 'motion_parse Some(MotionCmd(
count, count,
Motion::CharSearch(Direction::Backward, Dest::On, *ch), Motion::CharSearch(Direction::Backward, Dest::On, Grapheme::from(*ch)),
)); ));
} }
't' => { 't' => {
@@ -445,7 +446,7 @@ impl ViVisual {
break 'motion_parse Some(MotionCmd( break 'motion_parse Some(MotionCmd(
count, count,
Motion::CharSearch(Direction::Forward, Dest::Before, *ch), Motion::CharSearch(Direction::Forward, Dest::Before, Grapheme::from(*ch)),
)); ));
} }
'T' => { 'T' => {
@@ -455,7 +456,7 @@ impl ViVisual {
break 'motion_parse Some(MotionCmd( break 'motion_parse Some(MotionCmd(
count, count,
Motion::CharSearch(Direction::Backward, Dest::Before, *ch), Motion::CharSearch(Direction::Backward, Dest::Before, Grapheme::from(*ch)),
)); ));
} }
';' => { ';' => {

View File

@@ -1002,10 +1002,10 @@ impl From<Vec<String>> for Var {
} }
impl From<Vec<Candidate>> for Var { impl From<Vec<Candidate>> for Var {
fn from(value: Vec<Candidate>) -> Self { fn from(value: Vec<Candidate>) -> Self {
let as_strs = value.into_iter().map(|c| c.0).collect::<Vec<_>>(); let as_strs = value.into_iter().map(|c| c.0).collect::<Vec<_>>();
Self::new(VarKind::Arr(as_strs.into()), VarFlags::NONE) Self::new(VarKind::Arr(as_strs.into()), VarFlags::NONE)
} }
} }
impl From<&[String]> for Var { impl From<&[String]> for Var {