blob: 0160ac1de1ca5c8e13b8648c392444e72dbfacaf [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
19 long_u uc_argt; // The argument type
20 char_u *uc_rep; // The command's replacement string
21 long uc_def; // The default value for a range/count
22 int uc_compl; // completion type
Bram Moolenaarb7316892019-05-01 18:08:42 +020023 cmd_addr_T uc_addr_type; // The command's address type
Bram Moolenaarac9fb182019-04-27 13:04:13 +020024 sctx_T uc_script_ctx; // SCTX where the command was defined
Bram Moolenaar98b7fe72022-03-23 21:36:27 +000025 int uc_flags; // some UC_ flags
Bram Moolenaar9b8d6222020-12-28 18:26:00 +010026# ifdef FEAT_EVAL
Bram Moolenaarac9fb182019-04-27 13:04:13 +020027 char_u *uc_compl_arg; // completion argument if any
Bram Moolenaarac9fb182019-04-27 13:04:13 +020028# endif
29} ucmd_T;
30
31// List of all user commands.
32static garray_T ucmds = {0, 0, sizeof(ucmd_T), 4, NULL};
33
34#define USER_CMD(i) (&((ucmd_T *)(ucmds.ga_data))[i])
35#define USER_CMD_GA(gap, i) (&((ucmd_T *)((gap)->ga_data))[i])
36
37/*
38 * List of names for completion for ":command" with the EXPAND_ flag.
39 * Must be alphabetical for completion.
40 */
41static struct
42{
43 int expand;
44 char *name;
45} command_complete[] =
46{
47 {EXPAND_ARGLIST, "arglist"},
48 {EXPAND_AUGROUP, "augroup"},
49 {EXPAND_BEHAVE, "behave"},
50 {EXPAND_BUFFERS, "buffer"},
51 {EXPAND_COLORS, "color"},
52 {EXPAND_COMMANDS, "command"},
53 {EXPAND_COMPILER, "compiler"},
54#if defined(FEAT_CSCOPE)
55 {EXPAND_CSCOPE, "cscope"},
56#endif
Bram Moolenaar0a52df52019-08-18 22:26:31 +020057#if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +020058 {EXPAND_USER_DEFINED, "custom"},
59 {EXPAND_USER_LIST, "customlist"},
60#endif
Bram Moolenaarae7dba82019-12-29 13:56:33 +010061 {EXPAND_DIFF_BUFFERS, "diff_buffer"},
Bram Moolenaarac9fb182019-04-27 13:04:13 +020062 {EXPAND_DIRECTORIES, "dir"},
63 {EXPAND_ENV_VARS, "environment"},
64 {EXPAND_EVENTS, "event"},
65 {EXPAND_EXPRESSION, "expression"},
66 {EXPAND_FILES, "file"},
67 {EXPAND_FILES_IN_PATH, "file_in_path"},
68 {EXPAND_FILETYPE, "filetype"},
69 {EXPAND_FUNCTIONS, "function"},
70 {EXPAND_HELP, "help"},
71 {EXPAND_HIGHLIGHT, "highlight"},
Bram Moolenaarac9fb182019-04-27 13:04:13 +020072 {EXPAND_HISTORY, "history"},
Bram Moolenaarac9fb182019-04-27 13:04:13 +020073#if defined(HAVE_LOCALE_H) || defined(X_LOCALE)
74 {EXPAND_LOCALES, "locale"},
75#endif
76 {EXPAND_MAPCLEAR, "mapclear"},
77 {EXPAND_MAPPINGS, "mapping"},
78 {EXPAND_MENUS, "menu"},
79 {EXPAND_MESSAGES, "messages"},
80 {EXPAND_OWNSYNTAX, "syntax"},
81#if defined(FEAT_PROFILE)
82 {EXPAND_SYNTIME, "syntime"},
83#endif
84 {EXPAND_SETTINGS, "option"},
85 {EXPAND_PACKADD, "packadd"},
86 {EXPAND_SHELLCMD, "shellcmd"},
87#if defined(FEAT_SIGNS)
88 {EXPAND_SIGN, "sign"},
89#endif
90 {EXPAND_TAGS, "tag"},
91 {EXPAND_TAGS_LISTFILES, "tag_listfiles"},
92 {EXPAND_USER, "user"},
93 {EXPAND_USER_VARS, "var"},
Bram Moolenaar6e2e2cc2022-03-14 19:24:46 +000094#if defined(FEAT_EVAL)
95 {EXPAND_BREAKPOINT, "breakpoint"},
Yegappan Lakshmanan454ce672022-03-24 11:22:13 +000096 {EXPAND_SCRIPTNAMES, "scriptnames"},
Bram Moolenaar6e2e2cc2022-03-14 19:24:46 +000097#endif
Bram Moolenaarac9fb182019-04-27 13:04:13 +020098 {0, NULL}
99};
100
101/*
102 * List of names of address types. Must be alphabetical for completion.
103 */
104static struct
105{
Bram Moolenaarb7316892019-05-01 18:08:42 +0200106 cmd_addr_T expand;
107 char *name;
108 char *shortname;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200109} addr_type_complete[] =
110{
111 {ADDR_ARGUMENTS, "arguments", "arg"},
112 {ADDR_LINES, "lines", "line"},
113 {ADDR_LOADED_BUFFERS, "loaded_buffers", "load"},
114 {ADDR_TABS, "tabs", "tab"},
115 {ADDR_BUFFERS, "buffers", "buf"},
116 {ADDR_WINDOWS, "windows", "win"},
117 {ADDR_QUICKFIX, "quickfix", "qf"},
118 {ADDR_OTHER, "other", "?"},
Bram Moolenaarb7316892019-05-01 18:08:42 +0200119 {ADDR_NONE, NULL, NULL}
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200120};
121
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200122/*
123 * Search for a user command that matches "eap->cmd".
124 * Return cmdidx in "eap->cmdidx", flags in "eap->argt", idx in "eap->useridx".
125 * Return a pointer to just after the command.
126 * Return NULL if there is no matching command.
127 */
128 char_u *
129find_ucmd(
130 exarg_T *eap,
Bram Moolenaar0023f822022-01-16 21:54:19 +0000131 char_u *p, // end of the command (possibly including count)
132 int *full, // set to TRUE for a full match
133 expand_T *xp, // used for completion, NULL otherwise
134 int *complp) // completion flags or NULL
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200135{
136 int len = (int)(p - eap->cmd);
137 int j, k, matchlen = 0;
138 ucmd_T *uc;
139 int found = FALSE;
140 int possible = FALSE;
141 char_u *cp, *np; // Point into typed cmd and test name
142 garray_T *gap;
143 int amb_local = FALSE; // Found ambiguous buffer-local command,
144 // only full match global is accepted.
145
146 /*
147 * Look for buffer-local user commands first, then global ones.
148 */
Bram Moolenaar0f6e28f2022-02-20 20:49:35 +0000149 gap = &prevwin_curwin()->w_buffer->b_ucmds;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200150 for (;;)
151 {
152 for (j = 0; j < gap->ga_len; ++j)
153 {
154 uc = USER_CMD_GA(gap, j);
155 cp = eap->cmd;
156 np = uc->uc_name;
157 k = 0;
158 while (k < len && *np != NUL && *cp++ == *np++)
159 k++;
160 if (k == len || (*np == NUL && vim_isdigit(eap->cmd[k])))
161 {
162 // If finding a second match, the command is ambiguous. But
163 // not if a buffer-local command wasn't a full match and a
164 // global command is a full match.
165 if (k == len && found && *np != NUL)
166 {
167 if (gap == &ucmds)
168 return NULL;
169 amb_local = TRUE;
170 }
171
172 if (!found || (k == len && *np == NUL))
173 {
174 // If we matched up to a digit, then there could
175 // be another command including the digit that we
176 // should use instead.
177 if (k == len)
178 found = TRUE;
179 else
180 possible = TRUE;
181
182 if (gap == &ucmds)
183 eap->cmdidx = CMD_USER;
184 else
185 eap->cmdidx = CMD_USER_BUF;
186 eap->argt = (long)uc->uc_argt;
187 eap->useridx = j;
188 eap->addr_type = uc->uc_addr_type;
189
Bram Moolenaar52111f82019-04-29 21:30:45 +0200190 if (complp != NULL)
191 *complp = uc->uc_compl;
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200192# ifdef FEAT_EVAL
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200193 if (xp != NULL)
194 {
195 xp->xp_arg = uc->uc_compl_arg;
196 xp->xp_script_ctx = uc->uc_script_ctx;
Bram Moolenaar1a47ae32019-12-29 23:04:25 +0100197 xp->xp_script_ctx.sc_lnum += SOURCING_LNUM;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200198 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200199# endif
200 // Do not search for further abbreviations
201 // if this is an exact match.
202 matchlen = k;
203 if (k == len && *np == NUL)
204 {
205 if (full != NULL)
206 *full = TRUE;
207 amb_local = FALSE;
208 break;
209 }
210 }
211 }
212 }
213
214 // Stop if we found a full match or searched all.
215 if (j < gap->ga_len || gap == &ucmds)
216 break;
217 gap = &ucmds;
218 }
219
220 // Only found ambiguous matches.
221 if (amb_local)
222 {
223 if (xp != NULL)
224 xp->xp_context = EXPAND_UNSUCCESSFUL;
225 return NULL;
226 }
227
228 // The match we found may be followed immediately by a number. Move "p"
229 // back to point to it.
230 if (found || possible)
231 return p + (matchlen - len);
232 return p;
233}
234
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000235/*
236 * Set completion context for :command
237 */
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200238 char_u *
239set_context_in_user_cmd(expand_T *xp, char_u *arg_in)
240{
241 char_u *arg = arg_in;
242 char_u *p;
243
244 // Check for attributes
245 while (*arg == '-')
246 {
247 arg++; // Skip "-"
248 p = skiptowhite(arg);
249 if (*p == NUL)
250 {
251 // Cursor is still in the attribute
252 p = vim_strchr(arg, '=');
253 if (p == NULL)
254 {
255 // No "=", so complete attribute names
256 xp->xp_context = EXPAND_USER_CMD_FLAGS;
257 xp->xp_pattern = arg;
258 return NULL;
259 }
260
261 // For the -complete, -nargs and -addr attributes, we complete
262 // their arguments as well.
263 if (STRNICMP(arg, "complete", p - arg) == 0)
264 {
265 xp->xp_context = EXPAND_USER_COMPLETE;
266 xp->xp_pattern = p + 1;
267 return NULL;
268 }
269 else if (STRNICMP(arg, "nargs", p - arg) == 0)
270 {
271 xp->xp_context = EXPAND_USER_NARGS;
272 xp->xp_pattern = p + 1;
273 return NULL;
274 }
275 else if (STRNICMP(arg, "addr", p - arg) == 0)
276 {
277 xp->xp_context = EXPAND_USER_ADDR_TYPE;
278 xp->xp_pattern = p + 1;
279 return NULL;
280 }
281 return NULL;
282 }
283 arg = skipwhite(p);
284 }
285
286 // After the attributes comes the new command name
287 p = skiptowhite(arg);
288 if (*p == NUL)
289 {
290 xp->xp_context = EXPAND_USER_COMMANDS;
291 xp->xp_pattern = arg;
292 return NULL;
293 }
294
295 // And finally comes a normal command
296 return skipwhite(p);
297}
298
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000299/*
300 * Set the completion context for the argument of a user defined command.
301 */
302 char_u *
303set_context_in_user_cmdarg(
304 char_u *cmd UNUSED,
305 char_u *arg,
306 long argt,
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000307 int context,
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000308 expand_T *xp,
309 int forceit)
310{
311 char_u *p;
312
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000313 if (context == EXPAND_NOTHING)
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000314 return NULL;
315
316 if (argt & EX_XFILE)
317 {
318 // EX_XFILE: file names are handled before this call
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000319 xp->xp_context = context;
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000320 return NULL;
321 }
322
323#ifdef FEAT_MENU
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000324 if (context == EXPAND_MENUS)
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000325 return set_context_in_menu_cmd(xp, cmd, arg, forceit);
326#endif
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000327 if (context == EXPAND_COMMANDS)
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000328 return arg;
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000329 if (context == EXPAND_MAPPINGS)
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000330 return set_context_in_map_cmd(xp, (char_u *)"map", arg, forceit, FALSE,
331 FALSE, CMD_map);
332 // Find start of last argument.
333 p = arg;
334 while (*p)
335 {
336 if (*p == ' ')
337 // argument starts after a space
338 arg = p + 1;
339 else if (*p == '\\' && *(p + 1) != NUL)
340 ++p; // skip over escaped character
341 MB_PTR_ADV(p);
342 }
343 xp->xp_pattern = arg;
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000344 xp->xp_context = context;
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000345
346 return NULL;
347}
348
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200349 char_u *
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200350expand_user_command_name(int idx)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200351{
352 return get_user_commands(NULL, idx - (int)CMD_SIZE);
353}
354
355/*
356 * Function given to ExpandGeneric() to obtain the list of user command names.
357 */
358 char_u *
359get_user_commands(expand_T *xp UNUSED, int idx)
360{
Bram Moolenaarf03e3282019-07-22 21:55:18 +0200361 // In cmdwin, the alternative buffer should be used.
Bram Moolenaar0f6e28f2022-02-20 20:49:35 +0000362 buf_T *buf = prevwin_curwin()->w_buffer;
Bram Moolenaarf03e3282019-07-22 21:55:18 +0200363
364 if (idx < buf->b_ucmds.ga_len)
365 return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name;
Bram Moolenaarc2842ad2022-07-26 17:23:47 +0100366
Bram Moolenaarf03e3282019-07-22 21:55:18 +0200367 idx -= buf->b_ucmds.ga_len;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200368 if (idx < ucmds.ga_len)
Bram Moolenaarc2842ad2022-07-26 17:23:47 +0100369 {
370 int i;
371 char_u *name = USER_CMD(idx)->uc_name;
372
373 for (i = 0; i < buf->b_ucmds.ga_len; ++i)
374 if (STRCMP(name, USER_CMD_GA(&buf->b_ucmds, i)->uc_name) == 0)
375 // global command is overruled by buffer-local one
376 return (char_u *)"";
377 return name;
378 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200379 return NULL;
380}
381
Dominique Pelle748b3082022-01-08 12:41:16 +0000382#ifdef FEAT_EVAL
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200383/*
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200384 * Get the name of user command "idx". "cmdidx" can be CMD_USER or
385 * CMD_USER_BUF.
386 * Returns NULL if the command is not found.
387 */
388 char_u *
389get_user_command_name(int idx, int cmdidx)
390{
391 if (cmdidx == CMD_USER && idx < ucmds.ga_len)
392 return USER_CMD(idx)->uc_name;
393 if (cmdidx == CMD_USER_BUF)
394 {
395 // In cmdwin, the alternative buffer should be used.
Bram Moolenaar0f6e28f2022-02-20 20:49:35 +0000396 buf_T *buf = prevwin_curwin()->w_buffer;
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200397
398 if (idx < buf->b_ucmds.ga_len)
399 return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name;
400 }
401 return NULL;
402}
Dominique Pelle748b3082022-01-08 12:41:16 +0000403#endif
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200404
405/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200406 * Function given to ExpandGeneric() to obtain the list of user address type
407 * names.
408 */
409 char_u *
410get_user_cmd_addr_type(expand_T *xp UNUSED, int idx)
411{
412 return (char_u *)addr_type_complete[idx].name;
413}
414
415/*
416 * Function given to ExpandGeneric() to obtain the list of user command
417 * attributes.
418 */
419 char_u *
420get_user_cmd_flags(expand_T *xp UNUSED, int idx)
421{
422 static char *user_cmd_flags[] = {
423 "addr", "bang", "bar", "buffer", "complete",
Bram Moolenaar58ef8a32021-11-12 11:25:11 +0000424 "count", "nargs", "range", "register", "keepscript"
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200425 };
426
K.Takataeeec2542021-06-02 13:28:16 +0200427 if (idx >= (int)ARRAY_LENGTH(user_cmd_flags))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200428 return NULL;
429 return (char_u *)user_cmd_flags[idx];
430}
431
432/*
433 * Function given to ExpandGeneric() to obtain the list of values for -nargs.
434 */
435 char_u *
436get_user_cmd_nargs(expand_T *xp UNUSED, int idx)
437{
438 static char *user_cmd_nargs[] = {"0", "1", "*", "?", "+"};
439
K.Takataeeec2542021-06-02 13:28:16 +0200440 if (idx >= (int)ARRAY_LENGTH(user_cmd_nargs))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200441 return NULL;
442 return (char_u *)user_cmd_nargs[idx];
443}
444
445/*
446 * Function given to ExpandGeneric() to obtain the list of values for
447 * -complete.
448 */
449 char_u *
450get_user_cmd_complete(expand_T *xp UNUSED, int idx)
451{
452 return (char_u *)command_complete[idx].name;
453}
454
Dominique Pelle748b3082022-01-08 12:41:16 +0000455#ifdef FEAT_EVAL
Shougo Matsushita79d599b2022-05-07 12:48:29 +0100456/*
457 * Get the name of completion type "expand" as a string.
458 */
459 char_u *
460cmdcomplete_type_to_str(int expand)
461{
462 int i;
463
464 for (i = 0; command_complete[i].expand != 0; i++)
465 if (command_complete[i].expand == expand)
466 return (char_u *)command_complete[i].name;
467
468 return NULL;
469}
470
471/*
472 * Get the index of completion type "complete_str".
473 * Returns EXPAND_NOTHING if no match found.
474 */
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200475 int
476cmdcomplete_str_to_type(char_u *complete_str)
477{
478 int i;
479
480 for (i = 0; command_complete[i].expand != 0; ++i)
481 if (STRCMP(complete_str, command_complete[i].name) == 0)
482 return command_complete[i].expand;
483
484 return EXPAND_NOTHING;
485}
Dominique Pelle748b3082022-01-08 12:41:16 +0000486#endif
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200487
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200488/*
489 * List user commands starting with "name[name_len]".
490 */
491 static void
492uc_list(char_u *name, size_t name_len)
493{
494 int i, j;
495 int found = FALSE;
496 ucmd_T *cmd;
497 int len;
498 int over;
499 long a;
500 garray_T *gap;
501
Bram Moolenaare38eab22019-12-05 21:50:01 +0100502 // In cmdwin, the alternative buffer should be used.
Bram Moolenaar0f6e28f2022-02-20 20:49:35 +0000503 gap = &prevwin_curwin()->w_buffer->b_ucmds;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200504 for (;;)
505 {
506 for (i = 0; i < gap->ga_len; ++i)
507 {
508 cmd = USER_CMD_GA(gap, i);
509 a = (long)cmd->uc_argt;
510
511 // Skip commands which don't match the requested prefix and
512 // commands filtered out.
513 if (STRNCMP(name, cmd->uc_name, name_len) != 0
514 || message_filtered(cmd->uc_name))
515 continue;
516
517 // Put out the title first time
518 if (!found)
519 msg_puts_title(_("\n Name Args Address Complete Definition"));
520 found = TRUE;
521 msg_putchar('\n');
522 if (got_int)
523 break;
524
525 // Special cases
526 len = 4;
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200527 if (a & EX_BANG)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200528 {
529 msg_putchar('!');
530 --len;
531 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200532 if (a & EX_REGSTR)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200533 {
534 msg_putchar('"');
535 --len;
536 }
537 if (gap != &ucmds)
538 {
539 msg_putchar('b');
540 --len;
541 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200542 if (a & EX_TRLBAR)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200543 {
544 msg_putchar('|');
545 --len;
546 }
547 while (len-- > 0)
548 msg_putchar(' ');
549
550 msg_outtrans_attr(cmd->uc_name, HL_ATTR(HLF_D));
551 len = (int)STRLEN(cmd->uc_name) + 4;
552
553 do {
554 msg_putchar(' ');
555 ++len;
556 } while (len < 22);
557
558 // "over" is how much longer the name is than the column width for
559 // the name, we'll try to align what comes after.
560 over = len - 22;
561 len = 0;
562
563 // Arguments
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200564 switch ((int)(a & (EX_EXTRA|EX_NOSPC|EX_NEEDARG)))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200565 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200566 case 0: IObuff[len++] = '0'; break;
567 case (EX_EXTRA): IObuff[len++] = '*'; break;
568 case (EX_EXTRA|EX_NOSPC): IObuff[len++] = '?'; break;
569 case (EX_EXTRA|EX_NEEDARG): IObuff[len++] = '+'; break;
570 case (EX_EXTRA|EX_NOSPC|EX_NEEDARG): IObuff[len++] = '1'; break;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200571 }
572
573 do {
574 IObuff[len++] = ' ';
575 } while (len < 5 - over);
576
577 // Address / Range
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200578 if (a & (EX_RANGE|EX_COUNT))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200579 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200580 if (a & EX_COUNT)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200581 {
582 // -count=N
583 sprintf((char *)IObuff + len, "%ldc", cmd->uc_def);
584 len += (int)STRLEN(IObuff + len);
585 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200586 else if (a & EX_DFLALL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200587 IObuff[len++] = '%';
588 else if (cmd->uc_def >= 0)
589 {
590 // -range=N
591 sprintf((char *)IObuff + len, "%ld", cmd->uc_def);
592 len += (int)STRLEN(IObuff + len);
593 }
594 else
595 IObuff[len++] = '.';
596 }
597
598 do {
599 IObuff[len++] = ' ';
600 } while (len < 8 - over);
601
602 // Address Type
Bram Moolenaarb7316892019-05-01 18:08:42 +0200603 for (j = 0; addr_type_complete[j].expand != ADDR_NONE; ++j)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200604 if (addr_type_complete[j].expand != ADDR_LINES
605 && addr_type_complete[j].expand == cmd->uc_addr_type)
606 {
607 STRCPY(IObuff + len, addr_type_complete[j].shortname);
608 len += (int)STRLEN(IObuff + len);
609 break;
610 }
611
612 do {
613 IObuff[len++] = ' ';
614 } while (len < 13 - over);
615
616 // Completion
617 for (j = 0; command_complete[j].expand != 0; ++j)
618 if (command_complete[j].expand == cmd->uc_compl)
619 {
620 STRCPY(IObuff + len, command_complete[j].name);
621 len += (int)STRLEN(IObuff + len);
Bram Moolenaar78f60322022-01-17 22:16:33 +0000622#ifdef FEAT_EVAL
Bram Moolenaar3f3597b2022-01-17 19:06:56 +0000623 if (p_verbose > 0 && cmd->uc_compl_arg != NULL
624 && STRLEN(cmd->uc_compl_arg) < 200)
625 {
626 IObuff[len] = ',';
627 STRCPY(IObuff + len + 1, cmd->uc_compl_arg);
628 len += (int)STRLEN(IObuff + len);
629 }
Bram Moolenaar78f60322022-01-17 22:16:33 +0000630#endif
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200631 break;
632 }
633
634 do {
635 IObuff[len++] = ' ';
636 } while (len < 25 - over);
637
638 IObuff[len] = '\0';
639 msg_outtrans(IObuff);
640
641 msg_outtrans_special(cmd->uc_rep, FALSE,
642 name_len == 0 ? Columns - 47 : 0);
643#ifdef FEAT_EVAL
644 if (p_verbose > 0)
645 last_set_msg(cmd->uc_script_ctx);
646#endif
647 out_flush();
648 ui_breakcheck();
649 if (got_int)
650 break;
651 }
652 if (gap == &ucmds || i < gap->ga_len)
653 break;
654 gap = &ucmds;
655 }
656
657 if (!found)
658 msg(_("No user-defined commands found"));
659}
660
661 char *
662uc_fun_cmd(void)
663{
664 static char_u fcmd[] = {0x84, 0xaf, 0x60, 0xb9, 0xaf, 0xb5, 0x60, 0xa4,
665 0xa5, 0xad, 0xa1, 0xae, 0xa4, 0x60, 0xa1, 0x60,
666 0xb3, 0xa8, 0xb2, 0xb5, 0xa2, 0xa2, 0xa5, 0xb2,
667 0xb9, 0x7f, 0};
668 int i;
669
670 for (i = 0; fcmd[i]; ++i)
671 IObuff[i] = fcmd[i] - 0x40;
672 IObuff[i] = 0;
673 return (char *)IObuff;
674}
675
676/*
677 * Parse address type argument
678 */
679 static int
680parse_addr_type_arg(
681 char_u *value,
682 int vallen,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200683 cmd_addr_T *addr_type_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200684{
685 int i, a, b;
686
Bram Moolenaarb7316892019-05-01 18:08:42 +0200687 for (i = 0; addr_type_complete[i].expand != ADDR_NONE; ++i)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200688 {
689 a = (int)STRLEN(addr_type_complete[i].name) == vallen;
690 b = STRNCMP(value, addr_type_complete[i].name, vallen) == 0;
691 if (a && b)
692 {
693 *addr_type_arg = addr_type_complete[i].expand;
694 break;
695 }
696 }
697
Bram Moolenaarb7316892019-05-01 18:08:42 +0200698 if (addr_type_complete[i].expand == ADDR_NONE)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200699 {
700 char_u *err = value;
701
702 for (i = 0; err[i] != NUL && !VIM_ISWHITE(err[i]); i++)
703 ;
704 err[i] = NUL;
Bram Moolenaar11de43d2022-01-06 21:41:11 +0000705 semsg(_(e_invalid_address_type_value_str), err);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200706 return FAIL;
707 }
708
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200709 return OK;
710}
711
712/*
713 * Parse a completion argument "value[vallen]".
714 * The detected completion goes in "*complp", argument type in "*argt".
715 * When there is an argument, for function and user defined completion, it's
716 * copied to allocated memory and stored in "*compl_arg".
717 * Returns FAIL if something is wrong.
718 */
719 int
720parse_compl_arg(
721 char_u *value,
722 int vallen,
723 int *complp,
724 long *argt,
725 char_u **compl_arg UNUSED)
726{
727 char_u *arg = NULL;
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200728# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200729 size_t arglen = 0;
730# endif
731 int i;
732 int valend = vallen;
733
734 // Look for any argument part - which is the part after any ','
735 for (i = 0; i < vallen; ++i)
736 {
737 if (value[i] == ',')
738 {
739 arg = &value[i + 1];
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200740# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200741 arglen = vallen - i - 1;
742# endif
743 valend = i;
744 break;
745 }
746 }
747
748 for (i = 0; command_complete[i].expand != 0; ++i)
749 {
750 if ((int)STRLEN(command_complete[i].name) == valend
751 && STRNCMP(value, command_complete[i].name, valend) == 0)
752 {
753 *complp = command_complete[i].expand;
754 if (command_complete[i].expand == EXPAND_BUFFERS)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200755 *argt |= EX_BUFNAME;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200756 else if (command_complete[i].expand == EXPAND_DIRECTORIES
757 || command_complete[i].expand == EXPAND_FILES)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200758 *argt |= EX_XFILE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200759 break;
760 }
761 }
762
763 if (command_complete[i].expand == 0)
764 {
Bram Moolenaar1a992222021-12-31 17:25:48 +0000765 semsg(_(e_invalid_complete_value_str), value);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200766 return FAIL;
767 }
768
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200769# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200770 if (*complp != EXPAND_USER_DEFINED && *complp != EXPAND_USER_LIST
771 && arg != NULL)
772# else
773 if (arg != NULL)
774# endif
775 {
Bram Moolenaarb09feaa2022-01-02 20:20:45 +0000776 emsg(_(e_completion_argument_only_allowed_for_custom_completion));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200777 return FAIL;
778 }
779
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200780# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200781 if ((*complp == EXPAND_USER_DEFINED || *complp == EXPAND_USER_LIST)
782 && arg == NULL)
783 {
Bram Moolenaarb09feaa2022-01-02 20:20:45 +0000784 emsg(_(e_custom_completion_requires_function_argument));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200785 return FAIL;
786 }
787
788 if (arg != NULL)
Bram Moolenaar71ccd032020-06-12 22:59:11 +0200789 *compl_arg = vim_strnsave(arg, arglen);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200790# endif
791 return OK;
792}
793
794/*
795 * Scan attributes in the ":command" command.
796 * Return FAIL when something is wrong.
797 */
798 static int
799uc_scan_attr(
800 char_u *attr,
801 size_t len,
802 long *argt,
803 long *def,
804 int *flags,
Bram Moolenaar52111f82019-04-29 21:30:45 +0200805 int *complp,
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200806 char_u **compl_arg,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200807 cmd_addr_T *addr_type_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200808{
809 char_u *p;
810
811 if (len == 0)
812 {
Bram Moolenaar1a992222021-12-31 17:25:48 +0000813 emsg(_(e_no_attribute_specified));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200814 return FAIL;
815 }
816
817 // First, try the simple attributes (no arguments)
818 if (STRNICMP(attr, "bang", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200819 *argt |= EX_BANG;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200820 else if (STRNICMP(attr, "buffer", len) == 0)
821 *flags |= UC_BUFFER;
822 else if (STRNICMP(attr, "register", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200823 *argt |= EX_REGSTR;
Bram Moolenaar58ef8a32021-11-12 11:25:11 +0000824 else if (STRNICMP(attr, "keepscript", len) == 0)
825 *argt |= EX_KEEPSCRIPT;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200826 else if (STRNICMP(attr, "bar", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200827 *argt |= EX_TRLBAR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200828 else
829 {
830 int i;
831 char_u *val = NULL;
832 size_t vallen = 0;
833 size_t attrlen = len;
834
835 // Look for the attribute name - which is the part before any '='
836 for (i = 0; i < (int)len; ++i)
837 {
838 if (attr[i] == '=')
839 {
840 val = &attr[i + 1];
841 vallen = len - i - 1;
842 attrlen = i;
843 break;
844 }
845 }
846
847 if (STRNICMP(attr, "nargs", attrlen) == 0)
848 {
849 if (vallen == 1)
850 {
851 if (*val == '0')
852 // Do nothing - this is the default
853 ;
854 else if (*val == '1')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200855 *argt |= (EX_EXTRA | EX_NOSPC | EX_NEEDARG);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200856 else if (*val == '*')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200857 *argt |= EX_EXTRA;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200858 else if (*val == '?')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200859 *argt |= (EX_EXTRA | EX_NOSPC);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200860 else if (*val == '+')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200861 *argt |= (EX_EXTRA | EX_NEEDARG);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200862 else
863 goto wrong_nargs;
864 }
865 else
866 {
867wrong_nargs:
Bram Moolenaar1a992222021-12-31 17:25:48 +0000868 emsg(_(e_invalid_number_of_arguments));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200869 return FAIL;
870 }
871 }
872 else if (STRNICMP(attr, "range", attrlen) == 0)
873 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200874 *argt |= EX_RANGE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200875 if (vallen == 1 && *val == '%')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200876 *argt |= EX_DFLALL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200877 else if (val != NULL)
878 {
879 p = val;
880 if (*def >= 0)
881 {
882two_count:
Bram Moolenaar1a992222021-12-31 17:25:48 +0000883 emsg(_(e_count_cannot_be_specified_twice));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200884 return FAIL;
885 }
886
887 *def = getdigits(&p);
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200888 *argt |= EX_ZEROR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200889
890 if (p != val + vallen || vallen == 0)
891 {
892invalid_count:
Bram Moolenaar1a992222021-12-31 17:25:48 +0000893 emsg(_(e_invalid_default_value_for_count));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200894 return FAIL;
895 }
896 }
Bram Moolenaarb7316892019-05-01 18:08:42 +0200897 // default for -range is using buffer lines
898 if (*addr_type_arg == ADDR_NONE)
899 *addr_type_arg = ADDR_LINES;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200900 }
901 else if (STRNICMP(attr, "count", attrlen) == 0)
902 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200903 *argt |= (EX_COUNT | EX_ZEROR | EX_RANGE);
Bram Moolenaarb7316892019-05-01 18:08:42 +0200904 // default for -count is using any number
905 if (*addr_type_arg == ADDR_NONE)
906 *addr_type_arg = ADDR_OTHER;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200907
908 if (val != NULL)
909 {
910 p = val;
911 if (*def >= 0)
912 goto two_count;
913
914 *def = getdigits(&p);
915
916 if (p != val + vallen)
917 goto invalid_count;
918 }
919
920 if (*def < 0)
921 *def = 0;
922 }
923 else if (STRNICMP(attr, "complete", attrlen) == 0)
924 {
925 if (val == NULL)
926 {
Bram Moolenaar1a992222021-12-31 17:25:48 +0000927 semsg(_(e_argument_required_for_str), "-complete");
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200928 return FAIL;
929 }
930
Bram Moolenaar52111f82019-04-29 21:30:45 +0200931 if (parse_compl_arg(val, (int)vallen, complp, argt, compl_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200932 == FAIL)
933 return FAIL;
934 }
935 else if (STRNICMP(attr, "addr", attrlen) == 0)
936 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200937 *argt |= EX_RANGE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200938 if (val == NULL)
939 {
Bram Moolenaar1a992222021-12-31 17:25:48 +0000940 semsg(_(e_argument_required_for_str), "-addr");
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200941 return FAIL;
942 }
Bram Moolenaare4f5f3a2019-05-04 14:05:08 +0200943 if (parse_addr_type_arg(val, (int)vallen, addr_type_arg) == FAIL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200944 return FAIL;
Bram Moolenaare4f5f3a2019-05-04 14:05:08 +0200945 if (*addr_type_arg != ADDR_LINES)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200946 *argt |= EX_ZEROR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200947 }
948 else
949 {
950 char_u ch = attr[len];
951 attr[len] = '\0';
Bram Moolenaar1a992222021-12-31 17:25:48 +0000952 semsg(_(e_invalid_attribute_str), attr);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200953 attr[len] = ch;
954 return FAIL;
955 }
956 }
957
958 return OK;
959}
960
961/*
962 * Add a user command to the list or replace an existing one.
963 */
964 static int
965uc_add_command(
966 char_u *name,
967 size_t name_len,
968 char_u *rep,
969 long argt,
970 long def,
971 int flags,
972 int compl,
973 char_u *compl_arg UNUSED,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200974 cmd_addr_T addr_type,
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200975 int force)
976{
977 ucmd_T *cmd = NULL;
978 char_u *p;
979 int i;
980 int cmp = 1;
981 char_u *rep_buf = NULL;
982 garray_T *gap;
983
Bram Moolenaar459fd782019-10-13 16:43:39 +0200984 replace_termcodes(rep, &rep_buf, 0, NULL);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200985 if (rep_buf == NULL)
986 {
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +0200987 // can't replace termcodes - try using the string as is
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200988 rep_buf = vim_strsave(rep);
989
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +0200990 // give up if out of memory
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200991 if (rep_buf == NULL)
992 return FAIL;
993 }
994
995 // get address of growarray: global or in curbuf
996 if (flags & UC_BUFFER)
997 {
998 gap = &curbuf->b_ucmds;
999 if (gap->ga_itemsize == 0)
Bram Moolenaar04935fb2022-01-08 16:19:22 +00001000 ga_init2(gap, sizeof(ucmd_T), 4);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001001 }
1002 else
1003 gap = &ucmds;
1004
1005 // Search for the command in the already defined commands.
1006 for (i = 0; i < gap->ga_len; ++i)
1007 {
1008 size_t len;
1009
1010 cmd = USER_CMD_GA(gap, i);
1011 len = STRLEN(cmd->uc_name);
1012 cmp = STRNCMP(name, cmd->uc_name, name_len);
1013 if (cmp == 0)
1014 {
1015 if (name_len < len)
1016 cmp = -1;
1017 else if (name_len > len)
1018 cmp = 1;
1019 }
1020
1021 if (cmp == 0)
1022 {
1023 // Command can be replaced with "command!" and when sourcing the
1024 // same script again, but only once.
1025 if (!force
1026#ifdef FEAT_EVAL
1027 && (cmd->uc_script_ctx.sc_sid != current_sctx.sc_sid
1028 || cmd->uc_script_ctx.sc_seq == current_sctx.sc_seq)
1029#endif
1030 )
1031 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001032 semsg(_(e_command_already_exists_add_bang_to_replace_it_str),
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001033 name);
1034 goto fail;
1035 }
1036
1037 VIM_CLEAR(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001038#if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001039 VIM_CLEAR(cmd->uc_compl_arg);
1040#endif
1041 break;
1042 }
1043
1044 // Stop as soon as we pass the name to add
1045 if (cmp < 0)
1046 break;
1047 }
1048
1049 // Extend the array unless we're replacing an existing command
1050 if (cmp != 0)
1051 {
1052 if (ga_grow(gap, 1) != OK)
1053 goto fail;
Bram Moolenaar71ccd032020-06-12 22:59:11 +02001054 if ((p = vim_strnsave(name, name_len)) == NULL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001055 goto fail;
1056
1057 cmd = USER_CMD_GA(gap, i);
1058 mch_memmove(cmd + 1, cmd, (gap->ga_len - i) * sizeof(ucmd_T));
1059
1060 ++gap->ga_len;
1061
1062 cmd->uc_name = p;
1063 }
1064
1065 cmd->uc_rep = rep_buf;
1066 cmd->uc_argt = argt;
1067 cmd->uc_def = def;
1068 cmd->uc_compl = compl;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001069 cmd->uc_script_ctx = current_sctx;
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001070 if (flags & UC_VIM9)
1071 cmd->uc_script_ctx.sc_version = SCRIPT_VERSION_VIM9;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00001072 cmd->uc_flags = flags & UC_VIM9;
Bram Moolenaar9b8d6222020-12-28 18:26:00 +01001073#ifdef FEAT_EVAL
Bram Moolenaar1a47ae32019-12-29 23:04:25 +01001074 cmd->uc_script_ctx.sc_lnum += SOURCING_LNUM;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001075 cmd->uc_compl_arg = compl_arg;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001076#endif
1077 cmd->uc_addr_type = addr_type;
1078
1079 return OK;
1080
1081fail:
1082 vim_free(rep_buf);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001083#if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001084 vim_free(compl_arg);
1085#endif
1086 return FAIL;
1087}
1088
1089/*
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001090 * If "p" starts with "{" then read a block of commands until "}".
1091 * Used for ":command" and ":autocmd".
1092 */
1093 char_u *
1094may_get_cmd_block(exarg_T *eap, char_u *p, char_u **tofree, int *flags)
1095{
1096 char_u *retp = p;
1097
1098 if (*p == '{' && ends_excmd2(eap->arg, skipwhite(p + 1))
1099 && eap->getline != NULL)
1100 {
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001101 garray_T ga;
1102 char_u *line = NULL;
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001103
1104 ga_init2(&ga, sizeof(char_u *), 10);
Bram Moolenaar9f1a39a2022-01-08 15:39:39 +00001105 if (ga_copy_string(&ga, p) == FAIL)
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001106 return retp;
1107
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001108 // If the argument ends in "}" it must have been concatenated already
1109 // for ISN_EXEC.
1110 if (p[STRLEN(p) - 1] != '}')
1111 // Read lines between '{' and '}'. Does not support nesting or
1112 // here-doc constructs.
1113 for (;;)
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001114 {
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001115 vim_free(line);
1116 if ((line = eap->getline(':', eap->cookie,
1117 0, GETLINE_CONCAT_CONTBAR)) == NULL)
1118 {
1119 emsg(_(e_missing_rcurly));
1120 break;
1121 }
Bram Moolenaar9f1a39a2022-01-08 15:39:39 +00001122 if (ga_copy_string(&ga, line) == FAIL)
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001123 break;
1124 if (*skipwhite(line) == '}')
1125 break;
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001126 }
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001127 vim_free(line);
1128 retp = *tofree = ga_concat_strings(&ga, "\n");
1129 ga_clear_strings(&ga);
1130 *flags |= UC_VIM9;
1131 }
1132 return retp;
1133}
1134
1135/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001136 * ":command ..." implementation
1137 */
1138 void
1139ex_command(exarg_T *eap)
1140{
Bram Moolenaarb7316892019-05-01 18:08:42 +02001141 char_u *name;
1142 char_u *end;
1143 char_u *p;
1144 long argt = 0;
1145 long def = -1;
1146 int flags = 0;
1147 int compl = EXPAND_NOTHING;
1148 char_u *compl_arg = NULL;
1149 cmd_addr_T addr_type_arg = ADDR_NONE;
1150 int has_attr = (eap->arg[0] == '-');
1151 int name_len;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001152
1153 p = eap->arg;
1154
1155 // Check for attributes
1156 while (*p == '-')
1157 {
1158 ++p;
1159 end = skiptowhite(p);
1160 if (uc_scan_attr(p, end - p, &argt, &def, &flags, &compl,
1161 &compl_arg, &addr_type_arg) == FAIL)
1162 return;
1163 p = skipwhite(end);
1164 }
1165
1166 // Get the name (if any) and skip to the following argument
1167 name = p;
1168 if (ASCII_ISALPHA(*p))
1169 while (ASCII_ISALNUM(*p))
1170 ++p;
Bram Moolenaara72cfb82020-04-23 17:07:30 +02001171 if (!ends_excmd2(eap->arg, p) && !VIM_ISWHITE(*p))
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001172 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001173 emsg(_(e_invalid_command_name));
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001174 return;
1175 }
1176 end = p;
1177 name_len = (int)(end - name);
1178
1179 // If there is nothing after the name, and no attributes were specified,
1180 // we are listing commands
1181 p = skipwhite(end);
Bram Moolenaara72cfb82020-04-23 17:07:30 +02001182 if (!has_attr && ends_excmd2(eap->arg, p))
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001183 uc_list(name, end - name);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001184 else if (!ASCII_ISUPPER(*name))
Bram Moolenaar1a992222021-12-31 17:25:48 +00001185 emsg(_(e_user_defined_commands_must_start_with_an_uppercase_letter));
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001186 else if ((name_len == 1 && *name == 'X')
1187 || (name_len <= 4
1188 && STRNCMP(name, "Next", name_len > 4 ? 4 : name_len) == 0))
Bram Moolenaar9d00e4a2022-01-05 17:49:15 +00001189 emsg(_(e_reserved_name_cannot_be_used_for_user_defined_command));
Martin Tournoijde69a732021-07-11 14:28:25 +02001190 else if (compl > 0 && (argt & EX_EXTRA) == 0)
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001191 {
1192 // Some plugins rely on silently ignoring the mistake, only make this
1193 // an error in Vim9 script.
1194 if (in_vim9script())
Bram Moolenaar41a34852021-08-04 16:09:24 +02001195 emsg(_(e_complete_used_without_allowing_arguments));
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001196 else
1197 give_warning_with_source(
Bram Moolenaar41a34852021-08-04 16:09:24 +02001198 (char_u *)_(e_complete_used_without_allowing_arguments),
1199 TRUE, TRUE);
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001200 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001201 else
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001202 {
1203 char_u *tofree = NULL;
1204
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001205 p = may_get_cmd_block(eap, p, &tofree, &flags);
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001206
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001207 uc_add_command(name, end - name, p, argt, def, flags, compl, compl_arg,
1208 addr_type_arg, eap->forceit);
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001209 vim_free(tofree);
1210 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001211}
1212
1213/*
1214 * ":comclear" implementation
1215 * Clear all user commands, global and for current buffer.
1216 */
1217 void
1218ex_comclear(exarg_T *eap UNUSED)
1219{
1220 uc_clear(&ucmds);
Bram Moolenaare5c83282019-05-03 23:15:37 +02001221 if (curbuf != NULL)
1222 uc_clear(&curbuf->b_ucmds);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001223}
1224
1225/*
1226 * Clear all user commands for "gap".
1227 */
1228 void
1229uc_clear(garray_T *gap)
1230{
1231 int i;
1232 ucmd_T *cmd;
1233
1234 for (i = 0; i < gap->ga_len; ++i)
1235 {
1236 cmd = USER_CMD_GA(gap, i);
1237 vim_free(cmd->uc_name);
1238 vim_free(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001239# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001240 vim_free(cmd->uc_compl_arg);
1241# endif
1242 }
1243 ga_clear(gap);
1244}
1245
1246/*
1247 * ":delcommand" implementation
1248 */
1249 void
1250ex_delcommand(exarg_T *eap)
1251{
1252 int i = 0;
1253 ucmd_T *cmd = NULL;
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001254 int res = -1;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001255 garray_T *gap;
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001256 char_u *arg = eap->arg;
1257 int buffer_only = FALSE;
1258
1259 if (STRNCMP(arg, "-buffer", 7) == 0 && VIM_ISWHITE(arg[7]))
1260 {
1261 buffer_only = TRUE;
1262 arg = skipwhite(arg + 7);
1263 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001264
1265 gap = &curbuf->b_ucmds;
1266 for (;;)
1267 {
1268 for (i = 0; i < gap->ga_len; ++i)
1269 {
1270 cmd = USER_CMD_GA(gap, i);
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001271 res = STRCMP(arg, cmd->uc_name);
1272 if (res <= 0)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001273 break;
1274 }
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001275 if (gap == &ucmds || res == 0 || buffer_only)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001276 break;
1277 gap = &ucmds;
1278 }
1279
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001280 if (res != 0)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001281 {
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001282 semsg(_(buffer_only
1283 ? e_no_such_user_defined_command_in_current_buffer_str
1284 : e_no_such_user_defined_command_str), arg);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001285 return;
1286 }
1287
1288 vim_free(cmd->uc_name);
1289 vim_free(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001290# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001291 vim_free(cmd->uc_compl_arg);
1292# endif
1293
1294 --gap->ga_len;
1295
1296 if (i < gap->ga_len)
1297 mch_memmove(cmd, cmd + 1, (gap->ga_len - i) * sizeof(ucmd_T));
1298}
1299
1300/*
1301 * Split and quote args for <f-args>.
1302 */
1303 static char_u *
1304uc_split_args(char_u *arg, size_t *lenp)
1305{
1306 char_u *buf;
1307 char_u *p;
1308 char_u *q;
1309 int len;
1310
1311 // Precalculate length
1312 p = arg;
1313 len = 2; // Initial and final quotes
1314
1315 while (*p)
1316 {
1317 if (p[0] == '\\' && p[1] == '\\')
1318 {
1319 len += 2;
1320 p += 2;
1321 }
1322 else if (p[0] == '\\' && VIM_ISWHITE(p[1]))
1323 {
1324 len += 1;
1325 p += 2;
1326 }
1327 else if (*p == '\\' || *p == '"')
1328 {
1329 len += 2;
1330 p += 1;
1331 }
1332 else if (VIM_ISWHITE(*p))
1333 {
1334 p = skipwhite(p);
1335 if (*p == NUL)
1336 break;
Bram Moolenaar20d89e02020-10-20 23:11:33 +02001337 len += 4; // ", "
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001338 }
1339 else
1340 {
1341 int charlen = (*mb_ptr2len)(p);
1342
1343 len += charlen;
1344 p += charlen;
1345 }
1346 }
1347
1348 buf = alloc(len + 1);
1349 if (buf == NULL)
1350 {
1351 *lenp = 0;
1352 return buf;
1353 }
1354
1355 p = arg;
1356 q = buf;
1357 *q++ = '"';
1358 while (*p)
1359 {
1360 if (p[0] == '\\' && p[1] == '\\')
1361 {
1362 *q++ = '\\';
1363 *q++ = '\\';
1364 p += 2;
1365 }
1366 else if (p[0] == '\\' && VIM_ISWHITE(p[1]))
1367 {
1368 *q++ = p[1];
1369 p += 2;
1370 }
1371 else if (*p == '\\' || *p == '"')
1372 {
1373 *q++ = '\\';
1374 *q++ = *p++;
1375 }
1376 else if (VIM_ISWHITE(*p))
1377 {
1378 p = skipwhite(p);
1379 if (*p == NUL)
1380 break;
1381 *q++ = '"';
1382 *q++ = ',';
Bram Moolenaar20d89e02020-10-20 23:11:33 +02001383 *q++ = ' ';
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001384 *q++ = '"';
1385 }
1386 else
1387 {
1388 MB_COPY_CHAR(p, q);
1389 }
1390 }
1391 *q++ = '"';
1392 *q = 0;
1393
1394 *lenp = len;
1395 return buf;
1396}
1397
1398 static size_t
1399add_cmd_modifier(char_u *buf, char *mod_str, int *multi_mods)
1400{
1401 size_t result;
1402
1403 result = STRLEN(mod_str);
1404 if (*multi_mods)
1405 result += 1;
1406 if (buf != NULL)
1407 {
1408 if (*multi_mods)
1409 STRCAT(buf, " ");
1410 STRCAT(buf, mod_str);
1411 }
1412
1413 *multi_mods = 1;
1414
1415 return result;
1416}
1417
1418/*
Bram Moolenaar02194d22020-10-24 23:08:38 +02001419 * Add modifiers from "cmod->cmod_split" to "buf". Set "multi_mods" when one
Bram Moolenaare1004402020-10-24 20:49:43 +02001420 * was added. Return the number of bytes added.
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001421 */
1422 size_t
Bram Moolenaar02194d22020-10-24 23:08:38 +02001423add_win_cmd_modifers(char_u *buf, cmdmod_T *cmod, int *multi_mods)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001424{
1425 size_t result = 0;
1426
1427 // :aboveleft and :leftabove
Bram Moolenaar02194d22020-10-24 23:08:38 +02001428 if (cmod->cmod_split & WSP_ABOVE)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001429 result += add_cmd_modifier(buf, "aboveleft", multi_mods);
1430 // :belowright and :rightbelow
Bram Moolenaar02194d22020-10-24 23:08:38 +02001431 if (cmod->cmod_split & WSP_BELOW)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001432 result += add_cmd_modifier(buf, "belowright", multi_mods);
1433 // :botright
Bram Moolenaar02194d22020-10-24 23:08:38 +02001434 if (cmod->cmod_split & WSP_BOT)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001435 result += add_cmd_modifier(buf, "botright", multi_mods);
1436
1437 // :tab
Bram Moolenaar02194d22020-10-24 23:08:38 +02001438 if (cmod->cmod_tab > 0)
zeertzjq208567e2022-10-18 13:11:21 +01001439 {
1440 int tabnr = cmod->cmod_tab - 1;
1441
1442 if (tabnr == tabpage_index(curtab))
1443 {
1444 // For compatibility, don't add a tabpage number if it is the same
1445 // as the default number for :tab.
1446 result += add_cmd_modifier(buf, "tab", multi_mods);
1447 }
1448 else
1449 {
1450 char tab_buf[NUMBUFLEN + 3];
1451
1452 sprintf(tab_buf, "%dtab", tabnr);
1453 result += add_cmd_modifier(buf, tab_buf, multi_mods);
1454 }
1455 }
1456
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001457 // :topleft
Bram Moolenaar02194d22020-10-24 23:08:38 +02001458 if (cmod->cmod_split & WSP_TOP)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001459 result += add_cmd_modifier(buf, "topleft", multi_mods);
1460 // :vertical
Bram Moolenaar02194d22020-10-24 23:08:38 +02001461 if (cmod->cmod_split & WSP_VERT)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001462 result += add_cmd_modifier(buf, "vertical", multi_mods);
zeertzjqd3de1782022-09-01 12:58:52 +01001463 // :horizontal
1464 if (cmod->cmod_split & WSP_HOR)
1465 result += add_cmd_modifier(buf, "horizontal", multi_mods);
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001466 return result;
1467}
1468
1469/*
Bram Moolenaar02194d22020-10-24 23:08:38 +02001470 * Generate text for the "cmod" command modifiers.
1471 * If "buf" is NULL just return the length.
1472 */
Bram Moolenaara360dbe2020-10-26 18:46:53 +01001473 size_t
Bram Moolenaar02194d22020-10-24 23:08:38 +02001474produce_cmdmods(char_u *buf, cmdmod_T *cmod, int quote)
1475{
Bram Moolenaara360dbe2020-10-26 18:46:53 +01001476 size_t result = 0;
Bram Moolenaar02194d22020-10-24 23:08:38 +02001477 int multi_mods = 0;
1478 int i;
1479 typedef struct {
1480 int flag;
1481 char *name;
1482 } mod_entry_T;
1483 static mod_entry_T mod_entries[] = {
1484#ifdef FEAT_BROWSE_CMD
1485 {CMOD_BROWSE, "browse"},
1486#endif
1487#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
1488 {CMOD_CONFIRM, "confirm"},
1489#endif
1490 {CMOD_HIDE, "hide"},
1491 {CMOD_KEEPALT, "keepalt"},
1492 {CMOD_KEEPJUMPS, "keepjumps"},
1493 {CMOD_KEEPMARKS, "keepmarks"},
1494 {CMOD_KEEPPATTERNS, "keeppatterns"},
1495 {CMOD_LOCKMARKS, "lockmarks"},
1496 {CMOD_NOSWAPFILE, "noswapfile"},
1497 {CMOD_UNSILENT, "unsilent"},
1498 {CMOD_NOAUTOCMD, "noautocmd"},
1499#ifdef HAVE_SANDBOX
1500 {CMOD_SANDBOX, "sandbox"},
1501#endif
Bram Moolenaarb579f6e2021-12-04 11:57:00 +00001502 {CMOD_LEGACY, "legacy"},
Bram Moolenaar02194d22020-10-24 23:08:38 +02001503 {0, NULL}
1504 };
1505
1506 result = quote ? 2 : 0;
1507 if (buf != NULL)
1508 {
1509 if (quote)
1510 *buf++ = '"';
1511 *buf = '\0';
1512 }
1513
1514 // the modifiers that are simple flags
1515 for (i = 0; mod_entries[i].name != NULL; ++i)
1516 if (cmod->cmod_flags & mod_entries[i].flag)
1517 result += add_cmd_modifier(buf, mod_entries[i].name, &multi_mods);
1518
1519 // :silent
1520 if (cmod->cmod_flags & CMOD_SILENT)
1521 result += add_cmd_modifier(buf,
1522 (cmod->cmod_flags & CMOD_ERRSILENT) ? "silent!"
1523 : "silent", &multi_mods);
1524 // :verbose
zeertzjq9359e8a2022-07-03 13:16:09 +01001525 if (cmod->cmod_verbose > 0)
1526 {
1527 int verbose_value = cmod->cmod_verbose - 1;
1528
1529 if (verbose_value == 1)
1530 result += add_cmd_modifier(buf, "verbose", &multi_mods);
1531 else
1532 {
1533 char verbose_buf[NUMBUFLEN];
1534
1535 sprintf(verbose_buf, "%dverbose", verbose_value);
1536 result += add_cmd_modifier(buf, verbose_buf, &multi_mods);
1537 }
1538 }
Bram Moolenaar02194d22020-10-24 23:08:38 +02001539 // flags from cmod->cmod_split
1540 result += add_win_cmd_modifers(buf, cmod, &multi_mods);
zeertzjq9359e8a2022-07-03 13:16:09 +01001541
Bram Moolenaar02194d22020-10-24 23:08:38 +02001542 if (quote && buf != NULL)
1543 {
1544 buf += result - 2;
1545 *buf = '"';
1546 }
1547 return result;
1548}
1549
1550/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001551 * Check for a <> code in a user command.
1552 * "code" points to the '<'. "len" the length of the <> (inclusive).
1553 * "buf" is where the result is to be added.
1554 * "split_buf" points to a buffer used for splitting, caller should free it.
1555 * "split_len" is the length of what "split_buf" contains.
1556 * Returns the length of the replacement, which has been added to "buf".
1557 * Returns -1 if there was no match, and only the "<" has been copied.
1558 */
1559 static size_t
1560uc_check_code(
1561 char_u *code,
1562 size_t len,
1563 char_u *buf,
1564 ucmd_T *cmd, // the user command we're expanding
1565 exarg_T *eap, // ex arguments
1566 char_u **split_buf,
1567 size_t *split_len)
1568{
1569 size_t result = 0;
1570 char_u *p = code + 1;
1571 size_t l = len - 2;
1572 int quote = 0;
1573 enum {
1574 ct_ARGS,
1575 ct_BANG,
1576 ct_COUNT,
1577 ct_LINE1,
1578 ct_LINE2,
1579 ct_RANGE,
1580 ct_MODS,
1581 ct_REGISTER,
1582 ct_LT,
1583 ct_NONE
1584 } type = ct_NONE;
1585
1586 if ((vim_strchr((char_u *)"qQfF", *p) != NULL) && p[1] == '-')
1587 {
1588 quote = (*p == 'q' || *p == 'Q') ? 1 : 2;
1589 p += 2;
1590 l -= 2;
1591 }
1592
1593 ++l;
1594 if (l <= 1)
1595 type = ct_NONE;
1596 else if (STRNICMP(p, "args>", l) == 0)
1597 type = ct_ARGS;
1598 else if (STRNICMP(p, "bang>", l) == 0)
1599 type = ct_BANG;
1600 else if (STRNICMP(p, "count>", l) == 0)
1601 type = ct_COUNT;
1602 else if (STRNICMP(p, "line1>", l) == 0)
1603 type = ct_LINE1;
1604 else if (STRNICMP(p, "line2>", l) == 0)
1605 type = ct_LINE2;
1606 else if (STRNICMP(p, "range>", l) == 0)
1607 type = ct_RANGE;
1608 else if (STRNICMP(p, "lt>", l) == 0)
1609 type = ct_LT;
1610 else if (STRNICMP(p, "reg>", l) == 0 || STRNICMP(p, "register>", l) == 0)
1611 type = ct_REGISTER;
1612 else if (STRNICMP(p, "mods>", l) == 0)
1613 type = ct_MODS;
1614
1615 switch (type)
1616 {
1617 case ct_ARGS:
1618 // Simple case first
1619 if (*eap->arg == NUL)
1620 {
1621 if (quote == 1)
1622 {
1623 result = 2;
1624 if (buf != NULL)
1625 STRCPY(buf, "''");
1626 }
1627 else
1628 result = 0;
1629 break;
1630 }
1631
1632 // When specified there is a single argument don't split it.
1633 // Works for ":Cmd %" when % is "a b c".
Bram Moolenaar8071cb22019-07-12 17:58:01 +02001634 if ((eap->argt & EX_NOSPC) && quote == 2)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001635 quote = 1;
1636
1637 switch (quote)
1638 {
1639 case 0: // No quoting, no splitting
1640 result = STRLEN(eap->arg);
1641 if (buf != NULL)
1642 STRCPY(buf, eap->arg);
1643 break;
1644 case 1: // Quote, but don't split
1645 result = STRLEN(eap->arg) + 2;
1646 for (p = eap->arg; *p; ++p)
1647 {
1648 if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2)
1649 // DBCS can contain \ in a trail byte, skip the
1650 // double-byte character.
1651 ++p;
1652 else
1653 if (*p == '\\' || *p == '"')
1654 ++result;
1655 }
1656
1657 if (buf != NULL)
1658 {
1659 *buf++ = '"';
1660 for (p = eap->arg; *p; ++p)
1661 {
1662 if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2)
1663 // DBCS can contain \ in a trail byte, copy the
1664 // double-byte character to avoid escaping.
1665 *buf++ = *p++;
1666 else
1667 if (*p == '\\' || *p == '"')
1668 *buf++ = '\\';
1669 *buf++ = *p;
1670 }
1671 *buf = '"';
1672 }
1673
1674 break;
1675 case 2: // Quote and split (<f-args>)
1676 // This is hard, so only do it once, and cache the result
1677 if (*split_buf == NULL)
1678 *split_buf = uc_split_args(eap->arg, split_len);
1679
1680 result = *split_len;
1681 if (buf != NULL && result != 0)
1682 STRCPY(buf, *split_buf);
1683
1684 break;
1685 }
1686 break;
1687
1688 case ct_BANG:
1689 result = eap->forceit ? 1 : 0;
1690 if (quote)
1691 result += 2;
1692 if (buf != NULL)
1693 {
1694 if (quote)
1695 *buf++ = '"';
1696 if (eap->forceit)
1697 *buf++ = '!';
1698 if (quote)
1699 *buf = '"';
1700 }
1701 break;
1702
1703 case ct_LINE1:
1704 case ct_LINE2:
1705 case ct_RANGE:
1706 case ct_COUNT:
1707 {
1708 char num_buf[20];
1709 long num = (type == ct_LINE1) ? eap->line1 :
1710 (type == ct_LINE2) ? eap->line2 :
1711 (type == ct_RANGE) ? eap->addr_count :
1712 (eap->addr_count > 0) ? eap->line2 : cmd->uc_def;
1713 size_t num_len;
1714
1715 sprintf(num_buf, "%ld", num);
1716 num_len = STRLEN(num_buf);
1717 result = num_len;
1718
1719 if (quote)
1720 result += 2;
1721
1722 if (buf != NULL)
1723 {
1724 if (quote)
1725 *buf++ = '"';
1726 STRCPY(buf, num_buf);
1727 buf += num_len;
1728 if (quote)
1729 *buf = '"';
1730 }
1731
1732 break;
1733 }
1734
1735 case ct_MODS:
1736 {
Bram Moolenaar02194d22020-10-24 23:08:38 +02001737 result = produce_cmdmods(buf, &cmdmod, quote);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001738 break;
1739 }
1740
1741 case ct_REGISTER:
1742 result = eap->regname ? 1 : 0;
1743 if (quote)
1744 result += 2;
1745 if (buf != NULL)
1746 {
1747 if (quote)
1748 *buf++ = '\'';
1749 if (eap->regname)
1750 *buf++ = eap->regname;
1751 if (quote)
1752 *buf = '\'';
1753 }
1754 break;
1755
1756 case ct_LT:
1757 result = 1;
1758 if (buf != NULL)
1759 *buf = '<';
1760 break;
1761
1762 default:
1763 // Not recognized: just copy the '<' and return -1.
1764 result = (size_t)-1;
1765 if (buf != NULL)
1766 *buf = '<';
1767 break;
1768 }
1769
1770 return result;
1771}
1772
1773/*
1774 * Execute a user defined command.
1775 */
1776 void
1777do_ucmd(exarg_T *eap)
1778{
1779 char_u *buf;
1780 char_u *p;
1781 char_u *q;
1782
1783 char_u *start;
1784 char_u *end = NULL;
1785 char_u *ksp;
1786 size_t len, totlen;
1787
1788 size_t split_len = 0;
1789 char_u *split_buf = NULL;
1790 ucmd_T *cmd;
Bram Moolenaar205f29c2021-12-10 21:46:09 +00001791 sctx_T save_current_sctx;
1792 int restore_current_sctx = FALSE;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00001793#ifdef FEAT_EVAL
1794 int restore_script_version = 0;
1795#endif
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001796
1797 if (eap->cmdidx == CMD_USER)
1798 cmd = USER_CMD(eap->useridx);
1799 else
1800 cmd = USER_CMD_GA(&curbuf->b_ucmds, eap->useridx);
1801
1802 /*
1803 * Replace <> in the command by the arguments.
1804 * First round: "buf" is NULL, compute length, allocate "buf".
1805 * Second round: copy result into "buf".
1806 */
1807 buf = NULL;
1808 for (;;)
1809 {
1810 p = cmd->uc_rep; // source
1811 q = buf; // destination
1812 totlen = 0;
1813
1814 for (;;)
1815 {
1816 start = vim_strchr(p, '<');
1817 if (start != NULL)
1818 end = vim_strchr(start + 1, '>');
1819 if (buf != NULL)
1820 {
1821 for (ksp = p; *ksp != NUL && *ksp != K_SPECIAL; ++ksp)
1822 ;
1823 if (*ksp == K_SPECIAL
1824 && (start == NULL || ksp < start || end == NULL)
1825 && ((ksp[1] == KS_SPECIAL && ksp[2] == KE_FILLER)
1826# ifdef FEAT_GUI
1827 || (ksp[1] == KS_EXTRA && ksp[2] == (int)KE_CSI)
1828# endif
1829 ))
1830 {
1831 // K_SPECIAL has been put in the buffer as K_SPECIAL
1832 // KS_SPECIAL KE_FILLER, like for mappings, but
1833 // do_cmdline() doesn't handle that, so convert it back.
1834 // Also change K_SPECIAL KS_EXTRA KE_CSI into CSI.
1835 len = ksp - p;
1836 if (len > 0)
1837 {
1838 mch_memmove(q, p, len);
1839 q += len;
1840 }
1841 *q++ = ksp[1] == KS_SPECIAL ? K_SPECIAL : CSI;
1842 p = ksp + 3;
1843 continue;
1844 }
1845 }
1846
1847 // break if no <item> is found
1848 if (start == NULL || end == NULL)
1849 break;
1850
1851 // Include the '>'
1852 ++end;
1853
1854 // Take everything up to the '<'
1855 len = start - p;
1856 if (buf == NULL)
1857 totlen += len;
1858 else
1859 {
1860 mch_memmove(q, p, len);
1861 q += len;
1862 }
1863
1864 len = uc_check_code(start, end - start, q, cmd, eap,
1865 &split_buf, &split_len);
1866 if (len == (size_t)-1)
1867 {
1868 // no match, continue after '<'
1869 p = start + 1;
1870 len = 1;
1871 }
1872 else
1873 p = end;
1874 if (buf == NULL)
1875 totlen += len;
1876 else
1877 q += len;
1878 }
1879 if (buf != NULL) // second time here, finished
1880 {
1881 STRCPY(q, p);
1882 break;
1883 }
1884
1885 totlen += STRLEN(p); // Add on the trailing characters
Bram Moolenaar964b3742019-05-24 18:54:09 +02001886 buf = alloc(totlen + 1);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001887 if (buf == NULL)
1888 {
1889 vim_free(split_buf);
1890 return;
1891 }
1892 }
1893
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001894 if ((cmd->uc_argt & EX_KEEPSCRIPT) == 0)
1895 {
Bram Moolenaar205f29c2021-12-10 21:46:09 +00001896 restore_current_sctx = TRUE;
1897 save_current_sctx = current_sctx;
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001898 current_sctx.sc_version = cmd->uc_script_ctx.sc_version;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001899#ifdef FEAT_EVAL
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001900 current_sctx.sc_sid = cmd->uc_script_ctx.sc_sid;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00001901 if (cmd->uc_flags & UC_VIM9)
1902 {
1903 // In a {} block variables use Vim9 script rules, even in a legacy
1904 // script.
1905 restore_script_version =
1906 SCRIPT_ITEM(current_sctx.sc_sid)->sn_version;
1907 SCRIPT_ITEM(current_sctx.sc_sid)->sn_version = SCRIPT_VERSION_VIM9;
1908 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001909#endif
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001910 }
Bram Moolenaar205f29c2021-12-10 21:46:09 +00001911
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001912 (void)do_cmdline(buf, eap->getline, eap->cookie,
1913 DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED);
Bram Moolenaar205f29c2021-12-10 21:46:09 +00001914
1915 // Careful: Do not use "cmd" here, it may have become invalid if a user
1916 // command was added.
1917 if (restore_current_sctx)
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00001918 {
1919#ifdef FEAT_EVAL
1920 if (restore_script_version != 0)
1921 SCRIPT_ITEM(current_sctx.sc_sid)->sn_version =
1922 restore_script_version;
1923#endif
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001924 current_sctx = save_current_sctx;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00001925 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001926 vim_free(buf);
1927 vim_free(split_buf);
1928}