diff --git a/runtime/doc/vimfn.txt b/runtime/doc/vimfn.txt index 6752a70628..80030d0466 100644 --- 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 new file mode 100644 index 0000000000..281a5705ea --- /dev/null +++ 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 index 76bc5e8410..ff800d7d87 100644 --- 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 index 8619f54935..aba1f9aa9a 100644 --- 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 index f72148d0ba..7fe2789ae4 100644 --- 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 index 0ec11169e9..81ec6c75d9 100644 --- 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)