Mercurial > vim
view src/clientserver.c @ 22591:c4bb7a69c6a2
Added tag v8.2.1843 for changeset 13f4aee01ce5cdaac010879571a8bb87546bd66b
author | Bram Moolenaar <Bram@vim.org> |
---|---|
date | Tue, 13 Oct 2020 21:15:05 +0200 |
parents | 5b0796787cb2 |
children | acda780ffc3e |
line wrap: on
line source
/* vi:set ts=8 sts=4 sw=4 noet: * * VIM - Vi IMproved by Bram Moolenaar * * Do ":help uganda" in Vim to read copying and usage conditions. * Do ":help credits" in Vim to see a list of people who contributed. * See README.txt for an overview of the Vim source code. */ /* * clientserver.c: functions for Client Server functionality */ #include "vim.h" #if defined(FEAT_CLIENTSERVER) || defined(PROTO) static void cmdsrv_main(int *argc, char **argv, char_u *serverName_arg, char_u **serverStr); static char_u *serverMakeName(char_u *arg, char *cmd); /* * Replace termcodes such as <CR> and insert as key presses if there is room. */ void server_to_input_buf(char_u *str) { char_u *ptr = NULL; char_u *cpo_save = p_cpo; // Set 'cpoptions' the way we want it. // B set - backslashes are *not* treated specially // k set - keycodes are *not* reverse-engineered // < unset - <Key> sequences *are* interpreted // The last but one parameter of replace_termcodes() is TRUE so that the // <lt> sequence is recognised - needed for a real backslash. p_cpo = (char_u *)"Bk"; str = replace_termcodes((char_u *)str, &ptr, REPTERM_DO_LT, NULL); p_cpo = cpo_save; if (*ptr != NUL) // trailing CTRL-V results in nothing { /* * Add the string to the input stream. * Can't use add_to_input_buf() here, we now have K_SPECIAL bytes. * * First clear typed characters from the typeahead buffer, there could * be half a mapping there. Then append to the existing string, so * that multiple commands from a client are concatenated. */ if (typebuf.tb_maplen < typebuf.tb_len) del_typebuf(typebuf.tb_len - typebuf.tb_maplen, typebuf.tb_maplen); (void)ins_typebuf(str, REMAP_NONE, typebuf.tb_len, TRUE, FALSE); // Let input_available() know we inserted text in the typeahead // buffer. typebuf_was_filled = TRUE; } vim_free((char_u *)ptr); } /* * Evaluate an expression that the client sent to a string. */ char_u * eval_client_expr_to_string(char_u *expr) { char_u *res; int save_dbl = debug_break_level; int save_ro = redir_off; funccal_entry_T funccal_entry; int did_save_funccal = FALSE; // Evaluate the expression at the toplevel, don't use variables local to // the calling function. Except when in debug mode. if (!debug_mode) { save_funccal(&funccal_entry); did_save_funccal = TRUE; } // Disable debugging, otherwise Vim hangs, waiting for "cont" to be // typed. debug_break_level = -1; redir_off = 0; // Do not display error message, otherwise Vim hangs, waiting for "cont" // to be typed. Do generate errors so that try/catch works. ++emsg_silent; res = eval_to_string(expr, TRUE); debug_break_level = save_dbl; redir_off = save_ro; --emsg_silent; if (emsg_silent < 0) emsg_silent = 0; if (did_save_funccal) restore_funccal(); // A client can tell us to redraw, but not to display the cursor, so do // that here. setcursor(); out_flush_cursor(FALSE, FALSE); return res; } /* * Evaluate a command or expression sent to ourselves. */ int sendToLocalVim(char_u *cmd, int asExpr, char_u **result) { if (asExpr) { char_u *ret; ret = eval_client_expr_to_string(cmd); if (result != NULL) { if (ret == NULL) { char *err = _(e_invexprmsg); size_t len = STRLEN(cmd) + STRLEN(err) + 5; char_u *msg; msg = alloc(len); if (msg != NULL) vim_snprintf((char *)msg, len, "%s: \"%s\"", err, cmd); *result = msg; } else *result = ret; } else vim_free(ret); return ret == NULL ? -1 : 0; } server_to_input_buf(cmd); return 0; } /* * If conversion is needed, convert "data" from "client_enc" to 'encoding' and * return an allocated string. Otherwise return "data". * "*tofree" is set to the result when it needs to be freed later. */ char_u * serverConvert( char_u *client_enc UNUSED, char_u *data, char_u **tofree) { char_u *res = data; *tofree = NULL; if (client_enc != NULL && p_enc != NULL) { vimconv_T vimconv; vimconv.vc_type = CONV_NONE; if (convert_setup(&vimconv, client_enc, p_enc) != FAIL && vimconv.vc_type != CONV_NONE) { res = string_convert(&vimconv, data, NULL); if (res == NULL) res = data; else *tofree = res; } convert_setup(&vimconv, NULL, NULL); } return res; } #endif #if (defined(FEAT_CLIENTSERVER) && !defined(NO_VIM_MAIN)) || defined(PROTO) /* * Common code for the X command server and the Win32 command server. */ static char_u *build_drop_cmd(int filec, char **filev, int tabs, int sendReply); /* * Do the client-server stuff, unless "--servername ''" was used. */ void exec_on_server(mparm_T *parmp) { if (parmp->serverName_arg == NULL || *parmp->serverName_arg != NUL) { # ifdef MSWIN // Initialise the client/server messaging infrastructure. serverInitMessaging(); # endif /* * When a command server argument was found, execute it. This may * exit Vim when it was successful. Otherwise it's executed further * on. Remember the encoding used here in "serverStrEnc". */ if (parmp->serverArg) { cmdsrv_main(&parmp->argc, parmp->argv, parmp->serverName_arg, &parmp->serverStr); parmp->serverStrEnc = vim_strsave(p_enc); } // If we're still running, get the name to register ourselves. // On Win32 can register right now, for X11 need to setup the // clipboard first, it's further down. parmp->servername = serverMakeName(parmp->serverName_arg, parmp->argv[0]); # ifdef MSWIN if (parmp->servername != NULL) { serverSetName(parmp->servername); vim_free(parmp->servername); } # endif } } /* * Prepare for running as a Vim server. */ void prepare_server(mparm_T *parmp) { # if defined(FEAT_X11) /* * Register for remote command execution with :serversend and --remote * unless there was a -X or a --servername '' on the command line. * Only register nongui-vim's with an explicit --servername argument, * or when compiling with autoservername. * When running as root --servername is also required. */ if (X_DISPLAY != NULL && parmp->servername != NULL && ( # if defined(FEAT_AUTOSERVERNAME) || defined(FEAT_GUI) ( # if defined(FEAT_AUTOSERVERNAME) 1 # else gui.in_use # endif # ifdef UNIX && getuid() != ROOT_UID # endif ) || # endif parmp->serverName_arg != NULL)) { (void)serverRegisterName(X_DISPLAY, parmp->servername); vim_free(parmp->servername); TIME_MSG("register server name"); } else serverDelayedStartName = parmp->servername; # endif /* * Execute command ourselves if we're here because the send failed (or * else we would have exited above). */ if (parmp->serverStr != NULL) { char_u *p; server_to_input_buf(serverConvert(parmp->serverStrEnc, parmp->serverStr, &p)); vim_free(p); } } static void cmdsrv_main( int *argc, char **argv, char_u *serverName_arg, char_u **serverStr) { char_u *res; int i; char_u *sname; int ret; int didone = FALSE; int exiterr = 0; char **newArgV = argv + 1; int newArgC = 1, Argc = *argc; int argtype; #define ARGTYPE_OTHER 0 #define ARGTYPE_EDIT 1 #define ARGTYPE_EDIT_WAIT 2 #define ARGTYPE_SEND 3 int silent = FALSE; int tabs = FALSE; # ifndef FEAT_X11 HWND srv; # else Window srv; setup_term_clip(); # endif sname = serverMakeName(serverName_arg, argv[0]); if (sname == NULL) return; /* * Execute the command server related arguments and remove them * from the argc/argv array; We may have to return into main() */ for (i = 1; i < Argc; i++) { res = NULL; if (STRCMP(argv[i], "--") == 0) // end of option arguments { for (; i < *argc; i++) { *newArgV++ = argv[i]; newArgC++; } break; } if (STRICMP(argv[i], "--remote-send") == 0) argtype = ARGTYPE_SEND; else if (STRNICMP(argv[i], "--remote", 8) == 0) { char *p = argv[i] + 8; argtype = ARGTYPE_EDIT; while (*p != NUL) { if (STRNICMP(p, "-wait", 5) == 0) { argtype = ARGTYPE_EDIT_WAIT; p += 5; } else if (STRNICMP(p, "-silent", 7) == 0) { silent = TRUE; p += 7; } else if (STRNICMP(p, "-tab", 4) == 0) { tabs = TRUE; p += 4; } else { argtype = ARGTYPE_OTHER; break; } } } else argtype = ARGTYPE_OTHER; if (argtype != ARGTYPE_OTHER) { if (i == *argc - 1) mainerr_arg_missing((char_u *)argv[i]); if (argtype == ARGTYPE_SEND) { *serverStr = (char_u *)argv[i + 1]; i++; } else { *serverStr = build_drop_cmd(*argc - i - 1, argv + i + 1, tabs, argtype == ARGTYPE_EDIT_WAIT); if (*serverStr == NULL) { // Probably out of memory, exit. didone = TRUE; exiterr = 1; break; } Argc = i; } # ifdef FEAT_X11 if (xterm_dpy == NULL) { mch_errmsg(_("No display")); ret = -1; } else ret = serverSendToVim(xterm_dpy, sname, *serverStr, NULL, &srv, 0, 0, 0, silent); # else // Win32 always works? ret = serverSendToVim(sname, *serverStr, NULL, &srv, 0, 0, silent); # endif if (ret < 0) { if (argtype == ARGTYPE_SEND) { // Failed to send, abort. mch_errmsg(_(": Send failed.\n")); didone = TRUE; exiterr = 1; } else if (!silent) // Let vim start normally. mch_errmsg(_(": Send failed. Trying to execute locally\n")); break; } # ifdef FEAT_GUI_MSWIN // Guess that when the server name starts with "g" it's a GUI // server, which we can bring to the foreground here. // Foreground() in the server doesn't work very well. if (argtype != ARGTYPE_SEND && TOUPPER_ASC(*sname) == 'G') SetForegroundWindow(srv); # endif /* * For --remote-wait: Wait until the server did edit each * file. Also detect that the server no longer runs. */ if (ret >= 0 && argtype == ARGTYPE_EDIT_WAIT) { int numFiles = *argc - i - 1; int j; char_u *done = alloc(numFiles); char_u *p; # ifdef FEAT_GUI_MSWIN NOTIFYICONDATA ni; int count = 0; extern HWND message_window; # endif if (numFiles > 0 && argv[i + 1][0] == '+') // Skip "+cmd" argument, don't wait for it to be edited. --numFiles; # ifdef FEAT_GUI_MSWIN ni.cbSize = sizeof(ni); ni.hWnd = message_window; ni.uID = 0; ni.uFlags = NIF_ICON|NIF_TIP; ni.hIcon = LoadIcon((HINSTANCE)GetModuleHandle(0), "IDR_VIM"); sprintf(ni.szTip, _("%d of %d edited"), count, numFiles); Shell_NotifyIcon(NIM_ADD, &ni); # endif // Wait for all files to unload in remote vim_memset(done, 0, numFiles); while (memchr(done, 0, numFiles) != NULL) { # ifdef MSWIN p = serverGetReply(srv, NULL, TRUE, TRUE, 0); if (p == NULL) break; # else if (serverReadReply(xterm_dpy, srv, &p, TRUE, -1) < 0) break; # endif j = atoi((char *)p); if (j >= 0 && j < numFiles) { # ifdef FEAT_GUI_MSWIN ++count; sprintf(ni.szTip, _("%d of %d edited"), count, numFiles); Shell_NotifyIcon(NIM_MODIFY, &ni); # endif done[j] = 1; } } # ifdef FEAT_GUI_MSWIN Shell_NotifyIcon(NIM_DELETE, &ni); # endif vim_free(done); } } else if (STRICMP(argv[i], "--remote-expr") == 0) { if (i == *argc - 1) mainerr_arg_missing((char_u *)argv[i]); # ifdef MSWIN // Win32 always works? if (serverSendToVim(sname, (char_u *)argv[i + 1], &res, NULL, 1, 0, FALSE) < 0) # else if (xterm_dpy == NULL) mch_errmsg(_("No display: Send expression failed.\n")); else if (serverSendToVim(xterm_dpy, sname, (char_u *)argv[i + 1], &res, NULL, 1, 0, 1, FALSE) < 0) # endif { if (res != NULL && *res != NUL) { // Output error from remote mch_errmsg((char *)res); VIM_CLEAR(res); } mch_errmsg(_(": Send expression failed.\n")); } } else if (STRICMP(argv[i], "--serverlist") == 0) { # ifdef MSWIN // Win32 always works? res = serverGetVimNames(); # else if (xterm_dpy != NULL) res = serverGetVimNames(xterm_dpy); # endif if (did_emsg) mch_errmsg("\n"); } else if (STRICMP(argv[i], "--servername") == 0) { // Already processed. Take it out of the command line i++; continue; } else { *newArgV++ = argv[i]; newArgC++; continue; } didone = TRUE; if (res != NULL && *res != NUL) { mch_msg((char *)res); if (res[STRLEN(res) - 1] != '\n') mch_msg("\n"); } vim_free(res); } if (didone) { display_errors(); // display any collected messages exit(exiterr); // Mission accomplished - get out } // Return back into main() *argc = newArgC; vim_free(sname); } /* * Build a ":drop" command to send to a Vim server. */ static char_u * build_drop_cmd( int filec, char **filev, int tabs, // Use ":tab drop" instead of ":drop". int sendReply) { garray_T ga; int i; char_u *inicmd = NULL; char_u *p; char_u *cdp; char_u *cwd; if (filec > 0 && filev[0][0] == '+') { inicmd = (char_u *)filev[0] + 1; filev++; filec--; } // Check if we have at least one argument. if (filec <= 0) mainerr_arg_missing((char_u *)filev[-1]); // Temporarily cd to the current directory to handle relative file names. cwd = alloc(MAXPATHL); if (cwd == NULL) return NULL; if (mch_dirname(cwd, MAXPATHL) != OK) { vim_free(cwd); return NULL; } cdp = vim_strsave_escaped_ext(cwd, #ifdef BACKSLASH_IN_FILENAME (char_u *)"", // rem_backslash() will tell what chars to escape #else PATH_ESC_CHARS, #endif '\\', TRUE); vim_free(cwd); if (cdp == NULL) return NULL; ga_init2(&ga, 1, 100); ga_concat(&ga, (char_u *)"<C-\\><C-N>:cd "); ga_concat(&ga, cdp); // Call inputsave() so that a prompt for an encryption key works. ga_concat(&ga, (char_u *) "<CR>:if exists('*inputsave')|call inputsave()|endif|"); if (tabs) ga_concat(&ga, (char_u *)"tab "); ga_concat(&ga, (char_u *)"drop"); for (i = 0; i < filec; i++) { // On Unix the shell has already expanded the wildcards, don't want to // do it again in the Vim server. On MS-Windows only escape // non-wildcard characters. p = vim_strsave_escaped((char_u *)filev[i], #ifdef UNIX PATH_ESC_CHARS #else (char_u *)" \t%#" #endif ); if (p == NULL) { vim_free(ga.ga_data); return NULL; } ga_concat(&ga, (char_u *)" "); ga_concat(&ga, p); vim_free(p); } ga_concat(&ga, (char_u *) "|if exists('*inputrestore')|call inputrestore()|endif<CR>"); // The :drop commands goes to Insert mode when 'insertmode' is set, use // CTRL-\ CTRL-N again. ga_concat(&ga, (char_u *)"<C-\\><C-N>"); // Switch back to the correct current directory (prior to temporary path // switch) unless 'autochdir' is set, in which case it will already be // correct after the :drop command. With line breaks and spaces: // if !exists('+acd') || !&acd // if haslocaldir() // cd - // lcd - // elseif getcwd() ==# 'current path' // cd - // endif // endif ga_concat(&ga, (char_u *)":if !exists('+acd')||!&acd|if haslocaldir()|"); ga_concat(&ga, (char_u *)"cd -|lcd -|elseif getcwd() ==# '"); ga_concat(&ga, cdp); ga_concat(&ga, (char_u *)"'|cd -|endif|endif<CR>"); vim_free(cdp); if (sendReply) ga_concat(&ga, (char_u *)":call SetupRemoteReplies()<CR>"); ga_concat(&ga, (char_u *)":"); if (inicmd != NULL) { // Can't use <CR> after "inicmd", because an "startinsert" would cause // the following commands to be inserted as text. Use a "|", // hopefully "inicmd" does allow this... ga_concat(&ga, inicmd); ga_concat(&ga, (char_u *)"|"); } // Bring the window to the foreground, goto Insert mode when 'im' set and // clear command line. ga_concat(&ga, (char_u *)"cal foreground()|if &im|star|en|redr|f<CR>"); ga_append(&ga, NUL); return ga.ga_data; } /* * Make our basic server name: use the specified "arg" if given, otherwise use * the tail of the command "cmd" we were started with. * Return the name in allocated memory. This doesn't include a serial number. */ static char_u * serverMakeName(char_u *arg, char *cmd) { char_u *p; if (arg != NULL && *arg != NUL) p = vim_strsave_up(arg); else { p = vim_strsave_up(gettail((char_u *)cmd)); // Remove .exe or .bat from the name. if (p != NULL && vim_strchr(p, '.') != NULL) *vim_strchr(p, '.') = NUL; } return p; } #endif // FEAT_CLIENTSERVER #if defined(FEAT_CLIENTSERVER) && defined(FEAT_X11) static void make_connection(void) { if (X_DISPLAY == NULL # ifdef FEAT_GUI && !gui.in_use # endif ) { x_force_connect = TRUE; setup_term_clip(); x_force_connect = FALSE; } } static int check_connection(void) { make_connection(); if (X_DISPLAY == NULL) { emsg(_("E240: No connection to the X server")); return FAIL; } return OK; } #endif #ifdef FEAT_CLIENTSERVER static void remote_common(typval_T *argvars, typval_T *rettv, int expr) { char_u *server_name; char_u *keys; char_u *r = NULL; char_u buf[NUMBUFLEN]; int timeout = 0; # ifdef MSWIN HWND w; # else Window w; # endif if (check_restricted() || check_secure()) return; # ifdef FEAT_X11 if (check_connection() == FAIL) return; # endif if (argvars[2].v_type != VAR_UNKNOWN && argvars[3].v_type != VAR_UNKNOWN) timeout = tv_get_number(&argvars[3]); server_name = tv_get_string_chk(&argvars[0]); if (server_name == NULL) return; // type error; errmsg already given keys = tv_get_string_buf(&argvars[1], buf); # ifdef MSWIN if (serverSendToVim(server_name, keys, &r, &w, expr, timeout, TRUE) < 0) # else if (serverSendToVim(X_DISPLAY, server_name, keys, &r, &w, expr, timeout, 0, TRUE) < 0) # endif { if (r != NULL) { emsg((char *)r); // sending worked but evaluation failed vim_free(r); } else semsg(_("E241: Unable to send to %s"), server_name); return; } rettv->vval.v_string = r; if (argvars[2].v_type != VAR_UNKNOWN) { dictitem_T v; char_u str[30]; char_u *idvar; idvar = tv_get_string_chk(&argvars[2]); if (idvar != NULL && *idvar != NUL) { sprintf((char *)str, PRINTF_HEX_LONG_U, (long_u)w); v.di_tv.v_type = VAR_STRING; v.di_tv.vval.v_string = vim_strsave(str); set_var(idvar, &v.di_tv, FALSE); vim_free(v.di_tv.vval.v_string); } } } #endif #if defined(FEAT_EVAL) || defined(PROTO) /* * "remote_expr()" function */ void f_remote_expr(typval_T *argvars UNUSED, typval_T *rettv) { rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; #ifdef FEAT_CLIENTSERVER remote_common(argvars, rettv, TRUE); #endif } /* * "remote_foreground()" function */ void f_remote_foreground(typval_T *argvars UNUSED, typval_T *rettv UNUSED) { #ifdef FEAT_CLIENTSERVER # ifdef MSWIN // On Win32 it's done in this application. { char_u *server_name = tv_get_string_chk(&argvars[0]); if (server_name != NULL) serverForeground(server_name); } # else // Send a foreground() expression to the server. argvars[1].v_type = VAR_STRING; argvars[1].vval.v_string = vim_strsave((char_u *)"foreground()"); argvars[2].v_type = VAR_UNKNOWN; rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; remote_common(argvars, rettv, TRUE); vim_free(argvars[1].vval.v_string); # endif #endif } void f_remote_peek(typval_T *argvars UNUSED, typval_T *rettv) { #ifdef FEAT_CLIENTSERVER dictitem_T v; char_u *s = NULL; # ifdef MSWIN long_u n = 0; # endif char_u *serverid; if (check_restricted() || check_secure()) { rettv->vval.v_number = -1; return; } serverid = tv_get_string_chk(&argvars[0]); if (serverid == NULL) { rettv->vval.v_number = -1; return; // type error; errmsg already given } # ifdef MSWIN sscanf((const char *)serverid, SCANF_HEX_LONG_U, &n); if (n == 0) rettv->vval.v_number = -1; else { s = serverGetReply((HWND)n, FALSE, FALSE, FALSE, 0); rettv->vval.v_number = (s != NULL); } # else if (check_connection() == FAIL) return; rettv->vval.v_number = serverPeekReply(X_DISPLAY, serverStrToWin(serverid), &s); # endif if (argvars[1].v_type != VAR_UNKNOWN && rettv->vval.v_number > 0) { char_u *retvar; v.di_tv.v_type = VAR_STRING; v.di_tv.vval.v_string = vim_strsave(s); retvar = tv_get_string_chk(&argvars[1]); if (retvar != NULL) set_var(retvar, &v.di_tv, FALSE); vim_free(v.di_tv.vval.v_string); } #else rettv->vval.v_number = -1; #endif } void f_remote_read(typval_T *argvars UNUSED, typval_T *rettv) { char_u *r = NULL; #ifdef FEAT_CLIENTSERVER char_u *serverid = tv_get_string_chk(&argvars[0]); if (serverid != NULL && !check_restricted() && !check_secure()) { int timeout = 0; # ifdef MSWIN // The server's HWND is encoded in the 'id' parameter long_u n = 0; # endif if (argvars[1].v_type != VAR_UNKNOWN) timeout = tv_get_number(&argvars[1]); # ifdef MSWIN sscanf((char *)serverid, SCANF_HEX_LONG_U, &n); if (n != 0) r = serverGetReply((HWND)n, FALSE, TRUE, TRUE, timeout); if (r == NULL) # else if (check_connection() == FAIL || serverReadReply(X_DISPLAY, serverStrToWin(serverid), &r, FALSE, timeout) < 0) # endif emsg(_("E277: Unable to read a server reply")); } #endif rettv->v_type = VAR_STRING; rettv->vval.v_string = r; } /* * "remote_send()" function */ void f_remote_send(typval_T *argvars UNUSED, typval_T *rettv) { rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; #ifdef FEAT_CLIENTSERVER remote_common(argvars, rettv, FALSE); #endif } /* * "remote_startserver()" function */ void f_remote_startserver(typval_T *argvars UNUSED, typval_T *rettv UNUSED) { #ifdef FEAT_CLIENTSERVER char_u *server = tv_get_string_chk(&argvars[0]); if (server == NULL) return; // type error; errmsg already given if (serverName != NULL) emsg(_("E941: already started a server")); else { # ifdef FEAT_X11 if (check_connection() == OK) serverRegisterName(X_DISPLAY, server); # else serverSetName(server); # endif } #else emsg(_("E942: +clientserver feature not available")); #endif } void f_server2client(typval_T *argvars UNUSED, typval_T *rettv) { #ifdef FEAT_CLIENTSERVER char_u buf[NUMBUFLEN]; char_u *server = tv_get_string_chk(&argvars[0]); char_u *reply = tv_get_string_buf_chk(&argvars[1], buf); rettv->vval.v_number = -1; if (server == NULL || reply == NULL) return; if (check_restricted() || check_secure()) return; # ifdef FEAT_X11 if (check_connection() == FAIL) return; # endif if (serverSendReply(server, reply) < 0) { emsg(_("E258: Unable to send to client")); return; } rettv->vval.v_number = 0; #else rettv->vval.v_number = -1; #endif } void f_serverlist(typval_T *argvars UNUSED, typval_T *rettv) { char_u *r = NULL; #ifdef FEAT_CLIENTSERVER # ifdef MSWIN r = serverGetVimNames(); # else make_connection(); if (X_DISPLAY != NULL) r = serverGetVimNames(X_DISPLAY); # endif #endif rettv->v_type = VAR_STRING; rettv->vval.v_string = r; } #endif