Commit Graph

8689 Commits

Author SHA1 Message Date
Justin M. Keyes
cb2e9cdfd3 fix(multicursor): clipboard=unnamed, explicit "+yy #41804
Problem:
- With `clipboard=unnamed[plus]`, per-cursor yanks are joined into '"'
  on exit, but not the clipboard.
- Multicursor `"+yy` calls the clipboard provider per-cursor, behaving
  as "last wins".

Solution:
- Never cascade to/from the clipboard provider.
- Implicit clipboard (`clipboard=unnamed[plus]`) uses the cursor-local
  unnamed register, so the cascade needs no `start_batch_changes()`. On
  exit, the clipboard gets the same joined result as the `"` register.
- Operations that reference the explicit clipboard registers `"+` / `"*`
  while multicursor is active, use the cached (primary) clipboard value.
2026-09-09 07:25:32 -04:00
Lewis Russell
efdd73c096 feat(watch): add on_error callback
Problem: Watcher failures are reported inconsistently, and callers
cannot detect when a watcher is no longer usable.

Solution: Route startup and event failures and unexpected inotifywait
exits through an optional on_error callback. Log to nvim-watch.log by
default. Ignore disappearing child directories and exits caused by
cancellation. Let LSP log failures and notify once per message, using
INFO for missing roots and ERROR otherwise, without interrupting
registration.

AI-assisted
2026-09-09 11:39:57 +01:00
Justin M. Keyes
493a4dd571 fix(multicursor): mapping that creates initial cursors is not replayed #41788
Problem:
A Visual-mode mapping that creates cursors (`xmap I Q0i`) replays its
commands only if cursors already existed when it started.

Solution:
Always collect `composite`. Consumers are still decided per frame
(`CmdFrame.consumers`) and at emit/cascade.
2026-09-08 12:05:34 -04:00
Lewis Russell
6a54396db4 build: enable more EmmyLua checks
- call-non-callable
- missing-fields
- redefined-label
- redefined-local
- undefined-field
- unreachable-code

Promote redefined-local and unreachable-code to warnings.

Fix DOS line-ending handling for multiline semantic tokens, and support
intersection types in the help parser.

AI-assisted
2026-09-08 16:50:53 +01:00
zeertzjq
e66fdc1eac vim-patch:9.2.1038: CursorLineFold/Sign highlighting depends on 'cursorlineopt' (#41785)
Problem:  CursorLineFold and CursorLineSign are only applied when
          'cursorlineopt' contains "number" or is "both"
          (Evgeni Chasnovski).
Solution: Apply those groups whenever 'cursorline' is set, independent
          of 'cursorlineopt' (Vrushali Zampalkar).

fixes:  vim/vim#20480
closes: vim/vim#21112

1f8d3c44c5

Co-authored-by: Vrushali-Z <zampalkarvasu@gmail.com>
2026-09-08 15:24:45 +00:00
bfredl
623508dc06 Merge pull request #41751 from bfredl/stringly_nils
test(ci): non fatal errors for [NULL] safety in vim_snprintf
2026-09-08 17:12:26 +02:00
bfredl
b6b190291d fix(api): do not pull a fast one for nvim_del_augroup_by_id
This is a bit more boilerplate because of painful impedance mismatch (is
augroup names the root source of truth? is ids the source of truth? who
knows? what does truth even mean?), but show an error reflecting what
the caller actually tried (they passed in an invalid id, not a null
string)
2026-09-08 14:04:10 +02:00
bfredl
97660307f5 fix(strings): non fatal errors for [NULL] safety in vim_snprintf
context: vim_snprintf provides a "portable" replacement for a system
vsnprintf(). However we rely on system vsnprintf() in places. This
is quite arbitrary. I want to use system vsnprintf() only, and
reduce the machinery in string.c for typval printf only, which will
delet a lot of duplicated code.

The biggest hurdle here is not "portably" (we only support sane c
runtimes) but the following discrepancy: standard libc printf()
considers NULL args to %s to be undefined behavior strictly
while vim_snprintf() _defacto_ allows this by replacement to "[NULL]"

IMO, this should be seen as "graceful" error handling (nicer than crashing
on the user), not a valid means to have the string "[NULL]" intentionally
be shoved in the users face. more robust code should explicitly
check for NULL and replace it with a _appropriate_ fallback for
the situation, depending on what a NULL string actually means in the
context (unnamed buffer? anonymous namespace? global augroup?)

soo, this PR provides the most _gently_ incremental nudge towards
considering this case again as an error one could possible imagine.
If sanitizers are complied in, this minor incursion gets printed
into the sanitizers' log as a "report-and-continue" error with a
traceback. This doesn't annoy the end user or "stop the world"
in the test suite, but the CI will see the reported error and
fail the build, just like other non-fatal CI errors.
2026-09-08 12:46:24 +02:00
glepnir
b3bd442c5c fix(lsp): completion dynamicRegistration breaks capability readers #41771
Problem:
A server that supports dynamic registration must not also declare
completionProvider at initialize, so server_capabilities.completionProvider
is nil for anything reading it directly.

Solution:
Do not advertise it by default. A client that follows a registration can
declare it itself.
2026-09-08 05:48:38 -04:00
zeertzjq
03f5f25a1e fix(tui): don't treat ESC preceding repeat/release as ALT (#41770) 2026-09-08 17:25:35 +08:00
Yanuo Ma
b781e95a24 fix(scrollbind): window jumps back and forth with tall virt_lines block
Problem:
Scrolling a 'scrollbind' window whose peer has a virt_lines block
taller than the window makes the peer jump back and forth.

Solution:
After scrollup()/scrolldown(), sync w_scbind_pos to the actual
get_vtopline() when the target was not clamped to buffer bounds.

Ref: esmuellert/codediff.nvim#519
AI-assisted
2026-09-08 09:55:15 +01:00
Volodymyr Chernetskyi
9f425311c3 fix(lsp): semantic tokens ignore 'fileformat' when sizing line endings #41733
Problem:
The eol_offset used to decode multiline semantic tokens is computed as

    vim.bo.fileformat[bufnr] == 'dos' and 2 or 1

which indexes the option value rather than the buffer, so it is never
"dos" and the offset is always 1. A token that crosses a line boundary
in a 'fileformat' of "dos" is then decoded one code unit short per line
ending, and the highlight extends past the end of the token.

Solution:
Read the option with the buffer-scoped form, vim.bo[bufnr].fileformat.

The existing multiline test covers both formats now. Its token length of
82 counts the line endings it spans, so the same token reaches four
characters less far once the buffer is "dos".

AI-assisted
2026-09-07 12:47:48 -04:00
Lewis Russell
65ef6fdaee build: replace LuaLS with EmmyLua
Problem: LuaLS struggles with the generics used in Nvim's runtime,
requiring broad diagnostic suppressions. Indexing is also slow.

Solution: Use EmmyLua for type checks in the build and CI. It offers
more sophisticated type checking, substantially better support for
generics, and much better flow analysis.

Correct the affected annotations. Use `@internal`, supported directly by
EmmyLua, instead of `@nodoc` for shared internal declarations, and
support it in the help parser.

AI-assisted
2026-09-07 15:33:22 +01:00
Lewis Russell
cd52c77cd5 fix(help): resolve generic and nullable type references
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
2026-09-07 15:33:22 +01:00
glepnir
937f835a3e feat(lsp): support dynamic registration of completionProvider #41748
Problem:
dynamicRegistration is declared false, and completionProvider is read
from server_capabilities only.

Solution:
Declare it, and prefer registerOptions per field.
2026-09-07 09:53:35 -04:00
Lewis Russell
fdf071959e perf(watch): prune excluded subtrees during setup
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
2026-09-07 13:30:57 +01:00
Volodymyr Chernetskyi
a31f9affde fix(msgpack): strptime() fails east of UTC+12 #41743
Problem:
msgpack#strptime() finds a timestamp by bisecting strftime() output, and
brackets the search with the extreme UTC offsets. The lower bound
subtracts 12 hours, but -12:00 is the westernmost offset, which produces
the *largest* timestamp for a given local time. The easternmost offset
is +14:00, so in any zone east of UTC+12 the search can start above the
target and the function throws:

    internal-start-string:Internal error: start > string

With TZ=GMT-14, four of the five timestamps in msgpack_spec.lua fail,
as do three tests in shada_spec.lua, which reaches the same function
through shada#strings_to_sd().

Solution:
Subtract 14 hours instead. The upper bound is already generous enough at
+14, and widening a bisection bracket downwards cannot change the result
in zones that worked before.

The test disables the python3 provider, because msgpack#strptime() only
uses the Vimscript implementation changed here when no provider answers,
and otherwise hands the work to datetime.strptime().

AI-assisted
2026-09-07 08:24:16 -04:00
Justin M. Keyes
1c8d5581d9 Merge #41118 from altermo/optwin-tab-scope
optwin.lua improvements
2026-09-07 08:20:24 -04:00
Justin M. Keyes
c696bab847 fix(cmdatom): don't capture inputsecret() #41742 2026-09-07 06:47:17 -04:00
altermo
ec957ffd8c refactor(optwin): use current window as fallback 2026-09-07 11:05:19 +02:00
Justin M. Keyes
6cbde6a95f fix(multicursor): Visual-mode mapping loses its commands #41741
Problem:
A Visual-mode mapping that leaves Visual mode ("xmap I Q0i") does not
replay its motions/edits at the extra cursors, though an insert session
it starts does.

atom_map_start() skips while Visual is active, so the mapping has no
`composite`, and atom_capturable() then rejects its commands.

Solution:
Open the `composite` for Visual-mode mappings too.

Bonus: the mapping atom is now reported as:

    before: type=visual,  keys="viwcFOO<Esc>"
    after:  type=mapping, keys="viwc<Esc>iFOO<Esc>", lhs=",c"
2026-09-06 18:50:48 +00:00
Volodymyr Chernetskyi
5f1f5a8c2a fix(treesitter): conceal_lines is not applied to injected trees
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
2026-09-06 19:36:32 +02:00
Justin M. Keyes
2ac9dfc45d fix(multicursor): live-preview ":norm" visual selection #41736
Problem:
A Visual selection opened programmatically (`:normal! viw`) is not
previewed at the other cursors, and a user-typed operator completing it
does not cascade.

":normal" keys run as child frames, which are never user input, so the
session they build was classified "fed" once and never revisited.

Solution:
Recalculate the session kind (`vatom.state`) on every frame. Record
`vatom.frame` (the last frame to add to the session) so an enclosing
frame does not reset or recapture it.
2026-09-06 17:15:48 +00:00
Volodymyr Chernetskyi
d951908e12 fix(ui2): mouse movement dismisses expanded messages #41732
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
2026-09-06 12:37:28 -04:00
Volodymyr Chernetskyi
2f5df7add7 fix(treesitter): #has-parent? errors on a node with no parent
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
2026-09-06 18:27:02 +02:00
Volodymyr Chernetskyi
e8a0b400d8 fix(statusline): "%.0" renders garbage #41685
Problem:
`getdigits_int(pp, strict, def)` returns `def` only when the number
fails to parse or overflows, never when the text parses to a literal
`0`, so `%.0` leaves `maxwid` at 0. Both render paths then break down:
`l > maxwid` is always true and `while (l >= maxwid)` consumes the whole
string, so `%.0f` comes out as `<` and `%.0l` as `0>3`.

Vim guards for this right after the same call. Nvim had the guard too
until 3344cffe7b ("getdigits: introduce `strict`, `def` parameters"),
which folded it into the new `def` argument and so changed what a
literal zero means. The neighbouring `minwid` call was translated
correctly.

Solution:
Fall back to 50 when the parsed width is not positive, as Vim does.

AI-assisted
2026-09-06 11:24:52 -04:00
Justin M. Keyes
bd43394d4e fix(multicursor): skip insert live-mirror on conflicting edits #41730
Problem:
Cursors placed same-line before the primary, or producing multiline
edits on lines above the primary, shift the text in a way that breaks
the insert-mode live-mirroring.

Solution:
Skip live-mirroring under those conditions.

TODO (future): we could support live-mirroring if ins-completion's
internal bookkeeping tracked its regions more precisely.
2026-09-06 10:04:47 -04:00
Volodymyr Chernetskyi
050fa30632 fix(options): 'scrolloffpad' reads the wrong window #41684
Problem:
`get_scrolloffpad_value()` takes a window, but its else branch reads
`curwin->w_p_sop` instead of `wp->w_p_sop`.

Reachable from Vimscript, since `line('w0', winid)` runs
`update_topline()` on another window without making it current.

Solution:
Read the option from the window that was passed in.

AI-assisted
2026-09-05 13:36:14 -04:00
Justin M. Keyes
455b6a810c fix(multicursor): cursors may be "deduped" incorrectly #41721
Problem:
Freeing an unrelated buffer deletes the cursor under the primary.
Scratch-buffer cleanup triggers this unexpectedly.

Solution:
Separate dead-cursor cleanup from dedupe/merge. Merge overlapping
cursors only at cascade boundaries, not during buffer/namespace cleanup
or cursor removal.
2026-09-05 13:18:22 -04:00
Justin M. Keyes
c543e9e1de fix(cmdatom): exclude <expr> EVALUATION input from CmdAtom.keys #41718
Problem:
Input (`getchar()`, `input()`, …) consumed by an expr mapping EVALUATION
can leak into `CmdAtom.keys`, depending on count placement ("2dse" vs
"d2se").

Solution:
Skip payload capture while `expr_map_lock` is active.
- Input read during expr EVALUATION is recorded in `CmdAtom.lhs`.
- Only input read by mapping EXECUTION is appended to `CmdAtom.keys`.
2026-09-05 11:52:25 -04:00
Justin M. Keyes
0c642196b9 fix(multicursor): cascade mapping atoms with insert entry #41714
Problem:
Pending mapping atoms prevent live insert-cascading (`nnoremap i ^i`).
Flushing them at insert-session start (3a02a39957) adds a separate
cascade boundary, which muddles the architecture.

Solution:
Let the first insert-span cascade pending mapping atoms on insert-entry.
This is an alternative fix for #41692.

Composite children (`.atoms`) BEFORE:

    { { 'motion', '^' }, { 'insert', '1iX<Esc>' } }

Composite children (`.atoms`) AFTER:

    { { 'motion', '^' }, { 'insert', '1i<Esc>' }, { 'insert', 'iX<Esc>' } }
2026-09-05 09:53:41 -04:00
li
739a44753d fix(cwd): :bcd completion shows non-directories #41661
Problem:
Completion for `:bcd` shows files, not just directories.

Solution:
Set the ctx properly.
2026-09-05 07:56:27 -04:00
Volodymyr Chernetskyi
48864161cd fix(api): leaked error message in nvim_win_set_buf() #41688
Problem:
Both handle lookups write into the same `Error` before either one is
checked, and `api_set_error()` unconditionally `xmalloc`s `err->msg`
without freeing what is already there. When both handles are invalid,
the message allocated for the window is silently overwritten by the one
for the buffer and leaked.

Solution:
Check the window before looking up the buffer. As a side effect the
reported error no longer names the buffer when the window was wrong too.

AI-assisted
2026-09-04 20:57:55 -04:00
Justin M. Keyes
3a02a39957 fix(multicursor): live-mirror mapped Insert entry #41692
Problem:
A Normal-mode mapping that moves before entering Insert, does not
live-mirror. Its pending motion defers insert-cascade until Insert ends.

Solution:
Cascade pending atoms before the Insert entry replay, then start the
live insert span.
2026-09-04 20:03:19 -04:00
Volodymyr Chernetskyi
06bff27929 feat(ui2): highlight the [+x] spill indicator with MoreMsg #41679
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
2026-09-04 14:28:22 -04:00
Samiul Islam
b7a0c71a54 fix(multicursor): prevent clipboard crash after cursor jump #41698
Problem:
Changing from an empty-line cursor after a multicursor jump can crash
while flushing a deferred clipboard update. Exact context restore can
leave an omitted register with a null array and stale non-zero size.

Solution:
Make free_register() fully reset the register after freeing its contents,
so it always leaves a valid empty register. Add a regression test.

Signed-off-by: sami <samiulsami7786@gmail.com>
2026-09-04 13:06:50 -04:00
Lewis Russell
630e0592b7 test(lua): avoid async task completion flake #41702
Allow enough time for the timer and scheduled continuation to run on
Windows.

fixes #41699

AI-assisted
2026-09-04 12:51:23 -04:00
Volodymyr Chernetskyi
f1833490a0 fix(msgpack): out-of-bounds read on a truncated string #41683
Problem:
`unpack_string()` validates the declared length against `*size`, the
size *before* `mpack_rtoken()` consumed the token header, rather than
`size2`, the remainder after it. The header is one to five bytes, so any
declared length in the window `(size2, *size]` slips through. The
returned `String` then covers up to five bytes past the end of the
buffer, and `size2 - tok.length` underflows, leaving `*size` near
`SIZE_MAX` so every later unpack call on that entry believes it has an
unbounded buffer.

Reachable from ShaDa, where a history entry ending in a five-byte string
header followed by four bytes is enough, so a corrupted or hostile
`main.shada` triggers it at startup.

Solution:
Check the remainder left after the header.

AI-assisted
2026-09-04 12:48:55 -04:00
Volodymyr Chernetskyi
ba11f36695 fix(api): leaked window set in nvim__ns_set() #41689
Problem:
The early return for an invalid window handle bypasses the
`set_destroy()` at the end of the block, so the set's backing allocation
leaks whenever an earlier iteration already called `set_put()`.
Triggered by `{ wins = { valid_win, 9999 } }`.

Solution:
Destroy the set before returning.

The new test passes either way, since the leak is only visible to a
sanitizer; it is there so the ASAN job covers the path.

AI-assisted
2026-09-04 12:39:08 -04:00
Volodymyr Chernetskyi
6386e29636 fix(api): leaked error message in nvim_eval_statusline() #41690
Problem:
`find_window_by_handle()` returns NULL only after `VALIDATE_INT` has
already set `err`, so the `api_set_error()` that followed allocated a
second message over the first pointer and leaked it.

Solution:
Drop the redundant call and keep the helper's message, which is already
set and better worded.

AI-assisted
2026-09-04 12:37:25 -04:00
Volodymyr Chernetskyi
b21f2094ba feat(ui2): scroll the dialog window with the mouse wheel #41682
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
2026-09-04 11:33:53 -04:00
Volodymyr Chernetskyi
cbba00c15a fix(menu): menu_get() files terminal mode menus under the tooltip key #41687
Problem:
`menu_mode_chars` includes the two-character `"tl"` value. The hardcoded
`1` is the `key_len` argument of `tv_dict_add_dict()`, which truncated
it to `"t"`, the designator for a tooltip. Scripts keying on the
documented mode designators therefore miss terminal mode entries and
misread them as tooltips.

Solution:
Pass the real length of the designator.

AI-assisted
2026-09-04 11:32:34 -04:00
Volodymyr Chernetskyi
f22b017759 fix(menu): menu_get() cannot tell :noremenu from :menu <script> #41686
Problem:
`REMAP_NONE` is -1 and `REMAP_SCRIPT` is -2. These are enumerated
values, not bit flags, so `&` is the wrong operator: `x & -1` is `x`,
and `-1 & -2` is -2, which is truthy. Both kinds of non-remapping menu
therefore reported `noremap` and `sid` as 1 and became
indistinguishable.

The sibling `menu_get_info()` already compares with `==`.

Solution:
Compare rather than mask.

The expectations in menu_spec.lua had the
conflated values baked in, so 22 entries from plain `*noremenu` commands
now report `sid = 0`, and the two from `nnoremenu <script> Export.Script`
now report `noremap = 0`.

AI-assisted
2026-09-04 11:18:22 -04:00
Justin M. Keyes
2124f968a6 feat(multicursor): "]C"/"[C" leave a cursor at old position #41681
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.
2026-09-04 06:28:42 -04:00
Justin M. Keyes
18f5d47543 fix(multicursor): follow cursor-move by API #41675
Problem:
A non-edit mapping that moves the cursor by API (Lua,
`:call nvim_win_set_cursor()`, …) is not cascaded.

Solution:
Fallback to LHS-replay if the cursor moved and follow-mode is enabled.
2026-09-04 07:37:48 +00:00
Volodymyr Chernetskyi
034fca6c53 fix(move): zz does not center the cursor with wrapped lines #41656
Problem:
`scroll_cursor_halfway()` resets its `above` and `below` row counters on
every iteration of the `topline` scan, because they are declared inside
the loop. Each iteration then takes one line from each side regardless
of how many screen rows those lines occupy, so tall wrapped lines below
the cursor leave it near the top of the window instead of centered.

Affects the default `nosmoothscroll` path, reached from "zz" and from
`update_topline()`. Regression from 9b9ccac625 (vim-patch:9.0.1121),
which moved the declarations to their first use while porting; the
upstream patch did not touch them.

Solution:
Declare the counters at function scope, as Vim does, so they accumulate
across iterations.
2026-09-04 03:27:53 -04:00
Justin M. Keyes
5209695703 fix(multicursor): abandoned Visual selection does not move cursors #41670
Problem:
"v{motion}<Esc>" moves the primary cursor to the selection end, but the
other cursors snap back to their anchors unless follow-mode ("q=") is
on. The selection preview showed them at the selection end.

Solution:
Replay the abandoned selection at every cursor, not only in follow-mode.
An abandoned typed selection now always emits a "visual" CmdAtom.
2026-09-03 15:20:30 -04:00
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