Problem: K on generic and nullable type annotations can jump to the
wrong help tag. Generated See references repeat the type names.
Solution: Strip generic arguments and nullable suffixes when resolving
help tags. Remove the redundant generated See references.
AI-assisted
Problem:
dynamicRegistration is declared false, and completionProvider is read
from server_capabilities only.
Solution:
Declare it, and prefer registerOptions per field.
Problem:
The LSP client excludes paths such as Git objects and installed
dependencies from file watching. However, watchdirs() still traverses
these subtrees before filtering which directories receive watchers,
paying the filesystem and Lua traversal cost for excluded content.
Solution:
Prune excluded subdirectories during the initial walk, and document
that excluding a directory also excludes its descendants.
Using the LSP client's existing **/node_modules/*/** exclusion on a
synthetic macOS tree with 100 installed packages and 4,000 files:
Before After
Directory scans 503 103
Entries inspected 4502 202
Directory watchers 103 103
The measurements use real filesystem calls and watcher handles, with
identical watcher paths for this exclusion.
Ref: #23291
AI-assisted
Problem: vim.pack uses a private async implementation even though
vim.async is now available.
Solution: Remove the private implementation and run plugin operations in
structured task scopes. Bound parallel work with semaphores and use
protected awaits so cancellation still propagates.
AI-assisted
Problem:
TSHighlighter._on_conceal_line() parses with the range { row, row }. A
Range2 has an exclusive end, so that is the empty range, and no injected
region ever intercepts it.
The root tree is parsed regardless, because its region is empty, so only
injections are affected: conceal_lines metadata coming from an injected
language's highlights query is dropped. on_range_impl() then records the
row in _conceal_checked, so the miss persists until the buffer changes.
For a markdown code block nested in a markdown code block, the inner
fence delimiters stay visible and nvim_win_text_height() reports 5 rows
where 3 are displayed.
Solution:
Pass the one-row range, as the on_range_impl() call below already does.
AI-assisted
Problem:
cmd_on_key() tests the key it was handed against the literal string
'<MouseMove>', but at that point "typed" still holds raw key bytes. Thus
the comparison never holds and the branch its own comment describes
is dead. Moving the mouse over an expanded cmdline collapses it.
Solution:
Translate the key once, up front, so every comparison in the function
sees the same form.
AI-assisted
Problem:
The #has-parent? predicate indexes the result of node:parent() without
checking it, so a capture that matches a tree's root node raises
query.lua:600: attempt to index a nil value
instead of simply not matching. In a highlights query that breaks
highlighting for the whole buffer. The sibling #has-ancestor? predicate
handles the same situation.
Solution:
Treat a missing parent as "does not match".
AI-assisted
Problem:
The "spill" indicator that ui2 appends when messages overflow the
available height is drawn with whatever highlight the message tail
happens to have, so it is indistinguishable from the message text.
Solution:
Give the [+x] chunks an explicit `MoreMsg` highlight, and only fall back
to the message tail highlight for chunks that don't carry one of their
own.
AI-assisted
Problem:
The ui2 dialog implements paging for the arrow keys, Home/End and the
page keys, but not for the mouse wheel. When `mouse` contains `"c"`,
turning the wheel does nothing at all.
Solution:
Handle <ScrollWheelUp>/<ScrollWheelDown>, scrolling by the `mousescroll`
`"ver"` amount.
AI-assisted
Problem:
`]C` jumps the primary onto another cursor, which dedupes at the next
edit. Every `]C` consumes a cursor.
Solution:
`]C` adds a cursor at the current position before jumping. This
effectively "rotates" the primary cursor.
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
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.
Problem:
`parse_ssh_config()` compares tables against a freshly allocated empty
table.
- In `parse_multiple_values()`, the guard which avoids flushing an empty
accumulator never applies. Runs of separators and trailing whitespace
push empty strings into the results, and `is_valid()` does not filter
them. `Host alpha beta ` parses as `{ 'alpha', '', 'beta' }`.
- In `parse_value()`, the condition reduces to `chr == '"' and quoted`.
`quoted` starts false and only that branch sets it, so it can never
become true: quotes are never recognised and are inserted literally,
and the unterminated-quote check is unreachable.
Solution:
Compare `#val` instead. Add a test for repeated and trailing separators.
AI-assisted
Problem:
`IterArray:take()` iterates up to `self._tail`, but `_tail` is
exclusive. When no element fails the predicate, the loop reads one index
past the last element and calls the predicate with `nil`.
Solution:
Stop at `self._tail - inc`, which is the last in-range index for both
iteration directions. Add tests using a predicate which dereferences its
argument and matches every element, forward and reversed.
AI-assisted
Problem:
`inspect_tree()` documents `title` as
`string|fun(bufnr:integer):string|nil`, but the implementation handles
only `nil` and function values. A string title leaves `title` unset and
fails the assertion below.
Solution:
Use the string as the title.
Problem:
An explicitly passed `range` to `vim.lsp.buf.format()` in linewise
visual mode is silently replaced by the selection.
Solution:
Parenthesise the mode check.
Problem:
The `textDocument/documentLink` handler resolves the confirmation buffer
from the request URI and checks it for `nil`, then reads lines from
buffer `0`. When the confirmation buffer is not current, links are
computed from unrelated text and returned against the confirmation
buffer's line numbers, producing missing or misplaced links.
Solution:
Read lines from the resolved `bufnr`.
AI-assisted
Problems:
- Slow shell check measures time in nanoseconds, but reports seconds.
- `kdch1` check incorrectly tests `kbs_entry` instead.
- curl version is passed as an advice, so it is never reported.
Solutions:
- Scale the elapsed time to seconds before reporting.
- Test `kdch1_entry` for `kdch1` check.
- Format curl warning with `string.format()`.
AI-assisted
Problem:
`vim.version.intersect()` returns tables with `VersionRange` (method
table) as their metatable. Although the calculated bounds are correct,
the results do not expose `VersionRange` methods.
Solution:
Construct intersection tables with `range_mt`, matching
`vim.version.range()`. Add assertions covering method access and
repeated intersection.
Problem:
`compute_hash()` reconstructs buffer contents with `fileformat` set to
`mac` using CRLF (`dos`) line endings. The resulting hash does not match
the file's bytes.
Solution:
Define the line ending for every supported file format. Use that mapping
when reconstructing buffer contents for hashing.
Problem:
When disabling, the loader list is traversed forwards while entries are
removed. As a result, some entries are skipped and remain active. Thus,
disabling `vim.loader` restores `_G.loadfile`, but does not fully
restore the original package loader chain.
Solution:
Traverse `package.loaders` in reverse when removing cached loaders.
Capture the original `package.loaders` list in the test, and assert that
the list is restored properly.
Problem:
Fallback pynvim version lookup is broken since Vimscript-to-Lua rewrite.
- `vim.fs.basename()` returns the module filename instead of the
directory needed to discover adjacent metadata. This produces an
empty metadata list, hiding the remaining issues.
- `table.sort()` requires a Boolean comparator, while
`vim.version.cmp()` returns a number.
- `table.sort()` sorts in place and returns no value, so assigning its
result discards the metadata list.
Solution:
- Use `vim.fs.dirname()` to discover adjacent package metadata.
- Use `vim.version.gt()` as the descending Boolean comparator.
- Sort the metadata list in place.
This restores fallback version detection when `neovim.VERSION` is
unavailable.
Problem:
Coordinates-to-position conversion for the finish column in
`vim.hl.range()` checks the start column. This results in an incorrect
finish column when only one of the column coordinates is `vim.v.maxcol`.
Solution:
Check the finish column when converting the finish position. Update the
existing screen test so that a `vim.v.maxcol` finish highlights the
end-of-line marker, matching the existing `-1` behavior.
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#21196closes: vim/vim#21205fa1ddffcce
Co-authored-by: Hirohito Higashi <h.east.727@gmail.com>
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().
Problem: options related to ui2 (like `fillchars` with `msgsep`) do not
take effect if set during startup after enabling ui2.
Solution: explicitly check just after startup if relevant options were
changed during startup.
Problem:
Folding range markers are overwritten while ranges are evaluated. A range ending on a row can hide another range starting there, and multiple nested ranges ending together emit only the innermost ending level.
Solution:
Track starts and the number of ends per row before emitting markers. Prefer starts on shared boundary rows and use the outermost level when nested ranges end together.
AI-assisted
Problem:
Confirming cmdwin with a UTF-8 character containing 0x80 does not complete the
command.
Solution:
Escape K_SPECIAL bytes while feeding the cmdwin input after confirmation.
Problem:
The cmdline_hide callback causes an unnecessary cursor move to the cmdline,
before the normal redraw updates the cursor back to the current window. This
appears as "flicker" when using plugins such as matchit (legacy ":" mappings
instead of "<cmd>" mappings).
Solution:
Skip the immediate redraw for cmdline_hide events. The normal redraw
still updates the cursor after the cmdline window is hidden.
Problem:
If a snippet does not have a placeholder, we use insert mode instead of
select mode. From here <Esc> leaves the session and highlight active.
Solution:
Cancel the session on <Esc>.
Problem:
When 'cmdheight' is 0, the MsgSeparator can obscure the statusline when
a message is displayed, making its contents disappear until
the next redraw.
Solution:
Skip the separator when 'cmdheight=0' available.
Problem:
A blank line before the Content-Length header makes header parsing
fail with "Content-Length not found in header". An LF in the 'name'
state falls through to the 'invalid' state, which only return to
'name' at the *next* LF. That LF terminates the next line, so the
line with Content-Length is swallowed.
The same issue happens when a junk line is a partial match of the
header name (e.g. "Cont\nContent-Length: ...").
Solution:
When seeing an LF in the 'name' state, reset the cursor and stay in
'name' rather than entering 'invalid'.
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).
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#211431f56c351de
Co-authored-by: Hirohito Higashi <h.east.727@gmail.com>
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#211461afe7ad1bb
Co-authored-by: Barrett Ruth <br@barrettruth.com>
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.
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.
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.