]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.2.0721: serverlist() returns strings separated by \n v9.2.0721
authorFoxe Chen <chen.foxe@gmail.com>
Wed, 24 Jun 2026 18:40:50 +0000 (18:40 +0000)
committerChristian Brabandt <cb@256bit.org>
Wed, 24 Jun 2026 18:44:36 +0000 (18:44 +0000)
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 <chen.foxe@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
13 files changed:
runtime/doc/builtin.txt
runtime/doc/version9.txt
src/clientserver.c
src/evalfunc.c
src/if_xcmdsrv.c
src/os_mswin.c
src/proto/if_xcmdsrv.pro
src/proto/os_mswin.pro
src/proto/socketserver.pro
src/socketserver.c
src/testdir/test_clientserver.vim
src/testdir/test_vim9_builtin.vim
src/version.c

index d362273f4e9dd85e65c8ac07d0a1b7f733ba9b9a..47d320a73d1910979e68f0640c016425c2d7d99f 100644 (file)
@@ -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()*
index a515589de52b2913139a50d4352db4c26e8e5779..ecde25876464186d3014db9b81316314d1cd2eb4 100644 (file)
@@ -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*
index 60849fe1e1ab47664a0c42289e8c60b25f147933..13f3fafcb2a155331e3e4a55dc23b430b20b85d7 100644 (file)
@@ -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
 
index 45002c5bf833de4674f87d37fa307e391bf756e3..3aeda7e11b19f87ba57487e393606afd15fa8b03 100644 (file)
@@ -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,
index 43e1e34070ba48a5f480ce555a39ffbaa3d9afed..22423a2edf86c11c9b4a390b4b6efa281afea897 100644 (file)
@@ -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;
 }
 
 /////////////////////////////////////////////////////////////
index edaa03d4f76620beb257d650d9ac7a6abab2a1f8..6831171cf7d580cb7949324d31f86d8adf5770df 100644 (file)
@@ -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
index 74245b75a20808f8fde0dc384729a4fc37e027ad..d8ce00259bb4055ce02b0d5c8252a1c18eeeb02c 100644 (file)
@@ -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);
index bcb789f518d25dbd0e6367b95088ebe77f4dcdeb..32b4aff009f5ea59f124eeb123f3b6ad2c8760a8 100644 (file)
@@ -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);
index 69d292c3ab4f23c84453653fdc703b55aab20a76..c1cb3d9a10dc6b28e6029d76d15c59c79a14a982 100644 (file)
@@ -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);
index 48c00c358077a55e7f022d3cd0841739d98fdaaf..d109a217508e4c4cd9bf90ebd00218701657ff0f 100644 (file)
@@ -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
 }
 
index 754aa77305355ba50b0f0a602e72f9d39f8f05f7..18877a9d8bf1c7c767fc15e775b8c4e77ed7bcba 100644 (file)
@@ -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')
 
index 3f95ee8b251c293fbdbcf929f24f4404cdacc719..9b5abba5ce7b6bd783b4eac598d7dc2d7729125d 100644 (file)
@@ -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))
index 77fdc87d8851e9c2b4ba919c0cfb81063475b42f..05a8e6cd7a6c184a80f86158b6be69236d75378b 100644 (file)
@@ -759,6 +759,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    721,
 /**/
     720,
 /**/