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(&'\'') => {
|
||||
let ch = chars.next().unwrap();
|
||||
result.push(ch);
|
||||
}
|
||||
_ => result.push(q_ch),
|
||||
}
|
||||
}
|
||||
'\\' => match chars.peek() {
|
||||
Some(&'\\') | Some(&'\'') => {
|
||||
let ch = chars.next().unwrap();
|
||||
result.push(ch);
|
||||
}
|
||||
_ => result.push(q_ch),
|
||||
},
|
||||
'\'' => {
|
||||
result.push(markers::SNG_QUOTE);
|
||||
break;
|
||||
|
||||
@@ -161,10 +161,10 @@ fn main() -> ExitCode {
|
||||
|
||||
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();
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -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)]
|
||||
pub struct Candidate(pub String);
|
||||
|
||||
impl Eq for Candidate {}
|
||||
|
||||
impl PartialEq for Candidate {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0 == other.0
|
||||
}
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0 == other.0
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Candidate {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Candidate {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.0.cmp(&other.0)
|
||||
}
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.0.cmp(&other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Candidate {
|
||||
fn from(value: String) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
fn from(value: String) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&String> for Candidate {
|
||||
fn from(value: &String) -> Self {
|
||||
Self(value.clone())
|
||||
}
|
||||
fn from(value: &String) -> Self {
|
||||
Self(value.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Candidate {
|
||||
fn from(value: &str) -> Self {
|
||||
Self(value.to_string())
|
||||
}
|
||||
fn from(value: &str) -> Self {
|
||||
Self(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Candidate {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", &self.0)
|
||||
}
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", &self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for Candidate {
|
||||
fn as_ref(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
fn as_ref(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for Candidate {
|
||||
type Target = str;
|
||||
fn deref(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
type Target = str;
|
||||
fn deref(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Candidate {
|
||||
pub fn is_match(&self, other: &str) -> bool {
|
||||
let ignore_case = read_shopts(|o| o.prompt.completion_ignore_case);
|
||||
if ignore_case {
|
||||
let other_lower = other.to_lowercase();
|
||||
let self_lower = self.0.to_lowercase();
|
||||
self_lower.starts_with(&other_lower)
|
||||
} else {
|
||||
self.0.starts_with(other)
|
||||
}
|
||||
}
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
self.0.as_bytes()
|
||||
}
|
||||
pub fn starts_with(&self, pat: char) -> bool {
|
||||
self.0.starts_with(pat)
|
||||
}
|
||||
pub fn strip_prefix(&self, prefix: &str) -> Option<String> {
|
||||
let ignore_case = read_shopts(|o| o.prompt.completion_ignore_case);
|
||||
if ignore_case {
|
||||
let old_len = self.0.len();
|
||||
let prefix_lower = prefix.to_lowercase();
|
||||
let self_lower = self.0.to_lowercase();
|
||||
let stripped = self_lower.strip_prefix(&prefix_lower)?;
|
||||
let new_len = stripped.len();
|
||||
let delta = old_len - new_len;
|
||||
Some(self.0[delta..].to_string())
|
||||
} else {
|
||||
self.0.strip_prefix(prefix).map(|s| s.to_string())
|
||||
}
|
||||
}
|
||||
pub fn is_match(&self, other: &str) -> bool {
|
||||
let ignore_case = read_shopts(|o| o.prompt.completion_ignore_case);
|
||||
if ignore_case {
|
||||
let other_lower = other.to_lowercase();
|
||||
let self_lower = self.0.to_lowercase();
|
||||
self_lower.starts_with(&other_lower)
|
||||
} else {
|
||||
self.0.starts_with(other)
|
||||
}
|
||||
}
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
self.0.as_bytes()
|
||||
}
|
||||
pub fn starts_with(&self, pat: char) -> bool {
|
||||
self.0.starts_with(pat)
|
||||
}
|
||||
pub fn strip_prefix(&self, prefix: &str) -> Option<String> {
|
||||
let ignore_case = read_shopts(|o| o.prompt.completion_ignore_case);
|
||||
if ignore_case {
|
||||
let old_len = self.0.len();
|
||||
let prefix_lower = prefix.to_lowercase();
|
||||
let self_lower = self.0.to_lowercase();
|
||||
let stripped = self_lower.strip_prefix(&prefix_lower)?;
|
||||
let new_len = stripped.len();
|
||||
let delta = old_len - new_len;
|
||||
Some(self.0[delta..].to_string())
|
||||
} else {
|
||||
self.0.strip_prefix(prefix).map(|s| s.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
.to_string()
|
||||
})
|
||||
.map(Candidate::from)
|
||||
.map(Candidate::from)
|
||||
.filter(|s| s.is_match(start))
|
||||
.collect()
|
||||
}
|
||||
@@ -141,8 +185,8 @@ pub fn complete_signals(start: &str) -> Vec<Candidate> {
|
||||
pub fn complete_aliases(start: &str) -> Vec<Candidate> {
|
||||
read_logic(|l| {
|
||||
l.aliases()
|
||||
.keys()
|
||||
.map(Candidate::from)
|
||||
.keys()
|
||||
.map(Candidate::from)
|
||||
.filter(|a| a.is_match(start))
|
||||
.collect()
|
||||
})
|
||||
@@ -155,7 +199,7 @@ pub fn complete_jobs(start: &str) -> Vec<Candidate> {
|
||||
.iter()
|
||||
.filter_map(|j| j.as_ref())
|
||||
.filter_map(|j| j.name())
|
||||
.map(Candidate::from)
|
||||
.map(Candidate::from)
|
||||
.filter(|name| name.is_match(prefix))
|
||||
.map(|name| format!("%{name}").into())
|
||||
.collect()
|
||||
@@ -179,7 +223,7 @@ pub fn complete_users(start: &str) -> Vec<Candidate> {
|
||||
passwd
|
||||
.lines()
|
||||
.filter_map(|line| line.split(':').next())
|
||||
.map(Candidate::from)
|
||||
.map(Candidate::from)
|
||||
.filter(|username| username.is_match(start))
|
||||
.collect()
|
||||
}
|
||||
@@ -199,7 +243,7 @@ pub fn complete_vars(start: &str) -> Vec<Candidate> {
|
||||
.keys()
|
||||
.filter(|k| k.starts_with(&var_name) && *k != &var_name)
|
||||
.map(|k| format!("{prefix}{k}"))
|
||||
.map(Candidate::from)
|
||||
.map(Candidate::from)
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
}
|
||||
@@ -271,7 +315,7 @@ fn complete_commands(start: &str) -> Vec<Candidate> {
|
||||
let mut candidates: Vec<Candidate> = read_meta(|m| {
|
||||
m.cached_cmds()
|
||||
.iter()
|
||||
.map(Candidate::from)
|
||||
.map(Candidate::from)
|
||||
.filter(|c| c.is_match(start))
|
||||
.collect()
|
||||
});
|
||||
@@ -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;
|
||||
@@ -528,11 +570,11 @@ impl BashCompSpec {
|
||||
);
|
||||
exec_input(input, None, false, Some("comp_function".into()))?;
|
||||
|
||||
let comp_reply = read_vars(|v| v.get_arr_elems("COMPREPLY"))
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(Candidate::from)
|
||||
.collect();
|
||||
let comp_reply = read_vars(|v| v.get_arr_elems("COMPREPLY"))
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(Candidate::from)
|
||||
.collect();
|
||||
|
||||
Ok(comp_reply)
|
||||
}
|
||||
@@ -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
|
||||
.count_graphemes()
|
||||
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,23 +341,25 @@ 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
|
||||
}
|
||||
|
||||
/// A mutable reference to the currently focused editor
|
||||
/// This includes the main LineBuf, and sub-editors for modes like Ex mode.
|
||||
pub fn focused_editor(&mut self) -> &mut LineBuf {
|
||||
self.mode.editor().unwrap_or(&mut self.editor)
|
||||
}
|
||||
/// A mutable reference to the currently focused editor
|
||||
/// This includes the main LineBuf, and sub-editors for modes like Ex mode.
|
||||
pub fn focused_editor(&mut self) -> &mut LineBuf {
|
||||
self.mode.editor().unwrap_or(&mut self.editor)
|
||||
}
|
||||
|
||||
/// A mutable reference to the currently focused history, if any.
|
||||
/// This includes the main history struct, and history for sub-editors like Ex mode.
|
||||
pub fn focused_history(&mut self) -> &mut History {
|
||||
self.mode.history().unwrap_or(&mut self.history)
|
||||
}
|
||||
/// A mutable reference to the currently focused history, if any.
|
||||
/// This includes the main history struct, and history for sub-editors like Ex mode.
|
||||
pub fn focused_history(&mut self) -> &mut History {
|
||||
self.mode.history().unwrap_or(&mut self.history)
|
||||
}
|
||||
|
||||
/// Feed raw bytes from stdin into the reader's buffer
|
||||
pub fn feed_bytes(&mut self, bytes: &[u8]) {
|
||||
@@ -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<_>>>();
|
||||
@@ -476,21 +478,21 @@ impl ShedVi {
|
||||
SelectorResponse::Accept(cmd) => {
|
||||
let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnHistorySelect));
|
||||
|
||||
{
|
||||
let editor = self.focused_editor();
|
||||
editor.set_buffer(cmd.to_string());
|
||||
editor.move_cursor_to_end();
|
||||
}
|
||||
{
|
||||
let editor = self.focused_editor();
|
||||
editor.set_buffer(cmd.to_string());
|
||||
editor.move_cursor_to_end();
|
||||
}
|
||||
|
||||
self
|
||||
.history
|
||||
.update_pending_cmd((self.editor.as_str(), self.editor.cursor.get()));
|
||||
self.editor.set_hint(None);
|
||||
{
|
||||
let mut writer = std::mem::take(&mut self.writer);
|
||||
self.focused_history().fuzzy_finder.clear(&mut writer)?;
|
||||
self.writer = writer;
|
||||
}
|
||||
.update_pending_cmd((&self.editor.joined(), self.editor.cursor.get()));
|
||||
self.editor.set_hint(None);
|
||||
{
|
||||
let mut writer = std::mem::take(&mut self.writer);
|
||||
self.focused_history().fuzzy_finder.clear(&mut writer)?;
|
||||
self.writer = writer;
|
||||
}
|
||||
self.focused_history().fuzzy_finder.reset();
|
||||
|
||||
with_vars([("_HIST_ENTRY".into(), cmd.clone())], || {
|
||||
@@ -514,11 +516,11 @@ impl ShedVi {
|
||||
post_cmds.exec();
|
||||
|
||||
self.editor.set_hint(None);
|
||||
{
|
||||
let mut writer = std::mem::take(&mut self.writer);
|
||||
self.focused_history().fuzzy_finder.clear(&mut writer)?;
|
||||
self.writer = writer;
|
||||
}
|
||||
{
|
||||
let mut writer = std::mem::take(&mut self.writer);
|
||||
self.focused_history().fuzzy_finder.clear(&mut writer)?;
|
||||
self.writer = writer;
|
||||
}
|
||||
write_vars(|v| {
|
||||
v.set_var(
|
||||
"SHED_VI_MODE",
|
||||
@@ -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) {
|
||||
if self.mode.report_mode() != ModeReport::Ex
|
||||
&& 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();
|
||||
@@ -884,23 +885,21 @@ impl ShedVi {
|
||||
return Ok(None);
|
||||
}
|
||||
} 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 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<()> {
|
||||
@@ -1035,11 +1035,11 @@ impl ShedVi {
|
||||
let one_line = new_layout.end.row == 0;
|
||||
|
||||
self.completer.clear(&mut self.writer)?;
|
||||
{
|
||||
let mut writer = std::mem::take(&mut self.writer);
|
||||
self.focused_history().fuzzy_finder.clear(&mut writer)?;
|
||||
self.writer = writer;
|
||||
}
|
||||
{
|
||||
let mut writer = std::mem::take(&mut self.writer);
|
||||
self.focused_history().fuzzy_finder.clear(&mut writer)?;
|
||||
self.writer = writer;
|
||||
}
|
||||
|
||||
if let Some(layout) = self.old_layout.as_ref() {
|
||||
self.writer.clear_rows(layout)?;
|
||||
@@ -1136,11 +1136,11 @@ impl ShedVi {
|
||||
.fuzzy_finder
|
||||
.set_prompt_line_context(preceding_width, new_layout.cursor.col);
|
||||
|
||||
{
|
||||
let mut writer = std::mem::take(&mut self.writer);
|
||||
self.focused_history().fuzzy_finder.draw(&mut writer)?;
|
||||
self.writer = writer;
|
||||
}
|
||||
{
|
||||
let mut writer = std::mem::take(&mut self.writer);
|
||||
self.focused_history().fuzzy_finder.draw(&mut writer)?;
|
||||
self.writer = writer;
|
||||
}
|
||||
|
||||
self.old_layout = Some(new_layout);
|
||||
self.needs_redraw = false;
|
||||
@@ -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 {
|
||||
@@ -249,7 +247,7 @@ pub enum Verb {
|
||||
Read(ReadSrc),
|
||||
Write(WriteDest),
|
||||
Edit(PathBuf),
|
||||
Quit,
|
||||
Quit,
|
||||
Substitute(String, String, SubFlags),
|
||||
RepeatSubstitute,
|
||||
RepeatGlobal,
|
||||
@@ -296,9 +294,9 @@ impl Verb {
|
||||
| Self::JoinLines
|
||||
| Self::InsertChar(_)
|
||||
| Self::Insert(_)
|
||||
| Self::Dedent
|
||||
| Self::Indent
|
||||
| Self::Equalize
|
||||
| Self::Dedent
|
||||
| Self::Indent
|
||||
| Self::Equalize
|
||||
| Self::Rot13
|
||||
| Self::EndOfFile
|
||||
| Self::IncrementNumber(_)
|
||||
@@ -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> {
|
||||
@@ -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 "yank".starts_with(&cmd_name) => Ok(Some(Verb::Yank)),
|
||||
_ 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 "write".starts_with(&cmd_name) => parse_write(chars),
|
||||
_ if "edit".starts_with(&cmd_name) => parse_edit(chars),
|
||||
|
||||
@@ -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)),
|
||||
));
|
||||
}
|
||||
';' => {
|
||||
|
||||
@@ -1002,10 +1002,10 @@ impl From<Vec<String>> for Var {
|
||||
}
|
||||
|
||||
impl From<Vec<Candidate>> for Var {
|
||||
fn from(value: Vec<Candidate>) -> Self {
|
||||
let as_strs = value.into_iter().map(|c| c.0).collect::<Vec<_>>();
|
||||
Self::new(VarKind::Arr(as_strs.into()), VarFlags::NONE)
|
||||
}
|
||||
fn from(value: Vec<Candidate>) -> Self {
|
||||
let as_strs = value.into_iter().map(|c| c.0).collect::<Vec<_>>();
|
||||
Self::new(VarKind::Arr(as_strs.into()), VarFlags::NONE)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&[String]> for Var {
|
||||
|
||||
Reference in New Issue
Block a user