Commit Graph

21038 Commits

Author SHA1 Message Date
Justin M. Keyes
9b2dbc264b Merge #41370 from justinmk/fixci 2026-08-19 03:52:46 -04:00
Chen, Ting-An
b524338261 fix(l10n): refresh Traditional Chinese message catalog #41273
Problem:
The zh_TW catalog was stale, omitted 743 messages from the current source, and contained empty, fuzzy, and format-unsafe translations.

Solution:
Regenerate the catalog with the official translation build, preserve the focused translation repairs, and translate the reported E5200 diagnostic.
2026-08-18 16:27:14 -04:00
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
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
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
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
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
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
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
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
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
Justin M. Keyes
64a301184e feat(input)!: CmdAtom event #41297
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.
2026-08-14 09:30:31 -04:00
github-actions[bot]
485ae7e31a docs: update version.c #41288
vim-patch:9.2.0946: GTK2/3: mouse move starts Visual selection after a dialog
vim-patch:9.2.0947: GTK4: screen is cleared when moving the mouse after startup
vim-patch:9.2.0948: GTK4: mouse move starts Visual selection after a dialog
vim-patch:9.2.0949: GDK_KEY_VoidSymbol might be undefined
vim-patch:9.2.0951: GTK3: cursor does no longer blink
vim-patch:9.2.0955: tests: terminal tests are flaky
vim-patch:9.2.0956: GTK4: crash when the window is resized while redrawing

vim-patch:8.1.0768: updating completions may cause the popup menu to flicker
vim-patch:8.1.0863: cannot see what signal caused a job to end
vim-patch:8.1.0876: completion match not displayed when popup menu is not shown
vim-patch:8.1.0894: MS-Windows: resolve() does not return a reparse point
vim-patch:8.1.1218: cannot set a directory for a tab page
vim-patch:8.1.1224: MS-Windows: cannot specify font weight
vim-patch:8.1.1417: MS-Windows: resolve() does not resolve all components of path
vim-patch:8.1.1473: new resolve() implementation causes problem for plugins
vim-patch:8.1.1525: cannot move a popup window with the mouse
vim-patch:8.1.1558: popup_menu() and popup_filter_menu() are not implemented yet
vim-patch:8.1.1561: popup_setoptions() is not implemented yet
vim-patch:8.1.1577: command line redrawn for +arabic without Arabic characters
vim-patch:8.1.1580: cannot make part of a popup transparent
vim-patch:8.1.1589: popup window does not indicate scroll position
vim-patch:8.1.1597: cannot scroll a popup window with the mouse
vim-patch:8.1.1609: the user cannot easily close a popup window
vim-patch:8.1.1612: cannot show an existing buffer in a popup window
vim-patch:8.1.1626: no test for closing a popup window with a modified buffer
vim-patch:8.1.1628: popup window functions not in list of functions
vim-patch:8.1.1713: highlighting cursor line only works with popup_menu()
vim-patch:8.1.1714: cannot preview a file in a popup window
vim-patch:8.1.1718: popup menu highlighting does not look good
vim-patch:8.1.1770: cannot get the window ID of the popup preview window
vim-patch:8.1.1784: MS-Windows: resolve() does not work if serial nr duplicated
vim-patch:8.1.1787: cannot resize a popup window
vim-patch:8.1.1799: cannot avoid mapping for a popup window
vim-patch:8.1.1813: ATTENTION prompt for a preview popup window
vim-patch:8.1.1819: :pedit does not work with a popup preview window
vim-patch:8.1.1880: cannot show extra info for completion in a popup window
vim-patch:8.1.1882: cannot specify properties of the info popup window
vim-patch:8.1.1884: cannot use mouse scroll wheel in popup in Insert mode
vim-patch:8.1.1892: missing index entry and option menu for 'completepopup'
vim-patch:8.1.1904: cannot have an info popup align with the popup menu
vim-patch:8.1.1905: cannot set all properties of the info popup
vim-patch:8.1.1906: info popup size is sometimes incorrect
vim-patch:8.1.1908: every popup window consumes a buffer number
vim-patch:8.1.1928: popup windows don't move with the text when making changes
vim-patch:8.1.1969: popup window filter is used in all modes
vim-patch:8.1.2039: character from 'showbreak' does not use 'wincolor'
vim-patch:8.1.2092: MS-Windows: redirect in system() does not work
vim-patch:8.1.2093: MS-Windows: system() test fails
vim-patch:8.1.2139: the modifyOtherKeys codes are not tested
vim-patch:8.1.2142: some key mappings do not work with modifyOtherKeys
vim-patch:8.1.2153: combining text property and syntax highlight is wrong
vim-patch:8.1.2155: in a terminal window 'cursorlineopt' does not work properly
vim-patch:8.1.2158: terminal attributes missing in Terminal-normal mode
vim-patch:8.1.2192: cannot easily fill the info popup asynchronously
vim-patch:8.1.2208: Unix: Tabs in output might be expanded to spaces
vim-patch:8.1.2273: wrong default when "pos" is changed with popup_atcursor()
vim-patch:8.1.2279: computation of highlight attributes is too complicated
vim-patch:8.1.2324: with of scrollbar in popup menu not taken into account
vim-patch:8.1.2351: 'wincolor' not used for > for not fitting double width char
vim-patch:8.1.2362: cannot place signs in a popup window
vim-patch:8.1.2386: 'wincolor' is not used for 'listchars'
vim-patch:8.1.2399: info popup on top of cursor if it doesn't fit
vim-patch:8.1.2415: popup menu flickers if an info popup is used
vim-patch:8.1.2418: bufnr('$') is wrong after recycling popup buffer
2026-08-14 07:35:15 -04:00
bfredl
69e61d52e2 Merge pull request #41186 from Rawan10101/fix-wasm-browser
feat(wasm): track and fix ongoing issues + add WASM build workflow
2026-08-14 12:59:12 +02:00
Barrett Ruth
fc3f71fb7d fix(messages): inputlist() offers the mouse when it cannot be used (#41280)
Problem:
`inputlist()` advertises "click with the mouse" purely because it
implements click selection, so under the default `'mouse'` of "nvi" it
offers a click that command-line mode never receives.

Solution:
Only offer the mouse when `'mouse'` covers command-line mode, the same
condition `:help inputlist()` already documents.
2026-08-14 07:38:39 +08:00
rawan10101
b78adb98ba fix(wasm): remove custom log path and disable autoread 2026-08-13 15:52:30 +03:00
zeertzjq
2dec97464c vim-patch:9.2.0954: u_read_undo() can be improved (after 9.2.0935)
Problem:  u_read_undo() has comments that do not add anything to what
          the code says (afte rv9.2.0935).
Solution: Drop the redundant comments (Hirohito Higashi).

related: vim/vim#20942
closes:  vim/vim#21028

ecfea491aa

Co-authored-by: Hirohito Higashi <h.east.727@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 07:52:57 +08:00
zeertzjq
7525e87f10 vim-patch:9.2.0953: insert completion code can be improved
Problem:  The duplicate-check hashtab in insexpand.c has comments that
          do not add anything to what the code says (after v9.2.0909).
Solution: Drop the redundant comments.

related: vim/vim#20926
closes:  vim/vim#21027

4dde4afa1d

Co-authored-by: Hirohito Higashi <h.east.727@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 07:52:57 +08:00
zeertzjq
39dc19bab4 vim-patch:9.2.0950: transstr() can be improved (after 9.2.0906)
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#21026

fe65307d49

Co-authored-by: Hirohito Higashi <h.east.727@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 07:52:57 +08:00
zeertzjq
5e0d4ba389 vim-patch:9.2.0945: sort() with a numeric option can be improved
Problem:  The comments on the precomputed sort keys explain the code by
          contrasting it with the previous implementation, and one of
          them exceeds 80 columns (after v9.2.0937).
Solution: Drop the redundant comments and shorten the union member ones
          (Hirohito Higashi).

related: vim/vim#21003
closes:  vim/vim#21030

722f4292e5

Co-authored-by: Hirohito Higashi <h.east.727@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 07:52:57 +08:00
github-actions[bot]
a63393396c docs: update version.c #41258
vim-patch:8.1.1523: cannot show range of buffer lines in popup window
vim-patch:8.1.1537: using "tab" for popup window can be confusing
vim-patch:8.1.1538: cannot specify highlighting for notifications
vim-patch:8.1.1548: popup_dialog() is not implemented
vim-patch:8.1.1553: not easy to change the text in a popup window
vim-patch:8.1.1559: popup window title property not implemented yet
vim-patch:8.1.1574: tabpage option not yet implemented for popup window
vim-patch:8.1.1659: popup window "mousemoved" values not correct
vim-patch:8.1.1673: cannot easily find the popup window at a certain position
vim-patch:8.1.1690: default padding for popup window menu is too much
vim-patch:8.1.1944: leaking memory when using sound callback
vim-patch:8.1.1999: calling both PlaySoundW() and PlaySoundA()
vim-patch:8.1.2193: popup_setoptions(popup_getoptions()) does not work
vim-patch:8.1.2250: CTRL-U and CTRL-D don't work in popup window
vim-patch:8.1.2292: v:mouse_winid not set on click in popup window
vim-patch:8.2.0238: MS-Windows: job_stop() results in exit value zero
vim-patch:8.2.0286: cannot use popup_close() for a terminal popup
vim-patch:8.2.2105: sound test is a bit flaky
vim-patch:8.2.2296: cannot use CTRL-N and CTRL-P in a popup menu
vim-patch:8.2.2950: sound code not fully tested
vim-patch:8.2.2959: sound_playfile() is not tested on MS-Windows
vim-patch:9.0.0800: compiler complains about repeated typedef
vim-patch:9.0.0999: memory may leak
vim-patch:9.0.2089: sound_playfile() fails when using powershell
vim-patch:9.1.0271: CI sound test aborts with undefined variable
vim-patch:9.1.0949: popups inconsistently shifted to the left
vim-patch:1f045f324 runtime(doc): clarify buffer deletion on popup_close()
vim-patch:9.1.1985: tests: test_sound.vim fails on Windows
vim-patch:3d863d6aa runtime(doc): Update popup width rule
vim-patch:9.2.0928: MinGW: tests hang when Vim is built with coverage enabled
vim-patch:9.2.0940: GTK4: columns are lost when a scrollbar appears
vim-patch:9.2.0943: test: test_hardcopy fails on GTK4 UI
vim-patch:c6b041c63 CI: Keep the GTK4 job from affecting the other jobs
2026-08-12 09:12:57 -04:00
zeertzjq
f33c92348a vim-patch:9.2.0937: sort() with a numeric option converts each item on every comparison (#41286)
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#21003

c8c59db9df

Co-authored-by: Samuel Schlesinger <sgschlesinger@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-08-12 11:45:38 +00:00
zeertzjq
a0dc3f0067 vim-patch:9.2.0935: reading an undo file is slow with many undo headers (#41285)
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#20942

fccf613c8f

Co-authored-by: Samuel Schlesinger <sgschlesinger@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-08-12 10:00:09 +08:00
zeertzjq
ecaf9e9396 vim-patch:02bf089: runtime(doc): clarify diff_filler() function (#41283)
fixes: vim/vim#20990

02bf0893d5

Co-authored-by: Christian Brabandt <cb@256bit.org>
2026-08-12 08:24:48 +08:00
zeertzjq
e9dc4da86e vim-patch:9.2.0938: cursorbind: cursor in the other window is not updated after undo (#41282)
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#21004

2045a20d4b

Co-authored-by: Hirohito Higashi <h.east.727@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 00:16:08 +00:00
rawan10101
014b00a642 fix(wasm): fix browser input and runtime exit handling
Use ev.key directly to preserve Caps Lock casing, implement clipboard
paste, and prevent :q/:qa from leaving the runtime in a loop.
2026-08-11 14:08:40 +03:00
zeertzjq
2757f6eef9 vim-patch:9.2.0933: u_read_undo() leaks the file name when the undo file owner differs (#41269)
Problem:  When the owner of an undo file differs from the owner of
          the text file and the current user, u_read_undo() returns
          without freeing the file name it allocated with
          u_get_undo_file_name().
Solution: Free the file name before returning (Samuel Schlesinger).

Every other exit of the function frees it under the "theend" label;
this early return sits before the file pointer is initialized, so it
cannot use that label.

closes: vim/vim#20987

d03735e8d2

Co-authored-by: Samuel Schlesinger <sgschlesinger@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-08-11 10:35:51 +08:00
zeertzjq
7be8a8dda3 vim-patch:9.2.0932: NFA engine fallback can double free the compiled program (#41268)
Problem:  When the automatic regexp engine falls back to the
          backtracking engine in vim_regexec_string(), the compiled
          program is freed before the replacement is compiled; when
          saving the pattern fails from being out of memory the
          caller's "regprog" is left pointing to freed memory and
          is freed again.
Solution: Free the previous program only after compiling the
          replacement succeeded, like vim_regexec_multi() already
          does (Samuel Schlesinger).

closes: vim/vim#20986

cab0901f12

Co-authored-by: Samuel Schlesinger <sgschlesinger@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-08-11 10:19:52 +08:00
bfredl
344ea602f6 refactor(ex_cmds): use function arguments for magic behavior
Now this was a cargo cult anti-pattern to write home about.
Doing painful save-and-restore bookkeeping around a separate
`magic_overruled` decoy global is just as messy as doing painful
save-and-restore logic around `p_magic` itself. only that now you need
to wrap every access to the effective value in a function call.

This replaces this with a marvellous new Clean Code technique™:
passing in the intended behavior as a function parameter to functions
where either the option or an explicit value might be used.
2026-08-10 13:26:51 +02:00
Justin M. Keyes
5287a04be4 perf(mbyte): annotate pure functions #41251 2026-08-10 04:27:49 -04:00