| Commit message (Collapse) | Author | Age |
| | |
|
| |
|
|
| |
Now it shows something like `5 lines yanked into "~my.extreg`
|
| | |
|
| | |
|
| |
|
|
| |
Alias semantis are identical to using the full extreg name.
|
| | |
|
| | |
|
| |
|
|
| |
Added a big test suite for extregs
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
vim-patch:9.2.1191: crash with "//e" when only a substitute pattern exists
Problem: Crash when using "//e" or "//" while there is a substitute
pattern but no previous search pattern (after v9.2.0374).
Solution: Use the previous pattern selected for an empty pattern
(Jonas Ornfelt).
related: vim/vim#19998
closes: vim/vim#21533
AI-assisted
https://github.com/vim/vim/commit/c58607c9a0579de0da909fc46f68144a01378af1
|
| |
|
|
|
|
|
|
|
|
|
|
| |
Problem:
After clearing cursors, "]C" "[C" do nothing, although the previous
cursor positions ("gQ") may still be useful as marks.
Solution:
If multicursor is not active, "]C" "[C" iterate the
"nvim.multicursor.last" extmarks. They also work as an operator motion
("d]C"), since there is no cascade.
AI-assisted
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Problem:
`g@<C-v>j` and `y<C-v>j` set '] one char short of the block corner, so
'operatorfunc' cannot get the block. With 'virtualedit' "block", "y"
also yanks a short last line one column narrow.
Solution:
- Decide `inclusive` from the motion's own type, before get_op_vcol()
makes it blockwise (like Vim).
- Never exclude the end of a block from '] (`g@<C-v>l`): a block
includes its corner.
Co-authored-by: IcyJerry <263879414+Icyjerry@users.noreply.github.com>
Co-authored-by: Nagato Yuzuru <nagato@yuzuru.me>
|
| |
|
|
|
|
|
|
|
|
| |
Problem:
A Visual-mode mapping that reads input (`getchar()`) does not include
that input when cascading to other cursors (they only see ESC). OTOH,
Normal-mode mappings do replay the input.
Solution:
Append the payload to the Visual session's Lua-mapping subatom, and to the
session-ending command (`:<C-u>…` mapping).
|
| |
|
|
|
|
|
|
|
|
|
| |
Problem:
- `v:cascading` is false during the Insert-mode live-preview.
- `vim.on_key()` sees the internal keys of the Visual-selection preview
and "zq" steps.
Solution:
- Set `v:cascading` during the Insert-mode preview.
- Don't send internal keys to `on_key` callbacks.
- Use `v:cascading` as the source of truth for `mc_replaying()`.
|
| |
|
|
|
|
|
|
| |
Problem:
After `/\%Vfoo<CR>`, `zqn` places no cursors. `mc_zq_target()` clears
`'< '>` before `gn` (to detect a failed match), so `\%V` never matches.
Solution:
Keep the user's `'< '>` during `gn`; detect failure by its beep.
|
| |
|
|
|
| |
Problem: No way to check if cascade is in action.
Solution: Add `v:cascading` variable.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Problem:
With multicursors, an Insert-mode edit by a Lua/`<Cmd>` mapping or API
(nvim_put(), …) is live-previewed at the other cursors, but dropped at
ESC: the cascade replays _redo_, which does not contain such edits.
Solution:
When `<Cmd>`/Lua/RPC changes the buffer during Insert-mode, sync the
previews and restart the capture (like a non-captured cursor-move). The
edit stays as text.
- The mapping runs only at the primary. Its text is mirrored as-is
(similar to insert-completion). Re-running it per cursor would require
a "partial-remap" span replay, and breaks single-session plugins
(vim.snippet).
- Not during completion; redo already captures the pre-inserted
candidate on accept.
- Text typed before the edit is kept as mirrored, not replayed, so
per-cursor effects ('textwidth' wrapping, …) don't apply to it.
Known issues:
- "o"/"O"/"cc" with 'autoindent' followed by the mapping: the per-cursor
insert-entry replay is still pending, so the edit is lost.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
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:
- At session end, `'< '>` revert to the session-start snapshot, which
drops a selection made during the session. And the snapshot may be
from another buffer.
- `zqgj` skips screen rows: `zq` tries the motion before the steps, and
those trial runs advance `w_curswant`.
Solution:
- Keep the primary's `'< '>` at session-end.
- Restore `w_curswant` after the trial runs and after each run of steps.
|
| |
|
|
|
|
|
|
|
|
| |
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`.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Problem:
- "Vjzqfx" (on a line without "x") places a cursor at the selection start.
- 'nowrapscan': `zqN` places a cursor on a non-match; `zq#` mid-word
places it mid-word.
- `zqfa` places at primary even if not on an "a".
- `zqti` stops after one step (bc "ti" before an "i" does not move).
Solution:
- `did_beep_silent`: counts beeps silenced by `emsg_silent`, so `zq`
steps can detect failed motions.
- If a `zq` step does not move the cursor, retry it from the next char.
- Decide the first `zq` step the same way as `{Visual}zq` already does:
the primary is considered a step only if the motion lands on it.
|
| |
|
|
|
|
|
|
|
|
|
| |
- `{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:
`nvim_mcursor` is not fully baked, and not strongly motivated,
currently. The main programmatic interface for cursors is the
"nvim.multicursor" extmarks namespace.
Solution:
Drop `nvim_mcursor`. Add an internal hook so that multicursor can react
when extmarks are added to the "nvim.multicursor" namespace.
TODO:
If extmarks/namespaces are clumsy to work with, we should _fix that_
rather than adding wrappers.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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:
An operator on a target that creates a Visual selection (`cgn`,
`d<C-v>j`, an omap text object like `:<C-U>normal! viw`) sets `'< '>`,
`gv` and `visualmode()` to the operator target.
This causes at least the following problems:
- clobbers the user's previous `'< '>` (annoying and usually unwanted)
- E.g. after a custom textobj, `:help text-object-define`.
- "cgn" with a `\%V` pattern can't be dot-repeated (".")
Solution:
Don't save the Visual area if the operator target created it (operator
was pending).
Also fix `an`/`in` with `'selection'=exclusive` starting from an empty
range.
|
| |
|
|
|
|
|
|
| |
Problem:
- gQ does not restore follow-mode (q=).
- Users/plugins have no way to check if follow-mode is enabled.
Solution:
Introduce buffer-local 'follow' option.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
position #42105
vim-patch:9.2.1114: Visual selection is not highlighted at the cursor position
Problem: In a terminal the character under the cursor is normally not
shown as part of the visual selection.
Solution: Don't set "noinvcur" in win_line() (Shane Harper).
The problem did not occur in the GUI, only in a terminal.
The name "noinvcur" ("don't invert the cursor") was misleading: the
variable did not affect the cursor, but whether the character under the
cursor could be displayed as part of the visual selection.
The "noinvcur" handling may have been meant to help during a visual
selection when Visual is shown in reverse video and the terminal draws
the cursor by inverting the character under it, as otherwise the
character under the cursor would not look highlighted like the rest of
the selection. But Vim cannot know in general how a terminal draws the
cursor, and when either the cursor is not drawn by inverting the
character under it or Visual is not shown in reverse video, the handling
just left a character of the visual selection unhighlighted.
closes: vim/vim#21317
https://github.com/vim/vim/commit/d10aab4ba6602f8f2f2576d2d62ceb63869db6fb
Co-authored-by: Shane Harper <shane@shaneharper.net>
AI-assisted
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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:
UI clients (such as the builtin "ui2") redraw on 'showcmd' updates. When
multicursor is active this flushes a frame per-cursor during cascade,
which causes:
- flicker
- slow performance
- renders half-applied edits, displaced cursors, Visual selections
without highlights
Solution:
Skip 'showcmd' updates during cascade.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Problem:
- The primary-overlapping cursor is synced to primary only after
cascade, so it may display at a shifted position (e.g. after "rx")
during cascade.
- LHS-replay edits apply twice if there is a primary-overlapping cursor.
Solution:
- Sync the primary-overlapping cursor _before_ replay. This ensures the
mark is correct _during_ the cascade.
- Drop the post-cascade sync; this is safe because the overlapping
mark and the primary-tracking mark are both right-gravity, so they
get shifted the same way.
- Set `origin` on LHS-replay atoms, so the cascade can compare it.
Bonus: a buffer-switching mapping no longer LHS-replays if it ends up in
a different buffer.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
Problem:
Insert-mode `<c-r>x` stuffs the literal text, so multicursor cascade
puts the same (primary) text at each cursor.
Note: `<c-r>-` happens to work because `insert_reg()` special-cases it.
Solution:
Update `insert_reg` so that it preps redo with `<c-r>x` instead of the
literal text. Update `atom_key_class` so that CTRL-R flushes the
insert-span.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Problem:
Unlike "gv", "gQ" does not restore the primary-cursor position. This is
inconvenient for workflows where you temporarily clear the cursors,
navigate somewhere, then want to return to the multicursor session.
Solution:
Store the primary-cursor position at extmark-id 1 of the
`'nvim.multicursor.last'` namespace.
Note: users that *don't* want to restore the primary-cursor position can
delete extmark-id 1 from the `'nvim.multicursor.last'` namespace just
before `gQ`. (Intentionally undocumented because this interface is not
final, and should not block Nvim 0.13 release.)
vim.keymap.set('n', 'gQ', function()
local ns = vim.api.nvim_create_namespace('nvim.multicursor.last')
pcall(vim.api.nvim_buf_del_extmark, 0, ns, 1)
return 'gQ'
end, { expr = true })
|
| |
|
|
|
|
|
| |
Problem:
gQ is nonsense during an active multicursor session.
Solution:
Make it a no-op.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
Problem:
Jumps (intentionally) don't cascade, thus they don't queue atoms. But in
follow-mode, a mapping that moves the cursor but queues no atoms,
LHS-cascades (this is a general fallback for the case where `keys`
cannot be resolved). Thus `nnoremap <Down> ]C` replays `]C` at each
cursor, which causes overlapping cursors, which are then merged/deduped.
Solution:
If a composite contains a "jump", set `composite.queued` to disable
LHS-replay.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
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:
In follow-mode, a mapping that moves the cursor but doesn't queue an
atom, cascades LHS. Undo moves the cursor and (intentionally) does not
queue an atom, so `nnoremap u u` replays undo per-cursor.
Solution:
- Set `composite.queued`, to skip LHS-cascade.
- Also treat "U" (line-undo) as a "global op".
|
| |
|
|
|
|
|
|
|
|
|
| |
Problem:
A cursor overlapping the primary is deduped by comparing positions at
the clock edge, i.e. after the primary edit executed. A "replace" at the
cursor cell ("rx", "~", "g~iw") shifts the overlapping extmark, so it no
longer matches and gets its own replay. E.g. "vipQg~~" applies twice to
the 2nd line, which inverts the effect.
Solution:
Decide "overlaps the primary" at cmd start, before the edit.
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
Problem:
Jumping to a cursor adds one at the old position, so navigation has a
side-effect. "]CQ" no longer deletes a cursor, and returning with "[C"
after wandering off adds a cursor wherever you were.
This is useful in some cases, but as a default it is surprising. The
default behavior should be simple and composable.
Solution:
Revert the "]C"/"[C" cursor-plopping behavior.
Document a mapping for the "rotate" behavior.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
|