604 lines
21 KiB
Rust
604 lines
21 KiB
Rust
use std::{
|
|
fs,
|
|
path::PathBuf,
|
|
process::Command,
|
|
sync::atomic::{AtomicU64, Ordering},
|
|
thread,
|
|
time::{Duration, Instant},
|
|
};
|
|
|
|
use vamp::{
|
|
diff::{DisplayRow, Focus, Phase},
|
|
editor::{Cursor, Editor, Input, Mode, Selection},
|
|
session::EditorSession,
|
|
};
|
|
|
|
static NEXT: AtomicU64 = AtomicU64::new(0);
|
|
|
|
struct Fixture(PathBuf);
|
|
|
|
impl Fixture {
|
|
fn new() -> Self {
|
|
let root = std::env::temp_dir().join(format!(
|
|
"vamp-navigation-{}-{}",
|
|
std::process::id(),
|
|
NEXT.fetch_add(1, Ordering::Relaxed),
|
|
));
|
|
fs::create_dir_all(&root).unwrap();
|
|
Self(root)
|
|
}
|
|
|
|
fn file(&self, name: &str, text: &str) -> PathBuf {
|
|
let path = self.0.join(name);
|
|
fs::write(&path, text).unwrap();
|
|
path
|
|
}
|
|
|
|
fn editor(&self, text: &str) -> Editor {
|
|
let mut editor =
|
|
Editor::open(Some(self.file("buffer.txt", text)), vec!["text".into()]).unwrap();
|
|
editor.set_clipboard_mode("separate").unwrap();
|
|
editor
|
|
}
|
|
|
|
fn session(&self, text: &str, bindings: &str) -> EditorSession {
|
|
let source = self.file("buffer.txt", text);
|
|
let config = self.file(
|
|
"config.rb",
|
|
&format!(
|
|
"Vamp.configure do |config|\n config.clipboard_mode = 'separate'\n{bindings}\nend\n"
|
|
),
|
|
);
|
|
let session =
|
|
EditorSession::open(Some(source), vec!["text".into()], Some(&config)).unwrap();
|
|
assert!(session.editor().startup_diagnostic().is_none());
|
|
session
|
|
}
|
|
|
|
fn git(&self, args: &[&str]) -> String {
|
|
let output = Command::new("git")
|
|
.args(args)
|
|
.current_dir(self.0.join("repo"))
|
|
.env("GIT_CONFIG_NOSYSTEM", "1")
|
|
.env("GIT_CONFIG_GLOBAL", "/dev/null")
|
|
.env("GIT_AUTHOR_NAME", "Navigation Fixture")
|
|
.env("GIT_AUTHOR_EMAIL", "fixture@example.invalid")
|
|
.env("GIT_COMMITTER_NAME", "Navigation Fixture")
|
|
.env("GIT_COMMITTER_EMAIL", "fixture@example.invalid")
|
|
.env("GIT_AUTHOR_DATE", "2000-01-01T00:00:00Z")
|
|
.env("GIT_COMMITTER_DATE", "2000-01-01T00:00:00Z")
|
|
.output()
|
|
.unwrap();
|
|
assert!(
|
|
output.status.success(),
|
|
"{}",
|
|
String::from_utf8_lossy(&output.stderr)
|
|
);
|
|
String::from_utf8(output.stdout).unwrap().trim().to_owned()
|
|
}
|
|
}
|
|
|
|
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));
|
|
}
|
|
}
|
|
|
|
fn session_keys(session: &mut EditorSession, text: &str) {
|
|
for ch in text.chars() {
|
|
session.handle(Input::Char(ch));
|
|
}
|
|
}
|
|
|
|
fn palette_seek(session: &mut EditorSession, action: &str, target: char) {
|
|
session.handle(Input::Ctrl('p'));
|
|
session_keys(session, action);
|
|
session.handle(Input::Enter);
|
|
assert!(session.editor().palette().is_none());
|
|
session.handle(Input::Char(target));
|
|
}
|
|
|
|
fn wait_until(session: &mut EditorSession, ready: impl Fn(&EditorSession) -> bool) {
|
|
let deadline = Instant::now() + Duration::from_secs(10);
|
|
while !ready(session) {
|
|
session.poll();
|
|
assert!(
|
|
Instant::now() < deadline,
|
|
"diff state: {:?}",
|
|
session.editor().diff()
|
|
);
|
|
if let Some(diff) = session.editor().diff() {
|
|
assert!(diff.error.is_none(), "{:?}", diff.error);
|
|
}
|
|
thread::sleep(Duration::from_millis(1));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn submitting_search_keeps_each_caret_inside_a_current_match() {
|
|
let fixture = Fixture::new();
|
|
for (text, query, column, end) in [
|
|
("jj next jj", "jj", 0, 2),
|
|
("jj next jj", "jj", 1, 2),
|
|
("界e\u{301} next 界e\u{301}", "界e\u{301}", 1, 2),
|
|
("aaaa next aa", "aa", 1, 3),
|
|
] {
|
|
let mut editor = fixture.editor(text);
|
|
keys(&mut editor, &"l".repeat(column));
|
|
let cursor = editor.cursor();
|
|
keys(&mut editor, &format!("/{query}"));
|
|
editor.handle(Input::Enter);
|
|
assert_eq!(editor.cursor(), cursor, "{text:?}");
|
|
assert_eq!(editor.search_match().unwrap().end_column, end);
|
|
keys(&mut editor, "n");
|
|
assert_ne!(editor.cursor(), cursor);
|
|
assert!(!editor.dirty());
|
|
}
|
|
let mut editor = fixture.editor("jj next jj\n-- next jj");
|
|
editor.handle(Input::Ctrl('e'));
|
|
keys(&mut editor, "/jj");
|
|
editor.handle(Input::Enter);
|
|
assert_eq!(
|
|
editor.carets(),
|
|
[Cursor::default(), Cursor { row: 1, column: 8 }]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn normal_escape_hides_highlights_but_retains_search_and_rearms_star() {
|
|
let fixture = Fixture::new();
|
|
let mut editor = fixture.editor("cat cat");
|
|
keys(&mut editor, "*");
|
|
assert_eq!(editor.search_matches_on_row(0).count(), 2);
|
|
editor.handle(Input::Escape);
|
|
assert_eq!(editor.search_matches_on_row(0).count(), 0);
|
|
assert_eq!(editor.search_match(), None);
|
|
assert_eq!(editor.cursor(), Cursor::default());
|
|
keys(&mut editor, "*");
|
|
assert_eq!(editor.cursor(), Cursor::default());
|
|
assert_eq!(editor.search_matches_on_row(0).count(), 2);
|
|
keys(&mut editor, "*");
|
|
assert_eq!(editor.cursor(), Cursor { row: 0, column: 4 });
|
|
editor.handle(Input::Escape);
|
|
keys(&mut editor, "n");
|
|
assert_eq!(editor.cursor(), Cursor::default());
|
|
assert_eq!(editor.search_matches_on_row(0).count(), 2);
|
|
keys(&mut editor, "/missing");
|
|
editor.handle(Input::Escape);
|
|
assert_eq!(editor.search_matches_on_row(0).count(), 2);
|
|
editor.handle(Input::Escape);
|
|
editor.handle(Input::Char('/'));
|
|
editor.handle(Input::Enter);
|
|
assert_eq!(editor.cursor(), Cursor::default());
|
|
assert_eq!(editor.search_matches_on_row(0).count(), 2);
|
|
assert!(!editor.dirty());
|
|
}
|
|
|
|
#[test]
|
|
fn star_search_arms_without_moving_then_navigates_and_rearms_for_a_new_word_or_mode() {
|
|
let fixture = Fixture::new();
|
|
let mut editor = fixture.editor("cat dog cat dog");
|
|
keys(&mut editor, "l2*");
|
|
assert_eq!(editor.cursor(), Cursor { row: 0, column: 1 });
|
|
assert_eq!(editor.search_match().unwrap().start, Cursor::default());
|
|
keys(&mut editor, "*");
|
|
assert_eq!(editor.cursor(), Cursor { row: 0, column: 8 });
|
|
keys(&mut editor, "w*");
|
|
assert_eq!(editor.cursor(), Cursor { row: 0, column: 12 });
|
|
keys(&mut editor, "*");
|
|
assert_eq!(editor.cursor(), Cursor { row: 0, column: 4 });
|
|
|
|
keys(&mut editor, "/cat");
|
|
editor.handle(Input::Enter);
|
|
assert_eq!(editor.cursor(), Cursor { row: 0, column: 8 });
|
|
keys(&mut editor, "*");
|
|
assert_eq!(editor.cursor(), Cursor { row: 0, column: 8 });
|
|
keys(&mut editor, "*");
|
|
assert_eq!(editor.cursor(), Cursor::default());
|
|
}
|
|
|
|
#[test]
|
|
fn star_search_keeps_whole_word_counts_history_and_literal_search_distinct() {
|
|
let fixture = Fixture::new();
|
|
let text = "cat\nscatter\ncat_cat\ncat2\n2cat\nCat\ncat\ncat\n\n";
|
|
let mut editor = fixture.editor(text);
|
|
keys(&mut editor, "l**");
|
|
assert_eq!(editor.cursor(), Cursor { row: 6, column: 0 });
|
|
assert_eq!(editor.search_match().unwrap().end_column, 3);
|
|
keys(&mut editor, "nN2*");
|
|
assert_eq!(editor.cursor(), Cursor { row: 0, column: 0 });
|
|
editor.handle(Input::Ctrl('o'));
|
|
assert_eq!(editor.cursor(), Cursor { row: 6, column: 0 });
|
|
editor.handle(Input::Char('\t'));
|
|
assert_eq!(editor.cursor(), Cursor { row: 0, column: 0 });
|
|
|
|
keys(&mut editor, "/scatter");
|
|
editor.handle(Input::Escape);
|
|
editor.handle(Input::Char('/'));
|
|
editor.handle(Input::Enter);
|
|
assert_eq!(editor.cursor(), Cursor::default());
|
|
keys(&mut editor, "gb*");
|
|
assert_eq!(editor.cursor(), Cursor { row: 9, column: 0 });
|
|
keys(&mut editor, "n");
|
|
assert_eq!(editor.cursor(), Cursor { row: 0, column: 0 });
|
|
|
|
keys(&mut editor, "/cat");
|
|
editor.handle(Input::Enter);
|
|
keys(&mut editor, "n");
|
|
assert_eq!(editor.cursor(), Cursor { row: 1, column: 1 });
|
|
assert_eq!(editor.search_match().unwrap().end_column, 4);
|
|
assert_eq!(editor.context().text, text);
|
|
assert!(!editor.dirty());
|
|
}
|
|
|
|
#[test]
|
|
fn star_search_uses_unicode_words_and_vim_same_line_fallbacks() {
|
|
let fixture = Fixture::new();
|
|
for (text, positioning, row, length) in [
|
|
("e\u{301}lan\ne\u{301}lanx\ne\u{301}lan", "l", 2, 4),
|
|
("世界\n世界中\n世界", "l", 2, 2),
|
|
("snake_case\nsnake_case2\nsnake_case", "3l", 2, 10),
|
|
("\t-- word\nword", "", 1, 4),
|
|
("-- word\nword", "", 1, 4),
|
|
("word --\n--", "$", 1, 2),
|
|
(" .* \n.*", "", 1, 2),
|
|
("👩🚀\n👩\n👩🚀", "", 2, 1),
|
|
("only", "l", 0, 4),
|
|
] {
|
|
let mut editor = fixture.editor(text);
|
|
keys(&mut editor, positioning);
|
|
let origin = editor.cursor();
|
|
editor.handle(Input::Char('*'));
|
|
assert_eq!(editor.cursor(), origin, "{text:?}");
|
|
editor.handle(Input::Char('*'));
|
|
assert_eq!(editor.cursor(), Cursor { row, column: 0 }, "{text:?}");
|
|
assert_eq!(
|
|
editor.search_match().unwrap().end_column,
|
|
length,
|
|
"{text:?}"
|
|
);
|
|
keys(&mut editor, "Nn");
|
|
assert_eq!(editor.cursor(), Cursor { row, column: 0 }, "{text:?}");
|
|
assert!(!editor.dirty());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn star_search_rebinding_moves_all_carets_and_remains_view_local() {
|
|
let fixture = Fixture::new();
|
|
let mut session = fixture.session(
|
|
"cat cat_cat cat cat\ncat cat_cat cat cat",
|
|
" config.bind '*', nil\n config.bind 'q', config.core.search_word",
|
|
);
|
|
session.handle(Input::Ctrl('e'));
|
|
session_keys(&mut session, "2q");
|
|
assert_eq!(
|
|
session.editor().carets(),
|
|
[Cursor::default(), Cursor { row: 1, column: 0 }]
|
|
);
|
|
session_keys(&mut session, "2q");
|
|
assert_eq!(
|
|
session.editor().carets(),
|
|
[Cursor { row: 0, column: 16 }, Cursor { row: 1, column: 16 }]
|
|
);
|
|
session_keys(&mut session, "|2N");
|
|
assert_eq!(session.editor().cursor(), Cursor { row: 1, column: 0 });
|
|
session_keys(&mut session, "/cat");
|
|
session.handle(Input::Enter);
|
|
session_keys(&mut session, "n");
|
|
assert_eq!(session.editor().cursor(), Cursor { row: 1, column: 4 });
|
|
session.handle(Input::Alt('h'));
|
|
session_keys(&mut session, "2N");
|
|
assert_eq!(session.editor().cursor(), Cursor { row: 1, column: 0 });
|
|
assert!(!session.editor().dirty());
|
|
}
|
|
|
|
#[test]
|
|
fn counted_character_seeks_move_every_native_caret_independently() {
|
|
let fixture = Fixture::new();
|
|
let text = "xaxbx tail\nx---x----x tail\nx only\n";
|
|
let mut editor = fixture.editor(text);
|
|
editor.handle(Input::Ctrl('e'));
|
|
editor.handle(Input::Ctrl('e'));
|
|
assert_eq!(editor.selection_count(), 3);
|
|
|
|
keys(&mut editor, "2fx");
|
|
assert_eq!(
|
|
editor.carets(),
|
|
[
|
|
Cursor { row: 0, column: 4 },
|
|
Cursor { row: 1, column: 9 },
|
|
Cursor { row: 2, column: 0 },
|
|
]
|
|
);
|
|
keys(&mut editor, "2Fx");
|
|
assert_eq!(
|
|
editor.carets(),
|
|
[
|
|
Cursor { row: 0, column: 0 },
|
|
Cursor { row: 1, column: 0 },
|
|
Cursor { row: 2, column: 0 },
|
|
]
|
|
);
|
|
assert_eq!(editor.context().text, text);
|
|
assert!(!editor.dirty());
|
|
}
|
|
|
|
#[test]
|
|
fn visual_character_and_line_seeks_yank_the_selected_text_into_another_buffer() {
|
|
for (mode, expected) in [('v', "a x x"), ('V', "a x xx tail\n")] {
|
|
let fixture = Fixture::new();
|
|
let mut editor = fixture.editor("a x xx tail\n");
|
|
editor.handle(Input::Char(mode));
|
|
keys(&mut editor, "2fx");
|
|
assert_eq!(editor.mode(), Mode::Visual);
|
|
assert_eq!(editor.cursor(), Cursor { row: 0, column: 4 });
|
|
assert_eq!(editor.visual_linewise(), mode == 'V');
|
|
assert_eq!(
|
|
editor.selections(),
|
|
[Selection {
|
|
start: Cursor { row: 0, column: 0 },
|
|
end: if mode == 'V' {
|
|
Cursor { row: 1, column: 0 }
|
|
} else {
|
|
Cursor { row: 0, column: 5 }
|
|
},
|
|
}]
|
|
);
|
|
editor.handle(Input::Char('y'));
|
|
assert_eq!(editor.mode(), Mode::Normal);
|
|
assert!(!editor.dirty());
|
|
|
|
let destination = fixture.file("destination.txt", "");
|
|
editor.execute(&format!("edit {}", destination.display()));
|
|
editor.handle(Input::Char('P'));
|
|
assert_eq!(editor.context().text, expected, "{mode}");
|
|
assert_eq!(
|
|
fs::read_to_string(fixture.0.join("buffer.txt")).unwrap(),
|
|
"a x xx tail\n"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn typed_ruby_seek_remaps_and_palette_actions_preserve_normal_and_visual_navigation() {
|
|
let fixture = Fixture::new();
|
|
let text = "a x x tail\n";
|
|
let mut session = fixture.session(
|
|
text,
|
|
r#" config.bind 'leader s', config.core.seek_forward
|
|
config.bind 'leader r', config.core.seek_backward
|
|
config.bind 'leader s', config.core.seek_forward, mode: config.modes.visual
|
|
config.bind 'leader r', config.core.seek_backward, mode: config.modes.visual"#,
|
|
);
|
|
session_keys(&mut session, "2 sx");
|
|
assert_eq!(session.editor().cursor(), Cursor { row: 0, column: 4 });
|
|
session_keys(&mut session, " rx");
|
|
assert_eq!(session.editor().cursor(), Cursor { row: 0, column: 2 });
|
|
|
|
session_keys(&mut session, "v sx");
|
|
let expanded = [Selection {
|
|
start: Cursor { row: 0, column: 2 },
|
|
end: Cursor { row: 0, column: 5 },
|
|
}];
|
|
assert_eq!(session.editor().selections(), expanded);
|
|
session_keys(&mut session, " rx");
|
|
let contracted = [Selection {
|
|
start: Cursor { row: 0, column: 2 },
|
|
end: Cursor { row: 0, column: 3 },
|
|
}];
|
|
assert_eq!(session.editor().selections(), contracted);
|
|
palette_seek(&mut session, "core.seek_forward", 'x');
|
|
assert_eq!(session.editor().mode(), Mode::Visual);
|
|
assert_eq!(session.editor().selections(), expanded);
|
|
palette_seek(&mut session, "core.seek_backward", 'x');
|
|
assert_eq!(session.editor().selections(), contracted);
|
|
|
|
session.handle(Input::Escape);
|
|
palette_seek(&mut session, "core.seek_forward", 'x');
|
|
assert_eq!(session.editor().cursor(), Cursor { row: 0, column: 4 });
|
|
palette_seek(&mut session, "core.seek_backward", 'x');
|
|
assert_eq!(session.editor().cursor(), Cursor { row: 0, column: 2 });
|
|
assert_eq!(session.editor().mode(), Mode::Normal);
|
|
assert_eq!(session.editor().context().text, text);
|
|
assert!(!session.editor().dirty());
|
|
}
|
|
|
|
#[test]
|
|
fn repeating_a_visual_seek_change_resolves_the_counted_character_on_the_new_line() {
|
|
let fixture = Fixture::new();
|
|
let mut session = fixture.session(
|
|
"a x x tail\nlong x gap x tail\n",
|
|
" config.bind 'leader s', config.core.seek_forward, mode: config.modes.visual",
|
|
);
|
|
session_keys(&mut session, "v2 sxc!");
|
|
session.handle(Input::Escape);
|
|
assert_eq!(
|
|
session.editor().context().text,
|
|
"! tail\nlong x gap x tail\n"
|
|
);
|
|
|
|
session_keys(&mut session, "j0.");
|
|
assert_eq!(session.editor().context().text, "! tail\n! tail\n");
|
|
assert_eq!(session.editor().mode(), Mode::Normal);
|
|
session.handle(Input::Char('u'));
|
|
assert_eq!(
|
|
session.editor().context().text,
|
|
"! tail\nlong x gap x tail\n"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn readonly_before_and_historical_after_seeks_preserve_snapshots_and_folded_excerpts() {
|
|
let fixture = Fixture::new();
|
|
fs::create_dir_all(fixture.0.join("repo")).unwrap();
|
|
fixture.git(&["init", "-q"]);
|
|
let source = fixture.0.join("repo/context.txt");
|
|
let mut lines = (1..=40)
|
|
.map(|row| format!("stable_{row:03} [x] [x]"))
|
|
.collect::<Vec<_>>();
|
|
lines[19] = "before x x tail".into();
|
|
let before_text = lines.join("\n") + "\n";
|
|
fs::write(&source, &before_text).unwrap();
|
|
fixture.git(&["add", "context.txt"]);
|
|
fixture.git(&["commit", "-qm", "before"]);
|
|
let before = fixture.git(&["rev-parse", "HEAD"]);
|
|
lines[19] = "after x x tail".into();
|
|
let after_text = lines.join("\n") + "\n";
|
|
fs::write(&source, &after_text).unwrap();
|
|
fixture.git(&["commit", "-qam", "after"]);
|
|
let after = fixture.git(&["rev-parse", "HEAD"]);
|
|
fixture.git(&["checkout", "--detach", &before]);
|
|
lines[19] = "working x x tail".into();
|
|
let working_text = lines.join("\n") + "\n";
|
|
fs::write(&source, &working_text).unwrap();
|
|
let config = fixture.file(
|
|
"config.rb",
|
|
"Vamp.configure do |config|\n config.clipboard_mode = 'separate'\n config.diff_context_lines = 1\n config.diff_expand_lines = 2\nend\n",
|
|
);
|
|
let mut session =
|
|
EditorSession::open(Some(source.clone()), vec!["text".into()], Some(&config)).unwrap();
|
|
assert!(session.editor().startup_diagnostic().is_none());
|
|
session_keys(&mut session, "iunsaved ");
|
|
session.handle(Input::Escape);
|
|
let unsaved_text = session.editor().context().text;
|
|
let original_cursor = session.editor().cursor();
|
|
|
|
session.execute("diff");
|
|
wait_until(&mut session, |session| {
|
|
session
|
|
.editor()
|
|
.diff()
|
|
.is_some_and(|diff| diff.catalog.is_some() && !diff.loading)
|
|
});
|
|
for revision in [&before, &after] {
|
|
session_keys(&mut session, revision);
|
|
session.handle(Input::Down);
|
|
session.handle(Input::Enter);
|
|
}
|
|
wait_until(&mut session, |session| {
|
|
session
|
|
.editor()
|
|
.diff()
|
|
.is_some_and(|diff| diff.phase == Phase::View && diff.selected_file_loaded())
|
|
});
|
|
let excerpts = session.editor().diff().unwrap().display_rows.clone();
|
|
assert!(
|
|
excerpts
|
|
.iter()
|
|
.any(|row| matches!(row, DisplayRow::Fold { .. }))
|
|
);
|
|
|
|
for (focus, text, first_x, second_x) in [
|
|
(Focus::Before, &before_text, 7, 9),
|
|
(Focus::After, &after_text, 6, 8),
|
|
] {
|
|
for _ in 0..3 {
|
|
if session.editor().diff().unwrap().focus == focus {
|
|
break;
|
|
}
|
|
session.handle(Input::Char('\t'));
|
|
}
|
|
assert_eq!(session.editor().diff().unwrap().focus, focus);
|
|
let revision = session.editor().text_revision();
|
|
session_keys(&mut session, "gt02f[");
|
|
assert_eq!(
|
|
session.editor().cursor(),
|
|
Cursor {
|
|
row: 18,
|
|
column: 15
|
|
}
|
|
);
|
|
session_keys(&mut session, "F[f]");
|
|
assert_eq!(
|
|
session.editor().cursor(),
|
|
Cursor {
|
|
row: 18,
|
|
column: 13
|
|
}
|
|
);
|
|
session_keys(&mut session, "j02fx");
|
|
assert_eq!(
|
|
session.editor().cursor(),
|
|
Cursor {
|
|
row: 19,
|
|
column: second_x
|
|
}
|
|
);
|
|
session_keys(&mut session, "*");
|
|
assert_eq!(
|
|
session.editor().cursor(),
|
|
Cursor {
|
|
row: 19,
|
|
column: second_x
|
|
}
|
|
);
|
|
session_keys(&mut session, "*");
|
|
assert_eq!(
|
|
session.editor().cursor(),
|
|
Cursor {
|
|
row: 20,
|
|
column: 12
|
|
}
|
|
);
|
|
assert_eq!(session.editor().search_match().unwrap().end_column, 13);
|
|
session_keys(&mut session, "3*N");
|
|
assert_eq!(
|
|
session.editor().cursor(),
|
|
Cursor {
|
|
row: 18,
|
|
column: 12
|
|
}
|
|
);
|
|
session_keys(&mut session, "3n");
|
|
assert_eq!(
|
|
session.editor().cursor(),
|
|
Cursor {
|
|
row: 19,
|
|
column: second_x
|
|
}
|
|
);
|
|
session_keys(&mut session, "Fxvfx");
|
|
assert_eq!(
|
|
session.editor().selections(),
|
|
[Selection {
|
|
start: Cursor {
|
|
row: 19,
|
|
column: first_x
|
|
},
|
|
end: Cursor {
|
|
row: 19,
|
|
column: second_x + 1
|
|
},
|
|
}]
|
|
);
|
|
session_keys(&mut session, "yVfx");
|
|
assert!(session.editor().visual_linewise());
|
|
assert_eq!(
|
|
session.editor().cursor(),
|
|
Cursor {
|
|
row: 19,
|
|
column: second_x
|
|
}
|
|
);
|
|
session.handle(Input::Escape);
|
|
assert_eq!(session.editor().context().text, *text);
|
|
assert_eq!(session.editor().text_revision(), revision);
|
|
assert!(!session.editor().dirty());
|
|
assert_eq!(session.editor().diff().unwrap().display_rows, excerpts);
|
|
}
|
|
|
|
session.execute("diff.close");
|
|
assert_eq!(session.editor().context().text, unsaved_text);
|
|
assert_eq!(session.editor().cursor(), original_cursor);
|
|
assert!(session.editor().dirty());
|
|
assert_eq!(fs::read_to_string(&source).unwrap(), working_text);
|
|
assert_eq!(fixture.git(&["rev-parse", "HEAD"]), before);
|
|
}
|