use super::*;
use test_support::{ctrl, key, special, type_chars, vi_editor};
fn normal_keys(ed: &mut Editor, ta: &mut TextArea, keys: &str) -> Step {
ed.input(special(KeyCode::Esc), ta); let mut last = Step::Continue;
for c in keys.chars() {
last = ed.input(key(c), ta);
}
last
}
#[test]
fn bare_gt_is_next_not_goto_one() {
let mut ed = vi_editor();
let mut ta = TextArea::default();
assert_eq!(
normal_keys(&mut ed, &mut ta, "gt"),
Step::Tab(crate::tabs::TabAction::Next)
);
}
#[test]
fn a_counted_gt_goes_to_that_tab_absolutely() {
let mut ed = vi_editor();
let mut ta = TextArea::default();
assert_eq!(
normal_keys(&mut ed, &mut ta, "2gt"),
Step::Tab(crate::tabs::TabAction::Goto(2))
);
let mut ed = vi_editor();
let mut ta = TextArea::default();
assert_eq!(
normal_keys(&mut ed, &mut ta, "12gt"),
Step::Tab(crate::tabs::TabAction::Goto(12))
);
}
#[test]
fn gt_capital_is_relative_in_both_forms() {
let mut ed = vi_editor();
let mut ta = TextArea::default();
assert_eq!(
normal_keys(&mut ed, &mut ta, "gT"),
Step::Tab(crate::tabs::TabAction::Prev(1))
);
let mut ed = vi_editor();
let mut ta = TextArea::default();
assert_eq!(
normal_keys(&mut ed, &mut ta, "3gT"),
Step::Tab(crate::tabs::TabAction::Prev(3))
);
}
#[test]
fn gg_still_jumps_to_the_top_and_is_not_a_tab_motion() {
let mut ed = vi_editor();
let mut ta = textarea_with(Edit::Vi, "one\ntwo\nthree");
assert_eq!(
normal_keys(&mut ed, &mut ta, "gg"),
Step::Continue,
"gg is a cursor jump, never a tab action"
);
assert_eq!(ta.cursor().0, 0, "cursor is on the first line");
}
#[test]
fn an_unknown_g_suffix_stays_inert() {
let mut ed = vi_editor();
let mut ta = TextArea::default();
assert_eq!(normal_keys(&mut ed, &mut ta, "gz"), Step::Continue);
assert_eq!(
normal_keys(&mut ed, &mut ta, "gt"),
Step::Tab(crate::tabs::TabAction::Next),
"a stale count would have made this a Goto"
);
}
#[test]
fn gt_in_insert_mode_is_just_text() {
let mut ed = vi_editor(); let mut ta = TextArea::default();
type_chars(&mut ed, &mut ta, "gt");
assert_eq!(
ta.lines(),
&["gt".to_string()],
"insert mode types the characters"
);
}
#[test]
fn vi_o_opens_line_below_and_enters_insert() {
let mut ed = vi_editor();
let mut ta = TextArea::default();
type_chars(&mut ed, &mut ta, "first");
ed.input(special(KeyCode::Esc), &mut ta);
assert_eq!(ed.label(), "vi N");
ed.input(key('o'), &mut ta);
assert_eq!(ed.label(), "vi I");
type_chars(&mut ed, &mut ta, "second");
assert_eq!(ta.lines(), &["first".to_string(), "second".to_string()]);
}
#[test]
fn vi_uppercase_o_opens_line_above() {
let mut ed = vi_editor();
let mut ta = TextArea::default();
type_chars(&mut ed, &mut ta, "below");
ed.input(special(KeyCode::Esc), &mut ta);
ed.input(key('O'), &mut ta);
type_chars(&mut ed, &mut ta, "above");
assert_eq!(ta.lines(), &["above".to_string(), "below".to_string()]);
}
#[test]
fn vi_dd_deletes_the_line() {
let mut ed = vi_editor();
let mut ta = TextArea::default();
type_chars(&mut ed, &mut ta, "doomed");
ed.input(special(KeyCode::Esc), &mut ta);
ed.input(key('d'), &mut ta);
ed.input(key('d'), &mut ta);
assert_eq!(ta.lines(), &[String::new()], "dd cleared the only line");
}
#[test]
fn vi_x_with_count_deletes_n_chars() {
let mut ed = vi_editor();
let mut ta = TextArea::default();
type_chars(&mut ed, &mut ta, "abcdef");
ed.input(special(KeyCode::Esc), &mut ta); ed.input(key('0'), &mut ta); type_chars(&mut ed, &mut ta, "3x"); assert_eq!(ta.lines(), &["def".to_string()]);
}
fn vi_buffer(lines: &[&str]) -> (Editor, TextArea<'static>) {
let mut ed = vi_editor();
let mut ta = TextArea::default();
for (i, l) in lines.iter().enumerate() {
if i > 0 {
ed.input(KeyEvent::new(KeyCode::Enter, KeyModifiers::SHIFT), &mut ta);
}
type_chars(&mut ed, &mut ta, l);
}
ed.input(special(KeyCode::Esc), &mut ta); ed.input(key('g'), &mut ta);
ed.input(key('g'), &mut ta); (ed, ta)
}
#[test]
fn vi_uppercase_j_joins_line_below() {
let (mut ed, mut ta) = vi_buffer(&["foo", "bar"]);
ed.input(key('J'), &mut ta);
assert_eq!(ta.lines(), &["foo bar".to_string()], "J joins with a space");
ed.input(key('J'), &mut ta);
assert_eq!(ta.lines(), &["foo bar".to_string()]);
}
#[test]
fn vi_count_j_joins_multiple_lines() {
let (mut ed, mut ta) = vi_buffer(&["a", "b", "c"]);
ed.input(key('3'), &mut ta);
ed.input(key('J'), &mut ta);
assert_eq!(ta.lines(), &["a b c".to_string()]);
}
#[test]
fn vi_insert_normal_ctrl_o_runs_one_command() {
let mut ed = vi_editor();
let mut ta = TextArea::default();
type_chars(&mut ed, &mut ta, "hello"); ed.input(ctrl('o'), &mut ta);
assert_eq!(ed.label(), "vi N", "Ctrl-O drops to NORMAL");
ed.input(key('0'), &mut ta);
assert_eq!(ed.label(), "vi I", "resumes INSERT after one command");
type_chars(&mut ed, &mut ta, "X");
assert_eq!(ta.lines(), &["Xhello".to_string()], "inserted at head");
}
#[test]
fn vi_esc_cancels_incomplete_command() {
let (mut ed, mut ta) = vi_line("hello world");
ed.input(key('d'), &mut ta); ed.input(special(KeyCode::Esc), &mut ta); ed.input(key('w'), &mut ta); assert_eq!(
ta.lines(),
&["hello world".to_string()],
"Esc cancelled the d operator"
);
let (mut ed, mut ta) = vi_line("abcdef");
ed.input(key('3'), &mut ta); ed.input(special(KeyCode::Esc), &mut ta); ed.input(key('x'), &mut ta); assert_eq!(
ta.lines(),
&["bcdef".to_string()],
"Esc cancelled the count"
);
}
#[test]
fn vi_jumplist_back_and_forward() {
let (mut ed, mut ta) = vi_buffer(&["one", "two", "three"]);
assert_eq!(ta.cursor().0, 0, "gg → row 0");
ed.input(ctrl('o'), &mut ta);
assert_eq!(ta.cursor().0, 2, "Ctrl-O → back to row 2");
ed.input(special(KeyCode::Tab), &mut ta);
assert_eq!(ta.cursor().0, 0, "Ctrl-I → forward to row 0");
}
fn vi_line(s: &str) -> (Editor, TextArea<'static>) {
let mut ed = vi_editor();
let mut ta = TextArea::default();
type_chars(&mut ed, &mut ta, s);
ed.input(special(KeyCode::Esc), &mut ta);
ed.input(key('0'), &mut ta); (ed, ta)
}
#[test]
fn vi_f_and_semicolon_and_comma_char_search() {
let (mut ed, mut ta) = vi_line("a.b.c.d");
ed.input(key('f'), &mut ta);
ed.input(key('.'), &mut ta);
assert_eq!(ta.cursor().1, 1, "f. → first dot");
ed.input(key(';'), &mut ta);
assert_eq!(ta.cursor().1, 3, "; → next dot");
ed.input(key(','), &mut ta);
assert_eq!(ta.cursor().1, 1, ", → previous dot");
}
#[test]
fn vi_t_and_capital_f_char_search() {
let (mut ed, mut ta) = vi_line("abcXdef");
ed.input(key('t'), &mut ta);
ed.input(key('X'), &mut ta);
assert_eq!(ta.cursor().1, 2, "tX → col before X");
ed.input(key('$'), &mut ta);
ed.input(key('F'), &mut ta);
ed.input(key('X'), &mut ta);
assert_eq!(ta.cursor().1, 3, "FX → onto X");
}
#[test]
fn vi_operator_motions_dw_d_dollar_d0_yy() {
let (mut ed, mut ta) = vi_line("foo bar baz");
ed.input(key('d'), &mut ta);
ed.input(key('w'), &mut ta);
assert_eq!(ta.lines(), &["bar baz".to_string()], "dw");
let (mut ed, mut ta) = vi_line("keep DROP this");
ed.input(key('f'), &mut ta);
ed.input(key('D'), &mut ta); ed.input(key('d'), &mut ta);
ed.input(key('$'), &mut ta);
assert_eq!(ta.lines(), &["keep ".to_string()], "d$");
let (mut ed, mut ta) = vi_line("alpha beta");
ed.input(key('f'), &mut ta);
ed.input(key('b'), &mut ta); ed.input(key('d'), &mut ta);
ed.input(key('0'), &mut ta);
assert_eq!(ta.lines(), &["beta".to_string()], "d0 deletes to BOL");
let (mut ed, mut ta) = vi_line("dup");
ed.input(key('y'), &mut ta);
ed.input(key('y'), &mut ta);
ed.input(key('p'), &mut ta);
assert!(
ta.lines().iter().filter(|l| l.contains("dup")).count() >= 1,
"yy+p yanks and pastes the line"
);
}