| Commit message (Collapse) | Author | Age |
| |
|
|
|
|
|
|
|
|
|
|
| |
Problem: Repeated default fields and field-by-field assertions obscure
the values that each functional test is meant to check.
Solution: Add eq_partial() to check selected record fields while
preserving list length and order. Use local helpers to fill fixed
expected defaults where tests need full equality. Keep explicit absence
checks and test-specific values at the call sites.
AI-assisted
|
| |
|
|
|
|
|
|
|
|
| |
Problem:
When a flush discards dot-repeat keys mid-selection, flush_buffers()
ends Visual-mode immediately, triggering `ModeChanged` autocmds from
`emsg()` (before the error is shown) or `vgetorpeek()`.
Solution:
End Visual-mode from `normal_finish_command()` by checking a flag,
similar to `Visual.restart_select`.
|
| |
|
|
|
|
|
|
|
|
|
| |
- `{Visual}Q` / `{Visual}zqj`: for linewise motion ("j"), every line
gets a cursor.
- `zq`: handle 3-key motions (`g'a`), don't scroll the view, preserve
the search direction, update existing cursors `gv` target.
- Disallow `zq` in macros.
- Abort `zq` on non-motion ("zqx").
- Classify `g'x` classify like `'x`.
- Classify `'(` `'{` etc. as `type=motion` (they are cursor-relative).
- Perf: skip save/restore of marks when cascading motions ('follow').
|
| |
|
|
|
|
|
|
|
|
|
| |
Problem:
Skipping buffer flushing in Visual mode breaks map-error and leaves failed
redo motions that raise errors stuck in Visual mode.
Solution:
Keep flushing on errors and exit Visual mode when pending redo is
discarded. Cover failed motions, unset marks, macros, and mappings.
AI-assisted
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Problem:
- `[count]Q` and `{Visual}Q` are implemented as ad hoc utils, because
`Q` is not an "operator".
- `Q` always "toggles", it is not "idempotent".
- Various *cursor-relative* marks (`'.`, `'[`) are not saved/restored
per-cursor, so they don't work during cascade.
- Insert cascade clobbers the primary's `'^` and changelist.
Solution:
- Introduce the `zq{motion}` operator, which places cursors at every
reachable `{motion}`.
- `{Visual}Q` is now an alias to `{Visual}zqj`.
- Introduce `1Q` and `2Q` for idempotent cursor placement.
- Drop `[count]Q`, it's redundant with `zqgn`.
- Classify cursor-relative marks as `type=motion` instead of
`type=jump`. Save/restore per-cursor during cascade.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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:
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:
A Visual selection produced by a Lua mapping is captured as the keys it
fed (`:norm!`), so non-primary cursors get the wrong selection.
Examples:
- API cursor-moves around a fed "o" are lost.
- `setpos("'<")` + "gv" reselects the primary's marks (treesitter `an`).
- A mapping that feeds an absolute selection (`:norm! <Esc>`, then
`norm! 3GV6G`).
Solution:
Classify fed keys as `lossy` if their produced selection differs from
the selection at mapping-end, or fed-key-start: the mapping moved it by
other means (API). Same idea as `composite.lossy` LHS-replay.
Now the captured Visual session contains the mapping's own cmd
(`<Cmd>…`, `:…`, or a Lua mapping-id) as one subatom, instead of the
keys it fed. E.g. `v…<K_LUA id>` instead of `v…V…G`.
Also affects dot-repeat (".").
A `:norm!` that ends a typed selection keeps the Visual session
(`vatom`) open (instead of the usual `atom_visual_reset()` plus
emit/stage), so the enclosing user frame (the mapping's `<Cmd>`/Lua, or
a typed `:norm!`) can complete or replace it.
- A child frame re-entering Visual does not restart it, so a later
`norm! V` continues that session instead of a new `kVatomFed` one.
- At end of the user (toplevel) frame, the fed subatoms are dropped in
favor of the mapping's own atom: as a subatom if Visual is active,
else as the session's closing suffix.
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
Problem:
`nvim_feedkeys('gj', 'm')` (remap, but not "t"=typed) emits a CmdAtom
for the mapping's insert session. Same for `feedkeys('ihello<Esc>', '')`.
Keys fed without "t" land in `tb_maplen` ("mapped prefix"), just like
a mapping's expansion, and `atom_is_user_input()` treats that as user
input.
Solution:
Narrow the `atom_is_user_input` condition. A mapping resolved from typed
keys always has a composite by the time its commands run, so
`KeyTyped || (user cmd && composite active)` is sufficient.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Problem:
- `gv` reselects only primary's region, it doesn't have per-cursor
behavior. '< '> are buffer-global and the sandbox restores the
primary's marks. The Visual atom containing `gv` was voided anyway, so
it never cascaded.
- Same for change-marks (`'[ '] '.`): they are stale after edits from
other cursors, not managed per-cursor.
- `McSandbox` saves "editor state", which should be owned by the
`Context` module.
Solution:
Lift more state from `McSandbox` into `Context`. `kCtxCursor` (position,
curswant) and `kCtxMarks` (Visual area, change-marks), are owned by
`Context` now, as an "in-memory" special-case (TODO: support it for the
"serialized" case?).
- Classify a `gv` selection as replayable. Manage its state per-cursor.
Dot-repeat "." after `gv` behaves like Vim (the "1v" fallback).
- Save/restore change-marks per-cursor. The primary's change-marks are
shifted by edits from other cursors during the cascade.
`McSandbox` still contains (for now) the view and replay isolation
(typeahead/mode, redobuff, search patterns, the active selection).
Bonus:
- `visualmode()` is per cursor.
- A cursor's `curswant` follows its column when other cursors' edits
shift it, instead of the column captured after its own replay.
|
| |
|
|
|
|
|
|
|
|
| |
Problem:
- "do"/"dp" (:diffget/:diffput) clear the "d" operator before
`do_pending_operator()`, so the atom captured only "o"/"p".
- Dot-repeat "." does not repeat do/dp (as in Vim).
Solution:
- Capture the atom (and prep its redo) before the operator is cleared.
- Support dot-repeat for do/dp (diverges from Vim).
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Problem:
`u_undo_and_forget()` and `u_forget_unchanged()` do similar undo-tree
surgery: unlink a header, promote the alt branch it displaced.
Solution:
Extract common logic to `u_forget_header()`.
Bonus:
- The `:undo!` copy now fixes `b_u_oldhead`, so after forgetting a root
header, its alt branch is still listed by `undotree()`.
- Freeing can no longer unlink the promoted branch: `uh_alt_prev` is
cleared first. The old code relied on the forgotten header being the
first sibling.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| | |
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
Problem:
A Visual selection opened programmatically (`:normal! viw`) is not
previewed at the other cursors, and a user-typed operator completing it
does not cascade.
":normal" keys run as child frames, which are never user input, so the
session they build was classified "fed" once and never revisited.
Solution:
Recalculate the session kind (`vatom.state`) on every frame. Record
`vatom.frame` (the last frame to add to the session) so an enclosing
frame does not reset or recapture it.
|
| |
|
|
|
|
|
|
|
|
|
| |
Problem:
Input (`getchar()`, `input()`, …) consumed by an expr mapping EVALUATION
can leak into `CmdAtom.keys`, depending on count placement ("2dse" vs
"d2se").
Solution:
Skip payload capture while `expr_map_lock` is active.
- Input read during expr EVALUATION is recorded in `CmdAtom.lhs`.
- Only input read by mapping EXECUTION is appended to `CmdAtom.keys`.
|
| |
|
|
|
|
|
|
|
|
| |
Problem:
"v{motion}<Esc>" moves the primary cursor to the selection end, but the
other cursors snap back to their anchors unless follow-mode ("q=") is
on. The selection preview showed them at the selection end.
Solution:
Replay the abandoned selection at every cursor, not only in follow-mode.
An abandoned typed selection now always emits a "visual" CmdAtom.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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:
The "." example mapping at `:h edit-repeat` doesn't work well with
`nvim_feedkeys(…, 'mt', false)`.
Solution:
Use `vim.b[ev.buf].maxseq` instead of `undotree()`.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Problem:
Replaying a deleted Lua mapping, may call an arbitrary function.
RHS of a Lua mapping embeds its LuaRef (`<K_LUA><ref><CR>`). The raw
keys may outlive the ref (redobuff ".", CmdAtom `keys`). If the mapping
is deleted, replaying it either (1) dereferences a freed registry slot,
or (2) calls whatever callback reused the slot (autocmd, timer, other
mapping).
Solution:
Assign a monotonic (never recycled) id to Lua mappings and encode the
mapping keys as `<K_LUA><id><CR>`.
Note: in the case of Vimscript, a deleted function raises E117, but if
the function is redefined with the same name, the mapping will find it.
Alternatives?:
- Globally ensure `LuaRef` ids are not recycled.
- Problem: could exhaust `int` in a long-lived Nvim session? Also,
difficult to impl bc the "recycling" is done by `luaL_ref` itself.
ref: 5ac2e47acc999472042df4f10f8f7b5ffa72ba3e
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Problem:
An operator completed by a Lua `:omap` textobject emits
`CmdAtom.type="mapping"` (lhs-only, no keys) instead of `type="operator"`.
Analysis:
`atom_redo_set()` declined K_LUA, though the prepped redo
("op" + K_LUA + id + CR) is exactly what "." replays. A no-edit "g@"
emits nothing at all.
Solution:
- `atom_redo_set`: don't decline K_LUA; the redo route now captures the
operator atom.
- `atom_capture_cmd`: don't early-return if the frame has prepped redo.
- op_function(): save/restore redobuff when invoking 'operatorfunc',
like `call_user_func()` does for Vimscript. (Else the Lua callback
may clobber the prepped "g@" redo / dot-repeat.)
fix #41482
TODO:
- async Lua (timer/vim.schedule) can still clobber the pending dot-repeat...
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Problem:
The Vim "stuff" concept breaks the ability to reason about the call
stack and thus the boundaries of a `CmdAtom`: a stuffed translation ("x"
=> "dl") defers to the main loop. This "continuation" must be modeled in
`CmdAtom`, by checking global flags at undefined times, during undefined
circumstances.
Solution:
- After a stuffed "translation", eagerly execute the stuff buffer
(`exec_stuffed()`).
- Delete the CmdAtom "continuation" junk.
Note:
- op_colon runs its cmdline "nested", but that's fine because operators
already nest interactive sessions there (op_change runs edit()), and
the cmdline is frameless so the operator's frame can own/capture it.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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 enters terminal-mode (`:FzfLua files` via `:startinsert`)
never reaches a composite-end: terminal-mode runs no "normal" CmdFrames.
The composite collects the entire terminal session (and more) into `lhs`.
E.g. if I have `<M-/>` mapped to open `:FzfLua files`, then interact
with fzf-lua UI, the emitted CmdAtom looks like:
<M-/>… => { type='mapping', lhs='<M-/><C-N><C-N><CR>', keys=nil }
Solution:
End the composite when terminal-mode is entered, which emits a more
meaningful and repeatable atom:
<M-/>… => { type='mapping', lhs='<M-/>', keys=nil }
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Problem:
The "!" operator stuffs its cmdline continuation (`:.,.+1!`), so its
frame ends before capture (stuff pending) and the redo-prep disappears
with it.
!ipsort<CR> => { type='excmd', lhs=':.,.+1!sort<NL>', keys=':.,.+1!sort<NL>' }
Compare to builtin "." which works bc `do_bang()` completes the redo
(`!ip` + `sort<NL>`).
Solution:
Appoint the stuffed continuation frame as the "redo-prep" frame.
!ipsort<CR> => { type='operator', operator='!', lhs='!ipsort<NL>', keys='!ipsort<NL>' }
Notes:
- atom_cmd_end(): a frame ending with stuff pending re-points its
redo-prep to the next frame.
- atom_cmd_start(): a stuffed continuation frame (`KeyStuffed`) keeps
the redo-prep; flushed stuff discards it.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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:
- Dot-repeat of a Visual selection prepared by a `<Cmd>` mapping,
results in E1255 and leaves Visual mode active.
- Visual-mode capture is implemented as a second, parallel "capture
engine": it re-composes a CmdSpec from cmdarg_T per key and decides
replayability from its own table of "void" key classes.
Solution:
- Model nested `normal_execute()` as a `CmdFrame` stack,
instead of a single module-scoped `stage`.
- Capture `<Cmd>` ":norm …" commands as subatoms from its nested frames.
- Produce every command exactly once; atom_push_raw() routes the atom to
the Visual composite while a selection is open, like it already does
for mapping composites.
- `v/pat<CR>d` is now repeatable and cascades.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
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.
|
|
|
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.
|