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