blob: ded11e759b1798876ca7ec8f86601abd1dfc7c84 [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
Hirohito Higashia4a00a72025-05-08 22:58:31 +0200609 do
610 {
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200611 msg_putchar(' ');
612 ++len;
613 } while (len < 22);
614
615 // "over" is how much longer the name is than the column width for
616 // the name, we'll try to align what comes after.
617 over = len - 22;
618 len = 0;
619
620 // Arguments
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200621 switch ((int)(a & (EX_EXTRA|EX_NOSPC|EX_NEEDARG)))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200622 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200623 case 0: IObuff[len++] = '0'; break;
624 case (EX_EXTRA): IObuff[len++] = '*'; break;
625 case (EX_EXTRA|EX_NOSPC): IObuff[len++] = '?'; break;
626 case (EX_EXTRA|EX_NEEDARG): IObuff[len++] = '+'; break;
627 case (EX_EXTRA|EX_NOSPC|EX_NEEDARG): IObuff[len++] = '1'; break;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200628 }
629
Hirohito Higashia4a00a72025-05-08 22:58:31 +0200630 do
631 {
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200632 IObuff[len++] = ' ';
633 } while (len < 5 - over);
634
635 // Address / Range
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200636 if (a & (EX_RANGE|EX_COUNT))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200637 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200638 if (a & EX_COUNT)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200639 {
640 // -count=N
John Marriott9e795852024-08-20 20:57:23 +0200641 len += vim_snprintf((char *)IObuff + len, IOSIZE - len, "%ldc", cmd->uc_def);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200642 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200643 else if (a & EX_DFLALL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200644 IObuff[len++] = '%';
645 else if (cmd->uc_def >= 0)
646 {
647 // -range=N
John Marriott9e795852024-08-20 20:57:23 +0200648 len += vim_snprintf((char *)IObuff + len, IOSIZE - len, "%ld", cmd->uc_def);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200649 }
650 else
651 IObuff[len++] = '.';
652 }
653
Hirohito Higashia4a00a72025-05-08 22:58:31 +0200654 do
655 {
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200656 IObuff[len++] = ' ';
657 } while (len < 8 - over);
658
659 // Address Type
John Marriott9e795852024-08-20 20:57:23 +0200660 for (j = 0; j < (int)ARRAY_LENGTH(addr_type_complete_tab); ++j)
661 if (addr_type_complete_tab[j].key != ADDR_LINES
662 && addr_type_complete_tab[j].key == cmd->uc_addr_type)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200663 {
John Marriott9e795852024-08-20 20:57:23 +0200664 STRCPY(IObuff + len, addr_type_complete_tab[j].shortname);
665 len += (int)addr_type_complete_tab[j].shortnamelen;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200666 break;
667 }
668
Hirohito Higashia4a00a72025-05-08 22:58:31 +0200669 do
670 {
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200671 IObuff[len++] = ' ';
672 } while (len < 13 - over);
673
674 // Completion
John Marriott9e795852024-08-20 20:57:23 +0200675 entry = get_commandtype(cmd->uc_compl);
676 if (entry != NULL)
677 {
John Marriott8d4477e2024-11-02 15:59:01 +0100678 STRCPY(IObuff + len, entry->value.string);
Yegappan Lakshmanan084529c2024-12-24 09:50:01 +0100679 len += (int)entry->value.length;
Bram Moolenaar78f60322022-01-17 22:16:33 +0000680#ifdef FEAT_EVAL
John Marriott9e795852024-08-20 20:57:23 +0200681 if (p_verbose > 0 && cmd->uc_compl_arg != NULL)
682 {
683 size_t uc_compl_arglen = STRLEN(cmd->uc_compl_arg);
684
685 if (uc_compl_arglen < 200)
Bram Moolenaar3f3597b2022-01-17 19:06:56 +0000686 {
John Marriott9e795852024-08-20 20:57:23 +0200687 IObuff[len++] = ',';
688 STRCPY(IObuff + len, cmd->uc_compl_arg);
Yegappan Lakshmanan084529c2024-12-24 09:50:01 +0100689 len += (int)uc_compl_arglen;
Bram Moolenaar3f3597b2022-01-17 19:06:56 +0000690 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200691 }
John Marriott9e795852024-08-20 20:57:23 +0200692#endif
693 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200694
Hirohito Higashia4a00a72025-05-08 22:58:31 +0200695 do
696 {
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200697 IObuff[len++] = ' ';
698 } while (len < 25 - over);
699
John Marriott9e795852024-08-20 20:57:23 +0200700 IObuff[len] = NUL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200701 msg_outtrans(IObuff);
702
703 msg_outtrans_special(cmd->uc_rep, FALSE,
704 name_len == 0 ? Columns - 47 : 0);
705#ifdef FEAT_EVAL
706 if (p_verbose > 0)
707 last_set_msg(cmd->uc_script_ctx);
708#endif
709 out_flush();
710 ui_breakcheck();
711 if (got_int)
712 break;
713 }
714 if (gap == &ucmds || i < gap->ga_len)
715 break;
716 gap = &ucmds;
717 }
718
719 if (!found)
720 msg(_("No user-defined commands found"));
Bram Moolenaarcf2594f2022-11-13 23:30:06 +0000721
722 --ucmd_locked;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200723}
724
725 char *
726uc_fun_cmd(void)
727{
728 static char_u fcmd[] = {0x84, 0xaf, 0x60, 0xb9, 0xaf, 0xb5, 0x60, 0xa4,
729 0xa5, 0xad, 0xa1, 0xae, 0xa4, 0x60, 0xa1, 0x60,
730 0xb3, 0xa8, 0xb2, 0xb5, 0xa2, 0xa2, 0xa5, 0xb2,
731 0xb9, 0x7f, 0};
732 int i;
733
734 for (i = 0; fcmd[i]; ++i)
735 IObuff[i] = fcmd[i] - 0x40;
John Marriott9e795852024-08-20 20:57:23 +0200736 IObuff[i] = NUL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200737 return (char *)IObuff;
738}
739
740/*
741 * Parse address type argument
742 */
743 static int
744parse_addr_type_arg(
745 char_u *value,
746 int vallen,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200747 cmd_addr_T *addr_type_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200748{
John Marriott9e795852024-08-20 20:57:23 +0200749 addrtype_T target;
750 addrtype_T *entry;
751 static addrtype_T *last_entry; // cached result
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200752
John Marriott9e795852024-08-20 20:57:23 +0200753 target.key = 0;
754 target.fullname = (char *)value;
755 target.fullnamelen = vallen;
756
757 if (last_entry != NULL && cmp_addr_type(&target, last_entry) == 0)
758 entry = last_entry;
759 else
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200760 {
John Marriott9e795852024-08-20 20:57:23 +0200761 entry = (addrtype_T *)bsearch(&target,
762 &addr_type_complete_tab,
763 ARRAY_LENGTH(addr_type_complete_tab),
764 sizeof(addr_type_complete_tab[0]),
765 cmp_addr_type);
766 if (entry == NULL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200767 {
John Marriott9e795852024-08-20 20:57:23 +0200768 int i;
769 char_u *err = value;
770
771 for (i = 0; err[i] != NUL && !VIM_ISWHITE(err[i]); i++)
772 ;
773 err[i] = NUL;
774 semsg(_(e_invalid_address_type_value_str), err);
775 return FAIL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200776 }
John Marriott9e795852024-08-20 20:57:23 +0200777
778 last_entry = entry;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200779 }
780
John Marriott9e795852024-08-20 20:57:23 +0200781 *addr_type_arg = entry->key;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200782
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200783 return OK;
784}
785
John Marriott9e795852024-08-20 20:57:23 +0200786 static int
787cmp_addr_type(const void *a, const void *b)
788{
789 addrtype_T *at1 = (addrtype_T *)a;
790 addrtype_T *at2 = (addrtype_T *)b;
791
792 return STRNCMP(at1->fullname, at2->fullname, MAX(at1->fullnamelen, at2->fullnamelen));
793}
794
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200795/*
796 * Parse a completion argument "value[vallen]".
797 * The detected completion goes in "*complp", argument type in "*argt".
798 * When there is an argument, for function and user defined completion, it's
799 * copied to allocated memory and stored in "*compl_arg".
800 * Returns FAIL if something is wrong.
801 */
802 int
803parse_compl_arg(
804 char_u *value,
805 int vallen,
806 int *complp,
807 long *argt,
808 char_u **compl_arg UNUSED)
809{
810 char_u *arg = NULL;
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200811# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200812 size_t arglen = 0;
813# endif
814 int i;
815 int valend = vallen;
John Marriott9e795852024-08-20 20:57:23 +0200816 keyvalue_T target;
817 keyvalue_T *entry;
818 static keyvalue_T *last_entry = NULL; // cached result
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200819
820 // Look for any argument part - which is the part after any ','
821 for (i = 0; i < vallen; ++i)
822 {
823 if (value[i] == ',')
824 {
825 arg = &value[i + 1];
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200826# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200827 arglen = vallen - i - 1;
828# endif
829 valend = i;
830 break;
831 }
832 }
833
John Marriott9e795852024-08-20 20:57:23 +0200834 target.key = 0;
John Marriott8d4477e2024-11-02 15:59:01 +0100835 target.value.string = value;
836 target.value.length = valend;
John Marriott9e795852024-08-20 20:57:23 +0200837
838 if (last_entry != NULL && cmp_keyvalue_value_n(&target, last_entry) == 0)
839 entry = last_entry;
840 else
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200841 {
John Marriott9e795852024-08-20 20:57:23 +0200842 entry = (keyvalue_T *)bsearch(&target,
843 &command_complete_tab,
844 ARRAY_LENGTH(command_complete_tab),
845 sizeof(command_complete_tab[0]),
846 cmp_keyvalue_value_n);
847 if (entry == NULL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200848 {
John Marriott9e795852024-08-20 20:57:23 +0200849 semsg(_(e_invalid_complete_value_str), value);
850 return FAIL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200851 }
John Marriott9e795852024-08-20 20:57:23 +0200852
853 last_entry = entry;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200854 }
855
John Marriott9e795852024-08-20 20:57:23 +0200856 *complp = entry->key;
857 if (*complp == EXPAND_BUFFERS)
858 *argt |= EX_BUFNAME;
Ruslan Russkikh0407d622024-10-08 22:21:05 +0200859 else if (*complp == EXPAND_DIRECTORIES || *complp == EXPAND_FILES || *complp == EXPAND_SHELLCMDLINE)
John Marriott9e795852024-08-20 20:57:23 +0200860 *argt |= EX_XFILE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200861
John Marriott9e795852024-08-20 20:57:23 +0200862 if (
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200863# if defined(FEAT_EVAL)
John Marriott9e795852024-08-20 20:57:23 +0200864 *complp != EXPAND_USER_DEFINED && *complp != EXPAND_USER_LIST
865 &&
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200866# endif
John Marriott9e795852024-08-20 20:57:23 +0200867 arg != NULL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200868 {
Bram Moolenaarb09feaa2022-01-02 20:20:45 +0000869 emsg(_(e_completion_argument_only_allowed_for_custom_completion));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200870 return FAIL;
871 }
872
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200873# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200874 if ((*complp == EXPAND_USER_DEFINED || *complp == EXPAND_USER_LIST)
875 && arg == NULL)
876 {
Bram Moolenaarb09feaa2022-01-02 20:20:45 +0000877 emsg(_(e_custom_completion_requires_function_argument));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200878 return FAIL;
879 }
880
881 if (arg != NULL)
Bram Moolenaar71ccd032020-06-12 22:59:11 +0200882 *compl_arg = vim_strnsave(arg, arglen);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200883# endif
John Marriott9e795852024-08-20 20:57:23 +0200884
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200885 return OK;
886}
887
888/*
889 * Scan attributes in the ":command" command.
890 * Return FAIL when something is wrong.
891 */
892 static int
893uc_scan_attr(
894 char_u *attr,
895 size_t len,
896 long *argt,
897 long *def,
898 int *flags,
Bram Moolenaar52111f82019-04-29 21:30:45 +0200899 int *complp,
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200900 char_u **compl_arg,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200901 cmd_addr_T *addr_type_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200902{
903 char_u *p;
904
905 if (len == 0)
906 {
Bram Moolenaar1a992222021-12-31 17:25:48 +0000907 emsg(_(e_no_attribute_specified));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200908 return FAIL;
909 }
910
911 // First, try the simple attributes (no arguments)
912 if (STRNICMP(attr, "bang", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200913 *argt |= EX_BANG;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200914 else if (STRNICMP(attr, "buffer", len) == 0)
915 *flags |= UC_BUFFER;
916 else if (STRNICMP(attr, "register", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200917 *argt |= EX_REGSTR;
Bram Moolenaar58ef8a32021-11-12 11:25:11 +0000918 else if (STRNICMP(attr, "keepscript", len) == 0)
919 *argt |= EX_KEEPSCRIPT;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200920 else if (STRNICMP(attr, "bar", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200921 *argt |= EX_TRLBAR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200922 else
923 {
924 int i;
925 char_u *val = NULL;
926 size_t vallen = 0;
927 size_t attrlen = len;
928
929 // Look for the attribute name - which is the part before any '='
930 for (i = 0; i < (int)len; ++i)
931 {
932 if (attr[i] == '=')
933 {
934 val = &attr[i + 1];
935 vallen = len - i - 1;
936 attrlen = i;
937 break;
938 }
939 }
940
941 if (STRNICMP(attr, "nargs", attrlen) == 0)
942 {
943 if (vallen == 1)
944 {
945 if (*val == '0')
946 // Do nothing - this is the default
947 ;
948 else if (*val == '1')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200949 *argt |= (EX_EXTRA | EX_NOSPC | EX_NEEDARG);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200950 else if (*val == '*')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200951 *argt |= EX_EXTRA;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200952 else if (*val == '?')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200953 *argt |= (EX_EXTRA | EX_NOSPC);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200954 else if (*val == '+')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200955 *argt |= (EX_EXTRA | EX_NEEDARG);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200956 else
957 goto wrong_nargs;
958 }
959 else
960 {
961wrong_nargs:
Bram Moolenaar1a992222021-12-31 17:25:48 +0000962 emsg(_(e_invalid_number_of_arguments));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200963 return FAIL;
964 }
965 }
966 else if (STRNICMP(attr, "range", attrlen) == 0)
967 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200968 *argt |= EX_RANGE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200969 if (vallen == 1 && *val == '%')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200970 *argt |= EX_DFLALL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200971 else if (val != NULL)
972 {
973 p = val;
974 if (*def >= 0)
975 {
976two_count:
Bram Moolenaar1a992222021-12-31 17:25:48 +0000977 emsg(_(e_count_cannot_be_specified_twice));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200978 return FAIL;
979 }
980
981 *def = getdigits(&p);
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200982 *argt |= EX_ZEROR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200983
984 if (p != val + vallen || vallen == 0)
985 {
986invalid_count:
Bram Moolenaar1a992222021-12-31 17:25:48 +0000987 emsg(_(e_invalid_default_value_for_count));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200988 return FAIL;
989 }
990 }
Bram Moolenaarb7316892019-05-01 18:08:42 +0200991 // default for -range is using buffer lines
992 if (*addr_type_arg == ADDR_NONE)
993 *addr_type_arg = ADDR_LINES;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200994 }
995 else if (STRNICMP(attr, "count", attrlen) == 0)
996 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200997 *argt |= (EX_COUNT | EX_ZEROR | EX_RANGE);
Bram Moolenaarb7316892019-05-01 18:08:42 +0200998 // default for -count is using any number
999 if (*addr_type_arg == ADDR_NONE)
1000 *addr_type_arg = ADDR_OTHER;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001001
1002 if (val != NULL)
1003 {
1004 p = val;
1005 if (*def >= 0)
1006 goto two_count;
1007
1008 *def = getdigits(&p);
1009
1010 if (p != val + vallen)
1011 goto invalid_count;
1012 }
1013
1014 if (*def < 0)
1015 *def = 0;
1016 }
1017 else if (STRNICMP(attr, "complete", attrlen) == 0)
1018 {
1019 if (val == NULL)
1020 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001021 semsg(_(e_argument_required_for_str), "-complete");
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001022 return FAIL;
1023 }
1024
Bram Moolenaar52111f82019-04-29 21:30:45 +02001025 if (parse_compl_arg(val, (int)vallen, complp, argt, compl_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001026 == FAIL)
1027 return FAIL;
1028 }
1029 else if (STRNICMP(attr, "addr", attrlen) == 0)
1030 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +02001031 *argt |= EX_RANGE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001032 if (val == NULL)
1033 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001034 semsg(_(e_argument_required_for_str), "-addr");
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001035 return FAIL;
1036 }
Bram Moolenaare4f5f3a2019-05-04 14:05:08 +02001037 if (parse_addr_type_arg(val, (int)vallen, addr_type_arg) == FAIL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001038 return FAIL;
Bram Moolenaare4f5f3a2019-05-04 14:05:08 +02001039 if (*addr_type_arg != ADDR_LINES)
Bram Moolenaar8071cb22019-07-12 17:58:01 +02001040 *argt |= EX_ZEROR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001041 }
1042 else
1043 {
1044 char_u ch = attr[len];
1045 attr[len] = '\0';
Bram Moolenaar1a992222021-12-31 17:25:48 +00001046 semsg(_(e_invalid_attribute_str), attr);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001047 attr[len] = ch;
1048 return FAIL;
1049 }
1050 }
1051
1052 return OK;
1053}
1054
1055/*
1056 * Add a user command to the list or replace an existing one.
1057 */
1058 static int
1059uc_add_command(
1060 char_u *name,
1061 size_t name_len,
1062 char_u *rep,
1063 long argt,
1064 long def,
1065 int flags,
1066 int compl,
1067 char_u *compl_arg UNUSED,
Bram Moolenaarb7316892019-05-01 18:08:42 +02001068 cmd_addr_T addr_type,
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001069 int force)
1070{
1071 ucmd_T *cmd = NULL;
1072 char_u *p;
1073 int i;
1074 int cmp = 1;
1075 char_u *rep_buf = NULL;
1076 garray_T *gap;
1077
zeertzjq7e0bae02023-08-11 23:15:38 +02001078 replace_termcodes(rep, &rep_buf, 0, 0, NULL);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001079 if (rep_buf == NULL)
1080 {
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001081 // can't replace termcodes - try using the string as is
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001082 rep_buf = vim_strsave(rep);
1083
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001084 // give up if out of memory
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001085 if (rep_buf == NULL)
1086 return FAIL;
1087 }
1088
1089 // get address of growarray: global or in curbuf
1090 if (flags & UC_BUFFER)
1091 {
1092 gap = &curbuf->b_ucmds;
1093 if (gap->ga_itemsize == 0)
Bram Moolenaar04935fb2022-01-08 16:19:22 +00001094 ga_init2(gap, sizeof(ucmd_T), 4);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001095 }
1096 else
1097 gap = &ucmds;
1098
1099 // Search for the command in the already defined commands.
1100 for (i = 0; i < gap->ga_len; ++i)
1101 {
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001102 cmd = USER_CMD_GA(gap, i);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001103 cmp = STRNCMP(name, cmd->uc_name, name_len);
1104 if (cmp == 0)
1105 {
John Marriott9e795852024-08-20 20:57:23 +02001106 if (name_len < cmd->uc_namelen)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001107 cmp = -1;
John Marriott9e795852024-08-20 20:57:23 +02001108 else if (name_len > cmd->uc_namelen)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001109 cmp = 1;
1110 }
1111
1112 if (cmp == 0)
1113 {
1114 // Command can be replaced with "command!" and when sourcing the
1115 // same script again, but only once.
1116 if (!force
1117#ifdef FEAT_EVAL
1118 && (cmd->uc_script_ctx.sc_sid != current_sctx.sc_sid
1119 || cmd->uc_script_ctx.sc_seq == current_sctx.sc_seq)
1120#endif
1121 )
1122 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001123 semsg(_(e_command_already_exists_add_bang_to_replace_it_str),
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001124 name);
1125 goto fail;
1126 }
1127
1128 VIM_CLEAR(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001129#if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001130 VIM_CLEAR(cmd->uc_compl_arg);
1131#endif
1132 break;
1133 }
1134
1135 // Stop as soon as we pass the name to add
1136 if (cmp < 0)
1137 break;
1138 }
1139
1140 // Extend the array unless we're replacing an existing command
1141 if (cmp != 0)
1142 {
Yegappan Lakshmananfadc02a2023-01-27 21:03:12 +00001143 if (ga_grow(gap, 1) == FAIL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001144 goto fail;
Bram Moolenaar71ccd032020-06-12 22:59:11 +02001145 if ((p = vim_strnsave(name, name_len)) == NULL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001146 goto fail;
1147
1148 cmd = USER_CMD_GA(gap, i);
1149 mch_memmove(cmd + 1, cmd, (gap->ga_len - i) * sizeof(ucmd_T));
1150
1151 ++gap->ga_len;
1152
1153 cmd->uc_name = p;
John Marriott9e795852024-08-20 20:57:23 +02001154 cmd->uc_namelen = name_len;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001155 }
1156
1157 cmd->uc_rep = rep_buf;
1158 cmd->uc_argt = argt;
1159 cmd->uc_def = def;
1160 cmd->uc_compl = compl;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001161 cmd->uc_script_ctx = current_sctx;
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001162 if (flags & UC_VIM9)
1163 cmd->uc_script_ctx.sc_version = SCRIPT_VERSION_VIM9;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00001164 cmd->uc_flags = flags & UC_VIM9;
Bram Moolenaar9b8d6222020-12-28 18:26:00 +01001165#ifdef FEAT_EVAL
Bram Moolenaar1a47ae32019-12-29 23:04:25 +01001166 cmd->uc_script_ctx.sc_lnum += SOURCING_LNUM;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001167 cmd->uc_compl_arg = compl_arg;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001168#endif
1169 cmd->uc_addr_type = addr_type;
1170
1171 return OK;
1172
1173fail:
1174 vim_free(rep_buf);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001175#if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001176 vim_free(compl_arg);
1177#endif
1178 return FAIL;
1179}
1180
1181/*
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001182 * If "p" starts with "{" then read a block of commands until "}".
1183 * Used for ":command" and ":autocmd".
1184 */
1185 char_u *
1186may_get_cmd_block(exarg_T *eap, char_u *p, char_u **tofree, int *flags)
1187{
1188 char_u *retp = p;
1189
1190 if (*p == '{' && ends_excmd2(eap->arg, skipwhite(p + 1))
Zoltan Arpadffy6fdb6282023-12-19 20:53:07 +01001191 && eap->ea_getline != NULL)
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001192 {
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001193 garray_T ga;
1194 char_u *line = NULL;
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001195
1196 ga_init2(&ga, sizeof(char_u *), 10);
Bram Moolenaar9f1a39a2022-01-08 15:39:39 +00001197 if (ga_copy_string(&ga, p) == FAIL)
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001198 return retp;
1199
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001200 // If the argument ends in "}" it must have been concatenated already
1201 // for ISN_EXEC.
1202 if (p[STRLEN(p) - 1] != '}')
1203 // Read lines between '{' and '}'. Does not support nesting or
1204 // here-doc constructs.
1205 for (;;)
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001206 {
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001207 vim_free(line);
Zoltan Arpadffy6fdb6282023-12-19 20:53:07 +01001208 if ((line = eap->ea_getline(':', eap->cookie,
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001209 0, GETLINE_CONCAT_CONTBAR)) == NULL)
1210 {
1211 emsg(_(e_missing_rcurly));
1212 break;
1213 }
Bram Moolenaar9f1a39a2022-01-08 15:39:39 +00001214 if (ga_copy_string(&ga, line) == FAIL)
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001215 break;
1216 if (*skipwhite(line) == '}')
1217 break;
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001218 }
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001219 vim_free(line);
1220 retp = *tofree = ga_concat_strings(&ga, "\n");
1221 ga_clear_strings(&ga);
1222 *flags |= UC_VIM9;
1223 }
1224 return retp;
1225}
1226
1227/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001228 * ":command ..." implementation
1229 */
1230 void
1231ex_command(exarg_T *eap)
1232{
Bram Moolenaarb7316892019-05-01 18:08:42 +02001233 char_u *name;
1234 char_u *end;
1235 char_u *p;
1236 long argt = 0;
1237 long def = -1;
1238 int flags = 0;
1239 int compl = EXPAND_NOTHING;
1240 char_u *compl_arg = NULL;
1241 cmd_addr_T addr_type_arg = ADDR_NONE;
1242 int has_attr = (eap->arg[0] == '-');
1243 int name_len;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001244
1245 p = eap->arg;
1246
1247 // Check for attributes
1248 while (*p == '-')
1249 {
1250 ++p;
1251 end = skiptowhite(p);
1252 if (uc_scan_attr(p, end - p, &argt, &def, &flags, &compl,
1253 &compl_arg, &addr_type_arg) == FAIL)
zeertzjq33e54302022-12-19 16:49:27 +00001254 goto theend;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001255 p = skipwhite(end);
1256 }
1257
1258 // Get the name (if any) and skip to the following argument
1259 name = p;
1260 if (ASCII_ISALPHA(*p))
1261 while (ASCII_ISALNUM(*p))
1262 ++p;
Bram Moolenaara72cfb82020-04-23 17:07:30 +02001263 if (!ends_excmd2(eap->arg, p) && !VIM_ISWHITE(*p))
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001264 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001265 emsg(_(e_invalid_command_name));
zeertzjq33e54302022-12-19 16:49:27 +00001266 goto theend;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001267 }
1268 end = p;
1269 name_len = (int)(end - name);
1270
1271 // If there is nothing after the name, and no attributes were specified,
1272 // we are listing commands
1273 p = skipwhite(end);
Bram Moolenaara72cfb82020-04-23 17:07:30 +02001274 if (!has_attr && ends_excmd2(eap->arg, p))
zeertzjq33e54302022-12-19 16:49:27 +00001275 {
John Marriott9e795852024-08-20 20:57:23 +02001276 uc_list(name, name_len);
zeertzjq33e54302022-12-19 16:49:27 +00001277 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001278 else if (!ASCII_ISUPPER(*name))
zeertzjq33e54302022-12-19 16:49:27 +00001279 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001280 emsg(_(e_user_defined_commands_must_start_with_an_uppercase_letter));
zeertzjq33e54302022-12-19 16:49:27 +00001281 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001282 else if ((name_len == 1 && *name == 'X')
1283 || (name_len <= 4
1284 && STRNCMP(name, "Next", name_len > 4 ? 4 : name_len) == 0))
zeertzjq33e54302022-12-19 16:49:27 +00001285 {
Bram Moolenaar9d00e4a2022-01-05 17:49:15 +00001286 emsg(_(e_reserved_name_cannot_be_used_for_user_defined_command));
zeertzjq33e54302022-12-19 16:49:27 +00001287 }
Martin Tournoijde69a732021-07-11 14:28:25 +02001288 else if (compl > 0 && (argt & EX_EXTRA) == 0)
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001289 {
1290 // Some plugins rely on silently ignoring the mistake, only make this
1291 // an error in Vim9 script.
1292 if (in_vim9script())
Bram Moolenaar41a34852021-08-04 16:09:24 +02001293 emsg(_(e_complete_used_without_allowing_arguments));
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001294 else
1295 give_warning_with_source(
Bram Moolenaar41a34852021-08-04 16:09:24 +02001296 (char_u *)_(e_complete_used_without_allowing_arguments),
1297 TRUE, TRUE);
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001298 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001299 else
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001300 {
1301 char_u *tofree = NULL;
1302
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001303 p = may_get_cmd_block(eap, p, &tofree, &flags);
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001304
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001305 uc_add_command(name, end - name, p, argt, def, flags, compl, compl_arg,
1306 addr_type_arg, eap->forceit);
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001307 vim_free(tofree);
zeertzjq33e54302022-12-19 16:49:27 +00001308
1309 return; // success
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001310 }
zeertzjq33e54302022-12-19 16:49:27 +00001311
1312theend:
1313 vim_free(compl_arg);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001314}
1315
1316/*
1317 * ":comclear" implementation
1318 * Clear all user commands, global and for current buffer.
1319 */
1320 void
1321ex_comclear(exarg_T *eap UNUSED)
1322{
1323 uc_clear(&ucmds);
Bram Moolenaare5c83282019-05-03 23:15:37 +02001324 if (curbuf != NULL)
1325 uc_clear(&curbuf->b_ucmds);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001326}
1327
1328/*
Bram Moolenaarcf2594f2022-11-13 23:30:06 +00001329 * If ucmd_locked is set give an error and return TRUE.
1330 * Otherwise return FALSE.
1331 */
1332 static int
1333is_ucmd_locked(void)
1334{
1335 if (ucmd_locked > 0)
1336 {
1337 emsg(_(e_cannot_change_user_commands_while_listing));
1338 return TRUE;
1339 }
1340 return FALSE;
1341}
1342
1343/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001344 * Clear all user commands for "gap".
1345 */
1346 void
1347uc_clear(garray_T *gap)
1348{
1349 int i;
1350 ucmd_T *cmd;
1351
Bram Moolenaarcf2594f2022-11-13 23:30:06 +00001352 if (is_ucmd_locked())
1353 return;
1354
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001355 for (i = 0; i < gap->ga_len; ++i)
1356 {
1357 cmd = USER_CMD_GA(gap, i);
1358 vim_free(cmd->uc_name);
John Marriott9e795852024-08-20 20:57:23 +02001359 cmd->uc_namelen = 0;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001360 vim_free(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001361# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001362 vim_free(cmd->uc_compl_arg);
1363# endif
1364 }
1365 ga_clear(gap);
1366}
1367
1368/*
1369 * ":delcommand" implementation
1370 */
1371 void
1372ex_delcommand(exarg_T *eap)
1373{
1374 int i = 0;
1375 ucmd_T *cmd = NULL;
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001376 int res = -1;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001377 garray_T *gap;
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001378 char_u *arg = eap->arg;
1379 int buffer_only = FALSE;
1380
1381 if (STRNCMP(arg, "-buffer", 7) == 0 && VIM_ISWHITE(arg[7]))
1382 {
1383 buffer_only = TRUE;
1384 arg = skipwhite(arg + 7);
1385 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001386
1387 gap = &curbuf->b_ucmds;
1388 for (;;)
1389 {
1390 for (i = 0; i < gap->ga_len; ++i)
1391 {
1392 cmd = USER_CMD_GA(gap, i);
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001393 res = STRCMP(arg, cmd->uc_name);
1394 if (res <= 0)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001395 break;
1396 }
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001397 if (gap == &ucmds || res == 0 || buffer_only)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001398 break;
1399 gap = &ucmds;
1400 }
1401
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001402 if (res != 0)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001403 {
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001404 semsg(_(buffer_only
1405 ? e_no_such_user_defined_command_in_current_buffer_str
1406 : e_no_such_user_defined_command_str), arg);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001407 return;
1408 }
1409
Bram Moolenaarcf2594f2022-11-13 23:30:06 +00001410 if (is_ucmd_locked())
1411 return;
1412
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001413 vim_free(cmd->uc_name);
1414 vim_free(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001415# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001416 vim_free(cmd->uc_compl_arg);
1417# endif
1418
1419 --gap->ga_len;
1420
1421 if (i < gap->ga_len)
1422 mch_memmove(cmd, cmd + 1, (gap->ga_len - i) * sizeof(ucmd_T));
1423}
1424
1425/*
1426 * Split and quote args for <f-args>.
1427 */
1428 static char_u *
1429uc_split_args(char_u *arg, size_t *lenp)
1430{
1431 char_u *buf;
1432 char_u *p;
1433 char_u *q;
1434 int len;
1435
1436 // Precalculate length
1437 p = arg;
1438 len = 2; // Initial and final quotes
1439
1440 while (*p)
1441 {
1442 if (p[0] == '\\' && p[1] == '\\')
1443 {
1444 len += 2;
1445 p += 2;
1446 }
1447 else if (p[0] == '\\' && VIM_ISWHITE(p[1]))
1448 {
1449 len += 1;
1450 p += 2;
1451 }
1452 else if (*p == '\\' || *p == '"')
1453 {
1454 len += 2;
1455 p += 1;
1456 }
1457 else if (VIM_ISWHITE(*p))
1458 {
1459 p = skipwhite(p);
1460 if (*p == NUL)
1461 break;
Bram Moolenaar20d89e02020-10-20 23:11:33 +02001462 len += 4; // ", "
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001463 }
1464 else
1465 {
1466 int charlen = (*mb_ptr2len)(p);
1467
1468 len += charlen;
1469 p += charlen;
1470 }
1471 }
1472
1473 buf = alloc(len + 1);
1474 if (buf == NULL)
1475 {
1476 *lenp = 0;
1477 return buf;
1478 }
1479
1480 p = arg;
1481 q = buf;
1482 *q++ = '"';
1483 while (*p)
1484 {
1485 if (p[0] == '\\' && p[1] == '\\')
1486 {
1487 *q++ = '\\';
1488 *q++ = '\\';
1489 p += 2;
1490 }
1491 else if (p[0] == '\\' && VIM_ISWHITE(p[1]))
1492 {
1493 *q++ = p[1];
1494 p += 2;
1495 }
1496 else if (*p == '\\' || *p == '"')
1497 {
1498 *q++ = '\\';
1499 *q++ = *p++;
1500 }
1501 else if (VIM_ISWHITE(*p))
1502 {
1503 p = skipwhite(p);
1504 if (*p == NUL)
1505 break;
1506 *q++ = '"';
1507 *q++ = ',';
Bram Moolenaar20d89e02020-10-20 23:11:33 +02001508 *q++ = ' ';
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001509 *q++ = '"';
1510 }
1511 else
1512 {
1513 MB_COPY_CHAR(p, q);
1514 }
1515 }
1516 *q++ = '"';
John Marriott9e795852024-08-20 20:57:23 +02001517 *q = NUL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001518
1519 *lenp = len;
1520 return buf;
1521}
1522
1523 static size_t
John Marriott9e795852024-08-20 20:57:23 +02001524add_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 +02001525{
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001526 if (buf != NULL)
1527 {
1528 if (*multi_mods)
John Marriott9e795852024-08-20 20:57:23 +02001529 {
1530 STRCPY(buf + buflen, " "); // the separating space
1531 ++buflen;
1532 }
1533 STRCPY(buf + buflen, mod_str);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001534 }
1535
John Marriott9e795852024-08-20 20:57:23 +02001536 if (*multi_mods)
1537 ++mod_strlen; // +1 for the separating space
1538 else
1539 *multi_mods = 1;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001540
John Marriott9e795852024-08-20 20:57:23 +02001541 return mod_strlen;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001542}
1543
1544/*
Bram Moolenaar02194d22020-10-24 23:08:38 +02001545 * Add modifiers from "cmod->cmod_split" to "buf". Set "multi_mods" when one
Bram Moolenaare1004402020-10-24 20:49:43 +02001546 * was added. Return the number of bytes added.
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001547 */
1548 size_t
dundargocc57b5bc2022-11-02 13:30:51 +00001549add_win_cmd_modifiers(char_u *buf, cmdmod_T *cmod, int *multi_mods)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001550{
John Marriott9e795852024-08-20 20:57:23 +02001551 size_t buflen = 0;
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001552
1553 // :aboveleft and :leftabove
Bram Moolenaar02194d22020-10-24 23:08:38 +02001554 if (cmod->cmod_split & WSP_ABOVE)
John Marriott9e795852024-08-20 20:57:23 +02001555 buflen += add_cmd_modifier(buf, buflen, "aboveleft", STRLEN_LITERAL("aboveleft"), multi_mods);
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001556 // :belowright and :rightbelow
Bram Moolenaar02194d22020-10-24 23:08:38 +02001557 if (cmod->cmod_split & WSP_BELOW)
John Marriott9e795852024-08-20 20:57:23 +02001558 buflen += add_cmd_modifier(buf, buflen, "belowright", STRLEN_LITERAL("belowright"), multi_mods);
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001559 // :botright
Bram Moolenaar02194d22020-10-24 23:08:38 +02001560 if (cmod->cmod_split & WSP_BOT)
John Marriott9e795852024-08-20 20:57:23 +02001561 buflen += add_cmd_modifier(buf, buflen, "botright", STRLEN_LITERAL("botright"), multi_mods);
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001562
1563 // :tab
Bram Moolenaar02194d22020-10-24 23:08:38 +02001564 if (cmod->cmod_tab > 0)
zeertzjq208567e2022-10-18 13:11:21 +01001565 {
1566 int tabnr = cmod->cmod_tab - 1;
1567
1568 if (tabnr == tabpage_index(curtab))
1569 {
1570 // For compatibility, don't add a tabpage number if it is the same
1571 // as the default number for :tab.
John Marriott9e795852024-08-20 20:57:23 +02001572 buflen += add_cmd_modifier(buf, buflen, "tab", STRLEN_LITERAL("tab"), multi_mods);
zeertzjq208567e2022-10-18 13:11:21 +01001573 }
1574 else
1575 {
1576 char tab_buf[NUMBUFLEN + 3];
John Marriott9e795852024-08-20 20:57:23 +02001577 size_t tab_buflen;
zeertzjq208567e2022-10-18 13:11:21 +01001578
John Marriott9e795852024-08-20 20:57:23 +02001579 tab_buflen = vim_snprintf(tab_buf, sizeof(tab_buf), "%dtab", tabnr);
1580 buflen += add_cmd_modifier(buf, buflen, tab_buf, tab_buflen, multi_mods);
zeertzjq208567e2022-10-18 13:11:21 +01001581 }
1582 }
1583
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001584 // :topleft
Bram Moolenaar02194d22020-10-24 23:08:38 +02001585 if (cmod->cmod_split & WSP_TOP)
John Marriott9e795852024-08-20 20:57:23 +02001586 buflen += add_cmd_modifier(buf, buflen, "topleft", STRLEN_LITERAL("topleft"), multi_mods);
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001587 // :vertical
Bram Moolenaar02194d22020-10-24 23:08:38 +02001588 if (cmod->cmod_split & WSP_VERT)
John Marriott9e795852024-08-20 20:57:23 +02001589 buflen += add_cmd_modifier(buf, buflen, "vertical", STRLEN_LITERAL("vertical"), multi_mods);
zeertzjqd3de1782022-09-01 12:58:52 +01001590 // :horizontal
1591 if (cmod->cmod_split & WSP_HOR)
John Marriott9e795852024-08-20 20:57:23 +02001592 buflen += add_cmd_modifier(buf, buflen, "horizontal", STRLEN_LITERAL("horizontal"), multi_mods);
1593
1594 return buflen;
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001595}
1596
1597/*
Bram Moolenaar02194d22020-10-24 23:08:38 +02001598 * Generate text for the "cmod" command modifiers.
1599 * If "buf" is NULL just return the length.
1600 */
Bram Moolenaara360dbe2020-10-26 18:46:53 +01001601 size_t
Bram Moolenaar02194d22020-10-24 23:08:38 +02001602produce_cmdmods(char_u *buf, cmdmod_T *cmod, int quote)
1603{
John Marriott9e795852024-08-20 20:57:23 +02001604 size_t buflen = 0;
Bram Moolenaar02194d22020-10-24 23:08:38 +02001605 int multi_mods = 0;
1606 int i;
John Marriott9e795852024-08-20 20:57:23 +02001607 static keyvalue_T mod_entry_tab[] =
1608 {
Bram Moolenaar02194d22020-10-24 23:08:38 +02001609#ifdef FEAT_BROWSE_CMD
John Marriott9e795852024-08-20 20:57:23 +02001610 KEYVALUE_ENTRY(CMOD_BROWSE, "browse"),
Bram Moolenaar02194d22020-10-24 23:08:38 +02001611#endif
1612#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
John Marriott9e795852024-08-20 20:57:23 +02001613 KEYVALUE_ENTRY(CMOD_CONFIRM, "confirm"),
Bram Moolenaar02194d22020-10-24 23:08:38 +02001614#endif
John Marriott9e795852024-08-20 20:57:23 +02001615 KEYVALUE_ENTRY(CMOD_HIDE, "hide"),
1616 KEYVALUE_ENTRY(CMOD_KEEPALT, "keepalt"),
1617 KEYVALUE_ENTRY(CMOD_KEEPJUMPS, "keepjumps"),
1618 KEYVALUE_ENTRY(CMOD_KEEPMARKS, "keepmarks"),
1619 KEYVALUE_ENTRY(CMOD_KEEPPATTERNS, "keeppatterns"),
1620 KEYVALUE_ENTRY(CMOD_LOCKMARKS, "lockmarks"),
1621 KEYVALUE_ENTRY(CMOD_NOSWAPFILE, "noswapfile"),
1622 KEYVALUE_ENTRY(CMOD_UNSILENT, "unsilent"),
1623 KEYVALUE_ENTRY(CMOD_NOAUTOCMD, "noautocmd"),
Bram Moolenaar02194d22020-10-24 23:08:38 +02001624#ifdef HAVE_SANDBOX
John Marriott9e795852024-08-20 20:57:23 +02001625 KEYVALUE_ENTRY(CMOD_SANDBOX, "sandbox"),
Bram Moolenaar02194d22020-10-24 23:08:38 +02001626#endif
John Marriott9e795852024-08-20 20:57:23 +02001627 KEYVALUE_ENTRY(CMOD_LEGACY, "legacy")
Bram Moolenaar02194d22020-10-24 23:08:38 +02001628 };
1629
John Marriott9e795852024-08-20 20:57:23 +02001630 if (quote)
Bram Moolenaar02194d22020-10-24 23:08:38 +02001631 {
John Marriott9e795852024-08-20 20:57:23 +02001632 ++buflen;
1633 if (buf != NULL)
1634 {
1635 *buf = '"';
1636 *(buf + buflen) = NUL;
1637 }
Bram Moolenaar02194d22020-10-24 23:08:38 +02001638 }
John Marriott9e795852024-08-20 20:57:23 +02001639 else
1640 if (buf != NULL)
1641 *buf = NUL;
Bram Moolenaar02194d22020-10-24 23:08:38 +02001642
1643 // the modifiers that are simple flags
John Marriott9e795852024-08-20 20:57:23 +02001644 for (i = 0; i < (int)ARRAY_LENGTH(mod_entry_tab); ++i)
1645 if (cmod->cmod_flags & mod_entry_tab[i].key)
John Marriott8d4477e2024-11-02 15:59:01 +01001646 buflen += add_cmd_modifier(buf, buflen,
1647 (char *)mod_entry_tab[i].value.string,
1648 mod_entry_tab[i].value.length, &multi_mods);
Bram Moolenaar02194d22020-10-24 23:08:38 +02001649
1650 // :silent
1651 if (cmod->cmod_flags & CMOD_SILENT)
John Marriott9e795852024-08-20 20:57:23 +02001652 {
1653 if (cmod->cmod_flags & CMOD_ERRSILENT)
John Marriott8d4477e2024-11-02 15:59:01 +01001654 buflen += add_cmd_modifier(buf, buflen, "silent!",
1655 STRLEN_LITERAL("silent!"), &multi_mods);
John Marriott9e795852024-08-20 20:57:23 +02001656 else
John Marriott8d4477e2024-11-02 15:59:01 +01001657 buflen += add_cmd_modifier(buf, buflen, "silent",
1658 STRLEN_LITERAL("silent"), &multi_mods);
John Marriott9e795852024-08-20 20:57:23 +02001659 }
1660
Bram Moolenaar02194d22020-10-24 23:08:38 +02001661 // :verbose
zeertzjq9359e8a2022-07-03 13:16:09 +01001662 if (cmod->cmod_verbose > 0)
1663 {
1664 int verbose_value = cmod->cmod_verbose - 1;
1665
1666 if (verbose_value == 1)
John Marriott9e795852024-08-20 20:57:23 +02001667 buflen += add_cmd_modifier(buf, buflen, "verbose", STRLEN_LITERAL("verbose"), &multi_mods);
zeertzjq9359e8a2022-07-03 13:16:09 +01001668 else
1669 {
1670 char verbose_buf[NUMBUFLEN];
John Marriott9e795852024-08-20 20:57:23 +02001671 size_t verbose_buflen;
zeertzjq9359e8a2022-07-03 13:16:09 +01001672
John Marriott9e795852024-08-20 20:57:23 +02001673 verbose_buflen = vim_snprintf(verbose_buf, sizeof(verbose_buf), "%dverbose", verbose_value);
1674 buflen += add_cmd_modifier(buf, buflen, verbose_buf, verbose_buflen, &multi_mods);
zeertzjq9359e8a2022-07-03 13:16:09 +01001675 }
1676 }
zeertzjq9359e8a2022-07-03 13:16:09 +01001677
John Marriott9e795852024-08-20 20:57:23 +02001678 // flags from cmod->cmod_split
1679 buflen += add_win_cmd_modifiers((buf == NULL) ? NULL : buf + buflen, cmod, &multi_mods);
1680
1681 if (quote)
Bram Moolenaar02194d22020-10-24 23:08:38 +02001682 {
John Marriott9e795852024-08-20 20:57:23 +02001683 if (buf == NULL)
1684 ++buflen;
1685 else
1686 {
1687 *(buf + buflen) = '"';
1688 ++buflen;
1689 *(buf + buflen) = NUL;
1690 }
Bram Moolenaar02194d22020-10-24 23:08:38 +02001691 }
John Marriott9e795852024-08-20 20:57:23 +02001692
1693 return buflen;
Bram Moolenaar02194d22020-10-24 23:08:38 +02001694}
1695
1696/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001697 * Check for a <> code in a user command.
1698 * "code" points to the '<'. "len" the length of the <> (inclusive).
1699 * "buf" is where the result is to be added.
1700 * "split_buf" points to a buffer used for splitting, caller should free it.
1701 * "split_len" is the length of what "split_buf" contains.
1702 * Returns the length of the replacement, which has been added to "buf".
1703 * Returns -1 if there was no match, and only the "<" has been copied.
1704 */
1705 static size_t
1706uc_check_code(
1707 char_u *code,
1708 size_t len,
1709 char_u *buf,
1710 ucmd_T *cmd, // the user command we're expanding
1711 exarg_T *eap, // ex arguments
1712 char_u **split_buf,
1713 size_t *split_len)
1714{
1715 size_t result = 0;
1716 char_u *p = code + 1;
1717 size_t l = len - 2;
1718 int quote = 0;
1719 enum {
1720 ct_ARGS,
1721 ct_BANG,
1722 ct_COUNT,
1723 ct_LINE1,
1724 ct_LINE2,
1725 ct_RANGE,
1726 ct_MODS,
1727 ct_REGISTER,
1728 ct_LT,
1729 ct_NONE
1730 } type = ct_NONE;
1731
1732 if ((vim_strchr((char_u *)"qQfF", *p) != NULL) && p[1] == '-')
1733 {
1734 quote = (*p == 'q' || *p == 'Q') ? 1 : 2;
1735 p += 2;
1736 l -= 2;
1737 }
1738
1739 ++l;
1740 if (l <= 1)
1741 type = ct_NONE;
1742 else if (STRNICMP(p, "args>", l) == 0)
1743 type = ct_ARGS;
1744 else if (STRNICMP(p, "bang>", l) == 0)
1745 type = ct_BANG;
1746 else if (STRNICMP(p, "count>", l) == 0)
1747 type = ct_COUNT;
1748 else if (STRNICMP(p, "line1>", l) == 0)
1749 type = ct_LINE1;
1750 else if (STRNICMP(p, "line2>", l) == 0)
1751 type = ct_LINE2;
1752 else if (STRNICMP(p, "range>", l) == 0)
1753 type = ct_RANGE;
1754 else if (STRNICMP(p, "lt>", l) == 0)
1755 type = ct_LT;
1756 else if (STRNICMP(p, "reg>", l) == 0 || STRNICMP(p, "register>", l) == 0)
1757 type = ct_REGISTER;
1758 else if (STRNICMP(p, "mods>", l) == 0)
1759 type = ct_MODS;
1760
1761 switch (type)
1762 {
1763 case ct_ARGS:
1764 // Simple case first
1765 if (*eap->arg == NUL)
1766 {
1767 if (quote == 1)
1768 {
1769 result = 2;
1770 if (buf != NULL)
1771 STRCPY(buf, "''");
1772 }
1773 else
1774 result = 0;
1775 break;
1776 }
1777
1778 // When specified there is a single argument don't split it.
1779 // Works for ":Cmd %" when % is "a b c".
Bram Moolenaar8071cb22019-07-12 17:58:01 +02001780 if ((eap->argt & EX_NOSPC) && quote == 2)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001781 quote = 1;
1782
1783 switch (quote)
1784 {
1785 case 0: // No quoting, no splitting
1786 result = STRLEN(eap->arg);
1787 if (buf != NULL)
1788 STRCPY(buf, eap->arg);
1789 break;
1790 case 1: // Quote, but don't split
1791 result = STRLEN(eap->arg) + 2;
1792 for (p = eap->arg; *p; ++p)
1793 {
1794 if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2)
1795 // DBCS can contain \ in a trail byte, skip the
1796 // double-byte character.
1797 ++p;
1798 else
1799 if (*p == '\\' || *p == '"')
1800 ++result;
1801 }
1802
1803 if (buf != NULL)
1804 {
1805 *buf++ = '"';
1806 for (p = eap->arg; *p; ++p)
1807 {
1808 if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2)
1809 // DBCS can contain \ in a trail byte, copy the
1810 // double-byte character to avoid escaping.
1811 *buf++ = *p++;
1812 else
1813 if (*p == '\\' || *p == '"')
1814 *buf++ = '\\';
1815 *buf++ = *p;
1816 }
1817 *buf = '"';
1818 }
1819
1820 break;
1821 case 2: // Quote and split (<f-args>)
1822 // This is hard, so only do it once, and cache the result
1823 if (*split_buf == NULL)
1824 *split_buf = uc_split_args(eap->arg, split_len);
1825
1826 result = *split_len;
1827 if (buf != NULL && result != 0)
1828 STRCPY(buf, *split_buf);
1829
1830 break;
1831 }
1832 break;
1833
1834 case ct_BANG:
1835 result = eap->forceit ? 1 : 0;
1836 if (quote)
1837 result += 2;
1838 if (buf != NULL)
1839 {
1840 if (quote)
1841 *buf++ = '"';
1842 if (eap->forceit)
1843 *buf++ = '!';
1844 if (quote)
1845 *buf = '"';
1846 }
1847 break;
1848
1849 case ct_LINE1:
1850 case ct_LINE2:
1851 case ct_RANGE:
1852 case ct_COUNT:
1853 {
John Marriott9e795852024-08-20 20:57:23 +02001854 char num_buf[NUMBUFLEN];
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001855 long num = (type == ct_LINE1) ? eap->line1 :
1856 (type == ct_LINE2) ? eap->line2 :
1857 (type == ct_RANGE) ? eap->addr_count :
1858 (eap->addr_count > 0) ? eap->line2 : cmd->uc_def;
1859 size_t num_len;
1860
John Marriott9e795852024-08-20 20:57:23 +02001861 num_len = vim_snprintf(num_buf, sizeof(num_buf), "%ld", num);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001862 result = num_len;
1863
1864 if (quote)
1865 result += 2;
1866
1867 if (buf != NULL)
1868 {
1869 if (quote)
1870 *buf++ = '"';
1871 STRCPY(buf, num_buf);
1872 buf += num_len;
1873 if (quote)
1874 *buf = '"';
1875 }
1876
1877 break;
1878 }
1879
1880 case ct_MODS:
1881 {
Bram Moolenaar02194d22020-10-24 23:08:38 +02001882 result = produce_cmdmods(buf, &cmdmod, quote);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001883 break;
1884 }
1885
1886 case ct_REGISTER:
1887 result = eap->regname ? 1 : 0;
1888 if (quote)
1889 result += 2;
1890 if (buf != NULL)
1891 {
1892 if (quote)
1893 *buf++ = '\'';
1894 if (eap->regname)
1895 *buf++ = eap->regname;
1896 if (quote)
1897 *buf = '\'';
1898 }
1899 break;
1900
1901 case ct_LT:
1902 result = 1;
1903 if (buf != NULL)
1904 *buf = '<';
1905 break;
1906
1907 default:
1908 // Not recognized: just copy the '<' and return -1.
1909 result = (size_t)-1;
1910 if (buf != NULL)
1911 *buf = '<';
1912 break;
1913 }
1914
1915 return result;
1916}
1917
1918/*
1919 * Execute a user defined command.
1920 */
1921 void
1922do_ucmd(exarg_T *eap)
1923{
1924 char_u *buf;
1925 char_u *p;
1926 char_u *q;
1927
1928 char_u *start;
1929 char_u *end = NULL;
1930 char_u *ksp;
1931 size_t len, totlen;
1932
1933 size_t split_len = 0;
1934 char_u *split_buf = NULL;
1935 ucmd_T *cmd;
Bram Moolenaar205f29c2021-12-10 21:46:09 +00001936 sctx_T save_current_sctx;
1937 int restore_current_sctx = FALSE;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00001938#ifdef FEAT_EVAL
1939 int restore_script_version = 0;
1940#endif
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001941
1942 if (eap->cmdidx == CMD_USER)
1943 cmd = USER_CMD(eap->useridx);
1944 else
zeertzjqb444ee72023-02-20 15:25:13 +00001945 cmd = USER_CMD_GA(&prevwin_curwin()->w_buffer->b_ucmds, eap->useridx);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001946
1947 /*
1948 * Replace <> in the command by the arguments.
1949 * First round: "buf" is NULL, compute length, allocate "buf".
1950 * Second round: copy result into "buf".
1951 */
1952 buf = NULL;
1953 for (;;)
1954 {
1955 p = cmd->uc_rep; // source
1956 q = buf; // destination
1957 totlen = 0;
1958
1959 for (;;)
1960 {
1961 start = vim_strchr(p, '<');
1962 if (start != NULL)
1963 end = vim_strchr(start + 1, '>');
1964 if (buf != NULL)
1965 {
1966 for (ksp = p; *ksp != NUL && *ksp != K_SPECIAL; ++ksp)
1967 ;
1968 if (*ksp == K_SPECIAL
1969 && (start == NULL || ksp < start || end == NULL)
1970 && ((ksp[1] == KS_SPECIAL && ksp[2] == KE_FILLER)
1971# ifdef FEAT_GUI
1972 || (ksp[1] == KS_EXTRA && ksp[2] == (int)KE_CSI)
1973# endif
1974 ))
1975 {
1976 // K_SPECIAL has been put in the buffer as K_SPECIAL
1977 // KS_SPECIAL KE_FILLER, like for mappings, but
1978 // do_cmdline() doesn't handle that, so convert it back.
1979 // Also change K_SPECIAL KS_EXTRA KE_CSI into CSI.
1980 len = ksp - p;
1981 if (len > 0)
1982 {
1983 mch_memmove(q, p, len);
1984 q += len;
1985 }
1986 *q++ = ksp[1] == KS_SPECIAL ? K_SPECIAL : CSI;
1987 p = ksp + 3;
1988 continue;
1989 }
1990 }
1991
1992 // break if no <item> is found
1993 if (start == NULL || end == NULL)
1994 break;
1995
1996 // Include the '>'
1997 ++end;
1998
1999 // Take everything up to the '<'
2000 len = start - p;
2001 if (buf == NULL)
2002 totlen += len;
2003 else
2004 {
2005 mch_memmove(q, p, len);
2006 q += len;
2007 }
2008
2009 len = uc_check_code(start, end - start, q, cmd, eap,
2010 &split_buf, &split_len);
2011 if (len == (size_t)-1)
2012 {
2013 // no match, continue after '<'
2014 p = start + 1;
2015 len = 1;
2016 }
2017 else
2018 p = end;
2019 if (buf == NULL)
2020 totlen += len;
2021 else
2022 q += len;
2023 }
2024 if (buf != NULL) // second time here, finished
2025 {
2026 STRCPY(q, p);
2027 break;
2028 }
2029
2030 totlen += STRLEN(p); // Add on the trailing characters
Bram Moolenaar964b3742019-05-24 18:54:09 +02002031 buf = alloc(totlen + 1);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02002032 if (buf == NULL)
2033 {
2034 vim_free(split_buf);
2035 return;
2036 }
2037 }
2038
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00002039 if ((cmd->uc_argt & EX_KEEPSCRIPT) == 0)
2040 {
Bram Moolenaar205f29c2021-12-10 21:46:09 +00002041 restore_current_sctx = TRUE;
2042 save_current_sctx = current_sctx;
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00002043 current_sctx.sc_version = cmd->uc_script_ctx.sc_version;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02002044#ifdef FEAT_EVAL
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00002045 current_sctx.sc_sid = cmd->uc_script_ctx.sc_sid;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00002046 if (cmd->uc_flags & UC_VIM9)
2047 {
2048 // In a {} block variables use Vim9 script rules, even in a legacy
2049 // script.
2050 restore_script_version =
2051 SCRIPT_ITEM(current_sctx.sc_sid)->sn_version;
2052 SCRIPT_ITEM(current_sctx.sc_sid)->sn_version = SCRIPT_VERSION_VIM9;
2053 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02002054#endif
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00002055 }
Bram Moolenaar205f29c2021-12-10 21:46:09 +00002056
Zoltan Arpadffy6fdb6282023-12-19 20:53:07 +01002057 (void)do_cmdline(buf, eap->ea_getline, eap->cookie,
Bram Moolenaarac9fb182019-04-27 13:04:13 +02002058 DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED);
Bram Moolenaar205f29c2021-12-10 21:46:09 +00002059
2060 // Careful: Do not use "cmd" here, it may have become invalid if a user
2061 // command was added.
2062 if (restore_current_sctx)
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00002063 {
2064#ifdef FEAT_EVAL
2065 if (restore_script_version != 0)
2066 SCRIPT_ITEM(current_sctx.sc_sid)->sn_version =
2067 restore_script_version;
2068#endif
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00002069 current_sctx = save_current_sctx;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00002070 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02002071 vim_free(buf);
2072 vim_free(split_buf);
2073}