blob: 31912578091dad62a39db092187c9d4197f5b39d [file] [log] [blame]
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001/* vi:set ts=8 sts=4 sw=4 noet:
2 *
3 * VIM - Vi IMproved by Bram Moolenaar
4 *
5 * Do ":help uganda" in Vim to read copying and usage conditions.
6 * Do ":help credits" in Vim to see a list of people who contributed.
7 * See README.txt for an overview of the Vim source code.
8 */
9
10/*
11 * usercmd.c: User defined command support
12 */
13
14#include "vim.h"
15
16typedef struct ucmd
17{
18 char_u *uc_name; // The command name
19 long_u uc_argt; // The argument type
20 char_u *uc_rep; // The command's replacement string
21 long uc_def; // The default value for a range/count
22 int uc_compl; // completion type
Bram Moolenaarb7316892019-05-01 18:08:42 +020023 cmd_addr_T uc_addr_type; // The command's address type
Bram Moolenaarac9fb182019-04-27 13:04:13 +020024 sctx_T uc_script_ctx; // SCTX where the command was defined
Bram Moolenaar98b7fe72022-03-23 21:36:27 +000025 int uc_flags; // some UC_ flags
Bram Moolenaar9b8d6222020-12-28 18:26:00 +010026# ifdef FEAT_EVAL
Bram Moolenaarac9fb182019-04-27 13:04:13 +020027 char_u *uc_compl_arg; // completion argument if any
Bram Moolenaarac9fb182019-04-27 13:04:13 +020028# endif
29} ucmd_T;
30
31// List of all user commands.
32static garray_T ucmds = {0, 0, sizeof(ucmd_T), 4, NULL};
33
Bram Moolenaarcf2594f2022-11-13 23:30:06 +000034// When non-zero it is not allowed to add or remove user commands
35static int ucmd_locked = 0;
36
Bram Moolenaarac9fb182019-04-27 13:04:13 +020037#define USER_CMD(i) (&((ucmd_T *)(ucmds.ga_data))[i])
38#define USER_CMD_GA(gap, i) (&((ucmd_T *)((gap)->ga_data))[i])
39
40/*
41 * List of names for completion for ":command" with the EXPAND_ flag.
42 * Must be alphabetical for completion.
43 */
44static struct
45{
46 int expand;
47 char *name;
48} command_complete[] =
49{
50 {EXPAND_ARGLIST, "arglist"},
51 {EXPAND_AUGROUP, "augroup"},
52 {EXPAND_BEHAVE, "behave"},
53 {EXPAND_BUFFERS, "buffer"},
54 {EXPAND_COLORS, "color"},
55 {EXPAND_COMMANDS, "command"},
56 {EXPAND_COMPILER, "compiler"},
57#if defined(FEAT_CSCOPE)
58 {EXPAND_CSCOPE, "cscope"},
59#endif
Bram Moolenaar0a52df52019-08-18 22:26:31 +020060#if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +020061 {EXPAND_USER_DEFINED, "custom"},
62 {EXPAND_USER_LIST, "customlist"},
63#endif
Bram Moolenaarae7dba82019-12-29 13:56:33 +010064 {EXPAND_DIFF_BUFFERS, "diff_buffer"},
Bram Moolenaarac9fb182019-04-27 13:04:13 +020065 {EXPAND_DIRECTORIES, "dir"},
66 {EXPAND_ENV_VARS, "environment"},
67 {EXPAND_EVENTS, "event"},
68 {EXPAND_EXPRESSION, "expression"},
69 {EXPAND_FILES, "file"},
70 {EXPAND_FILES_IN_PATH, "file_in_path"},
71 {EXPAND_FILETYPE, "filetype"},
72 {EXPAND_FUNCTIONS, "function"},
73 {EXPAND_HELP, "help"},
74 {EXPAND_HIGHLIGHT, "highlight"},
Bram Moolenaarac9fb182019-04-27 13:04:13 +020075 {EXPAND_HISTORY, "history"},
Bram Moolenaarac9fb182019-04-27 13:04:13 +020076#if defined(HAVE_LOCALE_H) || defined(X_LOCALE)
77 {EXPAND_LOCALES, "locale"},
78#endif
79 {EXPAND_MAPCLEAR, "mapclear"},
80 {EXPAND_MAPPINGS, "mapping"},
81 {EXPAND_MENUS, "menu"},
82 {EXPAND_MESSAGES, "messages"},
83 {EXPAND_OWNSYNTAX, "syntax"},
84#if defined(FEAT_PROFILE)
85 {EXPAND_SYNTIME, "syntime"},
86#endif
87 {EXPAND_SETTINGS, "option"},
88 {EXPAND_PACKADD, "packadd"},
roota6759382023-01-21 21:56:06 +000089 {EXPAND_RUNTIME, "runtime"},
Bram Moolenaarac9fb182019-04-27 13:04:13 +020090 {EXPAND_SHELLCMD, "shellcmd"},
91#if defined(FEAT_SIGNS)
92 {EXPAND_SIGN, "sign"},
93#endif
94 {EXPAND_TAGS, "tag"},
95 {EXPAND_TAGS_LISTFILES, "tag_listfiles"},
96 {EXPAND_USER, "user"},
97 {EXPAND_USER_VARS, "var"},
Bram Moolenaar6e2e2cc2022-03-14 19:24:46 +000098#if defined(FEAT_EVAL)
99 {EXPAND_BREAKPOINT, "breakpoint"},
Yegappan Lakshmanan454ce672022-03-24 11:22:13 +0000100 {EXPAND_SCRIPTNAMES, "scriptnames"},
Bram Moolenaar6e2e2cc2022-03-14 19:24:46 +0000101#endif
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200102 {0, NULL}
103};
104
105/*
106 * List of names of address types. Must be alphabetical for completion.
107 */
108static struct
109{
Bram Moolenaarb7316892019-05-01 18:08:42 +0200110 cmd_addr_T expand;
111 char *name;
112 char *shortname;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200113} addr_type_complete[] =
114{
115 {ADDR_ARGUMENTS, "arguments", "arg"},
116 {ADDR_LINES, "lines", "line"},
117 {ADDR_LOADED_BUFFERS, "loaded_buffers", "load"},
118 {ADDR_TABS, "tabs", "tab"},
119 {ADDR_BUFFERS, "buffers", "buf"},
120 {ADDR_WINDOWS, "windows", "win"},
121 {ADDR_QUICKFIX, "quickfix", "qf"},
122 {ADDR_OTHER, "other", "?"},
Bram Moolenaarb7316892019-05-01 18:08:42 +0200123 {ADDR_NONE, NULL, NULL}
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200124};
125
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200126/*
127 * Search for a user command that matches "eap->cmd".
128 * Return cmdidx in "eap->cmdidx", flags in "eap->argt", idx in "eap->useridx".
129 * Return a pointer to just after the command.
130 * Return NULL if there is no matching command.
131 */
132 char_u *
133find_ucmd(
134 exarg_T *eap,
Bram Moolenaar0023f822022-01-16 21:54:19 +0000135 char_u *p, // end of the command (possibly including count)
136 int *full, // set to TRUE for a full match
137 expand_T *xp, // used for completion, NULL otherwise
138 int *complp) // completion flags or NULL
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200139{
140 int len = (int)(p - eap->cmd);
141 int j, k, matchlen = 0;
142 ucmd_T *uc;
143 int found = FALSE;
144 int possible = FALSE;
145 char_u *cp, *np; // Point into typed cmd and test name
146 garray_T *gap;
147 int amb_local = FALSE; // Found ambiguous buffer-local command,
148 // only full match global is accepted.
149
150 /*
151 * Look for buffer-local user commands first, then global ones.
152 */
Bram Moolenaar0f6e28f2022-02-20 20:49:35 +0000153 gap = &prevwin_curwin()->w_buffer->b_ucmds;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200154 for (;;)
155 {
156 for (j = 0; j < gap->ga_len; ++j)
157 {
158 uc = USER_CMD_GA(gap, j);
159 cp = eap->cmd;
160 np = uc->uc_name;
161 k = 0;
162 while (k < len && *np != NUL && *cp++ == *np++)
163 k++;
164 if (k == len || (*np == NUL && vim_isdigit(eap->cmd[k])))
165 {
166 // If finding a second match, the command is ambiguous. But
167 // not if a buffer-local command wasn't a full match and a
168 // global command is a full match.
169 if (k == len && found && *np != NUL)
170 {
171 if (gap == &ucmds)
172 return NULL;
173 amb_local = TRUE;
174 }
175
176 if (!found || (k == len && *np == NUL))
177 {
178 // If we matched up to a digit, then there could
179 // be another command including the digit that we
180 // should use instead.
181 if (k == len)
182 found = TRUE;
183 else
184 possible = TRUE;
185
186 if (gap == &ucmds)
187 eap->cmdidx = CMD_USER;
188 else
189 eap->cmdidx = CMD_USER_BUF;
190 eap->argt = (long)uc->uc_argt;
191 eap->useridx = j;
192 eap->addr_type = uc->uc_addr_type;
193
Bram Moolenaar52111f82019-04-29 21:30:45 +0200194 if (complp != NULL)
195 *complp = uc->uc_compl;
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200196# ifdef FEAT_EVAL
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200197 if (xp != NULL)
198 {
199 xp->xp_arg = uc->uc_compl_arg;
200 xp->xp_script_ctx = uc->uc_script_ctx;
Bram Moolenaar1a47ae32019-12-29 23:04:25 +0100201 xp->xp_script_ctx.sc_lnum += SOURCING_LNUM;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200202 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200203# endif
204 // Do not search for further abbreviations
205 // if this is an exact match.
206 matchlen = k;
207 if (k == len && *np == NUL)
208 {
209 if (full != NULL)
210 *full = TRUE;
211 amb_local = FALSE;
212 break;
213 }
214 }
215 }
216 }
217
218 // Stop if we found a full match or searched all.
219 if (j < gap->ga_len || gap == &ucmds)
220 break;
221 gap = &ucmds;
222 }
223
224 // Only found ambiguous matches.
225 if (amb_local)
226 {
227 if (xp != NULL)
228 xp->xp_context = EXPAND_UNSUCCESSFUL;
229 return NULL;
230 }
231
232 // The match we found may be followed immediately by a number. Move "p"
233 // back to point to it.
234 if (found || possible)
235 return p + (matchlen - len);
236 return p;
237}
238
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000239/*
240 * Set completion context for :command
241 */
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200242 char_u *
243set_context_in_user_cmd(expand_T *xp, char_u *arg_in)
244{
245 char_u *arg = arg_in;
246 char_u *p;
247
248 // Check for attributes
249 while (*arg == '-')
250 {
251 arg++; // Skip "-"
252 p = skiptowhite(arg);
253 if (*p == NUL)
254 {
255 // Cursor is still in the attribute
256 p = vim_strchr(arg, '=');
257 if (p == NULL)
258 {
259 // No "=", so complete attribute names
260 xp->xp_context = EXPAND_USER_CMD_FLAGS;
261 xp->xp_pattern = arg;
262 return NULL;
263 }
264
265 // For the -complete, -nargs and -addr attributes, we complete
266 // their arguments as well.
267 if (STRNICMP(arg, "complete", p - arg) == 0)
268 {
269 xp->xp_context = EXPAND_USER_COMPLETE;
270 xp->xp_pattern = p + 1;
271 return NULL;
272 }
273 else if (STRNICMP(arg, "nargs", p - arg) == 0)
274 {
275 xp->xp_context = EXPAND_USER_NARGS;
276 xp->xp_pattern = p + 1;
277 return NULL;
278 }
279 else if (STRNICMP(arg, "addr", p - arg) == 0)
280 {
281 xp->xp_context = EXPAND_USER_ADDR_TYPE;
282 xp->xp_pattern = p + 1;
283 return NULL;
284 }
285 return NULL;
286 }
287 arg = skipwhite(p);
288 }
289
290 // After the attributes comes the new command name
291 p = skiptowhite(arg);
292 if (*p == NUL)
293 {
294 xp->xp_context = EXPAND_USER_COMMANDS;
295 xp->xp_pattern = arg;
296 return NULL;
297 }
298
299 // And finally comes a normal command
300 return skipwhite(p);
301}
302
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000303/*
304 * Set the completion context for the argument of a user defined command.
305 */
306 char_u *
307set_context_in_user_cmdarg(
308 char_u *cmd UNUSED,
309 char_u *arg,
310 long argt,
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000311 int context,
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000312 expand_T *xp,
313 int forceit)
314{
315 char_u *p;
316
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000317 if (context == EXPAND_NOTHING)
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000318 return NULL;
319
320 if (argt & EX_XFILE)
321 {
322 // EX_XFILE: file names are handled before this call
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000323 xp->xp_context = context;
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000324 return NULL;
325 }
326
327#ifdef FEAT_MENU
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000328 if (context == EXPAND_MENUS)
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000329 return set_context_in_menu_cmd(xp, cmd, arg, forceit);
330#endif
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000331 if (context == EXPAND_COMMANDS)
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000332 return arg;
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000333 if (context == EXPAND_MAPPINGS)
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000334 return set_context_in_map_cmd(xp, (char_u *)"map", arg, forceit, FALSE,
335 FALSE, CMD_map);
336 // Find start of last argument.
337 p = arg;
338 while (*p)
339 {
340 if (*p == ' ')
341 // argument starts after a space
342 arg = p + 1;
343 else if (*p == '\\' && *(p + 1) != NUL)
344 ++p; // skip over escaped character
345 MB_PTR_ADV(p);
346 }
347 xp->xp_pattern = arg;
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000348 xp->xp_context = context;
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000349
350 return NULL;
351}
352
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200353 char_u *
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200354expand_user_command_name(int idx)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200355{
356 return get_user_commands(NULL, idx - (int)CMD_SIZE);
357}
358
359/*
360 * Function given to ExpandGeneric() to obtain the list of user command names.
361 */
362 char_u *
363get_user_commands(expand_T *xp UNUSED, int idx)
364{
Bram Moolenaarf03e3282019-07-22 21:55:18 +0200365 // In cmdwin, the alternative buffer should be used.
Bram Moolenaar0f6e28f2022-02-20 20:49:35 +0000366 buf_T *buf = prevwin_curwin()->w_buffer;
Bram Moolenaarf03e3282019-07-22 21:55:18 +0200367
368 if (idx < buf->b_ucmds.ga_len)
369 return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name;
Bram Moolenaarc2842ad2022-07-26 17:23:47 +0100370
Bram Moolenaarf03e3282019-07-22 21:55:18 +0200371 idx -= buf->b_ucmds.ga_len;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200372 if (idx < ucmds.ga_len)
Bram Moolenaarc2842ad2022-07-26 17:23:47 +0100373 {
374 int i;
375 char_u *name = USER_CMD(idx)->uc_name;
376
377 for (i = 0; i < buf->b_ucmds.ga_len; ++i)
378 if (STRCMP(name, USER_CMD_GA(&buf->b_ucmds, i)->uc_name) == 0)
379 // global command is overruled by buffer-local one
380 return (char_u *)"";
381 return name;
382 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200383 return NULL;
384}
385
Dominique Pelle748b3082022-01-08 12:41:16 +0000386#ifdef FEAT_EVAL
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200387/*
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200388 * Get the name of user command "idx". "cmdidx" can be CMD_USER or
389 * CMD_USER_BUF.
390 * Returns NULL if the command is not found.
391 */
392 char_u *
393get_user_command_name(int idx, int cmdidx)
394{
395 if (cmdidx == CMD_USER && idx < ucmds.ga_len)
396 return USER_CMD(idx)->uc_name;
397 if (cmdidx == CMD_USER_BUF)
398 {
399 // In cmdwin, the alternative buffer should be used.
Bram Moolenaar0f6e28f2022-02-20 20:49:35 +0000400 buf_T *buf = prevwin_curwin()->w_buffer;
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200401
402 if (idx < buf->b_ucmds.ga_len)
403 return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name;
404 }
405 return NULL;
406}
Dominique Pelle748b3082022-01-08 12:41:16 +0000407#endif
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200408
409/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200410 * Function given to ExpandGeneric() to obtain the list of user address type
411 * names.
412 */
413 char_u *
414get_user_cmd_addr_type(expand_T *xp UNUSED, int idx)
415{
416 return (char_u *)addr_type_complete[idx].name;
417}
418
419/*
420 * Function given to ExpandGeneric() to obtain the list of user command
421 * attributes.
422 */
423 char_u *
424get_user_cmd_flags(expand_T *xp UNUSED, int idx)
425{
426 static char *user_cmd_flags[] = {
427 "addr", "bang", "bar", "buffer", "complete",
Bram Moolenaar58ef8a32021-11-12 11:25:11 +0000428 "count", "nargs", "range", "register", "keepscript"
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200429 };
430
K.Takataeeec2542021-06-02 13:28:16 +0200431 if (idx >= (int)ARRAY_LENGTH(user_cmd_flags))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200432 return NULL;
433 return (char_u *)user_cmd_flags[idx];
434}
435
436/*
437 * Function given to ExpandGeneric() to obtain the list of values for -nargs.
438 */
439 char_u *
440get_user_cmd_nargs(expand_T *xp UNUSED, int idx)
441{
442 static char *user_cmd_nargs[] = {"0", "1", "*", "?", "+"};
443
K.Takataeeec2542021-06-02 13:28:16 +0200444 if (idx >= (int)ARRAY_LENGTH(user_cmd_nargs))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200445 return NULL;
446 return (char_u *)user_cmd_nargs[idx];
447}
448
449/*
450 * Function given to ExpandGeneric() to obtain the list of values for
451 * -complete.
452 */
453 char_u *
454get_user_cmd_complete(expand_T *xp UNUSED, int idx)
455{
456 return (char_u *)command_complete[idx].name;
457}
458
Dominique Pelle748b3082022-01-08 12:41:16 +0000459#ifdef FEAT_EVAL
Shougo Matsushita79d599b2022-05-07 12:48:29 +0100460/*
461 * Get the name of completion type "expand" as a string.
462 */
463 char_u *
464cmdcomplete_type_to_str(int expand)
465{
466 int i;
467
468 for (i = 0; command_complete[i].expand != 0; i++)
469 if (command_complete[i].expand == expand)
470 return (char_u *)command_complete[i].name;
471
472 return NULL;
473}
474
475/*
476 * Get the index of completion type "complete_str".
477 * Returns EXPAND_NOTHING if no match found.
478 */
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200479 int
480cmdcomplete_str_to_type(char_u *complete_str)
481{
482 int i;
483
Shougo Matsushita92997dd2023-08-20 20:55:55 +0200484 if (STRNCMP(complete_str, "custom,", 7) == 0)
485 return EXPAND_USER_DEFINED;
486 if (STRNCMP(complete_str, "customlist,", 11) == 0)
487 return EXPAND_USER_LIST;
488
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200489 for (i = 0; command_complete[i].expand != 0; ++i)
490 if (STRCMP(complete_str, command_complete[i].name) == 0)
491 return command_complete[i].expand;
492
493 return EXPAND_NOTHING;
494}
Dominique Pelle748b3082022-01-08 12:41:16 +0000495#endif
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200496
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200497/*
498 * List user commands starting with "name[name_len]".
499 */
500 static void
501uc_list(char_u *name, size_t name_len)
502{
503 int i, j;
504 int found = FALSE;
505 ucmd_T *cmd;
506 int len;
507 int over;
508 long a;
509 garray_T *gap;
510
Bram Moolenaarcf2594f2022-11-13 23:30:06 +0000511 // don't allow for adding or removing user commands here
512 ++ucmd_locked;
513
Bram Moolenaare38eab22019-12-05 21:50:01 +0100514 // In cmdwin, the alternative buffer should be used.
Bram Moolenaar0f6e28f2022-02-20 20:49:35 +0000515 gap = &prevwin_curwin()->w_buffer->b_ucmds;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200516 for (;;)
517 {
518 for (i = 0; i < gap->ga_len; ++i)
519 {
520 cmd = USER_CMD_GA(gap, i);
521 a = (long)cmd->uc_argt;
522
523 // Skip commands which don't match the requested prefix and
524 // commands filtered out.
525 if (STRNCMP(name, cmd->uc_name, name_len) != 0
526 || message_filtered(cmd->uc_name))
527 continue;
528
529 // Put out the title first time
530 if (!found)
531 msg_puts_title(_("\n Name Args Address Complete Definition"));
532 found = TRUE;
533 msg_putchar('\n');
534 if (got_int)
535 break;
536
537 // Special cases
538 len = 4;
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200539 if (a & EX_BANG)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200540 {
541 msg_putchar('!');
542 --len;
543 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200544 if (a & EX_REGSTR)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200545 {
546 msg_putchar('"');
547 --len;
548 }
549 if (gap != &ucmds)
550 {
551 msg_putchar('b');
552 --len;
553 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200554 if (a & EX_TRLBAR)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200555 {
556 msg_putchar('|');
557 --len;
558 }
559 while (len-- > 0)
560 msg_putchar(' ');
561
562 msg_outtrans_attr(cmd->uc_name, HL_ATTR(HLF_D));
563 len = (int)STRLEN(cmd->uc_name) + 4;
564
565 do {
566 msg_putchar(' ');
567 ++len;
568 } while (len < 22);
569
570 // "over" is how much longer the name is than the column width for
571 // the name, we'll try to align what comes after.
572 over = len - 22;
573 len = 0;
574
575 // Arguments
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200576 switch ((int)(a & (EX_EXTRA|EX_NOSPC|EX_NEEDARG)))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200577 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200578 case 0: IObuff[len++] = '0'; break;
579 case (EX_EXTRA): IObuff[len++] = '*'; break;
580 case (EX_EXTRA|EX_NOSPC): IObuff[len++] = '?'; break;
581 case (EX_EXTRA|EX_NEEDARG): IObuff[len++] = '+'; break;
582 case (EX_EXTRA|EX_NOSPC|EX_NEEDARG): IObuff[len++] = '1'; break;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200583 }
584
585 do {
586 IObuff[len++] = ' ';
587 } while (len < 5 - over);
588
589 // Address / Range
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200590 if (a & (EX_RANGE|EX_COUNT))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200591 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200592 if (a & EX_COUNT)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200593 {
594 // -count=N
595 sprintf((char *)IObuff + len, "%ldc", cmd->uc_def);
596 len += (int)STRLEN(IObuff + len);
597 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200598 else if (a & EX_DFLALL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200599 IObuff[len++] = '%';
600 else if (cmd->uc_def >= 0)
601 {
602 // -range=N
603 sprintf((char *)IObuff + len, "%ld", cmd->uc_def);
604 len += (int)STRLEN(IObuff + len);
605 }
606 else
607 IObuff[len++] = '.';
608 }
609
610 do {
611 IObuff[len++] = ' ';
612 } while (len < 8 - over);
613
614 // Address Type
Bram Moolenaarb7316892019-05-01 18:08:42 +0200615 for (j = 0; addr_type_complete[j].expand != ADDR_NONE; ++j)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200616 if (addr_type_complete[j].expand != ADDR_LINES
617 && addr_type_complete[j].expand == cmd->uc_addr_type)
618 {
619 STRCPY(IObuff + len, addr_type_complete[j].shortname);
620 len += (int)STRLEN(IObuff + len);
621 break;
622 }
623
624 do {
625 IObuff[len++] = ' ';
626 } while (len < 13 - over);
627
628 // Completion
629 for (j = 0; command_complete[j].expand != 0; ++j)
630 if (command_complete[j].expand == cmd->uc_compl)
631 {
632 STRCPY(IObuff + len, command_complete[j].name);
633 len += (int)STRLEN(IObuff + len);
Bram Moolenaar78f60322022-01-17 22:16:33 +0000634#ifdef FEAT_EVAL
Bram Moolenaar3f3597b2022-01-17 19:06:56 +0000635 if (p_verbose > 0 && cmd->uc_compl_arg != NULL
636 && STRLEN(cmd->uc_compl_arg) < 200)
637 {
638 IObuff[len] = ',';
639 STRCPY(IObuff + len + 1, cmd->uc_compl_arg);
640 len += (int)STRLEN(IObuff + len);
641 }
Bram Moolenaar78f60322022-01-17 22:16:33 +0000642#endif
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200643 break;
644 }
645
646 do {
647 IObuff[len++] = ' ';
648 } while (len < 25 - over);
649
650 IObuff[len] = '\0';
651 msg_outtrans(IObuff);
652
653 msg_outtrans_special(cmd->uc_rep, FALSE,
654 name_len == 0 ? Columns - 47 : 0);
655#ifdef FEAT_EVAL
656 if (p_verbose > 0)
657 last_set_msg(cmd->uc_script_ctx);
658#endif
659 out_flush();
660 ui_breakcheck();
661 if (got_int)
662 break;
663 }
664 if (gap == &ucmds || i < gap->ga_len)
665 break;
666 gap = &ucmds;
667 }
668
669 if (!found)
670 msg(_("No user-defined commands found"));
Bram Moolenaarcf2594f2022-11-13 23:30:06 +0000671
672 --ucmd_locked;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200673}
674
675 char *
676uc_fun_cmd(void)
677{
678 static char_u fcmd[] = {0x84, 0xaf, 0x60, 0xb9, 0xaf, 0xb5, 0x60, 0xa4,
679 0xa5, 0xad, 0xa1, 0xae, 0xa4, 0x60, 0xa1, 0x60,
680 0xb3, 0xa8, 0xb2, 0xb5, 0xa2, 0xa2, 0xa5, 0xb2,
681 0xb9, 0x7f, 0};
682 int i;
683
684 for (i = 0; fcmd[i]; ++i)
685 IObuff[i] = fcmd[i] - 0x40;
686 IObuff[i] = 0;
687 return (char *)IObuff;
688}
689
690/*
691 * Parse address type argument
692 */
693 static int
694parse_addr_type_arg(
695 char_u *value,
696 int vallen,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200697 cmd_addr_T *addr_type_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200698{
699 int i, a, b;
700
Bram Moolenaarb7316892019-05-01 18:08:42 +0200701 for (i = 0; addr_type_complete[i].expand != ADDR_NONE; ++i)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200702 {
703 a = (int)STRLEN(addr_type_complete[i].name) == vallen;
704 b = STRNCMP(value, addr_type_complete[i].name, vallen) == 0;
705 if (a && b)
706 {
707 *addr_type_arg = addr_type_complete[i].expand;
708 break;
709 }
710 }
711
Bram Moolenaarb7316892019-05-01 18:08:42 +0200712 if (addr_type_complete[i].expand == ADDR_NONE)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200713 {
714 char_u *err = value;
715
716 for (i = 0; err[i] != NUL && !VIM_ISWHITE(err[i]); i++)
717 ;
718 err[i] = NUL;
Bram Moolenaar11de43d2022-01-06 21:41:11 +0000719 semsg(_(e_invalid_address_type_value_str), err);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200720 return FAIL;
721 }
722
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200723 return OK;
724}
725
726/*
727 * Parse a completion argument "value[vallen]".
728 * The detected completion goes in "*complp", argument type in "*argt".
729 * When there is an argument, for function and user defined completion, it's
730 * copied to allocated memory and stored in "*compl_arg".
731 * Returns FAIL if something is wrong.
732 */
733 int
734parse_compl_arg(
735 char_u *value,
736 int vallen,
737 int *complp,
738 long *argt,
739 char_u **compl_arg UNUSED)
740{
741 char_u *arg = NULL;
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200742# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200743 size_t arglen = 0;
744# endif
745 int i;
746 int valend = vallen;
747
748 // Look for any argument part - which is the part after any ','
749 for (i = 0; i < vallen; ++i)
750 {
751 if (value[i] == ',')
752 {
753 arg = &value[i + 1];
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200754# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200755 arglen = vallen - i - 1;
756# endif
757 valend = i;
758 break;
759 }
760 }
761
762 for (i = 0; command_complete[i].expand != 0; ++i)
763 {
764 if ((int)STRLEN(command_complete[i].name) == valend
765 && STRNCMP(value, command_complete[i].name, valend) == 0)
766 {
767 *complp = command_complete[i].expand;
768 if (command_complete[i].expand == EXPAND_BUFFERS)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200769 *argt |= EX_BUFNAME;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200770 else if (command_complete[i].expand == EXPAND_DIRECTORIES
771 || command_complete[i].expand == EXPAND_FILES)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200772 *argt |= EX_XFILE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200773 break;
774 }
775 }
776
777 if (command_complete[i].expand == 0)
778 {
Bram Moolenaar1a992222021-12-31 17:25:48 +0000779 semsg(_(e_invalid_complete_value_str), value);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200780 return FAIL;
781 }
782
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200783# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200784 if (*complp != EXPAND_USER_DEFINED && *complp != EXPAND_USER_LIST
785 && arg != NULL)
786# else
787 if (arg != NULL)
788# endif
789 {
Bram Moolenaarb09feaa2022-01-02 20:20:45 +0000790 emsg(_(e_completion_argument_only_allowed_for_custom_completion));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200791 return FAIL;
792 }
793
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200794# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200795 if ((*complp == EXPAND_USER_DEFINED || *complp == EXPAND_USER_LIST)
796 && arg == NULL)
797 {
Bram Moolenaarb09feaa2022-01-02 20:20:45 +0000798 emsg(_(e_custom_completion_requires_function_argument));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200799 return FAIL;
800 }
801
802 if (arg != NULL)
Bram Moolenaar71ccd032020-06-12 22:59:11 +0200803 *compl_arg = vim_strnsave(arg, arglen);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200804# endif
805 return OK;
806}
807
808/*
809 * Scan attributes in the ":command" command.
810 * Return FAIL when something is wrong.
811 */
812 static int
813uc_scan_attr(
814 char_u *attr,
815 size_t len,
816 long *argt,
817 long *def,
818 int *flags,
Bram Moolenaar52111f82019-04-29 21:30:45 +0200819 int *complp,
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200820 char_u **compl_arg,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200821 cmd_addr_T *addr_type_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200822{
823 char_u *p;
824
825 if (len == 0)
826 {
Bram Moolenaar1a992222021-12-31 17:25:48 +0000827 emsg(_(e_no_attribute_specified));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200828 return FAIL;
829 }
830
831 // First, try the simple attributes (no arguments)
832 if (STRNICMP(attr, "bang", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200833 *argt |= EX_BANG;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200834 else if (STRNICMP(attr, "buffer", len) == 0)
835 *flags |= UC_BUFFER;
836 else if (STRNICMP(attr, "register", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200837 *argt |= EX_REGSTR;
Bram Moolenaar58ef8a32021-11-12 11:25:11 +0000838 else if (STRNICMP(attr, "keepscript", len) == 0)
839 *argt |= EX_KEEPSCRIPT;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200840 else if (STRNICMP(attr, "bar", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200841 *argt |= EX_TRLBAR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200842 else
843 {
844 int i;
845 char_u *val = NULL;
846 size_t vallen = 0;
847 size_t attrlen = len;
848
849 // Look for the attribute name - which is the part before any '='
850 for (i = 0; i < (int)len; ++i)
851 {
852 if (attr[i] == '=')
853 {
854 val = &attr[i + 1];
855 vallen = len - i - 1;
856 attrlen = i;
857 break;
858 }
859 }
860
861 if (STRNICMP(attr, "nargs", attrlen) == 0)
862 {
863 if (vallen == 1)
864 {
865 if (*val == '0')
866 // Do nothing - this is the default
867 ;
868 else if (*val == '1')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200869 *argt |= (EX_EXTRA | EX_NOSPC | EX_NEEDARG);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200870 else if (*val == '*')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200871 *argt |= EX_EXTRA;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200872 else if (*val == '?')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200873 *argt |= (EX_EXTRA | EX_NOSPC);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200874 else if (*val == '+')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200875 *argt |= (EX_EXTRA | EX_NEEDARG);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200876 else
877 goto wrong_nargs;
878 }
879 else
880 {
881wrong_nargs:
Bram Moolenaar1a992222021-12-31 17:25:48 +0000882 emsg(_(e_invalid_number_of_arguments));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200883 return FAIL;
884 }
885 }
886 else if (STRNICMP(attr, "range", attrlen) == 0)
887 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200888 *argt |= EX_RANGE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200889 if (vallen == 1 && *val == '%')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200890 *argt |= EX_DFLALL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200891 else if (val != NULL)
892 {
893 p = val;
894 if (*def >= 0)
895 {
896two_count:
Bram Moolenaar1a992222021-12-31 17:25:48 +0000897 emsg(_(e_count_cannot_be_specified_twice));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200898 return FAIL;
899 }
900
901 *def = getdigits(&p);
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200902 *argt |= EX_ZEROR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200903
904 if (p != val + vallen || vallen == 0)
905 {
906invalid_count:
Bram Moolenaar1a992222021-12-31 17:25:48 +0000907 emsg(_(e_invalid_default_value_for_count));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200908 return FAIL;
909 }
910 }
Bram Moolenaarb7316892019-05-01 18:08:42 +0200911 // default for -range is using buffer lines
912 if (*addr_type_arg == ADDR_NONE)
913 *addr_type_arg = ADDR_LINES;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200914 }
915 else if (STRNICMP(attr, "count", attrlen) == 0)
916 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200917 *argt |= (EX_COUNT | EX_ZEROR | EX_RANGE);
Bram Moolenaarb7316892019-05-01 18:08:42 +0200918 // default for -count is using any number
919 if (*addr_type_arg == ADDR_NONE)
920 *addr_type_arg = ADDR_OTHER;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200921
922 if (val != NULL)
923 {
924 p = val;
925 if (*def >= 0)
926 goto two_count;
927
928 *def = getdigits(&p);
929
930 if (p != val + vallen)
931 goto invalid_count;
932 }
933
934 if (*def < 0)
935 *def = 0;
936 }
937 else if (STRNICMP(attr, "complete", attrlen) == 0)
938 {
939 if (val == NULL)
940 {
Bram Moolenaar1a992222021-12-31 17:25:48 +0000941 semsg(_(e_argument_required_for_str), "-complete");
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200942 return FAIL;
943 }
944
Bram Moolenaar52111f82019-04-29 21:30:45 +0200945 if (parse_compl_arg(val, (int)vallen, complp, argt, compl_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200946 == FAIL)
947 return FAIL;
948 }
949 else if (STRNICMP(attr, "addr", attrlen) == 0)
950 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200951 *argt |= EX_RANGE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200952 if (val == NULL)
953 {
Bram Moolenaar1a992222021-12-31 17:25:48 +0000954 semsg(_(e_argument_required_for_str), "-addr");
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200955 return FAIL;
956 }
Bram Moolenaare4f5f3a2019-05-04 14:05:08 +0200957 if (parse_addr_type_arg(val, (int)vallen, addr_type_arg) == FAIL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200958 return FAIL;
Bram Moolenaare4f5f3a2019-05-04 14:05:08 +0200959 if (*addr_type_arg != ADDR_LINES)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200960 *argt |= EX_ZEROR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200961 }
962 else
963 {
964 char_u ch = attr[len];
965 attr[len] = '\0';
Bram Moolenaar1a992222021-12-31 17:25:48 +0000966 semsg(_(e_invalid_attribute_str), attr);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200967 attr[len] = ch;
968 return FAIL;
969 }
970 }
971
972 return OK;
973}
974
975/*
976 * Add a user command to the list or replace an existing one.
977 */
978 static int
979uc_add_command(
980 char_u *name,
981 size_t name_len,
982 char_u *rep,
983 long argt,
984 long def,
985 int flags,
986 int compl,
987 char_u *compl_arg UNUSED,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200988 cmd_addr_T addr_type,
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200989 int force)
990{
991 ucmd_T *cmd = NULL;
992 char_u *p;
993 int i;
994 int cmp = 1;
995 char_u *rep_buf = NULL;
996 garray_T *gap;
997
zeertzjq7e0bae02023-08-11 23:15:38 +0200998 replace_termcodes(rep, &rep_buf, 0, 0, NULL);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200999 if (rep_buf == NULL)
1000 {
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001001 // can't replace termcodes - try using the string as is
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001002 rep_buf = vim_strsave(rep);
1003
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001004 // give up if out of memory
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001005 if (rep_buf == NULL)
1006 return FAIL;
1007 }
1008
1009 // get address of growarray: global or in curbuf
1010 if (flags & UC_BUFFER)
1011 {
1012 gap = &curbuf->b_ucmds;
1013 if (gap->ga_itemsize == 0)
Bram Moolenaar04935fb2022-01-08 16:19:22 +00001014 ga_init2(gap, sizeof(ucmd_T), 4);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001015 }
1016 else
1017 gap = &ucmds;
1018
1019 // Search for the command in the already defined commands.
1020 for (i = 0; i < gap->ga_len; ++i)
1021 {
1022 size_t len;
1023
1024 cmd = USER_CMD_GA(gap, i);
1025 len = STRLEN(cmd->uc_name);
1026 cmp = STRNCMP(name, cmd->uc_name, name_len);
1027 if (cmp == 0)
1028 {
1029 if (name_len < len)
1030 cmp = -1;
1031 else if (name_len > len)
1032 cmp = 1;
1033 }
1034
1035 if (cmp == 0)
1036 {
1037 // Command can be replaced with "command!" and when sourcing the
1038 // same script again, but only once.
1039 if (!force
1040#ifdef FEAT_EVAL
1041 && (cmd->uc_script_ctx.sc_sid != current_sctx.sc_sid
1042 || cmd->uc_script_ctx.sc_seq == current_sctx.sc_seq)
1043#endif
1044 )
1045 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001046 semsg(_(e_command_already_exists_add_bang_to_replace_it_str),
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001047 name);
1048 goto fail;
1049 }
1050
1051 VIM_CLEAR(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001052#if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001053 VIM_CLEAR(cmd->uc_compl_arg);
1054#endif
1055 break;
1056 }
1057
1058 // Stop as soon as we pass the name to add
1059 if (cmp < 0)
1060 break;
1061 }
1062
1063 // Extend the array unless we're replacing an existing command
1064 if (cmp != 0)
1065 {
Yegappan Lakshmananfadc02a2023-01-27 21:03:12 +00001066 if (ga_grow(gap, 1) == FAIL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001067 goto fail;
Bram Moolenaar71ccd032020-06-12 22:59:11 +02001068 if ((p = vim_strnsave(name, name_len)) == NULL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001069 goto fail;
1070
1071 cmd = USER_CMD_GA(gap, i);
1072 mch_memmove(cmd + 1, cmd, (gap->ga_len - i) * sizeof(ucmd_T));
1073
1074 ++gap->ga_len;
1075
1076 cmd->uc_name = p;
1077 }
1078
1079 cmd->uc_rep = rep_buf;
1080 cmd->uc_argt = argt;
1081 cmd->uc_def = def;
1082 cmd->uc_compl = compl;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001083 cmd->uc_script_ctx = current_sctx;
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001084 if (flags & UC_VIM9)
1085 cmd->uc_script_ctx.sc_version = SCRIPT_VERSION_VIM9;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00001086 cmd->uc_flags = flags & UC_VIM9;
Bram Moolenaar9b8d6222020-12-28 18:26:00 +01001087#ifdef FEAT_EVAL
Bram Moolenaar1a47ae32019-12-29 23:04:25 +01001088 cmd->uc_script_ctx.sc_lnum += SOURCING_LNUM;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001089 cmd->uc_compl_arg = compl_arg;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001090#endif
1091 cmd->uc_addr_type = addr_type;
1092
1093 return OK;
1094
1095fail:
1096 vim_free(rep_buf);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001097#if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001098 vim_free(compl_arg);
1099#endif
1100 return FAIL;
1101}
1102
1103/*
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001104 * If "p" starts with "{" then read a block of commands until "}".
1105 * Used for ":command" and ":autocmd".
1106 */
1107 char_u *
1108may_get_cmd_block(exarg_T *eap, char_u *p, char_u **tofree, int *flags)
1109{
1110 char_u *retp = p;
1111
1112 if (*p == '{' && ends_excmd2(eap->arg, skipwhite(p + 1))
1113 && eap->getline != NULL)
1114 {
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001115 garray_T ga;
1116 char_u *line = NULL;
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001117
1118 ga_init2(&ga, sizeof(char_u *), 10);
Bram Moolenaar9f1a39a2022-01-08 15:39:39 +00001119 if (ga_copy_string(&ga, p) == FAIL)
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001120 return retp;
1121
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001122 // If the argument ends in "}" it must have been concatenated already
1123 // for ISN_EXEC.
1124 if (p[STRLEN(p) - 1] != '}')
1125 // Read lines between '{' and '}'. Does not support nesting or
1126 // here-doc constructs.
1127 for (;;)
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001128 {
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001129 vim_free(line);
1130 if ((line = eap->getline(':', eap->cookie,
1131 0, GETLINE_CONCAT_CONTBAR)) == NULL)
1132 {
1133 emsg(_(e_missing_rcurly));
1134 break;
1135 }
Bram Moolenaar9f1a39a2022-01-08 15:39:39 +00001136 if (ga_copy_string(&ga, line) == FAIL)
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001137 break;
1138 if (*skipwhite(line) == '}')
1139 break;
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001140 }
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001141 vim_free(line);
1142 retp = *tofree = ga_concat_strings(&ga, "\n");
1143 ga_clear_strings(&ga);
1144 *flags |= UC_VIM9;
1145 }
1146 return retp;
1147}
1148
1149/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001150 * ":command ..." implementation
1151 */
1152 void
1153ex_command(exarg_T *eap)
1154{
Bram Moolenaarb7316892019-05-01 18:08:42 +02001155 char_u *name;
1156 char_u *end;
1157 char_u *p;
1158 long argt = 0;
1159 long def = -1;
1160 int flags = 0;
1161 int compl = EXPAND_NOTHING;
1162 char_u *compl_arg = NULL;
1163 cmd_addr_T addr_type_arg = ADDR_NONE;
1164 int has_attr = (eap->arg[0] == '-');
1165 int name_len;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001166
1167 p = eap->arg;
1168
1169 // Check for attributes
1170 while (*p == '-')
1171 {
1172 ++p;
1173 end = skiptowhite(p);
1174 if (uc_scan_attr(p, end - p, &argt, &def, &flags, &compl,
1175 &compl_arg, &addr_type_arg) == FAIL)
zeertzjq33e54302022-12-19 16:49:27 +00001176 goto theend;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001177 p = skipwhite(end);
1178 }
1179
1180 // Get the name (if any) and skip to the following argument
1181 name = p;
1182 if (ASCII_ISALPHA(*p))
1183 while (ASCII_ISALNUM(*p))
1184 ++p;
Bram Moolenaara72cfb82020-04-23 17:07:30 +02001185 if (!ends_excmd2(eap->arg, p) && !VIM_ISWHITE(*p))
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001186 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001187 emsg(_(e_invalid_command_name));
zeertzjq33e54302022-12-19 16:49:27 +00001188 goto theend;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001189 }
1190 end = p;
1191 name_len = (int)(end - name);
1192
1193 // If there is nothing after the name, and no attributes were specified,
1194 // we are listing commands
1195 p = skipwhite(end);
Bram Moolenaara72cfb82020-04-23 17:07:30 +02001196 if (!has_attr && ends_excmd2(eap->arg, p))
zeertzjq33e54302022-12-19 16:49:27 +00001197 {
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001198 uc_list(name, end - name);
zeertzjq33e54302022-12-19 16:49:27 +00001199 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001200 else if (!ASCII_ISUPPER(*name))
zeertzjq33e54302022-12-19 16:49:27 +00001201 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001202 emsg(_(e_user_defined_commands_must_start_with_an_uppercase_letter));
zeertzjq33e54302022-12-19 16:49:27 +00001203 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001204 else if ((name_len == 1 && *name == 'X')
1205 || (name_len <= 4
1206 && STRNCMP(name, "Next", name_len > 4 ? 4 : name_len) == 0))
zeertzjq33e54302022-12-19 16:49:27 +00001207 {
Bram Moolenaar9d00e4a2022-01-05 17:49:15 +00001208 emsg(_(e_reserved_name_cannot_be_used_for_user_defined_command));
zeertzjq33e54302022-12-19 16:49:27 +00001209 }
Martin Tournoijde69a732021-07-11 14:28:25 +02001210 else if (compl > 0 && (argt & EX_EXTRA) == 0)
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001211 {
1212 // Some plugins rely on silently ignoring the mistake, only make this
1213 // an error in Vim9 script.
1214 if (in_vim9script())
Bram Moolenaar41a34852021-08-04 16:09:24 +02001215 emsg(_(e_complete_used_without_allowing_arguments));
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001216 else
1217 give_warning_with_source(
Bram Moolenaar41a34852021-08-04 16:09:24 +02001218 (char_u *)_(e_complete_used_without_allowing_arguments),
1219 TRUE, TRUE);
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001220 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001221 else
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001222 {
1223 char_u *tofree = NULL;
1224
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001225 p = may_get_cmd_block(eap, p, &tofree, &flags);
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001226
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001227 uc_add_command(name, end - name, p, argt, def, flags, compl, compl_arg,
1228 addr_type_arg, eap->forceit);
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001229 vim_free(tofree);
zeertzjq33e54302022-12-19 16:49:27 +00001230
1231 return; // success
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001232 }
zeertzjq33e54302022-12-19 16:49:27 +00001233
1234theend:
1235 vim_free(compl_arg);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001236}
1237
1238/*
1239 * ":comclear" implementation
1240 * Clear all user commands, global and for current buffer.
1241 */
1242 void
1243ex_comclear(exarg_T *eap UNUSED)
1244{
1245 uc_clear(&ucmds);
Bram Moolenaare5c83282019-05-03 23:15:37 +02001246 if (curbuf != NULL)
1247 uc_clear(&curbuf->b_ucmds);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001248}
1249
1250/*
Bram Moolenaarcf2594f2022-11-13 23:30:06 +00001251 * If ucmd_locked is set give an error and return TRUE.
1252 * Otherwise return FALSE.
1253 */
1254 static int
1255is_ucmd_locked(void)
1256{
1257 if (ucmd_locked > 0)
1258 {
1259 emsg(_(e_cannot_change_user_commands_while_listing));
1260 return TRUE;
1261 }
1262 return FALSE;
1263}
1264
1265/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001266 * Clear all user commands for "gap".
1267 */
1268 void
1269uc_clear(garray_T *gap)
1270{
1271 int i;
1272 ucmd_T *cmd;
1273
Bram Moolenaarcf2594f2022-11-13 23:30:06 +00001274 if (is_ucmd_locked())
1275 return;
1276
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001277 for (i = 0; i < gap->ga_len; ++i)
1278 {
1279 cmd = USER_CMD_GA(gap, i);
1280 vim_free(cmd->uc_name);
1281 vim_free(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001282# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001283 vim_free(cmd->uc_compl_arg);
1284# endif
1285 }
1286 ga_clear(gap);
1287}
1288
1289/*
1290 * ":delcommand" implementation
1291 */
1292 void
1293ex_delcommand(exarg_T *eap)
1294{
1295 int i = 0;
1296 ucmd_T *cmd = NULL;
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001297 int res = -1;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001298 garray_T *gap;
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001299 char_u *arg = eap->arg;
1300 int buffer_only = FALSE;
1301
1302 if (STRNCMP(arg, "-buffer", 7) == 0 && VIM_ISWHITE(arg[7]))
1303 {
1304 buffer_only = TRUE;
1305 arg = skipwhite(arg + 7);
1306 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001307
1308 gap = &curbuf->b_ucmds;
1309 for (;;)
1310 {
1311 for (i = 0; i < gap->ga_len; ++i)
1312 {
1313 cmd = USER_CMD_GA(gap, i);
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001314 res = STRCMP(arg, cmd->uc_name);
1315 if (res <= 0)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001316 break;
1317 }
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001318 if (gap == &ucmds || res == 0 || buffer_only)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001319 break;
1320 gap = &ucmds;
1321 }
1322
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001323 if (res != 0)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001324 {
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001325 semsg(_(buffer_only
1326 ? e_no_such_user_defined_command_in_current_buffer_str
1327 : e_no_such_user_defined_command_str), arg);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001328 return;
1329 }
1330
Bram Moolenaarcf2594f2022-11-13 23:30:06 +00001331 if (is_ucmd_locked())
1332 return;
1333
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001334 vim_free(cmd->uc_name);
1335 vim_free(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001336# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001337 vim_free(cmd->uc_compl_arg);
1338# endif
1339
1340 --gap->ga_len;
1341
1342 if (i < gap->ga_len)
1343 mch_memmove(cmd, cmd + 1, (gap->ga_len - i) * sizeof(ucmd_T));
1344}
1345
1346/*
1347 * Split and quote args for <f-args>.
1348 */
1349 static char_u *
1350uc_split_args(char_u *arg, size_t *lenp)
1351{
1352 char_u *buf;
1353 char_u *p;
1354 char_u *q;
1355 int len;
1356
1357 // Precalculate length
1358 p = arg;
1359 len = 2; // Initial and final quotes
1360
1361 while (*p)
1362 {
1363 if (p[0] == '\\' && p[1] == '\\')
1364 {
1365 len += 2;
1366 p += 2;
1367 }
1368 else if (p[0] == '\\' && VIM_ISWHITE(p[1]))
1369 {
1370 len += 1;
1371 p += 2;
1372 }
1373 else if (*p == '\\' || *p == '"')
1374 {
1375 len += 2;
1376 p += 1;
1377 }
1378 else if (VIM_ISWHITE(*p))
1379 {
1380 p = skipwhite(p);
1381 if (*p == NUL)
1382 break;
Bram Moolenaar20d89e02020-10-20 23:11:33 +02001383 len += 4; // ", "
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001384 }
1385 else
1386 {
1387 int charlen = (*mb_ptr2len)(p);
1388
1389 len += charlen;
1390 p += charlen;
1391 }
1392 }
1393
1394 buf = alloc(len + 1);
1395 if (buf == NULL)
1396 {
1397 *lenp = 0;
1398 return buf;
1399 }
1400
1401 p = arg;
1402 q = buf;
1403 *q++ = '"';
1404 while (*p)
1405 {
1406 if (p[0] == '\\' && p[1] == '\\')
1407 {
1408 *q++ = '\\';
1409 *q++ = '\\';
1410 p += 2;
1411 }
1412 else if (p[0] == '\\' && VIM_ISWHITE(p[1]))
1413 {
1414 *q++ = p[1];
1415 p += 2;
1416 }
1417 else if (*p == '\\' || *p == '"')
1418 {
1419 *q++ = '\\';
1420 *q++ = *p++;
1421 }
1422 else if (VIM_ISWHITE(*p))
1423 {
1424 p = skipwhite(p);
1425 if (*p == NUL)
1426 break;
1427 *q++ = '"';
1428 *q++ = ',';
Bram Moolenaar20d89e02020-10-20 23:11:33 +02001429 *q++ = ' ';
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001430 *q++ = '"';
1431 }
1432 else
1433 {
1434 MB_COPY_CHAR(p, q);
1435 }
1436 }
1437 *q++ = '"';
1438 *q = 0;
1439
1440 *lenp = len;
1441 return buf;
1442}
1443
1444 static size_t
1445add_cmd_modifier(char_u *buf, char *mod_str, int *multi_mods)
1446{
1447 size_t result;
1448
1449 result = STRLEN(mod_str);
1450 if (*multi_mods)
1451 result += 1;
1452 if (buf != NULL)
1453 {
1454 if (*multi_mods)
1455 STRCAT(buf, " ");
1456 STRCAT(buf, mod_str);
1457 }
1458
1459 *multi_mods = 1;
1460
1461 return result;
1462}
1463
1464/*
Bram Moolenaar02194d22020-10-24 23:08:38 +02001465 * Add modifiers from "cmod->cmod_split" to "buf". Set "multi_mods" when one
Bram Moolenaare1004402020-10-24 20:49:43 +02001466 * was added. Return the number of bytes added.
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001467 */
1468 size_t
dundargocc57b5bc2022-11-02 13:30:51 +00001469add_win_cmd_modifiers(char_u *buf, cmdmod_T *cmod, int *multi_mods)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001470{
1471 size_t result = 0;
1472
1473 // :aboveleft and :leftabove
Bram Moolenaar02194d22020-10-24 23:08:38 +02001474 if (cmod->cmod_split & WSP_ABOVE)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001475 result += add_cmd_modifier(buf, "aboveleft", multi_mods);
1476 // :belowright and :rightbelow
Bram Moolenaar02194d22020-10-24 23:08:38 +02001477 if (cmod->cmod_split & WSP_BELOW)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001478 result += add_cmd_modifier(buf, "belowright", multi_mods);
1479 // :botright
Bram Moolenaar02194d22020-10-24 23:08:38 +02001480 if (cmod->cmod_split & WSP_BOT)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001481 result += add_cmd_modifier(buf, "botright", multi_mods);
1482
1483 // :tab
Bram Moolenaar02194d22020-10-24 23:08:38 +02001484 if (cmod->cmod_tab > 0)
zeertzjq208567e2022-10-18 13:11:21 +01001485 {
1486 int tabnr = cmod->cmod_tab - 1;
1487
1488 if (tabnr == tabpage_index(curtab))
1489 {
1490 // For compatibility, don't add a tabpage number if it is the same
1491 // as the default number for :tab.
1492 result += add_cmd_modifier(buf, "tab", multi_mods);
1493 }
1494 else
1495 {
1496 char tab_buf[NUMBUFLEN + 3];
1497
1498 sprintf(tab_buf, "%dtab", tabnr);
1499 result += add_cmd_modifier(buf, tab_buf, multi_mods);
1500 }
1501 }
1502
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001503 // :topleft
Bram Moolenaar02194d22020-10-24 23:08:38 +02001504 if (cmod->cmod_split & WSP_TOP)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001505 result += add_cmd_modifier(buf, "topleft", multi_mods);
1506 // :vertical
Bram Moolenaar02194d22020-10-24 23:08:38 +02001507 if (cmod->cmod_split & WSP_VERT)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001508 result += add_cmd_modifier(buf, "vertical", multi_mods);
zeertzjqd3de1782022-09-01 12:58:52 +01001509 // :horizontal
1510 if (cmod->cmod_split & WSP_HOR)
1511 result += add_cmd_modifier(buf, "horizontal", multi_mods);
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001512 return result;
1513}
1514
1515/*
Bram Moolenaar02194d22020-10-24 23:08:38 +02001516 * Generate text for the "cmod" command modifiers.
1517 * If "buf" is NULL just return the length.
1518 */
Bram Moolenaara360dbe2020-10-26 18:46:53 +01001519 size_t
Bram Moolenaar02194d22020-10-24 23:08:38 +02001520produce_cmdmods(char_u *buf, cmdmod_T *cmod, int quote)
1521{
Bram Moolenaara360dbe2020-10-26 18:46:53 +01001522 size_t result = 0;
Bram Moolenaar02194d22020-10-24 23:08:38 +02001523 int multi_mods = 0;
1524 int i;
1525 typedef struct {
1526 int flag;
1527 char *name;
1528 } mod_entry_T;
1529 static mod_entry_T mod_entries[] = {
1530#ifdef FEAT_BROWSE_CMD
1531 {CMOD_BROWSE, "browse"},
1532#endif
1533#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
1534 {CMOD_CONFIRM, "confirm"},
1535#endif
1536 {CMOD_HIDE, "hide"},
1537 {CMOD_KEEPALT, "keepalt"},
1538 {CMOD_KEEPJUMPS, "keepjumps"},
1539 {CMOD_KEEPMARKS, "keepmarks"},
1540 {CMOD_KEEPPATTERNS, "keeppatterns"},
1541 {CMOD_LOCKMARKS, "lockmarks"},
1542 {CMOD_NOSWAPFILE, "noswapfile"},
1543 {CMOD_UNSILENT, "unsilent"},
1544 {CMOD_NOAUTOCMD, "noautocmd"},
1545#ifdef HAVE_SANDBOX
1546 {CMOD_SANDBOX, "sandbox"},
1547#endif
Bram Moolenaarb579f6e2021-12-04 11:57:00 +00001548 {CMOD_LEGACY, "legacy"},
Bram Moolenaar02194d22020-10-24 23:08:38 +02001549 {0, NULL}
1550 };
1551
1552 result = quote ? 2 : 0;
1553 if (buf != NULL)
1554 {
1555 if (quote)
1556 *buf++ = '"';
1557 *buf = '\0';
1558 }
1559
1560 // the modifiers that are simple flags
1561 for (i = 0; mod_entries[i].name != NULL; ++i)
1562 if (cmod->cmod_flags & mod_entries[i].flag)
1563 result += add_cmd_modifier(buf, mod_entries[i].name, &multi_mods);
1564
1565 // :silent
1566 if (cmod->cmod_flags & CMOD_SILENT)
1567 result += add_cmd_modifier(buf,
1568 (cmod->cmod_flags & CMOD_ERRSILENT) ? "silent!"
1569 : "silent", &multi_mods);
1570 // :verbose
zeertzjq9359e8a2022-07-03 13:16:09 +01001571 if (cmod->cmod_verbose > 0)
1572 {
1573 int verbose_value = cmod->cmod_verbose - 1;
1574
1575 if (verbose_value == 1)
1576 result += add_cmd_modifier(buf, "verbose", &multi_mods);
1577 else
1578 {
1579 char verbose_buf[NUMBUFLEN];
1580
1581 sprintf(verbose_buf, "%dverbose", verbose_value);
1582 result += add_cmd_modifier(buf, verbose_buf, &multi_mods);
1583 }
1584 }
Bram Moolenaar02194d22020-10-24 23:08:38 +02001585 // flags from cmod->cmod_split
dundargocc57b5bc2022-11-02 13:30:51 +00001586 result += add_win_cmd_modifiers(buf, cmod, &multi_mods);
zeertzjq9359e8a2022-07-03 13:16:09 +01001587
Bram Moolenaar02194d22020-10-24 23:08:38 +02001588 if (quote && buf != NULL)
1589 {
1590 buf += result - 2;
1591 *buf = '"';
1592 }
1593 return result;
1594}
1595
1596/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001597 * Check for a <> code in a user command.
1598 * "code" points to the '<'. "len" the length of the <> (inclusive).
1599 * "buf" is where the result is to be added.
1600 * "split_buf" points to a buffer used for splitting, caller should free it.
1601 * "split_len" is the length of what "split_buf" contains.
1602 * Returns the length of the replacement, which has been added to "buf".
1603 * Returns -1 if there was no match, and only the "<" has been copied.
1604 */
1605 static size_t
1606uc_check_code(
1607 char_u *code,
1608 size_t len,
1609 char_u *buf,
1610 ucmd_T *cmd, // the user command we're expanding
1611 exarg_T *eap, // ex arguments
1612 char_u **split_buf,
1613 size_t *split_len)
1614{
1615 size_t result = 0;
1616 char_u *p = code + 1;
1617 size_t l = len - 2;
1618 int quote = 0;
1619 enum {
1620 ct_ARGS,
1621 ct_BANG,
1622 ct_COUNT,
1623 ct_LINE1,
1624 ct_LINE2,
1625 ct_RANGE,
1626 ct_MODS,
1627 ct_REGISTER,
1628 ct_LT,
1629 ct_NONE
1630 } type = ct_NONE;
1631
1632 if ((vim_strchr((char_u *)"qQfF", *p) != NULL) && p[1] == '-')
1633 {
1634 quote = (*p == 'q' || *p == 'Q') ? 1 : 2;
1635 p += 2;
1636 l -= 2;
1637 }
1638
1639 ++l;
1640 if (l <= 1)
1641 type = ct_NONE;
1642 else if (STRNICMP(p, "args>", l) == 0)
1643 type = ct_ARGS;
1644 else if (STRNICMP(p, "bang>", l) == 0)
1645 type = ct_BANG;
1646 else if (STRNICMP(p, "count>", l) == 0)
1647 type = ct_COUNT;
1648 else if (STRNICMP(p, "line1>", l) == 0)
1649 type = ct_LINE1;
1650 else if (STRNICMP(p, "line2>", l) == 0)
1651 type = ct_LINE2;
1652 else if (STRNICMP(p, "range>", l) == 0)
1653 type = ct_RANGE;
1654 else if (STRNICMP(p, "lt>", l) == 0)
1655 type = ct_LT;
1656 else if (STRNICMP(p, "reg>", l) == 0 || STRNICMP(p, "register>", l) == 0)
1657 type = ct_REGISTER;
1658 else if (STRNICMP(p, "mods>", l) == 0)
1659 type = ct_MODS;
1660
1661 switch (type)
1662 {
1663 case ct_ARGS:
1664 // Simple case first
1665 if (*eap->arg == NUL)
1666 {
1667 if (quote == 1)
1668 {
1669 result = 2;
1670 if (buf != NULL)
1671 STRCPY(buf, "''");
1672 }
1673 else
1674 result = 0;
1675 break;
1676 }
1677
1678 // When specified there is a single argument don't split it.
1679 // Works for ":Cmd %" when % is "a b c".
Bram Moolenaar8071cb22019-07-12 17:58:01 +02001680 if ((eap->argt & EX_NOSPC) && quote == 2)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001681 quote = 1;
1682
1683 switch (quote)
1684 {
1685 case 0: // No quoting, no splitting
1686 result = STRLEN(eap->arg);
1687 if (buf != NULL)
1688 STRCPY(buf, eap->arg);
1689 break;
1690 case 1: // Quote, but don't split
1691 result = STRLEN(eap->arg) + 2;
1692 for (p = eap->arg; *p; ++p)
1693 {
1694 if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2)
1695 // DBCS can contain \ in a trail byte, skip the
1696 // double-byte character.
1697 ++p;
1698 else
1699 if (*p == '\\' || *p == '"')
1700 ++result;
1701 }
1702
1703 if (buf != NULL)
1704 {
1705 *buf++ = '"';
1706 for (p = eap->arg; *p; ++p)
1707 {
1708 if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2)
1709 // DBCS can contain \ in a trail byte, copy the
1710 // double-byte character to avoid escaping.
1711 *buf++ = *p++;
1712 else
1713 if (*p == '\\' || *p == '"')
1714 *buf++ = '\\';
1715 *buf++ = *p;
1716 }
1717 *buf = '"';
1718 }
1719
1720 break;
1721 case 2: // Quote and split (<f-args>)
1722 // This is hard, so only do it once, and cache the result
1723 if (*split_buf == NULL)
1724 *split_buf = uc_split_args(eap->arg, split_len);
1725
1726 result = *split_len;
1727 if (buf != NULL && result != 0)
1728 STRCPY(buf, *split_buf);
1729
1730 break;
1731 }
1732 break;
1733
1734 case ct_BANG:
1735 result = eap->forceit ? 1 : 0;
1736 if (quote)
1737 result += 2;
1738 if (buf != NULL)
1739 {
1740 if (quote)
1741 *buf++ = '"';
1742 if (eap->forceit)
1743 *buf++ = '!';
1744 if (quote)
1745 *buf = '"';
1746 }
1747 break;
1748
1749 case ct_LINE1:
1750 case ct_LINE2:
1751 case ct_RANGE:
1752 case ct_COUNT:
1753 {
1754 char num_buf[20];
1755 long num = (type == ct_LINE1) ? eap->line1 :
1756 (type == ct_LINE2) ? eap->line2 :
1757 (type == ct_RANGE) ? eap->addr_count :
1758 (eap->addr_count > 0) ? eap->line2 : cmd->uc_def;
1759 size_t num_len;
1760
1761 sprintf(num_buf, "%ld", num);
1762 num_len = STRLEN(num_buf);
1763 result = num_len;
1764
1765 if (quote)
1766 result += 2;
1767
1768 if (buf != NULL)
1769 {
1770 if (quote)
1771 *buf++ = '"';
1772 STRCPY(buf, num_buf);
1773 buf += num_len;
1774 if (quote)
1775 *buf = '"';
1776 }
1777
1778 break;
1779 }
1780
1781 case ct_MODS:
1782 {
Bram Moolenaar02194d22020-10-24 23:08:38 +02001783 result = produce_cmdmods(buf, &cmdmod, quote);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001784 break;
1785 }
1786
1787 case ct_REGISTER:
1788 result = eap->regname ? 1 : 0;
1789 if (quote)
1790 result += 2;
1791 if (buf != NULL)
1792 {
1793 if (quote)
1794 *buf++ = '\'';
1795 if (eap->regname)
1796 *buf++ = eap->regname;
1797 if (quote)
1798 *buf = '\'';
1799 }
1800 break;
1801
1802 case ct_LT:
1803 result = 1;
1804 if (buf != NULL)
1805 *buf = '<';
1806 break;
1807
1808 default:
1809 // Not recognized: just copy the '<' and return -1.
1810 result = (size_t)-1;
1811 if (buf != NULL)
1812 *buf = '<';
1813 break;
1814 }
1815
1816 return result;
1817}
1818
1819/*
1820 * Execute a user defined command.
1821 */
1822 void
1823do_ucmd(exarg_T *eap)
1824{
1825 char_u *buf;
1826 char_u *p;
1827 char_u *q;
1828
1829 char_u *start;
1830 char_u *end = NULL;
1831 char_u *ksp;
1832 size_t len, totlen;
1833
1834 size_t split_len = 0;
1835 char_u *split_buf = NULL;
1836 ucmd_T *cmd;
Bram Moolenaar205f29c2021-12-10 21:46:09 +00001837 sctx_T save_current_sctx;
1838 int restore_current_sctx = FALSE;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00001839#ifdef FEAT_EVAL
1840 int restore_script_version = 0;
1841#endif
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001842
1843 if (eap->cmdidx == CMD_USER)
1844 cmd = USER_CMD(eap->useridx);
1845 else
zeertzjqb444ee72023-02-20 15:25:13 +00001846 cmd = USER_CMD_GA(&prevwin_curwin()->w_buffer->b_ucmds, eap->useridx);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001847
1848 /*
1849 * Replace <> in the command by the arguments.
1850 * First round: "buf" is NULL, compute length, allocate "buf".
1851 * Second round: copy result into "buf".
1852 */
1853 buf = NULL;
1854 for (;;)
1855 {
1856 p = cmd->uc_rep; // source
1857 q = buf; // destination
1858 totlen = 0;
1859
1860 for (;;)
1861 {
1862 start = vim_strchr(p, '<');
1863 if (start != NULL)
1864 end = vim_strchr(start + 1, '>');
1865 if (buf != NULL)
1866 {
1867 for (ksp = p; *ksp != NUL && *ksp != K_SPECIAL; ++ksp)
1868 ;
1869 if (*ksp == K_SPECIAL
1870 && (start == NULL || ksp < start || end == NULL)
1871 && ((ksp[1] == KS_SPECIAL && ksp[2] == KE_FILLER)
1872# ifdef FEAT_GUI
1873 || (ksp[1] == KS_EXTRA && ksp[2] == (int)KE_CSI)
1874# endif
1875 ))
1876 {
1877 // K_SPECIAL has been put in the buffer as K_SPECIAL
1878 // KS_SPECIAL KE_FILLER, like for mappings, but
1879 // do_cmdline() doesn't handle that, so convert it back.
1880 // Also change K_SPECIAL KS_EXTRA KE_CSI into CSI.
1881 len = ksp - p;
1882 if (len > 0)
1883 {
1884 mch_memmove(q, p, len);
1885 q += len;
1886 }
1887 *q++ = ksp[1] == KS_SPECIAL ? K_SPECIAL : CSI;
1888 p = ksp + 3;
1889 continue;
1890 }
1891 }
1892
1893 // break if no <item> is found
1894 if (start == NULL || end == NULL)
1895 break;
1896
1897 // Include the '>'
1898 ++end;
1899
1900 // Take everything up to the '<'
1901 len = start - p;
1902 if (buf == NULL)
1903 totlen += len;
1904 else
1905 {
1906 mch_memmove(q, p, len);
1907 q += len;
1908 }
1909
1910 len = uc_check_code(start, end - start, q, cmd, eap,
1911 &split_buf, &split_len);
1912 if (len == (size_t)-1)
1913 {
1914 // no match, continue after '<'
1915 p = start + 1;
1916 len = 1;
1917 }
1918 else
1919 p = end;
1920 if (buf == NULL)
1921 totlen += len;
1922 else
1923 q += len;
1924 }
1925 if (buf != NULL) // second time here, finished
1926 {
1927 STRCPY(q, p);
1928 break;
1929 }
1930
1931 totlen += STRLEN(p); // Add on the trailing characters
Bram Moolenaar964b3742019-05-24 18:54:09 +02001932 buf = alloc(totlen + 1);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001933 if (buf == NULL)
1934 {
1935 vim_free(split_buf);
1936 return;
1937 }
1938 }
1939
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001940 if ((cmd->uc_argt & EX_KEEPSCRIPT) == 0)
1941 {
Bram Moolenaar205f29c2021-12-10 21:46:09 +00001942 restore_current_sctx = TRUE;
1943 save_current_sctx = current_sctx;
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001944 current_sctx.sc_version = cmd->uc_script_ctx.sc_version;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001945#ifdef FEAT_EVAL
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001946 current_sctx.sc_sid = cmd->uc_script_ctx.sc_sid;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00001947 if (cmd->uc_flags & UC_VIM9)
1948 {
1949 // In a {} block variables use Vim9 script rules, even in a legacy
1950 // script.
1951 restore_script_version =
1952 SCRIPT_ITEM(current_sctx.sc_sid)->sn_version;
1953 SCRIPT_ITEM(current_sctx.sc_sid)->sn_version = SCRIPT_VERSION_VIM9;
1954 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001955#endif
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001956 }
Bram Moolenaar205f29c2021-12-10 21:46:09 +00001957
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001958 (void)do_cmdline(buf, eap->getline, eap->cookie,
1959 DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED);
Bram Moolenaar205f29c2021-12-10 21:46:09 +00001960
1961 // Careful: Do not use "cmd" here, it may have become invalid if a user
1962 // command was added.
1963 if (restore_current_sctx)
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00001964 {
1965#ifdef FEAT_EVAL
1966 if (restore_script_version != 0)
1967 SCRIPT_ITEM(current_sctx.sc_sid)->sn_version =
1968 restore_script_version;
1969#endif
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001970 current_sctx = save_current_sctx;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00001971 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001972 vim_free(buf);
1973 vim_free(split_buf);
1974}