use hjkl_buffer::View;
use hjkl_engine::{DefaultHost, Editor, Options};
use hjkl_vim::{dispatch_input, insert::step_insert};
fn editor(lang: &str, content: &str) -> Editor<View, DefaultHost> {
let buf = View::from_str(content);
let host = DefaultHost::new();
let opts = Options {
filetype: lang.to_string(),
formatoptions: "ro".to_string(),
..Options::default()
};
hjkl_vim::vim_editor(buf, host, opts)
}
fn feed(ed: &mut Editor<View, DefaultHost>, keys: &str) {
use hjkl_engine::{Input, Key};
for ch in keys.chars() {
let input = match ch {
'\n' => Input {
key: Key::Enter,
ctrl: false,
alt: false,
shift: false,
},
'\x08' => Input {
key: Key::Backspace,
ctrl: false,
alt: false,
shift: false,
},
_ => Input {
key: Key::Char(ch),
ctrl: false,
alt: false,
shift: false,
},
};
dispatch_input(ed, input);
}
}
fn feed_insert(ed: &mut Editor<View, DefaultHost>, keys: &str) {
use hjkl_engine::{Input, Key};
for ch in keys.chars() {
let input = match ch {
'\n' => Input {
key: Key::Enter,
ctrl: false,
alt: false,
shift: false,
},
'\x08' => Input {
key: Key::Backspace,
ctrl: false,
alt: false,
shift: false,
},
_ => Input {
key: Key::Char(ch),
ctrl: false,
alt: false,
shift: false,
},
};
step_insert(ed, input);
}
}
fn buf_text(ed: &Editor<View, DefaultHost>) -> String {
ed.content().to_string()
}
#[test]
fn enter_continues_rust_line_comment() {
let mut ed = editor("rust", "// foo\n");
feed(&mut ed, "A"); feed_insert(&mut ed, "\n"); let text = buf_text(&ed);
let lines: Vec<&str> = text.lines().collect();
assert!(
lines.len() >= 2,
"expected at least 2 lines, got: {lines:?}"
);
assert!(
lines[1].starts_with("// "),
"line 2 should start with '// ', got: {:?}",
lines[1]
);
}
#[test]
fn enter_continues_rust_outer_doc_comment() {
let mut ed = editor("rust", "/// outer doc\n");
feed(&mut ed, "A");
feed_insert(&mut ed, "\n");
let text = buf_text(&ed);
let lines: Vec<&str> = text.lines().collect();
assert!(
lines.get(1).map(|l| l.starts_with("/// ")).unwrap_or(false),
"line 2 should start with '/// ', got: {lines:?}"
);
}
#[test]
fn enter_continues_rust_inner_doc_comment() {
let mut ed = editor("rust", "//! inner\n");
feed(&mut ed, "A");
feed_insert(&mut ed, "\n");
let text = buf_text(&ed);
let lines: Vec<&str> = text.lines().collect();
assert!(
lines.get(1).map(|l| l.starts_with("//! ")).unwrap_or(false),
"line 2 should start with '//! ', got: {lines:?}"
);
}
#[test]
fn enter_continues_python_comment() {
let mut ed = editor("python", "# comment\n");
feed(&mut ed, "A");
feed_insert(&mut ed, "\n");
let text = buf_text(&ed);
let lines: Vec<&str> = text.lines().collect();
assert!(
lines.get(1).map(|l| l.starts_with("# ")).unwrap_or(false),
"line 2 should start with '# ', got: {lines:?}"
);
}
#[test]
fn enter_does_not_continue_non_comment() {
let mut ed = editor("rust", "let x = 1;\n");
feed(&mut ed, "A");
feed_insert(&mut ed, "\n");
let text = buf_text(&ed);
let lines: Vec<&str> = text.lines().collect();
let line2 = lines.get(1).copied().unwrap_or("");
assert!(
!line2.starts_with("// "),
"non-comment Enter should not add //, got: {line2:?}"
);
}
#[test]
fn enter_no_continuation_when_r_cleared() {
let mut ed = editor("rust", "// foo\n");
ed.settings_mut().formatoptions = "o".to_string(); feed(&mut ed, "A");
feed_insert(&mut ed, "\n");
let text = buf_text(&ed);
let lines: Vec<&str> = text.lines().collect();
let line2 = lines.get(1).copied().unwrap_or("");
assert!(
!line2.starts_with("// "),
"r cleared → should not continue comment, got: {line2:?}"
);
}
#[test]
fn open_below_continues_rust_comment() {
let mut ed = editor("rust", "// foo\n");
feed(&mut ed, "o");
let (row, _) = ed.cursor();
assert_eq!(row, 1, "cursor should be on line 2 after 'o'");
let text = buf_text(&ed);
let lines: Vec<&str> = text.lines().collect();
let line2 = lines.get(1).copied().unwrap_or("");
assert!(
line2.starts_with("// "),
"open-below should start with '// ', got: {line2:?}"
);
}
#[test]
fn open_below_no_continuation_when_o_cleared() {
let mut ed = editor("rust", "// foo\n");
ed.settings_mut().formatoptions = "r".to_string(); feed(&mut ed, "o");
let text = buf_text(&ed);
let lines: Vec<&str> = text.lines().collect();
let line2 = lines.get(1).copied().unwrap_or("");
assert!(
!line2.starts_with("// "),
"o cleared → should not continue comment, got: {line2:?}"
);
}
#[test]
fn open_above_continues_rust_comment() {
let mut ed = editor("rust", "// foo\n");
feed(&mut ed, "O");
let text = buf_text(&ed);
let lines: Vec<&str> = text.lines().collect();
let line1 = lines.first().copied().unwrap_or("");
assert!(
line1.starts_with("// "),
"open-above should start with '// ', got: {line1:?}"
);
}
#[test]
fn backspace_strips_comment_prefix_at_end() {
let mut ed = editor("rust", "// \n");
feed(&mut ed, "A"); feed_insert(&mut ed, "\x08");
let text = buf_text(&ed);
let line1 = text.lines().next().unwrap_or("");
assert!(
line1.is_empty(),
"backspace should have stripped '// ', line is: {line1:?}"
);
}