From: Foxe Chen Date: Wed, 24 Jun 2026 18:40:50 +0000 (+0000) Subject: patch 9.2.0721: serverlist() returns strings separated by \n X-Git-Tag: v9.2.0721^0 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=37d9805675abc6c75fb44fabd510df5313f486b0;p=thirdparty%2Fvim.git patch 9.2.0721: serverlist() returns strings separated by \n Problem: serverlist() returns strings separated by \n (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 Signed-off-by: Christian Brabandt --- diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index d362273f4e..47d320a73d 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 setbufline({buf}, {lnum}, {text}) *setbufline()* diff --git a/runtime/doc/version9.txt b/runtime/doc/version9.txt index a515589de5..ecde258764 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 60849fe1e1..13f3fafcb2 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 *)"\n", TRUE, FALSE, 0); + ga_append(&ga, NUL); + list_free(list); + } + res = ga.ga_data; + if (did_emsg) mch_errmsg("\n"); } @@ -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 *)"\n", 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 45002c5bf8..3aeda7e11b 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 43e1e34070..22423a2edf 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, "\n"); - } + 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 edaa03d4f7..6831171cf7 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, "\n"); + 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 74245b75a2..d8ce00259b 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 bcb789f518..32b4aff009 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 69d292c3ab..c1cb3d9a10 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 48c00c3580..d109a21750 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, '\n'); } 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 754aa77305..18877a9d8b 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', 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 3f95ee8b25..9b5abba5ce 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', 'E1174: String required for argument 1']) enddef +def Test_remote_serverlist() + CheckFeature clientserver + + v9.CheckSourceDefAndScriptFailure(['serverlist("")'], ['E1013: Argument 1: type mismatch, expected dict 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 = [1, 2, 3, 4] assert_equal([1, 2], remove(l, 0, 1)) diff --git a/src/version.c b/src/version.c index 77fdc87d88..05a8e6cd7a 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, /**/