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, &regmatch, num_file, file);
5017   } else if (xp->xp_context == EXPAND_MAPPINGS) {
5018     ret = ExpandMappings(&regmatch, num_file, file);
5019   } else if (xp->xp_context == EXPAND_USER_DEFINED) {
5020     ret = ExpandUserDefined(xp, &regmatch, 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, &regmatch, 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(&regmatch, 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