aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/input_cmdatom_defs.h
Commit message (Collapse)AuthorAge
* fix(multicursor): "gv" + Insert-entering cmd #42110Justin M. Keyes2026-09-27
| | | | | | | | | | | | | | | | | | | | | Problem: - Multicursor does not cascade a `gv`-entered insert-session (`gvc`). - CmdAtom of a `gv`-entered insert-session (e.g. `gvcX<Esc>`) has `keys="1vcX<Esc>"` (redo's fallback), not the typed keys. And is not emitted at all if multicursor is inactive. Analysis: Redo can't contain "gv", so it does the "1v" fallback, and the insert-cascade replays redo. Solution: Replay "gv" + op at each cursor: store the typed selection in `InsSession.vsel`; it supplants redo's "1v" in the emitted atom and in the insert-cascade replay. A cursor without a Visual area skips a "gv"-based replay (else its keys would run as Normal-mode: "gvrx" => "rx"). Redo behavior is unchanged ("1v" fallback): "gv" is absolute, "." acts at the cursor.
* fix(multicursor): don't dedupe primary-overlapping cursor #42060Justin M. Keyes2026-09-24
| | | | | | | | | | | | | | Problem: A primary-overlapping cursor is deduped before an edit. But if a Visual/Insert action moves the primary first, the cursor is not skipped (e.g. `vipQva)c` replays at the primary-overlapping cursor). Solution: Skip _replay_ of a primary-overlapping cursor, but don't _dedupe_ it. Keep its position synced with the primary. Sample the primary-overlapping cursor (`CmdOrigin.mcursor`) at the action start (first root frame, or Visual session-start), instead of current frame start.
* feat(multicursor): cgn, failed insert-entry #42054Justin M. Keyes2026-09-23
| | | | | | | | | | | | | | | | | | Problem: - A failed motion in a replay (`vt;` where there is no `;`) flushes the rest of the replay, so `vt;cX` inserts `X` without changing the selection. - If the Insert-entering cmd fails at a cursor (`ct;`, `cgn` with no match), the text is still inserted at the failed cursor. Solution: - `beep_flush()`: Don't flush typeahead (i.e. keys following the failed cmd) during mc-replay. - Aligns with interactively-typed behavior. - Remove a cursor if its Insert-entering command aborts (`beep && !changedtick`). - This is particular to Insert-entering commands (`c{motion}`), because they are tricky. We don't usually remove cursors if a replayed cmd happens to fail at some cursors.
* feat(multicursor): jump vs motion #42052Justin M. Keyes2026-09-23
| | | | | | | | | | | | | | | | | | | | Problem: Incoherent classification of motions vs jumps. Solution: Define "motion" as cursor-relative, and "jump" as "absolute". - Classify G/gg/H/L/M/go/N% as `CmdAtom.type=jump`. - Classify `*`/`#` as motions. Behavior changes: - `*` in follow-mode now moves each cursor to the next occurrence of its own word. - Note: the "last search pattern" is not stored per-cursor, so `n` still searches for the primary's search pattern. - G/gg/H/M/L in follow-mode do not cascade (would converge/dedupe all cursors). - If you want to force cursors to converge for some reason, use `9999j`. - Also affects mappings that mix an absolute move with an edit. E.g. mapping `nnoremap X ggdd` only replays `dd` per-cursor now. This is how marks already behaved.
* fix(multicursor): cascade mapping-edit during Visual selection #42027Justin M. Keyes2026-09-21
| | | | | | | | | | | | | | | | Problem: - A mapping that edits the buffer during a Visual selection applies only at the primary cursor. The edit "voids" the pending Visual-session, because `mc_vsel_refresh` only previews the selection per-cursor and must not edit. - A cursor overlapping the primary applies an edit twice, if the command moves the primary before cascade ("yiwp"). Solution: Cascade a Visual session's edits span-by-span, like an insert-session. - Each span is prefixed with "gv" so per-cursor reselect works. - Void only if no subatom was captured. - Dedupe cursors against the primary's anchor at cmd start, not its position after.
* fix(cmdatom): capture <Cmd>, not its subatoms #42020Justin M. Keyes2026-09-21
| | | | | | | | | | | | | | | | | | | Problem: A command's `:norm` children are captured as Visual subatoms: `v<Cmd>normal! e<CR>d` is "decomposed" to `ved`. That is a cosmetic, nice-to-have feature, which greatly complicates the cmdatom logic: it must detect "lossy" cases (API moves, "gv" on marks the command set, a session ended and restarted). Solution: Drop the nice-to-have. - A command is captured as itself, never its `:norm` children. - While a child frame runs, a pending Visual session is kept open until the enclosing frame resolves. - `:`/`<Cmd>` no longer decline to be a Visual subatom. - Stuffed keys ("." replay, "x" => "dl") and K_EVENT children (RPC, timer), are still captured as their translation. - `mc_add()` discards any pending Visual session, so `{Visual}Q` does not cascade recursively to the cursors it just created.
* fix(multicursor): replay of Lua mapping-id ending in "0" #42011Justin M. Keyes2026-09-20
| | | | | | | | | | | | | | | | Problem: - A captured Lua mapping (`K_LUA`+id) whose id ends in "0" replays the wrong mapping: `atom_from_cmdline()` escapes the id like a typed cmdline (trailing "0" => CTRL-V "048"), but the id is read raw. E.g. treesitter `an` (id 60) replays `gc` (id 6), so `van` does not preview/cascade at mcursors. - Same for `<Cmd>…` ending in "0" or "^" (`<Cmd>normal! 0<CR>`). - `c` + Lua/":" textobj (`canX`) does not insert-cascade. Solution: - Only CTRL-V-escape ":", "/" cmdline; append `<Cmd>` and Lua mapping-ids "raw" (`sb_add_spec()`). - `kVInsMotion`: an insert entered by Ex/Lua motion re-executes the motion, unless it read input.
* fix(cmdatom): mapping tail after a mode-change is not captured #41897Justin M. Keyes2026-09-13
| | | | | | | | | | | | | | | | Problem: `inoremap <Esc> <Esc>l` starts in insert-mode and ends in normal-mode ("l"). Its normal-mode "l" is not captured as a CmdAtom (and thus doesn't cascade to multicursors in follow-mode). Analysis: CmdFrames only delineate in Normal/Visual/Op-pending (i.e. the `MODE_NORMAL` modes), so a mapping resolved in Insert, Cmdline or Terminal mode does not start a `composite`, then the mapping "tail" is lost. Solution: Start a "deferred" `composite` if state is not `MODE_NORMAL`, instead of skipping it entirely.
* feat(multicursor): MC HAMMER #41587Justin M. Keyes2026-09-01
| | | | | | | | | | | | | | | | | | | | | Other (squashed) commits: fix(tui): emit ui_send output atomically with the frame Problem: tui_ui_send() writes directly to the TTY, bypassing the output buffer. Sequences sent via nvim_ui_send() (e.g. kitty multiple-cursors, or visual-dot-repeat) always arrive in a separate TTY write from the frame they were computed for. This manifests as "tearing", or e.g. in the case of multicursor the terminal renders text with stale cursor overlays. Solution: - tui_ui_send(): while a frame is being assembled (pending invalid regions or buffered output), buffer instead of writing directly. - Out-of-frame sends (tty queries, clear-on-disable) still write immediately. - mcursor.lua: emit the terminal-cursor update at the end of the redraw cycle (`on_end`, when screen positions are final) instead of vim.schedule().
* fix(cmdatom): insert-session entered by feedkeys() ignores typed input #41518Justin M. Keyes2026-08-27
| | | | | | | | | | | | | | | Problem: An insert-session entered by a scheduled `feedkeys('i','n')` is classified on entry (as "not typed"), and not reevaluated after that, thus user input following it is not captured. Repro: an `:imap` that does `feedkeys('<esc>','n')` and schedules re-entering insert, emits one CmdAtom for the first press, then nothing else. Solution: Sample `maptick` (ticked by `gotchars()` on typed input, including mappings) at session start; if it advanced by session end, the session is user input.
* feat(cmdatom): mappings capture continuationJustin M. Keyes2026-08-22
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Problem: A mapping that ends mid-operation (`nnoremap ,D d`) emits a content-free "mapping" atom plus a `pending` field, and the "continuation" motion arrives as a sibling atom. Consumers must stitch the two together (which has broken cases, e.g. Insert-opening mappings (",i") lose their session entirely). - ",i": the session atom is dropped bc the mapping RHS is consumed before the session starts (typebuf_maplen()==0), so atom_is_user_input()=false. - ":normal"-in-opfunc: the opfunc internal "v..y" session (a) became kVatomTyped just because the deferred composite was open, masking the real operator capture via atom_captures, and (b) its nested frames re-derived the outer redo. - "Motion" based on `moved=true`, has false negatives. - `CmdAtom.remap` is unnecessary, and clutters the docs/usage. Solution: - Introduce `frame_id` to identify CmdFrames. - Classify `type=motion` better, via `NV_MOTION` flag on the `nv_cmds` table. - Drop `CmdAtom.pending`, `CmdAtom.remap`. - Defer atom_composite_end() at the clock edge while an operator is pending, Visual is active, or `restart_edit` is set: the composite keeps collecting, so the continuation is captured in the mapping atom. - ",i": Now an open composite counts as user input. - ":normal"-in-opfunc: Now handled correctly. - `remap` is now decided by `composite.payload || 0 subatoms`. atom_payload_mark() records the read the resolution never captures. - `toplevel` is now decided by `CmdFrame.parent == NULL`. before/after: INPUT BEFORE AFTER --------------------------------------------------------------------- ,D w {mapping lhs=,D pending=operator} {operator lhs=,Dw keys=dw} + {operator keys=dw} ysiw" {mapping lhs=ys pending=operator} {operator lhs=ysiw" keys=g@iw"} + {operator lhs=g@iw"} ,v d {mapping pending=visual} {visual lhs=,vd keys=viwd} + {visual lhs=viwd} ,i XY<Esc> {normal keys=i lhs=,iXY<Esc>} {insert keys=1iXY<Esc> text=XY}
* feat(cmdatom)!: eliminate the need for vim-repeat #41414Justin M. Keyes2026-08-21
| | | | | | | | | | | | | | | | | | | | | | | | | Problem: - `lhs` is not fully realized. E.g. for a "payload" mapping `lhs` omits the `getchar()` payload during a mapping (vim-surround `ds'` reports `lhs="ds"`). This means plugins like vim-repeat are still needed... - The "delta" fields of a CmdAtom are calculated too late. - `<abuf>` and `changed` check whatever (wrong) buffer a command (":bnext") might land in. - CTRL-W_w between two windows on the same buffer reports type="motion". Solution: - `CmdOrigin` samples (buf/win/cursor/changedtick) at each "scope" entry (CmdFrame, composite, Visual session, insert session). - `dd<C-w>l` reports `changed=true` for the buffer it edited, regardless of where the cursor ends up. - New fields: - `pos`: cursor position at command start. - `moved`: indicates whether the cursor moved (in same buffer). - `undoseq`: undo state at settlement. - lhs now includes the payload: "ds)" reports lhs="ds)" instead of "ds". - Easy for users to "replay" any atom. - Rename: type "command" => "normal", "ex" => "excmd"; `arg` => `cmdarg` - Drop `cascade` field (no reason to expose it)
* fix(cmdatom): lhs not always reported in CmdAtom #41386Justin M. Keyes2026-08-20
| | | | | | | | | | | | | | | | Problem: An unreplayable Visual operation does not emit a CmdAtom event. That's maybe not super important, but it hints at a flaw in how `vatom` "voiding" is plumbed: `vatom.state=kVatomVoid` replaces the "kind", so that info is lost to later parts in the lifecycle. Solution: - Define `VatomState` as "flags", so `kVatomVoid` can "poison" `vatom.state` without losing its kind flag. - Unify `lhs`: always report the original user input in `CmdAtom.lhs`, for all kinds of user actions: visual, "translated"/"stuffed" cmds, and dot-repeat (".") itself. - Unreplayable Visual atom emits CmdAtom with non-empty `lhs` and empty `keys`, like a mapping/macro composite.
* perf(input): drop buffheader_T #41353Justin M. Keyes2026-08-17
| | | | | | | | | | | | | | | | | | | | | | | | | | | Problem: `buffheader_T` is a linked list (Vim's favorite data structure) with complex bookkeeping, optimized for ancient hardware: 1. Memory topology: On old platforms (Amiga 512KB without MMU, MS-DOS 64KB segments), large contiguous allocations were hazardous (no VM). 2. Never-move appends: `add_buff()` fills spare space or links a new block. 3. OOM "recovery": a failed block allocation only loses one append. To avoid OOM, it prefers to allocate small, linked chunks, instead of resizing one continguous slice. Each stuff/drain cycle costs a malloc+free. Solution: Replace Vim's favorite data structure with Nvim's favorite data structure. Nvim doesn't have granular handling of OOM (`xmalloc`), and hardware has changed: caches favor contiguous memory, allocators handle fragmentation. And these buffers are ~kb scale, so OOM is irrelevant on any system that can run Nvim. - Use a flat `StringBuilder` + `read`/`insert` offsets. - Keeps capacity (does not shrink) until `free_buff`. - "Steady state" allocates nothing: 1 fewer malloc+free per dot-repeat.
* refactor(cmdatom): drop RedoBuf #41351Justin M. Keyes2026-08-17
| | | | | | | | | | | Problem: `RedoBuf` is mostly indirection. It has a mild benefit as an "ownership" signal but it counteracts the general goal of unifying how "redo state" is passed throughout the system, tends to sprout redundant interfaces, and reduces clarity. Solution: Add `CmdSpec.body` to hold the "prefixless" key sequence. Reuse `CmdSpec` to represent a "redo" buf.
* fix(cmdatom): <Cmd> mappings #41347Justin M. Keyes2026-08-16
| | | | | | | | | | | | Problem: `<cmd>` mappings do not emit `CmdAtom.text`. `<cmd>` and Lua-callback mappings that edit the buffer apply only at the primary cursor, not cascaded (multicursor). Solution: Capture the `<cmd>` command in getcmdkeycmd(). Add kKeyOpaque ("no capturable keys"); narrow kKeySynthetic ("not a keystroke") to K_EVENT/K_IGNORE, so an opaque mapping's edit still sets `map_edit` and cascades via LHS-replay.
* refactor(cmdatom): atom_redo_keys #41345Justin M. Keyes2026-08-16
| | | | Some names/comments are misleading. Also add some asserts.
* feat(input)!: CmdAtom event #41297Justin M. Keyes2026-08-14
Problem: There is no unified notion of a "user action". Vim processes input by one-char-at-a-time, and mostly throws away any hints it might gather about the user's action, with one exception: it stores the last _edit_ action (the "redo buffer", encoded as unstructured `["x][v][count]body` bytes). Plugins can only observe individual keys (vim.on_key) and high-level effects (TextChanged, CursorMoved). Solution: - Users can subscribe to `CmdAtom` events to handle any user action. - Event is deferred; handlers cannot cancel or interfere with user actions. - Capture `CmdSpec` from the normal/insert/visual subsystems. - typeahead/readahead stay unstructured (`buffheader_T`): they are key streams, not commands. - the redo/record buffers become `StringBuilder`: fewer allocations/copies. - Repurpose the input/redo engine to accept `CmdSpec` objects. "atom": one repeatable unit of user input, as a resolved (post-mapping) keysequence plus structured fields. Only user actions, not `:normal`, API calls, or non-"t" `feedkeys`. BREAKING: dot-repeat of an Insert session, replays the entire session including cursor-moves (:help ins-repeat). BREAKING: dot-repeat of a Visual operation, replays the selection instead of operating on a fixed-size region.