From: Szymon Wilczek <[email protected]>
Date: 25 Apr 2026
Subject: feat(server): return detailed info from serverlist({info=true})
Pull request: #39392
Merged: 6 May 2026, by Justin M. Keyes
signed, reported, fixes
- Author Szymon Wilczek author
- Merged-by Justin M. Keyes maintainer · merged it
diff
@@ -225,6 +225,9 @@ VIMSCRIPT • |v:exitreason| is set before |QuitPre|. • |v:starttime| is the process start time (nanoseconds since UNIX epoch).+• |serverlist()| accepts a new `info` option that returns a list of dicts+ with `addr`, `pid` and `own` for each server (own + peers),+ enabling pickers like the future `:connect` host selector. ============================================================================== CHANGED FEATURES *news-changed* @@ -9139,15 +9139,27 @@ serverlist([{opts}]) *serverlist()* will also be returned. (default: |FALSE|) Not supported on Windows yet. + info : If |TRUE|, return a list of Dicts with detailed info+ instead of addresses. Implies `peer=true`. Each Dict+ has the following items:+ addr (string) Server address.+ pid (number?) PID of the Nvim process owning the+ server, or |v:null| if the peer is+ unreachable.+ own (bool) Whether this server belongs to the+ current Nvim instance.+ (default: |FALSE|)+ Example: >vim echo serverlist()+ echo serverlist(#{info: v:true}) < Parameters: ~ • {opts} (`table?`) Return: ~- (`string[]`)+ (`string[]|vim.ServerInfo[]`) serverstart([{address}]) *serverstart()* Opens a socket or named pipe at {address} and listens for @@ -4,15 +4,35 @@ local M = {} --- Called by builtin serverlist(). Returns the combined server list (own + peers). ---+---@class vim.ServerInfo+---@field addr string Server address (socket path, named pipe, or TCP host:port).+---@field pid integer? PID of the Nvim process owning this server (nil if unreachable).+---@field own boolean True if this server belongs to the current Nvim instance.+ --- @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).+--- - opts.peer (boolean): If true, also discover peer servers.+--- - opts.info (boolean): If true, return a list of |vim.ServerInfo| dicts+--- instead of a list of addresses. Implies peer=true.+--- @param addrs string[] Internal ("own") addresses, from server_address_list.+--- @return string[]|vim.ServerInfo[] function M.serverlist(opts, addrs)- if type(opts) ~= 'table' or not opts.peer then+ if type(opts) ~= 'table' then return addrs end + local peer = opts.peer == true+ local want_info = opts.info == true+ local discover = peer or want_info++ if not discover then+ return addrs+ end++ local own_set = {} ---@type table<string, true>+ for _, a in ipairs(addrs) do+ own_set[a] = true+ end+ -- Discover peer servers in stdpath("run"). -- TODO: track TCP servers, somehow. -- TODO: support Windows named pipes.@@ -21,21 +41,44 @@ function M.serverlist(opts, addrs) return name:match('nvim.*') end, { path = root, type = 'socket', limit = math.huge }) + local peer_pids = {} ---@type table<string, integer>+ for _, socket in ipairs(socket_paths) do- if not vim.list_contains(addrs, socket) then+ if not own_set[socket] and not vim.list_contains(addrs, socket) then local ok, chan = pcall(vim.fn.sockconnect, 'pipe', socket, { rpc = true })- if ok and chan then+ if ok and chan and chan > 0 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+ local ok_chan, chan_info = pcall(vim.fn.rpcrequest, chan, 'nvim_get_chan_info', 0)+ if ok_chan and type(chan_info) == 'table' and chan_info.id then table.insert(addrs, socket)+ if want_info then+ local ok_pid, pid = pcall(vim.fn.rpcrequest, chan, 'nvim_eval', 'getpid()')+ if ok_pid and type(pid) == 'number' then+ peer_pids[socket] = pid+ end+ end end- vim.fn.chanclose(chan)+ pcall(vim.fn.chanclose, chan) end end end - return addrs+ if not want_info then+ return addrs+ end++ local self_pid = vim.fn.getpid()+ local result = {} ---@type vim.ServerInfo[]+ for _, addr in ipairs(addrs) do+ local own = own_set[addr] == true+ table.insert(result, {+ addr = addr,+ pid = own and self_pid or peer_pids[addr],+ own = own,+ })+ end+ return result end -- (Windows only) Canonical --listen address persisted across restarts. @@ -8289,12 +8289,24 @@ function vim.fn.searchpos(pattern, flags, stopline, timeout, skip) end --- will also be returned. (default: |FALSE|) --- Not supported on Windows yet. ---+--- info : If |TRUE|, return a list of Dicts with detailed info+--- instead of addresses. Implies `peer=true`. Each Dict+--- has the following items:+--- addr (string) Server address.+--- pid (number?) PID of the Nvim process owning the+--- server, or |v:null| if the peer is+--- unreachable.+--- own (bool) Whether this server belongs to the+--- current Nvim instance.+--- (default: |FALSE|)+--- --- Example: >vim --- echo serverlist()+--- echo serverlist(#{info: v:true}) --- < --- --- @param opts? table---- @return string[]+--- @return string[]|vim.ServerInfo[] function vim.fn.serverlist(opts) end --- Opens a socket or named pipe at {address} and listens for @@ -9971,13 +9971,25 @@ M.funcs = { will also be returned. (default: |FALSE|) Not supported on Windows yet. + info : If |TRUE|, return a list of Dicts with detailed info+ instead of addresses. Implies `peer=true`. Each Dict+ has the following items:+ addr (string) Server address.+ pid (number?) PID of the Nvim process owning the+ server, or |v:null| if the peer is+ unreachable.+ own (bool) Whether this server belongs to the+ current Nvim instance.+ (default: |FALSE|)+ Example: >vim echo serverlist()+ echo serverlist(#{info: v:true}) < ]=], name = 'serverlist', params = { { 'opts', 'table' } },- returns = 'string[]',+ returns = 'string[]|vim.ServerInfo[]', signature = 'serverlist([{opts}])', }, serverstart = { @@ -216,6 +216,37 @@ describe('server', function() eq(true, vim.list_contains(new_servs, peer_addr)) eq(true, #servers_without_peer < #new_servs) eq(true, old_servs_num < #new_servs)++ -- serverlist({info=true}) returns rich info dicts (implies peer=true)+ local info_servs = fn.serverlist({ info = true })+ eq(#new_servs, #info_servs)++ local own_addr = fn.serverlist()[1]+ local self_pid = fn.getpid()+ ---@type table<string, table>+ local by_addr = {}+ for _, entry in ipairs(info_servs) do+ eq('string', type(entry.addr))+ eq('boolean', type(entry.own))+ by_addr[entry.addr] = entry+ end++ -- own server: own=true, pid matches+ local own_entry = by_addr[own_addr]+ eq(true, own_entry ~= nil)+ eq(true, own_entry.own)+ eq(self_pid, own_entry.pid)++ -- peer server: own=false, pid is from peer process+ local peer_entry = by_addr[peer_addr]+ eq(true, peer_entry ~= nil)+ eq(false, peer_entry.own)+ eq('number', type(peer_entry.pid))+ n.set_session(client)+ local peer_pid = fn.getpid()+ n.set_session(current_server)+ eq(peer_pid, peer_entry.pid)+ client:close() end) discussion · on the pull request
v1 · 8 messages
szymonwilczek author 25 Apr 2026
feat(server): return detailed info from serverlist({info=true})
szymonwilczek author 25 Apr 2026
Looks like test flaked on
TUI :restart. I think I don't have permission (or at least do not see the re-run CI jobs button) from my fork, could someone give it a kick?
Edit: Not longer needed, I had to update the docs neverthelessszymonwilczek author 25 Apr 2026
@justinmk CI is fully green.
serverlist({info=v:true}) implementation is complete, I've covered both local sockets and cross-process PID retrieval, along with updated docs & tests.
Whenever you have a cycle, let me know if this looks solid to you. Once this gets the green light I'll rebase the remote-ssh pr onto it and wire new dicts into the ui picker.justinmk maintainer 26 Apr 2026
---@field own boolean True if this server belongs to the current Nvim instance.justinmk maintainer 26 Apr 2026
---@field addr string Server address (socket path, named pipe, or TCP host:port).szymonwilczek author 29 Apr 2026
I've rebased on
master since new news.txt caused conflicts. I've also updated the message to reflect the actual implementation (address -> addr, is_current -> own).
Edit: Regarding the Windows CI failure: it threw exit code and a bunch of EOFs from provider tests. I don't know much about Windows in development (not using), but this looks like a broken Windows runner or missing DLLs rather than an issue with Lua code. Could anyone confirm?szymonwilczek author 6 May 2026
@justinmk Windows functional CI timed out (55m, unrelated provider/DLL failures as before). All other checks are green. Could you confirm if the Windows failure is some kind of known issue so I can re-request review?
justinmk maintainer 6 May 2026
Let's pull the trigger on this, can continue iterating. Thanks!