Commit Graph

2165 Commits

Author SHA1 Message Date
Olivia Kinnear
6107629c5b feat(fs): vim.fs.normalize{plain:boolean} #41127
`opts.plain=true` does not expand tildes in addition to environment
variables, unlike `opts.expand_env=false`.

`opts.expand_env=false` is soft-deprecated.
2026-08-05 16:11:13 -04:00
Justin M. Keyes
ab80ea92cc fix(ui2): pager handling #41179
- Avoid shared state. Pass `focus` to set_pos()/expand_msg() instead of
  a shared `pager_focus` flag: the flag is only cleared when set_pos()
  actually enters the pager, so ":messages" from inside the pager left
  it set.
- pager_shown(): the pager window is invalid after leaving it with "q".
- Reuse pager_shown() in expand_msg().
2026-08-05 15:07:19 -04:00
Erdiansyah
a29a9130a9 fix(ui2): do not steal focus when consecutive cmds emit messages #41062
Problem:  A message emitted while a previous expanded message is still
          visible opens the pager and enters it, moving focus away from
          the buffer window without an explicit request (#41061).
Solution: Only enter the pager when it was explicitly requested ("g<",
          :messages, or entered from the expanded cmdline). An unfocused
          pager is dismissed by the cmdline key handler, which stays armed
          across the cmdline and no longer dismisses on non-typed keys
          (#39221).
2026-08-05 14:07:59 -04:00
Tanishq
e2c0c63452 fix(statusline): don't clobber global statusline during autocmd #41169
Problem:
With `laststatus=3`, a pager float shares the main grid's statusline
row. Setting a diagnostic fires `DiagnosticChanged`, whose handler calls
`nvim__redraw({ statusline = true })`.

Analysis:
Inside the autocmd, `curwin` is temporarily switched to the tiled window
showing that buffer, so `win_redr_status()` paints its statusline over
the pager's `[Pager]` statusline on the shared row. After the autocmd,
`curwin` is restored but the pager statusline is never repainted.

Solution:
Use `ctx_saved_curwin()` decide whether to draw the global statusline,
matching `win_redr_stl_expr()` and `update_screen()`. No behavior change
if no buffer-context switch is active.
2026-08-05 13:35:09 -04:00
glepnir
6d8401f8d5 fix(ui): missing vertical separator for a winbar only window (#41151)
Problem: A winbar-only window with zero text height still occupies one row,
         but win_update() returns early on w_view_height == 0 and skips the
         vertical separator.

Solution: Also draw the vertical separator in the early return path.
2026-08-05 06:42:39 +08:00
not_compiled
f6de99d028 fix(highlight): make Ignore hidden by default #41115
Problem:
Ignore is linked to Normal by default, making the text visible instead
of hidden.

Solution:
Replace the default link with an explicit highlight definition using
ctermfg=0 guifg=bg.
2026-08-04 13:43:22 -04:00
Sébastien Hoffmann
ca07e505f9 refactor(ruler)!: replace C implementation with 'rulerformat' expression
Problem:
The default 'ruler' is implemented in C instead of the 'statusline' DSL.

Solution:
Replace the C implementation with a default 'rulerformat' expression.
This is a continuation of #1248 and #33036.

Advantages:
- configuration is more discoverable, the default being a useful example
- users and plugins can augment the default
- code reuse and less C code to maintain
- ui2: due to the use of an item group with `minwid`, it can expand
  instead of truncating when the content gets too long, which is
  particularly useful for locales with long translations of Top/Bot/All

Implementation details:
As is the case for 'statusline', when trying to set 'rulerformat' to an
empty string, the default expression is restored instead, mimicking how
previously the default C implementation would have been activated.
Just like before, `:set rulerformat=` and `:set rulerformat&` have the
same effect, and the ruler is disabled with `:set noruler`.

The default expression uses an item group with `%=`, unlike the fallback
in the previous default statusline `%-14.(%l,%c%V%) %P`, because the
total width and how it is configured is immediately clear without
documentation, it is a more useful pattern in general that works when
both sides have flexible width, and it also works for vim, which is
useful for configuration sharing/reuse.

A truncation marker `%<` is added at the end to mimic how at small
screen widths, the scroll percentage would disappear first, so that the
cursor position can remain fully visible.

BREAKING CHANGES:
- `&rulerformat` can no longer be set to an empty string
- ui2: the default ruler is no longer of fixed width, but can expand
- at very small screen widths (< 36 columns)
  - ui2: it will no longer try to shrink white-space before truncating
  - it truncates gradually from the right, whereas previously, the
    scroll percentage would disappear all at once
- l10n can no longer add a space after the comma between line and column
  (this was only done for one language: Ukrainian)
2026-08-04 12:12:23 +02:00
Sébastien Hoffmann
259e6fa9cc fix(ruler)!: consistent width in last line and statusline
Problem: Traditionally, the ruler in the last line is one cell shorter
than in the statusline, leaving the last cell of the screen blank.
According to code comments, this is in order to prevent unwanted
scrolling on "some" (unspecified, but presumably ancient) terminals.
Berkeley vi is more specific in its `vs_modeline` function: dumb
terminals with hardware scroll, SunOS 4.1.1 and Ultrix 4.2 curses.
(n)curses still has a similar limitation in `(w)addstr`, but apparently
only for historical reasons.

Maintaining the different widths leads to awkward inconsistencies when
the ruler is configured with 'rulerformat', except for the special case
where it contains a top-level `%=`. Shifting the ruler in the last line
to the left would be a solution, but the empty cell at the end doesn't
seem to be relevant anymore.

Solution: extend the ruler in the last line all the way to the right
edge of the screen, just like in the statusline. The exact same amount
of place will be available to the rest of the UI as before.

BREAKING CHANGE:
- the default ruler width is now 18 cells
- the last cell of the screen is no longer empty

Closes #41076
2026-08-04 06:36:58 +02:00
Sébastien Hoffmann
81261804a8 fix(ruler): always clear ruler when disabled
Problem: the ruler is not cleared in the following circumstances:
- ui1 is running
- 'rulerformat' is configured
- the default ruler was not previously visible in the last line, for
  example because 'rulerformat' is configured in init.lua
- 'ruler' is disabled without using the command-line, for example via
  key-binding (entering the command-line would clear the ruler)

The corresponding test case did not fail because 'rulerformat' was set
while the default ruler was shown.

Solution: use a dedicated variable for tracking whether the ui1 ruler
was previously shown in the last line. `did_ruler_col` is now only used
for setting `msg_col`, which is not implemented in the case where
'rulerformat' is configured.

Reorder the test code to make the individual checks more independent
from each other, and to reflect the future where 'rulerformat' will
never be empty. Note that the check where 'rulerformat' was configured
relied on the default ruler not being cleared and a stale "0," still
being shown in front of the new ruler - this is also fixed with ui2.
2026-08-04 06:36:58 +02:00
Sébastien Hoffmann
dc21aa07f7 fix(ruler): no window-local highlights for last line 'rulerformat'
Fixes #38777 in case 'rulerformat' is set.

See PR 38879. Original message:
Problem:  When the 'ruler' is in the last line of the screen, it takes
          local highlight definitions of the current window, tripping an
          assert (since c1648cf).
Solution: Don't use window-local highlight definitions when the ruler is
          not part of a statusline.
2026-08-04 06:36:57 +02:00
Freddie Haddad
866d61e91a fix(decor): 'breakindent'/'showbreak' gap ignores active highlight #41073
Problem:
'breakindent' and 'showbreak' draw their own padding with no
reference to whatever decoration or syntax highlight is currently
active, so it goes unhighlighted even mid-highlight, not just past a
real EOL. Gating this on the decoration's `hl_eol` flag (as an
earlier version of this fix did) missed plain highlights with no
`hl_eol` at all, which have the exact same problem.

Solution:
Snapshot decor_attr into decor_attr_save right before it can be
reset by 'linebreak' filler handling, and pass it into
handle_breakindent()/handle_showbreak_and_filler() to extend into
their padding: it is not a real end of the highlight, just screen
cells with no buffer text. Like the 'linebreak' filler, an
underline/strikethrough/overline is excluded, since it looks like a
broken line drawn over the gap. This also fixes 'breakindent' losing
the highlight right after a real 'linebreak' word-push, since that
reset otherwise leaked into the next row.
2026-08-03 11:32:55 -04:00
debaditya
6538d0aadb fix(ui2): retain substitute confirmation highlight with nohlsearch #41067
Problem:
ui2 clears the substitute confirmation match when it updates its prompt buffer with hlsearch disabled.

Solution:
Only invalidate the match highlight when the current buffer changes.
2026-08-03 09:48:33 -04:00
Rob Pilling
2f09090134 fix(ui2): :$q should not target hidden windows #40992
Similar to #36123, the ui2 windows confuse `:$q`, which doesn't close
the last window on the tab, but targets a hidden ui2 window.

The problem seems to be a combination of using
`FOR_ALL_WINDOWS_IN_TAB()`, which includes hidden windows, and then
counting the windows without factoring in `win_has_winnr()`.

There are examples of using `win_has_winnr()`, such as:
- [`eval/buffer.c`'s `buf_win_common()`](4a5062cda6/src/nvim/eval/buffer.c (L474-L475))
- [`eval/window.c`'s `f_getwininfo()`](4a5062cda6/src/nvim/eval/window.c (L130-L135))
- [`window.c`'s `win_get_tabwin()`](4a5062cda6/src/nvim/window.c (L7860-L7869))

But possibly problematic ones:
- `ex_docmd.c`'s:
	- [`invalid_range()`](4a5062cda6/src/nvim/ex_docmd.c (L3891-L3893))
		- ^ should this include hidden windows? Can a user address them by number? `win_has_winnr()` suggests not
	- [`ex_close()`](4a5062cda6/src/nvim/ex_docmd.c (L5233-L5244))
	- [`ex_hide()`](4a5062cda6/src/nvim/ex_docmd.c (L5478-L5484))
- `eval/window.c`'s:
	- [`f_winrestcmd()`](4a5062cda6/src/nvim/eval/window.c (L797-L808))
	- [`find_win_by_nr()`](4a5062cda6/src/nvim/eval/window.c (L172-L180))
2026-08-02 14:37:26 -04:00
zeertzjq
a5103c0853 vim-patch:9.2.0892: highlight: wrong column highlighted with 'cursorcolumn' (#41110)
Problem:  With 'virtualedit' set to "all" and 'cursorcolumn' set, the wrong
          column may be highlighted after a command that moved the cursor
          into virtual space and back (van-de-bugger).
Solution: Make sure the virtual column is up to date before drawing the
          window (Hirohito Higashi).

fixes:  vim/vim#2576
closes: vim/vim#20902

5a90b9dbd2

Test only. This was already fixed by #39159.

Co-authored-by: Hirohito Higashi <h.east.727@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 02:20:11 +00:00
Nathan Zeng
1dbc766fa7 fix(ui2): clear empty cmdline after backspace #41043
Problem:
Cmdline area shows stale ":" after backspacing out of the command line.

Solution:
Clear the command line for empty commands. Note that `:<CR>` will now
clear the command line too.
2026-07-29 12:40:08 -04:00
tianrking
089c415cb2 fix(floatwin): relative='cursor' may use outdated cursor position #40768
Problem:
Cursor-relative floats can use stale screen coordinates after a cursor move is restored without a redraw.

Solution:
Validate the current cursor before converting cursor-relative coordinates.

Co-authored-by: zeertzjq <zeertzjq@outlook.com>
2026-07-28 07:32:35 -04:00
Justin M. Keyes
fbcb7a056c feat(exmode): "1q:", :exmode #41010
Problem:
Want `gQ` for _le multicursor_.

Solution:
- Don't use `gQ` for exmode.
- Introduce `:exmode`.
- Introduce `[count]q:` as an alias to `:exmode`.
2026-07-27 11:12:47 -04:00
Justin M. Keyes
359459dec6 refactor(exmode): Ex-mode as cmdwin + Lua #40991
Problem:
POSIX-compatible Ex-mode requires special-cases all over the codebase to
match various quirks that don't actually matter to users.
- The main utility of *interactive* Ex-mode is its REPL behavior, and
  that can be achieved with `cmdwin`, which also gains extra UX
  benefits.
- The main utility of *non-interactive* `nvim -es` is for shell
  scripting, where Ex-mode quirks are mostly unhelpful (e.g. the
  "Entering Ex mode" message).

Solution:
- Reimplement *interactive* Ex-mode as a "persistent, insert-mode
  cmdwin" in Lua.
  - "nvim -e/-E" is simply an alias to "gQ".
- Reframe *non-interactive* Ex-mode (`nvim -es`) as "script mode".
  - Drop POSIX Ex-mode quirks.

Improvements:
- "nvim -V1 -es" output ends with a final newline!
- "nvim -V1 -es" no longer shows the "Entering Ex mode" msg. (This was
  pointless noise, unwanted for scripting purposes.)
- stdin is no longer typeahead. Scripts (":lua io.read()") can read
  stdin as data.
- Empty line is a no-op: a stray blank line no longer moves the cursor
  (deviates from POSIX ex "+1"), no longer exits 1 at EOF (E501).

Preserved behavior:
- cursor starts at "$"
- mode()=="cv" (for non-interactive)
- multiline commands (:append/:function/heredoc pull continuation lines)
- bare-range print
- :print=>stdout
- -V1=>stderr
- CRLF input
- continue-after-error and exit codes

Dropped (regressed) POSIX behavior (non-interactive):
- Event loop only ticks while/between commands, not while blocked
  waiting for a stdin line.
- ":g/pat/visual...Q"
- input()/getchar()/":s/x/y/c" no longer consume stdin lines as
  answers: Nvim stops at end-of-input, skipping the rest of the script,
  exit 0. Use ":lua io.read()" instead.
  - If users care about this they should use interactive Ex-mode (`gQ`).
- ":@r" stops at end of the register instead of continuing to read
  cmdline input from stdin.
2026-07-27 06:25:21 -04:00
zeertzjq
7ee1bf91ad vim-patch:9.2.0855: 'showcmd' not redrawn with empty mapping triggered on timeout
Problem:  'showcmd' not redrawn with empty mapping triggered on timeout.
Solution: Don't postpone redraw when inside vgetorpeek(). Also move test
          for tabline 'showcmd' to test_tabline.vim.

fixes:  vim/vim#20839
closes: vim/vim#20840

2e9687647a
2026-07-26 07:07:38 +08:00
Justin M. Keyes
bf3d4210c3 refactor: 'previewpopup' #40945
- drop w_maxwidth/w_maxheight, calculate them on-demand from
  'previewpopup' (this is cheap bc of `opt_keyset`)
2026-07-24 13:49:47 -04:00
Justin M. Keyes
e8195baaef refactor(options): 'previewpopup' validation
Use a schema instead of manual parsing.
2026-07-24 13:05:20 +02:00
glepnir
f95bd73935 feat(float): 'previewpopup' option #26799
Problem:
The preview-window (:pedit, etc.) always uses a split, but it would be
useful as a floatwin (or "popup").

Solution:
Support Vim's 'previewpopup' option.
2026-07-23 17:27:14 -04:00
Ondrej Balaz
8b6bdb79e6 fix(ui): invalid ruler items in other windows #40891
Problem:
When buffer is open in multiple windows and its line count changes, any
'statusline' or 'rulerformat' items depending on it (e.g. %P) will
become invalid in non-current windows because w_redr_status is only set
on first change.

Solution:
Set w_redr_status on all windows with the buffer when the line count
changes.

Signed-off-by: Ondrej Balaz <blami@blami.net>
2026-07-22 12:05:37 -04:00
XiaowenHu
bd73d8c011 fix(inccommand): enforce timeout during matching, not just after a line #40892
Problem: do_sub() only checks the timeout limit after finishing a line.
         A pathological regex will run on a single line input unbounded
         until the compute is completed.

Solution: Pass the timeout limit to `vim_regexec_multi()` so the
          computation on the regex engine is bounded per-line.

Signed-off-by: XiaowenHu96 <me@xiaowenhu.com>
2026-07-22 08:27:54 -04:00
Justin M. Keyes
008ea4dd65 Merge #40873 fix(ui): handle combining chunked float titles 2026-07-21 11:45:44 -04:00
Justin M. Keyes
6d8c8b18d5 test(harness): migrate away from magic globals
Problem:
The magic globals `it`, `describe`, etc., are more trouble than they are
worth.

- Hooking into `after_each` requires `getfenv()` hacks.
- They confuse luals/emmylua, because the top-level `.luarc.json` isn't
  merged with `test/.luarc.json` (apparently a luals limitation?)
- They totally defeat discoverability because the user just has to
  "know" about the various magic symbols.

So they harm DX, which means they serve no purpose at all.

Solution:
- Expose the test API from `testutil`, so tests can call `t.it()`,
  `t.describe()`, etc., in the conventional way.
- Drop `getfenv()` hacks.
- Drop the `setfenv()` injection in `load_chunk`.
- Drop `test/_meta.lua`.
2026-07-21 13:22:39 +02:00
Barrett Ruth
16c145d240 fix(ui2): preserve Visual mode messages #40828
Problem:
The existing `showmode` overlay can immediately cover messages emitted while
Visual mode is active, including the `g CTRL-G` word count.

Solution:
Protect Visual mode messages with the existing message delay and temporarily
hide the previous last-line overlay until it is restored.
2026-07-20 15:44:37 -04:00
Artem Krinitsyn
18d6b2309f fix(ui): handle combining chunked float titles
Problem:
The title is combined with window's attributes only if the title is a
string (which implies the FloatTitle / FloatFooter highlight groups),
but not when the title is text-hl chunks.

Solution:
Combine specified highlight group with window-local Normal highlight as
well.
2026-07-20 19:35:08 +00:00
Artem Krinitsyn
746f5682a7 fix(ui): combine float border with window background #40796
Problem:
Float border highlight groups (FloatBorder, FloatTitle and FloatFooter)
fall back to Normal background highlight if no background color set.

Solution:
Combine border colors with window-local Normal highlight.

Fix #38330
2026-07-19 11:03:46 -04:00
Barrett Ruth
b2bb60d70a fix(ui2): avoid ModeChanged in message window #40788 2026-07-18 07:33:38 -04:00
Sean Dewar
1741da8412 fix(window): yeet grid later when closing float in other tabpage #40758
Problem: #40731 may still crash if close_buffer autocmds reinsert the float's
grid. Plus removing the grid (and posting win_close) is unneeded if
win_close_othertab refuses to close the window later, which is possible.

Solution: do the stuff before freeing the window, like win_close.
2026-07-15 19:09:54 -04:00
Justin M. Keyes
31ad7d4527 Merge #40621 fix(ui2)!: legacy 'ruler' inconsistencies 2026-07-15 08:13:37 -04:00
phanium
6f370f34f2 fix(window): remove float grid from compositor layers before free #40731
Problem: Closing a floating window from a non-current tab frees its grid
without removing it from the compositor's `layers` table, so the next
`ui_comp_put_grid()` walks a dangling pointer (UAF).

Solution: Call `ui_comp_remove_grid()` (and `ui_call_win_close()` for
multigrid UIs) before `win_free_mem()`, matching `win_close()` since
PR #21551.
2026-07-15 07:48:34 -04:00
Barrett Ruth
b2570e6852 fix(showcmd): avoid stale %S contents #40747
Co-authored-by: zeertzjq <zeertzjq@outlook.com>
2026-07-15 06:10:09 -04:00
Sébastien Hoffmann
0ffd74ae85 fix(ui2): fix ruler on repeated messages
Problem: a long message is cropped to keep the ruler visible. But when
the message is repeated, it can be written over the ruler, while the
repetition indicator "(1)" is still placed just before the ruler, which
ends up somewhere inside the message.

Solution: currently, long messages are only cropped when setting the
'last' virttext. Make sure this happens after a repeated long message.
2026-07-11 18:44:42 +02:00
Sébastien Hoffmann
2473cb62b7 fix(ui2): correctly crop message at multibyte/multicell character 2026-07-11 18:31:54 +02:00
Sébastien Hoffmann
fcc391d89a fix(ui2): clear showcmd virt_text together with search count
Problem: when the search count is displayed, the showcmd virt_text is
set to 11 empty spaces to ensure a consistent distance between search
count and ruler, or screen edge in case of noruler. But when the search
count is removed to make place for a message, the empty dummy showcmd
remains and unnecessarily erases part of the message.

Solution: properly remove showcmd together with search count.
2026-07-11 18:31:54 +02:00
Sébastien Hoffmann
60687ccb6a fix(ui2)!: consistent ruler width inside/outside statusline
Problem: before ui2, the ruler in the last line had a one cell safety
margin on the right because writing in the last column of the last line
"scrolls the screen up on some terminals," according to code comments.
When the ruler is included in the statusline, it aligns on the left with
the normal ruler, but goes all the way to the screen edge on the right,
and is therefore one cell longer.

Since ui2 doesn't have that padding cell, they don't align anymore.

Also, in the future, when the C implementation of the ruler will be
replaced with a default expression that will be included in the default
statusline (currently `%-14.(%l,%c%V%) %P`), the width will naturally be
the same in both places, and the unit tests will need adapting anyway.

Solution: when ui2 is active, increase the ruler width by 1.

BREAKING CHANGE: the default ruler in ui2 is now 1 cell wider.

ref https://github.com/neovim/neovim/issues/40247
2026-07-11 18:31:54 +02:00
Barrett Ruth
784765cb73 fix(winbar): update hidden tabpages on global 'winbar' change #40608
Problem:
Changing global 'winbar' only updates window layout state in the current
tabpage. This means existing hidden tabs can keep stale winbar height.

Solution:
Recompute winbar state for all tabpages on global `'winbar'` changes.
2026-07-10 12:49:52 -04:00
Justin M. Keyes
982d2f2531 fix(test): screen:expect({none=…}) with no any/grid
Problem:
`screen:expect({none=…})` with no any/grid crashed (concat on nil)
because actual_rows was only rendered when any or grid was present.

Solution:
Update the condition.
2026-07-10 00:06:59 +02:00
zeertzjq
7c9222f45e fix(mouse): reset click count on next click if mouse moved (#40637)
Also, drop drag event if the mouse hasn't moved, like in gVim.
2026-07-09 08:40:00 +08:00
Justin M. Keyes
b5f500263b fix(lua): bufwrite message overridden by :redrawstatus
Problem:
bufwrite message overridden by :redrawstatus command within Progress callback.

Solution:
- don't use globally-shared IObuf.
- use vim_snprintf to deduplicate `nlua_call_luaeval`, `nlua_call_vlua`.

fix https://github.com/neovim/neovim/issues/40616
2026-07-06 22:17:25 +02:00
Justin M. Keyes
e494c4777b fix(test): screen:expect() render crash masks the real failure #40593
Problem:
When screen:expect() fails, it renders a snapshot for the error
message. If the grid references a highlight id that was never defined
via "hl_attr_define", the renderer crashes:

    screen.lua:1910: attempt to index local 'entry' (a nil value)

This hides the actual failure, and appears "flaky": it only fires on the
failure path, and only when the shared screen is missing an id the grid
still references. A screen created in setup() attaches mid-session, so
highlight ids allocated before it attached (still referenced by stale
grid cells) are never sent to it.

Solution:
- Don't crash while rendering a diagnostic: show undefined highlight ids
  as "UNKNOWN_HL_ID(n)", so the real failure and the desync are legible.
- put_spec: fix `visualbell` typo. If it fails again then we can find
  the actual root cause.

ref https://github.com/neovim/neovim/issues/36250
2026-07-05 12:08:41 -04:00
Justin M. Keyes
3b8c19ea46 fix(bufwrite): cursor flicker on :write
PROBLEM:
Cursor briefly flickers in cmdline when the "written" message is printed
(very noticeable in Neovide with cursor animation).

SOLUTION:
- Mark UI "busy" (cursor hidden) while emitting the message, as done for
  the search message (cb2ca54331).
- Note: ff68fd6b8a moved the message from `filemess()` into
  `buf_write..msg_progress`.
- Fix a bug in `tui.c:flush_buf` which manifested after this change. See
  ANALYSIS below. https://github.com/libuv/libuv/issues/5182

ANALYSIS:
After this change...

    ui_busy_start();
    set_keep_msg(msg_progress(IObuff, msg_id, "success", 0, true, true), 0);
    ui_busy_stop();

...ASAN analyzer fails on tui_spec.lua test "with non-tty (pipe) stdout/stderr":

    2026-07-04T14:09:28.9521023Z = ==32405==ERROR: AddressSanitizer: ABRT on unknown address 0x03e900007e95
    ...
    4 0x7f5506a288fe in abort stdlib/abort.c:79:7
    5 0x559afd7113a2 in uv__epoll_ctl_flush
    build/src/libuv/src/unix/linux.c:1335:7
    6 0x559afd710a81 in uv__io_poll
    build/src/libuv/src/unix/linux.c:1448:9
    7 0x559afd6f67a7 in uv_run
    build/src/libuv/src/unix/core.c:460:5
    8 0x559afd2a72cc in flush_buf
    nvim/tui/tui.c:2642:5
    9 0x559afd2bce9f in tui_flush
    nvim/tui/tui.c:1747:3
    10 0x559afd2f39a0 in ui_client_event_flush
    nvim/auto/ui_events_client.generated.h:64:3
    11 0x559afcb8e014 in parse_msgpack
    nvim/msgpack_rpc/channel.c:255:11
    12 0x559afcb840e7 in receive_msgpack
    nvim/msgpack_rpc/channel.c:217:5
    13 0x559afc53b2af in read_event
    nvim/event/rstream.c:180:23
    14 0x559afc53ad52 in invoke_read_cb
    nvim/event/rstream.c:233:3
    15 0x559afc5382d6 in read_cb
    nvim/event/rstream.c:135:3
    16 0x559afd707b25 in uv__read
    build/src/libuv/src/unix/stream.c:1145:7
    17 0x559afd70744a in uv__stream_io
    build/src/libuv/src/unix/stream.c:1208:5
    18 0x559afd6f727e in uv__io_cb
    build/src/libuv/src/unix/core.c:930:5
    19 0x559afd710d7a in uv__io_poll
    build/src/libuv/src/unix/linux.c:1546:11
    20 0x559afd6f67a7 in uv_run
    build/src/libuv/src/unix/core.c:460:5
    21 0x559afc526e2d in loop_uv_run
    nvim/event/loop.c:59:3
    22 0x559afc526aa4 in loop_poll_events
    nvim/event/loop.c:80:26
    23 0x559afd2fdbb7 in ui_client_run
    nvim/ui_client.c:172:5
    24 0x559afc920d7b in main /home/runner/work/neovim/neovim/src/nvim/main.c:355:5
    25 0x7f5506a2a1c9 in __libc_start_call_main
    main.h:58:16
    26 0x7f5506a2a28a in __libc_start_main csu/../csu/libc-start.c:360:3
    27 0x559afbd6d254 in _start

The abort requires two rare conditions:
1. a `uv_write` whose buffer array *ends* in a zero-length buffer, which only
   happens by "flush while cursor-hidden".
2. an output fd that *epoll cannot watch*. The only CI test with such an fd is
   tui_spec.lua "with non-tty (pipe) stdout/stderr", which runs a TUI with
   `stdout > /dev/null`, then runs `:w testF`, which triggers the "written"
   message.

Our change guarantees trailing-empty flushes (`:w` fires busy + `ui_flush()`
while busy) in the only test that runs a TUI on `/dev/null`.

0. In a normal (non-busy) no-sync flush, post always contains `cursor_normal`.
1. Our change emits:
   ```
   busy_start → msg_progress("…written") → ui_flush → busy_stop
   ```
2. TUI flushes *while busy*, so `should_invisible() = true` and `flush_buf`
   skips the cursor-restore string (`bufs[2]` is zero-length).
3. libuv *cannot complete* a write whose tail is a zero-length buffer: EVERY
   trailing-empty write goes through epoll, even when every byte was already
   written. https://github.com/libuv/libuv/issues/5182
4. In `tui_spec.lua:3298`, the output fd is `/dev/null`, whose kernel
   `file_operations` has no `.poll` method (`drivers/char/mem.c`), so
   `EPOLL_CTL_ADD` fails with `EPERM`.

Trailing empty buffers are semantically pointless, and libuv punishes
them: forced async, one epoll round-trip *per trailing empty*, plus
a 0-byte `write()` syscall.

Helped-by: Shougo Matsushita <Shougo.Matsu@gmail.com>
Helped-by: Fred Sundvik <fsundvik@gmail.com>
2026-07-04 23:17:27 +02:00
Sébastien Hoffmann
444d0b8b6c feat(statusline)!: allow %= in item groups, scope %< to item groups #40369
Problem: Separation markers (%=) are ignored within item groups. This
lead to a regression when the C implementation of the statusline was
replaced with a default expression. When the user configured a custom
ruler expression with a %= and used the overloaded item group syntax to
set the ruler width, the separation marker worked in the ruler, but not
when the ruler was incorporated into the statusline where the item group
syntax was interpreted in the usual way.

Solution: Analogously to top-level behaviour, expand separation markers
evenly within item groups until `minwid` is reached (if set).

ref https://github.com/neovim/neovim/pull/33036
fix https://github.com/neovim/neovim/issues/39984
ref https://github.com/neovim/neovim/issues/40247

Problem: The recursion offset into the static `stl_items` was not taken
into account when adjusting the item count after truncation.

Steps to reproduce: first prepare `stl_items`:
  set stl=%{%repeat('%#Error#',10)%}
then watch how the Error highlight leaks into the recursive call:
  set stl=%l%l%l%{%nvim_eval_statusline('test%l%<',{'maxwidth':3,'highlights':1}).highlights%}

ref https://github.com/neovim/neovim/issues/32259

* fix(statusline): consistent truncation at multicell character

Problem 1: truncation of item groups at multicell character didn't take
into account that minwid can be specified as a negative number.

Problem 2: after truncation at top-level from the right at multicell
character, the returned width was always `maxwidth`, even though the
actual width was reduced. In vim, this can be observed as a statusline
that is not fully drawn until the edge of the screen:
  vim --clean +"set ls=2 stl=%{%repeat('x',&columns-2)%}🙂x%<"

Problem 3: after truncation at top-level from the left at multicell
character, the resulting gap to reach `maxwidth` again was filled with
fillchars, but then the final NUL was not set correctly.
This can be seen in the following example, where the statuscolumn spills
into the editing area starting from line 10:
  nvim --clean +"set number stc=%<x🙂%{repeat('x',43)}%l" +"norm yy10p"

Solution: fix the small errors and, at top-level, consistently reduce
the size instead of compensating with fillchars. In the case of the
statusline and the winbar, the remaining place is filled with the
configured fillchars in `win_redr_custom`, after `build_stl_str_hl` has
returned. In all other cases (title, icon, statuscol, tabline, ruler),
there seems to be no point in adding additional spaces at the end.

* feat(statusline)!: scope %< to item groups

Problem:
Previously, item groups were only truncated at the beginning, which is
often not desired. In the example
  %.15(path: %f%)
the group's title/label is truncated away:
  <th/to/file.txt
Truncation markers (%<) in item groups were processed at the top-level
in the end, which can be confusing. Only the first %< is used for the
whole string, and it is used even if the containing item group is
hidden. Additionally, in the case of hidden item groups, the marker's
position was not adapted. For example,
  %(hidden%<%)%f
had the effect of truncating the path somewhere in the middle:
  /path/<file.txt

Solution:
Make truncation consistent with top-level behaviour, which has a better
default of truncating at the first `Normal` item, i.e.
  path: <file.txt
and allows for fine-grained control with truncation markers (%<). E.g.
  %.15(path: %f%<%)
now yields
  path: /path/to>
The original behaviour can be restored like so:
  %.15(%<path: %f%)

BREAKING CHANGE: %< is no longer processed at top-level
- the default truncation behaviour has changed: now at first item
- truncation markers inside item groups don't affect truncation outside
  of the item group anymore
- several truncation markers can now have an effect when separated with
  item groups, whereas previously only the first one globally had

ref https://github.com/neovim/neovim/issues/39984
2026-06-27 11:10:05 -04:00
Justin M. Keyes
5675c11910 fix(cmdwin): space in cmdwin-char 'statuscolumn'
To match the old behavior, the cmdwin-char should not be followed by
a space char.
2026-06-26 21:04:10 +02:00
Justin M. Keyes
ac623bd417 fix(cmdwin): duplicate line in history
Problem:
After ctrl-f from the cmdline, the last 2 lines of cmdwin are redundant.

Solution:
In `open_cmdwin`, clear the live cmdline so that unwinding it (via
Ctrl_C) does not add it to history.
2026-06-26 21:04:10 +02:00
Justin M. Keyes
b2bf7bcfb1 feat(cmdwin): implement cmdwin as a normal buf+win
Problem:

cmdwin (the `:q` cmdline buffer) has various limitations which require
special-casing all over the codebase.

Besides complicating the code, it also breaks async plugins if they try
to create buffers/windows after some work is done, if the user happens
to open cmdwin at the wrong the moment:

    Lua callback: …/guh.nvim/lua/guh/util.lua:531:
    E11: Invalid in command-line window; <CR> executes, CTRL-C quits
    stack traceback:
        [C]: in function 'nvim_buf_delete'
        …/guh.nvim/lua/guh/util.lua:531: in function <…/guh.nvim/lua/guh/util.lua:526>

Solution:

Just say no to "inception". Reimplement cmdwin as a normal buffer+window.

All of the cmdwin contortions (in both core, and innocent plugins) exist
literally only to support "inception": recursive
cmdwin-in-cmdline-things, like `<c-r>=`, `/`, search-during-substitute,
`:input()`, etc. So we just won't support that (though I have
a potential plan for that later, which I call "modal parking lot").

The benefit is that plugins, and core, no longer have to care about
cmdwin.

BONUS:
- mouse-drag on vertical separators works (it only worked for
  horizontal/statusline before)
- inccommand-in-cmdwin now works correctly, for free (thus don't need
  #40077).

POTENTIAL FOLLOWUPS
- Drop `CHECK_CMDWIN` ("E11: Invalid in command-line window"), allow chaos.
- Unify `BUFLOCK_OK` / `LOCK_OK` ?

DESIGN:
- Eliminate lots of C globals, `EX_CMDWIN`, etc.
- `text_locked()` no longer reports true for cmdwin.
- cmdwin = a normal window with 'winfixbuf', 'bufhidden=wipe',
  'buftype=nofile'. Invariants come from those options rather than
  special cases throughout the codebase.
- `nv_record` for q:/q//q? calls Lua
  `nlua_call_vimfn("vim._core.cmdwin", …)`. No `K_CMDWIN`
  / cmdline-reader detour.
- `cedit_key` (`c_CTRL-F`) schedules a deferred event that calls
  `vim._core.cmdwin.open(type, content, pos)` and returns `Ctrl_C` so
  the in-flight cmdline cancels. Reader state is not serialized; instead
  the captured `(type, line, col)` is replayed via
  `nvim_feedkeys(type..line.."<CR>", "nt", …)` after user confirms.
- On confirm/cancel: `<CR>` / `<C-C>` calls into Lua which closes the
  window and re-feeds the cmdline.

BREAKING CHANGES:
- Expression-register cmdline (`<C-R>=` from insert-mode) no longer
  supports cmdwin. Same applies to `input()` / `inputlist()` (already
  covered by `text_locked`).
- Usage of cmdwin in macros/mappings will probably break (assuming they
  ever worked).
2026-06-25 18:36:24 +02:00
luukvbaal
28ffb334cf fix(autocmd): never draw aucmd_prepbuf temp win #40379
Problem:  An autocommand that redraws may do so while curwin is
          temporarily set for the autocommand scope. This can result in
          flickering or unexpected state with UI components (statusline,
          winbar, decor providers...) that depend on the current window.
          Current workaround for statusline and winbar specifically
          delays the redraw, which can itself be unexpected for the
          autocommand.

Solution: If redrawing happens with a temporary autocmd current window,
          temporarily restore the current window while redrawing.
2026-06-23 16:05:26 -04:00
Barrett Ruth
e4fbe162c1 fix(statusline): defer redraw in aucmd context #40309
Problem: `nvim_exec_autocmds({buf=...})` may temporarily switch curwin/curbuf
through `aucmd_prepbuf()`. Requested statuslines/winbars before
`aucmd_restbuf()` may erroneously see the target window as current.

Solution: Track `aucmd_prepbuf()` window-switch depth and leave statusline/winbar
redraws marked dirty until the original window is restored.
2026-06-23 06:05:11 -04:00