Commit Graph

11199 Commits

Author SHA1 Message Date
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
Stefan VanBuren
8d9a798d6d fix(man): don't grow a highlight into the previous line #41490
Problem:
With these two lines (`\b` marking a backspace):

    f\bfoo
    xb\bb

line 1's bold run ends at byte 1 and line 2's begins at byte 1, so line 1
renders "fo" in bold rather than "f", and line 2's "b" is not bold at all.

Solution:
Only grow a highlight group that is on the current row.
2026-08-26 09:09:17 -04:00
Justin M. Keyes
e7ae1b3c10 fix(ui2): cmdwin is not special #41508
Problem:
Unreliable ui2 test:

    FAILED   …/ui/messages2_spec.lua @ 277: messages2 multiline messages and pager
    …/ui/messages2_spec.lua:277: Row 1 did not match.
    Expected:
      ...
      |*{1::}echo "foo" | echo "bar\nbaz\n"->repeat(&lines)      |
      |*{1::}messages                                            |
      |*{1::}^                                                    |
      ...
    Actual:
      ...
      |*{9:vim.schedule callback: ...ork/neovim/neovim/runt [+7]}|

Solution:
ui2 is doing contortions to handle the old cmdwin behavior; stop doing
that, it's no longer necessary since b2bf7bcfb1.
2026-08-26 08:32:58 -04:00
zeertzjq
d393abad77 test: fix wrong order of args to eq(), neq() #41499 2026-08-26 07:18:22 -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
f9186e0c0e vim-patch:9.2.1011: [security]: arbitrary Ex command execution during C omni-completion (#41501)
Problem:  arbitrary Ex command execution during C omni-completion via
          tag file names (Yazan Balawneh)
Solution: Escape the | for all returned tag files

Github Security Advisory:
https://github.com/vim/vim/security/advisories/GHSA-r77m-8m55-rpr6

331d5d6702

Co-authored-by: Christian Brabandt <cb@256bit.org>
2026-08-26 07:53:04 +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
glepnir
f97bcbdf77 vim-patch:9.2.1007: fuzzy completion list wrongly sorted after complete() (#41498)
Problem:  With 'completeopt' "fuzzy", a re-sort after complete() leaves the
          last match unsorted at the end of the list.
Solution: Find the original text by its flag instead of assuming
          compl_shows_dir points at it.

closes: vim/vim#21103

7e74722999
2026-08-26 04:32:09 +00:00
zeertzjq
35ae925da9 vim-patch:9.2.1002: filetype: bazelrc files are not recognized (#41496)
Problem:  filetype: bazelrc files are not recognized
Solution: Detect *.bazelrc and tools/bazel.rc files as bazelrc filetype,
          include syntax und filetype plugins, update the menus
          (Barrett Ruth).

closes: vim/vim#21146

1afe7ad1bb

Co-authored-by: Barrett Ruth <br@barrettruth.com>
2026-08-26 11:41:17 +08:00
Justin M. Keyes
604bda445a test(harness): avoid overlong socket filename #41488
Problem:
Test sockets live under `$TMPDIR`, which the harness points at the build
dir.  On macOS/BSD `sockaddr_un.sun_path` is 104 bytes, and a CI build
path plus "nvim.<pid>.<n>" leaves little room:

    /Users/runner/work/neovim/neovim/build/Xtest_tmpdir_terminal/nvim.runner/aBcDeF/nvim.12345.0

Solution:
Point XDG_RUNTIME_DIR (`stdpath('run')`) at "/tmp/nvim_<pid>".  28 bytes:

    /tmp/nvim_19916/nvim.19919.1

TODO?:
- `TEMP_DIR_NAMES` prefers `$TMPDIR` over `/tmp`, so on macOS
  `stdpath('run')` defaults to the long `/var/folders/<xx>/<…>/T/` path
  instead of the short `/tmp` alias.

Note:
- The 104-byte limit applies to the `bind()` arg, not its "realpath",
  to, so `/tmp/…` symlinks can be used to workaround the limit.
2026-08-25 14:15:48 -04:00
Justin M. Keyes
aae43789d7 revert: "fix(lsp): do not expand $VAR in tagfunc filenames" #41489
revert c5cb0350a6
2026-08-25 18:09:02 +00:00
Justin M. Keyes
8adf6e769f Merge #41484 from justinmk/retrytest
test(harness): support `{retries=…}`
2026-08-25 11:05:16 -04:00
Justin M. Keyes
3aa323584e test(shell): unreliable "throttles shell-command output…" 2026-08-25 16:37:32 +02:00
Justin M. Keyes
03dfbe333c test(channel): unreliable "chansend sends lines…" 2026-08-25 16:34:21 +02:00
Justin M. Keyes
8ca36b9783 test(harness): support {retries=…}
Problem:
Cannot retry a test with its full `after_each`/`before_each` lifecycle.

Solution:
- Overload `it()` to accept an `opts` param:
  ```
  it('flaky', { retries = 2 }, function(ctx) end) -- 3 attempts.
  ```
- Pass `ctx` to test functions.
- Fix a bug in `t.read_file_list()`.
2026-08-25 16:34:21 +02:00
Justin M. Keyes
fb5d466a35 fix(help): :helptags on CRLF helpfiles
Problem:
The vimdoc parser does not treat "\r" as whitespace, so in a CRLF
helpfile the codeblock rule fails and "*" pairs inside examples become
tags. On Windows this generates duplicate "." and "/" tags:

    D:/a/neovim/neovim/build/bin/nvim.exe -u NONE -i NONE -e --headless -c "helptags ++t doc" -c "exe 'cquit' !empty(v:errmsg)""
    Error in command line:
    E154: Duplicate tag "." in gui.txt and repeat.txt
    E154: Duplicate tag "/" in pattern.txt and usr_08.txt
    E154: Duplicate tag "/" in usr_08.txt and pattern.txt

Note: The old C parser was unaffected because it read helpfiles in text
mode (`os_fopen(…, "r")`).

Solution:
Strip "\r" before parsing.
2026-08-25 13:36:01 +02:00
Justin M. Keyes
1ef030f162 fix(help): :helptags regressions
Problem:
Parent commit regressed some behavior of the old C helptags-gen impl:
- helpfiles in sub-directories are named by basename, so :help fails
- "help-tags" always names "tags", never "tags-nl"
- E150 E151 E152 E153 are never reported
- existing tags file is not overwritten if no tags were found
- a duplicate tag aborts the run, skipping the remaining directories
- `*.TXT` and `*.FRX` (uppercase) are not recognized as helpfiles
- tree-sitter-vimdoc accepts tags that the C parser rejected: `*a|b*`
  (breaks |links|) and unterminated `*tag`
- PUC Lua "<" compares with (localized) `strcoll()`, so the tags file is
  not sorted by byte value (E432)
- requiring `vim.treesitter` at load time breaks :help itself, not just
  :helptags, where the module is unavailable
- `vim.pack` runs :helptags for plugins that have no "doc/" directory,
  so every install/update emits E150

Solution:
- Fail the build if generating helptags reports any `v:errmsg`.
- Restore old behavior: tags generated for runtime/doc are now identical
  to those from the C implementation. Errors are non-fatal messages
  instead of exceptions, so all directories are still processed.
- Load treesitter lazily, report a plain error if parser is missing.
2026-08-25 13:32:25 +02:00
Yochem van Rosmalen
b36b3d7f3a feat(help): generate :helptags using Treesitter
Problem:
Tags are manually parsed in C which is not flexible and prone to errors.
Extending the help system to allow for other formats (e.g. Markdown)
would require a large rewrite in the C core, while with Treesitter it
only needs a query update.

Solution:
Use the power of treesitter to extract the tags from helpfiles.

- build: set `$VIMRUNTIME` when generating helptags, like
  `cmake/Util.cmake` already does for other generators.
- fix(help): only accept tags delimited by whitespace. The old C parser
  only accepted a `*tag*` preceded by start-of-line or whitespace and
  followed by whitespace or end-of-line. The vimdoc parser also captures
  tags followed by other text, e.g. `*$XDG_STATE_HOME*/.../logs` in
  starting.txt, which caused an E154 duplicate tag error for docs that
  were previously fine.
2026-08-25 10:52:12 +02:00
Justin M. Keyes
a1de07418b feat(ui2): lift ui2 options into 'messagesopt' (part 1) #41474
Problem:
In order for ui2 to graduate to the main "messages ui" its configuration
needs to graduate into actual options.

Solution:
Migrate some of its config to 'messagesopt':
- "maxheight" (note: currently this is an integer treated as
  a "percentage"; if we want to support a row count we could allow
  values with units, like `"42%"`)
- "pager"
- "timeout"

Also improves error messages:

    messagesopt=hit-enter,history:500,bogus       E474: Unknown item 'bogus'
    messagesopt=hit-enter,history:500,progress:x  E474: 'progress' must be one of: , c
    messagesopt=hit-enter,history:abc             E474: 'history' requires a number
2026-08-25 03:58:17 -04:00
Justin M. Keyes
510f61555b fix(ui2): pager_char does not match keycode aliases #41468
Problem:
`pager_char` compares the `keytrans()` result exactly. E.g. `pager_char
= "<cr>"` does not work, it expects `<CR>` (uppercase).

Solution:
Normalize `pager_char` in enable().
2026-08-24 15:01:50 -04:00
Étienne Robert
d1b811b6c2 fix(lsp): advance inline completion range over matching input #41434
Problem:
`Completor:accept()` applies the item against the range the server
answered with. Characters typed after the request but before accepting
lie inside neither that range nor the replacement, so they are left
behind by the insertion: typing `a` after `foob` and accepting `foobar`
within the 200 ms debounce yields `foobara`.

Solution:
Grow the item's range over the characters typed since the request, so
that `accept()` replaces them. Only while the candidate still matches,
decided by the longest common prefix `show()` already computes, so input
that contradicts it is left alone. Snippet items are unaffected, since
`accept()` ignores the range for them.
2026-08-24 10:51:39 -04:00
Justin M. Keyes
8c440c469b fix(ui2): ui2 misinterprets getchar() input as pager_char (CR) #41465
Problem:
`vim.on_key()` callbacks are invoked for keys that `getchar()` consumes,
which never reach the main loop, and there is no way to tell such a key
apart.  With ui2 the `on_key` handler activated after interactive
cmdline, handles it as `pager_char` (thus enters the ui2 pager).

Solution:
Skip `vim.on_key()` callbacks during `getchar()`/`getcharstr()`.
2026-08-24 10:18:47 -04:00
Étienne Robert
143bbfbb12 fix(lsp): clamp inline completion range to the line on accept #41435
Problem:
`Completor:accept()` hands the item's range straight to
`nvim_buf_set_text()`. That range is resolved when the response arrives,
so text deleted before accepting (backspacing within the 200 ms
debounce) leaves the range end past what is left of the line, and the
accept aborts with `Invalid 'end_col': out of range`.

18d6436 fixed the same staleness in `Completor:show()`, which drops an
item once its range *start* falls outside the buffer. That guard does
not cover the range end, and `accept()` was never given one, so this is
a hole left by that fix rather than a regression of it.

Solution:
Clamp the end of the range to the end of the line before writing. The
start needs no clamp: `show()` dropped the item one event loop tick
earlier if it had gone out of range.
2026-08-24 08:41:57 -04:00
glepnir
2a382ffb61 fix(lsp): completion overwrites text before the word boundary #41463
Problem:
One item with an edit range before the word boundary changes the start
column for all items. For tsserver `str.`, completing `str.char` can
become `strcharAt` and break filtering.

Solution:
Prepend the missing text and filter on it.
2026-08-24 06:14:49 -04:00
glepnir
92d5531f96 vim-patch:9.2.1001: complete_info() does not report the item highlight groups (#41461)
Problem:  complete_info() omits "abbr_hlgroup" and "kind_hlgroup".
Solution: Keep the highlight group ID with the match instead of the
          resolved attribute and add both entries to the returned items
          (glepnir).

closes: vim/vim#21105

1c32cede0a
2026-08-24 14:35:25 +08:00
zeertzjq
70958dae75 vim-patch:9.2.0996: debugger: crash when evaluating a variable in a :def function frame (#41460)
Problem:  In the debugger ">up" and ">frame" select an older function call
          frame, but get_funccal_local_ht() and the related functions check
          current_funccal while returning a dictionary of the frame that
          get_funccal() selected.  A :def function keeps its local variables
          on the vim9 stack, its funccall_T has no l: and a: dictionaries and
          is allocated cleared, so with such a frame selected the returned
          hashtab has a NULL ht_array and evaluating any variable name at the
          debug prompt crashes in hash_lookup().
Solution: Check the funccal that is actually used and return NULL when it has
          no l: variables, so that the variable is reported as undefined
          instead.

closes: vim/vim#21111

5ad47b07af

Co-authored-by: Christian Brabandt <cb@256bit.org>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 09:27:46 +08:00
Justin M. Keyes
eeefe7ca65 fix(cwd): E812 when a message opens a window during a file read #41458
Problem:
When ui2 is enabled, opening an already-open file in another Neovim
instance results in the error `E812: Autocommands changed buffer…`.

Analysis:
On E812 the file is not loaded.  The default SwapExists handler notifies
`W325: Ignoring swapfile…`, and ui2 shows that message by opening
a window, which is a temp context switch.  `ctx_dirs_restore()`
re-shortens every buffer name on the way out, and `shorten_buf_fname()`
always frees and reallocates `b_sfname`.  `readfile()` aliases `b_fname`
across `check_need_swap()` and compares the pointer to detect a rename.

Regression by b296666e41, which replaced the `cs_save_sfname` restore
(that kept curbuf's pointer) with `shorten_fnames(true)`.

Solution:
Keep the allocation in `shorten_buf_fname()` when the short name is
unchanged.  Pointer stability is what the E200/E201/E812 guards actually
assert.
2026-08-23 19:32:07 -04:00
not_compiled
716835c232 fix(ui2): dismiss msgs after mapped motions #41450
Problem:
Mapped keys can produce on_key callbacks with an empty `typed` value.
so mapped motions such as `j -> gj` are not recognized as typed input
and do not dismiss messages

Solution:
Track whether an empty `typed` callback follows typed input, allowing
mapped motions to dismiss messages like directly typed motions.
2026-08-23 19:09:26 -04:00
Justin M. Keyes
69bf8c7792 fix(cmdatom): terminal-mode keys leak into mapping lhs #41455
Problem:
A mapping that enters terminal-mode (`:FzfLua files` via `:startinsert`)
never reaches a composite-end: terminal-mode runs no "normal" CmdFrames.
The composite collects the entire terminal session (and more) into `lhs`.

E.g. if I have `<M-/>` mapped to open `:FzfLua files`, then interact
with fzf-lua UI, the emitted CmdAtom looks like:

    <M-/>… => { type='mapping', lhs='<M-/><C-N><C-N><CR>', keys=nil }

Solution:
End the composite when terminal-mode is entered, which emits a more
meaningful and repeatable atom:

    <M-/>… => { type='mapping', lhs='<M-/>', keys=nil }
2026-08-23 15:04:11 -04:00
Justin M. Keyes
bb82f9612c fix(cmdatom): "!" operator hardcodes its range #41451
Problem:
The "!" operator stuffs its cmdline continuation (`:.,.+1!`), so its
frame ends before capture (stuff pending) and the redo-prep disappears
with it.

    !ipsort<CR> => { type='excmd', lhs=':.,.+1!sort<NL>', keys=':.,.+1!sort<NL>' }

Compare to builtin "." which works bc `do_bang()` completes the redo
(`!ip` + `sort<NL>`).

Solution:
Appoint the stuffed continuation frame as the "redo-prep" frame.

    !ipsort<CR> => { type='operator', operator='!', lhs='!ipsort<NL>', keys='!ipsort<NL>' }

Notes:
- atom_cmd_end(): a frame ending with stuff pending re-points its
  redo-prep to the next frame.
- atom_cmd_start(): a stuffed continuation frame (`KeyStuffed`) keeps
  the redo-prep; flushed stuff discards it.
2026-08-23 13:58:45 -04:00
Shubh
c5cb0350a6 fix(lsp): do not expand $VAR in tagfunc filenames #41398
Problem:
CTRL-] via lsp tagfunc passes URI paths through :tag, which expands
\$VAR, so files like people_.\$personId.tsx fail with E429.

Solution:
fnameescape() the filename in tag items so \$ is treated as literal.
2026-08-23 11:30:17 -04:00
Shubh
bca9c4afa7 fix(man): allow :Man to open by absolute path #41401
Problem:
man -w echoes any existent file back unchanged. :Man rejected all such
paths, so :Man /usr/share/man/man1/bash.1 failed even for real man pages.

Solution:
Accept an echoed path when it looks like a man page (.../man1/foo.1).
2026-08-23 11:28:22 -04:00
Olivia Kinnear
8ce1aac477 feat(pos): mutable vim.Pos, vim.Range fields #41318
Allow plugins to set the human-readable fields.
2026-08-23 07:13:41 -04:00
zeertzjq
f534e06c70 test(netrw): fix failure on Windows 2026-08-23 09:33:26 +08:00
zeertzjq
9a4711dec7 vim-patch:9.2.0995: tests: no check that g:netrw_home has higher priority than $MYVIMDIR
Problem:  No check that g:netrw_home has higher priority than $MYVIMDIR.
Solution: Set $MYVIMDIR to a different directory at the start of the
          test function.

Also, s:NetrwHome() checks has('nvim') before exists('$MYVIMDIR'). This
ordering does make sense as Nvim's docs have no mention of $MYVIMDIR.
Match that in the test.

closes: vim/vim#21117

b3526a91b8
2026-08-23 09:33:26 +08:00
zeertzjq
3ab4a70338 vim-patch:9.2.0993: runtime(netrw): error when parent dir of g:netrw_home doesn't exist
Problem:  netrw: error when parent dir of g:netrw_home doesn't exist.
Solution: Use 'p' flag of mkdir() (zeertzjq).

closes: vim/vim#21113

ffce1ce58d
2026-08-23 09:33:26 +08:00
zeertzjq
bf7d308000 vim-patch:9.2.0980: runtime(netrw): prioritize g:netrw_home on Neovim
Problem:  runtime(netrw): g:netrw_home not respected on Neovim
Solution: prioritize g:netrw_home for bookmarks and history directory
          setting, add tests (J. Paulo Seibt)

related: neovim/neovim@5a78c5b
closes:  vim/vim#21091

4be03620b3

Co-authored-by: J. Paulo Seibt <jpseibt@gmail.com>
2026-08-23 09:33:25 +08:00
Justin M. Keyes
b296666e41 fix(cwd): stale buffer names after temp context-switch #41433
Problem:
1. `ctx_dirs_restore()` is the only chdir site that doesn't re-shorten
   buffer names; `post_chdir()`, `update_cwd()` and `do_autochdir()` all
   call `shorten_fnames(true)`.  So after a temp window-context switch
   that moved the CWD, every buffer's `b_fname` is still relative to the
   other directory.  ui2 renders messages in a float, and entering it is
   such a switch, so with a `:bcd` in a `BufReadPost` handler 'statusline'
   "%f" shows ".config/nvim/init.lua" while the CWD is already
   `~/.config/nvim`, and :write resolves the name against it:
   ```
   E212: Can't open file for writing: no such file or directory
   ```
2. `msg_multihl()` leaves `msg_ext_id` pointing at the caller's storage
   when nothing was emitted (e.g. 'msg_silent'): the reset only ran on
   a flush that produced chunks.  The next message then ships a dead
   stack frame as its msg_show id.  After ":silent write" the id is
   buf_write()'s `msg_id[MAXPATHL + 32]`:
   ```
   id = "\0\0\0\0\0\0\0\0\29\0(<C6>k\24R\17p<C7><C7><C7>\1\0\0\0..."
   ```

Solution:
1. `shorten_fnames(true)` after restoring the CWD.  Drop `cs_save_sfname`,
   which was a partial workaround for the same bug.
2. Release the id in `msg_multihl()`, where it is set and the caller's
   frame is still alive. Stop reading `.data.integer` out of a String
   union member.
2026-08-22 16:24:05 -04:00
Freddie Haddad
9b0bc7edef fix(plines): 'linebreak' ignores inline virtual text width #41227
Problem:
With 'linebreak', a word is moved to the next screen line when it does
not fit on the current one. The check measures only the buffer text, so
inline virtual text anchored inside the word is not counted. The word is
kept on a line it cannot fit, and the virtual text is split across the
screen line boundary.

Solution:
Count the width of inline virtual text anchored within the word, so the
check uses the width that is actually displayed.
2026-08-22 19:48:16 +00:00
Justin M. Keyes
7037e1effe fix(write): :write can target a garbage filename #41432
Problem:
`buf_write()` captures the buffer's `fname`/`sfname`/`ffname`, then emits its
progress-message before opening the file. This may run user code
synchronously: the `Progress` autocmd, and the `msg_show` handler of an
in-process UI (ui2). Either can change the CWD, and `shorten_fnames()`
then frees/reallocs every buffer's short name. The rest of `buf_write()`
reads the freed name...

    "foldtext()" [New] 41L, 997B written
    E212: Can't open file for writing: illegal byte sequence

ASAN, with ui2 enabled and a `BufEnter` handler that runs `:lcd`:

    READ  path_skip_sep <- path_tail <- match_file_list <- buf_write
    FREE  shorten_buf_fname <- shorten_fnames <- update_cwd <- set_curbuf
          <- win_set_buf <- nvim_open_win <- ui2 msg_show handler
          <- ui_call_msg_show <- msg_ext_ui_flush <- buf_write

Solution:
Copy the names after the `*Pre` autocmds.

Note: `readfile()` has the same shape, but its messages pass no
progress-id, so they skip `msg_progress()`. Safe, for now...
2026-08-22 15:07:45 -04:00
Justin M. Keyes
47cd769ed5 feat(cmdatom): mappings capture continuation
Problem:
A mapping that ends mid-operation (`nnoremap ,D d`) emits a content-free
"mapping" atom plus a `pending` field, and the "continuation" motion
arrives as a sibling atom. Consumers must stitch the two together (which
has broken cases, e.g. Insert-opening mappings (",i") lose their session
entirely).

- ",i": the session atom is dropped bc the mapping RHS is consumed
  before the session starts (typebuf_maplen()==0), so
  atom_is_user_input()=false.
- ":normal"-in-opfunc: the opfunc internal "v..y" session (a) became
  kVatomTyped just because the deferred composite was open, masking the
  real operator capture via atom_captures, and (b) its nested frames
  re-derived the outer redo.
- "Motion" based on `moved=true`, has false negatives.
- `CmdAtom.remap` is unnecessary, and clutters the docs/usage.

Solution:
- Introduce `frame_id` to identify CmdFrames.
- Classify `type=motion` better, via `NV_MOTION` flag on the `nv_cmds` table.
- Drop `CmdAtom.pending`, `CmdAtom.remap`.
- Defer atom_composite_end() at the clock edge while an operator is
  pending, Visual is active, or `restart_edit` is set: the composite
  keeps collecting, so the continuation is captured in the mapping atom.
- ",i": Now an open composite counts as user input.
- ":normal"-in-opfunc: Now handled correctly.
- `remap` is now decided by `composite.payload || 0 subatoms`.
  atom_payload_mark() records the read the resolution never captures.
- `toplevel` is now decided by `CmdFrame.parent == NULL`.

before/after:

    INPUT       BEFORE                             AFTER
    ---------------------------------------------------------------------
    ,D w        {mapping lhs=,D pending=operator}  {operator lhs=,Dw keys=dw}
                + {operator keys=dw}
    ysiw"       {mapping lhs=ys pending=operator}  {operator lhs=ysiw" keys=g@iw"}
                + {operator lhs=g@iw"}
    ,v d        {mapping pending=visual}           {visual lhs=,vd keys=viwd}
                + {visual lhs=viwd}
    ,i XY<Esc>  {normal keys=i lhs=,iXY<Esc>}      {insert keys=1iXY<Esc> text=XY}
2026-08-22 17:29:34 +02:00
Shubh
f583991553 fix(url): escape cmd.exe special chars in vim.ui.open() URL #41394
Problem:
On Windows, vim.ui.open() passes URLs to `cmd.exe /c start`. cmd.exe
treats `&` as a command separator, so query strings are truncated.

Solution:
Caret-escape `&|<>^%!` in URIs when the open handler is cmd.exe.
2026-08-22 10:49:55 -04:00
Sean Dewar
dadaa7fcad fix(ui2): offset extmarks copied to pager by srow #41425
Problem: wrong end_row when copying extmarks to the pager buffer when expanding
a message. Results in incorrect highlights or errors such as "invalid `end_col`
out of range".

Solution: don't forget to offset end_row by srow, as is already done for the
copied lines and extmark rows.
2026-08-22 10:19:16 -04:00
glepnir
2c5f37f6af fix(lsp): empty filterText/sortText/insertText not treated as unset #41421
Problem:
Empty string is falsy, so the spec's label fallback applies, but there only checks
for nil. An empty filterText drops the item, an empty sortText sorts it
first, and an empty insertText leaves the word empty.

Solution:
Treat empty string as unset.
2026-08-22 07:40:17 -04:00
zeertzjq
f9ea56f959 test: fix $NVIM_LOG_FILE from environment being overwritten (#41420)
Instead of setting it in the test runner (which runs in an Nvim instance
that already has a log file), make run_tests.zig set a $NVIM_LOG_FILE
fallback path like what RunTests.cmake does.
2026-08-22 15:58:47 +08:00
Justin M. Keyes
6423657352 feat(cmdatom)!: eliminate the need for vim-repeat #41414
Problem:
- `lhs` is not fully realized. E.g. for a "payload" mapping
  `lhs` omits the `getchar()` payload during a mapping (vim-surround
  `ds'` reports `lhs="ds"`). This means plugins like vim-repeat are
  still needed...
- The "delta" fields of a CmdAtom are calculated too late.
  - `<abuf>` and `changed` check whatever (wrong) buffer a command
    (":bnext") might land in.
  - CTRL-W_w between two windows on the same buffer reports type="motion".

Solution:
- `CmdOrigin` samples (buf/win/cursor/changedtick) at each "scope" entry
  (CmdFrame, composite, Visual session, insert session).
- `dd<C-w>l` reports `changed=true` for the buffer it edited, regardless
  of where the cursor ends up.
- New fields:
  - `pos`: cursor position at command start.
  - `moved`: indicates whether the cursor moved (in same buffer).
  - `undoseq`: undo state at settlement.
- lhs now includes the payload: "ds)" reports lhs="ds)" instead of "ds".
  - Easy for users to "replay" any atom.
- Rename: type "command" => "normal", "ex" => "excmd"; `arg` => `cmdarg`
- Drop `cascade` field (no reason to expose it)
2026-08-21 13:42:17 -04:00
zeertzjq
eaff5417be vim-patch:9.2.0987: heap-buffer-overflow in spell_suggest() (#41406)
Problem:  Heap-buffer-overflow in spell_suggest() when the cursor is
          beyond the end of the line, because a SpellFileMissing
          autocommand changed the buffer (dvaave2025).
Solution: parse_spelllang() may run autocommands, so validate the cursor
          position and re-take the saved position afterwards.

fixes:  vim/vim#21097
closes: vim/vim#21100

Supported by AI.

6073903cda

Co-authored-by: Christian Brabandt <cb@256bit.org>
2026-08-21 08:51:56 +08:00
zeertzjq
af37d459a6 vim-patch:9.2.0985: Multiline messages not visible when mapping starts cmdline (#41405)
Problem:  Multiline messages exceeding 'cmdheight' not visible when a
          mapping starts cmdline immediately after it (after 9.2.0967).
Solution: Revert patch 9.2.0967 and use a different solution (zeertzjq).

fixes:  vim/vim#21098
closes: vim/vim#21101

fb4866a2dd
2026-08-21 07:46:34 +08:00
Justin M. Keyes
31de0d69fc fix(cmdatom): lhs not always reported in CmdAtom #41386
Problem:
An unreplayable Visual operation does not emit a CmdAtom event. That's
maybe not super important, but it hints at a flaw in how `vatom`
"voiding" is plumbed: `vatom.state=kVatomVoid` replaces the "kind", so
that info is lost to later parts in the lifecycle.

Solution:
- Define `VatomState` as "flags", so `kVatomVoid` can "poison"
  `vatom.state` without losing its kind flag.
- Unify `lhs`: always report the original user input in `CmdAtom.lhs`,
  for all kinds of user actions: visual, "translated"/"stuffed" cmds,
  and dot-repeat (".") itself.
- Unreplayable Visual atom emits CmdAtom with non-empty `lhs` and empty
  `keys`, like a mapping/macro composite.
2026-08-20 05:18:36 -04:00