Skip to content

Stop relying on Vim mark for post-action cursor restore - #1682

Merged
SirVer merged 3 commits into
masterfrom
sirver/post-expand-mark-on-neovim
May 14, 2026
Merged

SirVer merged 3 commits into
masterfrom
sirver/post-expand-mark-on-neovim

Conversation

@SirVer

@SirVer SirVer commented May 14, 2026 •

Copy link
Copy Markdown
Owner

_execute_action (pythonx/UltiSnips/snippet/definition/base.py) used to set Vim mark ` at the cursor before the action ran and consult getpos() afterwards to restore the cursor (and to detect that the action mutated the cursor's line without setting snip.cursor). Neovim clears marks on a line when that line is replaced via the Python buffer API, so any post_expand action that rewrote the snippet_end line raised a spurious "line under the cursor was modified" - the reason SnippetActions_PostActionModifiesCharAfterSnippet carried a skip_if on Neovim.

The mark wasn't necessary for the actual cursor restore. Vim and Neovim both auto-adjust vim.current.window.cursor across buffer edits inside the action (lines inserted above shift the cursor down, etc.), so by the time _eval_code returns the cursor is already where it should be. The only thing the mark was buying us was the "did the line content under the cursor change?" guard.

Drop the mark machinery and compare line_till_cursor before and after the action directly: if snip.cursor is set, honor it; else, if the line content under the cursor changed, raise the same PebkacError as before; otherwise leave the cursor at whatever Vim/Neovim auto-adjusted to. SnippetActions_PostActionCanUseSnippetRange (action inserts lines before the snippet) and SnippetActions_ErrorOnModificationSnippetLine (:normal dd deletes cursor's line) keep their previous behaviour, and the skip_if on SnippetActions_PostActionModifiesCharAfterSnippet is dropped — the test now runs on both Vim and Neovim.

SirVer added 3 commits May 14, 2026 19:48
`_execute_action` used to set Vim mark `\`` at the cursor pre-action
and consult `getpos("'\`")` afterwards to restore the cursor (and to
detect that the action modified the cursor's line without setting
`snip.cursor`). Neovim clears marks on a line when that line is
replaced via the Python buffer API, so any action that rewrote the
`snippet_end` line raised a spurious "line under the cursor was
modified" — see the skip_if on
`SnippetActions_PostActionModifiesCharAfterSnippet`.

The mark wasn't load-bearing for the actual cursor restore — Vim and
Neovim both auto-adjust `vim.current.window.cursor` across buffer
edits inside the action (lines inserted above shift the cursor down,
etc.), so by the time we return from `_eval_code` the cursor is
already where it should be. All we needed the mark for was the
content-equality check.

Drop the mark machinery in `_execute_action`. Compare
`line_till_cursor` before and after the action directly:
- if `snip.cursor` is set, honor it;
- else, if the line content under the cursor changed, raise the same
  PebkacError as before;
- otherwise leave the cursor at whatever Vim/Neovim auto-adjusted to.

`SnippetActions_PostActionCanUseSnippetRange` (action inserts lines
before the snippet) and `SnippetActions_ErrorOnModificationSnippetLine`
(`:normal dd` deletes cursor's line) keep working, and the skip_if on
`SnippetActions_PostActionModifiesCharAfterSnippet` is dropped — the
test now runs on both Vim and Neovim.
CI failed `SnippetActions_ErrorOnBufferModificationThroughCommand` and
relatives because dropping the Vim mark removed the line-shift
adjustment we needed for actions like `vim.command('normal O')`: the
mark used to track the cursor's *content* across line insertions
above, so the line-content equality check below it passed and
`validate_buffer()` got a chance to fire its "changes are
untrackable" PebkacError on context exit.

Reintroduce the mark for the Vim-survives path, but treat a cleared
mark (Neovim's nvim_buf_set_lines drops marks when the cursor's line
is replaced via the buffer API) as "stay where the action left us"
instead of as "cursor invalid". The line_till_cursor comparison
below still catches actions that actually mutated the cursor's line
content without setting `snip.cursor`, so the
`SnippetActions_PostActionModifiesCharAfterSnippet` test that
motivated this branch still passes on both flavours.
@SirVer
SirVer merged commit 1d971e8 into master May 14, 2026
15 checks passed
@SirVer
SirVer deleted the sirver/post-expand-mark-on-neovim branch May 14, 2026 19:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant