blob: 0ed9598d83a2d14ceceebe6bbb651ba8fd7442dc [file] [log] [blame]
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001/* vi:set ts=8 sts=4 sw=4 noet:
2 *
3 * VIM - Vi IMproved by Bram Moolenaar
4 *
5 * Do ":help uganda" in Vim to read copying and usage conditions.
6 * Do ":help credits" in Vim to see a list of people who contributed.
7 * See README.txt for an overview of the Vim source code.
8 */
9
10/*
11 * usercmd.c: User defined command support
12 */
13
14#include "vim.h"
15
16typedef struct ucmd
17{
18 char_u *uc_name; // The command name
John Marriott9e795852024-08-20 20:57:23 +020019 size_t uc_namelen; // The length of the command name (excluding the NUL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +020020 long_u uc_argt; // The argument type
21 char_u *uc_rep; // The command's replacement string
22 long uc_def; // The default value for a range/count
23 int uc_compl; // completion type
Bram Moolenaarb7316892019-05-01 18:08:42 +020024 cmd_addr_T uc_addr_type; // The command's address type
Bram Moolenaarac9fb182019-04-27 13:04:13 +020025 sctx_T uc_script_ctx; // SCTX where the command was defined
Bram Moolenaar98b7fe72022-03-23 21:36:27 +000026 int uc_flags; // some UC_ flags
Bram Moolenaar9b8d6222020-12-28 18:26:00 +010027# ifdef FEAT_EVAL
Bram Moolenaarac9fb182019-04-27 13:04:13 +020028 char_u *uc_compl_arg; // completion argument if any
Bram Moolenaarac9fb182019-04-27 13:04:13 +020029# endif
30} ucmd_T;
31
32// List of all user commands.
33static garray_T ucmds = {0, 0, sizeof(ucmd_T), 4, NULL};
34
Bram Moolenaarcf2594f2022-11-13 23:30:06 +000035// When non-zero it is not allowed to add or remove user commands
36static int ucmd_locked = 0;
37
Bram Moolenaarac9fb182019-04-27 13:04:13 +020038#define USER_CMD(i) (&((ucmd_T *)(ucmds.ga_data))[i])
39#define USER_CMD_GA(gap, i) (&((ucmd_T *)((gap)->ga_data))[i])
40
41/*
42 * List of names for completion for ":command" with the EXPAND_ flag.
John Marriott9e795852024-08-20 20:57:23 +020043 * Must be alphabetical on the 'value' field for completion and because
44 * it is used by bsearch()!
Bram Moolenaarac9fb182019-04-27 13:04:13 +020045 */
John Marriott9e795852024-08-20 20:57:23 +020046static keyvalue_T command_complete_tab[] =
Bram Moolenaarac9fb182019-04-27 13:04:13 +020047{
John Marriott9e795852024-08-20 20:57:23 +020048 KEYVALUE_ENTRY(EXPAND_ARGLIST, "arglist"),
49 KEYVALUE_ENTRY(EXPAND_AUGROUP, "augroup"),
50 KEYVALUE_ENTRY(EXPAND_BEHAVE, "behave"),
51#if defined(FEAT_EVAL)
52 KEYVALUE_ENTRY(EXPAND_BREAKPOINT, "breakpoint"),
53#endif
54 KEYVALUE_ENTRY(EXPAND_BUFFERS, "buffer"),
55 KEYVALUE_ENTRY(EXPAND_COLORS, "color"),
56 KEYVALUE_ENTRY(EXPAND_COMMANDS, "command"),
57 KEYVALUE_ENTRY(EXPAND_COMPILER, "compiler"),
Bram Moolenaarac9fb182019-04-27 13:04:13 +020058#if defined(FEAT_CSCOPE)
John Marriott9e795852024-08-20 20:57:23 +020059 KEYVALUE_ENTRY(EXPAND_CSCOPE, "cscope"),
Bram Moolenaarac9fb182019-04-27 13:04:13 +020060#endif
Bram Moolenaar0a52df52019-08-18 22:26:31 +020061#if defined(FEAT_EVAL)
John Marriott9e795852024-08-20 20:57:23 +020062 KEYVALUE_ENTRY(EXPAND_USER_DEFINED, "custom"),
63 KEYVALUE_ENTRY(EXPAND_USER_LIST, "customlist"),
Bram Moolenaarac9fb182019-04-27 13:04:13 +020064#endif
John Marriott9e795852024-08-20 20:57:23 +020065 KEYVALUE_ENTRY(EXPAND_DIFF_BUFFERS, "diff_buffer"),
66 KEYVALUE_ENTRY(EXPAND_DIRECTORIES, "dir"),
67 KEYVALUE_ENTRY(EXPAND_DIRS_IN_CDPATH, "dir_in_path"),
68 KEYVALUE_ENTRY(EXPAND_ENV_VARS, "environment"),
69 KEYVALUE_ENTRY(EXPAND_EVENTS, "event"),
70 KEYVALUE_ENTRY(EXPAND_EXPRESSION, "expression"),
71 KEYVALUE_ENTRY(EXPAND_FILES, "file"),
72 KEYVALUE_ENTRY(EXPAND_FILES_IN_PATH, "file_in_path"),
73 KEYVALUE_ENTRY(EXPAND_FILETYPE, "filetype"),
Christian Brabandta3422aa2025-04-23 21:04:24 +020074 KEYVALUE_ENTRY(EXPAND_FILETYPECMD, "filetypecmd"),
John Marriott9e795852024-08-20 20:57:23 +020075 KEYVALUE_ENTRY(EXPAND_FUNCTIONS, "function"),
76 KEYVALUE_ENTRY(EXPAND_HELP, "help"),
77 KEYVALUE_ENTRY(EXPAND_HIGHLIGHT, "highlight"),
78 KEYVALUE_ENTRY(EXPAND_HISTORY, "history"),
Doug Kearns81642d92024-01-04 22:37:44 +010079#if defined(FEAT_KEYMAP)
John Marriott9e795852024-08-20 20:57:23 +020080 KEYVALUE_ENTRY(EXPAND_KEYMAP, "keymap"),
Doug Kearns81642d92024-01-04 22:37:44 +010081#endif
Bram Moolenaarac9fb182019-04-27 13:04:13 +020082#if defined(HAVE_LOCALE_H) || defined(X_LOCALE)
John Marriott9e795852024-08-20 20:57:23 +020083 KEYVALUE_ENTRY(EXPAND_LOCALES, "locale"),
Bram Moolenaarac9fb182019-04-27 13:04:13 +020084#endif
John Marriott9e795852024-08-20 20:57:23 +020085 KEYVALUE_ENTRY(EXPAND_MAPCLEAR, "mapclear"),
86 KEYVALUE_ENTRY(EXPAND_MAPPINGS, "mapping"),
87 KEYVALUE_ENTRY(EXPAND_MENUS, "menu"),
88 KEYVALUE_ENTRY(EXPAND_MESSAGES, "messages"),
89 KEYVALUE_ENTRY(EXPAND_SETTINGS, "option"),
90 KEYVALUE_ENTRY(EXPAND_PACKADD, "packadd"),
91 KEYVALUE_ENTRY(EXPAND_RUNTIME, "runtime"),
Bram Moolenaar6e2e2cc2022-03-14 19:24:46 +000092#if defined(FEAT_EVAL)
John Marriott9e795852024-08-20 20:57:23 +020093 KEYVALUE_ENTRY(EXPAND_SCRIPTNAMES, "scriptnames"),
Bram Moolenaar6e2e2cc2022-03-14 19:24:46 +000094#endif
John Marriott9e795852024-08-20 20:57:23 +020095 KEYVALUE_ENTRY(EXPAND_SHELLCMD, "shellcmd"),
Ruslan Russkikh0407d622024-10-08 22:21:05 +020096 KEYVALUE_ENTRY(EXPAND_SHELLCMDLINE, "shellcmdline"),
John Marriott9e795852024-08-20 20:57:23 +020097#if defined(FEAT_SIGNS)
98 KEYVALUE_ENTRY(EXPAND_SIGN, "sign"),
99#endif
100 KEYVALUE_ENTRY(EXPAND_OWNSYNTAX, "syntax"),
101#if defined(FEAT_PROFILE)
102 KEYVALUE_ENTRY(EXPAND_SYNTIME, "syntime"),
103#endif
104 KEYVALUE_ENTRY(EXPAND_TAGS, "tag"),
105 KEYVALUE_ENTRY(EXPAND_TAGS_LISTFILES, "tag_listfiles"),
106 KEYVALUE_ENTRY(EXPAND_USER, "user"),
107 KEYVALUE_ENTRY(EXPAND_USER_VARS, "var")
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200108};
109
John Marriott9e795852024-08-20 20:57:23 +0200110typedef struct
111{
112 cmd_addr_T key;
113 char *fullname;
114 size_t fullnamelen;
115 char *shortname;
116 size_t shortnamelen;
117} addrtype_T;
118
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200119/*
120 * List of names of address types. Must be alphabetical for completion.
John Marriott9e795852024-08-20 20:57:23 +0200121 * Must be sorted by the 'fullname' field because it is used by bsearch()!
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200122 */
John Marriott9e795852024-08-20 20:57:23 +0200123#define ADDRTYPE_ENTRY(k, fn, sn) \
124 {(k), (fn), STRLEN_LITERAL(fn), (sn), STRLEN_LITERAL(sn)}
125static addrtype_T addr_type_complete_tab[] =
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200126{
John Marriott9e795852024-08-20 20:57:23 +0200127 ADDRTYPE_ENTRY(ADDR_ARGUMENTS, "arguments", "arg"),
128 ADDRTYPE_ENTRY(ADDR_BUFFERS, "buffers", "buf"),
129 ADDRTYPE_ENTRY(ADDR_LINES, "lines", "line"),
130 ADDRTYPE_ENTRY(ADDR_LOADED_BUFFERS, "loaded_buffers", "load"),
131 ADDRTYPE_ENTRY(ADDR_OTHER, "other", "?"),
132 ADDRTYPE_ENTRY(ADDR_QUICKFIX, "quickfix", "qf"),
133 ADDRTYPE_ENTRY(ADDR_TABS, "tabs", "tab"),
134 ADDRTYPE_ENTRY(ADDR_WINDOWS, "windows", "win")
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200135};
136
John Marriott9e795852024-08-20 20:57:23 +0200137static int cmp_addr_type(const void *a, const void *b);
138
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200139/*
140 * Search for a user command that matches "eap->cmd".
141 * Return cmdidx in "eap->cmdidx", flags in "eap->argt", idx in "eap->useridx".
142 * Return a pointer to just after the command.
143 * Return NULL if there is no matching command.
144 */
145 char_u *
146find_ucmd(
147 exarg_T *eap,
Bram Moolenaar0023f822022-01-16 21:54:19 +0000148 char_u *p, // end of the command (possibly including count)
149 int *full, // set to TRUE for a full match
150 expand_T *xp, // used for completion, NULL otherwise
151 int *complp) // completion flags or NULL
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200152{
153 int len = (int)(p - eap->cmd);
154 int j, k, matchlen = 0;
155 ucmd_T *uc;
156 int found = FALSE;
157 int possible = FALSE;
158 char_u *cp, *np; // Point into typed cmd and test name
159 garray_T *gap;
160 int amb_local = FALSE; // Found ambiguous buffer-local command,
161 // only full match global is accepted.
162
163 /*
164 * Look for buffer-local user commands first, then global ones.
165 */
Bram Moolenaar0f6e28f2022-02-20 20:49:35 +0000166 gap = &prevwin_curwin()->w_buffer->b_ucmds;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200167 for (;;)
168 {
169 for (j = 0; j < gap->ga_len; ++j)
170 {
171 uc = USER_CMD_GA(gap, j);
172 cp = eap->cmd;
173 np = uc->uc_name;
174 k = 0;
175 while (k < len && *np != NUL && *cp++ == *np++)
176 k++;
177 if (k == len || (*np == NUL && vim_isdigit(eap->cmd[k])))
178 {
179 // If finding a second match, the command is ambiguous. But
180 // not if a buffer-local command wasn't a full match and a
181 // global command is a full match.
182 if (k == len && found && *np != NUL)
183 {
184 if (gap == &ucmds)
185 return NULL;
186 amb_local = TRUE;
187 }
188
189 if (!found || (k == len && *np == NUL))
190 {
191 // If we matched up to a digit, then there could
192 // be another command including the digit that we
193 // should use instead.
194 if (k == len)
195 found = TRUE;
196 else
197 possible = TRUE;
198
199 if (gap == &ucmds)
200 eap->cmdidx = CMD_USER;
201 else
202 eap->cmdidx = CMD_USER_BUF;
203 eap->argt = (long)uc->uc_argt;
204 eap->useridx = j;
205 eap->addr_type = uc->uc_addr_type;
206
Bram Moolenaar52111f82019-04-29 21:30:45 +0200207 if (complp != NULL)
208 *complp = uc->uc_compl;
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200209# ifdef FEAT_EVAL
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200210 if (xp != NULL)
211 {
212 xp->xp_arg = uc->uc_compl_arg;
213 xp->xp_script_ctx = uc->uc_script_ctx;
Bram Moolenaar1a47ae32019-12-29 23:04:25 +0100214 xp->xp_script_ctx.sc_lnum += SOURCING_LNUM;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200215 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200216# endif
217 // Do not search for further abbreviations
218 // if this is an exact match.
219 matchlen = k;
220 if (k == len && *np == NUL)
221 {
222 if (full != NULL)
223 *full = TRUE;
224 amb_local = FALSE;
225 break;
226 }
227 }
228 }
229 }
230
231 // Stop if we found a full match or searched all.
232 if (j < gap->ga_len || gap == &ucmds)
233 break;
234 gap = &ucmds;
235 }
236
237 // Only found ambiguous matches.
238 if (amb_local)
239 {
240 if (xp != NULL)
241 xp->xp_context = EXPAND_UNSUCCESSFUL;
242 return NULL;
243 }
244
245 // The match we found may be followed immediately by a number. Move "p"
246 // back to point to it.
247 if (found || possible)
248 return p + (matchlen - len);
249 return p;
250}
251
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000252/*
253 * Set completion context for :command
254 */
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200255 char_u *
256set_context_in_user_cmd(expand_T *xp, char_u *arg_in)
257{
258 char_u *arg = arg_in;
259 char_u *p;
260
261 // Check for attributes
262 while (*arg == '-')
263 {
264 arg++; // Skip "-"
265 p = skiptowhite(arg);
266 if (*p == NUL)
267 {
268 // Cursor is still in the attribute
269 p = vim_strchr(arg, '=');
270 if (p == NULL)
271 {
272 // No "=", so complete attribute names
273 xp->xp_context = EXPAND_USER_CMD_FLAGS;
274 xp->xp_pattern = arg;
275 return NULL;
276 }
277
278 // For the -complete, -nargs and -addr attributes, we complete
279 // their arguments as well.
280 if (STRNICMP(arg, "complete", p - arg) == 0)
281 {
282 xp->xp_context = EXPAND_USER_COMPLETE;
283 xp->xp_pattern = p + 1;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200284 }
285 else if (STRNICMP(arg, "nargs", p - arg) == 0)
286 {
287 xp->xp_context = EXPAND_USER_NARGS;
288 xp->xp_pattern = p + 1;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200289 }
290 else if (STRNICMP(arg, "addr", p - arg) == 0)
291 {
292 xp->xp_context = EXPAND_USER_ADDR_TYPE;
293 xp->xp_pattern = p + 1;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200294 }
295 return NULL;
296 }
297 arg = skipwhite(p);
298 }
299
300 // After the attributes comes the new command name
301 p = skiptowhite(arg);
302 if (*p == NUL)
303 {
304 xp->xp_context = EXPAND_USER_COMMANDS;
305 xp->xp_pattern = arg;
306 return NULL;
307 }
308
309 // And finally comes a normal command
310 return skipwhite(p);
311}
312
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000313/*
314 * Set the completion context for the argument of a user defined command.
315 */
316 char_u *
317set_context_in_user_cmdarg(
318 char_u *cmd UNUSED,
319 char_u *arg,
320 long argt,
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000321 int context,
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000322 expand_T *xp,
323 int forceit)
324{
325 char_u *p;
326
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000327 if (context == EXPAND_NOTHING)
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000328 return NULL;
329
330 if (argt & EX_XFILE)
331 {
332 // EX_XFILE: file names are handled before this call
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000333 return NULL;
334 }
335
336#ifdef FEAT_MENU
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000337 if (context == EXPAND_MENUS)
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000338 return set_context_in_menu_cmd(xp, cmd, arg, forceit);
339#endif
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000340 if (context == EXPAND_COMMANDS)
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000341 return arg;
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000342 if (context == EXPAND_MAPPINGS)
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000343 return set_context_in_map_cmd(xp, (char_u *)"map", arg, forceit, FALSE,
344 FALSE, CMD_map);
345 // Find start of last argument.
346 p = arg;
347 while (*p)
348 {
349 if (*p == ' ')
350 // argument starts after a space
351 arg = p + 1;
352 else if (*p == '\\' && *(p + 1) != NUL)
353 ++p; // skip over escaped character
354 MB_PTR_ADV(p);
355 }
356 xp->xp_pattern = arg;
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000357 xp->xp_context = context;
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000358
359 return NULL;
360}
361
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200362 char_u *
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200363expand_user_command_name(int idx)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200364{
365 return get_user_commands(NULL, idx - (int)CMD_SIZE);
366}
367
368/*
369 * Function given to ExpandGeneric() to obtain the list of user command names.
370 */
371 char_u *
372get_user_commands(expand_T *xp UNUSED, int idx)
373{
Bram Moolenaarf03e3282019-07-22 21:55:18 +0200374 // In cmdwin, the alternative buffer should be used.
Bram Moolenaar0f6e28f2022-02-20 20:49:35 +0000375 buf_T *buf = prevwin_curwin()->w_buffer;
Bram Moolenaarf03e3282019-07-22 21:55:18 +0200376
377 if (idx < buf->b_ucmds.ga_len)
378 return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name;
Bram Moolenaarc2842ad2022-07-26 17:23:47 +0100379
Bram Moolenaarf03e3282019-07-22 21:55:18 +0200380 idx -= buf->b_ucmds.ga_len;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200381 if (idx < ucmds.ga_len)
Bram Moolenaarc2842ad2022-07-26 17:23:47 +0100382 {
383 int i;
384 char_u *name = USER_CMD(idx)->uc_name;
385
386 for (i = 0; i < buf->b_ucmds.ga_len; ++i)
387 if (STRCMP(name, USER_CMD_GA(&buf->b_ucmds, i)->uc_name) == 0)
388 // global command is overruled by buffer-local one
389 return (char_u *)"";
390 return name;
391 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200392 return NULL;
393}
394
Dominique Pelle748b3082022-01-08 12:41:16 +0000395#ifdef FEAT_EVAL
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200396/*
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200397 * Get the name of user command "idx". "cmdidx" can be CMD_USER or
398 * CMD_USER_BUF.
399 * Returns NULL if the command is not found.
400 */
401 char_u *
402get_user_command_name(int idx, int cmdidx)
403{
404 if (cmdidx == CMD_USER && idx < ucmds.ga_len)
405 return USER_CMD(idx)->uc_name;
406 if (cmdidx == CMD_USER_BUF)
407 {
408 // In cmdwin, the alternative buffer should be used.
Bram Moolenaar0f6e28f2022-02-20 20:49:35 +0000409 buf_T *buf = prevwin_curwin()->w_buffer;
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200410
411 if (idx < buf->b_ucmds.ga_len)
412 return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name;
413 }
414 return NULL;
415}
Dominique Pelle748b3082022-01-08 12:41:16 +0000416#endif
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200417
418/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200419 * Function given to ExpandGeneric() to obtain the list of user address type
420 * names.
421 */
422 char_u *
423get_user_cmd_addr_type(expand_T *xp UNUSED, int idx)
424{
John Marriott9e795852024-08-20 20:57:23 +0200425 if (idx < 0 || idx >= (int)ARRAY_LENGTH(addr_type_complete_tab))
426 return NULL;
427 return (char_u *)addr_type_complete_tab[idx].fullname;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200428}
429
430/*
431 * Function given to ExpandGeneric() to obtain the list of user command
432 * attributes.
433 */
434 char_u *
435get_user_cmd_flags(expand_T *xp UNUSED, int idx)
436{
437 static char *user_cmd_flags[] = {
438 "addr", "bang", "bar", "buffer", "complete",
Bram Moolenaar58ef8a32021-11-12 11:25:11 +0000439 "count", "nargs", "range", "register", "keepscript"
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200440 };
441
John Marriott9e795852024-08-20 20:57:23 +0200442 if (idx < 0 || idx >= (int)ARRAY_LENGTH(user_cmd_flags))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200443 return NULL;
444 return (char_u *)user_cmd_flags[idx];
445}
446
447/*
448 * Function given to ExpandGeneric() to obtain the list of values for -nargs.
449 */
450 char_u *
451get_user_cmd_nargs(expand_T *xp UNUSED, int idx)
452{
453 static char *user_cmd_nargs[] = {"0", "1", "*", "?", "+"};
454
John Marriott9e795852024-08-20 20:57:23 +0200455 if (idx < 0 || idx >= (int)ARRAY_LENGTH(user_cmd_nargs))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200456 return NULL;
457 return (char_u *)user_cmd_nargs[idx];
458}
459
460/*
461 * Function given to ExpandGeneric() to obtain the list of values for
462 * -complete.
463 */
464 char_u *
465get_user_cmd_complete(expand_T *xp UNUSED, int idx)
466{
John Marriott9e795852024-08-20 20:57:23 +0200467 if (idx < 0 || idx >= (int)ARRAY_LENGTH(command_complete_tab))
468 return NULL;
John Marriott8d4477e2024-11-02 15:59:01 +0100469 return command_complete_tab[idx].value.string;
John Marriott9e795852024-08-20 20:57:23 +0200470}
471
472/*
473 * Return the row in the command_complete_tab table that contains the given key.
474 */
475 static keyvalue_T *
476get_commandtype(int expand)
477{
478 int i;
479
480 for (i = 0; i < (int)ARRAY_LENGTH(command_complete_tab); ++i)
481 if (command_complete_tab[i].key == expand)
482 return &command_complete_tab[i];
483
484 return NULL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200485}
486
Dominique Pelle748b3082022-01-08 12:41:16 +0000487#ifdef FEAT_EVAL
Shougo Matsushita79d599b2022-05-07 12:48:29 +0100488/*
zeertzjqe2c0f812025-07-06 20:26:56 +0200489 * Get the name of completion type "expand" as an allocated string.
490 * "compl_arg" is the function name for "custom" and "customlist" types.
491 * Returns NULL if no completion is available or on allocation failure.
Shougo Matsushita79d599b2022-05-07 12:48:29 +0100492 */
493 char_u *
zeertzjqe2c0f812025-07-06 20:26:56 +0200494cmdcomplete_type_to_str(int expand, char_u *compl_arg)
Shougo Matsushita79d599b2022-05-07 12:48:29 +0100495{
John Marriott9e795852024-08-20 20:57:23 +0200496 keyvalue_T *kv;
zeertzjqe2c0f812025-07-06 20:26:56 +0200497 char_u *cmd_compl;
Shougo Matsushita79d599b2022-05-07 12:48:29 +0100498
John Marriott9e795852024-08-20 20:57:23 +0200499 kv = get_commandtype(expand);
zeertzjqe2c0f812025-07-06 20:26:56 +0200500 if (kv == NULL || kv->value.string == NULL)
501 return NULL;
Shougo Matsushita79d599b2022-05-07 12:48:29 +0100502
zeertzjqe2c0f812025-07-06 20:26:56 +0200503 cmd_compl = kv->value.string;
504 if (expand == EXPAND_USER_LIST || expand == EXPAND_USER_DEFINED)
505 {
506 char_u *buffer;
507
508 buffer = alloc(STRLEN(cmd_compl) + STRLEN(compl_arg) + 2);
509 if (buffer == NULL)
510 return NULL;
511 sprintf((char *)buffer, "%s,%s", cmd_compl, compl_arg);
512 return buffer;
513 }
514
515 return vim_strsave(cmd_compl);
Shougo Matsushita79d599b2022-05-07 12:48:29 +0100516}
517
518/*
519 * Get the index of completion type "complete_str".
520 * Returns EXPAND_NOTHING if no match found.
521 */
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200522 int
523cmdcomplete_str_to_type(char_u *complete_str)
524{
John Marriott9e795852024-08-20 20:57:23 +0200525 keyvalue_T target;
526 keyvalue_T *entry;
527 static keyvalue_T *last_entry = NULL; // cached result
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200528
Shougo Matsushita92997dd2023-08-20 20:55:55 +0200529 if (STRNCMP(complete_str, "custom,", 7) == 0)
530 return EXPAND_USER_DEFINED;
531 if (STRNCMP(complete_str, "customlist,", 11) == 0)
532 return EXPAND_USER_LIST;
533
John Marriott9e795852024-08-20 20:57:23 +0200534 target.key = 0;
John Marriott8d4477e2024-11-02 15:59:01 +0100535 target.value.string = complete_str;
536 target.value.length = 0; // not used, see cmp_keyvalue_value()
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200537
John Marriott9e795852024-08-20 20:57:23 +0200538 if (last_entry != NULL && cmp_keyvalue_value(&target, last_entry) == 0)
539 entry = last_entry;
540 else
541 {
542 entry = (keyvalue_T *)bsearch(&target,
543 &command_complete_tab,
544 ARRAY_LENGTH(command_complete_tab),
545 sizeof(command_complete_tab[0]),
546 cmp_keyvalue_value);
547 if (entry == NULL)
548 return EXPAND_NOTHING;
549
550 last_entry = entry;
551 }
552
553 return entry->key;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200554}
Dominique Pelle748b3082022-01-08 12:41:16 +0000555#endif
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200556
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200557/*
558 * List user commands starting with "name[name_len]".
559 */
560 static void
561uc_list(char_u *name, size_t name_len)
562{
563 int i, j;
564 int found = FALSE;
565 ucmd_T *cmd;
566 int len;
567 int over;
568 long a;
569 garray_T *gap;
John Marriott9e795852024-08-20 20:57:23 +0200570 keyvalue_T *entry;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200571
Bram Moolenaarcf2594f2022-11-13 23:30:06 +0000572 // don't allow for adding or removing user commands here
573 ++ucmd_locked;
574
Bram Moolenaare38eab22019-12-05 21:50:01 +0100575 // In cmdwin, the alternative buffer should be used.
Bram Moolenaar0f6e28f2022-02-20 20:49:35 +0000576 gap = &prevwin_curwin()->w_buffer->b_ucmds;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200577 for (;;)
578 {
579 for (i = 0; i < gap->ga_len; ++i)
580 {
581 cmd = USER_CMD_GA(gap, i);
582 a = (long)cmd->uc_argt;
583
584 // Skip commands which don't match the requested prefix and
585 // commands filtered out.
586 if (STRNCMP(name, cmd->uc_name, name_len) != 0
587 || message_filtered(cmd->uc_name))
588 continue;
589
590 // Put out the title first time
591 if (!found)
592 msg_puts_title(_("\n Name Args Address Complete Definition"));
593 found = TRUE;
594 msg_putchar('\n');
595 if (got_int)
596 break;
597
598 // Special cases
599 len = 4;
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200600 if (a & EX_BANG)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200601 {
602 msg_putchar('!');
603 --len;
604 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200605 if (a & EX_REGSTR)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200606 {
607 msg_putchar('"');
608 --len;
609 }
610 if (gap != &ucmds)
611 {
612 msg_putchar('b');
613 --len;
614 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200615 if (a & EX_TRLBAR)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200616 {
617 msg_putchar('|');
618 --len;
619 }
Mike Williams0174d8f2025-06-08 15:41:52 +0200620 if (len != 0)
621 msg_puts(&" "[4 - len]);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200622
623 msg_outtrans_attr(cmd->uc_name, HL_ATTR(HLF_D));
John Marriott9e795852024-08-20 20:57:23 +0200624 len = (int)cmd->uc_namelen + 4;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200625
Mike Williams0174d8f2025-06-08 15:41:52 +0200626 if (len < 21)
Hirohito Higashia4a00a72025-05-08 22:58:31 +0200627 {
Mike Williams0174d8f2025-06-08 15:41:52 +0200628 // Field padding spaces 12345678901234567
629 static char spaces[18] = " ";
630 msg_puts(&spaces[len - 4]);
631 len = 21;
632 }
633 msg_putchar(' ');
634 ++len;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200635
636 // "over" is how much longer the name is than the column width for
637 // the name, we'll try to align what comes after.
638 over = len - 22;
639 len = 0;
640
641 // Arguments
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200642 switch ((int)(a & (EX_EXTRA|EX_NOSPC|EX_NEEDARG)))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200643 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200644 case 0: IObuff[len++] = '0'; break;
645 case (EX_EXTRA): IObuff[len++] = '*'; break;
646 case (EX_EXTRA|EX_NOSPC): IObuff[len++] = '?'; break;
647 case (EX_EXTRA|EX_NEEDARG): IObuff[len++] = '+'; break;
648 case (EX_EXTRA|EX_NOSPC|EX_NEEDARG): IObuff[len++] = '1'; break;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200649 }
650
Hirohito Higashia4a00a72025-05-08 22:58:31 +0200651 do
652 {
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200653 IObuff[len++] = ' ';
654 } while (len < 5 - over);
655
656 // Address / Range
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200657 if (a & (EX_RANGE|EX_COUNT))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200658 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200659 if (a & EX_COUNT)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200660 {
661 // -count=N
John Marriott9e795852024-08-20 20:57:23 +0200662 len += vim_snprintf((char *)IObuff + len, IOSIZE - len, "%ldc", cmd->uc_def);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200663 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200664 else if (a & EX_DFLALL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200665 IObuff[len++] = '%';
666 else if (cmd->uc_def >= 0)
667 {
668 // -range=N
John Marriott9e795852024-08-20 20:57:23 +0200669 len += vim_snprintf((char *)IObuff + len, IOSIZE - len, "%ld", cmd->uc_def);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200670 }
671 else
672 IObuff[len++] = '.';
673 }
674
Hirohito Higashia4a00a72025-05-08 22:58:31 +0200675 do
676 {
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200677 IObuff[len++] = ' ';
678 } while (len < 8 - over);
679
680 // Address Type
John Marriott9e795852024-08-20 20:57:23 +0200681 for (j = 0; j < (int)ARRAY_LENGTH(addr_type_complete_tab); ++j)
682 if (addr_type_complete_tab[j].key != ADDR_LINES
683 && addr_type_complete_tab[j].key == cmd->uc_addr_type)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200684 {
John Marriott9e795852024-08-20 20:57:23 +0200685 STRCPY(IObuff + len, addr_type_complete_tab[j].shortname);
686 len += (int)addr_type_complete_tab[j].shortnamelen;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200687 break;
688 }
689
Hirohito Higashia4a00a72025-05-08 22:58:31 +0200690 do
691 {
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200692 IObuff[len++] = ' ';
693 } while (len < 13 - over);
694
695 // Completion
John Marriott9e795852024-08-20 20:57:23 +0200696 entry = get_commandtype(cmd->uc_compl);
697 if (entry != NULL)
698 {
John Marriott8d4477e2024-11-02 15:59:01 +0100699 STRCPY(IObuff + len, entry->value.string);
Yegappan Lakshmanan084529c2024-12-24 09:50:01 +0100700 len += (int)entry->value.length;
Bram Moolenaar78f60322022-01-17 22:16:33 +0000701#ifdef FEAT_EVAL
John Marriott9e795852024-08-20 20:57:23 +0200702 if (p_verbose > 0 && cmd->uc_compl_arg != NULL)
703 {
704 size_t uc_compl_arglen = STRLEN(cmd->uc_compl_arg);
705
706 if (uc_compl_arglen < 200)
Bram Moolenaar3f3597b2022-01-17 19:06:56 +0000707 {
John Marriott9e795852024-08-20 20:57:23 +0200708 IObuff[len++] = ',';
709 STRCPY(IObuff + len, cmd->uc_compl_arg);
Yegappan Lakshmanan084529c2024-12-24 09:50:01 +0100710 len += (int)uc_compl_arglen;
Bram Moolenaar3f3597b2022-01-17 19:06:56 +0000711 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200712 }
John Marriott9e795852024-08-20 20:57:23 +0200713#endif
714 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200715
Hirohito Higashia4a00a72025-05-08 22:58:31 +0200716 do
717 {
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200718 IObuff[len++] = ' ';
719 } while (len < 25 - over);
720
John Marriott9e795852024-08-20 20:57:23 +0200721 IObuff[len] = NUL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200722 msg_outtrans(IObuff);
723
724 msg_outtrans_special(cmd->uc_rep, FALSE,
725 name_len == 0 ? Columns - 47 : 0);
726#ifdef FEAT_EVAL
727 if (p_verbose > 0)
728 last_set_msg(cmd->uc_script_ctx);
729#endif
730 out_flush();
731 ui_breakcheck();
732 if (got_int)
733 break;
734 }
735 if (gap == &ucmds || i < gap->ga_len)
736 break;
737 gap = &ucmds;
738 }
739
740 if (!found)
741 msg(_("No user-defined commands found"));
Bram Moolenaarcf2594f2022-11-13 23:30:06 +0000742
743 --ucmd_locked;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200744}
745
746 char *
747uc_fun_cmd(void)
748{
749 static char_u fcmd[] = {0x84, 0xaf, 0x60, 0xb9, 0xaf, 0xb5, 0x60, 0xa4,
750 0xa5, 0xad, 0xa1, 0xae, 0xa4, 0x60, 0xa1, 0x60,
751 0xb3, 0xa8, 0xb2, 0xb5, 0xa2, 0xa2, 0xa5, 0xb2,
752 0xb9, 0x7f, 0};
753 int i;
754
755 for (i = 0; fcmd[i]; ++i)
756 IObuff[i] = fcmd[i] - 0x40;
John Marriott9e795852024-08-20 20:57:23 +0200757 IObuff[i] = NUL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200758 return (char *)IObuff;
759}
760
761/*
762 * Parse address type argument
763 */
764 static int
765parse_addr_type_arg(
766 char_u *value,
767 int vallen,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200768 cmd_addr_T *addr_type_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200769{
John Marriott9e795852024-08-20 20:57:23 +0200770 addrtype_T target;
771 addrtype_T *entry;
772 static addrtype_T *last_entry; // cached result
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200773
John Marriott9e795852024-08-20 20:57:23 +0200774 target.key = 0;
775 target.fullname = (char *)value;
776 target.fullnamelen = vallen;
777
778 if (last_entry != NULL && cmp_addr_type(&target, last_entry) == 0)
779 entry = last_entry;
780 else
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200781 {
John Marriott9e795852024-08-20 20:57:23 +0200782 entry = (addrtype_T *)bsearch(&target,
783 &addr_type_complete_tab,
784 ARRAY_LENGTH(addr_type_complete_tab),
785 sizeof(addr_type_complete_tab[0]),
786 cmp_addr_type);
787 if (entry == NULL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200788 {
John Marriott9e795852024-08-20 20:57:23 +0200789 int i;
790 char_u *err = value;
791
792 for (i = 0; err[i] != NUL && !VIM_ISWHITE(err[i]); i++)
793 ;
794 err[i] = NUL;
795 semsg(_(e_invalid_address_type_value_str), err);
796 return FAIL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200797 }
John Marriott9e795852024-08-20 20:57:23 +0200798
799 last_entry = entry;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200800 }
801
John Marriott9e795852024-08-20 20:57:23 +0200802 *addr_type_arg = entry->key;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200803
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200804 return OK;
805}
806
John Marriott9e795852024-08-20 20:57:23 +0200807 static int
808cmp_addr_type(const void *a, const void *b)
809{
810 addrtype_T *at1 = (addrtype_T *)a;
811 addrtype_T *at2 = (addrtype_T *)b;
812
813 return STRNCMP(at1->fullname, at2->fullname, MAX(at1->fullnamelen, at2->fullnamelen));
814}
815
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200816/*
817 * Parse a completion argument "value[vallen]".
818 * The detected completion goes in "*complp", argument type in "*argt".
819 * When there is an argument, for function and user defined completion, it's
820 * copied to allocated memory and stored in "*compl_arg".
821 * Returns FAIL if something is wrong.
822 */
823 int
824parse_compl_arg(
825 char_u *value,
826 int vallen,
827 int *complp,
828 long *argt,
829 char_u **compl_arg UNUSED)
830{
831 char_u *arg = NULL;
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200832# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200833 size_t arglen = 0;
834# endif
835 int i;
836 int valend = vallen;
John Marriott9e795852024-08-20 20:57:23 +0200837 keyvalue_T target;
838 keyvalue_T *entry;
839 static keyvalue_T *last_entry = NULL; // cached result
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200840
841 // Look for any argument part - which is the part after any ','
842 for (i = 0; i < vallen; ++i)
843 {
844 if (value[i] == ',')
845 {
846 arg = &value[i + 1];
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200847# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200848 arglen = vallen - i - 1;
849# endif
850 valend = i;
851 break;
852 }
853 }
854
John Marriott9e795852024-08-20 20:57:23 +0200855 target.key = 0;
John Marriott8d4477e2024-11-02 15:59:01 +0100856 target.value.string = value;
857 target.value.length = valend;
John Marriott9e795852024-08-20 20:57:23 +0200858
859 if (last_entry != NULL && cmp_keyvalue_value_n(&target, last_entry) == 0)
860 entry = last_entry;
861 else
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200862 {
John Marriott9e795852024-08-20 20:57:23 +0200863 entry = (keyvalue_T *)bsearch(&target,
864 &command_complete_tab,
865 ARRAY_LENGTH(command_complete_tab),
866 sizeof(command_complete_tab[0]),
867 cmp_keyvalue_value_n);
868 if (entry == NULL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200869 {
John Marriott9e795852024-08-20 20:57:23 +0200870 semsg(_(e_invalid_complete_value_str), value);
871 return FAIL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200872 }
John Marriott9e795852024-08-20 20:57:23 +0200873
874 last_entry = entry;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200875 }
876
John Marriott9e795852024-08-20 20:57:23 +0200877 *complp = entry->key;
878 if (*complp == EXPAND_BUFFERS)
879 *argt |= EX_BUFNAME;
Ruslan Russkikh0407d622024-10-08 22:21:05 +0200880 else if (*complp == EXPAND_DIRECTORIES || *complp == EXPAND_FILES || *complp == EXPAND_SHELLCMDLINE)
John Marriott9e795852024-08-20 20:57:23 +0200881 *argt |= EX_XFILE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200882
John Marriott9e795852024-08-20 20:57:23 +0200883 if (
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200884# if defined(FEAT_EVAL)
John Marriott9e795852024-08-20 20:57:23 +0200885 *complp != EXPAND_USER_DEFINED && *complp != EXPAND_USER_LIST
886 &&
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200887# endif
John Marriott9e795852024-08-20 20:57:23 +0200888 arg != NULL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200889 {
Bram Moolenaarb09feaa2022-01-02 20:20:45 +0000890 emsg(_(e_completion_argument_only_allowed_for_custom_completion));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200891 return FAIL;
892 }
893
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200894# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200895 if ((*complp == EXPAND_USER_DEFINED || *complp == EXPAND_USER_LIST)
896 && arg == NULL)
897 {
Bram Moolenaarb09feaa2022-01-02 20:20:45 +0000898 emsg(_(e_custom_completion_requires_function_argument));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200899 return FAIL;
900 }
901
902 if (arg != NULL)
Bram Moolenaar71ccd032020-06-12 22:59:11 +0200903 *compl_arg = vim_strnsave(arg, arglen);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200904# endif
John Marriott9e795852024-08-20 20:57:23 +0200905
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200906 return OK;
907}
908
909/*
910 * Scan attributes in the ":command" command.
911 * Return FAIL when something is wrong.
912 */
913 static int
914uc_scan_attr(
915 char_u *attr,
916 size_t len,
917 long *argt,
918 long *def,
919 int *flags,
Bram Moolenaar52111f82019-04-29 21:30:45 +0200920 int *complp,
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200921 char_u **compl_arg,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200922 cmd_addr_T *addr_type_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200923{
924 char_u *p;
925
926 if (len == 0)
927 {
Bram Moolenaar1a992222021-12-31 17:25:48 +0000928 emsg(_(e_no_attribute_specified));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200929 return FAIL;
930 }
931
932 // First, try the simple attributes (no arguments)
933 if (STRNICMP(attr, "bang", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200934 *argt |= EX_BANG;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200935 else if (STRNICMP(attr, "buffer", len) == 0)
936 *flags |= UC_BUFFER;
937 else if (STRNICMP(attr, "register", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200938 *argt |= EX_REGSTR;
Bram Moolenaar58ef8a32021-11-12 11:25:11 +0000939 else if (STRNICMP(attr, "keepscript", len) == 0)
940 *argt |= EX_KEEPSCRIPT;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200941 else if (STRNICMP(attr, "bar", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200942 *argt |= EX_TRLBAR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200943 else
944 {
945 int i;
946 char_u *val = NULL;
947 size_t vallen = 0;
948 size_t attrlen = len;
949
950 // Look for the attribute name - which is the part before any '='
951 for (i = 0; i < (int)len; ++i)
952 {
953 if (attr[i] == '=')
954 {
955 val = &attr[i + 1];
956 vallen = len - i - 1;
957 attrlen = i;
958 break;
959 }
960 }
961
962 if (STRNICMP(attr, "nargs", attrlen) == 0)
963 {
964 if (vallen == 1)
965 {
966 if (*val == '0')
967 // Do nothing - this is the default
968 ;
969 else if (*val == '1')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200970 *argt |= (EX_EXTRA | EX_NOSPC | EX_NEEDARG);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200971 else if (*val == '*')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200972 *argt |= EX_EXTRA;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200973 else if (*val == '?')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200974 *argt |= (EX_EXTRA | EX_NOSPC);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200975 else if (*val == '+')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200976 *argt |= (EX_EXTRA | EX_NEEDARG);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200977 else
978 goto wrong_nargs;
979 }
980 else
981 {
982wrong_nargs:
Bram Moolenaar1a992222021-12-31 17:25:48 +0000983 emsg(_(e_invalid_number_of_arguments));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200984 return FAIL;
985 }
986 }
987 else if (STRNICMP(attr, "range", attrlen) == 0)
988 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200989 *argt |= EX_RANGE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200990 if (vallen == 1 && *val == '%')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200991 *argt |= EX_DFLALL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200992 else if (val != NULL)
993 {
994 p = val;
995 if (*def >= 0)
996 {
997two_count:
Bram Moolenaar1a992222021-12-31 17:25:48 +0000998 emsg(_(e_count_cannot_be_specified_twice));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200999 return FAIL;
1000 }
1001
1002 *def = getdigits(&p);
Bram Moolenaar8071cb22019-07-12 17:58:01 +02001003 *argt |= EX_ZEROR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001004
1005 if (p != val + vallen || vallen == 0)
1006 {
1007invalid_count:
Bram Moolenaar1a992222021-12-31 17:25:48 +00001008 emsg(_(e_invalid_default_value_for_count));
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001009 return FAIL;
1010 }
1011 }
Bram Moolenaarb7316892019-05-01 18:08:42 +02001012 // default for -range is using buffer lines
1013 if (*addr_type_arg == ADDR_NONE)
1014 *addr_type_arg = ADDR_LINES;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001015 }
1016 else if (STRNICMP(attr, "count", attrlen) == 0)
1017 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +02001018 *argt |= (EX_COUNT | EX_ZEROR | EX_RANGE);
Bram Moolenaarb7316892019-05-01 18:08:42 +02001019 // default for -count is using any number
1020 if (*addr_type_arg == ADDR_NONE)
1021 *addr_type_arg = ADDR_OTHER;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001022
1023 if (val != NULL)
1024 {
1025 p = val;
1026 if (*def >= 0)
1027 goto two_count;
1028
1029 *def = getdigits(&p);
1030
1031 if (p != val + vallen)
1032 goto invalid_count;
1033 }
1034
1035 if (*def < 0)
1036 *def = 0;
1037 }
1038 else if (STRNICMP(attr, "complete", attrlen) == 0)
1039 {
1040 if (val == NULL)
1041 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001042 semsg(_(e_argument_required_for_str), "-complete");
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001043 return FAIL;
1044 }
1045
Bram Moolenaar52111f82019-04-29 21:30:45 +02001046 if (parse_compl_arg(val, (int)vallen, complp, argt, compl_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001047 == FAIL)
1048 return FAIL;
1049 }
1050 else if (STRNICMP(attr, "addr", attrlen) == 0)
1051 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +02001052 *argt |= EX_RANGE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001053 if (val == NULL)
1054 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001055 semsg(_(e_argument_required_for_str), "-addr");
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001056 return FAIL;
1057 }
Bram Moolenaare4f5f3a2019-05-04 14:05:08 +02001058 if (parse_addr_type_arg(val, (int)vallen, addr_type_arg) == FAIL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001059 return FAIL;
Bram Moolenaare4f5f3a2019-05-04 14:05:08 +02001060 if (*addr_type_arg != ADDR_LINES)
Bram Moolenaar8071cb22019-07-12 17:58:01 +02001061 *argt |= EX_ZEROR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001062 }
1063 else
1064 {
1065 char_u ch = attr[len];
1066 attr[len] = '\0';
Bram Moolenaar1a992222021-12-31 17:25:48 +00001067 semsg(_(e_invalid_attribute_str), attr);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001068 attr[len] = ch;
1069 return FAIL;
1070 }
1071 }
1072
1073 return OK;
1074}
1075
1076/*
1077 * Add a user command to the list or replace an existing one.
1078 */
1079 static int
1080uc_add_command(
1081 char_u *name,
1082 size_t name_len,
1083 char_u *rep,
1084 long argt,
1085 long def,
1086 int flags,
1087 int compl,
1088 char_u *compl_arg UNUSED,
Bram Moolenaarb7316892019-05-01 18:08:42 +02001089 cmd_addr_T addr_type,
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001090 int force)
1091{
1092 ucmd_T *cmd = NULL;
1093 char_u *p;
1094 int i;
1095 int cmp = 1;
1096 char_u *rep_buf = NULL;
1097 garray_T *gap;
1098
zeertzjq7e0bae02023-08-11 23:15:38 +02001099 replace_termcodes(rep, &rep_buf, 0, 0, NULL);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001100 if (rep_buf == NULL)
1101 {
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001102 // can't replace termcodes - try using the string as is
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001103 rep_buf = vim_strsave(rep);
1104
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001105 // give up if out of memory
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001106 if (rep_buf == NULL)
1107 return FAIL;
1108 }
1109
1110 // get address of growarray: global or in curbuf
1111 if (flags & UC_BUFFER)
1112 {
1113 gap = &curbuf->b_ucmds;
1114 if (gap->ga_itemsize == 0)
Bram Moolenaar04935fb2022-01-08 16:19:22 +00001115 ga_init2(gap, sizeof(ucmd_T), 4);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001116 }
1117 else
1118 gap = &ucmds;
1119
1120 // Search for the command in the already defined commands.
1121 for (i = 0; i < gap->ga_len; ++i)
1122 {
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001123 cmd = USER_CMD_GA(gap, i);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001124 cmp = STRNCMP(name, cmd->uc_name, name_len);
1125 if (cmp == 0)
1126 {
John Marriott9e795852024-08-20 20:57:23 +02001127 if (name_len < cmd->uc_namelen)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001128 cmp = -1;
John Marriott9e795852024-08-20 20:57:23 +02001129 else if (name_len > cmd->uc_namelen)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001130 cmp = 1;
1131 }
1132
1133 if (cmp == 0)
1134 {
1135 // Command can be replaced with "command!" and when sourcing the
1136 // same script again, but only once.
1137 if (!force
1138#ifdef FEAT_EVAL
1139 && (cmd->uc_script_ctx.sc_sid != current_sctx.sc_sid
1140 || cmd->uc_script_ctx.sc_seq == current_sctx.sc_seq)
1141#endif
1142 )
1143 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001144 semsg(_(e_command_already_exists_add_bang_to_replace_it_str),
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001145 name);
1146 goto fail;
1147 }
1148
1149 VIM_CLEAR(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001150#if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001151 VIM_CLEAR(cmd->uc_compl_arg);
1152#endif
1153 break;
1154 }
1155
1156 // Stop as soon as we pass the name to add
1157 if (cmp < 0)
1158 break;
1159 }
1160
1161 // Extend the array unless we're replacing an existing command
1162 if (cmp != 0)
1163 {
Yegappan Lakshmananfadc02a2023-01-27 21:03:12 +00001164 if (ga_grow(gap, 1) == FAIL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001165 goto fail;
Bram Moolenaar71ccd032020-06-12 22:59:11 +02001166 if ((p = vim_strnsave(name, name_len)) == NULL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001167 goto fail;
1168
1169 cmd = USER_CMD_GA(gap, i);
1170 mch_memmove(cmd + 1, cmd, (gap->ga_len - i) * sizeof(ucmd_T));
1171
1172 ++gap->ga_len;
1173
1174 cmd->uc_name = p;
John Marriott9e795852024-08-20 20:57:23 +02001175 cmd->uc_namelen = name_len;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001176 }
1177
1178 cmd->uc_rep = rep_buf;
1179 cmd->uc_argt = argt;
1180 cmd->uc_def = def;
1181 cmd->uc_compl = compl;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001182 cmd->uc_script_ctx = current_sctx;
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001183 if (flags & UC_VIM9)
1184 cmd->uc_script_ctx.sc_version = SCRIPT_VERSION_VIM9;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00001185 cmd->uc_flags = flags & UC_VIM9;
Bram Moolenaar9b8d6222020-12-28 18:26:00 +01001186#ifdef FEAT_EVAL
Bram Moolenaar1a47ae32019-12-29 23:04:25 +01001187 cmd->uc_script_ctx.sc_lnum += SOURCING_LNUM;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001188 cmd->uc_compl_arg = compl_arg;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001189#endif
1190 cmd->uc_addr_type = addr_type;
1191
1192 return OK;
1193
1194fail:
1195 vim_free(rep_buf);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001196#if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001197 vim_free(compl_arg);
1198#endif
1199 return FAIL;
1200}
1201
1202/*
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001203 * If "p" starts with "{" then read a block of commands until "}".
1204 * Used for ":command" and ":autocmd".
1205 */
1206 char_u *
1207may_get_cmd_block(exarg_T *eap, char_u *p, char_u **tofree, int *flags)
1208{
1209 char_u *retp = p;
1210
1211 if (*p == '{' && ends_excmd2(eap->arg, skipwhite(p + 1))
Zoltan Arpadffy6fdb6282023-12-19 20:53:07 +01001212 && eap->ea_getline != NULL)
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001213 {
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001214 garray_T ga;
1215 char_u *line = NULL;
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001216
1217 ga_init2(&ga, sizeof(char_u *), 10);
Bram Moolenaar9f1a39a2022-01-08 15:39:39 +00001218 if (ga_copy_string(&ga, p) == FAIL)
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001219 return retp;
1220
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001221 // If the argument ends in "}" it must have been concatenated already
1222 // for ISN_EXEC.
1223 if (p[STRLEN(p) - 1] != '}')
1224 // Read lines between '{' and '}'. Does not support nesting or
1225 // here-doc constructs.
1226 for (;;)
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001227 {
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001228 vim_free(line);
Zoltan Arpadffy6fdb6282023-12-19 20:53:07 +01001229 if ((line = eap->ea_getline(':', eap->cookie,
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001230 0, GETLINE_CONCAT_CONTBAR)) == NULL)
1231 {
1232 emsg(_(e_missing_rcurly));
1233 break;
1234 }
Bram Moolenaar9f1a39a2022-01-08 15:39:39 +00001235 if (ga_copy_string(&ga, line) == FAIL)
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001236 break;
1237 if (*skipwhite(line) == '}')
1238 break;
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001239 }
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001240 vim_free(line);
1241 retp = *tofree = ga_concat_strings(&ga, "\n");
1242 ga_clear_strings(&ga);
1243 *flags |= UC_VIM9;
1244 }
1245 return retp;
1246}
1247
1248/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001249 * ":command ..." implementation
1250 */
1251 void
1252ex_command(exarg_T *eap)
1253{
Bram Moolenaarb7316892019-05-01 18:08:42 +02001254 char_u *name;
1255 char_u *end;
1256 char_u *p;
1257 long argt = 0;
1258 long def = -1;
1259 int flags = 0;
1260 int compl = EXPAND_NOTHING;
1261 char_u *compl_arg = NULL;
1262 cmd_addr_T addr_type_arg = ADDR_NONE;
1263 int has_attr = (eap->arg[0] == '-');
1264 int name_len;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001265
1266 p = eap->arg;
1267
1268 // Check for attributes
1269 while (*p == '-')
1270 {
1271 ++p;
1272 end = skiptowhite(p);
1273 if (uc_scan_attr(p, end - p, &argt, &def, &flags, &compl,
1274 &compl_arg, &addr_type_arg) == FAIL)
zeertzjq33e54302022-12-19 16:49:27 +00001275 goto theend;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001276 p = skipwhite(end);
1277 }
1278
1279 // Get the name (if any) and skip to the following argument
1280 name = p;
1281 if (ASCII_ISALPHA(*p))
1282 while (ASCII_ISALNUM(*p))
1283 ++p;
Bram Moolenaara72cfb82020-04-23 17:07:30 +02001284 if (!ends_excmd2(eap->arg, p) && !VIM_ISWHITE(*p))
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001285 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001286 emsg(_(e_invalid_command_name));
zeertzjq33e54302022-12-19 16:49:27 +00001287 goto theend;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001288 }
1289 end = p;
1290 name_len = (int)(end - name);
1291
1292 // If there is nothing after the name, and no attributes were specified,
1293 // we are listing commands
1294 p = skipwhite(end);
Bram Moolenaara72cfb82020-04-23 17:07:30 +02001295 if (!has_attr && ends_excmd2(eap->arg, p))
zeertzjq33e54302022-12-19 16:49:27 +00001296 {
John Marriott9e795852024-08-20 20:57:23 +02001297 uc_list(name, name_len);
zeertzjq33e54302022-12-19 16:49:27 +00001298 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001299 else if (!ASCII_ISUPPER(*name))
zeertzjq33e54302022-12-19 16:49:27 +00001300 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001301 emsg(_(e_user_defined_commands_must_start_with_an_uppercase_letter));
zeertzjq33e54302022-12-19 16:49:27 +00001302 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001303 else if ((name_len == 1 && *name == 'X')
1304 || (name_len <= 4
1305 && STRNCMP(name, "Next", name_len > 4 ? 4 : name_len) == 0))
zeertzjq33e54302022-12-19 16:49:27 +00001306 {
Bram Moolenaar9d00e4a2022-01-05 17:49:15 +00001307 emsg(_(e_reserved_name_cannot_be_used_for_user_defined_command));
zeertzjq33e54302022-12-19 16:49:27 +00001308 }
Martin Tournoijde69a732021-07-11 14:28:25 +02001309 else if (compl > 0 && (argt & EX_EXTRA) == 0)
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001310 {
1311 // Some plugins rely on silently ignoring the mistake, only make this
1312 // an error in Vim9 script.
1313 if (in_vim9script())
Bram Moolenaar41a34852021-08-04 16:09:24 +02001314 emsg(_(e_complete_used_without_allowing_arguments));
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001315 else
1316 give_warning_with_source(
Bram Moolenaar41a34852021-08-04 16:09:24 +02001317 (char_u *)_(e_complete_used_without_allowing_arguments),
1318 TRUE, TRUE);
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001319 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001320 else
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001321 {
1322 char_u *tofree = NULL;
1323
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001324 p = may_get_cmd_block(eap, p, &tofree, &flags);
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001325
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001326 uc_add_command(name, end - name, p, argt, def, flags, compl, compl_arg,
1327 addr_type_arg, eap->forceit);
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001328 vim_free(tofree);
zeertzjq33e54302022-12-19 16:49:27 +00001329
1330 return; // success
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001331 }
zeertzjq33e54302022-12-19 16:49:27 +00001332
1333theend:
1334 vim_free(compl_arg);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001335}
1336
1337/*
1338 * ":comclear" implementation
1339 * Clear all user commands, global and for current buffer.
1340 */
1341 void
1342ex_comclear(exarg_T *eap UNUSED)
1343{
1344 uc_clear(&ucmds);
Bram Moolenaare5c83282019-05-03 23:15:37 +02001345 if (curbuf != NULL)
1346 uc_clear(&curbuf->b_ucmds);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001347}
1348
1349/*
Bram Moolenaarcf2594f2022-11-13 23:30:06 +00001350 * If ucmd_locked is set give an error and return TRUE.
1351 * Otherwise return FALSE.
1352 */
1353 static int
1354is_ucmd_locked(void)
1355{
1356 if (ucmd_locked > 0)
1357 {
1358 emsg(_(e_cannot_change_user_commands_while_listing));
1359 return TRUE;
1360 }
1361 return FALSE;
1362}
1363
1364/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001365 * Clear all user commands for "gap".
1366 */
1367 void
1368uc_clear(garray_T *gap)
1369{
1370 int i;
1371 ucmd_T *cmd;
1372
Bram Moolenaarcf2594f2022-11-13 23:30:06 +00001373 if (is_ucmd_locked())
1374 return;
1375
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001376 for (i = 0; i < gap->ga_len; ++i)
1377 {
1378 cmd = USER_CMD_GA(gap, i);
1379 vim_free(cmd->uc_name);
John Marriott9e795852024-08-20 20:57:23 +02001380 cmd->uc_namelen = 0;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001381 vim_free(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001382# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001383 vim_free(cmd->uc_compl_arg);
1384# endif
1385 }
1386 ga_clear(gap);
1387}
1388
1389/*
1390 * ":delcommand" implementation
1391 */
1392 void
1393ex_delcommand(exarg_T *eap)
1394{
1395 int i = 0;
1396 ucmd_T *cmd = NULL;
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001397 int res = -1;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001398 garray_T *gap;
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001399 char_u *arg = eap->arg;
1400 int buffer_only = FALSE;
1401
1402 if (STRNCMP(arg, "-buffer", 7) == 0 && VIM_ISWHITE(arg[7]))
1403 {
1404 buffer_only = TRUE;
1405 arg = skipwhite(arg + 7);
1406 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001407
1408 gap = &curbuf->b_ucmds;
1409 for (;;)
1410 {
1411 for (i = 0; i < gap->ga_len; ++i)
1412 {
1413 cmd = USER_CMD_GA(gap, i);
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001414 res = STRCMP(arg, cmd->uc_name);
1415 if (res <= 0)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001416 break;
1417 }
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001418 if (gap == &ucmds || res == 0 || buffer_only)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001419 break;
1420 gap = &ucmds;
1421 }
1422
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001423 if (res != 0)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001424 {
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001425 semsg(_(buffer_only
1426 ? e_no_such_user_defined_command_in_current_buffer_str
1427 : e_no_such_user_defined_command_str), arg);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001428 return;
1429 }
1430
Bram Moolenaarcf2594f2022-11-13 23:30:06 +00001431 if (is_ucmd_locked())
1432 return;
1433
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001434 vim_free(cmd->uc_name);
1435 vim_free(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001436# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001437 vim_free(cmd->uc_compl_arg);
1438# endif
1439
1440 --gap->ga_len;
1441
1442 if (i < gap->ga_len)
1443 mch_memmove(cmd, cmd + 1, (gap->ga_len - i) * sizeof(ucmd_T));
1444}
1445
1446/*
1447 * Split and quote args for <f-args>.
1448 */
1449 static char_u *
1450uc_split_args(char_u *arg, size_t *lenp)
1451{
1452 char_u *buf;
1453 char_u *p;
1454 char_u *q;
1455 int len;
1456
1457 // Precalculate length
1458 p = arg;
1459 len = 2; // Initial and final quotes
1460
1461 while (*p)
1462 {
1463 if (p[0] == '\\' && p[1] == '\\')
1464 {
1465 len += 2;
1466 p += 2;
1467 }
1468 else if (p[0] == '\\' && VIM_ISWHITE(p[1]))
1469 {
1470 len += 1;
1471 p += 2;
1472 }
1473 else if (*p == '\\' || *p == '"')
1474 {
1475 len += 2;
1476 p += 1;
1477 }
1478 else if (VIM_ISWHITE(*p))
1479 {
1480 p = skipwhite(p);
1481 if (*p == NUL)
1482 break;
Bram Moolenaar20d89e02020-10-20 23:11:33 +02001483 len += 4; // ", "
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001484 }
1485 else
1486 {
1487 int charlen = (*mb_ptr2len)(p);
1488
1489 len += charlen;
1490 p += charlen;
1491 }
1492 }
1493
1494 buf = alloc(len + 1);
1495 if (buf == NULL)
1496 {
1497 *lenp = 0;
1498 return buf;
1499 }
1500
1501 p = arg;
1502 q = buf;
1503 *q++ = '"';
1504 while (*p)
1505 {
1506 if (p[0] == '\\' && p[1] == '\\')
1507 {
1508 *q++ = '\\';
1509 *q++ = '\\';
1510 p += 2;
1511 }
1512 else if (p[0] == '\\' && VIM_ISWHITE(p[1]))
1513 {
1514 *q++ = p[1];
1515 p += 2;
1516 }
1517 else if (*p == '\\' || *p == '"')
1518 {
1519 *q++ = '\\';
1520 *q++ = *p++;
1521 }
1522 else if (VIM_ISWHITE(*p))
1523 {
1524 p = skipwhite(p);
1525 if (*p == NUL)
1526 break;
1527 *q++ = '"';
1528 *q++ = ',';
Bram Moolenaar20d89e02020-10-20 23:11:33 +02001529 *q++ = ' ';
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001530 *q++ = '"';
1531 }
1532 else
1533 {
1534 MB_COPY_CHAR(p, q);
1535 }
1536 }
1537 *q++ = '"';
John Marriott9e795852024-08-20 20:57:23 +02001538 *q = NUL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001539
1540 *lenp = len;
1541 return buf;
1542}
1543
1544 static size_t
John Marriott9e795852024-08-20 20:57:23 +02001545add_cmd_modifier(char_u *buf, size_t buflen, char *mod_str, size_t mod_strlen, int *multi_mods)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001546{
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001547 if (buf != NULL)
1548 {
1549 if (*multi_mods)
John Marriott9e795852024-08-20 20:57:23 +02001550 {
1551 STRCPY(buf + buflen, " "); // the separating space
1552 ++buflen;
1553 }
1554 STRCPY(buf + buflen, mod_str);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001555 }
1556
John Marriott9e795852024-08-20 20:57:23 +02001557 if (*multi_mods)
1558 ++mod_strlen; // +1 for the separating space
1559 else
1560 *multi_mods = 1;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001561
John Marriott9e795852024-08-20 20:57:23 +02001562 return mod_strlen;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001563}
1564
1565/*
Bram Moolenaar02194d22020-10-24 23:08:38 +02001566 * Add modifiers from "cmod->cmod_split" to "buf". Set "multi_mods" when one
Bram Moolenaare1004402020-10-24 20:49:43 +02001567 * was added. Return the number of bytes added.
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001568 */
1569 size_t
dundargocc57b5bc2022-11-02 13:30:51 +00001570add_win_cmd_modifiers(char_u *buf, cmdmod_T *cmod, int *multi_mods)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001571{
John Marriott9e795852024-08-20 20:57:23 +02001572 size_t buflen = 0;
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001573
1574 // :aboveleft and :leftabove
Bram Moolenaar02194d22020-10-24 23:08:38 +02001575 if (cmod->cmod_split & WSP_ABOVE)
John Marriott9e795852024-08-20 20:57:23 +02001576 buflen += add_cmd_modifier(buf, buflen, "aboveleft", STRLEN_LITERAL("aboveleft"), multi_mods);
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001577 // :belowright and :rightbelow
Bram Moolenaar02194d22020-10-24 23:08:38 +02001578 if (cmod->cmod_split & WSP_BELOW)
John Marriott9e795852024-08-20 20:57:23 +02001579 buflen += add_cmd_modifier(buf, buflen, "belowright", STRLEN_LITERAL("belowright"), multi_mods);
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001580 // :botright
Bram Moolenaar02194d22020-10-24 23:08:38 +02001581 if (cmod->cmod_split & WSP_BOT)
John Marriott9e795852024-08-20 20:57:23 +02001582 buflen += add_cmd_modifier(buf, buflen, "botright", STRLEN_LITERAL("botright"), multi_mods);
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001583
1584 // :tab
Bram Moolenaar02194d22020-10-24 23:08:38 +02001585 if (cmod->cmod_tab > 0)
zeertzjq208567e2022-10-18 13:11:21 +01001586 {
1587 int tabnr = cmod->cmod_tab - 1;
1588
1589 if (tabnr == tabpage_index(curtab))
1590 {
1591 // For compatibility, don't add a tabpage number if it is the same
1592 // as the default number for :tab.
John Marriott9e795852024-08-20 20:57:23 +02001593 buflen += add_cmd_modifier(buf, buflen, "tab", STRLEN_LITERAL("tab"), multi_mods);
zeertzjq208567e2022-10-18 13:11:21 +01001594 }
1595 else
1596 {
1597 char tab_buf[NUMBUFLEN + 3];
John Marriott9e795852024-08-20 20:57:23 +02001598 size_t tab_buflen;
zeertzjq208567e2022-10-18 13:11:21 +01001599
John Marriott9e795852024-08-20 20:57:23 +02001600 tab_buflen = vim_snprintf(tab_buf, sizeof(tab_buf), "%dtab", tabnr);
1601 buflen += add_cmd_modifier(buf, buflen, tab_buf, tab_buflen, multi_mods);
zeertzjq208567e2022-10-18 13:11:21 +01001602 }
1603 }
1604
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001605 // :topleft
Bram Moolenaar02194d22020-10-24 23:08:38 +02001606 if (cmod->cmod_split & WSP_TOP)
John Marriott9e795852024-08-20 20:57:23 +02001607 buflen += add_cmd_modifier(buf, buflen, "topleft", STRLEN_LITERAL("topleft"), multi_mods);
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001608 // :vertical
Bram Moolenaar02194d22020-10-24 23:08:38 +02001609 if (cmod->cmod_split & WSP_VERT)
John Marriott9e795852024-08-20 20:57:23 +02001610 buflen += add_cmd_modifier(buf, buflen, "vertical", STRLEN_LITERAL("vertical"), multi_mods);
zeertzjqd3de1782022-09-01 12:58:52 +01001611 // :horizontal
1612 if (cmod->cmod_split & WSP_HOR)
John Marriott9e795852024-08-20 20:57:23 +02001613 buflen += add_cmd_modifier(buf, buflen, "horizontal", STRLEN_LITERAL("horizontal"), multi_mods);
1614
1615 return buflen;
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001616}
1617
1618/*
Bram Moolenaar02194d22020-10-24 23:08:38 +02001619 * Generate text for the "cmod" command modifiers.
1620 * If "buf" is NULL just return the length.
1621 */
Bram Moolenaara360dbe2020-10-26 18:46:53 +01001622 size_t
Bram Moolenaar02194d22020-10-24 23:08:38 +02001623produce_cmdmods(char_u *buf, cmdmod_T *cmod, int quote)
1624{
John Marriott9e795852024-08-20 20:57:23 +02001625 size_t buflen = 0;
Bram Moolenaar02194d22020-10-24 23:08:38 +02001626 int multi_mods = 0;
1627 int i;
John Marriott9e795852024-08-20 20:57:23 +02001628 static keyvalue_T mod_entry_tab[] =
1629 {
Bram Moolenaar02194d22020-10-24 23:08:38 +02001630#ifdef FEAT_BROWSE_CMD
John Marriott9e795852024-08-20 20:57:23 +02001631 KEYVALUE_ENTRY(CMOD_BROWSE, "browse"),
Bram Moolenaar02194d22020-10-24 23:08:38 +02001632#endif
1633#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
John Marriott9e795852024-08-20 20:57:23 +02001634 KEYVALUE_ENTRY(CMOD_CONFIRM, "confirm"),
Bram Moolenaar02194d22020-10-24 23:08:38 +02001635#endif
John Marriott9e795852024-08-20 20:57:23 +02001636 KEYVALUE_ENTRY(CMOD_HIDE, "hide"),
1637 KEYVALUE_ENTRY(CMOD_KEEPALT, "keepalt"),
1638 KEYVALUE_ENTRY(CMOD_KEEPJUMPS, "keepjumps"),
1639 KEYVALUE_ENTRY(CMOD_KEEPMARKS, "keepmarks"),
1640 KEYVALUE_ENTRY(CMOD_KEEPPATTERNS, "keeppatterns"),
1641 KEYVALUE_ENTRY(CMOD_LOCKMARKS, "lockmarks"),
1642 KEYVALUE_ENTRY(CMOD_NOSWAPFILE, "noswapfile"),
1643 KEYVALUE_ENTRY(CMOD_UNSILENT, "unsilent"),
1644 KEYVALUE_ENTRY(CMOD_NOAUTOCMD, "noautocmd"),
Bram Moolenaar02194d22020-10-24 23:08:38 +02001645#ifdef HAVE_SANDBOX
John Marriott9e795852024-08-20 20:57:23 +02001646 KEYVALUE_ENTRY(CMOD_SANDBOX, "sandbox"),
Bram Moolenaar02194d22020-10-24 23:08:38 +02001647#endif
John Marriott9e795852024-08-20 20:57:23 +02001648 KEYVALUE_ENTRY(CMOD_LEGACY, "legacy")
Bram Moolenaar02194d22020-10-24 23:08:38 +02001649 };
1650
John Marriott9e795852024-08-20 20:57:23 +02001651 if (quote)
Bram Moolenaar02194d22020-10-24 23:08:38 +02001652 {
John Marriott9e795852024-08-20 20:57:23 +02001653 ++buflen;
1654 if (buf != NULL)
1655 {
1656 *buf = '"';
1657 *(buf + buflen) = NUL;
1658 }
Bram Moolenaar02194d22020-10-24 23:08:38 +02001659 }
John Marriott9e795852024-08-20 20:57:23 +02001660 else
1661 if (buf != NULL)
1662 *buf = NUL;
Bram Moolenaar02194d22020-10-24 23:08:38 +02001663
1664 // the modifiers that are simple flags
John Marriott9e795852024-08-20 20:57:23 +02001665 for (i = 0; i < (int)ARRAY_LENGTH(mod_entry_tab); ++i)
1666 if (cmod->cmod_flags & mod_entry_tab[i].key)
John Marriott8d4477e2024-11-02 15:59:01 +01001667 buflen += add_cmd_modifier(buf, buflen,
1668 (char *)mod_entry_tab[i].value.string,
1669 mod_entry_tab[i].value.length, &multi_mods);
Bram Moolenaar02194d22020-10-24 23:08:38 +02001670
1671 // :silent
1672 if (cmod->cmod_flags & CMOD_SILENT)
John Marriott9e795852024-08-20 20:57:23 +02001673 {
1674 if (cmod->cmod_flags & CMOD_ERRSILENT)
John Marriott8d4477e2024-11-02 15:59:01 +01001675 buflen += add_cmd_modifier(buf, buflen, "silent!",
1676 STRLEN_LITERAL("silent!"), &multi_mods);
John Marriott9e795852024-08-20 20:57:23 +02001677 else
John Marriott8d4477e2024-11-02 15:59:01 +01001678 buflen += add_cmd_modifier(buf, buflen, "silent",
1679 STRLEN_LITERAL("silent"), &multi_mods);
John Marriott9e795852024-08-20 20:57:23 +02001680 }
1681
Bram Moolenaar02194d22020-10-24 23:08:38 +02001682 // :verbose
zeertzjq9359e8a2022-07-03 13:16:09 +01001683 if (cmod->cmod_verbose > 0)
1684 {
1685 int verbose_value = cmod->cmod_verbose - 1;
1686
1687 if (verbose_value == 1)
John Marriott9e795852024-08-20 20:57:23 +02001688 buflen += add_cmd_modifier(buf, buflen, "verbose", STRLEN_LITERAL("verbose"), &multi_mods);
zeertzjq9359e8a2022-07-03 13:16:09 +01001689 else
1690 {
1691 char verbose_buf[NUMBUFLEN];
John Marriott9e795852024-08-20 20:57:23 +02001692 size_t verbose_buflen;
zeertzjq9359e8a2022-07-03 13:16:09 +01001693
John Marriott9e795852024-08-20 20:57:23 +02001694 verbose_buflen = vim_snprintf(verbose_buf, sizeof(verbose_buf), "%dverbose", verbose_value);
1695 buflen += add_cmd_modifier(buf, buflen, verbose_buf, verbose_buflen, &multi_mods);
zeertzjq9359e8a2022-07-03 13:16:09 +01001696 }
1697 }
zeertzjq9359e8a2022-07-03 13:16:09 +01001698
John Marriott9e795852024-08-20 20:57:23 +02001699 // flags from cmod->cmod_split
1700 buflen += add_win_cmd_modifiers((buf == NULL) ? NULL : buf + buflen, cmod, &multi_mods);
1701
1702 if (quote)
Bram Moolenaar02194d22020-10-24 23:08:38 +02001703 {
John Marriott9e795852024-08-20 20:57:23 +02001704 if (buf == NULL)
1705 ++buflen;
1706 else
1707 {
1708 *(buf + buflen) = '"';
1709 ++buflen;
1710 *(buf + buflen) = NUL;
1711 }
Bram Moolenaar02194d22020-10-24 23:08:38 +02001712 }
John Marriott9e795852024-08-20 20:57:23 +02001713
1714 return buflen;
Bram Moolenaar02194d22020-10-24 23:08:38 +02001715}
1716
1717/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001718 * Check for a <> code in a user command.
1719 * "code" points to the '<'. "len" the length of the <> (inclusive).
1720 * "buf" is where the result is to be added.
1721 * "split_buf" points to a buffer used for splitting, caller should free it.
1722 * "split_len" is the length of what "split_buf" contains.
1723 * Returns the length of the replacement, which has been added to "buf".
1724 * Returns -1 if there was no match, and only the "<" has been copied.
1725 */
1726 static size_t
1727uc_check_code(
1728 char_u *code,
1729 size_t len,
1730 char_u *buf,
1731 ucmd_T *cmd, // the user command we're expanding
1732 exarg_T *eap, // ex arguments
1733 char_u **split_buf,
1734 size_t *split_len)
1735{
1736 size_t result = 0;
1737 char_u *p = code + 1;
1738 size_t l = len - 2;
1739 int quote = 0;
1740 enum {
1741 ct_ARGS,
1742 ct_BANG,
1743 ct_COUNT,
1744 ct_LINE1,
1745 ct_LINE2,
1746 ct_RANGE,
1747 ct_MODS,
1748 ct_REGISTER,
1749 ct_LT,
1750 ct_NONE
1751 } type = ct_NONE;
1752
1753 if ((vim_strchr((char_u *)"qQfF", *p) != NULL) && p[1] == '-')
1754 {
1755 quote = (*p == 'q' || *p == 'Q') ? 1 : 2;
1756 p += 2;
1757 l -= 2;
1758 }
1759
1760 ++l;
1761 if (l <= 1)
1762 type = ct_NONE;
1763 else if (STRNICMP(p, "args>", l) == 0)
1764 type = ct_ARGS;
1765 else if (STRNICMP(p, "bang>", l) == 0)
1766 type = ct_BANG;
1767 else if (STRNICMP(p, "count>", l) == 0)
1768 type = ct_COUNT;
1769 else if (STRNICMP(p, "line1>", l) == 0)
1770 type = ct_LINE1;
1771 else if (STRNICMP(p, "line2>", l) == 0)
1772 type = ct_LINE2;
1773 else if (STRNICMP(p, "range>", l) == 0)
1774 type = ct_RANGE;
1775 else if (STRNICMP(p, "lt>", l) == 0)
1776 type = ct_LT;
1777 else if (STRNICMP(p, "reg>", l) == 0 || STRNICMP(p, "register>", l) == 0)
1778 type = ct_REGISTER;
1779 else if (STRNICMP(p, "mods>", l) == 0)
1780 type = ct_MODS;
1781
1782 switch (type)
1783 {
1784 case ct_ARGS:
1785 // Simple case first
1786 if (*eap->arg == NUL)
1787 {
1788 if (quote == 1)
1789 {
1790 result = 2;
1791 if (buf != NULL)
1792 STRCPY(buf, "''");
1793 }
1794 else
1795 result = 0;
1796 break;
1797 }
1798
1799 // When specified there is a single argument don't split it.
1800 // Works for ":Cmd %" when % is "a b c".
Bram Moolenaar8071cb22019-07-12 17:58:01 +02001801 if ((eap->argt & EX_NOSPC) && quote == 2)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001802 quote = 1;
1803
1804 switch (quote)
1805 {
1806 case 0: // No quoting, no splitting
1807 result = STRLEN(eap->arg);
1808 if (buf != NULL)
1809 STRCPY(buf, eap->arg);
1810 break;
1811 case 1: // Quote, but don't split
1812 result = STRLEN(eap->arg) + 2;
1813 for (p = eap->arg; *p; ++p)
1814 {
1815 if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2)
1816 // DBCS can contain \ in a trail byte, skip the
1817 // double-byte character.
1818 ++p;
1819 else
1820 if (*p == '\\' || *p == '"')
1821 ++result;
1822 }
1823
1824 if (buf != NULL)
1825 {
1826 *buf++ = '"';
1827 for (p = eap->arg; *p; ++p)
1828 {
1829 if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2)
1830 // DBCS can contain \ in a trail byte, copy the
1831 // double-byte character to avoid escaping.
1832 *buf++ = *p++;
1833 else
1834 if (*p == '\\' || *p == '"')
1835 *buf++ = '\\';
1836 *buf++ = *p;
1837 }
1838 *buf = '"';
1839 }
1840
1841 break;
1842 case 2: // Quote and split (<f-args>)
1843 // This is hard, so only do it once, and cache the result
1844 if (*split_buf == NULL)
1845 *split_buf = uc_split_args(eap->arg, split_len);
1846
1847 result = *split_len;
1848 if (buf != NULL && result != 0)
1849 STRCPY(buf, *split_buf);
1850
1851 break;
1852 }
1853 break;
1854
1855 case ct_BANG:
1856 result = eap->forceit ? 1 : 0;
1857 if (quote)
1858 result += 2;
1859 if (buf != NULL)
1860 {
1861 if (quote)
1862 *buf++ = '"';
1863 if (eap->forceit)
1864 *buf++ = '!';
1865 if (quote)
1866 *buf = '"';
1867 }
1868 break;
1869
1870 case ct_LINE1:
1871 case ct_LINE2:
1872 case ct_RANGE:
1873 case ct_COUNT:
1874 {
John Marriott9e795852024-08-20 20:57:23 +02001875 char num_buf[NUMBUFLEN];
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001876 long num = (type == ct_LINE1) ? eap->line1 :
1877 (type == ct_LINE2) ? eap->line2 :
1878 (type == ct_RANGE) ? eap->addr_count :
1879 (eap->addr_count > 0) ? eap->line2 : cmd->uc_def;
1880 size_t num_len;
1881
John Marriott9e795852024-08-20 20:57:23 +02001882 num_len = vim_snprintf(num_buf, sizeof(num_buf), "%ld", num);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001883 result = num_len;
1884
1885 if (quote)
1886 result += 2;
1887
1888 if (buf != NULL)
1889 {
1890 if (quote)
1891 *buf++ = '"';
1892 STRCPY(buf, num_buf);
1893 buf += num_len;
1894 if (quote)
1895 *buf = '"';
1896 }
1897
1898 break;
1899 }
1900
1901 case ct_MODS:
1902 {
Bram Moolenaar02194d22020-10-24 23:08:38 +02001903 result = produce_cmdmods(buf, &cmdmod, quote);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001904 break;
1905 }
1906
1907 case ct_REGISTER:
1908 result = eap->regname ? 1 : 0;
1909 if (quote)
1910 result += 2;
1911 if (buf != NULL)
1912 {
1913 if (quote)
1914 *buf++ = '\'';
1915 if (eap->regname)
1916 *buf++ = eap->regname;
1917 if (quote)
1918 *buf = '\'';
1919 }
1920 break;
1921
1922 case ct_LT:
1923 result = 1;
1924 if (buf != NULL)
1925 *buf = '<';
1926 break;
1927
1928 default:
1929 // Not recognized: just copy the '<' and return -1.
1930 result = (size_t)-1;
1931 if (buf != NULL)
1932 *buf = '<';
1933 break;
1934 }
1935
1936 return result;
1937}
1938
1939/*
1940 * Execute a user defined command.
1941 */
1942 void
1943do_ucmd(exarg_T *eap)
1944{
1945 char_u *buf;
1946 char_u *p;
1947 char_u *q;
1948
1949 char_u *start;
1950 char_u *end = NULL;
1951 char_u *ksp;
1952 size_t len, totlen;
1953
1954 size_t split_len = 0;
1955 char_u *split_buf = NULL;
1956 ucmd_T *cmd;
Bram Moolenaar205f29c2021-12-10 21:46:09 +00001957 sctx_T save_current_sctx;
1958 int restore_current_sctx = FALSE;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00001959#ifdef FEAT_EVAL
1960 int restore_script_version = 0;
1961#endif
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001962
1963 if (eap->cmdidx == CMD_USER)
1964 cmd = USER_CMD(eap->useridx);
1965 else
zeertzjqb444ee72023-02-20 15:25:13 +00001966 cmd = USER_CMD_GA(&prevwin_curwin()->w_buffer->b_ucmds, eap->useridx);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001967
1968 /*
1969 * Replace <> in the command by the arguments.
1970 * First round: "buf" is NULL, compute length, allocate "buf".
1971 * Second round: copy result into "buf".
1972 */
1973 buf = NULL;
1974 for (;;)
1975 {
1976 p = cmd->uc_rep; // source
1977 q = buf; // destination
1978 totlen = 0;
1979
1980 for (;;)
1981 {
1982 start = vim_strchr(p, '<');
1983 if (start != NULL)
1984 end = vim_strchr(start + 1, '>');
1985 if (buf != NULL)
1986 {
1987 for (ksp = p; *ksp != NUL && *ksp != K_SPECIAL; ++ksp)
1988 ;
1989 if (*ksp == K_SPECIAL
1990 && (start == NULL || ksp < start || end == NULL)
1991 && ((ksp[1] == KS_SPECIAL && ksp[2] == KE_FILLER)
1992# ifdef FEAT_GUI
1993 || (ksp[1] == KS_EXTRA && ksp[2] == (int)KE_CSI)
1994# endif
1995 ))
1996 {
1997 // K_SPECIAL has been put in the buffer as K_SPECIAL
1998 // KS_SPECIAL KE_FILLER, like for mappings, but
1999 // do_cmdline() doesn't handle that, so convert it back.
2000 // Also change K_SPECIAL KS_EXTRA KE_CSI into CSI.
2001 len = ksp - p;
2002 if (len > 0)
2003 {
2004 mch_memmove(q, p, len);
2005 q += len;
2006 }
2007 *q++ = ksp[1] == KS_SPECIAL ? K_SPECIAL : CSI;
2008 p = ksp + 3;
2009 continue;
2010 }
2011 }
2012
2013 // break if no <item> is found
2014 if (start == NULL || end == NULL)
2015 break;
2016
2017 // Include the '>'
2018 ++end;
2019
2020 // Take everything up to the '<'
2021 len = start - p;
2022 if (buf == NULL)
2023 totlen += len;
2024 else
2025 {
2026 mch_memmove(q, p, len);
2027 q += len;
2028 }
2029
2030 len = uc_check_code(start, end - start, q, cmd, eap,
2031 &split_buf, &split_len);
2032 if (len == (size_t)-1)
2033 {
2034 // no match, continue after '<'
2035 p = start + 1;
2036 len = 1;
2037 }
2038 else
2039 p = end;
2040 if (buf == NULL)
2041 totlen += len;
2042 else
2043 q += len;
2044 }
2045 if (buf != NULL) // second time here, finished
2046 {
2047 STRCPY(q, p);
2048 break;
2049 }
2050
2051 totlen += STRLEN(p); // Add on the trailing characters
Bram Moolenaar964b3742019-05-24 18:54:09 +02002052 buf = alloc(totlen + 1);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02002053 if (buf == NULL)
2054 {
2055 vim_free(split_buf);
2056 return;
2057 }
2058 }
2059
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00002060 if ((cmd->uc_argt & EX_KEEPSCRIPT) == 0)
2061 {
Bram Moolenaar205f29c2021-12-10 21:46:09 +00002062 restore_current_sctx = TRUE;
2063 save_current_sctx = current_sctx;
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00002064 current_sctx.sc_version = cmd->uc_script_ctx.sc_version;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02002065#ifdef FEAT_EVAL
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00002066 current_sctx.sc_sid = cmd->uc_script_ctx.sc_sid;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00002067 if (cmd->uc_flags & UC_VIM9)
2068 {
2069 // In a {} block variables use Vim9 script rules, even in a legacy
2070 // script.
2071 restore_script_version =
2072 SCRIPT_ITEM(current_sctx.sc_sid)->sn_version;
2073 SCRIPT_ITEM(current_sctx.sc_sid)->sn_version = SCRIPT_VERSION_VIM9;
2074 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02002075#endif
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00002076 }
Bram Moolenaar205f29c2021-12-10 21:46:09 +00002077
Zoltan Arpadffy6fdb6282023-12-19 20:53:07 +01002078 (void)do_cmdline(buf, eap->ea_getline, eap->cookie,
Bram Moolenaarac9fb182019-04-27 13:04:13 +02002079 DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED);
Bram Moolenaar205f29c2021-12-10 21:46:09 +00002080
2081 // Careful: Do not use "cmd" here, it may have become invalid if a user
2082 // command was added.
2083 if (restore_current_sctx)
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00002084 {
2085#ifdef FEAT_EVAL
2086 if (restore_script_version != 0)
2087 SCRIPT_ITEM(current_sctx.sc_sid)->sn_version =
2088 restore_script_version;
2089#endif
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00002090 current_sctx = save_current_sctx;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00002091 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02002092 vim_free(buf);
2093 vim_free(split_buf);
2094}