finished linebuf refactor, all tests passing
This commit is contained in:
@@ -345,6 +345,33 @@ impl Pos {
|
|||||||
};
|
};
|
||||||
pub const MIN: Self = Pos { row: 0, col: 0 };
|
pub const MIN: Self = Pos { row: 0, col: 0 };
|
||||||
|
|
||||||
|
pub fn row_col_add(&self, row: isize, col: isize) -> Self {
|
||||||
|
Self {
|
||||||
|
row: self.row.saturating_add_signed(row),
|
||||||
|
col: self.col.saturating_add_signed(col),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn col_add(&self, rhs: usize) -> Self {
|
||||||
|
self.row_col_add(0, rhs as isize)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn col_add_signed(&self, rhs: isize) -> Self {
|
||||||
|
self.row_col_add(0, rhs)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn col_sub(&self, rhs: usize) -> Self {
|
||||||
|
self.row_col_add(0, -(rhs as isize))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn row_add(&self, rhs: usize) -> Self {
|
||||||
|
self.row_col_add(rhs as isize, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn row_sub(&self, rhs: usize) -> Self {
|
||||||
|
self.row_col_add(-(rhs as isize), 0)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn clamp_row<T>(&mut self, other: &[T]) {
|
pub fn clamp_row<T>(&mut self, other: &[T]) {
|
||||||
self.row = self.row.clamp(0, other.len().saturating_sub(1));
|
self.row = self.row.clamp(0, other.len().saturating_sub(1));
|
||||||
}
|
}
|
||||||
@@ -357,13 +384,17 @@ impl Pos {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub enum MotionKind {
|
pub enum MotionKind {
|
||||||
|
/// A flat range from one grapheme position to another
|
||||||
|
/// `start` is not necessarily less than `end`. `start` in most cases
|
||||||
|
/// is the cursor's position.
|
||||||
Char {
|
Char {
|
||||||
start: Pos,
|
start: Pos,
|
||||||
end: Pos,
|
end: Pos,
|
||||||
inclusive: bool,
|
inclusive: bool,
|
||||||
},
|
},
|
||||||
|
/// A range of whole lines.
|
||||||
Line {
|
Line {
|
||||||
start: usize,
|
start: usize,
|
||||||
end: usize,
|
end: usize,
|
||||||
@@ -575,6 +606,15 @@ impl LineBuf {
|
|||||||
fn line_mut(&mut self, row: usize) -> &mut Line {
|
fn line_mut(&mut self, row: usize) -> &mut Line {
|
||||||
&mut self.lines[row]
|
&mut self.lines[row]
|
||||||
}
|
}
|
||||||
|
/// Takes an inclusive range of line numbers and returns an iterator over immutable borrows of those lines.
|
||||||
|
fn line_iter(&mut self, start: usize, end: usize) -> impl Iterator<Item = &Line> {
|
||||||
|
let (start,end) = ordered(start,end);
|
||||||
|
self.lines.iter().take(end + 1).skip(start)
|
||||||
|
}
|
||||||
|
fn line_iter_mut(&mut self, start: usize, end: usize) -> impl Iterator<Item = &mut Line> {
|
||||||
|
let (start,end) = ordered(start,end);
|
||||||
|
self.lines.iter_mut().take(end + 1).skip(start)
|
||||||
|
}
|
||||||
fn line_to_cursor(&self) -> &[Grapheme] {
|
fn line_to_cursor(&self) -> &[Grapheme] {
|
||||||
let line = self.cur_line();
|
let line = self.cur_line();
|
||||||
let col = self.cursor.pos.col.min(line.len());
|
let col = self.cursor.pos.col.min(line.len());
|
||||||
@@ -662,7 +702,6 @@ impl LineBuf {
|
|||||||
fn break_line(&mut self) {
|
fn break_line(&mut self) {
|
||||||
let (row, col) = self.row_col();
|
let (row, col) = self.row_col();
|
||||||
let level = self.calc_indent_level();
|
let level = self.calc_indent_level();
|
||||||
log::debug!("level: {level}");
|
|
||||||
let mut rest = self.lines[row].split_off(col);
|
let mut rest = self.lines[row].split_off(col);
|
||||||
let mut col = 0;
|
let mut col = 0;
|
||||||
for tab in std::iter::repeat_n(Grapheme::from('\t'), level) {
|
for tab in std::iter::repeat_n(Grapheme::from('\t'), level) {
|
||||||
@@ -1322,6 +1361,11 @@ impl LineBuf {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If cursor is on '-', advance to the first digit
|
||||||
|
if self.gr_at(pos)?.as_char() == Some('-') {
|
||||||
|
pos = pos.col_add(1);
|
||||||
|
}
|
||||||
|
|
||||||
let mut start = self.scan_backward_from(pos, |g| !is_digit(g))
|
let mut start = self.scan_backward_from(pos, |g| !is_digit(g))
|
||||||
.map(|pos| Pos { row: pos.row, col: pos.col + 1 })
|
.map(|pos| Pos { row: pos.row, col: pos.col + 1 })
|
||||||
.unwrap_or(Pos::MIN);
|
.unwrap_or(Pos::MIN);
|
||||||
@@ -1383,6 +1427,7 @@ impl LineBuf {
|
|||||||
} else { return None };
|
} else { return None };
|
||||||
|
|
||||||
self.replace_range(s, e, &num_fmt);
|
self.replace_range(s, e, &num_fmt);
|
||||||
|
self.cursor.pos.col -= 1;
|
||||||
Some(())
|
Some(())
|
||||||
}
|
}
|
||||||
fn replace_range(&mut self, s: Pos, e: Pos, new: &str) -> Vec<Line> {
|
fn replace_range(&mut self, s: Pos, e: Pos, new: &str) -> Vec<Line> {
|
||||||
@@ -1418,6 +1463,38 @@ impl LineBuf {
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fn find_delim_match(&mut self) -> Option<MotionKind> {
|
||||||
|
let is_opener = |g: &Grapheme| matches!(g.as_char(), Some(c) if "([{<".contains(c));
|
||||||
|
let is_closer = |g: &Grapheme| matches!(g.as_char(), Some(c) if ")]}>".contains(c));
|
||||||
|
let is_delim = |g: &Grapheme| is_opener(g) || is_closer(g);
|
||||||
|
let first = self.scan_forward(is_delim)?;
|
||||||
|
|
||||||
|
let delim_match = if is_closer(self.gr_at(first)?) {
|
||||||
|
let opener = match self.gr_at(first)?.as_char()? {
|
||||||
|
')' => '(',
|
||||||
|
']' => '[',
|
||||||
|
'}' => '{',
|
||||||
|
'>' => '<',
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
self.scan_backward_from(first, |g| g.as_char() == Some(opener))?
|
||||||
|
} else if is_opener(self.gr_at(first)?) {
|
||||||
|
let closer = match self.gr_at(first)?.as_char()? {
|
||||||
|
'(' => ')',
|
||||||
|
'[' => ']',
|
||||||
|
'{' => '}',
|
||||||
|
'<' => '>',
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
self.scan_forward_from(first, |g| g.as_char() == Some(closer))?
|
||||||
|
} else { unreachable!() };
|
||||||
|
|
||||||
|
Some(MotionKind::Char {
|
||||||
|
start: self.cursor.pos,
|
||||||
|
end: delim_match,
|
||||||
|
inclusive: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
/// Wrapper for eval_motion_inner that calls it with `check_hint: false`
|
/// Wrapper for eval_motion_inner that calls it with `check_hint: false`
|
||||||
fn eval_motion(&mut self, cmd: &ViCmd) -> Option<MotionKind> {
|
fn eval_motion(&mut self, cmd: &ViCmd) -> Option<MotionKind> {
|
||||||
self.eval_motion_inner(cmd, false)
|
self.eval_motion_inner(cmd, false)
|
||||||
@@ -1432,10 +1509,11 @@ impl LineBuf {
|
|||||||
|
|
||||||
let kind = match motion {
|
let kind = match motion {
|
||||||
Motion::WholeLine => {
|
Motion::WholeLine => {
|
||||||
let row = (self.row() + (count.saturating_sub(1))).min(self.lines.len().saturating_sub(1));
|
let start = self.row();
|
||||||
|
let end = (self.row() + (count.saturating_sub(1))).min(self.lines.len().saturating_sub(1));
|
||||||
Some(MotionKind::Line {
|
Some(MotionKind::Line {
|
||||||
start: row,
|
start,
|
||||||
end: row,
|
end,
|
||||||
inclusive: true
|
inclusive: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -1586,11 +1664,62 @@ impl LineBuf {
|
|||||||
end: self.lines.len().saturating_sub(1),
|
end: self.lines.len().saturating_sub(1),
|
||||||
inclusive: false
|
inclusive: false
|
||||||
}),
|
}),
|
||||||
Motion::ToColumn => todo!(),
|
Motion::ToColumn => {
|
||||||
Motion::ToDelimMatch => todo!(),
|
let row = self.row();
|
||||||
Motion::ToBrace(direction) => todo!(),
|
let end = Pos { row, col: count.saturating_sub(1) };
|
||||||
Motion::ToBracket(direction) => todo!(),
|
Some(MotionKind::Char { start: self.cursor.pos, end, inclusive: end > self.cursor.pos })
|
||||||
Motion::ToParen(direction) => todo!(),
|
}
|
||||||
|
|
||||||
|
Motion::ToDelimMatch => self.find_delim_match(),
|
||||||
|
Motion::ToBracket(direction) |
|
||||||
|
Motion::ToParen(direction) |
|
||||||
|
Motion::ToBrace(direction) => {
|
||||||
|
let (opener,closer) = match motion {
|
||||||
|
Motion::ToBracket(_) => ('[', ']'),
|
||||||
|
Motion::ToParen(_) => ('(', ')'),
|
||||||
|
Motion::ToBrace(_) => ('{', '}'),
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
match direction {
|
||||||
|
Direction::Forward => {
|
||||||
|
let mut depth = 0;
|
||||||
|
let target_pos = self.scan_forward(|g| {
|
||||||
|
if g.as_char() == Some(opener) { depth += 1; }
|
||||||
|
if g.as_char() == Some(closer) {
|
||||||
|
depth -= 1;
|
||||||
|
if depth <= 0 {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
})?;
|
||||||
|
return Some(MotionKind::Char {
|
||||||
|
start: self.cursor.pos,
|
||||||
|
end: target_pos,
|
||||||
|
inclusive: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Direction::Backward => {
|
||||||
|
let mut depth = 0;
|
||||||
|
let target_pos = self.scan_backward(|g| {
|
||||||
|
if g.as_char() == Some(closer) { depth += 1; }
|
||||||
|
if g.as_char() == Some(opener) {
|
||||||
|
depth -= 1;
|
||||||
|
if depth <= 0 {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
})?;
|
||||||
|
return Some(MotionKind::Char {
|
||||||
|
start: self.cursor.pos,
|
||||||
|
end: target_pos,
|
||||||
|
inclusive: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Motion::CharRange(s, e) => {
|
Motion::CharRange(s, e) => {
|
||||||
let (s, e) = ordered(*s, *e);
|
let (s, e) = ordered(*s, *e);
|
||||||
Some(MotionKind::Char {
|
Some(MotionKind::Char {
|
||||||
@@ -1617,6 +1746,19 @@ impl LineBuf {
|
|||||||
self.lines = buffer;
|
self.lines = buffer;
|
||||||
kind
|
kind
|
||||||
}
|
}
|
||||||
|
fn move_to_start(&mut self, motion: MotionKind) {
|
||||||
|
match motion {
|
||||||
|
MotionKind::Char { start, end, inclusive } => {
|
||||||
|
let (s,_) = ordered(start, end);
|
||||||
|
self.set_cursor(s);
|
||||||
|
}
|
||||||
|
MotionKind::Line { start, end, inclusive } => {
|
||||||
|
let (s,_) = ordered(start, end);
|
||||||
|
self.set_cursor(Pos { row: s, col: 0 });
|
||||||
|
}
|
||||||
|
MotionKind::Block { start, end } => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
/// Wrapper for apply_motion_inner that calls it with `accept_hint: false`
|
/// Wrapper for apply_motion_inner that calls it with `accept_hint: false`
|
||||||
fn apply_motion(&mut self, motion: MotionKind) -> ShResult<()> {
|
fn apply_motion(&mut self, motion: MotionKind) -> ShResult<()> {
|
||||||
self.apply_motion_inner(motion, false)
|
self.apply_motion_inner(motion, false)
|
||||||
@@ -1683,7 +1825,6 @@ impl LineBuf {
|
|||||||
extracted
|
extracted
|
||||||
}
|
}
|
||||||
fn yank_range(&self, motion: &MotionKind) -> Vec<Line> {
|
fn yank_range(&self, motion: &MotionKind) -> Vec<Line> {
|
||||||
log::debug!("Yanking range: {:?}", motion);
|
|
||||||
let mut tmp = Self {
|
let mut tmp = Self {
|
||||||
lines: self.lines.clone(),
|
lines: self.lines.clone(),
|
||||||
cursor: self.cursor,
|
cursor: self.cursor,
|
||||||
@@ -1701,7 +1842,6 @@ impl LineBuf {
|
|||||||
let mut lines = self.lines.clone();
|
let mut lines = self.lines.clone();
|
||||||
split_lines_at(&mut lines, pos);
|
split_lines_at(&mut lines, pos);
|
||||||
let raw = join_lines(&lines);
|
let raw = join_lines(&lines);
|
||||||
log::debug!("Calculating indent level for pos {:?} with raw text:\n{:?}", pos, raw);
|
|
||||||
|
|
||||||
self.indent_ctx.calculate(&raw)
|
self.indent_ctx.calculate(&raw)
|
||||||
}
|
}
|
||||||
@@ -1760,18 +1900,18 @@ impl LineBuf {
|
|||||||
fn inplace_mutation(&mut self, count: u16, f: impl Fn(&Grapheme) -> Grapheme) {
|
fn inplace_mutation(&mut self, count: u16, f: impl Fn(&Grapheme) -> Grapheme) {
|
||||||
let mut first = true;
|
let mut first = true;
|
||||||
for i in 0..count {
|
for i in 0..count {
|
||||||
let pos = self.cursor.pos;
|
if first {
|
||||||
let motion = MotionKind::Char {
|
|
||||||
start: pos,
|
|
||||||
end: pos,
|
|
||||||
inclusive: false,
|
|
||||||
};
|
|
||||||
self.motion_mutation(motion, &f);
|
|
||||||
if !first {
|
|
||||||
first = false
|
first = false
|
||||||
} else {
|
} else {
|
||||||
self.cursor.pos = self.offset_cursor(0, 1);
|
self.cursor.pos = self.offset_cursor(0, 1);
|
||||||
}
|
}
|
||||||
|
let pos = self.cursor.pos;
|
||||||
|
let motion = MotionKind::Char {
|
||||||
|
start: pos,
|
||||||
|
end: pos,
|
||||||
|
inclusive: true,
|
||||||
|
};
|
||||||
|
self.motion_mutation(motion, &f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn exec_verb(&mut self, cmd: &ViCmd) -> ShResult<()> {
|
fn exec_verb(&mut self, cmd: &ViCmd) -> ShResult<()> {
|
||||||
@@ -1781,7 +1921,6 @@ impl LineBuf {
|
|||||||
motion,
|
motion,
|
||||||
..
|
..
|
||||||
} = cmd;
|
} = cmd;
|
||||||
log::debug!("Executing verb: {:?} with motion: {:?}", verb, motion);
|
|
||||||
let Some(VerbCmd(_, verb)) = verb else {
|
let Some(VerbCmd(_, verb)) = verb else {
|
||||||
// For verb-less motions in insert mode, merge hint before evaluating
|
// For verb-less motions in insert mode, merge hint before evaluating
|
||||||
// so motions like `w` can see into the hint text
|
// so motions like `w` can see into the hint text
|
||||||
@@ -1855,12 +1994,14 @@ impl LineBuf {
|
|||||||
.map(Grapheme::from)
|
.map(Grapheme::from)
|
||||||
.unwrap_or_else(|| gr.clone())
|
.unwrap_or_else(|| gr.clone())
|
||||||
});
|
});
|
||||||
|
self.move_to_start(motion);
|
||||||
}
|
}
|
||||||
Verb::ReplaceChar(ch) => {
|
Verb::ReplaceChar(ch) => {
|
||||||
let Some(motion) = self.eval_motion(cmd) else {
|
let Some(motion) = self.eval_motion(cmd) else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
self.motion_mutation(motion, |_| Grapheme::from(*ch));
|
self.motion_mutation(motion, |_| Grapheme::from(*ch));
|
||||||
|
self.move_to_start(motion);
|
||||||
}
|
}
|
||||||
Verb::ReplaceCharInplace(ch, count) => self.inplace_mutation(*count, |_| Grapheme::from(*ch)),
|
Verb::ReplaceCharInplace(ch, count) => self.inplace_mutation(*count, |_| Grapheme::from(*ch)),
|
||||||
Verb::ToggleCaseInplace(count) => {
|
Verb::ToggleCaseInplace(count) => {
|
||||||
@@ -1870,6 +2011,7 @@ impl LineBuf {
|
|||||||
.map(Grapheme::from)
|
.map(Grapheme::from)
|
||||||
.unwrap_or_else(|| gr.clone())
|
.unwrap_or_else(|| gr.clone())
|
||||||
});
|
});
|
||||||
|
self.cursor.pos = self.cursor.pos.col_add(1);
|
||||||
}
|
}
|
||||||
Verb::ToggleCaseRange => {
|
Verb::ToggleCaseRange => {
|
||||||
let Some(motion) = self.eval_motion(cmd) else {
|
let Some(motion) = self.eval_motion(cmd) else {
|
||||||
@@ -1881,6 +2023,7 @@ impl LineBuf {
|
|||||||
.map(Grapheme::from)
|
.map(Grapheme::from)
|
||||||
.unwrap_or_else(|| gr.clone())
|
.unwrap_or_else(|| gr.clone())
|
||||||
});
|
});
|
||||||
|
self.move_to_start(motion);
|
||||||
}
|
}
|
||||||
Verb::IncrementNumber(n) => { self.adjust_number(*n as i64); },
|
Verb::IncrementNumber(n) => { self.adjust_number(*n as i64); },
|
||||||
Verb::DecrementNumber(n) => { self.adjust_number(-(*n as i64)); },
|
Verb::DecrementNumber(n) => { self.adjust_number(-(*n as i64)); },
|
||||||
@@ -1890,10 +2033,11 @@ impl LineBuf {
|
|||||||
};
|
};
|
||||||
self.motion_mutation(motion, |gr| {
|
self.motion_mutation(motion, |gr| {
|
||||||
gr.as_char()
|
gr.as_char()
|
||||||
.map(|c| c.to_ascii_uppercase())
|
.map(|c| c.to_ascii_lowercase())
|
||||||
.map(Grapheme::from)
|
.map(Grapheme::from)
|
||||||
.unwrap_or_else(|| gr.clone())
|
.unwrap_or_else(|| gr.clone())
|
||||||
})
|
});
|
||||||
|
self.move_to_start(motion);
|
||||||
}
|
}
|
||||||
Verb::ToUpper => {
|
Verb::ToUpper => {
|
||||||
let Some(motion) = self.eval_motion(cmd) else {
|
let Some(motion) = self.eval_motion(cmd) else {
|
||||||
@@ -1904,7 +2048,8 @@ impl LineBuf {
|
|||||||
.map(|c| c.to_ascii_uppercase())
|
.map(|c| c.to_ascii_uppercase())
|
||||||
.map(Grapheme::from)
|
.map(Grapheme::from)
|
||||||
.unwrap_or_else(|| gr.clone())
|
.unwrap_or_else(|| gr.clone())
|
||||||
})
|
});
|
||||||
|
self.move_to_start(motion);
|
||||||
}
|
}
|
||||||
Verb::Undo => {
|
Verb::Undo => {
|
||||||
if let Some(edit) = self.undo_stack.pop() {
|
if let Some(edit) = self.undo_stack.pop() {
|
||||||
@@ -1920,7 +2065,6 @@ impl LineBuf {
|
|||||||
self.undo_stack.push(edit);
|
self.undo_stack.push(edit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Verb::RepeatLast => todo!(),
|
|
||||||
Verb::Put(anchor) => {
|
Verb::Put(anchor) => {
|
||||||
let Some(content) = register.read_from_register() else {
|
let Some(content) = register.read_from_register() else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@@ -1952,10 +2096,11 @@ impl LineBuf {
|
|||||||
self.lines[row + last].append(&mut right);
|
self.lines[row + last].append(&mut right);
|
||||||
|
|
||||||
let end_len = self.lines[row].len();
|
let end_len = self.lines[row].len();
|
||||||
let delta = end_len.saturating_sub(start_len);
|
let mut delta = end_len.saturating_sub(start_len);
|
||||||
|
if let Anchor::Before = anchor { delta = delta.saturating_sub(1); }
|
||||||
if move_cursor {
|
if move_cursor {
|
||||||
self.cursor.pos = self.offset_cursor(0, delta as isize);
|
self.cursor.pos = self.offset_cursor(0, delta as isize);
|
||||||
} else if content_len > 1 {
|
} else if content_len > 1 || *anchor == Anchor::After {
|
||||||
self.cursor.pos = self.offset_cursor(0, 1);
|
self.cursor.pos = self.offset_cursor(0, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2065,9 +2210,63 @@ impl LineBuf {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Verb::Insert(s) => self.insert_str(s),
|
Verb::Insert(s) => self.insert_str(s),
|
||||||
Verb::Indent => todo!(),
|
Verb::Indent | Verb::Dedent => {
|
||||||
Verb::Dedent => todo!(),
|
let Some(motion) = self.eval_motion(cmd) else {
|
||||||
Verb::Equalize => todo!(),
|
return Ok(());
|
||||||
|
};
|
||||||
|
let (s, e) = match motion {
|
||||||
|
MotionKind::Char { start, end, .. } => ordered(start.row, end.row),
|
||||||
|
MotionKind::Line { start, end, .. } => ordered(start, end),
|
||||||
|
MotionKind::Block { .. } => todo!(),
|
||||||
|
};
|
||||||
|
let mut col_offset = 0;
|
||||||
|
for line in self.line_iter_mut(s, e) {
|
||||||
|
match verb {
|
||||||
|
Verb::Indent => {
|
||||||
|
line.insert(0, Grapheme::from('\t'));
|
||||||
|
col_offset += 1;
|
||||||
|
}
|
||||||
|
Verb::Dedent => {
|
||||||
|
if line.0.first().is_some_and(|c| c.as_char() == Some('\t')) {
|
||||||
|
line.0.remove(0);
|
||||||
|
col_offset -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.cursor.pos = self.cursor.pos.col_add_signed(col_offset)
|
||||||
|
}
|
||||||
|
Verb::Equalize => {
|
||||||
|
let Some(motion) = self.eval_motion(cmd) else {
|
||||||
|
return Ok(())
|
||||||
|
};
|
||||||
|
let (s,e) = match motion {
|
||||||
|
MotionKind::Char { start, end, inclusive } => ordered(start.row, end.row),
|
||||||
|
MotionKind::Line { start, end, inclusive } => ordered(start, end),
|
||||||
|
MotionKind::Block { start, end } => todo!(),
|
||||||
|
};
|
||||||
|
for row in s..=e {
|
||||||
|
let line_len = self.line(row).len();
|
||||||
|
|
||||||
|
// we are going to calculate the level twice, once at column = 0 and once at column = line.len()
|
||||||
|
// "b-b-b-b-but the performance" i dont care. open a pull request genius
|
||||||
|
// the number of tabs we use for the line is the lesser of these two calculations
|
||||||
|
// if level_start > level_end, the line has an closer
|
||||||
|
// if level_end > level_start, the line has a opener
|
||||||
|
let level_start = self.calc_indent_level_for_pos(Pos { row, col: 0 });
|
||||||
|
let level_end = self.calc_indent_level_for_pos(Pos { row, col: line_len });
|
||||||
|
let num_tabs = level_start.min(level_end);
|
||||||
|
|
||||||
|
let line = self.line_mut(row);
|
||||||
|
while line.0.first().is_some_and(|c| c.as_char() == Some('\t')) {
|
||||||
|
line.0.remove(0);
|
||||||
|
}
|
||||||
|
for tab in std::iter::repeat_n(Grapheme::from('\t'), num_tabs) {
|
||||||
|
line.insert(0, tab);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Verb::AcceptLineOrNewline => {
|
Verb::AcceptLineOrNewline => {
|
||||||
// If we are here, we did not accept the line
|
// If we are here, we did not accept the line
|
||||||
// so we break to a new line
|
// so we break to a new line
|
||||||
@@ -2169,10 +2368,10 @@ impl LineBuf {
|
|||||||
| Verb::VisualModeBlock
|
| Verb::VisualModeBlock
|
||||||
| Verb::CompleteBackward
|
| Verb::CompleteBackward
|
||||||
| Verb::VisualModeSelectLast => {
|
| Verb::VisualModeSelectLast => {
|
||||||
let Some(motion_kind) = self.eval_motion(cmd) else {
|
let Some(motion_kind) = self.eval_motion_inner(cmd, true) else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
self.apply_motion(motion_kind)?;
|
self.apply_motion_inner(motion_kind, true)?;
|
||||||
}
|
}
|
||||||
Verb::Normal(_)
|
Verb::Normal(_)
|
||||||
| Verb::Substitute(..)
|
| Verb::Substitute(..)
|
||||||
@@ -2181,12 +2380,14 @@ impl LineBuf {
|
|||||||
| Verb::RepeatGlobal => {
|
| Verb::RepeatGlobal => {
|
||||||
log::warn!("Verb {:?} is not implemented yet", verb);
|
log::warn!("Verb {:?} is not implemented yet", verb);
|
||||||
}
|
}
|
||||||
|
Verb::RepeatLast => unreachable!("Verb::RepeatLast should be handled in readline/mod.rs"),
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn exec_cmd(&mut self, cmd: ViCmd) -> ShResult<()> {
|
pub fn exec_cmd(&mut self, cmd: ViCmd) -> ShResult<()> {
|
||||||
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 starts_merge = cmd.verb.as_ref().is_some_and(|v| matches!(v.1, Verb::Change));
|
||||||
let is_line_motion = cmd.is_line_motion()
|
let is_line_motion = cmd.is_line_motion()
|
||||||
|| cmd
|
|| cmd
|
||||||
.verb
|
.verb
|
||||||
@@ -2214,6 +2415,13 @@ impl LineBuf {
|
|||||||
|
|
||||||
let new_cursor = self.cursor.pos;
|
let new_cursor = self.cursor.pos;
|
||||||
|
|
||||||
|
// Stop merging on any non-char-insert command, even if buffer didn't change
|
||||||
|
if !is_char_insert && !is_undo_op {
|
||||||
|
if let Some(edit) = self.undo_stack.last_mut() {
|
||||||
|
edit.merging = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if self.lines != before && !is_undo_op {
|
if self.lines != before && !is_undo_op {
|
||||||
self.redo_stack.clear();
|
self.redo_stack.clear();
|
||||||
if is_char_insert {
|
if is_char_insert {
|
||||||
@@ -2231,11 +2439,13 @@ impl LineBuf {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Stop merging on any non-insert edit
|
|
||||||
if let Some(edit) = self.undo_stack.last_mut() {
|
|
||||||
edit.merging = false;
|
|
||||||
}
|
|
||||||
self.handle_edit(before, new_cursor, old_cursor);
|
self.handle_edit(before, new_cursor, old_cursor);
|
||||||
|
// Change starts a new merge chain so subsequent InsertChars merge into it
|
||||||
|
if starts_merge {
|
||||||
|
if let Some(edit) = self.undo_stack.last_mut() {
|
||||||
|
edit.merging = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2744,10 +2954,16 @@ struct CharClassIterRev<'a> {
|
|||||||
|
|
||||||
impl<'a> CharClassIterRev<'a> {
|
impl<'a> CharClassIterRev<'a> {
|
||||||
pub fn new(lines: &'a [Line], start_pos: Pos) -> Self {
|
pub fn new(lines: &'a [Line], start_pos: Pos) -> Self {
|
||||||
|
let row = start_pos.row.min(lines.len().saturating_sub(1));
|
||||||
|
let col = if lines.is_empty() || lines[row].is_empty() {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
start_pos.col.min(lines[row].len().saturating_sub(1))
|
||||||
|
};
|
||||||
Self {
|
Self {
|
||||||
lines,
|
lines,
|
||||||
row: start_pos.row,
|
row,
|
||||||
col: start_pos.col,
|
col,
|
||||||
exhausted: false,
|
exhausted: false,
|
||||||
at_boundary: false,
|
at_boundary: false,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1275,9 +1275,7 @@ impl ShedVi {
|
|||||||
|
|
||||||
if let Some(range) = self.editor.select_range() {
|
if let Some(range) = self.editor.select_range() {
|
||||||
cmd.motion = Some(MotionCmd(1, range))
|
cmd.motion = Some(MotionCmd(1, range))
|
||||||
} else {
|
}
|
||||||
log::warn!("You're in visual mode with no select range??");
|
|
||||||
};
|
|
||||||
|
|
||||||
// Set cursor clamp BEFORE executing the command so that motions
|
// Set cursor clamp BEFORE executing the command so that motions
|
||||||
// (like EndOfLine for 'A') can reach positions valid in the new mode
|
// (like EndOfLine for 'A') can reach positions valid in the new mode
|
||||||
@@ -1322,8 +1320,6 @@ impl ShedVi {
|
|||||||
pub fn exec_cmd(&mut self, mut cmd: ViCmd, from_replay: bool) -> ShResult<()> {
|
pub fn exec_cmd(&mut self, mut cmd: ViCmd, from_replay: bool) -> ShResult<()> {
|
||||||
if cmd.verb().is_some() && let Some(range) = self.editor.select_range() {
|
if cmd.verb().is_some() && let Some(range) = self.editor.select_range() {
|
||||||
cmd.motion = Some(MotionCmd(1, range))
|
cmd.motion = Some(MotionCmd(1, range))
|
||||||
} else {
|
|
||||||
log::warn!("You're in visual mode with no select range??");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if cmd.is_mode_transition() {
|
if cmd.is_mode_transition() {
|
||||||
@@ -1399,7 +1395,7 @@ impl ShedVi {
|
|||||||
};
|
};
|
||||||
let repeat_cmd = ViCmd {
|
let repeat_cmd = ViCmd {
|
||||||
register: RegisterName::default(),
|
register: RegisterName::default(),
|
||||||
verb: None,
|
verb: cmd.verb,
|
||||||
motion: Some(motion),
|
motion: Some(motion),
|
||||||
raw_seq: format!("{count};"),
|
raw_seq: format!("{count};"),
|
||||||
flags: CmdFlags::empty(),
|
flags: CmdFlags::empty(),
|
||||||
@@ -1414,7 +1410,7 @@ impl ShedVi {
|
|||||||
new_motion.0 = *count;
|
new_motion.0 = *count;
|
||||||
let repeat_cmd = ViCmd {
|
let repeat_cmd = ViCmd {
|
||||||
register: RegisterName::default(),
|
register: RegisterName::default(),
|
||||||
verb: None,
|
verb: cmd.verb,
|
||||||
motion: Some(new_motion),
|
motion: Some(new_motion),
|
||||||
raw_seq: format!("{count},"),
|
raw_seq: format!("{count},"),
|
||||||
flags: CmdFlags::empty(),
|
flags: CmdFlags::empty(),
|
||||||
|
|||||||
@@ -423,6 +423,16 @@ vi_test! {
|
|||||||
vi_percent_bracket : "[hello] world" => "%" => "[hello] world", 6;
|
vi_percent_bracket : "[hello] world" => "%" => "[hello] world", 6;
|
||||||
vi_percent_from_close: "(hello) world" => "f)%" => "(hello) world", 0;
|
vi_percent_from_close: "(hello) world" => "f)%" => "(hello) world", 0;
|
||||||
vi_d_percent_paren : "(hello) world" => "d%" => " world", 0;
|
vi_d_percent_paren : "(hello) world" => "d%" => " world", 0;
|
||||||
|
vi_to_paren_fwd : "foo (bar) baz" => "])" => "foo (bar) baz", 8;
|
||||||
|
vi_to_paren_bkwd : "foo (bar) baz" => "f)[(" => "foo (bar) baz", 4;
|
||||||
|
vi_to_brace_fwd : "foo {bar} baz" => "]}" => "foo {bar} baz", 8;
|
||||||
|
vi_to_brace_bkwd : "foo {bar} baz" => "f}[{" => "foo {bar} baz", 4;
|
||||||
|
vi_to_paren_nested : "((a)(b)) end" => "])" => "((a)(b)) end", 7;
|
||||||
|
vi_to_brace_nested : "{{a}{b}} end" => "]}" => "{{a}{b}} end", 7;
|
||||||
|
vi_d_to_paren_fwd : "foo (bar) baz" => "wd])" => "foo baz", 4;
|
||||||
|
vi_d_to_brace_fwd : "foo {bar} baz" => "wd]}" => "foo baz", 4;
|
||||||
|
vi_to_paren_no_match : "foo bar baz" => "])" => "foo bar baz", 0;
|
||||||
|
vi_to_brace_no_match : "foo bar baz" => "]}" => "foo bar baz", 0;
|
||||||
vi_i_insert : "hello" => "iX\x1b" => "Xhello", 0;
|
vi_i_insert : "hello" => "iX\x1b" => "Xhello", 0;
|
||||||
vi_a_append : "hello" => "aX\x1b" => "hXello", 1;
|
vi_a_append : "hello" => "aX\x1b" => "hXello", 1;
|
||||||
vi_I_front : " hello" => "IX\x1b" => " Xhello", 2;
|
vi_I_front : " hello" => "IX\x1b" => " Xhello", 2;
|
||||||
@@ -473,10 +483,10 @@ vi_test! {
|
|||||||
vi_V_S_change : "hello world" => "VSfoo\x1b" => "foo", 2;
|
vi_V_S_change : "hello world" => "VSfoo\x1b" => "foo", 2;
|
||||||
vi_ctrl_a_inc : "num 5 end" => "w\x01" => "num 6 end", 4;
|
vi_ctrl_a_inc : "num 5 end" => "w\x01" => "num 6 end", 4;
|
||||||
vi_ctrl_x_dec : "num 5 end" => "w\x18" => "num 4 end", 4;
|
vi_ctrl_x_dec : "num 5 end" => "w\x18" => "num 4 end", 4;
|
||||||
vi_ctrl_a_negative : "num -3 end" => "w\x01" => "num -2 end", 4;
|
vi_ctrl_a_negative : "num -3 end" => "w\x01" => "num -2 end", 5;
|
||||||
vi_ctrl_x_to_neg : "num 0 end" => "w\x18" => "num -1 end", 4;
|
vi_ctrl_x_to_neg : "num 0 end" => "w\x18" => "num -1 end", 5;
|
||||||
vi_ctrl_a_count : "num 5 end" => "w3\x01" => "num 8 end", 4;
|
vi_ctrl_a_count : "num 5 end" => "w3\x01" => "num 8 end", 4;
|
||||||
vi_ctrl_a_width : "num -00001 end" => "w\x01" => "num 00000 end", 4;
|
vi_ctrl_a_width : "num -00001 end" => "w\x01" => "num 00000 end", 8;
|
||||||
vi_delete_empty : "" => "x" => "", 0;
|
vi_delete_empty : "" => "x" => "", 0;
|
||||||
vi_undo_on_empty : "" => "u" => "", 0;
|
vi_undo_on_empty : "" => "u" => "", 0;
|
||||||
vi_w_single_char : "a b c" => "w" => "a b c", 2;
|
vi_w_single_char : "a b c" => "w" => "a b c", 2;
|
||||||
|
|||||||
@@ -309,7 +309,7 @@ impl Verb {
|
|||||||
pub fn is_char_insert(&self) -> bool {
|
pub fn is_char_insert(&self) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
self,
|
self,
|
||||||
Self::Change | Self::InsertChar(_) | Self::ReplaceChar(_) | Self::ReplaceCharInplace(_, _)
|
Self::InsertChar(_) | Self::ReplaceChar(_)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ impl ViVisual {
|
|||||||
let ch = chars_clone.next()?;
|
let ch = chars_clone.next()?;
|
||||||
return Some(ViCmd {
|
return Some(ViCmd {
|
||||||
register,
|
register,
|
||||||
verb: Some(VerbCmd(1, Verb::ReplaceCharInplace(ch, 1))),
|
verb: Some(VerbCmd(1, Verb::ReplaceChar(ch))),
|
||||||
motion: None,
|
motion: None,
|
||||||
raw_seq: self.take_cmd(),
|
raw_seq: self.take_cmd(),
|
||||||
flags: CmdFlags::empty(),
|
flags: CmdFlags::empty(),
|
||||||
|
|||||||
Reference in New Issue
Block a user