blob: 0050647ab449d4f5420b7b373d06b00eb59cbe5f [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 Moolenaar9b8d6222020-12-28 18:26:00 +010025# ifdef FEAT_EVAL
Bram Moolenaarac9fb182019-04-27 13:04:13 +020026 char_u *uc_compl_arg; // completion argument if any
Bram Moolenaarac9fb182019-04-27 13:04:13 +020027# endif
28} ucmd_T;
29
30// List of all user commands.
31static garray_T ucmds = {0, 0, sizeof(ucmd_T), 4, NULL};
32
33#define USER_CMD(i) (&((ucmd_T *)(ucmds.ga_data))[i])
34#define USER_CMD_GA(gap, i) (&((ucmd_T *)((gap)->ga_data))[i])
35
36/*
37 * List of names for completion for ":command" with the EXPAND_ flag.
38 * Must be alphabetical for completion.
39 */
40static struct
41{
42 int expand;
43 char *name;
44} command_complete[] =
45{
46 {EXPAND_ARGLIST, "arglist"},
47 {EXPAND_AUGROUP, "augroup"},
48 {EXPAND_BEHAVE, "behave"},
49 {EXPAND_BUFFERS, "buffer"},
50 {EXPAND_COLORS, "color"},
51 {EXPAND_COMMANDS, "command"},
52 {EXPAND_COMPILER, "compiler"},
53#if defined(FEAT_CSCOPE)
54 {EXPAND_CSCOPE, "cscope"},
55#endif
Bram Moolenaar0a52df52019-08-18 22:26:31 +020056#if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +020057 {EXPAND_USER_DEFINED, "custom"},
58 {EXPAND_USER_LIST, "customlist"},
59#endif
Bram Moolenaarae7dba82019-12-29 13:56:33 +010060 {EXPAND_DIFF_BUFFERS, "diff_buffer"},
Bram Moolenaarac9fb182019-04-27 13:04:13 +020061 {EXPAND_DIRECTORIES, "dir"},
62 {EXPAND_ENV_VARS, "environment"},
63 {EXPAND_EVENTS, "event"},
64 {EXPAND_EXPRESSION, "expression"},
65 {EXPAND_FILES, "file"},
66 {EXPAND_FILES_IN_PATH, "file_in_path"},
67 {EXPAND_FILETYPE, "filetype"},
68 {EXPAND_FUNCTIONS, "function"},
69 {EXPAND_HELP, "help"},
70 {EXPAND_HIGHLIGHT, "highlight"},
Bram Moolenaarac9fb182019-04-27 13:04:13 +020071 {EXPAND_HISTORY, "history"},
Bram Moolenaarac9fb182019-04-27 13:04:13 +020072#if defined(HAVE_LOCALE_H) || defined(X_LOCALE)
73 {EXPAND_LOCALES, "locale"},
74#endif
75 {EXPAND_MAPCLEAR, "mapclear"},
76 {EXPAND_MAPPINGS, "mapping"},
77 {EXPAND_MENUS, "menu"},
78 {EXPAND_MESSAGES, "messages"},
79 {EXPAND_OWNSYNTAX, "syntax"},
80#if defined(FEAT_PROFILE)
81 {EXPAND_SYNTIME, "syntime"},
82#endif
83 {EXPAND_SETTINGS, "option"},
84 {EXPAND_PACKADD, "packadd"},
85 {EXPAND_SHELLCMD, "shellcmd"},
86#if defined(FEAT_SIGNS)
87 {EXPAND_SIGN, "sign"},
88#endif
89 {EXPAND_TAGS, "tag"},
90 {EXPAND_TAGS_LISTFILES, "tag_listfiles"},
91 {EXPAND_USER, "user"},
92 {EXPAND_USER_VARS, "var"},
93 {0, NULL}
94};
95
96/*
97 * List of names of address types. Must be alphabetical for completion.
98 */
99static struct
100{
Bram Moolenaarb7316892019-05-01 18:08:42 +0200101 cmd_addr_T expand;
102 char *name;
103 char *shortname;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200104} addr_type_complete[] =
105{
106 {ADDR_ARGUMENTS, "arguments", "arg"},
107 {ADDR_LINES, "lines", "line"},
108 {ADDR_LOADED_BUFFERS, "loaded_buffers", "load"},
109 {ADDR_TABS, "tabs", "tab"},
110 {ADDR_BUFFERS, "buffers", "buf"},
111 {ADDR_WINDOWS, "windows", "win"},
112 {ADDR_QUICKFIX, "quickfix", "qf"},
113 {ADDR_OTHER, "other", "?"},
Bram Moolenaarb7316892019-05-01 18:08:42 +0200114 {ADDR_NONE, NULL, NULL}
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200115};
116
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200117/*
118 * Search for a user command that matches "eap->cmd".
119 * Return cmdidx in "eap->cmdidx", flags in "eap->argt", idx in "eap->useridx".
120 * Return a pointer to just after the command.
121 * Return NULL if there is no matching command.
122 */
123 char_u *
124find_ucmd(
125 exarg_T *eap,
126 char_u *p, // end of the command (possibly including count)
127 int *full, // set to TRUE for a full match
128 expand_T *xp, // used for completion, NULL otherwise
Bram Moolenaar52111f82019-04-29 21:30:45 +0200129 int *complp UNUSED) // completion flags or NULL
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200130{
131 int len = (int)(p - eap->cmd);
132 int j, k, matchlen = 0;
133 ucmd_T *uc;
134 int found = FALSE;
135 int possible = FALSE;
136 char_u *cp, *np; // Point into typed cmd and test name
137 garray_T *gap;
138 int amb_local = FALSE; // Found ambiguous buffer-local command,
139 // only full match global is accepted.
140
141 /*
142 * Look for buffer-local user commands first, then global ones.
143 */
144 gap = &curbuf->b_ucmds;
145 for (;;)
146 {
147 for (j = 0; j < gap->ga_len; ++j)
148 {
149 uc = USER_CMD_GA(gap, j);
150 cp = eap->cmd;
151 np = uc->uc_name;
152 k = 0;
153 while (k < len && *np != NUL && *cp++ == *np++)
154 k++;
155 if (k == len || (*np == NUL && vim_isdigit(eap->cmd[k])))
156 {
157 // If finding a second match, the command is ambiguous. But
158 // not if a buffer-local command wasn't a full match and a
159 // global command is a full match.
160 if (k == len && found && *np != NUL)
161 {
162 if (gap == &ucmds)
163 return NULL;
164 amb_local = TRUE;
165 }
166
167 if (!found || (k == len && *np == NUL))
168 {
169 // If we matched up to a digit, then there could
170 // be another command including the digit that we
171 // should use instead.
172 if (k == len)
173 found = TRUE;
174 else
175 possible = TRUE;
176
177 if (gap == &ucmds)
178 eap->cmdidx = CMD_USER;
179 else
180 eap->cmdidx = CMD_USER_BUF;
181 eap->argt = (long)uc->uc_argt;
182 eap->useridx = j;
183 eap->addr_type = uc->uc_addr_type;
184
Bram Moolenaar52111f82019-04-29 21:30:45 +0200185 if (complp != NULL)
186 *complp = uc->uc_compl;
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200187# ifdef FEAT_EVAL
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200188 if (xp != NULL)
189 {
190 xp->xp_arg = uc->uc_compl_arg;
191 xp->xp_script_ctx = uc->uc_script_ctx;
Bram Moolenaar1a47ae32019-12-29 23:04:25 +0100192 xp->xp_script_ctx.sc_lnum += SOURCING_LNUM;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200193 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200194# endif
195 // Do not search for further abbreviations
196 // if this is an exact match.
197 matchlen = k;
198 if (k == len && *np == NUL)
199 {
200 if (full != NULL)
201 *full = TRUE;
202 amb_local = FALSE;
203 break;
204 }
205 }
206 }
207 }
208
209 // Stop if we found a full match or searched all.
210 if (j < gap->ga_len || gap == &ucmds)
211 break;
212 gap = &ucmds;
213 }
214
215 // Only found ambiguous matches.
216 if (amb_local)
217 {
218 if (xp != NULL)
219 xp->xp_context = EXPAND_UNSUCCESSFUL;
220 return NULL;
221 }
222
223 // The match we found may be followed immediately by a number. Move "p"
224 // back to point to it.
225 if (found || possible)
226 return p + (matchlen - len);
227 return p;
228}
229
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200230 char_u *
231set_context_in_user_cmd(expand_T *xp, char_u *arg_in)
232{
233 char_u *arg = arg_in;
234 char_u *p;
235
236 // Check for attributes
237 while (*arg == '-')
238 {
239 arg++; // Skip "-"
240 p = skiptowhite(arg);
241 if (*p == NUL)
242 {
243 // Cursor is still in the attribute
244 p = vim_strchr(arg, '=');
245 if (p == NULL)
246 {
247 // No "=", so complete attribute names
248 xp->xp_context = EXPAND_USER_CMD_FLAGS;
249 xp->xp_pattern = arg;
250 return NULL;
251 }
252
253 // For the -complete, -nargs and -addr attributes, we complete
254 // their arguments as well.
255 if (STRNICMP(arg, "complete", p - arg) == 0)
256 {
257 xp->xp_context = EXPAND_USER_COMPLETE;
258 xp->xp_pattern = p + 1;
259 return NULL;
260 }
261 else if (STRNICMP(arg, "nargs", p - arg) == 0)
262 {
263 xp->xp_context = EXPAND_USER_NARGS;
264 xp->xp_pattern = p + 1;
265 return NULL;
266 }
267 else if (STRNICMP(arg, "addr", p - arg) == 0)
268 {
269 xp->xp_context = EXPAND_USER_ADDR_TYPE;
270 xp->xp_pattern = p + 1;
271 return NULL;
272 }
273 return NULL;
274 }
275 arg = skipwhite(p);
276 }
277
278 // After the attributes comes the new command name
279 p = skiptowhite(arg);
280 if (*p == NUL)
281 {
282 xp->xp_context = EXPAND_USER_COMMANDS;
283 xp->xp_pattern = arg;
284 return NULL;
285 }
286
287 // And finally comes a normal command
288 return skipwhite(p);
289}
290
291 char_u *
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200292expand_user_command_name(int idx)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200293{
294 return get_user_commands(NULL, idx - (int)CMD_SIZE);
295}
296
297/*
298 * Function given to ExpandGeneric() to obtain the list of user command names.
299 */
300 char_u *
301get_user_commands(expand_T *xp UNUSED, int idx)
302{
Bram Moolenaarf03e3282019-07-22 21:55:18 +0200303 // In cmdwin, the alternative buffer should be used.
304 buf_T *buf =
305#ifdef FEAT_CMDWIN
306 (cmdwin_type != 0 && get_cmdline_type() == NUL) ? prevwin->w_buffer :
307#endif
308 curbuf;
309
310 if (idx < buf->b_ucmds.ga_len)
311 return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name;
312 idx -= buf->b_ucmds.ga_len;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200313 if (idx < ucmds.ga_len)
314 return USER_CMD(idx)->uc_name;
315 return NULL;
316}
317
318/*
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200319 * Get the name of user command "idx". "cmdidx" can be CMD_USER or
320 * CMD_USER_BUF.
321 * Returns NULL if the command is not found.
322 */
323 char_u *
324get_user_command_name(int idx, int cmdidx)
325{
326 if (cmdidx == CMD_USER && idx < ucmds.ga_len)
327 return USER_CMD(idx)->uc_name;
328 if (cmdidx == CMD_USER_BUF)
329 {
330 // In cmdwin, the alternative buffer should be used.
331 buf_T *buf =
332#ifdef FEAT_CMDWIN
333 (cmdwin_type != 0 && get_cmdline_type() == NUL)
334 ? prevwin->w_buffer :
335#endif
336 curbuf;
337
338 if (idx < buf->b_ucmds.ga_len)
339 return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name;
340 }
341 return NULL;
342}
343
344/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200345 * Function given to ExpandGeneric() to obtain the list of user address type
346 * names.
347 */
348 char_u *
349get_user_cmd_addr_type(expand_T *xp UNUSED, int idx)
350{
351 return (char_u *)addr_type_complete[idx].name;
352}
353
354/*
355 * Function given to ExpandGeneric() to obtain the list of user command
356 * attributes.
357 */
358 char_u *
359get_user_cmd_flags(expand_T *xp UNUSED, int idx)
360{
361 static char *user_cmd_flags[] = {
362 "addr", "bang", "bar", "buffer", "complete",
Bram Moolenaar58ef8a32021-11-12 11:25:11 +0000363 "count", "nargs", "range", "register", "keepscript"
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200364 };
365
K.Takataeeec2542021-06-02 13:28:16 +0200366 if (idx >= (int)ARRAY_LENGTH(user_cmd_flags))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200367 return NULL;
368 return (char_u *)user_cmd_flags[idx];
369}
370
371/*
372 * Function given to ExpandGeneric() to obtain the list of values for -nargs.
373 */
374 char_u *
375get_user_cmd_nargs(expand_T *xp UNUSED, int idx)
376{
377 static char *user_cmd_nargs[] = {"0", "1", "*", "?", "+"};
378
K.Takataeeec2542021-06-02 13:28:16 +0200379 if (idx >= (int)ARRAY_LENGTH(user_cmd_nargs))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200380 return NULL;
381 return (char_u *)user_cmd_nargs[idx];
382}
383
384/*
385 * Function given to ExpandGeneric() to obtain the list of values for
386 * -complete.
387 */
388 char_u *
389get_user_cmd_complete(expand_T *xp UNUSED, int idx)
390{
391 return (char_u *)command_complete[idx].name;
392}
393
394 int
395cmdcomplete_str_to_type(char_u *complete_str)
396{
397 int i;
398
399 for (i = 0; command_complete[i].expand != 0; ++i)
400 if (STRCMP(complete_str, command_complete[i].name) == 0)
401 return command_complete[i].expand;
402
403 return EXPAND_NOTHING;
404}
405
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200406/*
407 * List user commands starting with "name[name_len]".
408 */
409 static void
410uc_list(char_u *name, size_t name_len)
411{
412 int i, j;
413 int found = FALSE;
414 ucmd_T *cmd;
415 int len;
416 int over;
417 long a;
418 garray_T *gap;
419
Bram Moolenaare38eab22019-12-05 21:50:01 +0100420 // In cmdwin, the alternative buffer should be used.
Bram Moolenaarf03e3282019-07-22 21:55:18 +0200421 gap =
422#ifdef FEAT_CMDWIN
423 (cmdwin_type != 0 && get_cmdline_type() == NUL) ?
424 &prevwin->w_buffer->b_ucmds :
425#endif
426 &curbuf->b_ucmds;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200427 for (;;)
428 {
429 for (i = 0; i < gap->ga_len; ++i)
430 {
431 cmd = USER_CMD_GA(gap, i);
432 a = (long)cmd->uc_argt;
433
434 // Skip commands which don't match the requested prefix and
435 // commands filtered out.
436 if (STRNCMP(name, cmd->uc_name, name_len) != 0
437 || message_filtered(cmd->uc_name))
438 continue;
439
440 // Put out the title first time
441 if (!found)
442 msg_puts_title(_("\n Name Args Address Complete Definition"));
443 found = TRUE;
444 msg_putchar('\n');
445 if (got_int)
446 break;
447
448 // Special cases
449 len = 4;
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200450 if (a & EX_BANG)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200451 {
452 msg_putchar('!');
453 --len;
454 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200455 if (a & EX_REGSTR)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200456 {
457 msg_putchar('"');
458 --len;
459 }
460 if (gap != &ucmds)
461 {
462 msg_putchar('b');
463 --len;
464 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200465 if (a & EX_TRLBAR)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200466 {
467 msg_putchar('|');
468 --len;
469 }
470 while (len-- > 0)
471 msg_putchar(' ');
472
473 msg_outtrans_attr(cmd->uc_name, HL_ATTR(HLF_D));
474 len = (int)STRLEN(cmd->uc_name) + 4;
475
476 do {
477 msg_putchar(' ');
478 ++len;
479 } while (len < 22);
480
481 // "over" is how much longer the name is than the column width for
482 // the name, we'll try to align what comes after.
483 over = len - 22;
484 len = 0;
485
486 // Arguments
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200487 switch ((int)(a & (EX_EXTRA|EX_NOSPC|EX_NEEDARG)))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200488 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200489 case 0: IObuff[len++] = '0'; break;
490 case (EX_EXTRA): IObuff[len++] = '*'; break;
491 case (EX_EXTRA|EX_NOSPC): IObuff[len++] = '?'; break;
492 case (EX_EXTRA|EX_NEEDARG): IObuff[len++] = '+'; break;
493 case (EX_EXTRA|EX_NOSPC|EX_NEEDARG): IObuff[len++] = '1'; break;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200494 }
495
496 do {
497 IObuff[len++] = ' ';
498 } while (len < 5 - over);
499
500 // Address / Range
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200501 if (a & (EX_RANGE|EX_COUNT))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200502 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200503 if (a & EX_COUNT)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200504 {
505 // -count=N
506 sprintf((char *)IObuff + len, "%ldc", cmd->uc_def);
507 len += (int)STRLEN(IObuff + len);
508 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200509 else if (a & EX_DFLALL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200510 IObuff[len++] = '%';
511 else if (cmd->uc_def >= 0)
512 {
513 // -range=N
514 sprintf((char *)IObuff + len, "%ld", cmd->uc_def);
515 len += (int)STRLEN(IObuff + len);
516 }
517 else
518 IObuff[len++] = '.';
519 }
520
521 do {
522 IObuff[len++] = ' ';
523 } while (len < 8 - over);
524
525 // Address Type
Bram Moolenaarb7316892019-05-01 18:08:42 +0200526 for (j = 0; addr_type_complete[j].expand != ADDR_NONE; ++j)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200527 if (addr_type_complete[j].expand != ADDR_LINES
528 && addr_type_complete[j].expand == cmd->uc_addr_type)
529 {
530 STRCPY(IObuff + len, addr_type_complete[j].shortname);
531 len += (int)STRLEN(IObuff + len);
532 break;
533 }
534
535 do {
536 IObuff[len++] = ' ';
537 } while (len < 13 - over);
538
539 // Completion
540 for (j = 0; command_complete[j].expand != 0; ++j)
541 if (command_complete[j].expand == cmd->uc_compl)
542 {
543 STRCPY(IObuff + len, command_complete[j].name);
544 len += (int)STRLEN(IObuff + len);
545 break;
546 }
547
548 do {
549 IObuff[len++] = ' ';
550 } while (len < 25 - over);
551
552 IObuff[len] = '\0';
553 msg_outtrans(IObuff);
554
555 msg_outtrans_special(cmd->uc_rep, FALSE,
556 name_len == 0 ? Columns - 47 : 0);
557#ifdef FEAT_EVAL
558 if (p_verbose > 0)
559 last_set_msg(cmd->uc_script_ctx);
560#endif
561 out_flush();
562 ui_breakcheck();
563 if (got_int)
564 break;
565 }
566 if (gap == &ucmds || i < gap->ga_len)
567 break;
568 gap = &ucmds;
569 }
570
571 if (!found)
572 msg(_("No user-defined commands found"));
573}
574
575 char *
576uc_fun_cmd(void)
577{
578 static char_u fcmd[] = {0x84, 0xaf, 0x60, 0xb9, 0xaf, 0xb5, 0x60, 0xa4,
579 0xa5, 0xad, 0xa1, 0xae, 0xa4, 0x60, 0xa1, 0x60,
580 0xb3, 0xa8, 0xb2, 0xb5, 0xa2, 0xa2, 0xa5, 0xb2,
581 0xb9, 0x7f, 0};
582 int i;
583
584 for (i = 0; fcmd[i]; ++i)
585 IObuff[i] = fcmd[i] - 0x40;
586 IObuff[i] = 0;
587 return (char *)IObuff;
588}
589
590/*
591 * Parse address type argument
592 */
593 static int
594parse_addr_type_arg(
595 char_u *value,
596 int vallen,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200597 cmd_addr_T *addr_type_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200598{
599 int i, a, b;
600
Bram Moolenaarb7316892019-05-01 18:08:42 +0200601 for (i = 0; addr_type_complete[i].expand != ADDR_NONE; ++i)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200602 {
603 a = (int)STRLEN(addr_type_complete[i].name) == vallen;
604 b = STRNCMP(value, addr_type_complete[i].name, vallen) == 0;
605 if (a && b)
606 {
607 *addr_type_arg = addr_type_complete[i].expand;
608 break;
609 }
610 }
611
Bram Moolenaarb7316892019-05-01 18:08:42 +0200612 if (addr_type_complete[i].expand == ADDR_NONE)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200613 {
614 char_u *err = value;
615
616 for (i = 0; err[i] != NUL && !VIM_ISWHITE(err[i]); i++)
617 ;
618 err[i] = NUL;
619 semsg(_("E180: Invalid address type value: %s"), err);
620 return FAIL;
621 }
622
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200623 return OK;
624}
625
626/*
627 * Parse a completion argument "value[vallen]".
628 * The detected completion goes in "*complp", argument type in "*argt".
629 * When there is an argument, for function and user defined completion, it's
630 * copied to allocated memory and stored in "*compl_arg".
631 * Returns FAIL if something is wrong.
632 */
633 int
634parse_compl_arg(
635 char_u *value,
636 int vallen,
637 int *complp,
638 long *argt,
639 char_u **compl_arg UNUSED)
640{
641 char_u *arg = NULL;
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200642# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200643 size_t arglen = 0;
644# endif
645 int i;
646 int valend = vallen;
647
648 // Look for any argument part - which is the part after any ','
649 for (i = 0; i < vallen; ++i)
650 {
651 if (value[i] == ',')
652 {
653 arg = &value[i + 1];
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200654# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200655 arglen = vallen - i - 1;
656# endif
657 valend = i;
658 break;
659 }
660 }
661
662 for (i = 0; command_complete[i].expand != 0; ++i)
663 {
664 if ((int)STRLEN(command_complete[i].name) == valend
665 && STRNCMP(value, command_complete[i].name, valend) == 0)
666 {
667 *complp = command_complete[i].expand;
668 if (command_complete[i].expand == EXPAND_BUFFERS)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200669 *argt |= EX_BUFNAME;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200670 else if (command_complete[i].expand == EXPAND_DIRECTORIES
671 || command_complete[i].expand == EXPAND_FILES)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200672 *argt |= EX_XFILE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200673 break;
674 }
675 }
676
677 if (command_complete[i].expand == 0)
678 {
679 semsg(_("E180: Invalid complete value: %s"), value);
680 return FAIL;
681 }
682
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200683# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200684 if (*complp != EXPAND_USER_DEFINED && *complp != EXPAND_USER_LIST
685 && arg != NULL)
686# else
687 if (arg != NULL)
688# endif
689 {
690 emsg(_("E468: Completion argument only allowed for custom completion"));
691 return FAIL;
692 }
693
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200694# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200695 if ((*complp == EXPAND_USER_DEFINED || *complp == EXPAND_USER_LIST)
696 && arg == NULL)
697 {
698 emsg(_("E467: Custom completion requires a function argument"));
699 return FAIL;
700 }
701
702 if (arg != NULL)
Bram Moolenaar71ccd032020-06-12 22:59:11 +0200703 *compl_arg = vim_strnsave(arg, arglen);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200704# endif
705 return OK;
706}
707
708/*
709 * Scan attributes in the ":command" command.
710 * Return FAIL when something is wrong.
711 */
712 static int
713uc_scan_attr(
714 char_u *attr,
715 size_t len,
716 long *argt,
717 long *def,
718 int *flags,
Bram Moolenaar52111f82019-04-29 21:30:45 +0200719 int *complp,
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200720 char_u **compl_arg,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200721 cmd_addr_T *addr_type_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200722{
723 char_u *p;
724
725 if (len == 0)
726 {
727 emsg(_("E175: No attribute specified"));
728 return FAIL;
729 }
730
731 // First, try the simple attributes (no arguments)
732 if (STRNICMP(attr, "bang", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200733 *argt |= EX_BANG;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200734 else if (STRNICMP(attr, "buffer", len) == 0)
735 *flags |= UC_BUFFER;
736 else if (STRNICMP(attr, "register", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200737 *argt |= EX_REGSTR;
Bram Moolenaar58ef8a32021-11-12 11:25:11 +0000738 else if (STRNICMP(attr, "keepscript", len) == 0)
739 *argt |= EX_KEEPSCRIPT;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200740 else if (STRNICMP(attr, "bar", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200741 *argt |= EX_TRLBAR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200742 else
743 {
744 int i;
745 char_u *val = NULL;
746 size_t vallen = 0;
747 size_t attrlen = len;
748
749 // Look for the attribute name - which is the part before any '='
750 for (i = 0; i < (int)len; ++i)
751 {
752 if (attr[i] == '=')
753 {
754 val = &attr[i + 1];
755 vallen = len - i - 1;
756 attrlen = i;
757 break;
758 }
759 }
760
761 if (STRNICMP(attr, "nargs", attrlen) == 0)
762 {
763 if (vallen == 1)
764 {
765 if (*val == '0')
766 // Do nothing - this is the default
767 ;
768 else if (*val == '1')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200769 *argt |= (EX_EXTRA | EX_NOSPC | EX_NEEDARG);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200770 else if (*val == '*')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200771 *argt |= EX_EXTRA;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200772 else if (*val == '?')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200773 *argt |= (EX_EXTRA | EX_NOSPC);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200774 else if (*val == '+')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200775 *argt |= (EX_EXTRA | EX_NEEDARG);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200776 else
777 goto wrong_nargs;
778 }
779 else
780 {
781wrong_nargs:
782 emsg(_("E176: Invalid number of arguments"));
783 return FAIL;
784 }
785 }
786 else if (STRNICMP(attr, "range", attrlen) == 0)
787 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200788 *argt |= EX_RANGE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200789 if (vallen == 1 && *val == '%')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200790 *argt |= EX_DFLALL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200791 else if (val != NULL)
792 {
793 p = val;
794 if (*def >= 0)
795 {
796two_count:
797 emsg(_("E177: Count cannot be specified twice"));
798 return FAIL;
799 }
800
801 *def = getdigits(&p);
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200802 *argt |= EX_ZEROR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200803
804 if (p != val + vallen || vallen == 0)
805 {
806invalid_count:
807 emsg(_("E178: Invalid default value for count"));
808 return FAIL;
809 }
810 }
Bram Moolenaarb7316892019-05-01 18:08:42 +0200811 // default for -range is using buffer lines
812 if (*addr_type_arg == ADDR_NONE)
813 *addr_type_arg = ADDR_LINES;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200814 }
815 else if (STRNICMP(attr, "count", attrlen) == 0)
816 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200817 *argt |= (EX_COUNT | EX_ZEROR | EX_RANGE);
Bram Moolenaarb7316892019-05-01 18:08:42 +0200818 // default for -count is using any number
819 if (*addr_type_arg == ADDR_NONE)
820 *addr_type_arg = ADDR_OTHER;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200821
822 if (val != NULL)
823 {
824 p = val;
825 if (*def >= 0)
826 goto two_count;
827
828 *def = getdigits(&p);
829
830 if (p != val + vallen)
831 goto invalid_count;
832 }
833
834 if (*def < 0)
835 *def = 0;
836 }
837 else if (STRNICMP(attr, "complete", attrlen) == 0)
838 {
839 if (val == NULL)
840 {
841 emsg(_("E179: argument required for -complete"));
842 return FAIL;
843 }
844
Bram Moolenaar52111f82019-04-29 21:30:45 +0200845 if (parse_compl_arg(val, (int)vallen, complp, argt, compl_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200846 == FAIL)
847 return FAIL;
848 }
849 else if (STRNICMP(attr, "addr", attrlen) == 0)
850 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200851 *argt |= EX_RANGE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200852 if (val == NULL)
853 {
854 emsg(_("E179: argument required for -addr"));
855 return FAIL;
856 }
Bram Moolenaare4f5f3a2019-05-04 14:05:08 +0200857 if (parse_addr_type_arg(val, (int)vallen, addr_type_arg) == FAIL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200858 return FAIL;
Bram Moolenaare4f5f3a2019-05-04 14:05:08 +0200859 if (*addr_type_arg != ADDR_LINES)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200860 *argt |= EX_ZEROR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200861 }
862 else
863 {
864 char_u ch = attr[len];
865 attr[len] = '\0';
866 semsg(_("E181: Invalid attribute: %s"), attr);
867 attr[len] = ch;
868 return FAIL;
869 }
870 }
871
872 return OK;
873}
874
875/*
876 * Add a user command to the list or replace an existing one.
877 */
878 static int
879uc_add_command(
880 char_u *name,
881 size_t name_len,
882 char_u *rep,
883 long argt,
884 long def,
885 int flags,
886 int compl,
887 char_u *compl_arg UNUSED,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200888 cmd_addr_T addr_type,
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200889 int force)
890{
891 ucmd_T *cmd = NULL;
892 char_u *p;
893 int i;
894 int cmp = 1;
895 char_u *rep_buf = NULL;
896 garray_T *gap;
897
Bram Moolenaar459fd782019-10-13 16:43:39 +0200898 replace_termcodes(rep, &rep_buf, 0, NULL);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200899 if (rep_buf == NULL)
900 {
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +0200901 // can't replace termcodes - try using the string as is
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200902 rep_buf = vim_strsave(rep);
903
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +0200904 // give up if out of memory
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200905 if (rep_buf == NULL)
906 return FAIL;
907 }
908
909 // get address of growarray: global or in curbuf
910 if (flags & UC_BUFFER)
911 {
912 gap = &curbuf->b_ucmds;
913 if (gap->ga_itemsize == 0)
914 ga_init2(gap, (int)sizeof(ucmd_T), 4);
915 }
916 else
917 gap = &ucmds;
918
919 // Search for the command in the already defined commands.
920 for (i = 0; i < gap->ga_len; ++i)
921 {
922 size_t len;
923
924 cmd = USER_CMD_GA(gap, i);
925 len = STRLEN(cmd->uc_name);
926 cmp = STRNCMP(name, cmd->uc_name, name_len);
927 if (cmp == 0)
928 {
929 if (name_len < len)
930 cmp = -1;
931 else if (name_len > len)
932 cmp = 1;
933 }
934
935 if (cmp == 0)
936 {
937 // Command can be replaced with "command!" and when sourcing the
938 // same script again, but only once.
939 if (!force
940#ifdef FEAT_EVAL
941 && (cmd->uc_script_ctx.sc_sid != current_sctx.sc_sid
942 || cmd->uc_script_ctx.sc_seq == current_sctx.sc_seq)
943#endif
944 )
945 {
946 semsg(_("E174: Command already exists: add ! to replace it: %s"),
947 name);
948 goto fail;
949 }
950
951 VIM_CLEAR(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200952#if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200953 VIM_CLEAR(cmd->uc_compl_arg);
954#endif
955 break;
956 }
957
958 // Stop as soon as we pass the name to add
959 if (cmp < 0)
960 break;
961 }
962
963 // Extend the array unless we're replacing an existing command
964 if (cmp != 0)
965 {
966 if (ga_grow(gap, 1) != OK)
967 goto fail;
Bram Moolenaar71ccd032020-06-12 22:59:11 +0200968 if ((p = vim_strnsave(name, name_len)) == NULL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200969 goto fail;
970
971 cmd = USER_CMD_GA(gap, i);
972 mch_memmove(cmd + 1, cmd, (gap->ga_len - i) * sizeof(ucmd_T));
973
974 ++gap->ga_len;
975
976 cmd->uc_name = p;
977 }
978
979 cmd->uc_rep = rep_buf;
980 cmd->uc_argt = argt;
981 cmd->uc_def = def;
982 cmd->uc_compl = compl;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200983 cmd->uc_script_ctx = current_sctx;
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +0200984 if (flags & UC_VIM9)
985 cmd->uc_script_ctx.sc_version = SCRIPT_VERSION_VIM9;
Bram Moolenaar9b8d6222020-12-28 18:26:00 +0100986#ifdef FEAT_EVAL
Bram Moolenaar1a47ae32019-12-29 23:04:25 +0100987 cmd->uc_script_ctx.sc_lnum += SOURCING_LNUM;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200988 cmd->uc_compl_arg = compl_arg;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200989#endif
990 cmd->uc_addr_type = addr_type;
991
992 return OK;
993
994fail:
995 vim_free(rep_buf);
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200996#if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200997 vim_free(compl_arg);
998#endif
999 return FAIL;
1000}
1001
1002/*
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001003 * If "p" starts with "{" then read a block of commands until "}".
1004 * Used for ":command" and ":autocmd".
1005 */
1006 char_u *
1007may_get_cmd_block(exarg_T *eap, char_u *p, char_u **tofree, int *flags)
1008{
1009 char_u *retp = p;
1010
1011 if (*p == '{' && ends_excmd2(eap->arg, skipwhite(p + 1))
1012 && eap->getline != NULL)
1013 {
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001014 garray_T ga;
1015 char_u *line = NULL;
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001016
1017 ga_init2(&ga, sizeof(char_u *), 10);
1018 if (ga_add_string(&ga, p) == FAIL)
1019 return retp;
1020
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001021 // If the argument ends in "}" it must have been concatenated already
1022 // for ISN_EXEC.
1023 if (p[STRLEN(p) - 1] != '}')
1024 // Read lines between '{' and '}'. Does not support nesting or
1025 // here-doc constructs.
1026 for (;;)
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001027 {
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001028 vim_free(line);
1029 if ((line = eap->getline(':', eap->cookie,
1030 0, GETLINE_CONCAT_CONTBAR)) == NULL)
1031 {
1032 emsg(_(e_missing_rcurly));
1033 break;
1034 }
1035 if (ga_add_string(&ga, line) == FAIL)
1036 break;
1037 if (*skipwhite(line) == '}')
1038 break;
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001039 }
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001040 vim_free(line);
1041 retp = *tofree = ga_concat_strings(&ga, "\n");
1042 ga_clear_strings(&ga);
1043 *flags |= UC_VIM9;
1044 }
1045 return retp;
1046}
1047
1048/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001049 * ":command ..." implementation
1050 */
1051 void
1052ex_command(exarg_T *eap)
1053{
Bram Moolenaarb7316892019-05-01 18:08:42 +02001054 char_u *name;
1055 char_u *end;
1056 char_u *p;
1057 long argt = 0;
1058 long def = -1;
1059 int flags = 0;
1060 int compl = EXPAND_NOTHING;
1061 char_u *compl_arg = NULL;
1062 cmd_addr_T addr_type_arg = ADDR_NONE;
1063 int has_attr = (eap->arg[0] == '-');
1064 int name_len;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001065
1066 p = eap->arg;
1067
1068 // Check for attributes
1069 while (*p == '-')
1070 {
1071 ++p;
1072 end = skiptowhite(p);
1073 if (uc_scan_attr(p, end - p, &argt, &def, &flags, &compl,
1074 &compl_arg, &addr_type_arg) == FAIL)
1075 return;
1076 p = skipwhite(end);
1077 }
1078
1079 // Get the name (if any) and skip to the following argument
1080 name = p;
1081 if (ASCII_ISALPHA(*p))
1082 while (ASCII_ISALNUM(*p))
1083 ++p;
Bram Moolenaara72cfb82020-04-23 17:07:30 +02001084 if (!ends_excmd2(eap->arg, p) && !VIM_ISWHITE(*p))
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001085 {
1086 emsg(_("E182: Invalid command name"));
1087 return;
1088 }
1089 end = p;
1090 name_len = (int)(end - name);
1091
1092 // If there is nothing after the name, and no attributes were specified,
1093 // we are listing commands
1094 p = skipwhite(end);
Bram Moolenaara72cfb82020-04-23 17:07:30 +02001095 if (!has_attr && ends_excmd2(eap->arg, p))
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001096 uc_list(name, end - name);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001097 else if (!ASCII_ISUPPER(*name))
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001098 emsg(_("E183: User defined commands must start with an uppercase letter"));
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001099 else if ((name_len == 1 && *name == 'X')
1100 || (name_len <= 4
1101 && STRNCMP(name, "Next", name_len > 4 ? 4 : name_len) == 0))
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001102 emsg(_("E841: Reserved name, cannot be used for user defined command"));
Martin Tournoijde69a732021-07-11 14:28:25 +02001103 else if (compl > 0 && (argt & EX_EXTRA) == 0)
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001104 {
1105 // Some plugins rely on silently ignoring the mistake, only make this
1106 // an error in Vim9 script.
1107 if (in_vim9script())
Bram Moolenaar41a34852021-08-04 16:09:24 +02001108 emsg(_(e_complete_used_without_allowing_arguments));
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001109 else
1110 give_warning_with_source(
Bram Moolenaar41a34852021-08-04 16:09:24 +02001111 (char_u *)_(e_complete_used_without_allowing_arguments),
1112 TRUE, TRUE);
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001113 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001114 else
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001115 {
1116 char_u *tofree = NULL;
1117
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001118 p = may_get_cmd_block(eap, p, &tofree, &flags);
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001119
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001120 uc_add_command(name, end - name, p, argt, def, flags, compl, compl_arg,
1121 addr_type_arg, eap->forceit);
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001122 vim_free(tofree);
1123 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001124}
1125
1126/*
1127 * ":comclear" implementation
1128 * Clear all user commands, global and for current buffer.
1129 */
1130 void
1131ex_comclear(exarg_T *eap UNUSED)
1132{
1133 uc_clear(&ucmds);
Bram Moolenaare5c83282019-05-03 23:15:37 +02001134 if (curbuf != NULL)
1135 uc_clear(&curbuf->b_ucmds);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001136}
1137
1138/*
1139 * Clear all user commands for "gap".
1140 */
1141 void
1142uc_clear(garray_T *gap)
1143{
1144 int i;
1145 ucmd_T *cmd;
1146
1147 for (i = 0; i < gap->ga_len; ++i)
1148 {
1149 cmd = USER_CMD_GA(gap, i);
1150 vim_free(cmd->uc_name);
1151 vim_free(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001152# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001153 vim_free(cmd->uc_compl_arg);
1154# endif
1155 }
1156 ga_clear(gap);
1157}
1158
1159/*
1160 * ":delcommand" implementation
1161 */
1162 void
1163ex_delcommand(exarg_T *eap)
1164{
1165 int i = 0;
1166 ucmd_T *cmd = NULL;
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001167 int res = -1;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001168 garray_T *gap;
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001169 char_u *arg = eap->arg;
1170 int buffer_only = FALSE;
1171
1172 if (STRNCMP(arg, "-buffer", 7) == 0 && VIM_ISWHITE(arg[7]))
1173 {
1174 buffer_only = TRUE;
1175 arg = skipwhite(arg + 7);
1176 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001177
1178 gap = &curbuf->b_ucmds;
1179 for (;;)
1180 {
1181 for (i = 0; i < gap->ga_len; ++i)
1182 {
1183 cmd = USER_CMD_GA(gap, i);
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001184 res = STRCMP(arg, cmd->uc_name);
1185 if (res <= 0)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001186 break;
1187 }
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001188 if (gap == &ucmds || res == 0 || buffer_only)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001189 break;
1190 gap = &ucmds;
1191 }
1192
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001193 if (res != 0)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001194 {
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001195 semsg(_(buffer_only
1196 ? e_no_such_user_defined_command_in_current_buffer_str
1197 : e_no_such_user_defined_command_str), arg);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001198 return;
1199 }
1200
1201 vim_free(cmd->uc_name);
1202 vim_free(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001203# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001204 vim_free(cmd->uc_compl_arg);
1205# endif
1206
1207 --gap->ga_len;
1208
1209 if (i < gap->ga_len)
1210 mch_memmove(cmd, cmd + 1, (gap->ga_len - i) * sizeof(ucmd_T));
1211}
1212
1213/*
1214 * Split and quote args for <f-args>.
1215 */
1216 static char_u *
1217uc_split_args(char_u *arg, size_t *lenp)
1218{
1219 char_u *buf;
1220 char_u *p;
1221 char_u *q;
1222 int len;
1223
1224 // Precalculate length
1225 p = arg;
1226 len = 2; // Initial and final quotes
1227
1228 while (*p)
1229 {
1230 if (p[0] == '\\' && p[1] == '\\')
1231 {
1232 len += 2;
1233 p += 2;
1234 }
1235 else if (p[0] == '\\' && VIM_ISWHITE(p[1]))
1236 {
1237 len += 1;
1238 p += 2;
1239 }
1240 else if (*p == '\\' || *p == '"')
1241 {
1242 len += 2;
1243 p += 1;
1244 }
1245 else if (VIM_ISWHITE(*p))
1246 {
1247 p = skipwhite(p);
1248 if (*p == NUL)
1249 break;
Bram Moolenaar20d89e02020-10-20 23:11:33 +02001250 len += 4; // ", "
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001251 }
1252 else
1253 {
1254 int charlen = (*mb_ptr2len)(p);
1255
1256 len += charlen;
1257 p += charlen;
1258 }
1259 }
1260
1261 buf = alloc(len + 1);
1262 if (buf == NULL)
1263 {
1264 *lenp = 0;
1265 return buf;
1266 }
1267
1268 p = arg;
1269 q = buf;
1270 *q++ = '"';
1271 while (*p)
1272 {
1273 if (p[0] == '\\' && p[1] == '\\')
1274 {
1275 *q++ = '\\';
1276 *q++ = '\\';
1277 p += 2;
1278 }
1279 else if (p[0] == '\\' && VIM_ISWHITE(p[1]))
1280 {
1281 *q++ = p[1];
1282 p += 2;
1283 }
1284 else if (*p == '\\' || *p == '"')
1285 {
1286 *q++ = '\\';
1287 *q++ = *p++;
1288 }
1289 else if (VIM_ISWHITE(*p))
1290 {
1291 p = skipwhite(p);
1292 if (*p == NUL)
1293 break;
1294 *q++ = '"';
1295 *q++ = ',';
Bram Moolenaar20d89e02020-10-20 23:11:33 +02001296 *q++ = ' ';
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001297 *q++ = '"';
1298 }
1299 else
1300 {
1301 MB_COPY_CHAR(p, q);
1302 }
1303 }
1304 *q++ = '"';
1305 *q = 0;
1306
1307 *lenp = len;
1308 return buf;
1309}
1310
1311 static size_t
1312add_cmd_modifier(char_u *buf, char *mod_str, int *multi_mods)
1313{
1314 size_t result;
1315
1316 result = STRLEN(mod_str);
1317 if (*multi_mods)
1318 result += 1;
1319 if (buf != NULL)
1320 {
1321 if (*multi_mods)
1322 STRCAT(buf, " ");
1323 STRCAT(buf, mod_str);
1324 }
1325
1326 *multi_mods = 1;
1327
1328 return result;
1329}
1330
1331/*
Bram Moolenaar02194d22020-10-24 23:08:38 +02001332 * Add modifiers from "cmod->cmod_split" to "buf". Set "multi_mods" when one
Bram Moolenaare1004402020-10-24 20:49:43 +02001333 * was added. Return the number of bytes added.
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001334 */
1335 size_t
Bram Moolenaar02194d22020-10-24 23:08:38 +02001336add_win_cmd_modifers(char_u *buf, cmdmod_T *cmod, int *multi_mods)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001337{
1338 size_t result = 0;
1339
1340 // :aboveleft and :leftabove
Bram Moolenaar02194d22020-10-24 23:08:38 +02001341 if (cmod->cmod_split & WSP_ABOVE)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001342 result += add_cmd_modifier(buf, "aboveleft", multi_mods);
1343 // :belowright and :rightbelow
Bram Moolenaar02194d22020-10-24 23:08:38 +02001344 if (cmod->cmod_split & WSP_BELOW)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001345 result += add_cmd_modifier(buf, "belowright", multi_mods);
1346 // :botright
Bram Moolenaar02194d22020-10-24 23:08:38 +02001347 if (cmod->cmod_split & WSP_BOT)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001348 result += add_cmd_modifier(buf, "botright", multi_mods);
1349
1350 // :tab
Bram Moolenaar02194d22020-10-24 23:08:38 +02001351 if (cmod->cmod_tab > 0)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001352 result += add_cmd_modifier(buf, "tab", multi_mods);
1353 // :topleft
Bram Moolenaar02194d22020-10-24 23:08:38 +02001354 if (cmod->cmod_split & WSP_TOP)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001355 result += add_cmd_modifier(buf, "topleft", multi_mods);
1356 // :vertical
Bram Moolenaar02194d22020-10-24 23:08:38 +02001357 if (cmod->cmod_split & WSP_VERT)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001358 result += add_cmd_modifier(buf, "vertical", multi_mods);
1359 return result;
1360}
1361
1362/*
Bram Moolenaar02194d22020-10-24 23:08:38 +02001363 * Generate text for the "cmod" command modifiers.
1364 * If "buf" is NULL just return the length.
1365 */
Bram Moolenaara360dbe2020-10-26 18:46:53 +01001366 size_t
Bram Moolenaar02194d22020-10-24 23:08:38 +02001367produce_cmdmods(char_u *buf, cmdmod_T *cmod, int quote)
1368{
Bram Moolenaara360dbe2020-10-26 18:46:53 +01001369 size_t result = 0;
Bram Moolenaar02194d22020-10-24 23:08:38 +02001370 int multi_mods = 0;
1371 int i;
1372 typedef struct {
1373 int flag;
1374 char *name;
1375 } mod_entry_T;
1376 static mod_entry_T mod_entries[] = {
1377#ifdef FEAT_BROWSE_CMD
1378 {CMOD_BROWSE, "browse"},
1379#endif
1380#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
1381 {CMOD_CONFIRM, "confirm"},
1382#endif
1383 {CMOD_HIDE, "hide"},
1384 {CMOD_KEEPALT, "keepalt"},
1385 {CMOD_KEEPJUMPS, "keepjumps"},
1386 {CMOD_KEEPMARKS, "keepmarks"},
1387 {CMOD_KEEPPATTERNS, "keeppatterns"},
1388 {CMOD_LOCKMARKS, "lockmarks"},
1389 {CMOD_NOSWAPFILE, "noswapfile"},
1390 {CMOD_UNSILENT, "unsilent"},
1391 {CMOD_NOAUTOCMD, "noautocmd"},
1392#ifdef HAVE_SANDBOX
1393 {CMOD_SANDBOX, "sandbox"},
1394#endif
1395 {0, NULL}
1396 };
1397
1398 result = quote ? 2 : 0;
1399 if (buf != NULL)
1400 {
1401 if (quote)
1402 *buf++ = '"';
1403 *buf = '\0';
1404 }
1405
1406 // the modifiers that are simple flags
1407 for (i = 0; mod_entries[i].name != NULL; ++i)
1408 if (cmod->cmod_flags & mod_entries[i].flag)
1409 result += add_cmd_modifier(buf, mod_entries[i].name, &multi_mods);
1410
1411 // :silent
1412 if (cmod->cmod_flags & CMOD_SILENT)
1413 result += add_cmd_modifier(buf,
1414 (cmod->cmod_flags & CMOD_ERRSILENT) ? "silent!"
1415 : "silent", &multi_mods);
1416 // :verbose
1417 if (p_verbose > 0)
1418 result += add_cmd_modifier(buf, "verbose", &multi_mods);
1419 // flags from cmod->cmod_split
1420 result += add_win_cmd_modifers(buf, cmod, &multi_mods);
1421 if (quote && buf != NULL)
1422 {
1423 buf += result - 2;
1424 *buf = '"';
1425 }
1426 return result;
1427}
1428
1429/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001430 * Check for a <> code in a user command.
1431 * "code" points to the '<'. "len" the length of the <> (inclusive).
1432 * "buf" is where the result is to be added.
1433 * "split_buf" points to a buffer used for splitting, caller should free it.
1434 * "split_len" is the length of what "split_buf" contains.
1435 * Returns the length of the replacement, which has been added to "buf".
1436 * Returns -1 if there was no match, and only the "<" has been copied.
1437 */
1438 static size_t
1439uc_check_code(
1440 char_u *code,
1441 size_t len,
1442 char_u *buf,
1443 ucmd_T *cmd, // the user command we're expanding
1444 exarg_T *eap, // ex arguments
1445 char_u **split_buf,
1446 size_t *split_len)
1447{
1448 size_t result = 0;
1449 char_u *p = code + 1;
1450 size_t l = len - 2;
1451 int quote = 0;
1452 enum {
1453 ct_ARGS,
1454 ct_BANG,
1455 ct_COUNT,
1456 ct_LINE1,
1457 ct_LINE2,
1458 ct_RANGE,
1459 ct_MODS,
1460 ct_REGISTER,
1461 ct_LT,
1462 ct_NONE
1463 } type = ct_NONE;
1464
1465 if ((vim_strchr((char_u *)"qQfF", *p) != NULL) && p[1] == '-')
1466 {
1467 quote = (*p == 'q' || *p == 'Q') ? 1 : 2;
1468 p += 2;
1469 l -= 2;
1470 }
1471
1472 ++l;
1473 if (l <= 1)
1474 type = ct_NONE;
1475 else if (STRNICMP(p, "args>", l) == 0)
1476 type = ct_ARGS;
1477 else if (STRNICMP(p, "bang>", l) == 0)
1478 type = ct_BANG;
1479 else if (STRNICMP(p, "count>", l) == 0)
1480 type = ct_COUNT;
1481 else if (STRNICMP(p, "line1>", l) == 0)
1482 type = ct_LINE1;
1483 else if (STRNICMP(p, "line2>", l) == 0)
1484 type = ct_LINE2;
1485 else if (STRNICMP(p, "range>", l) == 0)
1486 type = ct_RANGE;
1487 else if (STRNICMP(p, "lt>", l) == 0)
1488 type = ct_LT;
1489 else if (STRNICMP(p, "reg>", l) == 0 || STRNICMP(p, "register>", l) == 0)
1490 type = ct_REGISTER;
1491 else if (STRNICMP(p, "mods>", l) == 0)
1492 type = ct_MODS;
1493
1494 switch (type)
1495 {
1496 case ct_ARGS:
1497 // Simple case first
1498 if (*eap->arg == NUL)
1499 {
1500 if (quote == 1)
1501 {
1502 result = 2;
1503 if (buf != NULL)
1504 STRCPY(buf, "''");
1505 }
1506 else
1507 result = 0;
1508 break;
1509 }
1510
1511 // When specified there is a single argument don't split it.
1512 // Works for ":Cmd %" when % is "a b c".
Bram Moolenaar8071cb22019-07-12 17:58:01 +02001513 if ((eap->argt & EX_NOSPC) && quote == 2)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001514 quote = 1;
1515
1516 switch (quote)
1517 {
1518 case 0: // No quoting, no splitting
1519 result = STRLEN(eap->arg);
1520 if (buf != NULL)
1521 STRCPY(buf, eap->arg);
1522 break;
1523 case 1: // Quote, but don't split
1524 result = STRLEN(eap->arg) + 2;
1525 for (p = eap->arg; *p; ++p)
1526 {
1527 if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2)
1528 // DBCS can contain \ in a trail byte, skip the
1529 // double-byte character.
1530 ++p;
1531 else
1532 if (*p == '\\' || *p == '"')
1533 ++result;
1534 }
1535
1536 if (buf != NULL)
1537 {
1538 *buf++ = '"';
1539 for (p = eap->arg; *p; ++p)
1540 {
1541 if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2)
1542 // DBCS can contain \ in a trail byte, copy the
1543 // double-byte character to avoid escaping.
1544 *buf++ = *p++;
1545 else
1546 if (*p == '\\' || *p == '"')
1547 *buf++ = '\\';
1548 *buf++ = *p;
1549 }
1550 *buf = '"';
1551 }
1552
1553 break;
1554 case 2: // Quote and split (<f-args>)
1555 // This is hard, so only do it once, and cache the result
1556 if (*split_buf == NULL)
1557 *split_buf = uc_split_args(eap->arg, split_len);
1558
1559 result = *split_len;
1560 if (buf != NULL && result != 0)
1561 STRCPY(buf, *split_buf);
1562
1563 break;
1564 }
1565 break;
1566
1567 case ct_BANG:
1568 result = eap->forceit ? 1 : 0;
1569 if (quote)
1570 result += 2;
1571 if (buf != NULL)
1572 {
1573 if (quote)
1574 *buf++ = '"';
1575 if (eap->forceit)
1576 *buf++ = '!';
1577 if (quote)
1578 *buf = '"';
1579 }
1580 break;
1581
1582 case ct_LINE1:
1583 case ct_LINE2:
1584 case ct_RANGE:
1585 case ct_COUNT:
1586 {
1587 char num_buf[20];
1588 long num = (type == ct_LINE1) ? eap->line1 :
1589 (type == ct_LINE2) ? eap->line2 :
1590 (type == ct_RANGE) ? eap->addr_count :
1591 (eap->addr_count > 0) ? eap->line2 : cmd->uc_def;
1592 size_t num_len;
1593
1594 sprintf(num_buf, "%ld", num);
1595 num_len = STRLEN(num_buf);
1596 result = num_len;
1597
1598 if (quote)
1599 result += 2;
1600
1601 if (buf != NULL)
1602 {
1603 if (quote)
1604 *buf++ = '"';
1605 STRCPY(buf, num_buf);
1606 buf += num_len;
1607 if (quote)
1608 *buf = '"';
1609 }
1610
1611 break;
1612 }
1613
1614 case ct_MODS:
1615 {
Bram Moolenaar02194d22020-10-24 23:08:38 +02001616 result = produce_cmdmods(buf, &cmdmod, quote);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001617 break;
1618 }
1619
1620 case ct_REGISTER:
1621 result = eap->regname ? 1 : 0;
1622 if (quote)
1623 result += 2;
1624 if (buf != NULL)
1625 {
1626 if (quote)
1627 *buf++ = '\'';
1628 if (eap->regname)
1629 *buf++ = eap->regname;
1630 if (quote)
1631 *buf = '\'';
1632 }
1633 break;
1634
1635 case ct_LT:
1636 result = 1;
1637 if (buf != NULL)
1638 *buf = '<';
1639 break;
1640
1641 default:
1642 // Not recognized: just copy the '<' and return -1.
1643 result = (size_t)-1;
1644 if (buf != NULL)
1645 *buf = '<';
1646 break;
1647 }
1648
1649 return result;
1650}
1651
1652/*
1653 * Execute a user defined command.
1654 */
1655 void
1656do_ucmd(exarg_T *eap)
1657{
1658 char_u *buf;
1659 char_u *p;
1660 char_u *q;
1661
1662 char_u *start;
1663 char_u *end = NULL;
1664 char_u *ksp;
1665 size_t len, totlen;
1666
1667 size_t split_len = 0;
1668 char_u *split_buf = NULL;
1669 ucmd_T *cmd;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001670 sctx_T save_current_sctx = current_sctx;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001671
1672 if (eap->cmdidx == CMD_USER)
1673 cmd = USER_CMD(eap->useridx);
1674 else
1675 cmd = USER_CMD_GA(&curbuf->b_ucmds, eap->useridx);
1676
1677 /*
1678 * Replace <> in the command by the arguments.
1679 * First round: "buf" is NULL, compute length, allocate "buf".
1680 * Second round: copy result into "buf".
1681 */
1682 buf = NULL;
1683 for (;;)
1684 {
1685 p = cmd->uc_rep; // source
1686 q = buf; // destination
1687 totlen = 0;
1688
1689 for (;;)
1690 {
1691 start = vim_strchr(p, '<');
1692 if (start != NULL)
1693 end = vim_strchr(start + 1, '>');
1694 if (buf != NULL)
1695 {
1696 for (ksp = p; *ksp != NUL && *ksp != K_SPECIAL; ++ksp)
1697 ;
1698 if (*ksp == K_SPECIAL
1699 && (start == NULL || ksp < start || end == NULL)
1700 && ((ksp[1] == KS_SPECIAL && ksp[2] == KE_FILLER)
1701# ifdef FEAT_GUI
1702 || (ksp[1] == KS_EXTRA && ksp[2] == (int)KE_CSI)
1703# endif
1704 ))
1705 {
1706 // K_SPECIAL has been put in the buffer as K_SPECIAL
1707 // KS_SPECIAL KE_FILLER, like for mappings, but
1708 // do_cmdline() doesn't handle that, so convert it back.
1709 // Also change K_SPECIAL KS_EXTRA KE_CSI into CSI.
1710 len = ksp - p;
1711 if (len > 0)
1712 {
1713 mch_memmove(q, p, len);
1714 q += len;
1715 }
1716 *q++ = ksp[1] == KS_SPECIAL ? K_SPECIAL : CSI;
1717 p = ksp + 3;
1718 continue;
1719 }
1720 }
1721
1722 // break if no <item> is found
1723 if (start == NULL || end == NULL)
1724 break;
1725
1726 // Include the '>'
1727 ++end;
1728
1729 // Take everything up to the '<'
1730 len = start - p;
1731 if (buf == NULL)
1732 totlen += len;
1733 else
1734 {
1735 mch_memmove(q, p, len);
1736 q += len;
1737 }
1738
1739 len = uc_check_code(start, end - start, q, cmd, eap,
1740 &split_buf, &split_len);
1741 if (len == (size_t)-1)
1742 {
1743 // no match, continue after '<'
1744 p = start + 1;
1745 len = 1;
1746 }
1747 else
1748 p = end;
1749 if (buf == NULL)
1750 totlen += len;
1751 else
1752 q += len;
1753 }
1754 if (buf != NULL) // second time here, finished
1755 {
1756 STRCPY(q, p);
1757 break;
1758 }
1759
1760 totlen += STRLEN(p); // Add on the trailing characters
Bram Moolenaar964b3742019-05-24 18:54:09 +02001761 buf = alloc(totlen + 1);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001762 if (buf == NULL)
1763 {
1764 vim_free(split_buf);
1765 return;
1766 }
1767 }
1768
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001769 if ((cmd->uc_argt & EX_KEEPSCRIPT) == 0)
1770 {
1771 current_sctx.sc_version = cmd->uc_script_ctx.sc_version;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001772#ifdef FEAT_EVAL
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001773 current_sctx.sc_sid = cmd->uc_script_ctx.sc_sid;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001774#endif
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001775 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001776 (void)do_cmdline(buf, eap->getline, eap->cookie,
1777 DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED);
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001778 if ((cmd->uc_argt & EX_KEEPSCRIPT) == 0)
1779 current_sctx = save_current_sctx;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001780 vim_free(buf);
1781 vim_free(split_buf);
1782}