Commit Graph

21120 Commits

Author SHA1 Message Date
Lewis Russell
ce8a897f98 feat(lua): add vim.async
Problem: Nvim has many Lua APIs that start callback-driven work: timers,
jobs, libuv handles, and other event-loop tasks. Callers that need to
sequence or cancel that work have to build their own coroutine wrappers,
task bookkeeping, and cleanup rules. This makes async control flow hard
to share, test, and document.

Solution: Add `vim.async`, a structured-concurrency module vendored from
async.nvim. It provides task handles, await/pawait helpers,
sleep/timeout helpers, completion-order iteration, and semaphores on top
of Nvim's event loop.

The API follows the same broad model as Trio: async work has an owner,
tasks are awaited explicitly, and cancellation is cooperative. Include
generated vimdoc with an introductory overview and examples, a news
entry, and functional tests for the new module.

AI-assisted
2026-09-03 19:17:25 +01:00
Justin M. Keyes
da4355ab8f fix(multicursor): 'guicursor' uses Normal shape in Visual mode #41669
Problem:
'guicursor' is not respected when extra cursors exist.

Per-cursor selection toggles Visual off, and the sandbox restores it
only after restore_current_state(), whose ui_cursor_shape() caches the
Normal shape.

Solution:
Restore Visual before restore_current_state().
2026-09-03 13:22:50 -04:00
Sébastien Hoffmann
3808c00fc8 fix(statusline): default 'statusline' improvements #41597
Problem: there should be exactly one cell of padding between sections,
and exactly one cell of minimum padding between the left and the right.
- Spaces between sections waste space when a section is empty.
  According to a comment in #33036, this was the reason to avoid `%k`
  and implement the keymap section with a vim expression, but other
  sections still have this problem.
- The diagnostics section wastes space because it is not entirely empty
  when there are diagnostics in another buffer.
- The terminal exit code section can touch the right side, e.g. the
  ruler, even though it belongs to the left side.
Solution:
- Use auto-hiding item groups (`%(` without width fields) to get rid of
  unneeded spaces when a section shows no information.
  This simplifies the 'showcmd' and 'keymap' sections in particular.
- As a slight simplification, `term_exitcode` is moved into the flags
  section since it is formatted with square brackets like a flag.
- Count the diagnostics for the current buffer specifically.
- Ensure at least one cell of padding between the left and the right
  side by adding a space next to the separator `%=`.

Problem: some sections are implemented with `%{%`, even though
reevaluation of the expression result is not needed.
This leads to otherwise needless %-escaping in `progress_status`.
Solution: use `%{` instead.

Problem: `%{` and `%{%` (without items) replace spaces with fillchars.
This looks out-of-place inside the terminal exit code section, and in
contrast to all other sections, the 'busy' section is surrounded by
fillchars, which looks inconsistent, and with some terminal-font
combinations, ◐ overlaps the fillchar, e.g. Alacritty & JetBrains Mono.
Solution: use non-breaking spaces U+202F to avoid fillchar substitution.

Problem: sections that appear/disappear frequently can make otherwise
more stable sections jump around a lot.
Solution: sort the sections on the right roughly by volatility:
'showcmd' in first place, 'keymap' next to the ruler.
2026-09-03 12:44:30 -04:00
Justin M. Keyes
ba0ee081c8 fix(coverity): DEADCODE, FORWARD_NULL, UNINIT #41667
Problem:

    CID 655469:           (DEADCODE)
    /build/src/nvim/auto/lua_api_c_bindings.generated.h: 7696             in nlua_api_nvim__mcursor_cascading()
    7690
    7691     exit_0:
    7692       arena_mem_free(arena_finish(&arena));
    7693       if (ERROR_SET(&err)) {
    7694         luaL_where(lstate, 1);
    7695         if (err_param) {
    >>>     CID 655469:           (DEADCODE)
    >>>     Execution cannot reach this statement: "lua_pushstring(lstate, "Inv...".
    7696           lua_pushstring(lstate, "Invalid '");
    7697           lua_pushstring(lstate, err_param);
    7698           lua_pushstring(lstate, "': ");
    7699         }
    7700         lua_pushstring(lstate, err.msg);
    7701         api_clear_error(&err);
    /build/src/nvim/auto/lua_api_c_bindings.generated.h: 7702             in nlua_api_nvim__mcursor_cascading()
    7696           lua_pushstring(lstate, "Invalid '");
    7697           lua_pushstring(lstate, err_param);
    7698           lua_pushstring(lstate, "': ");
    7699         }
    7700         lua_pushstring(lstate, err.msg);
    7701         api_clear_error(&err);
    >>>     CID 655469:           (DEADCODE)
    >>>     Execution cannot reach the expression "5" inside this statement: "lua_concat(lstate, (err_par...".
    7702         lua_concat(lstate, err_param ? 5 : 2);
    7703         return lua_error(lstate);
    7704       }
    7705
    7706       return 1;
    7707     }

    CID 655468:         Null pointer dereferences  (FORWARD_NULL)
    /src/nvim/mcursor.c: 833             in mc_ins_cascade()
    827           mc_ins_span_push(keys.items, NULL);
    828           mc_ins_preview_rebase();
    829         }
    830       } else if (ins.data != NULL && ins.size < mc_ins_span.done_len) {
    831         // Capture shrank without a restart signal, e.g. completion surgery rewrote the pending keys.
    832         mc_ins_cascade_restart();
    >>>     CID 655468:         Null pointer dereferences  (FORWARD_NULL)
    >>>     Passing null pointer "ins.data + mc_ins_span.done_len" to "mc_ins_keys_nonliteral", which dereferences it.
    833       } else if (ins.size > mc_ins_span.done_len
    834                  && mc_ins_keys_nonliteral(ins.data + mc_ins_span.done_len,
    835                                            ins.size - mc_ins_span.done_len)) {
    836         // Non-literal keys pending (BS, CTRL-U, ...): re-execute instead of previewing.
    837         mc_ins_span_flush(&ins, false);
    838       } else {

    CID 655467:         Uninitialized variables  (UNINIT)
    /src/nvim/mcursor.c: 1041             in mc_vsel_refresh()
    1035       for (size_t i = 0; i < kv_size(mc_cursors); i++) {
    1036         Context *ctx = &kv_A(mc_cursors, i);
    1037         pos_T pos;
    1038         if (!mc_ctx_resolve(ctx, &pos)) {
    1039           continue;
    1040         }
    >>>     CID 655467:         Uninitialized variables  (UNINIT)
    >>>     Using uninitialized value "pos". Field "pos.coladd" is uninitialized.
    1041         curwin->w_cursor = pos;
    1042         check_cursor(curwin);
    1044         Visual.select = false;
    1045         nvim_feedkeys(span, cstr_as_string("nix"), false);
    1046         if (!Visual.active) {

Solution:

- CID 655467 UNINIT: real (minor). `mc_vsel_refresh()` copied
  uninitialized local into `curwin->w_cursor`, and garbage coladd
  survived into the Visual replay (`equalpos()` compares it).
  - Fix: `pos_T pos = { 0 }`
- CID 655468 FORWARD_NULL: false positive
- CID 655469 DEADCODE: generated code. `nvim__mcursor_cascading`
  is flagged bc it is new,
- TODO: teach `gen_api_dispatch.lua` to omit the branch for
  parameterless functions.
2026-09-03 12:41:40 -04:00
Justin M. Keyes
e1a28e52b2 fix(editor): shada "force" load clears v:oldfiles #41662
Problem:
`shada_read()` rebuilds `v:oldfiles` for any "forced" read, which first
*clears* the list. For an in-memory caller such as multicursor, the list
is never repopulated. So `v:oldfiles` is empty after `Q` + edit.

Solution:
Rebuild `v:oldfiles` only when `kShaDaGetOldfiles` is requested.
`shada_read_everything()` (`:rshada[!]`, startup) always requests it.

old bug from 411a06c8b6
2026-09-03 11:32:20 -04:00
Justin M. Keyes
3beb15da57 fix(multicursor): live-mirror 'autocomplete' <BS> #41660
Problem:
During 'autocomplete', a completion-ending `<BS>` restarts completion
immediately. Multicursor insert-cascade skips replay (`edit()` refuses
to nest if compl/pum is active), so the mcursors do not update until
ESC.

Solution:
Flush pending keys in `TRIGGER_AUTOCOMPLETE`, just before autocomplete
restarts again.
2026-09-03 10:28:17 -04:00
Justin M. Keyes
e1cde28ba2 fix(multicursor): live-mirror Visual-mode operator mapping #41655
Problem:
Insert entered by a Visual-mode operator mapping (`xnoremap c c`) does
not live-mirror at cursors.

Solution:
Decide `typed` from the Visual session's own provenance (`vatom`, which
spans the selection and knows whether it was user input) instead of only
negating it.
2026-09-03 08:44:22 -04:00
zeertzjq
51eacf284c fix(statuscolumn): heap buffer overflow with sign/number column (#41640)
Problem:  Drawing 'statuscolumn' leads to a heap-buffer-overflow if a
          sign/number column comes after many items.
Solution: Avoid curitem >= stl_items_len when writing to stl_items.
2026-09-03 16:56:41 +08:00
zeertzjq
b51a0b2dcc vim-patch:9.2.1036: syntax highlighting is slower than necessary (#41644)
Problem:  While advancing the syntax highlighting, store_current_state()
          calls syn_stack_find_entry() about once per parsed line, and
          that function rescans the state-cache list from its head every
          time. With the list capped at 1000 entries this linear rescan
          dominates the cost of highlighting a large file.
Solution: Keep a cached position to the entry last located in the list
          and resume the scan from it when it is at or before the wanted
          line, advancing the finger as new states are stored
          (Julien Voisin).

The list is sorted by line number, so when the remembered entry is at or
before the wanted line the answer can only follow it, never precede it;
resuming from there returns the same entry as a scan from the head.  The
"at or before" guard keeps this correct for any lookup order: a backward
or random-access lookup whose remembered entry is past the wanted line
falls back to a full scan.  The pointer is cleared whenever an entry is
freed, the array is reallocated or the block is freed, so it cannot
dangle.

Parsing the syntax of a 20000-line C file is about a third faster, a
5000-line file about a quarter faster, benchmarked via something like
this:

```
call synID(1, 1, 1)              " warm-up
let s = reltime()
for l in range(1, line('$')) | call synID(l, 1, 1) | endfor
call writefile([reltimefloat(reltime(s))], $T)
```

closes: vim/vim#21166

f1b4549129

Co-authored-by: Julien Voisin <julien.voisin@dustri.org>
2026-09-03 11:07:44 +08:00
zeertzjq
aff123a5d4 vim-patch:9.2.1034: NFA regexp matching is slow for ASCII text (#41642)
Problem:  NFA regexp matching is slower than necessary for ASCII text
          because two indirect function calls are made for every
          character.
Solution: Add an inline fast path for an ASCII byte that is not followed
          by a composing character (Julien Voisin).

The main loop of nfa_regmatch() fetched the current character and its
byte length with two calls through the mb_ptr2char and mb_ptr2len
function pointers on every character.  These pointers cannot be inlined,
yet for ASCII text, which is the common case, both merely return the byte
and a length of one.

Handle that case inline.  NUL is checked first so that reading the next
byte cannot go past the end of the line, and the "next byte is ASCII"
condition matches the check in utfc_ptr2len(), so a base character
followed by a composing character still falls through to the original
calls.

A "perf stat -e instructions" on a full scroll of a 60000 line C file with
syntax highlighting enabled shows an instructions count reduction of 4%.

closes: vim/vim#21179

58390ca285

Co-authored-by: Julien Voisin <julien.voisin@dustri.org>
2026-09-03 11:06:35 +08:00
zeertzjq
89837bdd72 vim-patch:9.2.1032: scrolling moves cursor up with 'scrolloffpad' (#41641)
Problem:  Cursor correction can move the cursor up at the end of the
          buffer when 'scrolloffpad' is enabled (gx089).
Solution: Allow missing context below EOF in cursor_correct().  Add
          regression tests for CTRL-D and CTRL-E (Seunghee Kim).

fixes:   vim/vim#21096
closes:  vim/vim#21188

6a3db67a52

Co-authored-by: SeungheeKim <ksh368@naver.com>
2026-09-03 11:05:33 +08:00
Justin M. Keyes
f1d89e874a fix(multicursor): 'operatorfunc' motion not followed #41627
Problem:
With follow-mode ("q="), an 'operatorfunc' that only moves the cursor
does not cascade.

Solution:
Update the `effect` condition to include cursor-moves if follow-mode is
active.
2026-09-02 12:00:54 -04:00
Justin M. Keyes
c75aae2bfb fix(multicursor): 'autocomplete' during insert-cascade raises E565 #41625
Problem:
With mcursors and 'autocomplete', a non-literal key (`<BS>`) during an
active completion attempts to insert-cascade, which attempts a nested
`edit()`, which refuses with E565.

Solution:
Defer the flush while completion is active; the pending keys will be
handled later.
2026-09-02 11:20:55 -04:00
Justin M. Keyes
80e9708afa fix(multicursor): atom may cascade in another buffer #41624
Problem:
An atom queued in buffer A cascades on B's cursors if the mapping ends
in B ("nnoremap X x:bnext<CR>").

Solution:
Check the atom's origin buffer (`CmdAtom.origin.buf`).

Note: This does not preclude mappings etc from doing work in temporary
throwaway buffers, as long as they return to the origin buffer.
2026-09-02 10:38:27 -04:00
Justin M. Keyes
350fa5ad7c fix(multicursor): act on fold contents, not the fold itself #41622
Problem:
If an edit cascades to cursor(s) that happen to be within a closed fold,
the edit acts on the fold itself (`:h fold-behavior`). E.g. "dd" deletes
the entire fold, not the cursor's line within the fold.

Solution:
Check `mc_replaying()` in `hasAnyFolding()`, so folds are ignored during
a multicursor cascade. This means multicursor always replays relative to
text *within* the fold.
2026-09-02 09:42:40 -04:00
Justin M. Keyes
7168e0d12f fix(mcursor): drop q= "follow motion" message 2026-09-02 13:09:30 +02:00
Justin M. Keyes
a6d1882cac fix(highlight): cterm default for Cursor hlgroup
Problem:
The `Cursor` highlight is invisible if 'termguicolors' is disabled.

Solution:
Change its default.
2026-09-02 13:09:30 +02:00
zeertzjq
17acde8471 vim-patch:9.2.1029: NFA regexp matching is slower than necessary (#41611)
Problem:  NFA regexp matching is slower than necessary because addstate()
          tracks the recursion depth in a static variable.
Solution: Pass the recursion depth as a function parameter so it stays in a
          register instead of being spilled to memory around every recursive
          call (Julien Voisin).

addstate() is called very frequently and recursively. Storing the depth
counter in a static variable forces the compiler to reload and store it in
memory around each recursive call, since it cannot prove the recursion does
not modify it. Passing it as a parameter keeps it in a register and drops
the increment/decrement bookkeeping on every return path. The recursion
limit behavior is unchanged.

This reduces the instruction count of NFA matching by roughly 4% on
state-heavy patterns. Benchmarking was done with something like:

```
set re=0
let s:word = repeat('abc123 def456 ghijk_lmnop QRSTUV wxyz0 ', 4000)
for i in range(120)
  let s = s:word | let s = substitute(s, '\%#=2\(\a\+\)\(\d\+\)', '\2\1', 'g')
  let s = s:word | let s = substitute(s, '\%#=2\a\+\d\+', 'X', 'g')
  call matchstr(s:word, '\%#=2\(\a\|\d\|_\)\{3,}')
endfor
qa!
```

and `taskset -c 7 perf stat -e instructions ./vim -u NONE -N -X -es -S /tmp/nfabench/count.vim`

closes: vim/vim#21202

0e47c9612e

Co-authored-by: Julien Voisin <julien.voisin@dustri.org>
2026-09-02 13:52:45 +08:00
zeertzjq
90fc8946e8 vim-patch:fac9e33: runtime(doc): fix example output of cosh (#41609)
closes: vim/vim#21197

fac9e333a8

Co-authored-by: Eisuke Kawashima <e-kwsm@users.noreply.github.com>
2026-09-02 13:52:12 +08:00
zeertzjq
749e0a06c2 vim-patch:9.2.1031: 'wildmode' list:full does not show 'wildmenu'
Problem:  With 'wildmode' set to list:full the matches are listed but the
          wildmenu is not shown, although it is "full" that starts
          wildmenu mode (zeertzjq).
Solution: List the matches and show the menu, as the two behaviors in
          the same phase ask for.  The menu is left to the phases that
          ask for it, so that "list" on its own still only lists
          (Hirohito Higashi).

fixes:  vim/vim#21196
closes: vim/vim#21205

fa1ddffcce

Co-authored-by: Hirohito Higashi <h.east.727@gmail.com>
2026-09-02 09:51:47 +08:00
zeertzjq
8bbcd3e00e vim-patch:9.2.1024: 'wildmode' list:full does not complete first match
Problem:  With 'wildmode' set to list:full, matches are listed but the
          first match is not completed on the first Tab press (rendcrx).
Solution: Do not suppress selection when list and full are active in
          the same completion phase.  Add regression tests for file
          completion, comma-separated phases and noselect precedence
          (Seunghee Kim).

fixes:   vim/vim#19532
related: vim/vim#18088
closes:  vim/vim#21181

54c988c6c5

Co-authored-by: SeungheeKim <ksh368@naver.com>
2026-09-02 09:51:47 +08:00
Justin M. Keyes
9a29622b54 feat(multicursor): MC HAMMER #41587
Other (squashed) commits:

fix(tui): emit ui_send output atomically with the frame

Problem:
tui_ui_send() writes directly to the TTY, bypassing the output buffer.
Sequences sent via nvim_ui_send() (e.g. kitty multiple-cursors, or
visual-dot-repeat) always arrive in a separate TTY write from the frame
they were computed for. This manifests as "tearing", or e.g. in the case
of multicursor the terminal renders text with stale cursor overlays.

Solution:
- tui_ui_send(): while a frame is being assembled (pending invalid
  regions or buffered output), buffer instead of writing directly.
  - Out-of-frame sends (tty queries, clear-on-disable) still write
    immediately.
- mcursor.lua: emit the terminal-cursor update at the end of the redraw
  cycle (`on_end`, when screen positions are final) instead of
  vim.schedule().
2026-09-01 15:17:22 +00:00
Jaehwang Jung
ca992e82d4 fix(buffer): keep trailing slash significant in URI names #41577
Problem:
Literal path comparison ignored one trailing slash for every buffer name,
including URIs. Generic URI syntax does not make a non-empty path
equivalent to the same path with a trailing slash, so distinct URI
buffers collapsed into one.

Solution:
Require equal lengths when comparing URI buffer names, while retaining
trailing-separator normalization for filesystem paths.

AI-assisted
2026-09-01 01:04:57 +00:00
Justin M. Keyes
9d2a31b05e fix(cwd): flicker after temp context-switch #41561 #41582
Problem:
`shorten_fnames()` always redraws the statusline/tabline, even if no
buffer name changed. Since b296666e a temp context-switch
(`win_execute()`, `vim._with{win=}`) restores the CWD, so every such
switch flickers the message area.

Solution:
Redraw only if `shorten_buf_fname()` actually changed a name.
2026-08-31 20:49:50 -04:00
github-actions[bot]
2b6a09c2f1 docs: update version.c #41506
vim-patch:8.2.0992: Vim9: crash when using :import in the Vim command
vim-patch:8.2.2872: Python tests fail without the channel feature
vim-patch:8.2.3589: failure when "term_rows" of term_start() is an unusual value
vim-patch:8.2.3597: Vim seems to hang when writing a long text to a terminal
vim-patch:8.2.3761: focus change is not passed on to a terminal window
vim-patch:8.2.4157: terminal test fails because Windows sets the title
vim-patch:8.2.4535: filename modifer ":8" removes the filename
vim-patch:8.2.4758: when using an LSP channel want to get the message ID
vim-patch:0646047b6 runtime(doc): clarify term_start() I/O behavior for Unix pty and MS-Windows ConPTY
vim-patch:9.2.0412: channel: term_start() out_cb/err_cb no longer deliver raw chunks
vim-patch:9.2.0420: channel: cannot handle binary data via channel callbacks
vim-patch:9.2.0565: [security]: out-of-bounds read in update_snapshot()
vim-patch:ba43008a2 runtime(doc): Update vim9.txt revisions, standardizing SS1,3-7
vim-patch:9.2.1013: [security]: out-of-bounds access in libvterm CSI 8 t resize
vim-patch:9.2.1021: GTK: Cursor blinks irregularly
vim-patch:4b2d42c43 CI: Bump github/codeql-action
vim-patch:9.2.1022: popup: width changes while scrolling
vim-patch:9.2.1023: GvimExt: destructors of polymorphic classes are not virtual

vim-patch:8.2.3979: Vim9: the feature is not mentioned in the right places
vim-patch:9.0.0058: Win32: cannot test low level events
vim-patch:9.0.1604: errors from the codestyle test are a bit confusing

vim-patch:3052bdb: runtime(doc): update the default value for 'isk' on Windows
2026-08-31 05:26:06 -04:00
Justin M. Keyes
25f7c87a70 fix(mappings): replaying a deleted Lua mapping is UB
Problem:
Replaying a deleted Lua mapping, may call an arbitrary function.

RHS of a Lua mapping embeds its LuaRef (`<K_LUA><ref><CR>`). The raw
keys may outlive the ref (redobuff ".", CmdAtom `keys`). If the mapping
is deleted, replaying it either (1) dereferences a freed registry slot,
or (2) calls whatever callback reused the slot (autocmd, timer, other
mapping).

Solution:
Assign a monotonic (never recycled) id to Lua mappings and encode the
mapping keys as `<K_LUA><id><CR>`.

Note: in the case of Vimscript, a deleted function raises E117, but if
the function is redefined with the same name, the mapping will find it.

Alternatives?:
- Globally ensure `LuaRef` ids are not recycled.
  - Problem: could exhaust `int` in a long-lived Nvim session? Also,
    difficult to impl bc the "recycling" is done by `luaL_ref` itself.

ref: 5ac2e47acc
2026-08-31 00:16:01 +02:00
Justin M. Keyes
bc16be3cd9 fix(cmdatom): operator with Lua textobject is not type=operator
Problem:
An operator completed by a Lua `:omap` textobject emits
`CmdAtom.type="mapping"` (lhs-only, no keys) instead of `type="operator"`.

Analysis:
`atom_redo_set()` declined K_LUA, though the prepped redo
("op" + K_LUA + id + CR) is exactly what "." replays. A no-edit "g@"
emits nothing at all.

Solution:
- `atom_redo_set`: don't decline K_LUA; the redo route now captures the
  operator atom.
- `atom_capture_cmd`: don't early-return if the frame has prepped redo.
- op_function(): save/restore redobuff when invoking 'operatorfunc',
  like `call_user_func()` does for Vimscript. (Else the Lua callback
  may clobber the prepped "g@" redo / dot-repeat.)

fix #41482

TODO:
- async Lua (timer/vim.schedule) can still clobber the pending dot-repeat...
2026-08-31 00:16:01 +02:00
Justin M. Keyes
97cf2041c1 Merge #41550 from janlazo/na-patch-channel
build(vim-patch): n/a channel,terminal,test,unicode patches
2026-08-30 08:45:06 -04:00
Christian Clason
6c96ec8c45 build(deps): vendor lpeg
Problem: LPeg is hosted on a single unreliable external hoster, forcing
is to mirror it in neovim/deps, which adds maintenance friction. Also,
we have already vendored the Lua `re.lua` module.

Solution: Vendor all of LPeg v1.1.0; as development is not very active
anymore, this should not add much overhead (and allow us to simplify in
particular the Zig build scripts).

Note: LPeg defines a `luaL_newlib` macro for Lua 5.1, which conflicts
with LuaJIT's extension. This requires inlining the macro defined in
`lptypes.h` and used in `lptree.c`; see README.md for the patch.
2026-08-30 10:53:36 +02:00
Jan Edmund Lazo
82ea5a8aac vim-patch:8.2.4439: accepting "iso8859" 'encoding' as "iso-8859-" (#41545)
Problem:    Accepting "iso8859" 'encoding' as "iso-8859-".
Solution:   use "iso8859" as "iso-8859-1".

1349bd712c

Co-authored-by: Bram Moolenaar <Bram@vim.org>
2026-08-30 07:06:07 +08:00
Jan Edmund Lazo
063c76f500 build(vim-patch): v8.2.3068 is almost auto n/a
Nvim uses utf8proc or an older process involving "src/unicode/"
and "download-unicode-files.sh" to decouple unicode characters
from "mbyte.c".
These unicode patches should be auto N/A but the diff hunk xfuncname
is wrong for utf_char2cells().
Is it because of the `#ifdef`?
Need a volunteer to inspect if these patches still have relevant
changes

Nvim

```c
/// same as utf_composinglike but operating on UCS-4 values
bool utf_iscomposing(int c1, int c2, GraphemeState *state)
{
  return (!utf8proc_grapheme_break_stateful(c1, c2, state)
          || arabic_combine(c1, c2));
}
```

Vim

```c
/*
 * Return TRUE if "c" is a composing UTF-8 character.  This means it will be
 * drawn on top of the preceding character.
 * Based on code from Markus Kuhn.
 */
    int
utf_iscomposing(int c)
{
    // Sorted list of non-overlapping intervals.
    // Generated by ../runtime/tools/unicode.vim.
    static struct interval combining[] =
    {
	{0x0300, 0x036f},
	{0x0483, 0x0489},
	{0x0591, 0x05bd},
        // ...
	{0x0300, 0x036f},
	{0x0483, 0x0489},
	{0x0591, 0x05bd},
    };

    return intable(combining, sizeof(combining), c);
}
```
2026-08-29 14:49:56 -04:00
Justin M. Keyes
cbb0775fb6 Merge #41529 from justinmk/cmdatom 2026-08-29 05:17:52 -04:00
zeertzjq
aea69c660b vim-patch:9.2.1019: completion asked for during 'autocompletedelay' looks automatic (#41535)
Problem:  A completion asked for with CTRL-X CTRL-O while
          'autocompletedelay' is running is given the look of an
          automatic one, the implicit "noselect" among it.
Solution: Turn autocompletion off where a typed key takes the completion
          over.  A completion already on screen goes on being what it
          was (Hirohito Higashi).

closes: vim/vim#21158

c8e432b266

Co-authored-by: Hirohito Higashi <h.east.727@gmail.com>
2026-08-29 08:50:00 +08:00
zeertzjq
5de6537c42 vim-patch:9.2.1017: heap-use-after-free in ml_open_file() (#41533)
Problem:  A SwapExists autocmd can re-open the buffer being edited,
          causing ml_close() to free the memfile that
          ml_open_file() still holds a local pointer to, causing
          use-after-free.
Solution: After findswapname() returns, verify that buf->b_ml.ml_mfp
          is still the same as the copy mfp we hold.

closes: vim/vim#21171

7aecb2cca8

Co-authored-by: Christian Brabandt <cb@256bit.org>
2026-08-29 08:33:38 +08:00
Justin M. Keyes
2514256d95 refactor(input): exec stuffed keys eagerly
Problem:
The Vim "stuff" concept breaks the ability to reason about the call
stack and thus the boundaries of a `CmdAtom`: a stuffed translation ("x"
=> "dl") defers to the main loop. This "continuation" must be modeled in
`CmdAtom`, by checking global flags at undefined times, during undefined
circumstances.

Solution:
- After a stuffed "translation", eagerly execute the stuff buffer
  (`exec_stuffed()`).
- Delete the CmdAtom "continuation" junk.

Note:
- op_colon runs its cmdline "nested", but that's fine because operators
  already nest interactive sessions there (op_change runs edit()), and
  the cmdline is frameless so the operator's frame can own/capture it.
2026-08-29 01:06:26 +02:00
Rob Pilling
61958f2335 fix(cmdwin): allow a user to switch to other buffers #41199 2026-08-28 13:34:31 -04:00
Justin M. Keyes
aaab8cdac4 fix(gen_help_html): cleanup #41525 2026-08-28 10:24:40 -04:00
Rob Pilling
f5d4b5975d fix(tabpage): focusing a tab while closing it, fails assert #41475
Problem:
If an autocommand handler focuses the tab page we're closing during a
`:tabonly` (with some conditions), we hit an assert failure in
`win_close_othertab()`.

For this to occur, we need:
- `nvim_buf_delete()` to trigger `close_windows()` (as is done in
	`cmdwin.lua`'s `_cleanup()`)
- `close_windows()` then calls `win_close_othertab()`, removing the
	window (`win_free_mem()`)
- then in the caller, `tabpage_close_other()`, the loop continues (we
	don't detect `tp_lastwin == wp` since we've unlinked the window)
- the loop assumes that `curtab != tp`

but we've refocused `curtab` so the `ex_win_close()` call passes `tp` as
`curtab`, causing the assert to fail

Solution:
Detect the focus of `curtab` and abort closing the tab.
2026-08-28 08:39:16 -04:00
Kevin
fb083f3850 docs(gen_help_html): generate Typst format #40665
Problem:
gen_help_html.lua only outputs HTML, but Typst is useful for producing PDFs.

Solution:
Add gen_one_typ() and ts_node_to_typ(), which mirror the existing
gen_one_html() and ts_node_to_html().
2026-08-28 07:43:46 -04:00
Justin M. Keyes
7e2e3f8c25 feat(editor): undo restores cursor position #41520
Problem:
Undo places the cursor wherever the cursor happened to sit at "save
time" (`uh_cursor` is sampled lazily on the first change).
Examples:
- `i` preserves, but `a` does not
- `diw`, `atest<Esc>`, `d^` abandon the original position
- `D`, `o` restore it (by accident).

Solution:
`composite` tracks the pending atom (and its `origin`) across frames.
A `stuffed` continuation frame inherits the `origin` + prepped redo.
Store `origin` info in the undo header, so undo can restore it.

- Not for a mid-command undo break (i_CTRL-G_u).
- Undoing a mapping restores where the mapping started (which
  technically may be different than where the "edit" started).
2026-08-27 16:12:11 -04:00
Barrett Ruth
617f9e628c fix(session): window sizes lost in tabpages after an excluded window #41478
Problem: `restore_size` is scoped to all of `makeopens()`, so a window
excluded by 'sessionoptions' latches it off for every tabpage written
afterwards: with `sessionoptions-=terminal`, a terminal in tab 2 makes
tab 3 restore with `wincmd =` instead of its stored sizes.

Solution: Recompute it per tabpage.
2026-08-27 15:48:34 -04:00
bfredl
66868ca408 Merge pull request #41517 from bfredl/pum_race
too many win_float_pos events for the popupmnu
2026-08-27 21:15:30 +02:00
Justin M. Keyes
61df463c5e fix(cmdatom): insert-session entered by feedkeys() ignores typed input #41518
Problem:
An insert-session entered by a scheduled `feedkeys('i','n')` is
classified on entry (as "not typed"), and not reevaluated after that,
thus user input following it is not captured.

Repro: an `:imap` that does `feedkeys('<esc>','n')` and schedules
re-entering insert, emits one CmdAtom for the first press, then nothing
else.

Solution:
Sample `maptick` (ticked by `gotchars()` on typed input, including
mappings) at session start; if it advanced by session end, the session
is user input.
2026-08-27 10:48:33 -04:00
bfredl
6addf6758d fix(ui): too many win_float_pos events for the popupmenu (partial fix)
I got annoyed about so many repeated `win_float_pos` events
for the popupmenu. This fixes some of them but not all of them.

vibe-less explanation: `need_highlight_changed = true` is very expensive
should not be needed when using a window-local highlight namespace. This
was only necessary when overriding the global highlight namespace. This
can instead be handled by using the correct `hl_attr_active` instead of
`highlight_attr`.

Also `pum_grid.pending_comp_index_update` can be cleared when using
win_float_pos prior to redraw.

I wanted to add a test for no repeated win_float_pos event in the same
redraw:flush cycle but that requires deeper cleanups, like
getting rid of crazy redraw panic intermingled deep into insexpand.c
(if "state" is correct, a single update_screen() after the weird
multilayer recursive dance should be enough.)
2026-08-27 14:57:53 +02:00
Justin M. Keyes
02b0c80422 fix(lua): blast radius of broken _G.debug #41507
Problem:
`nlua_pcall()` references `_G.debug.traceback`. If user code deletes it
or breaks it some other way, various Lua features are broken.

    _G.debug = nil
    vim.schedule(function() end)
    vim.wait(100)

    E5113: Lua chunk: attempt to index a nil value
    stack traceback:
            [C]: in function 'loop_poll'
            [string "vim/_core/editor"]:176: in function 'wait'
            crash.lua:3: in main chunk
    PANIC: unprotected error in call to Lua API (attempt to index a nil value)

Solution:
Check `_G.debug.traceback` before using it as errfunc. If it's broken,
omit the traceback and say so in the error message.

Note: We could cache `_G.debug` in LUA_REGISTRYINDEX on startup, but
that would prevent plugins from providing custom functionality there
(and we happen to do so in `tui_spec.lua` for example).
2026-08-27 06:14:24 -04:00
KBS
0346958153 fix(terminal): truecolor SGR with colour space id #41491
libvterm reads the first three sub-parameters of an SGR 38:2 or 48:2 sequence
as R:G:B, so a colour space id shifts the channels: the empty slot becomes red
via CSI_ARG_MISSING truncating to 255, and green and blue move over one.

Skip the colour space id when the colon-separated group holds more than three
arguments. The group length comes from CSI_ARG_HAS_MORE rather than the raw
argument count, so a following semicolon-separated parameter is not consumed.
2026-08-26 10:10:57 -04:00
github-actions[bot]
6e1744c092 docs: update version.c #41391
vim-patch:8.1.2195: Vim does not exit when the terminal window is last window
vim-patch:8.1.2219: no autocommand for open window with terminal
vim-patch:8.2.1160: Vim9: memory leak in allocated types
vim-patch:8.2.2331: Vim9: wrong error when modifying dict declared with :final
vim-patch:8.2.4153: MS-Windows: Global IME is no longer supported
vim-patch:8.2.4586: Vim9: no error for using lower case name for "func" argument
vim-patch:9.0.0627: "const" and "final" both make the type a constant
vim-patch:9.0.1605: crash when calling method on super in child constructor
vim-patch:9.0.1760: vim9 class problem with new() constructor
vim-patch:9.1.0050: Win32 Keyboard handling is sub-optimal
vim-patch:9.1.0270: a few minor issues to fix
vim-patch:fb745756d runtime(doc): add MsgArea to 'highlight' option description
vim-patch:f0837ba0b runtime(doc): In builtin overview use {buf} as param for appendbufline/setbufline
vim-patch:6081c1789 runtime(doc): update help-toc description
vim-patch:2afdb3a65 runtime(doc): Fix minor typo in options.txt
vim-patch:ba0062b0c runtime(helptoc): the helptoc package can be improved
vim-patch:9340aa1bf runtime(helptoc): add s keymap to split and jump to selected entry
vim-patch:b2e21cccc runtime(doc): Tweak documentation in vi_diff.txt
vim-patch:3913f13a7 runtime(doc): Improve :help builtin-function-list table formatting
vim-patch:b8f58dd69 runtime(doc): Fix typos in version9.txt
vim-patch:9.2.0973: Vim9: internal error when a class member is initialized with a closure
vim-patch:9.2.0974: tests: Test_clientserver_serverlist_list() is flaky
vim-patch:9.2.0975: Vim9: assignment to a member of an object member fails
vim-patch:9.2.0977: tests: test_terminal_visual_empty_listchars() is flaky
vim-patch:9.2.0988: tests: Test_terminal_csi_resize_oob() returns early
vim-patch:9.2.0989: libvterm: hang when rendering REP with no preceding char
vim-patch:9.2.0990: libvterm: crash when a scroll region outlives a resize
vim-patch:9.2.0992: popup filter gets the key at the hit-enter prompt
vim-patch:9.2.0994: Vim9: No error for :open during compilation
vim-patch:b618f7ea1 CI: Bump github/codeql-action
vim-patch:b7e0c1cb3 runtime(doc): correct typo in bug report in version5.txt
vim-patch:632abba51 runtime(doc): fix typos and grammar in CONTRIBUTING.md
vim-patch:9.2.0999: serverlist() fails when there is no connection to the server
vim-patch:9.2.1000: Vim9: listener_add() fails when given only a callback
vim-patch:9.2.1003: popup: border is not shown when the popup does not fit
vim-patch:3e46ee723 patch 9.2.1010: compile error when folding feature is disabled
2026-08-26 06:27:12 -04:00
glepnir
f0146bcbe7 vim-patch:9.2.1009: duplicate dict code in ins_compl_dict_alloc() (#41497)
Problem:  ins_compl_dict_alloc() builds the same dict as
          fill_complete_info_dict().
Solution: Call fill_complete_info_dict() instead (glepnir).

closes: vim/vim#21140

303a153694
2026-08-26 08:37:44 +00:00
zeertzjq
ec982dfb93 vim-patch:9.2.1004: a completion function cannot tell why it was called (#41500)
Problem:  A function used through 'omnifunc' or 'complete' is called the
          same way whether 'autocomplete' started the completion or a
          key asked for one, so it cannot answer differently.
Solution: Report which of the two it is in complete_info() as "auto". It
          is returned only when asked for in {what}, so what
          complete_info() says by itself does not change
          (Hirohito Higashi).

closes: vim/vim#21143

1f56c351de

Co-authored-by: Hirohito Higashi <h.east.727@gmail.com>
2026-08-26 07:21:22 +00:00
zeertzjq
0f0e89fd90 vim-patch:9.2.1005: backupcopy=auto overwrites a file in place with umask (#41502)
Problem:  backupcopy=auto overwrites a file in place when umask is restrictive.
          The probe treats permission restore as impossible and writes in
          place, keeping the same inode.
Solution: When creating the probe file, open() applies umask, so a 0644
          file becomes 0600 with umask 0077. Use fchmod() to fix the
          permissions of the probe (Pranav Dwivedi).

closes: vim/vim#21137

fd8aea135d

Co-authored-by: Pranav Dwivedi <dwivedipranav2021@gmail.com>
2026-08-26 07:07:29 +00:00