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