teardown_connection (TestConnection *test, gconstpointer data)
{
  if (test->service)
    {
      g_socket_service_stop (test->service);
      /* The outstanding accept_async will hold a ref on test->service,
       * which we want to wait for it to release if we're valgrinding.
       */
      g_object_add_weak_pointer (G_OBJECT (test->service), (gpointer *)&test->service);
      g_object_unref (test->service);
      WAIT_UNTIL_UNSET (test->service);
    }

  if (test->server_connection)
    {
      WAIT_UNTIL_UNSET (test->server_running);

      g_object_add_weak_pointer (G_OBJECT (test->server_connection),
                                 (gpointer *)&test->server_connection);
      g_object_unref (test->server_connection);
      WAIT_UNTIL_UNSET (test->server_connection);
    }

  if (test->client_connection)
    {
      g_object_add_weak_pointer (G_OBJECT (test->client_connection),
                                 (gpointer *)&test->client_connection);
      g_object_unref (test->client_connection);
      WAIT_UNTIL_UNSET (test->client_connection);
    }

  if (test->database)
    {
      g_object_add_weak_pointer (G_OBJECT (test->database),
                                 (gpointer *)&test->database);
      g_object_unref (test->database);
      WAIT_UNTIL_UNSET (test->database);
    }

  g_clear_object (&test->address);
  g_clear_object (&test->identity);
  g_clear_object (&test->server_certificate);

  g_main_loop_unref (test->loop);

  g_clear_error (&test->read_error);
  g_clear_error (&test->server_error);
  g_clear_error (&test->expected_client_close_error);
}


start_echo_server_and_connect_to_it (TestConnection *test)
{
  GSocketClient *client;
  GError *error = NULL;
  GSocketConnection *connection;

  start_echo_server_service (test);

  client = g_socket_client_new ();
  connection = g_socket_client_connect (client, G_SOCKET_CONNECTABLE (test->address),
                                        NULL, &error);
  g_assert_no_error (error);
  g_object_unref (client);

  return G_IO_STREAM (connection);
}


test_verified_chain (TestConnection *test,
                     gconstpointer   data)
{
  GTlsBackend *backend;
  GTlsCertificate *server_cert;
  GTlsCertificate *intermediate_cert;
  char *cert_data = NULL;
  char *key_data = NULL;
  GError *error = NULL;

  backend = g_tls_backend_get_default ();

  /* Prepare the intermediate cert. */
  intermediate_cert = g_tls_certificate_new_from_file (tls_test_file_path ("intermediate-ca.pem"), &error);
  g_assert_no_error (error);
  g_assert_nonnull (intermediate_cert);

  /* Prepare the server cert. */
  g_clear_pointer (&cert_data, g_free);
  g_file_get_contents (tls_test_file_path ("server-intermediate.pem"),
                       &cert_data, NULL, &error);
  g_assert_no_error (error);
  g_assert_nonnull (cert_data);

  g_file_get_contents (tls_test_file_path ("server-intermediate-key.pem"),
                       &key_data, NULL, &error);
  g_assert_no_error (error);
  g_assert_nonnull (key_data);

  server_cert = g_initable_new (g_tls_backend_get_certificate_type (backend),
                                NULL, &error,
                                "issuer", intermediate_cert,
                                "certificate-pem", cert_data,
                                "private-key-pem", key_data,
                                NULL);
  g_assert_no_error (error);
  g_assert_nonnull (server_cert);

  g_object_unref (intermediate_cert);
  g_free (cert_data);
  g_free (key_data);

  test->server_certificate = server_cert;
  test_verified_connection (test, data);
}


wait_until_server_finished (TestConnection *test)
{
    WAIT_UNTIL_UNSET (test->server_running);
}


test_readwrite_after_connection_destroyed (TestConnection *test,
                                           gconstpointer   data)
{
  GIOStream *connection;
  GOutputStream *ostream;
  GInputStream *istream;
  unsigned char buffer[1];
  GError *error = NULL;

  g_test_bug ("792219");

  connection = start_async_server_and_connect_to_it (test, G_TLS_AUTHENTICATION_NONE);
  test->client_connection = g_tls_client_connection_new (connection, test->identity, &error);
  g_assert_no_error (error);
  g_object_unref (connection);

  istream = g_object_ref (g_io_stream_get_input_stream (test->client_connection));
  ostream = g_object_ref (g_io_stream_get_output_stream (test->client_connection));
  g_clear_object (&test->client_connection);

  /* The GTlsConnection has been destroyed, but its underlying streams
   * live on, because we have reffed them. Verify that attempts to read
   * and write produce only nice GErrors.
   */
  g_input_stream_read (istream, buffer, sizeof (buffer), NULL, &error);
  g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CLOSED);
  g_clear_error (&error);

  g_output_stream_write (ostream, TEST_DATA, TEST_DATA_LENGTH,
                         G_PRIORITY_DEFAULT, &error);
  g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CLOSED);
  g_clear_error (&error);

  g_input_stream_close (istream, NULL, &error);
  g_assert_no_error (error);

  g_output_stream_close (ostream, NULL, &error);
  g_assert_no_error (error);

  g_object_unref (istream);
  g_object_unref (ostream);
}


simul_read_thread (gpointer user_data)
{
  TestConnection *test = user_data;
  GInputStream *istream = g_io_stream_get_input_stream (test->client_connection);
  GError *error = NULL;
  gssize nread;

  while (test->nread < TEST_DATA_LENGTH)
    {
      nread = g_input_stream_read (istream,
                                   test->buf + test->nread,
                                   MIN (TEST_DATA_LENGTH / 2, TEST_DATA_LENGTH - test->nread),
                                   NULL, &error);
      g_assert_no_error (error);

      test->nread += nread;
    }

  return NULL;
}


test_alpn (TestConnection *test,
           const char * const *client_protocols,
           const char * const *server_protocols,
           const char *negotiated_protocol)
{
#ifdef BACKEND_IS_OPENSSL
  g_test_skip ("this is not yet passing with openssl");
  return;
#endif

  GIOStream *connection;
  GError *error = NULL;

  test->server_protocols = server_protocols;

  test->database = g_tls_file_database_new (tls_test_file_path ("ca-roots.pem"), &error);
  g_assert_no_error (error);
  g_assert_nonnull (test->database);

  connection = start_async_server_and_connect_to_it (test, G_TLS_AUTHENTICATION_NONE);
  test->client_connection = g_tls_client_connection_new (connection, test->identity, &error);
  g_assert_no_error (error);
  g_object_unref (connection);

  if (client_protocols)
    {
      g_tls_connection_set_advertised_protocols (G_TLS_CONNECTION (test->client_connection),
                                                 client_protocols);
    }

  g_tls_connection_set_database (G_TLS_CONNECTION (test->client_connection), test->database);

  read_test_data_async (test);
  g_main_loop_run (test->loop);
  wait_until_server_finished (test);

  g_assert_no_error (test->read_error);
  g_assert_no_error (test->server_error);

  g_assert_cmpstr (g_tls_connection_get_negotiated_protocol (G_TLS_CONNECTION (test->server_connection)), ==, negotiated_protocol);
  g_assert_cmpstr (g_tls_connection_get_negotiated_protocol (G_TLS_CONNECTION (test->client_connection)), ==, negotiated_protocol);
}


g_tls_connection_base_request_certificate (GTlsConnectionBase *tls)
{
  GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
  GTlsInteractionResult res = G_TLS_INTERACTION_UNHANDLED;
  GTlsInteraction *interaction;
  GTlsConnection *conn;

  g_return_val_if_fail (G_IS_TLS_CONNECTION_BASE (tls), FALSE);

  conn = G_TLS_CONNECTION (tls);

  g_clear_error (&priv->interaction_error);

  interaction = g_tls_connection_get_interaction (conn);
  if (!interaction)
    return FALSE;

  res = g_tls_interaction_invoke_request_certificate (interaction, conn, 0,
                                                      priv->read_cancellable,
                                                      &priv->interaction_error);
  return res != G_TLS_INTERACTION_FAILED;
}


g_tls_connection_base_get_read_error (GTlsConnectionBase *tls)
{
  GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);

  return &priv->read_error;
}


socket_client_timed_out_write_connected (GObject      *source,
                                         GAsyncResult *result,
                                         gpointer      user_data)
{
  TestConnection *test = user_data;
  GSocketConnection *connection;
  GError *error = NULL;

  connection = g_socket_client_connect_finish (G_SOCKET_CLIENT (source),
                                               result, &error);
  g_assert_no_error (error);
  test->client_connection = G_IO_STREAM (connection);

  /* We need to use an idle callback here to guarantee that the upcoming call
   * to g_input_stream_read() executes on the next iteration of the main
   * context. Otherwise, we could deadlock ourselves: the read would not be able
   * to complete if GTask executes socket_client_timed_out_write_connected()
   * using g_task_return_now() instead of posting the invocation to the next
   * iteration of the main context, because the server will not progress until
   * the main context is iterated, but iteration would be blocked waiting for
   * client's read to complete.
   */
  g_idle_add (socket_client_timed_out_write, test);
}


test_failed_connection (TestConnection *test,
                        gconstpointer   data)
{
  GIOStream *connection;
  GError *error = NULL;
  GSocketConnectable *bad_addr;

  connection = start_async_server_and_connect_to_it (test, G_TLS_AUTHENTICATION_NONE);

  bad_addr = g_network_address_new ("wrong.example.com", 80);
  test->client_connection = g_tls_client_connection_new (connection, bad_addr, &error);
  g_object_unref (bad_addr);
  g_assert_no_error (error);
  g_object_unref (connection);

  g_tls_connection_handshake_async (G_TLS_CONNECTION (test->client_connection),
                                    G_PRIORITY_DEFAULT, NULL,
                                    handshake_failed_cb, test);
  g_main_loop_run (test->loop);

  g_tls_client_connection_set_validation_flags (G_TLS_CLIENT_CONNECTION (test->client_connection),
                                                G_TLS_CERTIFICATE_VALIDATE_ALL);

  read_test_data_async (test);
  g_main_loop_run (test->loop);
  wait_until_server_finished (test);

  g_assert_error (test->read_error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE);

#ifdef BACKEND_IS_GNUTLS
  g_assert_error (test->server_error, G_TLS_ERROR, G_TLS_ERROR_NOT_TLS);
#elif defined(BACKEND_IS_OPENSSL)
  /* FIXME: This is not OK. There should be an error here. */
  g_assert_no_error (test->server_error);
#endif
}


g_tls_connection_base_handshake (GTlsConnection   *conn,
                                 GCancellable     *cancellable,
                                 GError          **error)
{
  GTlsConnectionBase *tls = G_TLS_CONNECTION_BASE (conn);
  GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
  GTlsConnectionBaseClass *tls_class = G_TLS_CONNECTION_BASE_GET_CLASS (tls);
  GTask *task;
  gboolean success;
  gint64 *timeout = NULL;
  GError *my_error = NULL;

  g_assert (!priv->handshake_context);
  priv->handshake_context = g_main_context_new ();

  g_main_context_push_thread_default (priv->handshake_context);

  if (tls_class->prepare_handshake)
    tls_class->prepare_handshake (tls, priv->advertised_protocols);

  task = g_task_new (conn, cancellable, sync_handshake_thread_completed, NULL);
  g_task_set_source_tag (task, g_tls_connection_base_handshake);
  g_task_set_name (task, "[glib-networking] g_tls_connection_base_handshake");
  g_task_set_return_on_cancel (task, TRUE);

  timeout = g_new0 (gint64, 1);
  *timeout = -1; /* blocking */
  g_task_set_task_data (task, timeout, g_free);

  g_task_run_in_thread (task, handshake_thread);
  crank_sync_handshake_context (tls, cancellable);

  success = finish_handshake (tls, task, &my_error);
  g_object_unref (task);

  g_main_context_pop_thread_default (priv->handshake_context);
  g_clear_pointer (&priv->handshake_context, g_main_context_unref);

  yield_op (tls, G_TLS_CONNECTION_BASE_OP_HANDSHAKE,
            G_TLS_CONNECTION_BASE_OK);

  if (my_error)
    g_propagate_error (error, my_error);
  return success;
}


f_test_ignore_error(typval_T *argvars, typval_T *rettv UNUSED)
{
     ignore_error_for_testing(tv_get_string(&argvars[0]));
}


ex_foldopen(exarg_T *eap)
{
    opFoldRange(eap->line1, eap->line2, eap->cmdidx == CMD_foldopen,
							 eap->forceit, FALSE);
}


ex_folddo(exarg_T *eap)
{
    linenr_T	lnum;

#ifdef FEAT_CLIPBOARD
    start_global_changes();
#endif

    /* First set the marks for all lines closed/open. */
    for (lnum = eap->line1; lnum <= eap->line2; ++lnum)
	if (hasFolding(lnum, NULL, NULL) == (eap->cmdidx == CMD_folddoclosed))
	    ml_setmarked(lnum);

    /* Execute the command on the marked lines. */
    global_exe(eap->arg);
    ml_clearmarked();	   /* clear rest of the marks */
#ifdef FEAT_CLIPBOARD
    end_global_changes();
#endif
}


f_getreg(typval_T *argvars, typval_T *rettv)
{
    char_u	*strregname;
    int		regname;
    int		arg2 = FALSE;
    int		return_list = FALSE;
    int		error = FALSE;

    if (argvars[0].v_type != VAR_UNKNOWN)
    {
	strregname = tv_get_string_chk(&argvars[0]);
	error = strregname == NULL;
	if (argvars[1].v_type != VAR_UNKNOWN)
	{
	    arg2 = (int)tv_get_number_chk(&argvars[1], &error);
	    if (!error && argvars[2].v_type != VAR_UNKNOWN)
		return_list = (int)tv_get_number_chk(&argvars[2], &error);
	}
    }
    else
	strregname = get_vim_var_str(VV_REG);

    if (error)
	return;

    regname = (strregname == NULL ? '"' : *strregname);
    if (regname == 0)
	regname = '"';

    if (return_list)
    {
	rettv->v_type = VAR_LIST;
	rettv->vval.v_list = (list_T *)get_reg_contents(regname,
				      (arg2 ? GREG_EXPR_SRC : 0) | GREG_LIST);
	if (rettv->vval.v_list == NULL)
	    (void)rettv_list_alloc(rettv);
	else
	    ++rettv->vval.v_list->lv_refcount;
    }
    else
    {
	rettv->v_type = VAR_STRING;
	rettv->vval.v_string = get_reg_contents(regname,
						    arg2 ? GREG_EXPR_SRC : 0);
    }
}


f_hostname(typval_T *argvars UNUSED, typval_T *rettv)
{
    char_u hostname[256];

    mch_get_host_name(hostname, 256);
    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = vim_strsave(hostname);
}


f_getftime(typval_T *argvars, typval_T *rettv)
{
    char_u	*fname;
    stat_T	st;

    fname = tv_get_string(&argvars[0]);

    if (mch_stat((char *)fname, &st) >= 0)
	rettv->vval.v_number = (varnumber_T)st.st_mtime;
    else
	rettv->vval.v_number = -1;
}


f_libcall(typval_T *argvars, typval_T *rettv)
{
    libcall_common(argvars, rettv, VAR_STRING);
}


f_setloclist(typval_T *argvars, typval_T *rettv)
{
    win_T	*win;

    rettv->vval.v_number = -1;

    win = find_win_by_nr_or_id(&argvars[0]);
    if (win != NULL)
	set_qf_ll_list(win, &argvars[1], &argvars[2], &argvars[3], rettv);
}


ex_copymove(exarg_T *eap)
{
    long	n;

    n = get_address(eap, &eap->arg, eap->addr_type, FALSE, FALSE, FALSE, 1);
    if (eap->arg == NULL)	    /* error detected */
    {
	eap->nextcmd = NULL;
	return;
    }
    get_flags(eap);

    /*
     * move or copy lines from 'eap->line1'-'eap->line2' to below line 'n'
     */
    if (n == MAXLNUM || n < 0 || n > curbuf->b_ml.ml_line_count)
    {
	emsg(_(e_invaddr));
	return;
    }

    if (eap->cmdidx == CMD_move)
    {
	if (do_move(eap->line1, eap->line2, n) == FAIL)
	    return;
    }
    else
	ex_copy(eap->line1, eap->line2, n);
    u_clearline();
    beginline(BL_SOL | BL_FIX);
    ex_may_print(eap);
}


f_getcharsearch(typval_T *argvars UNUSED, typval_T *rettv)
{
    if (rettv_dict_alloc(rettv) != FAIL)
    {
	dict_T *dict = rettv->vval.v_dict;

	dict_add_string(dict, "char", last_csearch());
	dict_add_number(dict, "forward", last_csearch_forward());
	dict_add_number(dict, "until", last_csearch_until());
    }
}


uc_scan_attr(
    char_u	*attr,
    size_t	len,
    long	*argt,
    long	*def,
    int		*flags,
    int		*compl,
    char_u	**compl_arg,
    int		*addr_type_arg)
{
    char_u	*p;

    if (len == 0)
    {
	emsg(_("E175: No attribute specified"));
	return FAIL;
    }

    /* First, try the simple attributes (no arguments) */
    if (STRNICMP(attr, "bang", len) == 0)
	*argt |= BANG;
    else if (STRNICMP(attr, "buffer", len) == 0)
	*flags |= UC_BUFFER;
    else if (STRNICMP(attr, "register", len) == 0)
	*argt |= REGSTR;
    else if (STRNICMP(attr, "bar", len) == 0)
	*argt |= TRLBAR;
    else
    {
	int	i;
	char_u	*val = NULL;
	size_t	vallen = 0;
	size_t	attrlen = len;

	/* Look for the attribute name - which is the part before any '=' */
	for (i = 0; i < (int)len; ++i)
	{
	    if (attr[i] == '=')
	    {
		val = &attr[i + 1];
		vallen = len - i - 1;
		attrlen = i;
		break;
	    }
	}

	if (STRNICMP(attr, "nargs", attrlen) == 0)
	{
	    if (vallen == 1)
	    {
		if (*val == '0')
		    /* Do nothing - this is the default */;
		else if (*val == '1')
		    *argt |= (EXTRA | NOSPC | NEEDARG);
		else if (*val == '*')
		    *argt |= EXTRA;
		else if (*val == '?')
		    *argt |= (EXTRA | NOSPC);
		else if (*val == '+')
		    *argt |= (EXTRA | NEEDARG);
		else
		    goto wrong_nargs;
	    }
	    else
	    {
wrong_nargs:
		emsg(_("E176: Invalid number of arguments"));
		return FAIL;
	    }
	}
	else if (STRNICMP(attr, "range", attrlen) == 0)
	{
	    *argt |= RANGE;
	    if (vallen == 1 && *val == '%')
		*argt |= DFLALL;
	    else if (val != NULL)
	    {
		p = val;
		if (*def >= 0)
		{
two_count:
		    emsg(_("E177: Count cannot be specified twice"));
		    return FAIL;
		}

		*def = getdigits(&p);
		*argt |= (ZEROR | NOTADR);

		if (p != val + vallen || vallen == 0)
		{
invalid_count:
		    emsg(_("E178: Invalid default value for count"));
		    return FAIL;
		}
	    }
	}
	else if (STRNICMP(attr, "count", attrlen) == 0)
	{
	    *argt |= (COUNT | ZEROR | RANGE | NOTADR);

	    if (val != NULL)
	    {
		p = val;
		if (*def >= 0)
		    goto two_count;

		*def = getdigits(&p);

		if (p != val + vallen)
		    goto invalid_count;
	    }

	    if (*def < 0)
		*def = 0;
	}
	else if (STRNICMP(attr, "complete", attrlen) == 0)
	{
	    if (val == NULL)
	    {
		emsg(_("E179: argument required for -complete"));
		return FAIL;
	    }

	    if (parse_compl_arg(val, (int)vallen, compl, argt, compl_arg)
								      == FAIL)
		return FAIL;
	}
	else if (STRNICMP(attr, "addr", attrlen) == 0)
	{
	    *argt |= RANGE;
	    if (val == NULL)
	    {
		emsg(_("E179: argument required for -addr"));
		return FAIL;
	    }
	    if (parse_addr_type_arg(val, (int)vallen, argt, addr_type_arg)
								      == FAIL)
		return FAIL;
	    if (addr_type_arg != ADDR_LINES)
		*argt |= (ZEROR | NOTADR) ;
	}
	else
	{
	    char_u ch = attr[len];
	    attr[len] = '\0';
	    semsg(_("E181: Invalid attribute: %s"), attr);
	    attr[len] = ch;
	    return FAIL;
	}
    }

    return OK;
}


f_getcurpos(typval_T *argvars, typval_T *rettv)
{
    getpos_both(argvars, rettv, TRUE);
}


f_confirm(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
    char_u	*message;
    char_u	*buttons = NULL;
    char_u	buf[NUMBUFLEN];
    char_u	buf2[NUMBUFLEN];
    int		def = 1;
    int		type = VIM_GENERIC;
    char_u	*typestr;
    int		error = FALSE;

    message = tv_get_string_chk(&argvars[0]);
    if (message == NULL)
	error = TRUE;
    if (argvars[1].v_type != VAR_UNKNOWN)
    {
	buttons = tv_get_string_buf_chk(&argvars[1], buf);
	if (buttons == NULL)
	    error = TRUE;
	if (argvars[2].v_type != VAR_UNKNOWN)
	{
	    def = (int)tv_get_number_chk(&argvars[2], &error);
	    if (argvars[3].v_type != VAR_UNKNOWN)
	    {
		typestr = tv_get_string_buf_chk(&argvars[3], buf2);
		if (typestr == NULL)
		    error = TRUE;
		else
		{
		    switch (TOUPPER_ASC(*typestr))
		    {
			case 'E': type = VIM_ERROR; break;
			case 'Q': type = VIM_QUESTION; break;
			case 'I': type = VIM_INFO; break;
			case 'W': type = VIM_WARNING; break;
			case 'G': type = VIM_GENERIC; break;
		    }
		}
	    }
	}
    }

    if (buttons == NULL || *buttons == NUL)
	buttons = (char_u *)_("&Ok");

    if (!error)
	rettv->vval.v_number = do_dialog(type, NULL, message, buttons,
							    def, NULL, FALSE);
#endif
}


f_hasmapto(typval_T *argvars, typval_T *rettv)
{
    char_u	*name;
    char_u	*mode;
    char_u	buf[NUMBUFLEN];
    int		abbr = FALSE;

    name = tv_get_string(&argvars[0]);
    if (argvars[1].v_type == VAR_UNKNOWN)
	mode = (char_u *)"nvo";
    else
    {
	mode = tv_get_string_buf(&argvars[1], buf);
	if (argvars[2].v_type != VAR_UNKNOWN)
	    abbr = (int)tv_get_number(&argvars[2]);
    }

    if (map_to_exists(name, mode, abbr))
	rettv->vval.v_number = TRUE;
    else
	rettv->vval.v_number = FALSE;
}


ex_goto(exarg_T *eap)
{
    goto_byte(eap->line2);
}


f_getpid(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv->vval.v_number = mch_get_pid();
}


ex_mapclear(exarg_T *eap)
{
    map_clear(eap->cmd, eap->arg, eap->forceit, FALSE);
}


viminfo_error(char *errnum, char *message, char_u *line)
{
    vim_snprintf((char *)IObuff, IOSIZE, _("%sviminfo: %s in line: "),
							     errnum, message);
    STRNCAT(IObuff, line, IOSIZE - STRLEN(IObuff) - 1);
    if (IObuff[STRLEN(IObuff) - 1] == '\n')
	IObuff[STRLEN(IObuff) - 1] = NUL;
    emsg((char *)IObuff);
    if (++viminfo_errcnt >= 10)
    {
	emsg(_("E136: viminfo: Too many errors, skipping rest of file"));
	return TRUE;
    }
    return FALSE;
}


f_job_info(typval_T *argvars, typval_T *rettv)
{
    if (argvars[0].v_type != VAR_UNKNOWN)
    {
	job_T	*job = get_job_arg(&argvars[0]);

	if (job != NULL && rettv_dict_alloc(rettv) != FAIL)
	    job_info(job, rettv->vval.v_dict);
    }
    else if (rettv_list_alloc(rettv) == OK)
	job_info_all(rettv->vval.v_list);
}


expand_sfile(char_u *arg)
{
    char	*errormsg;
    int		len;
    char_u	*result;
    char_u	*newres;
    char_u	*repl;
    int		srclen;
    char_u	*p;

    result = vim_strsave(arg);
    if (result == NULL)
	return NULL;

    for (p = result; *p; )
    {
	if (STRNCMP(p, "<sfile>", 7) != 0)
	    ++p;
	else
	{
	    /* replace "<sfile>" with the sourced file name, and do ":" stuff */
	    repl = eval_vars(p, result, &srclen, NULL, &errormsg, NULL);
	    if (errormsg != NULL)
	    {
		if (*errormsg)
		    emsg(errormsg);
		vim_free(result);
		return NULL;
	    }
	    if (repl == NULL)		/* no match (cannot happen) */
	    {
		p += srclen;
		continue;
	    }
	    len = (int)STRLEN(result) - srclen + (int)STRLEN(repl) + 1;
	    newres = alloc(len);
	    if (newres == NULL)
	    {
		vim_free(repl);
		vim_free(result);
		return NULL;
	    }
	    mch_memmove(newres, result, (size_t)(p - result));
	    STRCPY(newres + (p - result), repl);
	    len = (int)STRLEN(newres);
	    STRCAT(newres, p + srclen);
	    vim_free(repl);
	    vim_free(result);
	    result = newres;
	    p = newres + len;		/* continue after the match */
	}
    }

    return result;
}


write_viminfo_version(FILE *fp_out)
{
    fprintf(fp_out, "# Viminfo version\n|%d,%d\n\n",
					    BARTYPE_VERSION, VIMINFO_VERSION);
}


f_test_scrollbar(typval_T *argvars, typval_T *rettv UNUSED)
{
    char_u	*which;
    long	value;
    int		dragging;
    scrollbar_T *sb = NULL;

    if (argvars[0].v_type != VAR_STRING
	    || (argvars[1].v_type) != VAR_NUMBER
	    || (argvars[2].v_type) != VAR_NUMBER)
    {
	emsg(_(e_invarg));
	return;
    }
    which = tv_get_string(&argvars[0]);
    value = tv_get_number(&argvars[1]);
    dragging = tv_get_number(&argvars[2]);

    if (STRCMP(which, "left") == 0)
	sb = &curwin->w_scrollbars[SBAR_LEFT];
    else if (STRCMP(which, "right") == 0)
	sb = &curwin->w_scrollbars[SBAR_RIGHT];
    else if (STRCMP(which, "hor") == 0)
	sb = &gui.bottom_sbar;
    if (sb == NULL)
    {
	semsg(_(e_invarg2), which);
	return;
    }
    gui_drag_scrollbar(sb, value, dragging);
# ifndef USE_ON_FLY_SCROLL
    // need to loop through normal_cmd() to handle the scroll events
    exec_normal(FALSE, TRUE, FALSE);
# endif
}


f_test_null_list(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv_list_set(rettv, NULL);
}


do_helptags(char_u *dirname, int add_help_tags)
{
#ifdef FEAT_MULTI_LANG
    int		len;
    int		i, j;
    garray_T	ga;
    char_u	lang[2];
    char_u	ext[5];
    char_u	fname[8];
    int		filecount;
    char_u	**files;

    /* Get a list of all files in the help directory and in subdirectories. */
    STRCPY(NameBuff, dirname);
    add_pathsep(NameBuff);
    STRCAT(NameBuff, "**");
    if (gen_expand_wildcards(1, &NameBuff, &filecount, &files,
						    EW_FILE|EW_SILENT) == FAIL
	    || filecount == 0)
    {
	semsg(_("E151: No match: %s"), NameBuff);
	return;
    }

    /* Go over all files in the directory to find out what languages are
     * present. */
    ga_init2(&ga, 1, 10);
    for (i = 0; i < filecount; ++i)
    {
	len = (int)STRLEN(files[i]);
	if (len > 4)
	{
	    if (STRICMP(files[i] + len - 4, ".txt") == 0)
	    {
		/* ".txt" -> language "en" */
		lang[0] = 'e';
		lang[1] = 'n';
	    }
	    else if (files[i][len - 4] == '.'
		    && ASCII_ISALPHA(files[i][len - 3])
		    && ASCII_ISALPHA(files[i][len - 2])
		    && TOLOWER_ASC(files[i][len - 1]) == 'x')
	    {
		/* ".abx" -> language "ab" */
		lang[0] = TOLOWER_ASC(files[i][len - 3]);
		lang[1] = TOLOWER_ASC(files[i][len - 2]);
	    }
	    else
		continue;

	    /* Did we find this language already? */
	    for (j = 0; j < ga.ga_len; j += 2)
		if (STRNCMP(lang, ((char_u *)ga.ga_data) + j, 2) == 0)
		    break;
	    if (j == ga.ga_len)
	    {
		/* New language, add it. */
		if (ga_grow(&ga, 2) == FAIL)
		    break;
		((char_u *)ga.ga_data)[ga.ga_len++] = lang[0];
		((char_u *)ga.ga_data)[ga.ga_len++] = lang[1];
	    }
	}
    }

    /*
     * Loop over the found languages to generate a tags file for each one.
     */
    for (j = 0; j < ga.ga_len; j += 2)
    {
	STRCPY(fname, "tags-xx");
	fname[5] = ((char_u *)ga.ga_data)[j];
	fname[6] = ((char_u *)ga.ga_data)[j + 1];
	if (fname[5] == 'e' && fname[6] == 'n')
	{
	    /* English is an exception: use ".txt" and "tags". */
	    fname[4] = NUL;
	    STRCPY(ext, ".txt");
	}
	else
	{
	    /* Language "ab" uses ".abx" and "tags-ab". */
	    STRCPY(ext, ".xxx");
	    ext[1] = fname[5];
	    ext[2] = fname[6];
	}
	helptags_one(dirname, ext, fname, add_help_tags);
    }

    ga_clear(&ga);
    FreeWild(filecount, files);

#else
    /* No language support, just use "*.txt" and "tags". */
    helptags_one(dirname, (char_u *)".txt", (char_u *)"tags", add_help_tags);
#endif
}


list2proftime(typval_T *arg, proftime_T *tm)
{
    long	n1, n2;
    int	error = FALSE;

    if (arg->v_type != VAR_LIST || arg->vval.v_list == NULL
					     || arg->vval.v_list->lv_len != 2)
	return FAIL;
    n1 = list_find_nr(arg->vval.v_list, 0L, &error);
    n2 = list_find_nr(arg->vval.v_list, 1L, &error);
# ifdef WIN3264
    tm->HighPart = n1;
    tm->LowPart = n2;
# else
    tm->tv_sec = n1;
    tm->tv_usec = n2;
# endif
    return error ? FAIL : OK;
}


f_ch_close(typval_T *argvars, typval_T *rettv UNUSED)
{
    channel_T *channel = get_channel_arg(&argvars[0], TRUE, FALSE, 0);

    if (channel != NULL)
    {
	channel_close(channel, FALSE);
	channel_clear(channel);
    }
}


f_getpos(typval_T *argvars, typval_T *rettv)
{
    getpos_both(argvars, rettv, FALSE);
}


f_spellbadword(typval_T *argvars UNUSED, typval_T *rettv)
{
    char_u	*word = (char_u *)"";
    hlf_T	attr = HLF_COUNT;
    int		len = 0;

    if (rettv_list_alloc(rettv) == FAIL)
	return;

#ifdef FEAT_SPELL
    if (argvars[0].v_type == VAR_UNKNOWN)
    {
	/* Find the start and length of the badly spelled word. */
	len = spell_move_to(curwin, FORWARD, TRUE, TRUE, &attr);
	if (len != 0)
	{
	    word = ml_get_cursor();
	    curwin->w_set_curswant = TRUE;
	}
    }
    else if (curwin->w_p_spell && *curbuf->b_s.b_p_spl != NUL)
    {
	char_u	*str = tv_get_string_chk(&argvars[0]);
	int	capcol = -1;

	if (str != NULL)
	{
	    /* Check the argument for spelling. */
	    while (*str != NUL)
	    {
		len = spell_check(curwin, str, &attr, &capcol, FALSE);
		if (attr != HLF_COUNT)
		{
		    word = str;
		    break;
		}
		str += len;
		capcol -= len;
	    }
	}
    }
#endif

    list_append_string(rettv->vval.v_list, word, len);
    list_append_string(rettv->vval.v_list, (char_u *)(
			attr == HLF_SPB ? "bad" :
			attr == HLF_SPR ? "rare" :
			attr == HLF_SPL ? "local" :
			attr == HLF_SPC ? "caps" :
			""), -1);
}


f_ch_canread(typval_T *argvars, typval_T *rettv)
{
    channel_T *channel = get_channel_arg(&argvars[0], FALSE, FALSE, 0);

    rettv->vval.v_number = 0;
    if (channel != NULL)
	rettv->vval.v_number = channel_has_readahead(channel, PART_SOCK)
			    || channel_has_readahead(channel, PART_OUT)
			    || channel_has_readahead(channel, PART_ERR);
}


alist_new(void)
{
    curwin->w_alist = (alist_T *)alloc((unsigned)sizeof(alist_T));
    if (curwin->w_alist == NULL)
    {
	curwin->w_alist = &global_alist;
	++global_alist.al_refcount;
    }
    else
    {
	curwin->w_alist->al_refcount = 1;
	curwin->w_alist->id = ++max_alist_id;
	alist_init(curwin->w_alist);
    }
}


ex_may_print(exarg_T *eap)
{
    if (eap->flags != 0)
    {
	print_line(curwin->w_cursor.lnum, (eap->flags & EXFLAG_NR),
						  (eap->flags & EXFLAG_LIST));
	ex_no_reprint = TRUE;
    }
}


do_searchpair(
    char_u	*spat,	    /* start pattern */
    char_u	*mpat,	    /* middle pattern */
    char_u	*epat,	    /* end pattern */
    int		dir,	    /* BACKWARD or FORWARD */
    typval_T	*skip,	    /* skip expression */
    int		flags,	    /* SP_SETPCMARK and other SP_ values */
    pos_T	*match_pos,
    linenr_T	lnum_stop,  /* stop at this line if not zero */
    long	time_limit UNUSED) /* stop after this many msec */
{
    char_u	*save_cpo;
    char_u	*pat, *pat2 = NULL, *pat3 = NULL;
    long	retval = 0;
    pos_T	pos;
    pos_T	firstpos;
    pos_T	foundpos;
    pos_T	save_cursor;
    pos_T	save_pos;
    int		n;
    int		r;
    int		nest = 1;
    int		use_skip = FALSE;
    int		err;
    int		options = SEARCH_KEEP;
    proftime_T	tm;

    /* Make 'cpoptions' empty, the 'l' flag should not be used here. */
    save_cpo = p_cpo;
    p_cpo = empty_option;

#ifdef FEAT_RELTIME
    /* Set the time limit, if there is one. */
    profile_setlimit(time_limit, &tm);
#endif

    /* Make two search patterns: start/end (pat2, for in nested pairs) and
     * start/middle/end (pat3, for the top pair). */
    pat2 = alloc((unsigned)(STRLEN(spat) + STRLEN(epat) + 17));
    pat3 = alloc((unsigned)(STRLEN(spat) + STRLEN(mpat) + STRLEN(epat) + 25));
    if (pat2 == NULL || pat3 == NULL)
	goto theend;
    sprintf((char *)pat2, "\\m\\(%s\\m\\)\\|\\(%s\\m\\)", spat, epat);
    if (*mpat == NUL)
	STRCPY(pat3, pat2);
    else
	sprintf((char *)pat3, "\\m\\(%s\\m\\)\\|\\(%s\\m\\)\\|\\(%s\\m\\)",
							    spat, epat, mpat);
    if (flags & SP_START)
	options |= SEARCH_START;

    if (skip != NULL)
    {
	/* Empty string means to not use the skip expression. */
	if (skip->v_type == VAR_STRING || skip->v_type == VAR_FUNC)
	    use_skip = skip->vval.v_string != NULL
						&& *skip->vval.v_string != NUL;
    }

    save_cursor = curwin->w_cursor;
    pos = curwin->w_cursor;
    CLEAR_POS(&firstpos);
    CLEAR_POS(&foundpos);
    pat = pat3;
    for (;;)
    {
	n = searchit(curwin, curbuf, &pos, NULL, dir, pat, 1L,
				     options, RE_SEARCH, lnum_stop, &tm, NULL);
	if (n == FAIL || (firstpos.lnum != 0 && EQUAL_POS(pos, firstpos)))
	    /* didn't find it or found the first match again: FAIL */
	    break;

	if (firstpos.lnum == 0)
	    firstpos = pos;
	if (EQUAL_POS(pos, foundpos))
	{
	    /* Found the same position again.  Can happen with a pattern that
	     * has "\zs" at the end and searching backwards.  Advance one
	     * character and try again. */
	    if (dir == BACKWARD)
		decl(&pos);
	    else
		incl(&pos);
	}
	foundpos = pos;

	/* clear the start flag to avoid getting stuck here */
	options &= ~SEARCH_START;

	/* If the skip pattern matches, ignore this match. */
	if (use_skip)
	{
	    save_pos = curwin->w_cursor;
	    curwin->w_cursor = pos;
	    err = FALSE;
	    r = eval_expr_to_bool(skip, &err);
	    curwin->w_cursor = save_pos;
	    if (err)
	    {
		/* Evaluating {skip} caused an error, break here. */
		curwin->w_cursor = save_cursor;
		retval = -1;
		break;
	    }
	    if (r)
		continue;
	}

	if ((dir == BACKWARD && n == 3) || (dir == FORWARD && n == 2))
	{
	    /* Found end when searching backwards or start when searching
	     * forward: nested pair. */
	    ++nest;
	    pat = pat2;		/* nested, don't search for middle */
	}
	else
	{
	    /* Found end when searching forward or start when searching
	     * backward: end of (nested) pair; or found middle in outer pair. */
	    if (--nest == 1)
		pat = pat3;	/* outer level, search for middle */
	}

	if (nest == 0)
	{
	    /* Found the match: return matchcount or line number. */
	    if (flags & SP_RETCOUNT)
		++retval;
	    else
		retval = pos.lnum;
	    if (flags & SP_SETPCMARK)
		setpcmark();
	    curwin->w_cursor = pos;
	    if (!(flags & SP_REPEAT))
		break;
	    nest = 1;	    /* search for next unmatched */
	}
    }

    if (match_pos != NULL)
    {
	/* Store the match cursor position */
	match_pos->lnum = curwin->w_cursor.lnum;
	match_pos->col = curwin->w_cursor.col + 1;
    }

    /* If 'n' flag is used or search failed: restore cursor position. */
    if ((flags & SP_NOMOVE) || retval == 0)
	curwin->w_cursor = save_cursor;

theend:
    vim_free(pat2);
    vim_free(pat3);
    if (p_cpo == empty_option)
	p_cpo = save_cpo;
    else
	/* Darn, evaluating the {skip} expression changed the value. */
	free_string_option(save_cpo);

    return retval;
}


f_getbufinfo(typval_T *argvars, typval_T *rettv)
{
    buf_T	*buf = NULL;
    buf_T	*argbuf = NULL;
    dict_T	*d;
    int		filtered = FALSE;
    int		sel_buflisted = FALSE;
    int		sel_bufloaded = FALSE;
    int		sel_bufmodified = FALSE;

    if (rettv_list_alloc(rettv) != OK)
	return;

    /* List of all the buffers or selected buffers */
    if (argvars[0].v_type == VAR_DICT)
    {
	dict_T	*sel_d = argvars[0].vval.v_dict;

	if (sel_d != NULL)
	{
	    dictitem_T	*di;

	    filtered = TRUE;

	    di = dict_find(sel_d, (char_u *)"buflisted", -1);
	    if (di != NULL && tv_get_number(&di->di_tv))
		sel_buflisted = TRUE;

	    di = dict_find(sel_d, (char_u *)"bufloaded", -1);
	    if (di != NULL && tv_get_number(&di->di_tv))
		sel_bufloaded = TRUE;

	    di = dict_find(sel_d, (char_u *)"bufmodified", -1);
	    if (di != NULL && tv_get_number(&di->di_tv))
		sel_bufmodified = TRUE;
	}
    }
    else if (argvars[0].v_type != VAR_UNKNOWN)
    {
	/* Information about one buffer.  Argument specifies the buffer */
	(void)tv_get_number(&argvars[0]);   /* issue errmsg if type error */
	++emsg_off;
	argbuf = tv_get_buf(&argvars[0], FALSE);
	--emsg_off;
	if (argbuf == NULL)
	    return;
    }

    /* Return information about all the buffers or a specified buffer */
    FOR_ALL_BUFFERS(buf)
    {
	if (argbuf != NULL && argbuf != buf)
	    continue;
	if (filtered && ((sel_bufloaded && buf->b_ml.ml_mfp == NULL)
			|| (sel_buflisted && !buf->b_p_bl)
			|| (sel_bufmodified && !buf->b_changed)))
	    continue;

	d = get_buffer_info(buf);
	if (d != NULL)
	    list_append_dict(rettv->vval.v_list, d);
	if (argbuf != NULL)
	    return;
    }
}


f_executable(typval_T *argvars, typval_T *rettv)
{
    char_u *name = tv_get_string(&argvars[0]);

    /* Check in $PATH and also check directly if there is a directory name. */
    rettv->vval.v_number = mch_can_exe(name, NULL, TRUE)
		 || (gettail(name) != name && mch_can_exe(name, NULL, FALSE));
}


f_wincol(typval_T *argvars UNUSED, typval_T *rettv)
{
    validate_cursor();
    rettv->vval.v_number = curwin->w_wcol + 1;
}


f_trunc(typval_T *argvars, typval_T *rettv)
{
    float_T	f = 0.0;

    rettv->v_type = VAR_FLOAT;
    if (get_float_arg(argvars, &f) == OK)
	/* trunc() is not in C90, use floor() or ceil() instead. */
	rettv->vval.v_float = f > 0 ? floor(f) : ceil(f);
    else
	rettv->vval.v_float = 0.0;
}


ex_redraw(exarg_T *eap)
{
    int		r = RedrawingDisabled;
    int		p = p_lz;

    RedrawingDisabled = 0;
    p_lz = FALSE;
    validate_cursor();
    update_topline();
    update_screen(eap->forceit ? CLEAR : VIsual_active ? INVERTED : 0);
#ifdef FEAT_TITLE
    if (need_maketitle)
	maketitle();
#endif
    RedrawingDisabled = r;
    p_lz = p;

    /* Reset msg_didout, so that a message that's there is overwritten. */
    msg_didout = FALSE;
    msg_col = 0;

    /* No need to wait after an intentional redraw. */
    need_wait_return = FALSE;

    out_flush();
}


f_finddir(typval_T *argvars, typval_T *rettv)
{
    findfilendir(argvars, rettv, FINDFILE_DIR);
}


f_delete(typval_T *argvars, typval_T *rettv)
{
    char_u	nbuf[NUMBUFLEN];
    char_u	*name;
    char_u	*flags;

    rettv->vval.v_number = -1;
    if (check_restricted() || check_secure())
	return;

    name = tv_get_string(&argvars[0]);
    if (name == NULL || *name == NUL)
    {
	emsg(_(e_invarg));
	return;
    }

    if (argvars[1].v_type != VAR_UNKNOWN)
	flags = tv_get_string_buf(&argvars[1], nbuf);
    else
	flags = (char_u *)"";

    if (*flags == NUL)
	/* delete a file */
	rettv->vval.v_number = mch_remove(name) == 0 ? 0 : -1;
    else if (STRCMP(flags, "d") == 0)
	/* delete an empty directory */
	rettv->vval.v_number = mch_rmdir(name) == 0 ? 0 : -1;
    else if (STRCMP(flags, "rf") == 0)
	/* delete a directory recursively */
	rettv->vval.v_number = delete_recursive(name);
    else
	semsg(_(e_invexpr2), flags);
}


check_secure(void)
{
    if (secure)
    {
	secure = 2;
	emsg(_(e_curdir));
	return TRUE;
    }
#ifdef HAVE_SANDBOX
    /*
     * In the sandbox more things are not allowed, including the things
     * disallowed in secure mode.
     */
    if (sandbox != 0)
    {
	emsg(_(e_sandbox));
	return TRUE;
    }
#endif
    return FALSE;
}


f_ch_status(typval_T *argvars, typval_T *rettv)
{
    channel_T	*channel;
    jobopt_T	opt;
    int		part = -1;

    /* return an empty string by default */
    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = NULL;

    channel = get_channel_arg(&argvars[0], FALSE, FALSE, 0);

    if (argvars[1].v_type != VAR_UNKNOWN)
    {
	clear_job_options(&opt);
	if (get_job_options(&argvars[1], &opt, JO_PART, 0) == OK
						     && (opt.jo_set & JO_PART))
	    part = opt.jo_part;
    }

    rettv->vval.v_string = vim_strsave((char_u *)channel_status(channel, part));
}


f_getftype(typval_T *argvars, typval_T *rettv)
{
    char_u	*fname;
    stat_T	st;
    char_u	*type = NULL;
    char	*t;

    fname = tv_get_string(&argvars[0]);

    rettv->v_type = VAR_STRING;
    if (mch_lstat((char *)fname, &st) >= 0)
    {
	if (S_ISREG(st.st_mode))
	    t = "file";
	else if (S_ISDIR(st.st_mode))
	    t = "dir";
	else if (S_ISLNK(st.st_mode))
	    t = "link";
	else if (S_ISBLK(st.st_mode))
	    t = "bdev";
	else if (S_ISCHR(st.st_mode))
	    t = "cdev";
	else if (S_ISFIFO(st.st_mode))
	    t = "fifo";
	else if (S_ISSOCK(st.st_mode))
	    t = "socket";
	else
	    t = "other";
	type = vim_strsave((char_u *)t);
    }
    rettv->vval.v_string = type;
}


check_readonly(int *forceit, buf_T *buf)
{
    stat_T	st;

    /* Handle a file being readonly when the 'readonly' option is set or when
     * the file exists and permissions are read-only.
     * We will send 0777 to check_file_readonly(), as the "perm" variable is
     * important for device checks but not here. */
    if (!*forceit && (buf->b_p_ro
		|| (mch_stat((char *)buf->b_ffname, &st) >= 0
		    && check_file_readonly(buf->b_ffname, 0777))))
    {
#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
	if ((p_confirm || cmdmod.confirm) && buf->b_fname != NULL)
	{
	    char_u	buff[DIALOG_MSG_SIZE];

	    if (buf->b_p_ro)
		dialog_msg(buff, _("'readonly' option is set for \"%s\".\nDo you wish to write anyway?"),
		    buf->b_fname);
	    else
		dialog_msg(buff, _("File permissions of \"%s\" are read-only.\nIt may still be possible to write it.\nDo you wish to try?"),
		    buf->b_fname);

	    if (vim_dialog_yesno(VIM_QUESTION, NULL, buff, 2) == VIM_YES)
	    {
		/* Set forceit, to force the writing of a readonly file */
		*forceit = TRUE;
		return FALSE;
	    }
	    else
		return TRUE;
	}
	else
#endif
	if (buf->b_p_ro)
	    emsg(_(e_readonly));
	else
	    semsg(_("E505: \"%s\" is read-only (add ! to override)"),
		    buf->b_fname);
	return TRUE;
    }

    return FALSE;
}


ex_syncbind(exarg_T *eap UNUSED)
{
    win_T	*wp;
    win_T	*save_curwin = curwin;
    buf_T	*save_curbuf = curbuf;
    long	topline;
    long	y;
    linenr_T	old_linenr = curwin->w_cursor.lnum;

    setpcmark();

    /*
     * determine max topline
     */
    if (curwin->w_p_scb)
    {
	topline = curwin->w_topline;
	FOR_ALL_WINDOWS(wp)
	{
	    if (wp->w_p_scb && wp->w_buffer)
	    {
		y = wp->w_buffer->b_ml.ml_line_count - get_scrolloff_value();
		if (topline > y)
		    topline = y;
	    }
	}
	if (topline < 1)
	    topline = 1;
    }
    else
    {
	topline = 1;
    }

    /*
     * Set all scrollbind windows to the same topline.
     */
    FOR_ALL_WINDOWS(curwin)
    {
	if (curwin->w_p_scb)
	{
	    curbuf = curwin->w_buffer;
	    y = topline - curwin->w_topline;
	    if (y > 0)
		scrollup(y, TRUE);
	    else
		scrolldown(-y, TRUE);
	    curwin->w_scbind_pos = topline;
	    redraw_later(VALID);
	    cursor_correct();
	    curwin->w_redr_status = TRUE;
	}
    }
    curwin = save_curwin;
    curbuf = save_curbuf;
    if (curwin->w_p_scb)
    {
	did_syncbind = TRUE;
	checkpcmark();
	if (old_linenr != curwin->w_cursor.lnum)
	{
	    char_u ctrl_o[2];

	    ctrl_o[0] = Ctrl_O;
	    ctrl_o[1] = 0;
	    ins_typebuf(ctrl_o, REMAP_NONE, 0, TRUE, FALSE);
	}
    }
}


ex_quit(exarg_T *eap)
{
    win_T	*wp;

#ifdef FEAT_CMDWIN
    if (cmdwin_type != 0)
    {
	cmdwin_result = Ctrl_C;
	return;
    }
#endif
    /* Don't quit while editing the command line. */
    if (text_locked())
    {
	text_locked_msg();
	return;
    }
    if (eap->addr_count > 0)
    {
	int	wnr = eap->line2;

	for (wp = firstwin; wp->w_next != NULL; wp = wp->w_next)
	    if (--wnr <= 0)
		break;
    }
    else
	wp = curwin;

    /* Refuse to quit when locked. */
    if (curbuf_locked())
	return;

    /* Trigger QuitPre and maybe ExitPre */
    if (before_quit_autocmds(wp, FALSE, eap->forceit))
	return;

#ifdef FEAT_NETBEANS_INTG
    netbeansForcedQuit = eap->forceit;
#endif

    /*
     * If there are more files or windows we won't exit.
     */
    if (check_more(FALSE, eap->forceit) == OK && only_one_window())
	exiting = TRUE;
    if ((!buf_hide(wp->w_buffer)
		&& check_changed(wp->w_buffer, (p_awa ? CCGD_AW : 0)
				       | (eap->forceit ? CCGD_FORCEIT : 0)
				       | CCGD_EXCMD))
	    || check_more(TRUE, eap->forceit) == FAIL
	    || (only_one_window() && check_changed_any(eap->forceit, TRUE)))
    {
	not_exiting();
    }
    else
    {
	/* quit last window
	 * Note: only_one_window() returns true, even so a help window is
	 * still open. In that case only quit, if no address has been
	 * specified. Example:
	 * :h|wincmd w|1q     - don't quit
	 * :h|wincmd w|q      - quit
	 */
	if (only_one_window() && (ONE_WINDOW || eap->addr_count == 0))
	    getout(0);
	not_exiting();
#ifdef FEAT_GUI
	need_mouse_correct = TRUE;
#endif
	/* close window; may free buffer */
	win_close(wp, !buf_hide(wp->w_buffer) || eap->forceit);
    }
}


f_shiftwidth(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv->vval.v_number = 0;

    if (argvars[0].v_type != VAR_UNKNOWN)
    {
	long	col;

	col = (long)tv_get_number_chk(argvars, NULL);
	if (col < 0)
	    return;	// type error; errmsg already given
#ifdef FEAT_VARTABS
	rettv->vval.v_number = get_sw_value_col(curbuf, col);
	return;
#endif
    }

    rettv->vval.v_number = get_sw_value(curbuf);
}


no_viminfo(void)
{
    /* "vim -i NONE" does not read or write a viminfo file */
    return STRCMP(p_viminfofile, "NONE") == 0;
}


f_sign_undefine(typval_T *argvars, typval_T *rettv)
{
    char_u *name;

    rettv->vval.v_number = -1;

    if (argvars[0].v_type == VAR_UNKNOWN)
    {
	// Free all the signs
	free_signs();
	rettv->vval.v_number = 0;
    }
    else
    {
	// Free only the specified sign
	name = tv_get_string_chk(&argvars[0]);
	if (name == NULL)
	    return;

	if (sign_undefine_by_name(name) == OK)
	    rettv->vval.v_number = 0;
    }
}


f_winline(typval_T *argvars UNUSED, typval_T *rettv)
{
    validate_cursor();
    rettv->vval.v_number = curwin->w_wrow + 1;
}


f_getloclist(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
#ifdef FEAT_QUICKFIX
    win_T	*wp;

    wp = find_win_by_nr_or_id(&argvars[0]);
    get_qf_loc_list(FALSE, wp, &argvars[1], rettv);
#endif
}


get_view_file(int c)
{
    int		len = 0;
    char_u	*p, *s;
    char_u	*retval;
    char_u	*sname;

    if (curbuf->b_ffname == NULL)
    {
	emsg(_(e_noname));
	return NULL;
    }
    sname = home_replace_save(NULL, curbuf->b_ffname);
    if (sname == NULL)
	return NULL;

    /*
     * We want a file name without separators, because we're not going to make
     * a directory.
     * "normal" path separator	-> "=+"
     * "="			-> "=="
     * ":" path separator	-> "=-"
     */
    for (p = sname; *p; ++p)
	if (*p == '=' || vim_ispathsep(*p))
	    ++len;
    retval = alloc((unsigned)(STRLEN(sname) + len + STRLEN(p_vdir) + 9));
    if (retval != NULL)
    {
	STRCPY(retval, p_vdir);
	add_pathsep(retval);
	s = retval + STRLEN(retval);
	for (p = sname; *p; ++p)
	{
	    if (*p == '=')
	    {
		*s++ = '=';
		*s++ = '=';
	    }
	    else if (vim_ispathsep(*p))
	    {
		*s++ = '=';
#if defined(BACKSLASH_IN_FILENAME) || defined(AMIGA) || defined(VMS)
		if (*p == ':')
		    *s++ = '-';
		else
#endif
		    *s++ = '+';
	    }
	    else
		*s++ = *p;
	}
	*s++ = '=';
	*s++ = c;
	STRCPY(s, ".vim");
    }

    vim_free(sname);
    return retval;
}


viminfo_readline(vir_T *virp)
{
    return vim_fgets(virp->vir_line, LSIZE, virp->vir_fd);
}


ex_wnext(exarg_T *eap)
{
    int		i;

    if (eap->cmd[1] == 'n')
	i = curwin->w_arg_idx + (int)eap->line2;
    else
	i = curwin->w_arg_idx - (int)eap->line2;
    eap->line1 = 1;
    eap->line2 = curbuf->b_ml.ml_line_count;
    if (do_write(eap) != FAIL)
	do_argfile(eap, i);
}


f_round(typval_T *argvars, typval_T *rettv)
{
    float_T	f = 0.0;

    rettv->v_type = VAR_FLOAT;
    if (get_float_arg(argvars, &f) == OK)
	rettv->vval.v_float = vim_round(f);
    else
	rettv->vval.v_float = 0.0;
}


alist_init(alist_T *al)
{
    ga_init2(&al->al_ga, (int)sizeof(aentry_T), 5);
}


f_mode(typval_T *argvars, typval_T *rettv)
{
    char_u	buf[4];

    vim_memset(buf, 0, sizeof(buf));

    if (time_for_testing == 93784)
    {
	/* Testing the two-character code. */
	buf[0] = 'x';
	buf[1] = '!';
    }
#ifdef FEAT_TERMINAL
    else if (term_use_loop())
	buf[0] = 't';
#endif
    else if (VIsual_active)
    {
	if (VIsual_select)
	    buf[0] = VIsual_mode + 's' - 'v';
	else
	    buf[0] = VIsual_mode;
    }
    else if (State == HITRETURN || State == ASKMORE || State == SETWSIZE
		|| State == CONFIRM)
    {
	buf[0] = 'r';
	if (State == ASKMORE)
	    buf[1] = 'm';
	else if (State == CONFIRM)
	    buf[1] = '?';
    }
    else if (State == EXTERNCMD)
	buf[0] = '!';
    else if (State & INSERT)
    {
	if (State & VREPLACE_FLAG)
	{
	    buf[0] = 'R';
	    buf[1] = 'v';
	}
	else
	{
	    if (State & REPLACE_FLAG)
		buf[0] = 'R';
	    else
		buf[0] = 'i';
#ifdef FEAT_INS_EXPAND
	    if (ins_compl_active())
		buf[1] = 'c';
	    else if (ctrl_x_mode_not_defined_yet())
		buf[1] = 'x';
#endif
	}
    }
    else if ((State & CMDLINE) || exmode_active)
    {
	buf[0] = 'c';
	if (exmode_active == EXMODE_VIM)
	    buf[1] = 'v';
	else if (exmode_active == EXMODE_NORMAL)
	    buf[1] = 'e';
    }
    else
    {
	buf[0] = 'n';
	if (finish_op)
	{
	    buf[1] = 'o';
	    // to be able to detect force-linewise/blockwise/characterwise operations
	    buf[2] = motion_force;
	}
	else if (restart_edit == 'I' || restart_edit == 'R'
							|| restart_edit == 'V')
	{
	    buf[1] = 'i';
	    buf[2] = restart_edit;
	}
    }

    /* Clear out the minor mode when the argument is not a non-zero number or
     * non-empty string.  */
    if (!non_zero_arg(&argvars[0]))
	buf[1] = NUL;

    rettv->vval.v_string = vim_strsave(buf);
    rettv->v_type = VAR_STRING;
}


f_inputsecret(typval_T *argvars, typval_T *rettv)
{
    ++cmdline_star;
    ++inputsecret_flag;
    f_input(argvars, rettv);
    --cmdline_star;
    --inputsecret_flag;
}


f_winwidth(typval_T *argvars, typval_T *rettv)
{
    win_T	*wp;

    wp = find_win_by_nr_or_id(&argvars[0]);
    if (wp == NULL)
	rettv->vval.v_number = -1;
    else
	rettv->vval.v_number = wp->w_width;
}


non_zero_arg(typval_T *argvars)
{
    return ((argvars[0].v_type == VAR_NUMBER
		&& argvars[0].vval.v_number != 0)
	    || (argvars[0].v_type == VAR_SPECIAL
		&& argvars[0].vval.v_number == VVAL_TRUE)
	    || (argvars[0].v_type == VAR_STRING
		&& argvars[0].vval.v_string != NULL
		&& *argvars[0].vval.v_string != NUL));
}


ex_later(exarg_T *eap)
{
    long	count = 0;
    int		sec = FALSE;
    int		file = FALSE;
    char_u	*p = eap->arg;

    if (*p == NUL)
	count = 1;
    else if (isdigit(*p))
    {
	count = getdigits(&p);
	switch (*p)
	{
	    case 's': ++p; sec = TRUE; break;
	    case 'm': ++p; sec = TRUE; count *= 60; break;
	    case 'h': ++p; sec = TRUE; count *= 60 * 60; break;
	    case 'd': ++p; sec = TRUE; count *= 24 * 60 * 60; break;
	    case 'f': ++p; file = TRUE; break;
	}
    }

    if (*p != NUL)
	semsg(_(e_invarg2), eap->arg);
    else
	undo_time(eap->cmdidx == CMD_earlier ? -count : count,
							    sec, file, FALSE);
}


get_qf_loc_list(int is_qf, win_T *wp, typval_T *what_arg, typval_T *rettv)
{
    if (what_arg->v_type == VAR_UNKNOWN)
    {
	if (rettv_list_alloc(rettv) == OK)
	    if (is_qf || wp != NULL)
		(void)get_errorlist(NULL, wp, -1, rettv->vval.v_list);
    }
    else
    {
	if (rettv_dict_alloc(rettv) == OK)
	    if (is_qf || (wp != NULL))
	    {
		if (what_arg->v_type == VAR_DICT)
		{
		    dict_T	*d = what_arg->vval.v_dict;

		    if (d != NULL)
			qf_get_properties(wp, d, rettv->vval.v_dict);
		}
		else
		    emsg(_(e_dictreq));
	    }
    }
}


set_qf_ll_list(
    win_T	*wp UNUSED,
    typval_T	*list_arg UNUSED,
    typval_T	*action_arg UNUSED,
    typval_T	*what_arg UNUSED,
    typval_T	*rettv)
{
#ifdef FEAT_QUICKFIX
    static char *e_invact = N_("E927: Invalid action: '%s'");
    char_u	*act;
    int		action = 0;
    static int	recursive = 0;
#endif

    rettv->vval.v_number = -1;

#ifdef FEAT_QUICKFIX
    if (list_arg->v_type != VAR_LIST)
	emsg(_(e_listreq));
    else if (recursive != 0)
	emsg(_(e_au_recursive));
    else
    {
	list_T  *l = list_arg->vval.v_list;
	dict_T	*d = NULL;
	int	valid_dict = TRUE;

	if (action_arg->v_type == VAR_STRING)
	{
	    act = tv_get_string_chk(action_arg);
	    if (act == NULL)
		return;		/* type error; errmsg already given */
	    if ((*act == 'a' || *act == 'r' || *act == ' ' || *act == 'f') &&
		    act[1] == NUL)
		action = *act;
	    else
		semsg(_(e_invact), act);
	}
	else if (action_arg->v_type == VAR_UNKNOWN)
	    action = ' ';
	else
	    emsg(_(e_stringreq));

	if (action_arg->v_type != VAR_UNKNOWN
		&& what_arg->v_type != VAR_UNKNOWN)
	{
	    if (what_arg->v_type == VAR_DICT)
		d = what_arg->vval.v_dict;
	    else
	    {
		emsg(_(e_dictreq));
		valid_dict = FALSE;
	    }
	}

	++recursive;
	if (l != NULL && action && valid_dict && set_errorlist(wp, l, action,
		     (char_u *)(wp == NULL ? ":setqflist()" : ":setloclist()"),
		     d) == OK)
	    rettv->vval.v_number = 0;
	--recursive;
    }
#endif
}


ex_startinsert(exarg_T *eap)
{
    if (eap->forceit)
    {
	/* cursor line can be zero on startup */
	if (!curwin->w_cursor.lnum)
	    curwin->w_cursor.lnum = 1;
	coladvance((colnr_T)MAXCOL);
	curwin->w_curswant = MAXCOL;
	curwin->w_set_curswant = FALSE;
    }

    /* Ignore the command when already in Insert mode.  Inserting an
     * expression register that invokes a function can do this. */
    if (State & INSERT)
	return;

    if (eap->cmdidx == CMD_startinsert)
	restart_edit = 'a';
    else if (eap->cmdidx == CMD_startreplace)
	restart_edit = 'R';
    else
	restart_edit = 'V';

    if (!eap->forceit)
    {
	if (eap->cmdidx == CMD_startinsert)
	    restart_edit = 'i';
	curwin->w_curswant = 0;	    /* avoid MAXCOL */
    }
}


f_matchaddpos(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
#ifdef FEAT_SEARCH_EXTRA
    char_u	buf[NUMBUFLEN];
    char_u	*group;
    int		prio = 10;
    int		id = -1;
    int		error = FALSE;
    list_T	*l;
    char_u	*conceal_char = NULL;
    win_T	*win = curwin;

    rettv->vval.v_number = -1;

    group = tv_get_string_buf_chk(&argvars[0], buf);
    if (group == NULL)
	return;

    if (argvars[1].v_type != VAR_LIST)
    {
	semsg(_(e_listarg), "matchaddpos()");
	return;
    }
    l = argvars[1].vval.v_list;
    if (l == NULL)
	return;

    if (argvars[2].v_type != VAR_UNKNOWN)
    {
	prio = (int)tv_get_number_chk(&argvars[2], &error);
	if (argvars[3].v_type != VAR_UNKNOWN)
	{
	    id = (int)tv_get_number_chk(&argvars[3], &error);

	    if (argvars[4].v_type != VAR_UNKNOWN
		&& matchadd_dict_arg(&argvars[4], &conceal_char, &win) == FAIL)
		return;
	}
    }
    if (error == TRUE)
	return;

    /* id == 3 is ok because matchaddpos() is supposed to substitute :3match */
    if (id == 1 || id == 2)
    {
	semsg(_("E798: ID is reserved for \":match\": %d"), id);
	return;
    }

    rettv->vval.v_number = match_add(win, group, NULL, prio, id, l,
								conceal_char);
#endif
}


mzscheme_call_vim(char_u *name, typval_T *args, typval_T *rettv)
{
    typval_T argvars[3];

    argvars[0].v_type = VAR_STRING;
    argvars[0].vval.v_string = name;
    copy_tv(args, &argvars[1]);
    argvars[2].v_type = VAR_UNKNOWN;
    f_call(argvars, rettv);
    clear_tv(&argvars[1]);
}


f_synIDattr(typval_T *argvars UNUSED, typval_T *rettv)
{
    char_u	*p = NULL;
#ifdef FEAT_SYN_HL
    int		id;
    char_u	*what;
    char_u	*mode;
    char_u	modebuf[NUMBUFLEN];
    int		modec;

    id = (int)tv_get_number(&argvars[0]);
    what = tv_get_string(&argvars[1]);
    if (argvars[2].v_type != VAR_UNKNOWN)
    {
	mode = tv_get_string_buf(&argvars[2], modebuf);
	modec = TOLOWER_ASC(mode[0]);
	if (modec != 't' && modec != 'c' && modec != 'g')
	    modec = 0;	/* replace invalid with current */
    }
    else
    {
#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
	if (USE_24BIT)
	    modec = 'g';
	else
#endif
	    if (t_colors > 1)
	    modec = 'c';
	else
	    modec = 't';
    }

    switch (TOLOWER_ASC(what[0]))
    {
	case 'b':
		if (TOLOWER_ASC(what[1]) == 'g')	/* bg[#] */
		    p = highlight_color(id, what, modec);
		else					/* bold */
		    p = highlight_has_attr(id, HL_BOLD, modec);
		break;

	case 'f':					/* fg[#] or font */
		p = highlight_color(id, what, modec);
		break;

	case 'i':
		if (TOLOWER_ASC(what[1]) == 'n')	/* inverse */
		    p = highlight_has_attr(id, HL_INVERSE, modec);
		else					/* italic */
		    p = highlight_has_attr(id, HL_ITALIC, modec);
		break;

	case 'n':					/* name */
		p = get_highlight_name_ext(NULL, id - 1, FALSE);
		break;

	case 'r':					/* reverse */
		p = highlight_has_attr(id, HL_INVERSE, modec);
		break;

	case 's':
		if (TOLOWER_ASC(what[1]) == 'p')	/* sp[#] */
		    p = highlight_color(id, what, modec);
							/* strikeout */
		else if (TOLOWER_ASC(what[1]) == 't' &&
			TOLOWER_ASC(what[2]) == 'r')
		    p = highlight_has_attr(id, HL_STRIKETHROUGH, modec);
		else					/* standout */
		    p = highlight_has_attr(id, HL_STANDOUT, modec);
		break;

	case 'u':
		if (STRLEN(what) <= 5 || TOLOWER_ASC(what[5]) != 'c')
							/* underline */
		    p = highlight_has_attr(id, HL_UNDERLINE, modec);
		else
							/* undercurl */
		    p = highlight_has_attr(id, HL_UNDERCURL, modec);
		break;
    }

    if (p != NULL)
	p = vim_strsave(p);
#endif
    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = p;
}


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
}


f_winrestcmd(typval_T *argvars UNUSED, typval_T *rettv)
{
    win_T	*wp;
    int		winnr = 1;
    garray_T	ga;
    char_u	buf[50];

    ga_init2(&ga, (int)sizeof(char), 70);
    FOR_ALL_WINDOWS(wp)
    {
	sprintf((char *)buf, "%dresize %d|", winnr, wp->w_height);
	ga_concat(&ga, buf);
	sprintf((char *)buf, "vert %dresize %d|", winnr, wp->w_width);
	ga_concat(&ga, buf);
	++winnr;
    }
    ga_append(&ga, NUL);

    rettv->vval.v_string = ga.ga_data;
    rettv->v_type = VAR_STRING;
}


do_ascii(exarg_T *eap UNUSED)
{
    int		c;
    int		cval;
    char	buf1[20];
    char	buf2[20];
    char_u	buf3[7];
#ifdef FEAT_DIGRAPHS
    char_u      *dig;
#endif
    int		cc[MAX_MCO];
    int		ci = 0;
    int		len;

    if (enc_utf8)
	c = utfc_ptr2char(ml_get_cursor(), cc);
    else
	c = gchar_cursor();
    if (c == NUL)
    {
	msg("NUL");
	return;
    }

    IObuff[0] = NUL;
    if (!has_mbyte || (enc_dbcs != 0 && c < 0x100) || c < 0x80)
    {
	if (c == NL)	    /* NUL is stored as NL */
	    c = NUL;
	if (c == CAR && get_fileformat(curbuf) == EOL_MAC)
	    cval = NL;	    /* NL is stored as CR */
	else
	    cval = c;
	if (vim_isprintc_strict(c) && (c < ' '
#ifndef EBCDIC
		    || c > '~'
#endif
			       ))
	{
	    transchar_nonprint(buf3, c);
	    vim_snprintf(buf1, sizeof(buf1), "  <%s>", (char *)buf3);
	}
	else
	    buf1[0] = NUL;
#ifndef EBCDIC
	if (c >= 0x80)
	    vim_snprintf(buf2, sizeof(buf2), "  <M-%s>",
						 (char *)transchar(c & 0x7f));
	else
#endif
	    buf2[0] = NUL;
#ifdef FEAT_DIGRAPHS
	dig = get_digraph_for_char(cval);
	if (dig != NULL)
	    vim_snprintf((char *)IObuff, IOSIZE,
		_("<%s>%s%s  %d,  Hex %02x,  Oct %03o, Digr %s"),
			      transchar(c), buf1, buf2, cval, cval, cval, dig);
	else
#endif
	    vim_snprintf((char *)IObuff, IOSIZE,
		_("<%s>%s%s  %d,  Hex %02x,  Octal %03o"),
				  transchar(c), buf1, buf2, cval, cval, cval);
	if (enc_utf8)
	    c = cc[ci++];
	else
	    c = 0;
    }

    /* Repeat for combining characters. */
    while (has_mbyte && (c >= 0x100 || (enc_utf8 && c >= 0x80)))
    {
	len = (int)STRLEN(IObuff);
	/* This assumes every multi-byte char is printable... */
	if (len > 0)
	    IObuff[len++] = ' ';
	IObuff[len++] = '<';
	if (enc_utf8 && utf_iscomposing(c)
# ifdef USE_GUI
		&& !gui.in_use
# endif
		)
	    IObuff[len++] = ' '; /* draw composing char on top of a space */
	len += (*mb_char2bytes)(c, IObuff + len);
#ifdef FEAT_DIGRAPHS
	dig = get_digraph_for_char(c);
	if (dig != NULL)
	    vim_snprintf((char *)IObuff + len, IOSIZE - len,
			c < 0x10000 ? _("> %d, Hex %04x, Oct %o, Digr %s")
				    : _("> %d, Hex %08x, Oct %o, Digr %s"),
					c, c, c, dig);
	else
#endif
	    vim_snprintf((char *)IObuff + len, IOSIZE - len,
			 c < 0x10000 ? _("> %d, Hex %04x, Octal %o")
				     : _("> %d, Hex %08x, Octal %o"),
				     c, c, c);
	if (ci == MAX_MCO)
	    break;
	if (enc_utf8)
	    c = cc[ci++];
	else
	    c = 0;
    }

    msg((char *)IObuff);
}


ex_help(exarg_T *eap)
{
    char_u	*arg;
    char_u	*tag;
    FILE	*helpfd;	/* file descriptor of help file */
    int		n;
    int		i;
    win_T	*wp;
    int		num_matches;
    char_u	**matches;
    char_u	*p;
    int		empty_fnum = 0;
    int		alt_fnum = 0;
    buf_T	*buf;
#ifdef FEAT_MULTI_LANG
    int		len;
    char_u	*lang;
#endif
#ifdef FEAT_FOLDING
    int		old_KeyTyped = KeyTyped;
#endif

    if (eap != NULL)
    {
	/*
	 * A ":help" command ends at the first LF, or at a '|' that is
	 * followed by some text.  Set nextcmd to the following command.
	 */
	for (arg = eap->arg; *arg; ++arg)
	{
	    if (*arg == '\n' || *arg == '\r'
		    || (*arg == '|' && arg[1] != NUL && arg[1] != '|'))
	    {
		*arg++ = NUL;
		eap->nextcmd = arg;
		break;
	    }
	}
	arg = eap->arg;

	if (eap->forceit && *arg == NUL && !curbuf->b_help)
	{
	    emsg(_("E478: Don't panic!"));
	    return;
	}

	if (eap->skip)	    /* not executing commands */
	    return;
    }
    else
	arg = (char_u *)"";

    /* remove trailing blanks */
    p = arg + STRLEN(arg) - 1;
    while (p > arg && VIM_ISWHITE(*p) && p[-1] != '\\')
	*p-- = NUL;

#ifdef FEAT_MULTI_LANG
    /* Check for a specified language */
    lang = check_help_lang(arg);
#endif

    /* When no argument given go to the index. */
    if (*arg == NUL)
	arg = (char_u *)"help.txt";

    /*
     * Check if there is a match for the argument.
     */
    n = find_help_tags(arg, &num_matches, &matches,
						 eap != NULL && eap->forceit);

    i = 0;
#ifdef FEAT_MULTI_LANG
    if (n != FAIL && lang != NULL)
	/* Find first item with the requested language. */
	for (i = 0; i < num_matches; ++i)
	{
	    len = (int)STRLEN(matches[i]);
	    if (len > 3 && matches[i][len - 3] == '@'
				  && STRICMP(matches[i] + len - 2, lang) == 0)
		break;
	}
#endif
    if (i >= num_matches || n == FAIL)
    {
#ifdef FEAT_MULTI_LANG
	if (lang != NULL)
	    semsg(_("E661: Sorry, no '%s' help for %s"), lang, arg);
	else
#endif
	    semsg(_("E149: Sorry, no help for %s"), arg);
	if (n != FAIL)
	    FreeWild(num_matches, matches);
	return;
    }

    /* The first match (in the requested language) is the best match. */
    tag = vim_strsave(matches[i]);
    FreeWild(num_matches, matches);

#ifdef FEAT_GUI
    need_mouse_correct = TRUE;
#endif

    /*
     * Re-use an existing help window or open a new one.
     * Always open a new one for ":tab help".
     */
    if (!bt_help(curwin->w_buffer) || cmdmod.tab != 0)
    {
	if (cmdmod.tab != 0)
	    wp = NULL;
	else
	    FOR_ALL_WINDOWS(wp)
		if (bt_help(wp->w_buffer))
		    break;
	if (wp != NULL && wp->w_buffer->b_nwindows > 0)
	    win_enter(wp, TRUE);
	else
	{
	    /*
	     * There is no help window yet.
	     * Try to open the file specified by the "helpfile" option.
	     */
	    if ((helpfd = mch_fopen((char *)p_hf, READBIN)) == NULL)
	    {
		smsg(_("Sorry, help file \"%s\" not found"), p_hf);
		goto erret;
	    }
	    fclose(helpfd);

	    /* Split off help window; put it at far top if no position
	     * specified, the current window is vertically split and
	     * narrow. */
	    n = WSP_HELP;
	    if (cmdmod.split == 0 && curwin->w_width != Columns
						  && curwin->w_width < 80)
		n |= WSP_TOP;
	    if (win_split(0, n) == FAIL)
		goto erret;

	    if (curwin->w_height < p_hh)
		win_setheight((int)p_hh);

	    /*
	     * Open help file (do_ecmd() will set b_help flag, readfile() will
	     * set b_p_ro flag).
	     * Set the alternate file to the previously edited file.
	     */
	    alt_fnum = curbuf->b_fnum;
	    (void)do_ecmd(0, NULL, NULL, NULL, ECMD_LASTL,
			  ECMD_HIDE + ECMD_SET_HELP,
			  NULL);  /* buffer is still open, don't store info */
	    if (!cmdmod.keepalt)
		curwin->w_alt_fnum = alt_fnum;
	    empty_fnum = curbuf->b_fnum;
	}
    }

    if (!p_im)
	restart_edit = 0;	    /* don't want insert mode in help file */

#ifdef FEAT_FOLDING
    /* Restore KeyTyped, setting 'filetype=help' may reset it.
     * It is needed for do_tag top open folds under the cursor. */
    KeyTyped = old_KeyTyped;
#endif

    if (tag != NULL)
	do_tag(tag, DT_HELP, 1, FALSE, TRUE);

    /* Delete the empty buffer if we're not using it.  Careful: autocommands
     * may have jumped to another window, check that the buffer is not in a
     * window. */
    if (empty_fnum != 0 && curbuf->b_fnum != empty_fnum)
    {
	buf = buflist_findnr(empty_fnum);
	if (buf != NULL && buf->b_nwindows == 0)
	    wipe_buffer(buf, TRUE);
    }

    /* keep the previous alternate file */
    if (alt_fnum != 0 && curwin->w_alt_fnum == empty_fnum && !cmdmod.keepalt)
	curwin->w_alt_fnum = alt_fnum;

erret:
    vim_free(tag);
}


get_user_cmd_addr_type(expand_T *xp UNUSED, int idx)
{
    return (char_u *)addr_type_complete[idx].name;
}


f_clearmatches(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
#ifdef FEAT_SEARCH_EXTRA
    clear_matches(curwin);
#endif
}


ex_preserve(exarg_T *eap UNUSED)
{
    curbuf->b_flags |= BF_PRESERVED;
    ml_preserve(curbuf, TRUE);
}


set_one_cmd_context(
    expand_T	*xp,
    char_u	*buff)	    /* buffer for command string */
{
    char_u		*p;
    char_u		*cmd, *arg;
    int			len = 0;
    exarg_T		ea;
#if defined(FEAT_USR_CMDS) && defined(FEAT_CMDL_COMPL)
    int			compl = EXPAND_NOTHING;
#endif
#ifdef FEAT_CMDL_COMPL
    int			delim;
#endif
    int			forceit = FALSE;
    int			usefilter = FALSE;  /* filter instead of file name */

    ExpandInit(xp);
    xp->xp_pattern = buff;
    xp->xp_context = EXPAND_COMMANDS;	/* Default until we get past command */
    ea.argt = 0;

/*
 * 1. skip comment lines and leading space, colons or bars
 */
    for (cmd = buff; vim_strchr((char_u *)" \t:|", *cmd) != NULL; cmd++)
	;
    xp->xp_pattern = cmd;

    if (*cmd == NUL)
	return NULL;
    if (*cmd == '"')	    /* ignore comment lines */
    {
	xp->xp_context = EXPAND_NOTHING;
	return NULL;
    }

/*
 * 3. Skip over the range to find the command.
 */
    cmd = skip_range(cmd, &xp->xp_context);
    xp->xp_pattern = cmd;
    if (*cmd == NUL)
	return NULL;
    if (*cmd == '"')
    {
	xp->xp_context = EXPAND_NOTHING;
	return NULL;
    }

    if (*cmd == '|' || *cmd == '\n')
	return cmd + 1;			/* There's another command */

    /*
     * Isolate the command and search for it in the command table.
     * Exceptions:
     * - the 'k' command can directly be followed by any character, but
     *   do accept "keepmarks", "keepalt" and "keepjumps".
     * - the 's' command can be followed directly by 'c', 'g', 'i', 'I' or 'r'
     */
    if (*cmd == 'k' && cmd[1] != 'e')
    {
	ea.cmdidx = CMD_k;
	p = cmd + 1;
    }
    else
    {
	p = cmd;
	while (ASCII_ISALPHA(*p) || *p == '*')    /* Allow * wild card */
	    ++p;
	/* a user command may contain digits */
	if (ASCII_ISUPPER(cmd[0]))
	    while (ASCII_ISALNUM(*p) || *p == '*')
		++p;
	/* for python 3.x: ":py3*" commands completion */
	if (cmd[0] == 'p' && cmd[1] == 'y' && p == cmd + 2 && *p == '3')
	{
	    ++p;
	    while (ASCII_ISALPHA(*p) || *p == '*')
		++p;
	}
	/* check for non-alpha command */
	if (p == cmd && vim_strchr((char_u *)"@*!=><&~#", *p) != NULL)
	    ++p;
	len = (int)(p - cmd);

	if (len == 0)
	{
	    xp->xp_context = EXPAND_UNSUCCESSFUL;
	    return NULL;
	}
	for (ea.cmdidx = (cmdidx_T)0; (int)ea.cmdidx < (int)CMD_SIZE;
				   ea.cmdidx = (cmdidx_T)((int)ea.cmdidx + 1))
	    if (STRNCMP(cmdnames[(int)ea.cmdidx].cmd_name, cmd,
							    (size_t)len) == 0)
		break;

#ifdef FEAT_USR_CMDS
	if (cmd[0] >= 'A' && cmd[0] <= 'Z')
	    while (ASCII_ISALNUM(*p) || *p == '*')	/* Allow * wild card */
		++p;
#endif
    }

    /*
     * If the cursor is touching the command, and it ends in an alpha-numeric
     * character, complete the command name.
     */
    if (*p == NUL && ASCII_ISALNUM(p[-1]))
	return NULL;

    if (ea.cmdidx == CMD_SIZE)
    {
	if (*cmd == 's' && vim_strchr((char_u *)"cgriI", cmd[1]) != NULL)
	{
	    ea.cmdidx = CMD_substitute;
	    p = cmd + 1;
	}
#ifdef FEAT_USR_CMDS
	else if (cmd[0] >= 'A' && cmd[0] <= 'Z')
	{
	    ea.cmd = cmd;
	    p = find_ucmd(&ea, p, NULL, xp,
# if defined(FEAT_CMDL_COMPL)
		    &compl
# else
		    NULL
# endif
		    );
	    if (p == NULL)
		ea.cmdidx = CMD_SIZE;	/* ambiguous user command */
	}
#endif
    }
    if (ea.cmdidx == CMD_SIZE)
    {
	/* Not still touching the command and it was an illegal one */
	xp->xp_context = EXPAND_UNSUCCESSFUL;
	return NULL;
    }

    xp->xp_context = EXPAND_NOTHING; /* Default now that we're past command */

    if (*p == '!')		    /* forced commands */
    {
	forceit = TRUE;
	++p;
    }

/*
 * 6. parse arguments
 */
    if (!IS_USER_CMDIDX(ea.cmdidx))
	ea.argt = (long)cmdnames[(int)ea.cmdidx].cmd_argt;

    arg = skipwhite(p);

    if (ea.cmdidx == CMD_write || ea.cmdidx == CMD_update)
    {
	if (*arg == '>')			/* append */
	{
	    if (*++arg == '>')
		++arg;
	    arg = skipwhite(arg);
	}
	else if (*arg == '!' && ea.cmdidx == CMD_write)	/* :w !filter */
	{
	    ++arg;
	    usefilter = TRUE;
	}
    }

    if (ea.cmdidx == CMD_read)
    {
	usefilter = forceit;			/* :r! filter if forced */
	if (*arg == '!')			/* :r !filter */
	{
	    ++arg;
	    usefilter = TRUE;
	}
    }

    if (ea.cmdidx == CMD_lshift || ea.cmdidx == CMD_rshift)
    {
	while (*arg == *cmd)	    /* allow any number of '>' or '<' */
	    ++arg;
	arg = skipwhite(arg);
    }

    /* Does command allow "+command"? */
    if ((ea.argt & EDITCMD) && !usefilter && *arg == '+')
    {
	/* Check if we're in the +command */
	p = arg + 1;
	arg = skip_cmd_arg(arg, FALSE);

	/* Still touching the command after '+'? */
	if (*arg == NUL)
	    return p;

	/* Skip space(s) after +command to get to the real argument */
	arg = skipwhite(arg);
    }

    /*
     * Check for '|' to separate commands and '"' to start comments.
     * Don't do this for ":read !cmd" and ":write !cmd".
     */
    if ((ea.argt & TRLBAR) && !usefilter)
    {
	p = arg;
	/* ":redir @" is not the start of a comment */
	if (ea.cmdidx == CMD_redir && p[0] == '@' && p[1] == '"')
	    p += 2;
	while (*p)
	{
	    if (*p == Ctrl_V)
	    {
		if (p[1] != NUL)
		    ++p;
	    }
	    else if ( (*p == '"' && !(ea.argt & NOTRLCOM))
		    || *p == '|' || *p == '\n')
	    {
		if (*(p - 1) != '\\')
		{
		    if (*p == '|' || *p == '\n')
			return p + 1;
		    return NULL;    /* It's a comment */
		}
	    }
	    MB_PTR_ADV(p);
	}
    }

						/* no arguments allowed */
    if (!(ea.argt & EXTRA) && *arg != NUL &&
				    vim_strchr((char_u *)"|\"", *arg) == NULL)
	return NULL;

    /* Find start of last argument (argument just before cursor): */
    p = buff;
    xp->xp_pattern = p;
    len = (int)STRLEN(buff);
    while (*p && p < buff + len)
    {
	if (*p == ' ' || *p == TAB)
	{
	    /* argument starts after a space */
	    xp->xp_pattern = ++p;
	}
	else
	{
	    if (*p == '\\' && *(p + 1) != NUL)
		++p; /* skip over escaped character */
	    MB_PTR_ADV(p);
	}
    }

    if (ea.argt & XFILE)
    {
	int	c;
	int	in_quote = FALSE;
	char_u	*bow = NULL;	/* Beginning of word */

	/*
	 * Allow spaces within back-quotes to count as part of the argument
	 * being expanded.
	 */
	xp->xp_pattern = skipwhite(arg);
	p = xp->xp_pattern;
	while (*p != NUL)
	{
	    if (has_mbyte)
		c = mb_ptr2char(p);
	    else
		c = *p;
	    if (c == '\\' && p[1] != NUL)
		++p;
	    else if (c == '`')
	    {
		if (!in_quote)
		{
		    xp->xp_pattern = p;
		    bow = p + 1;
		}
		in_quote = !in_quote;
	    }
	    /* An argument can contain just about everything, except
	     * characters that end the command and white space. */
	    else if (c == '|' || c == '\n' || c == '"' || (VIM_ISWHITE(c)
#ifdef SPACE_IN_FILENAME
					 && (!(ea.argt & NOSPC) || usefilter)
#endif
		    ))
	    {
		len = 0;  /* avoid getting stuck when space is in 'isfname' */
		while (*p != NUL)
		{
		    if (has_mbyte)
			c = mb_ptr2char(p);
		    else
			c = *p;
		    if (c == '`' || vim_isfilec_or_wc(c))
			break;
		    if (has_mbyte)
			len = (*mb_ptr2len)(p);
		    else
			len = 1;
		    MB_PTR_ADV(p);
		}
		if (in_quote)
		    bow = p;
		else
		    xp->xp_pattern = p;
		p -= len;
	    }
	    MB_PTR_ADV(p);
	}

	/*
	 * If we are still inside the quotes, and we passed a space, just
	 * expand from there.
	 */
	if (bow != NULL && in_quote)
	    xp->xp_pattern = bow;
	xp->xp_context = EXPAND_FILES;

	/* For a shell command more chars need to be escaped. */
	if (usefilter || ea.cmdidx == CMD_bang || ea.cmdidx == CMD_terminal)
	{
#ifndef BACKSLASH_IN_FILENAME
	    xp->xp_shell = TRUE;
#endif
	    /* When still after the command name expand executables. */
	    if (xp->xp_pattern == skipwhite(arg))
		xp->xp_context = EXPAND_SHELLCMD;
	}

	/* Check for environment variable */
	if (*xp->xp_pattern == '$'
#if defined(MSWIN)
		|| *xp->xp_pattern == '%'
#endif
		)
	{
	    for (p = xp->xp_pattern + 1; *p != NUL; ++p)
		if (!vim_isIDc(*p))
		    break;
	    if (*p == NUL)
	    {
		xp->xp_context = EXPAND_ENV_VARS;
		++xp->xp_pattern;
#if defined(FEAT_USR_CMDS) && defined(FEAT_CMDL_COMPL)
		/* Avoid that the assignment uses EXPAND_FILES again. */
		if (compl != EXPAND_USER_DEFINED && compl != EXPAND_USER_LIST)
		    compl = EXPAND_ENV_VARS;
#endif
	    }
	}
#if defined(FEAT_CMDL_COMPL)
	/* Check for user names */
	if (*xp->xp_pattern == '~')
	{
	    for (p = xp->xp_pattern + 1; *p != NUL && *p != '/'; ++p)
		;
	    /* Complete ~user only if it partially matches a user name.
	     * A full match ~user<Tab> will be replaced by user's home
	     * directory i.e. something like ~user<Tab> -> /home/user/ */
	    if (*p == NUL && p > xp->xp_pattern + 1
				       && match_user(xp->xp_pattern + 1) >= 1)
	    {
		xp->xp_context = EXPAND_USER;
		++xp->xp_pattern;
	    }
	}
#endif
    }

/*
 * 6. Switch on command name.
 */
    switch (ea.cmdidx)
    {
	case CMD_find:
	case CMD_sfind:
	case CMD_tabfind:
	    if (xp->xp_context == EXPAND_FILES)
		xp->xp_context = EXPAND_FILES_IN_PATH;
	    break;
	case CMD_cd:
	case CMD_chdir:
	case CMD_lcd:
	case CMD_lchdir:
	    if (xp->xp_context == EXPAND_FILES)
		xp->xp_context = EXPAND_DIRECTORIES;
	    break;
	case CMD_help:
	    xp->xp_context = EXPAND_HELP;
	    xp->xp_pattern = arg;
	    break;

	/* Command modifiers: return the argument.
	 * Also for commands with an argument that is a command. */
	case CMD_aboveleft:
	case CMD_argdo:
	case CMD_belowright:
	case CMD_botright:
	case CMD_browse:
	case CMD_bufdo:
	case CMD_cdo:
	case CMD_cfdo:
	case CMD_confirm:
	case CMD_debug:
	case CMD_folddoclosed:
	case CMD_folddoopen:
	case CMD_hide:
	case CMD_keepalt:
	case CMD_keepjumps:
	case CMD_keepmarks:
	case CMD_keeppatterns:
	case CMD_ldo:
	case CMD_leftabove:
	case CMD_lfdo:
	case CMD_lockmarks:
	case CMD_noautocmd:
	case CMD_noswapfile:
	case CMD_rightbelow:
	case CMD_sandbox:
	case CMD_silent:
	case CMD_tab:
	case CMD_tabdo:
	case CMD_topleft:
	case CMD_verbose:
	case CMD_vertical:
	case CMD_windo:
	    return arg;

	case CMD_filter:
	    if (*arg != NUL)
		arg = skip_vimgrep_pat(arg, NULL, NULL);
	    if (arg == NULL || *arg == NUL)
	    {
		xp->xp_context = EXPAND_NOTHING;
		return NULL;
	    }
	    return skipwhite(arg);

#ifdef FEAT_CMDL_COMPL
# ifdef FEAT_SEARCH_EXTRA
	case CMD_match:
	    if (*arg == NUL || !ends_excmd(*arg))
	    {
		/* also complete "None" */
		set_context_in_echohl_cmd(xp, arg);
		arg = skipwhite(skiptowhite(arg));
		if (*arg != NUL)
		{
		    xp->xp_context = EXPAND_NOTHING;
		    arg = skip_regexp(arg + 1, *arg, p_magic, NULL);
		}
	    }
	    return find_nextcmd(arg);
# endif

/*
 * All completion for the +cmdline_compl feature goes here.
 */

# ifdef FEAT_USR_CMDS
	case CMD_command:
	    /* Check for attributes */
	    while (*arg == '-')
	    {
		arg++;	    /* Skip "-" */
		p = skiptowhite(arg);
		if (*p == NUL)
		{
		    /* Cursor is still in the attribute */
		    p = vim_strchr(arg, '=');
		    if (p == NULL)
		    {
			/* No "=", so complete attribute names */
			xp->xp_context = EXPAND_USER_CMD_FLAGS;
			xp->xp_pattern = arg;
			return NULL;
		    }

		    /* For the -complete, -nargs and -addr attributes, we complete
		     * their arguments as well.
		     */
		    if (STRNICMP(arg, "complete", p - arg) == 0)
		    {
			xp->xp_context = EXPAND_USER_COMPLETE;
			xp->xp_pattern = p + 1;
			return NULL;
		    }
		    else if (STRNICMP(arg, "nargs", p - arg) == 0)
		    {
			xp->xp_context = EXPAND_USER_NARGS;
			xp->xp_pattern = p + 1;
			return NULL;
		    }
		    else if (STRNICMP(arg, "addr", p - arg) == 0)
		    {
			xp->xp_context = EXPAND_USER_ADDR_TYPE;
			xp->xp_pattern = p + 1;
			return NULL;
		    }
		    return NULL;
		}
		arg = skipwhite(p);
	    }

	    /* After the attributes comes the new command name */
	    p = skiptowhite(arg);
	    if (*p == NUL)
	    {
		xp->xp_context = EXPAND_USER_COMMANDS;
		xp->xp_pattern = arg;
		break;
	    }

	    /* And finally comes a normal command */
	    return skipwhite(p);

	case CMD_delcommand:
	    xp->xp_context = EXPAND_USER_COMMANDS;
	    xp->xp_pattern = arg;
	    break;
# endif

	case CMD_global:
	case CMD_vglobal:
	    delim = *arg;	    /* get the delimiter */
	    if (delim)
		++arg;		    /* skip delimiter if there is one */

	    while (arg[0] != NUL && arg[0] != delim)
	    {
		if (arg[0] == '\\' && arg[1] != NUL)
		    ++arg;
		++arg;
	    }
	    if (arg[0] != NUL)
		return arg + 1;
	    break;
	case CMD_and:
	case CMD_substitute:
	    delim = *arg;
	    if (delim)
	    {
		/* skip "from" part */
		++arg;
		arg = skip_regexp(arg, delim, p_magic, NULL);
	    }
	    /* skip "to" part */
	    while (arg[0] != NUL && arg[0] != delim)
	    {
		if (arg[0] == '\\' && arg[1] != NUL)
		    ++arg;
		++arg;
	    }
	    if (arg[0] != NUL)	/* skip delimiter */
		++arg;
	    while (arg[0] && vim_strchr((char_u *)"|\"#", arg[0]) == NULL)
		++arg;
	    if (arg[0] != NUL)
		return arg;
	    break;
	case CMD_isearch:
	case CMD_dsearch:
	case CMD_ilist:
	case CMD_dlist:
	case CMD_ijump:
	case CMD_psearch:
	case CMD_djump:
	case CMD_isplit:
	case CMD_dsplit:
	    arg = skipwhite(skipdigits(arg));	    /* skip count */
	    if (*arg == '/')	/* Match regexp, not just whole words */
	    {
		for (++arg; *arg && *arg != '/'; arg++)
		    if (*arg == '\\' && arg[1] != NUL)
			arg++;
		if (*arg)
		{
		    arg = skipwhite(arg + 1);

		    /* Check for trailing illegal characters */
		    if (*arg && vim_strchr((char_u *)"|\"\n", *arg) == NULL)
			xp->xp_context = EXPAND_NOTHING;
		    else
			return arg;
		}
	    }
	    break;

	case CMD_autocmd:
	    return set_context_in_autocmd(xp, arg, FALSE);
	case CMD_doautocmd:
	case CMD_doautoall:
	    return set_context_in_autocmd(xp, arg, TRUE);
	case CMD_set:
	    set_context_in_set_cmd(xp, arg, 0);
	    break;
	case CMD_setglobal:
	    set_context_in_set_cmd(xp, arg, OPT_GLOBAL);
	    break;
	case CMD_setlocal:
	    set_context_in_set_cmd(xp, arg, OPT_LOCAL);
	    break;
	case CMD_tag:
	case CMD_stag:
	case CMD_ptag:
	case CMD_ltag:
	case CMD_tselect:
	case CMD_stselect:
	case CMD_ptselect:
	case CMD_tjump:
	case CMD_stjump:
	case CMD_ptjump:
	    if (*p_wop != NUL)
		xp->xp_context = EXPAND_TAGS_LISTFILES;
	    else
		xp->xp_context = EXPAND_TAGS;
	    xp->xp_pattern = arg;
	    break;
	case CMD_augroup:
	    xp->xp_context = EXPAND_AUGROUP;
	    xp->xp_pattern = arg;
	    break;
#ifdef FEAT_SYN_HL
	case CMD_syntax:
	    set_context_in_syntax_cmd(xp, arg);
	    break;
#endif
#ifdef FEAT_EVAL
	case CMD_let:
	case CMD_if:
	case CMD_elseif:
	case CMD_while:
	case CMD_for:
	case CMD_echo:
	case CMD_echon:
	case CMD_execute:
	case CMD_echomsg:
	case CMD_echoerr:
	case CMD_call:
	case CMD_return:
	case CMD_cexpr:
	case CMD_caddexpr:
	case CMD_cgetexpr:
	case CMD_lexpr:
	case CMD_laddexpr:
	case CMD_lgetexpr:
	    set_context_for_expression(xp, arg, ea.cmdidx);
	    break;

	case CMD_unlet:
	    while ((xp->xp_pattern = vim_strchr(arg, ' ')) != NULL)
		arg = xp->xp_pattern + 1;

	    xp->xp_context = EXPAND_USER_VARS;
	    xp->xp_pattern = arg;

	    if (*xp->xp_pattern == '$')
	    {
		xp->xp_context = EXPAND_ENV_VARS;
		++xp->xp_pattern;
	    }

	    break;

	case CMD_function:
	case CMD_delfunction:
	    xp->xp_context = EXPAND_USER_FUNC;
	    xp->xp_pattern = arg;
	    break;

	case CMD_echohl:
	    set_context_in_echohl_cmd(xp, arg);
	    break;
#endif
	case CMD_highlight:
	    set_context_in_highlight_cmd(xp, arg);
	    break;
#ifdef FEAT_CSCOPE
	case CMD_cscope:
	case CMD_lcscope:
	case CMD_scscope:
	    set_context_in_cscope_cmd(xp, arg, ea.cmdidx);
	    break;
#endif
#ifdef FEAT_SIGNS
	case CMD_sign:
	    set_context_in_sign_cmd(xp, arg);
	    break;
#endif
	case CMD_bdelete:
	case CMD_bwipeout:
	case CMD_bunload:
	    while ((xp->xp_pattern = vim_strchr(arg, ' ')) != NULL)
		arg = xp->xp_pattern + 1;
	    /* FALLTHROUGH */
	case CMD_buffer:
	case CMD_sbuffer:
	case CMD_checktime:
	    xp->xp_context = EXPAND_BUFFERS;
	    xp->xp_pattern = arg;
	    break;
#ifdef FEAT_USR_CMDS
	case CMD_USER:
	case CMD_USER_BUF:
	    if (compl != EXPAND_NOTHING)
	    {
		/* XFILE: file names are handled above */
		if (!(ea.argt & XFILE))
		{
# ifdef FEAT_MENU
		    if (compl == EXPAND_MENUS)
			return set_context_in_menu_cmd(xp, cmd, arg, forceit);
# endif
		    if (compl == EXPAND_COMMANDS)
			return arg;
		    if (compl == EXPAND_MAPPINGS)
			return set_context_in_map_cmd(xp, (char_u *)"map",
					 arg, forceit, FALSE, FALSE, CMD_map);
		    /* Find start of last argument. */
		    p = arg;
		    while (*p)
		    {
			if (*p == ' ')
			    /* argument starts after a space */
			    arg = p + 1;
			else if (*p == '\\' && *(p + 1) != NUL)
			    ++p; /* skip over escaped character */
			MB_PTR_ADV(p);
		    }
		    xp->xp_pattern = arg;
		}
		xp->xp_context = compl;
	    }
	    break;
#endif
	case CMD_map:	    case CMD_noremap:
	case CMD_nmap:	    case CMD_nnoremap:
	case CMD_vmap:	    case CMD_vnoremap:
	case CMD_omap:	    case CMD_onoremap:
	case CMD_imap:	    case CMD_inoremap:
	case CMD_cmap:	    case CMD_cnoremap:
	case CMD_lmap:	    case CMD_lnoremap:
	case CMD_smap:	    case CMD_snoremap:
	case CMD_tmap:	    case CMD_tnoremap:
	case CMD_xmap:	    case CMD_xnoremap:
	    return set_context_in_map_cmd(xp, cmd, arg, forceit,
						     FALSE, FALSE, ea.cmdidx);
	case CMD_unmap:
	case CMD_nunmap:
	case CMD_vunmap:
	case CMD_ounmap:
	case CMD_iunmap:
	case CMD_cunmap:
	case CMD_lunmap:
	case CMD_sunmap:
	case CMD_tunmap:
	case CMD_xunmap:
	    return set_context_in_map_cmd(xp, cmd, arg, forceit,
						      FALSE, TRUE, ea.cmdidx);
	case CMD_mapclear:
	case CMD_nmapclear:
	case CMD_vmapclear:
	case CMD_omapclear:
	case CMD_imapclear:
	case CMD_cmapclear:
	case CMD_lmapclear:
	case CMD_smapclear:
	case CMD_tmapclear:
	case CMD_xmapclear:
	    xp->xp_context = EXPAND_MAPCLEAR;
	    xp->xp_pattern = arg;
	    break;

	case CMD_abbreviate:	case CMD_noreabbrev:
	case CMD_cabbrev:	case CMD_cnoreabbrev:
	case CMD_iabbrev:	case CMD_inoreabbrev:
	    return set_context_in_map_cmd(xp, cmd, arg, forceit,
						      TRUE, FALSE, ea.cmdidx);
	case CMD_unabbreviate:
	case CMD_cunabbrev:
	case CMD_iunabbrev:
	    return set_context_in_map_cmd(xp, cmd, arg, forceit,
						       TRUE, TRUE, ea.cmdidx);
#ifdef FEAT_MENU
	case CMD_menu:	    case CMD_noremenu:	    case CMD_unmenu:
	case CMD_amenu:	    case CMD_anoremenu:	    case CMD_aunmenu:
	case CMD_nmenu:	    case CMD_nnoremenu:	    case CMD_nunmenu:
	case CMD_vmenu:	    case CMD_vnoremenu:	    case CMD_vunmenu:
	case CMD_omenu:	    case CMD_onoremenu:	    case CMD_ounmenu:
	case CMD_imenu:	    case CMD_inoremenu:	    case CMD_iunmenu:
	case CMD_cmenu:	    case CMD_cnoremenu:	    case CMD_cunmenu:
	case CMD_tlmenu:    case CMD_tlnoremenu:    case CMD_tlunmenu:
	case CMD_tmenu:				    case CMD_tunmenu:
	case CMD_popup:	    case CMD_tearoff:	    case CMD_emenu:
	    return set_context_in_menu_cmd(xp, cmd, arg, forceit);
#endif

	case CMD_colorscheme:
	    xp->xp_context = EXPAND_COLORS;
	    xp->xp_pattern = arg;
	    break;

	case CMD_compiler:
	    xp->xp_context = EXPAND_COMPILER;
	    xp->xp_pattern = arg;
	    break;

	case CMD_ownsyntax:
	    xp->xp_context = EXPAND_OWNSYNTAX;
	    xp->xp_pattern = arg;
	    break;

	case CMD_setfiletype:
	    xp->xp_context = EXPAND_FILETYPE;
	    xp->xp_pattern = arg;
	    break;

	case CMD_packadd:
	    xp->xp_context = EXPAND_PACKADD;
	    xp->xp_pattern = arg;
	    break;

#if defined(HAVE_LOCALE_H) || defined(X_LOCALE)
	case CMD_language:
	    p = skiptowhite(arg);
	    if (*p == NUL)
	    {
		xp->xp_context = EXPAND_LANGUAGE;
		xp->xp_pattern = arg;
	    }
	    else
	    {
		if ( STRNCMP(arg, "messages", p - arg) == 0
		  || STRNCMP(arg, "ctype", p - arg) == 0
		  || STRNCMP(arg, "time", p - arg) == 0)
		{
		    xp->xp_context = EXPAND_LOCALES;
		    xp->xp_pattern = skipwhite(p);
		}
		else
		    xp->xp_context = EXPAND_NOTHING;
	    }
	    break;
#endif
#if defined(FEAT_PROFILE)
	case CMD_profile:
	    set_context_in_profile_cmd(xp, arg);
	    break;
#endif
	case CMD_behave:
	    xp->xp_context = EXPAND_BEHAVE;
	    xp->xp_pattern = arg;
	    break;

	case CMD_messages:
	    xp->xp_context = EXPAND_MESSAGES;
	    xp->xp_pattern = arg;
	    break;

#if defined(FEAT_CMDHIST)
	case CMD_history:
	    xp->xp_context = EXPAND_HISTORY;
	    xp->xp_pattern = arg;
	    break;
#endif
#if defined(FEAT_PROFILE)
	case CMD_syntime:
	    xp->xp_context = EXPAND_SYNTIME;
	    xp->xp_pattern = arg;
	    break;
#endif

	case CMD_argdelete:
	    while ((xp->xp_pattern = vim_strchr(arg, ' ')) != NULL)
		arg = xp->xp_pattern + 1;
	    xp->xp_context = EXPAND_ARGLIST;
	    xp->xp_pattern = arg;
	    break;

#endif /* FEAT_CMDL_COMPL */

	default:
	    break;
    }
    return NULL;
}


ses_fname(FILE *fd, buf_T *buf, unsigned *flagp, int add_eol)
{
    char_u	*name;

    /* Use the short file name if the current directory is known at the time
     * the session file will be sourced.
     * Don't do this for ":mkview", we don't know the current directory.
     * Don't do this after ":lcd", we don't keep track of what the current
     * directory is. */
    if (buf->b_sfname != NULL
	    && flagp == &ssop_flags
	    && (ssop_flags & (SSOP_CURDIR | SSOP_SESDIR))
#ifdef FEAT_AUTOCHDIR
	    && !p_acd
#endif
	    && !did_lcd)
	name = buf->b_sfname;
    else
	name = buf->b_ffname;
    if (ses_put_fname(fd, name, flagp) == FAIL
	    || (add_eol && put_eol(fd) == FAIL))
	return FAIL;
    return OK;
}


f_assert_exception(typval_T *argvars, typval_T *rettv)
{
    rettv->vval.v_number = assert_exception(argvars);
}


f_test_null_partial(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv->v_type = VAR_PARTIAL;
    rettv->vval.v_partial = NULL;
}


f_function(typval_T *argvars, typval_T *rettv)
{
    common_function(argvars, rettv, FALSE);
}


global_exe(char_u *cmd)
{
    linenr_T old_lcount;	/* b_ml.ml_line_count before the command */
    buf_T    *old_buf = curbuf;	/* remember what buffer we started in */
    linenr_T lnum;		/* line number according to old situation */

    /*
     * Set current position only once for a global command.
     * If global_busy is set, setpcmark() will not do anything.
     * If there is an error, global_busy will be incremented.
     */
    setpcmark();

    /* When the command writes a message, don't overwrite the command. */
    msg_didout = TRUE;

    sub_nsubs = 0;
    sub_nlines = 0;
    global_need_beginline = FALSE;
    global_busy = 1;
    old_lcount = curbuf->b_ml.ml_line_count;
    while (!got_int && (lnum = ml_firstmarked()) != 0 && global_busy == 1)
    {
	global_exe_one(cmd, lnum);
	ui_breakcheck();
    }

    global_busy = 0;
    if (global_need_beginline)
	beginline(BL_WHITE | BL_FIX);
    else
	check_cursor();	/* cursor may be beyond the end of the line */

    /* the cursor may not have moved in the text but a change in a previous
     * line may move it on the screen */
    changed_line_abv_curs();

    /* If it looks like no message was written, allow overwriting the
     * command with the report for number of changes. */
    if (msg_col == 0 && msg_scrolled == 0)
	msg_didout = FALSE;

    /* If substitutes done, report number of substitutes, otherwise report
     * number of extra or deleted lines.
     * Don't report extra or deleted lines in the edge case where the buffer
     * we are in after execution is different from the buffer we started in. */
    if (!do_sub_msg(FALSE) && curbuf == old_buf)
	msgmore(curbuf->b_ml.ml_line_count - old_lcount);
}


f_getcmdpos(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv->vval.v_number = get_cmdline_pos() + 1;
}


do_filter(
    linenr_T	line1,
    linenr_T	line2,
    exarg_T	*eap,		/* for forced 'ff' and 'fenc' */
    char_u	*cmd,
    int		do_in,
    int		do_out)
{
    char_u	*itmp = NULL;
    char_u	*otmp = NULL;
    linenr_T	linecount;
    linenr_T	read_linecount;
    pos_T	cursor_save;
    char_u	*cmd_buf;
    buf_T	*old_curbuf = curbuf;
    int		shell_flags = 0;

    if (*cmd == NUL)	    /* no filter command */
	return;

    cursor_save = curwin->w_cursor;
    linecount = line2 - line1 + 1;
    curwin->w_cursor.lnum = line1;
    curwin->w_cursor.col = 0;
    changed_line_abv_curs();
    invalidate_botline();

    /*
     * When using temp files:
     * 1. * Form temp file names
     * 2. * Write the lines to a temp file
     * 3.   Run the filter command on the temp file
     * 4. * Read the output of the command into the buffer
     * 5. * Delete the original lines to be filtered
     * 6. * Remove the temp files
     *
     * When writing the input with a pipe or when catching the output with a
     * pipe only need to do 3.
     */

    if (do_out)
	shell_flags |= SHELL_DOOUT;

#ifdef FEAT_FILTERPIPE
    if (!do_in && do_out && !p_stmp)
    {
	/* Use a pipe to fetch stdout of the command, do not use a temp file. */
	shell_flags |= SHELL_READ;
	curwin->w_cursor.lnum = line2;
    }
    else if (do_in && !do_out && !p_stmp)
    {
	/* Use a pipe to write stdin of the command, do not use a temp file. */
	shell_flags |= SHELL_WRITE;
	curbuf->b_op_start.lnum = line1;
	curbuf->b_op_end.lnum = line2;
    }
    else if (do_in && do_out && !p_stmp)
    {
	/* Use a pipe to write stdin and fetch stdout of the command, do not
	 * use a temp file. */
	shell_flags |= SHELL_READ|SHELL_WRITE;
	curbuf->b_op_start.lnum = line1;
	curbuf->b_op_end.lnum = line2;
	curwin->w_cursor.lnum = line2;
    }
    else
#endif
	if ((do_in && (itmp = vim_tempname('i', FALSE)) == NULL)
		|| (do_out && (otmp = vim_tempname('o', FALSE)) == NULL))
	{
	    emsg(_(e_notmp));
	    goto filterend;
	}

/*
 * The writing and reading of temp files will not be shown.
 * Vi also doesn't do this and the messages are not very informative.
 */
    ++no_wait_return;		/* don't call wait_return() while busy */
    if (itmp != NULL && buf_write(curbuf, itmp, NULL, line1, line2, eap,
					   FALSE, FALSE, FALSE, TRUE) == FAIL)
    {
	msg_putchar('\n');		/* keep message from buf_write() */
	--no_wait_return;
#if defined(FEAT_EVAL)
	if (!aborting())
#endif
	    (void)semsg(_(e_notcreate), itmp);	/* will call wait_return */
	goto filterend;
    }
    if (curbuf != old_curbuf)
	goto filterend;

    if (!do_out)
	msg_putchar('\n');

    /* Create the shell command in allocated memory. */
    cmd_buf = make_filter_cmd(cmd, itmp, otmp);
    if (cmd_buf == NULL)
	goto filterend;

    windgoto((int)Rows - 1, 0);
    cursor_on();

    /*
     * When not redirecting the output the command can write anything to the
     * screen. If 'shellredir' is equal to ">", screen may be messed up by
     * stderr output of external command. Clear the screen later.
     * If do_in is FALSE, this could be something like ":r !cat", which may
     * also mess up the screen, clear it later.
     */
    if (!do_out || STRCMP(p_srr, ">") == 0 || !do_in)
	redraw_later_clear();

    if (do_out)
    {
	if (u_save((linenr_T)(line2), (linenr_T)(line2 + 1)) == FAIL)
	{
	    vim_free(cmd_buf);
	    goto error;
	}
	redraw_curbuf_later(VALID);
    }
    read_linecount = curbuf->b_ml.ml_line_count;

    /*
     * When call_shell() fails wait_return() is called to give the user a
     * chance to read the error messages. Otherwise errors are ignored, so you
     * can see the error messages from the command that appear on stdout; use
     * 'u' to fix the text
     * Switch to cooked mode when not redirecting stdin, avoids that something
     * like ":r !cat" hangs.
     * Pass on the SHELL_DOOUT flag when the output is being redirected.
     */
    if (call_shell(cmd_buf, SHELL_FILTER | SHELL_COOKED | shell_flags))
    {
	redraw_later_clear();
	wait_return(FALSE);
    }
    vim_free(cmd_buf);

    did_check_timestamps = FALSE;
    need_check_timestamps = TRUE;

    /* When interrupting the shell command, it may still have produced some
     * useful output.  Reset got_int here, so that readfile() won't cancel
     * reading. */
    ui_breakcheck();
    got_int = FALSE;

    if (do_out)
    {
	if (otmp != NULL)
	{
	    if (readfile(otmp, NULL, line2, (linenr_T)0, (linenr_T)MAXLNUM,
						    eap, READ_FILTER) != OK)
	    {
#if defined(FEAT_EVAL)
		if (!aborting())
#endif
		{
		    msg_putchar('\n');
		    semsg(_(e_notread), otmp);
		}
		goto error;
	    }
	    if (curbuf != old_curbuf)
		goto filterend;
	}

	read_linecount = curbuf->b_ml.ml_line_count - read_linecount;

	if (shell_flags & SHELL_READ)
	{
	    curbuf->b_op_start.lnum = line2 + 1;
	    curbuf->b_op_end.lnum = curwin->w_cursor.lnum;
	    appended_lines_mark(line2, read_linecount);
	}

	if (do_in)
	{
	    if (cmdmod.keepmarks || vim_strchr(p_cpo, CPO_REMMARK) == NULL)
	    {
		if (read_linecount >= linecount)
		    /* move all marks from old lines to new lines */
		    mark_adjust(line1, line2, linecount, 0L);
		else
		{
		    /* move marks from old lines to new lines, delete marks
		     * that are in deleted lines */
		    mark_adjust(line1, line1 + read_linecount - 1,
								linecount, 0L);
		    mark_adjust(line1 + read_linecount, line2, MAXLNUM, 0L);
		}
	    }

	    /*
	     * Put cursor on first filtered line for ":range!cmd".
	     * Adjust '[ and '] (set by buf_write()).
	     */
	    curwin->w_cursor.lnum = line1;
	    del_lines(linecount, TRUE);
	    curbuf->b_op_start.lnum -= linecount;	/* adjust '[ */
	    curbuf->b_op_end.lnum -= linecount;		/* adjust '] */
	    write_lnum_adjust(-linecount);		/* adjust last line
							   for next write */
#ifdef FEAT_FOLDING
	    foldUpdate(curwin, curbuf->b_op_start.lnum, curbuf->b_op_end.lnum);
#endif
	}
	else
	{
	    /*
	     * Put cursor on last new line for ":r !cmd".
	     */
	    linecount = curbuf->b_op_end.lnum - curbuf->b_op_start.lnum + 1;
	    curwin->w_cursor.lnum = curbuf->b_op_end.lnum;
	}

	beginline(BL_WHITE | BL_FIX);	    /* cursor on first non-blank */
	--no_wait_return;

	if (linecount > p_report)
	{
	    if (do_in)
	    {
		vim_snprintf(msg_buf, sizeof(msg_buf),
				    _("%ld lines filtered"), (long)linecount);
		if (msg(msg_buf) && !msg_scroll)
		    /* save message to display it after redraw */
		    set_keep_msg((char_u *)msg_buf, 0);
	    }
	    else
		msgmore((long)linecount);
	}
    }
    else
    {
error:
	/* put cursor back in same position for ":w !cmd" */
	curwin->w_cursor = cursor_save;
	--no_wait_return;
	wait_return(FALSE);
    }

filterend:

    if (curbuf != old_curbuf)
    {
	--no_wait_return;
	emsg(_("E135: *Filter* Autocommands must not change current buffer"));
    }
    if (itmp != NULL)
	mch_remove(itmp);
    if (otmp != NULL)
	mch_remove(otmp);
    vim_free(itmp);
    vim_free(otmp);
}


f_eventhandler(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv->vval.v_number = vgetc_busy;
}


f_strdisplaywidth(typval_T *argvars, typval_T *rettv)
{
    char_u	*s = tv_get_string(&argvars[0]);
    int		col = 0;

    if (argvars[1].v_type != VAR_UNKNOWN)
	col = (int)tv_get_number(&argvars[1]);

    rettv->vval.v_number = (varnumber_T)(linetabsize_col(col, s) - col);
}


int load_image_gzipped_buffer(const char *filename, uint64_t max_sz,
                              uint8_t **buffer)
{
    uint8_t *compressed_data = NULL;
    uint8_t *data = NULL;
    gsize len;
    ssize_t bytes;
    int ret = -1;

    if (!g_file_get_contents(filename, (char **) &compressed_data, &len,
                             NULL)) {
        goto out;
    }

    /* Is it a gzip-compressed file? */
    if (len < 2 ||
        compressed_data[0] != 0x1f ||
        compressed_data[1] != 0x8b) {
        goto out;
    }

    if (max_sz > LOAD_IMAGE_MAX_GUNZIP_BYTES) {
        max_sz = LOAD_IMAGE_MAX_GUNZIP_BYTES;
    }

    data = g_malloc(max_sz);
    bytes = gunzip(data, max_sz, compressed_data, len);
    if (bytes < 0) {
        fprintf(stderr, "%s: unable to decompress gzipped kernel file\n",
                filename);
        goto out;
    }

    /* trim to actual size and return to caller */
    *buffer = g_realloc(data, bytes);
    ret = bytes;
    /* ownership has been transferred to caller */
    data = NULL;

 out:
    g_free(compressed_data);
    g_free(data);
    return ret;
}


void pstrcpy_targphys(const char *name, hwaddr dest, int buf_size,
                      const char *source)
{
    const char *nulp;
    char *ptr;

    if (buf_size <= 0) return;
    nulp = memchr(source, 0, buf_size);
    if (nulp) {
        rom_add_blob_fixed(name, source, (nulp - source) + 1, dest);
    } else {
        rom_add_blob_fixed(name, source, buf_size, dest);
        ptr = rom_ptr(dest + buf_size - 1, sizeof(*ptr));
        *ptr = 0;
    }
}


MemoryRegion *rom_add_blob(const char *name, const void *blob, size_t len,
                   size_t max_len, hwaddr addr, const char *fw_file_name,
                   FWCfgCallback fw_callback, void *callback_opaque,
                   AddressSpace *as, bool read_only)
{
    MachineClass *mc = MACHINE_GET_CLASS(qdev_get_machine());
    Rom *rom;
    MemoryRegion *mr = NULL;

    rom           = g_malloc0(sizeof(*rom));
    rom->name     = g_strdup(name);
    rom->as       = as;
    rom->addr     = addr;
    rom->romsize  = max_len ? max_len : len;
    rom->datasize = len;
    g_assert(rom->romsize >= rom->datasize);
    rom->data     = g_malloc0(rom->datasize);
    memcpy(rom->data, blob, len);
    rom_insert(rom);
    if (fw_file_name && fw_cfg) {
        char devpath[100];
        void *data;

        if (read_only) {
            snprintf(devpath, sizeof(devpath), "/rom@%s", fw_file_name);
        } else {
            snprintf(devpath, sizeof(devpath), "/ram@%s", fw_file_name);
        }

        if (mc->rom_file_has_mr) {
            data = rom_set_mr(rom, OBJECT(fw_cfg), devpath, read_only);
            mr = rom->mr;
        } else {
            data = rom->data;
        }

        fw_cfg_add_file_callback(fw_cfg, fw_file_name,
                                 fw_callback, NULL, callback_opaque,
                                 data, rom->datasize, read_only);
    }
    return mr;
}


int rom_check_and_register_reset(void)
{
    hwaddr addr = 0;
    MemoryRegionSection section;
    Rom *rom;
    AddressSpace *as = NULL;

    QTAILQ_FOREACH(rom, &roms, next) {
        if (rom->fw_file) {
            continue;
        }
        if (!rom->mr) {
            if ((addr > rom->addr) && (as == rom->as)) {
                fprintf(stderr, "rom: requested regions overlap "
                        "(rom %s. free=0x" TARGET_FMT_plx
                        ", addr=0x" TARGET_FMT_plx ")\n",
                        rom->name, addr, rom->addr);
                return -1;
            }
            addr  = rom->addr;
            addr += rom->romsize;
            as = rom->as;
        }
        section = memory_region_find(rom->mr ? rom->mr : get_system_memory(),
                                     rom->addr, 1);
        rom->isrom = int128_nz(section.size) && memory_region_is_rom(section.mr);
        memory_region_unref(section.mr);
    }
    qemu_register_reset(rom_reset, NULL);
    roms_loaded = 1;
    return 0;
}


static inline bool rom_order_compare(Rom *rom, Rom *item)
{
    return ((uintptr_t)(void *)rom->as > (uintptr_t)(void *)item->as) ||
           (rom->as == item->as && rom->addr >= item->addr);
}


void cliprdr_free_format_list(CLIPRDR_FORMAT_LIST* formatList)
{
	UINT index = 0;

	if (formatList == NULL)
		return;

	if (formatList->formats)
	{
		for (index = 0; index < formatList->numFormats; index++)
		{
			free(formatList->formats[index].formatName);
		}

		free(formatList->formats);
		formatList->formats = NULL;
		formatList->numFormats = 0;
	}
}