use std::borrow::Cow;
use crate::editing::{
application::ApplicationInfo,
context::Resolve,
cursor::{Adjustable, Cursor, CursorAdjustment, CursorChoice},
rope::EditRope,
store::{RegisterCell, RegisterPutFlags, Store},
};
use crate::errors::EditResult;
use crate::prelude::*;
use crate::util::into_range;
use super::{CursorRange, EditBuffer};
pub trait EditActions<C, I>
where
I: ApplicationInfo,
{
fn delete(
&mut self,
range: &CursorRange,
ctx: &C,
store: &mut Store<I>,
) -> EditResult<(CursorChoice, Vec<CursorAdjustment>), I>;
fn yank(
&mut self,
range: &CursorRange,
ctx: &C,
store: &mut Store<I>,
) -> EditResult<CursorChoice, I>;
fn replace(
&mut self,
c: char,
virt: bool,
range: &CursorRange,
ctx: &C,
store: &mut Store<I>,
) -> EditResult<(CursorChoice, Vec<CursorAdjustment>), I>;
fn changecase(
&mut self,
case: &Case,
range: &CursorRange,
ctx: &C,
store: &mut Store<I>,
) -> EditResult<(CursorChoice, Vec<CursorAdjustment>), I>;
fn format(
&mut self,
range: &CursorRange,
ctx: &C,
store: &mut Store<I>,
) -> EditResult<(CursorChoice, Vec<CursorAdjustment>), I>;
fn changenum(
&mut self,
change: &NumberChange,
mul: bool,
range: &CursorRange,
ctx: &C,
store: &mut Store<I>,
) -> EditResult<(CursorChoice, Vec<CursorAdjustment>), I>;
fn join(
&mut self,
spaces: JoinStyle,
range: &CursorRange,
ctx: &C,
store: &mut Store<I>,
) -> EditResult<(CursorChoice, Vec<CursorAdjustment>), I>;
fn indent(
&mut self,
change: &IndentChange,
range: &CursorRange,
ctx: &C,
store: &mut Store<I>,
) -> EditResult<(CursorChoice, Vec<CursorAdjustment>), I>;
}
impl<'a, I> EditActions<CursorMovementsContext<'a, Cursor>, I> for EditBuffer<I>
where
I: ApplicationInfo,
{
fn delete(
&mut self,
range: &CursorRange,
ctx: &CursorMovementsContext<'a, Cursor>,
store: &mut Store<I>,
) -> EditResult<(CursorChoice, Vec<CursorAdjustment>), I> {
let style = ctx.context.get_insert_style().unwrap_or(InsertStyle::Insert);
let (shape, ranges) = self._effective(range, ctx.context.get_target_shape());
let mut deleted = EditRope::from("");
let mut first = true;
let mut coff = self.text.cursor_to_offset(&range.start);
let mut adjs = vec![];
for (start, end, inclusive) in ranges.into_iter().rev() {
if first {
first = false;
} else {
deleted = EditRope::from("\n") + deleted;
}
let (prefix, text, suffix) = self.text.split(start, end, inclusive);
let tlines = text.get_lines();
let tlen = text.len() as isize;
let lstart = self.text.line_of_offset(start);
deleted = text + deleted;
match style {
InsertStyle::Insert => {
self.text = prefix + suffix;
},
InsertStyle::Replace => {
let current = self.history.current();
let restore = current.slice(into_range(start, end, inclusive));
self.text = prefix + restore + suffix;
},
}
if tlines == 0 {
let cstart = self.text.offset_to_cursor(start);
let adj = self._adjust_columns(cstart.y, cstart.x, 0, -tlen);
adjs.push(adj);
} else {
let lend = lstart.saturating_add(tlines - 1);
let adj = self._adjust_lines(lstart, lend, isize::MAX, -(tlines as isize));
adjs.push(adj);
}
coff = start;
}
self.text.trailing_newline();
let cell = RegisterCell::new(shape, deleted);
let register = ctx.context.get_register().unwrap_or(Register::Unnamed);
let mut flags = RegisterPutFlags::DELETE;
if ctx.context.get_register_append() {
flags |= RegisterPutFlags::APPEND
}
store.registers.put(®ister, cell, flags)?;
let cursor = self.text.offset_to_cursor(coff);
return Ok((CursorChoice::Single(cursor), adjs));
}
fn yank(
&mut self,
range: &CursorRange,
ctx: &CursorMovementsContext<'a, Cursor>,
store: &mut Store<I>,
) -> EditResult<CursorChoice, I> {
let (shape, ranges) = self._effective(range, ctx.context.get_target_shape());
let mut yanked = EditRope::from("");
let mut first = true;
let mut cursors = None;
for (start, end, inclusive) in ranges.into_iter() {
if first {
first = false;
} else {
yanked += EditRope::from('\n');
}
if let Some((_, ref mut eoff)) = cursors {
*eoff = end;
} else {
cursors = Some((start, end));
}
yanked += self.text.slice(into_range(start, end, inclusive));
}
let cell = RegisterCell::new(shape, yanked);
let register = ctx.context.get_register().unwrap_or(Register::Unnamed);
let mut flags = RegisterPutFlags::NONE;
if ctx.context.get_register_append() {
flags |= RegisterPutFlags::APPEND;
}
store.registers.put(®ister, cell, flags)?;
let choice = cursors
.map(|(start, end)| {
let start = self.text.offset_to_cursor(start);
let end = self.text.offset_to_cursor(end);
match shape {
TargetShape::CharWise | TargetShape::LineWise => {
CursorChoice::Range(start, end, range.start.clone())
},
TargetShape::BlockWise => {
CursorChoice::Range(start.clone(), end, start)
},
}
})
.unwrap_or_default();
Ok(choice)
}
fn replace(
&mut self,
c: char,
_virt: bool,
range: &CursorRange,
ctx: &CursorMovementsContext<'a, Cursor>,
_: &mut Store<I>,
) -> EditResult<(CursorChoice, Vec<CursorAdjustment>), I> {
let (_, ranges) = self._effective(range, ctx.context.get_target_shape());
let mut cursor = None;
let mut adjs = vec![];
for (start, end, inclusive) in ranges.into_iter().rev() {
let (choice, mut adjustments) = self.text.transform(start, end, inclusive, |r| {
let s: String = r.to_string();
let n: String =
s.chars().map(|i| if i == '\n' || i == '\r' { i } else { c }).collect();
return EditRope::from(n);
});
adjs.append(&mut adjustments);
let _ = cursor.get_or_insert(choice);
}
let choice = cursor
.and_then(|c| c.get(CursorEnd::End).cloned())
.map(CursorChoice::from)
.unwrap_or_default();
Ok((choice, adjs))
}
fn changecase(
&mut self,
case: &Case,
range: &CursorRange,
ctx: &CursorMovementsContext<'a, Cursor>,
_: &mut Store<I>,
) -> EditResult<(CursorChoice, Vec<CursorAdjustment>), I> {
let (shape, ranges) = self._effective(range, ctx.context.get_target_shape());
let mut cursors = None;
let mut adjs = vec![];
for (start, end, inclusive) in ranges.into_iter().rev() {
let (choice, mut adjustments) =
self.text.transform(start, end, inclusive, |r| r.changecase(case));
let Some((sc, ec)) = &mut cursors else {
cursors = choice.resolve(CursorEnd::Selection).map(|s| s.sorted());
adjs.append(&mut adjustments);
continue;
};
ec.adjust(&adjustments);
if let Some(start) = choice.get(CursorEnd::Start) {
*sc = start.clone();
} else {
sc.adjust(&adjustments);
}
adjs.append(&mut adjustments);
}
let choice = cursors
.map(|(start, end)| {
match shape {
TargetShape::CharWise | TargetShape::BlockWise => {
CursorChoice::Range(start.clone(), end, start)
},
TargetShape::LineWise => {
let default = if range.start.y == range.end.y {
self.text.first_word(&range.start, ctx)
} else {
range.start.clone()
};
CursorChoice::Range(start, end, default)
},
}
})
.unwrap_or_default();
Ok((choice, adjs))
}
fn indent(
&mut self,
_: &IndentChange,
_: &CursorRange,
_: &CursorMovementsContext<'a, Cursor>,
_: &mut Store<I>,
) -> EditResult<(CursorChoice, Vec<CursorAdjustment>), I> {
return Ok((CursorChoice::Empty, vec![]));
}
fn format(
&mut self,
_: &CursorRange,
_: &CursorMovementsContext<'a, Cursor>,
_: &mut Store<I>,
) -> EditResult<(CursorChoice, Vec<CursorAdjustment>), I> {
return Ok((CursorChoice::Empty, vec![]));
}
fn changenum(
&mut self,
change: &NumberChange,
mul: bool,
range: &CursorRange,
ctx: &CursorMovementsContext<'a, Cursor>,
_: &mut Store<I>,
) -> EditResult<(CursorChoice, Vec<CursorAdjustment>), I> {
let mut diff = match change {
NumberChange::Decrease(count) => -(ctx.context.resolve(count) as isize),
NumberChange::Increase(count) => ctx.context.resolve(count) as isize,
};
let off = if mul { diff } else { 0 };
let mut cursor = range.start.clone();
let mut res = CursorChoice::Empty;
let mut adjs = vec![];
while cursor.y <= range.end.y {
let style = WordStyle::Number(Radix::Decimal);
let number = match self.text.get_cursor_word_mut(&mut cursor, &style) {
Some(n) => n,
None => continue,
};
if cursor > range.end {
break;
}
let nlen = number.len();
let n = Cow::from(&number);
let n = match n.as_ref().parse::<isize>() {
Ok(n) => EditRope::from((n + diff).to_string()),
Err(_) => continue,
};
let mut nend = cursor.clone();
nend.x += nlen;
let start = self.text.cursor_to_offset(&cursor);
let end = self.text.cursor_to_offset(&nend);
let (choice, mut adjustments) = self.text.replace(start..end, n);
adjs.append(&mut adjustments);
res = if let CursorChoice::Range(s, e, _) = choice {
CursorChoice::Range(s, e.clone(), e)
} else {
choice
};
diff += off;
cursor.y += 1;
cursor.x = 0;
}
return Ok((res, adjs));
}
fn join(
&mut self,
spaces: JoinStyle,
range: &CursorRange,
_: &CursorMovementsContext<'a, Cursor>,
_: &mut Store<I>,
) -> EditResult<(CursorChoice, Vec<CursorAdjustment>), I> {
let (_, ranges) = self._effective(range, Some(TargetShape::LineWise));
let mut cursor = None;
let mut adjs = vec![];
for (start, end, inclusive) in ranges.into_iter().rev() {
let (choice, mut adjustments) = self.text.join_lines(start, end, inclusive, spaces);
let _ = cursor.get_or_insert(choice);
adjs.append(&mut adjustments);
}
return Ok((cursor.unwrap_or_default(), adjs));
}
}
#[cfg(test)]
mod tests {
use super::super::tests::*;
use super::*;
macro_rules! get_reg {
($store: expr, $reg: expr) => {
$store.registers.get(&$reg).unwrap()
};
}
macro_rules! get_named_reg {
($store: expr, $reg: expr) => {
get_reg!($store, Register::Named($reg))
};
}
macro_rules! get_recent_del_reg {
($store: expr, $n: expr) => {
get_reg!($store, Register::RecentlyDeleted($n))
};
}
#[test]
fn test_replace() {
let (mut ebuf, curid, vwctx, mut vctx, mut store) =
mkfivestr("hello world\na b c d e\nfoo bar baz");
let mov = MoveType::Column(MoveDir1D::Next, false);
vctx.action.replace = Some('!'.into());
edit!(ebuf, EditAction::Replace(false), mv!(mov, 3), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_text(), "!!!lo world\na b c d e\nfoo bar baz\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 2));
let mov = MoveType::WordBegin(WordStyle::Little, MoveDir1D::Next);
vctx.action.replace = Some('Q'.into());
edit!(ebuf, EditAction::Replace(false), mv!(mov, 3), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_text(), "!!QQQQQQQQQ\na b c d e\nfoo bar baz\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 10));
let mov = RangeType::Line;
vctx.action.replace = Some(':'.into());
edit!(ebuf, EditAction::Replace(false), range!(mov, 2), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_text(), ":::::::::::\n:::::::::\nfoo bar baz\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(1, 8));
}
#[test]
fn test_yank() {
let (mut ebuf, curid, vwctx, mut vctx, mut store) =
mkfivestr("hello world\na b c d e\nfoo bar baz");
let mov = MoveType::WordBegin(WordStyle::Little, MoveDir1D::Next);
edit!(ebuf, EditAction::Motion, mv!(mov), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 6));
edit!(ebuf, EditAction::Yank, mv!(mov), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 6));
assert_eq!(get_reg!(store, Register::LastYanked), cell!(CharWise, "world"));
assert_eq!(get_reg!(store, Register::Unnamed), cell!(CharWise, "world"));
vctx.action.count = Some(3);
vctx.action.register = Some(Register::Named('a'));
edit!(ebuf, EditAction::Yank, mv!(mov), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 6));
assert_eq!(get_reg!(store, Register::LastYanked), cell!(CharWise, "world"));
assert_eq!(get_reg!(store, Register::Unnamed), cell!(CharWise, "world\na b "));
assert_eq!(get_named_reg!(store, 'a'), cell!(CharWise, "world\na b "));
vctx.action.count = None;
vctx.action.register = Some(Register::Named('a'));
vctx.action.register_append = true;
edit!(ebuf, EditAction::Yank, range!(RangeType::Line), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 6));
assert_eq!(get_reg!(store, Register::LastYanked), cell!(CharWise, "world"));
assert_eq!(
get_reg!(store, Register::Unnamed),
cell!(LineWise, "world\na b \nhello world\n")
);
assert_eq!(get_named_reg!(store, 'a'), cell!(LineWise, "world\na b \nhello world\n"));
vctx.action.count = None;
vctx.action.register = Some(Register::Blackhole);
vctx.action.register_append = false;
edit!(ebuf, EditAction::Yank, mv!(mov), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 6));
assert_eq!(get_reg!(store, Register::LastYanked), cell!(CharWise, "world"));
assert_eq!(
get_reg!(store, Register::Unnamed),
cell!(LineWise, "world\na b \nhello world\n")
);
assert_eq!(get_named_reg!(store, 'a'), cell!(LineWise, "world\na b \nhello world\n"));
assert_eq!(get_reg!(store, Register::Blackhole), cell!(CharWise, ""));
}
#[test]
fn test_yank_word_search() {
let (mut ebuf, gid, vwctx, vctx, mut store) =
mkfivestr("hello world\nhellfire hello brimstone\nhello hell\n");
let op = EditAction::Yank;
let word = EditTarget::Search(
SearchType::Word(WordStyle::Little, false),
MoveDirMod::Same,
Count::Contextual,
);
ebuf.set_leader(gid, Cursor::new(0, 2));
edit!(ebuf, op, word, ctx!(gid, vwctx, vctx), store);
assert_eq!(ebuf.get_leader(gid), Cursor::new(0, 2));
assert_eq!(get_reg!(store, Register::Unnamed), cell!(CharWise, "llo world\nhellfire "));
}
#[test]
fn test_delete() {
let (mut ebuf, curid, vwctx, vctx, mut store) =
mkfivestr("hello world\na b c d e f\n\n\n1 2 3 4 5 6\n");
let mov = MoveType::WordBegin(WordStyle::Little, MoveDir1D::Next);
edit!(ebuf, EditAction::Delete, mv!(mov), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_text(), "world\na b c d e f\n\n\n1 2 3 4 5 6\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 0));
assert_eq!(get_reg!(store, Register::LastYanked), RegisterCell::default());
assert_eq!(get_reg!(store, Register::SmallDelete), cell!(CharWise, "hello "));
assert_eq!(get_recent_del_reg!(store, 0), RegisterCell::default());
edit!(ebuf, EditAction::Delete, mv!(mov, 3), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_text(), "c d e f\n\n\n1 2 3 4 5 6\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 0));
assert_eq!(get_reg!(store, Register::SmallDelete), cell!(CharWise, "hello "));
assert_eq!(get_recent_del_reg!(store, 0), cell!(CharWise, "world\na b "));
edit!(ebuf, EditAction::Delete, mv!(mov, 4), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_text(), "\n\n\n1 2 3 4 5 6\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 0));
assert_eq!(get_reg!(store, Register::SmallDelete), cell!(CharWise, "c d e f"));
assert_eq!(get_recent_del_reg!(store, 0), cell!(CharWise, "world\na b "));
edit!(
ebuf,
EditAction::Delete,
range!(RangeType::Line, 3),
ctx!(curid, vwctx, vctx),
store
);
assert_eq!(ebuf.get_text(), "1 2 3 4 5 6\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 0));
assert_eq!(get_reg!(store, Register::SmallDelete), cell!(CharWise, "c d e f"));
assert_eq!(get_recent_del_reg!(store, 0), cell!(LineWise, "\n\n\n"));
assert_eq!(get_recent_del_reg!(store, 1), cell!(CharWise, "world\na b "));
edit!(ebuf, EditAction::Motion, mv!(mov, 2), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_text(), "1 2 3 4 5 6\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 4));
edit!(ebuf, EditAction::Delete, mv!(mov, 2), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_text(), "1 2 5 6\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 4));
assert_eq!(get_reg!(store, Register::SmallDelete), cell!(CharWise, "3 4 "));
assert_eq!(get_recent_del_reg!(store, 0), cell!(LineWise, "\n\n\n"));
assert_eq!(get_recent_del_reg!(store, 1), cell!(CharWise, "world\na b "));
edit!(
ebuf,
EditAction::Delete,
range!(RangeType::Line, 3),
ctx!(curid, vwctx, vctx),
store
);
assert_eq!(ebuf.get_text(), "\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 0));
assert_eq!(get_reg!(store, Register::SmallDelete), cell!(CharWise, "3 4 "));
assert_eq!(get_recent_del_reg!(store, 0), cell!(LineWise, "1 2 5 6\n"));
assert_eq!(get_recent_del_reg!(store, 1), cell!(LineWise, "\n\n\n"));
assert_eq!(get_recent_del_reg!(store, 2), cell!(CharWise, "world\na b "));
}
#[test]
fn test_delete_cursor_group() {
let (mut ebuf, curid, vwctx, vctx, mut store) =
mkfivestr("hello world\na b c d e f\n\n\n1 2 3 4 5 6\n");
let leader = CursorState::from(Cursor::new(0, 5));
let members = vec![
CursorState::from(Cursor::new(0, 9)),
CursorState::from(Cursor::new(1, 2)),
CursorState::from(Cursor::new(1, 6)),
CursorState::from(Cursor::new(3, 0)),
CursorState::from(Cursor::new(4, 1)),
];
ebuf.set_group(curid, CursorGroup::new(leader, members));
let mov = MoveType::Column(MoveDir1D::Previous, true);
edit!(ebuf, EditAction::Delete, mv!(mov), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_text(), "hell wold\nab cd e f\n\n 2 3 4 5 6\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 4));
assert_eq!(ebuf.get_followers(curid), vec![
Cursor::new(0, 7),
Cursor::new(1, 1),
Cursor::new(1, 4),
Cursor::new(2, 0),
Cursor::new(3, 0),
]);
let mov = MoveType::Column(MoveDir1D::Previous, true);
edit!(ebuf, EditAction::Delete, mv!(mov), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_text(), "hel wld\nb d e f 2 3 4 5 6\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 3));
assert_eq!(ebuf.get_followers(curid), vec![
Cursor::new(0, 5),
Cursor::new(1, 0),
Cursor::new(1, 2),
Cursor::new(1, 6),
Cursor::new(1, 7),
]);
}
#[test]
fn test_delete_blockwise() {
let (mut ebuf, curid, vwctx, mut vctx, mut store) =
mkfivestr("hello world\n1 2 3 4 5 6\n a b c d e f\n");
ebuf.set_leader(curid, Cursor::new(0, 7));
vctx.persist.shape = Some(TargetShape::BlockWise);
edit!(
ebuf,
EditAction::Delete,
range!(RangeType::Line, 3),
ctx!(curid, vwctx, vctx),
store
);
assert_eq!(ebuf.get_text(), "herld\n1 5 6\n d e f\n");
assert_eq!(get_recent_del_reg!(store, 0), cell!(BlockWise, "llo wo\n2 3 4 \na b c "));
assert_eq!(get_reg!(store, Register::Unnamed), cell!(BlockWise, "llo wo\n2 3 4 \na b c "));
assert_eq!(get_reg!(store, Register::LastYanked), cell!(CharWise, ""));
assert_eq!(get_reg!(store, Register::SmallDelete), cell!(CharWise, ""));
}
#[test]
fn test_delete_eol() {
let (mut ebuf, curid, vwctx, vctx, mut store) =
mkfivestr("hello world\na b c d e f\n\n\n1 2 3 4 5 6\n");
ebuf.set_leader(curid, Cursor::new(0, 3));
let mov = MoveType::LinePos(MovePosition::End);
edit!(ebuf, EditAction::Delete, mv!(mov, 0), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 2));
assert_eq!(ebuf.get_text(), "hel\na b c d e f\n\n\n1 2 3 4 5 6\n");
}
#[test]
fn test_change() {
let (mut ebuf, curid, vwctx, mut vctx, mut store) =
mkfivestr("hello world\na b c d e f\n\n\n1 2 3 4 5 6\n");
vctx.persist.insert = Some(InsertStyle::Insert);
ebuf.set_leader(curid, Cursor::new(0, 3));
let mov = MoveType::LinePos(MovePosition::End);
edit!(ebuf, EditAction::Delete, mv!(mov, 0), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 3));
assert_eq!(ebuf.get_text(), "hel\na b c d e f\n\n\n1 2 3 4 5 6\n");
let mov = MoveType::Column(MoveDir1D::Previous, true);
edit!(ebuf, EditAction::Delete, mv!(mov), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 2));
assert_eq!(ebuf.get_text(), "he\na b c d e f\n\n\n1 2 3 4 5 6\n");
let mov = MoveType::WordBegin(WordStyle::Little, MoveDir1D::Previous);
edit!(ebuf, EditAction::Delete, mv!(mov), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 0));
assert_eq!(ebuf.get_text(), "\na b c d e f\n\n\n1 2 3 4 5 6\n");
let mov = MoveType::Column(MoveDir1D::Next, true);
edit!(ebuf, EditAction::Delete, mv!(mov), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 0));
assert_eq!(ebuf.get_text(), "a b c d e f\n\n\n1 2 3 4 5 6\n");
ebuf.set_leader(curid, Cursor::new(3, 0));
let mov = MoveType::Column(MoveDir1D::Previous, true);
edit!(ebuf, EditAction::Delete, mv!(mov), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_leader(curid), Cursor::new(2, 0));
assert_eq!(ebuf.get_text(), "a b c d e f\n\n1 2 3 4 5 6\n");
let mov = MoveType::Column(MoveDir1D::Previous, true);
vctx.action.count = Some(2);
edit!(ebuf, EditAction::Delete, mv!(mov), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 11));
assert_eq!(ebuf.get_text(), "a b c d e f1 2 3 4 5 6\n");
}
#[test]
fn test_changecase() {
let (mut ebuf, curid, vwctx, vctx, mut store) =
mkfivestr("thiS iS An eXaMpLE of mIxed cASE\n");
let mov = MoveType::WordBegin(WordStyle::Little, MoveDir1D::Next);
let operation = EditAction::ChangeCase(Case::Toggle);
edit!(ebuf, operation, mv!(mov), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_text(), "THIs iS An eXaMpLE of mIxed cASE\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 0));
edit!(ebuf, operation, range!(RangeType::Line), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_text(), "thiS Is aN ExAmPle OF MiXED Case\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 0));
let operation = EditAction::ChangeCase(Case::Upper);
edit!(ebuf, operation, mv!(mov), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_text(), "THIS Is aN ExAmPle OF MiXED Case\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 0));
edit!(ebuf, operation, mv!(mov), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_text(), "THIS Is aN ExAmPle OF MiXED Case\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 0));
edit!(ebuf, operation, range!(RangeType::Line), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_text(), "THIS IS AN EXAMPLE OF MIXED CASE\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 0));
edit!(ebuf, operation, range!(RangeType::Line), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_text(), "THIS IS AN EXAMPLE OF MIXED CASE\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 0));
let operation = EditAction::ChangeCase(Case::Lower);
edit!(ebuf, operation, mv!(mov), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_text(), "this IS AN EXAMPLE OF MIXED CASE\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 0));
edit!(ebuf, operation, mv!(mov), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_text(), "this IS AN EXAMPLE OF MIXED CASE\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 0));
edit!(ebuf, operation, range!(RangeType::Line), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_text(), "this is an example of mixed case\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 0));
edit!(ebuf, operation, range!(RangeType::Line), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_text(), "this is an example of mixed case\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 0));
}
#[test]
fn test_changecase_cursor_group() {
let (mut ebuf, curid, vwctx, vctx, mut store) = mkfivestr("reißen reißen reißen\n");
let leader = CursorState::from(Cursor::new(0, 3));
let members = vec![
CursorState::from(Cursor::new(0, 10)),
CursorState::from(Cursor::new(0, 17)),
];
ebuf.set_group(curid, CursorGroup::new(leader, members));
let operation = EditAction::ChangeCase(Case::Upper);
let mov = MoveType::Column(MoveDir1D::Next, false);
edit!(ebuf, operation, mv!(mov), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_text(), "reiSSen reiSSen reiSSen\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 3));
assert_eq!(ebuf.get_followers(curid), vec![Cursor::new(0, 11), Cursor::new(0, 19)]);
}
#[test]
fn test_changecase_tilde() {
let (mut ebuf, curid, vwctx, mut vctx, mut store) =
mkfivestr("thiS iS An eXaMpLE of mIxed cASE\nfoo bar\n");
let operation = EditAction::ChangeCase(Case::Toggle);
let mov = MoveType::Column(MoveDir1D::Next, false);
vctx.action.cursor_end = Some(CursorEnd::End);
ebuf.set_leader(curid, Cursor::new(0, 11));
edit!(ebuf, operation, mv!(mov, 100), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_text(), "thiS iS An ExAmPle OF MiXED Case\nfoo bar\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 31));
}
#[test]
fn test_forced_motion_char() {
let (mut ebuf, curid, vwctx, mut vctx, mut store) =
mkfivestr("hello\nworld\na b c d e\n word\n");
let op = EditAction::Yank;
vctx.persist.shape = Some(TargetShape::CharWise);
ebuf.set_leader(curid, Cursor::new(0, 2));
let mov = MoveType::Line(MoveDir1D::Next);
edit!(ebuf, op, mv!(mov, 2), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 2));
assert_eq!(get_reg!(store, Register::Unnamed), cell!(CharWise, "llo\nworld\na "));
let mov = MoveType::BufferLineOffset;
edit!(ebuf, op, mv!(mov, 4), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 2));
assert_eq!(
get_reg!(store, Register::Unnamed),
cell!(CharWise, "llo\nworld\na b c d e\n ")
);
ebuf.set_leader(curid, Cursor::new(3, 6));
ebuf.mark(mark!('a'), ctx!(curid, vwctx, vctx), &mut store).unwrap();
ebuf.set_leader(curid, Cursor::new(0, 2));
edit_line_mark!(ebuf, op, 'a', curid, vwctx, vctx, store);
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 2));
assert_eq!(
get_reg!(store, Register::Unnamed),
cell!(CharWise, "llo\nworld\na b c d e\n ")
);
}
#[test]
fn test_forced_motion_line() {
let (mut ebuf, curid, vwctx, mut vctx, mut store) =
mkfivestr("hello\nworld\na b c d e\n1 2 3 4 5 6");
let op = EditAction::Yank;
vctx.persist.shape = Some(TargetShape::LineWise);
ebuf.set_leader(curid, Cursor::new(0, 2));
let mov = MoveType::Column(MoveDir1D::Next, false);
edit!(ebuf, op, mv!(mov, 100), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 2));
assert_eq!(get_reg!(store, Register::Unnamed), cell!(LineWise, "hello\n"));
let mov = MoveType::ScreenLine(MoveDir1D::Next);
edit!(ebuf, op, mv!(mov, 2), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 2));
assert_eq!(
get_reg!(store, Register::Unnamed),
cell!(LineWise, "hello\nworld\na b c d e\n")
);
ebuf.set_leader(curid, Cursor::new(1, 2));
ebuf.mark(mark!('a'), ctx!(curid, vwctx, vctx), &mut store).unwrap();
ebuf.set_leader(curid, Cursor::new(0, 2));
edit_char_mark!(ebuf, op, 'a', curid, vwctx, vctx, store);
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 2));
assert_eq!(get_reg!(store, Register::Unnamed), cell!(LineWise, "hello\nworld\n"));
}
#[test]
fn test_forced_motion_block() {
let (mut ebuf, curid, vwctx, mut vctx, mut store) =
mkfivestr("hello\nworld\na b c d e\n1 2 3 4 5 6");
vctx.persist.shape = Some(TargetShape::BlockWise);
let mov = MoveType::Line(MoveDir1D::Next);
edit!(ebuf, EditAction::Yank, mv!(mov, 1), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 0));
assert_eq!(get_reg!(store, Register::Unnamed), cell!(BlockWise, "h\nw"));
edit!(ebuf, EditAction::Yank, mv!(mov, 3), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 0));
assert_eq!(get_reg!(store, Register::Unnamed), cell!(BlockWise, "h\nw\na\n1"));
ebuf.set_leader(curid, Cursor::new(3, 6));
let mov = MoveType::Line(MoveDir1D::Previous);
edit!(ebuf, EditAction::Yank, mv!(mov, 3), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 4));
assert_eq!(get_reg!(store, Register::Unnamed), cell!(BlockWise, "o\nd\nc d\n3 4"));
ebuf.set_leader(curid, Cursor::new(3, 6));
ebuf.mark(mark!('a'), ctx!(curid, vwctx, vctx), &mut store).unwrap();
ebuf.set_leader(curid, Cursor::new(0, 2));
let target = EditTarget::CharJump(Specifier::Exact(mark!('a')));
edit!(ebuf, EditAction::Yank, target, ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 2));
assert_eq!(get_reg!(store, Register::Unnamed), cell!(BlockWise, "llo\nrld\nb c d\n2 3 4"));
ebuf.set_leader(curid, Cursor::new(3, 0));
ebuf.mark(mark!('a'), ctx!(curid, vwctx, vctx), &mut store).unwrap();
ebuf.set_leader(curid, Cursor::new(0, 4));
let target = EditTarget::CharJump(Specifier::Exact(mark!('a')));
edit!(ebuf, EditAction::Yank, target, ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 0));
assert_eq!(
get_reg!(store, Register::Unnamed),
cell!(BlockWise, "hello\nworld\na b c\n1 2 3")
);
}
#[test]
fn test_join_spaces() {
let (mut ebuf, curid, vwctx, vctx, mut store) =
mkfivestr("foo\n hello world\n a b c\n d e\n first\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 0));
let operation = EditAction::Join(JoinStyle::OneSpace);
let mov = RangeType::Line;
edit!(ebuf, operation, range!(mov, 1), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_text(), "foo hello world\n a b c\n d e\n first\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 3));
let mov = RangeType::Line;
edit!(ebuf, operation, range!(mov, 3), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_text(), "foo hello world a b c d e\n first\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 21));
let mov = RangeType::Line;
edit!(ebuf, operation, range!(mov, 5), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_text(), "foo hello world a b c d e first\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 25));
let mov = RangeType::Line;
edit!(ebuf, operation, range!(mov), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_text(), "foo hello world a b c d e first\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 25));
}
#[test]
fn test_join_nospaces() {
let (mut ebuf, curid, vwctx, vctx, mut store) =
mkfivestr("foo\n hello world\n a b c\n d e\n first\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 0));
let operation = EditAction::Join(JoinStyle::NoChange);
let mov = RangeType::Line;
edit!(ebuf, operation, range!(mov, 1), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_text(), "foo hello world\n a b c\n d e\n first\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 3));
let mov = RangeType::Line;
edit!(ebuf, operation, range!(mov, 3), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_text(), "foo hello world a b c d e\n first\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 28));
let mov = RangeType::Line;
edit!(ebuf, operation, range!(mov, 4), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_text(), "foo hello world a b c d e first\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 34));
let mov = RangeType::Line;
edit!(ebuf, operation, range!(mov), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_text(), "foo hello world a b c d e first\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 34));
}
#[test]
fn test_join_new_spaces() {
let (mut ebuf, curid, vwctx, vctx, mut store) =
mkfivestr("foo\n hello world\n a b c\n d e\n first\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 0));
let operation = EditAction::Join(JoinStyle::NewSpace);
let mov = RangeType::Line;
edit!(ebuf, operation, range!(mov, 1), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_text(), "foo hello world\n a b c\n d e\n first\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 3));
let mov = RangeType::Line;
edit!(ebuf, operation, range!(mov, 3), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_text(), "foo hello world a b c d e\n first\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 30));
let mov = RangeType::Line;
edit!(ebuf, operation, range!(mov, 4), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_text(), "foo hello world a b c d e first\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 37));
let mov = RangeType::Line;
edit!(ebuf, operation, range!(mov), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_text(), "foo hello world a b c d e first\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 37));
}
#[test]
fn test_join_blanks() {
let (mut ebuf, curid, vwctx, vctx, mut store) = mkfivestr("foo\n\n\n \n first\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 0));
let operation = EditAction::Join(JoinStyle::OneSpace);
edit!(ebuf, operation, range!(RangeType::Line, 2), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_text(), "foo\n\n \n first\n");
edit!(ebuf, operation, range!(RangeType::Line, 3), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_text(), "foo\n first\n");
edit!(ebuf, operation, range!(RangeType::Line, 2), ctx!(curid, vwctx, vctx), store);
assert_eq!(ebuf.get_text(), "foo first\n");
}
#[test]
fn test_changenum() {
let (mut ebuf, curid, vwctx, mut vctx, mut store) = mkfivestr("a 1 b 2 c\nd 3 e 4 f\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 0));
let opdec = EditAction::ChangeNumber(NumberChange::Decrease(Count::Contextual), false);
let opinc = EditAction::ChangeNumber(NumberChange::Increase(Count::Contextual), false);
vctx.action.count = Some(3);
edit!(
ebuf,
opinc,
mv!(MoveType::LinePos(MovePosition::End), 0),
ctx!(curid, vwctx, vctx),
store
);
assert_eq!(ebuf.get_text(), "a 4 b 2 c\nd 3 e 4 f\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 2));
vctx.action.count = Some(9);
edit!(
ebuf,
opdec,
mv!(MoveType::LinePos(MovePosition::End), 0),
ctx!(curid, vwctx, vctx),
store
);
assert_eq!(ebuf.get_text(), "a -5 b 2 c\nd 3 e 4 f\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 3));
vctx.action.count = Some(1);
edit!(
ebuf,
opdec,
mv!(MoveType::LinePos(MovePosition::End), 0),
ctx!(curid, vwctx, vctx),
store
);
assert_eq!(ebuf.get_text(), "a -6 b 2 c\nd 3 e 4 f\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 3));
vctx.action.count = Some(7);
edit!(
ebuf,
opinc,
mv!(MoveType::LinePos(MovePosition::End), 0),
ctx!(curid, vwctx, vctx),
store
);
assert_eq!(ebuf.get_text(), "a 1 b 2 c\nd 3 e 4 f\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 2));
ebuf.set_leader(curid, Cursor::new(1, 4));
vctx.action.count = Some(2);
edit!(
ebuf,
opinc,
mv!(MoveType::LinePos(MovePosition::End), 0),
ctx!(curid, vwctx, vctx),
store
);
assert_eq!(ebuf.get_text(), "a 1 b 2 c\nd 3 e 6 f\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(1, 6));
vctx.action.count = Some(3);
edit!(
ebuf,
opdec,
mv!(MoveType::LinePos(MovePosition::End), 0),
ctx!(curid, vwctx, vctx),
store
);
assert_eq!(ebuf.get_text(), "a 1 b 2 c\nd 3 e 3 f\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(1, 6));
}
#[test]
fn test_changenum_mul() {
let (mut ebuf, curid, vwctx, mut vctx, mut store) =
mkfivestr("a 1 b 2 c\nd 3 e 4 f\ng 5 h 6 i\nj 7 k 8 l\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(0, 0));
let opdec = EditAction::ChangeNumber(NumberChange::Decrease(Count::Contextual), true);
let opinc = EditAction::ChangeNumber(NumberChange::Increase(Count::Contextual), true);
vctx.action.count = Some(5);
edit!(ebuf, opinc, &range!(RangeType::Buffer), ctx!(curid, vwctx, vctx), &mut store);
assert_eq!(ebuf.get_text(), "a 6 b 2 c\nd 13 e 4 f\ng 20 h 6 i\nj 27 k 8 l\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(3, 3));
vctx.action.count = Some(2);
edit!(ebuf, opdec, &range!(RangeType::Buffer), ctx!(curid, vwctx, vctx), &mut store);
assert_eq!(ebuf.get_text(), "a 4 b 2 c\nd 9 e 4 f\ng 14 h 6 i\nj 19 k 8 l\n");
assert_eq!(ebuf.get_leader(curid), Cursor::new(3, 3));
}
}