Files
neovim/runtime/lua/vim/_core/server.lua
T
Justin M. Keyes 28ba068372 feat(:restart): v:starttime, v:exitreason #39282
Problem:
- The `ZR` feature makes it more obvious that we need some sort of flag so that
  an `ExitPre` / `QuitPre` / `VimLeave` handler can handle restarts differently
  than a normal exit. For example, it's common that users want `:mksession` on
  restart, but perhaps not on a normal exit.
- Nvim has no way to report its "uptime".

Solution:
- Introduce `v:starttime`
- Introduce `v:exitreason`
2026-04-22 13:40:41 -04:00

105 lines
3.5 KiB
Lua

-- For "--listen" and related functionality.
local M = {}
--- Called by builtin serverlist(). Returns the combined server list (own + peers).
---
--- @param opts? table Options:
--- - opts.peer is true, also discover peer servers.
--- @param addrs string[] Internal ("own") addresses, from `server_address_list`.
--- @return string[] # Combined list of servers (own + peers).
function M.serverlist(opts, addrs)
if type(opts) ~= 'table' or not opts.peer then
return addrs
end
-- Discover peer servers in stdpath("run").
-- TODO: track TCP servers, somehow.
-- TODO: support Windows named pipes.
local root = vim.fs.normalize(vim.fn.stdpath('run') .. '/..')
local socket_paths = vim.fs.find(function(name, _)
return name:match('nvim.*')
end, { path = root, type = 'socket', limit = math.huge })
for _, socket in ipairs(socket_paths) do
if not vim.list_contains(addrs, socket) then
local ok, chan = pcall(vim.fn.sockconnect, 'pipe', socket, { rpc = true })
if ok and chan then
-- Check that the server is responding
-- TODO: do we need a timeout or error handling here?
if vim.fn.rpcrequest(chan, 'nvim_get_chan_info', 0).id then
table.insert(addrs, socket)
end
vim.fn.chanclose(chan)
end
end
end
return addrs
end
-- (Windows only) Canonical --listen address persisted across restarts.
M.restart_canonical_addr = nil ---@type string?
--- (Windows only)
--- Called on the new server via nvim_exec_lua RPC from the old server (:restart).
--- Windows named pipes can't be rebound immediately, so the new server starts on a
--- temporary bootstrap address and polls until the canonical address is reclaimable.
--- @param canonical_addr string The original --listen address to reclaim.
--- @param expected_uis integer Number of UIs expected to reattach (0 = don't wait).
function M.rebind_after_restart(canonical_addr, expected_uis)
M.restart_canonical_addr = canonical_addr
local bootstrap_addr = vim.v.servername -- Temporary autogenerated address.
local poll_ms = 50
local max_wait_ms = 30000
local timer = assert(vim.uv.new_timer())
-- Poll until the canonical address can be reclaimed (or timeout).
local poll_elapsed = 0
timer:start(poll_ms, poll_ms, function()
vim.schedule(function()
if timer:is_closing() then
return
end
poll_elapsed = poll_elapsed + poll_ms
if poll_elapsed >= max_wait_ms then
timer:stop()
timer:close()
return
end
if not vim.list_contains(vim.fn.serverlist(), canonical_addr) then
local ok = vim._with({ log_level = 5 }, function()
return pcall(vim.fn.serverstart, canonical_addr)
end)
if not ok then
return -- pipe still held by old server; retry next tick
end
end
-- Wait for UIs to reattach, then retire the bootstrap address.
local elapsed = 0
timer:stop()
timer:start(poll_ms, poll_ms, function()
vim.schedule(function()
if timer:is_closing() then
return
end
elapsed = elapsed + poll_ms
local all_uis = expected_uis <= 0 or #vim.api.nvim_list_uis() >= expected_uis
if all_uis or elapsed >= max_wait_ms then
if canonical_addr ~= bootstrap_addr then
vim._with({ log_level = 5 }, function()
pcall(vim.fn.serverstop, bootstrap_addr)
end)
end
timer:stop()
timer:close()
end
end)
end)
end)
end)
end
return M