blob: 09e7b26b3c4db48844f19c305b24417392eb3dd2 [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
117#define UC_BUFFER 1 // -buffer: local to current buffer
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +0200118#define UC_VIM9 2 // {} argument: Vim9 syntax.
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200119
120/*
121 * Search for a user command that matches "eap->cmd".
122 * Return cmdidx in "eap->cmdidx", flags in "eap->argt", idx in "eap->useridx".
123 * Return a pointer to just after the command.
124 * Return NULL if there is no matching command.
125 */
126 char_u *
127find_ucmd(
128 exarg_T *eap,
129 char_u *p, // end of the command (possibly including count)
130 int *full, // set to TRUE for a full match
131 expand_T *xp, // used for completion, NULL otherwise
Bram Moolenaar52111f82019-04-29 21:30:45 +0200132 int *complp UNUSED) // completion flags or NULL
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200133{
134 int len = (int)(p - eap->cmd);
135 int j, k, matchlen = 0;
136 ucmd_T *uc;
137 int found = FALSE;
138 int possible = FALSE;
139 char_u *cp, *np; // Point into typed cmd and test name
140 garray_T *gap;
141 int amb_local = FALSE; // Found ambiguous buffer-local command,
142 // only full match global is accepted.
143
144 /*
145 * Look for buffer-local user commands first, then global ones.
146 */
147 gap = &curbuf->b_ucmds;
148 for (;;)
149 {
150 for (j = 0; j < gap->ga_len; ++j)
151 {
152 uc = USER_CMD_GA(gap, j);
153 cp = eap->cmd;
154 np = uc->uc_name;
155 k = 0;
156 while (k < len && *np != NUL && *cp++ == *np++)
157 k++;
158 if (k == len || (*np == NUL && vim_isdigit(eap->cmd[k])))
159 {
160 // If finding a second match, the command is ambiguous. But
161 // not if a buffer-local command wasn't a full match and a
162 // global command is a full match.
163 if (k == len && found && *np != NUL)
164 {
165 if (gap == &ucmds)
166 return NULL;
167 amb_local = TRUE;
168 }
169
170 if (!found || (k == len && *np == NUL))
171 {
172 // If we matched up to a digit, then there could
173 // be another command including the digit that we
174 // should use instead.
175 if (k == len)
176 found = TRUE;
177 else
178 possible = TRUE;
179
180 if (gap == &ucmds)
181 eap->cmdidx = CMD_USER;
182 else
183 eap->cmdidx = CMD_USER_BUF;
184 eap->argt = (long)uc->uc_argt;
185 eap->useridx = j;
186 eap->addr_type = uc->uc_addr_type;
187
Bram Moolenaar52111f82019-04-29 21:30:45 +0200188 if (complp != NULL)
189 *complp = uc->uc_compl;
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200190# ifdef FEAT_EVAL
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200191 if (xp != NULL)
192 {
193 xp->xp_arg = uc->uc_compl_arg;
194 xp->xp_script_ctx = uc->uc_script_ctx;
Bram Moolenaar1a47ae32019-12-29 23:04:25 +0100195 xp->xp_script_ctx.sc_lnum += SOURCING_LNUM;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200196 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200197# endif
198 // Do not search for further abbreviations
199 // if this is an exact match.
200 matchlen = k;
201 if (k == len && *np == NUL)
202 {
203 if (full != NULL)
204 *full = TRUE;
205 amb_local = FALSE;
206 break;
207 }
208 }
209 }
210 }
211
212 // Stop if we found a full match or searched all.
213 if (j < gap->ga_len || gap == &ucmds)
214 break;
215 gap = &ucmds;
216 }
217
218 // Only found ambiguous matches.
219 if (amb_local)
220 {
221 if (xp != NULL)
222 xp->xp_context = EXPAND_UNSUCCESSFUL;
223 return NULL;
224 }
225
226 // The match we found may be followed immediately by a number. Move "p"
227 // back to point to it.
228 if (found || possible)
229 return p + (matchlen - len);
230 return p;
231}
232
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200233 char_u *
234set_context_in_user_cmd(expand_T *xp, char_u *arg_in)
235{
236 char_u *arg = arg_in;
237 char_u *p;
238
239 // Check for attributes
240 while (*arg == '-')
241 {
242 arg++; // Skip "-"
243 p = skiptowhite(arg);
244 if (*p == NUL)
245 {
246 // Cursor is still in the attribute
247 p = vim_strchr(arg, '=');
248 if (p == NULL)
249 {
250 // No "=", so complete attribute names
251 xp->xp_context = EXPAND_USER_CMD_FLAGS;
252 xp->xp_pattern = arg;
253 return NULL;
254 }
255
256 // For the -complete, -nargs and -addr attributes, we complete
257 // their arguments as well.
258 if (STRNICMP(arg, "complete", p - arg) == 0)
259 {
260 xp->xp_context = EXPAND_USER_COMPLETE;
261 xp->xp_pattern = p + 1;
262 return NULL;
263 }
264 else if (STRNICMP(arg, "nargs", p - arg) == 0)
265 {
266 xp->xp_context = EXPAND_USER_NARGS;
267 xp->xp_pattern = p + 1;
268 return NULL;
269 }
270 else if (STRNICMP(arg, "addr", p - arg) == 0)
271 {
272 xp->xp_context = EXPAND_USER_ADDR_TYPE;
273 xp->xp_pattern = p + 1;
274 return NULL;
275 }
276 return NULL;
277 }
278 arg = skipwhite(p);
279 }
280
281 // After the attributes comes the new command name
282 p = skiptowhite(arg);
283 if (*p == NUL)
284 {
285 xp->xp_context = EXPAND_USER_COMMANDS;
286 xp->xp_pattern = arg;
287 return NULL;
288 }
289
290 // And finally comes a normal command
291 return skipwhite(p);
292}
293
294 char_u *
295get_user_command_name(int idx)
296{
297 return get_user_commands(NULL, idx - (int)CMD_SIZE);
298}
299
300/*
301 * Function given to ExpandGeneric() to obtain the list of user command names.
302 */
303 char_u *
304get_user_commands(expand_T *xp UNUSED, int idx)
305{
Bram Moolenaarf03e3282019-07-22 21:55:18 +0200306 // In cmdwin, the alternative buffer should be used.
307 buf_T *buf =
308#ifdef FEAT_CMDWIN
309 (cmdwin_type != 0 && get_cmdline_type() == NUL) ? prevwin->w_buffer :
310#endif
311 curbuf;
312
313 if (idx < buf->b_ucmds.ga_len)
314 return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name;
315 idx -= buf->b_ucmds.ga_len;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200316 if (idx < ucmds.ga_len)
317 return USER_CMD(idx)->uc_name;
318 return NULL;
319}
320
321/*
322 * Function given to ExpandGeneric() to obtain the list of user address type
323 * names.
324 */
325 char_u *
326get_user_cmd_addr_type(expand_T *xp UNUSED, int idx)
327{
328 return (char_u *)addr_type_complete[idx].name;
329}
330
331/*
332 * Function given to ExpandGeneric() to obtain the list of user command
333 * attributes.
334 */
335 char_u *
336get_user_cmd_flags(expand_T *xp UNUSED, int idx)
337{
338 static char *user_cmd_flags[] = {
339 "addr", "bang", "bar", "buffer", "complete",
340 "count", "nargs", "range", "register"
341 };
342
K.Takataeeec2542021-06-02 13:28:16 +0200343 if (idx >= (int)ARRAY_LENGTH(user_cmd_flags))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200344 return NULL;
345 return (char_u *)user_cmd_flags[idx];
346}
347
348/*
349 * Function given to ExpandGeneric() to obtain the list of values for -nargs.
350 */
351 char_u *
352get_user_cmd_nargs(expand_T *xp UNUSED, int idx)
353{
354 static char *user_cmd_nargs[] = {"0", "1", "*", "?", "+"};
355
K.Takataeeec2542021-06-02 13:28:16 +0200356 if (idx >= (int)ARRAY_LENGTH(user_cmd_nargs))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200357 return NULL;
358 return (char_u *)user_cmd_nargs[idx];
359}
360
361/*
362 * Function given to ExpandGeneric() to obtain the list of values for
363 * -complete.
364 */
365 char_u *
366get_user_cmd_complete(expand_T *xp UNUSED, int idx)
367{
368 return (char_u *)command_complete[idx].name;
369}
370
371 int
372cmdcomplete_str_to_type(char_u *complete_str)
373{
374 int i;
375
376 for (i = 0; command_complete[i].expand != 0; ++i)
377 if (STRCMP(complete_str, command_complete[i].name) == 0)
378 return command_complete[i].expand;
379
380 return EXPAND_NOTHING;
381}
382
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200383/*
384 * List user commands starting with "name[name_len]".
385 */
386 static void
387uc_list(char_u *name, size_t name_len)
388{
389 int i, j;
390 int found = FALSE;
391 ucmd_T *cmd;
392 int len;
393 int over;
394 long a;
395 garray_T *gap;
396
Bram Moolenaare38eab22019-12-05 21:50:01 +0100397 // In cmdwin, the alternative buffer should be used.
Bram Moolenaarf03e3282019-07-22 21:55:18 +0200398 gap =
399#ifdef FEAT_CMDWIN
400 (cmdwin_type != 0 && get_cmdline_type() == NUL) ?
401 &prevwin->w_buffer->b_ucmds :
402#endif
403 &curbuf->b_ucmds;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200404 for (;;)
405 {
406 for (i = 0; i < gap->ga_len; ++i)
407 {
408 cmd = USER_CMD_GA(gap, i);
409 a = (long)cmd->uc_argt;
410
411 // Skip commands which don't match the requested prefix and
412 // commands filtered out.
413 if (STRNCMP(name, cmd->uc_name, name_len) != 0
414 || message_filtered(cmd->uc_name))
415 continue;
416
417 // Put out the title first time
418 if (!found)
419 msg_puts_title(_("\n Name Args Address Complete Definition"));
420 found = TRUE;
421 msg_putchar('\n');
422 if (got_int)
423 break;
424
425 // Special cases
426 len = 4;
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200427 if (a & EX_BANG)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200428 {
429 msg_putchar('!');
430 --len;
431 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200432 if (a & EX_REGSTR)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200433 {
434 msg_putchar('"');
435 --len;
436 }
437 if (gap != &ucmds)
438 {
439 msg_putchar('b');
440 --len;
441 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200442 if (a & EX_TRLBAR)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200443 {
444 msg_putchar('|');
445 --len;
446 }
447 while (len-- > 0)
448 msg_putchar(' ');
449
450 msg_outtrans_attr(cmd->uc_name, HL_ATTR(HLF_D));
451 len = (int)STRLEN(cmd->uc_name) + 4;
452
453 do {
454 msg_putchar(' ');
455 ++len;
456 } while (len < 22);
457
458 // "over" is how much longer the name is than the column width for
459 // the name, we'll try to align what comes after.
460 over = len - 22;
461 len = 0;
462
463 // Arguments
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200464 switch ((int)(a & (EX_EXTRA|EX_NOSPC|EX_NEEDARG)))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200465 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200466 case 0: IObuff[len++] = '0'; break;
467 case (EX_EXTRA): IObuff[len++] = '*'; break;
468 case (EX_EXTRA|EX_NOSPC): IObuff[len++] = '?'; break;
469 case (EX_EXTRA|EX_NEEDARG): IObuff[len++] = '+'; break;
470 case (EX_EXTRA|EX_NOSPC|EX_NEEDARG): IObuff[len++] = '1'; break;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200471 }
472
473 do {
474 IObuff[len++] = ' ';
475 } while (len < 5 - over);
476
477 // Address / Range
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200478 if (a & (EX_RANGE|EX_COUNT))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200479 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200480 if (a & EX_COUNT)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200481 {
482 // -count=N
483 sprintf((char *)IObuff + len, "%ldc", cmd->uc_def);
484 len += (int)STRLEN(IObuff + len);
485 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200486 else if (a & EX_DFLALL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200487 IObuff[len++] = '%';
488 else if (cmd->uc_def >= 0)
489 {
490 // -range=N
491 sprintf((char *)IObuff + len, "%ld", cmd->uc_def);
492 len += (int)STRLEN(IObuff + len);
493 }
494 else
495 IObuff[len++] = '.';
496 }
497
498 do {
499 IObuff[len++] = ' ';
500 } while (len < 8 - over);
501
502 // Address Type
Bram Moolenaarb7316892019-05-01 18:08:42 +0200503 for (j = 0; addr_type_complete[j].expand != ADDR_NONE; ++j)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200504 if (addr_type_complete[j].expand != ADDR_LINES
505 && addr_type_complete[j].expand == cmd->uc_addr_type)
506 {
507 STRCPY(IObuff + len, addr_type_complete[j].shortname);
508 len += (int)STRLEN(IObuff + len);
509 break;
510 }
511
512 do {
513 IObuff[len++] = ' ';
514 } while (len < 13 - over);
515
516 // Completion
517 for (j = 0; command_complete[j].expand != 0; ++j)
518 if (command_complete[j].expand == cmd->uc_compl)
519 {
520 STRCPY(IObuff + len, command_complete[j].name);
521 len += (int)STRLEN(IObuff + len);
522 break;
523 }
524
525 do {
526 IObuff[len++] = ' ';
527 } while (len < 25 - over);
528
529 IObuff[len] = '\0';
530 msg_outtrans(IObuff);
531
532 msg_outtrans_special(cmd->uc_rep, FALSE,
533 name_len == 0 ? Columns - 47 : 0);
534#ifdef FEAT_EVAL
535 if (p_verbose > 0)
536 last_set_msg(cmd->uc_script_ctx);
537#endif
538 out_flush();
539 ui_breakcheck();
540 if (got_int)
541 break;
542 }
543 if (gap == &ucmds || i < gap->ga_len)
544 break;
545 gap = &ucmds;
546 }
547
548 if (!found)
549 msg(_("No user-defined commands found"));
550}
551
552 char *
553uc_fun_cmd(void)
554{
555 static char_u fcmd[] = {0x84, 0xaf, 0x60, 0xb9, 0xaf, 0xb5, 0x60, 0xa4,
556 0xa5, 0xad, 0xa1, 0xae, 0xa4, 0x60, 0xa1, 0x60,
557 0xb3, 0xa8, 0xb2, 0xb5, 0xa2, 0xa2, 0xa5, 0xb2,
558 0xb9, 0x7f, 0};
559 int i;
560
561 for (i = 0; fcmd[i]; ++i)
562 IObuff[i] = fcmd[i] - 0x40;
563 IObuff[i] = 0;
564 return (char *)IObuff;
565}
566
567/*
568 * Parse address type argument
569 */
570 static int
571parse_addr_type_arg(
572 char_u *value,
573 int vallen,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200574 cmd_addr_T *addr_type_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200575{
576 int i, a, b;
577
Bram Moolenaarb7316892019-05-01 18:08:42 +0200578 for (i = 0; addr_type_complete[i].expand != ADDR_NONE; ++i)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200579 {
580 a = (int)STRLEN(addr_type_complete[i].name) == vallen;
581 b = STRNCMP(value, addr_type_complete[i].name, vallen) == 0;
582 if (a && b)
583 {
584 *addr_type_arg = addr_type_complete[i].expand;
585 break;
586 }
587 }
588
Bram Moolenaarb7316892019-05-01 18:08:42 +0200589 if (addr_type_complete[i].expand == ADDR_NONE)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200590 {
591 char_u *err = value;
592
593 for (i = 0; err[i] != NUL && !VIM_ISWHITE(err[i]); i++)
594 ;
595 err[i] = NUL;
596 semsg(_("E180: Invalid address type value: %s"), err);
597 return FAIL;
598 }
599
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200600 return OK;
601}
602
603/*
604 * Parse a completion argument "value[vallen]".
605 * The detected completion goes in "*complp", argument type in "*argt".
606 * When there is an argument, for function and user defined completion, it's
607 * copied to allocated memory and stored in "*compl_arg".
608 * Returns FAIL if something is wrong.
609 */
610 int
611parse_compl_arg(
612 char_u *value,
613 int vallen,
614 int *complp,
615 long *argt,
616 char_u **compl_arg UNUSED)
617{
618 char_u *arg = NULL;
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200619# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200620 size_t arglen = 0;
621# endif
622 int i;
623 int valend = vallen;
624
625 // Look for any argument part - which is the part after any ','
626 for (i = 0; i < vallen; ++i)
627 {
628 if (value[i] == ',')
629 {
630 arg = &value[i + 1];
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200631# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200632 arglen = vallen - i - 1;
633# endif
634 valend = i;
635 break;
636 }
637 }
638
639 for (i = 0; command_complete[i].expand != 0; ++i)
640 {
641 if ((int)STRLEN(command_complete[i].name) == valend
642 && STRNCMP(value, command_complete[i].name, valend) == 0)
643 {
644 *complp = command_complete[i].expand;
645 if (command_complete[i].expand == EXPAND_BUFFERS)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200646 *argt |= EX_BUFNAME;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200647 else if (command_complete[i].expand == EXPAND_DIRECTORIES
648 || command_complete[i].expand == EXPAND_FILES)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200649 *argt |= EX_XFILE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200650 break;
651 }
652 }
653
654 if (command_complete[i].expand == 0)
655 {
656 semsg(_("E180: Invalid complete value: %s"), value);
657 return FAIL;
658 }
659
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200660# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200661 if (*complp != EXPAND_USER_DEFINED && *complp != EXPAND_USER_LIST
662 && arg != NULL)
663# else
664 if (arg != NULL)
665# endif
666 {
667 emsg(_("E468: Completion argument only allowed for custom completion"));
668 return FAIL;
669 }
670
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200671# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200672 if ((*complp == EXPAND_USER_DEFINED || *complp == EXPAND_USER_LIST)
673 && arg == NULL)
674 {
675 emsg(_("E467: Custom completion requires a function argument"));
676 return FAIL;
677 }
678
679 if (arg != NULL)
Bram Moolenaar71ccd032020-06-12 22:59:11 +0200680 *compl_arg = vim_strnsave(arg, arglen);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200681# endif
682 return OK;
683}
684
685/*
686 * Scan attributes in the ":command" command.
687 * Return FAIL when something is wrong.
688 */
689 static int
690uc_scan_attr(
691 char_u *attr,
692 size_t len,
693 long *argt,
694 long *def,
695 int *flags,
Bram Moolenaar52111f82019-04-29 21:30:45 +0200696 int *complp,
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200697 char_u **compl_arg,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200698 cmd_addr_T *addr_type_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200699{
700 char_u *p;
701
702 if (len == 0)
703 {
704 emsg(_("E175: No attribute specified"));
705 return FAIL;
706 }
707
708 // First, try the simple attributes (no arguments)
709 if (STRNICMP(attr, "bang", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200710 *argt |= EX_BANG;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200711 else if (STRNICMP(attr, "buffer", len) == 0)
712 *flags |= UC_BUFFER;
713 else if (STRNICMP(attr, "register", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200714 *argt |= EX_REGSTR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200715 else if (STRNICMP(attr, "bar", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200716 *argt |= EX_TRLBAR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200717 else
718 {
719 int i;
720 char_u *val = NULL;
721 size_t vallen = 0;
722 size_t attrlen = len;
723
724 // Look for the attribute name - which is the part before any '='
725 for (i = 0; i < (int)len; ++i)
726 {
727 if (attr[i] == '=')
728 {
729 val = &attr[i + 1];
730 vallen = len - i - 1;
731 attrlen = i;
732 break;
733 }
734 }
735
736 if (STRNICMP(attr, "nargs", attrlen) == 0)
737 {
738 if (vallen == 1)
739 {
740 if (*val == '0')
741 // Do nothing - this is the default
742 ;
743 else if (*val == '1')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200744 *argt |= (EX_EXTRA | EX_NOSPC | EX_NEEDARG);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200745 else if (*val == '*')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200746 *argt |= EX_EXTRA;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200747 else if (*val == '?')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200748 *argt |= (EX_EXTRA | EX_NOSPC);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200749 else if (*val == '+')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200750 *argt |= (EX_EXTRA | EX_NEEDARG);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200751 else
752 goto wrong_nargs;
753 }
754 else
755 {
756wrong_nargs:
757 emsg(_("E176: Invalid number of arguments"));
758 return FAIL;
759 }
760 }
761 else if (STRNICMP(attr, "range", attrlen) == 0)
762 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200763 *argt |= EX_RANGE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200764 if (vallen == 1 && *val == '%')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200765 *argt |= EX_DFLALL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200766 else if (val != NULL)
767 {
768 p = val;
769 if (*def >= 0)
770 {
771two_count:
772 emsg(_("E177: Count cannot be specified twice"));
773 return FAIL;
774 }
775
776 *def = getdigits(&p);
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200777 *argt |= EX_ZEROR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200778
779 if (p != val + vallen || vallen == 0)
780 {
781invalid_count:
782 emsg(_("E178: Invalid default value for count"));
783 return FAIL;
784 }
785 }
Bram Moolenaarb7316892019-05-01 18:08:42 +0200786 // default for -range is using buffer lines
787 if (*addr_type_arg == ADDR_NONE)
788 *addr_type_arg = ADDR_LINES;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200789 }
790 else if (STRNICMP(attr, "count", attrlen) == 0)
791 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200792 *argt |= (EX_COUNT | EX_ZEROR | EX_RANGE);
Bram Moolenaarb7316892019-05-01 18:08:42 +0200793 // default for -count is using any number
794 if (*addr_type_arg == ADDR_NONE)
795 *addr_type_arg = ADDR_OTHER;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200796
797 if (val != NULL)
798 {
799 p = val;
800 if (*def >= 0)
801 goto two_count;
802
803 *def = getdigits(&p);
804
805 if (p != val + vallen)
806 goto invalid_count;
807 }
808
809 if (*def < 0)
810 *def = 0;
811 }
812 else if (STRNICMP(attr, "complete", attrlen) == 0)
813 {
814 if (val == NULL)
815 {
816 emsg(_("E179: argument required for -complete"));
817 return FAIL;
818 }
819
Bram Moolenaar52111f82019-04-29 21:30:45 +0200820 if (parse_compl_arg(val, (int)vallen, complp, argt, compl_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200821 == FAIL)
822 return FAIL;
823 }
824 else if (STRNICMP(attr, "addr", attrlen) == 0)
825 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200826 *argt |= EX_RANGE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200827 if (val == NULL)
828 {
829 emsg(_("E179: argument required for -addr"));
830 return FAIL;
831 }
Bram Moolenaare4f5f3a2019-05-04 14:05:08 +0200832 if (parse_addr_type_arg(val, (int)vallen, addr_type_arg) == FAIL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200833 return FAIL;
Bram Moolenaare4f5f3a2019-05-04 14:05:08 +0200834 if (*addr_type_arg != ADDR_LINES)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200835 *argt |= EX_ZEROR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200836 }
837 else
838 {
839 char_u ch = attr[len];
840 attr[len] = '\0';
841 semsg(_("E181: Invalid attribute: %s"), attr);
842 attr[len] = ch;
843 return FAIL;
844 }
845 }
846
847 return OK;
848}
849
850/*
851 * Add a user command to the list or replace an existing one.
852 */
853 static int
854uc_add_command(
855 char_u *name,
856 size_t name_len,
857 char_u *rep,
858 long argt,
859 long def,
860 int flags,
861 int compl,
862 char_u *compl_arg UNUSED,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200863 cmd_addr_T addr_type,
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200864 int force)
865{
866 ucmd_T *cmd = NULL;
867 char_u *p;
868 int i;
869 int cmp = 1;
870 char_u *rep_buf = NULL;
871 garray_T *gap;
872
Bram Moolenaar459fd782019-10-13 16:43:39 +0200873 replace_termcodes(rep, &rep_buf, 0, NULL);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200874 if (rep_buf == NULL)
875 {
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +0200876 // can't replace termcodes - try using the string as is
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200877 rep_buf = vim_strsave(rep);
878
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +0200879 // give up if out of memory
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200880 if (rep_buf == NULL)
881 return FAIL;
882 }
883
884 // get address of growarray: global or in curbuf
885 if (flags & UC_BUFFER)
886 {
887 gap = &curbuf->b_ucmds;
888 if (gap->ga_itemsize == 0)
889 ga_init2(gap, (int)sizeof(ucmd_T), 4);
890 }
891 else
892 gap = &ucmds;
893
894 // Search for the command in the already defined commands.
895 for (i = 0; i < gap->ga_len; ++i)
896 {
897 size_t len;
898
899 cmd = USER_CMD_GA(gap, i);
900 len = STRLEN(cmd->uc_name);
901 cmp = STRNCMP(name, cmd->uc_name, name_len);
902 if (cmp == 0)
903 {
904 if (name_len < len)
905 cmp = -1;
906 else if (name_len > len)
907 cmp = 1;
908 }
909
910 if (cmp == 0)
911 {
912 // Command can be replaced with "command!" and when sourcing the
913 // same script again, but only once.
914 if (!force
915#ifdef FEAT_EVAL
916 && (cmd->uc_script_ctx.sc_sid != current_sctx.sc_sid
917 || cmd->uc_script_ctx.sc_seq == current_sctx.sc_seq)
918#endif
919 )
920 {
921 semsg(_("E174: Command already exists: add ! to replace it: %s"),
922 name);
923 goto fail;
924 }
925
926 VIM_CLEAR(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200927#if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200928 VIM_CLEAR(cmd->uc_compl_arg);
929#endif
930 break;
931 }
932
933 // Stop as soon as we pass the name to add
934 if (cmp < 0)
935 break;
936 }
937
938 // Extend the array unless we're replacing an existing command
939 if (cmp != 0)
940 {
941 if (ga_grow(gap, 1) != OK)
942 goto fail;
Bram Moolenaar71ccd032020-06-12 22:59:11 +0200943 if ((p = vim_strnsave(name, name_len)) == NULL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200944 goto fail;
945
946 cmd = USER_CMD_GA(gap, i);
947 mch_memmove(cmd + 1, cmd, (gap->ga_len - i) * sizeof(ucmd_T));
948
949 ++gap->ga_len;
950
951 cmd->uc_name = p;
952 }
953
954 cmd->uc_rep = rep_buf;
955 cmd->uc_argt = argt;
956 cmd->uc_def = def;
957 cmd->uc_compl = compl;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200958 cmd->uc_script_ctx = current_sctx;
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +0200959 if (flags & UC_VIM9)
960 cmd->uc_script_ctx.sc_version = SCRIPT_VERSION_VIM9;
Bram Moolenaar9b8d6222020-12-28 18:26:00 +0100961#ifdef FEAT_EVAL
Bram Moolenaar1a47ae32019-12-29 23:04:25 +0100962 cmd->uc_script_ctx.sc_lnum += SOURCING_LNUM;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200963 cmd->uc_compl_arg = compl_arg;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200964#endif
965 cmd->uc_addr_type = addr_type;
966
967 return OK;
968
969fail:
970 vim_free(rep_buf);
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200971#if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200972 vim_free(compl_arg);
973#endif
974 return FAIL;
975}
976
977/*
978 * ":command ..." implementation
979 */
980 void
981ex_command(exarg_T *eap)
982{
Bram Moolenaarb7316892019-05-01 18:08:42 +0200983 char_u *name;
984 char_u *end;
985 char_u *p;
986 long argt = 0;
987 long def = -1;
988 int flags = 0;
989 int compl = EXPAND_NOTHING;
990 char_u *compl_arg = NULL;
991 cmd_addr_T addr_type_arg = ADDR_NONE;
992 int has_attr = (eap->arg[0] == '-');
993 int name_len;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200994
995 p = eap->arg;
996
997 // Check for attributes
998 while (*p == '-')
999 {
1000 ++p;
1001 end = skiptowhite(p);
1002 if (uc_scan_attr(p, end - p, &argt, &def, &flags, &compl,
1003 &compl_arg, &addr_type_arg) == FAIL)
1004 return;
1005 p = skipwhite(end);
1006 }
1007
1008 // Get the name (if any) and skip to the following argument
1009 name = p;
1010 if (ASCII_ISALPHA(*p))
1011 while (ASCII_ISALNUM(*p))
1012 ++p;
Bram Moolenaara72cfb82020-04-23 17:07:30 +02001013 if (!ends_excmd2(eap->arg, p) && !VIM_ISWHITE(*p))
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001014 {
1015 emsg(_("E182: Invalid command name"));
1016 return;
1017 }
1018 end = p;
1019 name_len = (int)(end - name);
1020
1021 // If there is nothing after the name, and no attributes were specified,
1022 // we are listing commands
1023 p = skipwhite(end);
Bram Moolenaara72cfb82020-04-23 17:07:30 +02001024 if (!has_attr && ends_excmd2(eap->arg, p))
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001025 uc_list(name, end - name);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001026 else if (!ASCII_ISUPPER(*name))
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001027 emsg(_("E183: User defined commands must start with an uppercase letter"));
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001028 else if ((name_len == 1 && *name == 'X')
1029 || (name_len <= 4
1030 && STRNCMP(name, "Next", name_len > 4 ? 4 : name_len) == 0))
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001031 emsg(_("E841: Reserved name, cannot be used for user defined command"));
Martin Tournoijde69a732021-07-11 14:28:25 +02001032 else if (compl > 0 && (argt & EX_EXTRA) == 0)
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001033 {
1034 // Some plugins rely on silently ignoring the mistake, only make this
1035 // an error in Vim9 script.
1036 if (in_vim9script())
1037 emsg(_(e_complete_used_without_nargs));
1038 else
1039 give_warning_with_source(
1040 (char_u *)_(e_complete_used_without_nargs), TRUE, TRUE);
1041 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001042 else
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001043 {
1044 char_u *tofree = NULL;
1045
1046 if (*p == '{' && ends_excmd2(eap->arg, skipwhite(p + 1))
1047 && eap->getline != NULL)
1048 {
1049 garray_T ga;
1050 char_u *line = NULL;
1051
1052 ga_init2(&ga, sizeof(char_u *), 10);
1053 if (ga_add_string(&ga, p) == FAIL)
1054 return;
1055
1056 // Read lines between '{' and '}'. Does not support nesting or
1057 // here-doc constructs.
1058 //
1059 for (;;)
1060 {
1061 vim_free(line);
1062 if ((line = eap->getline(':', eap->cookie,
1063 0, GETLINE_CONCAT_CONTBAR)) == NULL)
1064 {
1065 emsg(_(e_missing_rcurly));
1066 break;
1067 }
1068 if (ga_add_string(&ga, line) == FAIL)
1069 break;
1070 if (*skipwhite(line) == '}')
1071 break;
1072 }
1073 vim_free(line);
1074 p = tofree = ga_concat_strings(&ga, "\n");
1075 ga_clear_strings(&ga);
1076 flags |= UC_VIM9;
1077 }
1078
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001079 uc_add_command(name, end - name, p, argt, def, flags, compl, compl_arg,
1080 addr_type_arg, eap->forceit);
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001081 vim_free(tofree);
1082 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001083}
1084
1085/*
1086 * ":comclear" implementation
1087 * Clear all user commands, global and for current buffer.
1088 */
1089 void
1090ex_comclear(exarg_T *eap UNUSED)
1091{
1092 uc_clear(&ucmds);
Bram Moolenaare5c83282019-05-03 23:15:37 +02001093 if (curbuf != NULL)
1094 uc_clear(&curbuf->b_ucmds);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001095}
1096
1097/*
1098 * Clear all user commands for "gap".
1099 */
1100 void
1101uc_clear(garray_T *gap)
1102{
1103 int i;
1104 ucmd_T *cmd;
1105
1106 for (i = 0; i < gap->ga_len; ++i)
1107 {
1108 cmd = USER_CMD_GA(gap, i);
1109 vim_free(cmd->uc_name);
1110 vim_free(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001111# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001112 vim_free(cmd->uc_compl_arg);
1113# endif
1114 }
1115 ga_clear(gap);
1116}
1117
1118/*
1119 * ":delcommand" implementation
1120 */
1121 void
1122ex_delcommand(exarg_T *eap)
1123{
1124 int i = 0;
1125 ucmd_T *cmd = NULL;
1126 int cmp = -1;
1127 garray_T *gap;
1128
1129 gap = &curbuf->b_ucmds;
1130 for (;;)
1131 {
1132 for (i = 0; i < gap->ga_len; ++i)
1133 {
1134 cmd = USER_CMD_GA(gap, i);
1135 cmp = STRCMP(eap->arg, cmd->uc_name);
1136 if (cmp <= 0)
1137 break;
1138 }
1139 if (gap == &ucmds || cmp == 0)
1140 break;
1141 gap = &ucmds;
1142 }
1143
1144 if (cmp != 0)
1145 {
1146 semsg(_("E184: No such user-defined command: %s"), eap->arg);
1147 return;
1148 }
1149
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 --gap->ga_len;
1157
1158 if (i < gap->ga_len)
1159 mch_memmove(cmd, cmd + 1, (gap->ga_len - i) * sizeof(ucmd_T));
1160}
1161
1162/*
1163 * Split and quote args for <f-args>.
1164 */
1165 static char_u *
1166uc_split_args(char_u *arg, size_t *lenp)
1167{
1168 char_u *buf;
1169 char_u *p;
1170 char_u *q;
1171 int len;
1172
1173 // Precalculate length
1174 p = arg;
1175 len = 2; // Initial and final quotes
1176
1177 while (*p)
1178 {
1179 if (p[0] == '\\' && p[1] == '\\')
1180 {
1181 len += 2;
1182 p += 2;
1183 }
1184 else if (p[0] == '\\' && VIM_ISWHITE(p[1]))
1185 {
1186 len += 1;
1187 p += 2;
1188 }
1189 else if (*p == '\\' || *p == '"')
1190 {
1191 len += 2;
1192 p += 1;
1193 }
1194 else if (VIM_ISWHITE(*p))
1195 {
1196 p = skipwhite(p);
1197 if (*p == NUL)
1198 break;
Bram Moolenaar20d89e02020-10-20 23:11:33 +02001199 len += 4; // ", "
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001200 }
1201 else
1202 {
1203 int charlen = (*mb_ptr2len)(p);
1204
1205 len += charlen;
1206 p += charlen;
1207 }
1208 }
1209
1210 buf = alloc(len + 1);
1211 if (buf == NULL)
1212 {
1213 *lenp = 0;
1214 return buf;
1215 }
1216
1217 p = arg;
1218 q = buf;
1219 *q++ = '"';
1220 while (*p)
1221 {
1222 if (p[0] == '\\' && p[1] == '\\')
1223 {
1224 *q++ = '\\';
1225 *q++ = '\\';
1226 p += 2;
1227 }
1228 else if (p[0] == '\\' && VIM_ISWHITE(p[1]))
1229 {
1230 *q++ = p[1];
1231 p += 2;
1232 }
1233 else if (*p == '\\' || *p == '"')
1234 {
1235 *q++ = '\\';
1236 *q++ = *p++;
1237 }
1238 else if (VIM_ISWHITE(*p))
1239 {
1240 p = skipwhite(p);
1241 if (*p == NUL)
1242 break;
1243 *q++ = '"';
1244 *q++ = ',';
Bram Moolenaar20d89e02020-10-20 23:11:33 +02001245 *q++ = ' ';
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001246 *q++ = '"';
1247 }
1248 else
1249 {
1250 MB_COPY_CHAR(p, q);
1251 }
1252 }
1253 *q++ = '"';
1254 *q = 0;
1255
1256 *lenp = len;
1257 return buf;
1258}
1259
1260 static size_t
1261add_cmd_modifier(char_u *buf, char *mod_str, int *multi_mods)
1262{
1263 size_t result;
1264
1265 result = STRLEN(mod_str);
1266 if (*multi_mods)
1267 result += 1;
1268 if (buf != NULL)
1269 {
1270 if (*multi_mods)
1271 STRCAT(buf, " ");
1272 STRCAT(buf, mod_str);
1273 }
1274
1275 *multi_mods = 1;
1276
1277 return result;
1278}
1279
1280/*
Bram Moolenaar02194d22020-10-24 23:08:38 +02001281 * Add modifiers from "cmod->cmod_split" to "buf". Set "multi_mods" when one
Bram Moolenaare1004402020-10-24 20:49:43 +02001282 * was added. Return the number of bytes added.
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001283 */
1284 size_t
Bram Moolenaar02194d22020-10-24 23:08:38 +02001285add_win_cmd_modifers(char_u *buf, cmdmod_T *cmod, int *multi_mods)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001286{
1287 size_t result = 0;
1288
1289 // :aboveleft and :leftabove
Bram Moolenaar02194d22020-10-24 23:08:38 +02001290 if (cmod->cmod_split & WSP_ABOVE)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001291 result += add_cmd_modifier(buf, "aboveleft", multi_mods);
1292 // :belowright and :rightbelow
Bram Moolenaar02194d22020-10-24 23:08:38 +02001293 if (cmod->cmod_split & WSP_BELOW)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001294 result += add_cmd_modifier(buf, "belowright", multi_mods);
1295 // :botright
Bram Moolenaar02194d22020-10-24 23:08:38 +02001296 if (cmod->cmod_split & WSP_BOT)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001297 result += add_cmd_modifier(buf, "botright", multi_mods);
1298
1299 // :tab
Bram Moolenaar02194d22020-10-24 23:08:38 +02001300 if (cmod->cmod_tab > 0)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001301 result += add_cmd_modifier(buf, "tab", multi_mods);
1302 // :topleft
Bram Moolenaar02194d22020-10-24 23:08:38 +02001303 if (cmod->cmod_split & WSP_TOP)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001304 result += add_cmd_modifier(buf, "topleft", multi_mods);
1305 // :vertical
Bram Moolenaar02194d22020-10-24 23:08:38 +02001306 if (cmod->cmod_split & WSP_VERT)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001307 result += add_cmd_modifier(buf, "vertical", multi_mods);
1308 return result;
1309}
1310
1311/*
Bram Moolenaar02194d22020-10-24 23:08:38 +02001312 * Generate text for the "cmod" command modifiers.
1313 * If "buf" is NULL just return the length.
1314 */
Bram Moolenaara360dbe2020-10-26 18:46:53 +01001315 size_t
Bram Moolenaar02194d22020-10-24 23:08:38 +02001316produce_cmdmods(char_u *buf, cmdmod_T *cmod, int quote)
1317{
Bram Moolenaara360dbe2020-10-26 18:46:53 +01001318 size_t result = 0;
Bram Moolenaar02194d22020-10-24 23:08:38 +02001319 int multi_mods = 0;
1320 int i;
1321 typedef struct {
1322 int flag;
1323 char *name;
1324 } mod_entry_T;
1325 static mod_entry_T mod_entries[] = {
1326#ifdef FEAT_BROWSE_CMD
1327 {CMOD_BROWSE, "browse"},
1328#endif
1329#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
1330 {CMOD_CONFIRM, "confirm"},
1331#endif
1332 {CMOD_HIDE, "hide"},
1333 {CMOD_KEEPALT, "keepalt"},
1334 {CMOD_KEEPJUMPS, "keepjumps"},
1335 {CMOD_KEEPMARKS, "keepmarks"},
1336 {CMOD_KEEPPATTERNS, "keeppatterns"},
1337 {CMOD_LOCKMARKS, "lockmarks"},
1338 {CMOD_NOSWAPFILE, "noswapfile"},
1339 {CMOD_UNSILENT, "unsilent"},
1340 {CMOD_NOAUTOCMD, "noautocmd"},
1341#ifdef HAVE_SANDBOX
1342 {CMOD_SANDBOX, "sandbox"},
1343#endif
1344 {0, NULL}
1345 };
1346
1347 result = quote ? 2 : 0;
1348 if (buf != NULL)
1349 {
1350 if (quote)
1351 *buf++ = '"';
1352 *buf = '\0';
1353 }
1354
1355 // the modifiers that are simple flags
1356 for (i = 0; mod_entries[i].name != NULL; ++i)
1357 if (cmod->cmod_flags & mod_entries[i].flag)
1358 result += add_cmd_modifier(buf, mod_entries[i].name, &multi_mods);
1359
1360 // :silent
1361 if (cmod->cmod_flags & CMOD_SILENT)
1362 result += add_cmd_modifier(buf,
1363 (cmod->cmod_flags & CMOD_ERRSILENT) ? "silent!"
1364 : "silent", &multi_mods);
1365 // :verbose
1366 if (p_verbose > 0)
1367 result += add_cmd_modifier(buf, "verbose", &multi_mods);
1368 // flags from cmod->cmod_split
1369 result += add_win_cmd_modifers(buf, cmod, &multi_mods);
1370 if (quote && buf != NULL)
1371 {
1372 buf += result - 2;
1373 *buf = '"';
1374 }
1375 return result;
1376}
1377
1378/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001379 * Check for a <> code in a user command.
1380 * "code" points to the '<'. "len" the length of the <> (inclusive).
1381 * "buf" is where the result is to be added.
1382 * "split_buf" points to a buffer used for splitting, caller should free it.
1383 * "split_len" is the length of what "split_buf" contains.
1384 * Returns the length of the replacement, which has been added to "buf".
1385 * Returns -1 if there was no match, and only the "<" has been copied.
1386 */
1387 static size_t
1388uc_check_code(
1389 char_u *code,
1390 size_t len,
1391 char_u *buf,
1392 ucmd_T *cmd, // the user command we're expanding
1393 exarg_T *eap, // ex arguments
1394 char_u **split_buf,
1395 size_t *split_len)
1396{
1397 size_t result = 0;
1398 char_u *p = code + 1;
1399 size_t l = len - 2;
1400 int quote = 0;
1401 enum {
1402 ct_ARGS,
1403 ct_BANG,
1404 ct_COUNT,
1405 ct_LINE1,
1406 ct_LINE2,
1407 ct_RANGE,
1408 ct_MODS,
1409 ct_REGISTER,
1410 ct_LT,
1411 ct_NONE
1412 } type = ct_NONE;
1413
1414 if ((vim_strchr((char_u *)"qQfF", *p) != NULL) && p[1] == '-')
1415 {
1416 quote = (*p == 'q' || *p == 'Q') ? 1 : 2;
1417 p += 2;
1418 l -= 2;
1419 }
1420
1421 ++l;
1422 if (l <= 1)
1423 type = ct_NONE;
1424 else if (STRNICMP(p, "args>", l) == 0)
1425 type = ct_ARGS;
1426 else if (STRNICMP(p, "bang>", l) == 0)
1427 type = ct_BANG;
1428 else if (STRNICMP(p, "count>", l) == 0)
1429 type = ct_COUNT;
1430 else if (STRNICMP(p, "line1>", l) == 0)
1431 type = ct_LINE1;
1432 else if (STRNICMP(p, "line2>", l) == 0)
1433 type = ct_LINE2;
1434 else if (STRNICMP(p, "range>", l) == 0)
1435 type = ct_RANGE;
1436 else if (STRNICMP(p, "lt>", l) == 0)
1437 type = ct_LT;
1438 else if (STRNICMP(p, "reg>", l) == 0 || STRNICMP(p, "register>", l) == 0)
1439 type = ct_REGISTER;
1440 else if (STRNICMP(p, "mods>", l) == 0)
1441 type = ct_MODS;
1442
1443 switch (type)
1444 {
1445 case ct_ARGS:
1446 // Simple case first
1447 if (*eap->arg == NUL)
1448 {
1449 if (quote == 1)
1450 {
1451 result = 2;
1452 if (buf != NULL)
1453 STRCPY(buf, "''");
1454 }
1455 else
1456 result = 0;
1457 break;
1458 }
1459
1460 // When specified there is a single argument don't split it.
1461 // Works for ":Cmd %" when % is "a b c".
Bram Moolenaar8071cb22019-07-12 17:58:01 +02001462 if ((eap->argt & EX_NOSPC) && quote == 2)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001463 quote = 1;
1464
1465 switch (quote)
1466 {
1467 case 0: // No quoting, no splitting
1468 result = STRLEN(eap->arg);
1469 if (buf != NULL)
1470 STRCPY(buf, eap->arg);
1471 break;
1472 case 1: // Quote, but don't split
1473 result = STRLEN(eap->arg) + 2;
1474 for (p = eap->arg; *p; ++p)
1475 {
1476 if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2)
1477 // DBCS can contain \ in a trail byte, skip the
1478 // double-byte character.
1479 ++p;
1480 else
1481 if (*p == '\\' || *p == '"')
1482 ++result;
1483 }
1484
1485 if (buf != NULL)
1486 {
1487 *buf++ = '"';
1488 for (p = eap->arg; *p; ++p)
1489 {
1490 if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2)
1491 // DBCS can contain \ in a trail byte, copy the
1492 // double-byte character to avoid escaping.
1493 *buf++ = *p++;
1494 else
1495 if (*p == '\\' || *p == '"')
1496 *buf++ = '\\';
1497 *buf++ = *p;
1498 }
1499 *buf = '"';
1500 }
1501
1502 break;
1503 case 2: // Quote and split (<f-args>)
1504 // This is hard, so only do it once, and cache the result
1505 if (*split_buf == NULL)
1506 *split_buf = uc_split_args(eap->arg, split_len);
1507
1508 result = *split_len;
1509 if (buf != NULL && result != 0)
1510 STRCPY(buf, *split_buf);
1511
1512 break;
1513 }
1514 break;
1515
1516 case ct_BANG:
1517 result = eap->forceit ? 1 : 0;
1518 if (quote)
1519 result += 2;
1520 if (buf != NULL)
1521 {
1522 if (quote)
1523 *buf++ = '"';
1524 if (eap->forceit)
1525 *buf++ = '!';
1526 if (quote)
1527 *buf = '"';
1528 }
1529 break;
1530
1531 case ct_LINE1:
1532 case ct_LINE2:
1533 case ct_RANGE:
1534 case ct_COUNT:
1535 {
1536 char num_buf[20];
1537 long num = (type == ct_LINE1) ? eap->line1 :
1538 (type == ct_LINE2) ? eap->line2 :
1539 (type == ct_RANGE) ? eap->addr_count :
1540 (eap->addr_count > 0) ? eap->line2 : cmd->uc_def;
1541 size_t num_len;
1542
1543 sprintf(num_buf, "%ld", num);
1544 num_len = STRLEN(num_buf);
1545 result = num_len;
1546
1547 if (quote)
1548 result += 2;
1549
1550 if (buf != NULL)
1551 {
1552 if (quote)
1553 *buf++ = '"';
1554 STRCPY(buf, num_buf);
1555 buf += num_len;
1556 if (quote)
1557 *buf = '"';
1558 }
1559
1560 break;
1561 }
1562
1563 case ct_MODS:
1564 {
Bram Moolenaar02194d22020-10-24 23:08:38 +02001565 result = produce_cmdmods(buf, &cmdmod, quote);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001566 break;
1567 }
1568
1569 case ct_REGISTER:
1570 result = eap->regname ? 1 : 0;
1571 if (quote)
1572 result += 2;
1573 if (buf != NULL)
1574 {
1575 if (quote)
1576 *buf++ = '\'';
1577 if (eap->regname)
1578 *buf++ = eap->regname;
1579 if (quote)
1580 *buf = '\'';
1581 }
1582 break;
1583
1584 case ct_LT:
1585 result = 1;
1586 if (buf != NULL)
1587 *buf = '<';
1588 break;
1589
1590 default:
1591 // Not recognized: just copy the '<' and return -1.
1592 result = (size_t)-1;
1593 if (buf != NULL)
1594 *buf = '<';
1595 break;
1596 }
1597
1598 return result;
1599}
1600
1601/*
1602 * Execute a user defined command.
1603 */
1604 void
1605do_ucmd(exarg_T *eap)
1606{
1607 char_u *buf;
1608 char_u *p;
1609 char_u *q;
1610
1611 char_u *start;
1612 char_u *end = NULL;
1613 char_u *ksp;
1614 size_t len, totlen;
1615
1616 size_t split_len = 0;
1617 char_u *split_buf = NULL;
1618 ucmd_T *cmd;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001619 sctx_T save_current_sctx = current_sctx;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001620
1621 if (eap->cmdidx == CMD_USER)
1622 cmd = USER_CMD(eap->useridx);
1623 else
1624 cmd = USER_CMD_GA(&curbuf->b_ucmds, eap->useridx);
1625
1626 /*
1627 * Replace <> in the command by the arguments.
1628 * First round: "buf" is NULL, compute length, allocate "buf".
1629 * Second round: copy result into "buf".
1630 */
1631 buf = NULL;
1632 for (;;)
1633 {
1634 p = cmd->uc_rep; // source
1635 q = buf; // destination
1636 totlen = 0;
1637
1638 for (;;)
1639 {
1640 start = vim_strchr(p, '<');
1641 if (start != NULL)
1642 end = vim_strchr(start + 1, '>');
1643 if (buf != NULL)
1644 {
1645 for (ksp = p; *ksp != NUL && *ksp != K_SPECIAL; ++ksp)
1646 ;
1647 if (*ksp == K_SPECIAL
1648 && (start == NULL || ksp < start || end == NULL)
1649 && ((ksp[1] == KS_SPECIAL && ksp[2] == KE_FILLER)
1650# ifdef FEAT_GUI
1651 || (ksp[1] == KS_EXTRA && ksp[2] == (int)KE_CSI)
1652# endif
1653 ))
1654 {
1655 // K_SPECIAL has been put in the buffer as K_SPECIAL
1656 // KS_SPECIAL KE_FILLER, like for mappings, but
1657 // do_cmdline() doesn't handle that, so convert it back.
1658 // Also change K_SPECIAL KS_EXTRA KE_CSI into CSI.
1659 len = ksp - p;
1660 if (len > 0)
1661 {
1662 mch_memmove(q, p, len);
1663 q += len;
1664 }
1665 *q++ = ksp[1] == KS_SPECIAL ? K_SPECIAL : CSI;
1666 p = ksp + 3;
1667 continue;
1668 }
1669 }
1670
1671 // break if no <item> is found
1672 if (start == NULL || end == NULL)
1673 break;
1674
1675 // Include the '>'
1676 ++end;
1677
1678 // Take everything up to the '<'
1679 len = start - p;
1680 if (buf == NULL)
1681 totlen += len;
1682 else
1683 {
1684 mch_memmove(q, p, len);
1685 q += len;
1686 }
1687
1688 len = uc_check_code(start, end - start, q, cmd, eap,
1689 &split_buf, &split_len);
1690 if (len == (size_t)-1)
1691 {
1692 // no match, continue after '<'
1693 p = start + 1;
1694 len = 1;
1695 }
1696 else
1697 p = end;
1698 if (buf == NULL)
1699 totlen += len;
1700 else
1701 q += len;
1702 }
1703 if (buf != NULL) // second time here, finished
1704 {
1705 STRCPY(q, p);
1706 break;
1707 }
1708
1709 totlen += STRLEN(p); // Add on the trailing characters
Bram Moolenaar964b3742019-05-24 18:54:09 +02001710 buf = alloc(totlen + 1);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001711 if (buf == NULL)
1712 {
1713 vim_free(split_buf);
1714 return;
1715 }
1716 }
1717
Bram Moolenaar9b8d6222020-12-28 18:26:00 +01001718 current_sctx.sc_version = cmd->uc_script_ctx.sc_version;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001719#ifdef FEAT_EVAL
1720 current_sctx.sc_sid = cmd->uc_script_ctx.sc_sid;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001721#endif
1722 (void)do_cmdline(buf, eap->getline, eap->cookie,
1723 DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001724 current_sctx = save_current_sctx;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001725 vim_free(buf);
1726 vim_free(split_buf);
1727}