230 lines
7.7 KiB
Rust
230 lines
7.7 KiB
Rust
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());
|
|
}
|