use super::*;
use test_support::{ctrl, emacs_editor, key, nano_editor, special, type_chars, vi_editor};
fn run_ex(ed: &mut Editor, ta: &mut TextArea, cmd: &str) -> Step {
ed.input(special(KeyCode::Esc), ta); ed.input(key(':'), ta);
for c in cmd.chars() {
ed.input(key(c), ta);
}
ed.input(special(KeyCode::Enter), ta)
}
#[test]
fn vi_unbound_normal_key_emits_a_hint() {
let mut ed = vi_editor();
let mut ta = new_textarea(Edit::Vi);
type_chars(&mut ed, &mut ta, "hi");
ed.input(special(KeyCode::Esc), &mut ta); let _ = ed.take_msg(); ed.input(key('q'), &mut ta); let msg = ed
.take_msg()
.expect("an unbound NORMAL key should surface a hint");
assert!(msg.contains("insert"), "hint nudges toward insert: {msg:?}");
assert_eq!(ta.lines(), &["hi"], "`q` still types nothing in NORMAL");
}
#[test]
fn vi_w_submits_like_enter() {
let mut ed = vi_editor();
let mut ta = TextArea::default();
type_chars(&mut ed, &mut ta, "hello");
assert_eq!(run_ex(&mut ed, &mut ta, "w"), Step::Submit);
assert!(ed.confirm_prompt().is_none());
}
#[test]
fn vi_wq_confirms_then_y_submit_quits() {
let mut ed = vi_editor();
let mut ta = TextArea::default();
type_chars(&mut ed, &mut ta, "ship it");
assert_eq!(
run_ex(&mut ed, &mut ta, "wq"),
Step::Continue,
":wq must not submit until confirmed"
);
assert!(
ed.confirm_prompt().is_some(),
"the [y/N] question is showing"
);
assert_eq!(ed.input(key('y'), &mut ta), Step::SubmitQuit);
assert!(
ed.confirm_prompt().is_none(),
"confirm cleared after answer"
);
}
#[test]
fn vi_wq_confirm_cancels_on_n_or_enter() {
for answer in [KeyCode::Char('n'), KeyCode::Enter, KeyCode::Esc] {
let mut ed = vi_editor();
let mut ta = TextArea::default();
type_chars(&mut ed, &mut ta, "keep editing");
assert_eq!(run_ex(&mut ed, &mut ta, "wq"), Step::Continue);
assert_eq!(
ed.input(special(answer), &mut ta),
Step::Continue,
"{answer:?} cancels the confirm"
);
assert!(ed.confirm_prompt().is_none(), "confirm cleared on cancel");
assert_eq!(ta.lines(), &["keep editing".to_string()]);
}
}
#[test]
fn vi_wq_bang_forces_without_confirm() {
let mut ed = vi_editor();
let mut ta = TextArea::default();
type_chars(&mut ed, &mut ta, "no prompt");
assert_eq!(run_ex(&mut ed, &mut ta, "wq!"), Step::SubmitQuit);
assert!(ed.confirm_prompt().is_none());
}
#[test]
fn vi_q_quits_without_sending() {
let mut ed = vi_editor();
let mut ta = TextArea::default();
type_chars(&mut ed, &mut ta, "discard me");
assert_eq!(run_ex(&mut ed, &mut ta, "q"), Step::Eof);
}
#[test]
fn nano_is_modeless_and_labeled() {
let mut ed = nano_editor();
let mut ta = TextArea::default();
assert_eq!(ed.label(), "nano");
type_chars(&mut ed, &mut ta, "plain text");
assert_eq!(ed.label(), "nano", "no mode flip");
assert_eq!(ta.lines(), &["plain text".to_string()]);
assert_eq!(ed.input(ctrl('o'), &mut ta), Step::Continue);
assert_eq!(ed.input(special(KeyCode::Enter), &mut ta), Step::Submit);
assert!(Edit::Nano.is_modeless() && Edit::Emacs.is_modeless());
assert!(!Edit::Vi.is_modeless());
}
#[test]
fn emacs_enter_submits_and_ctrl_o_inserts_newline() {
let mut ed = emacs_editor();
let mut ta = TextArea::default();
type_chars(&mut ed, &mut ta, "hello");
assert_eq!(ed.input(ctrl('o'), &mut ta), Step::Continue);
type_chars(&mut ed, &mut ta, "world");
assert_eq!(ta.lines().len(), 2, "two lines after Ctrl-O");
assert_eq!(ed.input(special(KeyCode::Enter), &mut ta), Step::Submit);
}
#[test]
fn shift_enter_inserts_newline_without_submitting() {
let nl = || KeyEvent::new(KeyCode::Enter, KeyModifiers::SHIFT);
let mut ed = emacs_editor();
let mut ta = TextArea::default();
type_chars(&mut ed, &mut ta, "line one");
assert_eq!(
ed.input(nl(), &mut ta),
Step::Continue,
"Shift-Enter newline"
);
type_chars(&mut ed, &mut ta, "line two");
assert_eq!(ta.lines().len(), 2, "Shift-Enter added a line");
let mut ed = vi_editor();
let mut ta = TextArea::default();
type_chars(&mut ed, &mut ta, "vi line");
assert_eq!(ed.input(nl(), &mut ta), Step::Continue);
assert_eq!(ta.lines().len(), 2, "Shift-Enter newline in vi too");
let mut ed = emacs_editor();
let mut ta = TextArea::default();
type_chars(&mut ed, &mut ta, "x");
assert_eq!(ed.input(special(KeyCode::Enter), &mut ta), Step::Submit);
}
#[test]
fn ctrl_o_is_newline_in_modeless_but_reserved_in_vi() {
for ed_factory in [emacs_editor as fn() -> Editor, nano_editor] {
let mut ed = ed_factory();
let mut ta = TextArea::default();
type_chars(&mut ed, &mut ta, "a");
assert_eq!(ed.input(ctrl('o'), &mut ta), Step::Continue);
assert_eq!(ta.lines().len(), 2, "Ctrl-O newline in modeless mode");
}
let mut ed = vi_editor();
let mut ta = TextArea::default();
type_chars(&mut ed, &mut ta, "vi");
assert_eq!(ed.input(ctrl('o'), &mut ta), Step::Continue);
assert_eq!(ta.lines().len(), 1, "Ctrl-O does NOT newline in vi");
}
#[test]
fn enter_continues_an_open_bang_line() {
let mut ed = emacs_editor();
let mut ta = TextArea::default();
type_chars(&mut ed, &mut ta, "! ls \\");
assert_eq!(ed.input(special(KeyCode::Enter), &mut ta), Step::Continue);
assert_eq!(ta.lines().len(), 2, "Enter continued the bang line");
}
#[test]
fn ctrl_c_abandons_line_and_ctrl_d_empty_is_eof() {
let mut ed = emacs_editor();
let mut ta = TextArea::default();
type_chars(&mut ed, &mut ta, "throwaway");
assert_eq!(
ed.input(ctrl('c'), &mut ta),
Step::Continue,
"Ctrl-C does not exit"
);
assert!(buffer_is_empty(&ta), "Ctrl-C cleared the buffer");
assert_eq!(
ed.input(ctrl('d'), &mut ta),
Step::Eof,
"Ctrl-D empty → EOF"
);
type_chars(&mut ed, &mut ta, "x");
assert_eq!(ed.input(ctrl('d'), &mut ta), Step::Submit);
}
#[test]
fn vi_ex_wq_submits_and_q_is_eof() {
let mut ed = vi_editor();
let mut ta = TextArea::default();
type_chars(&mut ed, &mut ta, "payload");
ed.input(special(KeyCode::Esc), &mut ta);
ed.input(key(':'), &mut ta);
assert_eq!(ed.ex(), Some(""), "ex line is active");
ed.input(key('w'), &mut ta);
ed.input(key('q'), &mut ta);
assert_eq!(ed.input(special(KeyCode::Enter), &mut ta), Step::Continue);
assert!(ed.confirm_prompt().is_some());
let mut ed = vi_editor();
let mut ta = TextArea::default();
ed.input(special(KeyCode::Esc), &mut ta);
ed.input(key(':'), &mut ta);
ed.input(key('q'), &mut ta);
assert_eq!(ed.input(special(KeyCode::Enter), &mut ta), Step::Eof);
}
#[test]
fn mode_idiomatic_exit_keys() {
let mut ed = emacs_editor();
let mut ta = TextArea::default();
assert_eq!(
ed.input(ctrl('x'), &mut ta),
Step::Continue,
"C-x arms prefix"
);
assert_eq!(ed.input(ctrl('c'), &mut ta), Step::Eof, "C-x C-c → exit");
let mut ed = emacs_editor();
let mut ta = TextArea::default();
ed.input(ctrl('x'), &mut ta);
type_chars(&mut ed, &mut ta, "a"); assert_eq!(ta.lines(), &["a".to_string()]);
assert_eq!(
ed.input(ctrl('c'), &mut ta),
Step::Continue,
"bare C-c abandons the line, does not exit"
);
assert!(buffer_is_empty(&ta), "bare C-c cleared the line");
let mut ed = nano_editor();
let mut ta = TextArea::default();
assert_eq!(ed.input(ctrl('x'), &mut ta), Step::Eof, "nano ^X → exit");
let mut ed = vi_editor();
let mut ta = TextArea::default();
assert_eq!(
ed.input(ctrl('x'), &mut ta),
Step::Continue,
"vi C-x → no exit"
);
}
#[test]
fn mode_idiomatic_help_keys_queue_a_cheatsheet() {
let mut ed = nano_editor();
let mut ta = TextArea::default();
assert_eq!(ed.input(ctrl('g'), &mut ta), Step::Continue);
assert!(ed.take_msg().unwrap().starts_with("nano"));
let mut ed = emacs_editor();
let mut ta = TextArea::default();
assert_eq!(ed.input(ctrl('h'), &mut ta), Step::Continue);
assert!(ed.take_msg().unwrap().starts_with("emacs"));
let mut ed = vi_editor();
let mut ta = TextArea::default();
ed.input(special(KeyCode::Esc), &mut ta);
ed.input(key(':'), &mut ta);
for c in "help".chars() {
ed.input(key(c), &mut ta);
}
ed.input(special(KeyCode::Enter), &mut ta);
assert!(ed.take_msg().unwrap().starts_with("vi"));
let mut ed = emacs_editor();
let mut ta = TextArea::default();
ed.input(ctrl('g'), &mut ta);
assert!(ed.take_msg().is_none());
}
#[test]
fn vi_colon_jumps_queues_a_note() {
let mut ed = vi_editor();
let mut ta = TextArea::default();
ed.input(special(KeyCode::Esc), &mut ta);
ed.input(key(':'), &mut ta);
for c in "jumps".chars() {
ed.input(key(c), &mut ta);
}
ed.input(special(KeyCode::Enter), &mut ta);
assert!(ed.take_msg().is_some(), ":jumps queued a scrollback note");
assert!(ed.take_msg().is_none(), "note is one-shot");
}