blob: bc0b870322600492c876e78345953f56eac53d98 [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 *
292get_user_command_name(int idx)
293{
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/*
319 * Function given to ExpandGeneric() to obtain the list of user address type
320 * names.
321 */
322 char_u *
323get_user_cmd_addr_type(expand_T *xp UNUSED, int idx)
324{
325 return (char_u *)addr_type_complete[idx].name;
326}
327
328/*
329 * Function given to ExpandGeneric() to obtain the list of user command
330 * attributes.
331 */
332 char_u *
333get_user_cmd_flags(expand_T *xp UNUSED, int idx)
334{
335 static char *user_cmd_flags[] = {
336 "addr", "bang", "bar", "buffer", "complete",
337 "count", "nargs", "range", "register"
338 };
339
K.Takataeeec2542021-06-02 13:28:16 +0200340 if (idx >= (int)ARRAY_LENGTH(user_cmd_flags))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200341 return NULL;
342 return (char_u *)user_cmd_flags[idx];
343}
344
345/*
346 * Function given to ExpandGeneric() to obtain the list of values for -nargs.
347 */
348 char_u *
349get_user_cmd_nargs(expand_T *xp UNUSED, int idx)
350{
351 static char *user_cmd_nargs[] = {"0", "1", "*", "?", "+"};
352
K.Takataeeec2542021-06-02 13:28:16 +0200353 if (idx >= (int)ARRAY_LENGTH(user_cmd_nargs))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200354 return NULL;
355 return (char_u *)user_cmd_nargs[idx];
356}
357
358/*
359 * Function given to ExpandGeneric() to obtain the list of values for
360 * -complete.
361 */
362 char_u *
363get_user_cmd_complete(expand_T *xp UNUSED, int idx)
364{
365 return (char_u *)command_complete[idx].name;
366}
367
368 int
369cmdcomplete_str_to_type(char_u *complete_str)
370{
371 int i;
372
373 for (i = 0; command_complete[i].expand != 0; ++i)
374 if (STRCMP(complete_str, command_complete[i].name) == 0)
375 return command_complete[i].expand;
376
377 return EXPAND_NOTHING;
378}
379
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200380/*
381 * List user commands starting with "name[name_len]".
382 */
383 static void
384uc_list(char_u *name, size_t name_len)
385{
386 int i, j;
387 int found = FALSE;
388 ucmd_T *cmd;
389 int len;
390 int over;
391 long a;
392 garray_T *gap;
393
Bram Moolenaare38eab22019-12-05 21:50:01 +0100394 // In cmdwin, the alternative buffer should be used.
Bram Moolenaarf03e3282019-07-22 21:55:18 +0200395 gap =
396#ifdef FEAT_CMDWIN
397 (cmdwin_type != 0 && get_cmdline_type() == NUL) ?
398 &prevwin->w_buffer->b_ucmds :
399#endif
400 &curbuf->b_ucmds;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200401 for (;;)
402 {
403 for (i = 0; i < gap->ga_len; ++i)
404 {
405 cmd = USER_CMD_GA(gap, i);
406 a = (long)cmd->uc_argt;
407
408 // Skip commands which don't match the requested prefix and
409 // commands filtered out.
410 if (STRNCMP(name, cmd->uc_name, name_len) != 0
411 || message_filtered(cmd->uc_name))
412 continue;
413
414 // Put out the title first time
415 if (!found)
416 msg_puts_title(_("\n Name Args Address Complete Definition"));
417 found = TRUE;
418 msg_putchar('\n');
419 if (got_int)
420 break;
421
422 // Special cases
423 len = 4;
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200424 if (a & EX_BANG)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200425 {
426 msg_putchar('!');
427 --len;
428 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200429 if (a & EX_REGSTR)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200430 {
431 msg_putchar('"');
432 --len;
433 }
434 if (gap != &ucmds)
435 {
436 msg_putchar('b');
437 --len;
438 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200439 if (a & EX_TRLBAR)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200440 {
441 msg_putchar('|');
442 --len;
443 }
444 while (len-- > 0)
445 msg_putchar(' ');
446
447 msg_outtrans_attr(cmd->uc_name, HL_ATTR(HLF_D));
448 len = (int)STRLEN(cmd->uc_name) + 4;
449
450 do {
451 msg_putchar(' ');
452 ++len;
453 } while (len < 22);
454
455 // "over" is how much longer the name is than the column width for
456 // the name, we'll try to align what comes after.
457 over = len - 22;
458 len = 0;
459
460 // Arguments
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200461 switch ((int)(a & (EX_EXTRA|EX_NOSPC|EX_NEEDARG)))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200462 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200463 case 0: IObuff[len++] = '0'; break;
464 case (EX_EXTRA): IObuff[len++] = '*'; break;
465 case (EX_EXTRA|EX_NOSPC): IObuff[len++] = '?'; break;
466 case (EX_EXTRA|EX_NEEDARG): IObuff[len++] = '+'; break;
467 case (EX_EXTRA|EX_NOSPC|EX_NEEDARG): IObuff[len++] = '1'; break;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200468 }
469
470 do {
471 IObuff[len++] = ' ';
472 } while (len < 5 - over);
473
474 // Address / Range
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200475 if (a & (EX_RANGE|EX_COUNT))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200476 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200477 if (a & EX_COUNT)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200478 {
479 // -count=N
480 sprintf((char *)IObuff + len, "%ldc", cmd->uc_def);
481 len += (int)STRLEN(IObuff + len);
482 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200483 else if (a & EX_DFLALL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200484 IObuff[len++] = '%';
485 else if (cmd->uc_def >= 0)
486 {
487 // -range=N
488 sprintf((char *)IObuff + len, "%ld", cmd->uc_def);
489 len += (int)STRLEN(IObuff + len);
490 }
491 else
492 IObuff[len++] = '.';
493 }
494
495 do {
496 IObuff[len++] = ' ';
497 } while (len < 8 - over);
498
499 // Address Type
Bram Moolenaarb7316892019-05-01 18:08:42 +0200500 for (j = 0; addr_type_complete[j].expand != ADDR_NONE; ++j)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200501 if (addr_type_complete[j].expand != ADDR_LINES
502 && addr_type_complete[j].expand == cmd->uc_addr_type)
503 {
504 STRCPY(IObuff + len, addr_type_complete[j].shortname);
505 len += (int)STRLEN(IObuff + len);
506 break;
507 }
508
509 do {
510 IObuff[len++] = ' ';
511 } while (len < 13 - over);
512
513 // Completion
514 for (j = 0; command_complete[j].expand != 0; ++j)
515 if (command_complete[j].expand == cmd->uc_compl)
516 {
517 STRCPY(IObuff + len, command_complete[j].name);
518 len += (int)STRLEN(IObuff + len);
519 break;
520 }
521
522 do {
523 IObuff[len++] = ' ';
524 } while (len < 25 - over);
525
526 IObuff[len] = '\0';
527 msg_outtrans(IObuff);
528
529 msg_outtrans_special(cmd->uc_rep, FALSE,
530 name_len == 0 ? Columns - 47 : 0);
531#ifdef FEAT_EVAL
532 if (p_verbose > 0)
533 last_set_msg(cmd->uc_script_ctx);
534#endif
535 out_flush();
536 ui_breakcheck();
537 if (got_int)
538 break;
539 }
540 if (gap == &ucmds || i < gap->ga_len)
541 break;
542 gap = &ucmds;
543 }
544
545 if (!found)
546 msg(_("No user-defined commands found"));
547}
548
549 char *
550uc_fun_cmd(void)
551{
552 static char_u fcmd[] = {0x84, 0xaf, 0x60, 0xb9, 0xaf, 0xb5, 0x60, 0xa4,
553 0xa5, 0xad, 0xa1, 0xae, 0xa4, 0x60, 0xa1, 0x60,
554 0xb3, 0xa8, 0xb2, 0xb5, 0xa2, 0xa2, 0xa5, 0xb2,
555 0xb9, 0x7f, 0};
556 int i;
557
558 for (i = 0; fcmd[i]; ++i)
559 IObuff[i] = fcmd[i] - 0x40;
560 IObuff[i] = 0;
561 return (char *)IObuff;
562}
563
564/*
565 * Parse address type argument
566 */
567 static int
568parse_addr_type_arg(
569 char_u *value,
570 int vallen,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200571 cmd_addr_T *addr_type_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200572{
573 int i, a, b;
574
Bram Moolenaarb7316892019-05-01 18:08:42 +0200575 for (i = 0; addr_type_complete[i].expand != ADDR_NONE; ++i)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200576 {
577 a = (int)STRLEN(addr_type_complete[i].name) == vallen;
578 b = STRNCMP(value, addr_type_complete[i].name, vallen) == 0;
579 if (a && b)
580 {
581 *addr_type_arg = addr_type_complete[i].expand;
582 break;
583 }
584 }
585
Bram Moolenaarb7316892019-05-01 18:08:42 +0200586 if (addr_type_complete[i].expand == ADDR_NONE)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200587 {
588 char_u *err = value;
589
590 for (i = 0; err[i] != NUL && !VIM_ISWHITE(err[i]); i++)
591 ;
592 err[i] = NUL;
593 semsg(_("E180: Invalid address type value: %s"), err);
594 return FAIL;
595 }
596
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200597 return OK;
598}
599
600/*
601 * Parse a completion argument "value[vallen]".
602 * The detected completion goes in "*complp", argument type in "*argt".
603 * When there is an argument, for function and user defined completion, it's
604 * copied to allocated memory and stored in "*compl_arg".
605 * Returns FAIL if something is wrong.
606 */
607 int
608parse_compl_arg(
609 char_u *value,
610 int vallen,
611 int *complp,
612 long *argt,
613 char_u **compl_arg UNUSED)
614{
615 char_u *arg = NULL;
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200616# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200617 size_t arglen = 0;
618# endif
619 int i;
620 int valend = vallen;
621
622 // Look for any argument part - which is the part after any ','
623 for (i = 0; i < vallen; ++i)
624 {
625 if (value[i] == ',')
626 {
627 arg = &value[i + 1];
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200628# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200629 arglen = vallen - i - 1;
630# endif
631 valend = i;
632 break;
633 }
634 }
635
636 for (i = 0; command_complete[i].expand != 0; ++i)
637 {
638 if ((int)STRLEN(command_complete[i].name) == valend
639 && STRNCMP(value, command_complete[i].name, valend) == 0)
640 {
641 *complp = command_complete[i].expand;
642 if (command_complete[i].expand == EXPAND_BUFFERS)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200643 *argt |= EX_BUFNAME;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200644 else if (command_complete[i].expand == EXPAND_DIRECTORIES
645 || command_complete[i].expand == EXPAND_FILES)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200646 *argt |= EX_XFILE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200647 break;
648 }
649 }
650
651 if (command_complete[i].expand == 0)
652 {
653 semsg(_("E180: Invalid complete value: %s"), value);
654 return FAIL;
655 }
656
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200657# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200658 if (*complp != EXPAND_USER_DEFINED && *complp != EXPAND_USER_LIST
659 && arg != NULL)
660# else
661 if (arg != NULL)
662# endif
663 {
664 emsg(_("E468: Completion argument only allowed for custom completion"));
665 return FAIL;
666 }
667
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200668# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200669 if ((*complp == EXPAND_USER_DEFINED || *complp == EXPAND_USER_LIST)
670 && arg == NULL)
671 {
672 emsg(_("E467: Custom completion requires a function argument"));
673 return FAIL;
674 }
675
676 if (arg != NULL)
Bram Moolenaar71ccd032020-06-12 22:59:11 +0200677 *compl_arg = vim_strnsave(arg, arglen);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200678# endif
679 return OK;
680}
681
682/*
683 * Scan attributes in the ":command" command.
684 * Return FAIL when something is wrong.
685 */
686 static int
687uc_scan_attr(
688 char_u *attr,
689 size_t len,
690 long *argt,
691 long *def,
692 int *flags,
Bram Moolenaar52111f82019-04-29 21:30:45 +0200693 int *complp,
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200694 char_u **compl_arg,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200695 cmd_addr_T *addr_type_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200696{
697 char_u *p;
698
699 if (len == 0)
700 {
701 emsg(_("E175: No attribute specified"));
702 return FAIL;
703 }
704
705 // First, try the simple attributes (no arguments)
706 if (STRNICMP(attr, "bang", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200707 *argt |= EX_BANG;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200708 else if (STRNICMP(attr, "buffer", len) == 0)
709 *flags |= UC_BUFFER;
710 else if (STRNICMP(attr, "register", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200711 *argt |= EX_REGSTR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200712 else if (STRNICMP(attr, "bar", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200713 *argt |= EX_TRLBAR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200714 else
715 {
716 int i;
717 char_u *val = NULL;
718 size_t vallen = 0;
719 size_t attrlen = len;
720
721 // Look for the attribute name - which is the part before any '='
722 for (i = 0; i < (int)len; ++i)
723 {
724 if (attr[i] == '=')
725 {
726 val = &attr[i + 1];
727 vallen = len - i - 1;
728 attrlen = i;
729 break;
730 }
731 }
732
733 if (STRNICMP(attr, "nargs", attrlen) == 0)
734 {
735 if (vallen == 1)
736 {
737 if (*val == '0')
738 // Do nothing - this is the default
739 ;
740 else if (*val == '1')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200741 *argt |= (EX_EXTRA | EX_NOSPC | EX_NEEDARG);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200742 else if (*val == '*')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200743 *argt |= EX_EXTRA;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200744 else if (*val == '?')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200745 *argt |= (EX_EXTRA | EX_NOSPC);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200746 else if (*val == '+')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200747 *argt |= (EX_EXTRA | EX_NEEDARG);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200748 else
749 goto wrong_nargs;
750 }
751 else
752 {
753wrong_nargs:
754 emsg(_("E176: Invalid number of arguments"));
755 return FAIL;
756 }
757 }
758 else if (STRNICMP(attr, "range", attrlen) == 0)
759 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200760 *argt |= EX_RANGE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200761 if (vallen == 1 && *val == '%')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200762 *argt |= EX_DFLALL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200763 else if (val != NULL)
764 {
765 p = val;
766 if (*def >= 0)
767 {
768two_count:
769 emsg(_("E177: Count cannot be specified twice"));
770 return FAIL;
771 }
772
773 *def = getdigits(&p);
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200774 *argt |= EX_ZEROR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200775
776 if (p != val + vallen || vallen == 0)
777 {
778invalid_count:
779 emsg(_("E178: Invalid default value for count"));
780 return FAIL;
781 }
782 }
Bram Moolenaarb7316892019-05-01 18:08:42 +0200783 // default for -range is using buffer lines
784 if (*addr_type_arg == ADDR_NONE)
785 *addr_type_arg = ADDR_LINES;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200786 }
787 else if (STRNICMP(attr, "count", attrlen) == 0)
788 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200789 *argt |= (EX_COUNT | EX_ZEROR | EX_RANGE);
Bram Moolenaarb7316892019-05-01 18:08:42 +0200790 // default for -count is using any number
791 if (*addr_type_arg == ADDR_NONE)
792 *addr_type_arg = ADDR_OTHER;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200793
794 if (val != NULL)
795 {
796 p = val;
797 if (*def >= 0)
798 goto two_count;
799
800 *def = getdigits(&p);
801
802 if (p != val + vallen)
803 goto invalid_count;
804 }
805
806 if (*def < 0)
807 *def = 0;
808 }
809 else if (STRNICMP(attr, "complete", attrlen) == 0)
810 {
811 if (val == NULL)
812 {
813 emsg(_("E179: argument required for -complete"));
814 return FAIL;
815 }
816
Bram Moolenaar52111f82019-04-29 21:30:45 +0200817 if (parse_compl_arg(val, (int)vallen, complp, argt, compl_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200818 == FAIL)
819 return FAIL;
820 }
821 else if (STRNICMP(attr, "addr", attrlen) == 0)
822 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200823 *argt |= EX_RANGE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200824 if (val == NULL)
825 {
826 emsg(_("E179: argument required for -addr"));
827 return FAIL;
828 }
Bram Moolenaare4f5f3a2019-05-04 14:05:08 +0200829 if (parse_addr_type_arg(val, (int)vallen, addr_type_arg) == FAIL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200830 return FAIL;
Bram Moolenaare4f5f3a2019-05-04 14:05:08 +0200831 if (*addr_type_arg != ADDR_LINES)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200832 *argt |= EX_ZEROR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200833 }
834 else
835 {
836 char_u ch = attr[len];
837 attr[len] = '\0';
838 semsg(_("E181: Invalid attribute: %s"), attr);
839 attr[len] = ch;
840 return FAIL;
841 }
842 }
843
844 return OK;
845}
846
847/*
848 * Add a user command to the list or replace an existing one.
849 */
850 static int
851uc_add_command(
852 char_u *name,
853 size_t name_len,
854 char_u *rep,
855 long argt,
856 long def,
857 int flags,
858 int compl,
859 char_u *compl_arg UNUSED,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200860 cmd_addr_T addr_type,
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200861 int force)
862{
863 ucmd_T *cmd = NULL;
864 char_u *p;
865 int i;
866 int cmp = 1;
867 char_u *rep_buf = NULL;
868 garray_T *gap;
869
Bram Moolenaar459fd782019-10-13 16:43:39 +0200870 replace_termcodes(rep, &rep_buf, 0, NULL);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200871 if (rep_buf == NULL)
872 {
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +0200873 // can't replace termcodes - try using the string as is
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200874 rep_buf = vim_strsave(rep);
875
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +0200876 // give up if out of memory
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200877 if (rep_buf == NULL)
878 return FAIL;
879 }
880
881 // get address of growarray: global or in curbuf
882 if (flags & UC_BUFFER)
883 {
884 gap = &curbuf->b_ucmds;
885 if (gap->ga_itemsize == 0)
886 ga_init2(gap, (int)sizeof(ucmd_T), 4);
887 }
888 else
889 gap = &ucmds;
890
891 // Search for the command in the already defined commands.
892 for (i = 0; i < gap->ga_len; ++i)
893 {
894 size_t len;
895
896 cmd = USER_CMD_GA(gap, i);
897 len = STRLEN(cmd->uc_name);
898 cmp = STRNCMP(name, cmd->uc_name, name_len);
899 if (cmp == 0)
900 {
901 if (name_len < len)
902 cmp = -1;
903 else if (name_len > len)
904 cmp = 1;
905 }
906
907 if (cmp == 0)
908 {
909 // Command can be replaced with "command!" and when sourcing the
910 // same script again, but only once.
911 if (!force
912#ifdef FEAT_EVAL
913 && (cmd->uc_script_ctx.sc_sid != current_sctx.sc_sid
914 || cmd->uc_script_ctx.sc_seq == current_sctx.sc_seq)
915#endif
916 )
917 {
918 semsg(_("E174: Command already exists: add ! to replace it: %s"),
919 name);
920 goto fail;
921 }
922
923 VIM_CLEAR(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200924#if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200925 VIM_CLEAR(cmd->uc_compl_arg);
926#endif
927 break;
928 }
929
930 // Stop as soon as we pass the name to add
931 if (cmp < 0)
932 break;
933 }
934
935 // Extend the array unless we're replacing an existing command
936 if (cmp != 0)
937 {
938 if (ga_grow(gap, 1) != OK)
939 goto fail;
Bram Moolenaar71ccd032020-06-12 22:59:11 +0200940 if ((p = vim_strnsave(name, name_len)) == NULL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200941 goto fail;
942
943 cmd = USER_CMD_GA(gap, i);
944 mch_memmove(cmd + 1, cmd, (gap->ga_len - i) * sizeof(ucmd_T));
945
946 ++gap->ga_len;
947
948 cmd->uc_name = p;
949 }
950
951 cmd->uc_rep = rep_buf;
952 cmd->uc_argt = argt;
953 cmd->uc_def = def;
954 cmd->uc_compl = compl;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200955 cmd->uc_script_ctx = current_sctx;
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +0200956 if (flags & UC_VIM9)
957 cmd->uc_script_ctx.sc_version = SCRIPT_VERSION_VIM9;
Bram Moolenaar9b8d6222020-12-28 18:26:00 +0100958#ifdef FEAT_EVAL
Bram Moolenaar1a47ae32019-12-29 23:04:25 +0100959 cmd->uc_script_ctx.sc_lnum += SOURCING_LNUM;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200960 cmd->uc_compl_arg = compl_arg;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200961#endif
962 cmd->uc_addr_type = addr_type;
963
964 return OK;
965
966fail:
967 vim_free(rep_buf);
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200968#if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200969 vim_free(compl_arg);
970#endif
971 return FAIL;
972}
973
974/*
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +0200975 * If "p" starts with "{" then read a block of commands until "}".
976 * Used for ":command" and ":autocmd".
977 */
978 char_u *
979may_get_cmd_block(exarg_T *eap, char_u *p, char_u **tofree, int *flags)
980{
981 char_u *retp = p;
982
983 if (*p == '{' && ends_excmd2(eap->arg, skipwhite(p + 1))
984 && eap->getline != NULL)
985 {
Bram Moolenaare4db17f2021-08-01 21:19:43 +0200986 garray_T ga;
987 char_u *line = NULL;
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +0200988
989 ga_init2(&ga, sizeof(char_u *), 10);
990 if (ga_add_string(&ga, p) == FAIL)
991 return retp;
992
Bram Moolenaare4db17f2021-08-01 21:19:43 +0200993 // If the argument ends in "}" it must have been concatenated already
994 // for ISN_EXEC.
995 if (p[STRLEN(p) - 1] != '}')
996 // Read lines between '{' and '}'. Does not support nesting or
997 // here-doc constructs.
998 for (;;)
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +0200999 {
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001000 vim_free(line);
1001 if ((line = eap->getline(':', eap->cookie,
1002 0, GETLINE_CONCAT_CONTBAR)) == NULL)
1003 {
1004 emsg(_(e_missing_rcurly));
1005 break;
1006 }
1007 if (ga_add_string(&ga, line) == FAIL)
1008 break;
1009 if (*skipwhite(line) == '}')
1010 break;
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001011 }
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001012 vim_free(line);
1013 retp = *tofree = ga_concat_strings(&ga, "\n");
1014 ga_clear_strings(&ga);
1015 *flags |= UC_VIM9;
1016 }
1017 return retp;
1018}
1019
1020/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001021 * ":command ..." implementation
1022 */
1023 void
1024ex_command(exarg_T *eap)
1025{
Bram Moolenaarb7316892019-05-01 18:08:42 +02001026 char_u *name;
1027 char_u *end;
1028 char_u *p;
1029 long argt = 0;
1030 long def = -1;
1031 int flags = 0;
1032 int compl = EXPAND_NOTHING;
1033 char_u *compl_arg = NULL;
1034 cmd_addr_T addr_type_arg = ADDR_NONE;
1035 int has_attr = (eap->arg[0] == '-');
1036 int name_len;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001037
1038 p = eap->arg;
1039
1040 // Check for attributes
1041 while (*p == '-')
1042 {
1043 ++p;
1044 end = skiptowhite(p);
1045 if (uc_scan_attr(p, end - p, &argt, &def, &flags, &compl,
1046 &compl_arg, &addr_type_arg) == FAIL)
1047 return;
1048 p = skipwhite(end);
1049 }
1050
1051 // Get the name (if any) and skip to the following argument
1052 name = p;
1053 if (ASCII_ISALPHA(*p))
1054 while (ASCII_ISALNUM(*p))
1055 ++p;
Bram Moolenaara72cfb82020-04-23 17:07:30 +02001056 if (!ends_excmd2(eap->arg, p) && !VIM_ISWHITE(*p))
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001057 {
1058 emsg(_("E182: Invalid command name"));
1059 return;
1060 }
1061 end = p;
1062 name_len = (int)(end - name);
1063
1064 // If there is nothing after the name, and no attributes were specified,
1065 // we are listing commands
1066 p = skipwhite(end);
Bram Moolenaara72cfb82020-04-23 17:07:30 +02001067 if (!has_attr && ends_excmd2(eap->arg, p))
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001068 uc_list(name, end - name);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001069 else if (!ASCII_ISUPPER(*name))
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001070 emsg(_("E183: User defined commands must start with an uppercase letter"));
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001071 else if ((name_len == 1 && *name == 'X')
1072 || (name_len <= 4
1073 && STRNCMP(name, "Next", name_len > 4 ? 4 : name_len) == 0))
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001074 emsg(_("E841: Reserved name, cannot be used for user defined command"));
Martin Tournoijde69a732021-07-11 14:28:25 +02001075 else if (compl > 0 && (argt & EX_EXTRA) == 0)
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001076 {
1077 // Some plugins rely on silently ignoring the mistake, only make this
1078 // an error in Vim9 script.
1079 if (in_vim9script())
Bram Moolenaar41a34852021-08-04 16:09:24 +02001080 emsg(_(e_complete_used_without_allowing_arguments));
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001081 else
1082 give_warning_with_source(
Bram Moolenaar41a34852021-08-04 16:09:24 +02001083 (char_u *)_(e_complete_used_without_allowing_arguments),
1084 TRUE, TRUE);
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001085 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001086 else
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001087 {
1088 char_u *tofree = NULL;
1089
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001090 p = may_get_cmd_block(eap, p, &tofree, &flags);
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001091
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001092 uc_add_command(name, end - name, p, argt, def, flags, compl, compl_arg,
1093 addr_type_arg, eap->forceit);
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001094 vim_free(tofree);
1095 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001096}
1097
1098/*
1099 * ":comclear" implementation
1100 * Clear all user commands, global and for current buffer.
1101 */
1102 void
1103ex_comclear(exarg_T *eap UNUSED)
1104{
1105 uc_clear(&ucmds);
Bram Moolenaare5c83282019-05-03 23:15:37 +02001106 if (curbuf != NULL)
1107 uc_clear(&curbuf->b_ucmds);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001108}
1109
1110/*
1111 * Clear all user commands for "gap".
1112 */
1113 void
1114uc_clear(garray_T *gap)
1115{
1116 int i;
1117 ucmd_T *cmd;
1118
1119 for (i = 0; i < gap->ga_len; ++i)
1120 {
1121 cmd = USER_CMD_GA(gap, i);
1122 vim_free(cmd->uc_name);
1123 vim_free(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001124# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001125 vim_free(cmd->uc_compl_arg);
1126# endif
1127 }
1128 ga_clear(gap);
1129}
1130
1131/*
1132 * ":delcommand" implementation
1133 */
1134 void
1135ex_delcommand(exarg_T *eap)
1136{
1137 int i = 0;
1138 ucmd_T *cmd = NULL;
1139 int cmp = -1;
1140 garray_T *gap;
1141
1142 gap = &curbuf->b_ucmds;
1143 for (;;)
1144 {
1145 for (i = 0; i < gap->ga_len; ++i)
1146 {
1147 cmd = USER_CMD_GA(gap, i);
1148 cmp = STRCMP(eap->arg, cmd->uc_name);
1149 if (cmp <= 0)
1150 break;
1151 }
1152 if (gap == &ucmds || cmp == 0)
1153 break;
1154 gap = &ucmds;
1155 }
1156
1157 if (cmp != 0)
1158 {
1159 semsg(_("E184: No such user-defined command: %s"), eap->arg);
1160 return;
1161 }
1162
1163 vim_free(cmd->uc_name);
1164 vim_free(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001165# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001166 vim_free(cmd->uc_compl_arg);
1167# endif
1168
1169 --gap->ga_len;
1170
1171 if (i < gap->ga_len)
1172 mch_memmove(cmd, cmd + 1, (gap->ga_len - i) * sizeof(ucmd_T));
1173}
1174
1175/*
1176 * Split and quote args for <f-args>.
1177 */
1178 static char_u *
1179uc_split_args(char_u *arg, size_t *lenp)
1180{
1181 char_u *buf;
1182 char_u *p;
1183 char_u *q;
1184 int len;
1185
1186 // Precalculate length
1187 p = arg;
1188 len = 2; // Initial and final quotes
1189
1190 while (*p)
1191 {
1192 if (p[0] == '\\' && p[1] == '\\')
1193 {
1194 len += 2;
1195 p += 2;
1196 }
1197 else if (p[0] == '\\' && VIM_ISWHITE(p[1]))
1198 {
1199 len += 1;
1200 p += 2;
1201 }
1202 else if (*p == '\\' || *p == '"')
1203 {
1204 len += 2;
1205 p += 1;
1206 }
1207 else if (VIM_ISWHITE(*p))
1208 {
1209 p = skipwhite(p);
1210 if (*p == NUL)
1211 break;
Bram Moolenaar20d89e02020-10-20 23:11:33 +02001212 len += 4; // ", "
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001213 }
1214 else
1215 {
1216 int charlen = (*mb_ptr2len)(p);
1217
1218 len += charlen;
1219 p += charlen;
1220 }
1221 }
1222
1223 buf = alloc(len + 1);
1224 if (buf == NULL)
1225 {
1226 *lenp = 0;
1227 return buf;
1228 }
1229
1230 p = arg;
1231 q = buf;
1232 *q++ = '"';
1233 while (*p)
1234 {
1235 if (p[0] == '\\' && p[1] == '\\')
1236 {
1237 *q++ = '\\';
1238 *q++ = '\\';
1239 p += 2;
1240 }
1241 else if (p[0] == '\\' && VIM_ISWHITE(p[1]))
1242 {
1243 *q++ = p[1];
1244 p += 2;
1245 }
1246 else if (*p == '\\' || *p == '"')
1247 {
1248 *q++ = '\\';
1249 *q++ = *p++;
1250 }
1251 else if (VIM_ISWHITE(*p))
1252 {
1253 p = skipwhite(p);
1254 if (*p == NUL)
1255 break;
1256 *q++ = '"';
1257 *q++ = ',';
Bram Moolenaar20d89e02020-10-20 23:11:33 +02001258 *q++ = ' ';
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001259 *q++ = '"';
1260 }
1261 else
1262 {
1263 MB_COPY_CHAR(p, q);
1264 }
1265 }
1266 *q++ = '"';
1267 *q = 0;
1268
1269 *lenp = len;
1270 return buf;
1271}
1272
1273 static size_t
1274add_cmd_modifier(char_u *buf, char *mod_str, int *multi_mods)
1275{
1276 size_t result;
1277
1278 result = STRLEN(mod_str);
1279 if (*multi_mods)
1280 result += 1;
1281 if (buf != NULL)
1282 {
1283 if (*multi_mods)
1284 STRCAT(buf, " ");
1285 STRCAT(buf, mod_str);
1286 }
1287
1288 *multi_mods = 1;
1289
1290 return result;
1291}
1292
1293/*
Bram Moolenaar02194d22020-10-24 23:08:38 +02001294 * Add modifiers from "cmod->cmod_split" to "buf". Set "multi_mods" when one
Bram Moolenaare1004402020-10-24 20:49:43 +02001295 * was added. Return the number of bytes added.
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001296 */
1297 size_t
Bram Moolenaar02194d22020-10-24 23:08:38 +02001298add_win_cmd_modifers(char_u *buf, cmdmod_T *cmod, int *multi_mods)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001299{
1300 size_t result = 0;
1301
1302 // :aboveleft and :leftabove
Bram Moolenaar02194d22020-10-24 23:08:38 +02001303 if (cmod->cmod_split & WSP_ABOVE)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001304 result += add_cmd_modifier(buf, "aboveleft", multi_mods);
1305 // :belowright and :rightbelow
Bram Moolenaar02194d22020-10-24 23:08:38 +02001306 if (cmod->cmod_split & WSP_BELOW)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001307 result += add_cmd_modifier(buf, "belowright", multi_mods);
1308 // :botright
Bram Moolenaar02194d22020-10-24 23:08:38 +02001309 if (cmod->cmod_split & WSP_BOT)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001310 result += add_cmd_modifier(buf, "botright", multi_mods);
1311
1312 // :tab
Bram Moolenaar02194d22020-10-24 23:08:38 +02001313 if (cmod->cmod_tab > 0)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001314 result += add_cmd_modifier(buf, "tab", multi_mods);
1315 // :topleft
Bram Moolenaar02194d22020-10-24 23:08:38 +02001316 if (cmod->cmod_split & WSP_TOP)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001317 result += add_cmd_modifier(buf, "topleft", multi_mods);
1318 // :vertical
Bram Moolenaar02194d22020-10-24 23:08:38 +02001319 if (cmod->cmod_split & WSP_VERT)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001320 result += add_cmd_modifier(buf, "vertical", multi_mods);
1321 return result;
1322}
1323
1324/*
Bram Moolenaar02194d22020-10-24 23:08:38 +02001325 * Generate text for the "cmod" command modifiers.
1326 * If "buf" is NULL just return the length.
1327 */
Bram Moolenaara360dbe2020-10-26 18:46:53 +01001328 size_t
Bram Moolenaar02194d22020-10-24 23:08:38 +02001329produce_cmdmods(char_u *buf, cmdmod_T *cmod, int quote)
1330{
Bram Moolenaara360dbe2020-10-26 18:46:53 +01001331 size_t result = 0;
Bram Moolenaar02194d22020-10-24 23:08:38 +02001332 int multi_mods = 0;
1333 int i;
1334 typedef struct {
1335 int flag;
1336 char *name;
1337 } mod_entry_T;
1338 static mod_entry_T mod_entries[] = {
1339#ifdef FEAT_BROWSE_CMD
1340 {CMOD_BROWSE, "browse"},
1341#endif
1342#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
1343 {CMOD_CONFIRM, "confirm"},
1344#endif
1345 {CMOD_HIDE, "hide"},
1346 {CMOD_KEEPALT, "keepalt"},
1347 {CMOD_KEEPJUMPS, "keepjumps"},
1348 {CMOD_KEEPMARKS, "keepmarks"},
1349 {CMOD_KEEPPATTERNS, "keeppatterns"},
1350 {CMOD_LOCKMARKS, "lockmarks"},
1351 {CMOD_NOSWAPFILE, "noswapfile"},
1352 {CMOD_UNSILENT, "unsilent"},
1353 {CMOD_NOAUTOCMD, "noautocmd"},
1354#ifdef HAVE_SANDBOX
1355 {CMOD_SANDBOX, "sandbox"},
1356#endif
1357 {0, NULL}
1358 };
1359
1360 result = quote ? 2 : 0;
1361 if (buf != NULL)
1362 {
1363 if (quote)
1364 *buf++ = '"';
1365 *buf = '\0';
1366 }
1367
1368 // the modifiers that are simple flags
1369 for (i = 0; mod_entries[i].name != NULL; ++i)
1370 if (cmod->cmod_flags & mod_entries[i].flag)
1371 result += add_cmd_modifier(buf, mod_entries[i].name, &multi_mods);
1372
1373 // :silent
1374 if (cmod->cmod_flags & CMOD_SILENT)
1375 result += add_cmd_modifier(buf,
1376 (cmod->cmod_flags & CMOD_ERRSILENT) ? "silent!"
1377 : "silent", &multi_mods);
1378 // :verbose
1379 if (p_verbose > 0)
1380 result += add_cmd_modifier(buf, "verbose", &multi_mods);
1381 // flags from cmod->cmod_split
1382 result += add_win_cmd_modifers(buf, cmod, &multi_mods);
1383 if (quote && buf != NULL)
1384 {
1385 buf += result - 2;
1386 *buf = '"';
1387 }
1388 return result;
1389}
1390
1391/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001392 * Check for a <> code in a user command.
1393 * "code" points to the '<'. "len" the length of the <> (inclusive).
1394 * "buf" is where the result is to be added.
1395 * "split_buf" points to a buffer used for splitting, caller should free it.
1396 * "split_len" is the length of what "split_buf" contains.
1397 * Returns the length of the replacement, which has been added to "buf".
1398 * Returns -1 if there was no match, and only the "<" has been copied.
1399 */
1400 static size_t
1401uc_check_code(
1402 char_u *code,
1403 size_t len,
1404 char_u *buf,
1405 ucmd_T *cmd, // the user command we're expanding
1406 exarg_T *eap, // ex arguments
1407 char_u **split_buf,
1408 size_t *split_len)
1409{
1410 size_t result = 0;
1411 char_u *p = code + 1;
1412 size_t l = len - 2;
1413 int quote = 0;
1414 enum {
1415 ct_ARGS,
1416 ct_BANG,
1417 ct_COUNT,
1418 ct_LINE1,
1419 ct_LINE2,
1420 ct_RANGE,
1421 ct_MODS,
1422 ct_REGISTER,
1423 ct_LT,
1424 ct_NONE
1425 } type = ct_NONE;
1426
1427 if ((vim_strchr((char_u *)"qQfF", *p) != NULL) && p[1] == '-')
1428 {
1429 quote = (*p == 'q' || *p == 'Q') ? 1 : 2;
1430 p += 2;
1431 l -= 2;
1432 }
1433
1434 ++l;
1435 if (l <= 1)
1436 type = ct_NONE;
1437 else if (STRNICMP(p, "args>", l) == 0)
1438 type = ct_ARGS;
1439 else if (STRNICMP(p, "bang>", l) == 0)
1440 type = ct_BANG;
1441 else if (STRNICMP(p, "count>", l) == 0)
1442 type = ct_COUNT;
1443 else if (STRNICMP(p, "line1>", l) == 0)
1444 type = ct_LINE1;
1445 else if (STRNICMP(p, "line2>", l) == 0)
1446 type = ct_LINE2;
1447 else if (STRNICMP(p, "range>", l) == 0)
1448 type = ct_RANGE;
1449 else if (STRNICMP(p, "lt>", l) == 0)
1450 type = ct_LT;
1451 else if (STRNICMP(p, "reg>", l) == 0 || STRNICMP(p, "register>", l) == 0)
1452 type = ct_REGISTER;
1453 else if (STRNICMP(p, "mods>", l) == 0)
1454 type = ct_MODS;
1455
1456 switch (type)
1457 {
1458 case ct_ARGS:
1459 // Simple case first
1460 if (*eap->arg == NUL)
1461 {
1462 if (quote == 1)
1463 {
1464 result = 2;
1465 if (buf != NULL)
1466 STRCPY(buf, "''");
1467 }
1468 else
1469 result = 0;
1470 break;
1471 }
1472
1473 // When specified there is a single argument don't split it.
1474 // Works for ":Cmd %" when % is "a b c".
Bram Moolenaar8071cb22019-07-12 17:58:01 +02001475 if ((eap->argt & EX_NOSPC) && quote == 2)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001476 quote = 1;
1477
1478 switch (quote)
1479 {
1480 case 0: // No quoting, no splitting
1481 result = STRLEN(eap->arg);
1482 if (buf != NULL)
1483 STRCPY(buf, eap->arg);
1484 break;
1485 case 1: // Quote, but don't split
1486 result = STRLEN(eap->arg) + 2;
1487 for (p = eap->arg; *p; ++p)
1488 {
1489 if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2)
1490 // DBCS can contain \ in a trail byte, skip the
1491 // double-byte character.
1492 ++p;
1493 else
1494 if (*p == '\\' || *p == '"')
1495 ++result;
1496 }
1497
1498 if (buf != NULL)
1499 {
1500 *buf++ = '"';
1501 for (p = eap->arg; *p; ++p)
1502 {
1503 if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2)
1504 // DBCS can contain \ in a trail byte, copy the
1505 // double-byte character to avoid escaping.
1506 *buf++ = *p++;
1507 else
1508 if (*p == '\\' || *p == '"')
1509 *buf++ = '\\';
1510 *buf++ = *p;
1511 }
1512 *buf = '"';
1513 }
1514
1515 break;
1516 case 2: // Quote and split (<f-args>)
1517 // This is hard, so only do it once, and cache the result
1518 if (*split_buf == NULL)
1519 *split_buf = uc_split_args(eap->arg, split_len);
1520
1521 result = *split_len;
1522 if (buf != NULL && result != 0)
1523 STRCPY(buf, *split_buf);
1524
1525 break;
1526 }
1527 break;
1528
1529 case ct_BANG:
1530 result = eap->forceit ? 1 : 0;
1531 if (quote)
1532 result += 2;
1533 if (buf != NULL)
1534 {
1535 if (quote)
1536 *buf++ = '"';
1537 if (eap->forceit)
1538 *buf++ = '!';
1539 if (quote)
1540 *buf = '"';
1541 }
1542 break;
1543
1544 case ct_LINE1:
1545 case ct_LINE2:
1546 case ct_RANGE:
1547 case ct_COUNT:
1548 {
1549 char num_buf[20];
1550 long num = (type == ct_LINE1) ? eap->line1 :
1551 (type == ct_LINE2) ? eap->line2 :
1552 (type == ct_RANGE) ? eap->addr_count :
1553 (eap->addr_count > 0) ? eap->line2 : cmd->uc_def;
1554 size_t num_len;
1555
1556 sprintf(num_buf, "%ld", num);
1557 num_len = STRLEN(num_buf);
1558 result = num_len;
1559
1560 if (quote)
1561 result += 2;
1562
1563 if (buf != NULL)
1564 {
1565 if (quote)
1566 *buf++ = '"';
1567 STRCPY(buf, num_buf);
1568 buf += num_len;
1569 if (quote)
1570 *buf = '"';
1571 }
1572
1573 break;
1574 }
1575
1576 case ct_MODS:
1577 {
Bram Moolenaar02194d22020-10-24 23:08:38 +02001578 result = produce_cmdmods(buf, &cmdmod, quote);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001579 break;
1580 }
1581
1582 case ct_REGISTER:
1583 result = eap->regname ? 1 : 0;
1584 if (quote)
1585 result += 2;
1586 if (buf != NULL)
1587 {
1588 if (quote)
1589 *buf++ = '\'';
1590 if (eap->regname)
1591 *buf++ = eap->regname;
1592 if (quote)
1593 *buf = '\'';
1594 }
1595 break;
1596
1597 case ct_LT:
1598 result = 1;
1599 if (buf != NULL)
1600 *buf = '<';
1601 break;
1602
1603 default:
1604 // Not recognized: just copy the '<' and return -1.
1605 result = (size_t)-1;
1606 if (buf != NULL)
1607 *buf = '<';
1608 break;
1609 }
1610
1611 return result;
1612}
1613
1614/*
1615 * Execute a user defined command.
1616 */
1617 void
1618do_ucmd(exarg_T *eap)
1619{
1620 char_u *buf;
1621 char_u *p;
1622 char_u *q;
1623
1624 char_u *start;
1625 char_u *end = NULL;
1626 char_u *ksp;
1627 size_t len, totlen;
1628
1629 size_t split_len = 0;
1630 char_u *split_buf = NULL;
1631 ucmd_T *cmd;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001632 sctx_T save_current_sctx = current_sctx;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001633
1634 if (eap->cmdidx == CMD_USER)
1635 cmd = USER_CMD(eap->useridx);
1636 else
1637 cmd = USER_CMD_GA(&curbuf->b_ucmds, eap->useridx);
1638
1639 /*
1640 * Replace <> in the command by the arguments.
1641 * First round: "buf" is NULL, compute length, allocate "buf".
1642 * Second round: copy result into "buf".
1643 */
1644 buf = NULL;
1645 for (;;)
1646 {
1647 p = cmd->uc_rep; // source
1648 q = buf; // destination
1649 totlen = 0;
1650
1651 for (;;)
1652 {
1653 start = vim_strchr(p, '<');
1654 if (start != NULL)
1655 end = vim_strchr(start + 1, '>');
1656 if (buf != NULL)
1657 {
1658 for (ksp = p; *ksp != NUL && *ksp != K_SPECIAL; ++ksp)
1659 ;
1660 if (*ksp == K_SPECIAL
1661 && (start == NULL || ksp < start || end == NULL)
1662 && ((ksp[1] == KS_SPECIAL && ksp[2] == KE_FILLER)
1663# ifdef FEAT_GUI
1664 || (ksp[1] == KS_EXTRA && ksp[2] == (int)KE_CSI)
1665# endif
1666 ))
1667 {
1668 // K_SPECIAL has been put in the buffer as K_SPECIAL
1669 // KS_SPECIAL KE_FILLER, like for mappings, but
1670 // do_cmdline() doesn't handle that, so convert it back.
1671 // Also change K_SPECIAL KS_EXTRA KE_CSI into CSI.
1672 len = ksp - p;
1673 if (len > 0)
1674 {
1675 mch_memmove(q, p, len);
1676 q += len;
1677 }
1678 *q++ = ksp[1] == KS_SPECIAL ? K_SPECIAL : CSI;
1679 p = ksp + 3;
1680 continue;
1681 }
1682 }
1683
1684 // break if no <item> is found
1685 if (start == NULL || end == NULL)
1686 break;
1687
1688 // Include the '>'
1689 ++end;
1690
1691 // Take everything up to the '<'
1692 len = start - p;
1693 if (buf == NULL)
1694 totlen += len;
1695 else
1696 {
1697 mch_memmove(q, p, len);
1698 q += len;
1699 }
1700
1701 len = uc_check_code(start, end - start, q, cmd, eap,
1702 &split_buf, &split_len);
1703 if (len == (size_t)-1)
1704 {
1705 // no match, continue after '<'
1706 p = start + 1;
1707 len = 1;
1708 }
1709 else
1710 p = end;
1711 if (buf == NULL)
1712 totlen += len;
1713 else
1714 q += len;
1715 }
1716 if (buf != NULL) // second time here, finished
1717 {
1718 STRCPY(q, p);
1719 break;
1720 }
1721
1722 totlen += STRLEN(p); // Add on the trailing characters
Bram Moolenaar964b3742019-05-24 18:54:09 +02001723 buf = alloc(totlen + 1);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001724 if (buf == NULL)
1725 {
1726 vim_free(split_buf);
1727 return;
1728 }
1729 }
1730
Bram Moolenaar9b8d6222020-12-28 18:26:00 +01001731 current_sctx.sc_version = cmd->uc_script_ctx.sc_version;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001732#ifdef FEAT_EVAL
1733 current_sctx.sc_sid = cmd->uc_script_ctx.sc_sid;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001734#endif
1735 (void)do_cmdline(buf, eap->getline, eap->cookie,
1736 DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001737 current_sctx = save_current_sctx;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001738 vim_free(buf);
1739 vim_free(split_buf);
1740}