Commit: patch 9.2.0721: serverlist() returns strings separated by \n

0 views
Skip to first unread message

Christian Brabandt

unread,
3:00 PM (2 hours ago) 3:00 PM
to vim...@googlegroups.com
patch 9.2.0721: serverlist() returns strings separated by


Commit: https://github.com/vim/vim/commit/37d9805675abc6c75fb44fabd510df5313f486b0
Author: Foxe Chen <chen...@gmail.com>
Date: Wed Jun 24 18:40:50 2026 +0000

patch 9.2.0721: serverlist() returns strings separated by


Problem: serverlist() returns strings separated by

(Christian J. Robinson)
Solution: Return a list of server names when given the
option dict argument (Foxe Chen).

fixes: #20582
closes: #20601

Signed-off-by: Foxe Chen <chen...@gmail.com>
Signed-off-by: Christian Brabandt <c...@256bit.org>

diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt
index d362273f4..47d320a73 100644
--- a/runtime/doc/builtin.txt
+++ b/runtime/doc/builtin.txt
@@ -1,4 +1,4 @@
-*builtin.txt* For Vim version 9.2. Last change: 2026 Jun 17
+*builtin.txt* For Vim version 9.2. Last change: 2026 Jun 24


VIM REFERENCE MANUAL by Bram Moolenaar
@@ -9865,15 +9865,25 @@ server2client({clientid}, {string}) *server2client()*
Return type: |Number|


-serverlist() *serverlist()*
+serverlist([{dict}]) *serverlist()*
Return a list of available server names, one per line.
When there are no servers or the information is not available
- an empty string is returned. See also |clientserver|.
+ an empty string is returned.
{only available when compiled with the |+clientserver| feature}
+
+ If {dict} is given, then it is a |Dictionary| supporting the
+ following options:
+ key type meaning ~
+ list |Boolean| Return a list of strings,
+ where each string is a server
+ name.
+
+ See also |clientserver|.
+
Example: >
:echo serverlist()
<
- Return type: |String|
+ Return type: |String| or list<string>


setbufline({buf}, {lnum}, {text}) *setbufline()*
diff --git a/runtime/doc/version9.txt b/runtime/doc/version9.txt
index a515589de..ecde25876 100644
--- a/runtime/doc/version9.txt
+++ b/runtime/doc/version9.txt
@@ -52688,6 +52688,7 @@ Changed ~
- During |complete()|-triggered completion, CTRL-N and CTRL-P are now subject
to insert-mode mappings.
- It is possible to clear the alternate file register |quote#|.
+- |serverlist()| can return a list of all available server names.


*added-9.3*
diff --git a/src/clientserver.c b/src/clientserver.c
index 60849fe1e..13f3fafcb 100644
--- a/src/clientserver.c
+++ b/src/clientserver.c
@@ -598,24 +598,33 @@ cmdsrv_main(
}
else if (STRICMP(argv[i], "--serverlist") == 0)
{
+ list_T *list = NULL;
+ garray_T ga;
+
# ifdef MSWIN
if (clientserver_method == CLIENTSERVER_METHOD_MSWIN)
// Win32 always works?
- res = serverGetVimNames();
+ list = serverGetVimNames();
# endif
# ifdef FEAT_SOCKETSERVER
if (clientserver_method == CLIENTSERVER_METHOD_SOCKET)
-# ifdef MSWIN
- res = vim_strsave((char_u *)"");
-# else
- res = socketserver_list();
-# endif
+ list = socketserver_list();
# endif
# ifdef FEAT_X11
if (clientserver_method == CLIENTSERVER_METHOD_X11 &&
xterm_dpy != NULL)
- res = serverGetVimNames(xterm_dpy);
+ list = serverGetVimNames(xterm_dpy);
# endif
+
+ ga_init2(&ga, 1, 80);
+ if (list != NULL)
+ {
+ list_join(&ga, list, (char_u *)"
", TRUE, FALSE, 0);
+ ga_append(&ga, NUL);
+ list_free(list);
+ }
+ res = ga.ga_data;
+
if (did_emsg)
mch_errmsg("
");
}
@@ -1250,32 +1259,59 @@ f_server2client(typval_T *argvars UNUSED, typval_T *rettv)
void
f_serverlist(typval_T *argvars UNUSED, typval_T *rettv)
{
- char_u *r = NULL;
+ list_T *list = NULL;
+ bool use_list = false;
+
+ if (check_for_opt_dict_arg(argvars, 0) == FAIL)
+ return;
+
+ if (argvars[0].v_type != VAR_UNKNOWN)
+ {
+ dict_T *d = argvars[0].vval.v_dict;
+
+ use_list = dict_get_bool(d, "list", false);
+ }

# ifdef FEAT_CLIENTSERVER
# ifdef MSWIN
if (clientserver_method == CLIENTSERVER_METHOD_MSWIN)
- r = serverGetVimNames();
+ list = serverGetVimNames();
# endif
# ifdef FEAT_SOCKETSERVER
if (clientserver_method == CLIENTSERVER_METHOD_SOCKET)
-# ifdef MSWIN
- r = vim_strsave((char_u *)"");
-# else
- r = socketserver_list();
-# endif
+ list = socketserver_list();
# endif
# ifdef FEAT_X11
if (clientserver_method == CLIENTSERVER_METHOD_X11)
{
- make_connection();
- if (X_DISPLAY != NULL)
- r = serverGetVimNames(X_DISPLAY);
+ make_connection();
+ if (X_DISPLAY != NULL)
+ list = serverGetVimNames(X_DISPLAY);
}
# endif
# endif
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = r;
+ if (use_list && list != NULL)
+ {
+ list->lv_refcount++;
+ rettv->v_type = VAR_LIST;
+ rettv->vval.v_list = list;
+ }
+ else
+ {
+ garray_T ga;
+
+ ga_init2(&ga, 1, 80);
+
+ if (list != NULL)
+ {
+ list_join(&ga, list, (char_u *)"
", TRUE, FALSE, 0);
+ ga_append(&ga, NUL);
+ list_free(list);
+ }
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = (char_u *)ga.ga_data;
+ }
}
#endif

diff --git a/src/evalfunc.c b/src/evalfunc.c
index 45002c5bf..3aeda7e11 100644
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -2815,8 +2815,8 @@ static const funcentry_T global_functions[] =
ret_list_number, f_searchpos},
{"server2client", 2, 2, FEARG_1, arg2_string,
ret_number_bool, f_server2client},
- {"serverlist", 0, 0, 0, NULL,
- ret_string, f_serverlist},
+ {"serverlist", 0, 1, 0, arg1_dict_any,
+ ret_any, f_serverlist},
{"setbufline", 3, 3, FEARG_3, arg3_setbufline,
ret_number_bool, f_setbufline},
{"setbufvar", 3, 3, FEARG_3, arg3_buffer_string_any,
diff --git a/src/if_xcmdsrv.c b/src/if_xcmdsrv.c
index 43e1e3407..22423a2ed 100644
--- a/src/if_xcmdsrv.c
+++ b/src/if_xcmdsrv.c
@@ -629,9 +629,9 @@ ServerWait(
* Fetch a list of all the Vim instance names currently registered for the
* display.
*
- * Returns a newline separated list in allocated memory or NULL.
+ * Returns a list of strings or NULL on failure.
*/
- char_u *
+ list_T *
serverGetVimNames(Display *dpy)
{
char_u *regProp;
@@ -639,7 +639,7 @@ serverGetVimNames(Display *dpy)
char_u *p;
long_u numItems;
int_u w;
- garray_T ga;
+ list_T *list;

if (registryProperty == None)
{
@@ -647,6 +647,10 @@ serverGetVimNames(Display *dpy)
return NULL;
}

+ list = list_alloc();
+ if (list == NULL)
+ return NULL;
+
/*
* Read the registry property.
*/
@@ -656,7 +660,6 @@ serverGetVimNames(Display *dpy)
/*
* Scan all of the names out of the property.
*/
- ga_init2(&ga, 1, 100);
for (p = regProp; (long_u)(p - regProp) < numItems; p++)
{
entry = p;
@@ -667,18 +670,14 @@ serverGetVimNames(Display *dpy)
w = None;
sscanf((char *)entry, "%x", &w);
if (WindowValid(dpy, (Window)w))
- {
- ga_concat(&ga, p + 1);
- GA_CONCAT_LITERAL(&ga, "
");
- }
+ list_append_string(list, p + 1, -1);
while (*p != 0)
p++;
}
}
if (regProp != empty_prop)
XFree(regProp);
- ga_append(&ga, NUL);
- return ga.ga_data;
+ return list;
}

/////////////////////////////////////////////////////////////
diff --git a/src/os_mswin.c b/src/os_mswin.c
index edaa03d4f..6831171cf 100644
--- a/src/os_mswin.c
+++ b/src/os_mswin.c
@@ -2256,16 +2256,14 @@ enumWindowsGetServer(HWND hwnd, LPARAM lparam)
static BOOL CALLBACK
enumWindowsGetNames(HWND hwnd, LPARAM lparam)
{
- garray_T *ga = (garray_T *)lparam;
+ list_T *list = (list_T *)lparam;
char server[MAX_PATH];

// Get the title of the window
if (getVimServerName(hwnd, server, sizeof(server)) == 0)
return TRUE;

- // Add the name to the list
- ga_concat(ga, (char_u *)server);
- GA_CONCAT_LITERAL(ga, "
");
+ list_append_string(list, (char_u *)server, -1);
return TRUE;
}

@@ -2371,17 +2369,17 @@ serverSetName(char_u *name)
}
}

- char_u *
+ list_T *
serverGetVimNames(void)
{
- garray_T ga;
+ list_T *list = list_alloc();

- ga_init2(&ga, 1, 100);
+ if (list == NULL)
+ return NULL;

- enum_windows(enumWindowsGetNames, (LPARAM)(&ga));
- ga_append(&ga, NUL);
+ enum_windows(enumWindowsGetNames, (LPARAM)list);

- return ga.ga_data;
+ return list;
}

int
diff --git a/src/proto/if_xcmdsrv.pro b/src/proto/if_xcmdsrv.pro
index 74245b75a..d8ce00259 100644
--- a/src/proto/if_xcmdsrv.pro
+++ b/src/proto/if_xcmdsrv.pro
@@ -2,7 +2,7 @@
int serverRegisterName(Display *dpy, char_u *name);
void serverChangeRegisteredWindow(Display *dpy, Window newwin);
int serverSendToVim(Display *dpy, char_u *name, char_u *cmd, char_u **result, Window *server, Bool asExpr, int timeout, Bool localLoop, int silent);
-char_u *serverGetVimNames(Display *dpy);
+list_T *serverGetVimNames(Display *dpy);
Window serverStrToWin(char_u *str);
int serverSendReply(char_u *name, char_u *str);
int serverReadReply(Display *dpy, Window win, char_u **str, int localLoop, int timeout);
diff --git a/src/proto/os_mswin.pro b/src/proto/os_mswin.pro
index bcb789f51..32b4aff00 100644
--- a/src/proto/os_mswin.pro
+++ b/src/proto/os_mswin.pro
@@ -48,7 +48,7 @@ char_u *mch_resolve_path(char_u *fname, int reparse_point);
void win32_set_foreground(void);
void serverInitMessaging(void);
void serverSetName(char_u *name);
-char_u *serverGetVimNames(void);
+list_T *serverGetVimNames(void);
int serverSendReply(char_u *name, char_u *reply);
int serverSendToVim(char_u *name, char_u *cmd, char_u **result, void *ptarget, int asExpr, int timeout, int silent);
void serverForeground(char_u *name);
diff --git a/src/proto/socketserver.pro b/src/proto/socketserver.pro
index 69d292c3a..c1cb3d9a1 100644
--- a/src/proto/socketserver.pro
+++ b/src/proto/socketserver.pro
@@ -1,7 +1,7 @@
/* socketserver.c */
int socketserver_start(char_u *name, bool quiet);
void socketserver_stop(void);
-char_u *socketserver_list(void);
+list_T *socketserver_list(void);
int set_ref_in_socketserver_channel(int copyID);
void socketserver_parse_messages(void);
int socketserver_send(char_u *name, char_u *str, char_u **result, bool is_expr, int timeout, bool silent, channel_T **ch);
diff --git a/src/socketserver.c b/src/socketserver.c
index 48c00c358..d109a2175 100644
--- a/src/socketserver.c
+++ b/src/socketserver.c
@@ -225,16 +225,21 @@ socketserver_cleanup(void)
/*
* List available sockets that can be connected to, only in common directories
* that Vim knows about. Vim instances with custom socket paths will not be
- * detected. Returns a newline separated string on success and NULL on failure.
+ * detected. Returns a list of strings (with reference count not set) on success
+ * and NULL on failure.
*/
- char_u *
+ list_T *
socketserver_list(void)
{
+ list_T *list = list_alloc();
+
+ if (list == NULL)
+ return NULL;
+
# ifdef MSWIN
// Only support addresses on Windows
- return vim_strsave((char_u *)"");
+ return list;
# else
- garray_T str;
string_T buf;
string_T path;
DIR *dirp;
@@ -255,8 +260,6 @@ socketserver_list(void)
buf.length = 0;
path.length = 0;

- ga_init2(&str, 1, 100);
-
for (size_t i = 0 ; i < ARRAY_LENGTH(known_dirs); i++)
{
const char_u *dir = known_dirs[i];
@@ -285,9 +288,8 @@ socketserver_list(void)
buf.length = vim_snprintf_safelen((char *)buf.string, MAXPATHL,
"%s/%s", path.string, dp->d_name);

- ga_concat_len(&str, (char_u *)dp->d_name,
+ list_append_string(list, (char_u *)dp->d_name,
buf.length - (path.length + 1));
- ga_append(&str, '
');
}

closedir(dirp);
@@ -298,9 +300,7 @@ socketserver_list(void)
vim_free(path.string);
vim_free(buf.string);

- ga_append(&str, NUL);
-
- return str.ga_data;
+ return list;
# endif
}

diff --git a/src/testdir/test_clientserver.vim b/src/testdir/test_clientserver.vim
index 754aa7730..18877a9d8 100644
--- a/src/testdir/test_clientserver.vim
+++ b/src/testdir/test_clientserver.vim
@@ -540,6 +540,43 @@ func Test_clientserver_env_method()
endtry
endfunc

+" Test if serverlist() can return a list of strings
+func Test_clientserver_serverlist_list()
+ CheckNotGui
+
+ let g:test_is_flaky = 1
+ let cmd = GetVimCommand()
+
+ if cmd == ''
+ throw 'GetVimCommand() failed'
+ endif
+
+ " Don't use channel:2000, because previous tests use that and it may take a
+ " while for the channel to fully close.
+ let actual = cmd .. ' --servername XVIMTEST'
+
+ let job = job_start(actual, {'stoponexit': 'kill', 'out_io': 'null'})
+
+ call WaitForAssert({-> assert_match('XVIMTEST', serverlist())})
+
+ call assert_equal('list<string>', typename(serverlist(#{list: v:true})))
+ call assert_true(serverlist(#{list: v:true})->index('XVIMTEST') != -1)
+
+ if has('win32') || has('gui_running')
+ call job_stop(job, 'kill')
+ else
+ call system(actual .. " --remote-expr 'execute(\"qa!\")'")
+ endif
+ try
+ call WaitForAssert({-> assert_equal("dead", job_status(job))})
+ finally
+ if job_status(job) != 'dead'
+ call assert_report('Server did not exit')
+ call job_stop(job, 'kill')
+ endif
+ endtry
+endfunc
+
" Uncomment this line to get a debugging log
" call ch_logfile('channellog', 'w')

diff --git a/src/testdir/test_vim9_builtin.vim b/src/testdir/test_vim9_builtin.vim
index 3f95ee8b2..9b5abba5c 100644
--- a/src/testdir/test_vim9_builtin.vim
+++ b/src/testdir/test_vim9_builtin.vim
@@ -3600,6 +3600,17 @@ def Test_remote_startserver()
v9.CheckSourceDefAndScriptFailure(['remote_startserver({})'], ['E1013: Argument 1: type mismatch, expected string but got dict<any>', 'E1174: String required for argument 1'])
enddef

+def Test_remote_serverlist()
+ CheckFeature clientserver
+
+ v9.CheckSourceDefAndScriptFailure(['serverlist("")'], ['E1013: Argument 1: type mismatch, expected dict<any> but got string', 'E1206: Dictionary required for argument 1'])
+ v9.CheckSourceScriptFailure(['vim9script', 'serverlist({list: ""})'], 'E1135: Using a String as a Bool: ""')
+ var l: any = serverlist()
+ assert_equal(v:t_string, type(l))
+ l = serverlist({'list': true})
+ assert_equal(v:t_list, type(l))
+enddef
+
def Test_remove_literal_list()
var l: list<number> = [1, 2, 3, 4]
assert_equal([1, 2], remove(l, 0, 1))
diff --git a/src/version.c b/src/version.c
index 77fdc87d8..05a8e6cd7 100644
--- a/src/version.c
+++ b/src/version.c
@@ -759,6 +759,8 @@ static char *(features[]) =

static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 721,
/**/
720,
/**/
Reply all
Reply to author
Forward
0 new messages