use super::{
CharSearch, CharSearchDirection, ColumnMotion, Counted, ModeSwitch, Motion, NormalCommand,
NormalGrammar, NormalGrammarOutput, SearchOutcome, VimCursor, VimMode, VimSearchState,
VimSelectionState, VimStatusLine, VisualMode, motion,
};
#[derive(Clone, Debug, Default)]
pub struct NormalState {
grammar: NormalGrammar,
last_char_search: Option<CharSearch>,
}
impl NormalState {
pub const fn reset_grammar(&mut self) {
self.grammar.reset();
}
pub fn feed(
&mut self,
token: super::KeyToken,
context: NormalCommandContext<'_>,
) -> NormalGrammarOutput {
match self.feed_command(token) {
NormalGrammarOutput::Command(command) => {
self.apply_command(command, context);
NormalGrammarOutput::Command(command)
}
output @ (NormalGrammarOutput::Pending | NormalGrammarOutput::Unmatched) => output,
}
}
pub fn feed_command(&mut self, token: super::KeyToken) -> NormalGrammarOutput {
self.grammar.feed(token)
}
pub fn apply_command(&mut self, command: NormalCommand, context: NormalCommandContext<'_>) {
let NormalCommandContext {
text,
cursor,
mode,
selection_state,
search_state,
command_state,
status_line,
} = context;
match command {
NormalCommand::Motion(motion) => {
self.apply_counted_motion(text, cursor, motion);
}
NormalCommand::ModeSwitch(ModeSwitch::VisualCharacterwise) => {
toggle_visual_mode(
text,
cursor,
mode,
selection_state,
VisualMode::Characterwise,
);
}
NormalCommand::ModeSwitch(ModeSwitch::VisualLinewise) => {
toggle_visual_mode(text, cursor, mode, selection_state, VisualMode::Linewise);
}
NormalCommand::ExCommandStart => {
status_line.clear();
command_state.start();
}
NormalCommand::SearchStart(direction) => {
status_line.clear();
search_state.start(direction);
}
NormalCommand::SearchRepeat(direction) => {
let outcome = search_state.repeat_relative(text, cursor.byte_index(), direction);
apply_search_outcome(text, cursor, status_line, outcome);
}
NormalCommand::ViewportPosition(_) | NormalCommand::Operator { .. } => {}
}
}
pub fn apply_counted_motion(
&mut self,
text: &str,
cursor: &mut VimCursor,
counted: Counted<Motion>,
) {
match counted.item {
Motion::CharSearch(search) => {
self.last_char_search = Some(search);
for _step in 0..counted.count.get() {
cursor.apply_motion(text, Motion::CharSearch(search));
}
}
Motion::RepeatCharSearch => {
if let Some(search) = self.last_char_search {
for _step in 0..counted.count.get() {
cursor.apply_motion(text, Motion::CharSearch(search));
}
}
}
Motion::RepeatCharSearchReversed => {
if let Some(search) = self.last_char_search.map(reverse_char_search) {
for _step in 0..counted.count.get() {
cursor.apply_motion(text, Motion::CharSearch(search));
}
}
}
Motion::LineAddress(_) => cursor.apply_motion(text, counted.item),
Motion::Column(ColumnMotion::ScreenColumn) => {
cursor.set_byte_index(
text,
motion::apply_screen_column_motion(
text,
cursor.byte_index(),
counted.count.get(),
),
);
}
motion => {
for _step in 0..counted.count.get() {
cursor.apply_motion(text, motion);
}
}
}
}
}
pub struct NormalCommandContext<'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 super::VimCommandState,
pub status_line: &'state mut VimStatusLine,
}
fn toggle_visual_mode(
text: &str,
cursor: &VimCursor,
mode: &mut VimMode,
selection_state: &mut VimSelectionState,
visual_mode: VisualMode,
) {
if *mode == VimMode::Visual(visual_mode) {
*mode = VimMode::Normal;
selection_state.clear();
} else {
*mode = VimMode::Visual(visual_mode);
selection_state.start(text, cursor.byte_index());
}
}
const fn reverse_char_search(search: CharSearch) -> CharSearch {
let direction = match search.direction {
CharSearchDirection::Backward => CharSearchDirection::Forward,
CharSearchDirection::Forward => CharSearchDirection::Backward,
};
CharSearch {
direction,
..search
}
}
pub fn apply_search_outcome(
text: &str,
cursor: &mut VimCursor,
status_line: &mut VimStatusLine,
outcome: SearchOutcome,
) {
match outcome {
SearchOutcome::Match { byte_index } => {
cursor.set_byte_index(text, byte_index);
status_line.clear();
}
SearchOutcome::Error(error) => status_line.set_error(error),
}
}