text: /* 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_invalid_expression_received);
  		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(_(e_no_connection_to_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(_(e_unable_to_send_to_str), 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;
  
      if (in_vim9script()
  	    && (check_for_string_arg(argvars, 0) == FAIL
  		|| check_for_string_arg(argvars, 1) == FAIL
  		|| check_for_opt_string_arg(argvars, 2) == FAIL
  		|| (argvars[2].v_type != VAR_UNKNOWN
  		    && check_for_opt_number_arg(argvars, 3) == FAIL)))
  	return;
  
  #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
      if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
  	return;
  
  # 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;
  
      rettv->vval.v_number = -1;
      if (check_restricted() || check_secure())
  	return;
  
      if (in_vim9script()
  	    && (check_for_string_arg(argvars, 0) == FAIL
  		|| check_for_opt_string_arg(argvars, 1) == FAIL))
  	return;
  
      serverid = tv_get_string_chk(&argvars[0]);
      if (serverid == NULL)
  	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;
  
      if (in_vim9script()
  	    && (check_for_string_arg(argvars, 0) == FAIL
  		|| check_for_opt_number_arg(argvars, 1) == FAIL))
  	return;
  
      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(_(e_unable_to_read_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;
  
      if (in_vim9script()
  	    && (check_for_string_arg(argvars, 0) == FAIL
  		|| check_for_string_arg(argvars, 1) == FAIL
  		|| check_for_opt_string_arg(argvars, 2) == FAIL))
  	return;
  
  #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;
  
      if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
  	return;
  
      server = tv_get_string_chk(&argvars[0]);
      if (server == NULL)
  	return;		// type error; errmsg already given
      if (serverName != NULL)
  	emsg(_(e_already_started_server));
      else
      {
  # ifdef FEAT_X11
  	if (check_connection() == OK)
  	    serverRegisterName(X_DISPLAY, server);
  # else
  	serverSetName(server);
  # endif
      }
  #else
      emsg(_(e_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;
      char_u	*reply;
  
      rettv->vval.v_number = -1;
      if (check_restricted() || check_secure())
  	return;
  
      if (in_vim9script()
  	    && (check_for_string_arg(argvars, 0) == FAIL
  		|| check_for_string_arg(argvars, 1) == FAIL))
  	return;
  
      server = tv_get_string_chk(&argvars[0]);
      reply = tv_get_string_buf_chk(&argvars[1], buf);
      if (server == NULL || reply == NULL)
  	return;
  
  # ifdef FEAT_X11
      if (check_connection() == FAIL)
  	return;
  # endif
  
      if (serverSendReply(server, reply) < 0)
      {
  	emsg(_(e_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
  