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);
|
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(&'\\') |
|
|
||||||
Some(&'\'') => {
|
|
||||||
let ch = chars.next().unwrap();
|
let ch = chars.next().unwrap();
|
||||||
result.push(ch);
|
result.push(ch);
|
||||||
}
|
}
|
||||||
_ => result.push(q_ch),
|
_ => result.push(q_ch),
|
||||||
}
|
},
|
||||||
}
|
|
||||||
'\'' => {
|
'\'' => {
|
||||||
result.push(markers::SNG_QUOTE);
|
result.push(markers::SNG_QUOTE);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -29,6 +29,50 @@ use crate::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Compat shim: replaces the old ClampedUsize type that was removed in the linebuf refactor.
|
||||||
|
/// A simple wrapper around usize with wrapping arithmetic and a max bound.
|
||||||
|
#[derive(Clone, Default, Debug)]
|
||||||
|
pub struct ClampedUsize {
|
||||||
|
val: usize,
|
||||||
|
max: usize,
|
||||||
|
wrap: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClampedUsize {
|
||||||
|
pub fn new(val: usize, max: usize, wrap: bool) -> Self {
|
||||||
|
Self { val, max, wrap }
|
||||||
|
}
|
||||||
|
pub fn get(&self) -> usize {
|
||||||
|
self.val
|
||||||
|
}
|
||||||
|
pub fn set_max(&mut self, max: usize) {
|
||||||
|
self.max = max;
|
||||||
|
if self.val >= self.max && self.max > 0 {
|
||||||
|
self.val = self.max - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn wrap_add(&mut self, n: usize) {
|
||||||
|
if self.max == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if self.wrap {
|
||||||
|
self.val = (self.val + n) % self.max;
|
||||||
|
} else {
|
||||||
|
self.val = (self.val + n).min(self.max.saturating_sub(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn wrap_sub(&mut self, n: usize) {
|
||||||
|
if self.max == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if self.wrap {
|
||||||
|
self.val = (self.val + self.max - (n % self.max)) % self.max;
|
||||||
|
} else {
|
||||||
|
self.val = self.val.saturating_sub(n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Candidate(pub String);
|
pub struct Candidate(pub String);
|
||||||
|
|
||||||
@@ -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;
|
||||||
@@ -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
|
||||||
|
.linebuf
|
||||||
.count_graphemes()
|
.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:?}"
|
||||||
|
|||||||
@@ -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
@@ -341,9 +341,11 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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<_>>>();
|
||||||
@@ -484,7 +486,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()));
|
||||||
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);
|
||||||
@@ -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();
|
||||||
@@ -890,17 +891,15 @@ impl ShedVi {
|
|||||||
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<()> {
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
@@ -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
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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> {
|
||||||
|
|||||||
@@ -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)),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
';' => {
|
';' => {
|
||||||
|
|||||||
@@ -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)),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
';' => {
|
';' => {
|
||||||
|
|||||||
Reference in New Issue
Block a user