use escriba_buffer::BufferSet;
use escriba_core::Mode;
use escriba_runtime::EditorState;
use madori::event::{AppEvent, KeyCode, KeyEvent, Modifiers};
const DOC: &str = "alpha bravo\ncharlie delta\n";
fn state() -> EditorState {
state_with(DOC)
}
fn state_with(doc: &str) -> EditorState {
let mut bufs = BufferSet::new();
let id = bufs.scratch(doc);
EditorState::new_with_buffer(bufs, id)
}
fn press(st: &mut EditorState, key: KeyCode) {
st.tick(&AppEvent::Key(KeyEvent {
key,
pressed: true,
modifiers: Modifiers::default(),
text: None,
}));
}
fn press_ctrl(st: &mut EditorState, c: char) {
st.tick(&AppEvent::Key(KeyEvent {
key: KeyCode::Char(c),
pressed: true,
modifiers: Modifiers {
ctrl: true,
..Modifiers::default()
},
text: None,
}));
}
fn type_str(st: &mut EditorState, s: &str) {
for c in s.chars() {
press(st, KeyCode::Char(c));
}
}
fn text_of(st: &EditorState) -> String {
st.buffers
.get(st.active)
.map(escriba_buffer::Buffer::to_string)
.unwrap_or_default()
}
fn insert(st: &mut EditorState) {
press(st, KeyCode::Char('i'));
assert_eq!(st.modal.mode(), Mode::Insert, "`i` must enter Insert");
}
#[test]
fn backspace_erases_the_character_just_typed() {
let mut st = state();
insert(&mut st);
type_str(&mut st, "QQQ");
press(&mut st, KeyCode::Backspace);
assert!(
text_of(&st).starts_with("QQalpha bravo"),
"got {:?}",
text_of(&st)
);
assert_eq!(st.cursor().column, 2, "the caret follows the deletion");
assert_eq!(st.modal.mode(), Mode::Insert, "backspace does not leave Insert");
}
#[test]
fn delete_removes_the_character_ahead_and_leaves_the_caret_put() {
let mut st = state();
insert(&mut st);
press(&mut st, KeyCode::Delete);
assert!(text_of(&st).starts_with("lpha bravo"), "got {:?}", text_of(&st));
assert_eq!(
st.cursor().column,
0,
"forward-delete pulls text under a stationary caret"
);
}
#[test]
fn backspace_at_column_zero_joins_the_line_above() {
let mut st = state();
press(&mut st, KeyCode::Char('j')); insert(&mut st);
assert_eq!(st.cursor(), escriba_core::Position::new(1, 0));
press(&mut st, KeyCode::Backspace);
assert!(
text_of(&st).starts_with("alpha bravocharlie delta"),
"the lines must join; got {:?}",
text_of(&st)
);
assert_eq!(
st.cursor(),
escriba_core::Position::new(0, 11),
"the caret lands at the seam"
);
}
#[test]
fn delete_at_end_of_line_joins_the_line_below() {
let mut st = state();
insert(&mut st);
press(&mut st, KeyCode::End);
assert_eq!(st.cursor().column, 11);
press(&mut st, KeyCode::Delete);
assert!(
text_of(&st).starts_with("alpha bravocharlie delta"),
"got {:?}",
text_of(&st)
);
}
#[test]
fn backspace_at_the_start_of_the_document_is_a_no_op() {
let mut st = state();
insert(&mut st);
press(&mut st, KeyCode::Backspace);
assert_eq!(text_of(&st), DOC, "nothing to the left, nothing removed");
assert_eq!(st.cursor(), escriba_core::Position::ZERO);
}
#[test]
fn erasing_a_typo_does_not_clobber_the_register() {
let mut st = state();
press(&mut st, KeyCode::Char('y'));
press(&mut st, KeyCode::Char('w'));
let yanked = st.register().map(str::to_owned);
assert!(yanked.is_some(), "precondition: `yw` filled the register");
insert(&mut st);
type_str(&mut st, "Z");
press(&mut st, KeyCode::Backspace);
assert_eq!(st.register().map(str::to_owned), yanked);
}
#[test]
fn the_arrows_move_the_caret_without_leaving_insert() {
let mut st = state();
insert(&mut st);
press(&mut st, KeyCode::Right);
press(&mut st, KeyCode::Right);
press(&mut st, KeyCode::Down);
assert_eq!(st.modal.mode(), Mode::Insert);
assert_eq!(st.cursor(), escriba_core::Position::new(1, 2));
type_str(&mut st, "X");
assert!(text_of(&st).contains("chXarlie"), "got {:?}", text_of(&st));
}
#[test]
fn ctrl_w_erases_the_word_before_the_caret() {
let mut st = state();
insert(&mut st);
press(&mut st, KeyCode::End);
assert_eq!(st.cursor().column, 11);
press_ctrl(&mut st, 'w');
assert!(
text_of(&st).starts_with("alpha \ncharlie"),
"got {:?}",
text_of(&st)
);
assert_eq!(st.cursor(), escriba_core::Position::new(0, 6));
assert_eq!(st.modal.mode(), Mode::Insert);
}
#[test]
fn a_second_ctrl_w_eats_the_gap_and_the_word_before_it() {
let mut st = state();
insert(&mut st);
press(&mut st, KeyCode::End);
press_ctrl(&mut st, 'w');
press_ctrl(&mut st, 'w');
assert!(
text_of(&st).starts_with("\ncharlie"),
"both words gone; got {:?}",
text_of(&st)
);
assert_eq!(st.cursor(), escriba_core::Position::ZERO);
}
#[test]
fn ctrl_w_at_column_zero_joins_the_line_above() {
let mut st = state();
press(&mut st, KeyCode::Char('j'));
insert(&mut st);
assert_eq!(st.cursor(), escriba_core::Position::new(1, 0));
press_ctrl(&mut st, 'w');
assert!(
text_of(&st).starts_with("alpha bravocharlie delta"),
"got {:?}",
text_of(&st)
);
assert_eq!(st.cursor(), escriba_core::Position::new(0, 11));
}
#[test]
fn ctrl_u_clears_what_was_typed_first_and_the_indent_second() {
let mut st = state_with(" hello world\nnext\n");
insert(&mut st);
press(&mut st, KeyCode::End);
assert_eq!(st.cursor().column, 15);
press_ctrl(&mut st, 'u');
assert!(
text_of(&st).starts_with(" \nnext"),
"first press stops at the first non-blank; got {:?}",
text_of(&st)
);
assert_eq!(st.cursor(), escriba_core::Position::new(0, 4));
press_ctrl(&mut st, 'u');
assert!(
text_of(&st).starts_with("\nnext"),
"second press takes the indent; got {:?}",
text_of(&st)
);
assert_eq!(st.cursor(), escriba_core::Position::ZERO);
}
#[test]
fn ctrl_u_at_column_zero_is_a_no_op_not_a_line_merge() {
let mut st = state();
press(&mut st, KeyCode::Char('j'));
insert(&mut st);
assert_eq!(st.cursor(), escriba_core::Position::new(1, 0));
press_ctrl(&mut st, 'u');
assert_eq!(text_of(&st), DOC, "nothing erased");
assert_eq!(st.cursor(), escriba_core::Position::new(1, 0));
}
#[test]
fn ctrl_h_is_backspace() {
let mut st = state();
insert(&mut st);
type_str(&mut st, "QQQ");
press_ctrl(&mut st, 'h');
assert!(
text_of(&st).starts_with("QQalpha bravo"),
"got {:?}",
text_of(&st)
);
assert_eq!(st.cursor().column, 2);
}
#[test]
fn the_bigger_erases_leave_the_register_alone_too() {
let mut st = state();
press(&mut st, KeyCode::Char('y'));
press(&mut st, KeyCode::Char('w'));
let yanked = st.register().map(str::to_owned);
assert!(yanked.is_some(), "precondition: `yw` filled the register");
insert(&mut st);
press(&mut st, KeyCode::End);
press_ctrl(&mut st, 'w');
press_ctrl(&mut st, 'u');
assert!(
text_of(&st).starts_with("\ncharlie"),
"precondition: both erases ran; got {:?}",
text_of(&st)
);
assert_eq!(st.register().map(str::to_owned), yanked);
}
#[test]
fn every_insert_editing_key_resolves_to_something() {
use escriba_core::Action;
use escriba_keymap::{Key, Keymap};
let km = Keymap::default_vim();
for key in [
Key::Backspace,
Key::Delete,
Key::Left,
Key::Right,
Key::Up,
Key::Down,
Key::Home,
Key::End,
Key::Esc,
Key::Ctrl('w'),
Key::Ctrl('u'),
Key::Ctrl('h'),
] {
let b = km.lookup(Mode::Insert, &key);
assert!(b.is_some(), "Insert-mode {key:?} is unbound");
assert_ne!(
b.map(|b| b.action.clone()),
Some(Action::Pending),
"Insert-mode {key:?} resolves to a no-op"
);
}
}