1 // This is an open source non-commercial project. Dear PVS-Studio, please check
2 // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
3
4 /*
5 * ex_getln.c: Functions for entering and editing an Ex command line.
6 */
7
8 #include <assert.h>
9 #include <inttypes.h>
10 #include <stdbool.h>
11 #include <stdlib.h>
12 #include <string.h>
13
14 #include "nvim/api/private/helpers.h"
15 #include "nvim/arabic.h"
16 #include "nvim/ascii.h"
17 #include "nvim/assert.h"
18 #include "nvim/buffer.h"
19 #include "nvim/charset.h"
20 #include "nvim/cursor.h"
21 #include "nvim/cursor_shape.h"
22 #include "nvim/digraph.h"
23 #include "nvim/edit.h"
24 #include "nvim/eval.h"
25 #include "nvim/eval/userfunc.h"
26 #include "nvim/event/loop.h"
27 #include "nvim/ex_cmds.h"
28 #include "nvim/ex_cmds2.h"
29 #include "nvim/ex_docmd.h"
30 #include "nvim/ex_eval.h"
31 #include "nvim/ex_getln.h"
32 #include "nvim/fileio.h"
33 #include "nvim/func_attr.h"
34 #include "nvim/garray.h"
35 #include "nvim/getchar.h"
36 #include "nvim/highlight.h"
37 #include "nvim/highlight_defs.h"
38 #include "nvim/if_cscope.h"
39 #include "nvim/indent.h"
40 #include "nvim/keymap.h"
41 #include "nvim/lib/kvec.h"
42 #include "nvim/log.h"
43 #include "nvim/lua/executor.h"
44 #include "nvim/main.h"
45 #include "nvim/mark.h"
46 #include "nvim/mbyte.h"
47 #include "nvim/memline.h"
48 #include "nvim/memory.h"
49 #include "nvim/menu.h"
50 #include "nvim/message.h"
51 #include "nvim/misc1.h"
52 #include "nvim/mouse.h"
53 #include "nvim/move.h"
54 #include "nvim/ops.h"
55 #include "nvim/option.h"
56 #include "nvim/os/input.h"
57 #include "nvim/os/os.h"
58 #include "nvim/os/time.h"
59 #include "nvim/os_unix.h"
60 #include "nvim/path.h"
61 #include "nvim/popupmnu.h"
62 #include "nvim/regexp.h"
63 #include "nvim/screen.h"
64 #include "nvim/search.h"
65 #include "nvim/sign.h"
66 #include "nvim/state.h"
67 #include "nvim/strings.h"
68 #include "nvim/syntax.h"
69 #include "nvim/tag.h"
70 #include "nvim/ui.h"
71 #include "nvim/vim.h"
72 #include "nvim/viml/parser/expressions.h"
73 #include "nvim/viml/parser/parser.h"
74 #include "nvim/window.h"
75
76 /// Command-line colors: one chunk
77 ///
78 /// Defines a region which has the same highlighting.
79 typedef struct {
80 int start; ///< Colored chunk start.
81 int end; ///< Colored chunk end (exclusive, > start).
82 int attr; ///< Highlight attr.
83 } CmdlineColorChunk;
84
85 /// Command-line colors
86 ///
87 /// Holds data about all colors.
88 typedef kvec_t(CmdlineColorChunk) CmdlineColors;
89
90 /// Command-line coloring
91 ///
92 /// Holds both what are the colors and what have been colored. Latter is used to
93 /// suppress unnecessary calls to coloring callbacks.
94 typedef struct {
95 unsigned prompt_id; ///< ID of the prompt which was colored last.
96 char *cmdbuff; ///< What exactly was colored last time or NULL.
97 CmdlineColors colors; ///< Last colors.
98 } ColoredCmdline;
99
100 /// Keeps track how much state must be sent to external ui.
101 typedef enum {
102 kCmdRedrawNone,
103 kCmdRedrawPos,
104 kCmdRedrawAll,
105 } CmdRedraw;
106
107 /*
108 * Variables shared between getcmdline(), redrawcmdline() and others.
109 * These need to be saved when using CTRL-R |, that's why they are in a
110 * structure.
111 */
112 struct cmdline_info {
113 char_u *cmdbuff; // pointer to command line buffer
114 int cmdbufflen; // length of cmdbuff
115 int cmdlen; // number of chars in command line
116 int cmdpos; // current cursor position
117 int cmdspos; // cursor column on screen
118 int cmdfirstc; // ':', '/', '?', '=', '>' or NUL
119 int cmdindent; // number of spaces before cmdline
120 char_u *cmdprompt; // message in front of cmdline
121 int cmdattr; // attributes for prompt
122 int overstrike; // Typing mode on the command line. Shared by
123 // getcmdline() and put_on_cmdline().
124 expand_T *xpc; // struct being used for expansion, xp_pattern
125 // may point into cmdbuff
126 int xp_context; // type of expansion
127 char_u *xp_arg; // user-defined expansion arg
128 int input_fn; // when TRUE Invoked for input() function
129 unsigned prompt_id; ///< Prompt number, used to disable coloring on errors.
130 Callback highlight_callback; ///< Callback used for coloring user input.
131 ColoredCmdline last_colors; ///< Last cmdline colors
132 int level; // current cmdline level
133 struct cmdline_info *prev_ccline; ///< pointer to saved cmdline state
134 char special_char; ///< last putcmdline char (used for redraws)
135 bool special_shift; ///< shift of last putcmdline char
136 CmdRedraw redraw_state; ///< needed redraw for external cmdline
137 };
138 /// Last value of prompt_id, incremented when doing new prompt
139 static unsigned last_prompt_id = 0;
140
141 // Struct to store the viewstate during 'incsearch' highlighting.
142 typedef struct {
143 colnr_T vs_curswant;
144 colnr_T vs_leftcol;
145 linenr_T vs_topline;
146 int vs_topfill;
147 linenr_T vs_botline;
148 int vs_empty_rows;
149 } viewstate_T;
150
151 // Struct to store the state of 'incsearch' highlighting.
152 typedef struct {
153 pos_T search_start; // where 'incsearch' starts searching
154 pos_T save_cursor;
155 viewstate_T init_viewstate;
156 viewstate_T old_viewstate;
157 pos_T match_start;
158 pos_T match_end;
159 bool did_incsearch;
160 bool incsearch_postponed;
161 int magic_save;
162 } incsearch_state_T;
163
164 typedef struct command_line_state {
165 VimState state;
166 int firstc;
167 long count;
168 int indent;
169 int c;
170 int gotesc; // TRUE when <ESC> just typed
171 int do_abbr; // when TRUE check for abbr.
172 char_u *lookfor; // string to match
173 int hiscnt; // current history line in use
174 int save_hiscnt; // history line before attempting
175 // to jump to next match
176 int histype; // history type to be used
177 incsearch_state_T is_state;
178 int did_wild_list; // did wild_list() recently
179 int wim_index; // index in wim_flags[]
180 int res;
181 int save_msg_scroll;
182 int save_State; // remember State when called
183 char_u *save_p_icm;
184 int some_key_typed; // one of the keys was typed
185 // mouse drag and release events are ignored, unless they are
186 // preceded with a mouse down event
187 int ignore_drag_release;
188 int break_ctrl_c;
189 expand_T xpc;
190 long *b_im_ptr;
191 } CommandLineState;
192
193 typedef struct cmdline_info CmdlineInfo;
194
195 /* The current cmdline_info. It is initialized in getcmdline() and after that
196 * used by other functions. When invoking getcmdline() recursively it needs
197 * to be saved with save_cmdline() and restored with restore_cmdline().
198 * TODO: make it local to getcmdline() and pass it around. */
199 static struct cmdline_info ccline;
200
201 static int cmd_showtail; // Only show path tail in lists ?
202
203 static int new_cmdpos; // position set by set_cmdline_pos()
204
205 /// currently displayed block of context
206 static Array cmdline_block = ARRAY_DICT_INIT;
207
208 /*
209 * Type used by call_user_expand_func
210 */
211 typedef void *(*user_expand_func_T)(const char_u *, int, typval_T *);
212
213 static histentry_T *(history[HIST_COUNT]) = { NULL, NULL, NULL, NULL, NULL };
214 static int hisidx[HIST_COUNT] = { -1, -1, -1, -1, -1 }; // lastused entry
215 static int hisnum[HIST_COUNT] = { 0, 0, 0, 0, 0 };
216 // identifying (unique) number of newest history entry
217 static int hislen = 0; // actual length of history tables
218
219 /// Flag for command_line_handle_key to ignore <C-c>
220 ///
221 /// Used if it was received while processing highlight function in order for
222 /// user interrupting highlight function to not interrupt command-line.
223 static bool getln_interrupted_highlight = false;
224
225 // "compl_match_array" points the currently displayed list of entries in the
226 // popup menu. It is NULL when there is no popup menu.
227 static pumitem_T *compl_match_array = NULL;
228 static int compl_match_arraysize;
229 // First column in cmdline of the matched item for completion.
230 static int compl_startcol;
231 static int compl_selected;
232
233 /// |:checkhealth| completion items
234 ///
235 /// Regenerates on every new command line prompt, to accomodate changes on the
236 /// runtime files.
237 typedef struct {
238 garray_T names; // healthcheck names
239 unsigned last_gen; // last_prompt_id where names were generated
240 } CheckhealthComp;
241
242 /// Cookie used when converting filepath to name
243 struct healthchecks_cookie {
244 garray_T *names; // global healthchecks
245 bool is_lua; // true if the current entry is a Lua healthcheck
246 };
247
248 static CheckhealthComp healthchecks = { GA_INIT(sizeof(char_u *), 10), 0 };
249
250 #ifdef INCLUDE_GENERATED_DECLARATIONS
251 # include "ex_getln.c.generated.h"
252 #endif
253
254 static int cmd_hkmap = 0; // Hebrew mapping during command line
255
save_viewstate(viewstate_T * vs)256 static void save_viewstate(viewstate_T *vs)
257 FUNC_ATTR_NONNULL_ALL
258 {
259 vs->vs_curswant = curwin->w_curswant;
260 vs->vs_leftcol = curwin->w_leftcol;
261 vs->vs_topline = curwin->w_topline;
262 vs->vs_topfill = curwin->w_topfill;
263 vs->vs_botline = curwin->w_botline;
264 vs->vs_empty_rows = curwin->w_empty_rows;
265 }
266
restore_viewstate(viewstate_T * vs)267 static void restore_viewstate(viewstate_T *vs)
268 FUNC_ATTR_NONNULL_ALL
269 {
270 curwin->w_curswant = vs->vs_curswant;
271 curwin->w_leftcol = vs->vs_leftcol;
272 curwin->w_topline = vs->vs_topline;
273 curwin->w_topfill = vs->vs_topfill;
274 curwin->w_botline = vs->vs_botline;
275 curwin->w_empty_rows = vs->vs_empty_rows;
276 }
277
init_incsearch_state(incsearch_state_T * s)278 static void init_incsearch_state(incsearch_state_T *s)
279 {
280 s->match_start = curwin->w_cursor;
281 s->did_incsearch = false;
282 s->incsearch_postponed = false;
283 s->magic_save = p_magic;
284 clearpos(&s->match_end);
285 s->save_cursor = curwin->w_cursor; // may be restored later
286 s->search_start = curwin->w_cursor;
287 save_viewstate(&s->init_viewstate);
288 save_viewstate(&s->old_viewstate);
289 }
290
291 /// Completion for |:checkhealth| command.
292 ///
293 /// Given to ExpandGeneric() to obtain all available heathcheck names.
294 /// @param[in] idx Index of the healthcheck item.
295 /// @param[in] xp Not used.
get_healthcheck_names(expand_T * xp,int idx)296 static char_u *get_healthcheck_names(expand_T *xp, int idx)
297 {
298 // Generate the first time or on new prompt.
299 if (healthchecks.last_gen == 0 || healthchecks.last_gen != last_prompt_id) {
300 ga_clear_strings(&healthchecks.names);
301 char *patterns[3] = { "autoload/health/**.vim", "lua/**/**/health/init.lua", // NOLINT
302 "lua/**/**/health.lua" }; // NOLINT
303 for (int i = 0; i < 3; i++) {
304 struct healthchecks_cookie hcookie = { .names = &healthchecks.names, .is_lua = i != 0 };
305 do_in_runtimepath((char_u *)patterns[i], DIP_ALL, get_healthcheck_cb, &hcookie);
306
307 if (healthchecks.names.ga_len > 0) {
308 ga_remove_duplicate_strings(&healthchecks.names);
309 }
310 }
311 // Tracked to regenerate items on next prompt.
312 healthchecks.last_gen = last_prompt_id;
313 }
314 return idx <
315 (int)healthchecks.names.ga_len ? ((char_u **)(healthchecks.names.ga_data))[idx] : NULL;
316 }
317
318 /// Transform healthcheck file path into it's name.
319 ///
320 /// Used as a callback for do_in_runtimepath
321 /// @param[in] path Expanded path to a possible healthcheck.
322 /// @param[out] cookie Array where names will be inserted.
get_healthcheck_cb(char_u * path,void * cookie)323 static void get_healthcheck_cb(char_u *path, void *cookie)
324 {
325 if (path != NULL) {
326 struct healthchecks_cookie *hcookie = (struct healthchecks_cookie *)cookie;
327 char *pattern;
328 char *sub = "\\1";
329 char_u *res;
330
331 if (hcookie->is_lua) {
332 // Lua: transform "../lua/vim/lsp/health.lua" into "vim.lsp"
333 pattern = ".*lua[\\/]\\(.\\{-}\\)[\\/]health\\([\\/]init\\)\\?\\.lua$";
334 } else {
335 // Vim: transform "../autoload/health/provider.vim" into "provider"
336 pattern = ".*[\\/]\\([^\\/]*\\)\\.vim$";
337 }
338
339 res = do_string_sub(path, (char_u *)pattern, (char_u *)sub, NULL, (char_u *)"g");
340 if (hcookie->is_lua && res != NULL) {
341 // Replace slashes with dots as represented by the healthcheck plugin.
342 char_u *ares = do_string_sub(res, (char_u *)"[\\/]", (char_u *)".", NULL, (char_u *)"g");
343 xfree(res);
344 res = ares;
345 }
346
347 if (res != NULL) {
348 GA_APPEND(char_u *, hcookie->names, res);
349 }
350 }
351 }
352
353 // Return true when 'incsearch' highlighting is to be done.
354 // Sets search_first_line and search_last_line to the address range.
do_incsearch_highlighting(int firstc,int * search_delim,incsearch_state_T * s,int * skiplen,int * patlen)355 static bool do_incsearch_highlighting(int firstc, int *search_delim, incsearch_state_T *s,
356 int *skiplen, int *patlen)
357 FUNC_ATTR_NONNULL_ALL
358 {
359 char_u *cmd;
360 cmdmod_T save_cmdmod = cmdmod;
361 char_u *p;
362 bool delim_optional = false;
363 int delim;
364 char_u *end;
365 char *dummy;
366 exarg_T ea;
367 pos_T save_cursor;
368 bool use_last_pat;
369 bool retval = false;
370
371 *skiplen = 0;
372 *patlen = ccline.cmdlen;
373
374 if (!p_is || cmd_silent) {
375 return false;
376 }
377
378 // by default search all lines
379 search_first_line = 0;
380 search_last_line = MAXLNUM;
381
382 if (firstc == '/' || firstc == '?') {
383 *search_delim = firstc;
384 return true;
385 }
386 if (firstc != ':') {
387 return false;
388 }
389
390 emsg_off++;
391 memset(&ea, 0, sizeof(ea));
392 ea.line1 = 1;
393 ea.line2 = 1;
394 ea.cmd = ccline.cmdbuff;
395 ea.addr_type = ADDR_LINES;
396
397 parse_command_modifiers(&ea, &dummy, true);
398 cmdmod = save_cmdmod;
399
400 cmd = skip_range(ea.cmd, NULL);
401 if (vim_strchr((char_u *)"sgvl", *cmd) == NULL) {
402 goto theend;
403 }
404
405 // Skip over "substitute" to find the pattern separator.
406 for (p = cmd; ASCII_ISALPHA(*p); p++) {}
407 if (*skipwhite(p) == NUL) {
408 goto theend;
409 }
410
411 if (STRNCMP(cmd, "substitute", p - cmd) == 0
412 || STRNCMP(cmd, "smagic", p - cmd) == 0
413 || STRNCMP(cmd, "snomagic", MAX(p - cmd, 3)) == 0
414 || STRNCMP(cmd, "vglobal", p - cmd) == 0) {
415 if (*cmd == 's' && cmd[1] == 'm') {
416 p_magic = true;
417 } else if (*cmd == 's' && cmd[1] == 'n') {
418 p_magic = false;
419 }
420 } else if (STRNCMP(cmd, "sort", MAX(p - cmd, 3)) == 0) {
421 // skip over ! and flags
422 if (*p == '!') {
423 p = skipwhite(p + 1);
424 }
425 while (ASCII_ISALPHA(*(p = skipwhite(p)))) {
426 p++;
427 }
428 if (*p == NUL) {
429 goto theend;
430 }
431 } else if (STRNCMP(cmd, "vimgrep", MAX(p - cmd, 3)) == 0
432 || STRNCMP(cmd, "vimgrepadd", MAX(p - cmd, 8)) == 0
433 || STRNCMP(cmd, "lvimgrep", MAX(p - cmd, 2)) == 0
434 || STRNCMP(cmd, "lvimgrepadd", MAX(p - cmd, 9)) == 0
435 || STRNCMP(cmd, "global", p - cmd) == 0) {
436 // skip over "!/".
437 if (*p == '!') {
438 p++;
439 if (*skipwhite(p) == NUL) {
440 goto theend;
441 }
442 }
443 if (*cmd != 'g') {
444 delim_optional = true;
445 }
446 } else {
447 goto theend;
448 }
449
450 p = skipwhite(p);
451 delim = (delim_optional && vim_isIDc(*p)) ? ' ' : *p++;
452 *search_delim = delim;
453 end = skip_regexp(p, delim, p_magic, NULL);
454
455 use_last_pat = end == p && *end == delim;
456 if (end == p && !use_last_pat) {
457 goto theend;
458 }
459
460 // Don't do 'hlsearch' highlighting if the pattern matches everything.
461 if (!use_last_pat) {
462 char_u c = *end;
463 int empty;
464
465 *end = NUL;
466 empty = empty_pattern(p);
467 *end = c;
468 if (empty) {
469 goto theend;
470 }
471 }
472
473 // found a non-empty pattern or //
474 *skiplen = (int)(p - ccline.cmdbuff);
475 *patlen = (int)(end - p);
476
477 // parse the address range
478 save_cursor = curwin->w_cursor;
479 curwin->w_cursor = s->search_start;
480 parse_cmd_address(&ea, &dummy, true);
481 if (ea.addr_count > 0) {
482 // Allow for reverse match.
483 if (ea.line2 < ea.line1) {
484 search_first_line = ea.line2;
485 search_last_line = ea.line1;
486 } else {
487 search_first_line = ea.line1;
488 search_last_line = ea.line2;
489 }
490 } else if (cmd[0] == 's' && cmd[1] != 'o') {
491 // :s defaults to the current line
492 search_first_line = curwin->w_cursor.lnum;
493 search_last_line = curwin->w_cursor.lnum;
494 }
495
496 curwin->w_cursor = save_cursor;
497 retval = true;
498 theend:
499 emsg_off--;
500 return retval;
501 }
502
503 // May do 'incsearch' highlighting if desired.
may_do_incsearch_highlighting(int firstc,long count,incsearch_state_T * s)504 static void may_do_incsearch_highlighting(int firstc, long count, incsearch_state_T *s)
505 {
506 pos_T end_pos;
507 proftime_T tm;
508 searchit_arg_T sia;
509 int skiplen, patlen;
510 char_u next_char;
511 char_u use_last_pat;
512 int search_delim;
513
514 // Parsing range may already set the last search pattern.
515 // NOTE: must call restore_last_search_pattern() before returning!
516 save_last_search_pattern();
517
518 if (!do_incsearch_highlighting(firstc, &search_delim, s, &skiplen,
519 &patlen)) {
520 restore_last_search_pattern();
521 finish_incsearch_highlighting(false, s, true);
522 return;
523 }
524
525 // if there is a character waiting, search and redraw later
526 if (char_avail()) {
527 restore_last_search_pattern();
528 s->incsearch_postponed = true;
529 return;
530 }
531 s->incsearch_postponed = false;
532
533 if (search_first_line == 0) {
534 // start at the original cursor position
535 curwin->w_cursor = s->search_start;
536 } else if (search_first_line > curbuf->b_ml.ml_line_count) {
537 // start after the last line
538 curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
539 curwin->w_cursor.col = MAXCOL;
540 } else {
541 // start at the first line in the range
542 curwin->w_cursor.lnum = search_first_line;
543 curwin->w_cursor.col = 0;
544 }
545 int found; // do_search() result
546
547 // Use the previous pattern for ":s//".
548 next_char = ccline.cmdbuff[skiplen + patlen];
549 use_last_pat = patlen == 0 && skiplen > 0
550 && ccline.cmdbuff[skiplen - 1] == next_char;
551
552 // If there is no pattern, don't do anything.
553 if (patlen == 0 && !use_last_pat) {
554 found = 0;
555 set_no_hlsearch(true); // turn off previous highlight
556 redraw_all_later(SOME_VALID);
557 } else {
558 int search_flags = SEARCH_OPT + SEARCH_NOOF + SEARCH_PEEK;
559 ui_busy_start();
560 ui_flush();
561 emsg_off++; // So it doesn't beep if bad expr
562 // Set the time limit to half a second.
563 tm = profile_setlimit(500L);
564 if (!p_hls) {
565 search_flags += SEARCH_KEEP;
566 }
567 if (search_first_line != 0) {
568 search_flags += SEARCH_START;
569 }
570 ccline.cmdbuff[skiplen + patlen] = NUL;
571 memset(&sia, 0, sizeof(sia));
572 sia.sa_tm = &tm;
573 found = do_search(NULL, firstc == ':' ? '/' : firstc, search_delim,
574 ccline.cmdbuff + skiplen, count,
575 search_flags, &sia);
576 ccline.cmdbuff[skiplen + patlen] = next_char;
577 emsg_off--;
578 if (curwin->w_cursor.lnum < search_first_line
579 || curwin->w_cursor.lnum > search_last_line) {
580 // match outside of address range
581 found = 0;
582 curwin->w_cursor = s->search_start;
583 }
584
585 // if interrupted while searching, behave like it failed
586 if (got_int) {
587 (void)vpeekc(); // remove <C-C> from input stream
588 got_int = false; // don't abandon the command line
589 found = 0;
590 } else if (char_avail()) {
591 // cancelled searching because a char was typed
592 s->incsearch_postponed = true;
593 }
594 ui_busy_stop();
595 }
596
597 if (found != 0) {
598 highlight_match = true; // highlight position
599 } else {
600 highlight_match = false; // remove highlight
601 }
602
603 // first restore the old curwin values, so the screen is
604 // positioned in the same way as the actual search command
605 restore_viewstate(&s->old_viewstate);
606 changed_cline_bef_curs();
607 update_topline(curwin);
608
609 if (found != 0) {
610 pos_T save_pos = curwin->w_cursor;
611
612 s->match_start = curwin->w_cursor;
613 set_search_match(&curwin->w_cursor);
614 validate_cursor();
615 end_pos = curwin->w_cursor;
616 s->match_end = end_pos;
617 curwin->w_cursor = save_pos;
618 } else {
619 end_pos = curwin->w_cursor; // shutup gcc 4
620 }
621 //
622 // Disable 'hlsearch' highlighting if the pattern matches
623 // everything. Avoids a flash when typing "foo\|".
624 if (!use_last_pat) {
625 next_char = ccline.cmdbuff[skiplen + patlen];
626 ccline.cmdbuff[skiplen + patlen] = NUL;
627 if (empty_pattern(ccline.cmdbuff) && !no_hlsearch) {
628 redraw_all_later(SOME_VALID);
629 set_no_hlsearch(true);
630 }
631 ccline.cmdbuff[skiplen + patlen] = next_char;
632 }
633
634 validate_cursor();
635 // May redraw the status line to show the cursor position.
636 if (p_ru && curwin->w_status_height > 0) {
637 curwin->w_redr_status = true;
638 }
639
640 update_screen(SOME_VALID);
641 highlight_match = false;
642 restore_last_search_pattern();
643
644 // Leave it at the end to make CTRL-R CTRL-W work. But not when beyond the
645 // end of the pattern, e.g. for ":s/pat/".
646 if (ccline.cmdbuff[skiplen + patlen] != NUL) {
647 curwin->w_cursor = s->search_start;
648 } else if (found != 0) {
649 curwin->w_cursor = end_pos;
650 }
651
652 msg_starthere();
653 redrawcmdline();
654 s->did_incsearch = true;
655 }
656
657 // When CTRL-L typed: add character from the match to the pattern.
658 // May set "*c" to the added character.
659 // Return OK when calling command_line_not_changed.
may_add_char_to_search(int firstc,int * c,incsearch_state_T * s)660 static int may_add_char_to_search(int firstc, int *c, incsearch_state_T *s)
661 FUNC_ATTR_NONNULL_ALL
662 {
663 int skiplen, patlen;
664 int search_delim;
665
666 // Parsing range may already set the last search pattern.
667 // NOTE: must call restore_last_search_pattern() before returning!
668 save_last_search_pattern();
669
670 // Add a character from under the cursor for 'incsearch'
671 if (!do_incsearch_highlighting(firstc, &search_delim, s, &skiplen,
672 &patlen)) {
673 restore_last_search_pattern();
674 return FAIL;
675 }
676 restore_last_search_pattern();
677
678 if (s->did_incsearch) {
679 curwin->w_cursor = s->match_end;
680 *c = gchar_cursor();
681 if (*c != NUL) {
682 // If 'ignorecase' and 'smartcase' are set and the
683 // command line has no uppercase characters, convert
684 // the character to lowercase
685 if (p_ic && p_scs
686 && !pat_has_uppercase(ccline.cmdbuff + skiplen)) {
687 *c = mb_tolower(*c);
688 }
689 if (*c == search_delim
690 || vim_strchr((char_u *)(p_magic ? "\\~^$.*[" : "\\^$"), *c)
691 != NULL) {
692 // put a backslash before special characters
693 stuffcharReadbuff(*c);
694 *c = '\\';
695 }
696 return FAIL;
697 }
698 }
699 return OK;
700 }
701
finish_incsearch_highlighting(int gotesc,incsearch_state_T * s,bool call_update_screen)702 static void finish_incsearch_highlighting(int gotesc, incsearch_state_T *s, bool call_update_screen)
703 {
704 if (s->did_incsearch) {
705 s->did_incsearch = false;
706 if (gotesc) {
707 curwin->w_cursor = s->save_cursor;
708 } else {
709 if (!equalpos(s->save_cursor, s->search_start)) {
710 // put the '" mark at the original position
711 curwin->w_cursor = s->save_cursor;
712 setpcmark();
713 }
714 curwin->w_cursor = s->search_start; // -V519
715 }
716 restore_viewstate(&s->old_viewstate);
717 highlight_match = false;
718
719 // by default search all lines
720 search_first_line = 0;
721 search_last_line = MAXLNUM;
722
723 p_magic = s->magic_save;
724
725 validate_cursor(); // needed for TAB
726 redraw_all_later(SOME_VALID);
727 if (call_update_screen) {
728 update_screen(SOME_VALID);
729 }
730 }
731 }
732
733 /// Internal entry point for cmdline mode.
734 ///
735 /// caller must use save_cmdline and restore_cmdline. Best is to use
736 /// getcmdline or getcmdline_prompt, instead of calling this directly.
command_line_enter(int firstc,long count,int indent)737 static uint8_t *command_line_enter(int firstc, long count, int indent)
738 {
739 // can be invoked recursively, identify each level
740 static int cmdline_level = 0;
741 cmdline_level++;
742
743 CommandLineState state = {
744 .firstc = firstc,
745 .count = count,
746 .indent = indent,
747 .save_msg_scroll = msg_scroll,
748 .save_State = State,
749 .ignore_drag_release = true,
750 };
751 CommandLineState *s = &state;
752 s->save_p_icm = vim_strsave(p_icm);
753 init_incsearch_state(&s->is_state);
754
755 if (s->firstc == -1) {
756 s->firstc = NUL;
757 s->break_ctrl_c = true;
758 }
759
760 // start without Hebrew mapping for a command line
761 if (s->firstc == ':' || s->firstc == '=' || s->firstc == '>') {
762 cmd_hkmap = 0;
763 }
764
765 ccline.prompt_id = last_prompt_id++;
766 ccline.level = cmdline_level;
767 ccline.overstrike = false; // always start in insert mode
768
769 assert(indent >= 0);
770
771 // set some variables for redrawcmd()
772 ccline.cmdfirstc = (s->firstc == '@' ? 0 : s->firstc);
773 ccline.cmdindent = (s->firstc > 0 ? s->indent : 0);
774
775 // alloc initial ccline.cmdbuff
776 alloc_cmdbuff(exmode_active ? 250 : s->indent + 1);
777 ccline.cmdlen = ccline.cmdpos = 0;
778 ccline.cmdbuff[0] = NUL;
779
780 ccline.last_colors = (ColoredCmdline){ .cmdbuff = NULL,
781 .colors = KV_INITIAL_VALUE };
782 sb_text_start_cmdline();
783
784 // autoindent for :insert and :append
785 if (s->firstc <= 0) {
786 memset(ccline.cmdbuff, ' ', (size_t)s->indent);
787 ccline.cmdbuff[s->indent] = NUL;
788 ccline.cmdpos = s->indent;
789 ccline.cmdspos = s->indent;
790 ccline.cmdlen = s->indent;
791 }
792
793 ExpandInit(&s->xpc);
794 ccline.xpc = &s->xpc;
795
796 if (curwin->w_p_rl && *curwin->w_p_rlc == 's'
797 && (s->firstc == '/' || s->firstc == '?')) {
798 cmdmsg_rl = true;
799 } else {
800 cmdmsg_rl = false;
801 }
802
803 msg_grid_validate();
804
805 redir_off = true; // don't redirect the typed command
806 if (!cmd_silent) {
807 gotocmdline(true);
808 redrawcmdprompt(); // draw prompt or indent
809 ccline.cmdspos = cmd_startcol();
810 if (!msg_scroll) {
811 msg_ext_clear(false);
812 }
813 }
814 s->xpc.xp_context = EXPAND_NOTHING;
815 s->xpc.xp_backslash = XP_BS_NONE;
816 #ifndef BACKSLASH_IN_FILENAME
817 s->xpc.xp_shell = false;
818 #endif
819
820 if (ccline.input_fn) {
821 s->xpc.xp_context = ccline.xp_context;
822 s->xpc.xp_pattern = ccline.cmdbuff;
823 s->xpc.xp_arg = ccline.xp_arg;
824 }
825
826 // Avoid scrolling when called by a recursive do_cmdline(), e.g. when
827 // doing ":@0" when register 0 doesn't contain a CR.
828 msg_scroll = false;
829
830 State = CMDLINE;
831
832 if (s->firstc == '/' || s->firstc == '?' || s->firstc == '@') {
833 // Use ":lmap" mappings for search pattern and input().
834 if (curbuf->b_p_imsearch == B_IMODE_USE_INSERT) {
835 s->b_im_ptr = &curbuf->b_p_iminsert;
836 } else {
837 s->b_im_ptr = &curbuf->b_p_imsearch;
838 }
839
840 if (*s->b_im_ptr == B_IMODE_LMAP) {
841 State |= LANGMAP;
842 }
843 }
844
845 setmouse();
846 ui_cursor_shape(); // may show different cursor shape
847
848 init_history();
849 s->hiscnt = hislen; // set hiscnt to impossible history value
850 s->histype = hist_char2type(s->firstc);
851 do_digraph(-1); // init digraph typeahead
852
853 // If something above caused an error, reset the flags, we do want to type
854 // and execute commands. Display may be messed up a bit.
855 if (did_emsg) {
856 redrawcmd();
857 }
858
859 // Redraw the statusline in case it uses the current mode using the mode()
860 // function.
861 if (!cmd_silent && msg_scrolled == 0) {
862 bool found_one = false;
863
864 FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
865 if (*p_stl != NUL || *wp->w_p_stl != NUL) {
866 wp->w_redr_status = true;
867 found_one = true;
868 }
869 }
870 if (found_one) {
871 redraw_statuslines();
872 }
873 }
874
875 did_emsg = false;
876 got_int = false;
877 s->state.check = command_line_check;
878 s->state.execute = command_line_execute;
879
880 TryState tstate;
881 Error err = ERROR_INIT;
882 bool tl_ret = true;
883 dict_T *dict = get_vim_var_dict(VV_EVENT);
884 char firstcbuf[2];
885 firstcbuf[0] = (char)(firstc > 0 ? firstc : '-');
886 firstcbuf[1] = 0;
887
888 if (has_event(EVENT_CMDLINEENTER)) {
889 // set v:event to a dictionary with information about the commandline
890 tv_dict_add_str(dict, S_LEN("cmdtype"), firstcbuf);
891 tv_dict_add_nr(dict, S_LEN("cmdlevel"), ccline.level);
892 tv_dict_set_keys_readonly(dict);
893 try_enter(&tstate);
894
895 apply_autocmds(EVENT_CMDLINEENTER, (char_u *)firstcbuf, (char_u *)firstcbuf,
896 false, curbuf);
897 tv_dict_clear(dict);
898
899
900 tl_ret = try_leave(&tstate, &err);
901 if (!tl_ret && ERROR_SET(&err)) {
902 msg_putchar('\n');
903 msg_printf_attr(HL_ATTR(HLF_E)|MSG_HIST, (char *)e_autocmd_err, err.msg);
904 api_clear_error(&err);
905 redrawcmd();
906 }
907 tl_ret = true;
908 }
909
910 state_enter(&s->state);
911
912 if (has_event(EVENT_CMDLINELEAVE)) {
913 tv_dict_add_str(dict, S_LEN("cmdtype"), firstcbuf);
914 tv_dict_add_nr(dict, S_LEN("cmdlevel"), ccline.level);
915 tv_dict_set_keys_readonly(dict);
916 // not readonly:
917 tv_dict_add_bool(dict, S_LEN("abort"),
918 s->gotesc ? kBoolVarTrue : kBoolVarFalse);
919 try_enter(&tstate);
920 apply_autocmds(EVENT_CMDLINELEAVE, (char_u *)firstcbuf, (char_u *)firstcbuf,
921 false, curbuf);
922 // error printed below, to avoid redraw issues
923 tl_ret = try_leave(&tstate, &err);
924 if (tv_dict_get_number(dict, "abort") != 0) {
925 s->gotesc = 1;
926 }
927 tv_dict_clear(dict);
928 }
929
930 cmdmsg_rl = false;
931
932 ExpandCleanup(&s->xpc);
933 ccline.xpc = NULL;
934
935 if (s->gotesc) {
936 // There might be a preview window open for inccommand. Close it.
937 close_preview_windows();
938 }
939
940 finish_incsearch_highlighting(s->gotesc, &s->is_state, false);
941
942 if (ccline.cmdbuff != NULL) {
943 // Put line in history buffer (":" and "=" only when it was typed).
944 if (s->histype != HIST_INVALID
945 && ccline.cmdlen
946 && s->firstc != NUL
947 && (s->some_key_typed || s->histype == HIST_SEARCH)) {
948 add_to_history(s->histype, ccline.cmdbuff, true,
949 s->histype == HIST_SEARCH ? s->firstc : NUL);
950 if (s->firstc == ':') {
951 xfree(new_last_cmdline);
952 new_last_cmdline = vim_strsave(ccline.cmdbuff);
953 }
954 }
955
956 if (s->gotesc) {
957 abandon_cmdline();
958 }
959 }
960
961 // If the screen was shifted up, redraw the whole screen (later).
962 // If the line is too long, clear it, so ruler and shown command do
963 // not get printed in the middle of it.
964 msg_check();
965 msg_scroll = s->save_msg_scroll;
966 redir_off = false;
967
968 if (!tl_ret && ERROR_SET(&err)) {
969 msg_putchar('\n');
970 msg_printf_attr(HL_ATTR(HLF_E)|MSG_HIST, (char *)e_autocmd_err, err.msg);
971 api_clear_error(&err);
972 }
973
974 // When the command line was typed, no need for a wait-return prompt.
975 if (s->some_key_typed && tl_ret) {
976 need_wait_return = false;
977 }
978
979 set_string_option_direct("icm", -1, s->save_p_icm, OPT_FREE,
980 SID_NONE);
981 State = s->save_State;
982 setmouse();
983 ui_cursor_shape(); // may show different cursor shape
984 xfree(s->save_p_icm);
985 xfree(ccline.last_colors.cmdbuff);
986 kv_destroy(ccline.last_colors.colors);
987
988 sb_text_end_cmdline();
989
990 char_u *p = ccline.cmdbuff;
991
992 if (ui_has(kUICmdline)) {
993 ui_call_cmdline_hide(ccline.level);
994 msg_ext_clear_later();
995 }
996
997 cmdline_level--;
998 return p;
999 }
1000
command_line_check(VimState * state)1001 static int command_line_check(VimState *state)
1002 {
1003 redir_off = true; // Don't redirect the typed command.
1004 // Repeated, because a ":redir" inside
1005 // completion may switch it on.
1006 quit_more = false; // reset after CTRL-D which had a more-prompt
1007
1008 did_emsg = false; // There can't really be a reason why an error
1009 // that occurs while typing a command should
1010 // cause the command not to be executed.
1011
1012 cursorcmd(); // set the cursor on the right spot
1013 ui_cursor_shape();
1014 return 1;
1015 }
1016
command_line_execute(VimState * state,int key)1017 static int command_line_execute(VimState *state, int key)
1018 {
1019 if (key == K_IGNORE || key == K_NOP) {
1020 return -1; // get another key
1021 }
1022
1023 CommandLineState *s = (CommandLineState *)state;
1024 s->c = key;
1025
1026 if (s->c == K_EVENT || s->c == K_COMMAND) {
1027 if (s->c == K_EVENT) {
1028 state_handle_k_event();
1029 } else {
1030 do_cmdline(NULL, getcmdkeycmd, NULL, DOCMD_NOWAIT);
1031 }
1032
1033 if (!cmdline_was_last_drawn) {
1034 redrawcmdline();
1035 }
1036 return 1;
1037 }
1038
1039 if (KeyTyped) {
1040 s->some_key_typed = true;
1041 if (cmd_hkmap) {
1042 s->c = hkmap(s->c);
1043 }
1044
1045 if (cmdmsg_rl && !KeyStuffed) {
1046 // Invert horizontal movements and operations. Only when
1047 // typed by the user directly, not when the result of a
1048 // mapping.
1049 switch (s->c) {
1050 case K_RIGHT:
1051 s->c = K_LEFT; break;
1052 case K_S_RIGHT:
1053 s->c = K_S_LEFT; break;
1054 case K_C_RIGHT:
1055 s->c = K_C_LEFT; break;
1056 case K_LEFT:
1057 s->c = K_RIGHT; break;
1058 case K_S_LEFT:
1059 s->c = K_S_RIGHT; break;
1060 case K_C_LEFT:
1061 s->c = K_C_RIGHT; break;
1062 }
1063 }
1064 }
1065
1066 // Ignore got_int when CTRL-C was typed here.
1067 // Don't ignore it in :global, we really need to break then, e.g., for
1068 // ":g/pat/normal /pat" (without the <CR>).
1069 // Don't ignore it for the input() function.
1070 if ((s->c == Ctrl_C)
1071 && s->firstc != '@'
1072 && !s->break_ctrl_c
1073 && !global_busy) {
1074 got_int = false;
1075 }
1076
1077 // free old command line when finished moving around in the history
1078 // list
1079 if (s->lookfor != NULL
1080 && s->c != K_S_DOWN && s->c != K_S_UP
1081 && s->c != K_DOWN && s->c != K_UP
1082 && s->c != K_PAGEDOWN && s->c != K_PAGEUP
1083 && s->c != K_KPAGEDOWN && s->c != K_KPAGEUP
1084 && s->c != K_LEFT && s->c != K_RIGHT
1085 && (s->xpc.xp_numfiles > 0 || (s->c != Ctrl_P && s->c != Ctrl_N))) {
1086 XFREE_CLEAR(s->lookfor);
1087 }
1088
1089 // When there are matching completions to select <S-Tab> works like
1090 // CTRL-P (unless 'wc' is <S-Tab>).
1091 if (s->c != p_wc && s->c == K_S_TAB && s->xpc.xp_numfiles > 0) {
1092 s->c = Ctrl_P;
1093 }
1094
1095 // Special translations for 'wildmenu'
1096 if (s->did_wild_list && p_wmnu) {
1097 if (s->c == K_LEFT) {
1098 s->c = Ctrl_P;
1099 } else if (s->c == K_RIGHT) {
1100 s->c = Ctrl_N;
1101 }
1102 }
1103 if (compl_match_array || s->did_wild_list) {
1104 if (s->c == Ctrl_E) {
1105 s->res = nextwild(&s->xpc, WILD_CANCEL, WILD_NO_BEEP,
1106 s->firstc != '@');
1107 } else if (s->c == Ctrl_Y) {
1108 s->res = nextwild(&s->xpc, WILD_APPLY, WILD_NO_BEEP,
1109 s->firstc != '@');
1110 s->c = Ctrl_E;
1111 }
1112 }
1113
1114 // Hitting CR after "emenu Name.": complete submenu
1115 if (s->xpc.xp_context == EXPAND_MENUNAMES && p_wmnu
1116 && ccline.cmdpos > 1
1117 && ccline.cmdbuff[ccline.cmdpos - 1] == '.'
1118 && ccline.cmdbuff[ccline.cmdpos - 2] != '\\'
1119 && (s->c == '\n' || s->c == '\r' || s->c == K_KENTER)) {
1120 s->c = K_DOWN;
1121 }
1122
1123 // free expanded names when finished walking through matches
1124 if (!(s->c == p_wc && KeyTyped) && s->c != p_wcm && s->c != Ctrl_Z
1125 && s->c != Ctrl_N && s->c != Ctrl_P && s->c != Ctrl_A
1126 && s->c != Ctrl_L) {
1127 if (compl_match_array) {
1128 pum_undisplay(true);
1129 XFREE_CLEAR(compl_match_array);
1130 }
1131 if (s->xpc.xp_numfiles != -1) {
1132 (void)ExpandOne(&s->xpc, NULL, NULL, 0, WILD_FREE);
1133 }
1134 s->did_wild_list = false;
1135 if (!p_wmnu || (s->c != K_UP && s->c != K_DOWN)) {
1136 s->xpc.xp_context = EXPAND_NOTHING;
1137 }
1138 s->wim_index = 0;
1139 if (p_wmnu && wild_menu_showing != 0) {
1140 const bool skt = KeyTyped;
1141 int old_RedrawingDisabled = RedrawingDisabled;
1142
1143 if (ccline.input_fn) {
1144 RedrawingDisabled = 0;
1145 }
1146
1147 if (wild_menu_showing == WM_SCROLLED) {
1148 // Entered command line, move it up
1149 cmdline_row--;
1150 redrawcmd();
1151 wild_menu_showing = 0;
1152 } else if (save_p_ls != -1) {
1153 // restore 'laststatus' and 'winminheight'
1154 p_ls = save_p_ls;
1155 p_wmh = save_p_wmh;
1156 last_status(false);
1157 update_screen(VALID); // redraw the screen NOW
1158 redrawcmd();
1159 save_p_ls = -1;
1160 wild_menu_showing = 0;
1161 // don't redraw statusline if WM_LIST is showing
1162 } else if (wild_menu_showing != WM_LIST) {
1163 win_redraw_last_status(topframe);
1164 wild_menu_showing = 0; // must be before redraw_statuslines #8385
1165 redraw_statuslines();
1166 } else {
1167 wild_menu_showing = 0;
1168 }
1169 KeyTyped = skt;
1170 if (ccline.input_fn) {
1171 RedrawingDisabled = old_RedrawingDisabled;
1172 }
1173 }
1174 }
1175
1176 // Special translations for 'wildmenu'
1177 if (s->xpc.xp_context == EXPAND_MENUNAMES && p_wmnu) {
1178 // Hitting <Down> after "emenu Name.": complete submenu
1179 if (s->c == K_DOWN && ccline.cmdpos > 0
1180 && ccline.cmdbuff[ccline.cmdpos - 1] == '.') {
1181 s->c = (int)p_wc;
1182 KeyTyped = true; // in case the key was mapped
1183 } else if (s->c == K_UP) {
1184 // Hitting <Up>: Remove one submenu name in front of the
1185 // cursor
1186 int found = false;
1187
1188 int j = (int)(s->xpc.xp_pattern - ccline.cmdbuff);
1189 int i = 0;
1190 while (--j > 0) {
1191 // check for start of menu name
1192 if (ccline.cmdbuff[j] == ' '
1193 && ccline.cmdbuff[j - 1] != '\\') {
1194 i = j + 1;
1195 break;
1196 }
1197
1198 // check for start of submenu name
1199 if (ccline.cmdbuff[j] == '.'
1200 && ccline.cmdbuff[j - 1] != '\\') {
1201 if (found) {
1202 i = j + 1;
1203 break;
1204 } else {
1205 found = true;
1206 }
1207 }
1208 }
1209 if (i > 0) {
1210 cmdline_del(i);
1211 }
1212 s->c = (int)p_wc;
1213 KeyTyped = true; // in case the key was mapped
1214 s->xpc.xp_context = EXPAND_NOTHING;
1215 }
1216 }
1217 if ((s->xpc.xp_context == EXPAND_FILES
1218 || s->xpc.xp_context == EXPAND_DIRECTORIES
1219 || s->xpc.xp_context == EXPAND_SHELLCMD) && p_wmnu) {
1220 char_u upseg[5];
1221
1222 upseg[0] = PATHSEP;
1223 upseg[1] = '.';
1224 upseg[2] = '.';
1225 upseg[3] = PATHSEP;
1226 upseg[4] = NUL;
1227
1228 if (s->c == K_DOWN
1229 && ccline.cmdpos > 0
1230 && ccline.cmdbuff[ccline.cmdpos - 1] == PATHSEP
1231 && (ccline.cmdpos < 3
1232 || ccline.cmdbuff[ccline.cmdpos - 2] != '.'
1233 || ccline.cmdbuff[ccline.cmdpos - 3] != '.')) {
1234 // go down a directory
1235 s->c = (int)p_wc;
1236 KeyTyped = true; // in case the key was mapped
1237 } else if (STRNCMP(s->xpc.xp_pattern, upseg + 1, 3) == 0
1238 && s->c == K_DOWN) {
1239 // If in a direct ancestor, strip off one ../ to go down
1240 int found = false;
1241
1242 int j = ccline.cmdpos;
1243 int i = (int)(s->xpc.xp_pattern - ccline.cmdbuff);
1244 while (--j > i) {
1245 j -= utf_head_off(ccline.cmdbuff, ccline.cmdbuff + j);
1246 if (vim_ispathsep(ccline.cmdbuff[j])) {
1247 found = true;
1248 break;
1249 }
1250 }
1251 if (found
1252 && ccline.cmdbuff[j - 1] == '.'
1253 && ccline.cmdbuff[j - 2] == '.'
1254 && (vim_ispathsep(ccline.cmdbuff[j - 3]) || j == i + 2)) {
1255 cmdline_del(j - 2);
1256 s->c = (int)p_wc;
1257 KeyTyped = true; // in case the key was mapped
1258 }
1259 } else if (s->c == K_UP) {
1260 // go up a directory
1261 int found = false;
1262
1263 int j = ccline.cmdpos - 1;
1264 int i = (int)(s->xpc.xp_pattern - ccline.cmdbuff);
1265 while (--j > i) {
1266 j -= utf_head_off(ccline.cmdbuff, ccline.cmdbuff + j);
1267 if (vim_ispathsep(ccline.cmdbuff[j])
1268 #ifdef BACKSLASH_IN_FILENAME
1269 && vim_strchr((const char_u *)" *?[{`$%#", ccline.cmdbuff[j + 1])
1270 == NULL
1271 #endif
1272 ) {
1273 if (found) {
1274 i = j + 1;
1275 break;
1276 } else {
1277 found = true;
1278 }
1279 }
1280 }
1281
1282 if (!found) {
1283 j = i;
1284 } else if (STRNCMP(ccline.cmdbuff + j, upseg, 4) == 0) {
1285 j += 4;
1286 } else if (STRNCMP(ccline.cmdbuff + j, upseg + 1, 3) == 0
1287 && j == i) {
1288 j += 3;
1289 } else {
1290 j = 0;
1291 }
1292
1293 if (j > 0) {
1294 // TODO(tarruda): this is only for DOS/Unix systems - need to put in
1295 // machine-specific stuff here and in upseg init
1296 cmdline_del(j);
1297 put_on_cmdline(upseg + 1, 3, false);
1298 } else if (ccline.cmdpos > i) {
1299 cmdline_del(i);
1300 }
1301
1302 // Now complete in the new directory. Set KeyTyped in case the
1303 // Up key came from a mapping.
1304 s->c = (int)p_wc;
1305 KeyTyped = true;
1306 }
1307 }
1308
1309 // CTRL-\ CTRL-N goes to Normal mode, CTRL-\ CTRL-G goes to Insert
1310 // mode when 'insertmode' is set, CTRL-\ e prompts for an expression.
1311 if (s->c == Ctrl_BSL) {
1312 no_mapping++;
1313 s->c = plain_vgetc();
1314 no_mapping--;
1315 // CTRL-\ e doesn't work when obtaining an expression, unless it
1316 // is in a mapping.
1317 if (s->c != Ctrl_N
1318 && s->c != Ctrl_G
1319 && (s->c != 'e'
1320 || (ccline.cmdfirstc == '=' && KeyTyped)
1321 || cmdline_star > 0)) {
1322 vungetc(s->c);
1323 s->c = Ctrl_BSL;
1324 } else if (s->c == 'e') {
1325 char_u *p = NULL;
1326 int len;
1327
1328 // Replace the command line with the result of an expression.
1329 // Need to save and restore the current command line, to be
1330 // able to enter a new one...
1331 if (ccline.cmdpos == ccline.cmdlen) {
1332 new_cmdpos = 99999; // keep it at the end
1333 } else {
1334 new_cmdpos = ccline.cmdpos;
1335 }
1336
1337 s->c = get_expr_register();
1338 if (s->c == '=') {
1339 // Need to save and restore ccline. And set "textlock"
1340 // to avoid nasty things like going to another buffer when
1341 // evaluating an expression.
1342 CmdlineInfo save_ccline;
1343 save_cmdline(&save_ccline);
1344 textlock++;
1345 p = get_expr_line();
1346 textlock--;
1347 restore_cmdline(&save_ccline);
1348
1349 if (p != NULL) {
1350 len = (int)STRLEN(p);
1351 realloc_cmdbuff(len + 1);
1352 ccline.cmdlen = len;
1353 STRCPY(ccline.cmdbuff, p);
1354 xfree(p);
1355
1356 // Restore the cursor or use the position set with
1357 // set_cmdline_pos().
1358 if (new_cmdpos > ccline.cmdlen) {
1359 ccline.cmdpos = ccline.cmdlen;
1360 } else {
1361 ccline.cmdpos = new_cmdpos;
1362 }
1363
1364 KeyTyped = false; // Don't do p_wc completion.
1365 redrawcmd();
1366 return command_line_changed(s);
1367 }
1368 }
1369 beep_flush();
1370 got_int = false; // don't abandon the command line
1371 did_emsg = false;
1372 emsg_on_display = false;
1373 redrawcmd();
1374 return command_line_not_changed(s);
1375 } else {
1376 if (s->c == Ctrl_G && p_im && restart_edit == 0) {
1377 restart_edit = 'a';
1378 }
1379 s->gotesc = true; // will free ccline.cmdbuff after putting it
1380 // in history
1381 return 0; // back to Normal mode
1382 }
1383 }
1384
1385 if (s->c == cedit_key || s->c == K_CMDWIN) {
1386 // TODO(vim): why is ex_normal_busy checked here?
1387 if ((s->c == K_CMDWIN || ex_normal_busy == 0)
1388 && got_int == false) {
1389 // Open a window to edit the command line (and history).
1390 s->c = open_cmdwin();
1391 s->some_key_typed = true;
1392 }
1393 } else {
1394 s->c = do_digraph(s->c);
1395 }
1396
1397 if (s->c == '\n'
1398 || s->c == '\r'
1399 || s->c == K_KENTER
1400 || (s->c == ESC
1401 && (!KeyTyped || vim_strchr(p_cpo, CPO_ESC) != NULL))) {
1402 // In Ex mode a backslash escapes a newline.
1403 if (exmode_active
1404 && s->c != ESC
1405 && ccline.cmdpos == ccline.cmdlen
1406 && ccline.cmdpos > 0
1407 && ccline.cmdbuff[ccline.cmdpos - 1] == '\\') {
1408 if (s->c == K_KENTER) {
1409 s->c = '\n';
1410 }
1411 } else {
1412 s->gotesc = false; // Might have typed ESC previously, don't
1413 // truncate the cmdline now.
1414 if (ccheck_abbr(s->c + ABBR_OFF)) {
1415 return command_line_changed(s);
1416 }
1417
1418 if (!cmd_silent) {
1419 if (!ui_has(kUICmdline)) {
1420 cmd_cursor_goto(msg_row, 0);
1421 }
1422 ui_flush();
1423 }
1424 return 0;
1425 }
1426 }
1427
1428 // Completion for 'wildchar' or 'wildcharm' key.
1429 // - hitting <ESC> twice means: abandon command line.
1430 // - wildcard expansion is only done when the 'wildchar' key is really
1431 // typed, not when it comes from a macro
1432 if ((s->c == p_wc && !s->gotesc && KeyTyped) || s->c == p_wcm
1433 || s->c == Ctrl_Z) {
1434 int options = WILD_NO_BEEP;
1435 if (wim_flags[s->wim_index] & WIM_BUFLASTUSED) {
1436 options |= WILD_BUFLASTUSED;
1437 }
1438 if (s->xpc.xp_numfiles > 0) { // typed p_wc at least twice
1439 // if 'wildmode' contains "list" may still need to list
1440 if (s->xpc.xp_numfiles > 1
1441 && !s->did_wild_list
1442 && ((wim_flags[s->wim_index] & WIM_LIST)
1443 || (p_wmnu && (wim_flags[s->wim_index] & WIM_FULL) != 0))) {
1444 (void)showmatches(&s->xpc, p_wmnu
1445 && ((wim_flags[s->wim_index] & WIM_LIST) == 0));
1446 redrawcmd();
1447 s->did_wild_list = true;
1448 }
1449
1450 if (wim_flags[s->wim_index] & WIM_LONGEST) {
1451 s->res = nextwild(&s->xpc, WILD_LONGEST, options,
1452 s->firstc != '@');
1453 } else if (wim_flags[s->wim_index] & WIM_FULL) {
1454 s->res = nextwild(&s->xpc, WILD_NEXT, options,
1455 s->firstc != '@');
1456 } else {
1457 s->res = OK; // don't insert 'wildchar' now
1458 }
1459 } else { // typed p_wc first time
1460 s->wim_index = 0;
1461 int j = ccline.cmdpos;
1462
1463 // if 'wildmode' first contains "longest", get longest
1464 // common part
1465 if (wim_flags[0] & WIM_LONGEST) {
1466 s->res = nextwild(&s->xpc, WILD_LONGEST, options,
1467 s->firstc != '@');
1468 } else {
1469 s->res = nextwild(&s->xpc, WILD_EXPAND_KEEP, options,
1470 s->firstc != '@');
1471 }
1472
1473 // if interrupted while completing, behave like it failed
1474 if (got_int) {
1475 (void)vpeekc(); // remove <C-C> from input stream
1476 got_int = false; // don't abandon the command line
1477 (void)ExpandOne(&s->xpc, NULL, NULL, 0, WILD_FREE);
1478 s->xpc.xp_context = EXPAND_NOTHING;
1479 return command_line_changed(s);
1480 }
1481
1482 // when more than one match, and 'wildmode' first contains
1483 // "list", or no change and 'wildmode' contains "longest,list",
1484 // list all matches
1485 if (s->res == OK && s->xpc.xp_numfiles > 1) {
1486 // a "longest" that didn't do anything is skipped (but not
1487 // "list:longest")
1488 if (wim_flags[0] == WIM_LONGEST && ccline.cmdpos == j) {
1489 s->wim_index = 1;
1490 }
1491 if ((wim_flags[s->wim_index] & WIM_LIST)
1492 || (p_wmnu && (wim_flags[s->wim_index] & WIM_FULL) != 0)) {
1493 if (!(wim_flags[0] & WIM_LONGEST)) {
1494 int p_wmnu_save = p_wmnu;
1495 p_wmnu = 0;
1496 // remove match
1497 nextwild(&s->xpc, WILD_PREV, 0, s->firstc != '@');
1498 p_wmnu = p_wmnu_save;
1499 }
1500
1501 (void)showmatches(&s->xpc, p_wmnu
1502 && ((wim_flags[s->wim_index] & WIM_LIST) == 0));
1503 redrawcmd();
1504 s->did_wild_list = true;
1505
1506 if (wim_flags[s->wim_index] & WIM_LONGEST) {
1507 nextwild(&s->xpc, WILD_LONGEST, options,
1508 s->firstc != '@');
1509 } else if (wim_flags[s->wim_index] & WIM_FULL) {
1510 nextwild(&s->xpc, WILD_NEXT, options,
1511 s->firstc != '@');
1512 }
1513 } else {
1514 vim_beep(BO_WILD);
1515 }
1516 } else if (s->xpc.xp_numfiles == -1) {
1517 s->xpc.xp_context = EXPAND_NOTHING;
1518 }
1519 }
1520
1521 if (s->wim_index < 3) {
1522 ++s->wim_index;
1523 }
1524
1525 if (s->c == ESC) {
1526 s->gotesc = true;
1527 }
1528
1529 if (s->res == OK) {
1530 return command_line_changed(s);
1531 }
1532 }
1533
1534 s->gotesc = false;
1535
1536 // <S-Tab> goes to last match, in a clumsy way
1537 if (s->c == K_S_TAB && KeyTyped) {
1538 if (nextwild(&s->xpc, WILD_EXPAND_KEEP, 0, s->firstc != '@') == OK) {
1539 showmatches(&s->xpc, p_wmnu
1540 && ((wim_flags[s->wim_index] & WIM_LIST) == 0));
1541 nextwild(&s->xpc, WILD_PREV, 0, s->firstc != '@');
1542 nextwild(&s->xpc, WILD_PREV, 0, s->firstc != '@');
1543 return command_line_changed(s);
1544 }
1545 }
1546
1547 if (s->c == NUL || s->c == K_ZERO) {
1548 // NUL is stored as NL
1549 s->c = NL;
1550 }
1551
1552 s->do_abbr = true; // default: check for abbreviation
1553 return command_line_handle_key(s);
1554 }
1555
1556 // May adjust 'incsearch' highlighting for typing CTRL-G and CTRL-T, go to next
1557 // or previous match.
1558 // Returns FAIL when calling command_line_not_changed.
may_do_command_line_next_incsearch(int firstc,long count,incsearch_state_T * s,bool next_match)1559 static int may_do_command_line_next_incsearch(int firstc, long count, incsearch_state_T *s,
1560 bool next_match)
1561 FUNC_ATTR_NONNULL_ALL
1562 {
1563 int skiplen, patlen, search_delim;
1564
1565 // Parsing range may already set the last search pattern.
1566 // NOTE: must call restore_last_search_pattern() before returning!
1567 save_last_search_pattern();
1568
1569 if (!do_incsearch_highlighting(firstc, &search_delim, s, &skiplen,
1570 &patlen)) {
1571 restore_last_search_pattern();
1572 return OK;
1573 }
1574 if (patlen == 0 && ccline.cmdbuff[skiplen] == NUL) {
1575 restore_last_search_pattern();
1576 return FAIL;
1577 }
1578
1579 ui_busy_start();
1580 ui_flush();
1581
1582 pos_T t;
1583 char_u *pat;
1584 int search_flags = SEARCH_NOOF;
1585 char_u save;
1586
1587
1588 if (search_delim == ccline.cmdbuff[skiplen]) {
1589 pat = last_search_pattern();
1590 skiplen = 0;
1591 patlen = (int)STRLEN(pat);
1592 } else {
1593 pat = ccline.cmdbuff + skiplen;
1594 }
1595
1596 if (next_match) {
1597 t = s->match_end;
1598 if (lt(s->match_start, s->match_end)) {
1599 // start searching at the end of the match
1600 // not at the beginning of the next column
1601 (void)decl(&t);
1602 }
1603 search_flags += SEARCH_COL;
1604 } else {
1605 t = s->match_start;
1606 }
1607 if (!p_hls) {
1608 search_flags += SEARCH_KEEP;
1609 }
1610 emsg_off++;
1611 save = pat[patlen];
1612 pat[patlen] = NUL;
1613 int found = searchit(curwin, curbuf, &t, NULL,
1614 next_match ? FORWARD : BACKWARD,
1615 pat, count, search_flags,
1616 RE_SEARCH, NULL);
1617 emsg_off--;
1618 pat[patlen] = save;
1619 ui_busy_stop();
1620 if (found) {
1621 s->search_start = s->match_start;
1622 s->match_end = t;
1623 s->match_start = t;
1624 if (!next_match && firstc != '?') {
1625 // move just before the current match, so that
1626 // when nv_search finishes the cursor will be
1627 // put back on the match
1628 s->search_start = t;
1629 (void)decl(&s->search_start);
1630 } else if (next_match && firstc == '?') {
1631 // move just after the current match, so that
1632 // when nv_search finishes the cursor will be
1633 // put back on the match
1634 s->search_start = t;
1635 (void)incl(&s->search_start);
1636 }
1637 if (lt(t, s->search_start) && next_match) {
1638 // wrap around
1639 s->search_start = t;
1640 if (firstc == '?') {
1641 (void)incl(&s->search_start);
1642 } else {
1643 (void)decl(&s->search_start);
1644 }
1645 }
1646
1647 set_search_match(&s->match_end);
1648 curwin->w_cursor = s->match_start;
1649 changed_cline_bef_curs();
1650 update_topline(curwin);
1651 validate_cursor();
1652 highlight_match = true;
1653 save_viewstate(&s->old_viewstate);
1654 update_screen(NOT_VALID);
1655 highlight_match = false;
1656 redrawcmdline();
1657 curwin->w_cursor = s->match_end;
1658 } else {
1659 vim_beep(BO_ERROR);
1660 }
1661 restore_last_search_pattern();
1662 return FAIL;
1663 }
1664
command_line_next_histidx(CommandLineState * s,bool next_match)1665 static void command_line_next_histidx(CommandLineState *s, bool next_match)
1666 {
1667 int j = (int)STRLEN(s->lookfor);
1668 for (;;) {
1669 // one step backwards
1670 if (!next_match) {
1671 if (s->hiscnt == hislen) {
1672 // first time
1673 s->hiscnt = hisidx[s->histype];
1674 } else if (s->hiscnt == 0 && hisidx[s->histype] != hislen - 1) {
1675 s->hiscnt = hislen - 1;
1676 } else if (s->hiscnt != hisidx[s->histype] + 1) {
1677 s->hiscnt--;
1678 } else {
1679 // at top of list
1680 s->hiscnt = s->save_hiscnt;
1681 break;
1682 }
1683 } else { // one step forwards
1684 // on last entry, clear the line
1685 if (s->hiscnt == hisidx[s->histype]) {
1686 s->hiscnt = hislen;
1687 break;
1688 }
1689
1690 // not on a history line, nothing to do
1691 if (s->hiscnt == hislen) {
1692 break;
1693 }
1694
1695 if (s->hiscnt == hislen - 1) {
1696 // wrap around
1697 s->hiscnt = 0;
1698 } else {
1699 s->hiscnt++;
1700 }
1701 }
1702
1703 if (s->hiscnt < 0 || history[s->histype][s->hiscnt].hisstr == NULL) {
1704 s->hiscnt = s->save_hiscnt;
1705 break;
1706 }
1707
1708 if ((s->c != K_UP && s->c != K_DOWN)
1709 || s->hiscnt == s->save_hiscnt
1710 || STRNCMP(history[s->histype][s->hiscnt].hisstr,
1711 s->lookfor, (size_t)j) == 0) {
1712 break;
1713 }
1714 }
1715 }
1716
command_line_handle_key(CommandLineState * s)1717 static int command_line_handle_key(CommandLineState *s)
1718 {
1719 // Big switch for a typed command line character.
1720 switch (s->c) {
1721 case K_BS:
1722 case Ctrl_H:
1723 case K_DEL:
1724 case K_KDEL:
1725 case Ctrl_W:
1726 if (s->c == K_KDEL) {
1727 s->c = K_DEL;
1728 }
1729
1730 // delete current character is the same as backspace on next
1731 // character, except at end of line
1732 if (s->c == K_DEL && ccline.cmdpos != ccline.cmdlen) {
1733 ++ccline.cmdpos;
1734 }
1735
1736 if (s->c == K_DEL) {
1737 ccline.cmdpos += mb_off_next(ccline.cmdbuff,
1738 ccline.cmdbuff + ccline.cmdpos);
1739 }
1740
1741 if (ccline.cmdpos > 0) {
1742 char_u *p;
1743
1744 int j = ccline.cmdpos;
1745 p = mb_prevptr(ccline.cmdbuff, ccline.cmdbuff + j);
1746
1747 if (s->c == Ctrl_W) {
1748 while (p > ccline.cmdbuff && ascii_isspace(*p)) {
1749 p = mb_prevptr(ccline.cmdbuff, p);
1750 }
1751
1752 int i = mb_get_class(p);
1753 while (p > ccline.cmdbuff && mb_get_class(p) == i) {
1754 p = mb_prevptr(ccline.cmdbuff, p);
1755 }
1756
1757 if (mb_get_class(p) != i) {
1758 p += utfc_ptr2len(p);
1759 }
1760 }
1761
1762 ccline.cmdpos = (int)(p - ccline.cmdbuff);
1763 ccline.cmdlen -= j - ccline.cmdpos;
1764 int i = ccline.cmdpos;
1765
1766 while (i < ccline.cmdlen) {
1767 ccline.cmdbuff[i++] = ccline.cmdbuff[j++];
1768 }
1769
1770 // Truncate at the end, required for multi-byte chars.
1771 ccline.cmdbuff[ccline.cmdlen] = NUL;
1772 if (ccline.cmdlen == 0) {
1773 s->is_state.search_start = s->is_state.save_cursor;
1774 // save view settings, so that the screen won't be restored at the
1775 // wrong position
1776 s->is_state.old_viewstate = s->is_state.init_viewstate;
1777 }
1778 redrawcmd();
1779 } else if (ccline.cmdlen == 0 && s->c != Ctrl_W
1780 && ccline.cmdprompt == NULL && s->indent == 0) {
1781 // In ex and debug mode it doesn't make sense to return.
1782 if (exmode_active || ccline.cmdfirstc == '>') {
1783 return command_line_not_changed(s);
1784 }
1785
1786 XFREE_CLEAR(ccline.cmdbuff); // no commandline to return
1787 if (!cmd_silent && !ui_has(kUICmdline)) {
1788 if (cmdmsg_rl) {
1789 msg_col = Columns;
1790 } else {
1791 msg_col = 0;
1792 }
1793 msg_putchar(' '); // delete ':'
1794 }
1795 s->is_state.search_start = s->is_state.save_cursor;
1796 redraw_cmdline = true;
1797 return 0; // back to cmd mode
1798 }
1799 return command_line_changed(s);
1800
1801 case K_INS:
1802 case K_KINS:
1803 ccline.overstrike = !ccline.overstrike;
1804
1805 ui_cursor_shape(); // may show different cursor shape
1806 return command_line_not_changed(s);
1807
1808 case Ctrl_HAT:
1809 if (map_to_exists_mode("", LANGMAP, false)) {
1810 // ":lmap" mappings exists, toggle use of mappings.
1811 State ^= LANGMAP;
1812 if (s->b_im_ptr != NULL) {
1813 if (State & LANGMAP) {
1814 *s->b_im_ptr = B_IMODE_LMAP;
1815 } else {
1816 *s->b_im_ptr = B_IMODE_NONE;
1817 }
1818 }
1819 }
1820
1821 if (s->b_im_ptr != NULL) {
1822 if (s->b_im_ptr == &curbuf->b_p_iminsert) {
1823 set_iminsert_global();
1824 } else {
1825 set_imsearch_global();
1826 }
1827 }
1828 ui_cursor_shape(); // may show different cursor shape
1829 // Show/unshow value of 'keymap' in status lines later.
1830 status_redraw_curbuf();
1831 return command_line_not_changed(s);
1832
1833 case Ctrl_U: {
1834 // delete all characters left of the cursor
1835 int j = ccline.cmdpos;
1836 ccline.cmdlen -= j;
1837 int i = ccline.cmdpos = 0;
1838 while (i < ccline.cmdlen) {
1839 ccline.cmdbuff[i++] = ccline.cmdbuff[j++];
1840 }
1841
1842 // Truncate at the end, required for multi-byte chars.
1843 ccline.cmdbuff[ccline.cmdlen] = NUL;
1844 if (ccline.cmdlen == 0) {
1845 s->is_state.search_start = s->is_state.save_cursor;
1846 }
1847 redrawcmd();
1848 return command_line_changed(s);
1849 }
1850
1851 case ESC: // get here if p_wc != ESC or when ESC typed twice
1852 case Ctrl_C:
1853 // In exmode it doesn't make sense to return. Except when
1854 // ":normal" runs out of characters. Also when highlight callback is active
1855 // <C-c> should interrupt only it.
1856 if ((exmode_active && (ex_normal_busy == 0 || typebuf.tb_len > 0))
1857 || (getln_interrupted_highlight && s->c == Ctrl_C)) {
1858 getln_interrupted_highlight = false;
1859 return command_line_not_changed(s);
1860 }
1861
1862 s->gotesc = true; // will free ccline.cmdbuff after
1863 // putting it in history
1864 return 0; // back to cmd mode
1865
1866 case Ctrl_R: { // insert register
1867 putcmdline('"', true);
1868 no_mapping++;
1869 int i = s->c = plain_vgetc(); // CTRL-R <char>
1870 if (i == Ctrl_O) {
1871 i = Ctrl_R; // CTRL-R CTRL-O == CTRL-R CTRL-R
1872 }
1873
1874 if (i == Ctrl_R) {
1875 s->c = plain_vgetc(); // CTRL-R CTRL-R <char>
1876 }
1877 --no_mapping;
1878 // Insert the result of an expression.
1879 // Need to save the current command line, to be able to enter
1880 // a new one...
1881 new_cmdpos = -1;
1882 if (s->c == '=') {
1883 if (ccline.cmdfirstc == '=' // can't do this recursively
1884 || cmdline_star > 0) { // or when typing a password
1885 beep_flush();
1886 s->c = ESC;
1887 } else {
1888 CmdlineInfo save_ccline;
1889 save_cmdline(&save_ccline);
1890 s->c = get_expr_register();
1891 restore_cmdline(&save_ccline);
1892 }
1893 }
1894
1895 if (s->c != ESC) { // use ESC to cancel inserting register
1896 cmdline_paste(s->c, i == Ctrl_R, false);
1897
1898 // When there was a serious error abort getting the
1899 // command line.
1900 if (aborting()) {
1901 s->gotesc = true; // will free ccline.cmdbuff after
1902 // putting it in history
1903 return 0; // back to cmd mode
1904 }
1905 KeyTyped = false; // Don't do p_wc completion.
1906 if (new_cmdpos >= 0) {
1907 // set_cmdline_pos() was used
1908 if (new_cmdpos > ccline.cmdlen) {
1909 ccline.cmdpos = ccline.cmdlen;
1910 } else {
1911 ccline.cmdpos = new_cmdpos;
1912 }
1913 }
1914 }
1915 ccline.special_char = NUL;
1916 redrawcmd();
1917 return command_line_changed(s);
1918 }
1919
1920 case Ctrl_D:
1921 if (showmatches(&s->xpc, false) == EXPAND_NOTHING) {
1922 break; // Use ^D as normal char instead
1923 }
1924
1925 wild_menu_showing = WM_LIST;
1926 redrawcmd();
1927 return 1; // don't do incremental search now
1928
1929 case K_RIGHT:
1930 case K_S_RIGHT:
1931 case K_C_RIGHT:
1932 do {
1933 if (ccline.cmdpos >= ccline.cmdlen) {
1934 break;
1935 }
1936
1937 int cells = cmdline_charsize(ccline.cmdpos);
1938 if (KeyTyped && ccline.cmdspos + cells >= Columns * Rows) {
1939 break;
1940 }
1941
1942 ccline.cmdspos += cells;
1943 ccline.cmdpos += utfc_ptr2len(ccline.cmdbuff + ccline.cmdpos);
1944 } while ((s->c == K_S_RIGHT || s->c == K_C_RIGHT
1945 || (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL)))
1946 && ccline.cmdbuff[ccline.cmdpos] != ' ');
1947 ccline.cmdspos = cmd_screencol(ccline.cmdpos);
1948 return command_line_not_changed(s);
1949
1950 case K_LEFT:
1951 case K_S_LEFT:
1952 case K_C_LEFT:
1953 if (ccline.cmdpos == 0) {
1954 return command_line_not_changed(s);
1955 }
1956 do {
1957 ccline.cmdpos--;
1958 // Move to first byte of possibly multibyte char.
1959 ccline.cmdpos -= utf_head_off(ccline.cmdbuff,
1960 ccline.cmdbuff + ccline.cmdpos);
1961 ccline.cmdspos -= cmdline_charsize(ccline.cmdpos);
1962 } while (ccline.cmdpos > 0
1963 && (s->c == K_S_LEFT || s->c == K_C_LEFT
1964 || (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL)))
1965 && ccline.cmdbuff[ccline.cmdpos - 1] != ' ');
1966
1967 ccline.cmdspos = cmd_screencol(ccline.cmdpos);
1968 if (ccline.special_char != NUL) {
1969 putcmdline(ccline.special_char, ccline.special_shift);
1970 }
1971
1972 return command_line_not_changed(s);
1973
1974 case K_IGNORE:
1975 // Ignore mouse event or open_cmdwin() result.
1976 return command_line_not_changed(s);
1977
1978
1979 case K_MIDDLEDRAG:
1980 case K_MIDDLERELEASE:
1981 return command_line_not_changed(s); // Ignore mouse
1982
1983 case K_MIDDLEMOUSE:
1984 cmdline_paste(eval_has_provider("clipboard") ? '*' : 0, true, true);
1985 redrawcmd();
1986 return command_line_changed(s);
1987
1988
1989 case K_LEFTDRAG:
1990 case K_LEFTRELEASE:
1991 case K_RIGHTDRAG:
1992 case K_RIGHTRELEASE:
1993 // Ignore drag and release events when the button-down wasn't
1994 // seen before.
1995 if (s->ignore_drag_release) {
1996 return command_line_not_changed(s);
1997 }
1998 FALLTHROUGH;
1999 case K_LEFTMOUSE:
2000 case K_RIGHTMOUSE:
2001 if (s->c == K_LEFTRELEASE || s->c == K_RIGHTRELEASE) {
2002 s->ignore_drag_release = true;
2003 } else {
2004 s->ignore_drag_release = false;
2005 }
2006
2007 ccline.cmdspos = cmd_startcol();
2008 for (ccline.cmdpos = 0; ccline.cmdpos < ccline.cmdlen;
2009 ccline.cmdpos++) {
2010 int cells = cmdline_charsize(ccline.cmdpos);
2011 if (mouse_row <= cmdline_row + ccline.cmdspos / Columns
2012 && mouse_col < ccline.cmdspos % Columns + cells) {
2013 break;
2014 }
2015
2016 // Count ">" for double-wide char that doesn't fit.
2017 correct_screencol(ccline.cmdpos, cells, &ccline.cmdspos);
2018 ccline.cmdpos += utfc_ptr2len(ccline.cmdbuff + ccline.cmdpos) - 1;
2019 ccline.cmdspos += cells;
2020 }
2021 return command_line_not_changed(s);
2022
2023 // Mouse scroll wheel: ignored here
2024 case K_MOUSEDOWN:
2025 case K_MOUSEUP:
2026 case K_MOUSELEFT:
2027 case K_MOUSERIGHT:
2028 // Alternate buttons ignored here
2029 case K_X1MOUSE:
2030 case K_X1DRAG:
2031 case K_X1RELEASE:
2032 case K_X2MOUSE:
2033 case K_X2DRAG:
2034 case K_X2RELEASE:
2035 case K_MOUSEMOVE:
2036 return command_line_not_changed(s);
2037
2038
2039 case K_SELECT: // end of Select mode mapping - ignore
2040 return command_line_not_changed(s);
2041
2042 case Ctrl_B: // begin of command line
2043 case K_HOME:
2044 case K_KHOME:
2045 case K_S_HOME:
2046 case K_C_HOME:
2047 ccline.cmdpos = 0;
2048 ccline.cmdspos = cmd_startcol();
2049 return command_line_not_changed(s);
2050
2051 case Ctrl_E: // end of command line
2052 case K_END:
2053 case K_KEND:
2054 case K_S_END:
2055 case K_C_END:
2056 ccline.cmdpos = ccline.cmdlen;
2057 ccline.cmdspos = cmd_screencol(ccline.cmdpos);
2058 return command_line_not_changed(s);
2059
2060 case Ctrl_A: // all matches
2061 if (nextwild(&s->xpc, WILD_ALL, 0, s->firstc != '@') == FAIL) {
2062 break;
2063 }
2064 return command_line_changed(s);
2065
2066 case Ctrl_L:
2067 if (may_add_char_to_search(s->firstc, &s->c, &s->is_state) == OK) {
2068 return command_line_not_changed(s);
2069 }
2070
2071 // completion: longest common part
2072 if (nextwild(&s->xpc, WILD_LONGEST, 0, s->firstc != '@') == FAIL) {
2073 break;
2074 }
2075 return command_line_changed(s);
2076
2077 case Ctrl_N: // next match
2078 case Ctrl_P: // previous match
2079 if (s->xpc.xp_numfiles > 0) {
2080 if (nextwild(&s->xpc, (s->c == Ctrl_P) ? WILD_PREV : WILD_NEXT,
2081 0, s->firstc != '@') == FAIL) {
2082 break;
2083 }
2084 return command_line_not_changed(s);
2085 }
2086 FALLTHROUGH;
2087
2088 case K_UP:
2089 case K_DOWN:
2090 case K_S_UP:
2091 case K_S_DOWN:
2092 case K_PAGEUP:
2093 case K_KPAGEUP:
2094 case K_PAGEDOWN:
2095 case K_KPAGEDOWN:
2096 if (s->histype == HIST_INVALID || hislen == 0 || s->firstc == NUL) {
2097 // no history
2098 return command_line_not_changed(s);
2099 }
2100
2101 s->save_hiscnt = s->hiscnt;
2102
2103 // save current command string so it can be restored later
2104 if (s->lookfor == NULL) {
2105 s->lookfor = vim_strsave(ccline.cmdbuff);
2106 s->lookfor[ccline.cmdpos] = NUL;
2107 }
2108
2109 bool next_match = (s->c == K_DOWN || s->c == K_S_DOWN || s->c == Ctrl_N
2110 || s->c == K_PAGEDOWN || s->c == K_KPAGEDOWN);
2111 command_line_next_histidx(s, next_match);
2112
2113 if (s->hiscnt != s->save_hiscnt) {
2114 // jumped to other entry
2115 char_u *p;
2116 int len = 0;
2117 int old_firstc;
2118
2119 xfree(ccline.cmdbuff);
2120 s->xpc.xp_context = EXPAND_NOTHING;
2121 if (s->hiscnt == hislen) {
2122 p = s->lookfor; // back to the old one
2123 } else {
2124 p = history[s->histype][s->hiscnt].hisstr;
2125 }
2126
2127 if (s->histype == HIST_SEARCH
2128 && p != s->lookfor
2129 && (old_firstc = p[STRLEN(p) + 1]) != s->firstc) {
2130 // Correct for the separator character used when
2131 // adding the history entry vs the one used now.
2132 // First loop: count length.
2133 // Second loop: copy the characters.
2134 for (int i = 0; i <= 1; i++) {
2135 len = 0;
2136 for (int j = 0; p[j] != NUL; j++) {
2137 // Replace old sep with new sep, unless it is
2138 // escaped.
2139 if (p[j] == old_firstc
2140 && (j == 0 || p[j - 1] != '\\')) {
2141 if (i > 0) {
2142 ccline.cmdbuff[len] = (char_u)s->firstc;
2143 }
2144 } else {
2145 // Escape new sep, unless it is already
2146 // escaped.
2147 if (p[j] == s->firstc
2148 && (j == 0 || p[j - 1] != '\\')) {
2149 if (i > 0) {
2150 ccline.cmdbuff[len] = '\\';
2151 }
2152 ++len;
2153 }
2154
2155 if (i > 0) {
2156 ccline.cmdbuff[len] = p[j];
2157 }
2158 }
2159 ++len;
2160 }
2161
2162 if (i == 0) {
2163 alloc_cmdbuff(len);
2164 }
2165 }
2166 ccline.cmdbuff[len] = NUL;
2167 } else {
2168 alloc_cmdbuff((int)STRLEN(p));
2169 STRCPY(ccline.cmdbuff, p);
2170 }
2171
2172 ccline.cmdpos = ccline.cmdlen = (int)STRLEN(ccline.cmdbuff);
2173 redrawcmd();
2174 return command_line_changed(s);
2175 }
2176 beep_flush();
2177 return command_line_not_changed(s);
2178
2179 case Ctrl_G: // next match
2180 case Ctrl_T: // previous match
2181 if (may_do_command_line_next_incsearch(s->firstc, s->count, &s->is_state,
2182 s->c == Ctrl_G) == FAIL) {
2183 return command_line_not_changed(s);
2184 }
2185 break;
2186
2187 case Ctrl_V:
2188 case Ctrl_Q:
2189 s->ignore_drag_release = true;
2190 putcmdline('^', true);
2191 s->c = get_literal(); // get next (two) character(s)
2192 s->do_abbr = false; // don't do abbreviation now
2193 ccline.special_char = NUL;
2194 // may need to remove ^ when composing char was typed
2195 if (utf_iscomposing(s->c) && !cmd_silent) {
2196 if (ui_has(kUICmdline)) {
2197 // TODO(bfredl): why not make unputcmdline also work with true?
2198 unputcmdline();
2199 } else {
2200 draw_cmdline(ccline.cmdpos, ccline.cmdlen - ccline.cmdpos);
2201 msg_putchar(' ');
2202 cursorcmd();
2203 }
2204 }
2205 break;
2206
2207 case Ctrl_K:
2208 s->ignore_drag_release = true;
2209 putcmdline('?', true);
2210 s->c = get_digraph(true);
2211 ccline.special_char = NUL;
2212
2213 if (s->c != NUL) {
2214 break;
2215 }
2216
2217 redrawcmd();
2218 return command_line_not_changed(s);
2219
2220 case Ctrl__: // CTRL-_: switch language mode
2221 if (!p_ari) {
2222 break;
2223 }
2224 cmd_hkmap = !cmd_hkmap;
2225 return command_line_not_changed(s);
2226
2227 default:
2228 // Normal character with no special meaning. Just set mod_mask
2229 // to 0x0 so that typing Shift-Space in the GUI doesn't enter
2230 // the string <S-Space>. This should only happen after ^V.
2231 if (!IS_SPECIAL(s->c)) {
2232 mod_mask = 0x0;
2233 }
2234 break;
2235 }
2236
2237 // End of switch on command line character.
2238 // We come here if we have a normal character.
2239 if (s->do_abbr && (IS_SPECIAL(s->c) || !vim_iswordc(s->c))
2240 // Add ABBR_OFF for characters above 0x100, this is
2241 // what check_abbr() expects.
2242 && (ccheck_abbr((s->c >= 0x100) ? (s->c + ABBR_OFF) : s->c)
2243 || s->c == Ctrl_RSB)) {
2244 return command_line_changed(s);
2245 }
2246
2247 // put the character in the command line
2248 if (IS_SPECIAL(s->c) || mod_mask != 0) {
2249 put_on_cmdline(get_special_key_name(s->c, mod_mask), -1, true);
2250 } else {
2251 int j = utf_char2bytes(s->c, IObuff);
2252 IObuff[j] = NUL; // exclude composing chars
2253 put_on_cmdline(IObuff, j, true);
2254 }
2255 return command_line_changed(s);
2256 }
2257
2258
command_line_not_changed(CommandLineState * s)2259 static int command_line_not_changed(CommandLineState *s)
2260 {
2261 // Incremental searches for "/" and "?":
2262 // Enter command_line_not_changed() when a character has been read but the
2263 // command line did not change. Then we only search and redraw if something
2264 // changed in the past.
2265 // Enter command_line_changed() when the command line did change.
2266 if (!s->is_state.incsearch_postponed) {
2267 return 1;
2268 }
2269 return command_line_changed(s);
2270 }
2271
2272 /// Guess that the pattern matches everything. Only finds specific cases, such
2273 /// as a trailing \|, which can happen while typing a pattern.
empty_pattern(char_u * p)2274 static int empty_pattern(char_u *p)
2275 {
2276 size_t n = STRLEN(p);
2277
2278 // remove trailing \v and the like
2279 while (n >= 2 && p[n - 2] == '\\'
2280 && vim_strchr((char_u *)"mMvVcCZ", p[n - 1]) != NULL) {
2281 n -= 2;
2282 }
2283 return n == 0 || (n >= 2 && p[n - 2] == '\\' && p[n - 1] == '|');
2284 }
2285
command_line_changed(CommandLineState * s)2286 static int command_line_changed(CommandLineState *s)
2287 {
2288 // Trigger CmdlineChanged autocommands.
2289 if (has_event(EVENT_CMDLINECHANGED)) {
2290 TryState tstate;
2291 Error err = ERROR_INIT;
2292 dict_T *dict = get_vim_var_dict(VV_EVENT);
2293
2294 char firstcbuf[2];
2295 firstcbuf[0] = (char)(s->firstc > 0 ? s->firstc : '-');
2296 firstcbuf[1] = 0;
2297
2298 // set v:event to a dictionary with information about the commandline
2299 tv_dict_add_str(dict, S_LEN("cmdtype"), firstcbuf);
2300 tv_dict_add_nr(dict, S_LEN("cmdlevel"), ccline.level);
2301 tv_dict_set_keys_readonly(dict);
2302 try_enter(&tstate);
2303
2304 apply_autocmds(EVENT_CMDLINECHANGED, (char_u *)firstcbuf,
2305 (char_u *)firstcbuf, false, curbuf);
2306 tv_dict_clear(dict);
2307
2308 bool tl_ret = try_leave(&tstate, &err);
2309 if (!tl_ret && ERROR_SET(&err)) {
2310 msg_putchar('\n');
2311 msg_printf_attr(HL_ATTR(HLF_E)|MSG_HIST, (char *)e_autocmd_err, err.msg);
2312 api_clear_error(&err);
2313 redrawcmd();
2314 }
2315 }
2316
2317 // 'incsearch' highlighting.
2318 if (s->firstc == ':'
2319 && current_sctx.sc_sid == 0 // only if interactive
2320 && *p_icm != NUL // 'inccommand' is set
2321 && curbuf->b_p_ma // buffer is modifiable
2322 && cmdline_star == 0 // not typing a password
2323 && cmd_can_preview(ccline.cmdbuff)
2324 && !vpeekc_any()) {
2325 // Show 'inccommand' preview. It works like this:
2326 // 1. Do the command.
2327 // 2. Command implementation detects CMDPREVIEW state, then:
2328 // - Update the screen while the effects are in place.
2329 // - Immediately undo the effects.
2330 State |= CMDPREVIEW;
2331 emsg_silent++; // Block error reporting as the command may be incomplete
2332 msg_silent++; // Block messages, namely ones that prompt
2333 do_cmdline(ccline.cmdbuff, NULL, NULL, DOCMD_KEEPLINE|DOCMD_NOWAIT|DOCMD_PREVIEW);
2334 msg_silent--; // Unblock messages
2335 emsg_silent--; // Unblock error reporting
2336
2337 // Restore the window "view".
2338 curwin->w_cursor = s->is_state.save_cursor;
2339 restore_viewstate(&s->is_state.old_viewstate);
2340 update_topline(curwin);
2341
2342 redrawcmdline();
2343 } else if (State & CMDPREVIEW) {
2344 State = (State & ~CMDPREVIEW);
2345 close_preview_windows();
2346 update_screen(SOME_VALID); // Clear 'inccommand' preview.
2347 } else {
2348 if (s->xpc.xp_context == EXPAND_NOTHING && (KeyTyped || vpeekc() == NUL)) {
2349 may_do_incsearch_highlighting(s->firstc, s->count, &s->is_state);
2350 }
2351 }
2352
2353 if (cmdmsg_rl || (p_arshape && !p_tbidi)) {
2354 // Always redraw the whole command line to fix shaping and
2355 // right-left typing. Not efficient, but it works.
2356 // Do it only when there are no characters left to read
2357 // to avoid useless intermediate redraws.
2358 // if cmdline is external the ui handles shaping, no redraw needed.
2359 if (!ui_has(kUICmdline) && vpeekc() == NUL) {
2360 redrawcmd();
2361 }
2362 }
2363
2364 return 1;
2365 }
2366
2367 /// Abandon the command line.
abandon_cmdline(void)2368 static void abandon_cmdline(void)
2369 {
2370 XFREE_CLEAR(ccline.cmdbuff);
2371 ccline.redraw_state = kCmdRedrawNone;
2372 if (msg_scrolled == 0) {
2373 compute_cmdrow();
2374 }
2375 msg("");
2376 redraw_cmdline = true;
2377 }
2378
2379 /// getcmdline() - accept a command line starting with firstc.
2380 ///
2381 /// firstc == ':' get ":" command line.
2382 /// firstc == '/' or '?' get search pattern
2383 /// firstc == '=' get expression
2384 /// firstc == '@' get text for input() function
2385 /// firstc == '>' get text for debug mode
2386 /// firstc == NUL get text for :insert command
2387 /// firstc == -1 like NUL, and break on CTRL-C
2388 ///
2389 /// The line is collected in ccline.cmdbuff, which is reallocated to fit the
2390 /// command line.
2391 ///
2392 /// Careful: getcmdline() can be called recursively!
2393 ///
2394 /// Return pointer to allocated string if there is a commandline, NULL
2395 /// otherwise.
2396 ///
2397 /// @param count only used for incremental search
2398 /// @param indent indent for inside conditionals
getcmdline(int firstc,long count,int indent,bool do_concat FUNC_ATTR_UNUSED)2399 char_u *getcmdline(int firstc, long count, int indent, bool do_concat FUNC_ATTR_UNUSED)
2400 {
2401 // Be prepared for situations where cmdline can be invoked recursively.
2402 // That includes cmd mappings, event handlers, as well as update_screen()
2403 // (custom status line eval), which all may invoke ":normal :".
2404 CmdlineInfo save_ccline;
2405 save_cmdline(&save_ccline);
2406 char_u *retval = command_line_enter(firstc, count, indent);
2407 restore_cmdline(&save_ccline);
2408 return retval;
2409 }
2410
2411 /// Get a command line with a prompt
2412 ///
2413 /// This is prepared to be called recursively from getcmdline() (e.g. by
2414 /// f_input() when evaluating an expression from `<C-r>=`).
2415 ///
2416 /// @param[in] firstc Prompt type: e.g. '@' for input(), '>' for debug.
2417 /// @param[in] prompt Prompt string: what is displayed before the user text.
2418 /// @param[in] attr Prompt highlighting.
2419 /// @param[in] xp_context Type of expansion.
2420 /// @param[in] xp_arg User-defined expansion argument.
2421 /// @param[in] highlight_callback Callback used for highlighting user input.
2422 ///
2423 /// @return [allocated] Command line or NULL.
getcmdline_prompt(const char firstc,const char * const prompt,const int attr,const int xp_context,const char * const xp_arg,const Callback highlight_callback)2424 char *getcmdline_prompt(const char firstc, const char *const prompt, const int attr,
2425 const int xp_context, const char *const xp_arg,
2426 const Callback highlight_callback)
2427 FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_MALLOC
2428 {
2429 const int msg_col_save = msg_col;
2430
2431 CmdlineInfo save_ccline;
2432 save_cmdline(&save_ccline);
2433
2434 ccline.prompt_id = last_prompt_id++;
2435 ccline.cmdprompt = (char_u *)prompt;
2436 ccline.cmdattr = attr;
2437 ccline.xp_context = xp_context;
2438 ccline.xp_arg = (char_u *)xp_arg;
2439 ccline.input_fn = (firstc == '@');
2440 ccline.highlight_callback = highlight_callback;
2441
2442 int msg_silent_saved = msg_silent;
2443 msg_silent = 0;
2444
2445 char *const ret = (char *)command_line_enter(firstc, 1L, 0);
2446
2447 restore_cmdline(&save_ccline);
2448 msg_silent = msg_silent_saved;
2449 // Restore msg_col, the prompt from input() may have changed it.
2450 // But only if called recursively and the commandline is therefore being
2451 // restored to an old one; if not, the input() prompt stays on the screen,
2452 // so we need its modified msg_col left intact.
2453 if (ccline.cmdbuff != NULL) {
2454 msg_col = msg_col_save;
2455 }
2456
2457 return ret;
2458 }
2459
2460 /*
2461 * Return TRUE when the text must not be changed and we can't switch to
2462 * another window or buffer. Used when editing the command line etc.
2463 */
text_locked(void)2464 int text_locked(void)
2465 {
2466 if (cmdwin_type != 0) {
2467 return TRUE;
2468 }
2469 return textlock != 0;
2470 }
2471
2472 /*
2473 * Give an error message for a command that isn't allowed while the cmdline
2474 * window is open or editing the cmdline in another way.
2475 */
text_locked_msg(void)2476 void text_locked_msg(void)
2477 {
2478 emsg(_(get_text_locked_msg()));
2479 }
2480
get_text_locked_msg(void)2481 char *get_text_locked_msg(void)
2482 {
2483 if (cmdwin_type != 0) {
2484 return e_cmdwin;
2485 } else {
2486 return e_secure;
2487 }
2488 }
2489
2490 /// Check if "curbuf->b_ro_locked" or "allbuf_lock" is set and
2491 /// return TRUE when it is and give an error message.
curbuf_locked(void)2492 int curbuf_locked(void)
2493 {
2494 if (curbuf->b_ro_locked > 0) {
2495 emsg(_("E788: Not allowed to edit another buffer now"));
2496 return TRUE;
2497 }
2498 return allbuf_locked();
2499 }
2500
2501 /*
2502 * Check if "allbuf_lock" is set and return TRUE when it is and give an error
2503 * message.
2504 */
allbuf_locked(void)2505 int allbuf_locked(void)
2506 {
2507 if (allbuf_lock > 0) {
2508 emsg(_("E811: Not allowed to change buffer information now"));
2509 return TRUE;
2510 }
2511 return FALSE;
2512 }
2513
cmdline_charsize(int idx)2514 static int cmdline_charsize(int idx)
2515 {
2516 if (cmdline_star > 0) { // showing '*', always 1 position
2517 return 1;
2518 }
2519 return ptr2cells(ccline.cmdbuff + idx);
2520 }
2521
2522 /// Compute the offset of the cursor on the command line for the prompt and
2523 /// indent.
cmd_startcol(void)2524 static int cmd_startcol(void)
2525 {
2526 return ccline.cmdindent + ((ccline.cmdfirstc != NUL) ? 1 : 0);
2527 }
2528
2529
2530 /// Compute the column position for a byte position on the command line.
cmd_screencol(int bytepos)2531 static int cmd_screencol(int bytepos)
2532 {
2533 int m; // maximum column
2534
2535 int col = cmd_startcol();
2536 if (KeyTyped) {
2537 m = Columns * Rows;
2538 if (m < 0) { // overflow, Columns or Rows at weird value
2539 m = MAXCOL;
2540 }
2541 } else {
2542 m = MAXCOL;
2543 }
2544
2545 for (int i = 0; i < ccline.cmdlen && i < bytepos;
2546 i += utfc_ptr2len(ccline.cmdbuff + i)) {
2547 int c = cmdline_charsize(i);
2548 // Count ">" for double-wide multi-byte char that doesn't fit.
2549 correct_screencol(i, c, &col);
2550
2551 // If the cmdline doesn't fit, show cursor on last visible char.
2552 // Don't move the cursor itself, so we can still append.
2553 if ((col += c) >= m) {
2554 col -= c;
2555 break;
2556 }
2557 }
2558 return col;
2559 }
2560
2561 /// Check if the character at "idx", which is "cells" wide, is a multi-byte
2562 /// character that doesn't fit, so that a ">" must be displayed.
correct_screencol(int idx,int cells,int * col)2563 static void correct_screencol(int idx, int cells, int *col)
2564 {
2565 if (utfc_ptr2len(ccline.cmdbuff + idx) > 1
2566 && utf_ptr2cells(ccline.cmdbuff + idx) > 1
2567 && (*col) % Columns + cells > Columns) {
2568 (*col)++;
2569 }
2570 }
2571
2572 /// Get an Ex command line for the ":" command.
2573 ///
2574 /// @param c normally ':', NUL for ":append"
2575 /// @param indent indent for inside conditionals
getexline(int c,void * cookie,int indent,bool do_concat)2576 char_u *getexline(int c, void *cookie, int indent, bool do_concat)
2577 {
2578 // When executing a register, remove ':' that's in front of each line.
2579 if (exec_from_reg && vpeekc() == ':') {
2580 (void)vgetc();
2581 }
2582
2583 return getcmdline(c, 1L, indent, do_concat);
2584 }
2585
cmdline_overstrike(void)2586 bool cmdline_overstrike(void)
2587 {
2588 return ccline.overstrike;
2589 }
2590
2591
2592 /// Return true if the cursor is at the end of the cmdline.
cmdline_at_end(void)2593 bool cmdline_at_end(void)
2594 {
2595 return (ccline.cmdpos >= ccline.cmdlen);
2596 }
2597
2598 /*
2599 * Allocate a new command line buffer.
2600 * Assigns the new buffer to ccline.cmdbuff and ccline.cmdbufflen.
2601 * Returns the new value of ccline.cmdbuff and ccline.cmdbufflen.
2602 */
alloc_cmdbuff(int len)2603 static void alloc_cmdbuff(int len)
2604 {
2605 /*
2606 * give some extra space to avoid having to allocate all the time
2607 */
2608 if (len < 80) {
2609 len = 100;
2610 } else {
2611 len += 20;
2612 }
2613
2614 ccline.cmdbuff = xmalloc((size_t)len);
2615 ccline.cmdbufflen = len;
2616 }
2617
2618 /*
2619 * Re-allocate the command line to length len + something extra.
2620 */
realloc_cmdbuff(int len)2621 static void realloc_cmdbuff(int len)
2622 {
2623 if (len < ccline.cmdbufflen) {
2624 return; // no need to resize
2625 }
2626
2627 char_u *p = ccline.cmdbuff;
2628 alloc_cmdbuff(len); // will get some more
2629 // There isn't always a NUL after the command, but it may need to be
2630 // there, thus copy up to the NUL and add a NUL.
2631 memmove(ccline.cmdbuff, p, (size_t)ccline.cmdlen);
2632 ccline.cmdbuff[ccline.cmdlen] = NUL;
2633 xfree(p);
2634
2635 if (ccline.xpc != NULL
2636 && ccline.xpc->xp_pattern != NULL
2637 && ccline.xpc->xp_context != EXPAND_NOTHING
2638 && ccline.xpc->xp_context != EXPAND_UNSUCCESSFUL) {
2639 int i = (int)(ccline.xpc->xp_pattern - p);
2640
2641 // If xp_pattern points inside the old cmdbuff it needs to be adjusted
2642 // to point into the newly allocated memory.
2643 if (i >= 0 && i <= ccline.cmdlen) {
2644 ccline.xpc->xp_pattern = ccline.cmdbuff + i;
2645 }
2646 }
2647 }
2648
2649 static char_u *arshape_buf = NULL;
2650
2651 #if defined(EXITFREE)
free_arshape_buf(void)2652 void free_arshape_buf(void)
2653 {
2654 xfree(arshape_buf);
2655 }
2656
2657 #endif
2658
2659 enum { MAX_CB_ERRORS = 1, };
2660
2661 /// Color expression cmdline using built-in expressions parser
2662 ///
2663 /// @param[in] colored_ccline Command-line to color.
2664 /// @param[out] ret_ccline_colors What should be colored.
2665 ///
2666 /// Always colors the whole cmdline.
color_expr_cmdline(const CmdlineInfo * const colored_ccline,ColoredCmdline * const ret_ccline_colors)2667 static void color_expr_cmdline(const CmdlineInfo *const colored_ccline,
2668 ColoredCmdline *const ret_ccline_colors)
2669 FUNC_ATTR_NONNULL_ALL
2670 {
2671 ParserLine parser_lines[] = {
2672 {
2673 .data = (const char *)colored_ccline->cmdbuff,
2674 .size = STRLEN(colored_ccline->cmdbuff),
2675 .allocated = false,
2676 },
2677 { NULL, 0, false },
2678 };
2679 ParserLine *plines_p = parser_lines;
2680 ParserHighlight colors;
2681 kvi_init(colors);
2682 ParserState pstate;
2683 viml_parser_init(&pstate, parser_simple_get_line, &plines_p, &colors);
2684 ExprAST east = viml_pexpr_parse(&pstate, kExprFlagsDisallowEOC);
2685 viml_pexpr_free_ast(east);
2686 viml_parser_destroy(&pstate);
2687 kv_resize(ret_ccline_colors->colors, kv_size(colors));
2688 size_t prev_end = 0;
2689 for (size_t i = 0; i < kv_size(colors); i++) {
2690 const ParserHighlightChunk chunk = kv_A(colors, i);
2691 assert(chunk.start.col < INT_MAX);
2692 assert(chunk.end_col < INT_MAX);
2693 if (chunk.start.col != prev_end) {
2694 kv_push(ret_ccline_colors->colors, ((CmdlineColorChunk) {
2695 .start = (int)prev_end,
2696 .end = (int)chunk.start.col,
2697 .attr = 0,
2698 }));
2699 }
2700 const int id = syn_name2id(chunk.group);
2701 const int attr = (id == 0 ? 0 : syn_id2attr(id));
2702 kv_push(ret_ccline_colors->colors, ((CmdlineColorChunk) {
2703 .start = (int)chunk.start.col,
2704 .end = (int)chunk.end_col,
2705 .attr = attr,
2706 }));
2707 prev_end = chunk.end_col;
2708 }
2709 if (prev_end < (size_t)colored_ccline->cmdlen) {
2710 kv_push(ret_ccline_colors->colors, ((CmdlineColorChunk) {
2711 .start = (int)prev_end,
2712 .end = colored_ccline->cmdlen,
2713 .attr = 0,
2714 }));
2715 }
2716 kvi_destroy(colors);
2717 }
2718
2719 /// Color command-line
2720 ///
2721 /// Should use built-in command parser or user-specified one. Currently only the
2722 /// latter is supported.
2723 ///
2724 /// @param[in,out] colored_ccline Command-line to color. Also holds a cache:
2725 /// if ->prompt_id and ->cmdbuff values happen
2726 /// to be equal to those from colored_cmdline it
2727 /// will just do nothing, assuming that ->colors
2728 /// already contains needed data.
2729 ///
2730 /// Always colors the whole cmdline.
2731 ///
2732 /// @return true if draw_cmdline may proceed, false if it does not need anything
2733 /// to do.
color_cmdline(CmdlineInfo * colored_ccline)2734 static bool color_cmdline(CmdlineInfo *colored_ccline)
2735 FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
2736 {
2737 bool printed_errmsg = false;
2738
2739 #define PRINT_ERRMSG(...) \
2740 do { \
2741 msg_putchar('\n'); \
2742 msg_printf_attr(HL_ATTR(HLF_E)|MSG_HIST, __VA_ARGS__); \
2743 printed_errmsg = true; \
2744 } while (0)
2745 bool ret = true;
2746
2747 ColoredCmdline *ccline_colors = &colored_ccline->last_colors;
2748
2749 // Check whether result of the previous call is still valid.
2750 if (ccline_colors->prompt_id == colored_ccline->prompt_id
2751 && ccline_colors->cmdbuff != NULL
2752 && STRCMP(ccline_colors->cmdbuff, colored_ccline->cmdbuff) == 0) {
2753 return ret;
2754 }
2755
2756 kv_size(ccline_colors->colors) = 0;
2757
2758 if (colored_ccline->cmdbuff == NULL || *colored_ccline->cmdbuff == NUL) {
2759 // Nothing to do, exiting.
2760 XFREE_CLEAR(ccline_colors->cmdbuff);
2761 return ret;
2762 }
2763
2764 bool arg_allocated = false;
2765 typval_T arg = {
2766 .v_type = VAR_STRING,
2767 .vval.v_string = colored_ccline->cmdbuff,
2768 };
2769 typval_T tv = { .v_type = VAR_UNKNOWN };
2770
2771 static unsigned prev_prompt_id = UINT_MAX;
2772 static int prev_prompt_errors = 0;
2773 Callback color_cb = CALLBACK_NONE;
2774 bool can_free_cb = false;
2775 TryState tstate;
2776 Error err = ERROR_INIT;
2777 const char *err_errmsg = (const char *)e_intern2;
2778 bool dgc_ret = true;
2779 bool tl_ret = true;
2780
2781 if (colored_ccline->prompt_id != prev_prompt_id) {
2782 prev_prompt_errors = 0;
2783 prev_prompt_id = colored_ccline->prompt_id;
2784 } else if (prev_prompt_errors >= MAX_CB_ERRORS) {
2785 goto color_cmdline_end;
2786 }
2787 if (colored_ccline->highlight_callback.type != kCallbackNone) {
2788 // Currently this should only happen while processing input() prompts.
2789 assert(colored_ccline->input_fn);
2790 color_cb = colored_ccline->highlight_callback;
2791 } else if (colored_ccline->cmdfirstc == ':') {
2792 try_enter(&tstate);
2793 err_errmsg = N_("E5408: Unable to get g:Nvim_color_cmdline callback: %s");
2794 dgc_ret = tv_dict_get_callback(&globvardict, S_LEN("Nvim_color_cmdline"),
2795 &color_cb);
2796 tl_ret = try_leave(&tstate, &err);
2797 can_free_cb = true;
2798 } else if (colored_ccline->cmdfirstc == '=') {
2799 color_expr_cmdline(colored_ccline, ccline_colors);
2800 }
2801 if (!tl_ret || !dgc_ret) {
2802 goto color_cmdline_error;
2803 }
2804
2805 if (color_cb.type == kCallbackNone) {
2806 goto color_cmdline_end;
2807 }
2808 if (colored_ccline->cmdbuff[colored_ccline->cmdlen] != NUL) {
2809 arg_allocated = true;
2810 arg.vval.v_string = xmemdupz((const char *)colored_ccline->cmdbuff,
2811 (size_t)colored_ccline->cmdlen);
2812 }
2813 // msg_start() called by e.g. :echo may shift command-line to the first column
2814 // even though msg_silent is here. Two ways to workaround this problem without
2815 // altering message.c: use full_screen or save and restore msg_col.
2816 //
2817 // Saving and restoring full_screen does not work well with :redraw!. Saving
2818 // and restoring msg_col is neither ideal, but while with full_screen it
2819 // appears shifted one character to the right and cursor position is no longer
2820 // correct, with msg_col it just misses leading `:`. Since `redraw!` in
2821 // callback lags this is least of the user problems.
2822 //
2823 // Also using try_enter() because error messages may overwrite typed
2824 // command-line which is not expected.
2825 getln_interrupted_highlight = false;
2826 try_enter(&tstate);
2827 err_errmsg = N_("E5407: Callback has thrown an exception: %s");
2828 const int saved_msg_col = msg_col;
2829 msg_silent++;
2830 const bool cbcall_ret = callback_call(&color_cb, 1, &arg, &tv);
2831 msg_silent--;
2832 msg_col = saved_msg_col;
2833 if (got_int) {
2834 getln_interrupted_highlight = true;
2835 }
2836 if (!try_leave(&tstate, &err) || !cbcall_ret) {
2837 goto color_cmdline_error;
2838 }
2839 if (tv.v_type != VAR_LIST) {
2840 PRINT_ERRMSG("%s", _("E5400: Callback should return list"));
2841 goto color_cmdline_error;
2842 }
2843 if (tv.vval.v_list == NULL) {
2844 goto color_cmdline_end;
2845 }
2846 varnumber_T prev_end = 0;
2847 int i = 0;
2848 TV_LIST_ITER_CONST(tv.vval.v_list, li, {
2849 if (TV_LIST_ITEM_TV(li)->v_type != VAR_LIST) {
2850 PRINT_ERRMSG(_("E5401: List item %i is not a List"), i);
2851 goto color_cmdline_error;
2852 }
2853 const list_T *const l = TV_LIST_ITEM_TV(li)->vval.v_list;
2854 if (tv_list_len(l) != 3) {
2855 PRINT_ERRMSG(_("E5402: List item %i has incorrect length: %d /= 3"),
2856 i, tv_list_len(l));
2857 goto color_cmdline_error;
2858 }
2859 bool error = false;
2860 const varnumber_T start = (
2861 tv_get_number_chk(TV_LIST_ITEM_TV(tv_list_first(l)), &error));
2862 if (error) {
2863 goto color_cmdline_error;
2864 } else if (!(prev_end <= start && start < colored_ccline->cmdlen)) {
2865 PRINT_ERRMSG(_("E5403: Chunk %i start %" PRIdVARNUMBER " not in range "
2866 "[%" PRIdVARNUMBER ", %i)"),
2867 i, start, prev_end, colored_ccline->cmdlen);
2868 goto color_cmdline_error;
2869 } else if (utf8len_tab_zero[(uint8_t)colored_ccline->cmdbuff[start]] == 0) {
2870 PRINT_ERRMSG(_("E5405: Chunk %i start %" PRIdVARNUMBER " splits "
2871 "multibyte character"), i, start);
2872 goto color_cmdline_error;
2873 }
2874 if (start != prev_end) {
2875 kv_push(ccline_colors->colors, ((CmdlineColorChunk) {
2876 .start = (int)prev_end,
2877 .end = (int)start,
2878 .attr = 0,
2879 }));
2880 }
2881 const varnumber_T end =
2882 tv_get_number_chk(TV_LIST_ITEM_TV(TV_LIST_ITEM_NEXT(l, tv_list_first(l))), &error);
2883 if (error) {
2884 goto color_cmdline_error;
2885 } else if (!(start < end && end <= colored_ccline->cmdlen)) {
2886 PRINT_ERRMSG(_("E5404: Chunk %i end %" PRIdVARNUMBER " not in range "
2887 "(%" PRIdVARNUMBER ", %i]"),
2888 i, end, start, colored_ccline->cmdlen);
2889 goto color_cmdline_error;
2890 } else if (end < colored_ccline->cmdlen
2891 && (utf8len_tab_zero[(uint8_t)colored_ccline->cmdbuff[end]]
2892 == 0)) {
2893 PRINT_ERRMSG(_("E5406: Chunk %i end %" PRIdVARNUMBER " splits multibyte "
2894 "character"), i, end);
2895 goto color_cmdline_error;
2896 }
2897 prev_end = end;
2898 const char *const group = tv_get_string_chk(TV_LIST_ITEM_TV(tv_list_last(l)));
2899 if (group == NULL) {
2900 goto color_cmdline_error;
2901 }
2902 const int id = syn_name2id(group);
2903 const int attr = (id == 0 ? 0 : syn_id2attr(id));
2904 kv_push(ccline_colors->colors, ((CmdlineColorChunk) {
2905 .start = (int)start,
2906 .end = (int)end,
2907 .attr = attr,
2908 }));
2909 i++;
2910 });
2911 if (prev_end < colored_ccline->cmdlen) {
2912 kv_push(ccline_colors->colors, ((CmdlineColorChunk) {
2913 .start = (int)prev_end,
2914 .end = colored_ccline->cmdlen,
2915 .attr = 0,
2916 }));
2917 }
2918 prev_prompt_errors = 0;
2919 color_cmdline_end:
2920 assert(!ERROR_SET(&err));
2921 if (can_free_cb) {
2922 callback_free(&color_cb);
2923 }
2924 xfree(ccline_colors->cmdbuff);
2925 // Note: errors “output” is cached just as well as regular results.
2926 ccline_colors->prompt_id = colored_ccline->prompt_id;
2927 if (arg_allocated) {
2928 ccline_colors->cmdbuff = (char *)arg.vval.v_string;
2929 } else {
2930 ccline_colors->cmdbuff = xmemdupz((const char *)colored_ccline->cmdbuff,
2931 (size_t)colored_ccline->cmdlen);
2932 }
2933 tv_clear(&tv);
2934 return ret;
2935 color_cmdline_error:
2936 if (ERROR_SET(&err)) {
2937 PRINT_ERRMSG(_(err_errmsg), err.msg);
2938 api_clear_error(&err);
2939 }
2940 assert(printed_errmsg);
2941 (void)printed_errmsg;
2942
2943 prev_prompt_errors++;
2944 kv_size(ccline_colors->colors) = 0;
2945 redrawcmdline();
2946 ret = false;
2947 goto color_cmdline_end;
2948 #undef PRINT_ERRMSG
2949 }
2950
2951 /*
2952 * Draw part of the cmdline at the current cursor position. But draw stars
2953 * when cmdline_star is TRUE.
2954 */
draw_cmdline(int start,int len)2955 static void draw_cmdline(int start, int len)
2956 {
2957 if (!color_cmdline(&ccline)) {
2958 return;
2959 }
2960
2961 if (ui_has(kUICmdline)) {
2962 ccline.special_char = NUL;
2963 ccline.redraw_state = kCmdRedrawAll;
2964 return;
2965 }
2966
2967 if (cmdline_star > 0) {
2968 for (int i = 0; i < len; i++) {
2969 msg_putchar('*');
2970 i += utfc_ptr2len(ccline.cmdbuff + start + i) - 1;
2971 }
2972 } else if (p_arshape && !p_tbidi && len > 0) {
2973 bool do_arabicshape = false;
2974 int mb_l;
2975 for (int i = start; i < start + len; i += mb_l) {
2976 char_u *p = ccline.cmdbuff + i;
2977 int u8cc[MAX_MCO];
2978 int u8c = utfc_ptr2char_len(p, u8cc, start + len - i);
2979 mb_l = utfc_ptr2len_len(p, start + len - i);
2980 if (arabic_char(u8c)) {
2981 do_arabicshape = true;
2982 break;
2983 }
2984 }
2985 if (!do_arabicshape) {
2986 goto draw_cmdline_no_arabicshape;
2987 }
2988
2989 static size_t buflen = 0;
2990 assert(len >= 0);
2991
2992 // Do arabic shaping into a temporary buffer. This is very
2993 // inefficient!
2994 if ((size_t)len * 2 + 2 > buflen) {
2995 // Re-allocate the buffer. We keep it around to avoid a lot of
2996 // alloc()/free() calls.
2997 xfree(arshape_buf);
2998 buflen = (size_t)len * 2 + 2;
2999 arshape_buf = xmalloc(buflen);
3000 }
3001
3002 int newlen = 0;
3003 if (utf_iscomposing(utf_ptr2char(ccline.cmdbuff + start))) {
3004 // Prepend a space to draw the leading composing char on.
3005 arshape_buf[0] = ' ';
3006 newlen = 1;
3007 }
3008
3009 int prev_c = 0;
3010 int prev_c1 = 0;
3011 for (int i = start; i < start + len; i += mb_l) {
3012 char_u *p = ccline.cmdbuff + i;
3013 int u8cc[MAX_MCO];
3014 int u8c = utfc_ptr2char_len(p, u8cc, start + len - i);
3015 mb_l = utfc_ptr2len_len(p, start + len - i);
3016 if (arabic_char(u8c)) {
3017 int pc;
3018 int pc1 = 0;
3019 int nc = 0;
3020 // Do Arabic shaping.
3021 if (cmdmsg_rl) {
3022 // Displaying from right to left.
3023 pc = prev_c;
3024 pc1 = prev_c1;
3025 prev_c1 = u8cc[0];
3026 if (i + mb_l >= start + len) {
3027 nc = NUL;
3028 } else {
3029 nc = utf_ptr2char(p + mb_l);
3030 }
3031 } else {
3032 // Displaying from left to right.
3033 if (i + mb_l >= start + len) {
3034 pc = NUL;
3035 } else {
3036 int pcc[MAX_MCO];
3037
3038 pc = utfc_ptr2char_len(p + mb_l, pcc, start + len - i - mb_l);
3039 pc1 = pcc[0];
3040 }
3041 nc = prev_c;
3042 }
3043 prev_c = u8c;
3044
3045 u8c = arabic_shape(u8c, NULL, &u8cc[0], pc, pc1, nc);
3046
3047 newlen += utf_char2bytes(u8c, arshape_buf + newlen);
3048 if (u8cc[0] != 0) {
3049 newlen += utf_char2bytes(u8cc[0], arshape_buf + newlen);
3050 if (u8cc[1] != 0) {
3051 newlen += utf_char2bytes(u8cc[1], arshape_buf + newlen);
3052 }
3053 }
3054 } else {
3055 prev_c = u8c;
3056 memmove(arshape_buf + newlen, p, (size_t)mb_l);
3057 newlen += mb_l;
3058 }
3059 }
3060
3061 msg_outtrans_len(arshape_buf, newlen);
3062 } else {
3063 draw_cmdline_no_arabicshape:
3064 if (kv_size(ccline.last_colors.colors)) {
3065 for (size_t i = 0; i < kv_size(ccline.last_colors.colors); i++) {
3066 CmdlineColorChunk chunk = kv_A(ccline.last_colors.colors, i);
3067 if (chunk.end <= start) {
3068 continue;
3069 }
3070 const int chunk_start = MAX(chunk.start, start);
3071 msg_outtrans_len_attr(ccline.cmdbuff + chunk_start,
3072 chunk.end - chunk_start,
3073 chunk.attr);
3074 }
3075 } else {
3076 msg_outtrans_len(ccline.cmdbuff + start, len);
3077 }
3078 }
3079 }
3080
ui_ext_cmdline_show(CmdlineInfo * line)3081 static void ui_ext_cmdline_show(CmdlineInfo *line)
3082 {
3083 Array content = ARRAY_DICT_INIT;
3084 if (cmdline_star) {
3085 size_t len = 0;
3086 for (char_u *p = ccline.cmdbuff; *p; MB_PTR_ADV(p)) {
3087 len++;
3088 }
3089 char *buf = xmallocz(len);
3090 memset(buf, '*', len);
3091 Array item = ARRAY_DICT_INIT;
3092 ADD(item, INTEGER_OBJ(0));
3093 ADD(item, STRING_OBJ(((String) { .data = buf, .size = len })));
3094 ADD(content, ARRAY_OBJ(item));
3095 } else if (kv_size(line->last_colors.colors)) {
3096 for (size_t i = 0; i < kv_size(line->last_colors.colors); i++) {
3097 CmdlineColorChunk chunk = kv_A(line->last_colors.colors, i);
3098 Array item = ARRAY_DICT_INIT;
3099 ADD(item, INTEGER_OBJ(chunk.attr));
3100
3101 assert(chunk.end >= chunk.start);
3102 ADD(item, STRING_OBJ(cbuf_to_string((char *)line->cmdbuff + chunk.start,
3103 (size_t)(chunk.end-chunk.start))));
3104 ADD(content, ARRAY_OBJ(item));
3105 }
3106 } else {
3107 Array item = ARRAY_DICT_INIT;
3108 ADD(item, INTEGER_OBJ(0));
3109 ADD(item, STRING_OBJ(cstr_to_string((char *)(line->cmdbuff))));
3110 ADD(content, ARRAY_OBJ(item));
3111 }
3112 ui_call_cmdline_show(content, line->cmdpos,
3113 cchar_to_string((char)line->cmdfirstc),
3114 cstr_to_string((char *)(line->cmdprompt)),
3115 line->cmdindent,
3116 line->level);
3117 if (line->special_char) {
3118 ui_call_cmdline_special_char(cchar_to_string(line->special_char),
3119 line->special_shift,
3120 line->level);
3121 }
3122 }
3123
ui_ext_cmdline_block_append(size_t indent,const char * line)3124 void ui_ext_cmdline_block_append(size_t indent, const char *line)
3125 {
3126 char *buf = xmallocz(indent + strlen(line));
3127 memset(buf, ' ', indent);
3128 memcpy(buf + indent, line, strlen(line)); // -V575
3129
3130 Array item = ARRAY_DICT_INIT;
3131 ADD(item, INTEGER_OBJ(0));
3132 ADD(item, STRING_OBJ(cstr_as_string(buf)));
3133 Array content = ARRAY_DICT_INIT;
3134 ADD(content, ARRAY_OBJ(item));
3135 ADD(cmdline_block, ARRAY_OBJ(content));
3136 if (cmdline_block.size > 1) {
3137 ui_call_cmdline_block_append(copy_array(content));
3138 } else {
3139 ui_call_cmdline_block_show(copy_array(cmdline_block));
3140 }
3141 }
3142
ui_ext_cmdline_block_leave(void)3143 void ui_ext_cmdline_block_leave(void)
3144 {
3145 api_free_array(cmdline_block);
3146 cmdline_block = (Array)ARRAY_DICT_INIT;
3147 ui_call_cmdline_block_hide();
3148 }
3149
3150 /// Extra redrawing needed for redraw! and on ui_attach
3151 /// assumes "redrawcmdline()" will already be invoked
cmdline_screen_cleared(void)3152 void cmdline_screen_cleared(void)
3153 {
3154 if (!ui_has(kUICmdline)) {
3155 return;
3156 }
3157
3158 if (cmdline_block.size) {
3159 ui_call_cmdline_block_show(copy_array(cmdline_block));
3160 }
3161
3162 int prev_level = ccline.level-1;
3163 CmdlineInfo *line = ccline.prev_ccline;
3164 while (prev_level > 0 && line) {
3165 if (line->level == prev_level) {
3166 // don't redraw a cmdline already shown in the cmdline window
3167 if (prev_level != cmdwin_level) {
3168 line->redraw_state = kCmdRedrawAll;
3169 }
3170 prev_level--;
3171 }
3172 line = line->prev_ccline;
3173 }
3174 }
3175
3176 /// called by ui_flush, do what redraws necessary to keep cmdline updated.
cmdline_ui_flush(void)3177 void cmdline_ui_flush(void)
3178 {
3179 if (!ui_has(kUICmdline)) {
3180 return;
3181 }
3182 int level = ccline.level;
3183 CmdlineInfo *line = &ccline;
3184 while (level > 0 && line) {
3185 if (line->level == level) {
3186 if (line->redraw_state == kCmdRedrawAll) {
3187 ui_ext_cmdline_show(line);
3188 } else if (line->redraw_state == kCmdRedrawPos) {
3189 ui_call_cmdline_pos(line->cmdpos, line->level);
3190 }
3191 line->redraw_state = kCmdRedrawNone;
3192 level--;
3193 }
3194 line = line->prev_ccline;
3195 }
3196 }
3197
3198 /*
3199 * Put a character on the command line. Shifts the following text to the
3200 * right when "shift" is TRUE. Used for CTRL-V, CTRL-K, etc.
3201 * "c" must be printable (fit in one display cell)!
3202 */
putcmdline(char c,int shift)3203 void putcmdline(char c, int shift)
3204 {
3205 if (cmd_silent) {
3206 return;
3207 }
3208 if (!ui_has(kUICmdline)) {
3209 msg_no_more = true;
3210 msg_putchar(c);
3211 if (shift) {
3212 draw_cmdline(ccline.cmdpos, ccline.cmdlen - ccline.cmdpos);
3213 }
3214 msg_no_more = false;
3215 } else if (ccline.redraw_state != kCmdRedrawAll) {
3216 ui_call_cmdline_special_char(cchar_to_string(c), shift,
3217 ccline.level);
3218 }
3219 cursorcmd();
3220 ccline.special_char = c;
3221 ccline.special_shift = shift;
3222 ui_cursor_shape();
3223 }
3224
3225 /// Undo a putcmdline(c, FALSE).
unputcmdline(void)3226 void unputcmdline(void)
3227 {
3228 if (cmd_silent) {
3229 return;
3230 }
3231 msg_no_more = true;
3232 if (ccline.cmdlen == ccline.cmdpos && !ui_has(kUICmdline)) {
3233 msg_putchar(' ');
3234 } else {
3235 draw_cmdline(ccline.cmdpos, utfc_ptr2len(ccline.cmdbuff + ccline.cmdpos));
3236 }
3237 msg_no_more = false;
3238 cursorcmd();
3239 ccline.special_char = NUL;
3240 ui_cursor_shape();
3241 }
3242
3243 /*
3244 * Put the given string, of the given length, onto the command line.
3245 * If len is -1, then STRLEN() is used to calculate the length.
3246 * If 'redraw' is TRUE then the new part of the command line, and the remaining
3247 * part will be redrawn, otherwise it will not. If this function is called
3248 * twice in a row, then 'redraw' should be FALSE and redrawcmd() should be
3249 * called afterwards.
3250 */
put_on_cmdline(char_u * str,int len,int redraw)3251 void put_on_cmdline(char_u *str, int len, int redraw)
3252 {
3253 int i;
3254 int m;
3255 int c;
3256
3257 if (len < 0) {
3258 len = (int)STRLEN(str);
3259 }
3260
3261 realloc_cmdbuff(ccline.cmdlen + len + 1);
3262
3263 if (!ccline.overstrike) {
3264 memmove(ccline.cmdbuff + ccline.cmdpos + len,
3265 ccline.cmdbuff + ccline.cmdpos,
3266 (size_t)(ccline.cmdlen - ccline.cmdpos));
3267 ccline.cmdlen += len;
3268 } else {
3269 // Count nr of characters in the new string.
3270 m = 0;
3271 for (i = 0; i < len; i += utfc_ptr2len(str + i)) {
3272 m++;
3273 }
3274 // Count nr of bytes in cmdline that are overwritten by these
3275 // characters.
3276 for (i = ccline.cmdpos; i < ccline.cmdlen && m > 0;
3277 i += utfc_ptr2len(ccline.cmdbuff + i)) {
3278 m--;
3279 }
3280 if (i < ccline.cmdlen) {
3281 memmove(ccline.cmdbuff + ccline.cmdpos + len,
3282 ccline.cmdbuff + i, (size_t)(ccline.cmdlen - i));
3283 ccline.cmdlen += ccline.cmdpos + len - i;
3284 } else {
3285 ccline.cmdlen = ccline.cmdpos + len;
3286 }
3287 }
3288 memmove(ccline.cmdbuff + ccline.cmdpos, str, (size_t)len);
3289 ccline.cmdbuff[ccline.cmdlen] = NUL;
3290
3291 {
3292 // When the inserted text starts with a composing character,
3293 // backup to the character before it. There could be two of them.
3294 i = 0;
3295 c = utf_ptr2char(ccline.cmdbuff + ccline.cmdpos);
3296 while (ccline.cmdpos > 0 && utf_iscomposing(c)) {
3297 i = utf_head_off(ccline.cmdbuff, ccline.cmdbuff + ccline.cmdpos - 1) + 1;
3298 ccline.cmdpos -= i;
3299 len += i;
3300 c = utf_ptr2char(ccline.cmdbuff + ccline.cmdpos);
3301 }
3302 if (i == 0 && ccline.cmdpos > 0 && arabic_maycombine(c)) {
3303 // Check the previous character for Arabic combining pair.
3304 i = utf_head_off(ccline.cmdbuff, ccline.cmdbuff + ccline.cmdpos - 1) + 1;
3305 if (arabic_combine(utf_ptr2char(ccline.cmdbuff + ccline.cmdpos - i), c)) {
3306 ccline.cmdpos -= i;
3307 len += i;
3308 } else {
3309 i = 0;
3310 }
3311 }
3312 if (i != 0) {
3313 // Also backup the cursor position.
3314 i = ptr2cells(ccline.cmdbuff + ccline.cmdpos);
3315 ccline.cmdspos -= i;
3316 msg_col -= i;
3317 if (msg_col < 0) {
3318 msg_col += Columns;
3319 --msg_row;
3320 }
3321 }
3322 }
3323
3324 if (redraw && !cmd_silent) {
3325 msg_no_more = TRUE;
3326 i = cmdline_row;
3327 cursorcmd();
3328 draw_cmdline(ccline.cmdpos, ccline.cmdlen - ccline.cmdpos);
3329 // Avoid clearing the rest of the line too often.
3330 if (cmdline_row != i || ccline.overstrike) {
3331 msg_clr_eos();
3332 }
3333 msg_no_more = FALSE;
3334 }
3335 if (KeyTyped) {
3336 m = Columns * Rows;
3337 if (m < 0) { // overflow, Columns or Rows at weird value
3338 m = MAXCOL;
3339 }
3340 } else {
3341 m = MAXCOL;
3342 }
3343 for (i = 0; i < len; i++) {
3344 c = cmdline_charsize(ccline.cmdpos);
3345 // count ">" for a double-wide char that doesn't fit.
3346 correct_screencol(ccline.cmdpos, c, &ccline.cmdspos);
3347 // Stop cursor at the end of the screen, but do increment the
3348 // insert position, so that entering a very long command
3349 // works, even though you can't see it.
3350 if (ccline.cmdspos + c < m) {
3351 ccline.cmdspos += c;
3352 }
3353 c = utfc_ptr2len(ccline.cmdbuff + ccline.cmdpos) - 1;
3354 if (c > len - i - 1) {
3355 c = len - i - 1;
3356 }
3357 ccline.cmdpos += c;
3358 i += c;
3359 ccline.cmdpos++;
3360 }
3361
3362 if (redraw) {
3363 msg_check();
3364 }
3365 }
3366
3367 /*
3368 * Save ccline, because obtaining the "=" register may execute "normal :cmd"
3369 * and overwrite it. But get_cmdline_str() may need it, thus make it
3370 * available globally in prev_ccline.
3371 */
save_cmdline(struct cmdline_info * ccp)3372 static void save_cmdline(struct cmdline_info *ccp)
3373 {
3374 *ccp = ccline;
3375 ccline.prev_ccline = ccp;
3376 ccline.cmdbuff = NULL;
3377 ccline.cmdprompt = NULL;
3378 ccline.xpc = NULL;
3379 ccline.special_char = NUL;
3380 ccline.level = 0;
3381 }
3382
3383 /*
3384 * Restore ccline after it has been saved with save_cmdline().
3385 */
restore_cmdline(struct cmdline_info * ccp)3386 static void restore_cmdline(struct cmdline_info *ccp)
3387 FUNC_ATTR_NONNULL_ALL
3388 {
3389 ccline = *ccp;
3390 }
3391
3392 /*
3393 * Save the command line into allocated memory. Returns a pointer to be
3394 * passed to restore_cmdline_alloc() later.
3395 */
save_cmdline_alloc(void)3396 char_u *save_cmdline_alloc(void)
3397 FUNC_ATTR_NONNULL_RET
3398 {
3399 struct cmdline_info *p = xmalloc(sizeof(struct cmdline_info));
3400 save_cmdline(p);
3401 return (char_u *)p;
3402 }
3403
3404 /*
3405 * Restore the command line from the return value of save_cmdline_alloc().
3406 */
restore_cmdline_alloc(char_u * p)3407 void restore_cmdline_alloc(char_u *p)
3408 FUNC_ATTR_NONNULL_ALL
3409 {
3410 restore_cmdline((struct cmdline_info *)p);
3411 xfree(p);
3412 }
3413
3414 /// Paste a yank register into the command line.
3415 /// Used by CTRL-R command in command-line mode.
3416 /// insert_reg() can't be used here, because special characters from the
3417 /// register contents will be interpreted as commands.
3418 ///
3419 /// @param regname Register name.
3420 /// @param literally Insert text literally instead of "as typed".
3421 /// @param remcr When true, remove trailing CR.
3422 ///
3423 /// @returns FAIL for failure, OK otherwise
cmdline_paste(int regname,bool literally,bool remcr)3424 static bool cmdline_paste(int regname, bool literally, bool remcr)
3425 {
3426 char_u *arg;
3427 char_u *p;
3428 bool allocated;
3429 struct cmdline_info save_ccline;
3430
3431 // check for valid regname; also accept special characters for CTRL-R in
3432 // the command line
3433 if (regname != Ctrl_F && regname != Ctrl_P && regname != Ctrl_W
3434 && regname != Ctrl_A && regname != Ctrl_L
3435 && !valid_yank_reg(regname, false)) {
3436 return FAIL;
3437 }
3438
3439 // A register containing CTRL-R can cause an endless loop. Allow using
3440 // CTRL-C to break the loop.
3441 line_breakcheck();
3442 if (got_int) {
3443 return FAIL;
3444 }
3445
3446
3447 // Need to save and restore ccline. And set "textlock" to avoid nasty
3448 // things like going to another buffer when evaluating an expression.
3449 save_cmdline(&save_ccline);
3450 textlock++;
3451 const bool i = get_spec_reg(regname, &arg, &allocated, true);
3452 textlock--;
3453 restore_cmdline(&save_ccline);
3454
3455 if (i) {
3456 // Got the value of a special register in "arg".
3457 if (arg == NULL) {
3458 return FAIL;
3459 }
3460
3461 // When 'incsearch' is set and CTRL-R CTRL-W used: skip the duplicate
3462 // part of the word.
3463 p = arg;
3464 if (p_is && regname == Ctrl_W) {
3465 char_u *w;
3466 int len;
3467
3468 // Locate start of last word in the cmd buffer.
3469 for (w = ccline.cmdbuff + ccline.cmdpos; w > ccline.cmdbuff;) {
3470 len = utf_head_off(ccline.cmdbuff, w - 1) + 1;
3471 if (!vim_iswordc(utf_ptr2char(w - len))) {
3472 break;
3473 }
3474 w -= len;
3475 }
3476 len = (int)((ccline.cmdbuff + ccline.cmdpos) - w);
3477 if (p_ic ? STRNICMP(w, arg, len) == 0 : STRNCMP(w, arg, len) == 0) {
3478 p += len;
3479 }
3480 }
3481
3482 cmdline_paste_str(p, literally);
3483 if (allocated) {
3484 xfree(arg);
3485 }
3486 return OK;
3487 }
3488
3489 return cmdline_paste_reg(regname, literally, remcr);
3490 }
3491
3492 /*
3493 * Put a string on the command line.
3494 * When "literally" is TRUE, insert literally.
3495 * When "literally" is FALSE, insert as typed, but don't leave the command
3496 * line.
3497 */
cmdline_paste_str(char_u * s,int literally)3498 void cmdline_paste_str(char_u *s, int literally)
3499 {
3500 int c, cv;
3501
3502 if (literally) {
3503 put_on_cmdline(s, -1, TRUE);
3504 } else {
3505 while (*s != NUL) {
3506 cv = *s;
3507 if (cv == Ctrl_V && s[1]) {
3508 s++;
3509 }
3510 c = mb_cptr2char_adv((const char_u **)&s);
3511 if (cv == Ctrl_V || c == ESC || c == Ctrl_C
3512 || c == CAR || c == NL || c == Ctrl_L
3513 || (c == Ctrl_BSL && *s == Ctrl_N)) {
3514 stuffcharReadbuff(Ctrl_V);
3515 }
3516 stuffcharReadbuff(c);
3517 }
3518 }
3519 }
3520
3521 /// Delete characters on the command line, from "from" to the current position.
cmdline_del(int from)3522 static void cmdline_del(int from)
3523 {
3524 assert(ccline.cmdpos <= ccline.cmdlen);
3525 memmove(ccline.cmdbuff + from, ccline.cmdbuff + ccline.cmdpos,
3526 (size_t)ccline.cmdlen - (size_t)ccline.cmdpos + 1);
3527 ccline.cmdlen -= ccline.cmdpos - from;
3528 ccline.cmdpos = from;
3529 }
3530
3531 // This function is called when the screen size changes and with incremental
3532 // search and in other situations where the command line may have been
3533 // overwritten.
redrawcmdline(void)3534 void redrawcmdline(void)
3535 {
3536 if (cmd_silent) {
3537 return;
3538 }
3539 need_wait_return = false;
3540 compute_cmdrow();
3541 redrawcmd();
3542 cursorcmd();
3543 ui_cursor_shape();
3544 }
3545
redrawcmdprompt(void)3546 static void redrawcmdprompt(void)
3547 {
3548 int i;
3549
3550 if (cmd_silent) {
3551 return;
3552 }
3553 if (ui_has(kUICmdline)) {
3554 ccline.redraw_state = kCmdRedrawAll;
3555 return;
3556 }
3557 if (ccline.cmdfirstc != NUL) {
3558 msg_putchar(ccline.cmdfirstc);
3559 }
3560 if (ccline.cmdprompt != NULL) {
3561 msg_puts_attr((const char *)ccline.cmdprompt, ccline.cmdattr);
3562 ccline.cmdindent = msg_col + (msg_row - cmdline_row) * Columns;
3563 // do the reverse of cmd_startcol()
3564 if (ccline.cmdfirstc != NUL) {
3565 ccline.cmdindent--;
3566 }
3567 } else {
3568 for (i = ccline.cmdindent; i > 0; i--) {
3569 msg_putchar(' ');
3570 }
3571 }
3572 }
3573
3574 /*
3575 * Redraw what is currently on the command line.
3576 */
redrawcmd(void)3577 void redrawcmd(void)
3578 {
3579 if (cmd_silent) {
3580 return;
3581 }
3582
3583 if (ui_has(kUICmdline)) {
3584 draw_cmdline(0, ccline.cmdlen);
3585 return;
3586 }
3587
3588 // when 'incsearch' is set there may be no command line while redrawing
3589 if (ccline.cmdbuff == NULL) {
3590 cmd_cursor_goto(cmdline_row, 0);
3591 msg_clr_eos();
3592 return;
3593 }
3594
3595 redrawing_cmdline = true;
3596
3597 msg_start();
3598 redrawcmdprompt();
3599
3600 // Don't use more prompt, truncate the cmdline if it doesn't fit.
3601 msg_no_more = TRUE;
3602 draw_cmdline(0, ccline.cmdlen);
3603 msg_clr_eos();
3604 msg_no_more = FALSE;
3605
3606 ccline.cmdspos = cmd_screencol(ccline.cmdpos);
3607
3608 if (ccline.special_char != NUL) {
3609 putcmdline(ccline.special_char, ccline.special_shift);
3610 }
3611
3612 /*
3613 * An emsg() before may have set msg_scroll. This is used in normal mode,
3614 * in cmdline mode we can reset them now.
3615 */
3616 msg_scroll = FALSE; // next message overwrites cmdline
3617
3618 // Typing ':' at the more prompt may set skip_redraw. We don't want this
3619 // in cmdline mode.
3620 skip_redraw = false;
3621
3622 redrawing_cmdline = false;
3623 }
3624
compute_cmdrow(void)3625 void compute_cmdrow(void)
3626 {
3627 if (exmode_active || msg_scrolled != 0) {
3628 cmdline_row = Rows - 1;
3629 } else {
3630 win_T *wp = lastwin_nofloating();
3631 cmdline_row = wp->w_winrow + wp->w_height
3632 + wp->w_status_height;
3633 }
3634 lines_left = cmdline_row;
3635 }
3636
cursorcmd(void)3637 static void cursorcmd(void)
3638 {
3639 if (cmd_silent) {
3640 return;
3641 }
3642
3643 if (ui_has(kUICmdline)) {
3644 if (ccline.redraw_state < kCmdRedrawPos) {
3645 ccline.redraw_state = kCmdRedrawPos;
3646 }
3647 setcursor();
3648 return;
3649 }
3650
3651 if (cmdmsg_rl) {
3652 msg_row = cmdline_row + (ccline.cmdspos / (Columns - 1));
3653 msg_col = Columns - (ccline.cmdspos % (Columns - 1)) - 1;
3654 if (msg_row <= 0) {
3655 msg_row = Rows - 1;
3656 }
3657 } else {
3658 msg_row = cmdline_row + (ccline.cmdspos / Columns);
3659 msg_col = ccline.cmdspos % Columns;
3660 if (msg_row >= Rows) {
3661 msg_row = Rows - 1;
3662 }
3663 }
3664
3665 cmd_cursor_goto(msg_row, msg_col);
3666 }
3667
cmd_cursor_goto(int row,int col)3668 static void cmd_cursor_goto(int row, int col)
3669 {
3670 ScreenGrid *grid = &msg_grid_adj;
3671 screen_adjust_grid(&grid, &row, &col);
3672 ui_grid_cursor_goto(grid->handle, row, col);
3673 }
3674
gotocmdline(bool clr)3675 void gotocmdline(bool clr)
3676 {
3677 if (ui_has(kUICmdline)) {
3678 return;
3679 }
3680 msg_start();
3681 if (cmdmsg_rl) {
3682 msg_col = Columns - 1;
3683 } else {
3684 msg_col = 0; // always start in column 0
3685 }
3686 if (clr) { // clear the bottom line(s)
3687 msg_clr_eos(); // will reset clear_cmdline
3688 }
3689 cmd_cursor_goto(cmdline_row, 0);
3690 }
3691
3692 /*
3693 * Check the word in front of the cursor for an abbreviation.
3694 * Called when the non-id character "c" has been entered.
3695 * When an abbreviation is recognized it is removed from the text with
3696 * backspaces and the replacement string is inserted, followed by "c".
3697 */
ccheck_abbr(int c)3698 static int ccheck_abbr(int c)
3699 {
3700 int spos = 0;
3701
3702 if (p_paste || no_abbr) { // no abbreviations or in paste mode
3703 return false;
3704 }
3705
3706 // Do not consider '<,'> be part of the mapping, skip leading whitespace.
3707 // Actually accepts any mark.
3708 while (spos < ccline.cmdlen && ascii_iswhite(ccline.cmdbuff[spos])) {
3709 spos++;
3710 }
3711 if (ccline.cmdlen - spos > 5
3712 && ccline.cmdbuff[spos] == '\''
3713 && ccline.cmdbuff[spos + 2] == ','
3714 && ccline.cmdbuff[spos + 3] == '\'') {
3715 spos += 5;
3716 } else {
3717 // check abbreviation from the beginning of the commandline
3718 spos = 0;
3719 }
3720
3721 return check_abbr(c, ccline.cmdbuff, ccline.cmdpos, spos);
3722 }
3723
sort_func_compare(const void * s1,const void * s2)3724 static int sort_func_compare(const void *s1, const void *s2)
3725 {
3726 char_u *p1 = *(char_u **)s1;
3727 char_u *p2 = *(char_u **)s2;
3728
3729 if (*p1 != '<' && *p2 == '<') {
3730 return -1;
3731 }
3732 if (*p1 == '<' && *p2 != '<') {
3733 return 1;
3734 }
3735 return STRCMP(p1, p2);
3736 }
3737
3738 /// Return FAIL if this is not an appropriate context in which to do
3739 /// completion of anything, return OK if it is (even if there are no matches).
3740 /// For the caller, this means that the character is just passed through like a
3741 /// normal character (instead of being expanded). This allows :s/^I^D etc.
3742 ///
3743 /// @param options extra options for ExpandOne()
3744 /// @param escape if TRUE, escape the returned matches
nextwild(expand_T * xp,int type,int options,int escape)3745 static int nextwild(expand_T *xp, int type, int options, int escape)
3746 {
3747 int i, j;
3748 char_u *p1;
3749 char_u *p2;
3750 int difflen;
3751
3752 if (xp->xp_numfiles == -1) {
3753 set_expand_context(xp);
3754 cmd_showtail = expand_showtail(xp);
3755 }
3756
3757 if (xp->xp_context == EXPAND_UNSUCCESSFUL) {
3758 beep_flush();
3759 return OK; // Something illegal on command line
3760 }
3761 if (xp->xp_context == EXPAND_NOTHING) {
3762 // Caller can use the character as a normal char instead
3763 return FAIL;
3764 }
3765
3766 if (!(ui_has(kUICmdline) || ui_has(kUIWildmenu))) {
3767 msg_puts("..."); // show that we are busy
3768 ui_flush();
3769 }
3770
3771 i = (int)(xp->xp_pattern - ccline.cmdbuff);
3772 assert(ccline.cmdpos >= i);
3773 xp->xp_pattern_len = (size_t)ccline.cmdpos - (size_t)i;
3774
3775 if (type == WILD_NEXT || type == WILD_PREV) {
3776 // Get next/previous match for a previous expanded pattern.
3777 p2 = ExpandOne(xp, NULL, NULL, 0, type);
3778 } else {
3779 // Translate string into pattern and expand it.
3780 p1 = addstar(xp->xp_pattern, xp->xp_pattern_len, xp->xp_context);
3781 const int use_options = (
3782 options
3783 | WILD_HOME_REPLACE
3784 | WILD_ADD_SLASH
3785 | WILD_SILENT
3786 | (escape ? WILD_ESCAPE : 0)
3787 | (p_wic ? WILD_ICASE : 0));
3788 p2 = ExpandOne(xp, p1, vim_strnsave(&ccline.cmdbuff[i], xp->xp_pattern_len),
3789 use_options, type);
3790 xfree(p1);
3791
3792 // xp->xp_pattern might have been modified by ExpandOne (for example,
3793 // in lua completion), so recompute the pattern index and length
3794 i = (int)(xp->xp_pattern - ccline.cmdbuff);
3795 xp->xp_pattern_len = (size_t)ccline.cmdpos - (size_t)i;
3796
3797 // Longest match: make sure it is not shorter, happens with :help.
3798 if (p2 != NULL && type == WILD_LONGEST) {
3799 for (j = 0; (size_t)j < xp->xp_pattern_len; j++) {
3800 if (ccline.cmdbuff[i + j] == '*'
3801 || ccline.cmdbuff[i + j] == '?') {
3802 break;
3803 }
3804 }
3805 if ((int)STRLEN(p2) < j) {
3806 XFREE_CLEAR(p2);
3807 }
3808 }
3809 }
3810
3811 if (p2 != NULL && !got_int) {
3812 difflen = (int)STRLEN(p2) - (int)(xp->xp_pattern_len);
3813 if (ccline.cmdlen + difflen + 4 > ccline.cmdbufflen) {
3814 realloc_cmdbuff(ccline.cmdlen + difflen + 4);
3815 xp->xp_pattern = ccline.cmdbuff + i;
3816 }
3817 assert(ccline.cmdpos <= ccline.cmdlen);
3818 memmove(&ccline.cmdbuff[ccline.cmdpos + difflen],
3819 &ccline.cmdbuff[ccline.cmdpos],
3820 (size_t)ccline.cmdlen - (size_t)ccline.cmdpos + 1);
3821 memmove(&ccline.cmdbuff[i], p2, STRLEN(p2));
3822 ccline.cmdlen += difflen;
3823 ccline.cmdpos += difflen;
3824 }
3825 xfree(p2);
3826
3827 redrawcmd();
3828 cursorcmd();
3829
3830 /* When expanding a ":map" command and no matches are found, assume that
3831 * the key is supposed to be inserted literally */
3832 if (xp->xp_context == EXPAND_MAPPINGS && p2 == NULL) {
3833 return FAIL;
3834 }
3835
3836 if (xp->xp_numfiles <= 0 && p2 == NULL) {
3837 beep_flush();
3838 } else if (xp->xp_numfiles == 1) {
3839 // free expanded pattern
3840 (void)ExpandOne(xp, NULL, NULL, 0, WILD_FREE);
3841 }
3842
3843 return OK;
3844 }
3845
3846 /// Do wildcard expansion on the string 'str'.
3847 /// Chars that should not be expanded must be preceded with a backslash.
3848 /// Return a pointer to allocated memory containing the new string.
3849 /// Return NULL for failure.
3850 ///
3851 /// "orig" is the originally expanded string, copied to allocated memory. It
3852 /// should either be kept in orig_save or freed. When "mode" is WILD_NEXT or
3853 /// WILD_PREV "orig" should be NULL.
3854 ///
3855 /// Results are cached in xp->xp_files and xp->xp_numfiles, except when "mode"
3856 /// is WILD_EXPAND_FREE or WILD_ALL.
3857 ///
3858 /// mode = WILD_FREE: just free previously expanded matches
3859 /// mode = WILD_EXPAND_FREE: normal expansion, do not keep matches
3860 /// mode = WILD_EXPAND_KEEP: normal expansion, keep matches
3861 /// mode = WILD_NEXT: use next match in multiple match, wrap to first
3862 /// mode = WILD_PREV: use previous match in multiple match, wrap to first
3863 /// mode = WILD_ALL: return all matches concatenated
3864 /// mode = WILD_LONGEST: return longest matched part
3865 /// mode = WILD_ALL_KEEP: get all matches, keep matches
3866 ///
3867 /// options = WILD_LIST_NOTFOUND: list entries without a match
3868 /// options = WILD_HOME_REPLACE: do home_replace() for buffer names
3869 /// options = WILD_USE_NL: Use '\n' for WILD_ALL
3870 /// options = WILD_NO_BEEP: Don't beep for multiple matches
3871 /// options = WILD_ADD_SLASH: add a slash after directory names
3872 /// options = WILD_KEEP_ALL: don't remove 'wildignore' entries
3873 /// options = WILD_SILENT: don't print warning messages
3874 /// options = WILD_ESCAPE: put backslash before special chars
3875 /// options = WILD_ICASE: ignore case for files
3876 ///
3877 /// The variables xp->xp_context and xp->xp_backslash must have been set!
3878 ///
3879 /// @param orig allocated copy of original of expanded string
ExpandOne(expand_T * xp,char_u * str,char_u * orig,int options,int mode)3880 char_u *ExpandOne(expand_T *xp, char_u *str, char_u *orig, int options, int mode)
3881 {
3882 char_u *ss = NULL;
3883 static int findex;
3884 static char_u *orig_save = NULL; // kept value of orig
3885 int orig_saved = FALSE;
3886 int i;
3887 int non_suf_match; // number without matching suffix
3888
3889 /*
3890 * first handle the case of using an old match
3891 */
3892 if (mode == WILD_NEXT || mode == WILD_PREV) {
3893 if (xp->xp_numfiles > 0) {
3894 if (mode == WILD_PREV) {
3895 if (findex == -1) {
3896 findex = xp->xp_numfiles;
3897 }
3898 --findex;
3899 } else { // mode == WILD_NEXT
3900 ++findex;
3901 }
3902
3903 /*
3904 * When wrapping around, return the original string, set findex to
3905 * -1.
3906 */
3907 if (findex < 0) {
3908 if (orig_save == NULL) {
3909 findex = xp->xp_numfiles - 1;
3910 } else {
3911 findex = -1;
3912 }
3913 }
3914 if (findex >= xp->xp_numfiles) {
3915 if (orig_save == NULL) {
3916 findex = 0;
3917 } else {
3918 findex = -1;
3919 }
3920 }
3921 if (compl_match_array) {
3922 compl_selected = findex;
3923 cmdline_pum_display(false);
3924 } else if (p_wmnu) {
3925 win_redr_status_matches(xp, xp->xp_numfiles, xp->xp_files,
3926 findex, cmd_showtail);
3927 }
3928 if (findex == -1) {
3929 return vim_strsave(orig_save);
3930 }
3931 return vim_strsave(xp->xp_files[findex]);
3932 } else {
3933 return NULL;
3934 }
3935 }
3936
3937 if (mode == WILD_CANCEL) {
3938 ss = vim_strsave(orig_save ? orig_save : (char_u *)"");
3939 } else if (mode == WILD_APPLY) {
3940 ss = vim_strsave(findex == -1 ? (orig_save ? orig_save : (char_u *)"") :
3941 xp->xp_files[findex]);
3942 }
3943
3944 // free old names
3945 if (xp->xp_numfiles != -1 && mode != WILD_ALL && mode != WILD_LONGEST) {
3946 FreeWild(xp->xp_numfiles, xp->xp_files);
3947 xp->xp_numfiles = -1;
3948 XFREE_CLEAR(orig_save);
3949 }
3950 findex = 0;
3951
3952 if (mode == WILD_FREE) { // only release file name
3953 return NULL;
3954 }
3955
3956 if (xp->xp_numfiles == -1 && mode != WILD_APPLY && mode != WILD_CANCEL) {
3957 xfree(orig_save);
3958 orig_save = orig;
3959 orig_saved = TRUE;
3960
3961 /*
3962 * Do the expansion.
3963 */
3964 if (ExpandFromContext(xp, str, &xp->xp_numfiles, &xp->xp_files,
3965 options) == FAIL) {
3966 #ifdef FNAME_ILLEGAL
3967 /* Illegal file name has been silently skipped. But when there
3968 * are wildcards, the real problem is that there was no match,
3969 * causing the pattern to be added, which has illegal characters.
3970 */
3971 if (!(options & WILD_SILENT) && (options & WILD_LIST_NOTFOUND)) {
3972 semsg(_(e_nomatch2), str);
3973 }
3974 #endif
3975 } else if (xp->xp_numfiles == 0) {
3976 if (!(options & WILD_SILENT)) {
3977 semsg(_(e_nomatch2), str);
3978 }
3979 } else {
3980 // Escape the matches for use on the command line.
3981 ExpandEscape(xp, str, xp->xp_numfiles, xp->xp_files, options);
3982
3983 /*
3984 * Check for matching suffixes in file names.
3985 */
3986 if (mode != WILD_ALL && mode != WILD_ALL_KEEP
3987 && mode != WILD_LONGEST) {
3988 if (xp->xp_numfiles) {
3989 non_suf_match = xp->xp_numfiles;
3990 } else {
3991 non_suf_match = 1;
3992 }
3993 if ((xp->xp_context == EXPAND_FILES
3994 || xp->xp_context == EXPAND_DIRECTORIES)
3995 && xp->xp_numfiles > 1) {
3996 /*
3997 * More than one match; check suffix.
3998 * The files will have been sorted on matching suffix in
3999 * expand_wildcards, only need to check the first two.
4000 */
4001 non_suf_match = 0;
4002 for (i = 0; i < 2; ++i) {
4003 if (match_suffix(xp->xp_files[i])) {
4004 ++non_suf_match;
4005 }
4006 }
4007 }
4008 if (non_suf_match != 1) {
4009 /* Can we ever get here unless it's while expanding
4010 * interactively? If not, we can get rid of this all
4011 * together. Don't really want to wait for this message
4012 * (and possibly have to hit return to continue!).
4013 */
4014 if (!(options & WILD_SILENT)) {
4015 emsg(_(e_toomany));
4016 } else if (!(options & WILD_NO_BEEP)) {
4017 beep_flush();
4018 }
4019 }
4020 if (!(non_suf_match != 1 && mode == WILD_EXPAND_FREE)) {
4021 ss = vim_strsave(xp->xp_files[0]);
4022 }
4023 }
4024 }
4025 }
4026
4027 // Find longest common part
4028 if (mode == WILD_LONGEST && xp->xp_numfiles > 0) {
4029 size_t len = 0;
4030
4031 for (size_t mb_len; xp->xp_files[0][len]; len += mb_len) {
4032 mb_len = (size_t)utfc_ptr2len(&xp->xp_files[0][len]);
4033 int c0 = utf_ptr2char(&xp->xp_files[0][len]);
4034 for (i = 1; i < xp->xp_numfiles; i++) {
4035 int ci = utf_ptr2char(&xp->xp_files[i][len]);
4036
4037 if (p_fic && (xp->xp_context == EXPAND_DIRECTORIES
4038 || xp->xp_context == EXPAND_FILES
4039 || xp->xp_context == EXPAND_SHELLCMD
4040 || xp->xp_context == EXPAND_BUFFERS)) {
4041 if (mb_tolower(c0) != mb_tolower(ci)) {
4042 break;
4043 }
4044 } else if (c0 != ci) {
4045 break;
4046 }
4047 }
4048 if (i < xp->xp_numfiles) {
4049 if (!(options & WILD_NO_BEEP)) {
4050 vim_beep(BO_WILD);
4051 }
4052 break;
4053 }
4054 }
4055
4056 ss = (char_u *)xstrndup((char *)xp->xp_files[0], len);
4057 findex = -1; // next p_wc gets first one
4058 }
4059
4060 // Concatenate all matching names
4061 // TODO(philix): use xstpcpy instead of strcat in a loop (ExpandOne)
4062 if (mode == WILD_ALL && xp->xp_numfiles > 0) {
4063 size_t len = 0;
4064 for (i = 0; i < xp->xp_numfiles; ++i) {
4065 len += STRLEN(xp->xp_files[i]) + 1;
4066 }
4067 ss = xmalloc(len);
4068 *ss = NUL;
4069 for (i = 0; i < xp->xp_numfiles; ++i) {
4070 STRCAT(ss, xp->xp_files[i]);
4071 if (i != xp->xp_numfiles - 1) {
4072 STRCAT(ss, (options & WILD_USE_NL) ? "\n" : " ");
4073 }
4074 }
4075 }
4076
4077 if (mode == WILD_EXPAND_FREE || mode == WILD_ALL) {
4078 ExpandCleanup(xp);
4079 }
4080
4081 // Free "orig" if it wasn't stored in "orig_save".
4082 if (!orig_saved) {
4083 xfree(orig);
4084 }
4085
4086 return ss;
4087 }
4088
4089 /*
4090 * Prepare an expand structure for use.
4091 */
ExpandInit(expand_T * xp)4092 void ExpandInit(expand_T *xp)
4093 FUNC_ATTR_NONNULL_ALL
4094 {
4095 CLEAR_POINTER(xp);
4096 xp->xp_backslash = XP_BS_NONE;
4097 xp->xp_numfiles = -1;
4098 }
4099
4100 /*
4101 * Cleanup an expand structure after use.
4102 */
ExpandCleanup(expand_T * xp)4103 void ExpandCleanup(expand_T *xp)
4104 {
4105 if (xp->xp_numfiles >= 0) {
4106 FreeWild(xp->xp_numfiles, xp->xp_files);
4107 xp->xp_numfiles = -1;
4108 }
4109 }
4110
ExpandEscape(expand_T * xp,char_u * str,int numfiles,char_u ** files,int options)4111 void ExpandEscape(expand_T *xp, char_u *str, int numfiles, char_u **files, int options)
4112 {
4113 int i;
4114 char_u *p;
4115
4116 /*
4117 * May change home directory back to "~"
4118 */
4119 if (options & WILD_HOME_REPLACE) {
4120 tilde_replace(str, numfiles, files);
4121 }
4122
4123 if (options & WILD_ESCAPE) {
4124 if (xp->xp_context == EXPAND_FILES
4125 || xp->xp_context == EXPAND_FILES_IN_PATH
4126 || xp->xp_context == EXPAND_SHELLCMD
4127 || xp->xp_context == EXPAND_BUFFERS
4128 || xp->xp_context == EXPAND_DIRECTORIES) {
4129 /*
4130 * Insert a backslash into a file name before a space, \, %, #
4131 * and wildmatch characters, except '~'.
4132 */
4133 for (i = 0; i < numfiles; ++i) {
4134 // for ":set path=" we need to escape spaces twice
4135 if (xp->xp_backslash == XP_BS_THREE) {
4136 p = vim_strsave_escaped(files[i], (char_u *)" ");
4137 xfree(files[i]);
4138 files[i] = p;
4139 #if defined(BACKSLASH_IN_FILENAME)
4140 p = vim_strsave_escaped(files[i], (char_u *)" ");
4141 xfree(files[i]);
4142 files[i] = p;
4143 #endif
4144 }
4145 #ifdef BACKSLASH_IN_FILENAME
4146 p = (char_u *)vim_strsave_fnameescape((const char *)files[i], false);
4147 #else
4148 p = (char_u *)vim_strsave_fnameescape((const char *)files[i],
4149 xp->xp_shell);
4150 #endif
4151 xfree(files[i]);
4152 files[i] = p;
4153
4154 /* If 'str' starts with "\~", replace "~" at start of
4155 * files[i] with "\~". */
4156 if (str[0] == '\\' && str[1] == '~' && files[i][0] == '~') {
4157 escape_fname(&files[i]);
4158 }
4159 }
4160 xp->xp_backslash = XP_BS_NONE;
4161
4162 /* If the first file starts with a '+' escape it. Otherwise it
4163 * could be seen as "+cmd". */
4164 if (*files[0] == '+') {
4165 escape_fname(&files[0]);
4166 }
4167 } else if (xp->xp_context == EXPAND_TAGS) {
4168 /*
4169 * Insert a backslash before characters in a tag name that
4170 * would terminate the ":tag" command.
4171 */
4172 for (i = 0; i < numfiles; ++i) {
4173 p = vim_strsave_escaped(files[i], (char_u *)"\\|\"");
4174 xfree(files[i]);
4175 files[i] = p;
4176 }
4177 }
4178 }
4179 }
4180
4181 /// Escape special characters in a file name for use as a command argument
4182 ///
4183 /// @param[in] fname File name to escape.
4184 /// @param[in] shell What to escape for: if false, escapes for VimL command,
4185 /// if true then it escapes for a shell command.
4186 ///
4187 /// @return [allocated] escaped file name.
vim_strsave_fnameescape(const char * const fname,const bool shell FUNC_ATTR_UNUSED)4188 char *vim_strsave_fnameescape(const char *const fname, const bool shell FUNC_ATTR_UNUSED)
4189 FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL
4190 {
4191 #ifdef BACKSLASH_IN_FILENAME
4192 # define PATH_ESC_CHARS " \t\n*?[{`%#'\"|!<"
4193 char_u buf[sizeof(PATH_ESC_CHARS)];
4194 int j = 0;
4195
4196 // Don't escape '[', '{' and '!' if they are in 'isfname'.
4197 for (const char *s = PATH_ESC_CHARS; *s != NUL; s++) {
4198 if ((*s != '[' && *s != '{' && *s != '!') || !vim_isfilec(*s)) {
4199 buf[j++] = *s;
4200 }
4201 }
4202 buf[j] = NUL;
4203 char *p = (char *)vim_strsave_escaped((const char_u *)fname,
4204 (const char_u *)buf);
4205 #else
4206 # define PATH_ESC_CHARS ((char_u *)" \t\n*?[{`$\\%#'\"|!<")
4207 # define SHELL_ESC_CHARS ((char_u *)" \t\n*?[{`$\\%#'\"|!<>();&")
4208 char *p =
4209 (char *)vim_strsave_escaped((const char_u *)fname, (shell ? SHELL_ESC_CHARS : PATH_ESC_CHARS));
4210 if (shell && csh_like_shell()) {
4211 // For csh and similar shells need to put two backslashes before '!'.
4212 // One is taken by Vim, one by the shell.
4213 char *s = (char *)vim_strsave_escaped((const char_u *)p,
4214 (const char_u *)"!");
4215 xfree(p);
4216 p = s;
4217 }
4218 #endif
4219
4220 // '>' and '+' are special at the start of some commands, e.g. ":edit" and
4221 // ":write". "cd -" has a special meaning.
4222 if (*p == '>' || *p == '+' || (*p == '-' && p[1] == NUL)) {
4223 escape_fname((char_u **)&p);
4224 }
4225
4226 return p;
4227 }
4228
4229 /*
4230 * Put a backslash before the file name in "pp", which is in allocated memory.
4231 */
escape_fname(char_u ** pp)4232 static void escape_fname(char_u **pp)
4233 {
4234 char_u *p = xmalloc(STRLEN(*pp) + 2);
4235 p[0] = '\\';
4236 STRCPY(p + 1, *pp);
4237 xfree(*pp);
4238 *pp = p;
4239 }
4240
4241 /*
4242 * For each file name in files[num_files]:
4243 * If 'orig_pat' starts with "~/", replace the home directory with "~".
4244 */
tilde_replace(char_u * orig_pat,int num_files,char_u ** files)4245 void tilde_replace(char_u *orig_pat, int num_files, char_u **files)
4246 {
4247 int i;
4248 char_u *p;
4249
4250 if (orig_pat[0] == '~' && vim_ispathsep(orig_pat[1])) {
4251 for (i = 0; i < num_files; ++i) {
4252 p = home_replace_save(NULL, files[i]);
4253 xfree(files[i]);
4254 files[i] = p;
4255 }
4256 }
4257 }
4258
cmdline_pum_display(bool changed_array)4259 void cmdline_pum_display(bool changed_array)
4260 {
4261 pum_display(compl_match_array, compl_match_arraysize, compl_selected,
4262 changed_array, compl_startcol);
4263 }
4264
4265 /*
4266 * Show all matches for completion on the command line.
4267 * Returns EXPAND_NOTHING when the character that triggered expansion should
4268 * be inserted like a normal character.
4269 */
showmatches(expand_T * xp,int wildmenu)4270 static int showmatches(expand_T *xp, int wildmenu)
4271 {
4272 #define L_SHOWFILE(m) (showtail \
4273 ? sm_gettail(files_found[m], false) : files_found[m])
4274 int num_files;
4275 char_u **files_found;
4276 int i, j, k;
4277 int maxlen;
4278 int lines;
4279 int columns;
4280 char_u *p;
4281 int lastlen;
4282 int attr;
4283 int showtail;
4284
4285 if (xp->xp_numfiles == -1) {
4286 set_expand_context(xp);
4287 i = expand_cmdline(xp, ccline.cmdbuff, ccline.cmdpos,
4288 &num_files, &files_found);
4289 showtail = expand_showtail(xp);
4290 if (i != EXPAND_OK) {
4291 return i;
4292 }
4293 } else {
4294 num_files = xp->xp_numfiles;
4295 files_found = xp->xp_files;
4296 showtail = cmd_showtail;
4297 }
4298
4299 bool compl_use_pum = (ui_has(kUICmdline)
4300 ? ui_has(kUIPopupmenu)
4301 : wildmenu && (wop_flags & WOP_PUM))
4302 || ui_has(kUIWildmenu);
4303
4304 if (compl_use_pum) {
4305 assert(num_files >= 0);
4306 compl_match_arraysize = num_files;
4307 compl_match_array = xcalloc((size_t)compl_match_arraysize,
4308 sizeof(pumitem_T));
4309 for (i = 0; i < num_files; i++) {
4310 compl_match_array[i].pum_text = L_SHOWFILE(i);
4311 }
4312 char_u *endpos = (showtail
4313 ? sm_gettail(xp->xp_pattern, true) : xp->xp_pattern);
4314 if (ui_has(kUICmdline)) {
4315 compl_startcol = (int)(endpos - ccline.cmdbuff);
4316 } else {
4317 compl_startcol = cmd_screencol((int)(endpos - ccline.cmdbuff));
4318 }
4319 compl_selected = -1;
4320 cmdline_pum_display(true);
4321 return EXPAND_OK;
4322 }
4323
4324 if (!wildmenu) {
4325 msg_didany = false; // lines_left will be set
4326 msg_start(); // prepare for paging
4327 msg_putchar('\n');
4328 ui_flush();
4329 cmdline_row = msg_row;
4330 msg_didany = false; // lines_left will be set again
4331 msg_start(); // prepare for paging
4332 }
4333
4334 if (got_int) {
4335 got_int = false; // only int. the completion, not the cmd line
4336 } else if (wildmenu) {
4337 win_redr_status_matches(xp, num_files, files_found, -1, showtail);
4338 } else {
4339 // find the length of the longest file name
4340 maxlen = 0;
4341 for (i = 0; i < num_files; ++i) {
4342 if (!showtail && (xp->xp_context == EXPAND_FILES
4343 || xp->xp_context == EXPAND_SHELLCMD
4344 || xp->xp_context == EXPAND_BUFFERS)) {
4345 home_replace(NULL, files_found[i], NameBuff, MAXPATHL, TRUE);
4346 j = vim_strsize(NameBuff);
4347 } else {
4348 j = vim_strsize(L_SHOWFILE(i));
4349 }
4350 if (j > maxlen) {
4351 maxlen = j;
4352 }
4353 }
4354
4355 if (xp->xp_context == EXPAND_TAGS_LISTFILES) {
4356 lines = num_files;
4357 } else {
4358 // compute the number of columns and lines for the listing
4359 maxlen += 2; // two spaces between file names
4360 columns = (Columns + 2) / maxlen;
4361 if (columns < 1) {
4362 columns = 1;
4363 }
4364 lines = (num_files + columns - 1) / columns;
4365 }
4366
4367 attr = HL_ATTR(HLF_D); // find out highlighting for directories
4368
4369 if (xp->xp_context == EXPAND_TAGS_LISTFILES) {
4370 msg_puts_attr(_("tagname"), HL_ATTR(HLF_T));
4371 msg_clr_eos();
4372 msg_advance(maxlen - 3);
4373 msg_puts_attr(_(" kind file\n"), HL_ATTR(HLF_T));
4374 }
4375
4376 // list the files line by line
4377 for (i = 0; i < lines; ++i) {
4378 lastlen = 999;
4379 for (k = i; k < num_files; k += lines) {
4380 if (xp->xp_context == EXPAND_TAGS_LISTFILES) {
4381 msg_outtrans_attr(files_found[k], HL_ATTR(HLF_D));
4382 p = files_found[k] + STRLEN(files_found[k]) + 1;
4383 msg_advance(maxlen + 1);
4384 msg_puts((const char *)p);
4385 msg_advance(maxlen + 3);
4386 msg_outtrans_long_attr(p + 2, HL_ATTR(HLF_D));
4387 break;
4388 }
4389 for (j = maxlen - lastlen; --j >= 0;) {
4390 msg_putchar(' ');
4391 }
4392 if (xp->xp_context == EXPAND_FILES
4393 || xp->xp_context == EXPAND_SHELLCMD
4394 || xp->xp_context == EXPAND_BUFFERS) {
4395 // highlight directories
4396 if (xp->xp_numfiles != -1) {
4397 // Expansion was done before and special characters
4398 // were escaped, need to halve backslashes. Also
4399 // $HOME has been replaced with ~/.
4400 char_u *exp_path = expand_env_save_opt(files_found[k], true);
4401 char_u *path = exp_path != NULL ? exp_path : files_found[k];
4402 char_u *halved_slash = backslash_halve_save(path);
4403 j = os_isdir(halved_slash);
4404 xfree(exp_path);
4405 if (halved_slash != path) {
4406 xfree(halved_slash);
4407 }
4408 } else {
4409 // Expansion was done here, file names are literal.
4410 j = os_isdir(files_found[k]);
4411 }
4412 if (showtail) {
4413 p = L_SHOWFILE(k);
4414 } else {
4415 home_replace(NULL, files_found[k], NameBuff, MAXPATHL,
4416 TRUE);
4417 p = NameBuff;
4418 }
4419 } else {
4420 j = FALSE;
4421 p = L_SHOWFILE(k);
4422 }
4423 lastlen = msg_outtrans_attr(p, j ? attr : 0);
4424 }
4425 if (msg_col > 0) { // when not wrapped around
4426 msg_clr_eos();
4427 msg_putchar('\n');
4428 }
4429 ui_flush(); // show one line at a time
4430 if (got_int) {
4431 got_int = FALSE;
4432 break;
4433 }
4434 }
4435
4436 /*
4437 * we redraw the command below the lines that we have just listed
4438 * This is a bit tricky, but it saves a lot of screen updating.
4439 */
4440 cmdline_row = msg_row; // will put it back later
4441 }
4442
4443 if (xp->xp_numfiles == -1) {
4444 FreeWild(num_files, files_found);
4445 }
4446
4447 return EXPAND_OK;
4448 }
4449
4450 /*
4451 * Private path_tail for showmatches() (and win_redr_status_matches()):
4452 * Find tail of file name path, but ignore trailing "/".
4453 */
sm_gettail(char_u * s,bool eager)4454 char_u *sm_gettail(char_u *s, bool eager)
4455 {
4456 char_u *p;
4457 char_u *t = s;
4458 int had_sep = FALSE;
4459
4460 for (p = s; *p != NUL;) {
4461 if (vim_ispathsep(*p)
4462 #ifdef BACKSLASH_IN_FILENAME
4463 && !rem_backslash(p)
4464 #endif
4465 ) {
4466 if (eager) {
4467 t = p+1;
4468 } else {
4469 had_sep = true;
4470 }
4471 } else if (had_sep) {
4472 t = p;
4473 had_sep = FALSE;
4474 }
4475 MB_PTR_ADV(p);
4476 }
4477 return t;
4478 }
4479
4480 /*
4481 * Return TRUE if we only need to show the tail of completion matches.
4482 * When not completing file names or there is a wildcard in the path FALSE is
4483 * returned.
4484 */
expand_showtail(expand_T * xp)4485 static int expand_showtail(expand_T *xp)
4486 {
4487 char_u *s;
4488 char_u *end;
4489
4490 // When not completing file names a "/" may mean something different.
4491 if (xp->xp_context != EXPAND_FILES
4492 && xp->xp_context != EXPAND_SHELLCMD
4493 && xp->xp_context != EXPAND_DIRECTORIES) {
4494 return FALSE;
4495 }
4496
4497 end = path_tail(xp->xp_pattern);
4498 if (end == xp->xp_pattern) { // there is no path separator
4499 return FALSE;
4500 }
4501
4502 for (s = xp->xp_pattern; s < end; s++) {
4503 /* Skip escaped wildcards. Only when the backslash is not a path
4504 * separator, on DOS the '*' "path\*\file" must not be skipped. */
4505 if (rem_backslash(s)) {
4506 ++s;
4507 } else if (vim_strchr((char_u *)"*?[", *s) != NULL) {
4508 return FALSE;
4509 }
4510 }
4511 return TRUE;
4512 }
4513
4514 /// Prepare a string for expansion.
4515 ///
4516 /// When expanding file names: The string will be used with expand_wildcards().
4517 /// Copy "fname[len]" into allocated memory and add a '*' at the end.
4518 /// When expanding other names: The string will be used with regcomp(). Copy
4519 /// the name into allocated memory and prepend "^".
4520 ///
4521 /// @param context EXPAND_FILES etc.
addstar(char_u * fname,size_t len,int context)4522 char_u *addstar(char_u *fname, size_t len, int context)
4523 FUNC_ATTR_NONNULL_RET
4524 {
4525 char_u *retval;
4526 size_t i, j;
4527 size_t new_len;
4528 char_u *tail;
4529 int ends_in_star;
4530
4531 if (context != EXPAND_FILES
4532 && context != EXPAND_FILES_IN_PATH
4533 && context != EXPAND_SHELLCMD
4534 && context != EXPAND_DIRECTORIES) {
4535 /*
4536 * Matching will be done internally (on something other than files).
4537 * So we convert the file-matching-type wildcards into our kind for
4538 * use with vim_regcomp(). First work out how long it will be:
4539 */
4540
4541 // For help tags the translation is done in find_help_tags().
4542 // For a tag pattern starting with "/" no translation is needed.
4543 if (context == EXPAND_HELP
4544 || context == EXPAND_CHECKHEALTH
4545 || context == EXPAND_COLORS
4546 || context == EXPAND_COMPILER
4547 || context == EXPAND_OWNSYNTAX
4548 || context == EXPAND_FILETYPE
4549 || context == EXPAND_PACKADD
4550 || ((context == EXPAND_TAGS_LISTFILES || context == EXPAND_TAGS)
4551 && fname[0] == '/')) {
4552 retval = vim_strnsave(fname, len);
4553 } else {
4554 new_len = len + 2; // +2 for '^' at start, NUL at end
4555 for (i = 0; i < len; i++) {
4556 if (fname[i] == '*' || fname[i] == '~') {
4557 new_len++; /* '*' needs to be replaced by ".*"
4558 '~' needs to be replaced by "\~" */
4559 }
4560 // Buffer names are like file names. "." should be literal
4561 if (context == EXPAND_BUFFERS && fname[i] == '.') {
4562 new_len++; // "." becomes "\."
4563 }
4564 /* Custom expansion takes care of special things, match
4565 * backslashes literally (perhaps also for other types?) */
4566 if ((context == EXPAND_USER_DEFINED
4567 || context == EXPAND_USER_LIST) && fname[i] == '\\') {
4568 new_len++; // '\' becomes "\\"
4569 }
4570 }
4571 retval = xmalloc(new_len);
4572 {
4573 retval[0] = '^';
4574 j = 1;
4575 for (i = 0; i < len; i++, j++) {
4576 /* Skip backslash. But why? At least keep it for custom
4577 * expansion. */
4578 if (context != EXPAND_USER_DEFINED
4579 && context != EXPAND_USER_LIST
4580 && fname[i] == '\\'
4581 && ++i == len) {
4582 break;
4583 }
4584
4585 switch (fname[i]) {
4586 case '*':
4587 retval[j++] = '.';
4588 break;
4589 case '~':
4590 retval[j++] = '\\';
4591 break;
4592 case '?':
4593 retval[j] = '.';
4594 continue;
4595 case '.':
4596 if (context == EXPAND_BUFFERS) {
4597 retval[j++] = '\\';
4598 }
4599 break;
4600 case '\\':
4601 if (context == EXPAND_USER_DEFINED
4602 || context == EXPAND_USER_LIST) {
4603 retval[j++] = '\\';
4604 }
4605 break;
4606 }
4607 retval[j] = fname[i];
4608 }
4609 retval[j] = NUL;
4610 }
4611 }
4612 } else {
4613 retval = xmalloc(len + 4);
4614 STRLCPY(retval, fname, len + 1);
4615
4616 /*
4617 * Don't add a star to *, ~, ~user, $var or `cmd`.
4618 * * would become **, which walks the whole tree.
4619 * ~ would be at the start of the file name, but not the tail.
4620 * $ could be anywhere in the tail.
4621 * ` could be anywhere in the file name.
4622 * When the name ends in '$' don't add a star, remove the '$'.
4623 */
4624 tail = path_tail(retval);
4625 ends_in_star = (len > 0 && retval[len - 1] == '*');
4626 #ifndef BACKSLASH_IN_FILENAME
4627 for (ssize_t k = (ssize_t)len - 2; k >= 0; k--) {
4628 if (retval[k] != '\\') {
4629 break;
4630 }
4631 ends_in_star = !ends_in_star;
4632 }
4633 #endif
4634 if ((*retval != '~' || tail != retval)
4635 && !ends_in_star
4636 && vim_strchr(tail, '$') == NULL
4637 && vim_strchr(retval, '`') == NULL) {
4638 retval[len++] = '*';
4639 } else if (len > 0 && retval[len - 1] == '$') {
4640 --len;
4641 }
4642 retval[len] = NUL;
4643 }
4644 return retval;
4645 }
4646
4647 /*
4648 * Must parse the command line so far to work out what context we are in.
4649 * Completion can then be done based on that context.
4650 * This routine sets the variables:
4651 * xp->xp_pattern The start of the pattern to be expanded within
4652 * the command line (ends at the cursor).
4653 * xp->xp_context The type of thing to expand. Will be one of:
4654 *
4655 * EXPAND_UNSUCCESSFUL Used sometimes when there is something illegal on
4656 * the command line, like an unknown command. Caller
4657 * should beep.
4658 * EXPAND_NOTHING Unrecognised context for completion, use char like
4659 * a normal char, rather than for completion. eg
4660 * :s/^I/
4661 * EXPAND_COMMANDS Cursor is still touching the command, so complete
4662 * it.
4663 * EXPAND_BUFFERS Complete file names for :buf and :sbuf commands.
4664 * EXPAND_FILES After command with EX_XFILE set, or after setting
4665 * with P_EXPAND set. eg :e ^I, :w>>^I
4666 * EXPAND_DIRECTORIES In some cases this is used instead of the latter
4667 * when we know only directories are of interest. eg
4668 * :set dir=^I
4669 * EXPAND_SHELLCMD After ":!cmd", ":r !cmd" or ":w !cmd".
4670 * EXPAND_SETTINGS Complete variable names. eg :set d^I
4671 * EXPAND_BOOL_SETTINGS Complete boolean variables only, eg :set no^I
4672 * EXPAND_TAGS Complete tags from the files in p_tags. eg :ta a^I
4673 * EXPAND_TAGS_LISTFILES As above, but list filenames on ^D, after :tselect
4674 * EXPAND_HELP Complete tags from the file 'helpfile'/tags
4675 * EXPAND_EVENTS Complete event names
4676 * EXPAND_SYNTAX Complete :syntax command arguments
4677 * EXPAND_HIGHLIGHT Complete highlight (syntax) group names
4678 * EXPAND_AUGROUP Complete autocommand group names
4679 * EXPAND_USER_VARS Complete user defined variable names, eg :unlet a^I
4680 * EXPAND_MAPPINGS Complete mapping and abbreviation names,
4681 * eg :unmap a^I , :cunab x^I
4682 * EXPAND_FUNCTIONS Complete internal or user defined function names,
4683 * eg :call sub^I
4684 * EXPAND_USER_FUNC Complete user defined function names, eg :delf F^I
4685 * EXPAND_EXPRESSION Complete internal or user defined function/variable
4686 * names in expressions, eg :while s^I
4687 * EXPAND_ENV_VARS Complete environment variable names
4688 * EXPAND_USER Complete user names
4689 */
set_expand_context(expand_T * xp)4690 static void set_expand_context(expand_T *xp)
4691 {
4692 // only expansion for ':', '>' and '=' command-lines
4693 if (ccline.cmdfirstc != ':'
4694 && ccline.cmdfirstc != '>' && ccline.cmdfirstc != '='
4695 && !ccline.input_fn) {
4696 xp->xp_context = EXPAND_NOTHING;
4697 return;
4698 }
4699 set_cmd_context(xp, ccline.cmdbuff, ccline.cmdlen, ccline.cmdpos, true);
4700 }
4701
4702 /// @param str start of command line
4703 /// @param len length of command line (excl. NUL)
4704 /// @param col position of cursor
4705 /// @param use_ccline use ccline for info
set_cmd_context(expand_T * xp,char_u * str,int len,int col,int use_ccline)4706 void set_cmd_context(expand_T *xp, char_u *str, int len, int col, int use_ccline)
4707 {
4708 char_u old_char = NUL;
4709
4710 /*
4711 * Avoid a UMR warning from Purify, only save the character if it has been
4712 * written before.
4713 */
4714 if (col < len) {
4715 old_char = str[col];
4716 }
4717 str[col] = NUL;
4718 const char *nextcomm = (const char *)str;
4719
4720 if (use_ccline && ccline.cmdfirstc == '=') {
4721 // pass CMD_SIZE because there is no real command
4722 set_context_for_expression(xp, str, CMD_SIZE);
4723 } else if (use_ccline && ccline.input_fn) {
4724 xp->xp_context = ccline.xp_context;
4725 xp->xp_pattern = ccline.cmdbuff;
4726 xp->xp_arg = ccline.xp_arg;
4727 } else {
4728 while (nextcomm != NULL) {
4729 nextcomm = set_one_cmd_context(xp, nextcomm);
4730 }
4731 }
4732
4733 /* Store the string here so that call_user_expand_func() can get to them
4734 * easily. */
4735 xp->xp_line = str;
4736 xp->xp_col = col;
4737
4738 str[col] = old_char;
4739 }
4740
4741 /// Expand the command line "str" from context "xp".
4742 /// "xp" must have been set by set_cmd_context().
4743 /// xp->xp_pattern points into "str", to where the text that is to be expanded
4744 /// starts.
4745 /// Returns EXPAND_UNSUCCESSFUL when there is something illegal before the
4746 /// cursor.
4747 /// Returns EXPAND_NOTHING when there is nothing to expand, might insert the
4748 /// key that triggered expansion literally.
4749 /// Returns EXPAND_OK otherwise.
4750 ///
4751 /// @param str start of command line
4752 /// @param col position of cursor
4753 /// @param matchcount return: nr of matches
4754 /// @param matches return: array of pointers to matches
expand_cmdline(expand_T * xp,char_u * str,int col,int * matchcount,char_u *** matches)4755 int expand_cmdline(expand_T *xp, char_u *str, int col, int *matchcount, char_u ***matches)
4756 {
4757 char_u *file_str = NULL;
4758 int options = WILD_ADD_SLASH|WILD_SILENT;
4759
4760 if (xp->xp_context == EXPAND_UNSUCCESSFUL) {
4761 beep_flush();
4762 return EXPAND_UNSUCCESSFUL; // Something illegal on command line
4763 }
4764 if (xp->xp_context == EXPAND_NOTHING) {
4765 // Caller can use the character as a normal char instead
4766 return EXPAND_NOTHING;
4767 }
4768
4769 // add star to file name, or convert to regexp if not exp. files.
4770 assert((str + col) - xp->xp_pattern >= 0);
4771 xp->xp_pattern_len = (size_t)((str + col) - xp->xp_pattern);
4772 file_str = addstar(xp->xp_pattern, xp->xp_pattern_len, xp->xp_context);
4773
4774 if (p_wic) {
4775 options += WILD_ICASE;
4776 }
4777
4778 // find all files that match the description
4779 if (ExpandFromContext(xp, file_str, matchcount, matches, options) == FAIL) {
4780 *matchcount = 0;
4781 *matches = NULL;
4782 }
4783 xfree(file_str);
4784
4785 return EXPAND_OK;
4786 }
4787
4788 // Cleanup matches for help tags:
4789 // Remove "@ab" if the top of 'helplang' is "ab" and the language of the first
4790 // tag matches it. Otherwise remove "@en" if "en" is the only language.
cleanup_help_tags(int num_file,char_u ** file)4791 static void cleanup_help_tags(int num_file, char_u **file)
4792 {
4793 char_u buf[4];
4794 char_u *p = buf;
4795
4796 if (p_hlg[0] != NUL && (p_hlg[0] != 'e' || p_hlg[1] != 'n')) {
4797 *p++ = '@';
4798 *p++ = p_hlg[0];
4799 *p++ = p_hlg[1];
4800 }
4801 *p = NUL;
4802
4803 for (int i = 0; i < num_file; i++) {
4804 int len = (int)STRLEN(file[i]) - 3;
4805 if (len <= 0) {
4806 continue;
4807 }
4808 if (STRCMP(file[i] + len, "@en") == 0) {
4809 // Sorting on priority means the same item in another language may
4810 // be anywhere. Search all items for a match up to the "@en".
4811 int j;
4812 for (j = 0; j < num_file; j++) {
4813 if (j != i
4814 && (int)STRLEN(file[j]) == len + 3
4815 && STRNCMP(file[i], file[j], len + 1) == 0) {
4816 break;
4817 }
4818 }
4819 if (j == num_file) {
4820 // item only exists with @en, remove it
4821 file[i][len] = NUL;
4822 }
4823 }
4824 }
4825
4826 if (*buf != NUL) {
4827 for (int i = 0; i < num_file; i++) {
4828 int len = (int)STRLEN(file[i]) - 3;
4829 if (len <= 0) {
4830 continue;
4831 }
4832 if (STRCMP(file[i] + len, buf) == 0) {
4833 // remove the default language
4834 file[i][len] = NUL;
4835 }
4836 }
4837 }
4838 }
4839
4840 typedef char_u *(*ExpandFunc)(expand_T *, int);
4841
4842 /// Do the expansion based on xp->xp_context and "pat".
4843 ///
4844 /// @param options WILD_ flags
ExpandFromContext(expand_T * xp,char_u * pat,int * num_file,char_u *** file,int options)4845 static int ExpandFromContext(expand_T *xp, char_u *pat, int *num_file, char_u ***file, int options)
4846 {
4847 regmatch_T regmatch;
4848 int ret;
4849 int flags;
4850
4851 flags = EW_DIR; // include directories
4852 if (options & WILD_LIST_NOTFOUND) {
4853 flags |= EW_NOTFOUND;
4854 }
4855 if (options & WILD_ADD_SLASH) {
4856 flags |= EW_ADDSLASH;
4857 }
4858 if (options & WILD_KEEP_ALL) {
4859 flags |= EW_KEEPALL;
4860 }
4861 if (options & WILD_SILENT) {
4862 flags |= EW_SILENT;
4863 }
4864 if (options & WILD_NOERROR) {
4865 flags |= EW_NOERROR;
4866 }
4867 if (options & WILD_ALLLINKS) {
4868 flags |= EW_ALLLINKS;
4869 }
4870
4871 if (xp->xp_context == EXPAND_FILES
4872 || xp->xp_context == EXPAND_DIRECTORIES
4873 || xp->xp_context == EXPAND_FILES_IN_PATH) {
4874 /*
4875 * Expand file or directory names.
4876 */
4877 int free_pat = FALSE;
4878 int i;
4879
4880 // for ":set path=" and ":set tags=" halve backslashes for escaped space
4881 if (xp->xp_backslash != XP_BS_NONE) {
4882 free_pat = TRUE;
4883 pat = vim_strsave(pat);
4884 for (i = 0; pat[i]; ++i) {
4885 if (pat[i] == '\\') {
4886 if (xp->xp_backslash == XP_BS_THREE
4887 && pat[i + 1] == '\\'
4888 && pat[i + 2] == '\\'
4889 && pat[i + 3] == ' ') {
4890 STRMOVE(pat + i, pat + i + 3);
4891 }
4892 if (xp->xp_backslash == XP_BS_ONE
4893 && pat[i + 1] == ' ') {
4894 STRMOVE(pat + i, pat + i + 1);
4895 }
4896 }
4897 }
4898 }
4899
4900 if (xp->xp_context == EXPAND_FILES) {
4901 flags |= EW_FILE;
4902 } else if (xp->xp_context == EXPAND_FILES_IN_PATH) {
4903 flags |= (EW_FILE | EW_PATH);
4904 } else {
4905 flags = (flags | EW_DIR) & ~EW_FILE;
4906 }
4907 if (options & WILD_ICASE) {
4908 flags |= EW_ICASE;
4909 }
4910
4911 // Expand wildcards, supporting %:h and the like.
4912 ret = expand_wildcards_eval(&pat, num_file, file, flags);
4913 if (free_pat) {
4914 xfree(pat);
4915 }
4916 #ifdef BACKSLASH_IN_FILENAME
4917 if (p_csl[0] != NUL && (options & WILD_IGNORE_COMPLETESLASH) == 0) {
4918 for (int i = 0; i < *num_file; i++) {
4919 char_u *ptr = (*file)[i];
4920 while (*ptr != NUL) {
4921 if (p_csl[0] == 's' && *ptr == '\\') {
4922 *ptr = '/';
4923 } else if (p_csl[0] == 'b' && *ptr == '/') {
4924 *ptr = '\\';
4925 }
4926 ptr += utfc_ptr2len(ptr);
4927 }
4928 }
4929 }
4930 #endif
4931 return ret;
4932 }
4933
4934 *file = NULL;
4935 *num_file = 0;
4936 if (xp->xp_context == EXPAND_HELP) {
4937 /* With an empty argument we would get all the help tags, which is
4938 * very slow. Get matches for "help" instead. */
4939 if (find_help_tags(*pat == NUL ? (char_u *)"help" : pat,
4940 num_file, file, false) == OK) {
4941 cleanup_help_tags(*num_file, *file);
4942 return OK;
4943 }
4944 return FAIL;
4945 }
4946
4947 if (xp->xp_context == EXPAND_SHELLCMD) {
4948 *file = NULL;
4949 expand_shellcmd(pat, num_file, file, flags);
4950 return OK;
4951 }
4952 if (xp->xp_context == EXPAND_OLD_SETTING) {
4953 ExpandOldSetting(num_file, file);
4954 return OK;
4955 }
4956 if (xp->xp_context == EXPAND_BUFFERS) {
4957 return ExpandBufnames(pat, num_file, file, options);
4958 }
4959 if (xp->xp_context == EXPAND_DIFF_BUFFERS) {
4960 return ExpandBufnames(pat, num_file, file, options | BUF_DIFF_FILTER);
4961 }
4962 if (xp->xp_context == EXPAND_TAGS
4963 || xp->xp_context == EXPAND_TAGS_LISTFILES) {
4964 return expand_tags(xp->xp_context == EXPAND_TAGS, pat, num_file, file);
4965 }
4966 if (xp->xp_context == EXPAND_COLORS) {
4967 char *directories[] = { "colors", NULL };
4968 return ExpandRTDir(pat, DIP_START + DIP_OPT + DIP_LUA, num_file, file,
4969 directories);
4970 }
4971 if (xp->xp_context == EXPAND_COMPILER) {
4972 char *directories[] = { "compiler", NULL };
4973 return ExpandRTDir(pat, DIP_LUA, num_file, file, directories);
4974 }
4975 if (xp->xp_context == EXPAND_OWNSYNTAX) {
4976 char *directories[] = { "syntax", NULL };
4977 return ExpandRTDir(pat, 0, num_file, file, directories);
4978 }
4979 if (xp->xp_context == EXPAND_FILETYPE) {
4980 char *directories[] = { "syntax", "indent", "ftplugin", NULL };
4981 return ExpandRTDir(pat, DIP_LUA, num_file, file, directories);
4982 }
4983 if (xp->xp_context == EXPAND_USER_LIST) {
4984 return ExpandUserList(xp, num_file, file);
4985 }
4986 if (xp->xp_context == EXPAND_PACKADD) {
4987 return ExpandPackAddDir(pat, num_file, file);
4988 }
4989
4990 // When expanding a function name starting with s:, match the <SNR>nr_
4991 // prefix.
4992 char *tofree = NULL;
4993 if (xp->xp_context == EXPAND_USER_FUNC && STRNCMP(pat, "^s:", 3) == 0) {
4994 const size_t len = STRLEN(pat) + 20;
4995
4996 tofree = xmalloc(len);
4997 snprintf(tofree, len, "^<SNR>\\d\\+_%s", pat + 3);
4998 pat = (char_u *)tofree;
4999 }
5000
5001 if (xp->xp_context == EXPAND_LUA) {
5002 ILOG("PAT %s", pat);
5003 return nlua_expand_pat(xp, pat, num_file, file);
5004 }
5005
5006 regmatch.regprog = vim_regcomp(pat, p_magic ? RE_MAGIC : 0);
5007 if (regmatch.regprog == NULL) {
5008 return FAIL;
5009 }
5010
5011 // set ignore-case according to p_ic, p_scs and pat
5012 regmatch.rm_ic = ignorecase(pat);
5013
5014 if (xp->xp_context == EXPAND_SETTINGS
5015 || xp->xp_context == EXPAND_BOOL_SETTINGS) {
5016 ret = ExpandSettings(xp, ®match, num_file, file);
5017 } else if (xp->xp_context == EXPAND_MAPPINGS) {
5018 ret = ExpandMappings(®match, num_file, file);
5019 } else if (xp->xp_context == EXPAND_USER_DEFINED) {
5020 ret = ExpandUserDefined(xp, ®match, num_file, file);
5021 } else {
5022 static struct expgen {
5023 int context;
5024 ExpandFunc func;
5025 int ic;
5026 int escaped;
5027 } tab[] = {
5028 { EXPAND_COMMANDS, get_command_name, false, true },
5029 { EXPAND_BEHAVE, get_behave_arg, true, true },
5030 { EXPAND_MAPCLEAR, get_mapclear_arg, true, true },
5031 { EXPAND_MESSAGES, get_messages_arg, true, true },
5032 { EXPAND_HISTORY, get_history_arg, true, true },
5033 { EXPAND_USER_COMMANDS, get_user_commands, false, true },
5034 { EXPAND_USER_ADDR_TYPE, get_user_cmd_addr_type, false, true },
5035 { EXPAND_USER_CMD_FLAGS, get_user_cmd_flags, false, true },
5036 { EXPAND_USER_NARGS, get_user_cmd_nargs, false, true },
5037 { EXPAND_USER_COMPLETE, get_user_cmd_complete, false, true },
5038 { EXPAND_USER_VARS, get_user_var_name, false, true },
5039 { EXPAND_FUNCTIONS, get_function_name, false, true },
5040 { EXPAND_USER_FUNC, get_user_func_name, false, true },
5041 { EXPAND_EXPRESSION, get_expr_name, false, true },
5042 { EXPAND_MENUS, get_menu_name, false, true },
5043 { EXPAND_MENUNAMES, get_menu_names, false, true },
5044 { EXPAND_SYNTAX, get_syntax_name, true, true },
5045 { EXPAND_SYNTIME, get_syntime_arg, true, true },
5046 { EXPAND_HIGHLIGHT, (ExpandFunc)get_highlight_name, true, true },
5047 { EXPAND_EVENTS, get_event_name, true, true },
5048 { EXPAND_AUGROUP, get_augroup_name, true, true },
5049 { EXPAND_CSCOPE, get_cscope_name, true, true },
5050 { EXPAND_SIGN, get_sign_name, true, true },
5051 { EXPAND_PROFILE, get_profile_name, true, true },
5052 #ifdef HAVE_WORKING_LIBINTL
5053 { EXPAND_LANGUAGE, get_lang_arg, true, false },
5054 { EXPAND_LOCALES, get_locales, true, false },
5055 #endif
5056 { EXPAND_ENV_VARS, get_env_name, true, true },
5057 { EXPAND_USER, get_users, true, false },
5058 { EXPAND_ARGLIST, get_arglist_name, true, false },
5059 { EXPAND_CHECKHEALTH, get_healthcheck_names, true, false },
5060 };
5061 int i;
5062
5063 /*
5064 * Find a context in the table and call the ExpandGeneric() with the
5065 * right function to do the expansion.
5066 */
5067 ret = FAIL;
5068 for (i = 0; i < (int)ARRAY_SIZE(tab); ++i) {
5069 if (xp->xp_context == tab[i].context) {
5070 if (tab[i].ic) {
5071 regmatch.rm_ic = TRUE;
5072 }
5073 ExpandGeneric(xp, ®match, num_file, file, tab[i].func,
5074 tab[i].escaped);
5075 ret = OK;
5076 break;
5077 }
5078 }
5079 }
5080
5081 vim_regfree(regmatch.regprog);
5082 xfree(tofree);
5083
5084 return ret;
5085 }
5086
5087 /// Expand a list of names.
5088 ///
5089 /// Generic function for command line completion. It calls a function to
5090 /// obtain strings, one by one. The strings are matched against a regexp
5091 /// program. Matching strings are copied into an array, which is returned.
5092 ///
5093 /// @param func returns a string from the list
ExpandGeneric(expand_T * xp,regmatch_T * regmatch,int * num_file,char_u *** file,CompleteListItemGetter func,int escaped)5094 static void ExpandGeneric(expand_T *xp, regmatch_T *regmatch, int *num_file, char_u ***file,
5095 CompleteListItemGetter func, int escaped)
5096 {
5097 int i;
5098 size_t count = 0;
5099 char_u *str;
5100
5101 // count the number of matching names
5102 for (i = 0;; ++i) {
5103 str = (*func)(xp, i);
5104 if (str == NULL) { // end of list
5105 break;
5106 }
5107 if (*str == NUL) { // skip empty strings
5108 continue;
5109 }
5110 if (vim_regexec(regmatch, str, (colnr_T)0)) {
5111 ++count;
5112 }
5113 }
5114 if (count == 0) {
5115 return;
5116 }
5117 assert(count < INT_MAX);
5118 *num_file = (int)count;
5119 *file = (char_u **)xmalloc(count * sizeof(char_u *));
5120
5121 // copy the matching names into allocated memory
5122 count = 0;
5123 for (i = 0;; i++) {
5124 str = (*func)(xp, i);
5125 if (str == NULL) { // End of list.
5126 break;
5127 }
5128 if (*str == NUL) { // Skip empty strings.
5129 continue;
5130 }
5131 if (vim_regexec(regmatch, str, (colnr_T)0)) {
5132 if (escaped) {
5133 str = vim_strsave_escaped(str, (char_u *)" \t\\.");
5134 } else {
5135 str = vim_strsave(str);
5136 }
5137 (*file)[count++] = str;
5138 if (func == get_menu_names) {
5139 // Test for separator added by get_menu_names().
5140 str += STRLEN(str) - 1;
5141 if (*str == '\001') {
5142 *str = '.';
5143 }
5144 }
5145 }
5146 }
5147
5148 // Sort the results. Keep menu's in the specified order.
5149 if (xp->xp_context != EXPAND_MENUNAMES && xp->xp_context != EXPAND_MENUS) {
5150 if (xp->xp_context == EXPAND_EXPRESSION
5151 || xp->xp_context == EXPAND_FUNCTIONS
5152 || xp->xp_context == EXPAND_USER_FUNC) {
5153 // <SNR> functions should be sorted to the end.
5154 qsort((void *)*file, (size_t)*num_file, sizeof(char_u *),
5155 sort_func_compare);
5156 } else {
5157 sort_strings(*file, *num_file);
5158 }
5159 }
5160
5161 /* Reset the variables used for special highlight names expansion, so that
5162 * they don't show up when getting normal highlight names by ID. */
5163 reset_expand_highlight();
5164 }
5165
5166 /// Complete a shell command.
5167 ///
5168 /// @param filepat is a pattern to match with command names.
5169 /// @param[out] num_file is pointer to number of matches.
5170 /// @param[out] file is pointer to array of pointers to matches.
5171 /// *file will either be set to NULL or point to
5172 /// allocated memory.
5173 /// @param flagsarg is a combination of EW_* flags.
expand_shellcmd(char_u * filepat,int * num_file,char_u *** file,int flagsarg)5174 static void expand_shellcmd(char_u *filepat, int *num_file, char_u ***file, int flagsarg)
5175 FUNC_ATTR_NONNULL_ALL
5176 {
5177 char_u *pat;
5178 int i;
5179 char_u *path = NULL;
5180 garray_T ga;
5181 char_u *buf = xmalloc(MAXPATHL);
5182 size_t l;
5183 char_u *s, *e;
5184 int flags = flagsarg;
5185 int ret;
5186 bool did_curdir = false;
5187
5188 // for ":set path=" and ":set tags=" halve backslashes for escaped space
5189 pat = vim_strsave(filepat);
5190 for (i = 0; pat[i]; ++i) {
5191 if (pat[i] == '\\' && pat[i + 1] == ' ') {
5192 STRMOVE(pat + i, pat + i + 1);
5193 }
5194 }
5195
5196 flags |= EW_FILE | EW_EXEC | EW_SHELLCMD;
5197
5198 bool mustfree = false; // Track memory allocation for *path.
5199 if (pat[0] == '.' && (vim_ispathsep(pat[1])
5200 || (pat[1] == '.' && vim_ispathsep(pat[2])))) {
5201 path = (char_u *)".";
5202 } else {
5203 // For an absolute name we don't use $PATH.
5204 if (!path_is_absolute(pat)) {
5205 path = (char_u *)vim_getenv("PATH");
5206 }
5207 if (path == NULL) {
5208 path = (char_u *)"";
5209 } else {
5210 mustfree = true;
5211 }
5212 }
5213
5214 /*
5215 * Go over all directories in $PATH. Expand matches in that directory and
5216 * collect them in "ga". When "." is not in $PATH also expaned for the
5217 * current directory, to find "subdir/cmd".
5218 */
5219 ga_init(&ga, (int)sizeof(char *), 10);
5220 hashtab_T found_ht;
5221 hash_init(&found_ht);
5222 for (s = path;; s = e) {
5223 e = vim_strchr(s, ENV_SEPCHAR);
5224 if (e == NULL) {
5225 e = s + STRLEN(s);
5226 }
5227
5228 if (*s == NUL) {
5229 if (did_curdir) {
5230 break;
5231 }
5232 // Find directories in the current directory, path is empty.
5233 did_curdir = true;
5234 flags |= EW_DIR;
5235 } else if (STRNCMP(s, ".", e - s) == 0) {
5236 did_curdir = true;
5237 flags |= EW_DIR;
5238 } else {
5239 // Do not match directories inside a $PATH item.
5240 flags &= ~EW_DIR;
5241 }
5242
5243 l = (size_t)(e - s);
5244 if (l > MAXPATHL - 5) {
5245 break;
5246 }
5247 STRLCPY(buf, s, l + 1);
5248 add_pathsep((char *)buf);
5249 l = STRLEN(buf);
5250 STRLCPY(buf + l, pat, MAXPATHL - l);
5251
5252 // Expand matches in one directory of $PATH.
5253 ret = expand_wildcards(1, &buf, num_file, file, flags);
5254 if (ret == OK) {
5255 ga_grow(&ga, *num_file);
5256 {
5257 for (i = 0; i < *num_file; i++) {
5258 char_u *name = (*file)[i];
5259
5260 if (STRLEN(name) > l) {
5261 // Check if this name was already found.
5262 hash_T hash = hash_hash(name + l);
5263 hashitem_T *hi =
5264 hash_lookup(&found_ht, (const char *)(name + l),
5265 STRLEN(name + l), hash);
5266 if (HASHITEM_EMPTY(hi)) {
5267 // Remove the path that was prepended.
5268 STRMOVE(name, name + l);
5269 ((char_u **)ga.ga_data)[ga.ga_len++] = name;
5270 hash_add_item(&found_ht, hi, name, hash);
5271 name = NULL;
5272 }
5273 }
5274 xfree(name);
5275 }
5276 xfree(*file);
5277 }
5278 }
5279 if (*e != NUL) {
5280 ++e;
5281 }
5282 }
5283 *file = ga.ga_data;
5284 *num_file = ga.ga_len;
5285
5286 xfree(buf);
5287 xfree(pat);
5288 if (mustfree) {
5289 xfree(path);
5290 }
5291 hash_clear(&found_ht);
5292 }
5293
5294 /// Call "user_expand_func()" to invoke a user defined Vim script function and
5295 /// return the result (either a string, a List or NULL).
call_user_expand_func(user_expand_func_T user_expand_func,expand_T * xp,int * num_file,char_u *** file)5296 static void *call_user_expand_func(user_expand_func_T user_expand_func, expand_T *xp, int *num_file,
5297 char_u ***file)
5298 FUNC_ATTR_NONNULL_ALL
5299 {
5300 char_u keep = 0;
5301 typval_T args[4];
5302 char_u *pat = NULL;
5303 const sctx_T save_current_sctx = current_sctx;
5304 struct cmdline_info save_ccline;
5305
5306 if (xp->xp_arg == NULL || xp->xp_arg[0] == '\0' || xp->xp_line == NULL) {
5307 return NULL;
5308 }
5309 *num_file = 0;
5310 *file = NULL;
5311
5312 if (ccline.cmdbuff != NULL) {
5313 keep = ccline.cmdbuff[ccline.cmdlen];
5314 ccline.cmdbuff[ccline.cmdlen] = 0;
5315 }
5316
5317 pat = vim_strnsave(xp->xp_pattern, xp->xp_pattern_len);
5318 args[0].v_type = VAR_STRING;
5319 args[1].v_type = VAR_STRING;
5320 args[2].v_type = VAR_NUMBER;
5321 args[3].v_type = VAR_UNKNOWN;
5322 args[0].vval.v_string = pat;
5323 args[1].vval.v_string = xp->xp_line;
5324 args[2].vval.v_number = xp->xp_col;
5325
5326 // Save the cmdline, we don't know what the function may do.
5327 save_ccline = ccline;
5328 ccline.cmdbuff = NULL;
5329 ccline.cmdprompt = NULL;
5330 current_sctx = xp->xp_script_ctx;
5331
5332 void *const ret = user_expand_func(xp->xp_arg, 3, args);
5333
5334 ccline = save_ccline;
5335 current_sctx = save_current_sctx;
5336 if (ccline.cmdbuff != NULL) {
5337 ccline.cmdbuff[ccline.cmdlen] = keep;
5338 }
5339
5340 xfree(pat);
5341 return ret;
5342 }
5343
5344 /*
5345 * Expand names with a function defined by the user.
5346 */
ExpandUserDefined(expand_T * xp,regmatch_T * regmatch,int * num_file,char_u *** file)5347 static int ExpandUserDefined(expand_T *xp, regmatch_T *regmatch, int *num_file, char_u ***file)
5348 {
5349 char_u *e;
5350 garray_T ga;
5351
5352 char_u *const retstr = call_user_expand_func((user_expand_func_T)call_func_retstr, xp, num_file,
5353 file);
5354
5355 if (retstr == NULL) {
5356 return FAIL;
5357 }
5358
5359 ga_init(&ga, (int)sizeof(char *), 3);
5360 for (char_u *s = retstr; *s != NUL; s = e) {
5361 e = vim_strchr(s, '\n');
5362 if (e == NULL) {
5363 e = s + STRLEN(s);
5364 }
5365 const char_u keep = *e;
5366 *e = NUL;
5367
5368 const bool skip = xp->xp_pattern[0]
5369 && vim_regexec(regmatch, s, (colnr_T)0) == 0;
5370 *e = keep;
5371 if (!skip) {
5372 GA_APPEND(char_u *, &ga, vim_strnsave(s, (size_t)(e - s)));
5373 }
5374
5375 if (*e != NUL) {
5376 e++;
5377 }
5378 }
5379 xfree(retstr);
5380 *file = ga.ga_data;
5381 *num_file = ga.ga_len;
5382 return OK;
5383 }
5384
5385 /*
5386 * Expand names with a list returned by a function defined by the user.
5387 */
ExpandUserList(expand_T * xp,int * num_file,char_u *** file)5388 static int ExpandUserList(expand_T *xp, int *num_file, char_u ***file)
5389 {
5390 list_T *const retlist = call_user_expand_func((user_expand_func_T)call_func_retlist, xp, num_file,
5391 file);
5392 if (retlist == NULL) {
5393 return FAIL;
5394 }
5395
5396 garray_T ga;
5397 ga_init(&ga, (int)sizeof(char *), 3);
5398 // Loop over the items in the list.
5399 TV_LIST_ITER_CONST(retlist, li, {
5400 if (TV_LIST_ITEM_TV(li)->v_type != VAR_STRING
5401 || TV_LIST_ITEM_TV(li)->vval.v_string == NULL) {
5402 continue; // Skip non-string items and empty strings.
5403 }
5404
5405 GA_APPEND(char *, &ga, xstrdup((const char *)TV_LIST_ITEM_TV(li)->vval.v_string));
5406 });
5407 tv_list_unref(retlist);
5408
5409 *file = ga.ga_data;
5410 *num_file = ga.ga_len;
5411 return OK;
5412 }
5413
5414 /// Expand color scheme, compiler or filetype names.
5415 /// Search from 'runtimepath':
5416 /// 'runtimepath'/{dirnames}/{pat}.vim
5417 /// When "flags" has DIP_START: search also from 'start' of 'packpath':
5418 /// 'packpath'/pack/ * /start/ * /{dirnames}/{pat}.vim
5419 /// When "flags" has DIP_OPT: search also from 'opt' of 'packpath':
5420 /// 'packpath'/pack/ * /opt/ * /{dirnames}/{pat}.vim
5421 /// When "flags" has DIP_LUA: search also performed for .lua files
5422 /// "dirnames" is an array with one or more directory names.
ExpandRTDir(char_u * pat,int flags,int * num_file,char_u *** file,char * dirnames[])5423 static int ExpandRTDir(char_u *pat, int flags, int *num_file, char_u ***file, char *dirnames[])
5424 {
5425 *num_file = 0;
5426 *file = NULL;
5427 size_t pat_len = STRLEN(pat);
5428
5429 garray_T ga;
5430 ga_init(&ga, (int)sizeof(char *), 10);
5431
5432 // TODO(bfredl): this is bullshit, exandpath should not reinvent path logic.
5433 for (int i = 0; dirnames[i] != NULL; i++) {
5434 size_t size = STRLEN(dirnames[i]) + pat_len + 7;
5435 char_u *s = xmalloc(size);
5436 snprintf((char *)s, size, "%s/%s*.vim", dirnames[i], pat);
5437 globpath(p_rtp, s, &ga, 0);
5438 if (flags & DIP_LUA) {
5439 snprintf((char *)s, size, "%s/%s*.lua", dirnames[i], pat);
5440 globpath(p_rtp, s, &ga, 0);
5441 }
5442 xfree(s);
5443 }
5444
5445 if (flags & DIP_START) {
5446 for (int i = 0; dirnames[i] != NULL; i++) {
5447 size_t size = STRLEN(dirnames[i]) + pat_len + 22;
5448 char_u *s = xmalloc(size);
5449 snprintf((char *)s, size, "pack/*/start/*/%s/%s*.vim", dirnames[i], pat); // NOLINT
5450 globpath(p_pp, s, &ga, 0);
5451 if (flags & DIP_LUA) {
5452 snprintf((char *)s, size, "pack/*/start/*/%s/%s*.lua", dirnames[i], pat); // NOLINT
5453 globpath(p_pp, s, &ga, 0);
5454 }
5455 xfree(s);
5456 }
5457
5458 for (int i = 0; dirnames[i] != NULL; i++) {
5459 size_t size = STRLEN(dirnames[i]) + pat_len + 22;
5460 char_u *s = xmalloc(size);
5461 snprintf((char *)s, size, "start/*/%s/%s*.vim", dirnames[i], pat); // NOLINT
5462 globpath(p_pp, s, &ga, 0);
5463 if (flags & DIP_LUA) {
5464 snprintf((char *)s, size, "start/*/%s/%s*.lua", dirnames[i], pat); // NOLINT
5465 globpath(p_pp, s, &ga, 0);
5466 }
5467 xfree(s);
5468 }
5469 }
5470
5471 if (flags & DIP_OPT) {
5472 for (int i = 0; dirnames[i] != NULL; i++) {
5473 size_t size = STRLEN(dirnames[i]) + pat_len + 20;
5474 char_u *s = xmalloc(size);
5475 snprintf((char *)s, size, "pack/*/opt/*/%s/%s*.vim", dirnames[i], pat); // NOLINT
5476 globpath(p_pp, s, &ga, 0);
5477 if (flags & DIP_LUA) {
5478 snprintf((char *)s, size, "pack/*/opt/*/%s/%s*.lua", dirnames[i], pat); // NOLINT
5479 globpath(p_pp, s, &ga, 0);
5480 }
5481 xfree(s);
5482 }
5483
5484 for (int i = 0; dirnames[i] != NULL; i++) {
5485 size_t size = STRLEN(dirnames[i]) + pat_len + 20;
5486 char_u *s = xmalloc(size);
5487 snprintf((char *)s, size, "opt/*/%s/%s*.vim", dirnames[i], pat); // NOLINT
5488 globpath(p_pp, s, &ga, 0);
5489 if (flags & DIP_LUA) {
5490 snprintf((char *)s, size, "opt/*/%s/%s*.lua", dirnames[i], pat); // NOLINT
5491 globpath(p_pp, s, &ga, 0);
5492 }
5493 xfree(s);
5494 }
5495 }
5496
5497 for (int i = 0; i < ga.ga_len; i++) {
5498 char_u *match = ((char_u **)ga.ga_data)[i];
5499 char_u *s = match;
5500 char_u *e = s + STRLEN(s);
5501 if (e - s > 4 && (STRNICMP(e - 4, ".vim", 4) == 0
5502 || ((flags & DIP_LUA)
5503 && STRNICMP(e - 4, ".lua", 4) == 0))) {
5504 e -= 4;
5505 for (s = e; s > match; MB_PTR_BACK(match, s)) {
5506 if (vim_ispathsep(*s)) {
5507 break;
5508 }
5509 }
5510 s++;
5511 *e = NUL;
5512 assert((e - s) + 1 >= 0);
5513 memmove(match, s, (size_t)(e - s) + 1);
5514 }
5515 }
5516
5517 if (GA_EMPTY(&ga)) {
5518 return FAIL;
5519 }
5520
5521 /* Sort and remove duplicates which can happen when specifying multiple
5522 * directories in dirnames. */
5523 ga_remove_duplicate_strings(&ga);
5524
5525 *file = ga.ga_data;
5526 *num_file = ga.ga_len;
5527 return OK;
5528 }
5529
5530 /// Expand loadplugin names:
5531 /// 'packpath'/pack/ * /opt/{pat}
ExpandPackAddDir(char_u * pat,int * num_file,char_u *** file)5532 static int ExpandPackAddDir(char_u *pat, int *num_file, char_u ***file)
5533 {
5534 garray_T ga;
5535
5536 *num_file = 0;
5537 *file = NULL;
5538 size_t pat_len = STRLEN(pat);
5539 ga_init(&ga, (int)sizeof(char *), 10);
5540
5541 size_t buflen = pat_len + 26;
5542 char_u *s = xmalloc(buflen);
5543 snprintf((char *)s, buflen, "pack/*/opt/%s*", pat); // NOLINT
5544 globpath(p_pp, s, &ga, 0);
5545 snprintf((char *)s, buflen, "opt/%s*", pat); // NOLINT
5546 globpath(p_pp, s, &ga, 0);
5547 xfree(s);
5548
5549 for (int i = 0; i < ga.ga_len; i++) {
5550 char_u *match = ((char_u **)ga.ga_data)[i];
5551 s = path_tail(match);
5552 memmove(match, s, STRLEN(s)+1);
5553 }
5554
5555 if (GA_EMPTY(&ga)) {
5556 return FAIL;
5557 }
5558
5559 // Sort and remove duplicates which can happen when specifying multiple
5560 // directories in dirnames.
5561 ga_remove_duplicate_strings(&ga);
5562
5563 *file = ga.ga_data;
5564 *num_file = ga.ga_len;
5565 return OK;
5566 }
5567
5568
5569 /// Expand `file` for all comma-separated directories in `path`.
5570 /// Adds matches to `ga`.
globpath(char_u * path,char_u * file,garray_T * ga,int expand_options)5571 void globpath(char_u *path, char_u *file, garray_T *ga, int expand_options)
5572 {
5573 expand_T xpc;
5574 ExpandInit(&xpc);
5575 xpc.xp_context = EXPAND_FILES;
5576
5577 char_u *buf = xmalloc(MAXPATHL);
5578
5579 // Loop over all entries in {path}.
5580 while (*path != NUL) {
5581 // Copy one item of the path to buf[] and concatenate the file name.
5582 copy_option_part(&path, buf, MAXPATHL, ",");
5583 if (STRLEN(buf) + STRLEN(file) + 2 < MAXPATHL) {
5584 add_pathsep((char *)buf);
5585 STRCAT(buf, file); // NOLINT
5586
5587 char_u **p;
5588 int num_p = 0;
5589 (void)ExpandFromContext(&xpc, buf, &num_p, &p,
5590 WILD_SILENT | expand_options);
5591 if (num_p > 0) {
5592 ExpandEscape(&xpc, buf, num_p, p, WILD_SILENT | expand_options);
5593
5594 // Concatenate new results to previous ones.
5595 ga_grow(ga, num_p);
5596 // take over the pointers and put them in "ga"
5597 for (int i = 0; i < num_p; i++) {
5598 ((char_u **)ga->ga_data)[ga->ga_len] = p[i];
5599 ga->ga_len++;
5600 }
5601 xfree(p);
5602 }
5603 }
5604 }
5605
5606 xfree(buf);
5607 }
5608
5609
5610 /*********************************
5611 * Command line history stuff *
5612 *********************************/
5613
5614 /// Translate a history character to the associated type number
hist_char2type(const int c)5615 static HistoryType hist_char2type(const int c)
5616 FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT
5617 {
5618 switch (c) {
5619 case ':':
5620 return HIST_CMD;
5621 case '=':
5622 return HIST_EXPR;
5623 case '@':
5624 return HIST_INPUT;
5625 case '>':
5626 return HIST_DEBUG;
5627 case NUL:
5628 case '/':
5629 case '?':
5630 return HIST_SEARCH;
5631 default:
5632 return HIST_INVALID;
5633 }
5634 // Silence -Wreturn-type
5635 return 0;
5636 }
5637
5638 /*
5639 * Table of history names.
5640 * These names are used in :history and various hist...() functions.
5641 * It is sufficient to give the significant prefix of a history name.
5642 */
5643
5644 static char *(history_names[]) =
5645 {
5646 "cmd",
5647 "search",
5648 "expr",
5649 "input",
5650 "debug",
5651 NULL
5652 };
5653
5654 /*
5655 * Function given to ExpandGeneric() to obtain the possible first
5656 * arguments of the ":history command.
5657 */
get_history_arg(expand_T * xp,int idx)5658 static char_u *get_history_arg(expand_T *xp, int idx)
5659 {
5660 static char_u compl[2] = { NUL, NUL };
5661 char *short_names = ":=@>?/";
5662 int short_names_count = (int)STRLEN(short_names);
5663 int history_name_count = ARRAY_SIZE(history_names) - 1;
5664
5665 if (idx < short_names_count) {
5666 compl[0] = (char_u)short_names[idx];
5667 return compl;
5668 }
5669 if (idx < short_names_count + history_name_count) {
5670 return (char_u *)history_names[idx - short_names_count];
5671 }
5672 if (idx == short_names_count + history_name_count) {
5673 return (char_u *)"all";
5674 }
5675 return NULL;
5676 }
5677
5678 /// Initialize command line history.
5679 /// Also used to re-allocate history tables when size changes.
init_history(void)5680 void init_history(void)
5681 {
5682 assert(p_hi >= 0 && p_hi <= INT_MAX);
5683 int newlen = (int)p_hi;
5684 int oldlen = hislen;
5685
5686 // If history tables size changed, reallocate them.
5687 // Tables are circular arrays (current position marked by hisidx[type]).
5688 // On copying them to the new arrays, we take the chance to reorder them.
5689 if (newlen != oldlen) {
5690 for (int type = 0; type < HIST_COUNT; type++) {
5691 histentry_T *temp = (newlen
5692 ? xmalloc((size_t)newlen * sizeof(*temp))
5693 : NULL);
5694
5695 int j = hisidx[type];
5696 if (j >= 0) {
5697 // old array gets partitioned this way:
5698 // [0 , i1 ) --> newest entries to be deleted
5699 // [i1 , i1 + l1) --> newest entries to be copied
5700 // [i1 + l1 , i2 ) --> oldest entries to be deleted
5701 // [i2 , i2 + l2) --> oldest entries to be copied
5702 int l1 = MIN(j + 1, newlen); // how many newest to copy
5703 int l2 = MIN(newlen, oldlen) - l1; // how many oldest to copy
5704 int i1 = j + 1 - l1; // copy newest from here
5705 int i2 = MAX(l1, oldlen - newlen + l1); // copy oldest from here
5706
5707 // copy as much entries as they fit to new table, reordering them
5708 if (newlen) {
5709 // copy oldest entries
5710 memcpy(&temp[0], &history[type][i2], (size_t)l2 * sizeof(*temp));
5711 // copy newest entries
5712 memcpy(&temp[l2], &history[type][i1], (size_t)l1 * sizeof(*temp));
5713 }
5714
5715 // delete entries that don't fit in newlen, if any
5716 for (int i = 0; i < i1; i++) {
5717 hist_free_entry(history[type] + i);
5718 }
5719 for (int i = i1 + l1; i < i2; i++) {
5720 hist_free_entry(history[type] + i);
5721 }
5722 }
5723
5724 // clear remaining space, if any
5725 int l3 = j < 0 ? 0 : MIN(newlen, oldlen); // number of copied entries
5726 if (newlen) {
5727 memset(temp + l3, 0, (size_t)(newlen - l3) * sizeof(*temp));
5728 }
5729
5730 hisidx[type] = l3 - 1;
5731 xfree(history[type]);
5732 history[type] = temp;
5733 }
5734 hislen = newlen;
5735 }
5736 }
5737
hist_free_entry(histentry_T * hisptr)5738 static inline void hist_free_entry(histentry_T *hisptr)
5739 FUNC_ATTR_NONNULL_ALL
5740 {
5741 xfree(hisptr->hisstr);
5742 tv_list_unref(hisptr->additional_elements);
5743 clear_hist_entry(hisptr);
5744 }
5745
clear_hist_entry(histentry_T * hisptr)5746 static inline void clear_hist_entry(histentry_T *hisptr)
5747 FUNC_ATTR_NONNULL_ALL
5748 {
5749 memset(hisptr, 0, sizeof(*hisptr));
5750 }
5751
5752 /// Check if command line 'str' is already in history.
5753 /// If 'move_to_front' is TRUE, matching entry is moved to end of history.
5754 ///
5755 /// @param move_to_front Move the entry to the front if it exists
in_history(int type,char_u * str,int move_to_front,int sep)5756 static int in_history(int type, char_u *str, int move_to_front, int sep)
5757 {
5758 int i;
5759 int last_i = -1;
5760 char_u *p;
5761
5762 if (hisidx[type] < 0) {
5763 return FALSE;
5764 }
5765 i = hisidx[type];
5766 do {
5767 if (history[type][i].hisstr == NULL) {
5768 return FALSE;
5769 }
5770
5771 /* For search history, check that the separator character matches as
5772 * well. */
5773 p = history[type][i].hisstr;
5774 if (STRCMP(str, p) == 0
5775 && (type != HIST_SEARCH || sep == p[STRLEN(p) + 1])) {
5776 if (!move_to_front) {
5777 return TRUE;
5778 }
5779 last_i = i;
5780 break;
5781 }
5782 if (--i < 0) {
5783 i = hislen - 1;
5784 }
5785 } while (i != hisidx[type]);
5786
5787 if (last_i >= 0) {
5788 list_T *const list = history[type][i].additional_elements;
5789 str = history[type][i].hisstr;
5790 while (i != hisidx[type]) {
5791 if (++i >= hislen) {
5792 i = 0;
5793 }
5794 history[type][last_i] = history[type][i];
5795 last_i = i;
5796 }
5797 tv_list_unref(list);
5798 history[type][i].hisnum = ++hisnum[type];
5799 history[type][i].hisstr = str;
5800 history[type][i].timestamp = os_time();
5801 history[type][i].additional_elements = NULL;
5802 return true;
5803 }
5804 return false;
5805 }
5806
5807 /// Convert history name to its HIST_ equivalent
5808 ///
5809 /// Names are taken from the table above. When `name` is empty returns currently
5810 /// active history or HIST_DEFAULT, depending on `return_default` argument.
5811 ///
5812 /// @param[in] name Converted name.
5813 /// @param[in] len Name length.
5814 /// @param[in] return_default Determines whether HIST_DEFAULT should be
5815 /// returned or value based on `ccline.cmdfirstc`.
5816 ///
5817 /// @return Any value from HistoryType enum, including HIST_INVALID. May not
5818 /// return HIST_DEFAULT unless return_default is true.
get_histtype(const char * const name,const size_t len,const bool return_default)5819 HistoryType get_histtype(const char *const name, const size_t len, const bool return_default)
5820 FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
5821 {
5822 // No argument: use current history.
5823 if (len == 0) {
5824 return return_default ? HIST_DEFAULT : hist_char2type(ccline.cmdfirstc);
5825 }
5826
5827 for (HistoryType i = 0; history_names[i] != NULL; i++) {
5828 if (STRNICMP(name, history_names[i], len) == 0) {
5829 return i;
5830 }
5831 }
5832
5833 if (vim_strchr((char_u *)":=@>?/", name[0]) != NULL && len == 1) {
5834 return hist_char2type(name[0]);
5835 }
5836
5837 return HIST_INVALID;
5838 }
5839
5840 static int last_maptick = -1; // last seen maptick
5841
5842 /// Add the given string to the given history. If the string is already in the
5843 /// history then it is moved to the front. "histype" may be one of he HIST_
5844 /// values.
5845 ///
5846 /// @parma in_map consider maptick when inside a mapping
5847 /// @param sep separator character used (search hist)
add_to_history(int histype,char_u * new_entry,int in_map,int sep)5848 void add_to_history(int histype, char_u *new_entry, int in_map, int sep)
5849 {
5850 histentry_T *hisptr;
5851
5852 if (hislen == 0 || histype == HIST_INVALID) { // no history
5853 return;
5854 }
5855 assert(histype != HIST_DEFAULT);
5856
5857 if (cmdmod.keeppatterns && histype == HIST_SEARCH) {
5858 return;
5859 }
5860
5861 /*
5862 * Searches inside the same mapping overwrite each other, so that only
5863 * the last line is kept. Be careful not to remove a line that was moved
5864 * down, only lines that were added.
5865 */
5866 if (histype == HIST_SEARCH && in_map) {
5867 if (maptick == last_maptick && hisidx[HIST_SEARCH] >= 0) {
5868 // Current line is from the same mapping, remove it
5869 hisptr = &history[HIST_SEARCH][hisidx[HIST_SEARCH]];
5870 hist_free_entry(hisptr);
5871 --hisnum[histype];
5872 if (--hisidx[HIST_SEARCH] < 0) {
5873 hisidx[HIST_SEARCH] = hislen - 1;
5874 }
5875 }
5876 last_maptick = -1;
5877 }
5878 if (!in_history(histype, new_entry, true, sep)) {
5879 if (++hisidx[histype] == hislen) {
5880 hisidx[histype] = 0;
5881 }
5882 hisptr = &history[histype][hisidx[histype]];
5883 hist_free_entry(hisptr);
5884
5885 // Store the separator after the NUL of the string.
5886 size_t len = STRLEN(new_entry);
5887 hisptr->hisstr = vim_strnsave(new_entry, len + 2);
5888 hisptr->timestamp = os_time();
5889 hisptr->additional_elements = NULL;
5890 hisptr->hisstr[len + 1] = (char_u)sep;
5891
5892 hisptr->hisnum = ++hisnum[histype];
5893 if (histype == HIST_SEARCH && in_map) {
5894 last_maptick = maptick;
5895 }
5896 }
5897 }
5898
5899
5900 /*
5901 * Get identifier of newest history entry.
5902 * "histype" may be one of the HIST_ values.
5903 */
get_history_idx(int histype)5904 int get_history_idx(int histype)
5905 {
5906 if (hislen == 0 || histype < 0 || histype >= HIST_COUNT
5907 || hisidx[histype] < 0) {
5908 return -1;
5909 }
5910
5911 return history[histype][hisidx[histype]].hisnum;
5912 }
5913
5914
5915 /*
5916 * Get pointer to the command line info to use. cmdline_paste() may clear
5917 * ccline and put the previous value in prev_ccline.
5918 */
get_ccline_ptr(void)5919 static struct cmdline_info *get_ccline_ptr(void)
5920 {
5921 if ((State & CMDLINE) == 0) {
5922 return NULL;
5923 } else if (ccline.cmdbuff != NULL) {
5924 return &ccline;
5925 } else if (ccline.prev_ccline && ccline.prev_ccline->cmdbuff != NULL) {
5926 return ccline.prev_ccline;
5927 } else {
5928 return NULL;
5929 }
5930 }
5931
5932 /*
5933 * Get the current command line in allocated memory.
5934 * Only works when the command line is being edited.
5935 * Returns NULL when something is wrong.
5936 */
get_cmdline_str(void)5937 char_u *get_cmdline_str(void)
5938 {
5939 if (cmdline_star > 0) {
5940 return NULL;
5941 }
5942 struct cmdline_info *p = get_ccline_ptr();
5943
5944 if (p == NULL) {
5945 return NULL;
5946 }
5947 return vim_strnsave(p->cmdbuff, (size_t)p->cmdlen);
5948 }
5949
5950 /*
5951 * Get the current command line position, counted in bytes.
5952 * Zero is the first position.
5953 * Only works when the command line is being edited.
5954 * Returns -1 when something is wrong.
5955 */
get_cmdline_pos(void)5956 int get_cmdline_pos(void)
5957 {
5958 struct cmdline_info *p = get_ccline_ptr();
5959
5960 if (p == NULL) {
5961 return -1;
5962 }
5963 return p->cmdpos;
5964 }
5965
5966 /*
5967 * Set the command line byte position to "pos". Zero is the first position.
5968 * Only works when the command line is being edited.
5969 * Returns 1 when failed, 0 when OK.
5970 */
set_cmdline_pos(int pos)5971 int set_cmdline_pos(int pos)
5972 {
5973 struct cmdline_info *p = get_ccline_ptr();
5974
5975 if (p == NULL) {
5976 return 1;
5977 }
5978
5979 // The position is not set directly but after CTRL-\ e or CTRL-R = has
5980 // changed the command line.
5981 if (pos < 0) {
5982 new_cmdpos = 0;
5983 } else {
5984 new_cmdpos = pos;
5985 }
5986 return 0;
5987 }
5988
5989 /*
5990 * Get the current command-line type.
5991 * Returns ':' or '/' or '?' or '@' or '>' or '-'
5992 * Only works when the command line is being edited.
5993 * Returns NUL when something is wrong.
5994 */
get_cmdline_type(void)5995 int get_cmdline_type(void)
5996 {
5997 struct cmdline_info *p = get_ccline_ptr();
5998
5999 if (p == NULL) {
6000 return NUL;
6001 }
6002 if (p->cmdfirstc == NUL) {
6003 return (p->input_fn) ? '@' : '-';
6004 }
6005 return p->cmdfirstc;
6006 }
6007
6008 /*
6009 * Calculate history index from a number:
6010 * num > 0: seen as identifying number of a history entry
6011 * num < 0: relative position in history wrt newest entry
6012 * "histype" may be one of the HIST_ values.
6013 */
calc_hist_idx(int histype,int num)6014 static int calc_hist_idx(int histype, int num)
6015 {
6016 int i;
6017 histentry_T *hist;
6018 int wrapped = FALSE;
6019
6020 if (hislen == 0 || histype < 0 || histype >= HIST_COUNT
6021 || (i = hisidx[histype]) < 0 || num == 0) {
6022 return -1;
6023 }
6024
6025 hist = history[histype];
6026 if (num > 0) {
6027 while (hist[i].hisnum > num) {
6028 if (--i < 0) {
6029 if (wrapped) {
6030 break;
6031 }
6032 i += hislen;
6033 wrapped = TRUE;
6034 }
6035 }
6036 if (hist[i].hisnum == num && hist[i].hisstr != NULL) {
6037 return i;
6038 }
6039 } else if (-num <= hislen) {
6040 i += num + 1;
6041 if (i < 0) {
6042 i += hislen;
6043 }
6044 if (hist[i].hisstr != NULL) {
6045 return i;
6046 }
6047 }
6048 return -1;
6049 }
6050
6051 /*
6052 * Get a history entry by its index.
6053 * "histype" may be one of the HIST_ values.
6054 */
get_history_entry(int histype,int idx)6055 char_u *get_history_entry(int histype, int idx)
6056 {
6057 idx = calc_hist_idx(histype, idx);
6058 if (idx >= 0) {
6059 return history[histype][idx].hisstr;
6060 } else {
6061 return (char_u *)"";
6062 }
6063 }
6064
6065 /// Clear all entries in a history
6066 ///
6067 /// @param[in] histype One of the HIST_ values.
6068 ///
6069 /// @return OK if there was something to clean and histype was one of HIST_
6070 /// values, FAIL otherwise.
clr_history(const int histype)6071 int clr_history(const int histype)
6072 {
6073 if (hislen != 0 && histype >= 0 && histype < HIST_COUNT) {
6074 histentry_T *hisptr = history[histype];
6075 for (int i = hislen; i--; hisptr++) {
6076 hist_free_entry(hisptr);
6077 }
6078 hisidx[histype] = -1; // mark history as cleared
6079 hisnum[histype] = 0; // reset identifier counter
6080 return OK;
6081 }
6082 return FAIL;
6083 }
6084
6085 /*
6086 * Remove all entries matching {str} from a history.
6087 * "histype" may be one of the HIST_ values.
6088 */
del_history_entry(int histype,char_u * str)6089 int del_history_entry(int histype, char_u *str)
6090 {
6091 regmatch_T regmatch;
6092 histentry_T *hisptr;
6093 int idx;
6094 int i;
6095 int last;
6096 bool found = false;
6097
6098 regmatch.regprog = NULL;
6099 regmatch.rm_ic = FALSE; // always match case
6100 if (hislen != 0
6101 && histype >= 0
6102 && histype < HIST_COUNT
6103 && *str != NUL
6104 && (idx = hisidx[histype]) >= 0
6105 && (regmatch.regprog = vim_regcomp(str, RE_MAGIC + RE_STRING))
6106 != NULL) {
6107 i = last = idx;
6108 do {
6109 hisptr = &history[histype][i];
6110 if (hisptr->hisstr == NULL) {
6111 break;
6112 }
6113 if (vim_regexec(®match, hisptr->hisstr, (colnr_T)0)) {
6114 found = true;
6115 hist_free_entry(hisptr);
6116 } else {
6117 if (i != last) {
6118 history[histype][last] = *hisptr;
6119 clear_hist_entry(hisptr);
6120 }
6121 if (--last < 0) {
6122 last += hislen;
6123 }
6124 }
6125 if (--i < 0) {
6126 i += hislen;
6127 }
6128 } while (i != idx);
6129 if (history[histype][idx].hisstr == NULL) {
6130 hisidx[histype] = -1;
6131 }
6132 }
6133 vim_regfree(regmatch.regprog);
6134 return found;
6135 }
6136
6137 /*
6138 * Remove an indexed entry from a history.
6139 * "histype" may be one of the HIST_ values.
6140 */
del_history_idx(int histype,int idx)6141 int del_history_idx(int histype, int idx)
6142 {
6143 int i, j;
6144
6145 i = calc_hist_idx(histype, idx);
6146 if (i < 0) {
6147 return FALSE;
6148 }
6149 idx = hisidx[histype];
6150 hist_free_entry(&history[histype][i]);
6151
6152 /* When deleting the last added search string in a mapping, reset
6153 * last_maptick, so that the last added search string isn't deleted again.
6154 */
6155 if (histype == HIST_SEARCH && maptick == last_maptick && i == idx) {
6156 last_maptick = -1;
6157 }
6158
6159 while (i != idx) {
6160 j = (i + 1) % hislen;
6161 history[histype][i] = history[histype][j];
6162 i = j;
6163 }
6164 clear_hist_entry(&history[histype][idx]);
6165 if (--i < 0) {
6166 i += hislen;
6167 }
6168 hisidx[histype] = i;
6169 return TRUE;
6170 }
6171
6172 /// Get indices that specify a range within a list (not a range of text lines
6173 /// in a buffer!) from a string. Used for ":history" and ":clist".
6174 ///
6175 /// @param str string to parse range from
6176 /// @param num1 from
6177 /// @param num2 to
6178 ///
6179 /// @return OK if parsed successfully, otherwise FAIL.
get_list_range(char_u ** str,int * num1,int * num2)6180 int get_list_range(char_u **str, int *num1, int *num2)
6181 {
6182 int len;
6183 int first = false;
6184 varnumber_T num;
6185
6186 *str = skipwhite(*str);
6187 if (**str == '-' || ascii_isdigit(**str)) { // parse "from" part of range
6188 vim_str2nr(*str, NULL, &len, 0, &num, NULL, 0, false);
6189 *str += len;
6190 *num1 = (int)num;
6191 first = true;
6192 }
6193 *str = skipwhite(*str);
6194 if (**str == ',') { // parse "to" part of range
6195 *str = skipwhite(*str + 1);
6196 vim_str2nr(*str, NULL, &len, 0, &num, NULL, 0, false);
6197 if (len > 0) {
6198 *num2 = (int)num;
6199 *str = skipwhite(*str + len);
6200 } else if (!first) { // no number given at all
6201 return FAIL;
6202 }
6203 } else if (first) { // only one number given
6204 *num2 = *num1;
6205 }
6206 return OK;
6207 }
6208
6209 /*
6210 * :history command - print a history
6211 */
ex_history(exarg_T * eap)6212 void ex_history(exarg_T *eap)
6213 {
6214 histentry_T *hist;
6215 int histype1 = HIST_CMD;
6216 int histype2 = HIST_CMD;
6217 int hisidx1 = 1;
6218 int hisidx2 = -1;
6219 int idx;
6220 int i, j, k;
6221 char_u *end;
6222 char_u *arg = eap->arg;
6223
6224 if (hislen == 0) {
6225 msg(_("'history' option is zero"));
6226 return;
6227 }
6228
6229 if (!(ascii_isdigit(*arg) || *arg == '-' || *arg == ',')) {
6230 end = arg;
6231 while (ASCII_ISALPHA(*end)
6232 || vim_strchr((char_u *)":=@>/?", *end) != NULL) {
6233 end++;
6234 }
6235 histype1 = get_histtype((const char *)arg, (size_t)(end - arg), false);
6236 if (histype1 == HIST_INVALID) {
6237 if (STRNICMP(arg, "all", end - arg) == 0) {
6238 histype1 = 0;
6239 histype2 = HIST_COUNT-1;
6240 } else {
6241 emsg(_(e_trailing));
6242 return;
6243 }
6244 } else {
6245 histype2 = histype1;
6246 }
6247 } else {
6248 end = arg;
6249 }
6250 if (!get_list_range(&end, &hisidx1, &hisidx2) || *end != NUL) {
6251 emsg(_(e_trailing));
6252 return;
6253 }
6254
6255 for (; !got_int && histype1 <= histype2; ++histype1) {
6256 STRCPY(IObuff, "\n # ");
6257 assert(history_names[histype1] != NULL);
6258 STRCAT(STRCAT(IObuff, history_names[histype1]), " history");
6259 msg_puts_title((char *)IObuff);
6260 idx = hisidx[histype1];
6261 hist = history[histype1];
6262 j = hisidx1;
6263 k = hisidx2;
6264 if (j < 0) {
6265 j = (-j > hislen) ? 0 : hist[(hislen+j+idx+1) % hislen].hisnum;
6266 }
6267 if (k < 0) {
6268 k = (-k > hislen) ? 0 : hist[(hislen+k+idx+1) % hislen].hisnum;
6269 }
6270 if (idx >= 0 && j <= k) {
6271 for (i = idx + 1; !got_int; ++i) {
6272 if (i == hislen) {
6273 i = 0;
6274 }
6275 if (hist[i].hisstr != NULL
6276 && hist[i].hisnum >= j && hist[i].hisnum <= k) {
6277 msg_putchar('\n');
6278 snprintf((char *)IObuff, IOSIZE, "%c%6d ", i == idx ? '>' : ' ',
6279 hist[i].hisnum);
6280 if (vim_strsize(hist[i].hisstr) > Columns - 10) {
6281 trunc_string(hist[i].hisstr, IObuff + STRLEN(IObuff),
6282 Columns - 10, IOSIZE - (int)STRLEN(IObuff));
6283 } else {
6284 STRCAT(IObuff, hist[i].hisstr);
6285 }
6286 msg_outtrans(IObuff);
6287 ui_flush();
6288 }
6289 if (i == idx) {
6290 break;
6291 }
6292 }
6293 }
6294 }
6295 }
6296
6297 /// Translate a history type number to the associated character
hist_type2char(int type)6298 int hist_type2char(int type)
6299 FUNC_ATTR_CONST
6300 {
6301 switch (type) {
6302 case HIST_CMD:
6303 return ':';
6304 case HIST_SEARCH:
6305 return '/';
6306 case HIST_EXPR:
6307 return '=';
6308 case HIST_INPUT:
6309 return '@';
6310 case HIST_DEBUG:
6311 return '>';
6312 default:
6313 abort();
6314 }
6315 return NUL;
6316 }
6317
6318 /// Open a window on the current command line and history. Allow editing in
6319 /// the window. Returns when the window is closed.
6320 /// Returns:
6321 /// CR if the command is to be executed
6322 /// Ctrl_C if it is to be abandoned
6323 /// K_IGNORE if editing continues
open_cmdwin(void)6324 static int open_cmdwin(void)
6325 {
6326 struct cmdline_info save_ccline;
6327 bufref_T old_curbuf;
6328 bufref_T bufref;
6329 win_T *old_curwin = curwin;
6330 win_T *wp;
6331 int i;
6332 linenr_T lnum;
6333 garray_T winsizes;
6334 char_u typestr[2];
6335 int save_restart_edit = restart_edit;
6336 int save_State = State;
6337 bool save_exmode = exmode_active;
6338 int save_cmdmsg_rl = cmdmsg_rl;
6339
6340 // Can't do this recursively. Can't do it when typing a password.
6341 if (cmdwin_type != 0
6342 || cmdline_star > 0) {
6343 beep_flush();
6344 return K_IGNORE;
6345 }
6346
6347 set_bufref(&old_curbuf, curbuf);
6348
6349 // Save current window sizes.
6350 win_size_save(&winsizes);
6351
6352 // When using completion in Insert mode with <C-R>=<C-F> one can open the
6353 // command line window, but we don't want the popup menu then.
6354 pum_undisplay(true);
6355
6356 // don't use a new tab page
6357 cmdmod.tab = 0;
6358 cmdmod.noswapfile = 1;
6359
6360 // Create a window for the command-line buffer.
6361 if (win_split((int)p_cwh, WSP_BOT) == FAIL) {
6362 beep_flush();
6363 return K_IGNORE;
6364 }
6365 cmdwin_type = get_cmdline_type();
6366 cmdwin_level = ccline.level;
6367
6368 // Create empty command-line buffer.
6369 buf_open_scratch(0, _("[Command Line]"));
6370 // Command-line buffer has bufhidden=wipe, unlike a true "scratch" buffer.
6371 set_option_value("bh", 0L, "wipe", OPT_LOCAL);
6372 curwin->w_p_rl = cmdmsg_rl;
6373 cmdmsg_rl = false;
6374 curbuf->b_p_ma = true;
6375 curwin->w_p_fen = false;
6376
6377 // Don't allow switching to another buffer.
6378 curbuf->b_ro_locked++;
6379
6380 // Showing the prompt may have set need_wait_return, reset it.
6381 need_wait_return = false;
6382
6383 const int histtype = hist_char2type(cmdwin_type);
6384 if (histtype == HIST_CMD || histtype == HIST_DEBUG) {
6385 if (p_wc == TAB) {
6386 add_map((char_u *)"<buffer> <Tab> <C-X><C-V>", INSERT, false);
6387 add_map((char_u *)"<buffer> <Tab> a<C-X><C-V>", NORMAL, false);
6388 }
6389 set_option_value("ft", 0L, "vim", OPT_LOCAL);
6390 }
6391 curbuf->b_ro_locked--;
6392
6393 // Reset 'textwidth' after setting 'filetype' (the Vim filetype plugin
6394 // sets 'textwidth' to 78).
6395 curbuf->b_p_tw = 0;
6396
6397 // Fill the buffer with the history.
6398 init_history();
6399 if (hislen > 0 && histtype != HIST_INVALID) {
6400 i = hisidx[histtype];
6401 if (i >= 0) {
6402 lnum = 0;
6403 do {
6404 if (++i == hislen) {
6405 i = 0;
6406 }
6407 if (history[histtype][i].hisstr != NULL) {
6408 ml_append(lnum++, history[histtype][i].hisstr, (colnr_T)0, false);
6409 }
6410 } while (i != hisidx[histtype]);
6411 }
6412 }
6413
6414 // Replace the empty last line with the current command-line and put the
6415 // cursor there.
6416 ml_replace(curbuf->b_ml.ml_line_count, ccline.cmdbuff, true);
6417 curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
6418 curwin->w_cursor.col = ccline.cmdpos;
6419 changed_line_abv_curs();
6420 invalidate_botline();
6421 if (ui_has(kUICmdline)) {
6422 ccline.redraw_state = kCmdRedrawNone;
6423 ui_call_cmdline_hide(ccline.level);
6424 }
6425 redraw_later(curwin, SOME_VALID);
6426
6427 // Save the command line info, can be used recursively.
6428 save_cmdline(&save_ccline);
6429
6430 // No Ex mode here!
6431 exmode_active = false;
6432
6433 State = NORMAL;
6434 setmouse();
6435
6436 // Reset here so it can be set by a CmdWinEnter autocommand.
6437 cmdwin_result = 0;
6438
6439 // Trigger CmdwinEnter autocommands.
6440 typestr[0] = (char_u)cmdwin_type;
6441 typestr[1] = NUL;
6442 apply_autocmds(EVENT_CMDWINENTER, typestr, typestr, false, curbuf);
6443 if (restart_edit != 0) { // autocmd with ":startinsert"
6444 stuffcharReadbuff(K_NOP);
6445 }
6446
6447 i = RedrawingDisabled;
6448 RedrawingDisabled = 0;
6449 int save_count = save_batch_count();
6450
6451 /*
6452 * Call the main loop until <CR> or CTRL-C is typed.
6453 */
6454 normal_enter(true, false);
6455
6456 RedrawingDisabled = i;
6457 restore_batch_count(save_count);
6458
6459 const bool save_KeyTyped = KeyTyped;
6460
6461 // Trigger CmdwinLeave autocommands.
6462 apply_autocmds(EVENT_CMDWINLEAVE, typestr, typestr, false, curbuf);
6463
6464 // Restore KeyTyped in case it is modified by autocommands
6465 KeyTyped = save_KeyTyped;
6466
6467 // Restore the command line info.
6468 restore_cmdline(&save_ccline);
6469 cmdwin_type = 0;
6470 cmdwin_level = 0;
6471
6472 exmode_active = save_exmode;
6473
6474 // Safety check: The old window or buffer was deleted: It's a bug when
6475 // this happens!
6476 if (!win_valid(old_curwin) || !bufref_valid(&old_curbuf)) {
6477 cmdwin_result = Ctrl_C;
6478 emsg(_("E199: Active window or buffer deleted"));
6479 } else {
6480 // autocmds may abort script processing
6481 if (aborting() && cmdwin_result != K_IGNORE) {
6482 cmdwin_result = Ctrl_C;
6483 }
6484 // Set the new command line from the cmdline buffer.
6485 xfree(ccline.cmdbuff);
6486 if (cmdwin_result == K_XF1 || cmdwin_result == K_XF2) { // :qa[!] typed
6487 const char *p = (cmdwin_result == K_XF2) ? "qa" : "qa!";
6488
6489 if (histtype == HIST_CMD) {
6490 // Execute the command directly.
6491 ccline.cmdbuff = (char_u *)xstrdup(p);
6492 cmdwin_result = CAR;
6493 } else {
6494 // First need to cancel what we were doing.
6495 ccline.cmdbuff = NULL;
6496 stuffcharReadbuff(':');
6497 stuffReadbuff(p);
6498 stuffcharReadbuff(CAR);
6499 }
6500 } else if (cmdwin_result == Ctrl_C) {
6501 // :q or :close, don't execute any command
6502 // and don't modify the cmd window.
6503 ccline.cmdbuff = NULL;
6504 } else {
6505 ccline.cmdbuff = vim_strsave(get_cursor_line_ptr());
6506 }
6507 if (ccline.cmdbuff == NULL) {
6508 ccline.cmdbuff = vim_strsave((char_u *)"");
6509 ccline.cmdlen = 0;
6510 ccline.cmdbufflen = 1;
6511 ccline.cmdpos = 0;
6512 cmdwin_result = Ctrl_C;
6513 } else {
6514 ccline.cmdlen = (int)STRLEN(ccline.cmdbuff);
6515 ccline.cmdbufflen = ccline.cmdlen + 1;
6516 ccline.cmdpos = curwin->w_cursor.col;
6517 if (ccline.cmdpos > ccline.cmdlen) {
6518 ccline.cmdpos = ccline.cmdlen;
6519 }
6520 if (cmdwin_result == K_IGNORE) {
6521 ccline.cmdspos = cmd_screencol(ccline.cmdpos);
6522 redrawcmd();
6523 }
6524 }
6525
6526 // Avoid command-line window first character being concealed.
6527 curwin->w_p_cole = 0;
6528 wp = curwin;
6529 set_bufref(&bufref, curbuf);
6530 win_goto(old_curwin);
6531 if (win_valid(wp) && wp != curwin) {
6532 win_close(wp, true);
6533 }
6534
6535 // win_close() may have already wiped the buffer when 'bh' is
6536 // set to 'wipe', autocommands may have closed other windows
6537 if (bufref_valid(&bufref) && bufref.br_buf != curbuf) {
6538 close_buffer(NULL, bufref.br_buf, DOBUF_WIPE, false);
6539 }
6540
6541 // Restore window sizes.
6542 win_size_restore(&winsizes);
6543 }
6544
6545 ga_clear(&winsizes);
6546 restart_edit = save_restart_edit;
6547 cmdmsg_rl = save_cmdmsg_rl;
6548
6549 State = save_State;
6550 setmouse();
6551
6552 return cmdwin_result;
6553 }
6554
6555 /// Get script string
6556 ///
6557 /// Used for commands which accept either `:command script` or
6558 ///
6559 /// :command << endmarker
6560 /// script
6561 /// endmarker
6562 ///
6563 /// @param eap Command being run.
6564 /// @param[out] lenp Location where length of resulting string is saved. Will
6565 /// be set to zero when skipping.
6566 ///
6567 /// @return [allocated] NULL or script. Does not show any error messages.
6568 /// NULL is returned when skipping and on error.
script_get(exarg_T * const eap,size_t * const lenp)6569 char *script_get(exarg_T *const eap, size_t *const lenp)
6570 FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_MALLOC
6571 {
6572 const char *const cmd = (const char *)eap->arg;
6573
6574 if (cmd[0] != '<' || cmd[1] != '<' || eap->getline == NULL) {
6575 *lenp = STRLEN(eap->arg);
6576 return eap->skip ? NULL : xmemdupz(eap->arg, *lenp);
6577 }
6578
6579 garray_T ga = { .ga_data = NULL, .ga_len = 0 };
6580 if (!eap->skip) {
6581 ga_init(&ga, 1, 0x400);
6582 }
6583
6584 const char *const end_pattern = (
6585 cmd[2] != NUL
6586 ? (const char *)skipwhite((const char_u *)cmd + 2)
6587 : ".");
6588 for (;;) {
6589 char *const theline = (char *)eap->getline(eap->cstack->cs_looplevel > 0 ? -1 :
6590 NUL, eap->cookie, 0, true);
6591
6592 if (theline == NULL || strcmp(end_pattern, theline) == 0) {
6593 xfree(theline);
6594 break;
6595 }
6596
6597 if (!eap->skip) {
6598 ga_concat(&ga, theline);
6599 ga_append(&ga, '\n');
6600 }
6601 xfree(theline);
6602 }
6603 *lenp = (size_t)ga.ga_len; // Set length without trailing NUL.
6604 if (!eap->skip) {
6605 ga_append(&ga, NUL);
6606 }
6607
6608 return (char *)ga.ga_data;
6609 }
6610
6611 /// Iterate over history items
6612 ///
6613 /// @warning No history-editing functions must be run while iteration is in
6614 /// progress.
6615 ///
6616 /// @param[in] iter Pointer to the last history entry.
6617 /// @param[in] history_type Type of the history (HIST_*). Ignored if iter
6618 /// parameter is not NULL.
6619 /// @param[in] zero If true then zero (but not free) returned items.
6620 ///
6621 /// @warning When using this parameter user is
6622 /// responsible for calling clr_history()
6623 /// itself after iteration is over. If
6624 /// clr_history() is not called behaviour is
6625 /// undefined. No functions that work with
6626 /// history must be called during iteration
6627 /// in this case.
6628 /// @param[out] hist Next history entry.
6629 ///
6630 /// @return Pointer used in next iteration or NULL to indicate that iteration
6631 /// was finished.
hist_iter(const void * const iter,const uint8_t history_type,const bool zero,histentry_T * const hist)6632 const void *hist_iter(const void *const iter, const uint8_t history_type, const bool zero,
6633 histentry_T *const hist)
6634 FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(4)
6635 {
6636 *hist = (histentry_T) {
6637 .hisstr = NULL
6638 };
6639 if (hisidx[history_type] == -1) {
6640 return NULL;
6641 }
6642 histentry_T *const hstart = &(history[history_type][0]);
6643 histentry_T *const hlast = (
6644 &(history[history_type][hisidx[history_type]]));
6645 const histentry_T *const hend = &(history[history_type][hislen - 1]);
6646 histentry_T *hiter;
6647 if (iter == NULL) {
6648 histentry_T *hfirst = hlast;
6649 do {
6650 hfirst++;
6651 if (hfirst > hend) {
6652 hfirst = hstart;
6653 }
6654 if (hfirst->hisstr != NULL) {
6655 break;
6656 }
6657 } while (hfirst != hlast);
6658 hiter = hfirst;
6659 } else {
6660 hiter = (histentry_T *)iter;
6661 }
6662 if (hiter == NULL) {
6663 return NULL;
6664 }
6665 *hist = *hiter;
6666 if (zero) {
6667 memset(hiter, 0, sizeof(*hiter));
6668 }
6669 if (hiter == hlast) {
6670 return NULL;
6671 }
6672 hiter++;
6673 return (const void *)((hiter > hend) ? hstart : hiter);
6674 }
6675
6676 /// Get array of history items
6677 ///
6678 /// @param[in] history_type Type of the history to get array for.
6679 /// @param[out] new_hisidx Location where last index in the new array should
6680 /// be saved.
6681 /// @param[out] new_hisnum Location where last history number in the new
6682 /// history should be saved.
6683 ///
6684 /// @return Pointer to the array or NULL.
hist_get_array(const uint8_t history_type,int ** const new_hisidx,int ** const new_hisnum)6685 histentry_T *hist_get_array(const uint8_t history_type, int **const new_hisidx,
6686 int **const new_hisnum)
6687 FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
6688 {
6689 init_history();
6690 *new_hisidx = &(hisidx[history_type]);
6691 *new_hisnum = &(hisnum[history_type]);
6692 return history[history_type];
6693 }
6694
set_search_match(pos_T * t)6695 static void set_search_match(pos_T *t)
6696 {
6697 // First move cursor to end of match, then to the start. This
6698 // moves the whole match onto the screen when 'nowrap' is set.
6699 t->lnum += search_match_lines;
6700 t->col = search_match_endcol;
6701 if (t->lnum > curbuf->b_ml.ml_line_count) {
6702 t->lnum = curbuf->b_ml.ml_line_count;
6703 coladvance(MAXCOL);
6704 }
6705 }
6706