implemented more vi commands
implemented replace mode re-implemented undo and redo
This commit is contained in:
@@ -10,23 +10,23 @@ use crate::{expand::expand_prompt, libsh::error::ShResult, prelude::*, shopt::Fe
|
|||||||
/// Initialize the line editor
|
/// Initialize the line editor
|
||||||
fn get_prompt() -> ShResult<String> {
|
fn get_prompt() -> ShResult<String> {
|
||||||
let Ok(prompt) = env::var("PS1") else {
|
let Ok(prompt) = env::var("PS1") else {
|
||||||
|
// prompt expands to:
|
||||||
|
//
|
||||||
// username@hostname
|
// username@hostname
|
||||||
// short/path/to/pwd/
|
// short/path/to/pwd/
|
||||||
// $
|
// $
|
||||||
let default = "\\e[1;0m\\u\\e[1;36m@\\e[1;31m\\h\\n\\e[1;36m\\W\\e[1;32m/\\n\\e[1;32m\\$\\e[0m ";
|
let default = "\\n\\e[1;0m\\u\\e[1;36m@\\e[1;31m\\h\\n\\e[1;36m\\W\\e[1;32m/\\n\\e[1;32m\\$\\e[0m ";
|
||||||
return Ok(format!("\n{}",expand_prompt(default)?))
|
return Ok(format!("{}",expand_prompt(default)?))
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(format!("\n{}",expand_prompt(&prompt)?))
|
Ok(format!("\n{}",expand_prompt(&prompt)?))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_line(edit_mode: FernEditMode) -> ShResult<String> {
|
pub fn read_line(edit_mode: FernEditMode) -> ShResult<String> {
|
||||||
dbg!("hi");
|
|
||||||
let prompt = get_prompt()?;
|
let prompt = get_prompt()?;
|
||||||
let mut reader: Box<dyn Readline> = match edit_mode {
|
let mut reader: Box<dyn Readline> = match edit_mode {
|
||||||
FernEditMode::Vi => Box::new(FernVi::new(Some(prompt))),
|
FernEditMode::Vi => Box::new(FernVi::new(Some(prompt))),
|
||||||
FernEditMode::Emacs => todo!()
|
FernEditMode::Emacs => todo!()
|
||||||
};
|
};
|
||||||
dbg!("there");
|
|
||||||
reader.readline()
|
reader.readline()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ pub struct Edit {
|
|||||||
pub cursor_pos: usize,
|
pub cursor_pos: usize,
|
||||||
pub old: String,
|
pub old: String,
|
||||||
pub new: String,
|
pub new: String,
|
||||||
|
pub merging: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Edit {
|
impl Edit {
|
||||||
@@ -111,6 +112,7 @@ impl Edit {
|
|||||||
cursor_pos: old_cursor_pos,
|
cursor_pos: old_cursor_pos,
|
||||||
old: String::new(),
|
old: String::new(),
|
||||||
new: String::new(),
|
new: String::new(),
|
||||||
|
merging: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,8 +134,15 @@ impl Edit {
|
|||||||
cursor_pos: old_cursor_pos,
|
cursor_pos: old_cursor_pos,
|
||||||
old,
|
old,
|
||||||
new,
|
new,
|
||||||
|
merging: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn start_merge(&mut self) {
|
||||||
|
self.merging = true
|
||||||
|
}
|
||||||
|
pub fn stop_merge(&mut self) {
|
||||||
|
self.merging = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default,Debug)]
|
#[derive(Default,Debug)]
|
||||||
@@ -143,7 +152,7 @@ pub struct LineBuf {
|
|||||||
clamp_cursor: bool,
|
clamp_cursor: bool,
|
||||||
first_line_offset: usize,
|
first_line_offset: usize,
|
||||||
saved_col: Option<usize>,
|
saved_col: Option<usize>,
|
||||||
merge_edit: bool,
|
move_cursor_on_undo: bool,
|
||||||
undo_stack: Vec<Edit>,
|
undo_stack: Vec<Edit>,
|
||||||
redo_stack: Vec<Edit>,
|
redo_stack: Vec<Edit>,
|
||||||
}
|
}
|
||||||
@@ -176,11 +185,13 @@ impl LineBuf {
|
|||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.buffer.is_empty()
|
self.buffer.is_empty()
|
||||||
}
|
}
|
||||||
|
pub fn set_move_cursor_on_undo(&mut self, yn: bool) {
|
||||||
|
self.move_cursor_on_undo = yn;
|
||||||
|
}
|
||||||
pub fn clamp_cursor(&mut self) {
|
pub fn clamp_cursor(&mut self) {
|
||||||
// Normal mode does not allow you to sit on the edge of the buffer, you must be hovering over a character
|
// Normal mode does not allow you to sit on the edge of the buffer, you must be hovering over a character
|
||||||
// Insert mode does let you set on the edge though, so that you can append new characters
|
// Insert mode does let you set on the edge though, so that you can append new characters
|
||||||
// This method is used in Normal mode
|
// This method is used in Normal mode
|
||||||
dbg!("clamping");
|
|
||||||
if self.cursor == self.byte_len() || self.grapheme_at_cursor() == Some("\n") {
|
if self.cursor == self.byte_len() || self.grapheme_at_cursor() == Some("\n") {
|
||||||
self.cursor_back(1);
|
self.cursor_back(1);
|
||||||
}
|
}
|
||||||
@@ -417,11 +428,8 @@ impl LineBuf {
|
|||||||
if self.start_of_line() == 0 {
|
if self.start_of_line() == 0 {
|
||||||
return None
|
return None
|
||||||
};
|
};
|
||||||
let mut col = self.saved_col.unwrap_or(self.cursor_column());
|
let col = self.saved_col.unwrap_or(self.cursor_column());
|
||||||
let line = self.line_no();
|
let line = self.line_no();
|
||||||
if line == 1 {
|
|
||||||
col = col.saturating_sub(self.first_line_offset.saturating_sub(1))
|
|
||||||
}
|
|
||||||
if self.saved_col.is_none() {
|
if self.saved_col.is_none() {
|
||||||
self.saved_col = Some(col);
|
self.saved_col = Some(col);
|
||||||
}
|
}
|
||||||
@@ -432,11 +440,8 @@ impl LineBuf {
|
|||||||
if self.end_of_line() == self.byte_len() {
|
if self.end_of_line() == self.byte_len() {
|
||||||
return None
|
return None
|
||||||
};
|
};
|
||||||
let mut col = self.saved_col.unwrap_or(self.cursor_column());
|
let col = self.saved_col.unwrap_or(self.cursor_column());
|
||||||
let line = self.line_no();
|
let line = self.line_no();
|
||||||
if line == 0 {
|
|
||||||
col += self.first_line_offset.saturating_sub(1);
|
|
||||||
}
|
|
||||||
if self.saved_col.is_none() {
|
if self.saved_col.is_none() {
|
||||||
self.saved_col = Some(col);
|
self.saved_col = Some(col);
|
||||||
}
|
}
|
||||||
@@ -668,9 +673,7 @@ impl LineBuf {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
false => {
|
false => {
|
||||||
let last_ws = self.rfind_from(pos, |c| CharClass::from(c) == CharClass::Whitespace)?;
|
match self.rfind_from(pos, |c| CharClass::from(c) == CharClass::Whitespace) {
|
||||||
let prev_word_end = self.rfind_from(last_ws, |c| CharClass::from(c) != CharClass::Whitespace)?;
|
|
||||||
match self.rfind_from(prev_word_end, |c| CharClass::from(c) == CharClass::Whitespace) {
|
|
||||||
Some(n) => Some(n + 1), // Land on char after whitespace
|
Some(n) => Some(n + 1), // Land on char after whitespace
|
||||||
None => Some(0) // Start of buffer
|
None => Some(0) // Start of buffer
|
||||||
}
|
}
|
||||||
@@ -750,24 +753,23 @@ impl LineBuf {
|
|||||||
Direction::Backward => {
|
Direction::Backward => {
|
||||||
match to {
|
match to {
|
||||||
To::Start => {
|
To::Start => {
|
||||||
if self.on_start_of_word(word) {
|
match self.on_start_of_word(word) {
|
||||||
|
true => {
|
||||||
pos = pos.checked_sub(1)?;
|
pos = pos.checked_sub(1)?;
|
||||||
}
|
let prev_word_end = self.rfind_from(pos, |c| CharClass::from(c) != CharClass::Whitespace)?;
|
||||||
let cur_graph = self.grapheme_at(pos)?;
|
|
||||||
let diff_class_pos = self.rfind_from(pos, |c| is_other_class_or_ws(c, cur_graph))?;
|
|
||||||
if let CharClass::Whitespace = self.grapheme_at(diff_class_pos)?.into() {
|
|
||||||
let prev_word_end = self.rfind_from(diff_class_pos, |c| CharClass::from(c) != CharClass::Whitespace)?;
|
|
||||||
let cur_graph = self.grapheme_at(prev_word_end)?;
|
let cur_graph = self.grapheme_at(prev_word_end)?;
|
||||||
let Some(prev_word_start) = self.rfind_from(prev_word_end, |c| is_other_class_or_ws(c, cur_graph)) else {
|
match self.rfind_from(prev_word_end, |c| is_other_class_or_ws(c, cur_graph)) {
|
||||||
return Some(0)
|
Some(n) => Some(n + 1), // Land on char after whitespace
|
||||||
};
|
None => Some(0) // Start of buffer
|
||||||
Some(prev_word_start + 1)
|
}
|
||||||
} else {
|
}
|
||||||
let cur_graph = self.grapheme_at(diff_class_pos)?;
|
false => {
|
||||||
let Some(prev_word_start) = self.rfind_from(diff_class_pos, |c| is_other_class_or_ws(c, cur_graph)) else {
|
let cur_graph = self.grapheme_at(pos)?;
|
||||||
return Some(0)
|
match self.rfind_from(pos, |c| is_other_class_or_ws(c, cur_graph)) {
|
||||||
};
|
Some(n) => Some(n + 1), // Land on char after whitespace
|
||||||
Some(prev_word_start + 1)
|
None => Some(0) // Start of buffer
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
To::End => {
|
To::End => {
|
||||||
@@ -879,18 +881,8 @@ impl LineBuf {
|
|||||||
}
|
}
|
||||||
Motion::BackwardChar => MotionKind::Backward(1),
|
Motion::BackwardChar => MotionKind::Backward(1),
|
||||||
Motion::ForwardChar => MotionKind::Forward(1),
|
Motion::ForwardChar => MotionKind::Forward(1),
|
||||||
Motion::LineUp => {
|
Motion::LineUp => MotionKind::Line(-1),
|
||||||
match self.find_prev_line_pos() {
|
Motion::LineDown => MotionKind::Line(1),
|
||||||
None => MotionKind::Null,
|
|
||||||
Some(pos) => MotionKind::To(pos)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Motion::LineDown => {
|
|
||||||
match self.find_next_line_pos() {
|
|
||||||
None => MotionKind::Null,
|
|
||||||
Some(pos) => MotionKind::To(pos)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Motion::WholeBuffer => todo!(),
|
Motion::WholeBuffer => todo!(),
|
||||||
Motion::BeginningOfBuffer => MotionKind::To(0),
|
Motion::BeginningOfBuffer => MotionKind::To(0),
|
||||||
Motion::EndOfBuffer => MotionKind::To(self.byte_len()),
|
Motion::EndOfBuffer => MotionKind::To(self.byte_len()),
|
||||||
@@ -910,62 +902,63 @@ impl LineBuf {
|
|||||||
Motion::Null => todo!(),
|
Motion::Null => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn exec_verb(&mut self, verb: Verb, motion: MotionKind, register: RegisterName) -> ShResult<()> {
|
pub fn get_range_from_motion(&self, verb: &Verb, motion: &MotionKind) -> Option<Range<usize>> {
|
||||||
match verb {
|
match motion {
|
||||||
Verb::Change |
|
|
||||||
Verb::Delete => {
|
|
||||||
let deleted = match motion {
|
|
||||||
MotionKind::Forward(n) => {
|
MotionKind::Forward(n) => {
|
||||||
let Some(pos) = self.next_pos(n) else {
|
let pos = self.next_pos(*n)?;
|
||||||
return Ok(())
|
|
||||||
};
|
|
||||||
let range = self.cursor..pos;
|
let range = self.cursor..pos;
|
||||||
assert!(range.end < self.byte_len());
|
assert!(range.end <= self.byte_len());
|
||||||
self.buffer.drain(range)
|
Some(range)
|
||||||
}
|
}
|
||||||
MotionKind::To(n) => {
|
MotionKind::To(n) => {
|
||||||
let range = mk_range(self.cursor, n);
|
let range = mk_range(self.cursor, *n);
|
||||||
assert!(range.end < self.byte_len());
|
assert!(range.end <= self.byte_len());
|
||||||
self.buffer.drain(range)
|
Some(range)
|
||||||
}
|
}
|
||||||
MotionKind::Backward(n) => {
|
MotionKind::Backward(n) => {
|
||||||
let Some(back) = self.prev_pos(n) else {
|
let pos = self.prev_pos(*n)?;
|
||||||
return Ok(())
|
let range = pos..self.cursor;
|
||||||
};
|
Some(range)
|
||||||
let range = back..self.cursor;
|
|
||||||
dbg!(&range);
|
|
||||||
self.buffer.drain(range)
|
|
||||||
}
|
}
|
||||||
MotionKind::Range(range) => {
|
MotionKind::Range(range) => {
|
||||||
self.buffer.drain(range.0..range.1)
|
Some(range.0..range.1)
|
||||||
}
|
}
|
||||||
MotionKind::Line(n) => {
|
MotionKind::Line(n) => {
|
||||||
let (start,end) = match n.cmp(&0) {
|
let (start,end) = match n.cmp(&0) {
|
||||||
Ordering::Less => self.select_lines_up(n.unsigned_abs()),
|
Ordering::Less => self.select_lines_up(n.unsigned_abs()),
|
||||||
Ordering::Equal => self.this_line(),
|
Ordering::Equal => self.this_line(),
|
||||||
Ordering::Greater => self.select_lines_down(n as usize)
|
Ordering::Greater => self.select_lines_down(*n as usize)
|
||||||
};
|
};
|
||||||
let range = match verb {
|
let range = match verb {
|
||||||
Verb::Change => start..end,
|
Verb::Change => mk_range(start,end),
|
||||||
Verb::Delete => start..end.saturating_add(1),
|
Verb::Delete => mk_range(start,(end + 1).min(self.byte_len())),
|
||||||
_ => unreachable!()
|
_ => unreachable!()
|
||||||
};
|
};
|
||||||
self.buffer.drain(range)
|
Some(range)
|
||||||
}
|
}
|
||||||
MotionKind::ToLine(n) => {
|
MotionKind::ToLine(n) => {
|
||||||
let (start,end) = self.select_lines_to(n);
|
let (start,end) = self.select_lines_to(*n);
|
||||||
let range = match verb {
|
let range = match verb {
|
||||||
Verb::Change => start..end,
|
Verb::Change => start..end,
|
||||||
Verb::Delete => start..end.saturating_add(1),
|
Verb::Delete => start..end.saturating_add(1),
|
||||||
_ => unreachable!()
|
_ => unreachable!()
|
||||||
};
|
};
|
||||||
self.buffer.drain(range)
|
Some(range)
|
||||||
}
|
}
|
||||||
MotionKind::Null => return Ok(()),
|
MotionKind::Null => return None,
|
||||||
MotionKind::ToScreenPos(n) => todo!(),
|
MotionKind::ToScreenPos(n) => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn exec_verb(&mut self, verb: Verb, motion: MotionKind, register: RegisterName) -> ShResult<()> {
|
||||||
|
match verb {
|
||||||
|
Verb::Change |
|
||||||
|
Verb::Delete => {
|
||||||
|
let Some(range) = self.get_range_from_motion(&verb, &motion) else {
|
||||||
|
return Ok(())
|
||||||
};
|
};
|
||||||
|
let deleted = self.buffer.drain(range.clone());
|
||||||
register.write_to_register(deleted.collect());
|
register.write_to_register(deleted.collect());
|
||||||
self.apply_motion(motion);
|
self.cursor = range.start;
|
||||||
}
|
}
|
||||||
Verb::DeleteChar(anchor) => {
|
Verb::DeleteChar(anchor) => {
|
||||||
match anchor {
|
match anchor {
|
||||||
@@ -981,16 +974,94 @@ impl LineBuf {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Verb::Yank => todo!(),
|
Verb::Yank => {
|
||||||
Verb::ReplaceChar(_) => todo!(),
|
let Some(range) = self.get_range_from_motion(&verb, &motion) else {
|
||||||
|
return Ok(())
|
||||||
|
};
|
||||||
|
let yanked = &self.buffer[range.clone()];
|
||||||
|
register.write_to_register(yanked.to_string());
|
||||||
|
self.cursor = range.start;
|
||||||
|
}
|
||||||
|
Verb::ReplaceChar(c) => {
|
||||||
|
let Some(range) = self.get_range_from_motion(&verb, &motion) else {
|
||||||
|
return Ok(())
|
||||||
|
};
|
||||||
|
let new_range = format!("{c}");
|
||||||
|
let cursor_pos = range.end;
|
||||||
|
self.buffer.replace_range(range, &new_range);
|
||||||
|
self.cursor = cursor_pos
|
||||||
|
}
|
||||||
Verb::Substitute => todo!(),
|
Verb::Substitute => todo!(),
|
||||||
Verb::ToggleCase => todo!(),
|
Verb::ToggleCase => {
|
||||||
|
let Some(range) = self.get_range_from_motion(&verb, &motion) else {
|
||||||
|
return Ok(())
|
||||||
|
};
|
||||||
|
let mut new_range = String::new();
|
||||||
|
let slice = &self.buffer[range.clone()];
|
||||||
|
for ch in slice.chars() {
|
||||||
|
if ch.is_ascii_lowercase() {
|
||||||
|
new_range.push(ch.to_ascii_uppercase())
|
||||||
|
} else if ch.is_ascii_uppercase() {
|
||||||
|
new_range.push(ch.to_ascii_lowercase())
|
||||||
|
} else {
|
||||||
|
new_range.push(ch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.buffer.replace_range(range.clone(), &new_range);
|
||||||
|
self.cursor = range.end;
|
||||||
|
}
|
||||||
Verb::Complete => todo!(),
|
Verb::Complete => todo!(),
|
||||||
Verb::CompleteBackward => todo!(),
|
Verb::CompleteBackward => todo!(),
|
||||||
Verb::Undo => todo!(),
|
Verb::Undo => {
|
||||||
Verb::Redo => todo!(),
|
let Some(undo) = self.undo_stack.pop() else {
|
||||||
|
return Ok(())
|
||||||
|
};
|
||||||
|
flog!(DEBUG, undo);
|
||||||
|
let Edit { pos, cursor_pos, old, new, .. } = undo;
|
||||||
|
let range = pos..pos + new.len();
|
||||||
|
self.buffer.replace_range(range, &old);
|
||||||
|
let redo_cursor_pos = self.cursor;
|
||||||
|
if self.move_cursor_on_undo {
|
||||||
|
self.cursor = cursor_pos;
|
||||||
|
}
|
||||||
|
let redo = Edit { pos, cursor_pos: redo_cursor_pos, old: new, new: old, merging: false };
|
||||||
|
self.redo_stack.push(redo);
|
||||||
|
}
|
||||||
|
Verb::Redo => {
|
||||||
|
let Some(redo) = self.redo_stack.pop() else {
|
||||||
|
return Ok(())
|
||||||
|
};
|
||||||
|
flog!(DEBUG, redo);
|
||||||
|
let Edit { pos, cursor_pos, old, new, .. } = redo;
|
||||||
|
let range = pos..pos + new.len();
|
||||||
|
self.buffer.replace_range(range, &old);
|
||||||
|
let undo_cursor_pos = self.cursor;
|
||||||
|
if self.move_cursor_on_undo {
|
||||||
|
self.cursor = cursor_pos;
|
||||||
|
}
|
||||||
|
let undo = Edit { pos, cursor_pos: undo_cursor_pos, old: new, new: old, merging: false };
|
||||||
|
self.undo_stack.push(undo);
|
||||||
|
}
|
||||||
Verb::RepeatLast => todo!(),
|
Verb::RepeatLast => todo!(),
|
||||||
Verb::Put(anchor) => todo!(),
|
Verb::Put(anchor) => {
|
||||||
|
let Some(register_content) = register.read_from_register() else {
|
||||||
|
return Ok(())
|
||||||
|
};
|
||||||
|
match anchor {
|
||||||
|
Anchor::After => {
|
||||||
|
for ch in register_content.chars() {
|
||||||
|
self.cursor_fwd(1); // Only difference is which one you start with
|
||||||
|
self.insert(ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Anchor::Before => {
|
||||||
|
for ch in register_content.chars() {
|
||||||
|
self.insert(ch);
|
||||||
|
self.cursor_fwd(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Verb::InsertModeLineBreak(anchor) => {
|
Verb::InsertModeLineBreak(anchor) => {
|
||||||
match anchor {
|
match anchor {
|
||||||
Anchor::After => {
|
Anchor::After => {
|
||||||
@@ -1015,12 +1086,19 @@ impl LineBuf {
|
|||||||
Verb::Breakline(anchor) => todo!(),
|
Verb::Breakline(anchor) => todo!(),
|
||||||
Verb::Indent => todo!(),
|
Verb::Indent => todo!(),
|
||||||
Verb::Dedent => todo!(),
|
Verb::Dedent => todo!(),
|
||||||
Verb::Equalize => todo!(),
|
Verb::Equalize => todo!(), // I fear this one
|
||||||
Verb::AcceptLine => todo!(),
|
|
||||||
Verb::Builder(verb_builder) => todo!(),
|
Verb::Builder(verb_builder) => todo!(),
|
||||||
Verb::EndOfFile => todo!(),
|
Verb::EndOfFile => {
|
||||||
|
if !self.buffer.is_empty() {
|
||||||
|
self.cursor = 0;
|
||||||
|
self.buffer.clear();
|
||||||
|
} else {
|
||||||
|
sh_quit(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Verb::OverwriteMode |
|
Verb::AcceptLine |
|
||||||
|
Verb::ReplaceMode |
|
||||||
Verb::InsertMode |
|
Verb::InsertMode |
|
||||||
Verb::NormalMode |
|
Verb::NormalMode |
|
||||||
Verb::VisualMode => {
|
Verb::VisualMode => {
|
||||||
@@ -1031,7 +1109,6 @@ impl LineBuf {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn apply_motion(&mut self, motion: MotionKind) {
|
pub fn apply_motion(&mut self, motion: MotionKind) {
|
||||||
dbg!(&motion);
|
|
||||||
match motion {
|
match motion {
|
||||||
MotionKind::Forward(n) => {
|
MotionKind::Forward(n) => {
|
||||||
for _ in 0..n {
|
for _ in 0..n {
|
||||||
@@ -1048,9 +1125,12 @@ impl LineBuf {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
MotionKind::To(n) => {
|
MotionKind::To(n) => {
|
||||||
assert!((0..=self.byte_len()).contains(&n));
|
if n > self.byte_len() {
|
||||||
|
self.cursor = self.byte_len();
|
||||||
|
} else {
|
||||||
self.cursor = n
|
self.cursor = n
|
||||||
}
|
}
|
||||||
|
}
|
||||||
MotionKind::Range(range) => {
|
MotionKind::Range(range) => {
|
||||||
assert!((0..self.byte_len()).contains(&range.0));
|
assert!((0..self.byte_len()).contains(&range.0));
|
||||||
if self.cursor != range.0 {
|
if self.cursor != range.0 {
|
||||||
@@ -1085,8 +1165,11 @@ impl LineBuf {
|
|||||||
MotionKind::ToScreenPos(_) => todo!(),
|
MotionKind::ToScreenPos(_) => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn edit_is_merging(&self) -> bool {
|
||||||
|
self.undo_stack.last().is_some_and(|edit| edit.merging)
|
||||||
|
}
|
||||||
pub fn handle_edit(&mut self, old: String, new: String, curs_pos: usize) {
|
pub fn handle_edit(&mut self, old: String, new: String, curs_pos: usize) {
|
||||||
if self.merge_edit {
|
if self.edit_is_merging() {
|
||||||
let diff = Edit::diff(&old, &new, curs_pos);
|
let diff = Edit::diff(&old, &new, curs_pos);
|
||||||
let Some(mut edit) = self.undo_stack.pop() else {
|
let Some(mut edit) = self.undo_stack.pop() else {
|
||||||
self.undo_stack.push(diff);
|
self.undo_stack.push(diff);
|
||||||
@@ -1094,6 +1177,7 @@ impl LineBuf {
|
|||||||
};
|
};
|
||||||
|
|
||||||
edit.new.push_str(&diff.new);
|
edit.new.push_str(&diff.new);
|
||||||
|
edit.old.push_str(&diff.old);
|
||||||
|
|
||||||
self.undo_stack.push(edit);
|
self.undo_stack.push(edit);
|
||||||
} else {
|
} else {
|
||||||
@@ -1102,15 +1186,16 @@ impl LineBuf {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn exec_cmd(&mut self, cmd: ViCmd) -> ShResult<()> {
|
pub fn exec_cmd(&mut self, cmd: ViCmd) -> ShResult<()> {
|
||||||
flog!(DEBUG, cmd);
|
|
||||||
let clear_redos = !cmd.is_undo_op() || cmd.verb.as_ref().is_some_and(|v| v.1.is_edit());
|
let clear_redos = !cmd.is_undo_op() || cmd.verb.as_ref().is_some_and(|v| v.1.is_edit());
|
||||||
let is_char_insert = cmd.verb.as_ref().is_some_and(|v| v.1.is_char_insert());
|
let is_char_insert = cmd.verb.as_ref().is_some_and(|v| v.1.is_char_insert());
|
||||||
let is_line_motion = cmd.is_line_motion();
|
let is_line_motion = cmd.is_line_motion();
|
||||||
let is_undo_op = cmd.is_undo_op();
|
let is_undo_op = cmd.is_undo_op();
|
||||||
|
|
||||||
// Merge character inserts into one edit
|
// Merge character inserts into one edit
|
||||||
if self.merge_edit && cmd.verb.as_ref().is_none_or(|v| !v.1.is_char_insert()) {
|
if self.edit_is_merging() && cmd.verb.as_ref().is_none_or(|v| !v.1.is_char_insert()) {
|
||||||
self.merge_edit = false;
|
if let Some(edit) = self.undo_stack.last_mut() {
|
||||||
|
edit.stop_merge();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let ViCmd { register, verb, motion, .. } = cmd;
|
let ViCmd { register, verb, motion, .. } = cmd;
|
||||||
@@ -1150,7 +1235,9 @@ impl LineBuf {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if is_char_insert {
|
if is_char_insert {
|
||||||
self.merge_edit = true;
|
if let Some(edit) = self.undo_stack.last_mut() {
|
||||||
|
edit.start_merge();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.clamp_cursor {
|
if self.clamp_cursor {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use linebuf::{strip_ansi_codes_and_escapes, LineBuf};
|
use linebuf::{strip_ansi_codes_and_escapes, LineBuf};
|
||||||
use mode::{CmdReplay, ViInsert, ViMode, ViNormal};
|
use mode::{CmdReplay, ViInsert, ViMode, ViNormal, ViReplace};
|
||||||
use term::Terminal;
|
use term::Terminal;
|
||||||
use unicode_width::UnicodeWidthStr;
|
use unicode_width::UnicodeWidthStr;
|
||||||
use vicmd::{Motion, MotionCmd, RegisterName, Verb, VerbCmd, ViCmd};
|
use vicmd::{Motion, MotionCmd, RegisterName, Verb, VerbCmd, ViCmd};
|
||||||
@@ -60,6 +60,7 @@ impl Readline for FernVi {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if cmd.should_submit() {
|
if cmd.should_submit() {
|
||||||
|
self.term.write("\n");
|
||||||
return Ok(self.line.to_string());
|
return Ok(self.line.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,20 +111,23 @@ impl FernVi {
|
|||||||
let count = cmd.verb_count();
|
let count = cmd.verb_count();
|
||||||
let mut mode: Box<dyn ViMode> = match cmd.verb().unwrap().1 {
|
let mut mode: Box<dyn ViMode> = match cmd.verb().unwrap().1 {
|
||||||
Verb::InsertModeLineBreak(_) |
|
Verb::InsertModeLineBreak(_) |
|
||||||
|
Verb::Change |
|
||||||
Verb::InsertMode => {
|
Verb::InsertMode => {
|
||||||
self.line.set_cursor_clamp(false);
|
|
||||||
Box::new(ViInsert::new().with_count(count as u16))
|
Box::new(ViInsert::new().with_count(count as u16))
|
||||||
}
|
}
|
||||||
Verb::NormalMode => {
|
Verb::NormalMode => {
|
||||||
self.line.set_cursor_clamp(true);
|
|
||||||
Box::new(ViNormal::new())
|
Box::new(ViNormal::new())
|
||||||
}
|
}
|
||||||
|
Verb::ReplaceMode => {
|
||||||
|
Box::new(ViReplace::new().with_count(count as u16))
|
||||||
|
}
|
||||||
Verb::VisualMode => todo!(),
|
Verb::VisualMode => todo!(),
|
||||||
Verb::OverwriteMode => todo!(),
|
|
||||||
_ => unreachable!()
|
_ => unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
std::mem::swap(&mut mode, &mut self.mode);
|
std::mem::swap(&mut mode, &mut self.mode);
|
||||||
|
self.line.set_cursor_clamp(self.mode.clamp_cursor());
|
||||||
|
self.line.set_move_cursor_on_undo(self.mode.move_cursor_on_undo());
|
||||||
self.term.write(&mode.cursor_style());
|
self.term.write(&mode.cursor_style());
|
||||||
|
|
||||||
if mode.is_repeatable() {
|
if mode.is_repeatable() {
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ pub trait ViMode {
|
|||||||
fn as_replay(&self) -> Option<CmdReplay>;
|
fn as_replay(&self) -> Option<CmdReplay>;
|
||||||
fn cursor_style(&self) -> String;
|
fn cursor_style(&self) -> String;
|
||||||
fn pending_seq(&self) -> Option<String>;
|
fn pending_seq(&self) -> Option<String>;
|
||||||
|
fn move_cursor_on_undo(&self) -> bool;
|
||||||
|
fn clamp_cursor(&self) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default,Debug)]
|
#[derive(Default,Debug)]
|
||||||
@@ -58,7 +60,16 @@ impl ViInsert {
|
|||||||
pub fn register_and_return(&mut self) -> Option<ViCmd> {
|
pub fn register_and_return(&mut self) -> Option<ViCmd> {
|
||||||
let cmd = self.take_cmd();
|
let cmd = self.take_cmd();
|
||||||
self.register_cmd(&cmd);
|
self.register_cmd(&cmd);
|
||||||
return Some(cmd)
|
Some(cmd)
|
||||||
|
}
|
||||||
|
pub fn ctrl_w_is_undo(&self) -> bool {
|
||||||
|
let insert_count = self.cmds.iter().filter(|cmd| {
|
||||||
|
matches!(cmd.verb(),Some(VerbCmd(1, Verb::InsertChar(_))))
|
||||||
|
}).count();
|
||||||
|
let backspace_count = self.cmds.iter().filter(|cmd| {
|
||||||
|
matches!(cmd.verb(),Some(VerbCmd(1, Verb::Delete)))
|
||||||
|
}).count();
|
||||||
|
insert_count > backspace_count
|
||||||
}
|
}
|
||||||
pub fn register_cmd(&mut self, cmd: &ViCmd) {
|
pub fn register_cmd(&mut self, cmd: &ViCmd) {
|
||||||
self.cmds.push(cmd.clone())
|
self.cmds.push(cmd.clone())
|
||||||
@@ -76,6 +87,17 @@ impl ViMode for ViInsert {
|
|||||||
self.pending_cmd.set_motion(MotionCmd(1,Motion::ForwardChar));
|
self.pending_cmd.set_motion(MotionCmd(1,Motion::ForwardChar));
|
||||||
self.register_and_return()
|
self.register_and_return()
|
||||||
}
|
}
|
||||||
|
E(K::Char('W'), M::CTRL) => {
|
||||||
|
if self.ctrl_w_is_undo() {
|
||||||
|
self.pending_cmd.set_verb(VerbCmd(1,Verb::Undo));
|
||||||
|
self.cmds.clear();
|
||||||
|
Some(self.take_cmd())
|
||||||
|
} else {
|
||||||
|
self.pending_cmd.set_verb(VerbCmd(1, Verb::Delete));
|
||||||
|
self.pending_cmd.set_motion(MotionCmd(1, Motion::BackwardWord(To::Start, Word::Normal)));
|
||||||
|
self.register_and_return()
|
||||||
|
}
|
||||||
|
}
|
||||||
E(K::Char('H'), M::CTRL) |
|
E(K::Char('H'), M::CTRL) |
|
||||||
E(K::Backspace, M::NONE) => {
|
E(K::Backspace, M::NONE) => {
|
||||||
self.pending_cmd.set_verb(VerbCmd(1,Verb::Delete));
|
self.pending_cmd.set_verb(VerbCmd(1,Verb::Delete));
|
||||||
@@ -117,8 +139,113 @@ impl ViMode for ViInsert {
|
|||||||
fn pending_seq(&self) -> Option<String> {
|
fn pending_seq(&self) -> Option<String> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
fn move_cursor_on_undo(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
fn clamp_cursor(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default,Debug)]
|
||||||
|
pub struct ViReplace {
|
||||||
|
cmds: Vec<ViCmd>,
|
||||||
|
pending_cmd: ViCmd,
|
||||||
|
repeat_count: u16
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ViReplace {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
pub fn with_count(mut self, repeat_count: u16) -> Self {
|
||||||
|
self.repeat_count = repeat_count;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn register_and_return(&mut self) -> Option<ViCmd> {
|
||||||
|
let cmd = self.take_cmd();
|
||||||
|
self.register_cmd(&cmd);
|
||||||
|
Some(cmd)
|
||||||
|
}
|
||||||
|
pub fn ctrl_w_is_undo(&self) -> bool {
|
||||||
|
let insert_count = self.cmds.iter().filter(|cmd| {
|
||||||
|
matches!(cmd.verb(),Some(VerbCmd(1, Verb::ReplaceChar(_))))
|
||||||
|
}).count();
|
||||||
|
let backspace_count = self.cmds.iter().filter(|cmd| {
|
||||||
|
matches!(cmd.verb(),Some(VerbCmd(1, Verb::Delete)))
|
||||||
|
}).count();
|
||||||
|
insert_count > backspace_count
|
||||||
|
}
|
||||||
|
pub fn register_cmd(&mut self, cmd: &ViCmd) {
|
||||||
|
self.cmds.push(cmd.clone())
|
||||||
|
}
|
||||||
|
pub fn take_cmd(&mut self) -> ViCmd {
|
||||||
|
std::mem::take(&mut self.pending_cmd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ViMode for ViReplace {
|
||||||
|
fn handle_key(&mut self, key: E) -> Option<ViCmd> {
|
||||||
|
match key {
|
||||||
|
E(K::Char(ch), M::NONE) => {
|
||||||
|
self.pending_cmd.set_verb(VerbCmd(1,Verb::ReplaceChar(ch)));
|
||||||
|
self.pending_cmd.set_motion(MotionCmd(1,Motion::ForwardChar));
|
||||||
|
self.register_and_return()
|
||||||
|
}
|
||||||
|
E(K::Char('W'), M::CTRL) => {
|
||||||
|
if self.ctrl_w_is_undo() {
|
||||||
|
self.pending_cmd.set_verb(VerbCmd(1,Verb::Undo));
|
||||||
|
self.cmds.clear();
|
||||||
|
Some(self.take_cmd())
|
||||||
|
} else {
|
||||||
|
self.pending_cmd.set_motion(MotionCmd(1, Motion::BackwardWord(To::Start, Word::Normal)));
|
||||||
|
self.register_and_return()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
E(K::Char('H'), M::CTRL) |
|
||||||
|
E(K::Backspace, M::NONE) => {
|
||||||
|
self.pending_cmd.set_motion(MotionCmd(1,Motion::BackwardChar));
|
||||||
|
self.register_and_return()
|
||||||
|
}
|
||||||
|
|
||||||
|
E(K::BackTab, M::NONE) => {
|
||||||
|
self.pending_cmd.set_verb(VerbCmd(1,Verb::CompleteBackward));
|
||||||
|
self.register_and_return()
|
||||||
|
}
|
||||||
|
|
||||||
|
E(K::Char('I'), M::CTRL) |
|
||||||
|
E(K::Tab, M::NONE) => {
|
||||||
|
self.pending_cmd.set_verb(VerbCmd(1,Verb::Complete));
|
||||||
|
self.register_and_return()
|
||||||
|
}
|
||||||
|
|
||||||
|
E(K::Esc, M::NONE) => {
|
||||||
|
self.pending_cmd.set_verb(VerbCmd(1,Verb::NormalMode));
|
||||||
|
self.pending_cmd.set_motion(MotionCmd(1,Motion::BackwardChar));
|
||||||
|
self.register_and_return()
|
||||||
|
}
|
||||||
|
_ => common_cmds(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn is_repeatable(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
fn cursor_style(&self) -> String {
|
||||||
|
"\x1b[4 q".to_string()
|
||||||
|
}
|
||||||
|
fn pending_seq(&self) -> Option<String> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
fn as_replay(&self) -> Option<CmdReplay> {
|
||||||
|
Some(CmdReplay::mode(self.cmds.clone(), self.repeat_count))
|
||||||
|
}
|
||||||
|
fn move_cursor_on_undo(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
fn clamp_cursor(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
#[derive(Default,Debug)]
|
#[derive(Default,Debug)]
|
||||||
pub struct ViNormal {
|
pub struct ViNormal {
|
||||||
pending_seq: String,
|
pending_seq: String,
|
||||||
@@ -235,9 +362,7 @@ impl ViNormal {
|
|||||||
break 'verb_parse Some(VerbCmd(count, Verb::Put(Anchor::Before)));
|
break 'verb_parse Some(VerbCmd(count, Verb::Put(Anchor::Before)));
|
||||||
}
|
}
|
||||||
'r' => {
|
'r' => {
|
||||||
let Some(ch) = chars_clone.next() else {
|
let ch = chars_clone.next()?;
|
||||||
return None
|
|
||||||
};
|
|
||||||
return Some(
|
return Some(
|
||||||
ViCmd {
|
ViCmd {
|
||||||
register,
|
register,
|
||||||
@@ -247,6 +372,26 @@ impl ViNormal {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
'R' => {
|
||||||
|
return Some(
|
||||||
|
ViCmd {
|
||||||
|
register,
|
||||||
|
verb: Some(VerbCmd(count, Verb::ReplaceMode)),
|
||||||
|
motion: None,
|
||||||
|
raw_seq: self.take_cmd()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
'~' => {
|
||||||
|
return Some(
|
||||||
|
ViCmd {
|
||||||
|
register,
|
||||||
|
verb: Some(VerbCmd(1, Verb::ToggleCase)),
|
||||||
|
motion: Some(MotionCmd(count, Motion::ForwardChar)),
|
||||||
|
raw_seq: self.take_cmd()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
'u' => {
|
'u' => {
|
||||||
return Some(
|
return Some(
|
||||||
ViCmd {
|
ViCmd {
|
||||||
@@ -329,6 +474,36 @@ impl ViNormal {
|
|||||||
chars = chars_clone;
|
chars = chars_clone;
|
||||||
break 'verb_parse Some(VerbCmd(count, Verb::Change))
|
break 'verb_parse Some(VerbCmd(count, Verb::Change))
|
||||||
}
|
}
|
||||||
|
'Y' => {
|
||||||
|
return Some(
|
||||||
|
ViCmd {
|
||||||
|
register,
|
||||||
|
verb: Some(VerbCmd(count, Verb::Yank)),
|
||||||
|
motion: Some(MotionCmd(1, Motion::EndOfLine)),
|
||||||
|
raw_seq: self.take_cmd()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
'D' => {
|
||||||
|
return Some(
|
||||||
|
ViCmd {
|
||||||
|
register,
|
||||||
|
verb: Some(VerbCmd(count, Verb::Delete)),
|
||||||
|
motion: Some(MotionCmd(1, Motion::EndOfLine)),
|
||||||
|
raw_seq: self.take_cmd()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
'C' => {
|
||||||
|
return Some(
|
||||||
|
ViCmd {
|
||||||
|
register,
|
||||||
|
verb: Some(VerbCmd(count, Verb::Change)),
|
||||||
|
motion: Some(MotionCmd(1, Motion::EndOfLine)),
|
||||||
|
raw_seq: self.take_cmd()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
'=' => {
|
'=' => {
|
||||||
chars = chars_clone;
|
chars = chars_clone;
|
||||||
break 'verb_parse Some(VerbCmd(count, Verb::Equalize))
|
break 'verb_parse Some(VerbCmd(count, Verb::Equalize))
|
||||||
@@ -580,6 +755,13 @@ impl ViMode for ViNormal {
|
|||||||
fn pending_seq(&self) -> Option<String> {
|
fn pending_seq(&self) -> Option<String> {
|
||||||
Some(self.pending_seq.clone())
|
Some(self.pending_seq.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn move_cursor_on_undo(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
fn clamp_cursor(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn common_cmds(key: E) -> Option<ViCmd> {
|
pub fn common_cmds(key: E) -> Option<ViCmd> {
|
||||||
|
|||||||
@@ -181,6 +181,7 @@ impl Terminal {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Rewinds terminal writing, clears lines and lands on the anchor point of the prompt
|
||||||
pub fn unwrite(&mut self) -> ShResult<()> {
|
pub fn unwrite(&mut self) -> ShResult<()> {
|
||||||
self.unposition_cursor()?;
|
self.unposition_cursor()?;
|
||||||
let WriteMap { lines, cols, offset } = self.write_records;
|
let WriteMap { lines, cols, offset } = self.write_records;
|
||||||
@@ -194,7 +195,6 @@ impl Terminal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn position_cursor(&mut self, (lines,col): (usize,usize)) -> ShResult<()> {
|
pub fn position_cursor(&mut self, (lines,col): (usize,usize)) -> ShResult<()> {
|
||||||
dbg!(self.cursor_pos());
|
|
||||||
self.cursor_records.lines = lines;
|
self.cursor_records.lines = lines;
|
||||||
self.cursor_records.cols = col;
|
self.cursor_records.cols = col;
|
||||||
self.cursor_records.offset = self.cursor_pos().1;
|
self.cursor_records.offset = self.cursor_pos().1;
|
||||||
@@ -205,14 +205,11 @@ impl Terminal {
|
|||||||
|
|
||||||
self.write(&format!("\x1b[{col}G"));
|
self.write(&format!("\x1b[{col}G"));
|
||||||
|
|
||||||
dbg!("done moving");
|
|
||||||
dbg!(self.cursor_pos());
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Rewinds cursor positioning, lands on the end of the buffer
|
||||||
pub fn unposition_cursor(&mut self) ->ShResult<()> {
|
pub fn unposition_cursor(&mut self) ->ShResult<()> {
|
||||||
dbg!(self.cursor_pos());
|
|
||||||
let WriteMap { lines, cols, offset } = self.cursor_records;
|
let WriteMap { lines, cols, offset } = self.cursor_records;
|
||||||
|
|
||||||
for _ in 0..lines {
|
for _ in 0..lines {
|
||||||
@@ -221,9 +218,6 @@ impl Terminal {
|
|||||||
|
|
||||||
self.write(&format!("\x1b[{offset}G"));
|
self.write(&format!("\x1b[{offset}G"));
|
||||||
|
|
||||||
dbg!("done moving back");
|
|
||||||
dbg!(self.cursor_pos());
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -106,11 +106,12 @@ impl ViCmd {
|
|||||||
pub fn is_mode_transition(&self) -> bool {
|
pub fn is_mode_transition(&self) -> bool {
|
||||||
self.verb.as_ref().is_some_and(|v| {
|
self.verb.as_ref().is_some_and(|v| {
|
||||||
matches!(v.1,
|
matches!(v.1,
|
||||||
|
Verb::Change |
|
||||||
Verb::InsertMode |
|
Verb::InsertMode |
|
||||||
Verb::InsertModeLineBreak(_) |
|
Verb::InsertModeLineBreak(_) |
|
||||||
Verb::NormalMode |
|
Verb::NormalMode |
|
||||||
Verb::VisualMode |
|
Verb::VisualMode |
|
||||||
Verb::OverwriteMode
|
Verb::ReplaceMode
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -150,7 +151,7 @@ pub enum Verb {
|
|||||||
Redo,
|
Redo,
|
||||||
RepeatLast,
|
RepeatLast,
|
||||||
Put(Anchor),
|
Put(Anchor),
|
||||||
OverwriteMode,
|
ReplaceMode,
|
||||||
InsertMode,
|
InsertMode,
|
||||||
InsertModeLineBreak(Anchor),
|
InsertModeLineBreak(Anchor),
|
||||||
NormalMode,
|
NormalMode,
|
||||||
@@ -190,7 +191,7 @@ impl Verb {
|
|||||||
Self::Substitute |
|
Self::Substitute |
|
||||||
Self::ToggleCase |
|
Self::ToggleCase |
|
||||||
Self::Put(_) |
|
Self::Put(_) |
|
||||||
Self::OverwriteMode |
|
Self::ReplaceMode |
|
||||||
Self::InsertModeLineBreak(_) |
|
Self::InsertModeLineBreak(_) |
|
||||||
Self::JoinLines |
|
Self::JoinLines |
|
||||||
Self::InsertChar(_) |
|
Self::InsertChar(_) |
|
||||||
@@ -211,7 +212,7 @@ impl Verb {
|
|||||||
Self::ToggleCase |
|
Self::ToggleCase |
|
||||||
Self::RepeatLast |
|
Self::RepeatLast |
|
||||||
Self::Put(_) |
|
Self::Put(_) |
|
||||||
Self::OverwriteMode |
|
Self::ReplaceMode |
|
||||||
Self::InsertModeLineBreak(_) |
|
Self::InsertModeLineBreak(_) |
|
||||||
Self::JoinLines |
|
Self::JoinLines |
|
||||||
Self::InsertChar(_) |
|
Self::InsertChar(_) |
|
||||||
@@ -221,7 +222,10 @@ impl Verb {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
pub fn is_char_insert(&self) -> bool {
|
pub fn is_char_insert(&self) -> bool {
|
||||||
matches!(self, Self::InsertChar(_))
|
matches!(self,
|
||||||
|
Self::InsertChar(_) |
|
||||||
|
Self::ReplaceChar(_)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user