use escriba_core::{Action, Operator};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum OpState {
#[default]
Resting,
Awaiting {
op: Operator,
count: u32,
},
AwaitingSearch {
op: Operator,
count: u32,
},
}
pub struct OperatorPending;
impl zenmai::Machine for OperatorPending {
type State = OpState;
type Event = (Action, u32);
type Effect = (Action, u32);
fn step(state: &OpState, event: (Action, u32)) -> (OpState, Vec<(Action, u32)>) {
let (action, count) = event;
match (state, action) {
(s, Action::Pending) => (*s, vec![]),
(OpState::Resting, Action::Operator(op)) => (OpState::Awaiting { op, count }, vec![]),
(OpState::Resting, other) => (OpState::Resting, vec![(other, count)]),
(
OpState::Awaiting {
op,
count: op_count,
},
Action::Move(motion),
) => (
OpState::Resting,
vec![(
Action::ApplyOperator { op: *op, motion },
op_count.saturating_mul(count).max(1),
)],
),
(
OpState::Awaiting {
op,
count: op_count,
},
Action::TextObject(object),
) => (
OpState::Resting,
vec![(
Action::ApplyOperatorObject { op: *op, object },
op_count.saturating_mul(count).max(1),
)],
),
(
OpState::Awaiting {
op,
count: op_count,
},
Action::SearchOpen(dir),
) => (
OpState::AwaitingSearch {
op: *op,
count: *op_count,
},
vec![(Action::SearchOpen(dir), 1)],
),
(OpState::AwaitingSearch { .. }, a) if a.edits_prompt() => (*state, vec![(a, 1)]),
(
OpState::AwaitingSearch {
op,
count: op_count,
},
Action::SubmitCommand,
) => (
OpState::Resting,
vec![(Action::SearchSubmitOperated { op: *op }, *op_count)],
),
(OpState::AwaitingSearch { .. }, Action::ChangeMode(m)) => {
(OpState::Resting, vec![(Action::ChangeMode(m), 1)])
}
(OpState::AwaitingSearch { .. }, other) => (OpState::Resting, vec![(other, count)]),
(OpState::Awaiting { .. }, _) => (OpState::Resting, vec![]),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use escriba_core::{Mode, Motion};
use zenmai::Machine;
#[test]
fn operator_then_motion_composes_apply_operator() {
let (s, fx) =
OperatorPending::step(&OpState::Resting, (Action::Operator(Operator::Delete), 1));
assert_eq!(
s,
OpState::Awaiting {
op: Operator::Delete,
count: 1
}
);
assert!(fx.is_empty(), "the operator key runs nothing, it waits");
let (s, fx) = OperatorPending::step(&s, (Action::Move(Motion::WordStartNext), 1));
assert_eq!(s, OpState::Resting);
assert_eq!(
fx,
vec![(
Action::ApplyOperator {
op: Operator::Delete,
motion: Motion::WordStartNext
},
1
)]
);
}
#[test]
fn operator_count_multiplies_the_motion() {
let (s, _) =
OperatorPending::step(&OpState::Resting, (Action::Operator(Operator::Delete), 3));
assert_eq!(
s,
OpState::Awaiting {
op: Operator::Delete,
count: 3
}
);
let (_, fx) = OperatorPending::step(&s, (Action::Move(Motion::WordStartNext), 1));
assert_eq!(
fx,
vec![(
Action::ApplyOperator {
op: Operator::Delete,
motion: Motion::WordStartNext
},
3
)]
);
}
#[test]
fn operator_and_motion_counts_multiply() {
let (s, _) =
OperatorPending::step(&OpState::Resting, (Action::Operator(Operator::Delete), 3));
let (_, fx) = OperatorPending::step(&s, (Action::Move(Motion::WordStartNext), 2));
assert_eq!(fx[0].1, 6, "operator count × motion count");
}
#[test]
fn bare_motion_passes_through_carrying_its_count() {
let (s, fx) = OperatorPending::step(&OpState::Resting, (Action::Move(Motion::Down), 5));
assert_eq!(s, OpState::Resting);
assert_eq!(fx, vec![(Action::Move(Motion::Down), 5)]);
}
#[test]
fn esc_cancels_a_pending_operator_and_drops_the_key() {
let (s, fx) = OperatorPending::step(
&OpState::Awaiting {
op: Operator::Change,
count: 1,
},
(Action::ChangeMode(Mode::Normal), 1),
);
assert_eq!(s, OpState::Resting, "Esc cancels the operator");
assert!(fx.is_empty(), "the cancel key is dropped");
}
#[test]
fn pending_never_disturbs_operator_state() {
let (s, fx) = OperatorPending::step(
&OpState::Awaiting {
op: Operator::Yank,
count: 1,
},
(Action::Pending, 1),
);
assert_eq!(
s,
OpState::Awaiting {
op: Operator::Yank,
count: 1
}
);
assert!(fx.is_empty());
}
#[test]
fn inert_on_a_doubled_operator_dd_deferred() {
let (s, fx) = OperatorPending::step(
&OpState::Awaiting {
op: Operator::Delete,
count: 1,
},
(Action::Operator(Operator::Delete), 1),
);
assert_eq!(s, OpState::Resting);
assert!(fx.is_empty());
}
}