use super::{
effects::{VimEffect, VimEffectBatch},
insert::enter_insert_mode,
keyboard::{VimInputState, normalized_editor_tokens},
status::push_status_effect_from_line,
};
use crate::{
buffer::BufferEdit,
ecs::{
components::buffer::{BufferEntity, ViewEntity},
events::{
edit::{BufferEditRequested, EditHistoryRequested},
input::{EditorInputEvent, VimInputRejected, VimInputRejectionReason},
intent::ViewportIntent,
status::{StatusMessage, StatusMessageRequested},
},
},
text_stream::TextByteStream,
vim::{
ActionContext, ActionDispatcher, Count, InsertEntry, KeyToken, LeaderState, NormalCommand,
NormalGrammarOutput, NormalState, OperationEffect, OperationOutcome, Operator,
PastePlacement, PastePlanError, RegisterBank, RegisterName, VimAction, VimCommandState,
VimConfig, VimCursor, VimMode, VimOperation, VimSearchState, VimSelectionState,
VimStatusLine, VisualCommandContext, VisualGrammarOutput, VisualState, plan_paste,
repeat::{RepeatState, RepeatableOperation},
},
};
use bevy::prelude::MessageWriter;
const MAX_PASTE_TEXT_BYTES_PER_REQUEST: usize = 1024 * 1024;
pub(super) struct NormalInputContext<'state, 'writer> {
pub(super) stream: &'state TextByteStream,
pub(super) text: &'state str,
pub(super) cursor: &'state mut VimCursor,
pub(super) mode: &'state mut VimMode,
pub(super) selection_state: &'state mut VimSelectionState,
pub(super) search_state: &'state mut VimSearchState,
pub(super) command_state: &'state mut VimCommandState,
pub(super) status_line: &'state mut VimStatusLine,
pub(super) effects: &'state mut VimEffectBatch,
pub(super) target: ViewEntity,
pub(super) buffer_target: BufferEntity,
pub(super) registers: &'state mut RegisterBank,
pub(super) repeat_state: &'state mut RepeatState,
pub(super) leader_state: &'state mut LeaderState,
pub(super) normal_state: &'state mut NormalState,
pub(super) visual_state: &'state mut VisualState,
pub(super) input_state: &'state mut VimInputState,
pub(super) rejected: &'state mut MessageWriter<'writer, VimInputRejected>,
pub(super) vim_config: &'state VimConfig,
pub(super) visible_line_count: usize,
pub(super) input_events: &'state [EditorInputEvent],
pub(super) delta_millis: u64,
}
pub(super) fn handle_normal_input(mut context: NormalInputContext<'_, '_>) {
context
.leader_state
.tick(context.delta_millis, context.vim_config.leader.delay_ms);
let tokens = normalized_editor_tokens(context.input_events).collect::<Vec<_>>();
for token in tokens {
if handle_normal_token(&mut context, token).is_done() {
return;
}
}
if context.leader_state.is_pending() || context.leader_state.is_menu_visible() {
context.input_state.repeat.cancel();
return;
}
context.input_state.repeat.cancel();
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum NormalTokenOutcome {
Continue,
Done,
}
impl NormalTokenOutcome {
const fn is_done(self) -> bool {
matches!(self, Self::Done)
}
}
fn handle_normal_token(
context: &mut NormalInputContext<'_, '_>,
token: KeyToken,
) -> NormalTokenOutcome {
if *context.mode == VimMode::Normal {
if token == context.vim_config.leader.key && !context.leader_state.is_pending() {
context.normal_state.reset_grammar();
context.input_state.repeat.cancel();
context.leader_state.start();
return NormalTokenOutcome::Done;
}
if context.leader_state.is_pending() || context.leader_state.is_menu_visible() {
dispatch_leader_token(context, token);
return NormalTokenOutcome::Done;
}
}
if let VimMode::Visual(active_visual_mode) = *context.mode {
match context.visual_state.feed(
token,
VisualCommandContext {
text: context.text,
cursor: context.cursor,
mode: context.mode,
active_visual_mode,
selection_state: context.selection_state,
normal_state: context.normal_state,
},
) {
VisualGrammarOutput::Command(command) => {
context.leader_state.cancel();
context.input_state.repeat.cancel();
if let Some(operation) =
command.operator_operation(context.selection_state, active_visual_mode)
{
let result = dispatch_operator_operation(
operation,
OperatorDispatchContext {
stream: context.stream,
cursor: context.cursor,
mode: context.mode,
buffer_target: context.buffer_target,
registers: context.registers,
effects: context.effects,
repeat_state: None,
},
);
if result.is_applied() {
if !result.entered_insert() {
*context.mode = VimMode::Normal;
}
context.selection_state.clear();
}
}
return NormalTokenOutcome::Done;
}
VisualGrammarOutput::Unmatched => {
context.leader_state.cancel();
context.input_state.repeat.cancel();
return NormalTokenOutcome::Done;
}
VisualGrammarOutput::Pending => {
context.leader_state.cancel();
return NormalTokenOutcome::Done;
}
}
}
match context.normal_state.feed_command(token) {
NormalGrammarOutput::Command(command) => {
dispatch_completed_normal_command(
command,
CompletedNormalCommandContext {
stream: context.stream,
text: context.text,
cursor: context.cursor,
mode: context.mode,
selection_state: context.selection_state,
search_state: context.search_state,
command_state: context.command_state,
status_line: context.status_line,
normal_state: context.normal_state,
visible_line_count: context.visible_line_count,
options: &context.vim_config.options,
effects: context.effects,
target: context.target,
buffer_target: context.buffer_target,
registers: context.registers,
repeat_state: context.repeat_state,
rejected: context.rejected,
},
);
context.leader_state.cancel();
context.input_state.repeat.cancel();
NormalTokenOutcome::Done
}
NormalGrammarOutput::Pending => {
context.leader_state.cancel();
NormalTokenOutcome::Done
}
NormalGrammarOutput::Unmatched => {
context.leader_state.cancel();
NormalTokenOutcome::Continue
}
}
}
fn dispatch_leader_token(context: &mut NormalInputContext<'_, '_>, token: KeyToken) {
if let Some(binding) = context.vim_config.leader.binding_for(&[token]) {
ActionDispatcher::dispatch(
&binding.action,
ActionContext {
text: context.text,
cursor: context.cursor,
mode: context.mode,
selection_state: context.selection_state,
search_state: context.search_state,
command_state: context.command_state,
status_line: context.status_line,
normal_state: context.normal_state,
visible_line_count: context.visible_line_count,
options: &context.vim_config.options,
},
);
push_status_effect_from_line(context.target, context.status_line, context.effects);
}
context.leader_state.cancel();
context.input_state.repeat.cancel();
}
struct CompletedNormalCommandContext<'state, 'writer> {
stream: &'state TextByteStream,
text: &'state str,
cursor: &'state mut VimCursor,
mode: &'state mut VimMode,
selection_state: &'state mut VimSelectionState,
search_state: &'state mut VimSearchState,
command_state: &'state mut VimCommandState,
status_line: &'state mut VimStatusLine,
normal_state: &'state mut NormalState,
visible_line_count: usize,
options: &'state crate::vim::config::VimOptions,
effects: &'state mut VimEffectBatch,
target: ViewEntity,
buffer_target: BufferEntity,
registers: &'state mut RegisterBank,
repeat_state: &'state mut RepeatState,
rejected: &'state mut MessageWriter<'writer, VimInputRejected>,
}
fn dispatch_completed_normal_command(
command: NormalCommand,
context: CompletedNormalCommandContext<'_, '_>,
) {
let CompletedNormalCommandContext {
stream,
text,
cursor,
mode,
selection_state,
search_state,
command_state,
status_line,
normal_state,
visible_line_count,
options,
effects,
target,
buffer_target,
registers,
repeat_state,
rejected,
} = context;
if dispatch_viewport_position(command, target, effects) {
return;
}
if dispatch_normal_insert_command(
command,
NormalInsertDispatchContext {
text,
cursor,
mode,
selection_state,
target,
buffer_target,
effects,
},
) {
return;
}
dispatch_non_insert_normal_command(
command,
NonInsertNormalDispatchContext {
stream,
text,
cursor,
mode,
selection_state,
search_state,
command_state,
status_line,
normal_state,
visible_line_count,
options,
effects,
target,
buffer_target,
registers,
repeat_state,
rejected,
},
);
}
struct NonInsertNormalDispatchContext<'state, 'writer> {
stream: &'state TextByteStream,
text: &'state str,
cursor: &'state mut VimCursor,
mode: &'state mut VimMode,
selection_state: &'state mut VimSelectionState,
search_state: &'state mut VimSearchState,
command_state: &'state mut VimCommandState,
status_line: &'state mut VimStatusLine,
normal_state: &'state mut NormalState,
visible_line_count: usize,
options: &'state crate::vim::config::VimOptions,
effects: &'state mut VimEffectBatch,
target: ViewEntity,
buffer_target: BufferEntity,
registers: &'state mut RegisterBank,
repeat_state: &'state mut RepeatState,
rejected: &'state mut MessageWriter<'writer, VimInputRejected>,
}
fn dispatch_non_insert_normal_command(
command: NormalCommand,
context: NonInsertNormalDispatchContext<'_, '_>,
) {
let NonInsertNormalDispatchContext {
stream,
text,
cursor,
mode,
selection_state,
search_state,
command_state,
status_line,
normal_state,
visible_line_count,
options,
effects,
target,
buffer_target,
registers,
repeat_state,
rejected,
} = context;
if dispatch_normal_operator_command(
command,
NormalOperatorDispatchContext {
stream,
cursor,
mode,
buffer_target,
registers,
effects,
repeat_state,
},
) {
return;
}
if dispatch_normal_paste_command(
command,
NormalPasteDispatchContext {
text,
cursor,
target,
buffer_target,
registers,
effects,
repeat_state,
rejected,
},
) {
return;
}
if dispatch_repeat_last_change(
command,
repeat_state,
RepeatDispatchContext {
stream,
text,
cursor,
target,
buffer_target,
mode,
registers,
effects,
rejected,
},
) {
return;
}
if dispatch_edit_history_command(command, buffer_target, effects) {
return;
}
dispatch_pure_normal_command(
command,
PureNormalDispatchContext {
text,
cursor,
mode,
selection_state,
search_state,
command_state,
status_line,
normal_state,
visible_line_count,
options,
},
);
push_status_effect_from_line(target, status_line, effects);
}
struct NormalInsertDispatchContext<'state> {
text: &'state str,
cursor: &'state VimCursor,
mode: &'state mut VimMode,
selection_state: &'state mut VimSelectionState,
target: ViewEntity,
buffer_target: BufferEntity,
effects: &'state mut VimEffectBatch,
}
fn dispatch_normal_insert_command(
command: NormalCommand,
context: NormalInsertDispatchContext<'_>,
) -> bool {
let NormalCommand::Insert(entry) = command else {
return false;
};
let NormalInsertDispatchContext {
text,
cursor,
mode,
selection_state,
target,
buffer_target,
effects,
} = context;
dispatch_insert_entry(
entry,
InsertEntryDispatchContext {
text,
cursor,
mode,
selection_state,
target,
buffer_target,
effects,
},
);
true
}
struct PureNormalDispatchContext<'state> {
text: &'state str,
cursor: &'state mut VimCursor,
mode: &'state mut VimMode,
selection_state: &'state mut VimSelectionState,
search_state: &'state mut VimSearchState,
command_state: &'state mut VimCommandState,
status_line: &'state mut VimStatusLine,
normal_state: &'state mut NormalState,
visible_line_count: usize,
options: &'state crate::vim::config::VimOptions,
}
fn dispatch_pure_normal_command(command: NormalCommand, context: PureNormalDispatchContext<'_>) {
let PureNormalDispatchContext {
text,
cursor,
mode,
selection_state,
search_state,
command_state,
status_line,
normal_state,
visible_line_count,
options,
} = context;
ActionDispatcher::dispatch(
&VimAction::NormalCommand(command),
ActionContext {
text,
cursor,
mode,
selection_state,
search_state,
command_state,
status_line,
normal_state,
visible_line_count,
options,
},
);
}
fn dispatch_viewport_position(
command: NormalCommand,
target: ViewEntity,
effects: &mut VimEffectBatch,
) -> bool {
let NormalCommand::ViewportPosition(position) = command else {
return false;
};
effects.push(VimEffect::Viewport(ViewportIntent {
target,
placement: position,
}));
effects.push(VimEffect::Status(StatusMessageRequested {
target,
message: StatusMessage::Clear,
}));
true
}
struct NormalOperatorDispatchContext<'state> {
stream: &'state TextByteStream,
cursor: &'state mut VimCursor,
mode: &'state mut VimMode,
buffer_target: BufferEntity,
registers: &'state mut RegisterBank,
effects: &'state mut VimEffectBatch,
repeat_state: &'state mut RepeatState,
}
fn dispatch_normal_operator_command(
command: NormalCommand,
context: NormalOperatorDispatchContext<'_>,
) -> bool {
if !matches!(command, NormalCommand::Operator { .. }) {
return false;
}
let NormalOperatorDispatchContext {
stream,
cursor,
mode,
buffer_target,
registers,
effects,
repeat_state,
} = context;
let operation = VimOperation::from(command);
let _applied = dispatch_operator_operation(
operation.clone(),
OperatorDispatchContext {
stream,
cursor,
mode,
buffer_target,
registers,
effects,
repeat_state: RepeatableOperation::from_operation(operation)
.map(|operation| (repeat_state, operation)),
},
);
true
}
struct NormalPasteDispatchContext<'state, 'writer> {
text: &'state str,
cursor: &'state VimCursor,
target: ViewEntity,
buffer_target: BufferEntity,
registers: &'state RegisterBank,
effects: &'state mut VimEffectBatch,
repeat_state: &'state mut RepeatState,
rejected: &'state mut MessageWriter<'writer, VimInputRejected>,
}
fn dispatch_normal_paste_command(
command: NormalCommand,
context: NormalPasteDispatchContext<'_, '_>,
) -> bool {
let NormalCommand::Paste { count, placement } = command else {
return false;
};
let NormalPasteDispatchContext {
text,
cursor,
target,
buffer_target,
registers,
effects,
repeat_state,
rejected,
} = context;
if dispatch_paste_command(PasteDispatchContext {
text,
cursor,
target,
buffer_target,
registers,
count,
placement,
effects,
rejected,
}) && let Some(operation) =
RepeatableOperation::from_operation(VimOperation::Paste { count, placement })
{
repeat_state.record(operation);
}
true
}
fn dispatch_repeat_last_change(
command: NormalCommand,
repeat_state: &RepeatState,
context: RepeatDispatchContext<'_, '_>,
) -> bool {
if command != NormalCommand::RepeatLastChange {
return false;
}
if let Some(operation) = repeat_state.last_change() {
dispatch_repeat_operation(&operation, context);
}
true
}
fn dispatch_edit_history_command(
command: NormalCommand,
buffer_target: BufferEntity,
effects: &mut VimEffectBatch,
) -> bool {
let request = match command {
NormalCommand::Undo => EditHistoryRequested::undo(buffer_target),
NormalCommand::Redo => EditHistoryRequested::redo(buffer_target),
_ => return false,
};
effects.push(VimEffect::EditHistory(request));
true
}
struct PasteDispatchContext<'state, 'writer> {
text: &'state str,
cursor: &'state VimCursor,
target: ViewEntity,
buffer_target: BufferEntity,
registers: &'state RegisterBank,
count: Count,
placement: PastePlacement,
effects: &'state mut VimEffectBatch,
rejected: &'state mut MessageWriter<'writer, VimInputRejected>,
}
fn dispatch_paste_command(context: PasteDispatchContext<'_, '_>) -> bool {
let PasteDispatchContext {
text,
cursor,
target,
buffer_target,
registers,
count,
placement,
effects,
rejected,
} = context;
let Some(register) = registers.get(RegisterName::Unnamed) else {
return false;
};
let plan = match plan_paste(
text,
cursor.byte_index(),
register,
count,
placement,
MAX_PASTE_TEXT_BYTES_PER_REQUEST,
) {
Ok(plan) => plan,
Err(PastePlanError::EmptyRegister) => return false,
Err(PastePlanError::PayloadTooLarge {
requested_byte_len,
max_byte_len,
}) => {
let _sent = rejected.write(VimInputRejected {
target,
reason: VimInputRejectionReason::PastePayloadTooLarge {
target,
rejected_byte_len: requested_byte_len,
max_byte_len,
},
});
return false;
}
};
let insert_at = plan.byte_index();
let cursor_byte_index = plan.cursor_byte_index();
effects.push(VimEffect::BufferEdit(BufferEditRequested {
target: buffer_target,
edit: BufferEdit::insert(insert_at, plan.into_text()),
}));
if cursor.byte_index() != cursor_byte_index || cursor.desired_column().is_some() {
effects.push(VimEffect::CursorMove(
crate::ecs::events::cursor::CursorMoveRequested::cursor_cell(
target,
crate::ecs::components::cursor::ByteIndex(cursor_byte_index),
None,
),
));
}
true
}
struct RepeatDispatchContext<'state, 'writer> {
stream: &'state TextByteStream,
text: &'state str,
cursor: &'state mut VimCursor,
target: ViewEntity,
buffer_target: BufferEntity,
mode: &'state mut VimMode,
registers: &'state mut RegisterBank,
effects: &'state mut VimEffectBatch,
rejected: &'state mut MessageWriter<'writer, VimInputRejected>,
}
fn dispatch_repeat_operation(
operation: &RepeatableOperation,
context: RepeatDispatchContext<'_, '_>,
) {
let RepeatDispatchContext {
stream,
text,
cursor,
target,
buffer_target,
mode,
registers,
effects,
rejected,
} = context;
match operation.operation() {
VimOperation::ApplyOperator { .. } => {
let _result = dispatch_operator_operation(
operation.operation(),
OperatorDispatchContext {
stream,
cursor,
mode,
buffer_target,
registers,
effects,
repeat_state: None,
},
);
}
VimOperation::Paste { count, placement } => {
let _applied = dispatch_paste_command(PasteDispatchContext {
text,
cursor,
target,
buffer_target,
registers,
count,
placement,
effects,
rejected,
});
}
_ => {}
}
}
struct InsertEntryDispatchContext<'state> {
text: &'state str,
cursor: &'state VimCursor,
mode: &'state mut VimMode,
selection_state: &'state mut VimSelectionState,
target: ViewEntity,
buffer_target: BufferEntity,
effects: &'state mut VimEffectBatch,
}
fn dispatch_insert_entry(entry: InsertEntry, context: InsertEntryDispatchContext<'_>) {
let InsertEntryDispatchContext {
text,
cursor,
mode,
selection_state,
target,
buffer_target,
effects,
..
} = context;
selection_state.clear();
enter_insert_mode(
entry,
text,
cursor.byte_index(),
mode,
target,
buffer_target,
effects,
);
}
struct OperatorDispatchContext<'state> {
stream: &'state TextByteStream,
cursor: &'state mut VimCursor,
mode: &'state mut VimMode,
buffer_target: BufferEntity,
registers: &'state mut RegisterBank,
effects: &'state mut VimEffectBatch,
repeat_state: Option<(&'state mut RepeatState, RepeatableOperation)>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum OperatorDispatchResult {
NotApplied,
AppliedNormal,
AppliedInsert,
}
impl OperatorDispatchResult {
const fn is_applied(self) -> bool {
!matches!(self, Self::NotApplied)
}
const fn entered_insert(self) -> bool {
matches!(self, Self::AppliedInsert)
}
}
fn dispatch_operator_operation(
operation: VimOperation,
context: OperatorDispatchContext<'_>,
) -> OperatorDispatchResult {
let OperatorDispatchContext {
stream,
cursor,
mode,
buffer_target,
registers,
effects,
repeat_state,
} = context;
let Ok(outcome) = operation.resolve(stream, cursor.byte_index()) else {
return OperatorDispatchResult::NotApplied;
};
let result = emit_operator_outcome(
outcome,
OperatorOutcomeEmitContext {
stream,
cursor,
mode,
buffer_target,
registers,
effects,
},
);
if result.is_applied()
&& let Some((repeat_state, operation)) = repeat_state
{
repeat_state.record(operation);
}
result
}
struct OperatorOutcomeEmitContext<'state> {
stream: &'state TextByteStream,
cursor: &'state mut VimCursor,
mode: &'state mut VimMode,
buffer_target: BufferEntity,
registers: &'state mut RegisterBank,
effects: &'state mut VimEffectBatch,
}
fn emit_operator_outcome(
outcome: OperationOutcome,
context: OperatorOutcomeEmitContext<'_>,
) -> OperatorDispatchResult {
let OperatorOutcomeEmitContext {
stream,
cursor,
mode,
buffer_target,
registers,
effects,
} = context;
let text = stream.as_str();
let mut result = OperatorDispatchResult::NotApplied;
for effect in outcome.into_effects() {
let OperationEffect::ApplyOperator { operator, target } = effect else {
continue;
};
match operator {
Operator::Delete => {
if registers.write_delete(stream, target).is_err() {
continue;
}
cursor.set_byte_index(text, target.range().start());
cursor.set_desired_column(None);
effects.push(VimEffect::BufferEdit(BufferEditRequested {
target: buffer_target,
edit: BufferEdit::delete_validated(target.range().validated_text_range()),
}));
result = OperatorDispatchResult::AppliedNormal;
}
Operator::Yank => {
if registers.write_yank(stream, target).is_ok() {
result = OperatorDispatchResult::AppliedNormal;
}
}
Operator::Change => {
if registers.write_delete(stream, target).is_err() {
continue;
}
let caret = target.range().start();
cursor.set_byte_index(text, caret);
cursor.set_desired_column(None);
effects.push(VimEffect::BufferEdit(BufferEditRequested {
target: buffer_target,
edit: BufferEdit::delete_validated(target.range().validated_text_range()),
}));
*mode = VimMode::Insert(InsertEntry::AtCursor.insert_state());
result = OperatorDispatchResult::AppliedInsert;
}
}
}
result
}