blob: fafa97d15f248e7561e2b5d458e01a19ec809ad1 [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 }
603 while (len-- > 0)
604 msg_putchar(' ');
605
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
609 do {
610 msg_putchar(' ');
611 ++len;
612 } while (len < 22);
613
614 // "over" is how much longer the name is than the column width for
615 // the name, we'll try to align what comes after.
616 over = len - 22;
617 len = 0;
618
619 // Arguments
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200620 switch ((int)(a & (EX_EXTRA|EX_NOSPC|EX_NEEDARG)))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200621 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200622 case 0: IObuff[len++] = '0'; break;
623 case (EX_EXTRA): IObuff[len++] = '*'; break;
624 case (EX_EXTRA|EX_NOSPC): IObuff[len++] = '?'; break;
625 case (EX_EXTRA|EX_NEEDARG): IObuff[len++] = '+'; break;
626 case (EX_EXTRA|EX_NOSPC|EX_NEEDARG): IObuff[len++] = '1'; break;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200627 }
628
629 do {
630 IObuff[len++] = ' ';
631 } while (len < 5 - over);
632
633 // Address / Range
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200634 if (a & (EX_RANGE|EX_COUNT))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200635 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200636 if (a & EX_COUNT)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200637 {
638 // -count=N
John Marriott9e795852024-08-20 20:57:23 +0200639 len += vim_snprintf((char *)IObuff + len, IOSIZE - len, "%ldc", cmd->uc_def);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200640 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200641 else if (a & EX_DFLALL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200642 IObuff[len++] = '%';
643 else if (cmd->uc_def >= 0)
644 {
645 // -range=N
John Marriott9e795852024-08-20 20:57:23 +0200646 len += vim_snprintf((char *)IObuff + len, IOSIZE - len, "%ld", cmd->uc_def);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200647 }
648 else
649 IObuff[len++] = '.';
650 }
651
652 do {
653 IObuff[len++] = ' ';
654 } while (len < 8 - over);
655
656 // Address Type
John Marriott9e795852024-08-20 20:57:23 +0200657 for (j = 0; j < (int)ARRAY_LENGTH(addr_type_complete_tab); ++j)
658 if (addr_type_complete_tab[j].key != ADDR_LINES
659 && addr_type_complete_tab[j].key == cmd->uc_addr_type)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200660 {
John Marriott9e795852024-08-20 20:57:23 +0200661 STRCPY(IObuff + len, addr_type_complete_tab[j].shortname);
662 len += (int)addr_type_complete_tab[j].shortnamelen;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200663 break;
664 }
665
666 do {
667 IObuff[len++] = ' ';
668 } while (len < 13 - over);
669
670 // Completion
John Marriott9e795852024-08-20 20:57:23 +0200671 entry = get_commandtype(cmd->uc_compl);
672 if (entry != NULL)
673 {
John Marriott8d4477e2024-11-02 15:59:01 +0100674 STRCPY(IObuff + len, entry->value.string);
Yegappan Lakshmanan084529c2024-12-24 09:50:01 +0100675 len += (int)entry->value.length;
Bram Moolenaar78f60322022-01-17 22:16:33 +0000676#ifdef FEAT_EVAL
John Marriott9e795852024-08-20 20:57:23 +0200677 if (p_verbose > 0 && cmd->uc_compl_arg != NULL)
678 {
679 size_t uc_compl_arglen = STRLEN(cmd->uc_compl_arg);
680
681 if (uc_compl_arglen < 200)
Bram Moolenaar3f3597b2022-01-17 19:06:56 +0000682 {
John Marriott9e795852024-08-20 20:57:23 +0200683 IObuff[len++] = ',';
684 STRCPY(IObuff + len, cmd->uc_compl_arg);
Yegappan Lakshmanan084529c2024-12-24 09:50:01 +0100685 len += (int)uc_compl_arglen;
Bram Moolenaar3f3597b2022-01-17 19:06:56 +0000686 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200687 }
John Marriott9e795852024-08-20 20:57:23 +0200688#endif
689 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200690
691 do {
692 IObuff[len++] = ' ';
693 } while (len < 25 - over);
694
John Marriott9e795852024-08-20 20:57:23 +0200695 IObuff[len] = NUL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200696 msg_outtrans(IObuff);
697
698 msg_outtrans_special(cmd->uc_rep, FALSE,
699 name_len == 0 ? Columns - 47 : 0);
700#ifdef FEAT_EVAL
701 if (p_verbose > 0)
702 last_set_msg(cmd->uc_script_ctx);
703#endif
704 out_flush();
705 ui_breakcheck();
706 if (got_int)
707 break;
708 }
709 if (gap == &ucmds || i < gap->ga_len)
710 break;
711 gap = &ucmds;
712 }
713
714 if (!found)
715 msg(_("No user-defined commands found"));
Bram Moolenaarcf2594f2022-11-13 23:30:06 +0000716
717 --ucmd_locked;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200718}
719
720 char *
721uc_fun_cmd(void)
722{
723 static char_u fcmd[] = {0x84, 0xaf, 0x60, 0xb9, 0xaf, 0xb5, 0x60, 0xa4,
724 0xa5, 0xad, 0xa1, 0xae, 0xa4, 0x60, 0xa1, 0x60,
725 0xb3, 0xa8, 0xb2, 0xb5, 0xa2, 0xa2, 0xa5, 0xb2,
726 0xb9, 0x7f, 0};
727 int i;
728
729 for (i = 0; fcmd[i]; ++i)
730 IObuff[i] = fcmd[i] - 0x40;
John Marriott9e795852024-08-20 20:57:23 +0200731 IObuff[i] = NUL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200732 return (char *)IObuff;
733}
734
735/*
736 * Parse address type argument
737 */
738 static int
739parse_addr_type_arg(
740 char_u *value,
741 int vallen,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200742 cmd_addr_T *addr_type_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200743{
John Marriott9e795852024-08-20 20:57:23 +0200744 addrtype_T target;
745 addrtype_T *entry;
746 static addrtype_T *last_entry; // cached result
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200747
John Marriott9e795852024-08-20 20:57:23 +0200748 target.key = 0;
749 target.fullname = (char *)value;
750 target.fullnamelen = vallen;
751
752 if (last_entry != NULL && cmp_addr_type(&target, last_entry) == 0)
753 entry = last_entry;
754 else
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200755 {
John Marriott9e795852024-08-20 20:57:23 +0200756 entry = (addrtype_T *)bsearch(&target,
757 &addr_type_complete_tab,
758 ARRAY_LENGTH(addr_type_complete_tab),
759 sizeof(addr_type_complete_tab[0]),
760 cmp_addr_type);
761 if (entry == NULL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200762 {
John Marriott9e795852024-08-20 20:57:23 +0200763 int i;
764 char_u *err = value;
765
766 for (i = 0; err[i] != NUL && !VIM_ISWHITE(err[i]); i++)
767 ;
768 err[i] = NUL;
769 semsg(_(e_invalid_address_type_value_str), err);
770 return FAIL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200771 }
John Marriott9e795852024-08-20 20:57:23 +0200772
773 last_entry = entry;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200774 }
775
John Marriott9e795852024-08-20 20:57:23 +0200776 *addr_type_arg = entry->key;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200777
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200778 return OK;
779}
780
John Marriott9e795852024-08-20 20:57:23 +0200781 static int
782cmp_addr_type(const void *a, const void *b)
783{
784 addrtype_T *at1 = (addrtype_T *)a;
785 addrtype_T *at2 = (addrtype_T *)b;
786
787 return STRNCMP(at1->fullname, at2->fullname, MAX(at1->fullnamelen, at2->fullnamelen));
788}
789
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200790/*
791 * Parse a completion argument "value[vallen]".
792 * The detected completion goes in "*complp", argument type in "*argt".
793 * When there is an argument, for function and user defined completion, it's
794 * copied to allocated memory and stored in "*compl_arg".
795 * Returns FAIL if something is wrong.
796 */
797 int
798parse_compl_arg(
799 char_u *value,
800 int vallen,
801 int *complp,
802 long *argt,
803 char_u **compl_arg UNUSED)
804{
805 char_u *arg = NULL;
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200806# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200807 size_t arglen = 0;
808# endif
809 int i;
810 int valend = vallen;
John Marriott9e795852024-08-20 20:57:23 +0200811 keyvalue_T target;
812 keyvalue_T *entry;
813 static keyvalue_T *last_entry = NULL; // cached result
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200814
815 // Look for any argument part - which is the part after any ','
816 for (i = 0; i < vallen; ++i)
817 {
818 if (value[i] == ',')
819 {
820 arg = &value[i + 1];
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200821# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200822 arglen = vallen - i - 1;
823# endif
824 valend = i;
825 break;
826 }
827 }
828
John Marriott9e795852024-08-20 20:57:23 +0200829 target.key = 0;
John Marriott8d4477e2024-11-02 15:59:01 +0100830 target.value.string = value;
831 target.value.length = valend;
John Marriott9e795852024-08-20 20:57:23 +0200832
833 if (last_entry != NULL && cmp_keyvalue_value_n(&target, last_entry) == 0)
834 entry = last_entry;
835 else
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200836 {
John Marriott9e795852024-08-20 20:57:23 +0200837 entry = (keyvalue_T *)bsearch(&target,
838 &command_complete_tab,
839 ARRAY_LENGTH(command_complete_tab),
840 sizeof(command_complete_tab[0]),
841 cmp_keyvalue_value_n);
842 if (entry == NULL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200843 {
John Marriott9e795852024-08-20 20:57:23 +0200844 semsg(_(e_invalid_complete_value_str), value);
845 return FAIL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200846 }
John Marriott9e795852024-08-20 20:57:23 +0200847
848 last_entry = entry;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200849 }
850
John Marriott9e795852024-08-20 20:57:23 +0200851 *complp = entry->key;
852 if (*complp == EXPAND_BUFFERS)
853 *argt |= EX_BUFNAME;
Ruslan Russkikh0407d622024-10-08 22:21:05 +0200854 else if (*complp == EXPAND_DIRECTORIES || *complp == EXPAND_FILES || *complp == EXPAND_SHELLCMDLINE)
John Marriott9e795852024-08-20 20:57:23 +0200855 *argt |= EX_XFILE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200856
John Marriott9e795852024-08-20 20:57:23 +0200857 if (
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200858# if defined(FEAT_EVAL)
John Marriott9e795852024-08-20 20:57:23 +0200859 *complp != EXPAND_USER_DEFINED && *complp != EXPAND_USER_LIST
860 &&
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200861# endif
John Marriott9e795852024-08-20 20:57:23 +0200862 arg != NULL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200863 {
Bram Moolenaarb09feaa2022-01-02 20:20:45 +0000864 emsg(_(e_completion_argument_only_allowed_for_custom_completion));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200865 return FAIL;
866 }
867
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200868# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200869 if ((*complp == EXPAND_USER_DEFINED || *complp == EXPAND_USER_LIST)
870 && arg == NULL)
871 {
Bram Moolenaarb09feaa2022-01-02 20:20:45 +0000872 emsg(_(e_custom_completion_requires_function_argument));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200873 return FAIL;
874 }
875
876 if (arg != NULL)
Bram Moolenaar71ccd032020-06-12 22:59:11 +0200877 *compl_arg = vim_strnsave(arg, arglen);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200878# endif
John Marriott9e795852024-08-20 20:57:23 +0200879
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200880 return OK;
881}
882
883/*
884 * Scan attributes in the ":command" command.
885 * Return FAIL when something is wrong.
886 */
887 static int
888uc_scan_attr(
889 char_u *attr,
890 size_t len,
891 long *argt,
892 long *def,
893 int *flags,
Bram Moolenaar52111f82019-04-29 21:30:45 +0200894 int *complp,
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200895 char_u **compl_arg,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200896 cmd_addr_T *addr_type_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200897{
898 char_u *p;
899
900 if (len == 0)
901 {
Bram Moolenaar1a992222021-12-31 17:25:48 +0000902 emsg(_(e_no_attribute_specified));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200903 return FAIL;
904 }
905
906 // First, try the simple attributes (no arguments)
907 if (STRNICMP(attr, "bang", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200908 *argt |= EX_BANG;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200909 else if (STRNICMP(attr, "buffer", len) == 0)
910 *flags |= UC_BUFFER;
911 else if (STRNICMP(attr, "register", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200912 *argt |= EX_REGSTR;
Bram Moolenaar58ef8a32021-11-12 11:25:11 +0000913 else if (STRNICMP(attr, "keepscript", len) == 0)
914 *argt |= EX_KEEPSCRIPT;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200915 else if (STRNICMP(attr, "bar", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200916 *argt |= EX_TRLBAR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200917 else
918 {
919 int i;
920 char_u *val = NULL;
921 size_t vallen = 0;
922 size_t attrlen = len;
923
924 // Look for the attribute name - which is the part before any '='
925 for (i = 0; i < (int)len; ++i)
926 {
927 if (attr[i] == '=')
928 {
929 val = &attr[i + 1];
930 vallen = len - i - 1;
931 attrlen = i;
932 break;
933 }
934 }
935
936 if (STRNICMP(attr, "nargs", attrlen) == 0)
937 {
938 if (vallen == 1)
939 {
940 if (*val == '0')
941 // Do nothing - this is the default
942 ;
943 else if (*val == '1')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200944 *argt |= (EX_EXTRA | EX_NOSPC | EX_NEEDARG);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200945 else if (*val == '*')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200946 *argt |= EX_EXTRA;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200947 else if (*val == '?')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200948 *argt |= (EX_EXTRA | EX_NOSPC);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200949 else if (*val == '+')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200950 *argt |= (EX_EXTRA | EX_NEEDARG);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200951 else
952 goto wrong_nargs;
953 }
954 else
955 {
956wrong_nargs:
Bram Moolenaar1a992222021-12-31 17:25:48 +0000957 emsg(_(e_invalid_number_of_arguments));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200958 return FAIL;
959 }
960 }
961 else if (STRNICMP(attr, "range", attrlen) == 0)
962 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200963 *argt |= EX_RANGE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200964 if (vallen == 1 && *val == '%')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200965 *argt |= EX_DFLALL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200966 else if (val != NULL)
967 {
968 p = val;
969 if (*def >= 0)
970 {
971two_count:
Bram Moolenaar1a992222021-12-31 17:25:48 +0000972 emsg(_(e_count_cannot_be_specified_twice));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200973 return FAIL;
974 }
975
976 *def = getdigits(&p);
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200977 *argt |= EX_ZEROR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200978
979 if (p != val + vallen || vallen == 0)
980 {
981invalid_count:
Bram Moolenaar1a992222021-12-31 17:25:48 +0000982 emsg(_(e_invalid_default_value_for_count));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200983 return FAIL;
984 }
985 }
Bram Moolenaarb7316892019-05-01 18:08:42 +0200986 // default for -range is using buffer lines
987 if (*addr_type_arg == ADDR_NONE)
988 *addr_type_arg = ADDR_LINES;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200989 }
990 else if (STRNICMP(attr, "count", attrlen) == 0)
991 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200992 *argt |= (EX_COUNT | EX_ZEROR | EX_RANGE);
Bram Moolenaarb7316892019-05-01 18:08:42 +0200993 // default for -count is using any number
994 if (*addr_type_arg == ADDR_NONE)
995 *addr_type_arg = ADDR_OTHER;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200996
997 if (val != NULL)
998 {
999 p = val;
1000 if (*def >= 0)
1001 goto two_count;
1002
1003 *def = getdigits(&p);
1004
1005 if (p != val + vallen)
1006 goto invalid_count;
1007 }
1008
1009 if (*def < 0)
1010 *def = 0;
1011 }
1012 else if (STRNICMP(attr, "complete", attrlen) == 0)
1013 {
1014 if (val == NULL)
1015 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001016 semsg(_(e_argument_required_for_str), "-complete");
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001017 return FAIL;
1018 }
1019
Bram Moolenaar52111f82019-04-29 21:30:45 +02001020 if (parse_compl_arg(val, (int)vallen, complp, argt, compl_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001021 == FAIL)
1022 return FAIL;
1023 }
1024 else if (STRNICMP(attr, "addr", attrlen) == 0)
1025 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +02001026 *argt |= EX_RANGE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001027 if (val == NULL)
1028 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001029 semsg(_(e_argument_required_for_str), "-addr");
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001030 return FAIL;
1031 }
Bram Moolenaare4f5f3a2019-05-04 14:05:08 +02001032 if (parse_addr_type_arg(val, (int)vallen, addr_type_arg) == FAIL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001033 return FAIL;
Bram Moolenaare4f5f3a2019-05-04 14:05:08 +02001034 if (*addr_type_arg != ADDR_LINES)
Bram Moolenaar8071cb22019-07-12 17:58:01 +02001035 *argt |= EX_ZEROR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001036 }
1037 else
1038 {
1039 char_u ch = attr[len];
1040 attr[len] = '\0';
Bram Moolenaar1a992222021-12-31 17:25:48 +00001041 semsg(_(e_invalid_attribute_str), attr);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001042 attr[len] = ch;
1043 return FAIL;
1044 }
1045 }
1046
1047 return OK;
1048}
1049
1050/*
1051 * Add a user command to the list or replace an existing one.
1052 */
1053 static int
1054uc_add_command(
1055 char_u *name,
1056 size_t name_len,
1057 char_u *rep,
1058 long argt,
1059 long def,
1060 int flags,
1061 int compl,
1062 char_u *compl_arg UNUSED,
Bram Moolenaarb7316892019-05-01 18:08:42 +02001063 cmd_addr_T addr_type,
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001064 int force)
1065{
1066 ucmd_T *cmd = NULL;
1067 char_u *p;
1068 int i;
1069 int cmp = 1;
1070 char_u *rep_buf = NULL;
1071 garray_T *gap;
1072
zeertzjq7e0bae02023-08-11 23:15:38 +02001073 replace_termcodes(rep, &rep_buf, 0, 0, NULL);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001074 if (rep_buf == NULL)
1075 {
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001076 // can't replace termcodes - try using the string as is
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001077 rep_buf = vim_strsave(rep);
1078
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001079 // give up if out of memory
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001080 if (rep_buf == NULL)
1081 return FAIL;
1082 }
1083
1084 // get address of growarray: global or in curbuf
1085 if (flags & UC_BUFFER)
1086 {
1087 gap = &curbuf->b_ucmds;
1088 if (gap->ga_itemsize == 0)
Bram Moolenaar04935fb2022-01-08 16:19:22 +00001089 ga_init2(gap, sizeof(ucmd_T), 4);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001090 }
1091 else
1092 gap = &ucmds;
1093
1094 // Search for the command in the already defined commands.
1095 for (i = 0; i < gap->ga_len; ++i)
1096 {
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001097 cmd = USER_CMD_GA(gap, i);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001098 cmp = STRNCMP(name, cmd->uc_name, name_len);
1099 if (cmp == 0)
1100 {
John Marriott9e795852024-08-20 20:57:23 +02001101 if (name_len < cmd->uc_namelen)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001102 cmp = -1;
John Marriott9e795852024-08-20 20:57:23 +02001103 else if (name_len > cmd->uc_namelen)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001104 cmp = 1;
1105 }
1106
1107 if (cmp == 0)
1108 {
1109 // Command can be replaced with "command!" and when sourcing the
1110 // same script again, but only once.
1111 if (!force
1112#ifdef FEAT_EVAL
1113 && (cmd->uc_script_ctx.sc_sid != current_sctx.sc_sid
1114 || cmd->uc_script_ctx.sc_seq == current_sctx.sc_seq)
1115#endif
1116 )
1117 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001118 semsg(_(e_command_already_exists_add_bang_to_replace_it_str),
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001119 name);
1120 goto fail;
1121 }
1122
1123 VIM_CLEAR(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001124#if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001125 VIM_CLEAR(cmd->uc_compl_arg);
1126#endif
1127 break;
1128 }
1129
1130 // Stop as soon as we pass the name to add
1131 if (cmp < 0)
1132 break;
1133 }
1134
1135 // Extend the array unless we're replacing an existing command
1136 if (cmp != 0)
1137 {
Yegappan Lakshmananfadc02a2023-01-27 21:03:12 +00001138 if (ga_grow(gap, 1) == FAIL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001139 goto fail;
Bram Moolenaar71ccd032020-06-12 22:59:11 +02001140 if ((p = vim_strnsave(name, name_len)) == NULL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001141 goto fail;
1142
1143 cmd = USER_CMD_GA(gap, i);
1144 mch_memmove(cmd + 1, cmd, (gap->ga_len - i) * sizeof(ucmd_T));
1145
1146 ++gap->ga_len;
1147
1148 cmd->uc_name = p;
John Marriott9e795852024-08-20 20:57:23 +02001149 cmd->uc_namelen = name_len;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001150 }
1151
1152 cmd->uc_rep = rep_buf;
1153 cmd->uc_argt = argt;
1154 cmd->uc_def = def;
1155 cmd->uc_compl = compl;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001156 cmd->uc_script_ctx = current_sctx;
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001157 if (flags & UC_VIM9)
1158 cmd->uc_script_ctx.sc_version = SCRIPT_VERSION_VIM9;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00001159 cmd->uc_flags = flags & UC_VIM9;
Bram Moolenaar9b8d6222020-12-28 18:26:00 +01001160#ifdef FEAT_EVAL
Bram Moolenaar1a47ae32019-12-29 23:04:25 +01001161 cmd->uc_script_ctx.sc_lnum += SOURCING_LNUM;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001162 cmd->uc_compl_arg = compl_arg;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001163#endif
1164 cmd->uc_addr_type = addr_type;
1165
1166 return OK;
1167
1168fail:
1169 vim_free(rep_buf);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001170#if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001171 vim_free(compl_arg);
1172#endif
1173 return FAIL;
1174}
1175
1176/*
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001177 * If "p" starts with "{" then read a block of commands until "}".
1178 * Used for ":command" and ":autocmd".
1179 */
1180 char_u *
1181may_get_cmd_block(exarg_T *eap, char_u *p, char_u **tofree, int *flags)
1182{
1183 char_u *retp = p;
1184
1185 if (*p == '{' && ends_excmd2(eap->arg, skipwhite(p + 1))
Zoltan Arpadffy6fdb6282023-12-19 20:53:07 +01001186 && eap->ea_getline != NULL)
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001187 {
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001188 garray_T ga;
1189 char_u *line = NULL;
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001190
1191 ga_init2(&ga, sizeof(char_u *), 10);
Bram Moolenaar9f1a39a2022-01-08 15:39:39 +00001192 if (ga_copy_string(&ga, p) == FAIL)
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001193 return retp;
1194
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001195 // If the argument ends in "}" it must have been concatenated already
1196 // for ISN_EXEC.
1197 if (p[STRLEN(p) - 1] != '}')
1198 // Read lines between '{' and '}'. Does not support nesting or
1199 // here-doc constructs.
1200 for (;;)
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001201 {
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001202 vim_free(line);
Zoltan Arpadffy6fdb6282023-12-19 20:53:07 +01001203 if ((line = eap->ea_getline(':', eap->cookie,
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001204 0, GETLINE_CONCAT_CONTBAR)) == NULL)
1205 {
1206 emsg(_(e_missing_rcurly));
1207 break;
1208 }
Bram Moolenaar9f1a39a2022-01-08 15:39:39 +00001209 if (ga_copy_string(&ga, line) == FAIL)
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001210 break;
1211 if (*skipwhite(line) == '}')
1212 break;
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001213 }
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001214 vim_free(line);
1215 retp = *tofree = ga_concat_strings(&ga, "\n");
1216 ga_clear_strings(&ga);
1217 *flags |= UC_VIM9;
1218 }
1219 return retp;
1220}
1221
1222/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001223 * ":command ..." implementation
1224 */
1225 void
1226ex_command(exarg_T *eap)
1227{
Bram Moolenaarb7316892019-05-01 18:08:42 +02001228 char_u *name;
1229 char_u *end;
1230 char_u *p;
1231 long argt = 0;
1232 long def = -1;
1233 int flags = 0;
1234 int compl = EXPAND_NOTHING;
1235 char_u *compl_arg = NULL;
1236 cmd_addr_T addr_type_arg = ADDR_NONE;
1237 int has_attr = (eap->arg[0] == '-');
1238 int name_len;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001239
1240 p = eap->arg;
1241
1242 // Check for attributes
1243 while (*p == '-')
1244 {
1245 ++p;
1246 end = skiptowhite(p);
1247 if (uc_scan_attr(p, end - p, &argt, &def, &flags, &compl,
1248 &compl_arg, &addr_type_arg) == FAIL)
zeertzjq33e54302022-12-19 16:49:27 +00001249 goto theend;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001250 p = skipwhite(end);
1251 }
1252
1253 // Get the name (if any) and skip to the following argument
1254 name = p;
1255 if (ASCII_ISALPHA(*p))
1256 while (ASCII_ISALNUM(*p))
1257 ++p;
Bram Moolenaara72cfb82020-04-23 17:07:30 +02001258 if (!ends_excmd2(eap->arg, p) && !VIM_ISWHITE(*p))
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001259 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001260 emsg(_(e_invalid_command_name));
zeertzjq33e54302022-12-19 16:49:27 +00001261 goto theend;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001262 }
1263 end = p;
1264 name_len = (int)(end - name);
1265
1266 // If there is nothing after the name, and no attributes were specified,
1267 // we are listing commands
1268 p = skipwhite(end);
Bram Moolenaara72cfb82020-04-23 17:07:30 +02001269 if (!has_attr && ends_excmd2(eap->arg, p))
zeertzjq33e54302022-12-19 16:49:27 +00001270 {
John Marriott9e795852024-08-20 20:57:23 +02001271 uc_list(name, name_len);
zeertzjq33e54302022-12-19 16:49:27 +00001272 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001273 else if (!ASCII_ISUPPER(*name))
zeertzjq33e54302022-12-19 16:49:27 +00001274 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001275 emsg(_(e_user_defined_commands_must_start_with_an_uppercase_letter));
zeertzjq33e54302022-12-19 16:49:27 +00001276 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001277 else if ((name_len == 1 && *name == 'X')
1278 || (name_len <= 4
1279 && STRNCMP(name, "Next", name_len > 4 ? 4 : name_len) == 0))
zeertzjq33e54302022-12-19 16:49:27 +00001280 {
Bram Moolenaar9d00e4a2022-01-05 17:49:15 +00001281 emsg(_(e_reserved_name_cannot_be_used_for_user_defined_command));
zeertzjq33e54302022-12-19 16:49:27 +00001282 }
Martin Tournoijde69a732021-07-11 14:28:25 +02001283 else if (compl > 0 && (argt & EX_EXTRA) == 0)
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001284 {
1285 // Some plugins rely on silently ignoring the mistake, only make this
1286 // an error in Vim9 script.
1287 if (in_vim9script())
Bram Moolenaar41a34852021-08-04 16:09:24 +02001288 emsg(_(e_complete_used_without_allowing_arguments));
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001289 else
1290 give_warning_with_source(
Bram Moolenaar41a34852021-08-04 16:09:24 +02001291 (char_u *)_(e_complete_used_without_allowing_arguments),
1292 TRUE, TRUE);
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001293 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001294 else
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001295 {
1296 char_u *tofree = NULL;
1297
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001298 p = may_get_cmd_block(eap, p, &tofree, &flags);
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001299
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001300 uc_add_command(name, end - name, p, argt, def, flags, compl, compl_arg,
1301 addr_type_arg, eap->forceit);
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001302 vim_free(tofree);
zeertzjq33e54302022-12-19 16:49:27 +00001303
1304 return; // success
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001305 }
zeertzjq33e54302022-12-19 16:49:27 +00001306
1307theend:
1308 vim_free(compl_arg);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001309}
1310
1311/*
1312 * ":comclear" implementation
1313 * Clear all user commands, global and for current buffer.
1314 */
1315 void
1316ex_comclear(exarg_T *eap UNUSED)
1317{
1318 uc_clear(&ucmds);
Bram Moolenaare5c83282019-05-03 23:15:37 +02001319 if (curbuf != NULL)
1320 uc_clear(&curbuf->b_ucmds);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001321}
1322
1323/*
Bram Moolenaarcf2594f2022-11-13 23:30:06 +00001324 * If ucmd_locked is set give an error and return TRUE.
1325 * Otherwise return FALSE.
1326 */
1327 static int
1328is_ucmd_locked(void)
1329{
1330 if (ucmd_locked > 0)
1331 {
1332 emsg(_(e_cannot_change_user_commands_while_listing));
1333 return TRUE;
1334 }
1335 return FALSE;
1336}
1337
1338/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001339 * Clear all user commands for "gap".
1340 */
1341 void
1342uc_clear(garray_T *gap)
1343{
1344 int i;
1345 ucmd_T *cmd;
1346
Bram Moolenaarcf2594f2022-11-13 23:30:06 +00001347 if (is_ucmd_locked())
1348 return;
1349
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001350 for (i = 0; i < gap->ga_len; ++i)
1351 {
1352 cmd = USER_CMD_GA(gap, i);
1353 vim_free(cmd->uc_name);
John Marriott9e795852024-08-20 20:57:23 +02001354 cmd->uc_namelen = 0;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001355 vim_free(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001356# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001357 vim_free(cmd->uc_compl_arg);
1358# endif
1359 }
1360 ga_clear(gap);
1361}
1362
1363/*
1364 * ":delcommand" implementation
1365 */
1366 void
1367ex_delcommand(exarg_T *eap)
1368{
1369 int i = 0;
1370 ucmd_T *cmd = NULL;
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001371 int res = -1;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001372 garray_T *gap;
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001373 char_u *arg = eap->arg;
1374 int buffer_only = FALSE;
1375
1376 if (STRNCMP(arg, "-buffer", 7) == 0 && VIM_ISWHITE(arg[7]))
1377 {
1378 buffer_only = TRUE;
1379 arg = skipwhite(arg + 7);
1380 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001381
1382 gap = &curbuf->b_ucmds;
1383 for (;;)
1384 {
1385 for (i = 0; i < gap->ga_len; ++i)
1386 {
1387 cmd = USER_CMD_GA(gap, i);
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001388 res = STRCMP(arg, cmd->uc_name);
1389 if (res <= 0)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001390 break;
1391 }
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001392 if (gap == &ucmds || res == 0 || buffer_only)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001393 break;
1394 gap = &ucmds;
1395 }
1396
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001397 if (res != 0)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001398 {
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001399 semsg(_(buffer_only
1400 ? e_no_such_user_defined_command_in_current_buffer_str
1401 : e_no_such_user_defined_command_str), arg);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001402 return;
1403 }
1404
Bram Moolenaarcf2594f2022-11-13 23:30:06 +00001405 if (is_ucmd_locked())
1406 return;
1407
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001408 vim_free(cmd->uc_name);
1409 vim_free(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001410# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001411 vim_free(cmd->uc_compl_arg);
1412# endif
1413
1414 --gap->ga_len;
1415
1416 if (i < gap->ga_len)
1417 mch_memmove(cmd, cmd + 1, (gap->ga_len - i) * sizeof(ucmd_T));
1418}
1419
1420/*
1421 * Split and quote args for <f-args>.
1422 */
1423 static char_u *
1424uc_split_args(char_u *arg, size_t *lenp)
1425{
1426 char_u *buf;
1427 char_u *p;
1428 char_u *q;
1429 int len;
1430
1431 // Precalculate length
1432 p = arg;
1433 len = 2; // Initial and final quotes
1434
1435 while (*p)
1436 {
1437 if (p[0] == '\\' && p[1] == '\\')
1438 {
1439 len += 2;
1440 p += 2;
1441 }
1442 else if (p[0] == '\\' && VIM_ISWHITE(p[1]))
1443 {
1444 len += 1;
1445 p += 2;
1446 }
1447 else if (*p == '\\' || *p == '"')
1448 {
1449 len += 2;
1450 p += 1;
1451 }
1452 else if (VIM_ISWHITE(*p))
1453 {
1454 p = skipwhite(p);
1455 if (*p == NUL)
1456 break;
Bram Moolenaar20d89e02020-10-20 23:11:33 +02001457 len += 4; // ", "
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001458 }
1459 else
1460 {
1461 int charlen = (*mb_ptr2len)(p);
1462
1463 len += charlen;
1464 p += charlen;
1465 }
1466 }
1467
1468 buf = alloc(len + 1);
1469 if (buf == NULL)
1470 {
1471 *lenp = 0;
1472 return buf;
1473 }
1474
1475 p = arg;
1476 q = buf;
1477 *q++ = '"';
1478 while (*p)
1479 {
1480 if (p[0] == '\\' && p[1] == '\\')
1481 {
1482 *q++ = '\\';
1483 *q++ = '\\';
1484 p += 2;
1485 }
1486 else if (p[0] == '\\' && VIM_ISWHITE(p[1]))
1487 {
1488 *q++ = p[1];
1489 p += 2;
1490 }
1491 else if (*p == '\\' || *p == '"')
1492 {
1493 *q++ = '\\';
1494 *q++ = *p++;
1495 }
1496 else if (VIM_ISWHITE(*p))
1497 {
1498 p = skipwhite(p);
1499 if (*p == NUL)
1500 break;
1501 *q++ = '"';
1502 *q++ = ',';
Bram Moolenaar20d89e02020-10-20 23:11:33 +02001503 *q++ = ' ';
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001504 *q++ = '"';
1505 }
1506 else
1507 {
1508 MB_COPY_CHAR(p, q);
1509 }
1510 }
1511 *q++ = '"';
John Marriott9e795852024-08-20 20:57:23 +02001512 *q = NUL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001513
1514 *lenp = len;
1515 return buf;
1516}
1517
1518 static size_t
John Marriott9e795852024-08-20 20:57:23 +02001519add_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 +02001520{
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001521 if (buf != NULL)
1522 {
1523 if (*multi_mods)
John Marriott9e795852024-08-20 20:57:23 +02001524 {
1525 STRCPY(buf + buflen, " "); // the separating space
1526 ++buflen;
1527 }
1528 STRCPY(buf + buflen, mod_str);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001529 }
1530
John Marriott9e795852024-08-20 20:57:23 +02001531 if (*multi_mods)
1532 ++mod_strlen; // +1 for the separating space
1533 else
1534 *multi_mods = 1;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001535
John Marriott9e795852024-08-20 20:57:23 +02001536 return mod_strlen;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001537}
1538
1539/*
Bram Moolenaar02194d22020-10-24 23:08:38 +02001540 * Add modifiers from "cmod->cmod_split" to "buf". Set "multi_mods" when one
Bram Moolenaare1004402020-10-24 20:49:43 +02001541 * was added. Return the number of bytes added.
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001542 */
1543 size_t
dundargocc57b5bc2022-11-02 13:30:51 +00001544add_win_cmd_modifiers(char_u *buf, cmdmod_T *cmod, int *multi_mods)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001545{
John Marriott9e795852024-08-20 20:57:23 +02001546 size_t buflen = 0;
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001547
1548 // :aboveleft and :leftabove
Bram Moolenaar02194d22020-10-24 23:08:38 +02001549 if (cmod->cmod_split & WSP_ABOVE)
John Marriott9e795852024-08-20 20:57:23 +02001550 buflen += add_cmd_modifier(buf, buflen, "aboveleft", STRLEN_LITERAL("aboveleft"), multi_mods);
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001551 // :belowright and :rightbelow
Bram Moolenaar02194d22020-10-24 23:08:38 +02001552 if (cmod->cmod_split & WSP_BELOW)
John Marriott9e795852024-08-20 20:57:23 +02001553 buflen += add_cmd_modifier(buf, buflen, "belowright", STRLEN_LITERAL("belowright"), multi_mods);
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001554 // :botright
Bram Moolenaar02194d22020-10-24 23:08:38 +02001555 if (cmod->cmod_split & WSP_BOT)
John Marriott9e795852024-08-20 20:57:23 +02001556 buflen += add_cmd_modifier(buf, buflen, "botright", STRLEN_LITERAL("botright"), multi_mods);
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001557
1558 // :tab
Bram Moolenaar02194d22020-10-24 23:08:38 +02001559 if (cmod->cmod_tab > 0)
zeertzjq208567e2022-10-18 13:11:21 +01001560 {
1561 int tabnr = cmod->cmod_tab - 1;
1562
1563 if (tabnr == tabpage_index(curtab))
1564 {
1565 // For compatibility, don't add a tabpage number if it is the same
1566 // as the default number for :tab.
John Marriott9e795852024-08-20 20:57:23 +02001567 buflen += add_cmd_modifier(buf, buflen, "tab", STRLEN_LITERAL("tab"), multi_mods);
zeertzjq208567e2022-10-18 13:11:21 +01001568 }
1569 else
1570 {
1571 char tab_buf[NUMBUFLEN + 3];
John Marriott9e795852024-08-20 20:57:23 +02001572 size_t tab_buflen;
zeertzjq208567e2022-10-18 13:11:21 +01001573
John Marriott9e795852024-08-20 20:57:23 +02001574 tab_buflen = vim_snprintf(tab_buf, sizeof(tab_buf), "%dtab", tabnr);
1575 buflen += add_cmd_modifier(buf, buflen, tab_buf, tab_buflen, multi_mods);
zeertzjq208567e2022-10-18 13:11:21 +01001576 }
1577 }
1578
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001579 // :topleft
Bram Moolenaar02194d22020-10-24 23:08:38 +02001580 if (cmod->cmod_split & WSP_TOP)
John Marriott9e795852024-08-20 20:57:23 +02001581 buflen += add_cmd_modifier(buf, buflen, "topleft", STRLEN_LITERAL("topleft"), multi_mods);
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001582 // :vertical
Bram Moolenaar02194d22020-10-24 23:08:38 +02001583 if (cmod->cmod_split & WSP_VERT)
John Marriott9e795852024-08-20 20:57:23 +02001584 buflen += add_cmd_modifier(buf, buflen, "vertical", STRLEN_LITERAL("vertical"), multi_mods);
zeertzjqd3de1782022-09-01 12:58:52 +01001585 // :horizontal
1586 if (cmod->cmod_split & WSP_HOR)
John Marriott9e795852024-08-20 20:57:23 +02001587 buflen += add_cmd_modifier(buf, buflen, "horizontal", STRLEN_LITERAL("horizontal"), multi_mods);
1588
1589 return buflen;
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001590}
1591
1592/*
Bram Moolenaar02194d22020-10-24 23:08:38 +02001593 * Generate text for the "cmod" command modifiers.
1594 * If "buf" is NULL just return the length.
1595 */
Bram Moolenaara360dbe2020-10-26 18:46:53 +01001596 size_t
Bram Moolenaar02194d22020-10-24 23:08:38 +02001597produce_cmdmods(char_u *buf, cmdmod_T *cmod, int quote)
1598{
John Marriott9e795852024-08-20 20:57:23 +02001599 size_t buflen = 0;
Bram Moolenaar02194d22020-10-24 23:08:38 +02001600 int multi_mods = 0;
1601 int i;
John Marriott9e795852024-08-20 20:57:23 +02001602 static keyvalue_T mod_entry_tab[] =
1603 {
Bram Moolenaar02194d22020-10-24 23:08:38 +02001604#ifdef FEAT_BROWSE_CMD
John Marriott9e795852024-08-20 20:57:23 +02001605 KEYVALUE_ENTRY(CMOD_BROWSE, "browse"),
Bram Moolenaar02194d22020-10-24 23:08:38 +02001606#endif
1607#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
John Marriott9e795852024-08-20 20:57:23 +02001608 KEYVALUE_ENTRY(CMOD_CONFIRM, "confirm"),
Bram Moolenaar02194d22020-10-24 23:08:38 +02001609#endif
John Marriott9e795852024-08-20 20:57:23 +02001610 KEYVALUE_ENTRY(CMOD_HIDE, "hide"),
1611 KEYVALUE_ENTRY(CMOD_KEEPALT, "keepalt"),
1612 KEYVALUE_ENTRY(CMOD_KEEPJUMPS, "keepjumps"),
1613 KEYVALUE_ENTRY(CMOD_KEEPMARKS, "keepmarks"),
1614 KEYVALUE_ENTRY(CMOD_KEEPPATTERNS, "keeppatterns"),
1615 KEYVALUE_ENTRY(CMOD_LOCKMARKS, "lockmarks"),
1616 KEYVALUE_ENTRY(CMOD_NOSWAPFILE, "noswapfile"),
1617 KEYVALUE_ENTRY(CMOD_UNSILENT, "unsilent"),
1618 KEYVALUE_ENTRY(CMOD_NOAUTOCMD, "noautocmd"),
Bram Moolenaar02194d22020-10-24 23:08:38 +02001619#ifdef HAVE_SANDBOX
John Marriott9e795852024-08-20 20:57:23 +02001620 KEYVALUE_ENTRY(CMOD_SANDBOX, "sandbox"),
Bram Moolenaar02194d22020-10-24 23:08:38 +02001621#endif
John Marriott9e795852024-08-20 20:57:23 +02001622 KEYVALUE_ENTRY(CMOD_LEGACY, "legacy")
Bram Moolenaar02194d22020-10-24 23:08:38 +02001623 };
1624
John Marriott9e795852024-08-20 20:57:23 +02001625 if (quote)
Bram Moolenaar02194d22020-10-24 23:08:38 +02001626 {
John Marriott9e795852024-08-20 20:57:23 +02001627 ++buflen;
1628 if (buf != NULL)
1629 {
1630 *buf = '"';
1631 *(buf + buflen) = NUL;
1632 }
Bram Moolenaar02194d22020-10-24 23:08:38 +02001633 }
John Marriott9e795852024-08-20 20:57:23 +02001634 else
1635 if (buf != NULL)
1636 *buf = NUL;
Bram Moolenaar02194d22020-10-24 23:08:38 +02001637
1638 // the modifiers that are simple flags
John Marriott9e795852024-08-20 20:57:23 +02001639 for (i = 0; i < (int)ARRAY_LENGTH(mod_entry_tab); ++i)
1640 if (cmod->cmod_flags & mod_entry_tab[i].key)
John Marriott8d4477e2024-11-02 15:59:01 +01001641 buflen += add_cmd_modifier(buf, buflen,
1642 (char *)mod_entry_tab[i].value.string,
1643 mod_entry_tab[i].value.length, &multi_mods);
Bram Moolenaar02194d22020-10-24 23:08:38 +02001644
1645 // :silent
1646 if (cmod->cmod_flags & CMOD_SILENT)
John Marriott9e795852024-08-20 20:57:23 +02001647 {
1648 if (cmod->cmod_flags & CMOD_ERRSILENT)
John Marriott8d4477e2024-11-02 15:59:01 +01001649 buflen += add_cmd_modifier(buf, buflen, "silent!",
1650 STRLEN_LITERAL("silent!"), &multi_mods);
John Marriott9e795852024-08-20 20:57:23 +02001651 else
John Marriott8d4477e2024-11-02 15:59:01 +01001652 buflen += add_cmd_modifier(buf, buflen, "silent",
1653 STRLEN_LITERAL("silent"), &multi_mods);
John Marriott9e795852024-08-20 20:57:23 +02001654 }
1655
Bram Moolenaar02194d22020-10-24 23:08:38 +02001656 // :verbose
zeertzjq9359e8a2022-07-03 13:16:09 +01001657 if (cmod->cmod_verbose > 0)
1658 {
1659 int verbose_value = cmod->cmod_verbose - 1;
1660
1661 if (verbose_value == 1)
John Marriott9e795852024-08-20 20:57:23 +02001662 buflen += add_cmd_modifier(buf, buflen, "verbose", STRLEN_LITERAL("verbose"), &multi_mods);
zeertzjq9359e8a2022-07-03 13:16:09 +01001663 else
1664 {
1665 char verbose_buf[NUMBUFLEN];
John Marriott9e795852024-08-20 20:57:23 +02001666 size_t verbose_buflen;
zeertzjq9359e8a2022-07-03 13:16:09 +01001667
John Marriott9e795852024-08-20 20:57:23 +02001668 verbose_buflen = vim_snprintf(verbose_buf, sizeof(verbose_buf), "%dverbose", verbose_value);
1669 buflen += add_cmd_modifier(buf, buflen, verbose_buf, verbose_buflen, &multi_mods);
zeertzjq9359e8a2022-07-03 13:16:09 +01001670 }
1671 }
zeertzjq9359e8a2022-07-03 13:16:09 +01001672
John Marriott9e795852024-08-20 20:57:23 +02001673 // flags from cmod->cmod_split
1674 buflen += add_win_cmd_modifiers((buf == NULL) ? NULL : buf + buflen, cmod, &multi_mods);
1675
1676 if (quote)
Bram Moolenaar02194d22020-10-24 23:08:38 +02001677 {
John Marriott9e795852024-08-20 20:57:23 +02001678 if (buf == NULL)
1679 ++buflen;
1680 else
1681 {
1682 *(buf + buflen) = '"';
1683 ++buflen;
1684 *(buf + buflen) = NUL;
1685 }
Bram Moolenaar02194d22020-10-24 23:08:38 +02001686 }
John Marriott9e795852024-08-20 20:57:23 +02001687
1688 return buflen;
Bram Moolenaar02194d22020-10-24 23:08:38 +02001689}
1690
1691/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001692 * Check for a <> code in a user command.
1693 * "code" points to the '<'. "len" the length of the <> (inclusive).
1694 * "buf" is where the result is to be added.
1695 * "split_buf" points to a buffer used for splitting, caller should free it.
1696 * "split_len" is the length of what "split_buf" contains.
1697 * Returns the length of the replacement, which has been added to "buf".
1698 * Returns -1 if there was no match, and only the "<" has been copied.
1699 */
1700 static size_t
1701uc_check_code(
1702 char_u *code,
1703 size_t len,
1704 char_u *buf,
1705 ucmd_T *cmd, // the user command we're expanding
1706 exarg_T *eap, // ex arguments
1707 char_u **split_buf,
1708 size_t *split_len)
1709{
1710 size_t result = 0;
1711 char_u *p = code + 1;
1712 size_t l = len - 2;
1713 int quote = 0;
1714 enum {
1715 ct_ARGS,
1716 ct_BANG,
1717 ct_COUNT,
1718 ct_LINE1,
1719 ct_LINE2,
1720 ct_RANGE,
1721 ct_MODS,
1722 ct_REGISTER,
1723 ct_LT,
1724 ct_NONE
1725 } type = ct_NONE;
1726
1727 if ((vim_strchr((char_u *)"qQfF", *p) != NULL) && p[1] == '-')
1728 {
1729 quote = (*p == 'q' || *p == 'Q') ? 1 : 2;
1730 p += 2;
1731 l -= 2;
1732 }
1733
1734 ++l;
1735 if (l <= 1)
1736 type = ct_NONE;
1737 else if (STRNICMP(p, "args>", l) == 0)
1738 type = ct_ARGS;
1739 else if (STRNICMP(p, "bang>", l) == 0)
1740 type = ct_BANG;
1741 else if (STRNICMP(p, "count>", l) == 0)
1742 type = ct_COUNT;
1743 else if (STRNICMP(p, "line1>", l) == 0)
1744 type = ct_LINE1;
1745 else if (STRNICMP(p, "line2>", l) == 0)
1746 type = ct_LINE2;
1747 else if (STRNICMP(p, "range>", l) == 0)
1748 type = ct_RANGE;
1749 else if (STRNICMP(p, "lt>", l) == 0)
1750 type = ct_LT;
1751 else if (STRNICMP(p, "reg>", l) == 0 || STRNICMP(p, "register>", l) == 0)
1752 type = ct_REGISTER;
1753 else if (STRNICMP(p, "mods>", l) == 0)
1754 type = ct_MODS;
1755
1756 switch (type)
1757 {
1758 case ct_ARGS:
1759 // Simple case first
1760 if (*eap->arg == NUL)
1761 {
1762 if (quote == 1)
1763 {
1764 result = 2;
1765 if (buf != NULL)
1766 STRCPY(buf, "''");
1767 }
1768 else
1769 result = 0;
1770 break;
1771 }
1772
1773 // When specified there is a single argument don't split it.
1774 // Works for ":Cmd %" when % is "a b c".
Bram Moolenaar8071cb22019-07-12 17:58:01 +02001775 if ((eap->argt & EX_NOSPC) && quote == 2)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001776 quote = 1;
1777
1778 switch (quote)
1779 {
1780 case 0: // No quoting, no splitting
1781 result = STRLEN(eap->arg);
1782 if (buf != NULL)
1783 STRCPY(buf, eap->arg);
1784 break;
1785 case 1: // Quote, but don't split
1786 result = STRLEN(eap->arg) + 2;
1787 for (p = eap->arg; *p; ++p)
1788 {
1789 if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2)
1790 // DBCS can contain \ in a trail byte, skip the
1791 // double-byte character.
1792 ++p;
1793 else
1794 if (*p == '\\' || *p == '"')
1795 ++result;
1796 }
1797
1798 if (buf != NULL)
1799 {
1800 *buf++ = '"';
1801 for (p = eap->arg; *p; ++p)
1802 {
1803 if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2)
1804 // DBCS can contain \ in a trail byte, copy the
1805 // double-byte character to avoid escaping.
1806 *buf++ = *p++;
1807 else
1808 if (*p == '\\' || *p == '"')
1809 *buf++ = '\\';
1810 *buf++ = *p;
1811 }
1812 *buf = '"';
1813 }
1814
1815 break;
1816 case 2: // Quote and split (<f-args>)
1817 // This is hard, so only do it once, and cache the result
1818 if (*split_buf == NULL)
1819 *split_buf = uc_split_args(eap->arg, split_len);
1820
1821 result = *split_len;
1822 if (buf != NULL && result != 0)
1823 STRCPY(buf, *split_buf);
1824
1825 break;
1826 }
1827 break;
1828
1829 case ct_BANG:
1830 result = eap->forceit ? 1 : 0;
1831 if (quote)
1832 result += 2;
1833 if (buf != NULL)
1834 {
1835 if (quote)
1836 *buf++ = '"';
1837 if (eap->forceit)
1838 *buf++ = '!';
1839 if (quote)
1840 *buf = '"';
1841 }
1842 break;
1843
1844 case ct_LINE1:
1845 case ct_LINE2:
1846 case ct_RANGE:
1847 case ct_COUNT:
1848 {
John Marriott9e795852024-08-20 20:57:23 +02001849 char num_buf[NUMBUFLEN];
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001850 long num = (type == ct_LINE1) ? eap->line1 :
1851 (type == ct_LINE2) ? eap->line2 :
1852 (type == ct_RANGE) ? eap->addr_count :
1853 (eap->addr_count > 0) ? eap->line2 : cmd->uc_def;
1854 size_t num_len;
1855
John Marriott9e795852024-08-20 20:57:23 +02001856 num_len = vim_snprintf(num_buf, sizeof(num_buf), "%ld", num);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001857 result = num_len;
1858
1859 if (quote)
1860 result += 2;
1861
1862 if (buf != NULL)
1863 {
1864 if (quote)
1865 *buf++ = '"';
1866 STRCPY(buf, num_buf);
1867 buf += num_len;
1868 if (quote)
1869 *buf = '"';
1870 }
1871
1872 break;
1873 }
1874
1875 case ct_MODS:
1876 {
Bram Moolenaar02194d22020-10-24 23:08:38 +02001877 result = produce_cmdmods(buf, &cmdmod, quote);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001878 break;
1879 }
1880
1881 case ct_REGISTER:
1882 result = eap->regname ? 1 : 0;
1883 if (quote)
1884 result += 2;
1885 if (buf != NULL)
1886 {
1887 if (quote)
1888 *buf++ = '\'';
1889 if (eap->regname)
1890 *buf++ = eap->regname;
1891 if (quote)
1892 *buf = '\'';
1893 }
1894 break;
1895
1896 case ct_LT:
1897 result = 1;
1898 if (buf != NULL)
1899 *buf = '<';
1900 break;
1901
1902 default:
1903 // Not recognized: just copy the '<' and return -1.
1904 result = (size_t)-1;
1905 if (buf != NULL)
1906 *buf = '<';
1907 break;
1908 }
1909
1910 return result;
1911}
1912
1913/*
1914 * Execute a user defined command.
1915 */
1916 void
1917do_ucmd(exarg_T *eap)
1918{
1919 char_u *buf;
1920 char_u *p;
1921 char_u *q;
1922
1923 char_u *start;
1924 char_u *end = NULL;
1925 char_u *ksp;
1926 size_t len, totlen;
1927
1928 size_t split_len = 0;
1929 char_u *split_buf = NULL;
1930 ucmd_T *cmd;
Bram Moolenaar205f29c2021-12-10 21:46:09 +00001931 sctx_T save_current_sctx;
1932 int restore_current_sctx = FALSE;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00001933#ifdef FEAT_EVAL
1934 int restore_script_version = 0;
1935#endif
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001936
1937 if (eap->cmdidx == CMD_USER)
1938 cmd = USER_CMD(eap->useridx);
1939 else
zeertzjqb444ee72023-02-20 15:25:13 +00001940 cmd = USER_CMD_GA(&prevwin_curwin()->w_buffer->b_ucmds, eap->useridx);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001941
1942 /*
1943 * Replace <> in the command by the arguments.
1944 * First round: "buf" is NULL, compute length, allocate "buf".
1945 * Second round: copy result into "buf".
1946 */
1947 buf = NULL;
1948 for (;;)
1949 {
1950 p = cmd->uc_rep; // source
1951 q = buf; // destination
1952 totlen = 0;
1953
1954 for (;;)
1955 {
1956 start = vim_strchr(p, '<');
1957 if (start != NULL)
1958 end = vim_strchr(start + 1, '>');
1959 if (buf != NULL)
1960 {
1961 for (ksp = p; *ksp != NUL && *ksp != K_SPECIAL; ++ksp)
1962 ;
1963 if (*ksp == K_SPECIAL
1964 && (start == NULL || ksp < start || end == NULL)
1965 && ((ksp[1] == KS_SPECIAL && ksp[2] == KE_FILLER)
1966# ifdef FEAT_GUI
1967 || (ksp[1] == KS_EXTRA && ksp[2] == (int)KE_CSI)
1968# endif
1969 ))
1970 {
1971 // K_SPECIAL has been put in the buffer as K_SPECIAL
1972 // KS_SPECIAL KE_FILLER, like for mappings, but
1973 // do_cmdline() doesn't handle that, so convert it back.
1974 // Also change K_SPECIAL KS_EXTRA KE_CSI into CSI.
1975 len = ksp - p;
1976 if (len > 0)
1977 {
1978 mch_memmove(q, p, len);
1979 q += len;
1980 }
1981 *q++ = ksp[1] == KS_SPECIAL ? K_SPECIAL : CSI;
1982 p = ksp + 3;
1983 continue;
1984 }
1985 }
1986
1987 // break if no <item> is found
1988 if (start == NULL || end == NULL)
1989 break;
1990
1991 // Include the '>'
1992 ++end;
1993
1994 // Take everything up to the '<'
1995 len = start - p;
1996 if (buf == NULL)
1997 totlen += len;
1998 else
1999 {
2000 mch_memmove(q, p, len);
2001 q += len;
2002 }
2003
2004 len = uc_check_code(start, end - start, q, cmd, eap,
2005 &split_buf, &split_len);
2006 if (len == (size_t)-1)
2007 {
2008 // no match, continue after '<'
2009 p = start + 1;
2010 len = 1;
2011 }
2012 else
2013 p = end;
2014 if (buf == NULL)
2015 totlen += len;
2016 else
2017 q += len;
2018 }
2019 if (buf != NULL) // second time here, finished
2020 {
2021 STRCPY(q, p);
2022 break;
2023 }
2024
2025 totlen += STRLEN(p); // Add on the trailing characters
Bram Moolenaar964b3742019-05-24 18:54:09 +02002026 buf = alloc(totlen + 1);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02002027 if (buf == NULL)
2028 {
2029 vim_free(split_buf);
2030 return;
2031 }
2032 }
2033
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00002034 if ((cmd->uc_argt & EX_KEEPSCRIPT) == 0)
2035 {
Bram Moolenaar205f29c2021-12-10 21:46:09 +00002036 restore_current_sctx = TRUE;
2037 save_current_sctx = current_sctx;
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00002038 current_sctx.sc_version = cmd->uc_script_ctx.sc_version;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02002039#ifdef FEAT_EVAL
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00002040 current_sctx.sc_sid = cmd->uc_script_ctx.sc_sid;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00002041 if (cmd->uc_flags & UC_VIM9)
2042 {
2043 // In a {} block variables use Vim9 script rules, even in a legacy
2044 // script.
2045 restore_script_version =
2046 SCRIPT_ITEM(current_sctx.sc_sid)->sn_version;
2047 SCRIPT_ITEM(current_sctx.sc_sid)->sn_version = SCRIPT_VERSION_VIM9;
2048 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02002049#endif
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00002050 }
Bram Moolenaar205f29c2021-12-10 21:46:09 +00002051
Zoltan Arpadffy6fdb6282023-12-19 20:53:07 +01002052 (void)do_cmdline(buf, eap->ea_getline, eap->cookie,
Bram Moolenaarac9fb182019-04-27 13:04:13 +02002053 DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED);
Bram Moolenaar205f29c2021-12-10 21:46:09 +00002054
2055 // Careful: Do not use "cmd" here, it may have become invalid if a user
2056 // command was added.
2057 if (restore_current_sctx)
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00002058 {
2059#ifdef FEAT_EVAL
2060 if (restore_script_version != 0)
2061 SCRIPT_ITEM(current_sctx.sc_sid)->sn_version =
2062 restore_script_version;
2063#endif
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00002064 current_sctx = save_current_sctx;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00002065 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02002066 vim_free(buf);
2067 vim_free(split_buf);
2068}