Skip to main content

hjkl_vim/
lib.rs

1pub mod cmd;
2pub mod count;
3pub mod insert;
4pub mod motion;
5pub mod normal;
6pub mod operator;
7pub mod pending;
8pub mod search_prompt;
9
10pub use cmd::EngineCmd;
11pub use count::CountAccumulator;
12// MotionKind moved to hjkl-engine (Phase 6.6 cycle-break); re-exported here for back-compat.
13pub use hjkl_engine::MotionKind;
14pub use operator::OperatorKind;
15pub use pending::{Key, Outcome, PendingState, step};
16
17/// Mode discriminator for the hjkl editor stack.
18///
19/// Used as the mode parameter in `hjkl-keymap`'s generic `Keymap<A, M: Mode>`.
20/// Satisfies the `hjkl_keymap::Mode` trait via its blanket impl for any
21/// `Copy + Eq + Hash + Debug` type.
22#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
23pub enum Mode {
24    Normal,
25    Insert,
26    Visual,
27    VisualLine,
28    VisualBlock,
29    OpPending,
30    CommandLine,
31}
32
33/// Drive the vim FSM with a crossterm [`hjkl_engine::KeyEvent`]. Decodes the
34/// event to [`hjkl_engine::Input`] and dispatches through [`dispatch_input`].
35/// Emits the cursor-shape change after the FSM returns.
36///
37/// Returns `true` if the engine consumed the keystroke.
38#[cfg(feature = "crossterm")]
39pub fn handle_key<H: hjkl_engine::Host>(
40    editor: &mut hjkl_engine::Editor<hjkl_buffer::Buffer, H>,
41    key: hjkl_engine::KeyEvent,
42) -> bool {
43    let input = hjkl_engine::crossterm_to_input(key);
44    if input.key == hjkl_engine::Key::Null {
45        return false;
46    }
47    let consumed = dispatch_input(editor, input);
48    editor.emit_cursor_shape_if_changed();
49    consumed
50}
51
52/// Drive the vim FSM with a [`hjkl_engine::PlannedInput`]. Translates the
53/// planned input to engine [`hjkl_engine::Input`], dispatches through
54/// [`dispatch_input`], and emits cursor-shape changes.
55///
56/// Returns `true` if the engine consumed the keystroke. Returns `false` for
57/// variants the legacy FSM does not dispatch (`Mouse`, `Paste`, `FocusGained`,
58/// `FocusLost`, `Resize`) and for special-key variants that map to `Key::Null`.
59pub fn feed_input<H: hjkl_engine::Host>(
60    editor: &mut hjkl_engine::Editor<hjkl_buffer::Buffer, H>,
61    input: hjkl_engine::PlannedInput,
62) -> bool {
63    let Some(event) = hjkl_engine::decode_planned_input(input) else {
64        return false;
65    };
66    let consumed = dispatch_input(editor, event);
67    editor.emit_cursor_shape_if_changed();
68    consumed
69}
70
71/// Drive the vim FSM with one [`hjkl_engine::Input`].
72///
73/// This is the Phase 6.6 entry-point that decouples callers from the engine's
74/// internal FSM. Returns `true` if the engine consumed the keystroke.
75///
76/// # Migration guide
77///
78/// Replace `editor.step_input(input)` with `hjkl_vim::dispatch_input(&mut editor, input)`.
79/// The `Editor::step_input` method is deprecated; remove it in a later release.
80///
81/// # Phase 6.6c / 6.6d / 6.6e
82///
83/// Search-prompt mode (6.6c) is intercepted here before `begin_step` because
84/// it is a true short-circuit (no prelude/epilogue needed).
85///
86/// Insert mode (6.6d) is hosted in `hjkl-vim::insert::step_insert`.
87///
88/// Normal / Visual / VisualLine / VisualBlock / operator-pending modes (6.6e)
89/// are hosted in `hjkl-vim::normal::step_normal`. Both are wrapped with
90/// `begin_step` / `end_step` so macro recording, viewport scrolling, and
91/// `current_mode` sync all fire correctly.
92///
93/// The deprecated `Editor::step_input_raw` shim path is retained for
94/// back-compat until Phase 6.6h.
95pub fn dispatch_input<H: hjkl_engine::Host>(
96    editor: &mut hjkl_engine::Editor<hjkl_buffer::Buffer, H>,
97    input: hjkl_engine::Input,
98) -> bool {
99    // Search-prompt intercept: short-circuits before begin_step, matching
100    // vim::step's own search-prompt handling (which also skips begin_step).
101    if editor.search_prompt_state().is_some() {
102        return search_prompt::step_search_prompt(editor, input);
103    }
104    // Run the prelude (timestamps, chord-timeout, macro-stop, snapshots).
105    let bk = match editor.begin_step(input) {
106        Ok(bk) => bk,
107        Err(consumed) => return consumed,
108    };
109    // Per-mode FSM dispatch — hjkl-vim hosts all modes.
110    let consumed = match editor.vim_mode() {
111        hjkl_engine::VimMode::Insert => insert::step_insert(editor, input),
112        _ => normal::step_normal(editor, input),
113    };
114    // Run the epilogue (marks, one-shot-normal, sync, recorder, mode sync).
115    editor.end_step(input, bk, consumed)
116}