| Commit message (Collapse) | Author | Age |
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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().
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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}
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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)
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
| |
Some names/comments are misleading.
Also add some asserts.
|
|
|
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.
|