feat: repeat last native edit with dot
This commit is contained in:
+371
@@ -1006,6 +1006,377 @@ end
|
||||
assert!(session.editor().should_quit());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_dot_replaces_fresh_words_and_each_repeat_has_one_undo() {
|
||||
let fixture = Fixture::new();
|
||||
let buffer = fixture.file("buffer.txt", "old elephant zebra");
|
||||
let mut session = EditorSession::open(Some(buffer), vec!["text".into()], None).unwrap();
|
||||
for key in ['v', 'e', 'c'] {
|
||||
session.handle(Input::Char(key));
|
||||
}
|
||||
for ch in "item".chars() {
|
||||
session.handle(Input::Char(ch));
|
||||
}
|
||||
session.handle(Input::Escape);
|
||||
assert_eq!(session.editor().lines(), &["item elephant zebra"]);
|
||||
for key in ['w', '3', '.'] {
|
||||
session.handle(Input::Char(key));
|
||||
}
|
||||
assert_eq!(session.editor().lines(), &["item item zebra"]);
|
||||
assert_eq!(session.editor().mode(), crate::editor::Mode::Normal);
|
||||
session.handle(Input::Char('u'));
|
||||
assert_eq!(session.editor().lines(), &["item elephant zebra"]);
|
||||
session.handle(Input::Home);
|
||||
for key in ['w', 'w', 'v', 'e', '.'] {
|
||||
session.handle(Input::Char(key));
|
||||
}
|
||||
assert_eq!(session.editor().lines(), &["item elephant item"]);
|
||||
assert_eq!(session.editor().mode(), crate::editor::Mode::Normal);
|
||||
session.handle(Input::Char('u'));
|
||||
assert_eq!(session.editor().lines(), &["item elephant zebra"]);
|
||||
session.handle(Input::Char('u'));
|
||||
assert_eq!(session.editor().lines(), &["old elephant zebra"]);
|
||||
assert!(!session.editor().dirty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typed_repeat_remaps_use_resolved_motions_and_current_visual_ranges() {
|
||||
let fixture = Fixture::new();
|
||||
fixture.file("buffer.txt", "old enormous tail end");
|
||||
let mut session = fixture.open(
|
||||
r#"
|
||||
Vamp.configure do |config|
|
||||
config.bind ".", nil
|
||||
config.bind ".", nil, mode: config.modes.visual
|
||||
config.bind "r", config.core.repeat_last_edit
|
||||
config.bind "r", config.core.repeat_last_edit, mode: config.modes.visual
|
||||
config.bind "e", config.core.left, mode: config.modes.visual
|
||||
config.bind "ctrl+f", config.core.word_end, mode: config.modes.visual
|
||||
end
|
||||
"#,
|
||||
);
|
||||
session.handle(Input::Char('v'));
|
||||
session.handle(Input::Ctrl('f'));
|
||||
session.handle(Input::Char('c'));
|
||||
for ch in "item".chars() {
|
||||
session.handle(Input::Char(ch));
|
||||
}
|
||||
session.handle(Input::Escape);
|
||||
session.handle(Input::Char('w'));
|
||||
session.handle(Input::Char('.'));
|
||||
assert_eq!(session.editor().lines(), &["item enormous tail end"]);
|
||||
session.handle(Input::Char('r'));
|
||||
assert_eq!(session.editor().lines(), &["item item tail end"]);
|
||||
session.handle(Input::Char('u'));
|
||||
assert_eq!(session.editor().lines(), &["item enormous tail end"]);
|
||||
session.handle(Input::Home);
|
||||
for key in ['w', 'v', '2'] {
|
||||
session.handle(Input::Char(key));
|
||||
}
|
||||
session.handle(Input::Ctrl('f'));
|
||||
session.handle(Input::Char('.'));
|
||||
assert_eq!(session.editor().mode(), crate::editor::Mode::Visual);
|
||||
assert_eq!(session.editor().lines(), &["item enormous tail end"]);
|
||||
session.handle(Input::Char('r'));
|
||||
assert_eq!(session.editor().lines(), &["item item end"]);
|
||||
assert_eq!(session.editor().mode(), crate::editor::Mode::Normal);
|
||||
session.handle(Input::Char('u'));
|
||||
assert_eq!(session.editor().lines(), &["item enormous tail end"]);
|
||||
session.handle(Input::Char('u'));
|
||||
assert_eq!(session.editor().lines(), &["old enormous tail end"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn repeat_recipe_crosses_buffers_but_pending_insert_and_undo_stay_per_buffer() {
|
||||
let fixture = Fixture::new();
|
||||
fixture.file("buffer.txt", "alpha");
|
||||
let second = fixture.file("second.txt", "beta");
|
||||
let mut session = fixture.open("");
|
||||
session.handle(Input::Char('i'));
|
||||
for ch in "one".chars() {
|
||||
session.handle(Input::Char(ch));
|
||||
}
|
||||
session.execute(&format!("edit {}", second.display()));
|
||||
assert_eq!(session.editor().mode(), crate::editor::Mode::Normal);
|
||||
assert_eq!(session.editor().lines(), &["beta"]);
|
||||
session.handle(Input::Char('.'));
|
||||
assert_eq!(session.editor().lines(), &["onebeta"]);
|
||||
session.handle(Input::Char('u'));
|
||||
assert_eq!(session.editor().lines(), &["beta"]);
|
||||
session.execute("buffer 1");
|
||||
assert_eq!(session.editor().lines(), &["onealpha"]);
|
||||
session.handle(Input::Char('u'));
|
||||
assert_eq!(session.editor().lines(), &["alpha"]);
|
||||
session.handle(Input::Char('U'));
|
||||
assert_eq!(session.editor().lines(), &["onealpha"]);
|
||||
session.execute("buffer 2");
|
||||
assert_eq!(session.editor().lines(), &["beta"]);
|
||||
session.handle(Input::Char('U'));
|
||||
assert_eq!(session.editor().lines(), &["onebeta"]);
|
||||
session.handle(Input::Home);
|
||||
session.handle(Input::Char('i'));
|
||||
for ch in "two".chars() {
|
||||
session.handle(Input::Char(ch));
|
||||
}
|
||||
session.execute("buffer 1");
|
||||
session.handle(Input::Home);
|
||||
session.handle(Input::Char('.'));
|
||||
assert_eq!(session.editor().lines(), &["twoonealpha"]);
|
||||
session.handle(Input::Char('u'));
|
||||
assert_eq!(session.editor().lines(), &["onealpha"]);
|
||||
session.execute("buffer 2");
|
||||
assert_eq!(session.editor().lines(), &["twoonebeta"]);
|
||||
session.handle(Input::Char('u'));
|
||||
assert_eq!(session.editor().lines(), &["onebeta"]);
|
||||
session.handle(Input::Char('u'));
|
||||
assert_eq!(session.editor().lines(), &["beta"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plugin_effects_commit_pending_typing_without_replacing_or_reinvoking_native_recipe() {
|
||||
let fixture = Fixture::new();
|
||||
fixture.file("buffer.txt", "base");
|
||||
fixture.file(
|
||||
"effects.rb",
|
||||
r##"
|
||||
Vamp.plugin "effects", requires: ["text"]
|
||||
calls = 0
|
||||
Vamp.command("effect") do |editor, args|
|
||||
calls += 1
|
||||
File.open("calls.txt", "a") { |file| file.puts "#{calls} #{args}" }
|
||||
case args
|
||||
when "replace"
|
||||
editor.replace "plugin"
|
||||
when "insert"
|
||||
editor.insert "?"
|
||||
end
|
||||
editor.message "calls=#{calls}"
|
||||
end
|
||||
"##,
|
||||
);
|
||||
let mut session = fixture.open(
|
||||
r#"
|
||||
Vamp.configure do |config|
|
||||
config.plugin "effects.rb"
|
||||
end
|
||||
"#,
|
||||
);
|
||||
session.handle(Input::Char('i'));
|
||||
session.handle(Input::Char('!'));
|
||||
session.execute("effect replace");
|
||||
assert_eq!(session.editor().mode(), crate::editor::Mode::Normal);
|
||||
assert_eq!(session.editor().lines(), &["plugin"]);
|
||||
assert_eq!(session.editor().message(), "calls=1");
|
||||
session.handle(Input::Home);
|
||||
session.handle(Input::Char('.'));
|
||||
assert_eq!(session.editor().lines(), &["!plugin"]);
|
||||
assert_eq!(
|
||||
fs::read_to_string(fixture.0.join("calls.txt")).unwrap(),
|
||||
"1 replace\n"
|
||||
);
|
||||
session.handle(Input::Char('u'));
|
||||
assert_eq!(session.editor().lines(), &["plugin"]);
|
||||
session.handle(Input::Char('u'));
|
||||
assert_eq!(session.editor().lines(), &["!base"]);
|
||||
session.handle(Input::Char('u'));
|
||||
assert_eq!(session.editor().lines(), &["base"]);
|
||||
session.handle(Input::Home);
|
||||
session.execute("effect insert");
|
||||
assert_eq!(session.editor().lines(), &["?base"]);
|
||||
assert_eq!(session.editor().message(), "calls=2");
|
||||
session.handle(Input::Home);
|
||||
session.handle(Input::Char('.'));
|
||||
assert_eq!(session.editor().lines(), &["!?base"]);
|
||||
assert_eq!(
|
||||
fs::read_to_string(fixture.0.join("calls.txt")).unwrap(),
|
||||
"1 replace\n2 insert\n"
|
||||
);
|
||||
session.handle(Input::Char('u'));
|
||||
assert_eq!(session.editor().lines(), &["?base"]);
|
||||
session.handle(Input::Char('u'));
|
||||
assert_eq!(session.editor().lines(), &["base"]);
|
||||
session.execute("effect message");
|
||||
assert_eq!(session.editor().message(), "calls=3");
|
||||
session.handle(Input::Home);
|
||||
session.handle(Input::Char('.'));
|
||||
assert_eq!(session.editor().lines(), &["!base"]);
|
||||
assert_eq!(
|
||||
fs::read_to_string(fixture.0.join("calls.txt")).unwrap(),
|
||||
"1 replace\n2 insert\n3 message\n"
|
||||
);
|
||||
session.handle(Input::Char('u'));
|
||||
assert_eq!(session.editor().lines(), &["base"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explicit_save_commits_pending_change_and_repeat_does_not_save_again() {
|
||||
let fixture = Fixture::new();
|
||||
let buffer = fixture.file("buffer.txt", "old elephant");
|
||||
let mut session = fixture.open("");
|
||||
for key in ['v', 'e', 'c'] {
|
||||
session.handle(Input::Char(key));
|
||||
}
|
||||
for ch in "item".chars() {
|
||||
session.handle(Input::Char(ch));
|
||||
}
|
||||
session.execute("w");
|
||||
assert_eq!(session.editor().mode(), crate::editor::Mode::Normal);
|
||||
assert_eq!(fs::read_to_string(&buffer).unwrap(), "item elephant");
|
||||
session.handle(Input::Home);
|
||||
session.handle(Input::Char('w'));
|
||||
session.handle(Input::Char('.'));
|
||||
assert_eq!(session.editor().lines(), &["item item"]);
|
||||
assert!(session.editor().dirty());
|
||||
assert_eq!(fs::read_to_string(&buffer).unwrap(), "item elephant");
|
||||
session.handle(Input::Char('u'));
|
||||
assert_eq!(session.editor().lines(), &["item elephant"]);
|
||||
assert!(!session.editor().dirty());
|
||||
session.handle(Input::Char('u'));
|
||||
assert_eq!(session.editor().lines(), &["old elephant"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dots_in_insert_and_focused_queries_are_literal_and_excluded_from_recipe() {
|
||||
let fixture = Fixture::new();
|
||||
fixture.file("buffer.txt", "alpha");
|
||||
let mut session = fixture.open(
|
||||
r#"
|
||||
Vamp.configure do |config|
|
||||
config.file_explorer_view = "list"
|
||||
config.file_picker_view = "list"
|
||||
config.bind "ctrl+f", config.core.file_explorer
|
||||
config.bind "ctrl+o", config.core.file_picker
|
||||
end
|
||||
"#,
|
||||
);
|
||||
session.handle(Input::Char('i'));
|
||||
session.handle(Input::Char('X'));
|
||||
session.handle(Input::Escape);
|
||||
session.handle(Input::Home);
|
||||
session.handle(Input::Ctrl('f'));
|
||||
for ch in ".query".chars() {
|
||||
session.handle(Input::Char(ch));
|
||||
}
|
||||
assert_eq!(session.editor().file_picker().unwrap().query, ".query");
|
||||
assert_eq!(session.editor().lines(), &["Xalpha"]);
|
||||
session.handle(Input::Escape);
|
||||
assert!(!session.editor().file_picker().unwrap().focused);
|
||||
assert_eq!(session.editor().mode(), crate::editor::Mode::Normal);
|
||||
session.handle(Input::Ctrl('o'));
|
||||
session.handle(Input::Ctrl('u'));
|
||||
session.handle(Input::Char('.'));
|
||||
assert_eq!(session.editor().file_picker().unwrap().query, ".");
|
||||
assert_eq!(session.editor().lines(), &["Xalpha"]);
|
||||
session.handle(Input::Escape);
|
||||
assert!(session.editor().file_picker().is_none());
|
||||
session.handle(Input::Char('.'));
|
||||
assert_eq!(session.editor().lines(), &["XXalpha"]);
|
||||
session.handle(Input::Char('u'));
|
||||
assert_eq!(session.editor().lines(), &["Xalpha"]);
|
||||
session.handle(Input::Home);
|
||||
session.handle(Input::Char('i'));
|
||||
session.handle(Input::Char('.'));
|
||||
session.handle(Input::Ctrl('p'));
|
||||
assert!(
|
||||
session
|
||||
.editor()
|
||||
.palette()
|
||||
.unwrap()
|
||||
.entries
|
||||
.iter()
|
||||
.any(|entry| entry.name == "core.repeat_last_edit")
|
||||
);
|
||||
session.handle(Input::Char('.'));
|
||||
assert_eq!(session.editor().palette().unwrap().query, ".");
|
||||
assert_eq!(session.editor().lines(), &[".Xalpha"]);
|
||||
session.handle(Input::Escape);
|
||||
session.handle(Input::Char('!'));
|
||||
session.handle(Input::Escape);
|
||||
assert_eq!(session.editor().lines(), &[".!Xalpha"]);
|
||||
for prompt in [':', '/'] {
|
||||
session.handle(Input::Char(prompt));
|
||||
session.handle(Input::Char('.'));
|
||||
assert_eq!(session.editor().command_line(), ".");
|
||||
assert_eq!(session.editor().lines(), &[".!Xalpha"]);
|
||||
session.handle(Input::Escape);
|
||||
}
|
||||
session.handle(Input::Home);
|
||||
session.handle(Input::Char('.'));
|
||||
assert_eq!(session.editor().lines(), &[".!.!Xalpha"]);
|
||||
session.handle(Input::Char('u'));
|
||||
assert_eq!(session.editor().lines(), &[".!Xalpha"]);
|
||||
session.handle(Input::Char('u'));
|
||||
assert_eq!(session.editor().lines(), &["Xalpha"]);
|
||||
session.handle(Input::Char('u'));
|
||||
assert_eq!(session.editor().lines(), &["alpha"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn repeated_clipboard_paste_uses_captured_text_after_a_different_yank() {
|
||||
let fixture = Fixture::new();
|
||||
fixture.file("buffer.txt", "one two three");
|
||||
let mut session = fixture.open("");
|
||||
for key in ['v', 'e', 'y'] {
|
||||
session.handle(Input::Char(key));
|
||||
}
|
||||
session.handle(Input::Home);
|
||||
session.handle(Input::Char('P'));
|
||||
assert_eq!(session.editor().lines(), &["oneone two three"]);
|
||||
for key in ['w', 'v', 'e', 'y'] {
|
||||
session.handle(Input::Char(key));
|
||||
}
|
||||
session.handle(Input::Home);
|
||||
session.handle(Input::Char('.'));
|
||||
assert_eq!(session.editor().lines(), &["oneoneone two three"]);
|
||||
session.handle(Input::Char('u'));
|
||||
assert_eq!(session.editor().lines(), &["oneone two three"]);
|
||||
session.handle(Input::Home);
|
||||
session.handle(Input::Char('P'));
|
||||
assert_eq!(session.editor().lines(), &["twooneone two three"]);
|
||||
session.handle(Input::Char('u'));
|
||||
assert_eq!(session.editor().lines(), &["oneone two three"]);
|
||||
session.handle(Input::Char('u'));
|
||||
assert_eq!(session.editor().lines(), &["one two three"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_paste_repeats_without_dispatch_and_preserves_native_input_groups() {
|
||||
let fixture = Fixture::new();
|
||||
fixture.file("buffer.txt", "seed");
|
||||
let mut session = fixture.open("");
|
||||
session.handle(Input::Char('i'));
|
||||
session.handle(Input::Char('a'));
|
||||
session.handle(Input::Paste(".:q!\r\n界".into()));
|
||||
session.handle(Input::Char('b'));
|
||||
session.handle(Input::Escape);
|
||||
assert_eq!(session.editor().lines(), &["a.:q!", "界bseed"]);
|
||||
session.handle(Input::Char('u'));
|
||||
assert_eq!(session.editor().lines(), &["a.:q!", "界seed"]);
|
||||
session.handle(Input::Char('u'));
|
||||
assert_eq!(session.editor().lines(), &["aseed"]);
|
||||
session.handle(Input::Char('u'));
|
||||
assert_eq!(session.editor().lines(), &["seed"]);
|
||||
session.handle(Input::Home);
|
||||
session.handle(Input::Char('.'));
|
||||
assert_eq!(session.editor().lines(), &["bseed"]);
|
||||
session.handle(Input::Char('u'));
|
||||
assert_eq!(session.editor().lines(), &["seed"]);
|
||||
session.handle(Input::Home);
|
||||
session.handle(Input::Char('i'));
|
||||
session.handle(Input::Paste(".:q!\r\n界".into()));
|
||||
session.handle(Input::Escape);
|
||||
assert_eq!(session.editor().lines(), &[".:q!", "界seed"]);
|
||||
session.handle(Input::Home);
|
||||
session.handle(Input::Char('.'));
|
||||
assert_eq!(session.editor().lines(), &[".:q!", ".:q!", "界界seed"]);
|
||||
assert_eq!(session.editor().mode(), crate::editor::Mode::Normal);
|
||||
assert!(!session.editor().should_quit());
|
||||
session.handle(Input::Char('u'));
|
||||
assert_eq!(session.editor().lines(), &[".:q!", "界seed"]);
|
||||
session.handle(Input::Char('u'));
|
||||
assert_eq!(session.editor().lines(), &["seed"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typed_visual_change_remaps_c_and_command_execution_commits_one_replacement() {
|
||||
let fixture = Fixture::new();
|
||||
|
||||
Reference in New Issue
Block a user