commit 5151f635ca299dcb7aeb7e34d7c01fba07108b88
parent d08b265111548a40924acdbc2dcb2e4cbd901188
Author: Siddhant Agarwal <68201519+siddhantdev@users.noreply.github.com>
Date: Mon, 28 Jul 2025 10:10:04 +0530
feat: serverlist({peer=true}) returns peer addresses #34806
Problem:
serverlist() only lists servers that were started by the current Nvim.
Solution:
Look for other Nvim servers in stdpath("run").
Diffstat:
6 files changed, 112 insertions(+), 5 deletions(-)
diff --git a/runtime/doc/vimfn.txt b/runtime/doc/vimfn.txt
@@ -8736,13 +8736,23 @@ searchpos({pattern} [, {flags} [, {stopline} [, {timeout} [, {skip}]]]])
Return: ~
(`any`)
-serverlist() *serverlist()*
+serverlist([{opts}]) *serverlist()*
Returns a list of server addresses, or empty if all servers
were stopped. |serverstart()| |serverstop()|
+
+ The optional argument {opts} is a Dict and supports the following items:
+
+ peer : If |TRUE|, servers not started by |serverstart()|
+ will also be returned. (default: |FALSE|)
+ Not supported on Windows yet.
+
Example: >vim
echo serverlist()
<
+ Parameters: ~
+ • {opts} (`table?`)
+
Return: ~
(`string[]`)
diff --git a/runtime/lua/vim/_core/server.lua b/runtime/lua/vim/_core/server.lua
@@ -0,0 +1,33 @@
+local M = {}
+
+--- Called by builtin serverlist(). Returns all running servers.
+--- in stdpath("run"). Does not include named pipes or TCP servers.
+---
+--- @param listed string[] Already listed servers
+--- @return string[] A list of currently running servers in stdpath("run")
+function M.serverlist(listed)
+ -- TODO: also get named pipes on Windows
+ local socket_paths = vim.fs.find(function(name, _)
+ return name:match('nvim.*')
+ end, { path = vim.fn.stdpath('run'), type = 'socket', limit = math.huge })
+
+ local running_sockets = {}
+ for _, socket in ipairs(socket_paths) do
+ -- Don't list servers twice
+ if not vim.list_contains(listed, 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(running_sockets, socket)
+ end
+ vim.fn.chanclose(chan)
+ end
+ end
+ end
+
+ return running_sockets
+end
+
+return M
diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua
@@ -7964,12 +7964,20 @@ function vim.fn.searchpos(pattern, flags, stopline, timeout, skip) end
--- Returns a list of server addresses, or empty if all servers
--- were stopped. |serverstart()| |serverstop()|
+---
+--- The optional argument {opts} is a Dict and supports the following items:
+---
+--- peer : If |TRUE|, servers not started by |serverstart()|
+--- will also be returned. (default: |FALSE|)
+--- Not supported on Windows yet.
+---
--- Example: >vim
--- echo serverlist()
--- <
---
+--- @param opts? table
--- @return string[]
-function vim.fn.serverlist() end
+function vim.fn.serverlist(opts) end
--- Opens a socket or named pipe at {address} and listens for
--- |RPC| messages. Clients can send |API| commands to the
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
@@ -9655,17 +9655,25 @@ M.funcs = {
signature = 'searchpos({pattern} [, {flags} [, {stopline} [, {timeout} [, {skip}]]]])',
},
serverlist = {
+ args = { 0, 1 },
desc = [=[
Returns a list of server addresses, or empty if all servers
were stopped. |serverstart()| |serverstop()|
+
+ The optional argument {opts} is a Dict and supports the following items:
+
+ peer : If |TRUE|, servers not started by |serverstart()|
+ will also be returned. (default: |FALSE|)
+ Not supported on Windows yet.
+
Example: >vim
echo serverlist()
<
]=],
name = 'serverlist',
- params = {},
+ params = { { 'opts', 'table' } },
returns = 'string[]',
- signature = 'serverlist()',
+ signature = 'serverlist([{opts}])',
},
serverstart = {
args = { 0, 1 },
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
@@ -6724,12 +6724,42 @@ static void f_serverlist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
size_t n;
char **addrs = server_address_list(&n);
+ Arena arena = ARENA_EMPTY;
+ // Passed to vim._core.server.serverlist() to avoid duplicates
+ Array addrs_arr = arena_array(&arena, n);
+
// Copy addrs into a linked list.
list_T *const l = tv_list_alloc_ret(rettv, (ptrdiff_t)n);
for (size_t i = 0; i < n; i++) {
tv_list_append_allocated_string(l, addrs[i]);
+ ADD_C(addrs_arr, CSTR_AS_OBJ(addrs[i]));
+ }
+
+ if (!(argvars[0].v_type == VAR_DICT && tv_dict_get_bool(argvars[0].vval.v_dict, "peer", false))) {
+ goto cleanup;
}
+
+ MAXSIZE_TEMP_ARRAY(args, 1);
+ ADD_C(args, ARRAY_OBJ(addrs_arr));
+
+ Error err = ERROR_INIT;
+ Object rv = NLUA_EXEC_STATIC("return require('vim._core.server').serverlist(...)",
+ args, kRetObject,
+ &arena, &err);
+
+ if (ERROR_SET(&err)) {
+ ELOG("vim._core.serverlist failed: %s", err.msg);
+ goto cleanup;
+ }
+
+ for (size_t i = 0; i < rv.data.array.size; i++) {
+ char *curr_server = rv.data.array.items[i].data.string.data;
+ tv_list_append_string(l, curr_server, -1);
+ }
+
+cleanup:
xfree(addrs);
+ arena_mem_free(arena_finish(&arena));
}
/// "serverstart()" function
diff --git a/test/functional/core/server_spec.lua b/test/functional/core/server_spec.lua
@@ -162,7 +162,7 @@ describe('server', function()
end)
it('serverlist() returns the list of servers', function()
- clear()
+ local current_server = clear()
-- There should already be at least one server.
local _n = eval('len(serverlist())')
@@ -186,6 +186,24 @@ describe('server', function()
end
-- After serverstop() the servers should NOT be in the list.
eq(_n, eval('len(serverlist())'))
+
+ -- serverlist({ peer = true }) returns servers from other Nvim sessions.
+ if t.is_os('win') then
+ return
+ end
+ local client_address = n.new_pipename()
+ local client = n.new_session(
+ true,
+ { args = { '-u', 'NONE', '--listen', client_address, '--embed' }, merge = false }
+ )
+ n.set_session(client)
+ eq(client_address, fn.serverlist()[1])
+
+ n.set_session(current_server)
+
+ new_servs = fn.serverlist({ peer = true })
+ eq(true, vim.list_contains(new_servs, client_address))
+ client:close()
end)
end)