use super::{
Counted, Motion, NormalCommand, NormalCommandContext, NormalState, PageDirection,
SearchDirection, ViewportPosition, VimCommandState, VimCursor, VimMode, VimSearchState,
VimSelectionState, VimStatusLine, motion,
};
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum VimAction {
NormalCommand(NormalCommand),
RepeatSearch(SearchDirection),
ViewportPage(PageDirection),
ViewportPosition(ViewportPosition),
ExCommand(String),
NoOp,
}
impl From<NormalCommand> for VimAction {
fn from(command: NormalCommand) -> Self {
Self::NormalCommand(command)
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct ActionDispatcher;
impl ActionDispatcher {
pub fn dispatch(action: &VimAction, context: ActionContext<'_>) {
let ActionContext {
text,
cursor,
mode,
selection_state,
search_state,
command_state,
status_line,
normal_state,
visible_line_count,
} = context;
match action {
VimAction::NormalCommand(NormalCommand::Motion(counted)) => {
apply_motion(text, cursor, normal_state, *counted, visible_line_count);
}
VimAction::NormalCommand(NormalCommand::ViewportPosition(_)) => {
status_line.clear();
}
VimAction::NormalCommand(command) => normal_state.apply_command(
*command,
NormalCommandContext {
text,
cursor,
mode,
selection_state,
search_state,
command_state,
status_line,
},
),
VimAction::RepeatSearch(direction) => {
let outcome = search_state.repeat(text, cursor.byte_index(), *direction);
super::apply_search_outcome(text, cursor, status_line, outcome);
}
VimAction::ViewportPage(direction) => {
let next = motion::apply_page_motion(
text,
cursor.byte_index(),
*direction,
visible_line_count,
);
cursor.set_byte_index(text, next);
}
VimAction::ViewportPosition(_position) => {
status_line.clear();
}
VimAction::ExCommand(command) => {
status_line.clear();
command_state.start_with(command);
}
VimAction::NoOp => {}
}
}
}
pub struct ActionContext<'state> {
pub text: &'state str,
pub cursor: &'state mut VimCursor,
pub mode: &'state mut VimMode,
pub selection_state: &'state mut VimSelectionState,
pub search_state: &'state mut VimSearchState,
pub command_state: &'state mut VimCommandState,
pub status_line: &'state mut VimStatusLine,
pub normal_state: &'state mut NormalState,
pub visible_line_count: usize,
}
fn apply_motion(
text: &str,
cursor: &mut VimCursor,
normal_state: &mut NormalState,
counted: Counted<Motion>,
visible_line_count: usize,
) {
match counted.item {
Motion::Page(direction) => {
for _step in 0..counted.count.get() {
let next = motion::apply_page_motion(
text,
cursor.byte_index(),
direction,
visible_line_count,
);
cursor.set_byte_index(text, next);
}
}
Motion::LineAddress(_)
| Motion::CharSearch(_)
| Motion::RepeatCharSearch
| Motion::RepeatCharSearchReversed
| Motion::Left
| Motion::Down
| Motion::Up
| Motion::Right
| Motion::WordForward(_)
| Motion::WordBackward(_)
| Motion::WordEnd(_)
| Motion::Column(_)
| Motion::Paragraph(_) => normal_state.apply_counted_motion(text, cursor, counted),
}
}
#[cfg(test)]
mod tests {
use super::{ActionContext, ActionDispatcher, VimAction};
use crate::vim::{
PageDirection, SearchDirection, SearchOutcome, VimCommandState, VimCursor, VimMode,
VimSearchState, VimSelectionState, VimStatusLine,
};
#[test]
fn viewport_page_action_uses_visible_line_count() {
let text = "00\n01\n02\n03\n04";
let mut cursor = VimCursor::new();
let mut mode = VimMode::Normal;
let mut selection_state = VimSelectionState::default();
let mut search_state = VimSearchState::default();
let mut command_state = VimCommandState::default();
let mut status_line = VimStatusLine::default();
let mut normal_state = crate::vim::NormalState::default();
ActionDispatcher::dispatch(
&VimAction::ViewportPage(PageDirection::Forward),
ActionContext {
text,
cursor: &mut cursor,
mode: &mut mode,
selection_state: &mut selection_state,
search_state: &mut search_state,
command_state: &mut command_state,
status_line: &mut status_line,
normal_state: &mut normal_state,
visible_line_count: 3,
},
);
assert_eq!(cursor.byte_index(), "00\n01\n02\n".len());
}
#[test]
fn repeat_search_action_uses_search_state() {
let text = "one two one two";
let mut cursor = VimCursor::new();
let mut mode = VimMode::Normal;
let mut selection_state = VimSelectionState::default();
let mut search_state = VimSearchState::default();
let mut command_state = VimCommandState::default();
let mut status_line = VimStatusLine::default();
let mut normal_state = crate::vim::NormalState::default();
search_state.start(SearchDirection::Forward);
search_state.push_text("two");
assert_eq!(
search_state.submit(text, 0),
SearchOutcome::Match {
byte_index: "one ".len()
}
);
cursor.set_byte_index(text, "one ".len());
ActionDispatcher::dispatch(
&VimAction::RepeatSearch(SearchDirection::Forward),
ActionContext {
text,
cursor: &mut cursor,
mode: &mut mode,
selection_state: &mut selection_state,
search_state: &mut search_state,
command_state: &mut command_state,
status_line: &mut status_line,
normal_state: &mut normal_state,
visible_line_count: 20,
},
);
assert_eq!(cursor.byte_index(), "one two one ".len());
}
}