blob: 3f0781c4fcfb253f98f85b208e08b2f790c793bf [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"},
Doug Kearns81642d92024-01-04 22:37:44 +010076#if defined(FEAT_KEYMAP)
77 {EXPAND_KEYMAP, "keymap"},
78#endif
Bram Moolenaarac9fb182019-04-27 13:04:13 +020079#if defined(HAVE_LOCALE_H) || defined(X_LOCALE)
80 {EXPAND_LOCALES, "locale"},
81#endif
82 {EXPAND_MAPCLEAR, "mapclear"},
83 {EXPAND_MAPPINGS, "mapping"},
84 {EXPAND_MENUS, "menu"},
85 {EXPAND_MESSAGES, "messages"},
86 {EXPAND_OWNSYNTAX, "syntax"},
87#if defined(FEAT_PROFILE)
88 {EXPAND_SYNTIME, "syntime"},
89#endif
90 {EXPAND_SETTINGS, "option"},
91 {EXPAND_PACKADD, "packadd"},
roota6759382023-01-21 21:56:06 +000092 {EXPAND_RUNTIME, "runtime"},
Bram Moolenaarac9fb182019-04-27 13:04:13 +020093 {EXPAND_SHELLCMD, "shellcmd"},
94#if defined(FEAT_SIGNS)
95 {EXPAND_SIGN, "sign"},
96#endif
97 {EXPAND_TAGS, "tag"},
98 {EXPAND_TAGS_LISTFILES, "tag_listfiles"},
99 {EXPAND_USER, "user"},
100 {EXPAND_USER_VARS, "var"},
Bram Moolenaar6e2e2cc2022-03-14 19:24:46 +0000101#if defined(FEAT_EVAL)
102 {EXPAND_BREAKPOINT, "breakpoint"},
Yegappan Lakshmanan454ce672022-03-24 11:22:13 +0000103 {EXPAND_SCRIPTNAMES, "scriptnames"},
Bram Moolenaar6e2e2cc2022-03-14 19:24:46 +0000104#endif
LemonBoya20bf692024-07-11 22:35:53 +0200105 {EXPAND_DIRS_IN_CDPATH, "dir_in_path"},
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200106 {0, NULL}
107};
108
109/*
110 * List of names of address types. Must be alphabetical for completion.
111 */
112static struct
113{
Bram Moolenaarb7316892019-05-01 18:08:42 +0200114 cmd_addr_T expand;
115 char *name;
116 char *shortname;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200117} addr_type_complete[] =
118{
119 {ADDR_ARGUMENTS, "arguments", "arg"},
120 {ADDR_LINES, "lines", "line"},
121 {ADDR_LOADED_BUFFERS, "loaded_buffers", "load"},
122 {ADDR_TABS, "tabs", "tab"},
123 {ADDR_BUFFERS, "buffers", "buf"},
124 {ADDR_WINDOWS, "windows", "win"},
125 {ADDR_QUICKFIX, "quickfix", "qf"},
126 {ADDR_OTHER, "other", "?"},
Bram Moolenaarb7316892019-05-01 18:08:42 +0200127 {ADDR_NONE, NULL, NULL}
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200128};
129
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200130/*
131 * Search for a user command that matches "eap->cmd".
132 * Return cmdidx in "eap->cmdidx", flags in "eap->argt", idx in "eap->useridx".
133 * Return a pointer to just after the command.
134 * Return NULL if there is no matching command.
135 */
136 char_u *
137find_ucmd(
138 exarg_T *eap,
Bram Moolenaar0023f822022-01-16 21:54:19 +0000139 char_u *p, // end of the command (possibly including count)
140 int *full, // set to TRUE for a full match
141 expand_T *xp, // used for completion, NULL otherwise
142 int *complp) // completion flags or NULL
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200143{
144 int len = (int)(p - eap->cmd);
145 int j, k, matchlen = 0;
146 ucmd_T *uc;
147 int found = FALSE;
148 int possible = FALSE;
149 char_u *cp, *np; // Point into typed cmd and test name
150 garray_T *gap;
151 int amb_local = FALSE; // Found ambiguous buffer-local command,
152 // only full match global is accepted.
153
154 /*
155 * Look for buffer-local user commands first, then global ones.
156 */
Bram Moolenaar0f6e28f2022-02-20 20:49:35 +0000157 gap = &prevwin_curwin()->w_buffer->b_ucmds;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200158 for (;;)
159 {
160 for (j = 0; j < gap->ga_len; ++j)
161 {
162 uc = USER_CMD_GA(gap, j);
163 cp = eap->cmd;
164 np = uc->uc_name;
165 k = 0;
166 while (k < len && *np != NUL && *cp++ == *np++)
167 k++;
168 if (k == len || (*np == NUL && vim_isdigit(eap->cmd[k])))
169 {
170 // If finding a second match, the command is ambiguous. But
171 // not if a buffer-local command wasn't a full match and a
172 // global command is a full match.
173 if (k == len && found && *np != NUL)
174 {
175 if (gap == &ucmds)
176 return NULL;
177 amb_local = TRUE;
178 }
179
180 if (!found || (k == len && *np == NUL))
181 {
182 // If we matched up to a digit, then there could
183 // be another command including the digit that we
184 // should use instead.
185 if (k == len)
186 found = TRUE;
187 else
188 possible = TRUE;
189
190 if (gap == &ucmds)
191 eap->cmdidx = CMD_USER;
192 else
193 eap->cmdidx = CMD_USER_BUF;
194 eap->argt = (long)uc->uc_argt;
195 eap->useridx = j;
196 eap->addr_type = uc->uc_addr_type;
197
Bram Moolenaar52111f82019-04-29 21:30:45 +0200198 if (complp != NULL)
199 *complp = uc->uc_compl;
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200200# ifdef FEAT_EVAL
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200201 if (xp != NULL)
202 {
203 xp->xp_arg = uc->uc_compl_arg;
204 xp->xp_script_ctx = uc->uc_script_ctx;
Bram Moolenaar1a47ae32019-12-29 23:04:25 +0100205 xp->xp_script_ctx.sc_lnum += SOURCING_LNUM;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200206 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200207# endif
208 // Do not search for further abbreviations
209 // if this is an exact match.
210 matchlen = k;
211 if (k == len && *np == NUL)
212 {
213 if (full != NULL)
214 *full = TRUE;
215 amb_local = FALSE;
216 break;
217 }
218 }
219 }
220 }
221
222 // Stop if we found a full match or searched all.
223 if (j < gap->ga_len || gap == &ucmds)
224 break;
225 gap = &ucmds;
226 }
227
228 // Only found ambiguous matches.
229 if (amb_local)
230 {
231 if (xp != NULL)
232 xp->xp_context = EXPAND_UNSUCCESSFUL;
233 return NULL;
234 }
235
236 // The match we found may be followed immediately by a number. Move "p"
237 // back to point to it.
238 if (found || possible)
239 return p + (matchlen - len);
240 return p;
241}
242
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000243/*
244 * Set completion context for :command
245 */
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200246 char_u *
247set_context_in_user_cmd(expand_T *xp, char_u *arg_in)
248{
249 char_u *arg = arg_in;
250 char_u *p;
251
252 // Check for attributes
253 while (*arg == '-')
254 {
255 arg++; // Skip "-"
256 p = skiptowhite(arg);
257 if (*p == NUL)
258 {
259 // Cursor is still in the attribute
260 p = vim_strchr(arg, '=');
261 if (p == NULL)
262 {
263 // No "=", so complete attribute names
264 xp->xp_context = EXPAND_USER_CMD_FLAGS;
265 xp->xp_pattern = arg;
266 return NULL;
267 }
268
269 // For the -complete, -nargs and -addr attributes, we complete
270 // their arguments as well.
271 if (STRNICMP(arg, "complete", p - arg) == 0)
272 {
273 xp->xp_context = EXPAND_USER_COMPLETE;
274 xp->xp_pattern = p + 1;
275 return NULL;
276 }
277 else if (STRNICMP(arg, "nargs", p - arg) == 0)
278 {
279 xp->xp_context = EXPAND_USER_NARGS;
280 xp->xp_pattern = p + 1;
281 return NULL;
282 }
283 else if (STRNICMP(arg, "addr", p - arg) == 0)
284 {
285 xp->xp_context = EXPAND_USER_ADDR_TYPE;
286 xp->xp_pattern = p + 1;
287 return NULL;
288 }
289 return NULL;
290 }
291 arg = skipwhite(p);
292 }
293
294 // After the attributes comes the new command name
295 p = skiptowhite(arg);
296 if (*p == NUL)
297 {
298 xp->xp_context = EXPAND_USER_COMMANDS;
299 xp->xp_pattern = arg;
300 return NULL;
301 }
302
303 // And finally comes a normal command
304 return skipwhite(p);
305}
306
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000307/*
308 * Set the completion context for the argument of a user defined command.
309 */
310 char_u *
311set_context_in_user_cmdarg(
312 char_u *cmd UNUSED,
313 char_u *arg,
314 long argt,
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000315 int context,
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000316 expand_T *xp,
317 int forceit)
318{
319 char_u *p;
320
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000321 if (context == EXPAND_NOTHING)
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000322 return NULL;
323
324 if (argt & EX_XFILE)
325 {
326 // EX_XFILE: file names are handled before this call
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000327 xp->xp_context = context;
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000328 return NULL;
329 }
330
331#ifdef FEAT_MENU
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000332 if (context == EXPAND_MENUS)
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000333 return set_context_in_menu_cmd(xp, cmd, arg, forceit);
334#endif
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000335 if (context == EXPAND_COMMANDS)
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000336 return arg;
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000337 if (context == EXPAND_MAPPINGS)
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000338 return set_context_in_map_cmd(xp, (char_u *)"map", arg, forceit, FALSE,
339 FALSE, CMD_map);
340 // Find start of last argument.
341 p = arg;
342 while (*p)
343 {
344 if (*p == ' ')
345 // argument starts after a space
346 arg = p + 1;
347 else if (*p == '\\' && *(p + 1) != NUL)
348 ++p; // skip over escaped character
349 MB_PTR_ADV(p);
350 }
351 xp->xp_pattern = arg;
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000352 xp->xp_context = context;
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000353
354 return NULL;
355}
356
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200357 char_u *
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200358expand_user_command_name(int idx)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200359{
360 return get_user_commands(NULL, idx - (int)CMD_SIZE);
361}
362
363/*
364 * Function given to ExpandGeneric() to obtain the list of user command names.
365 */
366 char_u *
367get_user_commands(expand_T *xp UNUSED, int idx)
368{
Bram Moolenaarf03e3282019-07-22 21:55:18 +0200369 // In cmdwin, the alternative buffer should be used.
Bram Moolenaar0f6e28f2022-02-20 20:49:35 +0000370 buf_T *buf = prevwin_curwin()->w_buffer;
Bram Moolenaarf03e3282019-07-22 21:55:18 +0200371
372 if (idx < buf->b_ucmds.ga_len)
373 return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name;
Bram Moolenaarc2842ad2022-07-26 17:23:47 +0100374
Bram Moolenaarf03e3282019-07-22 21:55:18 +0200375 idx -= buf->b_ucmds.ga_len;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200376 if (idx < ucmds.ga_len)
Bram Moolenaarc2842ad2022-07-26 17:23:47 +0100377 {
378 int i;
379 char_u *name = USER_CMD(idx)->uc_name;
380
381 for (i = 0; i < buf->b_ucmds.ga_len; ++i)
382 if (STRCMP(name, USER_CMD_GA(&buf->b_ucmds, i)->uc_name) == 0)
383 // global command is overruled by buffer-local one
384 return (char_u *)"";
385 return name;
386 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200387 return NULL;
388}
389
Dominique Pelle748b3082022-01-08 12:41:16 +0000390#ifdef FEAT_EVAL
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200391/*
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200392 * Get the name of user command "idx". "cmdidx" can be CMD_USER or
393 * CMD_USER_BUF.
394 * Returns NULL if the command is not found.
395 */
396 char_u *
397get_user_command_name(int idx, int cmdidx)
398{
399 if (cmdidx == CMD_USER && idx < ucmds.ga_len)
400 return USER_CMD(idx)->uc_name;
401 if (cmdidx == CMD_USER_BUF)
402 {
403 // In cmdwin, the alternative buffer should be used.
Bram Moolenaar0f6e28f2022-02-20 20:49:35 +0000404 buf_T *buf = prevwin_curwin()->w_buffer;
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200405
406 if (idx < buf->b_ucmds.ga_len)
407 return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name;
408 }
409 return NULL;
410}
Dominique Pelle748b3082022-01-08 12:41:16 +0000411#endif
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200412
413/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200414 * Function given to ExpandGeneric() to obtain the list of user address type
415 * names.
416 */
417 char_u *
418get_user_cmd_addr_type(expand_T *xp UNUSED, int idx)
419{
420 return (char_u *)addr_type_complete[idx].name;
421}
422
423/*
424 * Function given to ExpandGeneric() to obtain the list of user command
425 * attributes.
426 */
427 char_u *
428get_user_cmd_flags(expand_T *xp UNUSED, int idx)
429{
430 static char *user_cmd_flags[] = {
431 "addr", "bang", "bar", "buffer", "complete",
Bram Moolenaar58ef8a32021-11-12 11:25:11 +0000432 "count", "nargs", "range", "register", "keepscript"
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200433 };
434
K.Takataeeec2542021-06-02 13:28:16 +0200435 if (idx >= (int)ARRAY_LENGTH(user_cmd_flags))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200436 return NULL;
437 return (char_u *)user_cmd_flags[idx];
438}
439
440/*
441 * Function given to ExpandGeneric() to obtain the list of values for -nargs.
442 */
443 char_u *
444get_user_cmd_nargs(expand_T *xp UNUSED, int idx)
445{
446 static char *user_cmd_nargs[] = {"0", "1", "*", "?", "+"};
447
K.Takataeeec2542021-06-02 13:28:16 +0200448 if (idx >= (int)ARRAY_LENGTH(user_cmd_nargs))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200449 return NULL;
450 return (char_u *)user_cmd_nargs[idx];
451}
452
453/*
454 * Function given to ExpandGeneric() to obtain the list of values for
455 * -complete.
456 */
457 char_u *
458get_user_cmd_complete(expand_T *xp UNUSED, int idx)
459{
460 return (char_u *)command_complete[idx].name;
461}
462
Dominique Pelle748b3082022-01-08 12:41:16 +0000463#ifdef FEAT_EVAL
Shougo Matsushita79d599b2022-05-07 12:48:29 +0100464/*
465 * Get the name of completion type "expand" as a string.
466 */
467 char_u *
468cmdcomplete_type_to_str(int expand)
469{
470 int i;
471
472 for (i = 0; command_complete[i].expand != 0; i++)
473 if (command_complete[i].expand == expand)
474 return (char_u *)command_complete[i].name;
475
476 return NULL;
477}
478
479/*
480 * Get the index of completion type "complete_str".
481 * Returns EXPAND_NOTHING if no match found.
482 */
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200483 int
484cmdcomplete_str_to_type(char_u *complete_str)
485{
486 int i;
487
Shougo Matsushita92997dd2023-08-20 20:55:55 +0200488 if (STRNCMP(complete_str, "custom,", 7) == 0)
489 return EXPAND_USER_DEFINED;
490 if (STRNCMP(complete_str, "customlist,", 11) == 0)
491 return EXPAND_USER_LIST;
492
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200493 for (i = 0; command_complete[i].expand != 0; ++i)
494 if (STRCMP(complete_str, command_complete[i].name) == 0)
495 return command_complete[i].expand;
496
497 return EXPAND_NOTHING;
498}
Dominique Pelle748b3082022-01-08 12:41:16 +0000499#endif
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200500
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200501/*
502 * List user commands starting with "name[name_len]".
503 */
504 static void
505uc_list(char_u *name, size_t name_len)
506{
507 int i, j;
508 int found = FALSE;
509 ucmd_T *cmd;
510 int len;
511 int over;
512 long a;
513 garray_T *gap;
514
Bram Moolenaarcf2594f2022-11-13 23:30:06 +0000515 // don't allow for adding or removing user commands here
516 ++ucmd_locked;
517
Bram Moolenaare38eab22019-12-05 21:50:01 +0100518 // In cmdwin, the alternative buffer should be used.
Bram Moolenaar0f6e28f2022-02-20 20:49:35 +0000519 gap = &prevwin_curwin()->w_buffer->b_ucmds;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200520 for (;;)
521 {
522 for (i = 0; i < gap->ga_len; ++i)
523 {
524 cmd = USER_CMD_GA(gap, i);
525 a = (long)cmd->uc_argt;
526
527 // Skip commands which don't match the requested prefix and
528 // commands filtered out.
529 if (STRNCMP(name, cmd->uc_name, name_len) != 0
530 || message_filtered(cmd->uc_name))
531 continue;
532
533 // Put out the title first time
534 if (!found)
535 msg_puts_title(_("\n Name Args Address Complete Definition"));
536 found = TRUE;
537 msg_putchar('\n');
538 if (got_int)
539 break;
540
541 // Special cases
542 len = 4;
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200543 if (a & EX_BANG)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200544 {
545 msg_putchar('!');
546 --len;
547 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200548 if (a & EX_REGSTR)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200549 {
550 msg_putchar('"');
551 --len;
552 }
553 if (gap != &ucmds)
554 {
555 msg_putchar('b');
556 --len;
557 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200558 if (a & EX_TRLBAR)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200559 {
560 msg_putchar('|');
561 --len;
562 }
563 while (len-- > 0)
564 msg_putchar(' ');
565
566 msg_outtrans_attr(cmd->uc_name, HL_ATTR(HLF_D));
567 len = (int)STRLEN(cmd->uc_name) + 4;
568
569 do {
570 msg_putchar(' ');
571 ++len;
572 } while (len < 22);
573
574 // "over" is how much longer the name is than the column width for
575 // the name, we'll try to align what comes after.
576 over = len - 22;
577 len = 0;
578
579 // Arguments
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200580 switch ((int)(a & (EX_EXTRA|EX_NOSPC|EX_NEEDARG)))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200581 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200582 case 0: IObuff[len++] = '0'; break;
583 case (EX_EXTRA): IObuff[len++] = '*'; break;
584 case (EX_EXTRA|EX_NOSPC): IObuff[len++] = '?'; break;
585 case (EX_EXTRA|EX_NEEDARG): IObuff[len++] = '+'; break;
586 case (EX_EXTRA|EX_NOSPC|EX_NEEDARG): IObuff[len++] = '1'; break;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200587 }
588
589 do {
590 IObuff[len++] = ' ';
591 } while (len < 5 - over);
592
593 // Address / Range
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200594 if (a & (EX_RANGE|EX_COUNT))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200595 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200596 if (a & EX_COUNT)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200597 {
598 // -count=N
599 sprintf((char *)IObuff + len, "%ldc", cmd->uc_def);
600 len += (int)STRLEN(IObuff + len);
601 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200602 else if (a & EX_DFLALL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200603 IObuff[len++] = '%';
604 else if (cmd->uc_def >= 0)
605 {
606 // -range=N
607 sprintf((char *)IObuff + len, "%ld", cmd->uc_def);
608 len += (int)STRLEN(IObuff + len);
609 }
610 else
611 IObuff[len++] = '.';
612 }
613
614 do {
615 IObuff[len++] = ' ';
616 } while (len < 8 - over);
617
618 // Address Type
Bram Moolenaarb7316892019-05-01 18:08:42 +0200619 for (j = 0; addr_type_complete[j].expand != ADDR_NONE; ++j)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200620 if (addr_type_complete[j].expand != ADDR_LINES
621 && addr_type_complete[j].expand == cmd->uc_addr_type)
622 {
623 STRCPY(IObuff + len, addr_type_complete[j].shortname);
624 len += (int)STRLEN(IObuff + len);
625 break;
626 }
627
628 do {
629 IObuff[len++] = ' ';
630 } while (len < 13 - over);
631
632 // Completion
633 for (j = 0; command_complete[j].expand != 0; ++j)
634 if (command_complete[j].expand == cmd->uc_compl)
635 {
636 STRCPY(IObuff + len, command_complete[j].name);
637 len += (int)STRLEN(IObuff + len);
Bram Moolenaar78f60322022-01-17 22:16:33 +0000638#ifdef FEAT_EVAL
Bram Moolenaar3f3597b2022-01-17 19:06:56 +0000639 if (p_verbose > 0 && cmd->uc_compl_arg != NULL
640 && STRLEN(cmd->uc_compl_arg) < 200)
641 {
642 IObuff[len] = ',';
643 STRCPY(IObuff + len + 1, cmd->uc_compl_arg);
644 len += (int)STRLEN(IObuff + len);
645 }
Bram Moolenaar78f60322022-01-17 22:16:33 +0000646#endif
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200647 break;
648 }
649
650 do {
651 IObuff[len++] = ' ';
652 } while (len < 25 - over);
653
654 IObuff[len] = '\0';
655 msg_outtrans(IObuff);
656
657 msg_outtrans_special(cmd->uc_rep, FALSE,
658 name_len == 0 ? Columns - 47 : 0);
659#ifdef FEAT_EVAL
660 if (p_verbose > 0)
661 last_set_msg(cmd->uc_script_ctx);
662#endif
663 out_flush();
664 ui_breakcheck();
665 if (got_int)
666 break;
667 }
668 if (gap == &ucmds || i < gap->ga_len)
669 break;
670 gap = &ucmds;
671 }
672
673 if (!found)
674 msg(_("No user-defined commands found"));
Bram Moolenaarcf2594f2022-11-13 23:30:06 +0000675
676 --ucmd_locked;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200677}
678
679 char *
680uc_fun_cmd(void)
681{
682 static char_u fcmd[] = {0x84, 0xaf, 0x60, 0xb9, 0xaf, 0xb5, 0x60, 0xa4,
683 0xa5, 0xad, 0xa1, 0xae, 0xa4, 0x60, 0xa1, 0x60,
684 0xb3, 0xa8, 0xb2, 0xb5, 0xa2, 0xa2, 0xa5, 0xb2,
685 0xb9, 0x7f, 0};
686 int i;
687
688 for (i = 0; fcmd[i]; ++i)
689 IObuff[i] = fcmd[i] - 0x40;
690 IObuff[i] = 0;
691 return (char *)IObuff;
692}
693
694/*
695 * Parse address type argument
696 */
697 static int
698parse_addr_type_arg(
699 char_u *value,
700 int vallen,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200701 cmd_addr_T *addr_type_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200702{
703 int i, a, b;
704
Bram Moolenaarb7316892019-05-01 18:08:42 +0200705 for (i = 0; addr_type_complete[i].expand != ADDR_NONE; ++i)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200706 {
707 a = (int)STRLEN(addr_type_complete[i].name) == vallen;
708 b = STRNCMP(value, addr_type_complete[i].name, vallen) == 0;
709 if (a && b)
710 {
711 *addr_type_arg = addr_type_complete[i].expand;
712 break;
713 }
714 }
715
Bram Moolenaarb7316892019-05-01 18:08:42 +0200716 if (addr_type_complete[i].expand == ADDR_NONE)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200717 {
718 char_u *err = value;
719
720 for (i = 0; err[i] != NUL && !VIM_ISWHITE(err[i]); i++)
721 ;
722 err[i] = NUL;
Bram Moolenaar11de43d2022-01-06 21:41:11 +0000723 semsg(_(e_invalid_address_type_value_str), err);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200724 return FAIL;
725 }
726
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200727 return OK;
728}
729
730/*
731 * Parse a completion argument "value[vallen]".
732 * The detected completion goes in "*complp", argument type in "*argt".
733 * When there is an argument, for function and user defined completion, it's
734 * copied to allocated memory and stored in "*compl_arg".
735 * Returns FAIL if something is wrong.
736 */
737 int
738parse_compl_arg(
739 char_u *value,
740 int vallen,
741 int *complp,
742 long *argt,
743 char_u **compl_arg UNUSED)
744{
745 char_u *arg = NULL;
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200746# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200747 size_t arglen = 0;
748# endif
749 int i;
750 int valend = vallen;
751
752 // Look for any argument part - which is the part after any ','
753 for (i = 0; i < vallen; ++i)
754 {
755 if (value[i] == ',')
756 {
757 arg = &value[i + 1];
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200758# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200759 arglen = vallen - i - 1;
760# endif
761 valend = i;
762 break;
763 }
764 }
765
766 for (i = 0; command_complete[i].expand != 0; ++i)
767 {
768 if ((int)STRLEN(command_complete[i].name) == valend
769 && STRNCMP(value, command_complete[i].name, valend) == 0)
770 {
771 *complp = command_complete[i].expand;
772 if (command_complete[i].expand == EXPAND_BUFFERS)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200773 *argt |= EX_BUFNAME;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200774 else if (command_complete[i].expand == EXPAND_DIRECTORIES
775 || command_complete[i].expand == EXPAND_FILES)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200776 *argt |= EX_XFILE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200777 break;
778 }
779 }
780
781 if (command_complete[i].expand == 0)
782 {
Bram Moolenaar1a992222021-12-31 17:25:48 +0000783 semsg(_(e_invalid_complete_value_str), value);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200784 return FAIL;
785 }
786
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200787# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200788 if (*complp != EXPAND_USER_DEFINED && *complp != EXPAND_USER_LIST
789 && arg != NULL)
790# else
791 if (arg != NULL)
792# endif
793 {
Bram Moolenaarb09feaa2022-01-02 20:20:45 +0000794 emsg(_(e_completion_argument_only_allowed_for_custom_completion));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200795 return FAIL;
796 }
797
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200798# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200799 if ((*complp == EXPAND_USER_DEFINED || *complp == EXPAND_USER_LIST)
800 && arg == NULL)
801 {
Bram Moolenaarb09feaa2022-01-02 20:20:45 +0000802 emsg(_(e_custom_completion_requires_function_argument));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200803 return FAIL;
804 }
805
806 if (arg != NULL)
Bram Moolenaar71ccd032020-06-12 22:59:11 +0200807 *compl_arg = vim_strnsave(arg, arglen);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200808# endif
809 return OK;
810}
811
812/*
813 * Scan attributes in the ":command" command.
814 * Return FAIL when something is wrong.
815 */
816 static int
817uc_scan_attr(
818 char_u *attr,
819 size_t len,
820 long *argt,
821 long *def,
822 int *flags,
Bram Moolenaar52111f82019-04-29 21:30:45 +0200823 int *complp,
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200824 char_u **compl_arg,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200825 cmd_addr_T *addr_type_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200826{
827 char_u *p;
828
829 if (len == 0)
830 {
Bram Moolenaar1a992222021-12-31 17:25:48 +0000831 emsg(_(e_no_attribute_specified));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200832 return FAIL;
833 }
834
835 // First, try the simple attributes (no arguments)
836 if (STRNICMP(attr, "bang", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200837 *argt |= EX_BANG;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200838 else if (STRNICMP(attr, "buffer", len) == 0)
839 *flags |= UC_BUFFER;
840 else if (STRNICMP(attr, "register", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200841 *argt |= EX_REGSTR;
Bram Moolenaar58ef8a32021-11-12 11:25:11 +0000842 else if (STRNICMP(attr, "keepscript", len) == 0)
843 *argt |= EX_KEEPSCRIPT;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200844 else if (STRNICMP(attr, "bar", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200845 *argt |= EX_TRLBAR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200846 else
847 {
848 int i;
849 char_u *val = NULL;
850 size_t vallen = 0;
851 size_t attrlen = len;
852
853 // Look for the attribute name - which is the part before any '='
854 for (i = 0; i < (int)len; ++i)
855 {
856 if (attr[i] == '=')
857 {
858 val = &attr[i + 1];
859 vallen = len - i - 1;
860 attrlen = i;
861 break;
862 }
863 }
864
865 if (STRNICMP(attr, "nargs", attrlen) == 0)
866 {
867 if (vallen == 1)
868 {
869 if (*val == '0')
870 // Do nothing - this is the default
871 ;
872 else if (*val == '1')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200873 *argt |= (EX_EXTRA | EX_NOSPC | EX_NEEDARG);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200874 else if (*val == '*')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200875 *argt |= EX_EXTRA;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200876 else if (*val == '?')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200877 *argt |= (EX_EXTRA | EX_NOSPC);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200878 else if (*val == '+')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200879 *argt |= (EX_EXTRA | EX_NEEDARG);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200880 else
881 goto wrong_nargs;
882 }
883 else
884 {
885wrong_nargs:
Bram Moolenaar1a992222021-12-31 17:25:48 +0000886 emsg(_(e_invalid_number_of_arguments));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200887 return FAIL;
888 }
889 }
890 else if (STRNICMP(attr, "range", attrlen) == 0)
891 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200892 *argt |= EX_RANGE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200893 if (vallen == 1 && *val == '%')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200894 *argt |= EX_DFLALL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200895 else if (val != NULL)
896 {
897 p = val;
898 if (*def >= 0)
899 {
900two_count:
Bram Moolenaar1a992222021-12-31 17:25:48 +0000901 emsg(_(e_count_cannot_be_specified_twice));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200902 return FAIL;
903 }
904
905 *def = getdigits(&p);
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200906 *argt |= EX_ZEROR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200907
908 if (p != val + vallen || vallen == 0)
909 {
910invalid_count:
Bram Moolenaar1a992222021-12-31 17:25:48 +0000911 emsg(_(e_invalid_default_value_for_count));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200912 return FAIL;
913 }
914 }
Bram Moolenaarb7316892019-05-01 18:08:42 +0200915 // default for -range is using buffer lines
916 if (*addr_type_arg == ADDR_NONE)
917 *addr_type_arg = ADDR_LINES;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200918 }
919 else if (STRNICMP(attr, "count", attrlen) == 0)
920 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200921 *argt |= (EX_COUNT | EX_ZEROR | EX_RANGE);
Bram Moolenaarb7316892019-05-01 18:08:42 +0200922 // default for -count is using any number
923 if (*addr_type_arg == ADDR_NONE)
924 *addr_type_arg = ADDR_OTHER;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200925
926 if (val != NULL)
927 {
928 p = val;
929 if (*def >= 0)
930 goto two_count;
931
932 *def = getdigits(&p);
933
934 if (p != val + vallen)
935 goto invalid_count;
936 }
937
938 if (*def < 0)
939 *def = 0;
940 }
941 else if (STRNICMP(attr, "complete", attrlen) == 0)
942 {
943 if (val == NULL)
944 {
Bram Moolenaar1a992222021-12-31 17:25:48 +0000945 semsg(_(e_argument_required_for_str), "-complete");
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200946 return FAIL;
947 }
948
Bram Moolenaar52111f82019-04-29 21:30:45 +0200949 if (parse_compl_arg(val, (int)vallen, complp, argt, compl_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200950 == FAIL)
951 return FAIL;
952 }
953 else if (STRNICMP(attr, "addr", attrlen) == 0)
954 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200955 *argt |= EX_RANGE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200956 if (val == NULL)
957 {
Bram Moolenaar1a992222021-12-31 17:25:48 +0000958 semsg(_(e_argument_required_for_str), "-addr");
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200959 return FAIL;
960 }
Bram Moolenaare4f5f3a2019-05-04 14:05:08 +0200961 if (parse_addr_type_arg(val, (int)vallen, addr_type_arg) == FAIL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200962 return FAIL;
Bram Moolenaare4f5f3a2019-05-04 14:05:08 +0200963 if (*addr_type_arg != ADDR_LINES)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200964 *argt |= EX_ZEROR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200965 }
966 else
967 {
968 char_u ch = attr[len];
969 attr[len] = '\0';
Bram Moolenaar1a992222021-12-31 17:25:48 +0000970 semsg(_(e_invalid_attribute_str), attr);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200971 attr[len] = ch;
972 return FAIL;
973 }
974 }
975
976 return OK;
977}
978
979/*
980 * Add a user command to the list or replace an existing one.
981 */
982 static int
983uc_add_command(
984 char_u *name,
985 size_t name_len,
986 char_u *rep,
987 long argt,
988 long def,
989 int flags,
990 int compl,
991 char_u *compl_arg UNUSED,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200992 cmd_addr_T addr_type,
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200993 int force)
994{
995 ucmd_T *cmd = NULL;
996 char_u *p;
997 int i;
998 int cmp = 1;
999 char_u *rep_buf = NULL;
1000 garray_T *gap;
1001
zeertzjq7e0bae02023-08-11 23:15:38 +02001002 replace_termcodes(rep, &rep_buf, 0, 0, NULL);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001003 if (rep_buf == NULL)
1004 {
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001005 // can't replace termcodes - try using the string as is
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001006 rep_buf = vim_strsave(rep);
1007
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001008 // give up if out of memory
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001009 if (rep_buf == NULL)
1010 return FAIL;
1011 }
1012
1013 // get address of growarray: global or in curbuf
1014 if (flags & UC_BUFFER)
1015 {
1016 gap = &curbuf->b_ucmds;
1017 if (gap->ga_itemsize == 0)
Bram Moolenaar04935fb2022-01-08 16:19:22 +00001018 ga_init2(gap, sizeof(ucmd_T), 4);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001019 }
1020 else
1021 gap = &ucmds;
1022
1023 // Search for the command in the already defined commands.
1024 for (i = 0; i < gap->ga_len; ++i)
1025 {
1026 size_t len;
1027
1028 cmd = USER_CMD_GA(gap, i);
1029 len = STRLEN(cmd->uc_name);
1030 cmp = STRNCMP(name, cmd->uc_name, name_len);
1031 if (cmp == 0)
1032 {
1033 if (name_len < len)
1034 cmp = -1;
1035 else if (name_len > len)
1036 cmp = 1;
1037 }
1038
1039 if (cmp == 0)
1040 {
1041 // Command can be replaced with "command!" and when sourcing the
1042 // same script again, but only once.
1043 if (!force
1044#ifdef FEAT_EVAL
1045 && (cmd->uc_script_ctx.sc_sid != current_sctx.sc_sid
1046 || cmd->uc_script_ctx.sc_seq == current_sctx.sc_seq)
1047#endif
1048 )
1049 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001050 semsg(_(e_command_already_exists_add_bang_to_replace_it_str),
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001051 name);
1052 goto fail;
1053 }
1054
1055 VIM_CLEAR(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001056#if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001057 VIM_CLEAR(cmd->uc_compl_arg);
1058#endif
1059 break;
1060 }
1061
1062 // Stop as soon as we pass the name to add
1063 if (cmp < 0)
1064 break;
1065 }
1066
1067 // Extend the array unless we're replacing an existing command
1068 if (cmp != 0)
1069 {
Yegappan Lakshmananfadc02a2023-01-27 21:03:12 +00001070 if (ga_grow(gap, 1) == FAIL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001071 goto fail;
Bram Moolenaar71ccd032020-06-12 22:59:11 +02001072 if ((p = vim_strnsave(name, name_len)) == NULL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001073 goto fail;
1074
1075 cmd = USER_CMD_GA(gap, i);
1076 mch_memmove(cmd + 1, cmd, (gap->ga_len - i) * sizeof(ucmd_T));
1077
1078 ++gap->ga_len;
1079
1080 cmd->uc_name = p;
1081 }
1082
1083 cmd->uc_rep = rep_buf;
1084 cmd->uc_argt = argt;
1085 cmd->uc_def = def;
1086 cmd->uc_compl = compl;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001087 cmd->uc_script_ctx = current_sctx;
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001088 if (flags & UC_VIM9)
1089 cmd->uc_script_ctx.sc_version = SCRIPT_VERSION_VIM9;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00001090 cmd->uc_flags = flags & UC_VIM9;
Bram Moolenaar9b8d6222020-12-28 18:26:00 +01001091#ifdef FEAT_EVAL
Bram Moolenaar1a47ae32019-12-29 23:04:25 +01001092 cmd->uc_script_ctx.sc_lnum += SOURCING_LNUM;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001093 cmd->uc_compl_arg = compl_arg;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001094#endif
1095 cmd->uc_addr_type = addr_type;
1096
1097 return OK;
1098
1099fail:
1100 vim_free(rep_buf);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001101#if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001102 vim_free(compl_arg);
1103#endif
1104 return FAIL;
1105}
1106
1107/*
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001108 * If "p" starts with "{" then read a block of commands until "}".
1109 * Used for ":command" and ":autocmd".
1110 */
1111 char_u *
1112may_get_cmd_block(exarg_T *eap, char_u *p, char_u **tofree, int *flags)
1113{
1114 char_u *retp = p;
1115
1116 if (*p == '{' && ends_excmd2(eap->arg, skipwhite(p + 1))
Zoltan Arpadffy6fdb6282023-12-19 20:53:07 +01001117 && eap->ea_getline != NULL)
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001118 {
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001119 garray_T ga;
1120 char_u *line = NULL;
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001121
1122 ga_init2(&ga, sizeof(char_u *), 10);
Bram Moolenaar9f1a39a2022-01-08 15:39:39 +00001123 if (ga_copy_string(&ga, p) == FAIL)
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001124 return retp;
1125
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001126 // If the argument ends in "}" it must have been concatenated already
1127 // for ISN_EXEC.
1128 if (p[STRLEN(p) - 1] != '}')
1129 // Read lines between '{' and '}'. Does not support nesting or
1130 // here-doc constructs.
1131 for (;;)
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001132 {
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001133 vim_free(line);
Zoltan Arpadffy6fdb6282023-12-19 20:53:07 +01001134 if ((line = eap->ea_getline(':', eap->cookie,
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001135 0, GETLINE_CONCAT_CONTBAR)) == NULL)
1136 {
1137 emsg(_(e_missing_rcurly));
1138 break;
1139 }
Bram Moolenaar9f1a39a2022-01-08 15:39:39 +00001140 if (ga_copy_string(&ga, line) == FAIL)
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001141 break;
1142 if (*skipwhite(line) == '}')
1143 break;
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001144 }
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001145 vim_free(line);
1146 retp = *tofree = ga_concat_strings(&ga, "\n");
1147 ga_clear_strings(&ga);
1148 *flags |= UC_VIM9;
1149 }
1150 return retp;
1151}
1152
1153/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001154 * ":command ..." implementation
1155 */
1156 void
1157ex_command(exarg_T *eap)
1158{
Bram Moolenaarb7316892019-05-01 18:08:42 +02001159 char_u *name;
1160 char_u *end;
1161 char_u *p;
1162 long argt = 0;
1163 long def = -1;
1164 int flags = 0;
1165 int compl = EXPAND_NOTHING;
1166 char_u *compl_arg = NULL;
1167 cmd_addr_T addr_type_arg = ADDR_NONE;
1168 int has_attr = (eap->arg[0] == '-');
1169 int name_len;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001170
1171 p = eap->arg;
1172
1173 // Check for attributes
1174 while (*p == '-')
1175 {
1176 ++p;
1177 end = skiptowhite(p);
1178 if (uc_scan_attr(p, end - p, &argt, &def, &flags, &compl,
1179 &compl_arg, &addr_type_arg) == FAIL)
zeertzjq33e54302022-12-19 16:49:27 +00001180 goto theend;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001181 p = skipwhite(end);
1182 }
1183
1184 // Get the name (if any) and skip to the following argument
1185 name = p;
1186 if (ASCII_ISALPHA(*p))
1187 while (ASCII_ISALNUM(*p))
1188 ++p;
Bram Moolenaara72cfb82020-04-23 17:07:30 +02001189 if (!ends_excmd2(eap->arg, p) && !VIM_ISWHITE(*p))
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001190 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001191 emsg(_(e_invalid_command_name));
zeertzjq33e54302022-12-19 16:49:27 +00001192 goto theend;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001193 }
1194 end = p;
1195 name_len = (int)(end - name);
1196
1197 // If there is nothing after the name, and no attributes were specified,
1198 // we are listing commands
1199 p = skipwhite(end);
Bram Moolenaara72cfb82020-04-23 17:07:30 +02001200 if (!has_attr && ends_excmd2(eap->arg, p))
zeertzjq33e54302022-12-19 16:49:27 +00001201 {
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001202 uc_list(name, end - name);
zeertzjq33e54302022-12-19 16:49:27 +00001203 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001204 else if (!ASCII_ISUPPER(*name))
zeertzjq33e54302022-12-19 16:49:27 +00001205 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001206 emsg(_(e_user_defined_commands_must_start_with_an_uppercase_letter));
zeertzjq33e54302022-12-19 16:49:27 +00001207 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001208 else if ((name_len == 1 && *name == 'X')
1209 || (name_len <= 4
1210 && STRNCMP(name, "Next", name_len > 4 ? 4 : name_len) == 0))
zeertzjq33e54302022-12-19 16:49:27 +00001211 {
Bram Moolenaar9d00e4a2022-01-05 17:49:15 +00001212 emsg(_(e_reserved_name_cannot_be_used_for_user_defined_command));
zeertzjq33e54302022-12-19 16:49:27 +00001213 }
Martin Tournoijde69a732021-07-11 14:28:25 +02001214 else if (compl > 0 && (argt & EX_EXTRA) == 0)
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001215 {
1216 // Some plugins rely on silently ignoring the mistake, only make this
1217 // an error in Vim9 script.
1218 if (in_vim9script())
Bram Moolenaar41a34852021-08-04 16:09:24 +02001219 emsg(_(e_complete_used_without_allowing_arguments));
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001220 else
1221 give_warning_with_source(
Bram Moolenaar41a34852021-08-04 16:09:24 +02001222 (char_u *)_(e_complete_used_without_allowing_arguments),
1223 TRUE, TRUE);
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001224 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001225 else
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001226 {
1227 char_u *tofree = NULL;
1228
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001229 p = may_get_cmd_block(eap, p, &tofree, &flags);
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001230
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001231 uc_add_command(name, end - name, p, argt, def, flags, compl, compl_arg,
1232 addr_type_arg, eap->forceit);
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001233 vim_free(tofree);
zeertzjq33e54302022-12-19 16:49:27 +00001234
1235 return; // success
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001236 }
zeertzjq33e54302022-12-19 16:49:27 +00001237
1238theend:
1239 vim_free(compl_arg);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001240}
1241
1242/*
1243 * ":comclear" implementation
1244 * Clear all user commands, global and for current buffer.
1245 */
1246 void
1247ex_comclear(exarg_T *eap UNUSED)
1248{
1249 uc_clear(&ucmds);
Bram Moolenaare5c83282019-05-03 23:15:37 +02001250 if (curbuf != NULL)
1251 uc_clear(&curbuf->b_ucmds);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001252}
1253
1254/*
Bram Moolenaarcf2594f2022-11-13 23:30:06 +00001255 * If ucmd_locked is set give an error and return TRUE.
1256 * Otherwise return FALSE.
1257 */
1258 static int
1259is_ucmd_locked(void)
1260{
1261 if (ucmd_locked > 0)
1262 {
1263 emsg(_(e_cannot_change_user_commands_while_listing));
1264 return TRUE;
1265 }
1266 return FALSE;
1267}
1268
1269/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001270 * Clear all user commands for "gap".
1271 */
1272 void
1273uc_clear(garray_T *gap)
1274{
1275 int i;
1276 ucmd_T *cmd;
1277
Bram Moolenaarcf2594f2022-11-13 23:30:06 +00001278 if (is_ucmd_locked())
1279 return;
1280
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001281 for (i = 0; i < gap->ga_len; ++i)
1282 {
1283 cmd = USER_CMD_GA(gap, i);
1284 vim_free(cmd->uc_name);
1285 vim_free(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001286# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001287 vim_free(cmd->uc_compl_arg);
1288# endif
1289 }
1290 ga_clear(gap);
1291}
1292
1293/*
1294 * ":delcommand" implementation
1295 */
1296 void
1297ex_delcommand(exarg_T *eap)
1298{
1299 int i = 0;
1300 ucmd_T *cmd = NULL;
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001301 int res = -1;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001302 garray_T *gap;
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001303 char_u *arg = eap->arg;
1304 int buffer_only = FALSE;
1305
1306 if (STRNCMP(arg, "-buffer", 7) == 0 && VIM_ISWHITE(arg[7]))
1307 {
1308 buffer_only = TRUE;
1309 arg = skipwhite(arg + 7);
1310 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001311
1312 gap = &curbuf->b_ucmds;
1313 for (;;)
1314 {
1315 for (i = 0; i < gap->ga_len; ++i)
1316 {
1317 cmd = USER_CMD_GA(gap, i);
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001318 res = STRCMP(arg, cmd->uc_name);
1319 if (res <= 0)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001320 break;
1321 }
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001322 if (gap == &ucmds || res == 0 || buffer_only)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001323 break;
1324 gap = &ucmds;
1325 }
1326
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001327 if (res != 0)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001328 {
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001329 semsg(_(buffer_only
1330 ? e_no_such_user_defined_command_in_current_buffer_str
1331 : e_no_such_user_defined_command_str), arg);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001332 return;
1333 }
1334
Bram Moolenaarcf2594f2022-11-13 23:30:06 +00001335 if (is_ucmd_locked())
1336 return;
1337
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001338 vim_free(cmd->uc_name);
1339 vim_free(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001340# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001341 vim_free(cmd->uc_compl_arg);
1342# endif
1343
1344 --gap->ga_len;
1345
1346 if (i < gap->ga_len)
1347 mch_memmove(cmd, cmd + 1, (gap->ga_len - i) * sizeof(ucmd_T));
1348}
1349
1350/*
1351 * Split and quote args for <f-args>.
1352 */
1353 static char_u *
1354uc_split_args(char_u *arg, size_t *lenp)
1355{
1356 char_u *buf;
1357 char_u *p;
1358 char_u *q;
1359 int len;
1360
1361 // Precalculate length
1362 p = arg;
1363 len = 2; // Initial and final quotes
1364
1365 while (*p)
1366 {
1367 if (p[0] == '\\' && p[1] == '\\')
1368 {
1369 len += 2;
1370 p += 2;
1371 }
1372 else if (p[0] == '\\' && VIM_ISWHITE(p[1]))
1373 {
1374 len += 1;
1375 p += 2;
1376 }
1377 else if (*p == '\\' || *p == '"')
1378 {
1379 len += 2;
1380 p += 1;
1381 }
1382 else if (VIM_ISWHITE(*p))
1383 {
1384 p = skipwhite(p);
1385 if (*p == NUL)
1386 break;
Bram Moolenaar20d89e02020-10-20 23:11:33 +02001387 len += 4; // ", "
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001388 }
1389 else
1390 {
1391 int charlen = (*mb_ptr2len)(p);
1392
1393 len += charlen;
1394 p += charlen;
1395 }
1396 }
1397
1398 buf = alloc(len + 1);
1399 if (buf == NULL)
1400 {
1401 *lenp = 0;
1402 return buf;
1403 }
1404
1405 p = arg;
1406 q = buf;
1407 *q++ = '"';
1408 while (*p)
1409 {
1410 if (p[0] == '\\' && p[1] == '\\')
1411 {
1412 *q++ = '\\';
1413 *q++ = '\\';
1414 p += 2;
1415 }
1416 else if (p[0] == '\\' && VIM_ISWHITE(p[1]))
1417 {
1418 *q++ = p[1];
1419 p += 2;
1420 }
1421 else if (*p == '\\' || *p == '"')
1422 {
1423 *q++ = '\\';
1424 *q++ = *p++;
1425 }
1426 else if (VIM_ISWHITE(*p))
1427 {
1428 p = skipwhite(p);
1429 if (*p == NUL)
1430 break;
1431 *q++ = '"';
1432 *q++ = ',';
Bram Moolenaar20d89e02020-10-20 23:11:33 +02001433 *q++ = ' ';
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001434 *q++ = '"';
1435 }
1436 else
1437 {
1438 MB_COPY_CHAR(p, q);
1439 }
1440 }
1441 *q++ = '"';
1442 *q = 0;
1443
1444 *lenp = len;
1445 return buf;
1446}
1447
1448 static size_t
1449add_cmd_modifier(char_u *buf, char *mod_str, int *multi_mods)
1450{
1451 size_t result;
1452
1453 result = STRLEN(mod_str);
1454 if (*multi_mods)
1455 result += 1;
1456 if (buf != NULL)
1457 {
1458 if (*multi_mods)
1459 STRCAT(buf, " ");
1460 STRCAT(buf, mod_str);
1461 }
1462
1463 *multi_mods = 1;
1464
1465 return result;
1466}
1467
1468/*
Bram Moolenaar02194d22020-10-24 23:08:38 +02001469 * Add modifiers from "cmod->cmod_split" to "buf". Set "multi_mods" when one
Bram Moolenaare1004402020-10-24 20:49:43 +02001470 * was added. Return the number of bytes added.
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001471 */
1472 size_t
dundargocc57b5bc2022-11-02 13:30:51 +00001473add_win_cmd_modifiers(char_u *buf, cmdmod_T *cmod, int *multi_mods)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001474{
1475 size_t result = 0;
1476
1477 // :aboveleft and :leftabove
Bram Moolenaar02194d22020-10-24 23:08:38 +02001478 if (cmod->cmod_split & WSP_ABOVE)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001479 result += add_cmd_modifier(buf, "aboveleft", multi_mods);
1480 // :belowright and :rightbelow
Bram Moolenaar02194d22020-10-24 23:08:38 +02001481 if (cmod->cmod_split & WSP_BELOW)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001482 result += add_cmd_modifier(buf, "belowright", multi_mods);
1483 // :botright
Bram Moolenaar02194d22020-10-24 23:08:38 +02001484 if (cmod->cmod_split & WSP_BOT)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001485 result += add_cmd_modifier(buf, "botright", multi_mods);
1486
1487 // :tab
Bram Moolenaar02194d22020-10-24 23:08:38 +02001488 if (cmod->cmod_tab > 0)
zeertzjq208567e2022-10-18 13:11:21 +01001489 {
1490 int tabnr = cmod->cmod_tab - 1;
1491
1492 if (tabnr == tabpage_index(curtab))
1493 {
1494 // For compatibility, don't add a tabpage number if it is the same
1495 // as the default number for :tab.
1496 result += add_cmd_modifier(buf, "tab", multi_mods);
1497 }
1498 else
1499 {
1500 char tab_buf[NUMBUFLEN + 3];
1501
1502 sprintf(tab_buf, "%dtab", tabnr);
1503 result += add_cmd_modifier(buf, tab_buf, multi_mods);
1504 }
1505 }
1506
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001507 // :topleft
Bram Moolenaar02194d22020-10-24 23:08:38 +02001508 if (cmod->cmod_split & WSP_TOP)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001509 result += add_cmd_modifier(buf, "topleft", multi_mods);
1510 // :vertical
Bram Moolenaar02194d22020-10-24 23:08:38 +02001511 if (cmod->cmod_split & WSP_VERT)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001512 result += add_cmd_modifier(buf, "vertical", multi_mods);
zeertzjqd3de1782022-09-01 12:58:52 +01001513 // :horizontal
1514 if (cmod->cmod_split & WSP_HOR)
1515 result += add_cmd_modifier(buf, "horizontal", multi_mods);
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001516 return result;
1517}
1518
1519/*
Bram Moolenaar02194d22020-10-24 23:08:38 +02001520 * Generate text for the "cmod" command modifiers.
1521 * If "buf" is NULL just return the length.
1522 */
Bram Moolenaara360dbe2020-10-26 18:46:53 +01001523 size_t
Bram Moolenaar02194d22020-10-24 23:08:38 +02001524produce_cmdmods(char_u *buf, cmdmod_T *cmod, int quote)
1525{
Bram Moolenaara360dbe2020-10-26 18:46:53 +01001526 size_t result = 0;
Bram Moolenaar02194d22020-10-24 23:08:38 +02001527 int multi_mods = 0;
1528 int i;
1529 typedef struct {
1530 int flag;
1531 char *name;
1532 } mod_entry_T;
1533 static mod_entry_T mod_entries[] = {
1534#ifdef FEAT_BROWSE_CMD
1535 {CMOD_BROWSE, "browse"},
1536#endif
1537#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
1538 {CMOD_CONFIRM, "confirm"},
1539#endif
1540 {CMOD_HIDE, "hide"},
1541 {CMOD_KEEPALT, "keepalt"},
1542 {CMOD_KEEPJUMPS, "keepjumps"},
1543 {CMOD_KEEPMARKS, "keepmarks"},
1544 {CMOD_KEEPPATTERNS, "keeppatterns"},
1545 {CMOD_LOCKMARKS, "lockmarks"},
1546 {CMOD_NOSWAPFILE, "noswapfile"},
1547 {CMOD_UNSILENT, "unsilent"},
1548 {CMOD_NOAUTOCMD, "noautocmd"},
1549#ifdef HAVE_SANDBOX
1550 {CMOD_SANDBOX, "sandbox"},
1551#endif
Bram Moolenaarb579f6e2021-12-04 11:57:00 +00001552 {CMOD_LEGACY, "legacy"},
Bram Moolenaar02194d22020-10-24 23:08:38 +02001553 {0, NULL}
1554 };
1555
1556 result = quote ? 2 : 0;
1557 if (buf != NULL)
1558 {
1559 if (quote)
1560 *buf++ = '"';
1561 *buf = '\0';
1562 }
1563
1564 // the modifiers that are simple flags
1565 for (i = 0; mod_entries[i].name != NULL; ++i)
1566 if (cmod->cmod_flags & mod_entries[i].flag)
1567 result += add_cmd_modifier(buf, mod_entries[i].name, &multi_mods);
1568
1569 // :silent
1570 if (cmod->cmod_flags & CMOD_SILENT)
1571 result += add_cmd_modifier(buf,
1572 (cmod->cmod_flags & CMOD_ERRSILENT) ? "silent!"
1573 : "silent", &multi_mods);
1574 // :verbose
zeertzjq9359e8a2022-07-03 13:16:09 +01001575 if (cmod->cmod_verbose > 0)
1576 {
1577 int verbose_value = cmod->cmod_verbose - 1;
1578
1579 if (verbose_value == 1)
1580 result += add_cmd_modifier(buf, "verbose", &multi_mods);
1581 else
1582 {
1583 char verbose_buf[NUMBUFLEN];
1584
1585 sprintf(verbose_buf, "%dverbose", verbose_value);
1586 result += add_cmd_modifier(buf, verbose_buf, &multi_mods);
1587 }
1588 }
Bram Moolenaar02194d22020-10-24 23:08:38 +02001589 // flags from cmod->cmod_split
dundargocc57b5bc2022-11-02 13:30:51 +00001590 result += add_win_cmd_modifiers(buf, cmod, &multi_mods);
zeertzjq9359e8a2022-07-03 13:16:09 +01001591
Bram Moolenaar02194d22020-10-24 23:08:38 +02001592 if (quote && buf != NULL)
1593 {
1594 buf += result - 2;
1595 *buf = '"';
1596 }
1597 return result;
1598}
1599
1600/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001601 * Check for a <> code in a user command.
1602 * "code" points to the '<'. "len" the length of the <> (inclusive).
1603 * "buf" is where the result is to be added.
1604 * "split_buf" points to a buffer used for splitting, caller should free it.
1605 * "split_len" is the length of what "split_buf" contains.
1606 * Returns the length of the replacement, which has been added to "buf".
1607 * Returns -1 if there was no match, and only the "<" has been copied.
1608 */
1609 static size_t
1610uc_check_code(
1611 char_u *code,
1612 size_t len,
1613 char_u *buf,
1614 ucmd_T *cmd, // the user command we're expanding
1615 exarg_T *eap, // ex arguments
1616 char_u **split_buf,
1617 size_t *split_len)
1618{
1619 size_t result = 0;
1620 char_u *p = code + 1;
1621 size_t l = len - 2;
1622 int quote = 0;
1623 enum {
1624 ct_ARGS,
1625 ct_BANG,
1626 ct_COUNT,
1627 ct_LINE1,
1628 ct_LINE2,
1629 ct_RANGE,
1630 ct_MODS,
1631 ct_REGISTER,
1632 ct_LT,
1633 ct_NONE
1634 } type = ct_NONE;
1635
1636 if ((vim_strchr((char_u *)"qQfF", *p) != NULL) && p[1] == '-')
1637 {
1638 quote = (*p == 'q' || *p == 'Q') ? 1 : 2;
1639 p += 2;
1640 l -= 2;
1641 }
1642
1643 ++l;
1644 if (l <= 1)
1645 type = ct_NONE;
1646 else if (STRNICMP(p, "args>", l) == 0)
1647 type = ct_ARGS;
1648 else if (STRNICMP(p, "bang>", l) == 0)
1649 type = ct_BANG;
1650 else if (STRNICMP(p, "count>", l) == 0)
1651 type = ct_COUNT;
1652 else if (STRNICMP(p, "line1>", l) == 0)
1653 type = ct_LINE1;
1654 else if (STRNICMP(p, "line2>", l) == 0)
1655 type = ct_LINE2;
1656 else if (STRNICMP(p, "range>", l) == 0)
1657 type = ct_RANGE;
1658 else if (STRNICMP(p, "lt>", l) == 0)
1659 type = ct_LT;
1660 else if (STRNICMP(p, "reg>", l) == 0 || STRNICMP(p, "register>", l) == 0)
1661 type = ct_REGISTER;
1662 else if (STRNICMP(p, "mods>", l) == 0)
1663 type = ct_MODS;
1664
1665 switch (type)
1666 {
1667 case ct_ARGS:
1668 // Simple case first
1669 if (*eap->arg == NUL)
1670 {
1671 if (quote == 1)
1672 {
1673 result = 2;
1674 if (buf != NULL)
1675 STRCPY(buf, "''");
1676 }
1677 else
1678 result = 0;
1679 break;
1680 }
1681
1682 // When specified there is a single argument don't split it.
1683 // Works for ":Cmd %" when % is "a b c".
Bram Moolenaar8071cb22019-07-12 17:58:01 +02001684 if ((eap->argt & EX_NOSPC) && quote == 2)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001685 quote = 1;
1686
1687 switch (quote)
1688 {
1689 case 0: // No quoting, no splitting
1690 result = STRLEN(eap->arg);
1691 if (buf != NULL)
1692 STRCPY(buf, eap->arg);
1693 break;
1694 case 1: // Quote, but don't split
1695 result = STRLEN(eap->arg) + 2;
1696 for (p = eap->arg; *p; ++p)
1697 {
1698 if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2)
1699 // DBCS can contain \ in a trail byte, skip the
1700 // double-byte character.
1701 ++p;
1702 else
1703 if (*p == '\\' || *p == '"')
1704 ++result;
1705 }
1706
1707 if (buf != NULL)
1708 {
1709 *buf++ = '"';
1710 for (p = eap->arg; *p; ++p)
1711 {
1712 if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2)
1713 // DBCS can contain \ in a trail byte, copy the
1714 // double-byte character to avoid escaping.
1715 *buf++ = *p++;
1716 else
1717 if (*p == '\\' || *p == '"')
1718 *buf++ = '\\';
1719 *buf++ = *p;
1720 }
1721 *buf = '"';
1722 }
1723
1724 break;
1725 case 2: // Quote and split (<f-args>)
1726 // This is hard, so only do it once, and cache the result
1727 if (*split_buf == NULL)
1728 *split_buf = uc_split_args(eap->arg, split_len);
1729
1730 result = *split_len;
1731 if (buf != NULL && result != 0)
1732 STRCPY(buf, *split_buf);
1733
1734 break;
1735 }
1736 break;
1737
1738 case ct_BANG:
1739 result = eap->forceit ? 1 : 0;
1740 if (quote)
1741 result += 2;
1742 if (buf != NULL)
1743 {
1744 if (quote)
1745 *buf++ = '"';
1746 if (eap->forceit)
1747 *buf++ = '!';
1748 if (quote)
1749 *buf = '"';
1750 }
1751 break;
1752
1753 case ct_LINE1:
1754 case ct_LINE2:
1755 case ct_RANGE:
1756 case ct_COUNT:
1757 {
1758 char num_buf[20];
1759 long num = (type == ct_LINE1) ? eap->line1 :
1760 (type == ct_LINE2) ? eap->line2 :
1761 (type == ct_RANGE) ? eap->addr_count :
1762 (eap->addr_count > 0) ? eap->line2 : cmd->uc_def;
1763 size_t num_len;
1764
1765 sprintf(num_buf, "%ld", num);
1766 num_len = STRLEN(num_buf);
1767 result = num_len;
1768
1769 if (quote)
1770 result += 2;
1771
1772 if (buf != NULL)
1773 {
1774 if (quote)
1775 *buf++ = '"';
1776 STRCPY(buf, num_buf);
1777 buf += num_len;
1778 if (quote)
1779 *buf = '"';
1780 }
1781
1782 break;
1783 }
1784
1785 case ct_MODS:
1786 {
Bram Moolenaar02194d22020-10-24 23:08:38 +02001787 result = produce_cmdmods(buf, &cmdmod, quote);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001788 break;
1789 }
1790
1791 case ct_REGISTER:
1792 result = eap->regname ? 1 : 0;
1793 if (quote)
1794 result += 2;
1795 if (buf != NULL)
1796 {
1797 if (quote)
1798 *buf++ = '\'';
1799 if (eap->regname)
1800 *buf++ = eap->regname;
1801 if (quote)
1802 *buf = '\'';
1803 }
1804 break;
1805
1806 case ct_LT:
1807 result = 1;
1808 if (buf != NULL)
1809 *buf = '<';
1810 break;
1811
1812 default:
1813 // Not recognized: just copy the '<' and return -1.
1814 result = (size_t)-1;
1815 if (buf != NULL)
1816 *buf = '<';
1817 break;
1818 }
1819
1820 return result;
1821}
1822
1823/*
1824 * Execute a user defined command.
1825 */
1826 void
1827do_ucmd(exarg_T *eap)
1828{
1829 char_u *buf;
1830 char_u *p;
1831 char_u *q;
1832
1833 char_u *start;
1834 char_u *end = NULL;
1835 char_u *ksp;
1836 size_t len, totlen;
1837
1838 size_t split_len = 0;
1839 char_u *split_buf = NULL;
1840 ucmd_T *cmd;
Bram Moolenaar205f29c2021-12-10 21:46:09 +00001841 sctx_T save_current_sctx;
1842 int restore_current_sctx = FALSE;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00001843#ifdef FEAT_EVAL
1844 int restore_script_version = 0;
1845#endif
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001846
1847 if (eap->cmdidx == CMD_USER)
1848 cmd = USER_CMD(eap->useridx);
1849 else
zeertzjqb444ee72023-02-20 15:25:13 +00001850 cmd = USER_CMD_GA(&prevwin_curwin()->w_buffer->b_ucmds, eap->useridx);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001851
1852 /*
1853 * Replace <> in the command by the arguments.
1854 * First round: "buf" is NULL, compute length, allocate "buf".
1855 * Second round: copy result into "buf".
1856 */
1857 buf = NULL;
1858 for (;;)
1859 {
1860 p = cmd->uc_rep; // source
1861 q = buf; // destination
1862 totlen = 0;
1863
1864 for (;;)
1865 {
1866 start = vim_strchr(p, '<');
1867 if (start != NULL)
1868 end = vim_strchr(start + 1, '>');
1869 if (buf != NULL)
1870 {
1871 for (ksp = p; *ksp != NUL && *ksp != K_SPECIAL; ++ksp)
1872 ;
1873 if (*ksp == K_SPECIAL
1874 && (start == NULL || ksp < start || end == NULL)
1875 && ((ksp[1] == KS_SPECIAL && ksp[2] == KE_FILLER)
1876# ifdef FEAT_GUI
1877 || (ksp[1] == KS_EXTRA && ksp[2] == (int)KE_CSI)
1878# endif
1879 ))
1880 {
1881 // K_SPECIAL has been put in the buffer as K_SPECIAL
1882 // KS_SPECIAL KE_FILLER, like for mappings, but
1883 // do_cmdline() doesn't handle that, so convert it back.
1884 // Also change K_SPECIAL KS_EXTRA KE_CSI into CSI.
1885 len = ksp - p;
1886 if (len > 0)
1887 {
1888 mch_memmove(q, p, len);
1889 q += len;
1890 }
1891 *q++ = ksp[1] == KS_SPECIAL ? K_SPECIAL : CSI;
1892 p = ksp + 3;
1893 continue;
1894 }
1895 }
1896
1897 // break if no <item> is found
1898 if (start == NULL || end == NULL)
1899 break;
1900
1901 // Include the '>'
1902 ++end;
1903
1904 // Take everything up to the '<'
1905 len = start - p;
1906 if (buf == NULL)
1907 totlen += len;
1908 else
1909 {
1910 mch_memmove(q, p, len);
1911 q += len;
1912 }
1913
1914 len = uc_check_code(start, end - start, q, cmd, eap,
1915 &split_buf, &split_len);
1916 if (len == (size_t)-1)
1917 {
1918 // no match, continue after '<'
1919 p = start + 1;
1920 len = 1;
1921 }
1922 else
1923 p = end;
1924 if (buf == NULL)
1925 totlen += len;
1926 else
1927 q += len;
1928 }
1929 if (buf != NULL) // second time here, finished
1930 {
1931 STRCPY(q, p);
1932 break;
1933 }
1934
1935 totlen += STRLEN(p); // Add on the trailing characters
Bram Moolenaar964b3742019-05-24 18:54:09 +02001936 buf = alloc(totlen + 1);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001937 if (buf == NULL)
1938 {
1939 vim_free(split_buf);
1940 return;
1941 }
1942 }
1943
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001944 if ((cmd->uc_argt & EX_KEEPSCRIPT) == 0)
1945 {
Bram Moolenaar205f29c2021-12-10 21:46:09 +00001946 restore_current_sctx = TRUE;
1947 save_current_sctx = current_sctx;
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001948 current_sctx.sc_version = cmd->uc_script_ctx.sc_version;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001949#ifdef FEAT_EVAL
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001950 current_sctx.sc_sid = cmd->uc_script_ctx.sc_sid;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00001951 if (cmd->uc_flags & UC_VIM9)
1952 {
1953 // In a {} block variables use Vim9 script rules, even in a legacy
1954 // script.
1955 restore_script_version =
1956 SCRIPT_ITEM(current_sctx.sc_sid)->sn_version;
1957 SCRIPT_ITEM(current_sctx.sc_sid)->sn_version = SCRIPT_VERSION_VIM9;
1958 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001959#endif
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001960 }
Bram Moolenaar205f29c2021-12-10 21:46:09 +00001961
Zoltan Arpadffy6fdb6282023-12-19 20:53:07 +01001962 (void)do_cmdline(buf, eap->ea_getline, eap->cookie,
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001963 DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED);
Bram Moolenaar205f29c2021-12-10 21:46:09 +00001964
1965 // Careful: Do not use "cmd" here, it may have become invalid if a user
1966 // command was added.
1967 if (restore_current_sctx)
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00001968 {
1969#ifdef FEAT_EVAL
1970 if (restore_script_version != 0)
1971 SCRIPT_ITEM(current_sctx.sc_sid)->sn_version =
1972 restore_script_version;
1973#endif
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001974 current_sctx = save_current_sctx;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00001975 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001976 vim_free(buf);
1977 vim_free(split_buf);
1978}