100 Commits
Author SHA1 Message Date
Justin M. Keyes aef67ce76f feat(remote-ssh): remote :restart #42708
Problem:
`:restart` doesn't work if UI is local and server is remote (i.e.
different machines).

Solution:
How remote-ssh `:restart` works:
1. The remote server restarts as usual; the new server takes over the
   same `--listen` socket path.
2. `vim.net._ssh.serve()` doesn't reattach anything. It doesn't exit
   when the old server-PID dies, so ssh session/tunnel stays alive. It
   gets the new PID after polling for a bit.
3. So the local `ssh -L` tunnel stays up, and forwards to the same
   remote socket path, now owned by the new server.
4. The UI reconnects through the tunnel (the `addr` stored by
   `nvim_chan_set`).
2026-11-05 19:06:09 -05:00
Justin M. Keyes 893d90fced feat(remote-ssh): supervise server in Lua, "Update Nvim" for nightly #42706
Problem:
- remote_ssh.sh detects ssh disconnect by writing a heartbeat every 1s,
  which the local side buffers for the whole session.
- The server's stdout (e.g. `io.write()` from a random plugin) writes to
  the tunnel, which in theory may contain a bogus "NVIM_READY".
- A nightly install is never updated on the remote.

Solution:
- Supervise the server in `vim.net._ssh.serve()` (Lua). It detects
  disconnect via stdin EOF and discards server stdout. The script only
  finds or installs `nvim`.
  - Also a step toward Windows remotes.
- User may choose "Update Nvim" to reinstall the nightly.
2026-11-05 09:48:04 -05:00
Justin M. Keyes 67f6f0503c feat(uri): structured querystring, join(), new(), normalize() #42705
Problem:
- vim.uri.parse() not fully baked, it does not structure some parts of its result (esp. `query`).
- The scheme is not lowercased, which violates the RFC.
- No "constructor" for vim.Uri.
- tostring(vim.Uri) is not fully driven by the vim.Uri fields.

Solution:
- Structure `authority`, `query`.
- Lowercase the scheme.
- Drop `_parse_ssh_uri()`.
- Add `vim.uri.new()`.
- Fix tostring(vim.Uri).
- Validation.
2026-11-05 07:49:38 -05:00
Justin M. Keyes 094621e4c9 feat(eval): mark getenv() as api-fast #42703
Problem:
`vim.env` (`vim.fn.getenv`) fails in a fast context (luv callbacks, LSP
rpc).

Solution:
Mark `getenv()` as |api-fast|. `vim_getenv()` never tickles `os_breakcheck()`.
2026-11-05 06:19:50 -05:00
Justin M. Keyes b712e7f6d5 feat(logging): "$VIMRUNTIME" instead of full path #42700 2026-11-05 05:04:56 -05:00
Justin M. Keyes 5c3563bfd3 feat(lua): vim.system():wait() "cond" predicate #42695
Problem:
Can't easily wait on the streamed output of a long-lived process (e.g.
`ssh -L`). remote-ssh used a private `run_wait()` util.

Solution:
Add `cond` to `SystemObj:wait()`: return early (process keeps running)
when `cond(stdout, stderr)` is true. Drop `run_wait()`.
2026-11-05 04:19:23 -05:00
Justin M. Keyes 01a6166b78 feat(rpc): vim.rpcrequest{timeout=…} #42690
Problem:
- vim.rpcrequest() blocks forever if the peer never responds.
- Nvim crashes on unexpected (unsolicited/late) RPC response (attempts
  to index empty call stack).
- rpcrequest()/rpcnotify() are redundant with vim.rpcrequest()/vim.rpcnotify().

Solution:
- `vim.rpcrequest(chan, opts, method, …)` accepts `opts.timeout`.
- Discard (and DLOG) responses to stale (timed-out) requests.
- Deprecate rpcrequest() and rpcnotify().
2026-11-04 16:58:55 -05:00
Justin M. Keyes dbecb8a6fc fix(health): graphics check waits 1s without a TTY UI #42688
Problem:
Tests are slow because of:
- kitty graphics check in a non-terminal
  - Also affects checkhealth outside of tests (if run headless).
- network call (`git ls-remote`)

Solution:
- Skip the query unless a UI has "stdout_tty".
- Skip the update-check in tests.
2026-11-04 10:14:34 -05:00
Justin M. Keyes a83b7129e8 feat(security): per-channel "trust" property #42687
Problem:
- The TUI rejects server-to-client API calls, via a hardcoded check.
  Other clients, e.g. `nvim --remote` or a `sockconnect()` channel, let
  untrusted peers call the local API.
- No per-channel control of this.

Solution:
- Support a per-channel "trust" flag for `sockconnect()` and `nvim_chan_set()`.
- remote-ssh uses `trust=false` instead of hand-encoded msgpack.
- UI and `--remote*` client channels are untrusted by default.
- Report the "trust" flag in `nvim_get_chan_info()`.
2026-11-04 09:41:24 -05:00
Justin M. Keyes e17147044e Merge #39378 ":connect ssh://" 2026-11-04 05:24:48 -05:00
Justin M. Keyes 62f79bd7e1 feat(remote-ssh): lifecycle, cleanup, slow tests
- SshHost fields: follow ssh's "first wins" rule, so e.g. a later User
  line in the same block no longer overrides an earlier one.
- Local server now exits on its own when the remote session ends.
- Before auto-install, try existing `nvim` found in remote `$PATH`.
- Combine the remote_ssh_x.sh scripts into one remote_ssh.sh script.
- Ignore `RemoteCommand` from ssh_config, which made ssh refuse our command.
- Don't accumulate the remote heartbeat in memory for the whole session:
  `run_wait()` captures output only until it returns.
- Before detaching the UI, check that the remote Nvim answers through the
  forward: sshd may refuse the forward, but still run the script.
- Stop the session (tunnel, remote Nvim) if any connect step fails, or if
  the UI detached meanwhile.
- Run `ssh -G` once per session, instead of for every ssh command.
- Remote script handles signals at once, instead of after the heartbeat's
- Error if :restart is attempted.
  FUTURE: Remote :restart prints a marker, with its [command], on
  stdout. It also sends the TUI a restart event pointing at the LOCAL
  server. The TUI reattaches there. Local server repeats the remote-ssh
  connect steps, and sends the TUI a connect event for the new forwarded
  socket. Then it runs [command] (e.g. session restore) on the new
  remote over RPC.
2026-11-03 19:05:58 +01:00
Justin M. Keyes b4d549f439 fix(security): reject server-to-client API calls #42668 2026-11-03 12:00:05 -05:00
Justin M. Keyes 6332ba64fe test(harness): remove socket dir on exit #42658
Problem:
Each test run leaves its stdpath("run") dir, /tmp/nvim_<pid>, behind.

Solution:
Delete it when the runner exits.

AI-assisted
2026-11-02 14:34:22 -05:00
Justin M. Keyes 9213ffc7da fix(path): path_shorten_fname() treats "c:x" as a root #42657
Problem:
On Windows, path_shorten_fname() treats "c:x" as a root ("c:\"), so it
shortens "c:xyz\foo" to "yz\foo".

Solution:
Only "c:\" (drive and separator) is a root, not "c:x". Also simplify
path_to_absolute().
2026-11-02 11:06:06 -05:00
Justin M. Keyes 54eac81f8f fix(watch): stop watch() handle on error #42656
Problem:
On Windows, a libuv watch may repeat an error forever (e.g. EPERM after the
watched file's parent directory is deleted). The previous fix
77443675ef only stops the watch if it sees the file deleted, so a lost
event (buffer overflow) or filtered event still spins. It also silently
kills the watch on any delete, so a recreated file gets no events.

Solution:
Stop the handle on the first error, which also marks the backend failed so it
is not reused. Don't stop on delete.
2026-11-02 09:40:14 -05:00
Justin M. Keyes 3a12b2bbc7 fix(normal): count before reg applied twice to r<Tab>, g<LeftMouse>, "." #42654
Problem:
`2"ar<Tab>` (with 'expandtab') replaces 4 chars, and `2"ag<LeftMouse>` jumps to
the 2nd tag. As with `2"_x`: the stuffed keys already hold the count, but a
nested command executed by exec_stuffed() sees the register, so it multiplies
by `opcount` again.

Solution:
Clear `opcount` in exec_stuffed() instead of only in nv_optrans().
`dd2"a.` now deletes 2 lines, as documented at `:help .` (Vim: 4).
2026-11-02 08:42:08 -05:00
Justin M. Keyes 124a28de97 fix(ui2): bogus "'replacement string' item" message #42650
Problem:
UI event handlers may clobber `IObuff` while the caller is using it.
With ui2 `targets='msg'`, `i_CTRL-O :edit` shows "'replacement string'
item" instead of the file info, because `check_string_array()` writes
`IObuff` on every `nvim_buf_set_lines()`.

Solution:
- Save and restore `IObuff` around UI event handlers. This is 1KB, but
  negligible vs the other work that UI handlers do (msgpack, Lua calls).
- `check_string_array()`: don't use `IObuff`.
2026-11-02 06:57:23 -05:00
Justin M. Keyes 7437121ea3 build(test): unittests fail with macOS 27 SDK #42646
Problem:
macOS 27 SDK `OS_ENUM` emits `typedef enum : uint8_t {…} foo_t;`,
which LuaJIT FFI can't parse.

    declaration specifier expected near 'task_shared_region_stubs_t'

Solution:
Rewrite such a typedef to its underlying type.
2026-11-01 21:13:25 -05:00
Justin M. Keyes 06ac562766 fix(eval): leaks and Lua stack misuse on failed conversion #42643
Problem:
- `json_encode()` leaks the conversion stack if a dict key nested more
  than 8 levels deep is invalid UTF-8, and leaks the string of an
  invalid special string (`v:msgpack_types.string`).
- Lua conversion passes the total stack size to `lua_checkstack()`,
  which expects the extra slots, so it fails at half the real limit.
- If `nlua_push_typval()` fails, the Lua stack is not restored: e.g.
  `vim.g.x` returns part of the half-built value.

Solution:
- `encode_vim_to_*()` owns the conversion state, so converter macros
  may return at any point.
- Check the Lua stack once per value, for 3 extra slots.
- Restore the Lua stack and push nil on failure.
2026-11-01 20:31:29 -05:00
Justin M. Keyes 96011df71a fix(redraw): screenpos(), winrestview(), conceal_lines edge cases #42642
Problem:
- An invalidated `conceal_lines` mark conceals the line it collapses
  onto.
- Treesitter: rechecking the last row of a `conceal_lines` mark deletes
  it, and that row's query may not return the capture (`#offset!`, or
  a capture shrunk by an edit). The rows stay unconcealed until a
  redraw.
- `screenpos()` of a char fully hidden by extmark-conceal returns
  `endcol` = `col - 1`.
- `screenpos()` of a char whose inline virt_text or TAB starts on a row
  above reports that row, with `curscol`/`endcol` past the window
  width.
- `winrestview({topline=N})` without "skipcol" keeps the skipcol of the
  previous topline.

Solution:
- Skip invalid marks in `decor_row_has_mark()`.
- Query the rows of a deleted `conceal_lines` mark that the row's query
  did not re-add.
- Report the cursor's row; clamp `col` to the row's first cell, and
  `endcol` to `col`.
- Reset skipcol when "topline" changes, unless "skipcol" is given.
2026-11-01 15:27:23 -05:00
Justin M. Keyes 5a37eb7a49 feat(lua): vim.uri #24527
Problem:
- `vim.fs.abspath('fugitive:///a')` returns "<CWD>/fugitive:/a", so
  filetype functions get a mangled path for URI buffers.
- `vim.fs.root()` doesn't accept "file://" URIs.
- No URI parser, so modules use ad-hoc scheme patterns, e.g.
  `vim.ui.open('~/a:b')` treats the path as a URL.

Solution:
- Introduce `vim.uri.parse()`, and use it everywhere.
  - DWIM: a one-letter "scheme" is a drive letter.
- `vim.fs.abspath()` returns URLs as-is.
- Expose `vim.uri` a module. Deprecate the old `vim.uri_xx()` names.
2026-11-01 13:20:08 -05:00
Justin M. Keyes 48dcaab903 fix(lsp): stop uninitialized clients #42640
Problem:
Various stop paths skip clients still waiting for an "initialize" response.
- Quitting Nvim leaves their servers running/dangling.
- `vim.lsp.enable(name, false)` and `:lsp stop|restart|disable` don't
  stop them.
- Extra `stop()` sends extra "shutdown" (instead of force-stopping as
  documented).
- Restart scheduled before exit *starts a server during exit*.
- A late "initialize" response initializes a client that is already
  being force-stopped...
- Force-stopping warns "Client X quit with exit code N".

Solution:
- Include `_uninitialized` clients in all stop paths.
- Extra `stop()` force-stops. The exit handler skips clients that are
  already stopping, so `exit_timeout=false` is still respected.
- Skip a scheduled restart during Nvim exit.
- Ignore the "initialize" response of a stopping client.
- Don't warn about the exit status of a stopped server.
2026-11-01 12:15:34 -05:00
Justin M. Keyes e1a1ef620e Merge #42634 from justinmk/fixmisc2
fix(conceal): provider called on deleted cursor-line
2026-11-01 08:34:52 -05:00
Justin M. Keyes cd280bb81d test: exrc tests race with ShaDa write of orphaned child
Problem:
The "exrc from cwd" tests leave the :terminal child running. clear()
hangs it up, and it writes ShaDa to Xstate while after_each() deletes
it, so the rename fails and logs E136:

    WRN … getout:880: Exiting with error: "E136: Do not forget to remove
    Xstate/nvim/shada/main.shada.tmp.a or rename it manually to
    Xstate/nvim/shada/main.shada."

Solution:
Quit the child before clear().
2026-11-01 13:33:39 +01:00
Justin M. Keyes 596945b66d fix(conceal): provider called on deleted cursor-line
Problem:
Deleting the last line (the cursor-line) with `'conceallevel' > 1` and
empty 'concealcursor', makes check_cursor_moved() check if the old
cursor-line is concealed.

    Decoration provider "conceal_line" (ns=nvim.treesitter.highlighter):
    Lua: …/treesitter/highlighter.lua:410: Index out of bounds
    stack traceback:
      [C]: in function 'nvim_buf_get_lines'
      …/treesitter/highlighter.lua:410: in function 'on_range_impl'
      …/treesitter/highlighter.lua:639: in function <…/treesitter/highlighter.lua:618>

Solution:
decor_conceal_line(): return false for a row past the end of the buffer.
2026-11-01 12:13:46 +01:00
Justin M. Keyes 2b13dfb2a4 fix(ui): curs_columns() scrolls a window due for a full redraw #42625
Problem:
After a resize, a decoration provider's "on_start" calling line('w0')
makes curs_columns() scroll a window whose grid offsets are stale: it
shifts cells of another window and sends a wasted grid_scroll.

Solution:
Don't scroll a window that is due for a full redraw.
2026-11-01 04:33:28 -05:00
Justin M. Keyes 2cd3387b66 fix(multicursor): cascade touches v:count #42623
Problem:
With multicursor, cascade sets `v:count`, e.g. after `Qj` an Insert-mode
`<expr>` mapping sees `v:count=1` instead of 0.

Solution:
Save/restore `kCtxCount` in the replay sandbox.
2026-10-31 19:45:32 -04:00
Justin M. Keyes 974aad5b59 fix(conceal): 'list' TAB after extmark conceal with 'linebreak' #42621
Problem:
With 'list' and 'linebreak', a TAB after extmark-concealed text is drawn
as `>-` plus spaces instead of `>---…`: the linebreak code absorbs the
hidden cells before the TAB code sizes the tab.

Solution:
Count the hidden cells in the 'list' TAB width; let "Tab alignment"
absorb them.
2026-10-31 18:40:37 -04:00
Justin M. Keyes d299306564 fix(ui): ui_attach handler resets v:count #42619
Problem:
With ui2 `msg.targets='msg'`, a message (e.g. `print()`) from a mapping
resets `v:count`: the msg_show handler scrolls the msg window with
`:normal`, which sets `v:count`.

Solution:
Add `kCtxCount` to Context. Save/restore it around Lua UI event
handlers.
2026-10-31 17:02:03 -04:00
Justin M. Keyes 79b1fea2b5 fix(shada): repair damaged shada file, write atomically #42618
Problem:
A truncated shada file (crash, or conflicting Nvim sibling) fails with
E576 on every startup and exit, forever: the merge treats it as "not
a shada file" and refuses to replace it. And when main.shada is missing
or unreadable, or with `:wshada!`, shada is written in-place, not
atomically.

Solution:
- If a valid shada _header_ is found, we know it is a (damaged) shada
  file, so we shouldn't just throw away the entire thing. Keep the
  entries gathered so far and replace the file.
- Always write a temp file and rename it.
- Flush (and fsync) the temp file before renaming it. Check the final
  flush.
2026-10-31 13:01:52 -04:00
Justin M. Keyes aeb1e67d0f refactor(shada): get available ".tmp.X" filename #42617 2026-10-31 11:48:52 -04:00
Justin M. Keyes 33a812f4fd fix(shada): old .tmp.X files block shada writes forever #42616
Problem:
Each failed shada write leaves a `.tmp.X` file, which is never removed.
This is not uncommon on Windows: rename() fails if another process (AV,
indexer, another Nvim, …) has the file open. Once the `.tmp.a…z`
fallback names are exhausted, every write fails with E138.

Solution:
- Reuse the oldest `.tmp.X` when all names are taken.
- Windows: retry rename() briefly on EPERM/EBUSY.
2026-10-31 11:01:04 -04:00
Justin M. Keyes cc917b90ad fix(event): timers timeout too soon after loop was blocked #42615
Problem:
libuv timers count from the loop's cached "now", updated once per loop
iteration. libuv caches it to avoid a clock read per timer start
(historically a syscall on older kernels, etc.). Nvim runs Lua/Vimscript
in loop callbacks, so after blocking code a timer fires at the next loop
wakeup: thus e.g. `vim.system()` timeout kills the process immediately,
`jobstop()` sends SIGKILL before the 2s grace period, etc.

Solution:
Call `uv_update_time()` before starting these timers.
2026-10-31 07:38:08 -04:00
Justin M. Keyes 7ad3506fa0 Merge #42370 conceal-aware 'wrap' 2026-10-31 07:23:47 -04:00
Justin M. Keyes 1d5c87b845 fix(event): wait() skips final condition check #42600
Problem:
`wait()` and `vim.wait()` return -1 without checking the condition again
if an event processed during the last poll exceed the timeout, though
the docs say the condition is evaluated on internal events. So -1 may
mean the condition is already true.

If we "document" this, we would essentially be documenting a race
condition.

Solution:
Check the condition once more after the last poll, like Python's
`Condition.wait_for()`.
2026-10-31 06:52:03 -04:00
Justin M. Keyes 6b98b23694 refactor(conceal): uncruft conceal-aware 'wrap'
Problem:
- Reflow is bolted-on beside the raw-column code: conceal-aware copies
  of the motion, mouse and vcol walks, a third "layout column" kind,
  parallel state (walkers, caches, flags), and rows that
  redraw-and-rollback.
- Numerous bugs: wrong cursor/click/TAB positions, stale heights (Visual
  area, skipcol), reads of freed marktree nodes, and every line on the
  slow path if ANY conceal mark exists.

Solution:
- Change the existing code: one displayed->raw vcol map feeds the base
  coladvance(), vcol2col() and nv_screengo(); one conceal_replacement()
  rule serves :syntax, matchadd() and extmarks.
- Calculate, don't track: conceal is read from the marktree where it
  is needed, and a column is either a raw or a displayed vcol.
  - Eliminates `ConcealWalk`, `LinebreakState`, `ConcealOffState`,
    `ConcealSizeState`, `conceal_cursor_win`, the provider-namespace
    map, etc. Rows without conceal marks stay on the fast path.
- Flat control flow: a row ends at the line's displayed size, with no
  "roll-back".
- One name per concept: "extmark-conceal", "displayed vcol", defined
  once at `CharsizeArg.extconceal_off`.
- Invalidate what changed: when 'concealcursor' reveals or conceals
  lines again, only those line-heights are invalidated, only in the
  windows that reveal them.
- Treesitter: one row-hook parses each row before concealing it, so rows
  not yet drawn are measured concealed. The redraw parse is synchronous
  only when a window shows conceal.
- Tests: extend existing ones, table-driven, `screen:expect`.

PERF COST: a row's first conceal check parses and queries that row
alone (~28 µs per row in `:help`). One CTRL-F page in a 50-line window
takes 1.6 ms in options.txt (master: 0.3 ms) and 2.9 ms in lua.txt
(master: 0.45 ms). Markdown pays the same on master via
`_on_conceal_line`. Potential fixes:
- #36030: LanguageTree keeps injected trees across parse ranges (the
  per-row parse cost).
- #41913: checks conceal a window-height of rows at a time (the per-row
  query cost).

DESIGN NOTE: `_on_conceal` lets a row be measured concealed before it is
drawn (`G`, scrolling, nvim_win_text_height()), extending base's
`_on_conceal_line`.
- COSTS:
  - One Lua call, parse and query per row.
  - `_conceal_checked`, a cache that edits, tree changes and injection
    changes must invalidate.
  - The marktree changes during measuring and drawing.
  - Conceal is written twice: ephemeral marks for drawing, stored marks
    for layout.
  - A per-buffer opt-in (nvim__buf_set_conceal_provider()), because
    decoration providers are global.
GOAL(?): one range hook, `_on_conceal(win, buf, srow, erow)`, replacing
`_on_conceal_line` and `_on_conceal` (see #41913). After #36030 only
the query cost is left, which batching removes.
2026-10-30 17:15:55 +01:00
Justin M. Keyes 7fa72edcd4 fix(event): wait(), jobwait() timeouts #42599
Problem:
- wait() never times out if each check returns in under 1 ms (e.g. the
  condition closes a timer), because each pass truncates the elapsed time
  to whole milliseconds.
- jobwait() with a timeout returns -1 for all jobs after the first one
  that exits.
- CursorHold drops sub-millisecond waits, so frequent events postpone it.
- wait() leaks the condition result if it is a String, List, etc.

Solution:
- Compute the remaining time from a fixed start time.
- Track the CursorHold wait time in nanoseconds.
- Free the condition result.
2026-10-30 10:14:26 -04:00
Justin M. Keyes e0516a46e8 test: runner loads Lua modules from system install #42582 2026-10-29 19:07:13 -04:00
Justin M. Keyes 63497867b6 test: runner loads Lua modules from system install #42581
Problem:
Tests may fail locally but pass on CI, because the test runner may use
the Nvim runtime installed on the system.

The test runner (`nvim -l test/runner.lua`) starts without $VIMRUNTIME,
so Nvim falls back to the runtime under the system install (e.g.
/usr/local/share/nvim/runtime). `runner.lua` sets $VIMRUNTIME, but only
after startup has built 'runtimepath', so the runner's own Lua (e.g.
`vim.glob` in glob_spec) loads stale modules from the system install.

Solution:
Set `$VIMRUNTIME` before starting the runner.
2026-10-28 09:57:43 -04:00
Justin M. Keyes 1cfebe93d5 fix(exit): prompts wait forever when no key can arrive #42494
Problem:
After a deadly signal, or UI-disconnect during exit, a prompt waits for
a key that never arrives, so the server never exits. For example,
a multiline Lua error queued at exit opens "-- More --". The prompt's
input-wait continually runs libuv timers but not `main_loop.events`, so
a repeating timer that calls `vim.schedule()` queues callbacks (and Lua
refs) without bound (GB of memory...).

Solution:
During exit without a UI, or after a deadly signal, prompts don't wait:
- hit-enter and "-- More --" are skipped (as in headless-mode).
- confirm() returns its default button (same as without a UI).
- Other prompts (input(), getchar()) are interrupted as if CTRL-C was
  typed. Like a typed CTRL-C this sets `got_int`, so code that prompts
  _again_ (`while getchar() != 'q'`) terminates; remaining autocmds are
  skipped.

Note:
- This also interrupts a prompt already waiting when the last UI
  detaches.
2026-10-21 09:14:15 -04:00
Justin M. Keyes 1944cb3201 refactor(lsp): simplify semantic token request checks #42472
Problem:
- Stale responses are checked three times: in the response handler, at
  the start of process_response(), and in is_current().
- is_current() also compares highlighter and client_state identity,
  because on_detach() drops the client state without clearing the active
  request.
- process_response() takes `version`, which is already
  `request.version`.
- merge_highlight_ranges() allocates a vim.Range for every token it
  checks.
- No test covers a synchronous response whose token processing yields.

Solution:
- on_detach() cancels active requests, so `state[field] == request` also
  covers detach.
- Drop the redundant checks and the `version` param.
- Compare positions directly in merge_highlight_ranges().
- Tests: set `_yield_interval_ns` so tests are deterministisc
  (synchronous response that yields on every token).
2026-10-20 17:17:41 -04:00
Justin M. Keyes f808a5b1c9 fix(tui): resume after suspend in fish -c exits #42474
Problem:
On macOS, resuming Nvim after a suspend under `fish -c` sometimes exits instead:

    FAILED   test/functional/terminal/buffer_spec.lua @ 696: :terminal buffer can resume suspended PTY process running in fish
    Row 1 did not match.
    Actual:
      |*[Process exited 0]                                |

On macOS, `kill(0, SIGSTOP)` stops the members of the process group one
at a time. fish can see Nvim stop while fish itself is still running;
when the group resumes, fish exits instead of waiting for Nvim.

Solution:
If the parent is in our process group, stop it first.
2026-10-19 14:02:17 -04:00
Justin M. Keyes 4c50814fca Merge #42471 unreliable tests 2026-10-19 13:22:43 -04:00
Justin M. Keyes 9916944868 ci(windows): MSYS tools sometimes crash with "add_item … failed"
Problem:
On Windows, a test that runs a Git `usr/bin` tool (`cat`, `diff`, …) sometimes stalls 15s and
then fails:

    FAILED   test/functional/vimscript/system_spec.lua @ 470: systemlist() passing string with linefeed characters as input splits the output on linefeed characters
    Expected:
    { "abc", "def", "ghi" }
    Actual:
    { '      0 [main] cat (2736) C:\\Program Files\\Git\\usr\\bin\\cat.exe: *** fatal error - add_item ("\\??\\C:\\Program Files\\Git", "/", ...) failed, errno 1' }

The MSYS runtime sets up per-user shared state on first use. If a test kills an MSYS process
(CTRL-C) during that setup, the next MSYS process (in any parallel test group) waits 15s for
it, then sets it up again and dies.

Solution:
Keep an MSYS process (`sleep infinity`) alive for the test run, so the setup happens only once.
2026-10-19 18:50:55 +02:00
Justin M. Keyes 139a376a6d test: unreliable "detach! keeps UI attached, sets detach=true"
Problem:
On Windows, the server sometimes dies after the TUI client is killed:

    FAILED   test/functional/terminal/tui_spec.lua @ 303: TUI :detach detach! keeps UI attached, sets detach=true
    retry() attempts: 149
    test\client\uv_stream.lua:90: ENOENT

Analysis:
The server shares the client's console until rpc_close_event() swaps it
to a hidden console (os_detach_ui_console). If the :terminal closes the
console (on client exit) first, the server gets SIGHUP (CTRL_CLOSE_EVENT)
and exits.

Solution:
Delay the :terminal's job-exit handling (`vim.wait(fast_only=true)`)
until the server has dropped the UI.
2026-10-19 18:50:55 +02:00
Justin M. Keyes 3c196b8650 test: unreliable "TUI client exits … with :cquit"
Problem:
On Windows, the remote TUI client sometimes exits 0 instead of 42:

    FAILED   test/functional/terminal/tui_spec.lua @ 5179: TUI client exits when server quits with :cquit
    Failed to match any screen lines.
    Expected (anywhere): "Process exited 42"
    Actual:
      |[Process exited 0]                                |

Analysis:
Possibly the client drops the unread "error_exit" event when a write to
the closed pipe fails.

Solution:
Set `retries = 2`.
2026-10-19 18:50:55 +02:00
Justin M. Keyes 399cecb8e7 test: unreliable treesitter 'foldexpr' tests
Problem:
On slow CI runners (asan/tsan/macOS), fold_spec sometimes reads incomplete fold levels:

    FAILED   test/functional/treesitter/fold_spec.lua @ 88: treesitter foldexpr recomputes fold levels after lines are added/removed
    Expected:
    { "0", "0", ">1", "1", "1", "0", "0", ">1", "1", "1", "1", "1", ">2", "2", "2", "1", "0" }
    Actual:
    { "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0" }

Folds are computed after an async parse, which runs in 3ms slices. `poke_eventloop()` only
runs the first slice.

Solution:
Queue a parse callback (it runs after the fold update) and wait for it.
2026-10-19 18:50:55 +02:00
Justin M. Keyes ab70bed2ed Merge #42466 from justinmk/fixmisc
test: misc unreliable tests 2
2026-10-19 08:29:04 -04:00
Justin M. Keyes 0aec9da8c3 test: unreliable "exits immediately when stdin is closed"
FAILED   …/terminal/tui_spec.lua @ 3758: TUI exits immediately when stdin is closed
    …/terminal/tui_spec.lua:3758: Failed to match any screen lines.
    Expected (anywhere): "%[Process exited 1%]"
    Actual:
      |^                                                  |
      |~                                                 |
      |~                                                 |
      |~                                                 |
      |{2:[No Name]                       0,0-1          All}|
      |                                                  |
      |{5:-- TERMINAL --}                                    |
2026-10-19 03:12:16 +02:00
Justin M. Keyes 4fb7e215f9 fix(tui): resize during pager shows half-drawn frame
Problem:
screen_resize() at the pager flushes after repeat_message(), before
do_more_prompt() redraws. With synchronized output (:terminal mode
2026), that frame shows on its own: garbled top row, full "-- More --"
hint. Also on Windows, input can overtake the resize.

    FAILED   test/functional/terminal/tui_spec.lua @ 1241: TUI accepts resize while pager is active
    test/functional/terminal/tui_spec.lua:1241: Row 1 did not match.
    Expected:
      |*{101:FAIL 1}                                            |
      |{101:FAIL 2}                                            |
      |{101:FAIL 3}                                            |
      |{101:FAIL 4}                                            |
      |{101:FAIL 5}                                            |
      |*{102:-- More --}^                                        |
      |{5:-- TERMINAL --}                                    |
    Actual:
      |*  {101:IL 1}                                            |
      |{101:FAIL 2}                                            |
      |{101:FAIL 3}                                            |
      |{101:FAIL 4}                                            |
      |{101:FAIL 5}                                            |
      |*{102:-- More --^ SPACE/d/j: screen/page/line down, b/u/k}|
      |{5:-- TERMINAL --}                                    |

Solution:
- Don't flush in screen_resize() at the pager; do_more_prompt() will
  redraw it.
- Test: wait for VimResized before sending more input.
2026-10-19 03:10:29 +02:00
Justin M. Keyes 2aa32e4acd Merge #42465 from justinmk/fixmisc
test: misc unreliable tests
2026-10-18 21:09:46 -04:00
Justin M. Keyes a926855412 fix(tui): ESC before a terminal response is lost
Problem:
termkey treats ESC followed by a response (DCS/OSC/APC) as ALT + the
response, so the ESC is dropped. E.g. a slow reply to a remote UI's
UIEnter queries can land after a typed `<Esc>`, which then never leaves
Insert-mode.

Solution:
Emit the ESC as `<Esc>`, like for repeat/release events.
ref 03f5f25a
2026-10-19 02:48:05 +02:00
Justin M. Keyes 4db95f417f fix: unreliable "exit status 1 … with --listen error"
The server may print the error before the TUI enters the alternate
screen. Retry.
2026-10-19 02:42:00 +02:00
Justin M. Keyes 216ed9f9c6 test: unreliable "killing TUI process without :preserve"
FAILED   ...st/functional/ex_cmds/swapfile_preserve_recover_spec.lua @ 141: preserve and (R)ecover with custom 'directory' killing TUI process without :preserve #22096
    ...st/functional/ex_cmds/swapfile_preserve_recover_spec.lua:141: Failed to match any screen lines.
    Expected (anywhere): "%[Process exited 1%]"
    Actual:
      |{UNEXPECTED background = Screen.colors.NvimLightGrey2, foreground = Screen.colors.NvimDarkGrey2:^sometext                                             }|
      |{UNEXPECTED background = Screen.colors.NvimLightGrey2, foreground = Screen.colors.NvimLightGrey4:~                                                    }|
      |{UNEXPECTED background = Screen.colors.NvimLightGrey2, foreground = Screen.colors.NvimLightGrey4:~                                                    }|
2026-10-19 02:34:47 +02:00
Justin M. Keyes 58f1ec2e5e test(treesitter): unreliable "async parses with string parsers"
Problem:
The test assumes async parse() finishes within its first 3ms time slice.
On slow CI it doesn't, so parse() returns nil:

    parser_spec.lua:603: attempt to index a nil value

Solution:
Wait for the on_parse callback.
2026-10-19 01:26:34 +02:00
Justin M. Keyes c049b8f002 fix(bufwrite): abort :write if Progress handler changes the buffer #42464
Problem:
If a Progress handler changes the buffer during a slow `:write`, the
written file mixes old and new lines, or gets "???" lines (E315) if
lines were deleted. 89e915b67a only keeps 'modified'.

Solution:
Abort the write with E787 and restore the original file ('writebackup').
2026-10-18 19:26:16 -04:00
Justin M. Keyes 52a5843ce7 feat(progress): progress-message for slow :edit/:read #42463
Problem:
When reading a file is slow (`:edit` of a huge log) there is no UI
feedback.

Solution:
- Similar to `:write` (eb25a1192a), emit a progress-message (with %)
  if the file-read takes longer than `slow_io_ns()`.
- Lock the buffer while emitting, to prevent accidental buffer-unload by
  a Progress handler.
- Tests: abuse `$NVIM_TEST` + `v:testing` to activate "all file IO is
  considered slow", so tests can exercise while still being fast.
2026-10-18 17:12:01 -04:00
Justin M. Keyes 89e915b67a fix(bufwrite): 'nomodified' if buffer changed during :write #42458
Problem:
If a (naughty) autocmd (e.g. a `Progress` handler) changes the buffer
during a slow `:write`, buf_write() still unsets 'modified', even though
the buffer has unwritten changes. `:bdelete` would silently lose the
changes.

Solution:
Reset 'modified' only if `b:changedtick` did not change while writing.
2026-10-18 14:39:13 -04:00
Justin M. Keyes eb25a1192a fix(editor): noisy :write Progress event #42456
Problem:
- Every :write emits a progress-message #41222. Progress events fire and
  terminals flash a progress spinner (OSC 9;4), even for a fast write.
- The "running" Progress event fires before `b_saving` is set, and the
  "failed" one after it is cleared, so a Progress handler could unload
  the buffer being written (use-after-free).

Solution:
- Emit a progress-message only if `:write` is slow (longer than 0.5s),
  otherwise emit only a "bufwrite" msg-kind.
- Clear `b_saving` after reporting errors.

Note: a Progress handler can still change the buffer text during a slow
write: the file gets mixed content, and 'modified' is reset anyway (no
memory corruption though).
2026-10-18 12:06:13 -04:00
Justin M. Keyes f4d4409a0d fix(bufwrite): cursor jumps to cmdline during :write #42452
Problem:
3b8c19ea46 only hid the cursor around the "written" message.

Solution:
Hide the cursor from the start of the write (filename is shown) until it
finishes. Skip if filtering or :silent (no message).
2026-10-18 10:42:42 -04:00
Justin M. Keyes 868b4612c4 Merge #41879 from echasnovski/pack-packspec-part2
pack: implement packspec support, part 2 (`manifest.dependencies` in `:checkhealth`)
2026-10-18 10:05:53 -04:00
Justin M. Keyes 3119e5d964 Merge #42437 from justinmk/doc3
docs: all help docs should have a title
2026-10-18 07:22:52 -04:00
Justin M. Keyes 55befca234 refactor(docs): drop typst logic
Typst logic was added but has not be used for anything. We use Chrome to
generate (pretty decent-looking) PDFs from the combined HTML pages.
2026-10-17 22:06:25 +02:00
Justin M. Keyes d4847da881 docs: all help docs should have a title
Problem:
Autogenerating a title based on the "foo.txt" filename is not great. And
currently, the PDF result has no title because we aren't doing that for
the "print all" codepath.

Solution:
Add a validation check that ensures every doc has a title.
Drop the filename-based autogenerated title.
2026-10-17 22:06:25 +02:00
Justin M. Keyes 0851e7dabe docs(help): specify h4 "function signature" format #42434
Work towards enabling ["flow" layout](https://github.com/neovim/neovim/blob/9244da12e400ec0291450a5e9400d995e7a29b83/src/gen/gen_help_html.lua#L86-L88)
in all `:help` files.

Problem:
The final boss of vimdoc pseudo-structure: Vim's quasi-headings on
function signatures and excmd docs is visually distinctive but too
inconsistent for a nice parse rule.

Solution:
- Define the quasi-heading format as an "h4" variant, specified at
  `:help help-tag-heading`.
  - For simplicity and predictability, it is defined as any line that
    ends with a help tag(s) preceded by 2 or more spaces (or 1 tab).
- Update existing docs.
  - This one-time update has a lot of changes, but is very unlikely to
    conflict with ported Vim-patches because (1) Vim rarely adds new
    *excmds* that we want, and (2) *function* docs are auto-generated.

ref https://github.com/neovim/tree-sitter-vimdoc/issues/110
2026-10-17 11:34:02 -04:00
Justin M. Keyes f8b704e019 feat(defaults): drop TAB from 'termpastefilter' #42433
Problem:
By default, 'termpastefilter' removes TAB chars when pasting to a :terminal.
Very annoying, and does not improve "security".

Solution:
Drop "HT" from the 'termpastefilter' default.
2026-10-17 10:15:28 -04:00
Justin M. Keyes c390fe813a docs: vimdoc specification #42412
Problem:
No "spec" for various :help format (vimdoc) conventions, including the
new "table" support.

Solution:
Introduce `:help help-spec`.
Also, bump the vimdoc parser.
2026-10-16 14:21:00 -04:00
Justin M. Keyes 8ad261b25a Merge #42411 from justinmk/doc3
docs: flow layout
2026-10-16 13:52:45 -04:00
Justin M. Keyes b83273aefd docs: flow layout
Work towards enabling "flow" layout in most `:help` files.

Since [tables are supported in help](https://github.com/neovim/tree-sitter-vimdoc#tables),
it's now possible to render most help files as ["flow" layout](https://github.com/neovim/neovim/blob/9244da12e400ec0291450a5e9400d995e7a29b83/src/gen/gen_help_html.lua#L86-L88)
instead of the legacy fixed-width layout. Besides [improved HTML rendering](https://github.com/neovim/neovim/pull/42381#discussion_r4285705739),
flow layout will allow `:help` presentation-mode to un-hardwrap text
blocks.
2026-10-16 19:02:23 +02:00
Justin M. Keyes d65f9daf45 docs: cleanup invalid help source
Work towards enabling "flow" layout in most `:help` files.

Since [tables are supported in help](https://github.com/neovim/tree-sitter-vimdoc#tables),
it's now possible to render most help files as ["flow" layout](https://github.com/neovim/neovim/blob/9244da12e400ec0291450a5e9400d995e7a29b83/src/gen/gen_help_html.lua#L86-L88)
instead of the legacy fixed-width layout. Besides [improved HTML rendering](https://github.com/neovim/neovim/pull/42381#discussion_r4285705739),
flow layout will allow `:help` presentation-mode to un-hardwrap text
blocks.
2026-10-16 19:02:23 +02:00
Justin M. Keyes ca05480e0d docs: cleanup #42395
Work towards enabling "flow" layout in most `:help` files.

Since [tables are supported in
help](https://github.com/neovim/tree-sitter-vimdoc#tables), it's now possible to
render most help files as ["flow" layout](https://github.com/neovim/neovim/blob/9244da12e400ec0291450a5e9400d995e7a29b83/src/gen/gen_help_html.lua#L86-L88)
instead of the legacy fixed-width layout. Besides [improved HTML rendering](https://github.com/neovim/neovim/pull/42381#discussion_r4285705739),
flow layout will allow `:help` presentation-mode to un-hardwrap text blocks.
https://github.com/neovim/neovim/issues/31825
2026-10-16 10:23:33 -04:00
Justin M. Keyes 45f83c70ee docs: cleanup tables, highlights.scm #42381
highlights.scm: `#match` is slow, prefer `#lua-match`.
2026-10-16 05:49:31 -04:00
Justin M. Keyes 1bfbf41783 Merge #42378 from justinmk/doc3
feat(docs): table rendering, PDF generation
2026-10-15 12:18:36 -04:00
Justin M. Keyes 0912a44c70 feat(docs): generate PDF
Generate PDF using `chrome`.
TODO: we probably don't need the `typst` codepath.
2026-10-15 18:01:31 +02:00
Justin M. Keyes 6e4ab11798 feat(docs): table rendering
Render tables in both `:help` and generated HTML.

Use NBSP (nonbreaking space) char in empty table cells.
2026-10-15 17:43:50 +02:00
Justin M. Keyes 3068a8f652 fix(docs): listitems with codeblocks #42367
Problem:
- `<` before a listitem (`< - foo`) is not concealed.
- Codeblock HTML has extra blank lines.
- Codeblock in a listitem ends the listitem, so HTML docs drop the rest
  of the item and its nesting. Example from `:h nvim_open_win()`:
  ```
        • border: … the double-box style can be specified as: >
          [ "╔", "═" ,"╗", "║", "╝", "═", "╚", "║" ].
    <
          If fewer than eight chars are given, they will be repeated.
        • bufpos: …

    tree-sitter-vimdoc v4.1.0:
      (block
        (line_li (prefix) (line) (codeblock (code (line)))))
      (block
        (line)                                  ; "If fewer…"
        (line_li (prefix) (line) (line)))       ; "bufpos"

    tree-sitter-vimdoc v4.2.0:
      (block
        (line_li (prefix) (line) (codeblock (code (line)))
          (line))                               ; "If fewer…"
        (line_li (prefix) (line) (line)))       ; "bufpos"
  ```

Solution:
- Bump tree-sitter-vimdoc to v4.2.0. https://github.com/neovim/tree-sitter-vimdoc/pull/174
- gen_help_html: get listitem indent from its first line
- gen_help_html: don't double codeblock newlines.
- highlight query: conceal `<` in `line_li`.
- docs: fix doc sources where a blank line was missing after listitems
  followed by non-listitem text.
2026-10-15 07:03:25 -04:00
Justin M. Keyes feeefbcc71 docs: fix invalid vimdoc #42355
Fixes some vimdoc CI errors: https://github.com/neovim/tree-sitter-vimdoc/blob/0c53e5be84a62cee1e583b15febf2b90720e52c9/.github/workflows/main.yml#L41-L47

The other errors are "lone backtick" cases, which will be fixed by https://github.com/neovim/tree-sitter-vimdoc/pull/171
2026-10-14 12:38:51 -04:00
Justin M. Keyes d3b606ef3c docs: fix invalid vimdoc #42353 2026-10-14 11:44:49 -04:00
Justin M. Keyes c4452903e3 fix(api): nvim_buf_call() from expression for window in non-current tabpage #42352
Problem:
- nvim_buf_call() aborts (assert) when called from an expression evaluated
  for a window in another tabpage ('foldexpr', 'statusline'), because
  `curwin` is not in `curtab`.
- After it returns, `curwin` is wrong if it switched windows.
- `vim._with{win=…, keepcwd=true}` does not undo `:lcd` if `win` is in
  another tabpage.

Solution:
- Allow `curwin` outside `curtab`, and restore if it was outside
  `curtab` at `ctx_switch()`.
- Find the target window in any tabpage when restoring its dirs.
2026-10-14 11:43:58 -04:00
Justin M. Keyes 613988b5aa fix(api): nvim_set_option_value() "remove" ignores non-adjacent items #42351
Problem:
`nvim_set_option_value('fo', {'c','r','o'}, {operation='remove'})` does
nothing on "jcqrol": the items are joined into one `:set-=` value, which
only matches a contiguous substring. Removing map keys, e.g. {'eol','space'}
from 'listchars', never matches. `vim.opt:remove{…}` inherits this.

(Regression from f34ee3da80e8; 429338e74b fixed the case where the
flags are adjacent and in order.)

Solution:
Remove each item separately.
2026-10-14 10:47:30 -04:00
Justin M. Keyes 326d764e3e fix(terminal): [Process exited] msg hides last line of full screen #42349
Problem:
If a job exits with cursor at last row, the `[Process exited]` message
covers the text of that row.

Solution:
Start a new line before showing the message, like the old
`\r\n[Process exited]` did.
2026-10-14 07:27:54 -04:00
Justin M. Keyes 12eb3fdfa1 test(version): adapt to vim-patch updates 2026-10-13 13:50:08 +02:00
Justin M. Keyes 13f2bb2903 refactor(cmdatom): add payload in atom_from_frame() #42325
Problem:
Redundant code led to bugs like 2323746bfe

Solution:
Append the payload in atom_from_frame().
2026-10-13 07:12:53 -04:00
Justin M. Keyes e7cd0e4375 fix(ui): ext_messages/ui2: stale 'rulerformat' "[+]" after :write #42315
Problem:
With 'ruler' in the cmdline ('laststatus=0') and ext_messages (ui2), a
'rulerformat' "%m" does not update the "[+]" indicator after ":write",
until a cursor-move.

Analysis:
`redraw_buf_status_later()` only marks windows with a statusline or
winbar. The symptom is hidden by the legacy message grid because the
write message overwrites the cmdline, which redraws the ruler.

Solution:
Also mark the cmdline 'ruler', like `status_redraw_buf()`.
2026-10-12 13:51:18 -04:00
Justin M. Keyes 2323746bfe fix(multicursor): Visual-mode mapping drops getchar() payload #42324
Problem:
A Visual-mode mapping that reads input (`getchar()`) does not include
that input when cascading to other cursors (they only see ESC). OTOH,
Normal-mode mappings do replay the input.

Solution:
Append the payload to the Visual session's Lua-mapping subatom, and to the
session-ending command (`:<C-u>…` mapping).
2026-10-12 13:42:55 -04:00
Justin M. Keyes 13995eb22e fix(multicursor): v:cascading during on_key, insert-preview #42319
Problem:
- `v:cascading` is false during the Insert-mode live-preview.
- `vim.on_key()` sees the internal keys of the Visual-selection preview
  and "zq" steps.

Solution:
- Set `v:cascading` during the Insert-mode preview.
- Don't send internal keys to `on_key` callbacks.
- Use `v:cascading` as the source of truth for `mc_replaying()`.
2026-10-12 13:07:51 -04:00
Justin M. Keyes 092035aed5 fix(multicursor): "zqn" places no cursors after "/\%V" search #42313
Problem:
After `/\%Vfoo<CR>`, `zqn` places no cursors. `mc_zq_target()` clears
`'< '>` before `gn` (to detect a failed match), so `\%V` never matches.

Solution:
Keep the user's `'< '>` during `gn`; detect failure by its beep.
2026-10-12 08:40:48 -04:00
Justin M. Keyes 4bfbad6e91 fix(multicursor): insert-mode Lua/API edit dropped on ESC #42312
Problem:
With multicursors, an Insert-mode edit by a Lua/`<Cmd>` mapping or API
(nvim_put(), …) is live-previewed at the other cursors, but dropped at
ESC: the cascade replays _redo_, which does not contain such edits.

Solution:
When `<Cmd>`/Lua/RPC changes the buffer during Insert-mode, sync the
previews and restart the capture (like a non-captured cursor-move). The
edit stays as text.

- The mapping runs only at the primary. Its text is mirrored as-is
  (similar to insert-completion). Re-running it per cursor would require
  a "partial-remap" span replay, and breaks single-session plugins
  (vim.snippet).
- Not during completion; redo already captures the pre-inserted
  candidate on accept.
- Text typed before the edit is kept as mirrored, not replayed, so
  per-cursor effects ('textwidth' wrapping, …) don't apply to it.

Known issues:
- "o"/"O"/"cc" with 'autoindent' followed by the mapping: the per-cursor
  insert-entry replay is still pending, so the edit is lost.
2026-10-12 07:40:50 -04:00
Justin M. Keyes 0f17fab619 fix(diagnostic): "Failed to delete autocmd" after noautocmd bwipeout #42303
Problem:
Handlers defer `show()` on unloaded buffers with a buffer-local BufRead
autocmd whose id is stored per-namespace:
- `noautocmd bwipeout` deletes the autocmd, so the next show/hide fails.
- Showing another buffer in the same namespace cancels the pending show.

Solution:
Handlers skip unloaded buffers. The existing global BufRead autocmd
shows cached diagnostics when the buffer loads.
2026-10-12 05:44:45 -04:00
Justin M. Keyes 7e1ae5115f Merge #42291 from justinmk/fixmisc2
feat(startup): --embed --listen waits for UI if stdin is not a pipe
2026-10-09 12:44:28 -04:00
Justin M. Keyes 0e17a1a0e2 fix(windows): server dies with terminal after :restart
Problem:
On Windows, a server started by :restart attaches to the TUI's console.
Closing the host-terminal kills the server, even after :detach or
:detach!: the swap to a hidden console only happens when a stdio UI
disconnects, but after :restart the TUI is on a socket.

Solution:
Track whether the console is inherited from the parent. When the last UI
disconnects and the server keeps running, swap an inherited console for
a hidden one.
2026-10-09 18:11:09 +02:00
Justin M. Keyes d212676f90 feat(startup): --embed --listen waits for UI if stdin is not a pipe
Problem:
`nvim --embed --listen addr` always treats stdin as an RPC channel. If
stdin is /dev/null (e.g. backgrounded over ssh), the channel closes
before a UI can attach, so Nvim exits (detach=false behavior). If stdin
is a tty, keystrokes are read as RPC.

Solution:
With `--embed --listen`, open the stdio RPC channel only if stdin is
a pipe or socket. Else wait for a UI at the `--listen` address.

    $ nvim --embed --listen ./nvim.sock
    Nvim server (pid: 71984) waiting for UI, listening on: "./nvim.sock"

Note:
- No change when stdin is a pipe. So the test harness, TUI, and :restart
  all behave exactly as before.
- The #29634 use-case of configuring a UI that attaches *after* startup
  (a second UI, or reattach) still requires #25377 (reliable UIEnter).
  Not affected by this change.
2026-10-09 16:57:44 +02:00
Justin M. Keyes f5e839bdec Merge #42288 from justinmk/fixmisc2
feat(api): nvim_chan_set()
2026-10-09 07:41:57 -04:00
Justin M. Keyes e867705116 refactor(ui): remove "stop" UI event
Problem:
"stop" has been a no-op since the TUI moved out-of-process. Remote UIs
only see EOF, and nothing implements it.

Solution:
Remove it.
2026-10-09 13:21:19 +02:00
Justin M. Keyes d2d8188851 feat(api): nvim_chan_set()
Problem:
Only the stdio channel can "own" a server (self-exit on close). After
:restart the TUI reconnects over a socket, so the restarted server does
not have "self-exit" behavior when the channel closes (e.g. by UI
disconnect, host-terminal close, etc.), which leaves a dangling server.

Solution:
- Introduce `nvim_chan_set`. (`nvim_set_client_info` doesn't fit:
  exposing it to Lua would require a `channel` param, which is not
  backwards-compatible, plus it's awkward because it doesn't merge info.)
- Broadcast per-channel properties in the "restart" UI event as an
  opaque dict (a potentially useful pattern for other UI-events in the
  future).
  - UIs are expected to restore the channel properties on restart.
- `nvim_get_chan_info()` reports "detach".
- Remove `nvim__chan_set_detach()`.
2026-10-09 13:16:00 +02:00
Justin M. Keyes 501dda72aa fix(terminal): nvim_open_term crash in zero-size window #42248
Problem:
nvim_open_term() sizes the terminal from the current window, which may
have 0 rows (bordered float, 'cmdheight' taking all rows). libvterm
aborts when resizing from 0 rows.

Solution:
In `terminal_alloc()`, treat zero width/height as the pty default
(80x24), like jobstart(term=true).

Open question:
nvim_win_get_height() on such a float returns -1 (winheight() returns 0).
2026-10-06 07:41:00 -04:00
Justin M. Keyes 299ca3c81e fix(fileio): coverity CHECKED_RETURN #42247
CID 657024:         Error handling issues  (CHECKED_RETURN)
    /src/nvim/fileio.c: 2720             in rename_with_tmp()
    >>>     Calling "os_rename" without checking return value (as is done elsewhere 4 out of 5 times).
    2720             os_rename(tempname, from);

If moving the file back fails, log where it is (the temp name).
2026-10-05 14:42:39 -04:00
Justin M. Keyes 6745cdf94d Merge #42241 from justinmk/fixenv
feat(env)!: 'isident' does not affect $NAME parsing
2026-10-05 13:29:48 -04:00
Justin M. Keyes 37a2296d42 fix(options): default of expanded option out of sync at startup
Problem:
set_options_default() expands `$VAR` in the value but not the stored
default. set_init_expand_env() then expands the value (already
expanded), sees no change, and leaves the default unexpanded, so `:set`
lists 'helpfile' as changed.

This was accidentally hidden until 211623c1388d because env var depended
on 'isident', which was not initialized at set_options_default() time.

Solution:
set_init_expand_env() expands the default, and sets both from it.
2026-10-05 18:57:28 +02:00
Justin M. Keyes 96bbe82ed8 feat(env)!: 'isident' does not affect $NAME parsing
Problem:
'isident' decides how env var names are parsed, which is more
"confusing" than "useful":
- Changing 'isident' (for 'define' or "\i") can break `$NAME` expansion.
- Its default byte ranges let multibyte names work on Windows (`$aあ`)
  but not Unix (`$é`).

Solution:
Ignore 'isident' when parsing env var names.
2026-10-05 18:57:28 +02:00