Commit Graph

37758 Commits

Author SHA1 Message Date
Justin M. Keyes
73e766f8f6 fix(lifecycle): json_create_config leak
Problem:
As it turns out, when you have 17 different ways to "exit", you need to
plug 17 different holes.

Leak still reported by ASAN CI, even after 1eae512285 f238788601.

    ERROR: LeakSanitizer: detected memory leaks
        strbuf_init src/cjson/strbuf.c:62:22
        json_create_config src/cjson/lua_cjson.c:635:5
        lua_cjson_new src/cjson/lua_cjson.c:2060:5
        nlua_state_add_stdlib src/nvim/lua/stdlib.c:840:3
        nlua_state_init src/nvim/lua/executor.c:973:3

Analysis:
`os_breakcheck()` may process a `exit_event` placed by
`exit_on_closed_chan()` during shutdown. This would re-enter
`preserve_exit()`, which then skips everything because it sees
`really_exiting=true`.

Solution:
Set `exiting` in `preserve_exit()`, not only in `getout()`.
2026-08-18 21:43:35 +02:00
Justin M. Keyes
c184ca2c68 fix(lifecycle): ignore signals during os_exit
Problem:
SIGTERM received during `os_exit()` can cause it to "nest":

    os_exit src/nvim/main.c:720
    preserve_exit src/nvim/main.c:910
    deadly_signal src/nvim/os/signal.c:205
    on_signal src/nvim/os/signal.c:240
    signal_event src/nvim/event/signal.c:45
    multiqueue_process_events src/nvim/event/multiqueue.c:159
    event_teardown src/nvim/main.c:178
    os_exit src/nvim/main.c:720

Solution:
Call `signal_reject_deadly()` at the start of `os_exit()` like
`preserve_exit()` already does.
2026-08-18 18:04:19 +02:00
Justin M. Keyes
f238788601 fix(tui): ui may attempt reattach during shutdown #41368
Problem:
TSan CI, always at exit:

    WARNING: ThreadSanitizer: use of an invalid mutex (e.g. uninitialized or destroyed)
        #1 uv_mutex_lock
        #2 tui_flush src/nvim/tui/tui.c:1708
        #3 ui_client_event_flush
        #4 parse_msgpack src/nvim/msgpack_rpc/channel.c:259
        ...
        #10 event_teardown src/nvim/main.c:187
        #11 os_exit src/nvim/main.c:720
        ...
        #19 tinput_done_event src/nvim/tui/input.c:184

Analysis (guess):
`rpc_close_event()` may run during `event_teardown()` and reattach to
a restarted server, then `ui_client_attached=true` gets set and redraws
resume in an already-stopped TUI.

Solution:
Check `!exiting` in `rpc_close_event`, like we already do in
`channel_proc_exit_cb()`.
2026-08-18 11:38:52 -04:00
Justin M. Keyes
0c091cedc2 test(tui): unreliable "TUI exits immediately when stdin is closed" #41367
Problem:
Unreliable test on slow CI (ASAN/TSAN):

    FAILED  .../tui_spec.lua @ 3054: TUI exits immediately when stdin is closed
    retry() attempts: 1
    Expected: vim.NIL
    Actual: { name = "nvim", pid = 33201, ppid = -1 }

The test asserts "immediate" exit of the Nvim process, but this may be
subject to OS delays outside of our control.

Solution:
Make the "timed out waiting for DA1" log conditional on the actual
timeout, and assert the logs in the test.
2026-08-18 09:22:05 -04:00
Goldpigg
ab82c2c6b0 feat(extmark): virt_lines can highlight until EOL #41289 2026-08-18 08:35:22 -04:00
Justin M. Keyes
1eae512285 fix(lifecycle): Lua state not freed if free_all_mem() is re-entered #41366
Problem:
Leak reported by ASAN CI, always at Lua state init.

      ERROR: LeakSanitizer: detected memory leaks
          strbuf_init src/cjson/strbuf.c:62:22
          json_create_config src/cjson/lua_cjson.c:635:5
          lua_cjson_new src/cjson/lua_cjson.c:2060:5
          nlua_state_add_stdlib src/nvim/lua/stdlib.c:840:3
          nlua_state_init src/nvim/lua/executor.c:973:3
      ...
      SUMMARY: AddressSanitizer: 1055 byte(s) leaked in 2 allocation(s).

Analysis:
`getout()` may run during `free_all_mem()` (that's why it checks
`!entered_free_all_mem`). If that happens, the second `os_exit()`
returns early from `free_all_mem()` then `exit()` is called and Lua
state is never closed.

Solution:
Always call `nlua_free_all_mem()` in the early-return case.
2026-08-18 08:30:42 -04:00
Justin M. Keyes
f6bf814378 fixfix(cmdatom): repeat Visual <Cmd>; CmdFrame + stage #41355
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.
2026-08-18 07:50:27 -04:00
Justin M. Keyes
c5ea9ca2ad fix(zip): missed "password:" prompt if output trails it #41364
Problem:
Flaky test:

    RUN      T1158 nvim.zip reports an incorrect archive password: 11092.84 ms FAIL

The prompt is detected only if the pty output *ends* with "password: ",
but a read may return the prompt plus following bytes.

Solution:
- Match anywhere in the output since the last password was sent. The
  buffer is cleared before each send, so won't match stale text.
- Assert on the reported message, so a failure shows what was reported.
2026-08-18 06:23:59 -04:00
Marcus Caisey
d9b4fb1273 feat(lsp): fallback to textDocument/formatting from vim.lsp.formatexpr #40079
Problem:
With the addition of the `:help al` text object, you can now easily
format the whole buffer with `gqal`. However, `vim.lsp.formatexpr` only
uses `textDocument/rangeFormatting` which some language servers (like
gopls) don't support.

Solution:
- Fall back to `textDocument/formatting` if the whole buffer is being formatted
  and the server doesn't support `textDocument/rangeFormatting`.
  - In theory, these two methods should return the same response if the whole
    buffer is being formatted, but I preserved the existing behaviour of
    prioritising `textDocument/rangeFormatting` in case that does not hold (i.e.
    LS bug).
- Also: `vim.lsp.formatexpr` had no tests at all, so actually add tests for it.
2026-08-18 05:16:16 -04:00
Justin M. Keyes
27d40762bc Merge #41350 from janlazo/vim-8.1.1880
vim-patch:8.1.1880,9.0.0885
2026-08-18 04:35:47 -04:00
Max Coplan
750c665ab3 build(zig): keep all symbols for native Lua modules #41361
Native Lua modules loaded with `require` or `package.loadlib` resolve Lua
C API symbols from the nvim executable itself. Those symbols must stay in
the binary even when nothing inside nvim references them.

`rdynamic` is enough on Linux. On macOS the linker dead-strips
unreferenced symbols before rdynamic exports them, so `_lua_tothread` is
missing from the executable and loading such a module fails with:

    symbol not found in flat namespace '_lua_tothread'

Disable section garbage collection so all non-static symbols are kept.
2026-08-18 04:16:35 -04:00
github-actions[bot]
4a77d1e1db docs: update version.c #41307
vim-patch:8.2.0514: several global functions are used in only one file
vim-patch:8.2.1814: missing change to remove "static"
vim-patch:8.2.2860: adding a text property causes the whole window to be redawn
vim-patch:8.2.3276: Vim9: exists() can only be evaluated at runtime
vim-patch:8.2.3753: Vim9: function unreferenced while called is never deleted
vim-patch:8.2.4669: in compiled code len('string') is not inlined
vim-patch:9.0.1821: Vim9 constructors are always static
vim-patch:9.0.1953: Misplaced comment in errors.h
vim-patch:9.1.0349: Vim9: need static type for typealias
vim-patch:9.1.0584: Warning about redeclaring f_id() non-static
vim-patch:ae3a8664b runtime(doc): Add explanation for Vim's IME
vim-patch:9.1.2033: tests: Test_terminal_cwd flaky when using ConPTY
vim-patch:9.2.0026: The ss_pending_cmds variable is visible globally
vim-patch:9.2.0643: Missing Image ifdefs
vim-patch:381da9f85 CI: Bump github/codeql-action
vim-patch:9.2.0965: GTK4: blurry text rendering
vim-patch:9.2.0966: GTK4: window opens two lines too small
vim-patch:0b7700e1a runtime(doc): add PREVENT LOADING section for pi_getscript.txt
vim-patch:068657ffc runtime(vimlogo): remove coincident lines and reshade vimlogo.svg

vim-patch:8.2.0149: maintaining a Vim9 branch separately is more work
vim-patch:8.2.0173: build fails with old compiler
vim-patch:8.2.0191: cannot put a terminal in a popup window
vim-patch:8.2.0194: some commands can cause problems in terminal popup
vim-patch:8.2.0196: blocking commands for a finished job in a popup window
vim-patch:8.2.0202: when 'lazyredraw' is set the window title may not be updated
vim-patch:8.2.0204: crash when using winnr('j') in a popup window
vim-patch:8.2.0320: no Haiku support
vim-patch:8.2.0328: no redraw when leaving term-normal mode in popup terminal
vim-patch:8.2.0364: printf test failing on Haiku
vim-patch:8.2.0367: can use :pedit in a popup window
vim-patch:8.2.0399: various memory leaks
vim-patch:8.2.0516: client-server code is spread out
vim-patch:8.2.0557: no IPv6 support for channels
vim-patch:8.2.0574: ipv6 feature not shown in :version output
vim-patch:8.2.0588: Putty does not use "sgr" 'ttymouse' by default
vim-patch:8.2.0596: crash in test49
vim-patch:8.2.0611: Vim9: no check for space before #comment
vim-patch:8.2.0613: Vim9: no check for space before #comment
vim-patch:8.2.0624: Vim9: no check for space before #comment
vim-patch:8.2.0641: Vim9:  not expanded in :hardcopy and syn-include
vim-patch:8.2.0650: Vim9: script function can be deleted
vim-patch:8.2.0653: using uninitialized pointer
vim-patch:8.2.0656: MS-Windows: redrawing right screen edge may not be needed
vim-patch:8.2.0694: Haiku: channel and terminal do not work
vim-patch:8.2.0718: gcc warning for returning pointer to local variable
vim-patch:8.2.0747: cannot forcefully close all popups
vim-patch:8.2.0748: cannot get a list of all popups
vim-patch:8.2.1601: Vim9: cannot use 'true" with garbagecollect()
vim-patch:8.2.1602: Vim9: cannot use 'true" with getbufinfo()
vim-patch:8.2.1603: Vim9: cannot use "true" with getchar()
vim-patch:8.2.1604: Vim9: cannot use "true" with getcompletion()
vim-patch:8.2.1606: Vim9: cannot use "true" with has()
vim-patch:8.2.1610: Vim9: cannot pass "true" to list2str() and str2list()
vim-patch:8.2.1611: Vim9: cannot pass "true" to nr2char()
vim-patch:8.2.1614: Vim9: cannot pass "true" to searchcount()
vim-patch:8.2.1615: Vim9: cannot pass "true" to searchdecl()
vim-patch:8.2.1616: Vim9: cannot pass "true" to synID()
vim-patch:8.2.1617: Vim9: cannot pass "true" to win_splitmove()
vim-patch:8.2.1619: Vim9: cannot pass "true" to spellsuggest()
vim-patch:8.2.4597: LuaV_debug() not covered by tests
vim-patch:8.2.4648: handling LSP messages is a bit slow
vim-patch:8.2.4650: "import autoload" only works with using 'runtimepath'
vim-patch:8.2.4653: "import autoload" does not check the file name

vim-patch:8.2.0528: Vim9: function arguments insufficiently tested
vim-patch:9.0.0375: the footer feature is unused
vim-patch:9.1.0202: leaking memory in add_user() on failure
2026-08-18 03:32:47 -04:00
zeertzjq
82c751db4e vim-patch:9.2.0967: hit-enter prompt eats keys from a running mapping (#41359)
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>
2026-08-18 09:23:34 +08:00
zeertzjq
bce3bf06a3 vim-patch:9.2.0963: crash when sound-folding a crafted spell file (#41358)
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#21076

6ac008db96

Co-authored-by: Christian Brabandt <cb@256bit.org>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-18 09:03:37 +08:00
zeertzjq
cc25b2f13c vim-patch:9.2.0960: double-free in string_reduce() (#41357)
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>
2026-08-18 09:03:27 +08:00
Justin M. Keyes
b107154ba2 docs: misc, cwd, vimscript.txt #41356
Extract vimscript.txt from repeat.txt
2026-08-17 13:37:08 -04:00
Jan Edmund Lazo
a7ca7ed318 vim-patch:9.0.0885: informational message has an error message number
Problem:    Informational message has an error message number.
Solution:   Use a message without an error number. (closes vim/vim#11530)

b53a190e9f

Co-authored-by: Bram Moolenaar <Bram@vim.org>
2026-08-17 09:33:44 -04:00
Rob Pilling
a4aa0417cf feat(ui2): drop default enter-pager-via-CR mapping #40993 2026-08-17 08:03:29 -04:00
Justin M. Keyes
98053bfecf perf(input): drop buffheader_T #41353
Problem:
`buffheader_T` is a linked list (Vim's favorite data structure) with
complex bookkeeping, optimized for ancient hardware:

1. Memory topology: On old platforms (Amiga 512KB without MMU, MS-DOS
   64KB segments), large contiguous allocations were hazardous (no VM).
2. Never-move appends: `add_buff()` fills spare space or links a new
   block.
3. OOM "recovery": a failed block allocation only loses one append.

To avoid OOM, it prefers to allocate small, linked chunks, instead of
resizing one continguous slice. Each stuff/drain cycle costs
a malloc+free.

Solution:
Replace Vim's favorite data structure with Nvim's favorite data structure.

Nvim doesn't have granular handling of OOM (`xmalloc`), and hardware has
changed: caches favor contiguous memory, allocators handle
fragmentation. And these buffers are ~kb scale, so OOM is irrelevant on
any system that can run Nvim.

- Use a flat `StringBuilder` + `read`/`insert` offsets.
- Keeps capacity (does not shrink) until `free_buff`.
- "Steady state" allocates nothing: 1 fewer malloc+free per dot-repeat.
2026-08-17 07:55:47 -04:00
Justin M. Keyes
5f07fc91c2 refactor(cmdatom): drop RedoBuf #41351
Problem:
`RedoBuf` is mostly indirection. It has a mild benefit as an "ownership"
signal but it counteracts the general goal of unifying how "redo state"
is passed throughout the system, tends to sprout redundant interfaces,
and reduces clarity.

Solution:
Add `CmdSpec.body` to hold the "prefixless" key sequence.
Reuse `CmdSpec` to represent a "redo" buf.
2026-08-17 05:37:06 -04:00
Jan Edmund Lazo
976d05b083 vim-patch:8.1.1880: cannot show extra info for completion in a popup window
Problem:    Cannot show extra info for completion in a popup window.
Solution:   Add the "popup" entry in 'completeopt'.

576a4a6ff1

Co-authored-by: Bram Moolenaar <Bram@vim.org>
2026-08-17 02:13:09 -04:00
Justin M. Keyes
6947c8501d Merge #41349 from janlazo/na-patch-8.2.0000
build(vim-patch): n/a patch for error messages, FEAT_ guards, vim9 interface/types, static funcs
2026-08-17 02:07:15 -04:00
Justin M. Keyes
ea6abf15fe refactor(cmdatom): do "redo prep" in one place #41348
Problem:
Redo prep is scattered/duplicated.
- `do_pending_operator()` has 3 prep blocks whose conditions must be in
  sync with `atom_capture_op()`.
- insert.c, spell_suggest() hand-roll `redo_new()` + `redo_append_xx()`
  sequences.
- prep_redo() has 2 roles, decided by `keys != NULL`.

Solution:
- `atom_capture_op()` is the "operator" entry point: capture, then
  prep.
- Extract `prep_redo_visual()`, `atom_capturable()`.
2026-08-17 01:55:14 -04:00
Jan Edmund Lazo
e96be45bba build(vim-patch): v8.2.4153 is n/a 2026-08-16 23:28:43 -04:00
not_compiled
8c0bf18374 fix(spell): avoid invalid window state after async spell select (#41346)
Problem:
When `z=` delegates to `vim.ui.select()`, the picker may change the
current window before returning. `spell_suggest()` then continues to the
cursor restoration branch with the new window and assigns `prev_cursor`,
which belongs to the original window. This can leave Normal mode with an
invalid cursor position and produce E315.

Solution:
Clean up the spell suggestion state and return immediately after handing
control to `vim.ui.select()`.
2026-08-17 11:16:11 +08:00
Jan Edmund Lazo
cd71a13589 build(vim-patch): auto-n/a refactor patch like v8.2.0514
Follow-up refactor patches, mixed with unrelated patches in the middle,
is normal in vim-dev when the original patch is not fully tested
across all builds via CI or reviewed by others.

Unless core maintainers push "vim-patch:" directly to master/main branch
without running the full test-suite,
there is little reason to merge incomplete ports that will fail
on Nvim's CI or code review.

Relevant changes in patches like v8.2.0514 are either ported
or (will) become N/A.
2026-08-16 22:23:06 -04:00
Jan Edmund Lazo
19a7f26a9c build(vim-patch): n/a docs for v8.1.2219
Ignoring incompatible implementation,
TerminalOpen and TermOpen events are not 1-1.
"TerminalWinOpen" was accepted as N/A in
commit c7ee6af777 .

I planned to not do this to have more test cases
after manipulating the hunks header to filter out more hunks
but Justin is eager to just mark these N/A
to bump the Vim major.minor version.

Time to move on from v8.1.x.
2026-08-16 22:23:05 -04:00
Jan Edmund Lazo
833e58341c build(vim-patch): n/a ":terminal" opts
Target v8.1.2195 .

Nvim did not port Vim's ":terminal" opts.
Incompatible implementations.
Ex-command was ported from C to Lua.
Vim needs them partly because of splitting the current window.

I keep forgetting `++close` option so I either run ":qall!"
or kill the parent process (ie. terminal emulator).

Unsatisfied users should create their Ex-command that runs jobstart().
2026-08-16 21:22:15 -04:00
Jan Edmund Lazo
1e153b9a1c build(vim-patch): v9.2.0026 is n/a 2026-08-16 20:54:24 -04:00
Jan Edmund Lazo
fe5cf3259c build(vim-patch): v9.1.2033 is n/a 2026-08-16 20:54:24 -04:00
Jan Edmund Lazo
5d1901b904 build(vim-patch): v9.1.0349 is n/a 2026-08-16 20:54:24 -04:00
Jan Edmund Lazo
9143da2a05 build(vim-patch): n/a vim9 patch 9.0.1821 2026-08-16 20:54:24 -04:00
Justin M. Keyes
581ce0b3da fix(cmdatom): <Cmd> mappings #41347
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.
2026-08-16 18:00:55 -04:00
Justin M. Keyes
0e436350a5 refactor(cmdatom): atom_redo_keys #41345
Some names/comments are misleading.
Also add some asserts.
2026-08-16 16:14:33 -04:00
Willaaaaaaa
e0e2f978a0 feat(vim.fs): slug() supports URI #41241
Problem:
`vim.fs.slug()` does not handle URIs like `term://foo//123:bash`,
so callers (e.g. terminal persistence) must strip the scheme before
calling `slug()`.

Solution:
Detect `scheme://` from the raw input before `normalize()` and
replace it with a `=uri-<scheme>-` prefix.
2026-08-16 13:29:34 -04:00
Justin M. Keyes
aaf57a053d fix(coverity): false positives in kvec usages #41341
Coverity can't follow kv_ensure_space()'s `kv_roundup32()` bit math, so
every kv_concat_len() looks like an overrun; and it doesn't know
`kv_push()` allocates when `size == capacity`.

    _____________________________________________________________________________________________
    CID 653191:         Memory - illegal accesses  (OVERRUN)
    /src/nvim/input.c: 3536             in paste_store()
    3530
    3531         if (s > start) {
    3532           if (need_redo) {
    3533             kv_concat_len(redobuff.cur.keys, start, (size_t)(s - start));
    3534           }
    3535           if (need_record) {
    >>>     CID 653191:         Memory - illegal accesses  (OVERRUN)
    >>>     Overrunning dynamic array "recordbuff.items" at offset corresponding to index variable "recordbuff.size" through dereference in call to "memcpy".
    3536             kv_concat_len(recordbuff, start, (size_t)(s - start));
    3537           }
    3538         }
    3539
    3540         if (s < str_end) {
    3541           int c = (uint8_t)(*s++);

    _____________________________________________________________________________________________
    CID 653190:         Memory - illegal accesses  (OVERRUN)
    /src/nvim/input.c: 730             in redo_append_spec()
    724         return;
    725       }
    726
    727       while (*s != NUL) {
    728         if ((uint8_t)(*s) == K_SPECIAL && s[1] != NUL && s[2] != NUL) {
    729           // Insert special key literally.
    >>>     CID 653190:         Memory - illegal accesses  (OVERRUN)
    >>>     Overrunning dynamic array "redobuff.cur.keys.items" at offset corresponding to index variable "redobuff.cur.keys.size" through dereference in call to "memcpy".
    730           kv_concat_len(redobuff.cur.keys, s, 3);
    731           s += 3;
    732         } else {
    733           sb_add_char(&redobuff.cur.keys, mb_cptr2char_adv(&s));
    734         }
    735       }

    CID 653189:         (OVERRUN)
    /src/nvim/input.c: 3533           in paste_store()
    /src/nvim/input.c: 3536           in paste_store()

    _____________________________________________________________________________________________
    CID 653189:           (OVERRUN)
    /src/nvim/input.c: 3533             in paste_store()
    3527                && *s != NL && !(crlf && *s == CAR)) {
    3528           s++;
    3529         }
    3530
    3531         if (s > start) {
    3532           if (need_redo) {
    >>>     CID 653189:           (OVERRUN)
    >>>     Overrunning dynamic array "redobuff.cur.keys.items" at offset corresponding to index variable "redobuff.cur.keys.size" through dereference in call to "memcpy".
    3533             kv_concat_len(redobuff.cur.keys, start, (size_t)(s - start));
    3534           }
    3535           if (need_record) {
    3536             kv_concat_len(recordbuff, start, (size_t)(s - start));
    3537           }
    3538         }
    /src/nvim/input.c: 3536             in paste_store()
    3530
    3531         if (s > start) {
    3532           if (need_redo) {
    3533             kv_concat_len(redobuff.cur.keys, start, (size_t)(s - start));
    3534           }
    3535           if (need_record) {
    >>>     CID 653189:           (OVERRUN)
    >>>     Overrunning dynamic array "recordbuff.items" at offset corresponding to index variable "recordbuff.size" through dereference in call to "memcpy".
    3536             kv_concat_len(recordbuff, start, (size_t)(s - start));
    3537           }
    3538         }
    3539
    3540         if (s < str_end) {
    3541           int c = (uint8_t)(*s++);

    _____________________________________________________________________________________________
    CID 653188:         Memory - illegal accesses  (OVERRUN)
    /src/nvim/input_cmdatom.c: 216             in atoms_concat_keys()
    210
    211     /// Concatenates the keys of multiple atoms into one (allocated) string.
    212     static String atoms_concat_keys(CmdAtomVec atoms)
    213     {
    214       StringBuilder keys = KV_INITIAL_VALUE;
    215       for (size_t i = 0; i < kv_size(atoms); i++) {
    >>>     CID 653188:         Memory - illegal accesses  (OVERRUN)
    >>>     Overrunning dynamic array "keys.items" at offset corresponding to index variable "keys.size" through dereference in call to "memcpy".
    216         kv_concat(keys, kv_A(atoms, i).keys);
    217       }
    218       size_t len = kv_size(keys);
    219       kv_push(keys, NUL);
    220       return (String){ .data = keys.items, .size = len };
    221     }

    CID 653187:       Null pointer dereferences  (FORWARD_NULL)

    _____________________________________________________________________________________________
    CID 653186:         Null pointer dereferences  (FORWARD_NULL)
    /src/nvim/input_cmdatom.c: 173             in atom_compose_keys()
    167       StringBuilder sb = KV_INITIAL_VALUE;
    168       redo_prefix(&spec, &sb, false);
    169       redo_chars(&spec, &sb, false);
    170       if (sb.size == 0) {
    171         return NULL;
    172       }
    >>>     CID 653186:         Null pointer dereferences  (FORWARD_NULL)
    >>>     Dereferencing null pointer "((sb.size == sb.capacity) ? (sb.capacity = (sb.capacity ? sb.capacity << 1 : 8UL)) , (sb.items = xrealloc(sb.items, 1UL * sb.capacity)) , 0 : 0) , (sb.items + sb.size++)".
    173       kv_push(sb, NUL);
    174       return sb.items;
    175     }
    176
    177     /// The pending change as a CmdAtom: the composed keysequence plus the structured fields.
    178     /// Caller owns `keys`.

    _____________________________________________________________________________________________
    CID 653185:         Null pointer dereferences  (FORWARD_NULL)
    /src/nvim/input.c: 296             in redo_compose()
    290       StringBuilder buf = KV_INITIAL_VALUE;
    291       redo_prefix(&r->spec, &buf, false);
    292       kv_splice(buf, r->keys);
    293       if (buf.size == 0) {
    294         return (String)STRING_INIT;
    295       }
    >>>     CID 653185:         Null pointer dereferences  (FORWARD_NULL)
    >>>     Dereferencing null pointer "((buf.size == buf.capacity) ? (buf.capacity = (buf.capacity ? buf.capacity << 1 : 8UL)) , (buf.items = xrealloc(buf.items, 1UL * buf.capacity)) , 0 : 0) , (buf.items + buf.size++)".
    296       kv_push(buf, NUL);
    297       return cbuf_as_string(buf.items, buf.size - 1);
    298     }
    299
    301     String redo_keys(void)

    _____________________________________________________________________________________________
    CID 653184:         Memory - illegal accesses  (OVERRUN)
    /src/nvim/input.c: 3533             in paste_store()
    3527                && *s != NL && !(crlf && *s == CAR)) {
    3528           s++;
    3529         }
    3530
    3531         if (s > start) {
    3532           if (need_redo) {
    >>>     CID 653184:         Memory - illegal accesses  (OVERRUN)
    >>>     Overrunning dynamic array "redobuff.cur.keys.items" at offset corresponding to index variable "redobuff.cur.keys.size" through dereference in call to "memcpy".
    3533             kv_concat_len(redobuff.cur.keys, start, (size_t)(s - start));
    3534           }
    3535           if (need_record) {
    3536             kv_concat_len(recordbuff, start, (size_t)(s - start));
    3537           }
    3538         }

    _____________________________________________________________________________________________
    CID 653183:         Memory - illegal accesses  (OVERRUN)
    /src/nvim/input.c: 730             in redo_append_spec()
    724         return;
    725       }
    726
    727       while (*s != NUL) {
    728         if ((uint8_t)(*s) == K_SPECIAL && s[1] != NUL && s[2] != NUL) {
    729           // Insert special key literally.
    >>>     CID 653183:         Memory - illegal accesses  (OVERRUN)
    >>>     Overrunning dynamic array "redobuff.cur.keys.items" at offset corresponding to index variable "redobuff.cur.keys.size" through dereference in call to "memcpy".
    730           kv_concat_len(redobuff.cur.keys, s, 3);
    731           s += 3;
    732         } else {
    733           sb_add_char(&redobuff.cur.keys, mb_cptr2char_adv(&s));
    734         }
2026-08-16 11:37:51 -04:00
Nathan Zeng
7b6f344627 fix(restart): preserve global cwd on :restart #41304
Problem:
On :restart, the new Nvim may "inherit" a local dir as its global CWD.

Solution:
Inherit the global CWD explicitly.
2026-08-16 10:34:17 -04:00
Justin M. Keyes
214bcf24cc fix(undo): crash on corrupted undo file #41339
Problem:
`:rundo` on a corrupted undo file crashes or hangs, instead of failing
with E825. Patching one 4-byte field is enough:

    ue_size = 0xFFFFFFFF  " walks a NULL ue_array
    ue_size = 0x7FFFFFF0  " 17 GB xmalloc + memset, then preserve_exit()
    ue_top  = 0xFFFFFFFB  " negative lnum reaches ml_delete()

Analysis:
Every count in the file is read with `undo_read_4c()` and then checked,
differently at each site. None bounds the value by what the file can
hold, so a 2 GB count reaches `xmalloc()`.

Note:
- Vim doesn't have `bi_fsize` because it checks `U_ALLOC_LINE` result
  everywhere (thus doesn't crash, but may thrash...); those checks were
  dropped when Nvim moved to `xmalloc()`, and the `ue_size` loop counter
  became unsigned.
- Vim *does* have the negative line numbers bug: `u_undoredo()` checks
  `top > ml_line_count || top >= bot || bot > ml_line_count + 1`, which
  rejects none of them.

Solution:
- Introduce `undo_read_len()` and use it to fail early instead of
  continuing with nonsense.
- Validate `ue_top`/`ue_bot`/ `ue_lcount`.
- Use `xcalloc()`, so no site can proceed with a NULL array.
- Report a truncated "U" line, distinguish EOF from a 0xFFFFFFFF field,
  and free the header on the extmark error path.
2026-08-16 10:24:24 -04:00
Justin M. Keyes
83730db647 perf(marktree): binary search the node intersect array #41331
Problem:
Undo of a change spanning many paired marks is quadratic. A node's
"intersect" array holds every pair crossing that node, and both
intersect_node() and unintersect_node() walked it linearly. Undoing an
edit over 1M paired marks spends 68% of its time in unintersect_node()'s
scan alone.

Solution:
The array is sorted, so binary search it.

    marks    undo before    after
    200k          831ms     385ms
      1M        14006ms    3820ms

Redo is unaffected: it is dominated by marktree_move() actually
repositioning the marks.
2026-08-16 08:29:58 -04:00
zeertzjq
2edb1c0009 fix(lua): don't limit indexed vim.cmd positional argument count (#41317) 2026-08-16 07:25:04 +08:00
Justin M. Keyes
37c670e682 fix(marks): undo reverts a mark set after the change #41330
Problem:
A named mark updated after a change is moved back (treated as the
original mark) by undo:

    :1mark d
    :$
    dw
    :2mark d   " 'd is on line 2
    :undo      " 'd is back on line 1

The undo header snapshots `b_namedm` when the change is recorded, and
`u_undoredo()` restores that snapshot indiscriminately.

Solution:
Update the pending header's snapshot when a mark is set explicitly.
Marks that the change itself moved go through mark_adjust(), not
setmark_pos(), so those are still reverted.

Similar to 2546741d1b (for extmarks): an explicit set inside an undo
block is confused with an edit-driven adjustment. But the extmarks case
is dealing with mid-edit moves, whereas named/regular marks only need
the stale snapshot dropped.
2026-08-15 13:34:51 -04:00
Justin M. Keyes
fb180287d0 fix(cwd): nvim_win_set_buf of :bcd buf, changes caller CWD #41329
Problem:
Setting a :bcd buffer into another window, modifies the caller's CWD.

    local b = vim.api.nvim_create_buf(true, true)
    vim.api.nvim_buf_call(b, function() vim.cmd.bcd('..') end)
    vim.cmd('vsplit')
    vim.api.nvim_win_set_buf(vim.fn.win_getid(2), b)

    :echo haslocaldir(0) haslocaldir(-1,0) haslocaldir(-1,-1,0)
    0 0 0
    :echo getcwd() ==# getcwd(-1,-1)
    0

Analysis:
`ctx_dirs_save` only saves CWD if it predicts the switch can change it.
But win_set_buf() replaces the target window's buffer *after* the
switch, which cannot be "predicted" from `ctx_dirs_save`.

Solution:
Always snapshot whenever the switch enters another window.
Skipping `os_dirname` was a micro-optimization.
2026-08-15 13:15:37 -04:00
Justin M. Keyes
cd2db7913a fix(cwd): nvim_win_set_buf changes global CWD #41328
Problem:
nvim_win_set_buf() on a non-current win, while the current win has
a win-local dir, changes the global CWD:

    :vsplit | lcd ..
    :call nvim_win_set_buf(other_win, buf)
    :wincmd l
    :verbose pwd
    [global] /parent        " expected: the initial cwd

Analysis:
`globaldir` is where to return when no local dir applies; NULL means the
process CWD is already there. Switching to a window with no local dir
makes update_cwd() chdir back to `globaldir` and clear it. kCtxKeepCwd
restores the process CWD but not that bookkeeping, so the restored
window-local dir is mistaken for the global one.

Solution:
Save/restore `globaldir` with the CWD.
2026-08-15 11:45:44 -04:00
Justin M. Keyes
9cca923ab4 fix(extmarks): redo of a mark created during an edit #41324
Problem:
A mark created by `nvim_buf_set_extmark()` while an undo block is open
never comes back on redo.

Analysis:
Undo deletes the text it covers and collapses the range; redo replays
the splices, which re-insert the text but cannot re-expand the mark.
`extmark_set()` records a position only for a mark it moves, not for one
it creates.

Solution:
Record the created position for redo; undo leaves the mark to the splice
replay, since it did not exist before the edit. Each side of a paired
mark gets its own entry. Redo also revives a mark that undo invalidated,
else its position returns but its highlight does not.
2026-08-15 10:13:32 -04:00
Jan Edmund Lazo
aef9356e34 build(vim-patch): fix compiled,popup-window regexp for '|' #41326
git "-I" regex seem to be "extended".
grep's default regex is "basic".
Fix regexps for switch/case "runtime/doc/*.txt"
based on the "*.h" switch/case.
2026-08-15 08:26:57 -04:00
Justin M. Keyes
a458bfb595 Merge #41316 from janlazo/na-patch-tcd
build(vim-patch): detect more n/a patches for popupwin and terminal
2026-08-15 07:22:16 -04:00
Barrett Ruth
b169d9376c test(dir_spec): fix failures with PUC Lua 2026-08-15 13:07:21 +08:00
Jan Edmund Lazo
c7ee6af777 build(vim-patch): v8.1.2219 is n/a
Vim's TerminalWinOpen seems to be required because of Vim
buffer-job-popupwin implementation.
Based on the patch, I'm puzzled why fzf needs this on Vim.

Nvim's TermOpen, TermEnter, and detection mechanisms to know
if buffer is on a (active,visible) window should suffice to not port it.

If there was a feature request or issue without a merged fix,
then I can't find it.

https://github.com/junegunn/fzf/pull/2000
2026-08-15 00:24:09 -04:00
Jan Edmund Lazo
d137073024 build(vim-patch): v8.1.2195 is n/a 2026-08-14 17:13:30 -04:00
Jan Edmund Lazo
692e41bb66 build(vim-patch): v8.1.1713 is n/a 2026-08-14 17:13:29 -04:00