pub use super::*;
#[cfg(test)]
mod reviewer_battery {
use super::*;
#[test]
fn counts_multiply_non_operator_commands() {
let mut e = Editor::new(Buffer::from_text("abcde\n"));
e.feed_text("2x");
assert_eq!(e.buf().rope.to_string(), "cde\n");
let mut e = Editor::new(Buffer::from_text("a\nb\nc\n"));
e.feed_text("dd");
e.feed_text("dd");
assert_eq!(e.buf().rope.to_string(), "c\n");
e.feed_text("2u");
assert_eq!(e.buf().rope.to_string(), "a\nb\nc\n");
let mut e = Editor::new(Buffer::from_text("abcde\n"));
e.feed_text("3rx");
assert_eq!(e.buf().rope.to_string(), "xxxde\n"); let mut e = Editor::new(Buffer::from_text("ab\n"));
e.feed_text("yiw");
e.feed_text("2p");
assert_eq!(e.buf().rope.to_string(), "aababb\n"); }
#[test]
fn counted_insert_repeats_text() {
let mut e = Editor::new(Buffer::from_text("x\n"));
e.feed_text("3iZ");
e.feed(crate::editor::Key::Esc);
assert_eq!(e.buf().rope.to_string(), "ZZZx\n"); e.feed_text("u"); assert_eq!(e.buf().rope.to_string(), "x\n");
}
#[test]
fn cc_keeps_the_line_and_indent() {
let mut e = Editor::new(Buffer::from_text("one\n two\nthree\n"));
e.feed_text("jccX");
e.feed(crate::editor::Key::Esc);
assert_eq!(e.buf().rope.to_string(), "one\n X\nthree\n");
e.feed_text("u");
assert_eq!(e.buf().rope.to_string(), "one\n two\nthree\n");
let mut e = Editor::new(Buffer::from_text("one\ntwo\nthree\n"));
e.feed_text("jcc");
e.feed(crate::editor::Key::Esc);
assert_eq!(e.buf().rope.to_string(), "one\n\nthree\n");
}
#[test]
fn c_enters_insert_at_deletion_start() {
let mut e = Editor::new(Buffer::from_text("hello world\n"));
e.feed_text("wCthere");
e.feed(crate::editor::Key::Esc);
assert_eq!(e.buf().rope.to_string(), "hello there\n");
let mut e = Editor::new(Buffer::from_text("abcdef\n"));
e.feed_text("3lCX");
e.feed(crate::editor::Key::Esc);
assert_eq!(e.buf().rope.to_string(), "abcX\n");
}
#[test]
fn i_caret_tilde_s_work() {
let mut e = Editor::new(Buffer::from_text(" let x = 1;\n"));
e.feed_text("0^"); assert_eq!(e.head(), 4);
let mut e = Editor::new(Buffer::from_text(" let x = 1;\n"));
e.feed_text("I"); assert_eq!(e.mode, crate::editor::Mode::Insert);
assert_eq!(e.head(), 4);
e.feed(crate::editor::Key::Esc);
let mut e = Editor::new(Buffer::from_text("abc\n"));
e.feed_text("~~");
assert_eq!(e.buf().rope.to_string(), "ABc\n");
let mut e = Editor::new(Buffer::from_text("one\ntwo\n"));
e.feed_text("SX");
e.feed(crate::editor::Key::Esc);
assert_eq!(e.buf().rope.to_string(), "X\ntwo\n");
}
#[test]
fn unknown_bare_keys_say_so() {
let mut e = Editor::new(Buffer::from_text("x\n"));
e.feed_text("="); assert!(e.message.contains("not an editor command"));
}
}
#[cfg(test)]
mod scratch_tests {
use super::*;
#[test]
fn first_open_replaces_the_scratch_buffer() {
let dir = tempfile::tempdir().unwrap();
let f = dir.path().join("scratch-test.rs");
std::fs::write(&f, "fn a() {}\n").unwrap();
let mut e = Editor::new(Buffer::from_text(""));
e.open_buffer(f.to_str().unwrap()).unwrap();
assert_eq!(e.docs.len(), 1, "scratch replaced, not stacked");
assert_eq!(e.buf().path.as_deref(), f.to_str());
e.feed_text(":q\r");
assert!(e.should_quit, "one :q quits");
}
#[test]
fn view_marks_readonly_and_edits_refuse() {
let mut e = Editor::new(Buffer::from_text("one\ntwo\n"));
e.feed_text(":view\r");
assert!(e.buf().readonly);
e.feed_text("x");
assert_eq!(e.buf().rope.to_string(), "one\ntwo\n", "no edit landed");
assert!(e.message.contains("readonly"));
}
#[test]
fn edited_scratch_survives() {
let mut e = Editor::new(Buffer::from_text(""));
e.feed_text("ix"); e.feed(crate::editor::Key::Esc);
let dir = tempfile::tempdir().unwrap();
let f = dir.path().join("scratch-test.rs");
std::fs::write(&f, "fn a() {}\n").unwrap();
e.open_buffer(f.to_str().unwrap()).unwrap();
assert_eq!(e.docs.len(), 2, "edited scratch is real work");
}
}
#[cfg(test)]
mod edit_tests {
use super::*;
fn editor_with(text: &str) -> Editor {
Editor::new(Buffer::from_text(text))
}
fn text(e: &Editor) -> String {
e.buf().rope.to_string()
}
#[test]
fn named_registers_yank_and_paste() {
let mut e = editor_with("alpha\nbeta\ngamma\n");
e.feed_text("\"ayy"); assert_eq!(e.register(Some('a')).0, "alpha\n");
e.feed_text("j");
e.feed_text("\"ap"); assert_eq!(text(&e), "alpha\nbeta\nalpha\ngamma\n");
assert!(e.register(None).0.is_empty());
}
#[test]
fn space_y_yanks_motion_to_system_register() {
let mut e = Editor::new(Buffer::from_text("hello world\n"));
e.feed_text(" yw");
assert_eq!(e.register(Some('+')).0, "hello ");
assert!(e.osc52.is_some(), "OSC52 payload staged for the TUI");
}
#[test]
fn visual_space_y_yanks_selection_to_system_register() {
let mut e = Editor::new(Buffer::from_text("hello world\n"));
e.feed_text("vl y");
assert_eq!(e.register(Some('+')).0, "he");
assert!(e.osc52.is_some());
}
#[test]
fn clipboard_paste_inserts_read_result() {
let mut e = Editor::new(Buffer::from_text("ab\n"));
e.clip_paste_pending = Some(false);
e.clip_tx.send(Some("XY".into())).unwrap();
e.drain_clipboard();
assert_eq!(e.buf().rope.to_string(), "aXYb\n");
}
#[test]
fn clipboard_paste_reports_missing_provider() {
let mut e = Editor::new(Buffer::from_text("ab\n"));
e.clip_paste_pending = Some(false);
e.clip_tx.send(None).unwrap();
e.drain_clipboard();
assert!(e.message.contains("clipboard"));
assert_eq!(e.buf().rope.to_string(), "ab\n");
}
#[test]
fn alias_verbs() {
let mut e = editor_with("let edge = hone;\n");
e.feed_text("0wD"); assert_eq!(text(&e), "let \n");
let mut e = editor_with("let x = 1;\n");
e.feed_text("0wY"); assert_eq!(e.register(None).0, "let x = 1;\n");
let mut e = editor_with("abc\n");
e.feed_text("sZ"); e.feed(crate::editor::Key::Esc);
assert_eq!(text(&e), "Zbc\n");
}
#[test]
fn replace_char_and_join() {
let mut e = editor_with("abc\ndef\n");
e.feed_text("rX");
assert_eq!(text(&e), "Xbc\ndef\n");
e.feed_text("J");
assert_eq!(text(&e), "Xbc def\n");
}
#[test]
fn indent_and_dedent() {
let mut e = editor_with("a\nb\nc\n");
e.feed_text("2>>");
assert_eq!(text(&e), " a\n b\nc\n");
e.feed_text("0<<");
assert_eq!(text(&e), "a\n b\nc\n");
}
#[test]
fn dot_repeat_replays_insert() {
let mut e = editor_with("one\ntwo\n");
e.feed_text("A!");
e.feed(crate::editor::Key::Esc);
e.feed_text("j.");
assert_eq!(text(&e), "one!\ntwo!\n");
}
#[test]
fn visual_line_deletes_whole_lines() {
let mut e = editor_with("a\nb\nc\nd\n");
e.feed_text("Vjd");
assert_eq!(text(&e), "c\nd\n");
assert!(e.register(None).1); e.feed_text("P");
assert_eq!(text(&e), "a\nb\nc\nd\n"); }
#[test]
fn paste_is_one_undo_unit() {
let mut e = Editor::new(Buffer::from_text("hello world\n"));
e.feed_text("yiw"); e.feed_text("ep");
assert_eq!(e.buf().rope.to_string(), "hellohello world\n");
e.feed_text("u");
assert_eq!(e.buf().rope.to_string(), "hello world\n");
}
#[test]
fn semicolon_and_comma_repeat_find() {
let mut e = Editor::new(Buffer::from_text("a.b.c.d\n"));
e.feed_text("f."); assert_eq!(e.head(), 1);
e.feed_text(";");
assert_eq!(e.head(), 3);
e.feed_text(";");
assert_eq!(e.head(), 5);
e.feed_text(","); assert_eq!(e.head(), 3);
}
#[test]
fn star_searches_word_under_cursor_whole_word() {
let mut e = Editor::new(Buffer::from_text("hone honed hone\n"));
e.feed_text("*"); assert_eq!(e.head(), 11);
e.feed_text("n"); assert_eq!(e.head(), 0);
e.feed_text("#"); assert_eq!(e.head(), 11);
let mut e = Editor::new(Buffer::from_text("hone honed\n"));
e.feed_text("*");
assert_eq!(e.head(), 0, "honed is not a whole-word match for hone");
}
#[test]
fn count_motions_and_ex_line_jump() {
let mut e = Editor::new(Buffer::from_text("1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n"));
e.feed_text("10j");
assert_eq!(e.buf().line_of(e.head()), 10);
e.feed_text(":30\r");
assert_eq!(e.buf().line_of(e.head()), 11);
e.feed_text(":4\r");
assert_eq!(e.buf().line_of(e.head()), 3);
}
#[test]
fn visual_indent_and_dedent() {
let mut e = Editor::new(Buffer::from_text("a\nb\nc\n"));
e.feed_text("Vj>");
assert_eq!(e.buf().rope.to_string(), " a\n b\nc\n");
e.feed_text("u");
assert_eq!(e.buf().rope.to_string(), "a\nb\nc\n");
e.feed_text("Vj>");
e.feed_text("Vj<");
assert_eq!(e.buf().rope.to_string(), "a\nb\nc\n");
}
#[test]
fn noh_clears_search_highlight() {
let mut e = Editor::new(Buffer::from_text("foo bar\n"));
e.feed_text("/foo\r");
assert!(e.last_search.is_some());
e.feed_text(":noh\r");
assert!(e.last_search.is_none());
}
#[test]
fn dot_repeats_delete_and_change() {
let mut e = Editor::new(Buffer::from_text("one\ntwo\nthree\n"));
e.feed_text("dd");
assert_eq!(e.buf().rope.to_string(), "two\nthree\n");
e.feed_text(".");
assert_eq!(e.buf().rope.to_string(), "three\n");
let mut e = Editor::new(Buffer::from_text("aa bb\ncc dd\n"));
e.feed_text("cwX");
e.feed(crate::editor::Key::Esc);
assert_eq!(e.buf().rope.to_string(), "X bb\ncc dd\n");
e.feed_text("j"); e.feed_text(".");
assert_eq!(e.buf().rope.to_string(), "X bb\nX dd\n");
}
#[test]
fn last_search_highlights_persistently() {
let mut e = Editor::new(Buffer::from_text("foo bar foo\n"));
e.feed_text("/foo\r");
assert!(e.search_pattern().is_none());
assert_eq!(e.last_search.as_ref().unwrap().pattern, "foo");
let frame = crate::headless::frame_string(&mut e, 40, 8);
assert!(frame.contains("foo bar foo"));
}
#[test]
fn empty_search_repeats_last() {
let mut e = Editor::new(Buffer::from_text("aa line\nbb line\ncc line\n"));
e.feed_text("/line\r");
assert_eq!(e.buf().line_of(e.head()), 0);
e.feed_text("/\r");
assert_eq!(e.buf().line_of(e.head()), 1, "empty / repeats forward");
e.feed_text("/\r");
assert_eq!(e.buf().line_of(e.head()), 2);
e.feed_text("?\r");
assert_eq!(e.buf().line_of(e.head()), 1, "empty ? reverses");
assert!(e.message.is_empty());
}
#[test]
fn x_on_multibyte_char_deletes_the_whole_char() {
let mut e = Editor::new(Buffer::from_text("héllo\n"));
e.feed_text("lx"); assert_eq!(e.buf().rope.to_string(), "hllo\n");
e.feed_text("u");
assert_eq!(e.buf().rope.to_string(), "héllo\n");
e.feed_text("a"); assert!(e.buf().is_boundary(e.head()));
}
#[test]
fn arrows_speak_hjkl() {
let mut e = Editor::new(Buffer::from_text("one\ntwo\nthree\n"));
e.feed(crate::editor::Key::Down);
assert_eq!(e.buf().line_of(e.head()), 1, "Down is j");
e.feed(crate::editor::Key::Right);
assert_eq!(e.head(), e.buf().line_start(1) + 1, "Right is l");
e.feed(crate::editor::Key::Up);
assert_eq!(e.buf().line_of(e.head()), 0, "Up is k");
e.feed(crate::editor::Key::Left);
assert_eq!(e.head(), 0, "Left is h");
}
#[test]
fn wq_never_closes_a_failed_save() {
let dir = tempfile::tempdir().unwrap();
let f = dir.path().join("wq.txt");
std::fs::write(&f, "one\n").unwrap();
let mut e = Editor::new(Buffer::open(f.to_str().unwrap()).unwrap());
e.feed_text("ix");
e.feed(crate::editor::Key::Esc);
std::fs::write(&f, "external\n").unwrap(); e.feed_text(":wq\r");
assert!(!e.should_quit, "failed save must not close");
assert!(e.buf().dirty, "still dirty");
assert!(e.message.contains("changed on disk"));
assert_eq!(std::fs::read_to_string(&f).unwrap(), "external\n");
e.feed_text(":wq!\r");
assert!(e.should_quit, "forced write quits");
assert_eq!(std::fs::read_to_string(&f).unwrap(), "xone\n");
}
#[test]
fn ex_open_and_close_buffers() {
std::fs::write("/tmp/strop-test-b.rs", "second\n").unwrap();
let mut e = editor_with("first\n");
e.feed_text(":e /tmp/strop-test-b.rs<cr>");
assert_eq!(e.docs.len(), 2);
assert_eq!(text(&e), "second\n");
e.feed_text(":q<cr>");
assert_eq!(e.docs.len(), 1);
assert_eq!(text(&e), "first\n");
e.feed_text("ix");
e.feed(crate::editor::Key::Esc);
e.feed_text(":q<cr>");
assert_eq!(e.docs.len(), 1);
assert!(e.message.contains("unsaved"));
e.feed_text(":q!<cr>");
assert!(e.should_quit);
}
}
#[cfg(test)]
mod indent_tests {
use super::*;
#[test]
fn enter_copies_and_deepens_indent() {
let mut e = Editor::new(Buffer::from_text("fn f() {\n let x = 1;\n}\n"));
e.feed_text("j$"); e.feed(crate::editor::Key::Char('a'));
e.feed(crate::editor::Key::Enter);
e.feed_text("let y = 2;");
assert_eq!(
e.buf().rope.to_string(),
"fn f() {\n let x = 1;\n let y = 2;\n}\n"
);
e.feed(crate::editor::Key::Esc);
e.feed_text("gg$");
e.feed(crate::editor::Key::Char('a'));
e.feed(crate::editor::Key::Enter);
e.feed_text("// body");
let got = e.buf().rope.to_string();
assert!(got.starts_with("fn f() {\n // body"), "got: {got:?}");
}
#[test]
fn o_auto_indents() {
let mut e = Editor::new(Buffer::from_text("fn f() {\n}\n"));
e.feed_text("o");
e.feed_text("let x = 1;");
assert_eq!(e.buf().rope.to_string(), "fn f() {\n let x = 1;\n}\n");
}
#[test]
fn tab_size_from_config() {
let mut e = Editor::new(Buffer::from_text("a\nb\n"));
e.config = crate::config::Config {
tab_size: 2,
..Default::default()
};
e.feed_text(">>");
assert_eq!(e.buf().rope.to_string(), " a\nb\n");
e.feed_text("<<");
assert_eq!(e.buf().rope.to_string(), "a\nb\n");
}
#[test]
fn new_file_opens_empty_and_saves() {
let path = "/tmp/strop-newfile-test.rs";
std::fs::remove_file(path).ok();
let mut e = Editor::new(Buffer::open(path).expect("missing file is a new buffer"));
assert_eq!(e.buf().len_bytes(), 0);
e.feed_text("ifresh");
e.feed(crate::editor::Key::Esc);
e.feed_text(":w<cr>");
assert_eq!(std::fs::read_to_string(path).unwrap(), "fresh");
std::fs::remove_file(path).ok();
}
}
#[cfg(test)]
mod alignment_tests {
use super::*;
#[test]
fn document_set_stays_honest() {
let dir = tempfile::tempdir().unwrap();
let a = dir.path().join("align-a.rs");
let b = dir.path().join("align-b.rs");
std::fs::write(&a, "a\n").unwrap();
std::fs::write(&b, "b\n").unwrap();
let mut e = Editor::new(Buffer::open(a.to_str().unwrap()).unwrap());
e.open_buffer(b.to_str().unwrap()).unwrap();
assert_eq!(e.docs.len(), 2);
e.open_diff_surface("delta", "f.rs", vec![], None);
assert_eq!(e.docs.len(), 3);
assert!(e.cur().surface.is_some());
e.close_buffer(true);
assert_eq!(e.docs.len(), 2);
e.close_buffer(true);
e.close_buffer(true);
assert!(e.should_quit, "closing the last document quits");
}
}
#[cfg(test)]
mod smartindent_tests {
use super::*;
#[test]
fn closer_dedents_on_indent_only_line() {
let mut e = Editor::new(Buffer::from_text("fn f() {\n}\n"));
e.feed_text("o"); assert_eq!(e.buf().line_text(1), " ");
e.feed_text("}"); assert_eq!(e.buf().rope.to_string(), "fn f() {\n}\n}\n");
}
#[test]
fn closer_noop_with_real_text_before() {
let mut e = Editor::new(Buffer::from_text("fn f() {\n}\n"));
e.feed_text("o"); e.feed_text("let x = 1;"); e.feed_text("}"); assert_eq!(e.buf().line_text(1), " let x = 1;}");
}
}
#[cfg(test)]
mod undo_tests {
use super::*;
#[test]
fn insert_session_undoes_as_one_unit() {
let mut e = Editor::new(Buffer::from_text("hello\n"));
e.feed_text("A world"); e.feed(crate::editor::Key::Esc);
assert_eq!(e.buf().rope.to_string(), "hello world\n");
e.feed_text("u");
assert_eq!(e.buf().rope.to_string(), "hello\n");
e.feed(crate::editor::Key::CtrlR);
assert_eq!(e.buf().rope.to_string(), "hello world\n");
}
#[test]
fn change_op_holds_one_undo_unit() {
let mut e = Editor::new(Buffer::from_text("say [old] now\n"));
e.feed_text("w"); e.feed_text("ci["); e.feed_text("new");
e.feed(crate::editor::Key::Esc);
assert_eq!(e.buf().rope.to_string(), "say [new] now\n");
e.feed_text("u"); assert_eq!(e.buf().rope.to_string(), "say [old] now\n");
}
#[test]
fn n_repeats_search_and_wraps() {
let mut e = Editor::new(Buffer::from_text("foo bar\nfoo baz\n"));
e.feed_text("/foo\r"); assert_eq!(e.head(), 8);
e.feed_text("n"); assert_eq!(e.head(), 0);
e.feed_text("N"); assert_eq!(e.head(), 8);
}
#[test]
fn edit_after_undo_forks_and_ctrlr_redoes_last_branch() {
let mut e = Editor::new(Buffer::from_text("ab\n"));
e.feed_text("rx"); e.feed_text("u");
e.feed_text("ry"); e.feed_text("u"); e.feed(crate::editor::Key::CtrlR); assert_eq!(e.buf().rope.to_string(), "yb\n");
e.feed(crate::editor::Key::CtrlR);
assert_eq!(e.buf().rope.to_string(), "yb\n");
}
#[test]
fn readonly_buffers_refuse_undo() {
let mut e = Editor::new(Buffer::from_text("x\n"));
e.buf_mut().readonly = true;
e.feed_text("u");
assert!(e.message.contains("readonly"));
assert_eq!(e.buf().rope.to_string(), "x\n");
}
}
#[cfg(test)]
mod surround_tests {
use super::*;
#[test]
fn ds_deletes_pair() {
let mut e = Editor::new(Buffer::from_text("say \"hi\" now\n"));
e.feed_text("w"); e.feed_text("ds\"");
assert_eq!(e.buf().rope.to_string(), "say hi now\n");
e.feed_text("u");
assert_eq!(e.buf().rope.to_string(), "say \"hi\" now\n");
}
#[test]
fn cs_changes_pair() {
let mut e = Editor::new(Buffer::from_text("call(a, b)\n"));
e.feed_text("f("); e.feed_text("cs(["); assert_eq!(e.buf().rope.to_string(), "call[a, b]\n");
e.feed_text("u");
assert_eq!(e.buf().rope.to_string(), "call(a, b)\n");
}
#[test]
fn ysiw_wraps_word() {
let mut e = Editor::new(Buffer::from_text("make it sharp\n"));
e.feed_text("w"); e.feed_text("ysiw\"");
assert_eq!(e.buf().rope.to_string(), "make \"it\" sharp\n");
e.feed_text("u");
assert_eq!(e.buf().rope.to_string(), "make it sharp\n");
}
#[test]
fn visual_s_wraps_selection() {
let mut e = Editor::new(Buffer::from_text("wrap me up\n"));
e.feed_text("ve"); e.feed_text("S(");
assert_eq!(e.buf().rope.to_string(), "(wrap) me up\n");
}
}
#[cfg(test)]
mod visual_object_tests {
use super::*;
#[test]
fn vi_paren_selects_inner() {
let mut e = Editor::new(Buffer::from_text("call(a, b)\n"));
e.feed_text("f("); e.feed_text("vi(");
let r = e.visual_range().expect("visual range");
assert_eq!(e.buf().slice_string(r), "a, b");
e.feed_text("d");
assert_eq!(e.buf().rope.to_string(), "call()\n");
}
#[test]
fn va_quote_includes_quotes() {
let mut e = Editor::new(Buffer::from_text("say \"hi\" now\n"));
e.feed_text("w"); e.feed_text("va\"");
let r = e.visual_range().expect("visual range");
assert_eq!(e.buf().slice_string(r), "\"hi\"");
}
}
#[cfg(test)]
mod hardening_tests {
use super::*;
#[test]
fn undo_after_visual_delete() {
let mut e = Editor::new(Buffer::from_text("say \"hi\" now\n"));
e.feed_text("ved"); assert_eq!(e.buf().rope.to_string(), " \"hi\" now\n");
e.feed_text("u");
assert_eq!(e.buf().rope.to_string(), "say \"hi\" now\n");
}
#[test]
fn quote_object_scans_forward_on_the_line() {
let mut e = Editor::new(Buffer::from_text("say \"hi\" now\n"));
e.feed_text("vi\"");
let r = e.visual_range().expect("selection");
assert_eq!(e.buf().slice_string(r), "hi");
}
#[test]
fn quit_leaves_editor_drain_safe() {
let mut e = Editor::new(Buffer::from_text("x\n"));
e.feed_text(":q!\r");
assert!(e.should_quit);
assert!(e.docs.is_empty());
e.drain_picker();
e.drain_git_jobs();
e.drain_lsp();
e.lsp_sync_changed();
}
#[test]
fn undo_lands_cursor_at_change_start() {
let mut e = Editor::new(Buffer::from_text("hello\n"));
e.feed_text("A world");
e.feed(crate::editor::Key::Esc);
e.feed_text("0"); e.feed_text("u");
assert_eq!(e.buf().rope.to_string(), "hello\n");
assert_eq!(e.head(), 4);
}
}
#[cfg(test)]
mod keybinds_tests {
use super::*;
#[test]
fn marks_set_and_jump() {
std::fs::write("/tmp/strop-mark-a.rs", "one\ntwo\nthree\n").unwrap();
std::fs::write("/tmp/strop-mark-b.rs", "alpha\nbeta\n").unwrap();
let mut e = Editor::new(Buffer::open("/tmp/strop-mark-a.rs").unwrap());
e.feed_text("jj"); e.feed_text("mb"); e.feed_text(":e /tmp/strop-mark-b.rs<cr>");
e.feed_text("'b"); assert_eq!(e.buf().path.as_deref(), Some("/tmp/strop-mark-a.rs"));
assert_eq!(e.buf().line_of(e.head()), 2);
std::fs::remove_file("/tmp/strop-mark-a.rs").ok();
std::fs::remove_file("/tmp/strop-mark-b.rs").ok();
}
#[test]
fn question_mark_is_search_backward() {
let mut e = Editor::new(Buffer::from_text("one two one two\n"));
e.feed_text("$"); e.feed_text("?one\r");
assert_eq!(
e.buf().col_of(e.head()),
8,
"backward search lands on the second 'one'"
);
}
}
#[cfg(test)]
mod w_probe {
#[test]
fn w_moves() {
let cmd = match strop_grammar::parse("w") {
strop_grammar::Parse::Complete(c) => c,
other => panic!("parse w: {other:?}"),
};
let buf = strop_core::Buffer::from_text("hello world\n");
let r = strop_grammar::resolve(&buf, 0, &cmd);
assert!(r.is_some(), "resolve w");
let r = r.unwrap();
assert_eq!((r.range.start, r.range.end), (0, 6), "w range");
let after = strop_grammar::cursor_after(&buf, 0, &cmd, &r);
assert_eq!(after, 6, "cursor after w");
let mut e = crate::editor::Editor::new(strop_core::Buffer::from_text("hello world\n"));
e.feed_text("w");
assert_eq!(e.head(), 6, "w moves to word start");
}
}