more work on linebuf reimpl
This commit is contained in:
@@ -1,37 +1,32 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::HashSet,
|
|
||||||
fmt::Display,
|
fmt::Display,
|
||||||
ops::{Index, IndexMut, Range, RangeBounds, RangeFull, RangeInclusive},
|
ops::{Index, IndexMut},
|
||||||
slice::SliceIndex,
|
slice::SliceIndex,
|
||||||
};
|
};
|
||||||
|
|
||||||
use itertools::Itertools;
|
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
|
use unicode_width::UnicodeWidthChar;
|
||||||
|
|
||||||
use super::vicmd::{
|
use super::vicmd::{
|
||||||
Anchor, Bound, CmdFlags, Dest, Direction, Motion, MotionCmd, RegisterName, TextObj, To, Verb,
|
Anchor, Bound, Dest, Direction, Motion, MotionCmd, TextObj, To, Verb,
|
||||||
ViCmd, Word,
|
ViCmd, Word,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
expand::expand_cmd_sub,
|
expand::expand_cmd_sub,
|
||||||
libsh::{error::ShResult, guards::var_ctx_guard},
|
libsh::error::ShResult,
|
||||||
parse::{
|
parse::{
|
||||||
Redir, RedirType,
|
Redir, RedirType,
|
||||||
execute::exec_input,
|
execute::exec_input,
|
||||||
lex::{LexFlags, LexStream, QuoteState, Tk, TkFlags, TkRule},
|
lex::{LexFlags, LexStream, Tk, TkFlags, TkRule},
|
||||||
},
|
},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
procio::{IoFrame, IoMode, IoStack},
|
procio::{IoFrame, IoMode, IoStack},
|
||||||
readline::{
|
readline::{
|
||||||
history::History,
|
|
||||||
markers,
|
markers,
|
||||||
register::{RegisterContent, write_register},
|
register::RegisterContent, vicmd::{ReadSrc, VerbCmd, WriteDest},
|
||||||
term::{RawModeGuard, get_win_size},
|
|
||||||
vicmd::{ReadSrc, VerbCmd, WriteDest},
|
|
||||||
},
|
},
|
||||||
state::{VarFlags, VarKind, read_shopts, write_meta, write_vars},
|
state::{read_vars, write_meta},
|
||||||
};
|
};
|
||||||
|
|
||||||
const PUNCTUATION: [&str; 3] = ["?", "!", "."];
|
const PUNCTUATION: [&str; 3] = ["?", "!", "."];
|
||||||
@@ -124,6 +119,14 @@ pub fn to_lines(s: impl ToString) -> Vec<Line> {
|
|||||||
s.split("\n").map(to_graphemes).map(Line::from).collect()
|
s.split("\n").map(to_graphemes).map(Line::from).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn join_lines(lines: &[Line]) -> String {
|
||||||
|
lines
|
||||||
|
.iter()
|
||||||
|
.map(|line| line.to_string())
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join("\n")
|
||||||
|
}
|
||||||
|
|
||||||
pub fn trim_lines(lines: &mut Vec<Line>) {
|
pub fn trim_lines(lines: &mut Vec<Line>) {
|
||||||
while lines.last().is_some_and(|line| line.is_empty()) {
|
while lines.last().is_some_and(|line| line.is_empty()) {
|
||||||
lines.pop();
|
lines.pop();
|
||||||
@@ -138,10 +141,10 @@ pub fn split_lines_at(lines: &mut Vec<Line>, pos: Pos) -> Vec<Line> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn attach_lines(lines: &mut Vec<Line>, other: &mut Vec<Line>) {
|
pub fn attach_lines(lines: &mut Vec<Line>, other: &mut Vec<Line>) {
|
||||||
if other.len() == 0 {
|
if other.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if lines.len() == 0 {
|
if lines.is_empty() {
|
||||||
lines.append(other);
|
lines.append(other);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -328,13 +331,6 @@ pub enum SelectMode {
|
|||||||
Block(Pos),
|
Block(Pos),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
enum CaseTransform {
|
|
||||||
Toggle,
|
|
||||||
Lower,
|
|
||||||
Upper,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct Pos {
|
pub struct Pos {
|
||||||
pub row: usize,
|
pub row: usize,
|
||||||
@@ -371,6 +367,7 @@ pub enum MotionKind {
|
|||||||
Line {
|
Line {
|
||||||
start: usize,
|
start: usize,
|
||||||
end: usize,
|
end: usize,
|
||||||
|
inclusive: bool
|
||||||
},
|
},
|
||||||
Block {
|
Block {
|
||||||
start: Pos,
|
start: Pos,
|
||||||
@@ -424,7 +421,6 @@ impl Edit {
|
|||||||
pub struct IndentCtx {
|
pub struct IndentCtx {
|
||||||
depth: usize,
|
depth: usize,
|
||||||
ctx: Vec<Tk>,
|
ctx: Vec<Tk>,
|
||||||
in_escaped_line: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IndentCtx {
|
impl IndentCtx {
|
||||||
@@ -459,16 +455,12 @@ impl IndentCtx {
|
|||||||
self.descend(tk);
|
self.descend(tk);
|
||||||
} else if self.ctx.last().is_some_and(|t| tk.is_closer_for(t)) {
|
} else if self.ctx.last().is_some_and(|t| tk.is_closer_for(t)) {
|
||||||
self.ascend();
|
self.ascend();
|
||||||
} else if matches!(tk.class, TkRule::Sep) && self.in_escaped_line {
|
|
||||||
self.in_escaped_line = false;
|
|
||||||
self.depth = self.depth.saturating_sub(1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn calculate(&mut self, input: &str) -> usize {
|
pub fn calculate(&mut self, input: &str) -> usize {
|
||||||
self.depth = 0;
|
self.depth = 0;
|
||||||
self.ctx.clear();
|
self.ctx.clear();
|
||||||
self.in_escaped_line = false;
|
|
||||||
|
|
||||||
let input_arc = Arc::new(input.to_string());
|
let input_arc = Arc::new(input.to_string());
|
||||||
let Ok(tokens) =
|
let Ok(tokens) =
|
||||||
@@ -482,11 +474,6 @@ impl IndentCtx {
|
|||||||
self.check_tk(tk);
|
self.check_tk(tk);
|
||||||
}
|
}
|
||||||
|
|
||||||
if input.ends_with("\\\n") {
|
|
||||||
self.in_escaped_line = true;
|
|
||||||
self.depth += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.depth
|
self.depth
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -651,8 +638,6 @@ impl LineBuf {
|
|||||||
self.cursor.pos = pos;
|
self.cursor.pos = pos;
|
||||||
}
|
}
|
||||||
fn set_row(&mut self, row: usize) {
|
fn set_row(&mut self, row: usize) {
|
||||||
let target_col = self.saved_col.unwrap_or(self.cursor.pos.col);
|
|
||||||
|
|
||||||
self.set_cursor(Pos {
|
self.set_cursor(Pos {
|
||||||
row,
|
row,
|
||||||
col: self.saved_col.unwrap_or(self.cursor.pos.col),
|
col: self.saved_col.unwrap_or(self.cursor.pos.col),
|
||||||
@@ -676,11 +661,19 @@ 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 rest = self.lines[row].split_off(col);
|
let level = self.calc_indent_level();
|
||||||
|
log::debug!("level: {level}");
|
||||||
|
let mut rest = self.lines[row].split_off(col);
|
||||||
|
let mut col = 0;
|
||||||
|
for tab in std::iter::repeat_n(Grapheme::from('\t'), level) {
|
||||||
|
rest.insert(0, tab);
|
||||||
|
col += 1;
|
||||||
|
}
|
||||||
|
|
||||||
self.lines.insert(row + 1, rest);
|
self.lines.insert(row + 1, rest);
|
||||||
self.cursor.pos = Pos {
|
self.cursor.pos = Pos {
|
||||||
row: row + 1,
|
row: row + 1,
|
||||||
col: 0,
|
col,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
fn verb_shell_cmd(&self, cmd: &str) -> ShResult<()> {
|
fn verb_shell_cmd(&self, cmd: &str) -> ShResult<()> {
|
||||||
@@ -706,7 +699,7 @@ impl LineBuf {
|
|||||||
self.break_line();
|
self.break_line();
|
||||||
} else {
|
} else {
|
||||||
self.insert(gr);
|
self.insert(gr);
|
||||||
self.cursor.pos = self.offset_cursor(0, 1);
|
self.cursor.pos.col += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -954,7 +947,7 @@ impl LineBuf {
|
|||||||
|
|
||||||
let mut last = first_non_ws;
|
let mut last = first_non_ws;
|
||||||
while let Some((_, c)) = classes.peek() {
|
while let Some((_, c)) = classes.peek() {
|
||||||
if c.is_other_class_or_ws(&first_non_ws.1) {
|
if c.is_ws() {
|
||||||
return Some(last.0);
|
return Some(last.0);
|
||||||
}
|
}
|
||||||
last = classes.next()?;
|
last = classes.next()?;
|
||||||
@@ -1031,14 +1024,16 @@ impl LineBuf {
|
|||||||
fn dispatch_text_obj(&mut self, count: u16, obj: TextObj) -> Option<MotionKind> {
|
fn dispatch_text_obj(&mut self, count: u16, obj: TextObj) -> Option<MotionKind> {
|
||||||
match obj {
|
match obj {
|
||||||
// text structures
|
// text structures
|
||||||
TextObj::Word(word, bound) => todo!(),
|
TextObj::Word(word, bound) => self.text_obj_word(count, word, obj, bound),
|
||||||
TextObj::Sentence(direction) => todo!(),
|
TextObj::Sentence(direction) => todo!(),
|
||||||
TextObj::Paragraph(direction) => todo!(),
|
TextObj::Paragraph(direction) => todo!(),
|
||||||
TextObj::WholeSentence(bound) => todo!(),
|
TextObj::WholeSentence(bound) => todo!(),
|
||||||
TextObj::WholeParagraph(bound) => todo!(),
|
TextObj::WholeParagraph(bound) => todo!(),
|
||||||
|
|
||||||
// quote stuff
|
// quote stuff
|
||||||
TextObj::DoubleQuote(bound) | TextObj::SingleQuote(bound) | TextObj::BacktickQuote(bound) => {
|
TextObj::DoubleQuote(bound) |
|
||||||
|
TextObj::SingleQuote(bound) |
|
||||||
|
TextObj::BacktickQuote(bound) => {
|
||||||
self.text_obj_quote(count, obj, bound)
|
self.text_obj_quote(count, obj, bound)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1052,7 +1047,165 @@ impl LineBuf {
|
|||||||
TextObj::Custom(_) => todo!(),
|
TextObj::Custom(_) => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn text_obj_quote(&mut self, count: u16, obj: TextObj, bound: Bound) -> Option<MotionKind> {
|
fn text_obj_word(
|
||||||
|
&mut self,
|
||||||
|
count: u16,
|
||||||
|
word: Word,
|
||||||
|
obj: TextObj,
|
||||||
|
bound: Bound,
|
||||||
|
) -> Option<MotionKind> {
|
||||||
|
use CharClass as C;
|
||||||
|
let mut fwd_classes = self.char_classes_forward();
|
||||||
|
let first_class = fwd_classes.next()?;
|
||||||
|
match first_class {
|
||||||
|
(pos,C::Whitespace) => {
|
||||||
|
match bound {
|
||||||
|
Bound::Inside => {
|
||||||
|
let mut fwd_classes = self.char_classes_forward_from(pos).peekable();
|
||||||
|
let mut bkwd_classes = self.char_classes_backward_from(pos).peekable();
|
||||||
|
let mut first = (pos,C::Whitespace);
|
||||||
|
let mut last = (pos,C::Whitespace);
|
||||||
|
while let Some((_,c)) = bkwd_classes.peek() {
|
||||||
|
if !c.is_ws() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
first = bkwd_classes.next()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
while let Some((_,c)) = fwd_classes.peek() {
|
||||||
|
if !c.is_ws() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
last = fwd_classes.next()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(MotionKind::Char {
|
||||||
|
start: first.0,
|
||||||
|
end: last.0,
|
||||||
|
inclusive: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Bound::Around => {
|
||||||
|
let mut fwd_classes = self.char_classes_forward_from(pos).peekable();
|
||||||
|
let mut bkwd_classes = self.char_classes_backward_from(pos).peekable();
|
||||||
|
let mut first = (pos,C::Whitespace);
|
||||||
|
let mut last = (pos,C::Whitespace);
|
||||||
|
while let Some((_,cl)) = bkwd_classes.peek() {
|
||||||
|
if !cl.is_ws() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
first = bkwd_classes.next()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
while let Some((_,cl)) = fwd_classes.peek() {
|
||||||
|
if !cl.is_ws() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
last = fwd_classes.next()?;
|
||||||
|
}
|
||||||
|
let word_class = fwd_classes.next()?.1;
|
||||||
|
while let Some((_,cl)) = fwd_classes.peek() {
|
||||||
|
match word {
|
||||||
|
Word::Big => {
|
||||||
|
if cl.is_ws() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Word::Normal => {
|
||||||
|
if cl.is_other_class_or_ws(&word_class) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
last = fwd_classes.next()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(MotionKind::Char {
|
||||||
|
start: first.0,
|
||||||
|
end: last.0,
|
||||||
|
inclusive: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(pos, c) => {
|
||||||
|
let break_cond = |cl: &C, c: &C| -> bool {
|
||||||
|
match word {
|
||||||
|
Word::Big => cl.is_ws(),
|
||||||
|
Word::Normal => cl.is_other_class(c),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match bound {
|
||||||
|
Bound::Inside => {
|
||||||
|
let mut fwd_classes = self.char_classes_forward_from(pos).peekable();
|
||||||
|
let mut bkwd_classes = self.char_classes_backward_from(pos).peekable();
|
||||||
|
let mut first = (pos,c);
|
||||||
|
let mut last = (pos,c);
|
||||||
|
|
||||||
|
while let Some((_,cl)) = bkwd_classes.peek() {
|
||||||
|
if break_cond(cl, &c) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
first = bkwd_classes.next()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
while let Some((_,cl)) = fwd_classes.peek() {
|
||||||
|
if break_cond(cl, &c) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
last = fwd_classes.next()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(MotionKind::Char {
|
||||||
|
start: first.0,
|
||||||
|
end: last.0,
|
||||||
|
inclusive: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Bound::Around => {
|
||||||
|
let mut fwd_classes = self.char_classes_forward_from(pos).peekable();
|
||||||
|
let mut bkwd_classes = self.char_classes_backward_from(pos).peekable();
|
||||||
|
let mut first = (pos,c);
|
||||||
|
let mut last = (pos,c);
|
||||||
|
|
||||||
|
while let Some((_,cl)) = bkwd_classes.peek() {
|
||||||
|
if break_cond(cl, &c) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
first = bkwd_classes.next()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
while let Some((_,cl)) = fwd_classes.peek() {
|
||||||
|
if break_cond(cl, &c) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
last = fwd_classes.next()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Include trailing whitespace
|
||||||
|
while let Some((_,cl)) = fwd_classes.peek() {
|
||||||
|
if !cl.is_ws() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
last = fwd_classes.next()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(MotionKind::Char {
|
||||||
|
start: first.0,
|
||||||
|
end: last.0,
|
||||||
|
inclusive: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn text_obj_quote(
|
||||||
|
&mut self,
|
||||||
|
count: u16,
|
||||||
|
obj: TextObj,
|
||||||
|
bound: Bound,
|
||||||
|
) -> Option<MotionKind> {
|
||||||
let q_ch = match obj {
|
let q_ch = match obj {
|
||||||
TextObj::DoubleQuote(_) => '"',
|
TextObj::DoubleQuote(_) => '"',
|
||||||
TextObj::SingleQuote(_) => '\'',
|
TextObj::SingleQuote(_) => '\'',
|
||||||
@@ -1104,11 +1257,6 @@ impl LineBuf {
|
|||||||
TextObj::Angle(_) => ('<', '>'),
|
TextObj::Angle(_) => ('<', '>'),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
log::debug!(
|
|
||||||
"Finding text object delimited by '{}' and '{}'",
|
|
||||||
opener,
|
|
||||||
closer
|
|
||||||
);
|
|
||||||
let mut depth = 0;
|
let mut depth = 0;
|
||||||
let start_pos = self
|
let start_pos = self
|
||||||
.scan_backward(|g| {
|
.scan_backward(|g| {
|
||||||
@@ -1124,7 +1272,6 @@ impl LineBuf {
|
|||||||
false
|
false
|
||||||
})
|
})
|
||||||
.or_else(|| self.scan_forward(|g| g.as_char() == Some(opener)))?;
|
.or_else(|| self.scan_forward(|g| g.as_char() == Some(opener)))?;
|
||||||
log::debug!("Found opener at {:?}", start_pos);
|
|
||||||
|
|
||||||
depth = 0;
|
depth = 0;
|
||||||
let end_pos = self.scan_forward_from(start_pos, |g| {
|
let end_pos = self.scan_forward_from(start_pos, |g| {
|
||||||
@@ -1136,7 +1283,6 @@ impl LineBuf {
|
|||||||
}
|
}
|
||||||
depth == 0
|
depth == 0
|
||||||
})?;
|
})?;
|
||||||
log::debug!("Found closer at {:?}", end_pos);
|
|
||||||
|
|
||||||
match bound {
|
match bound {
|
||||||
Bound::Around => Some(MotionKind::Char {
|
Bound::Around => Some(MotionKind::Char {
|
||||||
@@ -1155,6 +1301,124 @@ impl LineBuf {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fn gr_at(&self, pos: Pos) -> Option<&Grapheme> {
|
||||||
|
self.lines.get(pos.row)?.0.get(pos.col)
|
||||||
|
}
|
||||||
|
fn clamp_pos(&self, mut pos: Pos) -> Pos {
|
||||||
|
pos.clamp_row(&self.lines);
|
||||||
|
pos.clamp_col(&self.lines[pos.row].0, false);
|
||||||
|
pos
|
||||||
|
}
|
||||||
|
fn number_at_cursor(&self) -> Option<(Pos,Pos)> {
|
||||||
|
self.number_at(self.cursor.pos)
|
||||||
|
}
|
||||||
|
/// Returns the start/end span of a number at a given position, if any
|
||||||
|
fn number_at(&self, mut pos: Pos) -> Option<(Pos,Pos)> {
|
||||||
|
let is_number_char = |gr: &Grapheme| gr.as_char().is_some_and(|c| c == '.' || c == '-' || c.is_ascii_digit());
|
||||||
|
let is_digit = |gr: &Grapheme| gr.as_char().is_some_and(|c| c.is_ascii_digit());
|
||||||
|
|
||||||
|
pos = self.clamp_pos(pos);
|
||||||
|
if !is_number_char(self.gr_at(pos)?) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut start = self.scan_backward_from(pos, |g| !is_digit(g))
|
||||||
|
.map(|pos| Pos { row: pos.row, col: pos.col + 1 })
|
||||||
|
.unwrap_or(Pos::MIN);
|
||||||
|
let end = self.scan_forward_from(pos, |g| !is_digit(g))
|
||||||
|
.map(|pos| Pos { row: pos.row, col: pos.col.saturating_sub(1) })
|
||||||
|
.unwrap_or(Pos { row: pos.row, col: self.lines[pos.row].len().saturating_sub(1) });
|
||||||
|
|
||||||
|
if start > Pos::MIN && self.lines[start.row][start.col.saturating_sub(1)].as_char() == Some('-') {
|
||||||
|
start.col -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some((start, end))
|
||||||
|
}
|
||||||
|
fn adjust_number(&mut self, inc: i64) -> Option<()> {
|
||||||
|
let (s,e) = if let Some(range) = self.select_range() {
|
||||||
|
match range {
|
||||||
|
Motion::CharRange(s, e) => (s,e),
|
||||||
|
_ => return None,
|
||||||
|
}
|
||||||
|
} else if let Some((s,e)) = self.number_at_cursor() {
|
||||||
|
(s,e)
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
let word = self.pos_slice_str(s,e);
|
||||||
|
|
||||||
|
let num_fmt = if word.starts_with("0x") {
|
||||||
|
let body = word.strip_prefix("0x").unwrap();
|
||||||
|
let width = body.len();
|
||||||
|
let num = i64::from_str_radix(body, 16).ok()?;
|
||||||
|
let new_num = num + inc;
|
||||||
|
format!("0x{new_num:0>width$x}")
|
||||||
|
} else if word.starts_with("0b") {
|
||||||
|
let body = word.strip_prefix("0b").unwrap();
|
||||||
|
let width = body.len();
|
||||||
|
let num = i64::from_str_radix(body, 2).ok()?;
|
||||||
|
let new_num = num + inc;
|
||||||
|
format!("0b{new_num:0>width$b}")
|
||||||
|
} else if word.starts_with("0o") {
|
||||||
|
let body = word.strip_prefix("0o").unwrap();
|
||||||
|
let width = body.len();
|
||||||
|
let num = i64::from_str_radix(body, 8).ok()?;
|
||||||
|
let new_num = num + inc;
|
||||||
|
format!("0o{new_num:0>width$o}")
|
||||||
|
} else if let Ok(num) = word.parse::<i64>() {
|
||||||
|
let width = word.len();
|
||||||
|
let new_num = num + inc;
|
||||||
|
if new_num < 0 {
|
||||||
|
let abs = new_num.unsigned_abs();
|
||||||
|
let digit_width = if num < 0 { width - 1 } else { width };
|
||||||
|
format!("-{abs:0>digit_width$}")
|
||||||
|
} else if num < 0 {
|
||||||
|
let digit_width = width - 1;
|
||||||
|
format!("{new_num:0>digit_width$}")
|
||||||
|
} else {
|
||||||
|
format!("{new_num:0>width$}")
|
||||||
|
}
|
||||||
|
} else { return None };
|
||||||
|
|
||||||
|
self.replace_range(s, e, &num_fmt);
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
fn replace_range(&mut self, s: Pos, e: Pos, new: &str) -> Vec<Line> {
|
||||||
|
let motion = MotionKind::Char { start: s, end: e, inclusive: true };
|
||||||
|
let content = self.extract_range(&motion);
|
||||||
|
self.set_cursor(s);
|
||||||
|
self.insert_str(new);
|
||||||
|
content
|
||||||
|
}
|
||||||
|
fn pos_slice_str(&self, s: Pos, e: Pos) -> String {
|
||||||
|
let (s,e) = ordered(s,e);
|
||||||
|
if s.row == e.row {
|
||||||
|
self.lines[s.row].0[s.col..=e.col]
|
||||||
|
.iter()
|
||||||
|
.map(|g| g.to_string())
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
let mut result = String::new();
|
||||||
|
// First line from s.col to end
|
||||||
|
for g in &self.lines[s.row].0[s.col..] {
|
||||||
|
result.push_str(&g.to_string());
|
||||||
|
}
|
||||||
|
// Middle lines
|
||||||
|
for line in &self.lines[s.row + 1..e.row] {
|
||||||
|
result.push('\n');
|
||||||
|
result.push_str(&line.to_string());
|
||||||
|
}
|
||||||
|
// Last line from start to e.col
|
||||||
|
result.push('\n');
|
||||||
|
for g in &self.lines[e.row].0[..=e.col] {
|
||||||
|
result.push_str(&g.to_string());
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// 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)
|
||||||
}
|
}
|
||||||
@@ -1168,10 +1432,11 @@ impl LineBuf {
|
|||||||
|
|
||||||
let kind = match motion {
|
let kind = match motion {
|
||||||
Motion::WholeLine => {
|
Motion::WholeLine => {
|
||||||
let row = self.row();
|
let row = (self.row() + (count.saturating_sub(1))).min(self.lines.len().saturating_sub(1));
|
||||||
Some(MotionKind::Line {
|
Some(MotionKind::Line {
|
||||||
start: row,
|
start: row,
|
||||||
end: row,
|
end: row,
|
||||||
|
inclusive: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Motion::TextObj(text_obj) => self.dispatch_text_obj(*count as u16, text_obj.clone()),
|
Motion::TextObj(text_obj) => self.dispatch_text_obj(*count as u16, text_obj.clone()),
|
||||||
@@ -1191,7 +1456,7 @@ impl LineBuf {
|
|||||||
inclusive: true,
|
inclusive: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Motion::BeginningOfFirstWord => {
|
Motion::StartOfFirstWord => {
|
||||||
let mut target = Pos {
|
let mut target = Pos {
|
||||||
row: self.row(),
|
row: self.row(),
|
||||||
col: 0,
|
col: 0,
|
||||||
@@ -1210,17 +1475,17 @@ impl LineBuf {
|
|||||||
inclusive: true,
|
inclusive: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
dir @ (Motion::BeginningOfLine | Motion::EndOfLine) => {
|
dir @ (Motion::StartOfLine | Motion::EndOfLine) => {
|
||||||
let off = match dir {
|
let (inclusive,off) = match dir {
|
||||||
Motion::BeginningOfLine => isize::MIN,
|
Motion::StartOfLine => (false,isize::MIN),
|
||||||
Motion::EndOfLine => isize::MAX,
|
Motion::EndOfLine => (true,isize::MAX),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
let target = self.offset_cursor(0, off);
|
let target = self.offset_cursor(0, off);
|
||||||
(target != self.cursor.pos).then_some(MotionKind::Char {
|
(target != self.cursor.pos).then_some(MotionKind::Char {
|
||||||
start: self.cursor.pos,
|
start: self.cursor.pos,
|
||||||
end: target,
|
end: target,
|
||||||
inclusive: true,
|
inclusive,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Motion::WordMotion(to, word, dir) => {
|
Motion::WordMotion(to, word, dir) => {
|
||||||
@@ -1239,11 +1504,10 @@ impl LineBuf {
|
|||||||
Motion::CharSearch(dir, dest, char) => {
|
Motion::CharSearch(dir, dest, char) => {
|
||||||
let off = self.search_char(dir, dest, char);
|
let off = self.search_char(dir, dest, char);
|
||||||
let target = self.offset_cursor(0, off);
|
let target = self.offset_cursor(0, off);
|
||||||
let inclusive = matches!(dest, Dest::On);
|
|
||||||
(target != self.cursor.pos).then_some(MotionKind::Char {
|
(target != self.cursor.pos).then_some(MotionKind::Char {
|
||||||
start: self.cursor.pos,
|
start: self.cursor.pos,
|
||||||
end: target,
|
end: target,
|
||||||
inclusive,
|
inclusive: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
dir @ (Motion::BackwardChar | Motion::ForwardChar)
|
dir @ (Motion::BackwardChar | Motion::ForwardChar)
|
||||||
@@ -1277,7 +1541,7 @@ impl LineBuf {
|
|||||||
let row = self.row();
|
let row = self.row();
|
||||||
let target_row = self.offset_row(off);
|
let target_row = self.offset_row(off);
|
||||||
let (s, e) = ordered(row, target_row);
|
let (s, e) = ordered(row, target_row);
|
||||||
Some(MotionKind::Line { start: s, end: e })
|
Some(MotionKind::Line { start: s, end: e, inclusive: true })
|
||||||
} else {
|
} else {
|
||||||
if self.saved_col.is_none() {
|
if self.saved_col.is_none() {
|
||||||
self.saved_col = Some(self.cursor.pos.col);
|
self.saved_col = Some(self.cursor.pos.col);
|
||||||
@@ -1307,7 +1571,7 @@ impl LineBuf {
|
|||||||
let row = self.row();
|
let row = self.row();
|
||||||
let target_row = self.offset_row(off);
|
let target_row = self.offset_row(off);
|
||||||
let (s, e) = ordered(row, target_row);
|
let (s, e) = ordered(row, target_row);
|
||||||
Some(MotionKind::Line { start: s, end: e })
|
Some(MotionKind::Line { start: s, end: e, inclusive: false })
|
||||||
} else {
|
} else {
|
||||||
let target = self.offset_cursor(off, 0);
|
let target = self.offset_cursor(off, 0);
|
||||||
(target != self.cursor.pos).then_some(MotionKind::Char {
|
(target != self.cursor.pos).then_some(MotionKind::Char {
|
||||||
@@ -1320,6 +1584,7 @@ impl LineBuf {
|
|||||||
Motion::WholeBuffer => Some(MotionKind::Line {
|
Motion::WholeBuffer => Some(MotionKind::Line {
|
||||||
start: 0,
|
start: 0,
|
||||||
end: self.lines.len().saturating_sub(1),
|
end: self.lines.len().saturating_sub(1),
|
||||||
|
inclusive: false
|
||||||
}),
|
}),
|
||||||
Motion::ToColumn => todo!(),
|
Motion::ToColumn => todo!(),
|
||||||
Motion::ToDelimMatch => todo!(),
|
Motion::ToDelimMatch => todo!(),
|
||||||
@@ -1336,7 +1601,7 @@ impl LineBuf {
|
|||||||
}
|
}
|
||||||
Motion::LineRange(s, e) => {
|
Motion::LineRange(s, e) => {
|
||||||
let (s, e) = ordered(*s, *e);
|
let (s, e) = ordered(*s, *e);
|
||||||
Some(MotionKind::Line { start: s, end: e })
|
Some(MotionKind::Line { start: s, end: e, inclusive: false })
|
||||||
}
|
}
|
||||||
Motion::BlockRange(s, e) => {
|
Motion::BlockRange(s, e) => {
|
||||||
let (s, e) = ordered(*s, *e);
|
let (s, e) = ordered(*s, *e);
|
||||||
@@ -1352,15 +1617,11 @@ impl LineBuf {
|
|||||||
self.lines = buffer;
|
self.lines = buffer;
|
||||||
kind
|
kind
|
||||||
}
|
}
|
||||||
|
/// 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)
|
||||||
}
|
}
|
||||||
fn apply_motion_inner(&mut self, motion: MotionKind, accept_hint: bool) -> ShResult<()> {
|
fn apply_motion_inner(&mut self, motion: MotionKind, accept_hint: bool) -> ShResult<()> {
|
||||||
log::debug!(
|
|
||||||
"Applying motion: {:?}, current cursor: {:?}",
|
|
||||||
motion,
|
|
||||||
self.cursor.pos
|
|
||||||
);
|
|
||||||
match motion {
|
match motion {
|
||||||
MotionKind::Char { end, .. } => {
|
MotionKind::Char { end, .. } => {
|
||||||
if accept_hint && self.has_hint() && end >= self.end_pos() {
|
if accept_hint && self.has_hint() && end >= self.end_pos() {
|
||||||
@@ -1397,7 +1658,14 @@ impl LineBuf {
|
|||||||
self.lines = buf;
|
self.lines = buf;
|
||||||
extracted
|
extracted
|
||||||
}
|
}
|
||||||
MotionKind::Line { start, end } => self.lines.drain(*start..=*end).collect(),
|
MotionKind::Line { start, end, inclusive } => {
|
||||||
|
let end = if *inclusive {
|
||||||
|
*end
|
||||||
|
} else {
|
||||||
|
end.saturating_sub(1)
|
||||||
|
};
|
||||||
|
self.lines.drain(*start..=end).collect()
|
||||||
|
}
|
||||||
MotionKind::Block { start, end } => {
|
MotionKind::Block { start, end } => {
|
||||||
let (s, e) = ordered(*start, *end);
|
let (s, e) = ordered(*start, *end);
|
||||||
(s.row..=e.row)
|
(s.row..=e.row)
|
||||||
@@ -1415,6 +1683,7 @@ 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,
|
||||||
@@ -1425,6 +1694,17 @@ impl LineBuf {
|
|||||||
fn delete_range(&mut self, motion: &MotionKind) -> Vec<Line> {
|
fn delete_range(&mut self, motion: &MotionKind) -> Vec<Line> {
|
||||||
self.extract_range(motion)
|
self.extract_range(motion)
|
||||||
}
|
}
|
||||||
|
pub fn calc_indent_level(&mut self) -> usize {
|
||||||
|
self.calc_indent_level_for_pos(self.cursor.pos)
|
||||||
|
}
|
||||||
|
pub fn calc_indent_level_for_pos(&mut self, pos: Pos) -> usize {
|
||||||
|
let mut lines = self.lines.clone();
|
||||||
|
split_lines_at(&mut lines, pos);
|
||||||
|
let raw = join_lines(&lines);
|
||||||
|
log::debug!("Calculating indent level for pos {:?} with raw text:\n{:?}", pos, raw);
|
||||||
|
|
||||||
|
self.indent_ctx.calculate(&raw)
|
||||||
|
}
|
||||||
fn motion_mutation(&mut self, motion: MotionKind, f: impl Fn(&Grapheme) -> Grapheme) {
|
fn motion_mutation(&mut self, motion: MotionKind, f: impl Fn(&Grapheme) -> Grapheme) {
|
||||||
match motion {
|
match motion {
|
||||||
MotionKind::Char {
|
MotionKind::Char {
|
||||||
@@ -1464,7 +1744,8 @@ impl LineBuf {
|
|||||||
self.lines[e.row][col] = f(&self.lines[e.row][col]);
|
self.lines[e.row][col] = f(&self.lines[e.row][col]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MotionKind::Line { start, end } => {
|
MotionKind::Line { start, end, inclusive } => {
|
||||||
|
let end = if inclusive { end } else { end.saturating_sub(1) };
|
||||||
let end = end.min(self.lines.len().saturating_sub(1));
|
let end = end.min(self.lines.len().saturating_sub(1));
|
||||||
for row in start..=end {
|
for row in start..=end {
|
||||||
let line = self.line_mut(row);
|
let line = self.line_mut(row);
|
||||||
@@ -1500,6 +1781,7 @@ 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
|
||||||
@@ -1518,6 +1800,14 @@ impl LineBuf {
|
|||||||
};
|
};
|
||||||
let content = if *verb == Verb::Yank {
|
let content = if *verb == Verb::Yank {
|
||||||
self.yank_range(&motion)
|
self.yank_range(&motion)
|
||||||
|
} else if *verb == Verb::Change && matches!(motion, MotionKind::Line {..}) {
|
||||||
|
let n_lines = self.lines.len();
|
||||||
|
let content = self.delete_range(&motion);
|
||||||
|
let row = self.row();
|
||||||
|
if n_lines > 1 {
|
||||||
|
self.lines.insert(row, Line::default());
|
||||||
|
}
|
||||||
|
content
|
||||||
} else {
|
} else {
|
||||||
self.delete_range(&motion)
|
self.delete_range(&motion)
|
||||||
};
|
};
|
||||||
@@ -1533,9 +1823,21 @@ impl LineBuf {
|
|||||||
let (s, _) = ordered(start, end);
|
let (s, _) = ordered(start, end);
|
||||||
self.set_cursor(s);
|
self.set_cursor(s);
|
||||||
}
|
}
|
||||||
MotionKind::Line { start, end } => {
|
MotionKind::Line { start, end, inclusive } => {
|
||||||
|
let end = if inclusive { end } else { end.saturating_sub(1) };
|
||||||
let (s, _) = ordered(start, end);
|
let (s, _) = ordered(start, end);
|
||||||
self.set_row(s);
|
self.set_row(s);
|
||||||
|
if *verb == Verb::Change {
|
||||||
|
// we've gotta indent
|
||||||
|
let level = self.calc_indent_level();
|
||||||
|
let line = self.cur_line_mut();
|
||||||
|
let mut col = 0;
|
||||||
|
for tab in std::iter::repeat_n(Grapheme::from('\t'), level) {
|
||||||
|
line.0.insert(col, tab);
|
||||||
|
col += 1;
|
||||||
|
}
|
||||||
|
self.cursor.pos = self.offset_cursor(0, col as isize);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
MotionKind::Block { start, .. } => {
|
MotionKind::Block { start, .. } => {
|
||||||
let (s, _) = ordered(self.cursor.pos, start);
|
let (s, _) = ordered(self.cursor.pos, start);
|
||||||
@@ -1580,8 +1882,8 @@ impl LineBuf {
|
|||||||
.unwrap_or_else(|| gr.clone())
|
.unwrap_or_else(|| gr.clone())
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Verb::IncrementNumber(_) => todo!(),
|
Verb::IncrementNumber(n) => { self.adjust_number(*n as i64); },
|
||||||
Verb::DecrementNumber(_) => todo!(),
|
Verb::DecrementNumber(n) => { self.adjust_number(-(*n as i64)); },
|
||||||
Verb::ToLower => {
|
Verb::ToLower => {
|
||||||
let Some(motion) = self.eval_motion(cmd) else {
|
let Some(motion) = self.eval_motion(cmd) else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@@ -1625,11 +1927,14 @@ impl LineBuf {
|
|||||||
};
|
};
|
||||||
match content {
|
match content {
|
||||||
RegisterContent::Span(lines) => {
|
RegisterContent::Span(lines) => {
|
||||||
|
let move_cursor = lines.len() == 1 && lines[0].len() > 1;
|
||||||
|
let content_len: usize = lines.iter().map(|l| l.len()).sum();
|
||||||
let row = self.row();
|
let row = self.row();
|
||||||
let col = match anchor {
|
let col = match anchor {
|
||||||
Anchor::After => (self.col() + 1).min(self.cur_line().len()),
|
Anchor::After => (self.col() + 1).min(self.cur_line().len()),
|
||||||
Anchor::Before => self.col(),
|
Anchor::Before => self.col(),
|
||||||
};
|
};
|
||||||
|
let start_len = self.lines[row].len();
|
||||||
let mut right = self.lines[row].split_off(col);
|
let mut right = self.lines[row].split_off(col);
|
||||||
|
|
||||||
let mut lines = lines.clone();
|
let mut lines = lines.clone();
|
||||||
@@ -1645,6 +1950,14 @@ impl LineBuf {
|
|||||||
|
|
||||||
// Reattach right half to the last inserted line
|
// Reattach right half to the last inserted line
|
||||||
self.lines[row + last].append(&mut right);
|
self.lines[row + last].append(&mut right);
|
||||||
|
|
||||||
|
let end_len = self.lines[row].len();
|
||||||
|
let delta = end_len.saturating_sub(start_len);
|
||||||
|
if move_cursor {
|
||||||
|
self.cursor.pos = self.offset_cursor(0, delta as isize);
|
||||||
|
} else if content_len > 1 {
|
||||||
|
self.cursor.pos = self.offset_cursor(0, 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
RegisterContent::Line(lines) => {
|
RegisterContent::Line(lines) => {
|
||||||
let row = match anchor {
|
let row = match anchor {
|
||||||
@@ -1665,9 +1978,18 @@ impl LineBuf {
|
|||||||
let row = self.row();
|
let row = self.row();
|
||||||
let target = (row + 1).min(self.lines.len());
|
let target = (row + 1).min(self.lines.len());
|
||||||
self.lines.insert(target, Line::default());
|
self.lines.insert(target, Line::default());
|
||||||
|
|
||||||
|
let level = self.calc_indent_level_for_pos(Pos { row: target, col: 0 });
|
||||||
|
let line = self.line_mut(target);
|
||||||
|
let mut col = 0;
|
||||||
|
for tab in std::iter::repeat_n(Grapheme::from('\t'), level) {
|
||||||
|
line.insert(0, tab);
|
||||||
|
col += 1;
|
||||||
|
}
|
||||||
|
|
||||||
self.cursor.pos = Pos {
|
self.cursor.pos = Pos {
|
||||||
row: row + 1,
|
row: row + 1,
|
||||||
col: 0,
|
col,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
Anchor::Before => {
|
Anchor::Before => {
|
||||||
@@ -1724,10 +2046,23 @@ impl LineBuf {
|
|||||||
self.cursor.exclusive = old_exclusive;
|
self.cursor.exclusive = old_exclusive;
|
||||||
}
|
}
|
||||||
Verb::InsertChar(ch) => {
|
Verb::InsertChar(ch) => {
|
||||||
|
let level = self.calc_indent_level();
|
||||||
self.insert(Grapheme::from(*ch));
|
self.insert(Grapheme::from(*ch));
|
||||||
if let Some(motion) = self.eval_motion(cmd) {
|
if let Some(motion) = self.eval_motion(cmd) {
|
||||||
self.apply_motion(motion)?;
|
self.apply_motion(motion)?;
|
||||||
}
|
}
|
||||||
|
let new_level = self.calc_indent_level();
|
||||||
|
if new_level < level {
|
||||||
|
let delta = level - new_level;
|
||||||
|
let line = self.cur_line_mut();
|
||||||
|
for _ in 0..delta {
|
||||||
|
if line.0.first().is_some_and(|c| c.as_char() == Some('\t')) {
|
||||||
|
line.0.remove(0);
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Verb::Insert(s) => self.insert_str(s),
|
Verb::Insert(s) => self.insert_str(s),
|
||||||
Verb::Indent => todo!(),
|
Verb::Indent => todo!(),
|
||||||
@@ -1745,7 +2080,7 @@ impl LineBuf {
|
|||||||
write_meta(|m| m.post_system_message(format!("{} is not a file", path_buf.display())));
|
write_meta(|m| m.post_system_message(format!("{} is not a file", path_buf.display())));
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let Ok(contents) = std::fs::read_to_string(&path_buf) else {
|
let Ok(contents) = std::fs::read_to_string(path_buf) else {
|
||||||
write_meta(|m| {
|
write_meta(|m| {
|
||||||
m.post_system_message(format!("Failed to read file {}", path_buf.display()))
|
m.post_system_message(format!("Failed to read file {}", path_buf.display()))
|
||||||
});
|
});
|
||||||
@@ -1810,9 +2145,17 @@ impl LineBuf {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
Verb::Edit(path) => {
|
Verb::Edit(path) => {
|
||||||
|
if read_vars(|v| v.try_get_var("EDITOR")).is_none() {
|
||||||
|
write_meta(|m| {
|
||||||
|
m.post_system_message(
|
||||||
|
"$EDITOR is unset. Aborting edit.".into(),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
} else {
|
||||||
let input = format!("$EDITOR {}", path.display());
|
let input = format!("$EDITOR {}", path.display());
|
||||||
exec_input(input, None, true, Some("ex edit".into()))?;
|
exec_input(input, None, true, Some("ex edit".into()))?;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Verb::Complete
|
Verb::Complete
|
||||||
| Verb::ExMode
|
| Verb::ExMode
|
||||||
@@ -1919,11 +2262,6 @@ impl LineBuf {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn fix_cursor(&mut self) {
|
pub fn fix_cursor(&mut self) {
|
||||||
log::debug!(
|
|
||||||
"Fixing cursor, exclusive: {}, current pos: {:?}",
|
|
||||||
self.cursor.exclusive,
|
|
||||||
self.cursor.pos
|
|
||||||
);
|
|
||||||
if self.cursor.pos.row >= self.lines.len() {
|
if self.cursor.pos.row >= self.lines.len() {
|
||||||
self.cursor.pos.row = self.lines.len().saturating_sub(1);
|
self.cursor.pos.row = self.lines.len().saturating_sub(1);
|
||||||
}
|
}
|
||||||
@@ -2210,6 +2548,10 @@ impl LineBuf {
|
|||||||
offset + pos.col.min(self.lines[row].len())
|
offset + pos.col.min(self.lines[row].len())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn cursor_to_flat(&self) -> usize {
|
||||||
|
self.pos_to_flat(self.cursor.pos)
|
||||||
|
}
|
||||||
|
|
||||||
/// Compat shim: attempt history expansion. Stub that returns false.
|
/// Compat shim: attempt history expansion. Stub that returns false.
|
||||||
pub fn attempt_history_expansion(&mut self, _history: &super::history::History) -> bool {
|
pub fn attempt_history_expansion(&mut self, _history: &super::history::History) -> bool {
|
||||||
// TODO: implement history expansion for 2D buffer
|
// TODO: implement history expansion for 2D buffer
|
||||||
@@ -2239,12 +2581,6 @@ impl LineBuf {
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compat shim: calculate indent level.
|
|
||||||
pub fn calc_indent_level(&mut self) {
|
|
||||||
let joined = self.joined();
|
|
||||||
self.indent_ctx.calculate(&joined);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compat shim: mark where insert mode started.
|
/// Compat shim: mark where insert mode started.
|
||||||
pub fn mark_insert_mode_start_pos(&mut self) {
|
pub fn mark_insert_mode_start_pos(&mut self) {
|
||||||
self.insert_mode_start_pos = Some(self.cursor.pos);
|
self.insert_mode_start_pos = Some(self.cursor.pos);
|
||||||
|
|||||||
@@ -901,7 +901,12 @@ impl ShedVi {
|
|||||||
return Ok(Some(ReadlineEvent::Eof));
|
return Ok(Some(ReadlineEvent::Eof));
|
||||||
}
|
}
|
||||||
|
|
||||||
let has_edit_verb = cmd.verb().is_some_and(|v| v.1.is_edit());
|
// check if it's an edit
|
||||||
|
// we don't count Verb::Change since its possible for it to be called and not actually change anything
|
||||||
|
// e.g. 'cc' on an empty line, 'C' at the end of a line, etc.
|
||||||
|
// this is only used for ringing the bell
|
||||||
|
let has_edit_verb = cmd.verb().is_some_and(|v| v.1.is_edit() && v.1 != Verb::Change);
|
||||||
|
|
||||||
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);
|
||||||
if is_shell_cmd {
|
if is_shell_cmd {
|
||||||
@@ -1315,6 +1320,12 @@ 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() {
|
||||||
|
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() {
|
||||||
return self.exec_mode_transition(cmd, from_replay);
|
return self.exec_mode_transition(cmd, from_replay);
|
||||||
} else if cmd.is_cmd_repeat() {
|
} else if cmd.is_cmd_repeat() {
|
||||||
|
|||||||
@@ -24,7 +24,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.joined(), $expected_text);
|
assert_eq!(vi.editor.joined(), $expected_text);
|
||||||
assert_eq!(vi.editor.cursor.get(), $expected_cursor);
|
assert_eq!(vi.editor.cursor_to_flat(), $expected_cursor);
|
||||||
}
|
}
|
||||||
)*
|
)*
|
||||||
|
|
||||||
@@ -513,6 +513,6 @@ fn vi_auto_indent() {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
vi.editor.joined(),
|
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\tbar \\\n\t\t\t\tbiz \\\n\t\t\t\tbazz\n\t\t\t\tbreak\n\t\t\tdone\n\t\t;;\n\tesac\n}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -319,8 +319,8 @@ pub enum Motion {
|
|||||||
WholeLine,
|
WholeLine,
|
||||||
TextObj(TextObj),
|
TextObj(TextObj),
|
||||||
EndOfLastWord,
|
EndOfLastWord,
|
||||||
BeginningOfFirstWord,
|
StartOfFirstWord,
|
||||||
BeginningOfLine,
|
StartOfLine,
|
||||||
EndOfLine,
|
EndOfLine,
|
||||||
WordMotion(To, Word, Direction),
|
WordMotion(To, Word, Direction),
|
||||||
CharSearch(Direction, Dest, Grapheme),
|
CharSearch(Direction, Dest, Grapheme),
|
||||||
@@ -369,8 +369,8 @@ impl Motion {
|
|||||||
pub fn is_exclusive(&self) -> bool {
|
pub fn is_exclusive(&self) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
&self,
|
&self,
|
||||||
Self::BeginningOfLine
|
Self::StartOfLine
|
||||||
| Self::BeginningOfFirstWord
|
| Self::StartOfFirstWord
|
||||||
| Self::ToColumn
|
| Self::ToColumn
|
||||||
| Self::TextObj(TextObj::Sentence(_))
|
| Self::TextObj(TextObj::Sentence(_))
|
||||||
| Self::TextObj(TextObj::Paragraph(_))
|
| Self::TextObj(TextObj::Paragraph(_))
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ pub trait ViMode {
|
|||||||
pub fn common_cmds(key: E) -> Option<ViCmd> {
|
pub fn common_cmds(key: E) -> Option<ViCmd> {
|
||||||
let mut pending_cmd = ViCmd::new();
|
let mut pending_cmd = ViCmd::new();
|
||||||
match key {
|
match key {
|
||||||
E(K::Home, M::NONE) => pending_cmd.set_motion(MotionCmd(1, Motion::BeginningOfLine)),
|
E(K::Home, M::NONE) => pending_cmd.set_motion(MotionCmd(1, Motion::StartOfLine)),
|
||||||
E(K::End, M::NONE) => pending_cmd.set_motion(MotionCmd(1, Motion::EndOfLine)),
|
E(K::End, M::NONE) => pending_cmd.set_motion(MotionCmd(1, Motion::EndOfLine)),
|
||||||
E(K::Left, M::NONE) => pending_cmd.set_motion(MotionCmd(1, Motion::BackwardChar)),
|
E(K::Left, M::NONE) => pending_cmd.set_motion(MotionCmd(1, Motion::BackwardChar)),
|
||||||
E(K::Right, M::NONE) => pending_cmd.set_motion(MotionCmd(1, Motion::ForwardChar)),
|
E(K::Right, M::NONE) => pending_cmd.set_motion(MotionCmd(1, Motion::ForwardChar)),
|
||||||
|
|||||||
@@ -332,7 +332,7 @@ impl ViNormal {
|
|||||||
return Some(ViCmd {
|
return Some(ViCmd {
|
||||||
register,
|
register,
|
||||||
verb: Some(VerbCmd(count, Verb::InsertMode)),
|
verb: Some(VerbCmd(count, Verb::InsertMode)),
|
||||||
motion: Some(MotionCmd(1, Motion::BeginningOfFirstWord)),
|
motion: Some(MotionCmd(1, Motion::StartOfFirstWord)),
|
||||||
raw_seq: self.take_cmd(),
|
raw_seq: self.take_cmd(),
|
||||||
flags: self.flags(),
|
flags: self.flags(),
|
||||||
});
|
});
|
||||||
@@ -583,11 +583,11 @@ impl ViNormal {
|
|||||||
}
|
}
|
||||||
'^' => {
|
'^' => {
|
||||||
chars = chars_clone;
|
chars = chars_clone;
|
||||||
break 'motion_parse Some(MotionCmd(count, Motion::BeginningOfFirstWord));
|
break 'motion_parse Some(MotionCmd(count, Motion::StartOfFirstWord));
|
||||||
}
|
}
|
||||||
'0' => {
|
'0' => {
|
||||||
chars = chars_clone;
|
chars = chars_clone;
|
||||||
break 'motion_parse Some(MotionCmd(count, Motion::BeginningOfLine));
|
break 'motion_parse Some(MotionCmd(count, Motion::StartOfLine));
|
||||||
}
|
}
|
||||||
'$' => {
|
'$' => {
|
||||||
chars = chars_clone;
|
chars = chars_clone;
|
||||||
|
|||||||
@@ -287,7 +287,7 @@ impl ViVisual {
|
|||||||
return Some(ViCmd {
|
return Some(ViCmd {
|
||||||
register,
|
register,
|
||||||
verb: Some(VerbCmd(count, Verb::InsertMode)),
|
verb: Some(VerbCmd(count, Verb::InsertMode)),
|
||||||
motion: Some(MotionCmd(1, Motion::BeginningOfLine)),
|
motion: Some(MotionCmd(1, Motion::StartOfLine)),
|
||||||
raw_seq: self.take_cmd(),
|
raw_seq: self.take_cmd(),
|
||||||
flags: CmdFlags::empty(),
|
flags: CmdFlags::empty(),
|
||||||
});
|
});
|
||||||
@@ -473,7 +473,7 @@ impl ViVisual {
|
|||||||
}
|
}
|
||||||
'0' => {
|
'0' => {
|
||||||
chars = chars_clone;
|
chars = chars_clone;
|
||||||
break 'motion_parse Some(MotionCmd(count, Motion::BeginningOfLine));
|
break 'motion_parse Some(MotionCmd(count, Motion::StartOfLine));
|
||||||
}
|
}
|
||||||
'$' => {
|
'$' => {
|
||||||
chars = chars_clone;
|
chars = chars_clone;
|
||||||
|
|||||||
Reference in New Issue
Block a user