blob: 9f1efb4b7e8d1d6c928902916eb9bac900832eb8 [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/*
489 * Get the name of completion type "expand" as a string.
490 */
491 char_u *
492cmdcomplete_type_to_str(int expand)
493{
John Marriott9e795852024-08-20 20:57:23 +0200494 keyvalue_T *kv;
Shougo Matsushita79d599b2022-05-07 12:48:29 +0100495
John Marriott9e795852024-08-20 20:57:23 +0200496 kv = get_commandtype(expand);
Shougo Matsushita79d599b2022-05-07 12:48:29 +0100497
John Marriott8d4477e2024-11-02 15:59:01 +0100498 return (kv == NULL) ? NULL : kv->value.string;
Shougo Matsushita79d599b2022-05-07 12:48:29 +0100499}
500
501/*
502 * Get the index of completion type "complete_str".
503 * Returns EXPAND_NOTHING if no match found.
504 */
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200505 int
506cmdcomplete_str_to_type(char_u *complete_str)
507{
John Marriott9e795852024-08-20 20:57:23 +0200508 keyvalue_T target;
509 keyvalue_T *entry;
510 static keyvalue_T *last_entry = NULL; // cached result
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200511
Shougo Matsushita92997dd2023-08-20 20:55:55 +0200512 if (STRNCMP(complete_str, "custom,", 7) == 0)
513 return EXPAND_USER_DEFINED;
514 if (STRNCMP(complete_str, "customlist,", 11) == 0)
515 return EXPAND_USER_LIST;
516
John Marriott9e795852024-08-20 20:57:23 +0200517 target.key = 0;
John Marriott8d4477e2024-11-02 15:59:01 +0100518 target.value.string = complete_str;
519 target.value.length = 0; // not used, see cmp_keyvalue_value()
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200520
John Marriott9e795852024-08-20 20:57:23 +0200521 if (last_entry != NULL && cmp_keyvalue_value(&target, last_entry) == 0)
522 entry = last_entry;
523 else
524 {
525 entry = (keyvalue_T *)bsearch(&target,
526 &command_complete_tab,
527 ARRAY_LENGTH(command_complete_tab),
528 sizeof(command_complete_tab[0]),
529 cmp_keyvalue_value);
530 if (entry == NULL)
531 return EXPAND_NOTHING;
532
533 last_entry = entry;
534 }
535
536 return entry->key;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200537}
Dominique Pelle748b3082022-01-08 12:41:16 +0000538#endif
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200539
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200540/*
541 * List user commands starting with "name[name_len]".
542 */
543 static void
544uc_list(char_u *name, size_t name_len)
545{
546 int i, j;
547 int found = FALSE;
548 ucmd_T *cmd;
549 int len;
550 int over;
551 long a;
552 garray_T *gap;
John Marriott9e795852024-08-20 20:57:23 +0200553 keyvalue_T *entry;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200554
Bram Moolenaarcf2594f2022-11-13 23:30:06 +0000555 // don't allow for adding or removing user commands here
556 ++ucmd_locked;
557
Bram Moolenaare38eab22019-12-05 21:50:01 +0100558 // In cmdwin, the alternative buffer should be used.
Bram Moolenaar0f6e28f2022-02-20 20:49:35 +0000559 gap = &prevwin_curwin()->w_buffer->b_ucmds;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200560 for (;;)
561 {
562 for (i = 0; i < gap->ga_len; ++i)
563 {
564 cmd = USER_CMD_GA(gap, i);
565 a = (long)cmd->uc_argt;
566
567 // Skip commands which don't match the requested prefix and
568 // commands filtered out.
569 if (STRNCMP(name, cmd->uc_name, name_len) != 0
570 || message_filtered(cmd->uc_name))
571 continue;
572
573 // Put out the title first time
574 if (!found)
575 msg_puts_title(_("\n Name Args Address Complete Definition"));
576 found = TRUE;
577 msg_putchar('\n');
578 if (got_int)
579 break;
580
581 // Special cases
582 len = 4;
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200583 if (a & EX_BANG)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200584 {
585 msg_putchar('!');
586 --len;
587 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200588 if (a & EX_REGSTR)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200589 {
590 msg_putchar('"');
591 --len;
592 }
593 if (gap != &ucmds)
594 {
595 msg_putchar('b');
596 --len;
597 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200598 if (a & EX_TRLBAR)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200599 {
600 msg_putchar('|');
601 --len;
602 }
Mike Williams0174d8f2025-06-08 15:41:52 +0200603 if (len != 0)
604 msg_puts(&" "[4 - len]);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200605
606 msg_outtrans_attr(cmd->uc_name, HL_ATTR(HLF_D));
John Marriott9e795852024-08-20 20:57:23 +0200607 len = (int)cmd->uc_namelen + 4;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200608
Mike Williams0174d8f2025-06-08 15:41:52 +0200609 if (len < 21)
Hirohito Higashia4a00a72025-05-08 22:58:31 +0200610 {
Mike Williams0174d8f2025-06-08 15:41:52 +0200611 // Field padding spaces 12345678901234567
612 static char spaces[18] = " ";
613 msg_puts(&spaces[len - 4]);
614 len = 21;
615 }
616 msg_putchar(' ');
617 ++len;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200618
619 // "over" is how much longer the name is than the column width for
620 // the name, we'll try to align what comes after.
621 over = len - 22;
622 len = 0;
623
624 // Arguments
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200625 switch ((int)(a & (EX_EXTRA|EX_NOSPC|EX_NEEDARG)))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200626 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200627 case 0: IObuff[len++] = '0'; break;
628 case (EX_EXTRA): IObuff[len++] = '*'; break;
629 case (EX_EXTRA|EX_NOSPC): IObuff[len++] = '?'; break;
630 case (EX_EXTRA|EX_NEEDARG): IObuff[len++] = '+'; break;
631 case (EX_EXTRA|EX_NOSPC|EX_NEEDARG): IObuff[len++] = '1'; break;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200632 }
633
Hirohito Higashia4a00a72025-05-08 22:58:31 +0200634 do
635 {
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200636 IObuff[len++] = ' ';
637 } while (len < 5 - over);
638
639 // Address / Range
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200640 if (a & (EX_RANGE|EX_COUNT))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200641 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200642 if (a & EX_COUNT)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200643 {
644 // -count=N
John Marriott9e795852024-08-20 20:57:23 +0200645 len += vim_snprintf((char *)IObuff + len, IOSIZE - len, "%ldc", cmd->uc_def);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200646 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200647 else if (a & EX_DFLALL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200648 IObuff[len++] = '%';
649 else if (cmd->uc_def >= 0)
650 {
651 // -range=N
John Marriott9e795852024-08-20 20:57:23 +0200652 len += vim_snprintf((char *)IObuff + len, IOSIZE - len, "%ld", cmd->uc_def);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200653 }
654 else
655 IObuff[len++] = '.';
656 }
657
Hirohito Higashia4a00a72025-05-08 22:58:31 +0200658 do
659 {
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200660 IObuff[len++] = ' ';
661 } while (len < 8 - over);
662
663 // Address Type
John Marriott9e795852024-08-20 20:57:23 +0200664 for (j = 0; j < (int)ARRAY_LENGTH(addr_type_complete_tab); ++j)
665 if (addr_type_complete_tab[j].key != ADDR_LINES
666 && addr_type_complete_tab[j].key == cmd->uc_addr_type)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200667 {
John Marriott9e795852024-08-20 20:57:23 +0200668 STRCPY(IObuff + len, addr_type_complete_tab[j].shortname);
669 len += (int)addr_type_complete_tab[j].shortnamelen;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200670 break;
671 }
672
Hirohito Higashia4a00a72025-05-08 22:58:31 +0200673 do
674 {
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200675 IObuff[len++] = ' ';
676 } while (len < 13 - over);
677
678 // Completion
John Marriott9e795852024-08-20 20:57:23 +0200679 entry = get_commandtype(cmd->uc_compl);
680 if (entry != NULL)
681 {
John Marriott8d4477e2024-11-02 15:59:01 +0100682 STRCPY(IObuff + len, entry->value.string);
Yegappan Lakshmanan084529c2024-12-24 09:50:01 +0100683 len += (int)entry->value.length;
Bram Moolenaar78f60322022-01-17 22:16:33 +0000684#ifdef FEAT_EVAL
John Marriott9e795852024-08-20 20:57:23 +0200685 if (p_verbose > 0 && cmd->uc_compl_arg != NULL)
686 {
687 size_t uc_compl_arglen = STRLEN(cmd->uc_compl_arg);
688
689 if (uc_compl_arglen < 200)
Bram Moolenaar3f3597b2022-01-17 19:06:56 +0000690 {
John Marriott9e795852024-08-20 20:57:23 +0200691 IObuff[len++] = ',';
692 STRCPY(IObuff + len, cmd->uc_compl_arg);
Yegappan Lakshmanan084529c2024-12-24 09:50:01 +0100693 len += (int)uc_compl_arglen;
Bram Moolenaar3f3597b2022-01-17 19:06:56 +0000694 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200695 }
John Marriott9e795852024-08-20 20:57:23 +0200696#endif
697 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200698
Hirohito Higashia4a00a72025-05-08 22:58:31 +0200699 do
700 {
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200701 IObuff[len++] = ' ';
702 } while (len < 25 - over);
703
John Marriott9e795852024-08-20 20:57:23 +0200704 IObuff[len] = NUL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200705 msg_outtrans(IObuff);
706
707 msg_outtrans_special(cmd->uc_rep, FALSE,
708 name_len == 0 ? Columns - 47 : 0);
709#ifdef FEAT_EVAL
710 if (p_verbose > 0)
711 last_set_msg(cmd->uc_script_ctx);
712#endif
713 out_flush();
714 ui_breakcheck();
715 if (got_int)
716 break;
717 }
718 if (gap == &ucmds || i < gap->ga_len)
719 break;
720 gap = &ucmds;
721 }
722
723 if (!found)
724 msg(_("No user-defined commands found"));
Bram Moolenaarcf2594f2022-11-13 23:30:06 +0000725
726 --ucmd_locked;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200727}
728
729 char *
730uc_fun_cmd(void)
731{
732 static char_u fcmd[] = {0x84, 0xaf, 0x60, 0xb9, 0xaf, 0xb5, 0x60, 0xa4,
733 0xa5, 0xad, 0xa1, 0xae, 0xa4, 0x60, 0xa1, 0x60,
734 0xb3, 0xa8, 0xb2, 0xb5, 0xa2, 0xa2, 0xa5, 0xb2,
735 0xb9, 0x7f, 0};
736 int i;
737
738 for (i = 0; fcmd[i]; ++i)
739 IObuff[i] = fcmd[i] - 0x40;
John Marriott9e795852024-08-20 20:57:23 +0200740 IObuff[i] = NUL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200741 return (char *)IObuff;
742}
743
744/*
745 * Parse address type argument
746 */
747 static int
748parse_addr_type_arg(
749 char_u *value,
750 int vallen,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200751 cmd_addr_T *addr_type_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200752{
John Marriott9e795852024-08-20 20:57:23 +0200753 addrtype_T target;
754 addrtype_T *entry;
755 static addrtype_T *last_entry; // cached result
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200756
John Marriott9e795852024-08-20 20:57:23 +0200757 target.key = 0;
758 target.fullname = (char *)value;
759 target.fullnamelen = vallen;
760
761 if (last_entry != NULL && cmp_addr_type(&target, last_entry) == 0)
762 entry = last_entry;
763 else
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200764 {
John Marriott9e795852024-08-20 20:57:23 +0200765 entry = (addrtype_T *)bsearch(&target,
766 &addr_type_complete_tab,
767 ARRAY_LENGTH(addr_type_complete_tab),
768 sizeof(addr_type_complete_tab[0]),
769 cmp_addr_type);
770 if (entry == NULL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200771 {
John Marriott9e795852024-08-20 20:57:23 +0200772 int i;
773 char_u *err = value;
774
775 for (i = 0; err[i] != NUL && !VIM_ISWHITE(err[i]); i++)
776 ;
777 err[i] = NUL;
778 semsg(_(e_invalid_address_type_value_str), err);
779 return FAIL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200780 }
John Marriott9e795852024-08-20 20:57:23 +0200781
782 last_entry = entry;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200783 }
784
John Marriott9e795852024-08-20 20:57:23 +0200785 *addr_type_arg = entry->key;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200786
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200787 return OK;
788}
789
John Marriott9e795852024-08-20 20:57:23 +0200790 static int
791cmp_addr_type(const void *a, const void *b)
792{
793 addrtype_T *at1 = (addrtype_T *)a;
794 addrtype_T *at2 = (addrtype_T *)b;
795
796 return STRNCMP(at1->fullname, at2->fullname, MAX(at1->fullnamelen, at2->fullnamelen));
797}
798
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200799/*
800 * Parse a completion argument "value[vallen]".
801 * The detected completion goes in "*complp", argument type in "*argt".
802 * When there is an argument, for function and user defined completion, it's
803 * copied to allocated memory and stored in "*compl_arg".
804 * Returns FAIL if something is wrong.
805 */
806 int
807parse_compl_arg(
808 char_u *value,
809 int vallen,
810 int *complp,
811 long *argt,
812 char_u **compl_arg UNUSED)
813{
814 char_u *arg = NULL;
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200815# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200816 size_t arglen = 0;
817# endif
818 int i;
819 int valend = vallen;
John Marriott9e795852024-08-20 20:57:23 +0200820 keyvalue_T target;
821 keyvalue_T *entry;
822 static keyvalue_T *last_entry = NULL; // cached result
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200823
824 // Look for any argument part - which is the part after any ','
825 for (i = 0; i < vallen; ++i)
826 {
827 if (value[i] == ',')
828 {
829 arg = &value[i + 1];
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200830# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200831 arglen = vallen - i - 1;
832# endif
833 valend = i;
834 break;
835 }
836 }
837
John Marriott9e795852024-08-20 20:57:23 +0200838 target.key = 0;
John Marriott8d4477e2024-11-02 15:59:01 +0100839 target.value.string = value;
840 target.value.length = valend;
John Marriott9e795852024-08-20 20:57:23 +0200841
842 if (last_entry != NULL && cmp_keyvalue_value_n(&target, last_entry) == 0)
843 entry = last_entry;
844 else
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200845 {
John Marriott9e795852024-08-20 20:57:23 +0200846 entry = (keyvalue_T *)bsearch(&target,
847 &command_complete_tab,
848 ARRAY_LENGTH(command_complete_tab),
849 sizeof(command_complete_tab[0]),
850 cmp_keyvalue_value_n);
851 if (entry == NULL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200852 {
John Marriott9e795852024-08-20 20:57:23 +0200853 semsg(_(e_invalid_complete_value_str), value);
854 return FAIL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200855 }
John Marriott9e795852024-08-20 20:57:23 +0200856
857 last_entry = entry;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200858 }
859
John Marriott9e795852024-08-20 20:57:23 +0200860 *complp = entry->key;
861 if (*complp == EXPAND_BUFFERS)
862 *argt |= EX_BUFNAME;
Ruslan Russkikh0407d622024-10-08 22:21:05 +0200863 else if (*complp == EXPAND_DIRECTORIES || *complp == EXPAND_FILES || *complp == EXPAND_SHELLCMDLINE)
John Marriott9e795852024-08-20 20:57:23 +0200864 *argt |= EX_XFILE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200865
John Marriott9e795852024-08-20 20:57:23 +0200866 if (
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200867# if defined(FEAT_EVAL)
John Marriott9e795852024-08-20 20:57:23 +0200868 *complp != EXPAND_USER_DEFINED && *complp != EXPAND_USER_LIST
869 &&
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200870# endif
John Marriott9e795852024-08-20 20:57:23 +0200871 arg != NULL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200872 {
Bram Moolenaarb09feaa2022-01-02 20:20:45 +0000873 emsg(_(e_completion_argument_only_allowed_for_custom_completion));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200874 return FAIL;
875 }
876
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200877# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200878 if ((*complp == EXPAND_USER_DEFINED || *complp == EXPAND_USER_LIST)
879 && arg == NULL)
880 {
Bram Moolenaarb09feaa2022-01-02 20:20:45 +0000881 emsg(_(e_custom_completion_requires_function_argument));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200882 return FAIL;
883 }
884
885 if (arg != NULL)
Bram Moolenaar71ccd032020-06-12 22:59:11 +0200886 *compl_arg = vim_strnsave(arg, arglen);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200887# endif
John Marriott9e795852024-08-20 20:57:23 +0200888
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200889 return OK;
890}
891
892/*
893 * Scan attributes in the ":command" command.
894 * Return FAIL when something is wrong.
895 */
896 static int
897uc_scan_attr(
898 char_u *attr,
899 size_t len,
900 long *argt,
901 long *def,
902 int *flags,
Bram Moolenaar52111f82019-04-29 21:30:45 +0200903 int *complp,
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200904 char_u **compl_arg,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200905 cmd_addr_T *addr_type_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200906{
907 char_u *p;
908
909 if (len == 0)
910 {
Bram Moolenaar1a992222021-12-31 17:25:48 +0000911 emsg(_(e_no_attribute_specified));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200912 return FAIL;
913 }
914
915 // First, try the simple attributes (no arguments)
916 if (STRNICMP(attr, "bang", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200917 *argt |= EX_BANG;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200918 else if (STRNICMP(attr, "buffer", len) == 0)
919 *flags |= UC_BUFFER;
920 else if (STRNICMP(attr, "register", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200921 *argt |= EX_REGSTR;
Bram Moolenaar58ef8a32021-11-12 11:25:11 +0000922 else if (STRNICMP(attr, "keepscript", len) == 0)
923 *argt |= EX_KEEPSCRIPT;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200924 else if (STRNICMP(attr, "bar", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200925 *argt |= EX_TRLBAR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200926 else
927 {
928 int i;
929 char_u *val = NULL;
930 size_t vallen = 0;
931 size_t attrlen = len;
932
933 // Look for the attribute name - which is the part before any '='
934 for (i = 0; i < (int)len; ++i)
935 {
936 if (attr[i] == '=')
937 {
938 val = &attr[i + 1];
939 vallen = len - i - 1;
940 attrlen = i;
941 break;
942 }
943 }
944
945 if (STRNICMP(attr, "nargs", attrlen) == 0)
946 {
947 if (vallen == 1)
948 {
949 if (*val == '0')
950 // Do nothing - this is the default
951 ;
952 else if (*val == '1')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200953 *argt |= (EX_EXTRA | EX_NOSPC | EX_NEEDARG);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200954 else if (*val == '*')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200955 *argt |= EX_EXTRA;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200956 else if (*val == '?')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200957 *argt |= (EX_EXTRA | EX_NOSPC);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200958 else if (*val == '+')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200959 *argt |= (EX_EXTRA | EX_NEEDARG);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200960 else
961 goto wrong_nargs;
962 }
963 else
964 {
965wrong_nargs:
Bram Moolenaar1a992222021-12-31 17:25:48 +0000966 emsg(_(e_invalid_number_of_arguments));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200967 return FAIL;
968 }
969 }
970 else if (STRNICMP(attr, "range", attrlen) == 0)
971 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200972 *argt |= EX_RANGE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200973 if (vallen == 1 && *val == '%')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200974 *argt |= EX_DFLALL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200975 else if (val != NULL)
976 {
977 p = val;
978 if (*def >= 0)
979 {
980two_count:
Bram Moolenaar1a992222021-12-31 17:25:48 +0000981 emsg(_(e_count_cannot_be_specified_twice));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200982 return FAIL;
983 }
984
985 *def = getdigits(&p);
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200986 *argt |= EX_ZEROR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200987
988 if (p != val + vallen || vallen == 0)
989 {
990invalid_count:
Bram Moolenaar1a992222021-12-31 17:25:48 +0000991 emsg(_(e_invalid_default_value_for_count));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200992 return FAIL;
993 }
994 }
Bram Moolenaarb7316892019-05-01 18:08:42 +0200995 // default for -range is using buffer lines
996 if (*addr_type_arg == ADDR_NONE)
997 *addr_type_arg = ADDR_LINES;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200998 }
999 else if (STRNICMP(attr, "count", attrlen) == 0)
1000 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +02001001 *argt |= (EX_COUNT | EX_ZEROR | EX_RANGE);
Bram Moolenaarb7316892019-05-01 18:08:42 +02001002 // default for -count is using any number
1003 if (*addr_type_arg == ADDR_NONE)
1004 *addr_type_arg = ADDR_OTHER;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001005
1006 if (val != NULL)
1007 {
1008 p = val;
1009 if (*def >= 0)
1010 goto two_count;
1011
1012 *def = getdigits(&p);
1013
1014 if (p != val + vallen)
1015 goto invalid_count;
1016 }
1017
1018 if (*def < 0)
1019 *def = 0;
1020 }
1021 else if (STRNICMP(attr, "complete", attrlen) == 0)
1022 {
1023 if (val == NULL)
1024 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001025 semsg(_(e_argument_required_for_str), "-complete");
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001026 return FAIL;
1027 }
1028
Bram Moolenaar52111f82019-04-29 21:30:45 +02001029 if (parse_compl_arg(val, (int)vallen, complp, argt, compl_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001030 == FAIL)
1031 return FAIL;
1032 }
1033 else if (STRNICMP(attr, "addr", attrlen) == 0)
1034 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +02001035 *argt |= EX_RANGE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001036 if (val == NULL)
1037 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001038 semsg(_(e_argument_required_for_str), "-addr");
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001039 return FAIL;
1040 }
Bram Moolenaare4f5f3a2019-05-04 14:05:08 +02001041 if (parse_addr_type_arg(val, (int)vallen, addr_type_arg) == FAIL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001042 return FAIL;
Bram Moolenaare4f5f3a2019-05-04 14:05:08 +02001043 if (*addr_type_arg != ADDR_LINES)
Bram Moolenaar8071cb22019-07-12 17:58:01 +02001044 *argt |= EX_ZEROR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001045 }
1046 else
1047 {
1048 char_u ch = attr[len];
1049 attr[len] = '\0';
Bram Moolenaar1a992222021-12-31 17:25:48 +00001050 semsg(_(e_invalid_attribute_str), attr);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001051 attr[len] = ch;
1052 return FAIL;
1053 }
1054 }
1055
1056 return OK;
1057}
1058
1059/*
1060 * Add a user command to the list or replace an existing one.
1061 */
1062 static int
1063uc_add_command(
1064 char_u *name,
1065 size_t name_len,
1066 char_u *rep,
1067 long argt,
1068 long def,
1069 int flags,
1070 int compl,
1071 char_u *compl_arg UNUSED,
Bram Moolenaarb7316892019-05-01 18:08:42 +02001072 cmd_addr_T addr_type,
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001073 int force)
1074{
1075 ucmd_T *cmd = NULL;
1076 char_u *p;
1077 int i;
1078 int cmp = 1;
1079 char_u *rep_buf = NULL;
1080 garray_T *gap;
1081
zeertzjq7e0bae02023-08-11 23:15:38 +02001082 replace_termcodes(rep, &rep_buf, 0, 0, NULL);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001083 if (rep_buf == NULL)
1084 {
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001085 // can't replace termcodes - try using the string as is
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001086 rep_buf = vim_strsave(rep);
1087
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001088 // give up if out of memory
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001089 if (rep_buf == NULL)
1090 return FAIL;
1091 }
1092
1093 // get address of growarray: global or in curbuf
1094 if (flags & UC_BUFFER)
1095 {
1096 gap = &curbuf->b_ucmds;
1097 if (gap->ga_itemsize == 0)
Bram Moolenaar04935fb2022-01-08 16:19:22 +00001098 ga_init2(gap, sizeof(ucmd_T), 4);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001099 }
1100 else
1101 gap = &ucmds;
1102
1103 // Search for the command in the already defined commands.
1104 for (i = 0; i < gap->ga_len; ++i)
1105 {
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001106 cmd = USER_CMD_GA(gap, i);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001107 cmp = STRNCMP(name, cmd->uc_name, name_len);
1108 if (cmp == 0)
1109 {
John Marriott9e795852024-08-20 20:57:23 +02001110 if (name_len < cmd->uc_namelen)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001111 cmp = -1;
John Marriott9e795852024-08-20 20:57:23 +02001112 else if (name_len > cmd->uc_namelen)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001113 cmp = 1;
1114 }
1115
1116 if (cmp == 0)
1117 {
1118 // Command can be replaced with "command!" and when sourcing the
1119 // same script again, but only once.
1120 if (!force
1121#ifdef FEAT_EVAL
1122 && (cmd->uc_script_ctx.sc_sid != current_sctx.sc_sid
1123 || cmd->uc_script_ctx.sc_seq == current_sctx.sc_seq)
1124#endif
1125 )
1126 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001127 semsg(_(e_command_already_exists_add_bang_to_replace_it_str),
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001128 name);
1129 goto fail;
1130 }
1131
1132 VIM_CLEAR(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001133#if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001134 VIM_CLEAR(cmd->uc_compl_arg);
1135#endif
1136 break;
1137 }
1138
1139 // Stop as soon as we pass the name to add
1140 if (cmp < 0)
1141 break;
1142 }
1143
1144 // Extend the array unless we're replacing an existing command
1145 if (cmp != 0)
1146 {
Yegappan Lakshmananfadc02a2023-01-27 21:03:12 +00001147 if (ga_grow(gap, 1) == FAIL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001148 goto fail;
Bram Moolenaar71ccd032020-06-12 22:59:11 +02001149 if ((p = vim_strnsave(name, name_len)) == NULL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001150 goto fail;
1151
1152 cmd = USER_CMD_GA(gap, i);
1153 mch_memmove(cmd + 1, cmd, (gap->ga_len - i) * sizeof(ucmd_T));
1154
1155 ++gap->ga_len;
1156
1157 cmd->uc_name = p;
John Marriott9e795852024-08-20 20:57:23 +02001158 cmd->uc_namelen = name_len;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001159 }
1160
1161 cmd->uc_rep = rep_buf;
1162 cmd->uc_argt = argt;
1163 cmd->uc_def = def;
1164 cmd->uc_compl = compl;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001165 cmd->uc_script_ctx = current_sctx;
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001166 if (flags & UC_VIM9)
1167 cmd->uc_script_ctx.sc_version = SCRIPT_VERSION_VIM9;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00001168 cmd->uc_flags = flags & UC_VIM9;
Bram Moolenaar9b8d6222020-12-28 18:26:00 +01001169#ifdef FEAT_EVAL
Bram Moolenaar1a47ae32019-12-29 23:04:25 +01001170 cmd->uc_script_ctx.sc_lnum += SOURCING_LNUM;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001171 cmd->uc_compl_arg = compl_arg;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001172#endif
1173 cmd->uc_addr_type = addr_type;
1174
1175 return OK;
1176
1177fail:
1178 vim_free(rep_buf);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001179#if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001180 vim_free(compl_arg);
1181#endif
1182 return FAIL;
1183}
1184
1185/*
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001186 * If "p" starts with "{" then read a block of commands until "}".
1187 * Used for ":command" and ":autocmd".
1188 */
1189 char_u *
1190may_get_cmd_block(exarg_T *eap, char_u *p, char_u **tofree, int *flags)
1191{
1192 char_u *retp = p;
1193
1194 if (*p == '{' && ends_excmd2(eap->arg, skipwhite(p + 1))
Zoltan Arpadffy6fdb6282023-12-19 20:53:07 +01001195 && eap->ea_getline != NULL)
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001196 {
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001197 garray_T ga;
1198 char_u *line = NULL;
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001199
1200 ga_init2(&ga, sizeof(char_u *), 10);
Bram Moolenaar9f1a39a2022-01-08 15:39:39 +00001201 if (ga_copy_string(&ga, p) == FAIL)
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001202 return retp;
1203
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001204 // If the argument ends in "}" it must have been concatenated already
1205 // for ISN_EXEC.
1206 if (p[STRLEN(p) - 1] != '}')
1207 // Read lines between '{' and '}'. Does not support nesting or
1208 // here-doc constructs.
1209 for (;;)
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001210 {
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001211 vim_free(line);
Zoltan Arpadffy6fdb6282023-12-19 20:53:07 +01001212 if ((line = eap->ea_getline(':', eap->cookie,
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001213 0, GETLINE_CONCAT_CONTBAR)) == NULL)
1214 {
1215 emsg(_(e_missing_rcurly));
1216 break;
1217 }
Bram Moolenaar9f1a39a2022-01-08 15:39:39 +00001218 if (ga_copy_string(&ga, line) == FAIL)
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001219 break;
1220 if (*skipwhite(line) == '}')
1221 break;
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001222 }
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001223 vim_free(line);
1224 retp = *tofree = ga_concat_strings(&ga, "\n");
1225 ga_clear_strings(&ga);
1226 *flags |= UC_VIM9;
1227 }
1228 return retp;
1229}
1230
1231/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001232 * ":command ..." implementation
1233 */
1234 void
1235ex_command(exarg_T *eap)
1236{
Bram Moolenaarb7316892019-05-01 18:08:42 +02001237 char_u *name;
1238 char_u *end;
1239 char_u *p;
1240 long argt = 0;
1241 long def = -1;
1242 int flags = 0;
1243 int compl = EXPAND_NOTHING;
1244 char_u *compl_arg = NULL;
1245 cmd_addr_T addr_type_arg = ADDR_NONE;
1246 int has_attr = (eap->arg[0] == '-');
1247 int name_len;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001248
1249 p = eap->arg;
1250
1251 // Check for attributes
1252 while (*p == '-')
1253 {
1254 ++p;
1255 end = skiptowhite(p);
1256 if (uc_scan_attr(p, end - p, &argt, &def, &flags, &compl,
1257 &compl_arg, &addr_type_arg) == FAIL)
zeertzjq33e54302022-12-19 16:49:27 +00001258 goto theend;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001259 p = skipwhite(end);
1260 }
1261
1262 // Get the name (if any) and skip to the following argument
1263 name = p;
1264 if (ASCII_ISALPHA(*p))
1265 while (ASCII_ISALNUM(*p))
1266 ++p;
Bram Moolenaara72cfb82020-04-23 17:07:30 +02001267 if (!ends_excmd2(eap->arg, p) && !VIM_ISWHITE(*p))
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001268 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001269 emsg(_(e_invalid_command_name));
zeertzjq33e54302022-12-19 16:49:27 +00001270 goto theend;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001271 }
1272 end = p;
1273 name_len = (int)(end - name);
1274
1275 // If there is nothing after the name, and no attributes were specified,
1276 // we are listing commands
1277 p = skipwhite(end);
Bram Moolenaara72cfb82020-04-23 17:07:30 +02001278 if (!has_attr && ends_excmd2(eap->arg, p))
zeertzjq33e54302022-12-19 16:49:27 +00001279 {
John Marriott9e795852024-08-20 20:57:23 +02001280 uc_list(name, name_len);
zeertzjq33e54302022-12-19 16:49:27 +00001281 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001282 else if (!ASCII_ISUPPER(*name))
zeertzjq33e54302022-12-19 16:49:27 +00001283 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001284 emsg(_(e_user_defined_commands_must_start_with_an_uppercase_letter));
zeertzjq33e54302022-12-19 16:49:27 +00001285 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001286 else if ((name_len == 1 && *name == 'X')
1287 || (name_len <= 4
1288 && STRNCMP(name, "Next", name_len > 4 ? 4 : name_len) == 0))
zeertzjq33e54302022-12-19 16:49:27 +00001289 {
Bram Moolenaar9d00e4a2022-01-05 17:49:15 +00001290 emsg(_(e_reserved_name_cannot_be_used_for_user_defined_command));
zeertzjq33e54302022-12-19 16:49:27 +00001291 }
Martin Tournoijde69a732021-07-11 14:28:25 +02001292 else if (compl > 0 && (argt & EX_EXTRA) == 0)
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001293 {
1294 // Some plugins rely on silently ignoring the mistake, only make this
1295 // an error in Vim9 script.
1296 if (in_vim9script())
Bram Moolenaar41a34852021-08-04 16:09:24 +02001297 emsg(_(e_complete_used_without_allowing_arguments));
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001298 else
1299 give_warning_with_source(
Bram Moolenaar41a34852021-08-04 16:09:24 +02001300 (char_u *)_(e_complete_used_without_allowing_arguments),
1301 TRUE, TRUE);
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001302 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001303 else
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001304 {
1305 char_u *tofree = NULL;
1306
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001307 p = may_get_cmd_block(eap, p, &tofree, &flags);
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001308
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001309 uc_add_command(name, end - name, p, argt, def, flags, compl, compl_arg,
1310 addr_type_arg, eap->forceit);
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001311 vim_free(tofree);
zeertzjq33e54302022-12-19 16:49:27 +00001312
1313 return; // success
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001314 }
zeertzjq33e54302022-12-19 16:49:27 +00001315
1316theend:
1317 vim_free(compl_arg);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001318}
1319
1320/*
1321 * ":comclear" implementation
1322 * Clear all user commands, global and for current buffer.
1323 */
1324 void
1325ex_comclear(exarg_T *eap UNUSED)
1326{
1327 uc_clear(&ucmds);
Bram Moolenaare5c83282019-05-03 23:15:37 +02001328 if (curbuf != NULL)
1329 uc_clear(&curbuf->b_ucmds);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001330}
1331
1332/*
Bram Moolenaarcf2594f2022-11-13 23:30:06 +00001333 * If ucmd_locked is set give an error and return TRUE.
1334 * Otherwise return FALSE.
1335 */
1336 static int
1337is_ucmd_locked(void)
1338{
1339 if (ucmd_locked > 0)
1340 {
1341 emsg(_(e_cannot_change_user_commands_while_listing));
1342 return TRUE;
1343 }
1344 return FALSE;
1345}
1346
1347/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001348 * Clear all user commands for "gap".
1349 */
1350 void
1351uc_clear(garray_T *gap)
1352{
1353 int i;
1354 ucmd_T *cmd;
1355
Bram Moolenaarcf2594f2022-11-13 23:30:06 +00001356 if (is_ucmd_locked())
1357 return;
1358
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001359 for (i = 0; i < gap->ga_len; ++i)
1360 {
1361 cmd = USER_CMD_GA(gap, i);
1362 vim_free(cmd->uc_name);
John Marriott9e795852024-08-20 20:57:23 +02001363 cmd->uc_namelen = 0;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001364 vim_free(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001365# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001366 vim_free(cmd->uc_compl_arg);
1367# endif
1368 }
1369 ga_clear(gap);
1370}
1371
1372/*
1373 * ":delcommand" implementation
1374 */
1375 void
1376ex_delcommand(exarg_T *eap)
1377{
1378 int i = 0;
1379 ucmd_T *cmd = NULL;
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001380 int res = -1;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001381 garray_T *gap;
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001382 char_u *arg = eap->arg;
1383 int buffer_only = FALSE;
1384
1385 if (STRNCMP(arg, "-buffer", 7) == 0 && VIM_ISWHITE(arg[7]))
1386 {
1387 buffer_only = TRUE;
1388 arg = skipwhite(arg + 7);
1389 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001390
1391 gap = &curbuf->b_ucmds;
1392 for (;;)
1393 {
1394 for (i = 0; i < gap->ga_len; ++i)
1395 {
1396 cmd = USER_CMD_GA(gap, i);
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001397 res = STRCMP(arg, cmd->uc_name);
1398 if (res <= 0)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001399 break;
1400 }
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001401 if (gap == &ucmds || res == 0 || buffer_only)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001402 break;
1403 gap = &ucmds;
1404 }
1405
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001406 if (res != 0)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001407 {
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001408 semsg(_(buffer_only
1409 ? e_no_such_user_defined_command_in_current_buffer_str
1410 : e_no_such_user_defined_command_str), arg);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001411 return;
1412 }
1413
Bram Moolenaarcf2594f2022-11-13 23:30:06 +00001414 if (is_ucmd_locked())
1415 return;
1416
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001417 vim_free(cmd->uc_name);
1418 vim_free(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001419# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001420 vim_free(cmd->uc_compl_arg);
1421# endif
1422
1423 --gap->ga_len;
1424
1425 if (i < gap->ga_len)
1426 mch_memmove(cmd, cmd + 1, (gap->ga_len - i) * sizeof(ucmd_T));
1427}
1428
1429/*
1430 * Split and quote args for <f-args>.
1431 */
1432 static char_u *
1433uc_split_args(char_u *arg, size_t *lenp)
1434{
1435 char_u *buf;
1436 char_u *p;
1437 char_u *q;
1438 int len;
1439
1440 // Precalculate length
1441 p = arg;
1442 len = 2; // Initial and final quotes
1443
1444 while (*p)
1445 {
1446 if (p[0] == '\\' && p[1] == '\\')
1447 {
1448 len += 2;
1449 p += 2;
1450 }
1451 else if (p[0] == '\\' && VIM_ISWHITE(p[1]))
1452 {
1453 len += 1;
1454 p += 2;
1455 }
1456 else if (*p == '\\' || *p == '"')
1457 {
1458 len += 2;
1459 p += 1;
1460 }
1461 else if (VIM_ISWHITE(*p))
1462 {
1463 p = skipwhite(p);
1464 if (*p == NUL)
1465 break;
Bram Moolenaar20d89e02020-10-20 23:11:33 +02001466 len += 4; // ", "
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001467 }
1468 else
1469 {
1470 int charlen = (*mb_ptr2len)(p);
1471
1472 len += charlen;
1473 p += charlen;
1474 }
1475 }
1476
1477 buf = alloc(len + 1);
1478 if (buf == NULL)
1479 {
1480 *lenp = 0;
1481 return buf;
1482 }
1483
1484 p = arg;
1485 q = buf;
1486 *q++ = '"';
1487 while (*p)
1488 {
1489 if (p[0] == '\\' && p[1] == '\\')
1490 {
1491 *q++ = '\\';
1492 *q++ = '\\';
1493 p += 2;
1494 }
1495 else if (p[0] == '\\' && VIM_ISWHITE(p[1]))
1496 {
1497 *q++ = p[1];
1498 p += 2;
1499 }
1500 else if (*p == '\\' || *p == '"')
1501 {
1502 *q++ = '\\';
1503 *q++ = *p++;
1504 }
1505 else if (VIM_ISWHITE(*p))
1506 {
1507 p = skipwhite(p);
1508 if (*p == NUL)
1509 break;
1510 *q++ = '"';
1511 *q++ = ',';
Bram Moolenaar20d89e02020-10-20 23:11:33 +02001512 *q++ = ' ';
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001513 *q++ = '"';
1514 }
1515 else
1516 {
1517 MB_COPY_CHAR(p, q);
1518 }
1519 }
1520 *q++ = '"';
John Marriott9e795852024-08-20 20:57:23 +02001521 *q = NUL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001522
1523 *lenp = len;
1524 return buf;
1525}
1526
1527 static size_t
John Marriott9e795852024-08-20 20:57:23 +02001528add_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 +02001529{
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001530 if (buf != NULL)
1531 {
1532 if (*multi_mods)
John Marriott9e795852024-08-20 20:57:23 +02001533 {
1534 STRCPY(buf + buflen, " "); // the separating space
1535 ++buflen;
1536 }
1537 STRCPY(buf + buflen, mod_str);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001538 }
1539
John Marriott9e795852024-08-20 20:57:23 +02001540 if (*multi_mods)
1541 ++mod_strlen; // +1 for the separating space
1542 else
1543 *multi_mods = 1;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001544
John Marriott9e795852024-08-20 20:57:23 +02001545 return mod_strlen;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001546}
1547
1548/*
Bram Moolenaar02194d22020-10-24 23:08:38 +02001549 * Add modifiers from "cmod->cmod_split" to "buf". Set "multi_mods" when one
Bram Moolenaare1004402020-10-24 20:49:43 +02001550 * was added. Return the number of bytes added.
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001551 */
1552 size_t
dundargocc57b5bc2022-11-02 13:30:51 +00001553add_win_cmd_modifiers(char_u *buf, cmdmod_T *cmod, int *multi_mods)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001554{
John Marriott9e795852024-08-20 20:57:23 +02001555 size_t buflen = 0;
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001556
1557 // :aboveleft and :leftabove
Bram Moolenaar02194d22020-10-24 23:08:38 +02001558 if (cmod->cmod_split & WSP_ABOVE)
John Marriott9e795852024-08-20 20:57:23 +02001559 buflen += add_cmd_modifier(buf, buflen, "aboveleft", STRLEN_LITERAL("aboveleft"), multi_mods);
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001560 // :belowright and :rightbelow
Bram Moolenaar02194d22020-10-24 23:08:38 +02001561 if (cmod->cmod_split & WSP_BELOW)
John Marriott9e795852024-08-20 20:57:23 +02001562 buflen += add_cmd_modifier(buf, buflen, "belowright", STRLEN_LITERAL("belowright"), multi_mods);
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001563 // :botright
Bram Moolenaar02194d22020-10-24 23:08:38 +02001564 if (cmod->cmod_split & WSP_BOT)
John Marriott9e795852024-08-20 20:57:23 +02001565 buflen += add_cmd_modifier(buf, buflen, "botright", STRLEN_LITERAL("botright"), multi_mods);
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001566
1567 // :tab
Bram Moolenaar02194d22020-10-24 23:08:38 +02001568 if (cmod->cmod_tab > 0)
zeertzjq208567e2022-10-18 13:11:21 +01001569 {
1570 int tabnr = cmod->cmod_tab - 1;
1571
1572 if (tabnr == tabpage_index(curtab))
1573 {
1574 // For compatibility, don't add a tabpage number if it is the same
1575 // as the default number for :tab.
John Marriott9e795852024-08-20 20:57:23 +02001576 buflen += add_cmd_modifier(buf, buflen, "tab", STRLEN_LITERAL("tab"), multi_mods);
zeertzjq208567e2022-10-18 13:11:21 +01001577 }
1578 else
1579 {
1580 char tab_buf[NUMBUFLEN + 3];
John Marriott9e795852024-08-20 20:57:23 +02001581 size_t tab_buflen;
zeertzjq208567e2022-10-18 13:11:21 +01001582
John Marriott9e795852024-08-20 20:57:23 +02001583 tab_buflen = vim_snprintf(tab_buf, sizeof(tab_buf), "%dtab", tabnr);
1584 buflen += add_cmd_modifier(buf, buflen, tab_buf, tab_buflen, multi_mods);
zeertzjq208567e2022-10-18 13:11:21 +01001585 }
1586 }
1587
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001588 // :topleft
Bram Moolenaar02194d22020-10-24 23:08:38 +02001589 if (cmod->cmod_split & WSP_TOP)
John Marriott9e795852024-08-20 20:57:23 +02001590 buflen += add_cmd_modifier(buf, buflen, "topleft", STRLEN_LITERAL("topleft"), multi_mods);
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001591 // :vertical
Bram Moolenaar02194d22020-10-24 23:08:38 +02001592 if (cmod->cmod_split & WSP_VERT)
John Marriott9e795852024-08-20 20:57:23 +02001593 buflen += add_cmd_modifier(buf, buflen, "vertical", STRLEN_LITERAL("vertical"), multi_mods);
zeertzjqd3de1782022-09-01 12:58:52 +01001594 // :horizontal
1595 if (cmod->cmod_split & WSP_HOR)
John Marriott9e795852024-08-20 20:57:23 +02001596 buflen += add_cmd_modifier(buf, buflen, "horizontal", STRLEN_LITERAL("horizontal"), multi_mods);
1597
1598 return buflen;
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001599}
1600
1601/*
Bram Moolenaar02194d22020-10-24 23:08:38 +02001602 * Generate text for the "cmod" command modifiers.
1603 * If "buf" is NULL just return the length.
1604 */
Bram Moolenaara360dbe2020-10-26 18:46:53 +01001605 size_t
Bram Moolenaar02194d22020-10-24 23:08:38 +02001606produce_cmdmods(char_u *buf, cmdmod_T *cmod, int quote)
1607{
John Marriott9e795852024-08-20 20:57:23 +02001608 size_t buflen = 0;
Bram Moolenaar02194d22020-10-24 23:08:38 +02001609 int multi_mods = 0;
1610 int i;
John Marriott9e795852024-08-20 20:57:23 +02001611 static keyvalue_T mod_entry_tab[] =
1612 {
Bram Moolenaar02194d22020-10-24 23:08:38 +02001613#ifdef FEAT_BROWSE_CMD
John Marriott9e795852024-08-20 20:57:23 +02001614 KEYVALUE_ENTRY(CMOD_BROWSE, "browse"),
Bram Moolenaar02194d22020-10-24 23:08:38 +02001615#endif
1616#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
John Marriott9e795852024-08-20 20:57:23 +02001617 KEYVALUE_ENTRY(CMOD_CONFIRM, "confirm"),
Bram Moolenaar02194d22020-10-24 23:08:38 +02001618#endif
John Marriott9e795852024-08-20 20:57:23 +02001619 KEYVALUE_ENTRY(CMOD_HIDE, "hide"),
1620 KEYVALUE_ENTRY(CMOD_KEEPALT, "keepalt"),
1621 KEYVALUE_ENTRY(CMOD_KEEPJUMPS, "keepjumps"),
1622 KEYVALUE_ENTRY(CMOD_KEEPMARKS, "keepmarks"),
1623 KEYVALUE_ENTRY(CMOD_KEEPPATTERNS, "keeppatterns"),
1624 KEYVALUE_ENTRY(CMOD_LOCKMARKS, "lockmarks"),
1625 KEYVALUE_ENTRY(CMOD_NOSWAPFILE, "noswapfile"),
1626 KEYVALUE_ENTRY(CMOD_UNSILENT, "unsilent"),
1627 KEYVALUE_ENTRY(CMOD_NOAUTOCMD, "noautocmd"),
Bram Moolenaar02194d22020-10-24 23:08:38 +02001628#ifdef HAVE_SANDBOX
John Marriott9e795852024-08-20 20:57:23 +02001629 KEYVALUE_ENTRY(CMOD_SANDBOX, "sandbox"),
Bram Moolenaar02194d22020-10-24 23:08:38 +02001630#endif
John Marriott9e795852024-08-20 20:57:23 +02001631 KEYVALUE_ENTRY(CMOD_LEGACY, "legacy")
Bram Moolenaar02194d22020-10-24 23:08:38 +02001632 };
1633
John Marriott9e795852024-08-20 20:57:23 +02001634 if (quote)
Bram Moolenaar02194d22020-10-24 23:08:38 +02001635 {
John Marriott9e795852024-08-20 20:57:23 +02001636 ++buflen;
1637 if (buf != NULL)
1638 {
1639 *buf = '"';
1640 *(buf + buflen) = NUL;
1641 }
Bram Moolenaar02194d22020-10-24 23:08:38 +02001642 }
John Marriott9e795852024-08-20 20:57:23 +02001643 else
1644 if (buf != NULL)
1645 *buf = NUL;
Bram Moolenaar02194d22020-10-24 23:08:38 +02001646
1647 // the modifiers that are simple flags
John Marriott9e795852024-08-20 20:57:23 +02001648 for (i = 0; i < (int)ARRAY_LENGTH(mod_entry_tab); ++i)
1649 if (cmod->cmod_flags & mod_entry_tab[i].key)
John Marriott8d4477e2024-11-02 15:59:01 +01001650 buflen += add_cmd_modifier(buf, buflen,
1651 (char *)mod_entry_tab[i].value.string,
1652 mod_entry_tab[i].value.length, &multi_mods);
Bram Moolenaar02194d22020-10-24 23:08:38 +02001653
1654 // :silent
1655 if (cmod->cmod_flags & CMOD_SILENT)
John Marriott9e795852024-08-20 20:57:23 +02001656 {
1657 if (cmod->cmod_flags & CMOD_ERRSILENT)
John Marriott8d4477e2024-11-02 15:59:01 +01001658 buflen += add_cmd_modifier(buf, buflen, "silent!",
1659 STRLEN_LITERAL("silent!"), &multi_mods);
John Marriott9e795852024-08-20 20:57:23 +02001660 else
John Marriott8d4477e2024-11-02 15:59:01 +01001661 buflen += add_cmd_modifier(buf, buflen, "silent",
1662 STRLEN_LITERAL("silent"), &multi_mods);
John Marriott9e795852024-08-20 20:57:23 +02001663 }
1664
Bram Moolenaar02194d22020-10-24 23:08:38 +02001665 // :verbose
zeertzjq9359e8a2022-07-03 13:16:09 +01001666 if (cmod->cmod_verbose > 0)
1667 {
1668 int verbose_value = cmod->cmod_verbose - 1;
1669
1670 if (verbose_value == 1)
John Marriott9e795852024-08-20 20:57:23 +02001671 buflen += add_cmd_modifier(buf, buflen, "verbose", STRLEN_LITERAL("verbose"), &multi_mods);
zeertzjq9359e8a2022-07-03 13:16:09 +01001672 else
1673 {
1674 char verbose_buf[NUMBUFLEN];
John Marriott9e795852024-08-20 20:57:23 +02001675 size_t verbose_buflen;
zeertzjq9359e8a2022-07-03 13:16:09 +01001676
John Marriott9e795852024-08-20 20:57:23 +02001677 verbose_buflen = vim_snprintf(verbose_buf, sizeof(verbose_buf), "%dverbose", verbose_value);
1678 buflen += add_cmd_modifier(buf, buflen, verbose_buf, verbose_buflen, &multi_mods);
zeertzjq9359e8a2022-07-03 13:16:09 +01001679 }
1680 }
zeertzjq9359e8a2022-07-03 13:16:09 +01001681
John Marriott9e795852024-08-20 20:57:23 +02001682 // flags from cmod->cmod_split
1683 buflen += add_win_cmd_modifiers((buf == NULL) ? NULL : buf + buflen, cmod, &multi_mods);
1684
1685 if (quote)
Bram Moolenaar02194d22020-10-24 23:08:38 +02001686 {
John Marriott9e795852024-08-20 20:57:23 +02001687 if (buf == NULL)
1688 ++buflen;
1689 else
1690 {
1691 *(buf + buflen) = '"';
1692 ++buflen;
1693 *(buf + buflen) = NUL;
1694 }
Bram Moolenaar02194d22020-10-24 23:08:38 +02001695 }
John Marriott9e795852024-08-20 20:57:23 +02001696
1697 return buflen;
Bram Moolenaar02194d22020-10-24 23:08:38 +02001698}
1699
1700/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001701 * Check for a <> code in a user command.
1702 * "code" points to the '<'. "len" the length of the <> (inclusive).
1703 * "buf" is where the result is to be added.
1704 * "split_buf" points to a buffer used for splitting, caller should free it.
1705 * "split_len" is the length of what "split_buf" contains.
1706 * Returns the length of the replacement, which has been added to "buf".
1707 * Returns -1 if there was no match, and only the "<" has been copied.
1708 */
1709 static size_t
1710uc_check_code(
1711 char_u *code,
1712 size_t len,
1713 char_u *buf,
1714 ucmd_T *cmd, // the user command we're expanding
1715 exarg_T *eap, // ex arguments
1716 char_u **split_buf,
1717 size_t *split_len)
1718{
1719 size_t result = 0;
1720 char_u *p = code + 1;
1721 size_t l = len - 2;
1722 int quote = 0;
1723 enum {
1724 ct_ARGS,
1725 ct_BANG,
1726 ct_COUNT,
1727 ct_LINE1,
1728 ct_LINE2,
1729 ct_RANGE,
1730 ct_MODS,
1731 ct_REGISTER,
1732 ct_LT,
1733 ct_NONE
1734 } type = ct_NONE;
1735
1736 if ((vim_strchr((char_u *)"qQfF", *p) != NULL) && p[1] == '-')
1737 {
1738 quote = (*p == 'q' || *p == 'Q') ? 1 : 2;
1739 p += 2;
1740 l -= 2;
1741 }
1742
1743 ++l;
1744 if (l <= 1)
1745 type = ct_NONE;
1746 else if (STRNICMP(p, "args>", l) == 0)
1747 type = ct_ARGS;
1748 else if (STRNICMP(p, "bang>", l) == 0)
1749 type = ct_BANG;
1750 else if (STRNICMP(p, "count>", l) == 0)
1751 type = ct_COUNT;
1752 else if (STRNICMP(p, "line1>", l) == 0)
1753 type = ct_LINE1;
1754 else if (STRNICMP(p, "line2>", l) == 0)
1755 type = ct_LINE2;
1756 else if (STRNICMP(p, "range>", l) == 0)
1757 type = ct_RANGE;
1758 else if (STRNICMP(p, "lt>", l) == 0)
1759 type = ct_LT;
1760 else if (STRNICMP(p, "reg>", l) == 0 || STRNICMP(p, "register>", l) == 0)
1761 type = ct_REGISTER;
1762 else if (STRNICMP(p, "mods>", l) == 0)
1763 type = ct_MODS;
1764
1765 switch (type)
1766 {
1767 case ct_ARGS:
1768 // Simple case first
1769 if (*eap->arg == NUL)
1770 {
1771 if (quote == 1)
1772 {
1773 result = 2;
1774 if (buf != NULL)
1775 STRCPY(buf, "''");
1776 }
1777 else
1778 result = 0;
1779 break;
1780 }
1781
1782 // When specified there is a single argument don't split it.
1783 // Works for ":Cmd %" when % is "a b c".
Bram Moolenaar8071cb22019-07-12 17:58:01 +02001784 if ((eap->argt & EX_NOSPC) && quote == 2)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001785 quote = 1;
1786
1787 switch (quote)
1788 {
1789 case 0: // No quoting, no splitting
1790 result = STRLEN(eap->arg);
1791 if (buf != NULL)
1792 STRCPY(buf, eap->arg);
1793 break;
1794 case 1: // Quote, but don't split
1795 result = STRLEN(eap->arg) + 2;
1796 for (p = eap->arg; *p; ++p)
1797 {
1798 if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2)
1799 // DBCS can contain \ in a trail byte, skip the
1800 // double-byte character.
1801 ++p;
1802 else
1803 if (*p == '\\' || *p == '"')
1804 ++result;
1805 }
1806
1807 if (buf != NULL)
1808 {
1809 *buf++ = '"';
1810 for (p = eap->arg; *p; ++p)
1811 {
1812 if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2)
1813 // DBCS can contain \ in a trail byte, copy the
1814 // double-byte character to avoid escaping.
1815 *buf++ = *p++;
1816 else
1817 if (*p == '\\' || *p == '"')
1818 *buf++ = '\\';
1819 *buf++ = *p;
1820 }
1821 *buf = '"';
1822 }
1823
1824 break;
1825 case 2: // Quote and split (<f-args>)
1826 // This is hard, so only do it once, and cache the result
1827 if (*split_buf == NULL)
1828 *split_buf = uc_split_args(eap->arg, split_len);
1829
1830 result = *split_len;
1831 if (buf != NULL && result != 0)
1832 STRCPY(buf, *split_buf);
1833
1834 break;
1835 }
1836 break;
1837
1838 case ct_BANG:
1839 result = eap->forceit ? 1 : 0;
1840 if (quote)
1841 result += 2;
1842 if (buf != NULL)
1843 {
1844 if (quote)
1845 *buf++ = '"';
1846 if (eap->forceit)
1847 *buf++ = '!';
1848 if (quote)
1849 *buf = '"';
1850 }
1851 break;
1852
1853 case ct_LINE1:
1854 case ct_LINE2:
1855 case ct_RANGE:
1856 case ct_COUNT:
1857 {
John Marriott9e795852024-08-20 20:57:23 +02001858 char num_buf[NUMBUFLEN];
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001859 long num = (type == ct_LINE1) ? eap->line1 :
1860 (type == ct_LINE2) ? eap->line2 :
1861 (type == ct_RANGE) ? eap->addr_count :
1862 (eap->addr_count > 0) ? eap->line2 : cmd->uc_def;
1863 size_t num_len;
1864
John Marriott9e795852024-08-20 20:57:23 +02001865 num_len = vim_snprintf(num_buf, sizeof(num_buf), "%ld", num);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001866 result = num_len;
1867
1868 if (quote)
1869 result += 2;
1870
1871 if (buf != NULL)
1872 {
1873 if (quote)
1874 *buf++ = '"';
1875 STRCPY(buf, num_buf);
1876 buf += num_len;
1877 if (quote)
1878 *buf = '"';
1879 }
1880
1881 break;
1882 }
1883
1884 case ct_MODS:
1885 {
Bram Moolenaar02194d22020-10-24 23:08:38 +02001886 result = produce_cmdmods(buf, &cmdmod, quote);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001887 break;
1888 }
1889
1890 case ct_REGISTER:
1891 result = eap->regname ? 1 : 0;
1892 if (quote)
1893 result += 2;
1894 if (buf != NULL)
1895 {
1896 if (quote)
1897 *buf++ = '\'';
1898 if (eap->regname)
1899 *buf++ = eap->regname;
1900 if (quote)
1901 *buf = '\'';
1902 }
1903 break;
1904
1905 case ct_LT:
1906 result = 1;
1907 if (buf != NULL)
1908 *buf = '<';
1909 break;
1910
1911 default:
1912 // Not recognized: just copy the '<' and return -1.
1913 result = (size_t)-1;
1914 if (buf != NULL)
1915 *buf = '<';
1916 break;
1917 }
1918
1919 return result;
1920}
1921
1922/*
1923 * Execute a user defined command.
1924 */
1925 void
1926do_ucmd(exarg_T *eap)
1927{
1928 char_u *buf;
1929 char_u *p;
1930 char_u *q;
1931
1932 char_u *start;
1933 char_u *end = NULL;
1934 char_u *ksp;
1935 size_t len, totlen;
1936
1937 size_t split_len = 0;
1938 char_u *split_buf = NULL;
1939 ucmd_T *cmd;
Bram Moolenaar205f29c2021-12-10 21:46:09 +00001940 sctx_T save_current_sctx;
1941 int restore_current_sctx = FALSE;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00001942#ifdef FEAT_EVAL
1943 int restore_script_version = 0;
1944#endif
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001945
1946 if (eap->cmdidx == CMD_USER)
1947 cmd = USER_CMD(eap->useridx);
1948 else
zeertzjqb444ee72023-02-20 15:25:13 +00001949 cmd = USER_CMD_GA(&prevwin_curwin()->w_buffer->b_ucmds, eap->useridx);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001950
1951 /*
1952 * Replace <> in the command by the arguments.
1953 * First round: "buf" is NULL, compute length, allocate "buf".
1954 * Second round: copy result into "buf".
1955 */
1956 buf = NULL;
1957 for (;;)
1958 {
1959 p = cmd->uc_rep; // source
1960 q = buf; // destination
1961 totlen = 0;
1962
1963 for (;;)
1964 {
1965 start = vim_strchr(p, '<');
1966 if (start != NULL)
1967 end = vim_strchr(start + 1, '>');
1968 if (buf != NULL)
1969 {
1970 for (ksp = p; *ksp != NUL && *ksp != K_SPECIAL; ++ksp)
1971 ;
1972 if (*ksp == K_SPECIAL
1973 && (start == NULL || ksp < start || end == NULL)
1974 && ((ksp[1] == KS_SPECIAL && ksp[2] == KE_FILLER)
1975# ifdef FEAT_GUI
1976 || (ksp[1] == KS_EXTRA && ksp[2] == (int)KE_CSI)
1977# endif
1978 ))
1979 {
1980 // K_SPECIAL has been put in the buffer as K_SPECIAL
1981 // KS_SPECIAL KE_FILLER, like for mappings, but
1982 // do_cmdline() doesn't handle that, so convert it back.
1983 // Also change K_SPECIAL KS_EXTRA KE_CSI into CSI.
1984 len = ksp - p;
1985 if (len > 0)
1986 {
1987 mch_memmove(q, p, len);
1988 q += len;
1989 }
1990 *q++ = ksp[1] == KS_SPECIAL ? K_SPECIAL : CSI;
1991 p = ksp + 3;
1992 continue;
1993 }
1994 }
1995
1996 // break if no <item> is found
1997 if (start == NULL || end == NULL)
1998 break;
1999
2000 // Include the '>'
2001 ++end;
2002
2003 // Take everything up to the '<'
2004 len = start - p;
2005 if (buf == NULL)
2006 totlen += len;
2007 else
2008 {
2009 mch_memmove(q, p, len);
2010 q += len;
2011 }
2012
2013 len = uc_check_code(start, end - start, q, cmd, eap,
2014 &split_buf, &split_len);
2015 if (len == (size_t)-1)
2016 {
2017 // no match, continue after '<'
2018 p = start + 1;
2019 len = 1;
2020 }
2021 else
2022 p = end;
2023 if (buf == NULL)
2024 totlen += len;
2025 else
2026 q += len;
2027 }
2028 if (buf != NULL) // second time here, finished
2029 {
2030 STRCPY(q, p);
2031 break;
2032 }
2033
2034 totlen += STRLEN(p); // Add on the trailing characters
Bram Moolenaar964b3742019-05-24 18:54:09 +02002035 buf = alloc(totlen + 1);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02002036 if (buf == NULL)
2037 {
2038 vim_free(split_buf);
2039 return;
2040 }
2041 }
2042
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00002043 if ((cmd->uc_argt & EX_KEEPSCRIPT) == 0)
2044 {
Bram Moolenaar205f29c2021-12-10 21:46:09 +00002045 restore_current_sctx = TRUE;
2046 save_current_sctx = current_sctx;
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00002047 current_sctx.sc_version = cmd->uc_script_ctx.sc_version;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02002048#ifdef FEAT_EVAL
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00002049 current_sctx.sc_sid = cmd->uc_script_ctx.sc_sid;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00002050 if (cmd->uc_flags & UC_VIM9)
2051 {
2052 // In a {} block variables use Vim9 script rules, even in a legacy
2053 // script.
2054 restore_script_version =
2055 SCRIPT_ITEM(current_sctx.sc_sid)->sn_version;
2056 SCRIPT_ITEM(current_sctx.sc_sid)->sn_version = SCRIPT_VERSION_VIM9;
2057 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02002058#endif
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00002059 }
Bram Moolenaar205f29c2021-12-10 21:46:09 +00002060
Zoltan Arpadffy6fdb6282023-12-19 20:53:07 +01002061 (void)do_cmdline(buf, eap->ea_getline, eap->cookie,
Bram Moolenaarac9fb182019-04-27 13:04:13 +02002062 DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED);
Bram Moolenaar205f29c2021-12-10 21:46:09 +00002063
2064 // Careful: Do not use "cmd" here, it may have become invalid if a user
2065 // command was added.
2066 if (restore_current_sctx)
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00002067 {
2068#ifdef FEAT_EVAL
2069 if (restore_script_version != 0)
2070 SCRIPT_ITEM(current_sctx.sc_sid)->sn_version =
2071 restore_script_version;
2072#endif
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00002073 current_sctx = save_current_sctx;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00002074 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02002075 vim_free(buf);
2076 vim_free(split_buf);
2077}