Problem: Heap-buffer-overflow in spell_suggest() when the cursor is
beyond the end of the line, because a SpellFileMissing
autocommand changed the buffer (dvaave2025).
Solution: parse_spelllang() may run autocommands, so validate the cursor
position and re-take the saved position afterwards.
fixes: vim/vim#21097closes: vim/vim#21100
Supported by AI.
6073903cda
Co-authored-by: Christian Brabandt <cb@256bit.org>
Problem: Multiline messages exceeding 'cmdheight' not visible when a
mapping starts cmdline immediately after it (after 9.2.0967).
Solution: Revert patch 9.2.0967 and use a different solution (zeertzjq).
fixes: vim/vim#21098closes: vim/vim#21101fb4866a2dd
Problem: tests: test_substitute leaves swapfiles behind
Solution: Close open buffer using :bw!
be66c98572
Co-authored-by: Christian Brabandt <cb@256bit.org>
Problem: b:match_words groups "{" with the if/for/while/switch keywords
and "}" with "break" which breaks % matching on braces
Solution: Drop the brace and bracket groups, matchit appends
'matchpairs' by itself (Matthias Bruns).
matchit counts every alternative in a group instead of pairing the
alternatives with each other. Listing `{` alongside the if, for,
while, switch, struct and class keywords therefore makes a line such
as `for (...) {` count as two openers, and listing `break` alongside
`}` lets a brace pair with a break statement. As a result % on the
opening brace of a function does not move at all, and % on
`switch (x) {` jumps to `break;` instead of the closing brace.
Braces and brackets do not need to be listed: matchit appends
'matchpairs' to b:match_words by itself. Drop them and leave the
preprocessor group unchanged.
closes: vim/vim#2106408c74ce09a
Co-authored-by: Matthias Bruns <matthiasbruns35@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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: The hit-enter prompt fires whenever a message scrolls the screen.
When this happens while a mapping is being processed, it consumes
the mapping's next key, causing unexpected behavior for users.
Solution: Similar to what 9.1.1969 did for stuffed characters, skip the
hit-enter prompt when there are still keys pending from a mapping
in the typeahead buffer.
related: neovim/neovim#38298
related: neovim/neovim#20635
related: neovim/neovim#30890
closes: vim/vim#20753
AI assisted.
6025ea9e02
Co-authored-by: XiaowenHu96 <me@xiaowenhu.com>
Problem: A SAL rule longer than MAXWLEN is silently truncated to an
empty lead. set_sal_first() then reorders the sl_sal entries
by their index byte and can move the terminating sentinel out
of the last slot, so spell_soundfold_wsal() reads past the end
of the array, e.g. when soundfold() or spellsuggest() is used
(Erick Alex).
Solution: Bound the sound-folding loops against sl_sal.ga_len.
closes: vim/vim#210766ac008db96
Co-authored-by: Christian Brabandt <cb@256bit.org>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Problem: string_reduce() copies *rettv into argv[0] before calling
eval_expr_typval(). When the evaluator fails early, rettv is
never reset and still aliases argv[0] v_string.
clear_tv(&argv[0]) frees it, leaving rettv dangling and when
in vim9script get_func_tv() frees it again (Ave Dva).
Solution: Set rettv->v_type = VAR_UNKNOWN like what is done in
list_reduce() and tuple_reduce(), use tv_get_string_strict()
in f_reduce()
closes: vim/vim#21048
Supported by AI.
cd59994c45
Co-authored-by: Christian Brabandt <cb@256bit.org>
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.
Problem: transstr() has comments that do not add anything to what the
code says, and it casts a length to int only to cast it back to
size_t.
Solution: Drop the comments and keep the length in a size_t
(Hirohito Higashi).
related: vim/vim#20925
closes: vim/vim#21026fe65307d49
Co-authored-by: Hirohito Higashi <h.east.727@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Problem: sort() with "n", "N" or "f" converts an item to its number on
every comparison. For "n" that is a tv2string() plus strtod()
per comparison, so sorting a list of numbers turns each number
into a string and back O(n log n) times, dwarfing the sort.
Solution: Compute the numeric key of each item once, before the sort,
and compare the stored key (Samuel Schlesinger). Only the
builtin numeric compare modes are affected; uniq(), which
passes a bare list item to the compare function, and the
string and user-function paths are unchanged.
Sorting a list of 100000 numbers (min of 3, macOS arm64):
- sort(l, 'n'): 0.205s -> 0.017s
- sort(l, 'N'): 0.017s -> 0.010s
- sort(l, 'f'): 0.014s -> 0.010s
The result is identical, including that a string is still treated as 0
in "n" mode and that "N" keeps full 64-bit precision.
Add Test_sort_numeric_precomputed(): a large shuffled list sorted with
"n", mixed integers and floats, int64 values beyond the exact range of
a double for "N", and uniq() over the non-precomputed path.
closes: vim/vim#21003c8c59db9df
Co-authored-by: Samuel Schlesinger <sgschlesinger@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Problem: Reading an undo file resolves every stored sequence number
with a linear scan over all headers, making loading
quadratic in the number of undo states.
Solution: Sort uhp_table on uh_seq once and resolve each reference
with a binary search; the duplicate uh_seq check becomes a
single pass over the sorted table (Samuel Schlesinger).
At the default 'undolevels' of 1000 the quadratic cost is not
measurable; it takes 'undolevels' in the tens of thousands to matter.
Loading an undo file with 20000 states and 50 alternate branches with
:rundo goes from 1.49s to 0.11s (min of 3, macOS arm64), with the
same undotree().
Also make old_idx/new_idx/cur_idx and the loop index "i" long instead
of short/int: they index uhp_table, whose length num_head is a long
read from the file. A short index truncated above 32767 headers,
making the restored b_u_oldhead/b_u_newhead/b_u_curhead pointers
wrong in exactly the many-headers case this change is about.
Add tests: a round-trip test with alternate branches that compares
the entries of the tree and the text at every sequence number, a
corruption test with a duplicated uh_seq, and a test for reading an
undo file with zero headers, which is written when only the line for
the "U" command is saved.
closes: vim/vim#20942fccf613c8f
Co-authored-by: Samuel Schlesinger <sgschlesinger@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Problem: In diff mode with 'cursorbind' the cursor in the other window is
not updated after an undo that changes which lines correspond.
Solution: Also check whether the text changed before skipping the update
(Hirohito Higashi).
fixes: vim/vim#20982
related: vim/vim#13219
related: vim/vim#13210
closes: vim/vim#210042045a20d4b
Co-authored-by: Hirohito Higashi <h.east.727@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Problem: Crash when getcompletiontype()/getcompletion() gets a NULL string
(dvaave2025).
Solution: Do not write the NUL terminator in set_cmd_context() when the
cursor column is at or past the end of the string, since the
string may be a read-only literal.
fixes: vim/vim#20963closes: vim/vim#20964
Supported by AI.
e2dcefa0d8
Co-authored-by: Christian Brabandt <cb@256bit.org>
Problem: Closing the current tab page resets the alternate tab page, even
when that is another tab page which still exists, so that
CTRL-Tab stops working (igorlfs).
Solution: Restore the last used tab page after entering another one to
close the current one (Hirohito Higashi).
related: vim/vim#20965
closes: vim/vim#20973a05bd64c1d
Co-authored-by: Hirohito Higashi <h.east.727@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Problem: Messages from tests were not always displayed.
Solution: Always show messages, the timing is always useful. (Ken Takata,
closesvim/vim#6792)
6e3aeec846
Co-authored-by: Bram Moolenaar <Bram@vim.org>
Problem:
- buf-local CWD scope is lower priority than :lcd, which is weird.
```
win > buf > tab > global
```
- No way to clear current CWD at a given scope.
Solution:
- Rerrange scope precedence to:
```
buf > win > tab > global
```
- Introduce "bang" variants (`:bcd!`/`:lcd!`/`:tcd!`) which clears the
local CWD for the given scope.
Problem:
- Explicit `:bcd` (etc.) persists from `nvim_buf_call()` but not from an
autocmd handler targeting a hidden buf (`LspAttach`, `TermRequest`, …),
which needs a `vim.schedule()` workaround.
- `vim._with()` is supposed to work as a "sandbox", discarding
side-effects, but it leaks CWD changes: `:lcd` from a `win` context,
any chdir from a visible-buffer context.
Solution:
- Explicit :cd/:tcd/:bcd during a temp context persists by default.
- "Ambient" directory changes ('autochdir', existing win-local CWD,
etc.) are still undone, as before.
- Add `kCtxKeepDirs`: snapshot/restore the target's full CWD state
(w/b/tp-local, global, cwd). Used by `vim._with()` and `'inccommand'`,
which must not leak state.
Problem: After :diffget into an empty buffer, undo does not restore the
empty buffer, the last line stays behind (Narendran
Gopalakrishnan)
Solution: Include the empty line of the empty buffer in the undo
information, it is deleted once the first line was obtained
(Hirohito Higashi).
fixes: vim/vim#20950closes: vim/vim#20951c44f35ca1a
Co-authored-by: Hirohito Higashi <h.east.727@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Problem: ins_compl_add() checks for a duplicate by scanning the whole
match list, making collection of N matches quadratic.
Solution: Look matches up in a hashtab instead; each entry counts the
matches with that string (Samuel Schlesinger).
closes: vim/vim#2092631b7b1a7da
Co-authored-by: Samuel Schlesinger <sgschlesinger@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Problem: transstr() appends with STRCAT()/STRLEN() from the start of
the result on every iteration, making it quadratic to the
length of the string.
Solution: Keep a tail pointer and append at it. (Samuel Schlesinger).
closes: vim/vim#20925124c86868c
Co-authored-by: Samuel Schlesinger <sgschlesinger@gmail.com>
Problem: "zb" scrolls incorrectly with cursor just above fold.
Solution: Handle boff.lnum being set to the last line of a fold
(zeertzjq).
With the cursor just above fold, botline_forw() moves boff.lnum to the
last line of the fold, but curwin->w_botline is at the first line of the
fold, so the boff.lnum == curwin->w_botline condition never holds.
Instead, check that boff.lnum has just moved to or past w_botline by
comparing its previous value with w_botline.
Also make a similar change to the loff.lnum check above for symmetry.
That one doesn't change behavior, as topline_back() sets loff.lnum to
the first line of a fold.
related: neovim/neovim#41122
closes: vim/vim#20923aee686334c
Problem: The test comparing the top line before and after using the
autocommand window is flaky in the GUI.
Solution: Run the test in a window with a fixed size. In the GUI a
pending resize of the shell is applied at the end of a screen
update, thus the size may change between the two measurements.
related: vim/vim#20884
closes: vim/vim#2091385b8034dff
Co-authored-by: Hirohito Higashi <h.east.727@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Problem: With 'virtualedit' set to "all" and 'cursorcolumn' set, the wrong
column may be highlighted after a command that moved the cursor
into virtual space and back (van-de-bugger).
Solution: Make sure the virtual column is up to date before drawing the
window (Hirohito Higashi).
fixes: vim/vim#2576closes: vim/vim#209025a90b9dbd2
Test only. This was already fixed by #39159.
Co-authored-by: Hirohito Higashi <h.east.727@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Problem: filetype: ed script files not recognised.
Solution: Add filetype detection for *.ed files and shebang lines,
include syntax script and syntax tests.
Features of the ed syntax file:
- BSD and GNU extensions are supported
- Andrew L. Moore's ed extensions are not supported
- Rebuild synmenu.vim
closes: vim/vim#19602c28515b999
Co-authored-by: Doug Kearns <dougkearns@gmail.com>
Problem: The :compiler command causes a crash. (Daniel Steinberg)
Solution: Do not use the script index if it isn't set.
----
ex_vim9script() is N/A.
----
165315584d
Co-authored-by: Bram Moolenaar <Bram@vim.org>
Problem: With 'smoothscroll' the scroll position in a long line is lost when
a window is temporarily squeezed to a couple of lines, for example
when opening and closing a help window.
Solution: When the cursor ends up in the skipped columns, skip up to the
screen line the cursor is in instead of showing the start of the
line.
closes: vim/vim#2089215f8ba5cec
Co-authored-by: Hirohito Higashi <h.east.727@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Problem: With 'smoothscroll' the scroll position in a long line is lost when
moving to a column with "|".
Solution: Adjust the skipped columns for the column the cursor ends up in,
not for column zero.
related: vim/vim#20885
closes: vim/vim#208905ed8fc10fa
Co-authored-by: Hirohito Higashi <h.east.727@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Problem: :bwipe crashes if WinLeave wipes all other buffers
(after 9.1.2068).
Solution: Check for NULL pointer.
related: neovim/neovim#41066closes: vim/vim#208883e4019a082
Problem:
Cannot assign Lua functions/closures to "func" ('completefunc',
'tagfun', …) or "expr" ('foldexpr', 'indentexpr', …) options.
Solution:
- Store "func"/"expr" options as `Callback` instead of string.
- Delete oceans of copy-pasted code.
- BREAKING: LuaRef returned via RPC/Vimscript is now represented as
`"<Lua N: file:line>"` (like what `:map` shows) instead of `nil`.
- Note: `man.vim` still uses `v:lua` string, bc it's a vimscript ftplugin.
Helped-by: Lewis Russell <lewis6991@gmail.com>
Problem: With 'smoothscroll' the scroll position of a window is lost when
its height changes.
Solution: Only reset the skipped columns when 'smoothscroll' is off, where
they just serve to keep the cursor visible.
closes: vim/vim#2088517f3923b8c
Co-authored-by: Hirohito Higashi <h.east.727@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Problem: The window scrolls when an autocommand window is used while the
cursor is behind multi-byte characters.
Solution: Use the byte column instead of the character count when computing
how many screen lines the text up to the cursor takes.
fixes: vim/vim#12085closes: vim/vim#208847fe3ea7658
Co-authored-by: Hirohito Higashi <h.east.727@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Problem: The zip autoload script aborts loading when the "zip"
command is not available, so even read-only browsing of an
archive fails with E117 (zip#Browse undefined) on systems
that have "unzip" but not "zip" (e.g. the Windows CI
runner). Regressed in b0e0b22.
Solution: Drop the load-time executable gate and check each command
per operation instead, so a missing "zip" only affects
writing. Update the test to match the reworded message.
e241ac0a62
Co-authored-by: Christian Brabandt <cb@256bit.org>
Problem: When the only window has 'winfixheight' set and 'laststatus'
is one, splitting it leaves one screen line unused. This
happens for example when jumping to an item from a maximized
quickfix window (rendcrx)
Solution: Do not subtract the height of the status line twice
(Hirohito Higashi)
fixes: vim/vim#20495closes: vim/vim#20871ab36bcc870
Co-authored-by: Hirohito Higashi <h.east.727@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Problem: checkSmall() compares the fold size of window "wp" against
the current window's 'foldminlines'. A fold of another
window, e.g. measured while it is redrawn, is judged by an
unrelated option value.
Solution: Use 'foldminlines' of the window containing the fold
(Igor Mikushkin).
closes: vim/vim#20864773dc19f14
Co-authored-by: Igor Mikushkin <igor.mikushkin@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Problem:
The bundled `zip` plugin is implemented in Vimscript, making it harder to
maintain and build on with Nvim's Lua runtime infrastructure.
Solution:
Add an opt-out `zip.lua` browser backed by `nvim.dir`, and disable the legacy
Vimscript implementation by moving it to `pack/dist/opt/zip/`. Load it with
`:packadd zip`.
Problem: An insecurely-set 'indentexpr', 'formatexpr', 'includeexpr'
or 'complete' value can end up evaluated outside the
sandbox after buf_copy_options() and clears the flag.
Solution: Copy the insecure flag alongside the value in
buf_copy_options(), and make 'complete' a per-buffer
insecure-flags field
Supported by AI.
closes: vim/vim#2086135f7fdfdfb
I'm a bit hesitant to port this, but it's a follow-up to #39452.
Co-authored-by: Christian Brabandt <cb@256bit.org>
Problem:
Want `gQ` for _le multicursor_.
Solution:
- Don't use `gQ` for exmode.
- Introduce `:exmode`.
- Introduce `[count]q:` as an alias to `:exmode`.
Problem:
POSIX-compatible Ex-mode requires special-cases all over the codebase to
match various quirks that don't actually matter to users.
- The main utility of *interactive* Ex-mode is its REPL behavior, and
that can be achieved with `cmdwin`, which also gains extra UX
benefits.
- The main utility of *non-interactive* `nvim -es` is for shell
scripting, where Ex-mode quirks are mostly unhelpful (e.g. the
"Entering Ex mode" message).
Solution:
- Reimplement *interactive* Ex-mode as a "persistent, insert-mode
cmdwin" in Lua.
- "nvim -e/-E" is simply an alias to "gQ".
- Reframe *non-interactive* Ex-mode (`nvim -es`) as "script mode".
- Drop POSIX Ex-mode quirks.
Improvements:
- "nvim -V1 -es" output ends with a final newline!
- "nvim -V1 -es" no longer shows the "Entering Ex mode" msg. (This was
pointless noise, unwanted for scripting purposes.)
- stdin is no longer typeahead. Scripts (":lua io.read()") can read
stdin as data.
- Empty line is a no-op: a stray blank line no longer moves the cursor
(deviates from POSIX ex "+1"), no longer exits 1 at EOF (E501).
Preserved behavior:
- cursor starts at "$"
- mode()=="cv" (for non-interactive)
- multiline commands (:append/:function/heredoc pull continuation lines)
- bare-range print
- :print=>stdout
- -V1=>stderr
- CRLF input
- continue-after-error and exit codes
Dropped (regressed) POSIX behavior (non-interactive):
- Event loop only ticks while/between commands, not while blocked
waiting for a stdin line.
- ":g/pat/visual...Q"
- input()/getchar()/":s/x/y/c" no longer consume stdin lines as
answers: Nvim stops at end-of-input, skipping the rest of the script,
exit 0. Use ":lua io.read()" instead.
- If users care about this they should use interactive Ex-mode (`gQ`).
- ":@r" stops at end of the register instead of continuing to read
cmdline input from stdin.
Problem: 'showcmd' not redrawn with empty mapping triggered on timeout.
Solution: Don't postpone redraw when inside vgetorpeek(). Also move test
for tabline 'showcmd' to test_tabline.vim.
fixes: vim/vim#20839closes: vim/vim#208402e9687647a
Problem: The `showcmd` statusline item may show internal command keys when a
`<Cmd>` or `<ScriptCmd>` mapping redraws the statusline, and may
leave stale text behind when `%S` is rendered directly.
Solution: Do not add these internal mapping dispatch keys to the `showcmd`
buffer, and keep the clear state in sync when `%S` renders it
(Barrett Ruth)
closes: vim/vim#20769bd730293dc
Co-authored-by: Barrett Ruth <br@barrettruth.com>
Problem: [security]: heap buffer overflow in set_sofo()
(Yazan Balawneh)
Solution: Reset sl_sal_first (Yasuhiro Matsumoto).
A crafted spell file with an empty SN_SAL section before an SN_SOFO
section reaches set_sofo() with sl_sal_first[] already set to -1 by
set_sal_first(). The counting loop then under-counts colliding
multi-byte "from" characters, allocates an undersized list and writes
past its end.
Github Security Advisory:
https://github.com/vim/vim/security/advisories/GHSA-9jqx-hgpr-6v6405c41c9223
Co-authored-by: Yasuhiro Matsumoto <mattn.jp@gmail.com>
Problem: When a tagfunc returns a "cmd" that is neither a line number nor
a search pattern, the tag entry is corrupted: the "kind" field is
lost and taglist() returns a mangled "cmd".
Solution: Accept any Ex command in "cmd" as in a tags file, terminate a
generic command with a bar so the trailing fields are preserved,
and reject a value that cannot be stored in a tag line with E987
(Hirohito Higashi).
fixes: vim/vim#20781
related: vim/vim#20790
closes: vim/vim#2082886adef19fc
Co-authored-by: Hirohito Higashi <h.east.727@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Mao-Yining <mao.yining@outlook.com>