fix: yank whole lines with yy and preserve paste shape
This commit is contained in:
@@ -0,0 +1,229 @@
|
||||
use std::{
|
||||
fs,
|
||||
path::PathBuf,
|
||||
sync::atomic::{AtomicU64, Ordering},
|
||||
};
|
||||
|
||||
use vamp::editor::{Cursor, Editor, Input, Mode};
|
||||
|
||||
static NEXT: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
struct Fixture(PathBuf);
|
||||
|
||||
impl Fixture {
|
||||
fn new() -> Self {
|
||||
let root = std::env::temp_dir().join(format!(
|
||||
"vamp-paste-shapes-{}-{}",
|
||||
std::process::id(),
|
||||
NEXT.fetch_add(1, Ordering::Relaxed),
|
||||
));
|
||||
fs::create_dir_all(&root).unwrap();
|
||||
Self(root)
|
||||
}
|
||||
|
||||
fn editor(&self, text: &str) -> Editor {
|
||||
let path = self.0.join("buffer.txt");
|
||||
fs::write(&path, text).unwrap();
|
||||
let mut editor = Editor::open(Some(path), vec!["text".into()]).unwrap();
|
||||
editor.set_clipboard_mode("separate").unwrap();
|
||||
editor
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Fixture {
|
||||
fn drop(&mut self) {
|
||||
let _ = fs::remove_dir_all(&self.0);
|
||||
}
|
||||
}
|
||||
|
||||
fn keys(editor: &mut Editor, text: &str) {
|
||||
for ch in text.chars() {
|
||||
editor.handle(Input::Char(ch));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn whole_line_yank_pastes_above_or_below_the_target_and_keeps_indentation() {
|
||||
let source = " \t界e\u{301}👩🚀\nother\nlast\n";
|
||||
for (paste, expected, row) in [
|
||||
('p', " \t界e\u{301}👩🚀\nother\n \t界e\u{301}👩🚀\nlast\n", 2),
|
||||
('P', " \t界e\u{301}👩🚀\n \t界e\u{301}👩🚀\nother\nlast\n", 1),
|
||||
] {
|
||||
let fixture = Fixture::new();
|
||||
let mut editor = fixture.editor(source);
|
||||
keys(&mut editor, "3l");
|
||||
let yank_caret = editor.cursor();
|
||||
keys(&mut editor, "yy");
|
||||
assert_eq!(editor.context().text, source);
|
||||
assert_eq!(editor.cursor(), yank_caret);
|
||||
assert_eq!(editor.mode(), Mode::Normal);
|
||||
assert!(!editor.dirty());
|
||||
keys(&mut editor, "j2l");
|
||||
let paste_caret = editor.cursor();
|
||||
editor.handle(Input::Char(paste));
|
||||
assert_eq!(editor.context().text, expected, "{paste}");
|
||||
assert_eq!(editor.cursor(), Cursor { row, column: 2 });
|
||||
assert_eq!(editor.mode(), Mode::Normal);
|
||||
keys(&mut editor, "u");
|
||||
assert_eq!(editor.context().text, source);
|
||||
assert_eq!(editor.cursor(), paste_caret);
|
||||
assert!(!editor.dirty());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn whole_line_paste_handles_unterminated_rows_empty_rows_and_the_final_empty_row() {
|
||||
for (source, position, paste, expected, row) in [
|
||||
("top\n last", "j2l", 'p', "top\n last\n last", 2),
|
||||
("top\n last", "j2l", 'P', "top\n last\n last", 1),
|
||||
("top\n last\n", "j2l", 'p', "top\n last\n last\n", 2),
|
||||
("top\n last\n", "j2l", 'P', "top\n last\n last\n", 1),
|
||||
("top\n\nend", "j", 'p', "top\n\n\nend", 2),
|
||||
("top\n\nend", "j", 'P', "top\n\n\nend", 1),
|
||||
("top\n", "j", 'p', "top\n\n", 2),
|
||||
("top\n", "j", 'P', "top\n\n", 1),
|
||||
("", "", 'p', "\n", 1),
|
||||
("", "", 'P', "\n", 0),
|
||||
] {
|
||||
let fixture = Fixture::new();
|
||||
let mut editor = fixture.editor(source);
|
||||
keys(&mut editor, position);
|
||||
let caret = editor.cursor();
|
||||
keys(&mut editor, "yy");
|
||||
editor.handle(Input::Char(paste));
|
||||
assert_eq!(
|
||||
editor.context().text,
|
||||
expected,
|
||||
"{source:?} {position}yy{paste}"
|
||||
);
|
||||
assert_eq!(
|
||||
editor.cursor(),
|
||||
Cursor {
|
||||
row,
|
||||
column: caret.column
|
||||
}
|
||||
);
|
||||
keys(&mut editor, "u");
|
||||
assert_eq!(editor.context().text, source);
|
||||
assert_eq!(editor.cursor(), caret);
|
||||
assert!(!editor.dirty());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn counted_line_yanks_multiply_prefixes_and_clamp_at_the_last_row() {
|
||||
let source = "one\ntwo\nthree\nfour\nfive\nsix\nseven";
|
||||
for (command, expected) in [
|
||||
("2yy", "one\none\ntwo\ntwo\nthree\nfour\nfive\nsix\nseven"),
|
||||
("y2y", "one\none\ntwo\ntwo\nthree\nfour\nfive\nsix\nseven"),
|
||||
(
|
||||
"2y3y",
|
||||
"one\none\ntwo\nthree\nfour\nfive\nsix\ntwo\nthree\nfour\nfive\nsix\nseven",
|
||||
),
|
||||
(
|
||||
"9yy",
|
||||
"one\none\ntwo\nthree\nfour\nfive\nsix\nseven\ntwo\nthree\nfour\nfive\nsix\nseven",
|
||||
),
|
||||
] {
|
||||
let fixture = Fixture::new();
|
||||
let mut editor = fixture.editor(source);
|
||||
keys(&mut editor, command);
|
||||
assert_eq!(editor.context().text, source, "{command}");
|
||||
keys(&mut editor, "p");
|
||||
assert_eq!(editor.context().text, expected, "{command}p");
|
||||
keys(&mut editor, "u");
|
||||
assert_eq!(editor.context().text, source);
|
||||
assert!(!editor.dirty());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn visual_character_yank_pastes_before_or_after_whole_extended_graphemes() {
|
||||
let source = "ae\u{301}👩🚀z\nx👨👩👧👦q\n";
|
||||
for (paste, expected, column) in [
|
||||
('p', "ae\u{301}👩🚀z\nx👨👩👧👦e\u{301}👩🚀q\n", 3),
|
||||
('P', "ae\u{301}👩🚀z\nxe\u{301}👩🚀👨👩👧👦q\n", 2),
|
||||
] {
|
||||
let fixture = Fixture::new();
|
||||
let mut editor = fixture.editor(source);
|
||||
keys(&mut editor, "lvly");
|
||||
assert_eq!(editor.context().text, source);
|
||||
assert!(!editor.dirty());
|
||||
keys(&mut editor, "j0l");
|
||||
let caret = editor.cursor();
|
||||
editor.handle(Input::Char(paste));
|
||||
assert_eq!(editor.context().text, expected, "{paste}");
|
||||
assert_eq!(editor.cursor(), Cursor { row: 1, column });
|
||||
assert_eq!(editor.mode(), Mode::Normal);
|
||||
keys(&mut editor, "u");
|
||||
assert_eq!(editor.context().text, source);
|
||||
assert_eq!(editor.cursor(), caret);
|
||||
editor.handle(Input::Char(paste));
|
||||
assert_eq!(
|
||||
editor.context().text,
|
||||
expected,
|
||||
"register survives undo and paste"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dot_keeps_the_pasted_line_shape_without_replacing_the_new_yank_register() {
|
||||
let fixture = Fixture::new();
|
||||
let source = " a\nb\nc";
|
||||
let mut editor = fixture.editor(source);
|
||||
keys(&mut editor, "yyp");
|
||||
assert_eq!(editor.context().text, " a\n a\nb\nc");
|
||||
keys(&mut editor, "jyy.");
|
||||
assert_eq!(editor.context().text, " a\n a\nb\n a\nc");
|
||||
keys(&mut editor, "u");
|
||||
assert_eq!(editor.context().text, " a\n a\nb\nc");
|
||||
keys(&mut editor, "p");
|
||||
assert_eq!(editor.context().text, " a\n a\nb\nb\nc");
|
||||
keys(&mut editor, "u");
|
||||
assert_eq!(editor.context().text, " a\n a\nb\nc");
|
||||
keys(&mut editor, "u");
|
||||
assert_eq!(editor.context().text, source);
|
||||
assert!(!editor.dirty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn native_multicarets_yank_matching_whole_lines_and_paste_in_one_undo_step() {
|
||||
let fixture = Fixture::new();
|
||||
let source = " a\n\tb\nc";
|
||||
let mut editor = fixture.editor(source);
|
||||
keys(&mut editor, "l");
|
||||
editor.handle(Input::Ctrl('e'));
|
||||
let carets = editor.carets();
|
||||
assert_eq!(carets.len(), 2);
|
||||
keys(&mut editor, "yy");
|
||||
assert_eq!(editor.carets(), carets);
|
||||
keys(&mut editor, "p");
|
||||
assert_eq!(editor.context().text, " a\n a\n\tb\n\tb\nc");
|
||||
assert_eq!(
|
||||
editor.carets(),
|
||||
vec![Cursor { row: 1, column: 1 }, Cursor { row: 3, column: 1 }],
|
||||
);
|
||||
keys(&mut editor, "u");
|
||||
assert_eq!(editor.context().text, source);
|
||||
assert_eq!(editor.carets(), carets);
|
||||
assert!(!editor.dirty());
|
||||
keys(&mut editor, "P");
|
||||
assert_eq!(editor.context().text, " a\n a\n\tb\n\tb\nc");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_visual_yank_ending_in_a_newline_stays_characterwise() {
|
||||
let fixture = Fixture::new();
|
||||
let source = "\nxy";
|
||||
let mut editor = fixture.editor(source);
|
||||
keys(&mut editor, "vyj0p");
|
||||
assert_eq!(editor.context().text, "\nx\ny");
|
||||
keys(&mut editor, "u");
|
||||
assert_eq!(editor.context().text, source);
|
||||
keys(&mut editor, "P");
|
||||
assert_eq!(editor.context().text, "\n\nxy");
|
||||
keys(&mut editor, "u");
|
||||
assert_eq!(editor.context().text, source);
|
||||
assert!(!editor.dirty());
|
||||
}
|
||||
Reference in New Issue
Block a user