blob: ff2c353e39205566d71c337e779eb377d68770a6 [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
John Marriott9e795852024-08-20 20:57:23 +020019 size_t uc_namelen; // The length of the command name (excluding the NUL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +020020 long_u uc_argt; // The argument type
21 char_u *uc_rep; // The command's replacement string
22 long uc_def; // The default value for a range/count
23 int uc_compl; // completion type
Bram Moolenaarb7316892019-05-01 18:08:42 +020024 cmd_addr_T uc_addr_type; // The command's address type
Bram Moolenaarac9fb182019-04-27 13:04:13 +020025 sctx_T uc_script_ctx; // SCTX where the command was defined
Bram Moolenaar98b7fe72022-03-23 21:36:27 +000026 int uc_flags; // some UC_ flags
Bram Moolenaar9b8d6222020-12-28 18:26:00 +010027# ifdef FEAT_EVAL
Bram Moolenaarac9fb182019-04-27 13:04:13 +020028 char_u *uc_compl_arg; // completion argument if any
Bram Moolenaarac9fb182019-04-27 13:04:13 +020029# endif
30} ucmd_T;
31
32// List of all user commands.
33static garray_T ucmds = {0, 0, sizeof(ucmd_T), 4, NULL};
34
Bram Moolenaarcf2594f2022-11-13 23:30:06 +000035// When non-zero it is not allowed to add or remove user commands
36static int ucmd_locked = 0;
37
Bram Moolenaarac9fb182019-04-27 13:04:13 +020038#define USER_CMD(i) (&((ucmd_T *)(ucmds.ga_data))[i])
39#define USER_CMD_GA(gap, i) (&((ucmd_T *)((gap)->ga_data))[i])
40
41/*
42 * List of names for completion for ":command" with the EXPAND_ flag.
John Marriott9e795852024-08-20 20:57:23 +020043 * Must be alphabetical on the 'value' field for completion and because
44 * it is used by bsearch()!
Bram Moolenaarac9fb182019-04-27 13:04:13 +020045 */
John Marriott9e795852024-08-20 20:57:23 +020046static keyvalue_T command_complete_tab[] =
Bram Moolenaarac9fb182019-04-27 13:04:13 +020047{
John Marriott9e795852024-08-20 20:57:23 +020048 KEYVALUE_ENTRY(EXPAND_ARGLIST, "arglist"),
49 KEYVALUE_ENTRY(EXPAND_AUGROUP, "augroup"),
50 KEYVALUE_ENTRY(EXPAND_BEHAVE, "behave"),
51#if defined(FEAT_EVAL)
52 KEYVALUE_ENTRY(EXPAND_BREAKPOINT, "breakpoint"),
53#endif
54 KEYVALUE_ENTRY(EXPAND_BUFFERS, "buffer"),
55 KEYVALUE_ENTRY(EXPAND_COLORS, "color"),
56 KEYVALUE_ENTRY(EXPAND_COMMANDS, "command"),
57 KEYVALUE_ENTRY(EXPAND_COMPILER, "compiler"),
Bram Moolenaarac9fb182019-04-27 13:04:13 +020058#if defined(FEAT_CSCOPE)
John Marriott9e795852024-08-20 20:57:23 +020059 KEYVALUE_ENTRY(EXPAND_CSCOPE, "cscope"),
Bram Moolenaarac9fb182019-04-27 13:04:13 +020060#endif
Bram Moolenaar0a52df52019-08-18 22:26:31 +020061#if defined(FEAT_EVAL)
John Marriott9e795852024-08-20 20:57:23 +020062 KEYVALUE_ENTRY(EXPAND_USER_DEFINED, "custom"),
63 KEYVALUE_ENTRY(EXPAND_USER_LIST, "customlist"),
Bram Moolenaarac9fb182019-04-27 13:04:13 +020064#endif
John Marriott9e795852024-08-20 20:57:23 +020065 KEYVALUE_ENTRY(EXPAND_DIFF_BUFFERS, "diff_buffer"),
66 KEYVALUE_ENTRY(EXPAND_DIRECTORIES, "dir"),
67 KEYVALUE_ENTRY(EXPAND_DIRS_IN_CDPATH, "dir_in_path"),
68 KEYVALUE_ENTRY(EXPAND_ENV_VARS, "environment"),
69 KEYVALUE_ENTRY(EXPAND_EVENTS, "event"),
70 KEYVALUE_ENTRY(EXPAND_EXPRESSION, "expression"),
71 KEYVALUE_ENTRY(EXPAND_FILES, "file"),
72 KEYVALUE_ENTRY(EXPAND_FILES_IN_PATH, "file_in_path"),
73 KEYVALUE_ENTRY(EXPAND_FILETYPE, "filetype"),
74 KEYVALUE_ENTRY(EXPAND_FUNCTIONS, "function"),
75 KEYVALUE_ENTRY(EXPAND_HELP, "help"),
76 KEYVALUE_ENTRY(EXPAND_HIGHLIGHT, "highlight"),
77 KEYVALUE_ENTRY(EXPAND_HISTORY, "history"),
Doug Kearns81642d92024-01-04 22:37:44 +010078#if defined(FEAT_KEYMAP)
John Marriott9e795852024-08-20 20:57:23 +020079 KEYVALUE_ENTRY(EXPAND_KEYMAP, "keymap"),
Doug Kearns81642d92024-01-04 22:37:44 +010080#endif
Bram Moolenaarac9fb182019-04-27 13:04:13 +020081#if defined(HAVE_LOCALE_H) || defined(X_LOCALE)
John Marriott9e795852024-08-20 20:57:23 +020082 KEYVALUE_ENTRY(EXPAND_LOCALES, "locale"),
Bram Moolenaarac9fb182019-04-27 13:04:13 +020083#endif
John Marriott9e795852024-08-20 20:57:23 +020084 KEYVALUE_ENTRY(EXPAND_MAPCLEAR, "mapclear"),
85 KEYVALUE_ENTRY(EXPAND_MAPPINGS, "mapping"),
86 KEYVALUE_ENTRY(EXPAND_MENUS, "menu"),
87 KEYVALUE_ENTRY(EXPAND_MESSAGES, "messages"),
88 KEYVALUE_ENTRY(EXPAND_SETTINGS, "option"),
89 KEYVALUE_ENTRY(EXPAND_PACKADD, "packadd"),
90 KEYVALUE_ENTRY(EXPAND_RUNTIME, "runtime"),
Bram Moolenaar6e2e2cc2022-03-14 19:24:46 +000091#if defined(FEAT_EVAL)
John Marriott9e795852024-08-20 20:57:23 +020092 KEYVALUE_ENTRY(EXPAND_SCRIPTNAMES, "scriptnames"),
Bram Moolenaar6e2e2cc2022-03-14 19:24:46 +000093#endif
John Marriott9e795852024-08-20 20:57:23 +020094 KEYVALUE_ENTRY(EXPAND_SHELLCMD, "shellcmd"),
Ruslan Russkikh0407d622024-10-08 22:21:05 +020095 KEYVALUE_ENTRY(EXPAND_SHELLCMDLINE, "shellcmdline"),
John Marriott9e795852024-08-20 20:57:23 +020096#if defined(FEAT_SIGNS)
97 KEYVALUE_ENTRY(EXPAND_SIGN, "sign"),
98#endif
99 KEYVALUE_ENTRY(EXPAND_OWNSYNTAX, "syntax"),
100#if defined(FEAT_PROFILE)
101 KEYVALUE_ENTRY(EXPAND_SYNTIME, "syntime"),
102#endif
103 KEYVALUE_ENTRY(EXPAND_TAGS, "tag"),
104 KEYVALUE_ENTRY(EXPAND_TAGS_LISTFILES, "tag_listfiles"),
105 KEYVALUE_ENTRY(EXPAND_USER, "user"),
106 KEYVALUE_ENTRY(EXPAND_USER_VARS, "var")
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200107};
108
John Marriott9e795852024-08-20 20:57:23 +0200109typedef struct
110{
111 cmd_addr_T key;
112 char *fullname;
113 size_t fullnamelen;
114 char *shortname;
115 size_t shortnamelen;
116} addrtype_T;
117
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200118/*
119 * List of names of address types. Must be alphabetical for completion.
John Marriott9e795852024-08-20 20:57:23 +0200120 * Must be sorted by the 'fullname' field because it is used by bsearch()!
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200121 */
John Marriott9e795852024-08-20 20:57:23 +0200122#define ADDRTYPE_ENTRY(k, fn, sn) \
123 {(k), (fn), STRLEN_LITERAL(fn), (sn), STRLEN_LITERAL(sn)}
124static addrtype_T addr_type_complete_tab[] =
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200125{
John Marriott9e795852024-08-20 20:57:23 +0200126 ADDRTYPE_ENTRY(ADDR_ARGUMENTS, "arguments", "arg"),
127 ADDRTYPE_ENTRY(ADDR_BUFFERS, "buffers", "buf"),
128 ADDRTYPE_ENTRY(ADDR_LINES, "lines", "line"),
129 ADDRTYPE_ENTRY(ADDR_LOADED_BUFFERS, "loaded_buffers", "load"),
130 ADDRTYPE_ENTRY(ADDR_OTHER, "other", "?"),
131 ADDRTYPE_ENTRY(ADDR_QUICKFIX, "quickfix", "qf"),
132 ADDRTYPE_ENTRY(ADDR_TABS, "tabs", "tab"),
133 ADDRTYPE_ENTRY(ADDR_WINDOWS, "windows", "win")
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200134};
135
John Marriott9e795852024-08-20 20:57:23 +0200136static int cmp_addr_type(const void *a, const void *b);
137
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200138/*
139 * Search for a user command that matches "eap->cmd".
140 * Return cmdidx in "eap->cmdidx", flags in "eap->argt", idx in "eap->useridx".
141 * Return a pointer to just after the command.
142 * Return NULL if there is no matching command.
143 */
144 char_u *
145find_ucmd(
146 exarg_T *eap,
Bram Moolenaar0023f822022-01-16 21:54:19 +0000147 char_u *p, // end of the command (possibly including count)
148 int *full, // set to TRUE for a full match
149 expand_T *xp, // used for completion, NULL otherwise
150 int *complp) // completion flags or NULL
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200151{
152 int len = (int)(p - eap->cmd);
153 int j, k, matchlen = 0;
154 ucmd_T *uc;
155 int found = FALSE;
156 int possible = FALSE;
157 char_u *cp, *np; // Point into typed cmd and test name
158 garray_T *gap;
159 int amb_local = FALSE; // Found ambiguous buffer-local command,
160 // only full match global is accepted.
161
162 /*
163 * Look for buffer-local user commands first, then global ones.
164 */
Bram Moolenaar0f6e28f2022-02-20 20:49:35 +0000165 gap = &prevwin_curwin()->w_buffer->b_ucmds;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200166 for (;;)
167 {
168 for (j = 0; j < gap->ga_len; ++j)
169 {
170 uc = USER_CMD_GA(gap, j);
171 cp = eap->cmd;
172 np = uc->uc_name;
173 k = 0;
174 while (k < len && *np != NUL && *cp++ == *np++)
175 k++;
176 if (k == len || (*np == NUL && vim_isdigit(eap->cmd[k])))
177 {
178 // If finding a second match, the command is ambiguous. But
179 // not if a buffer-local command wasn't a full match and a
180 // global command is a full match.
181 if (k == len && found && *np != NUL)
182 {
183 if (gap == &ucmds)
184 return NULL;
185 amb_local = TRUE;
186 }
187
188 if (!found || (k == len && *np == NUL))
189 {
190 // If we matched up to a digit, then there could
191 // be another command including the digit that we
192 // should use instead.
193 if (k == len)
194 found = TRUE;
195 else
196 possible = TRUE;
197
198 if (gap == &ucmds)
199 eap->cmdidx = CMD_USER;
200 else
201 eap->cmdidx = CMD_USER_BUF;
202 eap->argt = (long)uc->uc_argt;
203 eap->useridx = j;
204 eap->addr_type = uc->uc_addr_type;
205
Bram Moolenaar52111f82019-04-29 21:30:45 +0200206 if (complp != NULL)
207 *complp = uc->uc_compl;
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200208# ifdef FEAT_EVAL
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200209 if (xp != NULL)
210 {
211 xp->xp_arg = uc->uc_compl_arg;
212 xp->xp_script_ctx = uc->uc_script_ctx;
Bram Moolenaar1a47ae32019-12-29 23:04:25 +0100213 xp->xp_script_ctx.sc_lnum += SOURCING_LNUM;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200214 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200215# endif
216 // Do not search for further abbreviations
217 // if this is an exact match.
218 matchlen = k;
219 if (k == len && *np == NUL)
220 {
221 if (full != NULL)
222 *full = TRUE;
223 amb_local = FALSE;
224 break;
225 }
226 }
227 }
228 }
229
230 // Stop if we found a full match or searched all.
231 if (j < gap->ga_len || gap == &ucmds)
232 break;
233 gap = &ucmds;
234 }
235
236 // Only found ambiguous matches.
237 if (amb_local)
238 {
239 if (xp != NULL)
240 xp->xp_context = EXPAND_UNSUCCESSFUL;
241 return NULL;
242 }
243
244 // The match we found may be followed immediately by a number. Move "p"
245 // back to point to it.
246 if (found || possible)
247 return p + (matchlen - len);
248 return p;
249}
250
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000251/*
252 * Set completion context for :command
253 */
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200254 char_u *
255set_context_in_user_cmd(expand_T *xp, char_u *arg_in)
256{
257 char_u *arg = arg_in;
258 char_u *p;
259
260 // Check for attributes
261 while (*arg == '-')
262 {
263 arg++; // Skip "-"
264 p = skiptowhite(arg);
265 if (*p == NUL)
266 {
267 // Cursor is still in the attribute
268 p = vim_strchr(arg, '=');
269 if (p == NULL)
270 {
271 // No "=", so complete attribute names
272 xp->xp_context = EXPAND_USER_CMD_FLAGS;
273 xp->xp_pattern = arg;
274 return NULL;
275 }
276
277 // For the -complete, -nargs and -addr attributes, we complete
278 // their arguments as well.
279 if (STRNICMP(arg, "complete", p - arg) == 0)
280 {
281 xp->xp_context = EXPAND_USER_COMPLETE;
282 xp->xp_pattern = p + 1;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200283 }
284 else if (STRNICMP(arg, "nargs", p - arg) == 0)
285 {
286 xp->xp_context = EXPAND_USER_NARGS;
287 xp->xp_pattern = p + 1;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200288 }
289 else if (STRNICMP(arg, "addr", p - arg) == 0)
290 {
291 xp->xp_context = EXPAND_USER_ADDR_TYPE;
292 xp->xp_pattern = p + 1;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200293 }
294 return NULL;
295 }
296 arg = skipwhite(p);
297 }
298
299 // After the attributes comes the new command name
300 p = skiptowhite(arg);
301 if (*p == NUL)
302 {
303 xp->xp_context = EXPAND_USER_COMMANDS;
304 xp->xp_pattern = arg;
305 return NULL;
306 }
307
308 // And finally comes a normal command
309 return skipwhite(p);
310}
311
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000312/*
313 * Set the completion context for the argument of a user defined command.
314 */
315 char_u *
316set_context_in_user_cmdarg(
317 char_u *cmd UNUSED,
318 char_u *arg,
319 long argt,
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000320 int context,
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000321 expand_T *xp,
322 int forceit)
323{
324 char_u *p;
325
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000326 if (context == EXPAND_NOTHING)
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000327 return NULL;
328
329 if (argt & EX_XFILE)
330 {
331 // EX_XFILE: file names are handled before this call
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000332 return NULL;
333 }
334
335#ifdef FEAT_MENU
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000336 if (context == EXPAND_MENUS)
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000337 return set_context_in_menu_cmd(xp, cmd, arg, forceit);
338#endif
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000339 if (context == EXPAND_COMMANDS)
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000340 return arg;
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000341 if (context == EXPAND_MAPPINGS)
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000342 return set_context_in_map_cmd(xp, (char_u *)"map", arg, forceit, FALSE,
343 FALSE, CMD_map);
344 // Find start of last argument.
345 p = arg;
346 while (*p)
347 {
348 if (*p == ' ')
349 // argument starts after a space
350 arg = p + 1;
351 else if (*p == '\\' && *(p + 1) != NUL)
352 ++p; // skip over escaped character
353 MB_PTR_ADV(p);
354 }
355 xp->xp_pattern = arg;
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000356 xp->xp_context = context;
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000357
358 return NULL;
359}
360
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200361 char_u *
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200362expand_user_command_name(int idx)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200363{
364 return get_user_commands(NULL, idx - (int)CMD_SIZE);
365}
366
367/*
368 * Function given to ExpandGeneric() to obtain the list of user command names.
369 */
370 char_u *
371get_user_commands(expand_T *xp UNUSED, int idx)
372{
Bram Moolenaarf03e3282019-07-22 21:55:18 +0200373 // In cmdwin, the alternative buffer should be used.
Bram Moolenaar0f6e28f2022-02-20 20:49:35 +0000374 buf_T *buf = prevwin_curwin()->w_buffer;
Bram Moolenaarf03e3282019-07-22 21:55:18 +0200375
376 if (idx < buf->b_ucmds.ga_len)
377 return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name;
Bram Moolenaarc2842ad2022-07-26 17:23:47 +0100378
Bram Moolenaarf03e3282019-07-22 21:55:18 +0200379 idx -= buf->b_ucmds.ga_len;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200380 if (idx < ucmds.ga_len)
Bram Moolenaarc2842ad2022-07-26 17:23:47 +0100381 {
382 int i;
383 char_u *name = USER_CMD(idx)->uc_name;
384
385 for (i = 0; i < buf->b_ucmds.ga_len; ++i)
386 if (STRCMP(name, USER_CMD_GA(&buf->b_ucmds, i)->uc_name) == 0)
387 // global command is overruled by buffer-local one
388 return (char_u *)"";
389 return name;
390 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200391 return NULL;
392}
393
Dominique Pelle748b3082022-01-08 12:41:16 +0000394#ifdef FEAT_EVAL
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200395/*
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200396 * Get the name of user command "idx". "cmdidx" can be CMD_USER or
397 * CMD_USER_BUF.
398 * Returns NULL if the command is not found.
399 */
400 char_u *
401get_user_command_name(int idx, int cmdidx)
402{
403 if (cmdidx == CMD_USER && idx < ucmds.ga_len)
404 return USER_CMD(idx)->uc_name;
405 if (cmdidx == CMD_USER_BUF)
406 {
407 // In cmdwin, the alternative buffer should be used.
Bram Moolenaar0f6e28f2022-02-20 20:49:35 +0000408 buf_T *buf = prevwin_curwin()->w_buffer;
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200409
410 if (idx < buf->b_ucmds.ga_len)
411 return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name;
412 }
413 return NULL;
414}
Dominique Pelle748b3082022-01-08 12:41:16 +0000415#endif
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200416
417/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200418 * Function given to ExpandGeneric() to obtain the list of user address type
419 * names.
420 */
421 char_u *
422get_user_cmd_addr_type(expand_T *xp UNUSED, int idx)
423{
John Marriott9e795852024-08-20 20:57:23 +0200424 if (idx < 0 || idx >= (int)ARRAY_LENGTH(addr_type_complete_tab))
425 return NULL;
426 return (char_u *)addr_type_complete_tab[idx].fullname;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200427}
428
429/*
430 * Function given to ExpandGeneric() to obtain the list of user command
431 * attributes.
432 */
433 char_u *
434get_user_cmd_flags(expand_T *xp UNUSED, int idx)
435{
436 static char *user_cmd_flags[] = {
437 "addr", "bang", "bar", "buffer", "complete",
Bram Moolenaar58ef8a32021-11-12 11:25:11 +0000438 "count", "nargs", "range", "register", "keepscript"
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200439 };
440
John Marriott9e795852024-08-20 20:57:23 +0200441 if (idx < 0 || idx >= (int)ARRAY_LENGTH(user_cmd_flags))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200442 return NULL;
443 return (char_u *)user_cmd_flags[idx];
444}
445
446/*
447 * Function given to ExpandGeneric() to obtain the list of values for -nargs.
448 */
449 char_u *
450get_user_cmd_nargs(expand_T *xp UNUSED, int idx)
451{
452 static char *user_cmd_nargs[] = {"0", "1", "*", "?", "+"};
453
John Marriott9e795852024-08-20 20:57:23 +0200454 if (idx < 0 || idx >= (int)ARRAY_LENGTH(user_cmd_nargs))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200455 return NULL;
456 return (char_u *)user_cmd_nargs[idx];
457}
458
459/*
460 * Function given to ExpandGeneric() to obtain the list of values for
461 * -complete.
462 */
463 char_u *
464get_user_cmd_complete(expand_T *xp UNUSED, int idx)
465{
John Marriott9e795852024-08-20 20:57:23 +0200466 if (idx < 0 || idx >= (int)ARRAY_LENGTH(command_complete_tab))
467 return NULL;
John Marriott8d4477e2024-11-02 15:59:01 +0100468 return command_complete_tab[idx].value.string;
John Marriott9e795852024-08-20 20:57:23 +0200469}
470
471/*
472 * Return the row in the command_complete_tab table that contains the given key.
473 */
474 static keyvalue_T *
475get_commandtype(int expand)
476{
477 int i;
478
479 for (i = 0; i < (int)ARRAY_LENGTH(command_complete_tab); ++i)
480 if (command_complete_tab[i].key == expand)
481 return &command_complete_tab[i];
482
483 return NULL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200484}
485
Dominique Pelle748b3082022-01-08 12:41:16 +0000486#ifdef FEAT_EVAL
Shougo Matsushita79d599b2022-05-07 12:48:29 +0100487/*
488 * Get the name of completion type "expand" as a string.
489 */
490 char_u *
491cmdcomplete_type_to_str(int expand)
492{
John Marriott9e795852024-08-20 20:57:23 +0200493 keyvalue_T *kv;
Shougo Matsushita79d599b2022-05-07 12:48:29 +0100494
John Marriott9e795852024-08-20 20:57:23 +0200495 kv = get_commandtype(expand);
Shougo Matsushita79d599b2022-05-07 12:48:29 +0100496
John Marriott8d4477e2024-11-02 15:59:01 +0100497 return (kv == NULL) ? NULL : kv->value.string;
Shougo Matsushita79d599b2022-05-07 12:48:29 +0100498}
499
500/*
501 * Get the index of completion type "complete_str".
502 * Returns EXPAND_NOTHING if no match found.
503 */
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200504 int
505cmdcomplete_str_to_type(char_u *complete_str)
506{
John Marriott9e795852024-08-20 20:57:23 +0200507 keyvalue_T target;
508 keyvalue_T *entry;
509 static keyvalue_T *last_entry = NULL; // cached result
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200510
Shougo Matsushita92997dd2023-08-20 20:55:55 +0200511 if (STRNCMP(complete_str, "custom,", 7) == 0)
512 return EXPAND_USER_DEFINED;
513 if (STRNCMP(complete_str, "customlist,", 11) == 0)
514 return EXPAND_USER_LIST;
515
John Marriott9e795852024-08-20 20:57:23 +0200516 target.key = 0;
John Marriott8d4477e2024-11-02 15:59:01 +0100517 target.value.string = complete_str;
518 target.value.length = 0; // not used, see cmp_keyvalue_value()
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200519
John Marriott9e795852024-08-20 20:57:23 +0200520 if (last_entry != NULL && cmp_keyvalue_value(&target, last_entry) == 0)
521 entry = last_entry;
522 else
523 {
524 entry = (keyvalue_T *)bsearch(&target,
525 &command_complete_tab,
526 ARRAY_LENGTH(command_complete_tab),
527 sizeof(command_complete_tab[0]),
528 cmp_keyvalue_value);
529 if (entry == NULL)
530 return EXPAND_NOTHING;
531
532 last_entry = entry;
533 }
534
535 return entry->key;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200536}
Dominique Pelle748b3082022-01-08 12:41:16 +0000537#endif
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200538
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200539/*
540 * List user commands starting with "name[name_len]".
541 */
542 static void
543uc_list(char_u *name, size_t name_len)
544{
545 int i, j;
546 int found = FALSE;
547 ucmd_T *cmd;
548 int len;
549 int over;
550 long a;
551 garray_T *gap;
John Marriott9e795852024-08-20 20:57:23 +0200552 keyvalue_T *entry;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200553
Bram Moolenaarcf2594f2022-11-13 23:30:06 +0000554 // don't allow for adding or removing user commands here
555 ++ucmd_locked;
556
Bram Moolenaare38eab22019-12-05 21:50:01 +0100557 // In cmdwin, the alternative buffer should be used.
Bram Moolenaar0f6e28f2022-02-20 20:49:35 +0000558 gap = &prevwin_curwin()->w_buffer->b_ucmds;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200559 for (;;)
560 {
561 for (i = 0; i < gap->ga_len; ++i)
562 {
563 cmd = USER_CMD_GA(gap, i);
564 a = (long)cmd->uc_argt;
565
566 // Skip commands which don't match the requested prefix and
567 // commands filtered out.
568 if (STRNCMP(name, cmd->uc_name, name_len) != 0
569 || message_filtered(cmd->uc_name))
570 continue;
571
572 // Put out the title first time
573 if (!found)
574 msg_puts_title(_("\n Name Args Address Complete Definition"));
575 found = TRUE;
576 msg_putchar('\n');
577 if (got_int)
578 break;
579
580 // Special cases
581 len = 4;
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200582 if (a & EX_BANG)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200583 {
584 msg_putchar('!');
585 --len;
586 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200587 if (a & EX_REGSTR)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200588 {
589 msg_putchar('"');
590 --len;
591 }
592 if (gap != &ucmds)
593 {
594 msg_putchar('b');
595 --len;
596 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200597 if (a & EX_TRLBAR)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200598 {
599 msg_putchar('|');
600 --len;
601 }
602 while (len-- > 0)
603 msg_putchar(' ');
604
605 msg_outtrans_attr(cmd->uc_name, HL_ATTR(HLF_D));
John Marriott9e795852024-08-20 20:57:23 +0200606 len = (int)cmd->uc_namelen + 4;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200607
608 do {
609 msg_putchar(' ');
610 ++len;
611 } while (len < 22);
612
613 // "over" is how much longer the name is than the column width for
614 // the name, we'll try to align what comes after.
615 over = len - 22;
616 len = 0;
617
618 // Arguments
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200619 switch ((int)(a & (EX_EXTRA|EX_NOSPC|EX_NEEDARG)))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200620 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200621 case 0: IObuff[len++] = '0'; break;
622 case (EX_EXTRA): IObuff[len++] = '*'; break;
623 case (EX_EXTRA|EX_NOSPC): IObuff[len++] = '?'; break;
624 case (EX_EXTRA|EX_NEEDARG): IObuff[len++] = '+'; break;
625 case (EX_EXTRA|EX_NOSPC|EX_NEEDARG): IObuff[len++] = '1'; break;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200626 }
627
628 do {
629 IObuff[len++] = ' ';
630 } while (len < 5 - over);
631
632 // Address / Range
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200633 if (a & (EX_RANGE|EX_COUNT))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200634 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200635 if (a & EX_COUNT)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200636 {
637 // -count=N
John Marriott9e795852024-08-20 20:57:23 +0200638 len += vim_snprintf((char *)IObuff + len, IOSIZE - len, "%ldc", cmd->uc_def);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200639 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200640 else if (a & EX_DFLALL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200641 IObuff[len++] = '%';
642 else if (cmd->uc_def >= 0)
643 {
644 // -range=N
John Marriott9e795852024-08-20 20:57:23 +0200645 len += vim_snprintf((char *)IObuff + len, IOSIZE - len, "%ld", cmd->uc_def);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200646 }
647 else
648 IObuff[len++] = '.';
649 }
650
651 do {
652 IObuff[len++] = ' ';
653 } while (len < 8 - over);
654
655 // Address Type
John Marriott9e795852024-08-20 20:57:23 +0200656 for (j = 0; j < (int)ARRAY_LENGTH(addr_type_complete_tab); ++j)
657 if (addr_type_complete_tab[j].key != ADDR_LINES
658 && addr_type_complete_tab[j].key == cmd->uc_addr_type)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200659 {
John Marriott9e795852024-08-20 20:57:23 +0200660 STRCPY(IObuff + len, addr_type_complete_tab[j].shortname);
661 len += (int)addr_type_complete_tab[j].shortnamelen;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200662 break;
663 }
664
665 do {
666 IObuff[len++] = ' ';
667 } while (len < 13 - over);
668
669 // Completion
John Marriott9e795852024-08-20 20:57:23 +0200670 entry = get_commandtype(cmd->uc_compl);
671 if (entry != NULL)
672 {
John Marriott8d4477e2024-11-02 15:59:01 +0100673 STRCPY(IObuff + len, entry->value.string);
Yegappan Lakshmanan084529c2024-12-24 09:50:01 +0100674 len += (int)entry->value.length;
Bram Moolenaar78f60322022-01-17 22:16:33 +0000675#ifdef FEAT_EVAL
John Marriott9e795852024-08-20 20:57:23 +0200676 if (p_verbose > 0 && cmd->uc_compl_arg != NULL)
677 {
678 size_t uc_compl_arglen = STRLEN(cmd->uc_compl_arg);
679
680 if (uc_compl_arglen < 200)
Bram Moolenaar3f3597b2022-01-17 19:06:56 +0000681 {
John Marriott9e795852024-08-20 20:57:23 +0200682 IObuff[len++] = ',';
683 STRCPY(IObuff + len, cmd->uc_compl_arg);
Yegappan Lakshmanan084529c2024-12-24 09:50:01 +0100684 len += (int)uc_compl_arglen;
Bram Moolenaar3f3597b2022-01-17 19:06:56 +0000685 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200686 }
John Marriott9e795852024-08-20 20:57:23 +0200687#endif
688 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200689
690 do {
691 IObuff[len++] = ' ';
692 } while (len < 25 - over);
693
John Marriott9e795852024-08-20 20:57:23 +0200694 IObuff[len] = NUL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200695 msg_outtrans(IObuff);
696
697 msg_outtrans_special(cmd->uc_rep, FALSE,
698 name_len == 0 ? Columns - 47 : 0);
699#ifdef FEAT_EVAL
700 if (p_verbose > 0)
701 last_set_msg(cmd->uc_script_ctx);
702#endif
703 out_flush();
704 ui_breakcheck();
705 if (got_int)
706 break;
707 }
708 if (gap == &ucmds || i < gap->ga_len)
709 break;
710 gap = &ucmds;
711 }
712
713 if (!found)
714 msg(_("No user-defined commands found"));
Bram Moolenaarcf2594f2022-11-13 23:30:06 +0000715
716 --ucmd_locked;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200717}
718
719 char *
720uc_fun_cmd(void)
721{
722 static char_u fcmd[] = {0x84, 0xaf, 0x60, 0xb9, 0xaf, 0xb5, 0x60, 0xa4,
723 0xa5, 0xad, 0xa1, 0xae, 0xa4, 0x60, 0xa1, 0x60,
724 0xb3, 0xa8, 0xb2, 0xb5, 0xa2, 0xa2, 0xa5, 0xb2,
725 0xb9, 0x7f, 0};
726 int i;
727
728 for (i = 0; fcmd[i]; ++i)
729 IObuff[i] = fcmd[i] - 0x40;
John Marriott9e795852024-08-20 20:57:23 +0200730 IObuff[i] = NUL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200731 return (char *)IObuff;
732}
733
734/*
735 * Parse address type argument
736 */
737 static int
738parse_addr_type_arg(
739 char_u *value,
740 int vallen,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200741 cmd_addr_T *addr_type_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200742{
John Marriott9e795852024-08-20 20:57:23 +0200743 addrtype_T target;
744 addrtype_T *entry;
745 static addrtype_T *last_entry; // cached result
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200746
John Marriott9e795852024-08-20 20:57:23 +0200747 target.key = 0;
748 target.fullname = (char *)value;
749 target.fullnamelen = vallen;
750
751 if (last_entry != NULL && cmp_addr_type(&target, last_entry) == 0)
752 entry = last_entry;
753 else
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200754 {
John Marriott9e795852024-08-20 20:57:23 +0200755 entry = (addrtype_T *)bsearch(&target,
756 &addr_type_complete_tab,
757 ARRAY_LENGTH(addr_type_complete_tab),
758 sizeof(addr_type_complete_tab[0]),
759 cmp_addr_type);
760 if (entry == NULL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200761 {
John Marriott9e795852024-08-20 20:57:23 +0200762 int i;
763 char_u *err = value;
764
765 for (i = 0; err[i] != NUL && !VIM_ISWHITE(err[i]); i++)
766 ;
767 err[i] = NUL;
768 semsg(_(e_invalid_address_type_value_str), err);
769 return FAIL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200770 }
John Marriott9e795852024-08-20 20:57:23 +0200771
772 last_entry = entry;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200773 }
774
John Marriott9e795852024-08-20 20:57:23 +0200775 *addr_type_arg = entry->key;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200776
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200777 return OK;
778}
779
John Marriott9e795852024-08-20 20:57:23 +0200780 static int
781cmp_addr_type(const void *a, const void *b)
782{
783 addrtype_T *at1 = (addrtype_T *)a;
784 addrtype_T *at2 = (addrtype_T *)b;
785
786 return STRNCMP(at1->fullname, at2->fullname, MAX(at1->fullnamelen, at2->fullnamelen));
787}
788
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200789/*
790 * Parse a completion argument "value[vallen]".
791 * The detected completion goes in "*complp", argument type in "*argt".
792 * When there is an argument, for function and user defined completion, it's
793 * copied to allocated memory and stored in "*compl_arg".
794 * Returns FAIL if something is wrong.
795 */
796 int
797parse_compl_arg(
798 char_u *value,
799 int vallen,
800 int *complp,
801 long *argt,
802 char_u **compl_arg UNUSED)
803{
804 char_u *arg = NULL;
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200805# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200806 size_t arglen = 0;
807# endif
808 int i;
809 int valend = vallen;
John Marriott9e795852024-08-20 20:57:23 +0200810 keyvalue_T target;
811 keyvalue_T *entry;
812 static keyvalue_T *last_entry = NULL; // cached result
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200813
814 // Look for any argument part - which is the part after any ','
815 for (i = 0; i < vallen; ++i)
816 {
817 if (value[i] == ',')
818 {
819 arg = &value[i + 1];
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200820# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200821 arglen = vallen - i - 1;
822# endif
823 valend = i;
824 break;
825 }
826 }
827
John Marriott9e795852024-08-20 20:57:23 +0200828 target.key = 0;
John Marriott8d4477e2024-11-02 15:59:01 +0100829 target.value.string = value;
830 target.value.length = valend;
John Marriott9e795852024-08-20 20:57:23 +0200831
832 if (last_entry != NULL && cmp_keyvalue_value_n(&target, last_entry) == 0)
833 entry = last_entry;
834 else
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200835 {
John Marriott9e795852024-08-20 20:57:23 +0200836 entry = (keyvalue_T *)bsearch(&target,
837 &command_complete_tab,
838 ARRAY_LENGTH(command_complete_tab),
839 sizeof(command_complete_tab[0]),
840 cmp_keyvalue_value_n);
841 if (entry == NULL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200842 {
John Marriott9e795852024-08-20 20:57:23 +0200843 semsg(_(e_invalid_complete_value_str), value);
844 return FAIL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200845 }
John Marriott9e795852024-08-20 20:57:23 +0200846
847 last_entry = entry;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200848 }
849
John Marriott9e795852024-08-20 20:57:23 +0200850 *complp = entry->key;
851 if (*complp == EXPAND_BUFFERS)
852 *argt |= EX_BUFNAME;
Ruslan Russkikh0407d622024-10-08 22:21:05 +0200853 else if (*complp == EXPAND_DIRECTORIES || *complp == EXPAND_FILES || *complp == EXPAND_SHELLCMDLINE)
John Marriott9e795852024-08-20 20:57:23 +0200854 *argt |= EX_XFILE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200855
John Marriott9e795852024-08-20 20:57:23 +0200856 if (
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200857# if defined(FEAT_EVAL)
John Marriott9e795852024-08-20 20:57:23 +0200858 *complp != EXPAND_USER_DEFINED && *complp != EXPAND_USER_LIST
859 &&
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200860# endif
John Marriott9e795852024-08-20 20:57:23 +0200861 arg != NULL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200862 {
Bram Moolenaarb09feaa2022-01-02 20:20:45 +0000863 emsg(_(e_completion_argument_only_allowed_for_custom_completion));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200864 return FAIL;
865 }
866
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200867# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200868 if ((*complp == EXPAND_USER_DEFINED || *complp == EXPAND_USER_LIST)
869 && arg == NULL)
870 {
Bram Moolenaarb09feaa2022-01-02 20:20:45 +0000871 emsg(_(e_custom_completion_requires_function_argument));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200872 return FAIL;
873 }
874
875 if (arg != NULL)
Bram Moolenaar71ccd032020-06-12 22:59:11 +0200876 *compl_arg = vim_strnsave(arg, arglen);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200877# endif
John Marriott9e795852024-08-20 20:57:23 +0200878
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200879 return OK;
880}
881
882/*
883 * Scan attributes in the ":command" command.
884 * Return FAIL when something is wrong.
885 */
886 static int
887uc_scan_attr(
888 char_u *attr,
889 size_t len,
890 long *argt,
891 long *def,
892 int *flags,
Bram Moolenaar52111f82019-04-29 21:30:45 +0200893 int *complp,
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200894 char_u **compl_arg,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200895 cmd_addr_T *addr_type_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200896{
897 char_u *p;
898
899 if (len == 0)
900 {
Bram Moolenaar1a992222021-12-31 17:25:48 +0000901 emsg(_(e_no_attribute_specified));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200902 return FAIL;
903 }
904
905 // First, try the simple attributes (no arguments)
906 if (STRNICMP(attr, "bang", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200907 *argt |= EX_BANG;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200908 else if (STRNICMP(attr, "buffer", len) == 0)
909 *flags |= UC_BUFFER;
910 else if (STRNICMP(attr, "register", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200911 *argt |= EX_REGSTR;
Bram Moolenaar58ef8a32021-11-12 11:25:11 +0000912 else if (STRNICMP(attr, "keepscript", len) == 0)
913 *argt |= EX_KEEPSCRIPT;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200914 else if (STRNICMP(attr, "bar", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200915 *argt |= EX_TRLBAR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200916 else
917 {
918 int i;
919 char_u *val = NULL;
920 size_t vallen = 0;
921 size_t attrlen = len;
922
923 // Look for the attribute name - which is the part before any '='
924 for (i = 0; i < (int)len; ++i)
925 {
926 if (attr[i] == '=')
927 {
928 val = &attr[i + 1];
929 vallen = len - i - 1;
930 attrlen = i;
931 break;
932 }
933 }
934
935 if (STRNICMP(attr, "nargs", attrlen) == 0)
936 {
937 if (vallen == 1)
938 {
939 if (*val == '0')
940 // Do nothing - this is the default
941 ;
942 else if (*val == '1')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200943 *argt |= (EX_EXTRA | EX_NOSPC | EX_NEEDARG);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200944 else if (*val == '*')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200945 *argt |= EX_EXTRA;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200946 else if (*val == '?')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200947 *argt |= (EX_EXTRA | EX_NOSPC);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200948 else if (*val == '+')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200949 *argt |= (EX_EXTRA | EX_NEEDARG);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200950 else
951 goto wrong_nargs;
952 }
953 else
954 {
955wrong_nargs:
Bram Moolenaar1a992222021-12-31 17:25:48 +0000956 emsg(_(e_invalid_number_of_arguments));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200957 return FAIL;
958 }
959 }
960 else if (STRNICMP(attr, "range", attrlen) == 0)
961 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200962 *argt |= EX_RANGE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200963 if (vallen == 1 && *val == '%')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200964 *argt |= EX_DFLALL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200965 else if (val != NULL)
966 {
967 p = val;
968 if (*def >= 0)
969 {
970two_count:
Bram Moolenaar1a992222021-12-31 17:25:48 +0000971 emsg(_(e_count_cannot_be_specified_twice));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200972 return FAIL;
973 }
974
975 *def = getdigits(&p);
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200976 *argt |= EX_ZEROR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200977
978 if (p != val + vallen || vallen == 0)
979 {
980invalid_count:
Bram Moolenaar1a992222021-12-31 17:25:48 +0000981 emsg(_(e_invalid_default_value_for_count));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200982 return FAIL;
983 }
984 }
Bram Moolenaarb7316892019-05-01 18:08:42 +0200985 // default for -range is using buffer lines
986 if (*addr_type_arg == ADDR_NONE)
987 *addr_type_arg = ADDR_LINES;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200988 }
989 else if (STRNICMP(attr, "count", attrlen) == 0)
990 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200991 *argt |= (EX_COUNT | EX_ZEROR | EX_RANGE);
Bram Moolenaarb7316892019-05-01 18:08:42 +0200992 // default for -count is using any number
993 if (*addr_type_arg == ADDR_NONE)
994 *addr_type_arg = ADDR_OTHER;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200995
996 if (val != NULL)
997 {
998 p = val;
999 if (*def >= 0)
1000 goto two_count;
1001
1002 *def = getdigits(&p);
1003
1004 if (p != val + vallen)
1005 goto invalid_count;
1006 }
1007
1008 if (*def < 0)
1009 *def = 0;
1010 }
1011 else if (STRNICMP(attr, "complete", attrlen) == 0)
1012 {
1013 if (val == NULL)
1014 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001015 semsg(_(e_argument_required_for_str), "-complete");
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001016 return FAIL;
1017 }
1018
Bram Moolenaar52111f82019-04-29 21:30:45 +02001019 if (parse_compl_arg(val, (int)vallen, complp, argt, compl_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001020 == FAIL)
1021 return FAIL;
1022 }
1023 else if (STRNICMP(attr, "addr", attrlen) == 0)
1024 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +02001025 *argt |= EX_RANGE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001026 if (val == NULL)
1027 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001028 semsg(_(e_argument_required_for_str), "-addr");
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001029 return FAIL;
1030 }
Bram Moolenaare4f5f3a2019-05-04 14:05:08 +02001031 if (parse_addr_type_arg(val, (int)vallen, addr_type_arg) == FAIL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001032 return FAIL;
Bram Moolenaare4f5f3a2019-05-04 14:05:08 +02001033 if (*addr_type_arg != ADDR_LINES)
Bram Moolenaar8071cb22019-07-12 17:58:01 +02001034 *argt |= EX_ZEROR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001035 }
1036 else
1037 {
1038 char_u ch = attr[len];
1039 attr[len] = '\0';
Bram Moolenaar1a992222021-12-31 17:25:48 +00001040 semsg(_(e_invalid_attribute_str), attr);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001041 attr[len] = ch;
1042 return FAIL;
1043 }
1044 }
1045
1046 return OK;
1047}
1048
1049/*
1050 * Add a user command to the list or replace an existing one.
1051 */
1052 static int
1053uc_add_command(
1054 char_u *name,
1055 size_t name_len,
1056 char_u *rep,
1057 long argt,
1058 long def,
1059 int flags,
1060 int compl,
1061 char_u *compl_arg UNUSED,
Bram Moolenaarb7316892019-05-01 18:08:42 +02001062 cmd_addr_T addr_type,
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001063 int force)
1064{
1065 ucmd_T *cmd = NULL;
1066 char_u *p;
1067 int i;
1068 int cmp = 1;
1069 char_u *rep_buf = NULL;
1070 garray_T *gap;
1071
zeertzjq7e0bae02023-08-11 23:15:38 +02001072 replace_termcodes(rep, &rep_buf, 0, 0, NULL);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001073 if (rep_buf == NULL)
1074 {
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001075 // can't replace termcodes - try using the string as is
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001076 rep_buf = vim_strsave(rep);
1077
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001078 // give up if out of memory
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001079 if (rep_buf == NULL)
1080 return FAIL;
1081 }
1082
1083 // get address of growarray: global or in curbuf
1084 if (flags & UC_BUFFER)
1085 {
1086 gap = &curbuf->b_ucmds;
1087 if (gap->ga_itemsize == 0)
Bram Moolenaar04935fb2022-01-08 16:19:22 +00001088 ga_init2(gap, sizeof(ucmd_T), 4);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001089 }
1090 else
1091 gap = &ucmds;
1092
1093 // Search for the command in the already defined commands.
1094 for (i = 0; i < gap->ga_len; ++i)
1095 {
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001096 cmd = USER_CMD_GA(gap, i);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001097 cmp = STRNCMP(name, cmd->uc_name, name_len);
1098 if (cmp == 0)
1099 {
John Marriott9e795852024-08-20 20:57:23 +02001100 if (name_len < cmd->uc_namelen)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001101 cmp = -1;
John Marriott9e795852024-08-20 20:57:23 +02001102 else if (name_len > cmd->uc_namelen)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001103 cmp = 1;
1104 }
1105
1106 if (cmp == 0)
1107 {
1108 // Command can be replaced with "command!" and when sourcing the
1109 // same script again, but only once.
1110 if (!force
1111#ifdef FEAT_EVAL
1112 && (cmd->uc_script_ctx.sc_sid != current_sctx.sc_sid
1113 || cmd->uc_script_ctx.sc_seq == current_sctx.sc_seq)
1114#endif
1115 )
1116 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001117 semsg(_(e_command_already_exists_add_bang_to_replace_it_str),
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001118 name);
1119 goto fail;
1120 }
1121
1122 VIM_CLEAR(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001123#if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001124 VIM_CLEAR(cmd->uc_compl_arg);
1125#endif
1126 break;
1127 }
1128
1129 // Stop as soon as we pass the name to add
1130 if (cmp < 0)
1131 break;
1132 }
1133
1134 // Extend the array unless we're replacing an existing command
1135 if (cmp != 0)
1136 {
Yegappan Lakshmananfadc02a2023-01-27 21:03:12 +00001137 if (ga_grow(gap, 1) == FAIL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001138 goto fail;
Bram Moolenaar71ccd032020-06-12 22:59:11 +02001139 if ((p = vim_strnsave(name, name_len)) == NULL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001140 goto fail;
1141
1142 cmd = USER_CMD_GA(gap, i);
1143 mch_memmove(cmd + 1, cmd, (gap->ga_len - i) * sizeof(ucmd_T));
1144
1145 ++gap->ga_len;
1146
1147 cmd->uc_name = p;
John Marriott9e795852024-08-20 20:57:23 +02001148 cmd->uc_namelen = name_len;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001149 }
1150
1151 cmd->uc_rep = rep_buf;
1152 cmd->uc_argt = argt;
1153 cmd->uc_def = def;
1154 cmd->uc_compl = compl;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001155 cmd->uc_script_ctx = current_sctx;
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001156 if (flags & UC_VIM9)
1157 cmd->uc_script_ctx.sc_version = SCRIPT_VERSION_VIM9;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00001158 cmd->uc_flags = flags & UC_VIM9;
Bram Moolenaar9b8d6222020-12-28 18:26:00 +01001159#ifdef FEAT_EVAL
Bram Moolenaar1a47ae32019-12-29 23:04:25 +01001160 cmd->uc_script_ctx.sc_lnum += SOURCING_LNUM;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001161 cmd->uc_compl_arg = compl_arg;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001162#endif
1163 cmd->uc_addr_type = addr_type;
1164
1165 return OK;
1166
1167fail:
1168 vim_free(rep_buf);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001169#if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001170 vim_free(compl_arg);
1171#endif
1172 return FAIL;
1173}
1174
1175/*
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001176 * If "p" starts with "{" then read a block of commands until "}".
1177 * Used for ":command" and ":autocmd".
1178 */
1179 char_u *
1180may_get_cmd_block(exarg_T *eap, char_u *p, char_u **tofree, int *flags)
1181{
1182 char_u *retp = p;
1183
1184 if (*p == '{' && ends_excmd2(eap->arg, skipwhite(p + 1))
Zoltan Arpadffy6fdb6282023-12-19 20:53:07 +01001185 && eap->ea_getline != NULL)
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001186 {
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001187 garray_T ga;
1188 char_u *line = NULL;
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001189
1190 ga_init2(&ga, sizeof(char_u *), 10);
Bram Moolenaar9f1a39a2022-01-08 15:39:39 +00001191 if (ga_copy_string(&ga, p) == FAIL)
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001192 return retp;
1193
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001194 // If the argument ends in "}" it must have been concatenated already
1195 // for ISN_EXEC.
1196 if (p[STRLEN(p) - 1] != '}')
1197 // Read lines between '{' and '}'. Does not support nesting or
1198 // here-doc constructs.
1199 for (;;)
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001200 {
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001201 vim_free(line);
Zoltan Arpadffy6fdb6282023-12-19 20:53:07 +01001202 if ((line = eap->ea_getline(':', eap->cookie,
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001203 0, GETLINE_CONCAT_CONTBAR)) == NULL)
1204 {
1205 emsg(_(e_missing_rcurly));
1206 break;
1207 }
Bram Moolenaar9f1a39a2022-01-08 15:39:39 +00001208 if (ga_copy_string(&ga, line) == FAIL)
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001209 break;
1210 if (*skipwhite(line) == '}')
1211 break;
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001212 }
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001213 vim_free(line);
1214 retp = *tofree = ga_concat_strings(&ga, "\n");
1215 ga_clear_strings(&ga);
1216 *flags |= UC_VIM9;
1217 }
1218 return retp;
1219}
1220
1221/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001222 * ":command ..." implementation
1223 */
1224 void
1225ex_command(exarg_T *eap)
1226{
Bram Moolenaarb7316892019-05-01 18:08:42 +02001227 char_u *name;
1228 char_u *end;
1229 char_u *p;
1230 long argt = 0;
1231 long def = -1;
1232 int flags = 0;
1233 int compl = EXPAND_NOTHING;
1234 char_u *compl_arg = NULL;
1235 cmd_addr_T addr_type_arg = ADDR_NONE;
1236 int has_attr = (eap->arg[0] == '-');
1237 int name_len;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001238
1239 p = eap->arg;
1240
1241 // Check for attributes
1242 while (*p == '-')
1243 {
1244 ++p;
1245 end = skiptowhite(p);
1246 if (uc_scan_attr(p, end - p, &argt, &def, &flags, &compl,
1247 &compl_arg, &addr_type_arg) == FAIL)
zeertzjq33e54302022-12-19 16:49:27 +00001248 goto theend;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001249 p = skipwhite(end);
1250 }
1251
1252 // Get the name (if any) and skip to the following argument
1253 name = p;
1254 if (ASCII_ISALPHA(*p))
1255 while (ASCII_ISALNUM(*p))
1256 ++p;
Bram Moolenaara72cfb82020-04-23 17:07:30 +02001257 if (!ends_excmd2(eap->arg, p) && !VIM_ISWHITE(*p))
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001258 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001259 emsg(_(e_invalid_command_name));
zeertzjq33e54302022-12-19 16:49:27 +00001260 goto theend;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001261 }
1262 end = p;
1263 name_len = (int)(end - name);
1264
1265 // If there is nothing after the name, and no attributes were specified,
1266 // we are listing commands
1267 p = skipwhite(end);
Bram Moolenaara72cfb82020-04-23 17:07:30 +02001268 if (!has_attr && ends_excmd2(eap->arg, p))
zeertzjq33e54302022-12-19 16:49:27 +00001269 {
John Marriott9e795852024-08-20 20:57:23 +02001270 uc_list(name, name_len);
zeertzjq33e54302022-12-19 16:49:27 +00001271 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001272 else if (!ASCII_ISUPPER(*name))
zeertzjq33e54302022-12-19 16:49:27 +00001273 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001274 emsg(_(e_user_defined_commands_must_start_with_an_uppercase_letter));
zeertzjq33e54302022-12-19 16:49:27 +00001275 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001276 else if ((name_len == 1 && *name == 'X')
1277 || (name_len <= 4
1278 && STRNCMP(name, "Next", name_len > 4 ? 4 : name_len) == 0))
zeertzjq33e54302022-12-19 16:49:27 +00001279 {
Bram Moolenaar9d00e4a2022-01-05 17:49:15 +00001280 emsg(_(e_reserved_name_cannot_be_used_for_user_defined_command));
zeertzjq33e54302022-12-19 16:49:27 +00001281 }
Martin Tournoijde69a732021-07-11 14:28:25 +02001282 else if (compl > 0 && (argt & EX_EXTRA) == 0)
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001283 {
1284 // Some plugins rely on silently ignoring the mistake, only make this
1285 // an error in Vim9 script.
1286 if (in_vim9script())
Bram Moolenaar41a34852021-08-04 16:09:24 +02001287 emsg(_(e_complete_used_without_allowing_arguments));
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001288 else
1289 give_warning_with_source(
Bram Moolenaar41a34852021-08-04 16:09:24 +02001290 (char_u *)_(e_complete_used_without_allowing_arguments),
1291 TRUE, TRUE);
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001292 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001293 else
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001294 {
1295 char_u *tofree = NULL;
1296
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001297 p = may_get_cmd_block(eap, p, &tofree, &flags);
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001298
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001299 uc_add_command(name, end - name, p, argt, def, flags, compl, compl_arg,
1300 addr_type_arg, eap->forceit);
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001301 vim_free(tofree);
zeertzjq33e54302022-12-19 16:49:27 +00001302
1303 return; // success
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001304 }
zeertzjq33e54302022-12-19 16:49:27 +00001305
1306theend:
1307 vim_free(compl_arg);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001308}
1309
1310/*
1311 * ":comclear" implementation
1312 * Clear all user commands, global and for current buffer.
1313 */
1314 void
1315ex_comclear(exarg_T *eap UNUSED)
1316{
1317 uc_clear(&ucmds);
Bram Moolenaare5c83282019-05-03 23:15:37 +02001318 if (curbuf != NULL)
1319 uc_clear(&curbuf->b_ucmds);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001320}
1321
1322/*
Bram Moolenaarcf2594f2022-11-13 23:30:06 +00001323 * If ucmd_locked is set give an error and return TRUE.
1324 * Otherwise return FALSE.
1325 */
1326 static int
1327is_ucmd_locked(void)
1328{
1329 if (ucmd_locked > 0)
1330 {
1331 emsg(_(e_cannot_change_user_commands_while_listing));
1332 return TRUE;
1333 }
1334 return FALSE;
1335}
1336
1337/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001338 * Clear all user commands for "gap".
1339 */
1340 void
1341uc_clear(garray_T *gap)
1342{
1343 int i;
1344 ucmd_T *cmd;
1345
Bram Moolenaarcf2594f2022-11-13 23:30:06 +00001346 if (is_ucmd_locked())
1347 return;
1348
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001349 for (i = 0; i < gap->ga_len; ++i)
1350 {
1351 cmd = USER_CMD_GA(gap, i);
1352 vim_free(cmd->uc_name);
John Marriott9e795852024-08-20 20:57:23 +02001353 cmd->uc_namelen = 0;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001354 vim_free(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001355# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001356 vim_free(cmd->uc_compl_arg);
1357# endif
1358 }
1359 ga_clear(gap);
1360}
1361
1362/*
1363 * ":delcommand" implementation
1364 */
1365 void
1366ex_delcommand(exarg_T *eap)
1367{
1368 int i = 0;
1369 ucmd_T *cmd = NULL;
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001370 int res = -1;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001371 garray_T *gap;
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001372 char_u *arg = eap->arg;
1373 int buffer_only = FALSE;
1374
1375 if (STRNCMP(arg, "-buffer", 7) == 0 && VIM_ISWHITE(arg[7]))
1376 {
1377 buffer_only = TRUE;
1378 arg = skipwhite(arg + 7);
1379 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001380
1381 gap = &curbuf->b_ucmds;
1382 for (;;)
1383 {
1384 for (i = 0; i < gap->ga_len; ++i)
1385 {
1386 cmd = USER_CMD_GA(gap, i);
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001387 res = STRCMP(arg, cmd->uc_name);
1388 if (res <= 0)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001389 break;
1390 }
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001391 if (gap == &ucmds || res == 0 || buffer_only)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001392 break;
1393 gap = &ucmds;
1394 }
1395
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001396 if (res != 0)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001397 {
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001398 semsg(_(buffer_only
1399 ? e_no_such_user_defined_command_in_current_buffer_str
1400 : e_no_such_user_defined_command_str), arg);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001401 return;
1402 }
1403
Bram Moolenaarcf2594f2022-11-13 23:30:06 +00001404 if (is_ucmd_locked())
1405 return;
1406
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001407 vim_free(cmd->uc_name);
1408 vim_free(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001409# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001410 vim_free(cmd->uc_compl_arg);
1411# endif
1412
1413 --gap->ga_len;
1414
1415 if (i < gap->ga_len)
1416 mch_memmove(cmd, cmd + 1, (gap->ga_len - i) * sizeof(ucmd_T));
1417}
1418
1419/*
1420 * Split and quote args for <f-args>.
1421 */
1422 static char_u *
1423uc_split_args(char_u *arg, size_t *lenp)
1424{
1425 char_u *buf;
1426 char_u *p;
1427 char_u *q;
1428 int len;
1429
1430 // Precalculate length
1431 p = arg;
1432 len = 2; // Initial and final quotes
1433
1434 while (*p)
1435 {
1436 if (p[0] == '\\' && p[1] == '\\')
1437 {
1438 len += 2;
1439 p += 2;
1440 }
1441 else if (p[0] == '\\' && VIM_ISWHITE(p[1]))
1442 {
1443 len += 1;
1444 p += 2;
1445 }
1446 else if (*p == '\\' || *p == '"')
1447 {
1448 len += 2;
1449 p += 1;
1450 }
1451 else if (VIM_ISWHITE(*p))
1452 {
1453 p = skipwhite(p);
1454 if (*p == NUL)
1455 break;
Bram Moolenaar20d89e02020-10-20 23:11:33 +02001456 len += 4; // ", "
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001457 }
1458 else
1459 {
1460 int charlen = (*mb_ptr2len)(p);
1461
1462 len += charlen;
1463 p += charlen;
1464 }
1465 }
1466
1467 buf = alloc(len + 1);
1468 if (buf == NULL)
1469 {
1470 *lenp = 0;
1471 return buf;
1472 }
1473
1474 p = arg;
1475 q = buf;
1476 *q++ = '"';
1477 while (*p)
1478 {
1479 if (p[0] == '\\' && p[1] == '\\')
1480 {
1481 *q++ = '\\';
1482 *q++ = '\\';
1483 p += 2;
1484 }
1485 else if (p[0] == '\\' && VIM_ISWHITE(p[1]))
1486 {
1487 *q++ = p[1];
1488 p += 2;
1489 }
1490 else if (*p == '\\' || *p == '"')
1491 {
1492 *q++ = '\\';
1493 *q++ = *p++;
1494 }
1495 else if (VIM_ISWHITE(*p))
1496 {
1497 p = skipwhite(p);
1498 if (*p == NUL)
1499 break;
1500 *q++ = '"';
1501 *q++ = ',';
Bram Moolenaar20d89e02020-10-20 23:11:33 +02001502 *q++ = ' ';
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001503 *q++ = '"';
1504 }
1505 else
1506 {
1507 MB_COPY_CHAR(p, q);
1508 }
1509 }
1510 *q++ = '"';
John Marriott9e795852024-08-20 20:57:23 +02001511 *q = NUL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001512
1513 *lenp = len;
1514 return buf;
1515}
1516
1517 static size_t
John Marriott9e795852024-08-20 20:57:23 +02001518add_cmd_modifier(char_u *buf, size_t buflen, char *mod_str, size_t mod_strlen, int *multi_mods)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001519{
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001520 if (buf != NULL)
1521 {
1522 if (*multi_mods)
John Marriott9e795852024-08-20 20:57:23 +02001523 {
1524 STRCPY(buf + buflen, " "); // the separating space
1525 ++buflen;
1526 }
1527 STRCPY(buf + buflen, mod_str);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001528 }
1529
John Marriott9e795852024-08-20 20:57:23 +02001530 if (*multi_mods)
1531 ++mod_strlen; // +1 for the separating space
1532 else
1533 *multi_mods = 1;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001534
John Marriott9e795852024-08-20 20:57:23 +02001535 return mod_strlen;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001536}
1537
1538/*
Bram Moolenaar02194d22020-10-24 23:08:38 +02001539 * Add modifiers from "cmod->cmod_split" to "buf". Set "multi_mods" when one
Bram Moolenaare1004402020-10-24 20:49:43 +02001540 * was added. Return the number of bytes added.
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001541 */
1542 size_t
dundargocc57b5bc2022-11-02 13:30:51 +00001543add_win_cmd_modifiers(char_u *buf, cmdmod_T *cmod, int *multi_mods)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001544{
John Marriott9e795852024-08-20 20:57:23 +02001545 size_t buflen = 0;
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001546
1547 // :aboveleft and :leftabove
Bram Moolenaar02194d22020-10-24 23:08:38 +02001548 if (cmod->cmod_split & WSP_ABOVE)
John Marriott9e795852024-08-20 20:57:23 +02001549 buflen += add_cmd_modifier(buf, buflen, "aboveleft", STRLEN_LITERAL("aboveleft"), multi_mods);
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001550 // :belowright and :rightbelow
Bram Moolenaar02194d22020-10-24 23:08:38 +02001551 if (cmod->cmod_split & WSP_BELOW)
John Marriott9e795852024-08-20 20:57:23 +02001552 buflen += add_cmd_modifier(buf, buflen, "belowright", STRLEN_LITERAL("belowright"), multi_mods);
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001553 // :botright
Bram Moolenaar02194d22020-10-24 23:08:38 +02001554 if (cmod->cmod_split & WSP_BOT)
John Marriott9e795852024-08-20 20:57:23 +02001555 buflen += add_cmd_modifier(buf, buflen, "botright", STRLEN_LITERAL("botright"), multi_mods);
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001556
1557 // :tab
Bram Moolenaar02194d22020-10-24 23:08:38 +02001558 if (cmod->cmod_tab > 0)
zeertzjq208567e2022-10-18 13:11:21 +01001559 {
1560 int tabnr = cmod->cmod_tab - 1;
1561
1562 if (tabnr == tabpage_index(curtab))
1563 {
1564 // For compatibility, don't add a tabpage number if it is the same
1565 // as the default number for :tab.
John Marriott9e795852024-08-20 20:57:23 +02001566 buflen += add_cmd_modifier(buf, buflen, "tab", STRLEN_LITERAL("tab"), multi_mods);
zeertzjq208567e2022-10-18 13:11:21 +01001567 }
1568 else
1569 {
1570 char tab_buf[NUMBUFLEN + 3];
John Marriott9e795852024-08-20 20:57:23 +02001571 size_t tab_buflen;
zeertzjq208567e2022-10-18 13:11:21 +01001572
John Marriott9e795852024-08-20 20:57:23 +02001573 tab_buflen = vim_snprintf(tab_buf, sizeof(tab_buf), "%dtab", tabnr);
1574 buflen += add_cmd_modifier(buf, buflen, tab_buf, tab_buflen, multi_mods);
zeertzjq208567e2022-10-18 13:11:21 +01001575 }
1576 }
1577
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001578 // :topleft
Bram Moolenaar02194d22020-10-24 23:08:38 +02001579 if (cmod->cmod_split & WSP_TOP)
John Marriott9e795852024-08-20 20:57:23 +02001580 buflen += add_cmd_modifier(buf, buflen, "topleft", STRLEN_LITERAL("topleft"), multi_mods);
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001581 // :vertical
Bram Moolenaar02194d22020-10-24 23:08:38 +02001582 if (cmod->cmod_split & WSP_VERT)
John Marriott9e795852024-08-20 20:57:23 +02001583 buflen += add_cmd_modifier(buf, buflen, "vertical", STRLEN_LITERAL("vertical"), multi_mods);
zeertzjqd3de1782022-09-01 12:58:52 +01001584 // :horizontal
1585 if (cmod->cmod_split & WSP_HOR)
John Marriott9e795852024-08-20 20:57:23 +02001586 buflen += add_cmd_modifier(buf, buflen, "horizontal", STRLEN_LITERAL("horizontal"), multi_mods);
1587
1588 return buflen;
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001589}
1590
1591/*
Bram Moolenaar02194d22020-10-24 23:08:38 +02001592 * Generate text for the "cmod" command modifiers.
1593 * If "buf" is NULL just return the length.
1594 */
Bram Moolenaara360dbe2020-10-26 18:46:53 +01001595 size_t
Bram Moolenaar02194d22020-10-24 23:08:38 +02001596produce_cmdmods(char_u *buf, cmdmod_T *cmod, int quote)
1597{
John Marriott9e795852024-08-20 20:57:23 +02001598 size_t buflen = 0;
Bram Moolenaar02194d22020-10-24 23:08:38 +02001599 int multi_mods = 0;
1600 int i;
John Marriott9e795852024-08-20 20:57:23 +02001601 static keyvalue_T mod_entry_tab[] =
1602 {
Bram Moolenaar02194d22020-10-24 23:08:38 +02001603#ifdef FEAT_BROWSE_CMD
John Marriott9e795852024-08-20 20:57:23 +02001604 KEYVALUE_ENTRY(CMOD_BROWSE, "browse"),
Bram Moolenaar02194d22020-10-24 23:08:38 +02001605#endif
1606#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
John Marriott9e795852024-08-20 20:57:23 +02001607 KEYVALUE_ENTRY(CMOD_CONFIRM, "confirm"),
Bram Moolenaar02194d22020-10-24 23:08:38 +02001608#endif
John Marriott9e795852024-08-20 20:57:23 +02001609 KEYVALUE_ENTRY(CMOD_HIDE, "hide"),
1610 KEYVALUE_ENTRY(CMOD_KEEPALT, "keepalt"),
1611 KEYVALUE_ENTRY(CMOD_KEEPJUMPS, "keepjumps"),
1612 KEYVALUE_ENTRY(CMOD_KEEPMARKS, "keepmarks"),
1613 KEYVALUE_ENTRY(CMOD_KEEPPATTERNS, "keeppatterns"),
1614 KEYVALUE_ENTRY(CMOD_LOCKMARKS, "lockmarks"),
1615 KEYVALUE_ENTRY(CMOD_NOSWAPFILE, "noswapfile"),
1616 KEYVALUE_ENTRY(CMOD_UNSILENT, "unsilent"),
1617 KEYVALUE_ENTRY(CMOD_NOAUTOCMD, "noautocmd"),
Bram Moolenaar02194d22020-10-24 23:08:38 +02001618#ifdef HAVE_SANDBOX
John Marriott9e795852024-08-20 20:57:23 +02001619 KEYVALUE_ENTRY(CMOD_SANDBOX, "sandbox"),
Bram Moolenaar02194d22020-10-24 23:08:38 +02001620#endif
John Marriott9e795852024-08-20 20:57:23 +02001621 KEYVALUE_ENTRY(CMOD_LEGACY, "legacy")
Bram Moolenaar02194d22020-10-24 23:08:38 +02001622 };
1623
John Marriott9e795852024-08-20 20:57:23 +02001624 if (quote)
Bram Moolenaar02194d22020-10-24 23:08:38 +02001625 {
John Marriott9e795852024-08-20 20:57:23 +02001626 ++buflen;
1627 if (buf != NULL)
1628 {
1629 *buf = '"';
1630 *(buf + buflen) = NUL;
1631 }
Bram Moolenaar02194d22020-10-24 23:08:38 +02001632 }
John Marriott9e795852024-08-20 20:57:23 +02001633 else
1634 if (buf != NULL)
1635 *buf = NUL;
Bram Moolenaar02194d22020-10-24 23:08:38 +02001636
1637 // the modifiers that are simple flags
John Marriott9e795852024-08-20 20:57:23 +02001638 for (i = 0; i < (int)ARRAY_LENGTH(mod_entry_tab); ++i)
1639 if (cmod->cmod_flags & mod_entry_tab[i].key)
John Marriott8d4477e2024-11-02 15:59:01 +01001640 buflen += add_cmd_modifier(buf, buflen,
1641 (char *)mod_entry_tab[i].value.string,
1642 mod_entry_tab[i].value.length, &multi_mods);
Bram Moolenaar02194d22020-10-24 23:08:38 +02001643
1644 // :silent
1645 if (cmod->cmod_flags & CMOD_SILENT)
John Marriott9e795852024-08-20 20:57:23 +02001646 {
1647 if (cmod->cmod_flags & CMOD_ERRSILENT)
John Marriott8d4477e2024-11-02 15:59:01 +01001648 buflen += add_cmd_modifier(buf, buflen, "silent!",
1649 STRLEN_LITERAL("silent!"), &multi_mods);
John Marriott9e795852024-08-20 20:57:23 +02001650 else
John Marriott8d4477e2024-11-02 15:59:01 +01001651 buflen += add_cmd_modifier(buf, buflen, "silent",
1652 STRLEN_LITERAL("silent"), &multi_mods);
John Marriott9e795852024-08-20 20:57:23 +02001653 }
1654
Bram Moolenaar02194d22020-10-24 23:08:38 +02001655 // :verbose
zeertzjq9359e8a2022-07-03 13:16:09 +01001656 if (cmod->cmod_verbose > 0)
1657 {
1658 int verbose_value = cmod->cmod_verbose - 1;
1659
1660 if (verbose_value == 1)
John Marriott9e795852024-08-20 20:57:23 +02001661 buflen += add_cmd_modifier(buf, buflen, "verbose", STRLEN_LITERAL("verbose"), &multi_mods);
zeertzjq9359e8a2022-07-03 13:16:09 +01001662 else
1663 {
1664 char verbose_buf[NUMBUFLEN];
John Marriott9e795852024-08-20 20:57:23 +02001665 size_t verbose_buflen;
zeertzjq9359e8a2022-07-03 13:16:09 +01001666
John Marriott9e795852024-08-20 20:57:23 +02001667 verbose_buflen = vim_snprintf(verbose_buf, sizeof(verbose_buf), "%dverbose", verbose_value);
1668 buflen += add_cmd_modifier(buf, buflen, verbose_buf, verbose_buflen, &multi_mods);
zeertzjq9359e8a2022-07-03 13:16:09 +01001669 }
1670 }
zeertzjq9359e8a2022-07-03 13:16:09 +01001671
John Marriott9e795852024-08-20 20:57:23 +02001672 // flags from cmod->cmod_split
1673 buflen += add_win_cmd_modifiers((buf == NULL) ? NULL : buf + buflen, cmod, &multi_mods);
1674
1675 if (quote)
Bram Moolenaar02194d22020-10-24 23:08:38 +02001676 {
John Marriott9e795852024-08-20 20:57:23 +02001677 if (buf == NULL)
1678 ++buflen;
1679 else
1680 {
1681 *(buf + buflen) = '"';
1682 ++buflen;
1683 *(buf + buflen) = NUL;
1684 }
Bram Moolenaar02194d22020-10-24 23:08:38 +02001685 }
John Marriott9e795852024-08-20 20:57:23 +02001686
1687 return buflen;
Bram Moolenaar02194d22020-10-24 23:08:38 +02001688}
1689
1690/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001691 * Check for a <> code in a user command.
1692 * "code" points to the '<'. "len" the length of the <> (inclusive).
1693 * "buf" is where the result is to be added.
1694 * "split_buf" points to a buffer used for splitting, caller should free it.
1695 * "split_len" is the length of what "split_buf" contains.
1696 * Returns the length of the replacement, which has been added to "buf".
1697 * Returns -1 if there was no match, and only the "<" has been copied.
1698 */
1699 static size_t
1700uc_check_code(
1701 char_u *code,
1702 size_t len,
1703 char_u *buf,
1704 ucmd_T *cmd, // the user command we're expanding
1705 exarg_T *eap, // ex arguments
1706 char_u **split_buf,
1707 size_t *split_len)
1708{
1709 size_t result = 0;
1710 char_u *p = code + 1;
1711 size_t l = len - 2;
1712 int quote = 0;
1713 enum {
1714 ct_ARGS,
1715 ct_BANG,
1716 ct_COUNT,
1717 ct_LINE1,
1718 ct_LINE2,
1719 ct_RANGE,
1720 ct_MODS,
1721 ct_REGISTER,
1722 ct_LT,
1723 ct_NONE
1724 } type = ct_NONE;
1725
1726 if ((vim_strchr((char_u *)"qQfF", *p) != NULL) && p[1] == '-')
1727 {
1728 quote = (*p == 'q' || *p == 'Q') ? 1 : 2;
1729 p += 2;
1730 l -= 2;
1731 }
1732
1733 ++l;
1734 if (l <= 1)
1735 type = ct_NONE;
1736 else if (STRNICMP(p, "args>", l) == 0)
1737 type = ct_ARGS;
1738 else if (STRNICMP(p, "bang>", l) == 0)
1739 type = ct_BANG;
1740 else if (STRNICMP(p, "count>", l) == 0)
1741 type = ct_COUNT;
1742 else if (STRNICMP(p, "line1>", l) == 0)
1743 type = ct_LINE1;
1744 else if (STRNICMP(p, "line2>", l) == 0)
1745 type = ct_LINE2;
1746 else if (STRNICMP(p, "range>", l) == 0)
1747 type = ct_RANGE;
1748 else if (STRNICMP(p, "lt>", l) == 0)
1749 type = ct_LT;
1750 else if (STRNICMP(p, "reg>", l) == 0 || STRNICMP(p, "register>", l) == 0)
1751 type = ct_REGISTER;
1752 else if (STRNICMP(p, "mods>", l) == 0)
1753 type = ct_MODS;
1754
1755 switch (type)
1756 {
1757 case ct_ARGS:
1758 // Simple case first
1759 if (*eap->arg == NUL)
1760 {
1761 if (quote == 1)
1762 {
1763 result = 2;
1764 if (buf != NULL)
1765 STRCPY(buf, "''");
1766 }
1767 else
1768 result = 0;
1769 break;
1770 }
1771
1772 // When specified there is a single argument don't split it.
1773 // Works for ":Cmd %" when % is "a b c".
Bram Moolenaar8071cb22019-07-12 17:58:01 +02001774 if ((eap->argt & EX_NOSPC) && quote == 2)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001775 quote = 1;
1776
1777 switch (quote)
1778 {
1779 case 0: // No quoting, no splitting
1780 result = STRLEN(eap->arg);
1781 if (buf != NULL)
1782 STRCPY(buf, eap->arg);
1783 break;
1784 case 1: // Quote, but don't split
1785 result = STRLEN(eap->arg) + 2;
1786 for (p = eap->arg; *p; ++p)
1787 {
1788 if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2)
1789 // DBCS can contain \ in a trail byte, skip the
1790 // double-byte character.
1791 ++p;
1792 else
1793 if (*p == '\\' || *p == '"')
1794 ++result;
1795 }
1796
1797 if (buf != NULL)
1798 {
1799 *buf++ = '"';
1800 for (p = eap->arg; *p; ++p)
1801 {
1802 if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2)
1803 // DBCS can contain \ in a trail byte, copy the
1804 // double-byte character to avoid escaping.
1805 *buf++ = *p++;
1806 else
1807 if (*p == '\\' || *p == '"')
1808 *buf++ = '\\';
1809 *buf++ = *p;
1810 }
1811 *buf = '"';
1812 }
1813
1814 break;
1815 case 2: // Quote and split (<f-args>)
1816 // This is hard, so only do it once, and cache the result
1817 if (*split_buf == NULL)
1818 *split_buf = uc_split_args(eap->arg, split_len);
1819
1820 result = *split_len;
1821 if (buf != NULL && result != 0)
1822 STRCPY(buf, *split_buf);
1823
1824 break;
1825 }
1826 break;
1827
1828 case ct_BANG:
1829 result = eap->forceit ? 1 : 0;
1830 if (quote)
1831 result += 2;
1832 if (buf != NULL)
1833 {
1834 if (quote)
1835 *buf++ = '"';
1836 if (eap->forceit)
1837 *buf++ = '!';
1838 if (quote)
1839 *buf = '"';
1840 }
1841 break;
1842
1843 case ct_LINE1:
1844 case ct_LINE2:
1845 case ct_RANGE:
1846 case ct_COUNT:
1847 {
John Marriott9e795852024-08-20 20:57:23 +02001848 char num_buf[NUMBUFLEN];
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001849 long num = (type == ct_LINE1) ? eap->line1 :
1850 (type == ct_LINE2) ? eap->line2 :
1851 (type == ct_RANGE) ? eap->addr_count :
1852 (eap->addr_count > 0) ? eap->line2 : cmd->uc_def;
1853 size_t num_len;
1854
John Marriott9e795852024-08-20 20:57:23 +02001855 num_len = vim_snprintf(num_buf, sizeof(num_buf), "%ld", num);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001856 result = num_len;
1857
1858 if (quote)
1859 result += 2;
1860
1861 if (buf != NULL)
1862 {
1863 if (quote)
1864 *buf++ = '"';
1865 STRCPY(buf, num_buf);
1866 buf += num_len;
1867 if (quote)
1868 *buf = '"';
1869 }
1870
1871 break;
1872 }
1873
1874 case ct_MODS:
1875 {
Bram Moolenaar02194d22020-10-24 23:08:38 +02001876 result = produce_cmdmods(buf, &cmdmod, quote);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001877 break;
1878 }
1879
1880 case ct_REGISTER:
1881 result = eap->regname ? 1 : 0;
1882 if (quote)
1883 result += 2;
1884 if (buf != NULL)
1885 {
1886 if (quote)
1887 *buf++ = '\'';
1888 if (eap->regname)
1889 *buf++ = eap->regname;
1890 if (quote)
1891 *buf = '\'';
1892 }
1893 break;
1894
1895 case ct_LT:
1896 result = 1;
1897 if (buf != NULL)
1898 *buf = '<';
1899 break;
1900
1901 default:
1902 // Not recognized: just copy the '<' and return -1.
1903 result = (size_t)-1;
1904 if (buf != NULL)
1905 *buf = '<';
1906 break;
1907 }
1908
1909 return result;
1910}
1911
1912/*
1913 * Execute a user defined command.
1914 */
1915 void
1916do_ucmd(exarg_T *eap)
1917{
1918 char_u *buf;
1919 char_u *p;
1920 char_u *q;
1921
1922 char_u *start;
1923 char_u *end = NULL;
1924 char_u *ksp;
1925 size_t len, totlen;
1926
1927 size_t split_len = 0;
1928 char_u *split_buf = NULL;
1929 ucmd_T *cmd;
Bram Moolenaar205f29c2021-12-10 21:46:09 +00001930 sctx_T save_current_sctx;
1931 int restore_current_sctx = FALSE;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00001932#ifdef FEAT_EVAL
1933 int restore_script_version = 0;
1934#endif
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001935
1936 if (eap->cmdidx == CMD_USER)
1937 cmd = USER_CMD(eap->useridx);
1938 else
zeertzjqb444ee72023-02-20 15:25:13 +00001939 cmd = USER_CMD_GA(&prevwin_curwin()->w_buffer->b_ucmds, eap->useridx);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001940
1941 /*
1942 * Replace <> in the command by the arguments.
1943 * First round: "buf" is NULL, compute length, allocate "buf".
1944 * Second round: copy result into "buf".
1945 */
1946 buf = NULL;
1947 for (;;)
1948 {
1949 p = cmd->uc_rep; // source
1950 q = buf; // destination
1951 totlen = 0;
1952
1953 for (;;)
1954 {
1955 start = vim_strchr(p, '<');
1956 if (start != NULL)
1957 end = vim_strchr(start + 1, '>');
1958 if (buf != NULL)
1959 {
1960 for (ksp = p; *ksp != NUL && *ksp != K_SPECIAL; ++ksp)
1961 ;
1962 if (*ksp == K_SPECIAL
1963 && (start == NULL || ksp < start || end == NULL)
1964 && ((ksp[1] == KS_SPECIAL && ksp[2] == KE_FILLER)
1965# ifdef FEAT_GUI
1966 || (ksp[1] == KS_EXTRA && ksp[2] == (int)KE_CSI)
1967# endif
1968 ))
1969 {
1970 // K_SPECIAL has been put in the buffer as K_SPECIAL
1971 // KS_SPECIAL KE_FILLER, like for mappings, but
1972 // do_cmdline() doesn't handle that, so convert it back.
1973 // Also change K_SPECIAL KS_EXTRA KE_CSI into CSI.
1974 len = ksp - p;
1975 if (len > 0)
1976 {
1977 mch_memmove(q, p, len);
1978 q += len;
1979 }
1980 *q++ = ksp[1] == KS_SPECIAL ? K_SPECIAL : CSI;
1981 p = ksp + 3;
1982 continue;
1983 }
1984 }
1985
1986 // break if no <item> is found
1987 if (start == NULL || end == NULL)
1988 break;
1989
1990 // Include the '>'
1991 ++end;
1992
1993 // Take everything up to the '<'
1994 len = start - p;
1995 if (buf == NULL)
1996 totlen += len;
1997 else
1998 {
1999 mch_memmove(q, p, len);
2000 q += len;
2001 }
2002
2003 len = uc_check_code(start, end - start, q, cmd, eap,
2004 &split_buf, &split_len);
2005 if (len == (size_t)-1)
2006 {
2007 // no match, continue after '<'
2008 p = start + 1;
2009 len = 1;
2010 }
2011 else
2012 p = end;
2013 if (buf == NULL)
2014 totlen += len;
2015 else
2016 q += len;
2017 }
2018 if (buf != NULL) // second time here, finished
2019 {
2020 STRCPY(q, p);
2021 break;
2022 }
2023
2024 totlen += STRLEN(p); // Add on the trailing characters
Bram Moolenaar964b3742019-05-24 18:54:09 +02002025 buf = alloc(totlen + 1);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02002026 if (buf == NULL)
2027 {
2028 vim_free(split_buf);
2029 return;
2030 }
2031 }
2032
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00002033 if ((cmd->uc_argt & EX_KEEPSCRIPT) == 0)
2034 {
Bram Moolenaar205f29c2021-12-10 21:46:09 +00002035 restore_current_sctx = TRUE;
2036 save_current_sctx = current_sctx;
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00002037 current_sctx.sc_version = cmd->uc_script_ctx.sc_version;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02002038#ifdef FEAT_EVAL
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00002039 current_sctx.sc_sid = cmd->uc_script_ctx.sc_sid;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00002040 if (cmd->uc_flags & UC_VIM9)
2041 {
2042 // In a {} block variables use Vim9 script rules, even in a legacy
2043 // script.
2044 restore_script_version =
2045 SCRIPT_ITEM(current_sctx.sc_sid)->sn_version;
2046 SCRIPT_ITEM(current_sctx.sc_sid)->sn_version = SCRIPT_VERSION_VIM9;
2047 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02002048#endif
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00002049 }
Bram Moolenaar205f29c2021-12-10 21:46:09 +00002050
Zoltan Arpadffy6fdb6282023-12-19 20:53:07 +01002051 (void)do_cmdline(buf, eap->ea_getline, eap->cookie,
Bram Moolenaarac9fb182019-04-27 13:04:13 +02002052 DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED);
Bram Moolenaar205f29c2021-12-10 21:46:09 +00002053
2054 // Careful: Do not use "cmd" here, it may have become invalid if a user
2055 // command was added.
2056 if (restore_current_sctx)
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00002057 {
2058#ifdef FEAT_EVAL
2059 if (restore_script_version != 0)
2060 SCRIPT_ITEM(current_sctx.sc_sid)->sn_version =
2061 restore_script_version;
2062#endif
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00002063 current_sctx = save_current_sctx;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00002064 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02002065 vim_free(buf);
2066 vim_free(split_buf);
2067}