-- 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