Commit Graph

23 Commits

Author SHA1 Message Date
Justin M. Keyes
d24cafc06a fix(multicursor): failed motion at primary is still cascaded #41812
Problem:
In follow-mode, a motion that fails at the primary cursor (e.g. "j" at
EOB) still cascades to other cursors, so they move while the primary
does not, which causes them to be deduplicated...

Also affects Visual sessions: "Vjd" at EOB deletes 1 line at the primary
but (potentially) 2 lines at every multicursor.

Solution:
If a motion "beeps", don't replay it.

"3w" near the end still replays (it moved), as does "0" at column
0 (does not beep). This feels like a usable and intuitive compromise.
2026-09-09 12:10:01 -04:00
Justin M. Keyes
cb2e9cdfd3 fix(multicursor): clipboard=unnamed, explicit "+yy #41804
Problem:
- With `clipboard=unnamed[plus]`, per-cursor yanks are joined into '"'
  on exit, but not the clipboard.
- Multicursor `"+yy` calls the clipboard provider per-cursor, behaving
  as "last wins".

Solution:
- Never cascade to/from the clipboard provider.
- Implicit clipboard (`clipboard=unnamed[plus]`) uses the cursor-local
  unnamed register, so the cascade needs no `start_batch_changes()`. On
  exit, the clipboard gets the same joined result as the `"` register.
- Operations that reference the explicit clipboard registers `"+` / `"*`
  while multicursor is active, use the cached (primary) clipboard value.
2026-09-09 07:25:32 -04:00
Justin M. Keyes
493a4dd571 fix(multicursor): mapping that creates initial cursors is not replayed #41788
Problem:
A Visual-mode mapping that creates cursors (`xmap I Q0i`) replays its
commands only if cursors already existed when it started.

Solution:
Always collect `composite`. Consumers are still decided per frame
(`CmdFrame.consumers`) and at emit/cascade.
2026-09-08 12:05:34 -04:00
Justin M. Keyes
6cbde6a95f fix(multicursor): Visual-mode mapping loses its commands #41741
Problem:
A Visual-mode mapping that leaves Visual mode ("xmap I Q0i") does not
replay its motions/edits at the extra cursors, though an insert session
it starts does.

atom_map_start() skips while Visual is active, so the mapping has no
`composite`, and atom_capturable() then rejects its commands.

Solution:
Open the `composite` for Visual-mode mappings too.

Bonus: the mapping atom is now reported as:

    before: type=visual,  keys="viwcFOO<Esc>"
    after:  type=mapping, keys="viwc<Esc>iFOO<Esc>", lhs=",c"
2026-09-06 18:50:48 +00:00
Justin M. Keyes
2ac9dfc45d fix(multicursor): live-preview ":norm" visual selection #41736
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.
2026-09-06 17:15:48 +00:00
Justin M. Keyes
bd43394d4e fix(multicursor): skip insert live-mirror on conflicting edits #41730
Problem:
Cursors placed same-line before the primary, or producing multiline
edits on lines above the primary, shift the text in a way that breaks
the insert-mode live-mirroring.

Solution:
Skip live-mirroring under those conditions.

TODO (future): we could support live-mirroring if ins-completion's
internal bookkeeping tracked its regions more precisely.
2026-09-06 10:04:47 -04:00
Justin M. Keyes
455b6a810c fix(multicursor): cursors may be "deduped" incorrectly #41721
Problem:
Freeing an unrelated buffer deletes the cursor under the primary.
Scratch-buffer cleanup triggers this unexpectedly.

Solution:
Separate dead-cursor cleanup from dedupe/merge. Merge overlapping
cursors only at cascade boundaries, not during buffer/namespace cleanup
or cursor removal.
2026-09-05 13:18:22 -04:00
Justin M. Keyes
0c642196b9 fix(multicursor): cascade mapping atoms with insert entry #41714
Problem:
Pending mapping atoms prevent live insert-cascading (`nnoremap i ^i`).
Flushing them at insert-session start (3a02a39957) adds a separate
cascade boundary, which muddles the architecture.

Solution:
Let the first insert-span cascade pending mapping atoms on insert-entry.
This is an alternative fix for #41692.

Composite children (`.atoms`) BEFORE:

    { { 'motion', '^' }, { 'insert', '1iX<Esc>' } }

Composite children (`.atoms`) AFTER:

    { { 'motion', '^' }, { 'insert', '1i<Esc>' }, { 'insert', 'iX<Esc>' } }
2026-09-05 09:53:41 -04:00
Justin M. Keyes
3a02a39957 fix(multicursor): live-mirror mapped Insert entry #41692
Problem:
A Normal-mode mapping that moves before entering Insert, does not
live-mirror. Its pending motion defers insert-cascade until Insert ends.

Solution:
Cascade pending atoms before the Insert entry replay, then start the
live insert span.
2026-09-04 20:03:19 -04:00
Samiul Islam
b7a0c71a54 fix(multicursor): prevent clipboard crash after cursor jump #41698
Problem:
Changing from an empty-line cursor after a multicursor jump can crash
while flushing a deferred clipboard update. Exact context restore can
leave an omitted register with a null array and stale non-zero size.

Solution:
Make free_register() fully reset the register after freeing its contents,
so it always leaves a valid empty register. Add a regression test.

Signed-off-by: sami <samiulsami7786@gmail.com>
2026-09-04 13:06:50 -04:00
Justin M. Keyes
2124f968a6 feat(multicursor): "]C"/"[C" leave a cursor at old position #41681
Problem:
`]C` jumps the primary onto another cursor, which dedupes at the next
edit. Every `]C` consumes a cursor.

Solution:
`]C` adds a cursor at the current position before jumping. This
effectively "rotates" the primary cursor.
2026-09-04 06:28:42 -04:00
Justin M. Keyes
18f5d47543 fix(multicursor): follow cursor-move by API #41675
Problem:
A non-edit mapping that moves the cursor by API (Lua,
`:call nvim_win_set_cursor()`, …) is not cascaded.

Solution:
Fallback to LHS-replay if the cursor moved and follow-mode is enabled.
2026-09-04 07:37:48 +00:00
Justin M. Keyes
5209695703 fix(multicursor): abandoned Visual selection does not move cursors #41670
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.
2026-09-03 15:20:30 -04:00
Justin M. Keyes
da4355ab8f fix(multicursor): 'guicursor' uses Normal shape in Visual mode #41669
Problem:
'guicursor' is not respected when extra cursors exist.

Per-cursor selection toggles Visual off, and the sandbox restores it
only after restore_current_state(), whose ui_cursor_shape() caches the
Normal shape.

Solution:
Restore Visual before restore_current_state().
2026-09-03 13:22:50 -04:00
Justin M. Keyes
e1a28e52b2 fix(editor): shada "force" load clears v:oldfiles #41662
Problem:
`shada_read()` rebuilds `v:oldfiles` for any "forced" read, which first
*clears* the list. For an in-memory caller such as multicursor, the list
is never repopulated. So `v:oldfiles` is empty after `Q` + edit.

Solution:
Rebuild `v:oldfiles` only when `kShaDaGetOldfiles` is requested.
`shada_read_everything()` (`:rshada[!]`, startup) always requests it.

old bug from 411a06c8b6
2026-09-03 11:32:20 -04:00
Justin M. Keyes
3beb15da57 fix(multicursor): live-mirror 'autocomplete' <BS> #41660
Problem:
During 'autocomplete', a completion-ending `<BS>` restarts completion
immediately. Multicursor insert-cascade skips replay (`edit()` refuses
to nest if compl/pum is active), so the mcursors do not update until
ESC.

Solution:
Flush pending keys in `TRIGGER_AUTOCOMPLETE`, just before autocomplete
restarts again.
2026-09-03 10:28:17 -04:00
Justin M. Keyes
e1cde28ba2 fix(multicursor): live-mirror Visual-mode operator mapping #41655
Problem:
Insert entered by a Visual-mode operator mapping (`xnoremap c c`) does
not live-mirror at cursors.

Solution:
Decide `typed` from the Visual session's own provenance (`vatom`, which
spans the selection and knows whether it was user input) instead of only
negating it.
2026-09-03 08:44:22 -04:00
Justin M. Keyes
f1d89e874a fix(multicursor): 'operatorfunc' motion not followed #41627
Problem:
With follow-mode ("q="), an 'operatorfunc' that only moves the cursor
does not cascade.

Solution:
Update the `effect` condition to include cursor-moves if follow-mode is
active.
2026-09-02 12:00:54 -04:00
Justin M. Keyes
c75aae2bfb fix(multicursor): 'autocomplete' during insert-cascade raises E565 #41625
Problem:
With mcursors and 'autocomplete', a non-literal key (`<BS>`) during an
active completion attempts to insert-cascade, which attempts a nested
`edit()`, which refuses with E565.

Solution:
Defer the flush while completion is active; the pending keys will be
handled later.
2026-09-02 11:20:55 -04:00
Justin M. Keyes
80e9708afa fix(multicursor): atom may cascade in another buffer #41624
Problem:
An atom queued in buffer A cascades on B's cursors if the mapping ends
in B ("nnoremap X x:bnext<CR>").

Solution:
Check the atom's origin buffer (`CmdAtom.origin.buf`).

Note: This does not preclude mappings etc from doing work in temporary
throwaway buffers, as long as they return to the origin buffer.
2026-09-02 10:38:27 -04:00
Justin M. Keyes
350fa5ad7c fix(multicursor): act on fold contents, not the fold itself #41622
Problem:
If an edit cascades to cursor(s) that happen to be within a closed fold,
the edit acts on the fold itself (`:h fold-behavior`). E.g. "dd" deletes
the entire fold, not the cursor's line within the fold.

Solution:
Check `mc_replaying()` in `hasAnyFolding()`, so folds are ignored during
a multicursor cascade. This means multicursor always replays relative to
text *within* the fold.
2026-09-02 09:42:40 -04:00
Justin M. Keyes
7168e0d12f fix(mcursor): drop q= "follow motion" message 2026-09-02 13:09:30 +02:00
Justin M. Keyes
9a29622b54 feat(multicursor): MC HAMMER #41587
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().
2026-09-01 15:17:22 +00:00