Skip to main content

hjkl_vim/
insert.rs

1//! Phase 6.6d: insert-mode FSM body relocated from `hjkl-engine::vim`.
2//!
3//! Dispatched by [`crate::dispatch_input`] for insert mode.
4use crate::editor_ext::VimEditorExt;
5use hjkl_engine::{Host, Input, Key};
6
7/// Drive the insert-mode FSM for one keystroke.
8///
9/// Returns `true` (consumed) unconditionally — every key inside insert mode
10/// is swallowed regardless of whether it produced a visible effect.
11pub fn step_insert<H: Host>(
12    ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
13    input: Input,
14) -> bool {
15    // `Ctrl-R {reg}` paste — the previous keystroke armed the wait. Any
16    // non-char key cancels (matches vim, which beeps on selectors like
17    // Esc and re-emits the literal text otherwise).
18    if ed.insert_pending_register() {
19        ed.set_insert_pending_register(false);
20        if let Key::Char(c) = input.key
21            && !input.ctrl
22        {
23            ed.insert_paste_register(c);
24        }
25        return true;
26    }
27
28    if input.key == Key::Esc {
29        ed.leave_insert_to_normal();
30        return true;
31    }
32
33    // Ctrl-prefixed insert-mode shortcuts — thin dispatcher to public Editor methods.
34    if input.ctrl {
35        match input.key {
36            Key::Char('w') => {
37                ed.insert_ctrl_w();
38                return true;
39            }
40            Key::Char('u') => {
41                ed.insert_ctrl_u();
42                return true;
43            }
44            Key::Char('h') => {
45                ed.insert_ctrl_h();
46                return true;
47            }
48            Key::Char('o') => {
49                ed.insert_ctrl_o_arm();
50                return true;
51            }
52            Key::Char('r') => {
53                ed.insert_ctrl_r_arm();
54                return true;
55            }
56            Key::Char('t') => {
57                ed.insert_ctrl_t();
58                return true;
59            }
60            Key::Char('d') => {
61                ed.insert_ctrl_d();
62                return true;
63            }
64            Key::Char(']') => {
65                // `<C-]>` — expand abbreviation WITHOUT inserting any character.
66                ed.insert_ctrl_bracket();
67                return true;
68            }
69            // B1: `<C-a>` re-inserts the text typed during the last insert
70            // session, `<C-e>`/`<C-y>` copy the char below/above the cursor.
71            Key::Char('a') => {
72                ed.insert_ctrl_a();
73                return true;
74            }
75            Key::Char('e') => {
76                ed.insert_ctrl_e();
77                return true;
78            }
79            Key::Char('y') => {
80                ed.insert_ctrl_y();
81                return true;
82            }
83            // DELIBERATE vim divergence — any other ctrl-key combo has no
84            // dedicated insert-mode binding, and we consume it as a NO-OP.
85            //
86            // Real nvim instead inserts the raw control byte for unbound
87            // ctrl keys (verified against nvim 0.12: `i<C-b>x<Esc>` on
88            // "hello" produces "\x02xhello" — a literal ^B in the buffer).
89            // Reproducing that would put unprintable, un-typeable bytes in
90            // the document for keys that have no editing purpose in a TUI,
91            // so the no-op is intentional. Do NOT "fix" this to match nvim
92            // without deciding that trade-off deliberately; the choice is
93            // pinned by dispatch_input.rs::
94            // insert_unhandled_ctrl_key_is_noop_not_literal_letter.
95            //
96            // What this arm must never regress to: falling through to
97            // `handle_insert_key` below, which would type the ctrl LETTER
98            // itself as a literal character (the pre-fix bug: `<C-a>`
99            // inserted a plain "a").
100            Key::Char(_) => {
101                return true;
102            }
103            _ => {}
104        }
105    }
106
107    // Widen the session's visited row window *before* handling the key
108    // so navigation-only keystrokes (arrow keys) still extend the range.
109    let (row, _) = ed.cursor();
110    if let Some(session) = ed.insert_session_mut() {
111        session.row_min = session.row_min.min(row);
112        session.row_max = session.row_max.max(row);
113    }
114    let mutated = handle_insert_key(ed, input);
115    if mutated {
116        ed.mark_content_dirty();
117        let (row, _) = ed.cursor();
118        if let Some(session) = ed.insert_session_mut() {
119            session.row_min = session.row_min.min(row);
120            session.row_max = session.row_max.max(row);
121        }
122    }
123    true
124}
125
126/// Insert-mode key dispatcher — thin shim that routes each key to the
127/// corresponding public `Editor::*` method (Phase 6.6a). Returns `true`
128/// when the buffer mutated (editing keys), `false` for navigation-only keys.
129pub(crate) fn handle_insert_key<H: Host>(
130    ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
131    input: Input,
132) -> bool {
133    use hjkl_engine::InsertDir;
134    match input.key {
135        Key::Char(c) => {
136            ed.insert_char(c);
137            true
138        }
139        Key::Enter => {
140            ed.insert_newline();
141            true
142        }
143        Key::Tab => {
144            ed.insert_tab();
145            true
146        }
147        Key::Backspace => {
148            ed.insert_backspace();
149            true
150        }
151        Key::Delete => {
152            ed.insert_delete();
153            true
154        }
155        Key::Left => {
156            ed.insert_arrow(InsertDir::Left);
157            false
158        }
159        Key::Right => {
160            ed.insert_arrow(InsertDir::Right);
161            false
162        }
163        Key::Up => {
164            ed.insert_arrow(InsertDir::Up);
165            false
166        }
167        Key::Down => {
168            ed.insert_arrow(InsertDir::Down);
169            false
170        }
171        Key::Home => {
172            ed.insert_home();
173            false
174        }
175        Key::End => {
176            ed.insert_end();
177            false
178        }
179        Key::PageUp => {
180            let h = ed.viewport_height_value();
181            ed.insert_pageup(h);
182            false
183        }
184        Key::PageDown => {
185            let h = ed.viewport_height_value();
186            ed.insert_pagedown(h);
187            false
188        }
189        // F-keys, mouse scroll, copy/cut/paste virtual keys, Null —
190        // no insert-mode behaviour.
191        _ => false,
192    }
193}