changeset 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 eb356c5e6287
children e7c242103171
files Filelist runtime/doc/eval.txt runtime/doc/various.txt src/Make_bc5.mak src/Make_cyg_ming.mak src/Make_dice.mak src/Make_ivc.mak src/Make_manx.mak src/Make_morph.mak src/Make_mvc.mak src/Make_sas.mak src/Make_vms.mms src/Makefile src/README.md src/buffer.c src/eval.c src/evalfunc.c src/ex_cmds.h src/ex_docmd.c src/ex_getln.c src/feature.h src/macros.h src/misc2.c src/proto.h src/proto/ex_docmd.pro src/proto/usercmd.pro src/structs.h src/usercmd.c src/version.c
diffstat 29 files changed, 1779 insertions(+), 1739 deletions(-) [+]
line wrap: on
line diff
--- a/Filelist
+++ b/Filelist
@@ -98,6 +98,7 @@ SRC_ALL =	\
 		src/textprop.c \
 		src/ui.c \
 		src/undo.c \
+		src/usercmd.c \
 		src/userfunc.c \
 		src/version.c \
 		src/version.h \
@@ -212,6 +213,7 @@ SRC_ALL =	\
 		src/proto/textprop.pro \
 		src/proto/ui.pro \
 		src/proto/undo.pro \
+		src/proto/usercmd.pro \
 		src/proto/userfunc.pro \
 		src/proto/version.pro \
 		src/proto/winclip.pro \
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -1,4 +1,4 @@
-*eval.txt*	For Vim version 8.1.  Last change: 2019 Apr 21
+*eval.txt*	For Vim version 8.1.  Last change: 2019 Apr 27
 
 
 		  VIM REFERENCE MANUAL	  by Bram Moolenaar
@@ -10550,7 +10550,7 @@ ttyin			input is a terminal (tty)
 ttyout			output is a terminal (tty)
 unix			Unix version of Vim. *+unix*
 unnamedplus		Compiled with support for "unnamedplus" in 'clipboard'
-user_commands		User-defined commands.
+user_commands		User-defined commands. (always true)
 vcon			Win32: Virtual console support is working, can use
 			'termguicolors'. Also see |+vtp|.
 vertsplit		Compiled with vertically split windows |:vsplit|.
--- a/runtime/doc/various.txt
+++ b/runtime/doc/various.txt
@@ -456,7 +456,8 @@ N  *+textprop*		|text-properties|
 N  *+timers*		the |timer_start()| function
 N  *+title*		Setting the window 'title' and 'icon'
 N  *+toolbar*		|gui-toolbar|
-N  *+user_commands*	User-defined commands. |user-commands|
+T  *+user_commands*	User-defined commands. |user-commands|
+			Always enabled since 8.1.1210.
 B  *+vartabs*		Variable-width tabstops. |'vartabstop'|
 N  *+viminfo*		|'viminfo'|
    *+vertsplit*		Vertically split windows |:vsplit|; Always enabled
--- a/src/Make_bc5.mak
+++ b/src/Make_bc5.mak
@@ -565,6 +565,7 @@ vimobj =  \
 	$(OBJDIR)\term.obj \
 	$(OBJDIR)\ui.obj \
 	$(OBJDIR)\undo.obj \
+	$(OBJDIR)\usercmd.obj \
 	$(OBJDIR)\userfunc.obj \
 	$(OBJDIR)\version.obj \
 	$(OBJDIR)\window.obj \
--- a/src/Make_cyg_ming.mak
+++ b/src/Make_cyg_ming.mak
@@ -757,6 +757,7 @@ OBJ = \
 	$(OUTDIR)/textprop.o \
 	$(OUTDIR)/ui.o \
 	$(OUTDIR)/undo.o \
+	$(OUTDIR)/usercmd.o \
 	$(OUTDIR)/userfunc.o \
 	$(OUTDIR)/version.o \
 	$(OUTDIR)/vimrc.o \
--- a/src/Make_dice.mak
+++ b/src/Make_dice.mak
@@ -83,6 +83,7 @@ SRC = \
 	term.c \
 	ui.c \
 	undo.c \
+	usercmd.c \
 	userfunc.c \
 	window.c \
 	version.c
@@ -144,6 +145,7 @@ OBJ =	o/arabic.o \
 	o/term.o \
 	o/ui.o \
 	o/undo.o \
+	o/usercmd.o \
 	o/userfunc.o \
 	o/window.o \
 	$(TERMLIB)
@@ -288,6 +290,8 @@ o/ui.o: 	ui.c	$(SYMS)
 
 o/undo.o: 	undo.c	$(SYMS)
 
+o/usercmd.o: 	usercmd.c  $(SYMS)
+
 o/userfunc.o: 	userfunc.c  $(SYMS)
 
 o/window.o: 	window.c  $(SYMS)
--- a/src/Make_ivc.mak
+++ b/src/Make_ivc.mak
@@ -269,6 +269,7 @@ LINK32_OBJS= \
 	"$(INTDIR)/term.obj" \
 	"$(INTDIR)/ui.obj" \
 	"$(INTDIR)/undo.obj" \
+	"$(INTDIR)/usercmd.obj" \
 	"$(INTDIR)/userfunc.obj" \
 	"$(INTDIR)/version.obj" \
 	"$(INTDIR)/window.obj"
@@ -728,6 +729,10 @@ SOURCE=.\undo.c
 # End Source File
 # Begin Source File
 
+SOURCE=.\usercmd.c
+# End Source File
+# Begin Source File
+
 SOURCE=.\userfunc.c
 # End Source File
 # Begin Source File
--- a/src/Make_manx.mak
+++ b/src/Make_manx.mak
@@ -93,6 +93,7 @@ SRC =	arabic.c \
 	term.c \
 	ui.c \
 	undo.c \
+	usercmd.c \
 	userfunc.c \
 	window.c \
 	version.c
@@ -156,6 +157,7 @@ OBJ =	obj/arabic.o \
 	obj/term.o \
 	obj/ui.o \
 	obj/undo.o \
+	obj/usercmd.o \
 	obj/userfunc.o \
 	obj/window.o \
 	$(TERMLIB)
@@ -218,6 +220,7 @@ PRO =	proto/arabic.pro \
 	proto/termlib.pro \
 	proto/ui.pro \
 	proto/undo.pro \
+	proto/usercmd.pro \
 	proto/userfunc.pro \
 	proto/window.pro
 
@@ -443,6 +446,9 @@ obj/ui.o:	ui.c
 obj/undo.o:	undo.c
 	$(CCSYM) $@ undo.c
 
+obj/usercmd.o:	usercmd.c
+	$(CCSYM) $@ usercmd.c
+
 obj/userfunc.o:	userfunc.c
 	$(CCSYM) $@ userfunc.c
 
--- a/src/Make_morph.mak
+++ b/src/Make_morph.mak
@@ -81,6 +81,7 @@ SRC =	arabic.c						\
 	term.c							\
 	ui.c							\
 	undo.c							\
+	usercmd.c						\
 	userfunc.c						\
 	version.c						\
 	window.c						\
--- a/src/Make_mvc.mak
+++ b/src/Make_mvc.mak
@@ -765,6 +765,7 @@ OBJ = \
 	$(OUTDIR)\textprop.obj \
 	$(OUTDIR)\ui.obj \
 	$(OUTDIR)\undo.obj \
+	$(OUTDIR)\usercmd.obj \
 	$(OUTDIR)\userfunc.obj \
 	$(OUTDIR)\winclip.obj \
 	$(OUTDIR)\window.obj \
@@ -1550,6 +1551,8 @@ lib$(MZSCHEME_MAIN_LIB)$(MZSCHEME_VER).l
 
 $(OUTDIR)/undo.obj:	$(OUTDIR) undo.c  $(INCL)
 
+$(OUTDIR)/usercmd.obj:	$(OUTDIR) usercmd.c  $(INCL)
+
 $(OUTDIR)/userfunc.obj:	$(OUTDIR) userfunc.c  $(INCL)
 
 $(OUTDIR)/window.obj:	$(OUTDIR) window.c  $(INCL)
@@ -1693,6 +1696,7 @@ proto.h: \
 	proto/textprop.pro \
 	proto/ui.pro \
 	proto/undo.pro \
+	proto/usercmd.pro \
 	proto/userfunc.pro \
 	proto/window.pro \
 	$(NETBEANS_PRO) \
--- a/src/Make_sas.mak
+++ b/src/Make_sas.mak
@@ -146,6 +146,7 @@ SRC = \
 	term.c \
 	ui.c \
 	undo.c \
+	usercmd.c \
 	userfunc.c \
 	window.c \
 	version.c
@@ -208,6 +209,7 @@ OBJ = \
 	term.o \
 	ui.o \
 	undo.o \
+	usercmd.o \
 	userfunc.o \
 	window.o \
 	$(TERMLIB)
@@ -271,6 +273,7 @@ PRO = \
 	proto/termlib.pro \
 	proto/ui.pro \
 	proto/undo.pro \
+	proto/usercmd.pro \
 	proto/userfunc.pro \
 	proto/window.pro
 
@@ -445,6 +448,8 @@ ui.o:			ui.c
 proto/ui.pro:		ui.c
 undo.o:			undo.c
 proto/undo.pro:		undo.c
+usercmd.o:		usercmd.c
+proto/usercmd.pro:	usercmd.c
 userfunc.o:		userfunc.c
 proto/userfunc.pro:	userfunc.c
 window.o:		window.c
--- a/src/Make_vms.mms
+++ b/src/Make_vms.mms
@@ -2,7 +2,7 @@
 # Makefile for Vim on OpenVMS
 #
 # Maintainer:   Zoltan Arpadffy <arpadffy@polarhome.com>
-# Last change:  2019 Mar 22
+# Last change:  2019 Apr 26
 #
 # This has script been tested on VMS 6.2 to 8.2 on DEC Alpha, VAX and IA64
 # with MMS and MMK
@@ -315,8 +315,8 @@ SRC =	arabic.c autocmd.c beval.c blob.c 
 	menu.c mbyte.c memfile.c memline.c message.c misc1.c misc2.c move.c \
 	normal.c ops.c option.c popupmnu.c quickfix.c regexp.c search.c \
 	sha256.c sign.c spell.c spellfile.c syntax.c tag.c term.c termlib.c \
-	textprop.c ui.c undo.c userfunc.c version.c screen.c window.c \
-	os_unix.c os_vms.c pathdef.c \
+	textprop.c ui.c undo.c usercmd.c userfunc.c version.c screen.c \
+	window.c os_unix.c os_vms.c pathdef.c \
 	$(GUI_SRC) $(PERL_SRC) $(PYTHON_SRC) $(TCL_SRC) \
  	$(RUBY_SRC) $(HANGULIN_SRC) $(MZSCH_SRC) $(XDIFF_SRC)
 
@@ -330,7 +330,7 @@ OBJ = 	arabic.obj autocmd.obj beval.obj 
 	move.obj mbyte.obj normal.obj ops.obj option.obj popupmnu.obj \
 	quickfix.obj regexp.obj search.obj sha256.obj sign.obj spell.obj \
 	spellfile.obj syntax.obj tag.obj term.obj termlib.obj textprop.obj \
-	ui.obj undo.obj userfunc.obj screen.obj version.obj \
+	ui.obj undo.obj usercmd.obj userfunc.obj screen.obj version.obj \
 	window.obj os_unix.obj os_vms.obj pathdef.obj if_mzsch.obj \
 	$(GUI_OBJ) $(PERL_OBJ) $(PYTHON_OBJ) $(TCL_OBJ) \
  	$(RUBY_OBJ) $(HANGULIN_OBJ) $(MZSCH_OBJ) $(XDIFF_OBJ)
@@ -744,10 +744,16 @@ undo.obj : undo.c vim.h [.auto]config.h 
  ascii.h keymap.h term.h macros.h structs.h regexp.h gui.h beval.h \
  [.proto]gui_beval.pro option.h ex_cmds.h proto.h globals.h \
 
+usercmd.obj : usercmd.c vim.h [.auto]config.h feature.h os_unix.h \
+ ascii.h keymap.h term.h macros.h option.h structs.h \
+ regexp.h gui.h beval.h [.proto]gui_beval.pro alloc.h ex_cmds.h spell.h \
+ proto.h globals.h
+
 userfunc.obj : userfunc.c vim.h [.auto]config.h feature.h os_unix.h \
  ascii.h keymap.h term.h macros.h option.h structs.h \
  regexp.h gui.h beval.h [.proto]gui_beval.pro alloc.h ex_cmds.h spell.h \
  proto.h globals.h
+
 version.obj : version.c vim.h [.auto]config.h feature.h os_unix.h \
  ascii.h keymap.h term.h macros.h structs.h regexp.h \
  gui.h beval.h [.proto]gui_beval.pro option.h ex_cmds.h proto.h \
--- a/src/Makefile
+++ b/src/Makefile
@@ -1635,6 +1635,7 @@ BASIC_SRC = \
 	textprop.c \
 	ui.c \
 	undo.c \
+	usercmd.c \
 	userfunc.c \
 	version.c \
 	window.c \
@@ -1747,6 +1748,7 @@ OBJ_COMMON = \
 	objects/textprop.o \
 	objects/ui.o \
 	objects/undo.o \
+	objects/usercmd.o \
 	objects/userfunc.o \
 	objects/version.o \
 	objects/window.o \
@@ -1885,6 +1887,7 @@ PRO_AUTO = \
 	textprop.pro \
 	ui.pro \
 	undo.pro \
+	usercmd.pro \
 	userfunc.pro \
 	version.pro \
 	window.pro \
@@ -3242,6 +3245,9 @@ objects/ui.o: ui.c
 objects/undo.o: undo.c
 	$(CCC) -o $@ undo.c
 
+objects/usercmd.o: usercmd.c
+	$(CCC) -o $@ usercmd.c
+
 objects/userfunc.o: userfunc.c
 	$(CCC) -o $@ userfunc.c
 
@@ -3657,6 +3663,10 @@ objects/undo.o: undo.c vim.h protodef.h 
  auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
  proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
  proto.h globals.h
+objects/usercmd.o: usercmd.c vim.h protodef.h auto/config.h feature.h os_unix.h \
+ auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
+ proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
+ proto.h globals.h
 objects/userfunc.o: userfunc.c vim.h protodef.h auto/config.h feature.h os_unix.h \
  auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
  proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
--- a/src/README.md
+++ b/src/README.md
@@ -28,6 +28,7 @@ buffer.c	| manipulating buffers (loaded 
 debugger.c	| vim script debugger
 diff.c		| diff mode (vimdiff)
 eval.c		| expression evaluation
+evalfunc.c	| built-in functions
 fileio.c	| reading and writing files
 findfile.c	| search for files in 'path'
 fold.c		| folding
@@ -40,7 +41,7 @@ memfile.c	| storing lines for buffers in
 memline.c	| storing lines for buffers in memory
 menu.c		| menus
 message.c	| (error) messages
-ops.c		  | handling operators ("d", "y", "p")
+ops.c		| handling operators ("d", "y", "p")
 option.c	| options
 quickfix.c	| quickfix commands (":make", ":cn")
 regexp.c	| pattern matching
@@ -49,9 +50,11 @@ search.c	| pattern searching
 sign.c		| signs
 spell.c		| spell checking
 syntax.c	|  syntax and other highlighting
-tag.c		  | tags
+tag.c		| tags
 term.c		| terminal handling, termcap codes
 undo.c		| undo and redo
+usercmd.c	| user defined commands
+userfunc.c	| user defined functions
 window.c	| handling split windows
 
 
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -925,11 +925,9 @@ free_buffer_stuff(
 	CHANGEDTICK(buf) = tick;
     }
 #endif
-#ifdef FEAT_USR_CMDS
-    uc_clear(&buf->b_ucmds);		/* clear local user commands */
-#endif
+    uc_clear(&buf->b_ucmds);		// clear local user commands
 #ifdef FEAT_SIGNS
-    buf_delete_signs(buf, (char_u *)"*");	// delete any signs */
+    buf_delete_signs(buf, (char_u *)"*");	// delete any signs
 #endif
 #ifdef FEAT_NETBEANS_INTG
     netbeans_file_killed(buf);
--- a/src/eval.c
+++ b/src/eval.c
@@ -1120,10 +1120,10 @@ call_func_retnr(
     return retval;
 }
 
-#if (defined(FEAT_USR_CMDS) && defined(FEAT_CMDL_COMPL)) \
+#if defined(FEAT_CMDL_COMPL) \
 	|| defined(FEAT_COMPL_FUNC) || defined(PROTO)
 
-# if (defined(FEAT_USR_CMDS) && defined(FEAT_CMDL_COMPL)) || defined(PROTO)
+# if defined(FEAT_CMDL_COMPL) || defined(PROTO)
 /*
  * Call Vim script function "func" and return the result as a string.
  * Returns NULL when calling the function fails.
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -6611,10 +6611,8 @@ f_has(typval_T *argvars, typval_T *rettv
 #if defined(FEAT_CLIPBOARD) && defined(FEAT_X11)
 	"unnamedplus",
 #endif
-#ifdef FEAT_USR_CMDS
 	"user-commands",    /* was accidentally included in 5.4 */
 	"user_commands",
-#endif
 #ifdef FEAT_VARTABS
 	"vartabs",
 #endif
--- a/src/ex_cmds.h
+++ b/src/ex_cmds.h
@@ -1753,13 +1753,9 @@ EX(CMD_tilde,		"~",		do_sub,
 			ADDR_LINES),
 
 #ifndef DO_DECLARE_EXCMD
-#ifdef FEAT_USR_CMDS
     CMD_SIZE,		/* MUST be after all real commands! */
     CMD_USER = -1,	/* User-defined command */
     CMD_USER_BUF = -2	/* User-defined command local to buffer */
-#else
-    CMD_SIZE	/* MUST be the last one! */
-#endif
 #endif
 };
 
@@ -1795,9 +1791,7 @@ struct exarg
     int		force_ff;	/* ++ff= argument (first char of argument) */
     int		force_enc;	/* ++enc= argument (index in cmd[]) */
     int		bad_char;	/* BAD_KEEP, BAD_DROP or replacement byte */
-#ifdef FEAT_USR_CMDS
     int		useridx;	/* user command index */
-#endif
     char	*errmsg;	/* returned error message */
     char_u	*(*getline)(int, void *, int);
     void	*cookie;	/* argument for getline() */
--- a/src/ex_docmd.c
+++ b/src/ex_docmd.c
@@ -19,48 +19,6 @@ static int	ex_pressedreturn = FALSE;
 # define ex_hardcopy	ex_ni
 #endif
 
-#ifdef FEAT_USR_CMDS
-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;
-
-#define UC_BUFFER	1	/* -buffer: local to current buffer */
-
-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])
-
-static void do_ucmd(exarg_T *eap);
-static void ex_command(exarg_T *eap);
-static void ex_delcommand(exarg_T *eap);
-# ifdef FEAT_CMDL_COMPL
-static char_u *get_user_command_name(int idx);
-# endif
-
-/* Wether a command index indicates a user command. */
-# define IS_USER_CMDIDX(idx) ((int)(idx) < 0)
-
-#else
-# define ex_command	ex_ni
-# define ex_comclear	ex_ni
-# define ex_delcommand	ex_ni
-/* Wether a command index indicates a user command. */
-# define IS_USER_CMDIDX(idx) (FALSE)
-#endif
-
 #ifdef FEAT_EVAL
 static char_u	*do_one_cmd(char_u **, int, struct condstack *, char_u *(*fgetline)(int, void *, int), void *cookie);
 #else
@@ -300,10 +258,6 @@ static void	ex_redrawtabline(exarg_T *ea
 static void	close_redir(void);
 static void	ex_mkrc(exarg_T *eap);
 static void	ex_mark(exarg_T *eap);
-#ifdef FEAT_USR_CMDS
-static char	*uc_fun_cmd(void);
-static char_u	*find_ucmd(exarg_T *eap, char_u *p, int *full, expand_T *xp, int *compl);
-#endif
 static void	ex_startinsert(exarg_T *eap);
 static void	ex_stopinsert(exarg_T *eap);
 #ifdef FEAT_FIND_ID
@@ -1929,21 +1883,20 @@ do_one_cmd(
 		) ? find_command(&ea, NULL) : ea.cmd;
     }
 
-#ifdef FEAT_USR_CMDS
     if (p == NULL)
     {
 	if (!ea.skip)
 	    errormsg = _("E464: Ambiguous use of user-defined command");
 	goto doend;
     }
-    /* Check for wrong commands. */
+    // Check for wrong commands.
     if (*p == '!' && ea.cmd[1] == 0151 && ea.cmd[0] == 78
 	    && !IS_USER_CMDIDX(ea.cmdidx))
     {
 	errormsg = uc_fun_cmd();
 	goto doend;
     }
-#endif
+
     if (ea.cmdidx == CMD_SIZE)
     {
 	if (!ea.skip)
@@ -2508,7 +2461,6 @@ do_one_cmd(
  * 7. Execute the command.
  */
 
-#ifdef FEAT_USR_CMDS
     if (IS_USER_CMDIDX(ea.cmdidx))
     {
 	/*
@@ -2517,10 +2469,9 @@ do_one_cmd(
 	do_ucmd(&ea);
     }
     else
-#endif
     {
 	/*
-	 * Call the function to execute the command.
+	 * Call the function to execute the builtin command.
 	 */
 	ea.errmsg = NULL;
 	(cmdnames[ea.cmdidx].cmd_func)(&ea);
@@ -3235,18 +3186,16 @@ find_command(exarg_T *eap, int *full UNU
 		break;
 	    }
 
-#ifdef FEAT_USR_CMDS
-	/* Look for a user defined command as a last resort.  Let ":Print" be
-	 * overruled by a user defined command. */
+	// Look for a user defined command as a last resort.  Let ":Print" be
+	// overruled by a user defined command.
 	if ((eap->cmdidx == CMD_SIZE || eap->cmdidx == CMD_Print)
 		&& *eap->cmd >= 'A' && *eap->cmd <= 'Z')
 	{
-	    /* User defined commands may contain digits. */
+	    // User defined commands may contain digits.
 	    while (ASCII_ISALNUM(*p))
 		++p;
 	    p = find_ucmd(eap, p, full, NULL, NULL);
 	}
-#endif
 	if (p == eap->cmd)
 	    eap->cmdidx = CMD_SIZE;
     }
@@ -3254,124 +3203,6 @@ find_command(exarg_T *eap, int *full UNU
     return p;
 }
 
-#ifdef FEAT_USR_CMDS
-/*
- * 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.
- */
-    static 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)	/* 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;
-}
-#endif
-
 #if defined(FEAT_EVAL) || defined(PROTO)
 static struct cmdmod
 {
@@ -3483,10 +3314,8 @@ set_one_cmd_context(
     char_u		*cmd, *arg;
     int			len = 0;
     exarg_T		ea;
-#if defined(FEAT_USR_CMDS) && defined(FEAT_CMDL_COMPL)
+#ifdef FEAT_CMDL_COMPL
     int			compl = EXPAND_NOTHING;
-#endif
-#ifdef FEAT_CMDL_COMPL
     int			delim;
 #endif
     int			forceit = FALSE;
@@ -3572,11 +3401,9 @@ set_one_cmd_context(
 							    (size_t)len) == 0)
 		break;
 
-#ifdef FEAT_USR_CMDS
 	if (cmd[0] >= 'A' && cmd[0] <= 'Z')
-	    while (ASCII_ISALNUM(*p) || *p == '*')	/* Allow * wild card */
+	    while (ASCII_ISALNUM(*p) || *p == '*')	// Allow * wild card
 		++p;
-#endif
     }
 
     /*
@@ -3593,21 +3420,19 @@ set_one_cmd_context(
 	    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)
+#if defined(FEAT_CMDL_COMPL)
 		    &compl
-# else
+#else
 		    NULL
-# endif
+#endif
 		    );
 	    if (p == NULL)
-		ea.cmdidx = CMD_SIZE;	/* ambiguous user command */
-	}
-#endif
+		ea.cmdidx = CMD_SIZE;	// ambiguous user command
+	}
     }
     if (ea.cmdidx == CMD_SIZE)
     {
@@ -3828,7 +3653,7 @@ set_one_cmd_context(
 	    {
 		xp->xp_context = EXPAND_ENV_VARS;
 		++xp->xp_pattern;
-#if defined(FEAT_USR_CMDS) && defined(FEAT_CMDL_COMPL)
+#if 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;
@@ -3944,68 +3769,13 @@ set_one_cmd_context(
  * 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);
+	    return set_context_in_user_cmd(xp, arg);
 
 	case CMD_delcommand:
 	    xp->xp_context = EXPAND_USER_COMMANDS;
 	    xp->xp_pattern = arg;
 	    break;
-# endif
 
 	case CMD_global:
 	case CMD_vglobal:
@@ -4186,32 +3956,32 @@ set_one_cmd_context(
 	    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 */
+		// XFILE: file names are handled above
 		if (!(ea.argt & XFILE))
 		{
-# ifdef FEAT_MENU
+#ifdef FEAT_MENU
 		    if (compl == EXPAND_MENUS)
 			return set_context_in_menu_cmd(xp, cmd, arg, forceit);
-# endif
+#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. */
+		    // Find start of last argument.
 		    p = arg;
 		    while (*p)
 		    {
 			if (*p == ' ')
-			    /* argument starts after a space */
+			    // argument starts after a space
 			    arg = p + 1;
 			else if (*p == '\\' && *(p + 1) != NUL)
-			    ++p; /* skip over escaped character */
+			    ++p; // skip over escaped character
 			MB_PTR_ADV(p);
 		    }
 		    xp->xp_pattern = arg;
@@ -4219,7 +3989,7 @@ set_one_cmd_context(
 		xp->xp_context = compl;
 	    }
 	    break;
-#endif
+
 	case CMD_map:	    case CMD_noremap:
 	case CMD_nmap:	    case CMD_nnoremap:
 	case CMD_vmap:	    case CMD_vnoremap:
@@ -5771,7 +5541,7 @@ check_more(
     return OK;
 }
 
-#ifdef FEAT_CMDL_COMPL
+#if defined(FEAT_CMDL_COMPL) || defined(PROTO)
 /*
  * Function given to ExpandGeneric() to obtain the list of command names.
  */
@@ -5779,1440 +5549,11 @@ check_more(
 get_command_name(expand_T *xp UNUSED, int idx)
 {
     if (idx >= (int)CMD_SIZE)
-# ifdef FEAT_USR_CMDS
 	return get_user_command_name(idx);
-# else
-	return NULL;
-# endif
     return cmdnames[idx].cmd_name;
 }
 #endif
 
-#if defined(FEAT_USR_CMDS) || defined(PROTO)
-    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,
-    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 && (cmd->uc_script_ctx.sc_sid != current_sctx.sc_sid
-			  || cmd->uc_script_ctx.sc_seq == current_sctx.sc_seq))
-	    {
-		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;
-}
-#endif
-
-#if defined(FEAT_USR_CMDS)
-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}
-};
-#endif
-
-#if defined(FEAT_USR_CMDS) || defined(FEAT_EVAL) || defined(PROTO)
-/*
- * 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}
-};
-#endif
-
-#if defined(FEAT_USR_CMDS) || defined(PROTO)
-    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"));
-}
-
-    static 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;
-}
-
-    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;
-}
-
-/*
- * ":command ..."
- */
-    static 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"
- * 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);
-}
-
-    static 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;
-}
-
-    static 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);
-}
-
-# if defined(FEAT_CMDL_COMPL) || defined(PROTO)
-    static 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;
-}
-# endif /* FEAT_CMDL_COMPL */
-
-/*
- * Parse address type argument
- */
-    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;
-}
-
-#endif	/* FEAT_USR_CMDS */
-
-#if defined(FEAT_USR_CMDS) || defined(FEAT_EVAL) || defined(PROTO)
-/*
- * 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;
-}
-
-    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
-
     static void
 ex_colorscheme(exarg_T *eap)
 {
--- a/src/ex_getln.c
+++ b/src/ex_getln.c
@@ -111,7 +111,7 @@ static int	ExpandPackAddDir(char_u *pat,
 # ifdef FEAT_CMDHIST
 static char_u	*get_history_arg(expand_T *xp, int idx);
 # endif
-# if defined(FEAT_USR_CMDS) && defined(FEAT_EVAL)
+# if defined(FEAT_EVAL)
 static int	ExpandUserDefined(expand_T *xp, regmatch_T *regmatch, int *num_file, char_u ***file);
 static int	ExpandUserList(expand_T *xp, int *num_file, char_u ***file);
 # endif
@@ -939,7 +939,7 @@ getcmdline_int(
     {
 	xpc.xp_context = ccline.xp_context;
 	xpc.xp_pattern = ccline.cmdbuff;
-# if defined(FEAT_USR_CMDS) && defined(FEAT_CMDL_COMPL)
+# if defined(FEAT_CMDL_COMPL)
 	xpc.xp_arg = ccline.xp_arg;
 # endif
     }
@@ -4210,7 +4210,7 @@ ExpandInit(expand_T *xp)
 #endif
     xp->xp_numfiles = -1;
     xp->xp_files = NULL;
-#if defined(FEAT_USR_CMDS) && defined(FEAT_EVAL) && defined(FEAT_CMDL_COMPL)
+#if defined(FEAT_EVAL) && defined(FEAT_CMDL_COMPL)
     xp->xp_arg = NULL;
 #endif
     xp->xp_line = NULL;
@@ -4879,7 +4879,7 @@ set_cmd_context(
     {
 	xp->xp_context = ccline.xp_context;
 	xp->xp_pattern = ccline.cmdbuff;
-# if defined(FEAT_USR_CMDS) && defined(FEAT_CMDL_COMPL)
+# if defined(FEAT_CMDL_COMPL)
 	xp->xp_arg = ccline.xp_arg;
 # endif
     }
@@ -5130,7 +5130,7 @@ ExpandFromContext(
 	char *directories[] = {"syntax", "indent", "ftplugin", NULL};
 	return ExpandRTDir(pat, 0, num_file, file, directories);
     }
-# if defined(FEAT_USR_CMDS) && defined(FEAT_EVAL)
+# if defined(FEAT_EVAL)
     if (xp->xp_context == EXPAND_USER_LIST)
 	return ExpandUserList(xp, num_file, file);
 # endif
@@ -5149,7 +5149,7 @@ ExpandFromContext(
 	ret = ExpandSettings(xp, &regmatch, num_file, file);
     else if (xp->xp_context == EXPAND_MAPPINGS)
 	ret = ExpandMappings(&regmatch, num_file, file);
-# if defined(FEAT_USR_CMDS) && defined(FEAT_EVAL)
+# if defined(FEAT_EVAL)
     else if (xp->xp_context == EXPAND_USER_DEFINED)
 	ret = ExpandUserDefined(xp, &regmatch, num_file, file);
 # endif
@@ -5170,13 +5170,11 @@ ExpandFromContext(
 #ifdef FEAT_CMDHIST
 	    {EXPAND_HISTORY, get_history_arg, TRUE, TRUE},
 #endif
-#ifdef FEAT_USR_CMDS
 	    {EXPAND_USER_COMMANDS, get_user_commands, FALSE, TRUE},
 	    {EXPAND_USER_ADDR_TYPE, get_user_cmd_addr_type, FALSE, TRUE},
 	    {EXPAND_USER_CMD_FLAGS, get_user_cmd_flags, FALSE, TRUE},
 	    {EXPAND_USER_NARGS, get_user_cmd_nargs, FALSE, TRUE},
 	    {EXPAND_USER_COMPLETE, get_user_cmd_complete, FALSE, TRUE},
-#endif
 #ifdef FEAT_EVAL
 	    {EXPAND_USER_VARS, get_user_var_name, FALSE, TRUE},
 	    {EXPAND_FUNCTIONS, get_function_name, FALSE, TRUE},
@@ -5473,7 +5471,7 @@ expand_shellcmd(
 }
 
 
-# if defined(FEAT_USR_CMDS) && defined(FEAT_EVAL)
+# if defined(FEAT_EVAL)
 /*
  * Call "user_expand_func()" to invoke a user defined Vim script function and
  * return the result (either a string or a List).
--- a/src/feature.h
+++ b/src/feature.h
@@ -379,10 +379,8 @@
 
 /*
  * +user_commands	Allow the user to define his own commands.
+ *			Now always enabled.
  */
-#ifdef FEAT_NORMAL
-# define FEAT_USR_CMDS
-#endif
 
 /*
  * +printer		":hardcopy" command
--- a/src/macros.h
+++ b/src/macros.h
@@ -336,3 +336,6 @@
 	    (p) = NULL; \
 	} \
     } while (0)
+
+/* Wether a command index indicates a user command. */
+#define IS_USER_CMDIDX(idx) ((int)(idx) < 0)
--- a/src/misc2.c
+++ b/src/misc2.c
@@ -1082,10 +1082,8 @@ free_all_mem(void)
     ui_remove_balloon();
 # endif
 
-# if defined(FEAT_USR_CMDS)
-    /* Clear user commands (before deleting buffers). */
+    // Clear user commands (before deleting buffers).
     ex_comclear(NULL);
-# endif
 
 # ifdef FEAT_MENU
     /* Clear menus. */
@@ -1130,7 +1128,9 @@ free_all_mem(void)
     free_search_patterns();
     free_old_sub();
     free_last_insert();
+# if defined(FEAT_INS_EXPAND)
     free_insexpand_stuff();
+# endif
     free_prev_shellcmd();
     free_regexp_stuff();
     free_tag_stuff();
--- a/src/proto.h
+++ b/src/proto.h
@@ -227,6 +227,7 @@ void qsort(void *base, size_t elm_count,
 # endif
 # include "ui.pro"
 # include "undo.pro"
+# include "usercmd.pro"
 # include "userfunc.pro"
 # include "version.pro"
 # include "window.pro"
--- a/src/proto/ex_docmd.pro
+++ b/src/proto/ex_docmd.pro
@@ -19,16 +19,6 @@ int ends_excmd(int c);
 char_u *find_nextcmd(char_u *p);
 char_u *check_nextcmd(char_u *p);
 char_u *get_command_name(expand_T *xp, int idx);
-void ex_comclear(exarg_T *eap);
-void uc_clear(garray_T *gap);
-char_u *get_user_commands(expand_T *xp, int idx);
-char_u *get_user_cmd_addr_type(expand_T *xp, int idx);
-char_u *get_user_cmd_flags(expand_T *xp, int idx);
-char_u *get_user_cmd_nargs(expand_T *xp, int idx);
-char_u *get_user_cmd_complete(expand_T *xp, int idx);
-int parse_addr_type_arg(char_u *value, int vallen, long *argt, int *addr_type_arg);
-int parse_compl_arg(char_u *value, int vallen, int *complp, long *argt, char_u **compl_arg);
-int cmdcomplete_str_to_type(char_u *complete_str);
 void not_exiting(void);
 void tabpage_close(int forceit);
 void tabpage_close_other(tabpage_T *tp, int forceit);
new file mode 100644
--- /dev/null
+++ b/src/proto/usercmd.pro
@@ -0,0 +1,18 @@
+/* usercmd.c */
+char_u *find_ucmd(exarg_T *eap, char_u *p, int *full, expand_T *xp, int *compl);
+char_u *set_context_in_user_cmd(expand_T *xp, char_u *arg_in);
+char_u *get_user_command_name(int idx);
+char_u *get_user_commands(expand_T *xp, int idx);
+char_u *get_user_cmd_addr_type(expand_T *xp, int idx);
+char_u *get_user_cmd_flags(expand_T *xp, int idx);
+char_u *get_user_cmd_nargs(expand_T *xp, int idx);
+char_u *get_user_cmd_complete(expand_T *xp, int idx);
+char *uc_fun_cmd(void);
+void ex_command(exarg_T *eap);
+void ex_comclear(exarg_T *eap);
+void uc_clear(garray_T *gap);
+void ex_delcommand(exarg_T *eap);
+void do_ucmd(exarg_T *eap);
+int parse_compl_arg(char_u *value, int vallen, int *complp, long *argt, char_u **compl_arg);
+int cmdcomplete_str_to_type(char_u *complete_str);
+/* vim: set ft=c : */
--- a/src/structs.h
+++ b/src/structs.h
@@ -549,7 +549,7 @@ typedef struct expand
     int		xp_context;		/* type of expansion */
     char_u	*xp_pattern;		/* start of item to expand */
     int		xp_pattern_len;		/* bytes in xp_pattern before cursor */
-#if defined(FEAT_USR_CMDS) && defined(FEAT_EVAL) && defined(FEAT_CMDL_COMPL)
+#if defined(FEAT_EVAL) && defined(FEAT_CMDL_COMPL)
     char_u	*xp_arg;		/* completion function */
     sctx_T	xp_script_ctx;		/* SCTX for completion function */
 #endif
@@ -2143,10 +2143,8 @@ struct file_buffer
     /* First abbreviation local to a buffer. */
     mapblock_T	*b_first_abbr;
 #endif
-#ifdef FEAT_USR_CMDS
-    /* User commands local to the buffer. */
+    // User commands local to the buffer.
     garray_T	b_ucmds;
-#endif
     /*
      * start and end of an operator, also used for '[ and ']
      */
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);
+}
--- a/src/version.c
+++ b/src/version.c
@@ -672,11 +672,7 @@ static char *(features[]) =
 #else
 	"-toolbar",
 #endif
-#ifdef FEAT_USR_CMDS
 	"+user_commands",
-#else
-	"-user_commands",
-#endif
 #ifdef FEAT_VARTABS
 	"+vartabs",
 #else
@@ -772,6 +768,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1210,
+/**/
     1209,
 /**/
     1208,