diff src/usercmd.c @ 16411:5b5c5daf57de v8.1.1210

patch 8.1.1210: support for user commands is spread out commit https://github.com/vim/vim/commit/ac9fb18020d7e8bf16d02d45fbb02cf47328aaf7 Author: Bram Moolenaar <Bram@vim.org> Date: Sat Apr 27 13:04:13 2019 +0200 patch 8.1.1210: support for user commands is spread out Problem: Support for user commands is spread out. No good reason to make user commands optional. Solution: Move user command support to usercmd.c. Always enable the user_commands feature.
author Bram Moolenaar <Bram@vim.org>
date Sat, 27 Apr 2019 13:15:07 +0200
parents
children a2befc9b3729
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/src/usercmd.c
@@ -0,0 +1,1656 @@
+/* vi:set ts=8 sts=4 sw=4 noet:
+ *
+ * VIM - Vi IMproved	by Bram Moolenaar
+ *
+ * Do ":help uganda"  in Vim to read copying and usage conditions.
+ * Do ":help credits" in Vim to see a list of people who contributed.
+ * See README.txt for an overview of the Vim source code.
+ */
+
+/*
+ * usercmd.c: User defined command support
+ */
+
+#include "vim.h"
+
+typedef struct ucmd
+{
+    char_u	*uc_name;	// The command name
+    long_u	uc_argt;	// The argument type
+    char_u	*uc_rep;	// The command's replacement string
+    long	uc_def;		// The default value for a range/count
+    int		uc_compl;	// completion type
+    int		uc_addr_type;	// The command's address type
+# ifdef FEAT_EVAL
+    sctx_T	uc_script_ctx;	// SCTX where the command was defined
+#  ifdef FEAT_CMDL_COMPL
+    char_u	*uc_compl_arg;	// completion argument if any
+#  endif
+# endif
+} ucmd_T;
+
+// List of all user commands.
+static garray_T ucmds = {0, 0, sizeof(ucmd_T), 4, NULL};
+
+#define USER_CMD(i) (&((ucmd_T *)(ucmds.ga_data))[i])
+#define USER_CMD_GA(gap, i) (&((ucmd_T *)((gap)->ga_data))[i])
+
+/*
+ * List of names for completion for ":command" with the EXPAND_ flag.
+ * Must be alphabetical for completion.
+ */
+static struct
+{
+    int	    expand;
+    char    *name;
+} command_complete[] =
+{
+    {EXPAND_ARGLIST, "arglist"},
+    {EXPAND_AUGROUP, "augroup"},
+    {EXPAND_BEHAVE, "behave"},
+    {EXPAND_BUFFERS, "buffer"},
+    {EXPAND_COLORS, "color"},
+    {EXPAND_COMMANDS, "command"},
+    {EXPAND_COMPILER, "compiler"},
+#if defined(FEAT_CSCOPE)
+    {EXPAND_CSCOPE, "cscope"},
+#endif
+#if defined(FEAT_EVAL) && defined(FEAT_CMDL_COMPL)
+    {EXPAND_USER_DEFINED, "custom"},
+    {EXPAND_USER_LIST, "customlist"},
+#endif
+    {EXPAND_DIRECTORIES, "dir"},
+    {EXPAND_ENV_VARS, "environment"},
+    {EXPAND_EVENTS, "event"},
+    {EXPAND_EXPRESSION, "expression"},
+    {EXPAND_FILES, "file"},
+    {EXPAND_FILES_IN_PATH, "file_in_path"},
+    {EXPAND_FILETYPE, "filetype"},
+    {EXPAND_FUNCTIONS, "function"},
+    {EXPAND_HELP, "help"},
+    {EXPAND_HIGHLIGHT, "highlight"},
+#if defined(FEAT_CMDHIST)
+    {EXPAND_HISTORY, "history"},
+#endif
+#if defined(HAVE_LOCALE_H) || defined(X_LOCALE)
+    {EXPAND_LOCALES, "locale"},
+#endif
+    {EXPAND_MAPCLEAR, "mapclear"},
+    {EXPAND_MAPPINGS, "mapping"},
+    {EXPAND_MENUS, "menu"},
+    {EXPAND_MESSAGES, "messages"},
+    {EXPAND_OWNSYNTAX, "syntax"},
+#if defined(FEAT_PROFILE)
+    {EXPAND_SYNTIME, "syntime"},
+#endif
+    {EXPAND_SETTINGS, "option"},
+    {EXPAND_PACKADD, "packadd"},
+    {EXPAND_SHELLCMD, "shellcmd"},
+#if defined(FEAT_SIGNS)
+    {EXPAND_SIGN, "sign"},
+#endif
+    {EXPAND_TAGS, "tag"},
+    {EXPAND_TAGS_LISTFILES, "tag_listfiles"},
+    {EXPAND_USER, "user"},
+    {EXPAND_USER_VARS, "var"},
+    {0, NULL}
+};
+
+/*
+ * List of names of address types.  Must be alphabetical for completion.
+ */
+static struct
+{
+    int	    expand;
+    char    *name;
+    char    *shortname;
+} addr_type_complete[] =
+{
+    {ADDR_ARGUMENTS, "arguments", "arg"},
+    {ADDR_LINES, "lines", "line"},
+    {ADDR_LOADED_BUFFERS, "loaded_buffers", "load"},
+    {ADDR_TABS, "tabs", "tab"},
+    {ADDR_BUFFERS, "buffers", "buf"},
+    {ADDR_WINDOWS, "windows", "win"},
+    {ADDR_QUICKFIX, "quickfix", "qf"},
+    {ADDR_OTHER, "other", "?"},
+    {-1, NULL, NULL}
+};
+
+#define UC_BUFFER	1	// -buffer: local to current buffer
+
+/*
+ * Search for a user command that matches "eap->cmd".
+ * Return cmdidx in "eap->cmdidx", flags in "eap->argt", idx in "eap->useridx".
+ * Return a pointer to just after the command.
+ * Return NULL if there is no matching command.
+ */
+    char_u *
+find_ucmd(
+    exarg_T	*eap,
+    char_u	*p,	// end of the command (possibly including count)
+    int		*full,	// set to TRUE for a full match
+    expand_T	*xp,	// used for completion, NULL otherwise
+    int		*compl UNUSED)	// completion flags or NULL
+{
+    int		len = (int)(p - eap->cmd);
+    int		j, k, matchlen = 0;
+    ucmd_T	*uc;
+    int		found = FALSE;
+    int		possible = FALSE;
+    char_u	*cp, *np;	    // Point into typed cmd and test name
+    garray_T	*gap;
+    int		amb_local = FALSE;  // Found ambiguous buffer-local command,
+				    // only full match global is accepted.
+
+    /*
+     * Look for buffer-local user commands first, then global ones.
+     */
+    gap = &curbuf->b_ucmds;
+    for (;;)
+    {
+	for (j = 0; j < gap->ga_len; ++j)
+	{
+	    uc = USER_CMD_GA(gap, j);
+	    cp = eap->cmd;
+	    np = uc->uc_name;
+	    k = 0;
+	    while (k < len && *np != NUL && *cp++ == *np++)
+		k++;
+	    if (k == len || (*np == NUL && vim_isdigit(eap->cmd[k])))
+	    {
+		// If finding a second match, the command is ambiguous.  But
+		// not if a buffer-local command wasn't a full match and a
+		// global command is a full match.
+		if (k == len && found && *np != NUL)
+		{
+		    if (gap == &ucmds)
+			return NULL;
+		    amb_local = TRUE;
+		}
+
+		if (!found || (k == len && *np == NUL))
+		{
+		    // If we matched up to a digit, then there could
+		    // be another command including the digit that we
+		    // should use instead.
+		    if (k == len)
+			found = TRUE;
+		    else
+			possible = TRUE;
+
+		    if (gap == &ucmds)
+			eap->cmdidx = CMD_USER;
+		    else
+			eap->cmdidx = CMD_USER_BUF;
+		    eap->argt = (long)uc->uc_argt;
+		    eap->useridx = j;
+		    eap->addr_type = uc->uc_addr_type;
+
+# ifdef FEAT_CMDL_COMPL
+		    if (compl != NULL)
+			*compl = uc->uc_compl;
+#  ifdef FEAT_EVAL
+		    if (xp != NULL)
+		    {
+			xp->xp_arg = uc->uc_compl_arg;
+			xp->xp_script_ctx = uc->uc_script_ctx;
+			xp->xp_script_ctx.sc_lnum += sourcing_lnum;
+		    }
+#  endif
+# endif
+		    // Do not search for further abbreviations
+		    // if this is an exact match.
+		    matchlen = k;
+		    if (k == len && *np == NUL)
+		    {
+			if (full != NULL)
+			    *full = TRUE;
+			amb_local = FALSE;
+			break;
+		    }
+		}
+	    }
+	}
+
+	// Stop if we found a full match or searched all.
+	if (j < gap->ga_len || gap == &ucmds)
+	    break;
+	gap = &ucmds;
+    }
+
+    // Only found ambiguous matches.
+    if (amb_local)
+    {
+	if (xp != NULL)
+	    xp->xp_context = EXPAND_UNSUCCESSFUL;
+	return NULL;
+    }
+
+    // The match we found may be followed immediately by a number.  Move "p"
+    // back to point to it.
+    if (found || possible)
+	return p + (matchlen - len);
+    return p;
+}
+
+#if defined(FEAT_CMDL_COMPL) || defined(PROTO)
+
+    char_u *
+set_context_in_user_cmd(expand_T *xp, char_u *arg_in)
+{
+    char_u	*arg = arg_in;
+    char_u	*p;
+
+    // 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;
+	return NULL;
+    }
+
+    // And finally comes a normal command
+    return skipwhite(p);
+}
+
+    char_u *
+get_user_command_name(int idx)
+{
+    return get_user_commands(NULL, idx - (int)CMD_SIZE);
+}
+
+/*
+ * Function given to ExpandGeneric() to obtain the list of user command names.
+ */
+    char_u *
+get_user_commands(expand_T *xp UNUSED, int idx)
+{
+    if (idx < curbuf->b_ucmds.ga_len)
+	return USER_CMD_GA(&curbuf->b_ucmds, idx)->uc_name;
+    idx -= curbuf->b_ucmds.ga_len;
+    if (idx < ucmds.ga_len)
+	return USER_CMD(idx)->uc_name;
+    return NULL;
+}
+
+/*
+ * Function given to ExpandGeneric() to obtain the list of user address type
+ * names.
+ */
+    char_u *
+get_user_cmd_addr_type(expand_T *xp UNUSED, int idx)
+{
+    return (char_u *)addr_type_complete[idx].name;
+}
+
+/*
+ * Function given to ExpandGeneric() to obtain the list of user command
+ * attributes.
+ */
+    char_u *
+get_user_cmd_flags(expand_T *xp UNUSED, int idx)
+{
+    static char *user_cmd_flags[] = {
+	"addr", "bang", "bar", "buffer", "complete",
+	"count", "nargs", "range", "register"
+    };
+
+    if (idx >= (int)(sizeof(user_cmd_flags) / sizeof(user_cmd_flags[0])))
+	return NULL;
+    return (char_u *)user_cmd_flags[idx];
+}
+
+/*
+ * Function given to ExpandGeneric() to obtain the list of values for -nargs.
+ */
+    char_u *
+get_user_cmd_nargs(expand_T *xp UNUSED, int idx)
+{
+    static char *user_cmd_nargs[] = {"0", "1", "*", "?", "+"};
+
+    if (idx >= (int)(sizeof(user_cmd_nargs) / sizeof(user_cmd_nargs[0])))
+	return NULL;
+    return (char_u *)user_cmd_nargs[idx];
+}
+
+/*
+ * Function given to ExpandGeneric() to obtain the list of values for
+ * -complete.
+ */
+    char_u *
+get_user_cmd_complete(expand_T *xp UNUSED, int idx)
+{
+    return (char_u *)command_complete[idx].name;
+}
+
+    int
+cmdcomplete_str_to_type(char_u *complete_str)
+{
+    int i;
+
+    for (i = 0; command_complete[i].expand != 0; ++i)
+	if (STRCMP(complete_str, command_complete[i].name) == 0)
+	    return command_complete[i].expand;
+
+    return EXPAND_NOTHING;
+}
+
+#endif // FEAT_CMDL_COMPL
+
+/*
+ * List user commands starting with "name[name_len]".
+ */
+    static void
+uc_list(char_u *name, size_t name_len)
+{
+    int		i, j;
+    int		found = FALSE;
+    ucmd_T	*cmd;
+    int		len;
+    int		over;
+    long	a;
+    garray_T	*gap;
+
+    gap = &curbuf->b_ucmds;
+    for (;;)
+    {
+	for (i = 0; i < gap->ga_len; ++i)
+	{
+	    cmd = USER_CMD_GA(gap, i);
+	    a = (long)cmd->uc_argt;
+
+	    // Skip commands which don't match the requested prefix and
+	    // commands filtered out.
+	    if (STRNCMP(name, cmd->uc_name, name_len) != 0
+		    || message_filtered(cmd->uc_name))
+		continue;
+
+	    // Put out the title first time
+	    if (!found)
+		msg_puts_title(_("\n    Name              Args Address Complete    Definition"));
+	    found = TRUE;
+	    msg_putchar('\n');
+	    if (got_int)
+		break;
+
+	    // Special cases
+	    len = 4;
+	    if (a & BANG)
+	    {
+		msg_putchar('!');
+		--len;
+	    }
+	    if (a & REGSTR)
+	    {
+		msg_putchar('"');
+		--len;
+	    }
+	    if (gap != &ucmds)
+	    {
+		msg_putchar('b');
+		--len;
+	    }
+	    if (a & TRLBAR)
+	    {
+		msg_putchar('|');
+		--len;
+	    }
+	    while (len-- > 0)
+		msg_putchar(' ');
+
+	    msg_outtrans_attr(cmd->uc_name, HL_ATTR(HLF_D));
+	    len = (int)STRLEN(cmd->uc_name) + 4;
+
+	    do {
+		msg_putchar(' ');
+		++len;
+	    } while (len < 22);
+
+	    // "over" is how much longer the name is than the column width for
+	    // the name, we'll try to align what comes after.
+	    over = len - 22;
+	    len = 0;
+
+	    // Arguments
+	    switch ((int)(a & (EXTRA|NOSPC|NEEDARG)))
+	    {
+		case 0:			    IObuff[len++] = '0'; break;
+		case (EXTRA):		    IObuff[len++] = '*'; break;
+		case (EXTRA|NOSPC):	    IObuff[len++] = '?'; break;
+		case (EXTRA|NEEDARG):	    IObuff[len++] = '+'; break;
+		case (EXTRA|NOSPC|NEEDARG): IObuff[len++] = '1'; break;
+	    }
+
+	    do {
+		IObuff[len++] = ' ';
+	    } while (len < 5 - over);
+
+	    // Address / Range
+	    if (a & (RANGE|COUNT))
+	    {
+		if (a & COUNT)
+		{
+		    // -count=N
+		    sprintf((char *)IObuff + len, "%ldc", cmd->uc_def);
+		    len += (int)STRLEN(IObuff + len);
+		}
+		else if (a & DFLALL)
+		    IObuff[len++] = '%';
+		else if (cmd->uc_def >= 0)
+		{
+		    // -range=N
+		    sprintf((char *)IObuff + len, "%ld", cmd->uc_def);
+		    len += (int)STRLEN(IObuff + len);
+		}
+		else
+		    IObuff[len++] = '.';
+	    }
+
+	    do {
+		IObuff[len++] = ' ';
+	    } while (len < 8 - over);
+
+	    // Address Type
+	    for (j = 0; addr_type_complete[j].expand != -1; ++j)
+		if (addr_type_complete[j].expand != ADDR_LINES
+			&& addr_type_complete[j].expand == cmd->uc_addr_type)
+		{
+		    STRCPY(IObuff + len, addr_type_complete[j].shortname);
+		    len += (int)STRLEN(IObuff + len);
+		    break;
+		}
+
+	    do {
+		IObuff[len++] = ' ';
+	    } while (len < 13 - over);
+
+	    // Completion
+	    for (j = 0; command_complete[j].expand != 0; ++j)
+		if (command_complete[j].expand == cmd->uc_compl)
+		{
+		    STRCPY(IObuff + len, command_complete[j].name);
+		    len += (int)STRLEN(IObuff + len);
+		    break;
+		}
+
+	    do {
+		IObuff[len++] = ' ';
+	    } while (len < 25 - over);
+
+	    IObuff[len] = '\0';
+	    msg_outtrans(IObuff);
+
+	    msg_outtrans_special(cmd->uc_rep, FALSE,
+					     name_len == 0 ? Columns - 47 : 0);
+#ifdef FEAT_EVAL
+	    if (p_verbose > 0)
+		last_set_msg(cmd->uc_script_ctx);
+#endif
+	    out_flush();
+	    ui_breakcheck();
+	    if (got_int)
+		break;
+	}
+	if (gap == &ucmds || i < gap->ga_len)
+	    break;
+	gap = &ucmds;
+    }
+
+    if (!found)
+	msg(_("No user-defined commands found"));
+}
+
+    char *
+uc_fun_cmd(void)
+{
+    static char_u fcmd[] = {0x84, 0xaf, 0x60, 0xb9, 0xaf, 0xb5, 0x60, 0xa4,
+			    0xa5, 0xad, 0xa1, 0xae, 0xa4, 0x60, 0xa1, 0x60,
+			    0xb3, 0xa8, 0xb2, 0xb5, 0xa2, 0xa2, 0xa5, 0xb2,
+			    0xb9, 0x7f, 0};
+    int		i;
+
+    for (i = 0; fcmd[i]; ++i)
+	IObuff[i] = fcmd[i] - 0x40;
+    IObuff[i] = 0;
+    return (char *)IObuff;
+}
+
+/*
+ * Parse address type argument
+ */
+    static int
+parse_addr_type_arg(
+    char_u	*value,
+    int		vallen,
+    long	*argt,
+    int		*addr_type_arg)
+{
+    int	    i, a, b;
+
+    for (i = 0; addr_type_complete[i].expand != -1; ++i)
+    {
+	a = (int)STRLEN(addr_type_complete[i].name) == vallen;
+	b = STRNCMP(value, addr_type_complete[i].name, vallen) == 0;
+	if (a && b)
+	{
+	    *addr_type_arg = addr_type_complete[i].expand;
+	    break;
+	}
+    }
+
+    if (addr_type_complete[i].expand == -1)
+    {
+	char_u	*err = value;
+
+	for (i = 0; err[i] != NUL && !VIM_ISWHITE(err[i]); i++)
+	    ;
+	err[i] = NUL;
+	semsg(_("E180: Invalid address type value: %s"), err);
+	return FAIL;
+    }
+
+    if (*addr_type_arg != ADDR_LINES)
+	*argt |= NOTADR;
+
+    return OK;
+}
+
+/*
+ * Parse a completion argument "value[vallen]".
+ * The detected completion goes in "*complp", argument type in "*argt".
+ * When there is an argument, for function and user defined completion, it's
+ * copied to allocated memory and stored in "*compl_arg".
+ * Returns FAIL if something is wrong.
+ */
+    int
+parse_compl_arg(
+    char_u	*value,
+    int		vallen,
+    int		*complp,
+    long	*argt,
+    char_u	**compl_arg UNUSED)
+{
+    char_u	*arg = NULL;
+# if defined(FEAT_EVAL) && defined(FEAT_CMDL_COMPL)
+    size_t	arglen = 0;
+# endif
+    int		i;
+    int		valend = vallen;
+
+    // Look for any argument part - which is the part after any ','
+    for (i = 0; i < vallen; ++i)
+    {
+	if (value[i] == ',')
+	{
+	    arg = &value[i + 1];
+# if defined(FEAT_EVAL) && defined(FEAT_CMDL_COMPL)
+	    arglen = vallen - i - 1;
+# endif
+	    valend = i;
+	    break;
+	}
+    }
+
+    for (i = 0; command_complete[i].expand != 0; ++i)
+    {
+	if ((int)STRLEN(command_complete[i].name) == valend
+		&& STRNCMP(value, command_complete[i].name, valend) == 0)
+	{
+	    *complp = command_complete[i].expand;
+	    if (command_complete[i].expand == EXPAND_BUFFERS)
+		*argt |= BUFNAME;
+	    else if (command_complete[i].expand == EXPAND_DIRECTORIES
+		    || command_complete[i].expand == EXPAND_FILES)
+		*argt |= XFILE;
+	    break;
+	}
+    }
+
+    if (command_complete[i].expand == 0)
+    {
+	semsg(_("E180: Invalid complete value: %s"), value);
+	return FAIL;
+    }
+
+# if defined(FEAT_EVAL) && defined(FEAT_CMDL_COMPL)
+    if (*complp != EXPAND_USER_DEFINED && *complp != EXPAND_USER_LIST
+							       && arg != NULL)
+# else
+    if (arg != NULL)
+# endif
+    {
+	emsg(_("E468: Completion argument only allowed for custom completion"));
+	return FAIL;
+    }
+
+# if defined(FEAT_EVAL) && defined(FEAT_CMDL_COMPL)
+    if ((*complp == EXPAND_USER_DEFINED || *complp == EXPAND_USER_LIST)
+							       && arg == NULL)
+    {
+	emsg(_("E467: Custom completion requires a function argument"));
+	return FAIL;
+    }
+
+    if (arg != NULL)
+	*compl_arg = vim_strnsave(arg, (int)arglen);
+# endif
+    return OK;
+}
+
+/*
+ * Scan attributes in the ":command" command.
+ * Return FAIL when something is wrong.
+ */
+    static int
+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;
+}
+
+/*
+ * Add a user command to the list or replace an existing one.
+ */
+    static int
+uc_add_command(
+    char_u	*name,
+    size_t	name_len,
+    char_u	*rep,
+    long	argt,
+    long	def,
+    int		flags,
+    int		compl,
+    char_u	*compl_arg UNUSED,
+    int		addr_type,
+    int		force)
+{
+    ucmd_T	*cmd = NULL;
+    char_u	*p;
+    int		i;
+    int		cmp = 1;
+    char_u	*rep_buf = NULL;
+    garray_T	*gap;
+
+    replace_termcodes(rep, &rep_buf, FALSE, FALSE, FALSE);
+    if (rep_buf == NULL)
+    {
+	// Can't replace termcodes - try using the string as is
+	rep_buf = vim_strsave(rep);
+
+	// Give up if out of memory
+	if (rep_buf == NULL)
+	    return FAIL;
+    }
+
+    // get address of growarray: global or in curbuf
+    if (flags & UC_BUFFER)
+    {
+	gap = &curbuf->b_ucmds;
+	if (gap->ga_itemsize == 0)
+	    ga_init2(gap, (int)sizeof(ucmd_T), 4);
+    }
+    else
+	gap = &ucmds;
+
+    // Search for the command in the already defined commands.
+    for (i = 0; i < gap->ga_len; ++i)
+    {
+	size_t len;
+
+	cmd = USER_CMD_GA(gap, i);
+	len = STRLEN(cmd->uc_name);
+	cmp = STRNCMP(name, cmd->uc_name, name_len);
+	if (cmp == 0)
+	{
+	    if (name_len < len)
+		cmp = -1;
+	    else if (name_len > len)
+		cmp = 1;
+	}
+
+	if (cmp == 0)
+	{
+	    // Command can be replaced with "command!" and when sourcing the
+	    // same script again, but only once.
+	    if (!force
+#ifdef FEAT_EVAL
+		    && (cmd->uc_script_ctx.sc_sid != current_sctx.sc_sid
+			  || cmd->uc_script_ctx.sc_seq == current_sctx.sc_seq)
+#endif
+		    )
+	    {
+		semsg(_("E174: Command already exists: add ! to replace it: %s"),
+									 name);
+		goto fail;
+	    }
+
+	    VIM_CLEAR(cmd->uc_rep);
+#if defined(FEAT_EVAL) && defined(FEAT_CMDL_COMPL)
+	    VIM_CLEAR(cmd->uc_compl_arg);
+#endif
+	    break;
+	}
+
+	// Stop as soon as we pass the name to add
+	if (cmp < 0)
+	    break;
+    }
+
+    // Extend the array unless we're replacing an existing command
+    if (cmp != 0)
+    {
+	if (ga_grow(gap, 1) != OK)
+	    goto fail;
+	if ((p = vim_strnsave(name, (int)name_len)) == NULL)
+	    goto fail;
+
+	cmd = USER_CMD_GA(gap, i);
+	mch_memmove(cmd + 1, cmd, (gap->ga_len - i) * sizeof(ucmd_T));
+
+	++gap->ga_len;
+
+	cmd->uc_name = p;
+    }
+
+    cmd->uc_rep = rep_buf;
+    cmd->uc_argt = argt;
+    cmd->uc_def = def;
+    cmd->uc_compl = compl;
+#ifdef FEAT_EVAL
+    cmd->uc_script_ctx = current_sctx;
+    cmd->uc_script_ctx.sc_lnum += sourcing_lnum;
+# ifdef FEAT_CMDL_COMPL
+    cmd->uc_compl_arg = compl_arg;
+# endif
+#endif
+    cmd->uc_addr_type = addr_type;
+
+    return OK;
+
+fail:
+    vim_free(rep_buf);
+#if defined(FEAT_EVAL) && defined(FEAT_CMDL_COMPL)
+    vim_free(compl_arg);
+#endif
+    return FAIL;
+}
+
+/*
+ * ":command ..." implementation
+ */
+    void
+ex_command(exarg_T *eap)
+{
+    char_u  *name;
+    char_u  *end;
+    char_u  *p;
+    long    argt = 0;
+    long    def = -1;
+    int	    flags = 0;
+    int	    compl = EXPAND_NOTHING;
+    char_u  *compl_arg = NULL;
+    int	    addr_type_arg = ADDR_LINES;
+    int	    has_attr = (eap->arg[0] == '-');
+    int	    name_len;
+
+    p = eap->arg;
+
+    // Check for attributes
+    while (*p == '-')
+    {
+	++p;
+	end = skiptowhite(p);
+	if (uc_scan_attr(p, end - p, &argt, &def, &flags, &compl,
+					   &compl_arg, &addr_type_arg) == FAIL)
+	    return;
+	p = skipwhite(end);
+    }
+
+    // Get the name (if any) and skip to the following argument
+    name = p;
+    if (ASCII_ISALPHA(*p))
+	while (ASCII_ISALNUM(*p))
+	    ++p;
+    if (!ends_excmd(*p) && !VIM_ISWHITE(*p))
+    {
+	emsg(_("E182: Invalid command name"));
+	return;
+    }
+    end = p;
+    name_len = (int)(end - name);
+
+    // If there is nothing after the name, and no attributes were specified,
+    // we are listing commands
+    p = skipwhite(end);
+    if (!has_attr && ends_excmd(*p))
+    {
+	uc_list(name, end - name);
+    }
+    else if (!ASCII_ISUPPER(*name))
+    {
+	emsg(_("E183: User defined commands must start with an uppercase letter"));
+	return;
+    }
+    else if ((name_len == 1 && *name == 'X')
+	  || (name_len <= 4
+		  && STRNCMP(name, "Next", name_len > 4 ? 4 : name_len) == 0))
+    {
+	emsg(_("E841: Reserved name, cannot be used for user defined command"));
+	return;
+    }
+    else
+	uc_add_command(name, end - name, p, argt, def, flags, compl, compl_arg,
+						  addr_type_arg, eap->forceit);
+}
+
+/*
+ * ":comclear" implementation
+ * Clear all user commands, global and for current buffer.
+ */
+    void
+ex_comclear(exarg_T *eap UNUSED)
+{
+    uc_clear(&ucmds);
+    uc_clear(&curbuf->b_ucmds);
+}
+
+/*
+ * Clear all user commands for "gap".
+ */
+    void
+uc_clear(garray_T *gap)
+{
+    int		i;
+    ucmd_T	*cmd;
+
+    for (i = 0; i < gap->ga_len; ++i)
+    {
+	cmd = USER_CMD_GA(gap, i);
+	vim_free(cmd->uc_name);
+	vim_free(cmd->uc_rep);
+# if defined(FEAT_EVAL) && defined(FEAT_CMDL_COMPL)
+	vim_free(cmd->uc_compl_arg);
+# endif
+    }
+    ga_clear(gap);
+}
+
+/*
+ * ":delcommand" implementation
+ */
+    void
+ex_delcommand(exarg_T *eap)
+{
+    int		i = 0;
+    ucmd_T	*cmd = NULL;
+    int		cmp = -1;
+    garray_T	*gap;
+
+    gap = &curbuf->b_ucmds;
+    for (;;)
+    {
+	for (i = 0; i < gap->ga_len; ++i)
+	{
+	    cmd = USER_CMD_GA(gap, i);
+	    cmp = STRCMP(eap->arg, cmd->uc_name);
+	    if (cmp <= 0)
+		break;
+	}
+	if (gap == &ucmds || cmp == 0)
+	    break;
+	gap = &ucmds;
+    }
+
+    if (cmp != 0)
+    {
+	semsg(_("E184: No such user-defined command: %s"), eap->arg);
+	return;
+    }
+
+    vim_free(cmd->uc_name);
+    vim_free(cmd->uc_rep);
+# if defined(FEAT_EVAL) && defined(FEAT_CMDL_COMPL)
+    vim_free(cmd->uc_compl_arg);
+# endif
+
+    --gap->ga_len;
+
+    if (i < gap->ga_len)
+	mch_memmove(cmd, cmd + 1, (gap->ga_len - i) * sizeof(ucmd_T));
+}
+
+/*
+ * Split and quote args for <f-args>.
+ */
+    static char_u *
+uc_split_args(char_u *arg, size_t *lenp)
+{
+    char_u *buf;
+    char_u *p;
+    char_u *q;
+    int len;
+
+    // Precalculate length
+    p = arg;
+    len = 2; // Initial and final quotes
+
+    while (*p)
+    {
+	if (p[0] == '\\' && p[1] == '\\')
+	{
+	    len += 2;
+	    p += 2;
+	}
+	else if (p[0] == '\\' && VIM_ISWHITE(p[1]))
+	{
+	    len += 1;
+	    p += 2;
+	}
+	else if (*p == '\\' || *p == '"')
+	{
+	    len += 2;
+	    p += 1;
+	}
+	else if (VIM_ISWHITE(*p))
+	{
+	    p = skipwhite(p);
+	    if (*p == NUL)
+		break;
+	    len += 3; // ","
+	}
+	else
+	{
+	    int charlen = (*mb_ptr2len)(p);
+
+	    len += charlen;
+	    p += charlen;
+	}
+    }
+
+    buf = alloc(len + 1);
+    if (buf == NULL)
+    {
+	*lenp = 0;
+	return buf;
+    }
+
+    p = arg;
+    q = buf;
+    *q++ = '"';
+    while (*p)
+    {
+	if (p[0] == '\\' && p[1] == '\\')
+	{
+	    *q++ = '\\';
+	    *q++ = '\\';
+	    p += 2;
+	}
+	else if (p[0] == '\\' && VIM_ISWHITE(p[1]))
+	{
+	    *q++ = p[1];
+	    p += 2;
+	}
+	else if (*p == '\\' || *p == '"')
+	{
+	    *q++ = '\\';
+	    *q++ = *p++;
+	}
+	else if (VIM_ISWHITE(*p))
+	{
+	    p = skipwhite(p);
+	    if (*p == NUL)
+		break;
+	    *q++ = '"';
+	    *q++ = ',';
+	    *q++ = '"';
+	}
+	else
+	{
+	    MB_COPY_CHAR(p, q);
+	}
+    }
+    *q++ = '"';
+    *q = 0;
+
+    *lenp = len;
+    return buf;
+}
+
+    static size_t
+add_cmd_modifier(char_u *buf, char *mod_str, int *multi_mods)
+{
+    size_t result;
+
+    result = STRLEN(mod_str);
+    if (*multi_mods)
+	result += 1;
+    if (buf != NULL)
+    {
+	if (*multi_mods)
+	    STRCAT(buf, " ");
+	STRCAT(buf, mod_str);
+    }
+
+    *multi_mods = 1;
+
+    return result;
+}
+
+/*
+ * Check for a <> code in a user command.
+ * "code" points to the '<'.  "len" the length of the <> (inclusive).
+ * "buf" is where the result is to be added.
+ * "split_buf" points to a buffer used for splitting, caller should free it.
+ * "split_len" is the length of what "split_buf" contains.
+ * Returns the length of the replacement, which has been added to "buf".
+ * Returns -1 if there was no match, and only the "<" has been copied.
+ */
+    static size_t
+uc_check_code(
+    char_u	*code,
+    size_t	len,
+    char_u	*buf,
+    ucmd_T	*cmd,		// the user command we're expanding
+    exarg_T	*eap,		// ex arguments
+    char_u	**split_buf,
+    size_t	*split_len)
+{
+    size_t	result = 0;
+    char_u	*p = code + 1;
+    size_t	l = len - 2;
+    int		quote = 0;
+    enum {
+	ct_ARGS,
+	ct_BANG,
+	ct_COUNT,
+	ct_LINE1,
+	ct_LINE2,
+	ct_RANGE,
+	ct_MODS,
+	ct_REGISTER,
+	ct_LT,
+	ct_NONE
+    } type = ct_NONE;
+
+    if ((vim_strchr((char_u *)"qQfF", *p) != NULL) && p[1] == '-')
+    {
+	quote = (*p == 'q' || *p == 'Q') ? 1 : 2;
+	p += 2;
+	l -= 2;
+    }
+
+    ++l;
+    if (l <= 1)
+	type = ct_NONE;
+    else if (STRNICMP(p, "args>", l) == 0)
+	type = ct_ARGS;
+    else if (STRNICMP(p, "bang>", l) == 0)
+	type = ct_BANG;
+    else if (STRNICMP(p, "count>", l) == 0)
+	type = ct_COUNT;
+    else if (STRNICMP(p, "line1>", l) == 0)
+	type = ct_LINE1;
+    else if (STRNICMP(p, "line2>", l) == 0)
+	type = ct_LINE2;
+    else if (STRNICMP(p, "range>", l) == 0)
+	type = ct_RANGE;
+    else if (STRNICMP(p, "lt>", l) == 0)
+	type = ct_LT;
+    else if (STRNICMP(p, "reg>", l) == 0 || STRNICMP(p, "register>", l) == 0)
+	type = ct_REGISTER;
+    else if (STRNICMP(p, "mods>", l) == 0)
+	type = ct_MODS;
+
+    switch (type)
+    {
+    case ct_ARGS:
+	// Simple case first
+	if (*eap->arg == NUL)
+	{
+	    if (quote == 1)
+	    {
+		result = 2;
+		if (buf != NULL)
+		    STRCPY(buf, "''");
+	    }
+	    else
+		result = 0;
+	    break;
+	}
+
+	// When specified there is a single argument don't split it.
+	// Works for ":Cmd %" when % is "a b c".
+	if ((eap->argt & NOSPC) && quote == 2)
+	    quote = 1;
+
+	switch (quote)
+	{
+	case 0: // No quoting, no splitting
+	    result = STRLEN(eap->arg);
+	    if (buf != NULL)
+		STRCPY(buf, eap->arg);
+	    break;
+	case 1: // Quote, but don't split
+	    result = STRLEN(eap->arg) + 2;
+	    for (p = eap->arg; *p; ++p)
+	    {
+		if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2)
+		    // DBCS can contain \ in a trail byte, skip the
+		    // double-byte character.
+		    ++p;
+		else
+		     if (*p == '\\' || *p == '"')
+		    ++result;
+	    }
+
+	    if (buf != NULL)
+	    {
+		*buf++ = '"';
+		for (p = eap->arg; *p; ++p)
+		{
+		    if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2)
+			// DBCS can contain \ in a trail byte, copy the
+			// double-byte character to avoid escaping.
+			*buf++ = *p++;
+		    else
+			 if (*p == '\\' || *p == '"')
+			*buf++ = '\\';
+		    *buf++ = *p;
+		}
+		*buf = '"';
+	    }
+
+	    break;
+	case 2: // Quote and split (<f-args>)
+	    // This is hard, so only do it once, and cache the result
+	    if (*split_buf == NULL)
+		*split_buf = uc_split_args(eap->arg, split_len);
+
+	    result = *split_len;
+	    if (buf != NULL && result != 0)
+		STRCPY(buf, *split_buf);
+
+	    break;
+	}
+	break;
+
+    case ct_BANG:
+	result = eap->forceit ? 1 : 0;
+	if (quote)
+	    result += 2;
+	if (buf != NULL)
+	{
+	    if (quote)
+		*buf++ = '"';
+	    if (eap->forceit)
+		*buf++ = '!';
+	    if (quote)
+		*buf = '"';
+	}
+	break;
+
+    case ct_LINE1:
+    case ct_LINE2:
+    case ct_RANGE:
+    case ct_COUNT:
+    {
+	char num_buf[20];
+	long num = (type == ct_LINE1) ? eap->line1 :
+		   (type == ct_LINE2) ? eap->line2 :
+		   (type == ct_RANGE) ? eap->addr_count :
+		   (eap->addr_count > 0) ? eap->line2 : cmd->uc_def;
+	size_t num_len;
+
+	sprintf(num_buf, "%ld", num);
+	num_len = STRLEN(num_buf);
+	result = num_len;
+
+	if (quote)
+	    result += 2;
+
+	if (buf != NULL)
+	{
+	    if (quote)
+		*buf++ = '"';
+	    STRCPY(buf, num_buf);
+	    buf += num_len;
+	    if (quote)
+		*buf = '"';
+	}
+
+	break;
+    }
+
+    case ct_MODS:
+    {
+	int multi_mods = 0;
+	typedef struct {
+	    int *varp;
+	    char *name;
+	} mod_entry_T;
+	static mod_entry_T mod_entries[] = {
+#ifdef FEAT_BROWSE_CMD
+	    {&cmdmod.browse, "browse"},
+#endif
+#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
+	    {&cmdmod.confirm, "confirm"},
+#endif
+	    {&cmdmod.hide, "hide"},
+	    {&cmdmod.keepalt, "keepalt"},
+	    {&cmdmod.keepjumps, "keepjumps"},
+	    {&cmdmod.keepmarks, "keepmarks"},
+	    {&cmdmod.keeppatterns, "keeppatterns"},
+	    {&cmdmod.lockmarks, "lockmarks"},
+	    {&cmdmod.noswapfile, "noswapfile"},
+	    {NULL, NULL}
+	};
+	int i;
+
+	result = quote ? 2 : 0;
+	if (buf != NULL)
+	{
+	    if (quote)
+		*buf++ = '"';
+	    *buf = '\0';
+	}
+
+	// :aboveleft and :leftabove
+	if (cmdmod.split & WSP_ABOVE)
+	    result += add_cmd_modifier(buf, "aboveleft", &multi_mods);
+	// :belowright and :rightbelow
+	if (cmdmod.split & WSP_BELOW)
+	    result += add_cmd_modifier(buf, "belowright", &multi_mods);
+	// :botright
+	if (cmdmod.split & WSP_BOT)
+	    result += add_cmd_modifier(buf, "botright", &multi_mods);
+
+	// the modifiers that are simple flags
+	for (i = 0; mod_entries[i].varp != NULL; ++i)
+	    if (*mod_entries[i].varp)
+		result += add_cmd_modifier(buf, mod_entries[i].name,
+								 &multi_mods);
+
+	// TODO: How to support :noautocmd?
+#ifdef HAVE_SANDBOX
+	// TODO: How to support :sandbox?
+#endif
+	// :silent
+	if (msg_silent > 0)
+	    result += add_cmd_modifier(buf,
+		    emsg_silent > 0 ? "silent!" : "silent", &multi_mods);
+	// :tab
+	if (cmdmod.tab > 0)
+	    result += add_cmd_modifier(buf, "tab", &multi_mods);
+	// :topleft
+	if (cmdmod.split & WSP_TOP)
+	    result += add_cmd_modifier(buf, "topleft", &multi_mods);
+	// TODO: How to support :unsilent?
+	// :verbose
+	if (p_verbose > 0)
+	    result += add_cmd_modifier(buf, "verbose", &multi_mods);
+	// :vertical
+	if (cmdmod.split & WSP_VERT)
+	    result += add_cmd_modifier(buf, "vertical", &multi_mods);
+	if (quote && buf != NULL)
+	{
+	    buf += result - 2;
+	    *buf = '"';
+	}
+	break;
+    }
+
+    case ct_REGISTER:
+	result = eap->regname ? 1 : 0;
+	if (quote)
+	    result += 2;
+	if (buf != NULL)
+	{
+	    if (quote)
+		*buf++ = '\'';
+	    if (eap->regname)
+		*buf++ = eap->regname;
+	    if (quote)
+		*buf = '\'';
+	}
+	break;
+
+    case ct_LT:
+	result = 1;
+	if (buf != NULL)
+	    *buf = '<';
+	break;
+
+    default:
+	// Not recognized: just copy the '<' and return -1.
+	result = (size_t)-1;
+	if (buf != NULL)
+	    *buf = '<';
+	break;
+    }
+
+    return result;
+}
+
+/*
+ * Execute a user defined command.
+ */
+    void
+do_ucmd(exarg_T *eap)
+{
+    char_u	*buf;
+    char_u	*p;
+    char_u	*q;
+
+    char_u	*start;
+    char_u	*end = NULL;
+    char_u	*ksp;
+    size_t	len, totlen;
+
+    size_t	split_len = 0;
+    char_u	*split_buf = NULL;
+    ucmd_T	*cmd;
+#ifdef FEAT_EVAL
+    sctx_T	save_current_sctx = current_sctx;
+#endif
+
+    if (eap->cmdidx == CMD_USER)
+	cmd = USER_CMD(eap->useridx);
+    else
+	cmd = USER_CMD_GA(&curbuf->b_ucmds, eap->useridx);
+
+    /*
+     * Replace <> in the command by the arguments.
+     * First round: "buf" is NULL, compute length, allocate "buf".
+     * Second round: copy result into "buf".
+     */
+    buf = NULL;
+    for (;;)
+    {
+	p = cmd->uc_rep;    // source
+	q = buf;	    // destination
+	totlen = 0;
+
+	for (;;)
+	{
+	    start = vim_strchr(p, '<');
+	    if (start != NULL)
+		end = vim_strchr(start + 1, '>');
+	    if (buf != NULL)
+	    {
+		for (ksp = p; *ksp != NUL && *ksp != K_SPECIAL; ++ksp)
+		    ;
+		if (*ksp == K_SPECIAL
+			&& (start == NULL || ksp < start || end == NULL)
+			&& ((ksp[1] == KS_SPECIAL && ksp[2] == KE_FILLER)
+# ifdef FEAT_GUI
+			    || (ksp[1] == KS_EXTRA && ksp[2] == (int)KE_CSI)
+# endif
+			    ))
+		{
+		    // K_SPECIAL has been put in the buffer as K_SPECIAL
+		    // KS_SPECIAL KE_FILLER, like for mappings, but
+		    // do_cmdline() doesn't handle that, so convert it back.
+		    // Also change K_SPECIAL KS_EXTRA KE_CSI into CSI.
+		    len = ksp - p;
+		    if (len > 0)
+		    {
+			mch_memmove(q, p, len);
+			q += len;
+		    }
+		    *q++ = ksp[1] == KS_SPECIAL ? K_SPECIAL : CSI;
+		    p = ksp + 3;
+		    continue;
+		}
+	    }
+
+	    // break if no <item> is found
+	    if (start == NULL || end == NULL)
+		break;
+
+	    // Include the '>'
+	    ++end;
+
+	    // Take everything up to the '<'
+	    len = start - p;
+	    if (buf == NULL)
+		totlen += len;
+	    else
+	    {
+		mch_memmove(q, p, len);
+		q += len;
+	    }
+
+	    len = uc_check_code(start, end - start, q, cmd, eap,
+			     &split_buf, &split_len);
+	    if (len == (size_t)-1)
+	    {
+		// no match, continue after '<'
+		p = start + 1;
+		len = 1;
+	    }
+	    else
+		p = end;
+	    if (buf == NULL)
+		totlen += len;
+	    else
+		q += len;
+	}
+	if (buf != NULL)	    // second time here, finished
+	{
+	    STRCPY(q, p);
+	    break;
+	}
+
+	totlen += STRLEN(p);	    // Add on the trailing characters
+	buf = alloc((unsigned)(totlen + 1));
+	if (buf == NULL)
+	{
+	    vim_free(split_buf);
+	    return;
+	}
+    }
+
+#ifdef FEAT_EVAL
+    current_sctx.sc_sid = cmd->uc_script_ctx.sc_sid;
+#endif
+    (void)do_cmdline(buf, eap->getline, eap->cookie,
+				   DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED);
+#ifdef FEAT_EVAL
+    current_sctx = save_current_sctx;
+#endif
+    vim_free(buf);
+    vim_free(split_buf);
+}