lattice_host/action.rs
1//! Action enum -- the dispatch language of the editor.
2//!
3//! Phase 5.2: extracted from `lattice-ui-tui::app` to its own
4//! module in `lattice-host`. Renderer-agnostic by construction
5//! (every variant references either `lattice-grammar` types,
6//! `lattice-protocol` types, `lattice-host::chord::KeyChord`,
7//! or std types). Both `lattice-ui-tui` and the future
8//! `lattice-ui-gpui` produce `Action` values via their own
9//! keystroke dispatch and feed them into the same host
10//! `App::apply` path.
11//!
12//! `lattice-ui-tui::app` re-exports `Action`, `FindKind`,
13//! `EchoMessage`, `EchoLevel` so existing `crate::app::Action`
14//! call sites continue to resolve unchanged across the move.
15
16use lattice_grammar::ModalState;
17use lattice_grammar::PaneDirection;
18use lattice_grammar::Register;
19use lattice_grammar::ScrollPos;
20use lattice_grammar::SearchDirection;
21use lattice_grammar::ViewportPos;
22use lattice_grammar::VisualKind;
23use lattice_grammar::command::CommandInvocation;
24
25/// Single-line message rendered in the echo area below the mode
26/// line (DESIGN.md §5.9.10). Replaced by the next call to
27/// `App::set_message` (no timeout-based fade yet).
28#[derive(Debug, Clone, PartialEq, Eq)]
29pub struct EchoMessage {
30 pub text: String,
31 pub level: EchoLevel,
32}
33
34/// Renderer-side display level for echo messages. Mirrors
35/// `lattice_grammar::EchoLevel` (wire-typed) but kept separate so
36/// renderers can adopt their own display semantics around the
37/// shared wire levels.
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub enum EchoLevel {
40 Trace,
41 Debug,
42 Info,
43 Warn,
44 Error,
45}
46
47/// `f` / `F` / `t` / `T` direction-and-stop discriminant for the inline find
48/// family.
49///
50/// VM.3c: the definition moved down to `lattice-grammar` so `;` and `,` could
51/// become motions — a motion's evaluator has to name the kind, and the grammar
52/// cannot depend upward on the host. Re-exported under the old path so the
53/// keymap and dispatch call sites did not have to move with it.
54pub use lattice_grammar::FindKind;
55
56#[derive(Debug, Clone)]
57pub enum Action {
58 None,
59 Quit,
60 /// CG.1: foreground cancellation — flip the armed token AND snap
61 /// back to Normal. Reached by `action:cancel`, bound to `<C-g>` at
62 /// `KeymapLayer::Builtin` (emacs `keyboard-quit`).
63 /// See [`Editor::cancel_foreground`](crate::Editor::cancel_foreground)
64 /// and `docs/dev/architecture/cancellation.md`.
65 ///
66 /// Deliberately NOT in [`action_is_document_mutation`]: cancel is
67 /// an escape hatch, so it must keep working on a read-only buffer.
68 ///
69 /// [`action_is_document_mutation`]: crate::dispatch::action_is_document_mutation
70 Cancel,
71 /// Run a CommandInvocation through `lattice_grammar::execute()`.
72 Invoke(CommandInvocation),
73 /// Slice 8.i.4.a -- absorb the captured chord into
74 /// `App::partial_chord`, marking that we're partway through
75 /// a multi-key sequence the trie hasn't fully resolved yet.
76 /// Replaces the `Action::SetPending(Pending::After*)` flow
77 /// for prefixes whose only role was "wait for the next key"
78 /// (`g`, `z`, `<C-w>`, `m`, `'`, `` ` ``, `"`, `q`, `@`).
79 /// `App::apply` appends the chord and otherwise no-ops; the
80 /// next keystroke runs through `dispatch_normal` with
81 /// `partial_chord` as the prefix, hitting the trie's
82 /// resolved binding (`gd`, `zo`, `<C-w>v`, ...). Parameterised
83 /// Pending variants (`AfterOperator(_)`,
84 /// `AfterTextObject{_}`, `AfterFindChar{_}`, `AfterCtrlX`)
85 /// stay on the `SetPending` flow for now -- 8.i.4.b retires
86 /// those.
87 AbsorbPartialChord(crate::chord::KeyChord),
88 /// Insert a string at the cursor (used by Insert mode).
89 Insert(String),
90 /// Delete the byte before the cursor (Insert-mode backspace).
91 DeleteCharBackward,
92 /// Insert-mode line editing (readline/vim `<C-a>`, `<C-e>`, `<C-w>`,
93 /// `<C-u>`, `<C-k>`, `<C-t>`, `<C-d>`, …) — general across all buffers.
94 InsertLineEdit(lattice_grammar::InsertLineEdit),
95 /// Move into a different modal state (Insert, Normal, ...).
96 EnterMode(ModalState),
97 /// Vim's `a`: move cursor one byte right (clamped) and enter Insert.
98 EnterAppend,
99 /// Vim's `I`: move to first non-blank of line and enter Insert.
100 EnterInsertFirstNonBlank,
101 /// Vim's `A`: move to end of line and enter Insert.
102 EnterAppendEndOfLine,
103 /// Vim's `gj`: move down one display line (wrap segment).
104 DisplayLineDown,
105 /// Vim's `gk`: move up one display line (wrap segment).
106 DisplayLineUp,
107 /// Vim's `g0`: move to the first byte of the current display segment.
108 DisplayLineStart,
109 /// Vim's `g$`: move to the last byte of the current display segment.
110 DisplayLineEnd,
111 /// Vim's blockwise-visual `I`: move cursor to the leftmost
112 /// column of the block on the top line, enter Insert, and on
113 /// Esc replicate the typed text to every other line in the
114 /// block at the same column. Issued only from Visual(Blockwise).
115 EnterBlockVisualInsert,
116 /// Vim's blockwise-visual `A`: same as [`Self::EnterBlockVisualInsert`]
117 /// but the cursor lands one byte past the rightmost column of
118 /// the block on each line.
119 EnterBlockVisualAppend,
120 /// Vim's `o`: open a new line below the current line and enter Insert.
121 OpenLineBelow,
122 /// Vim's `O`: open a new line above the current line and enter Insert.
123 OpenLineAbove,
124 Undo,
125 Redo,
126 /// Append a digit (0-9) to the in-progress count prefix.
127 PushDigit(u8),
128 /// Enter Visual modal state (`v` Charwise, `V` Linewise) anchored at
129 /// the current cursor.
130 EnterVisual(VisualKind),
131 /// Exit Visual to Normal, collapsing the selection.
132 ExitVisual,
133 /// Vim's `gv` -- re-enter Visual with the same anchor / head / kind
134 /// as the most recently exited Visual selection.
135 ReselectLastVisual,
136 /// SN.3d Select-mode entry (`gh` / `gH` / `g<C-h>`) — anchor a
137 /// zero-width Select selection of the named kind at the cursor,
138 /// mirroring [`Self::EnterVisual`]. Typing then overtypes it.
139 EnterSelect(VisualKind),
140 /// SN.3d Select mode — a bare printable key replaces the whole
141 /// selection with this char and drops into Insert (one undo step).
142 /// The load-bearing new behaviour: see
143 /// `docs/dev/architecture/select-mode.md` §3. Emitted only by
144 /// `translate_select`'s printable fallthrough; the host handler
145 /// (`do_select_overtype`) lands it as a single `Edit::replace`.
146 SelectOvertype(char),
147 /// SN.3d Select-mode `<Esc>` — collapse the selection to a cursor
148 /// and drop to Normal (the Select analogue of [`Self::ExitVisual`]).
149 ExitSelect,
150 /// SN.3d `<C-g>` — toggle between `Visual(k)` and `Select(k)`,
151 /// preserving the selection geometry (reserved in both modes for
152 /// this toggle). One handler flips whichever of the two is active.
153 ToggleVisualSelect,
154 /// Vim's `o` in Visual -- swap the cursor to the other end of the
155 /// selection so motions / text objects act on that end.
156 SwapVisualEnds,
157 /// Vim's `*` (Forward) and `#` (Backward) -- search for the word under
158 /// the cursor in the given direction.
159 SearchWordUnderCursor(SearchDirection),
160 /// Vim's `%` -- jump to the matching bracket. Looks at or beyond the
161 /// cursor on the current line for the first `()[]{}` and seeks its
162 /// pair using a depth-tracking scan.
163 MatchBracket,
164 /// Vim's `~` -- toggle the case of the char at the cursor and
165 /// advance by one byte.
166 ToggleCaseAtCursor,
167 /// Vim's `J` (with-space) and `gJ` (no-space): join the current line
168 /// with the next, replacing the joining newline with a single space
169 /// (or nothing for `gJ`).
170 JoinLines {
171 with_space: bool,
172 },
173 /// Vim's `;` (no-reverse) and `,` (reverse): repeat the last
174 /// f/F/t/T find on the current line.
175 FindRepeat {
176 reverse: bool,
177 },
178 /// Vim's `zf` -- create a fold from the current Visual selection.
179 CreateFoldFromVisual,
180 /// Vim's `zo` -- open the fold containing the cursor.
181 OpenFoldAtCursor,
182 /// Vim's `zc` -- close the fold containing the cursor.
183 CloseFoldAtCursor,
184 /// Vim's `za` -- toggle the fold containing the cursor.
185 ToggleFoldAtCursor,
186 /// Vim's `zR` -- open all folds.
187 OpenAllFolds,
188 /// Vim's `zM` -- close all folds.
189 CloseAllFolds,
190 /// org-cycle `z<Space>` / `:fold-cycle` -- cycle the fold under the
191 /// cursor through FOLDED → CHILDREN → SUBTREE.
192 CycleFoldAtCursor,
193 /// org-cycle `z<Tab>` / `:fold-cycle-global` -- cycle the whole buffer
194 /// through OVERVIEW → CONTENTS → SHOW-ALL.
195 CycleFoldsGlobal,
196 /// `zp` / `:fold-goto-parent` -- move the cursor to the parent heading
197 /// (one level up the fold hierarchy).
198 GotoParentFold,
199 /// Vim's `zd` -- delete the fold containing the cursor.
200 DeleteFoldAtCursor,
201 /// Vim's `zO` -- open the folds containing the cursor, recursively.
202 OpenFoldsRecursively,
203 /// Vim's `zC` -- close the folds containing the cursor.
204 CloseFoldsRecursively,
205 /// Vim's `zD` -- delete the innermost fold at the cursor and its nested folds.
206 DeleteFoldsRecursively,
207 /// Vim's `zj` -- move cursor to the start of the next fold.
208 GotoNextFold,
209 /// Vim's `zk` -- move cursor to the end of the previous fold.
210 GotoPrevFold,
211 /// MO.2: the wheel turned over `pane`.
212 ///
213 /// The pane under the POINTER scrolls, and it does not take focus —
214 /// the convention vim (`mousescroll`), Zed and Helix share, and the
215 /// one that makes a wheel over a reference split usable while you
216 /// are typing in another.
217 MouseScroll {
218 pane: lattice_core::ui::pane::PaneId,
219 down: bool,
220 },
221 /// MO.2: a press or drag resolved to a buffer position in `pane`.
222 ///
223 /// The renderer has already inverted its own geometry; this carries
224 /// a buffer coordinate and nothing about the screen. `extend` is
225 /// what separates the two gestures: a press moves the cursor and
226 /// leaves any Visual selection behind, a drag keeps the press's
227 /// anchor and extends charwise Visual to here. Unlike scroll, this
228 /// DOES focus the pane — clicking into a split is how you move to
229 /// it.
230 MouseGoto {
231 pane: lattice_core::ui::pane::PaneId,
232 line: u32,
233 byte: u32,
234 extend: bool,
235 },
236 /// Vim's `zi` -- toggle [`App::foldenable`]. With folds disabled
237 /// every line renders flat regardless of any closed flag.
238 ToggleFoldEnable,
239 // L7 (lsp-architecture.md §16): the 7 nav `Action::Lsp*Request`
240 // variants (`K` / `gd` / `gD` / `gy` / `gI` / `gr` / `gx`) removed.
241 // The nav surface is mode-owned now — `lsp-mode`'s `action_handlers()`
242 // closures emit `Effect::Lsp(LspRequest::…)`, dispatched host-side by
243 // `editor.lsp_request` onto the unchanged request substrate. The
244 // non-nav LSP actions below (signature-help / completion / symbols /
245 // on-type-formatting) are ex-command / insert-autopilot triggered,
246 // not chord-bound, and stay.
247 /// `:lsp-signature-help` (Phase 4.3). Sends
248 /// `textDocument/signatureHelp` to attached servers; the
249 /// first non-empty response renders into a popup near the
250 /// cursor. In Insert mode the same request fires
251 /// automatically when the user types a server-advertised
252 /// trigger character (commonly `(` and `,`).
253 LspSignatureHelpRequest,
254 /// `:complete` (Phase 4.2.g, picker-flavoured). Fires
255 /// `textDocument/completion` at the cursor; the merged item
256 /// list opens as a vertico picker (label + kind glyph +
257 /// detail). Accept replaces the prefix-under-cursor with
258 /// the item's insert text. Snippet expansion + lazy
259 /// `completionItem/resolve` are queued behind buffer-level
260 /// Insert-mode completion (which doesn't exist yet -- this
261 /// is the bridge until that lands).
262 LspCompletionRequest,
263 /// `<C-t>` -- pop the tag stack (vim's tag-stack
264 /// `:pop`). Walks back through the LIFO chain of `gd` /
265 /// `gD` / `gy` / `gI` drill-downs. Independent of the
266 /// jump-list `<C-o>` walk: the stack and the list have
267 /// different push semantics and can have different lengths.
268 TagStackPop,
269 /// **Insert-mode completion** (Phase 4.2.g.1). Manually
270 /// open the popup at the cursor or refresh an open one.
271 /// Bound by default to `<C-x><C-o>` / `<C-Space>` /
272 /// smart-tab.
273 CompletionTrigger,
274 /// Move the popup selection down (`<C-n>` / `<Down>` /
275 /// `<Tab>` cycle).
276 CompletionNext,
277 /// Move the popup selection up (`<C-p>` / `<Up>` /
278 /// `<S-Tab>` cycle).
279 CompletionPrev,
280 /// Accept the focused candidate (`<C-y>` / `<Tab>` /
281 /// `<CR>`). Splices the candidate's insert text into the
282 /// buffer at the popup's anchor and closes the popup.
283 CompletionAccept,
284 /// Close the popup, stay in Insert (`<C-e>`).
285 CompletionCancel,
286 /// Close the popup AND exit Insert mode (`<Esc>`). Mirrors
287 /// vim's `<Esc>` semantics with one extra step (drop the
288 /// popup before the modal switch).
289 CompletionCancelAndExitInsert,
290 /// Toggle the side documentation popup for the focused
291 /// candidate (`<C-d>`, only inside the completion-popup
292 /// minor mode).
293 CompletionToggleDocs,
294 /// Scroll the docs side popup forward (`<C-f>` inside
295 /// the completion-popup minor mode).
296 CompletionDocsScrollDown,
297 /// Scroll the docs side popup backward (`<C-b>` inside
298 /// the completion-popup minor mode).
299 CompletionDocsScrollUp,
300 /// Restrict the popup to a single completion source.
301 /// String is the `SourceId` (e.g. `"gen:buffer-words"`).
302 /// Bound to the popup-mode filter chords introduced in
303 /// CSM.K2 (`<C-b>` buffer, `<C-o>` lsp, `<C-f>` path,
304 /// `<C-t>` tree-sitter, ...).
305 CompletionFilterToSource(String),
306 /// Clear the active source filter (`<C-Space>`). Restores
307 /// the mixed merged candidate list.
308 CompletionFilterClear,
309 /// Insert-mode character key while the completion popup
310 /// is open (Phase 4.2.g.7 commit-char polish). The App's
311 /// handler decides at apply time:
312 ///
313 /// - If the typed `char` is in the focused candidate's
314 /// effective commit-character set (LSP-supplied per-item
315 /// list union'd with `completion.extra_commit_chars`),
316 /// the popup accepts the candidate then inserts the
317 /// typed `char` afterward (vim convention: a commit
318 /// character behaves like "accept and continue typing").
319 /// - Otherwise the typed `char` flows through plain
320 /// `do_insert_text`; the popup refilters against the
321 /// updated query as if the layer had returned `None`.
322 ///
323 /// Routing every popup-time char through this single
324 /// action keeps the input layer ignorant of commit-char
325 /// state -- the App reads it once at apply time.
326 CompletionAcceptThenInsert(char),
327 /// YR.5: insert register `c`'s contents at the cursor.
328 InsertRegister(char),
329 /// YR.5: open the yank-ring picker over the current surface.
330 OpenYankPicker,
331 /// YR.6: open the picker registered for the `:`-line argument under
332 /// the cursor, filling the pick back into that argument.
333 OpenArgPicker,
334 // SN.3c.1 (2026-06-14): `Action::SnippetExpand` removed.
335 // `<C-x><C-s>` is mode-owned now (`snippet-mode`'s `keymap()` +
336 // `action_handlers()` emit `Effect::ExpandSnippet`); no host
337 // `Action` round-trip. (`feedback_mode_owns_its_surface`.)
338 // SN.2b (2026-06-12): `SnippetNextPlaceholder` /
339 // `SnippetPrevPlaceholder` removed — `<Tab>` / `<S-Tab>`
340 // placeholder navigation is mode-owned
341 // (`active-snippet-mode`'s `ActionHandlerRegistry` closures),
342 // not a host `Action`.
343 // SN.3c.2 (2026-06-14): `Action::SnippetLeave` removed.
344 // `<Esc>` while a snippet is active is mode-owned now
345 // (`active-snippet-mode`'s per-buffer handler clears the
346 // session + returns `Effect::EnterMode(Normal)`); no host
347 // `Action` round-trip. (`feedback_mode_owns_its_surface`.)
348 // CR.1 (2026-06-24): `Action::DiffGet` / `Action::DiffPut` deleted.
349 // The diff `do`/`dp` chords are mode-owned (`DiffMode::action_handlers()`
350 // → `Effect::ApplyEdit`), so they flow through the generic
351 // `Action::ApplyEdit` below instead of a host-side diff variant — the
352 // mode-ownership acid test (`feedback_mode_owns_its_surface`).
353 /// CR.0: host counterpart of [`lattice_grammar::Effect::ApplyEdit`].
354 /// Applies a mode-computed `edit` to `target` (routing through the
355 /// active-document pipeline when `target` is the focused buffer, or
356 /// the peer-buffer registry handle otherwise) and, when `cursor` is
357 /// `Some`, parks the active cursor at that row. `handle_effect`
358 /// translates the `Effect` into this `Action` and queues it on
359 /// `out.next_actions`; the applier arm in `handle_action` calls
360 /// `Editor::apply_targeted_edit`. The generic primitive the diff
361 /// (and future) modes drive instead of host `do_<x>` methods.
362 ApplyEdit {
363 target: lattice_core::BufferId,
364 edit: lattice_protocol::edit::Edit,
365 cursor: Option<lattice_protocol::position::Position>,
366 },
367 // M.10.7 (2026-06-03): four dead Action variants deleted —
368 // `MultibufferExpand`, `SearchTrigger`, `SearchJumpToSource`,
369 // `SearchRefresh`. All four are now mode-owned via the
370 // M.10.1.b ActionHandlerRegistry (the chord/ex-command
371 // routes through `run_invocation` → registry consultation →
372 // handler) OR work happens inline in the apply_effect arm
373 // (`:multibuffer-expand` + `:search`). No production path
374 // constructs these variants any longer.
375 /// `:lsp-symbols` (Phase 4.2.e). Send
376 /// `textDocument/documentSymbol` to every attached server;
377 /// render the merged outline as a vertico picker. Selecting
378 /// a row jumps to the symbol's location.
379 LspDocumentSymbolRequest,
380 /// `:lsp-workspace-symbol [query]` (Phase 4.2.f). Send
381 /// `workspace/symbol` to every attached server with the
382 /// user-supplied query string (server-side substring filter).
383 /// Empty query returns the server's idea of "everything"
384 /// (rust-analyzer streams all crate symbols). Picker UX
385 /// mirrors the document-symbol path.
386 LspWorkspaceSymbolRequest(String),
387 /// `"<reg>` prefix -- stash the named register for the next operator
388 /// / paste invocation.
389 SelectRegister(Register),
390 /// Vim's `Ctrl-O` -- step backward in the position history.
391 JumpHistoryBack,
392 /// Vim's `Ctrl-I` (Tab) -- step forward.
393 JumpHistoryForward,
394 /// PBH.3: `<C-6>` -- step back through the ACTIVE PANE's buffer
395 /// trail. Per-pane and buffer-granular, unlike the global,
396 /// position-granular `JumpHistoryBack`.
397 PaneHistoryBack,
398 /// PBH.3: `<C-7>` -- step forward through the active pane's
399 /// buffer trail.
400 PaneHistoryForward,
401 /// Vim's `Ctrl-L` -- force a full redraw. Reparses the syntax
402 /// tree, recomputes folds, clears the visible-highlight cache,
403 /// and tells the runtime to clear the terminal screen on the
404 /// next frame. Intended escape hatch for any visual glitch
405 /// (stale highlights, leftover ANSI escape sequences from a
406 /// crashed external program, terminal-resize race).
407 RedrawScreen,
408 /// Vim's `g;` -- step backward through `NamedMark` entries in the
409 /// unified position history.
410 WalkMarkHistoryBack,
411 /// Vim's `g,` -- step forward.
412 WalkMarkHistoryForward,
413 /// Vim's `q<reg>` to start recording into a register; `q` while
414 /// recording stops. App handles routing internally.
415 StartMacroRecord(char),
416 StopMacroRecord,
417 /// Vim's `@<reg>` to play. Replays the recorded Action stream.
418 PlayMacro(char),
419 /// Vim's `@@` to repeat the most recently played macro.
420 PlayLastMacro,
421 /// Vim's `.` -- re-dispatch the last buffer-mutating invocation from
422 /// the current cursor.
423 RepeatLastChange,
424 /// Replace mode: overwrite the char at the cursor with `c` and advance.
425 /// Beyond end-of-line, falls back to insert (vim behavior).
426 OverwriteChar(char),
427 /// Backspace within Replace -- pop the latest entry from
428 /// `replace_history` and restore the original byte (or delete if the
429 /// overwrite was a line extension).
430 ReplaceUndoLast,
431 /// Jump cursor to a viewport-relative line (vim's `H`, `M`, `L`).
432 JumpViewport(ViewportPos),
433 /// Adjust scroll so the cursor lands at the viewport top / center /
434 /// bottom (vim's `zt`, `zz`, `zb`).
435 ScrollCursorTo(ScrollPos),
436 /// HS.2: manual horizontal scroll (vim `z{l,h,L,H,s,e}`).
437 HorizontalScroll(lattice_grammar::HScroll),
438 /// Move cursor down / up by one viewport-page (vim's Ctrl-F / Ctrl-B).
439 PageDown,
440 PageUp,
441 /// VM.3j-2: vim's `<C-d>` / `<C-u>` — view and cursor together by
442 /// `scroll` lines. Scroll commands, so no operator composes with them.
443 HalfPageDown,
444 HalfPageUp,
445 /// Scroll the viewport one line up (Ctrl-Y) or down (Ctrl-E),
446 /// nudging the cursor to keep it on-screen.
447 ScrollLineUp,
448 ScrollLineDown,
449 /// `m<letter>` -- record the cursor at mark `<letter>`.
450 SetMark(char),
451 /// `'<letter>` -- jump to the line of mark `<letter>` (column = first
452 /// non-blank).
453 JumpToMarkLine(char),
454 /// `` `<letter> `` -- jump to the exact position of mark `<letter>`.
455 JumpToMarkExact(char),
456
457 // ---- Command-line minibuffer (Phase 2: simple, single-line) ----
458 /// Open the command picker (`:`/`M-x`). On accept: if the
459 /// chosen command needs a required arg, arm the cmdline;
460 /// otherwise execute immediately.
461 OpenCommandPicker,
462 /// MB.3: `q:` -- open the command-line *history* picker over
463 /// `command_history`; accept loads the picked command into the
464 /// `:` line without executing.
465 OpenHistoryPicker,
466 /// MB.5: `q/` / `q?` — open the search-line *history* picker
467 /// over `search_history`; accept loads the picked term into the
468 /// `/` search line without executing.
469 OpenSearchHistoryPicker,
470 /// Pressed `:` in Normal mode -- enter command modal with empty buffer.
471 EnterCommandLine,
472 /// Append a character to the in-progress command line.
473 CommandLineAppend(char),
474 /// Delete the last character. If the buffer is empty, leave Command mode.
475 CommandLineBackspace,
476 /// Submit the current command line: parse + execute, then leave Command.
477 CommandLineSubmit,
478 /// Drop the current command line and leave Command modal.
479 CommandLineCancel,
480 /// Walk to an older entry in the command history (`Up` arrow in
481 /// Command modal).
482 CommandLineHistoryPrev,
483 /// Walk to a newer entry, eventually returning to the user's
484 /// in-progress line.
485 CommandLineHistoryNext,
486 /// MB.2: toggle the `:` line's expanded tier-2 mini-buffer band
487 /// (`<C-x><C-e>`); collapse returns the edited text to the one-row
488 /// line for review.
489 CommandLineToggleExpand,
490 /// MB.5a: `<CR>` on the `/`·`?` search line — submit the search
491 /// pattern. Resolved from `search-line-mode`'s Insert keymap.
492 SearchLineSubmit,
493 /// MB.5a: `<Esc>` / `<C-c>` on the `/`·`?` search line — cancel
494 /// the search and restore the prior editing buffer.
495 SearchLineCancel,
496 /// `<BS>` on the `/`·`?` line: delete a char, or cancel when empty.
497 SearchLineBackspace,
498 /// MB.5b: `<C-p>` / `<Up>` on the `/`·`?` search line — walk to an
499 /// older entry in `search_history`.
500 SearchLineHistoryPrev,
501 /// MB.5b: `<C-n>` / `<Down>` on the `/`·`?` search line — walk to a
502 /// newer entry in `search_history`.
503 SearchLineHistoryNext,
504 /// `<CR>` in `prompt-line-mode` — submit the typed text to the
505 /// caller-named `on_submit_action` handler.
506 PromptLineSubmit,
507 /// `<Esc>` / `<C-c>` in `prompt-line-mode` — cancel, restore the
508 /// prior editing buffer.
509 PromptLineCancel,
510 /// Replace the echo area with a typed message.
511 Echo(EchoMessage),
512
513 // ---- Hover popup ----
514 /// Dismiss the hover popup. Mirrors the `:HoverClose` ex-command
515 /// for the keymap path. Once a hover is *promoted* to a help
516 /// buffer (via the second-K gesture), the standard
517 /// help-dismissal path (`HelpDismiss`) closes it instead.
518 CloseHover,
519
520 // ---- Picker (DESIGN.md §5.9.7) ----
521 /// Append a character to the picker's query and refilter.
522 PickerAppend(char),
523 /// Drop the last char from the picker's query and refilter.
524 PickerBackspace,
525 /// Move the selection cursor down one row (wraps).
526 PickerSelectNext,
527 /// Move the selection cursor up one row (wraps).
528 PickerSelectPrev,
529 /// Run the picker's accept action against the selected
530 /// candidate and dismiss.
531 PickerAccept,
532 /// Issue #32 (2026-05-22): `<C-s>` — accept candidate,
533 /// opening files in a horizontal split. Non-file outcomes
534 /// ignore the override.
535 PickerAcceptInSplit,
536 /// `<C-v>` — accept candidate in a vertical split.
537 PickerAcceptInVSplit,
538 /// `<C-t>` — accept candidate in a new tab.
539 PickerAcceptInTab,
540 /// PC.10: `<C-l>` — go INTO the selected candidate rather than
541 /// choosing it. The source answers through
542 /// `PickerSourceGenerator::descend`; a source with no notion of
543 /// going deeper (every one but `dir-pick` today) returns `None` and
544 /// this does nothing at all.
545 PickerDescend,
546 /// PC.10 / PH.1: `<C-w>` — the peer of [`Self::PickerDescend`], one
547 /// level up where the source has depth (`PickerSourceGenerator::ascend`),
548 /// and the command-line's delete-word everywhere else.
549 ///
550 /// The shape of [`Self::PickerDescendOrSelectNext`], for the same reason:
551 /// only the dispatcher can ask which source seated the picker. Ascend is
552 /// asked FIRST, so a source with depth gets its own notion of "up" rather
553 /// than a word boundary; a grep pattern, whose source declines, loses a
554 /// word rather than everything back to a `/`.
555 ///
556 /// Was `<C-h>` until PH.1 gave that key to picker help.
557 PickerAscendOrDeleteWord,
558 /// PH.1: `<C-h>` — close the picker and open its help page. See
559 /// `Editor::do_picker_help` for how the page is chosen.
560 PickerHelp,
561 /// PP.5: `<Tab>` — drill in where the source has depth, select the next
562 /// row everywhere else.
563 ///
564 /// One action rather than a `<Tab>` that translate resolves two ways,
565 /// because translate cannot see which source seated the picker — only the
566 /// dispatcher can ask it. The fallback is what keeps `<Tab>` meaning
567 /// select-next in the pickers that have no notion of depth, which is every
568 /// one but `dir-pick` today.
569 PickerDescendOrSelectNext,
570 /// PD.1: `<C-d>` — remove the selected row from whatever backs the list.
571 ///
572 /// The source names the verb through
573 /// `PickerSourceSpec::delete_command`; a source that names none leaves
574 /// this doing nothing at all. Never a filesystem delete — see that
575 /// field's doc for why that boundary is load-bearing.
576 PickerDelete,
577 /// LR.5 (2026-08-11): `<C-q>` — send every candidate that survived
578 /// the current query to the picker's declared bulk outcome, and
579 /// dismiss. Echoes when the opener declared none.
580 PickerBulkAccept,
581 /// Drop the picker without acting on any candidate.
582 PickerDismiss,
583
584 // ---- Transient (picker transient mode — PICK.1) ----
585 /// Fire the action for a transient item key press. The string
586 /// is the item's label (used as the lookup key in the current
587 /// transient spec's groups).
588 TransientTrigger(String),
589 /// Toggle a boolean flag in the transient state.
590 TransientToggleFlag(String),
591 /// Dismiss the transient (close the picker overlay).
592 TransientDismiss,
593
594 // ---- Paste (`p`, `P`) ----
595 /// Vim's `p` -- paste the unnamed register after the cursor (charwise)
596 /// or below the current line (linewise).
597 PasteAfter,
598 /// Vim's `P` -- paste before cursor / above current line.
599 PasteBefore,
600 /// A bracketed-paste burst from the terminal -- the user pressed
601 /// their terminal's paste shortcut (Ctrl-Shift-V, Cmd-V, mouse
602 /// middle-click, ...) and the terminal handed us the whole payload
603 /// in one event. Mode-dependent target: cursor in Insert/Normal/
604 /// Visual/Replace, command line in Command, search line in Search.
605 /// One undo unit, so a single `u` reverts the entire paste.
606 PasteText(String),
607
608 // ---- Command-line editing (DESIGN.md §5.11.3) ----
609 /// `<C-u>` -- clear the entire command line.
610 CommandLineClear,
611 /// `<C-w>` -- delete the word to the left of the cursor.
612 /// (v1: cursor is at end-of-line, so deletes the trailing word.)
613 CommandLineDeleteWordBackward,
614 /// `<C-h>` -- describe the command word / arg under cursor.
615 /// Hybrid resolution: word-at-cursor describes itself if it
616 /// resolves to a registered command; else describe the parent
617 /// command at the relevant `arg:<name>` anchor.
618 CommandLineDescribeUnderCursor,
619 /// Chord-capture overlay (`ArgKind::Chord` slot): append one
620 /// pre-formatted chord token (`<C-c>`, `<Esc>`, `gg`, ...) to
621 /// the cmdline. Translation from the raw `KeyEvent` happens
622 /// in `input::translate_command_chord_capture`.
623 CommandLineAppendChord(String),
624 /// A transient menu row's key, already in canonical chord spelling
625 /// (`<CR>`, `<Tab>`, `<Space>`, `g`). Emitted by
626 /// `Editor::retarget_claimed_transient_key` when the showing spec claims
627 /// a key the picker would otherwise spend on its own navigation, so a
628 /// menu can bind `<CR>` without losing it to "accept the selected row".
629 TransientKey(String),
630
631 // ---- Completion popup (DESIGN.md §5.11.3) ----
632 /// `<Tab>` -- open completion popup if closed; advance the
633 /// selected candidate if open.
634 CommandLineCompleteOrAdvance,
635 /// `<S-Tab>` -- previous candidate when popup is open.
636 CommandLineCompletePrev,
637 /// `<CR>` while popup open -- replace the prefix with the
638 /// selected candidate's `text` and close the popup.
639 CommandLineAcceptCompletion,
640 /// `<Esc>` while popup open -- close the popup without
641 /// touching the command line. (Two-stage Esc: a second Esc
642 /// then cancels the command line.)
643 CommandLineDismissCompletion,
644
645 // ---- Pane tree (DESIGN.md §5.9) ----
646 /// `<C-w>s` -- split the active pane horizontally (new pane below).
647 SplitPaneHorizontal,
648 /// `<C-w>v` -- split the active pane vertically (new pane right).
649 SplitPaneVertical,
650 /// `<C-w>c` / `<C-w>q` -- close the active pane.
651 ClosePane,
652 /// `<C-w>o` / `:only` / emacs `C-x 1` -- close every pane except
653 /// the active one. S3b (2026-06-22).
654 OnlyPane,
655 /// ZP.2: `<C-w>z` / `<C-w><C-z>` / `:zoom-pane` -- toggle
656 /// tmux-style zoom on the active pane. The non-destructive
657 /// counterpart of [`Self::OnlyPane`]: the split layout survives
658 /// and the second toggle restores it verbatim.
659 ToggleZoomPane,
660 /// `<C-w>{h,j,k,l}` -- move the active pane cardinally.
661 NavigatePane(PaneDirection),
662 /// `<C-w>w` -- cycle to the next pane in declaration order.
663 NextPane,
664 /// `<C-w>W` -- cycle to the previous pane.
665 PrevPane,
666 /// Issue #29 (2026-05-22): vim's `gt` — next tab.
667 NextTab,
668 /// Vim's `gT` — previous tab.
669 PrevTab,
670 /// Vim's `{N}gt` — switch to tab N (1-indexed; clamped).
671 GoToTab(u32),
672 /// `:tabnew` — new empty tab (scratch buffer).
673 NewTab,
674 /// `:tabnew <path>` — new tab opening `path`.
675 NewTabAt(String),
676 /// Issue #40 / Terminal-mode T1 (2026-05-22):
677 /// `:terminal [cmd]` — spawn a shell (or `cmd` if given)
678 /// under a fresh PTY and activate as a new terminal
679 /// buffer. None ⇒ spawn the user's shell from
680 /// `terminal.shell` (default `$SHELL` else `/bin/sh`).
681 TerminalSpawn(Option<String>),
682 /// Terminal-mode T2.a (2026-05-25): write encoded bytes to
683 /// the active Terminal buffer's PTY stdin. Emitted by the
684 /// translate layer when the user is in Terminal-Insert mode
685 /// on a Terminal buffer and the chord encodes to ANSI via
686 /// `keymap_terminal::key_to_ansi`. The host handler
687 /// (`Editor::do_terminal_input`) looks up the active
688 /// buffer's `PtyHandle` and forwards. No-op on non-Terminal
689 /// active buffers (defensive — translate never emits in
690 /// that state but the handler stays safe).
691 TerminalInput(Vec<u8>),
692 /// Terminal-mode T2.a: activate `terminal-insert-mode` on
693 /// the active Terminal buffer. Emitted by `i` (later `a`/
694 /// `I`/`A`) in Normal-in-terminal. No-op when the active
695 /// buffer is not a Terminal.
696 EnterTerminalInsert,
697 /// Terminal-mode T2.a: deactivate `terminal-insert-mode`
698 /// on the active Terminal buffer. Emitted by `<C-\><C-n>`
699 /// (and, T2.b, optionally `<Esc>` when `terminal.esc_exits`
700 /// is true).
701 ExitTerminalInsert,
702 /// Terminal-mode T3 (2026-05-25): re-position the active
703 /// terminal's scrollback viewport. Emitted by Normal-in-
704 /// terminal motions (`j`/`k`/`<C-d>`/`<C-u>`/`gg`/`G`).
705 /// No-op when the active buffer is not a Terminal.
706 TerminalScroll(lattice_terminal::TerminalScrollKind),
707 /// Terminal-mode T2.c (2026-05-25): user pressed `<C-\>` in
708 /// Terminal-Insert; arm the two-key exit chord on the
709 /// active terminal buffer. The next keystroke resolves it
710 /// (`<C-n>` exits; anything else sends `\x1c` + that key
711 /// to the PTY). Cleared automatically by the
712 /// `ExitTerminalInsert` / `TerminalInput` arms.
713 TerminalArmExitChord,
714 /// T4 (2026-05-25): `<C-w>T` — move the active pane (and
715 /// its buffer) to a fresh tab. Vim convention. The
716 /// previous tab loses the pane via the standard close-pane
717 /// path; the new tab opens with a single pane referencing
718 /// the same `BufferId`.
719 MovePaneToNewTab,
720 /// `:tabclose` — close active tab (no-op when only one tab).
721 CloseTab,
722 /// `:tabonly` — close every tab except the active one.
723 OnlyTab,
724 /// `:tabmove [N]` — move the active tab to position N
725 /// (1-indexed). Negative or omitted N is handled by the
726 /// ex-command parser; the runtime value here is the
727 /// resolved target index.
728 MoveTab(u32),
729 /// `<C-w>=` -- reset every split's ratio to 0.5 (equalize all
730 /// panes). Issue #28 (2026-05-22).
731 EqualizePanes,
732 /// `<C-w>+` -- grow the active pane vertically (nudge the
733 /// nearest HorizontalSplit ancestor's ratio).
734 GrowPaneHeight,
735 /// `<C-w>-` -- shrink the active pane vertically.
736 ShrinkPaneHeight,
737 /// `<C-w>>` -- grow the active pane horizontally (nudge the
738 /// nearest VerticalSplit ancestor's ratio).
739 GrowPaneWidth,
740 /// `<C-w><` -- shrink the active pane horizontally.
741 ShrinkPaneWidth,
742
743 // ---- Help buffer (DESIGN.md §5.11, §5.9) ----
744 //
745 // Help is a regular buffer routed through the same Normal-mode
746 // chord grammar as the document buffer (motions, page motions,
747 // viewport jumps, `<C-o>` / `<C-i>`, `gg` / `G`, etc.). The
748 // App's `active_buffer` field decides which cursor an action
749 // affects. Only two help-specific actions remain -- buffer-local
750 // bindings emitted by `translate()` when active_buffer == Help:
751 /// Close the active help overlay (`Esc` / `q`).
752 HelpDismiss,
753 /// Follow the link under the cursor (`<CR>`). Resolves the
754 /// link's URL scheme and dispatches: `command:NAME` re-runs
755 /// `:describe-command NAME`, `key:CHORD` re-runs
756 /// `:describe-key CHORD`, `file:PATH:LINE` opens the file at
757 /// the line. Cursor not on a link is a no-op.
758 FollowLink,
759 /// `-` in any normal-mode context — context-sensitive:
760 /// • Document / FileTree → open oil for parent dir of current file / hovered entry
761 /// • Oil buffer → `oil.navigate_up()`
762 OilNavigateUp,
763
764 // ---- 5.5.G.23.insert: host→App LSP autopilot follow-ups ----
765 /// 5.5.G.23.insert: emitted by host-side `Editor::do_insert_text`
766 /// after a typed character matches the active document's
767 /// `onTypeFormatting` trigger-char set. App-side handler fires
768 /// `textDocument/onTypeFormatting` against the highest-priority
769 /// server advertising the trigger and applies the returned edits
770 /// as one undo unit.
771 LspOnTypeFormattingRequest(char),
772 /// 5.5.G.23.insert: emitted by host-side
773 /// `Editor::maybe_refresh_insert_completion_after_edit` when the
774 /// last LSP completion response was `isIncomplete` and the popup
775 /// just refiltered against the new query. App-side handler
776 /// dispatches `textDocument/completion` through the
777 /// `LspCompletionSource`'s async fan-out.
778 LspInsertCompletionRequest,
779
780 // ---- Search (`/`, `?`, `n`, `N`) ----
781 /// Pressed `/` (Forward) or `?` (Backward) -- enter Search modal with
782 /// empty pattern, remembering origin so cancel restores cursor.
783 EnterSearch(SearchDirection),
784 SearchAppend(char),
785 /// Delete one char from the pattern. If pattern is empty, leave Search.
786 SearchBackspace,
787 /// Confirm the pattern: jump to current match (if any) and store it
788 /// as `last_search` for `n`/`N` repeat.
789 SearchSubmit,
790 /// Drop the in-progress pattern, restore cursor, leave Search.
791 SearchCancel,
792 /// MB.5c: toggle the `/`·`?` search line's expanded tier-2
793 /// mini-buffer band (`<C-x><C-e>`).
794 SearchLineToggleExpand,
795 /// Repeat the last search in its original direction.
796 SearchNext,
797 /// Repeat the last search in the opposite direction.
798 SearchPrevious,
799 // ---- Phase 5.8.AF.5 / Slice 3c.final.C ----
800 // Renderer-thread non-dispatch mutations lifted to Action
801 // variants so the renderer doesn't need `&mut Editor` to
802 // perform them. Each fires from the per-frame setup code in
803 // the TUI's `main_loop` / GPUI's `EditorView::render` and
804 // dispatches through the standard `apply` tail (which
805 // publishes RenderState).
806 /// Set the active pane's viewport-height (rows). Triggered by
807 /// the renderer when the window size changes. Mirrors the
808 /// pre-3c.final `App::set_viewport_height` shape — clamps to
809 /// `>= 1` and runs `ensure_cursor_visible` host-side.
810 SetViewportHeight(u32),
811 /// Auto-scroll so the cursor stays visible in the active
812 /// pane. Idempotent: no-op when the cursor is already on
813 /// screen.
814 EnsureCursorVisible,
815 /// Dismiss the active popup (closes `popup_buffer`, restores
816 /// the previous pane focus). No-op when no popup is open.
817 DismissPopup,
818 /// Mirror the TUI's terminal width into editor state so
819 /// status-line layout matches what crossterm reported.
820 SetTerminalWidth(u16),
821 /// IM.7a — the drawing peer's CELL geometry in pixels: one row's
822 /// height and one column's advance.
823 ///
824 /// The host sizes an inline media block (`block_geometry` wants a line
825 /// height and a pane width, both in pixels) and has no other way to
826 /// learn either — `terminal_width` is columns and `viewport_height` is
827 /// rows. Combined with the pane's existing column count this yields the
828 /// pane's pixel width, so this is two numbers rather than a per-pane
829 /// channel.
830 ///
831 /// A peer that cannot draw images never sends it, and the host then
832 /// reserves the provisional row count and reads no image file at all.
833 /// That is the TUI: it shows alt text, which needs no header read.
834 SetCellMetrics {
835 row_px: f32,
836 col_px: f32,
837 },
838 /// Clear `pending_redraw` after the renderer has cleared the
839 /// terminal buffer in response to `<C-l>` (`RedrawScreen`).
840 AcknowledgeRedraw,
841 /// SN.3c.2b: run a sequence of actions in order. Produced by
842 /// `dispatch_insert` for a `fall_through` binding —
843 /// `[mode_action, native_action]` — where the mode's chord
844 /// augments a native chord (`active-snippet-mode`'s `<Esc>` clears
845 /// the session, then continues to the builtin `<Esc>` → exit
846 /// insert). The renderer's `apply` applies each in order. General
847 /// (not snippet-specific): any future binding that wants to run +
848 /// continue resolves to a `Chain`.
849 Chain(Vec<Action>),
850}