diff options
Diffstat (limited to 'src/nvim/ex_getln.c')
-rw-r--r-- | src/nvim/ex_getln.c | 2323 |
1 files changed, 1659 insertions, 664 deletions
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index cd28554970..8efb027575 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -1,3 +1,6 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + /* * ex_getln.c: Functions for entering and editing an Ex command line. */ @@ -8,6 +11,8 @@ #include <stdlib.h> #include <inttypes.h> +#include "nvim/assert.h" +#include "nvim/log.h" #include "nvim/vim.h" #include "nvim/ascii.h" #include "nvim/arabic.h" @@ -26,15 +31,16 @@ #include "nvim/fileio.h" #include "nvim/func_attr.h" #include "nvim/getchar.h" +#include "nvim/highlight.h" #include "nvim/if_cscope.h" #include "nvim/indent.h" #include "nvim/main.h" +#include "nvim/mark.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/menu.h" #include "nvim/message.h" #include "nvim/misc1.h" -#include "nvim/misc2.h" #include "nvim/memory.h" #include "nvim/cursor_shape.h" #include "nvim/keymap.h" @@ -58,6 +64,42 @@ #include "nvim/os/os.h" #include "nvim/event/loop.h" #include "nvim/os/time.h" +#include "nvim/lib/kvec.h" +#include "nvim/api/private/helpers.h" +#include "nvim/highlight_defs.h" +#include "nvim/viml/parser/parser.h" +#include "nvim/viml/parser/expressions.h" + +/// Command-line colors: one chunk +/// +/// Defines a region which has the same highlighting. +typedef struct { + int start; ///< Colored chunk start. + int end; ///< Colored chunk end (exclusive, > start). + int attr; ///< Highlight attr. +} CmdlineColorChunk; + +/// Command-line colors +/// +/// Holds data about all colors. +typedef kvec_t(CmdlineColorChunk) CmdlineColors; + +/// Command-line coloring +/// +/// Holds both what are the colors and what have been colored. Latter is used to +/// suppress unnecessary calls to coloring callbacks. +typedef struct { + unsigned prompt_id; ///< ID of the prompt which was colored last. + char *cmdbuff; ///< What exactly was colored last time or NULL. + CmdlineColors colors; ///< Last colors. +} ColoredCmdline; + +/// Keeps track how much state must be sent to external ui. +typedef enum { + kCmdRedrawNone, + kCmdRedrawPos, + kCmdRedrawAll, +} CmdRedraw; /* * Variables shared between getcmdline(), redrawcmdline() and others. @@ -65,23 +107,33 @@ * structure. */ struct cmdline_info { - char_u *cmdbuff; /* pointer to command line buffer */ - int cmdbufflen; /* length of cmdbuff */ - int cmdlen; /* number of chars in command line */ - int cmdpos; /* current cursor position */ - int cmdspos; /* cursor column on screen */ - int cmdfirstc; /* ':', '/', '?', '=', '>' or NUL */ - int cmdindent; /* number of spaces before cmdline */ - char_u *cmdprompt; /* message in front of cmdline */ - int cmdattr; /* attributes for prompt */ - int overstrike; /* Typing mode on the command line. Shared by - getcmdline() and put_on_cmdline(). */ - expand_T *xpc; /* struct being used for expansion, xp_pattern - may point into cmdbuff */ - int xp_context; /* type of expansion */ - char_u *xp_arg; /* user-defined expansion arg */ - int input_fn; /* when TRUE Invoked for input() function */ + char_u *cmdbuff; // pointer to command line buffer + int cmdbufflen; // length of cmdbuff + int cmdlen; // number of chars in command line + int cmdpos; // current cursor position + int cmdspos; // cursor column on screen + int cmdfirstc; // ':', '/', '?', '=', '>' or NUL + int cmdindent; // number of spaces before cmdline + char_u *cmdprompt; // message in front of cmdline + int cmdattr; // attributes for prompt + int overstrike; // Typing mode on the command line. Shared by + // getcmdline() and put_on_cmdline(). + expand_T *xpc; // struct being used for expansion, xp_pattern + // may point into cmdbuff + int xp_context; // type of expansion + char_u *xp_arg; // user-defined expansion arg + int input_fn; // when TRUE Invoked for input() function + unsigned prompt_id; ///< Prompt number, used to disable coloring on errors. + Callback highlight_callback; ///< Callback used for coloring user input. + ColoredCmdline last_colors; ///< Last cmdline colors + int level; // current cmdline level + struct cmdline_info *prev_ccline; ///< pointer to saved cmdline state + char special_char; ///< last putcmdline char (used for redraws) + bool special_shift; ///< shift of last putcmdline char + CmdRedraw redraw_state; ///< needed redraw for external cmdline }; +/// Last value of prompt_id, incremented when doing new prompt +static unsigned last_prompt_id = 0; typedef struct command_line_state { VimState state; @@ -96,19 +148,28 @@ typedef struct command_line_state { char_u *lookfor; // string to match int hiscnt; // current history line in use int histype; // history type to be used - pos_T old_cursor; - colnr_T old_curswant; - colnr_T old_leftcol; - linenr_T old_topline; - int old_topfill; - linenr_T old_botline; + pos_T search_start; // where 'incsearch' starts searching + pos_T save_cursor; + colnr_T old_curswant; + colnr_T init_curswant; + colnr_T old_leftcol; + colnr_T init_leftcol; + linenr_T old_topline; + linenr_T init_topline; + int old_topfill; + int init_topfill; + linenr_T old_botline; + linenr_T init_botline; + pos_T match_start; + pos_T match_end; int did_incsearch; int incsearch_postponed; int did_wild_list; // did wild_list() recently int wim_index; // index in wim_flags[] int res; - int save_msg_scroll; - int save_State; // remember State when called + int save_msg_scroll; + int save_State; // remember State when called + char_u *save_p_icm; int some_key_typed; // one of the keys was typed // mouse drag and release events are ignored, unless they are // preceded with a mouse down event @@ -116,12 +177,10 @@ typedef struct command_line_state { int break_ctrl_c; expand_T xpc; long *b_im_ptr; - // Everything that may work recursively should save and restore the - // current command line in save_ccline. That includes update_screen(), a - // custom status line may invoke ":normal". - struct cmdline_info save_ccline; } CommandLineState; +typedef struct cmdline_info CmdlineInfo; + /* The current cmdline_info. It is initialized in getcmdline() and after that * used by other functions. When invoking getcmdline() recursively it needs * to be saved with save_cmdline() and restored with restore_cmdline(). @@ -132,10 +191,16 @@ static int cmd_showtail; /* Only show path tail in lists ? */ static int new_cmdpos; /* position set by set_cmdline_pos() */ +/// currently displayed block of context +static Array cmdline_block = ARRAY_DICT_INIT; + /* * Type used by call_user_expand_func */ -typedef void *(*user_expand_func_T)(char_u *, int, char_u **, int); +typedef void *(*user_expand_func_T)(const char_u *, + int, + const char_u * const *, + bool); static histentry_T *(history[HIST_COUNT]) = {NULL, NULL, NULL, NULL, NULL}; static int hisidx[HIST_COUNT] = {-1, -1, -1, -1, -1}; /* lastused entry */ @@ -143,16 +208,30 @@ static int hisnum[HIST_COUNT] = {0, 0, 0, 0, 0}; /* identifying (unique) number of newest history entry */ static int hislen = 0; /* actual length of history tables */ +/// Flag for command_line_handle_key to ignore <C-c> +/// +/// Used if it was received while processing highlight function in order for +/// user interrupting highlight function to not interrupt command-line. +static bool getln_interrupted_highlight = false; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ex_getln.c.generated.h" #endif -static int cmd_hkmap = 0; /* Hebrew mapping during command line */ +static int cmd_hkmap = 0; // Hebrew mapping during command line +static int cmd_fkmap = 0; // Farsi mapping during command line -static int cmd_fkmap = 0; /* Farsi mapping during command line */ +/// Internal entry point for cmdline mode. +/// +/// caller must use save_cmdline and restore_cmdline. Best is to use +/// getcmdline or getcmdline_prompt, instead of calling this directly. static uint8_t *command_line_enter(int firstc, long count, int indent) { + // can be invoked recursively, identify each level + static int cmdline_level = 0; + cmdline_level++; + CommandLineState state, *s = &state; memset(s, 0, sizeof(CommandLineState)); s->firstc = firstc; @@ -160,7 +239,14 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) s->indent = indent; s->save_msg_scroll = msg_scroll; s->save_State = State; + s->save_p_icm = vim_strsave(p_icm); s->ignore_drag_release = true; + s->match_start = curwin->w_cursor; + s->init_curswant = curwin->w_curswant; + s->init_leftcol = curwin->w_leftcol; + s->init_topline = curwin->w_topline; + s->init_topfill = curwin->w_topfill; + s->init_botline = curwin->w_botline; if (s->firstc == -1) { s->firstc = NUL; @@ -172,8 +258,12 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) cmd_hkmap = 0; } + ccline.prompt_id = last_prompt_id++; + ccline.level = cmdline_level; ccline.overstrike = false; // always start in insert mode - s->old_cursor = curwin->w_cursor; // needs to be restored later + clearpos(&s->match_end); + s->save_cursor = curwin->w_cursor; // may be restored later + s->search_start = curwin->w_cursor; s->old_curswant = curwin->w_curswant; s->old_leftcol = curwin->w_leftcol; s->old_topline = curwin->w_topline; @@ -189,6 +279,10 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) ccline.cmdlen = ccline.cmdpos = 0; ccline.cmdbuff[0] = NUL; + ccline.last_colors = (ColoredCmdline){ .cmdbuff = NULL, + .colors = KV_INITIAL_VALUE }; + sb_text_start_cmdline(); + // autoindent for :insert and :append if (s->firstc <= 0) { memset(ccline.cmdbuff, ' ', s->indent); @@ -210,10 +304,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) redir_off = true; // don't redirect the typed command if (!cmd_silent) { - s->i = msg_scrolled; - msg_scrolled = 0; // avoid wait_return message gotocmdline(true); - msg_scrolled += s->i; redrawcmdprompt(); // draw prompt or indent set_cmdspos(); } @@ -262,12 +353,68 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) redrawcmd(); } + // redraw the statusline for statuslines that display the current mode + // using the mode() function. + if (KeyTyped && msg_scrolled == 0) { + curwin->w_redr_status = true; + redraw_statuslines(); + } + did_emsg = false; got_int = false; s->state.check = command_line_check; s->state.execute = command_line_execute; + + TryState tstate; + Error err = ERROR_INIT; + bool tl_ret = true; + dict_T *dict = get_vim_var_dict(VV_EVENT); + char firstcbuf[2]; + firstcbuf[0] = firstc > 0 ? firstc : '-'; + firstcbuf[1] = 0; + + if (has_event(EVENT_CMDLINEENTER)) { + // set v:event to a dictionary with information about the commandline + tv_dict_add_str(dict, S_LEN("cmdtype"), firstcbuf); + tv_dict_add_nr(dict, S_LEN("cmdlevel"), ccline.level); + tv_dict_set_keys_readonly(dict); + try_enter(&tstate); + + apply_autocmds(EVENT_CMDLINEENTER, (char_u *)firstcbuf, (char_u *)firstcbuf, + false, curbuf); + tv_dict_clear(dict); + + + tl_ret = try_leave(&tstate, &err); + if (!tl_ret && ERROR_SET(&err)) { + msg_putchar('\n'); + msg_printf_attr(HL_ATTR(HLF_E)|MSG_HIST, (char *)e_autocmd_err, err.msg); + api_clear_error(&err); + redrawcmd(); + } + tl_ret = true; + } + state_enter(&s->state); + if (has_event(EVENT_CMDLINELEAVE)) { + tv_dict_add_str(dict, S_LEN("cmdtype"), firstcbuf); + tv_dict_add_nr(dict, S_LEN("cmdlevel"), ccline.level); + tv_dict_set_keys_readonly(dict); + // not readonly: + tv_dict_add_special(dict, S_LEN("abort"), + s->gotesc ? kSpecialVarTrue : kSpecialVarFalse); + try_enter(&tstate); + apply_autocmds(EVENT_CMDLINELEAVE, (char_u *)firstcbuf, (char_u *)firstcbuf, + false, curbuf); + // error printed below, to avoid redraw issues + tl_ret = try_leave(&tstate, &err); + if (tv_dict_get_number(dict, "abort") != 0) { + s->gotesc = 1; + } + tv_dict_clear(dict); + } + cmdmsg_rl = false; cmd_fkmap = 0; @@ -276,7 +423,16 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) ccline.xpc = NULL; if (s->did_incsearch) { - curwin->w_cursor = s->old_cursor; + if (s->gotesc) { + curwin->w_cursor = s->save_cursor; + } else { + if (!equalpos(s->save_cursor, s->search_start)) { + // put the '" mark at the original position + curwin->w_cursor = s->save_cursor; + setpcmark(); + } + curwin->w_cursor = s->search_start; // -V519 + } curwin->w_curswant = s->old_curswant; curwin->w_leftcol = s->old_leftcol; curwin->w_topline = s->old_topline; @@ -284,7 +440,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) curwin->w_botline = s->old_botline; highlight_match = false; validate_cursor(); // needed for TAB - redraw_later(SOME_VALID); + redraw_all_later(SOME_VALID); } if (ccline.cmdbuff != NULL) { @@ -301,14 +457,8 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) } } - if (s->gotesc) { // abandon command line - xfree(ccline.cmdbuff); - ccline.cmdbuff = NULL; - if (msg_scrolled == 0) { - compute_cmdrow(); - } - MSG(""); - redraw_cmdline = true; + if (s->gotesc) { + abandon_cmdline(); } } @@ -319,22 +469,36 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) msg_scroll = s->save_msg_scroll; redir_off = false; + if (!tl_ret && ERROR_SET(&err)) { + msg_putchar('\n'); + msg_printf_attr(HL_ATTR(HLF_E)|MSG_HIST, (char *)e_autocmd_err, err.msg); + api_clear_error(&err); + } + // When the command line was typed, no need for a wait-return prompt. - if (s->some_key_typed) { + if (s->some_key_typed && tl_ret) { need_wait_return = false; } + set_string_option_direct((char_u *)"icm", -1, s->save_p_icm, OPT_FREE, + SID_NONE); State = s->save_State; setmouse(); ui_cursor_shape(); // may show different cursor shape + xfree(s->save_p_icm); + xfree(ccline.last_colors.cmdbuff); + kv_destroy(ccline.last_colors.colors); - { - char_u *p = ccline.cmdbuff; + sb_text_end_cmdline(); + + char_u *p = ccline.cmdbuff; - // Make ccline empty, getcmdline() may try to use it. - ccline.cmdbuff = NULL; - return p; + if (ui_is_external(kUICmdline)) { + ui_call_cmdline_hide(ccline.level); } + + cmdline_level--; + return p; } static int command_line_check(VimState *state) @@ -344,7 +508,12 @@ static int command_line_check(VimState *state) // completion may switch it on. quit_more = false; // reset after CTRL-D which had a more-prompt + did_emsg = false; // There can't really be a reason why an error + // that occurs while typing a command should + // cause the command not to be executed. + cursorcmd(); // set the cursor on the right spot + ui_cursor_shape(); return 1; } @@ -357,8 +526,12 @@ static int command_line_execute(VimState *state, int key) CommandLineState *s = (CommandLineState *)state; s->c = key; - if (s->c == K_EVENT) { - queue_process_events(main_loop.events); + if (s->c == K_EVENT || s->c == K_COMMAND) { + if (s->c == K_EVENT) { + multiqueue_process_events(main_loop.events); + } else { + do_cmdline(NULL, getcmdkeycmd, NULL, DOCMD_NOWAIT); + } redrawcmdline(); return 1; } @@ -437,18 +610,22 @@ static int command_line_execute(VimState *state, int key) } // free expanded names when finished walking through matches - if (s->xpc.xp_numfiles != -1 - && !(s->c == p_wc && KeyTyped) && s->c != p_wcm + if (!(s->c == p_wc && KeyTyped) && s->c != p_wcm && s->c != Ctrl_N && s->c != Ctrl_P && s->c != Ctrl_A && s->c != Ctrl_L) { - (void)ExpandOne(&s->xpc, NULL, NULL, 0, WILD_FREE); + if (ui_is_external(kUIWildmenu)) { + ui_call_wildmenu_hide(); + } + if (s->xpc.xp_numfiles != -1) { + (void)ExpandOne(&s->xpc, NULL, NULL, 0, WILD_FREE); + } s->did_wild_list = false; if (!p_wmnu || (s->c != K_UP && s->c != K_DOWN)) { s->xpc.xp_context = EXPAND_NOTHING; } s->wim_index = 0; if (p_wmnu && wild_menu_showing != 0) { - int skt = KeyTyped; + const bool skt = KeyTyped; int old_RedrawingDisabled = RedrawingDisabled; if (ccline.input_fn) { @@ -459,22 +636,22 @@ static int command_line_execute(VimState *state, int key) // Entered command line, move it up cmdline_row--; redrawcmd(); + wild_menu_showing = 0; } else if (save_p_ls != -1) { // restore 'laststatus' and 'winminheight' p_ls = save_p_ls; p_wmh = save_p_wmh; last_status(false); - save_cmdline(&s->save_ccline); update_screen(VALID); // redraw the screen NOW - restore_cmdline(&s->save_ccline); redrawcmd(); save_p_ls = -1; + wild_menu_showing = 0; } else { win_redraw_last_status(topframe); + wild_menu_showing = 0; // must be before redraw_statuslines #8385 redraw_statuslines(); } KeyTyped = skt; - wild_menu_showing = 0; if (ccline.input_fn) { RedrawingDisabled = old_RedrawingDisabled; } @@ -547,9 +724,7 @@ static int command_line_execute(VimState *state, int key) s->j = ccline.cmdpos; s->i = (int)(s->xpc.xp_pattern - ccline.cmdbuff); while (--s->j > s->i) { - if (has_mbyte) { - s->j -= (*mb_head_off)(ccline.cmdbuff, ccline.cmdbuff + s->j); - } + s->j -= utf_head_off(ccline.cmdbuff, ccline.cmdbuff + s->j); if (vim_ispathsep(ccline.cmdbuff[s->j])) { found = true; break; @@ -569,12 +744,10 @@ static int command_line_execute(VimState *state, int key) s->j = ccline.cmdpos - 1; s->i = (int)(s->xpc.xp_pattern - ccline.cmdbuff); while (--s->j > s->i) { - if (has_mbyte) { - s->j -= (*mb_head_off)(ccline.cmdbuff, ccline.cmdbuff + s->j); - } + s->j -= utf_head_off(ccline.cmdbuff, ccline.cmdbuff + s->j); if (vim_ispathsep(ccline.cmdbuff[s->j]) #ifdef BACKSLASH_IN_FILENAME - && vim_strchr(" *?[{`$%#", ccline.cmdbuff[s->j + 1]) + && vim_strchr((const char_u *)" *?[{`$%#", ccline.cmdbuff[s->j + 1]) == NULL #endif ) { @@ -617,16 +790,16 @@ static int command_line_execute(VimState *state, int key) // CTRL-\ CTRL-N goes to Normal mode, CTRL-\ CTRL-G goes to Insert // mode when 'insertmode' is set, CTRL-\ e prompts for an expression. if (s->c == Ctrl_BSL) { - ++no_mapping; - ++allow_keys; + no_mapping++; s->c = plain_vgetc(); - --no_mapping; - --allow_keys; + no_mapping--; // CTRL-\ e doesn't work when obtaining an expression, unless it // is in a mapping. - if (s->c != Ctrl_N && s->c != Ctrl_G && (s->c != 'e' - || (ccline.cmdfirstc == '=' - && KeyTyped))) { + if (s->c != Ctrl_N + && s->c != Ctrl_G + && (s->c != 'e' + || (ccline.cmdfirstc == '=' && KeyTyped) + || cmdline_star > 0)) { vungetc(s->c); s->c = Ctrl_BSL; } else if (s->c == 'e') { @@ -642,18 +815,17 @@ static int command_line_execute(VimState *state, int key) new_cmdpos = ccline.cmdpos; } - save_cmdline(&s->save_ccline); s->c = get_expr_register(); - restore_cmdline(&s->save_ccline); if (s->c == '=') { // Need to save and restore ccline. And set "textlock" // to avoid nasty things like going to another buffer when // evaluating an expression. - save_cmdline(&s->save_ccline); - ++textlock; + CmdlineInfo save_ccline; + save_cmdline(&save_ccline); + textlock++; p = get_expr_line(); - --textlock; - restore_cmdline(&s->save_ccline); + textlock--; + restore_cmdline(&save_ccline); if (p != NULL) { len = (int)STRLEN(p); @@ -694,7 +866,7 @@ static int command_line_execute(VimState *state, int key) if (s->c == cedit_key || s->c == K_CMDWIN) { if (ex_normal_busy == 0 && got_int == false) { // Open a window to edit the command line (and history). - s->c = ex_window(); + s->c = open_cmdwin(); s->some_key_typed = true; } } else { @@ -723,7 +895,9 @@ static int command_line_execute(VimState *state, int key) } if (!cmd_silent) { - ui_cursor_goto(msg_row, 0); + if (!ui_is_external(kUICmdline)) { + ui_cursor_goto(msg_row, 0); + } ui_flush(); } return 0; @@ -849,6 +1023,144 @@ static int command_line_execute(VimState *state, int key) return command_line_handle_key(s); } +static void command_line_next_incsearch(CommandLineState *s, bool next_match) +{ + ui_busy_start(); + ui_flush(); + + pos_T t; + char_u *pat; + int search_flags = SEARCH_NOOF; + + + if (s->firstc == ccline.cmdbuff[0]) { + pat = last_search_pattern(); + } else { + pat = ccline.cmdbuff; + } + + save_last_search_pattern(); + + if (next_match) { + t = s->match_end; + if (lt(s->match_start, s->match_end)) { + // start searching at the end of the match + // not at the beginning of the next column + (void)decl(&t); + } + search_flags += SEARCH_COL; + } else { + t = s->match_start; + } + if (!p_hls) { + search_flags += SEARCH_KEEP; + } + emsg_off++; + s->i = searchit(curwin, curbuf, &t, + next_match ? FORWARD : BACKWARD, + pat, s->count, search_flags, + RE_SEARCH, 0, NULL); + emsg_off--; + ui_busy_stop(); + if (s->i) { + s->search_start = s->match_start; + s->match_end = t; + s->match_start = t; + if (!next_match && s->firstc == '/') { + // move just before the current match, so that + // when nv_search finishes the cursor will be + // put back on the match + s->search_start = t; + (void)decl(&s->search_start); + } else if (next_match && s->firstc == '?') { + // move just after the current match, so that + // when nv_search finishes the cursor will be + // put back on the match + s->search_start = t; + (void)incl(&s->search_start); + } + if (lt(t, s->search_start) && next_match) { + // wrap around + s->search_start = t; + if (s->firstc == '?') { + (void)incl(&s->search_start); + } else { + (void)decl(&s->search_start); + } + } + + set_search_match(&s->match_end); + curwin->w_cursor = s->match_start; + changed_cline_bef_curs(); + update_topline(); + validate_cursor(); + highlight_match = true; + s->old_curswant = curwin->w_curswant; + s->old_leftcol = curwin->w_leftcol; + s->old_topline = curwin->w_topline; + s->old_topfill = curwin->w_topfill; + s->old_botline = curwin->w_botline; + update_screen(NOT_VALID); + redrawcmdline(); + } else { + vim_beep(BO_ERROR); + } + restore_last_search_pattern(); + return; +} + +static void command_line_next_histidx(CommandLineState *s, bool next_match) +{ + s->j = (int)STRLEN(s->lookfor); + for (;; ) { + // one step backwards + if (!next_match) { + if (s->hiscnt == hislen) { + // first time + s->hiscnt = hisidx[s->histype]; + } else if (s->hiscnt == 0 && hisidx[s->histype] != hislen - 1) { + s->hiscnt = hislen - 1; + } else if (s->hiscnt != hisidx[s->histype] + 1) { + s->hiscnt--; + } else { + // at top of list + s->hiscnt = s->i; + break; + } + } else { // one step forwards + // on last entry, clear the line + if (s->hiscnt == hisidx[s->histype]) { + s->hiscnt = hislen; + break; + } + + // not on a history line, nothing to do + if (s->hiscnt == hislen) { + break; + } + + if (s->hiscnt == hislen - 1) { + // wrap around + s->hiscnt = 0; + } else { + s->hiscnt++; + } + } + + if (s->hiscnt < 0 || history[s->histype][s->hiscnt].hisstr == NULL) { + s->hiscnt = s->i; + break; + } + + if ((s->c != K_UP && s->c != K_DOWN) + || s->hiscnt == s->i + || STRNCMP(history[s->histype][s->hiscnt].hisstr, + s->lookfor, (size_t)s->j) == 0) { + break; + } + } +} + static int command_line_handle_key(CommandLineState *s) { // Big switch for a typed command line character. @@ -921,6 +1233,16 @@ static int command_line_handle_key(CommandLineState *s) // Truncate at the end, required for multi-byte chars. ccline.cmdbuff[ccline.cmdlen] = NUL; + if (ccline.cmdlen == 0) { + s->search_start = s->save_cursor; + // save view settings, so that the screen won't be restored at the + // wrong position + s->old_curswant = s->init_curswant; + s->old_leftcol = s->init_leftcol; + s->old_topline = s->init_topline; + s->old_topfill = s->init_topfill; + s->old_botline = s->init_botline; + } redrawcmd(); } else if (ccline.cmdlen == 0 && s->c != Ctrl_W && ccline.cmdprompt == NULL && s->indent == 0) { @@ -931,7 +1253,7 @@ static int command_line_handle_key(CommandLineState *s) xfree(ccline.cmdbuff); // no commandline to return ccline.cmdbuff = NULL; - if (!cmd_silent) { + if (!cmd_silent && !ui_is_external(kUICmdline)) { if (cmdmsg_rl) { msg_col = Columns; } else { @@ -939,6 +1261,7 @@ static int command_line_handle_key(CommandLineState *s) } msg_putchar(' '); // delete ':' } + s->search_start = s->save_cursor; redraw_cmdline = true; return 0; // back to cmd mode } @@ -958,7 +1281,7 @@ static int command_line_handle_key(CommandLineState *s) return command_line_not_changed(s); case Ctrl_HAT: - if (map_to_exists_mode((char_u *)"", LANGMAP, false)) { + if (map_to_exists_mode("", LANGMAP, false)) { // ":lmap" mappings exists, toggle use of mappings. State ^= LANGMAP; if (s->b_im_ptr != NULL) { @@ -982,7 +1305,6 @@ static int command_line_handle_key(CommandLineState *s) status_redraw_curbuf(); return command_line_not_changed(s); - // case '@': only in very old vi case Ctrl_U: // delete all characters left of the cursor s->j = ccline.cmdpos; @@ -994,15 +1316,20 @@ static int command_line_handle_key(CommandLineState *s) // Truncate at the end, required for multi-byte chars. ccline.cmdbuff[ccline.cmdlen] = NUL; + if (ccline.cmdlen == 0) { + s->search_start = s->save_cursor; + } redrawcmd(); return command_line_changed(s); - case ESC: // get here if p_wc != ESC or when ESC typed twice case Ctrl_C: // In exmode it doesn't make sense to return. Except when - // ":normal" runs out of characters. - if (exmode_active && (ex_normal_busy == 0 || typebuf.tb_len > 0)) { + // ":normal" runs out of characters. Also when highlight callback is active + // <C-c> should interrupt only it. + if ((exmode_active && (ex_normal_busy == 0 || typebuf.tb_len > 0)) + || (getln_interrupted_highlight && s->c == Ctrl_C)) { + getln_interrupted_highlight = false; return command_line_not_changed(s); } @@ -1027,13 +1354,15 @@ static int command_line_handle_key(CommandLineState *s) // a new one... new_cmdpos = -1; if (s->c == '=') { - if (ccline.cmdfirstc == '=') { // can't do this recursively + if (ccline.cmdfirstc == '=' // can't do this recursively + || cmdline_star > 0) { // or when typing a password beep_flush(); s->c = ESC; } else { - save_cmdline(&s->save_ccline); + CmdlineInfo save_ccline; + save_cmdline(&save_ccline); s->c = get_expr_register(); - restore_cmdline(&s->save_ccline); + restore_cmdline(&save_ccline); } } @@ -1065,6 +1394,7 @@ static int command_line_handle_key(CommandLineState *s) break; // Use ^D as normal char instead } + wild_menu_showing = WM_LIST; redrawcmd(); return 1; // don't do incremental search now @@ -1103,25 +1433,22 @@ static int command_line_handle_key(CommandLineState *s) return command_line_not_changed(s); } do { - --ccline.cmdpos; - if (has_mbyte) { // move to first byte of char - ccline.cmdpos -= (*mb_head_off)(ccline.cmdbuff, - ccline.cmdbuff + ccline.cmdpos); - } + ccline.cmdpos--; + // Move to first byte of possibly multibyte char. + ccline.cmdpos -= utf_head_off(ccline.cmdbuff, + ccline.cmdbuff + ccline.cmdpos); ccline.cmdspos -= cmdline_charsize(ccline.cmdpos); } while (ccline.cmdpos > 0 && (s->c == K_S_LEFT || s->c == K_C_LEFT || (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL))) && ccline.cmdbuff[ccline.cmdpos - 1] != ' '); - if (has_mbyte) { - set_cmdspos_cursor(); - } + set_cmdspos_cursor(); return command_line_not_changed(s); case K_IGNORE: - // Ignore mouse event or ex_window() result. + // Ignore mouse event or open_cmdwin() result. return command_line_not_changed(s); @@ -1147,7 +1474,7 @@ static int command_line_handle_key(CommandLineState *s) if (s->ignore_drag_release) { return command_line_not_changed(s); } - // FALLTHROUGH + FALLTHROUGH; case K_LEFTMOUSE: case K_RIGHTMOUSE: if (s->c == K_LEFTRELEASE || s->c == K_RIGHTRELEASE) { @@ -1224,24 +1551,27 @@ static int command_line_handle_key(CommandLineState *s) case Ctrl_L: if (p_is && !cmd_silent && (s->firstc == '/' || s->firstc == '?')) { // Add a character from under the cursor for 'incsearch' - if (s->did_incsearch && !equalpos(curwin->w_cursor, s->old_cursor)) { - s->c = gchar_cursor(); - // If 'ignorecase' and 'smartcase' are set and the - // command line has no uppercase characters, convert - // the character to lowercase - if (p_ic && p_scs && !pat_has_uppercase(ccline.cmdbuff)) { - s->c = vim_tolower(s->c); - } - - if (s->c != NUL) { - if (s->c == s->firstc - || vim_strchr((char_u *)(p_magic ? "\\^$.*[" : "\\^$"), s->c) - != NULL) { - // put a backslash before special characters - stuffcharReadbuff(s->c); - s->c = '\\'; + if (s->did_incsearch) { + curwin->w_cursor = s->match_end; + if (!equalpos(curwin->w_cursor, s->search_start)) { + s->c = gchar_cursor(); + // If 'ignorecase' and 'smartcase' are set and the + // command line has no uppercase characters, convert + // the character to lowercase + if (p_ic && p_scs + && !pat_has_uppercase(ccline.cmdbuff)) { + s->c = mb_tolower(s->c); + } + if (s->c != NUL) { + if (s->c == s->firstc + || vim_strchr((char_u *)(p_magic ? "\\~^$.*[" : "\\^$"), s->c) + != NULL) { + // put a backslash before special characters + stuffcharReadbuff(s->c); + s->c = '\\'; + } + break; } - break; } } return command_line_not_changed(s); @@ -1260,8 +1590,9 @@ static int command_line_handle_key(CommandLineState *s) 0, s->firstc != '@') == FAIL) { break; } - return command_line_changed(s); + return command_line_not_changed(s); } + FALLTHROUGH; case K_UP: case K_DOWN: @@ -1284,60 +1615,14 @@ static int command_line_handle_key(CommandLineState *s) s->lookfor[ccline.cmdpos] = NUL; } - s->j = (int)STRLEN(s->lookfor); - for (;; ) { - // one step backwards - if (s->c == K_UP|| s->c == K_S_UP || s->c == Ctrl_P - || s->c == K_PAGEUP || s->c == K_KPAGEUP) { - if (s->hiscnt == hislen) { - // first time - s->hiscnt = hisidx[s->histype]; - } else if (s->hiscnt == 0 && hisidx[s->histype] != hislen - 1) { - s->hiscnt = hislen - 1; - } else if (s->hiscnt != hisidx[s->histype] + 1) { - --s->hiscnt; - } else { - // at top of list - s->hiscnt = s->i; - break; - } - } else { // one step forwards - // on last entry, clear the line - if (s->hiscnt == hisidx[s->histype]) { - s->hiscnt = hislen; - break; - } - - // not on a history line, nothing to do - if (s->hiscnt == hislen) { - break; - } - - if (s->hiscnt == hislen - 1) { - // wrap around - s->hiscnt = 0; - } else { - ++s->hiscnt; - } - } - - if (s->hiscnt < 0 || history[s->histype][s->hiscnt].hisstr == NULL) { - s->hiscnt = s->i; - break; - } - - if ((s->c != K_UP && s->c != K_DOWN) - || s->hiscnt == s->i - || STRNCMP(history[s->histype][s->hiscnt].hisstr, - s->lookfor, (size_t)s->j) == 0) { - break; - } - } + bool next_match = (s->c == K_DOWN || s->c == K_S_DOWN || s->c == Ctrl_N + || s->c == K_PAGEDOWN || s->c == K_KPAGEDOWN); + command_line_next_histidx(s, next_match); if (s->hiscnt != s->i) { // jumped to other entry char_u *p; - int len; + int len = 0; int old_firstc; xfree(ccline.cmdbuff); @@ -1400,6 +1685,16 @@ static int command_line_handle_key(CommandLineState *s) beep_flush(); return command_line_not_changed(s); + case Ctrl_G: // next match + case Ctrl_T: // previous match + if (p_is && !cmd_silent && (s->firstc == '/' || s->firstc == '?')) { + if (ccline.cmdlen != 0) { + command_line_next_incsearch(s, s->c == Ctrl_G); + } + return command_line_not_changed(s); + } + break; + case Ctrl_V: case Ctrl_Q: s->ignore_drag_release = true; @@ -1408,9 +1703,14 @@ static int command_line_handle_key(CommandLineState *s) s->do_abbr = false; // don't do abbreviation now // may need to remove ^ when composing char was typed if (enc_utf8 && utf_iscomposing(s->c) && !cmd_silent) { - draw_cmdline(ccline.cmdpos, ccline.cmdlen - ccline.cmdpos); - msg_putchar(' '); - cursorcmd(); + if (ui_is_external(kUICmdline)) { + // TODO(bfredl): why not make unputcmdline also work with true? + unputcmdline(); + } else { + draw_cmdline(ccline.cmdpos, ccline.cmdlen - ccline.cmdpos); + msg_putchar(' '); + cursorcmd(); + } } break; @@ -1442,14 +1742,6 @@ static int command_line_handle_key(CommandLineState *s) } return command_line_not_changed(s); - case K_FOCUSGAINED: // Neovim has been given focus - apply_autocmds(EVENT_FOCUSGAINED, NULL, NULL, false, curbuf); - return command_line_not_changed(s); - - case K_FOCUSLOST: // Neovim has lost focus - apply_autocmds(EVENT_FOCUSLOST, NULL, NULL, false, curbuf); - return command_line_not_changed(s); - default: // Normal character with no special meaning. Just set mod_mask // to 0x0 so that typing Shift-Space in the GUI doesn't enter @@ -1475,14 +1767,9 @@ static int command_line_handle_key(CommandLineState *s) if (IS_SPECIAL(s->c) || mod_mask != 0) { put_on_cmdline(get_special_key_name(s->c, mod_mask), -1, true); } else { - if (has_mbyte) { - s->j = (*mb_char2bytes)(s->c, IObuff); - IObuff[s->j] = NUL; // exclude composing chars - put_on_cmdline(IObuff, s->j, true); - } else { - IObuff[0] = s->c; - put_on_cmdline(IObuff, 1, true); - } + s->j = utf_char2bytes(s->c, IObuff); + IObuff[s->j] = NUL; // exclude composing chars + put_on_cmdline(IObuff, s->j, true); } return command_line_changed(s); } @@ -1490,19 +1777,62 @@ static int command_line_handle_key(CommandLineState *s) static int command_line_not_changed(CommandLineState *s) { - // This part implements incremental searches for "/" and "?" Jump to - // cmdline_not_changed when a character has been read but the command line - // did not change. Then we only search and redraw if something changed in - // the past. Jump to cmdline_changed when the command line did change. - // (Sorry for the goto's, I know it is ugly). + // Incremental searches for "/" and "?": + // Enter command_line_not_changed() when a character has been read but the + // command line did not change. Then we only search and redraw if something + // changed in the past. + // Enter command_line_changed() when the command line did change. if (!s->incsearch_postponed) { return 1; } return command_line_changed(s); } +/// Guess that the pattern matches everything. Only finds specific cases, such +/// as a trailing \|, which can happen while typing a pattern. +static int empty_pattern(char_u *p) +{ + size_t n = STRLEN(p); + + // remove trailing \v and the like + while (n >= 2 && p[n - 2] == '\\' + && vim_strchr((char_u *)"mMvVcCZ", p[n - 1]) != NULL) { + n -= 2; + } + return n == 0 || (n >= 2 && p[n - 2] == '\\' && p[n - 1] == '|'); +} + static int command_line_changed(CommandLineState *s) { + // Trigger CmdlineChanged autocommands. + if (has_event(EVENT_CMDLINECHANGED)) { + TryState tstate; + Error err = ERROR_INIT; + dict_T *dict = get_vim_var_dict(VV_EVENT); + + char firstcbuf[2]; + firstcbuf[0] = s->firstc > 0 ? s->firstc : '-'; + firstcbuf[1] = 0; + + // set v:event to a dictionary with information about the commandline + tv_dict_add_str(dict, S_LEN("cmdtype"), firstcbuf); + tv_dict_add_nr(dict, S_LEN("cmdlevel"), ccline.level); + tv_dict_set_keys_readonly(dict); + try_enter(&tstate); + + apply_autocmds(EVENT_CMDLINECHANGED, (char_u *)firstcbuf, + (char_u *)firstcbuf, false, curbuf); + tv_dict_clear(dict); + + bool tl_ret = try_leave(&tstate, &err); + if (!tl_ret && ERROR_SET(&err)) { + msg_putchar('\n'); + msg_printf_attr(HL_ATTR(HLF_E)|MSG_HIST, (char *)e_autocmd_err, err.msg); + api_clear_error(&err); + redrawcmd(); + } + } + // 'incsearch' highlighting. if (p_is && !cmd_silent && (s->firstc == '/' || s->firstc == '?')) { pos_T end_pos; @@ -1514,21 +1844,28 @@ static int command_line_changed(CommandLineState *s) return 1; } s->incsearch_postponed = false; - curwin->w_cursor = s->old_cursor; // start at old position + curwin->w_cursor = s->search_start; // start at old position + save_last_search_pattern(); // If there is no command line, don't do anything if (ccline.cmdlen == 0) { s->i = 0; + SET_NO_HLSEARCH(true); // turn off previous highlight + redraw_all_later(SOME_VALID); } else { + int search_flags = SEARCH_OPT + SEARCH_NOOF + SEARCH_PEEK; ui_busy_start(); ui_flush(); ++emsg_off; // So it doesn't beep if bad expr // Set the time limit to half a second. tm = profile_setlimit(500L); + if (!p_hls) { + search_flags += SEARCH_KEEP; + } s->i = do_search(NULL, s->firstc, ccline.cmdbuff, s->count, - SEARCH_KEEP + SEARCH_OPT + SEARCH_NOOF + SEARCH_PEEK, - &tm); - --emsg_off; + search_flags, + &tm); + emsg_off--; // if interrupted while searching, behave like it failed if (got_int) { (void)vpeekc(); // remove <C-C> from input stream @@ -1559,30 +1896,30 @@ static int command_line_changed(CommandLineState *s) if (s->i != 0) { pos_T save_pos = curwin->w_cursor; - // First move cursor to end of match, then to the start. This - // moves the whole match onto the screen when 'nowrap' is set. - curwin->w_cursor.lnum += search_match_lines; - curwin->w_cursor.col = search_match_endcol; - if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) { - curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; - coladvance((colnr_T)MAXCOL); - } + s->match_start = curwin->w_cursor; + set_search_match(&curwin->w_cursor); validate_cursor(); end_pos = curwin->w_cursor; + s->match_end = end_pos; curwin->w_cursor = save_pos; } else { end_pos = curwin->w_cursor; // shutup gcc 4 } + // Disable 'hlsearch' highlighting if the pattern matches + // everything. Avoids a flash when typing "foo\|". + if (empty_pattern(ccline.cmdbuff)) { + SET_NO_HLSEARCH(true); + } + validate_cursor(); // May redraw the status line to show the cursor position. if (p_ru && curwin->w_status_height > 0) { curwin->w_redr_status = true; } - save_cmdline(&s->save_ccline); update_screen(SOME_VALID); - restore_cmdline(&s->save_ccline); + restore_last_search_pattern(); // Leave it at the end to make CTRL-R CTRL-W work. if (s->i != 0) { @@ -1592,6 +1929,36 @@ static int command_line_changed(CommandLineState *s) msg_starthere(); redrawcmdline(); s->did_incsearch = true; + } else if (s->firstc == ':' + && current_SID == 0 // only if interactive + && *p_icm != NUL // 'inccommand' is set + && curbuf->b_p_ma // buffer is modifiable + && cmdline_star == 0 // not typing a password + && cmd_can_preview(ccline.cmdbuff) + && !vpeekc_any()) { + // Show 'inccommand' preview. It works like this: + // 1. Do the command. + // 2. Command implementation detects CMDPREVIEW state, then: + // - Update the screen while the effects are in place. + // - Immediately undo the effects. + State |= CMDPREVIEW; + emsg_silent++; // Block error reporting as the command may be incomplete + do_cmdline(ccline.cmdbuff, NULL, NULL, DOCMD_KEEPLINE|DOCMD_NOWAIT); + emsg_silent--; // Unblock error reporting + + // Restore the window "view". + curwin->w_cursor = s->save_cursor; + curwin->w_curswant = s->old_curswant; + curwin->w_leftcol = s->old_leftcol; + curwin->w_topline = s->old_topline; + curwin->w_topfill = s->old_topfill; + curwin->w_botline = s->old_botline; + update_topline(); + + redrawcmdline(); + } else if (State & CMDPREVIEW) { + State = (State & ~CMDPREVIEW); + update_screen(SOME_VALID); // Clear 'inccommand' preview. } if (cmdmsg_rl || (p_arshape && !p_tbidi && enc_utf8)) { @@ -1599,7 +1966,8 @@ static int command_line_changed(CommandLineState *s) // right-left typing. Not efficient, but it works. // Do it only when there are no characters left to read // to avoid useless intermediate redraws. - if (vpeekc() == NUL) { + // if cmdline is external the ui handles shaping, no redraw needed. + if (!ui_is_external(kUICmdline) && vpeekc() == NUL) { redrawcmd(); } } @@ -1607,6 +1975,18 @@ static int command_line_changed(CommandLineState *s) return 1; } +/// Abandon the command line. +static void abandon_cmdline(void) +{ + xfree(ccline.cmdbuff); + ccline.cmdbuff = NULL; + if (msg_scrolled == 0) { + compute_cmdrow(); + } + MSG(""); + redraw_cmdline = true; +} + /* * getcmdline() - accept a command line starting with firstc. * @@ -1633,44 +2013,64 @@ getcmdline ( int indent // indent for inside conditionals ) { - return command_line_enter(firstc, count, indent); + // Be prepared for situations where cmdline can be invoked recursively. + // That includes cmd mappings, event handlers, as well as update_screen() + // (custom status line eval), which all may invoke ":normal :". + CmdlineInfo save_ccline; + save_cmdline(&save_ccline); + char_u *retval = command_line_enter(firstc, count, indent); + restore_cmdline(&save_ccline); + return retval; } -/* - * Get a command line with a prompt. - * This is prepared to be called recursively from getcmdline() (e.g. by - * f_input() when evaluating an expression from CTRL-R =). - * Returns the command line in allocated memory, or NULL. - */ -char_u * -getcmdline_prompt ( - int firstc, - char_u *prompt, /* command line prompt */ - int attr, /* attributes for prompt */ - int xp_context, /* type of expansion */ - char_u *xp_arg /* user-defined expansion argument */ -) +/// Get a command line with a prompt +/// +/// This is prepared to be called recursively from getcmdline() (e.g. by +/// f_input() when evaluating an expression from `<C-r>=`). +/// +/// @param[in] firstc Prompt type: e.g. '@' for input(), '>' for debug. +/// @param[in] prompt Prompt string: what is displayed before the user text. +/// @param[in] attr Prompt highlighting. +/// @param[in] xp_context Type of expansion. +/// @param[in] xp_arg User-defined expansion argument. +/// @param[in] highlight_callback Callback used for highlighting user input. +/// +/// @return [allocated] Command line or NULL. +char *getcmdline_prompt(const char firstc, const char *const prompt, + const int attr, const int xp_context, + const char *const xp_arg, + const Callback highlight_callback) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_MALLOC { - char_u *s; - struct cmdline_info save_ccline; - int msg_col_save = msg_col; + const int msg_col_save = msg_col; + CmdlineInfo save_ccline; save_cmdline(&save_ccline); - ccline.cmdprompt = prompt; + + ccline.prompt_id = last_prompt_id++; + ccline.cmdprompt = (char_u *)prompt; ccline.cmdattr = attr; ccline.xp_context = xp_context; - ccline.xp_arg = xp_arg; + ccline.xp_arg = (char_u *)xp_arg; ccline.input_fn = (firstc == '@'); - s = getcmdline(firstc, 1L, 0); + ccline.highlight_callback = highlight_callback; + + int msg_silent_saved = msg_silent; + msg_silent = 0; + + char *const ret = (char *)command_line_enter(firstc, 1L, 0); + restore_cmdline(&save_ccline); - /* Restore msg_col, the prompt from input() may have changed it. - * But only if called recursively and the commandline is therefore being - * restored to an old one; if not, the input() prompt stays on the screen, - * so we need its modified msg_col left intact. */ - if (ccline.cmdbuff != NULL) + msg_silent = msg_silent_saved; + // Restore msg_col, the prompt from input() may have changed it. + // But only if called recursively and the commandline is therefore being + // restored to an old one; if not, the input() prompt stays on the screen, + // so we need its modified msg_col left intact. + if (ccline.cmdbuff != NULL) { msg_col = msg_col_save; + } - return s; + return ret; } /* @@ -1689,10 +2089,15 @@ int text_locked(void) { */ void text_locked_msg(void) { - if (cmdwin_type != 0) - EMSG(_(e_cmdwin)); - else - EMSG(_(e_secure)); + EMSG(_(get_text_locked_msg())); +} + +char_u * get_text_locked_msg(void) { + if (cmdwin_type != 0) { + return e_cmdwin; + } else { + return e_secure; + } } /* @@ -1776,10 +2181,11 @@ static void set_cmdspos_cursor(void) */ static void correct_cmdspos(int idx, int cells) { - if ((*mb_ptr2len)(ccline.cmdbuff + idx) > 1 - && (*mb_ptr2cells)(ccline.cmdbuff + idx) > 1 - && ccline.cmdspos % Columns + cells > Columns) + if (utfc_ptr2len(ccline.cmdbuff + idx) > 1 + && utf_ptr2cells(ccline.cmdbuff + idx) > 1 + && ccline.cmdspos % Columns + cells > Columns) { ccline.cmdspos++; + } } /* @@ -1795,6 +2201,7 @@ getexline ( /* When executing a register, remove ':' that's in front of each line. */ if (exec_from_reg && vpeekc() == ':') (void)vgetc(); + return getcmdline(c, 1L, indent); } @@ -1842,7 +2249,7 @@ getexmodeline ( vcol = indent; while (indent >= 8) { ga_append(&line_ga, TAB); - msg_puts((char_u *)" "); + msg_puts(" "); indent -= 8; } while (indent-- > 0) { @@ -1850,8 +2257,7 @@ getexmodeline ( msg_putchar(' '); } } - ++no_mapping; - ++allow_keys; + no_mapping++; /* * Get the line, one character at a time. @@ -1863,7 +2269,13 @@ getexmodeline ( /* Get one character at a time. Don't use inchar(), it can't handle * special characters. */ prev_char = c1; - c1 = vgetc(); + + // Check for a ":normal" command and no more characters left. + if (ex_normal_busy > 0 && typebuf.tb_len == 0) { + c1 = '\n'; + } else { + c1 = vgetc(); + } /* * Handle line editing. @@ -1882,14 +2294,10 @@ getexmodeline ( if (c1 == BS || c1 == K_BS || c1 == DEL || c1 == K_DEL || c1 == K_KDEL) { if (!GA_EMPTY(&line_ga)) { - if (has_mbyte) { - p = (char_u *)line_ga.ga_data; - p[line_ga.ga_len] = NUL; - len = (*mb_head_off)(p, p + line_ga.ga_len - 1) + 1; - line_ga.ga_len -= len; - } else { - line_ga.ga_len--; - } + p = (char_u *)line_ga.ga_data; + p[line_ga.ga_len] = NUL; + len = utf_head_off(p, p + line_ga.ga_len - 1) + 1; + line_ga.ga_len -= len; goto redraw; } continue; @@ -1993,16 +2401,11 @@ redraw: if (IS_SPECIAL(c1)) { c1 = '?'; } - if (has_mbyte) { - len = (*mb_char2bytes)(c1, (char_u *)line_ga.ga_data + line_ga.ga_len); - } else { - len = 1; - ((char_u *)line_ga.ga_data)[line_ga.ga_len] = c1; - } - if (c1 == '\n') + len = utf_char2bytes(c1, (char_u *)line_ga.ga_data + line_ga.ga_len); + if (c1 == '\n') { msg_putchar('\n'); - else if (c1 == TAB) { - /* Don't use chartabsize(), 'ts' can be different */ + } else if (c1 == TAB) { + // Don't use chartabsize(), 'ts' can be different. do { msg_putchar(' '); } while (++vcol % 8); @@ -2041,8 +2444,7 @@ redraw: } } - --no_mapping; - --allow_keys; + no_mapping--; /* make following messages go to the next line */ msg_didout = FALSE; @@ -2057,6 +2459,18 @@ redraw: return (char_u *)line_ga.ga_data; } +bool cmdline_overstrike(void) +{ + return ccline.overstrike; +} + + +/// Return true if the cursor is at the end of the cmdline. +bool cmdline_at_end(void) +{ + return (ccline.cmdpos >= ccline.cmdlen); +} + /* * Allocate a new command line buffer. * Assigns the new buffer to ccline.cmdbuff and ccline.cmdbufflen. @@ -2116,75 +2530,389 @@ void free_cmdline_buf(void) # endif +enum { MAX_CB_ERRORS = 1 }; + +/// Color expression cmdline using built-in expressions parser +/// +/// @param[in] colored_ccline Command-line to color. +/// @param[out] ret_ccline_colors What should be colored. +/// +/// Always colors the whole cmdline. +static void color_expr_cmdline(const CmdlineInfo *const colored_ccline, + ColoredCmdline *const ret_ccline_colors) + FUNC_ATTR_NONNULL_ALL +{ + ParserLine plines[] = { + { + .data = (const char *)colored_ccline->cmdbuff, + .size = STRLEN(colored_ccline->cmdbuff), + .allocated = false, + }, + { NULL, 0, false }, + }; + ParserLine *plines_p = plines; + ParserHighlight colors; + kvi_init(colors); + ParserState pstate; + viml_parser_init( + &pstate, parser_simple_get_line, &plines_p, &colors); + ExprAST east = viml_pexpr_parse(&pstate, kExprFlagsDisallowEOC); + viml_pexpr_free_ast(east); + viml_parser_destroy(&pstate); + kv_resize(ret_ccline_colors->colors, kv_size(colors)); + size_t prev_end = 0; + for (size_t i = 0 ; i < kv_size(colors) ; i++) { + const ParserHighlightChunk chunk = kv_A(colors, i); + if (chunk.start.col != prev_end) { + kv_push(ret_ccline_colors->colors, ((CmdlineColorChunk) { + .start = prev_end, + .end = chunk.start.col, + .attr = 0, + })); + } + const int id = syn_name2id((const char_u *)chunk.group); + const int attr = (id == 0 ? 0 : syn_id2attr(id)); + kv_push(ret_ccline_colors->colors, ((CmdlineColorChunk) { + .start = chunk.start.col, + .end = chunk.end_col, + .attr = attr, + })); + prev_end = chunk.end_col; + } + if (prev_end < (size_t)colored_ccline->cmdlen) { + kv_push(ret_ccline_colors->colors, ((CmdlineColorChunk) { + .start = prev_end, + .end = (size_t)colored_ccline->cmdlen, + .attr = 0, + })); + } + kvi_destroy(colors); +} + +/// Color command-line +/// +/// Should use built-in command parser or user-specified one. Currently only the +/// latter is supported. +/// +/// @param[in,out] colored_ccline Command-line to color. Also holds a cache: +/// if ->prompt_id and ->cmdbuff values happen +/// to be equal to those from colored_cmdline it +/// will just do nothing, assuming that ->colors +/// already contains needed data. +/// +/// Always colors the whole cmdline. +/// +/// @return true if draw_cmdline may proceed, false if it does not need anything +/// to do. +static bool color_cmdline(CmdlineInfo *colored_ccline) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + bool printed_errmsg = false; + +#define PRINT_ERRMSG(...) \ + do { \ + msg_putchar('\n'); \ + msg_printf_attr(HL_ATTR(HLF_E)|MSG_HIST, __VA_ARGS__); \ + printed_errmsg = true; \ + } while (0) + bool ret = true; + + ColoredCmdline *ccline_colors = &colored_ccline->last_colors; + + // Check whether result of the previous call is still valid. + if (ccline_colors->prompt_id == colored_ccline->prompt_id + && ccline_colors->cmdbuff != NULL + && STRCMP(ccline_colors->cmdbuff, colored_ccline->cmdbuff) == 0) { + return ret; + } + + kv_size(ccline_colors->colors) = 0; + + if (colored_ccline->cmdbuff == NULL || *colored_ccline->cmdbuff == NUL) { + // Nothing to do, exiting. + xfree(ccline_colors->cmdbuff); + ccline_colors->cmdbuff = NULL; + return ret; + } + + bool arg_allocated = false; + typval_T arg = { + .v_type = VAR_STRING, + .vval.v_string = colored_ccline->cmdbuff, + }; + typval_T tv = { .v_type = VAR_UNKNOWN }; + + static unsigned prev_prompt_id = UINT_MAX; + static int prev_prompt_errors = 0; + Callback color_cb = CALLBACK_NONE; + bool can_free_cb = false; + TryState tstate; + Error err = ERROR_INIT; + const char *err_errmsg = (const char *)e_intern2; + bool dgc_ret = true; + bool tl_ret = true; + + if (colored_ccline->prompt_id != prev_prompt_id) { + prev_prompt_errors = 0; + prev_prompt_id = colored_ccline->prompt_id; + } else if (prev_prompt_errors >= MAX_CB_ERRORS) { + goto color_cmdline_end; + } + if (colored_ccline->highlight_callback.type != kCallbackNone) { + // Currently this should only happen while processing input() prompts. + assert(colored_ccline->input_fn); + color_cb = colored_ccline->highlight_callback; + } else if (colored_ccline->cmdfirstc == ':') { + try_enter(&tstate); + err_errmsg = N_( + "E5408: Unable to get g:Nvim_color_cmdline callback: %s"); + dgc_ret = tv_dict_get_callback(&globvardict, S_LEN("Nvim_color_cmdline"), + &color_cb); + tl_ret = try_leave(&tstate, &err); + can_free_cb = true; + } else if (colored_ccline->cmdfirstc == '=') { + color_expr_cmdline(colored_ccline, ccline_colors); + } + if (!tl_ret || !dgc_ret) { + goto color_cmdline_error; + } + + if (color_cb.type == kCallbackNone) { + goto color_cmdline_end; + } + if (colored_ccline->cmdbuff[colored_ccline->cmdlen] != NUL) { + arg_allocated = true; + arg.vval.v_string = xmemdupz((const char *)colored_ccline->cmdbuff, + (size_t)colored_ccline->cmdlen); + } + // msg_start() called by e.g. :echo may shift command-line to the first column + // even though msg_silent is here. Two ways to workaround this problem without + // altering message.c: use full_screen or save and restore msg_col. + // + // Saving and restoring full_screen does not work well with :redraw!. Saving + // and restoring msg_col is neither ideal, but while with full_screen it + // appears shifted one character to the right and cursor position is no longer + // correct, with msg_col it just misses leading `:`. Since `redraw!` in + // callback lags this is least of the user problems. + // + // Also using try_enter() because error messages may overwrite typed + // command-line which is not expected. + getln_interrupted_highlight = false; + try_enter(&tstate); + err_errmsg = N_("E5407: Callback has thrown an exception: %s"); + const int saved_msg_col = msg_col; + msg_silent++; + const bool cbcall_ret = callback_call(&color_cb, 1, &arg, &tv); + msg_silent--; + msg_col = saved_msg_col; + if (got_int) { + getln_interrupted_highlight = true; + } + if (!try_leave(&tstate, &err) || !cbcall_ret) { + goto color_cmdline_error; + } + if (tv.v_type != VAR_LIST) { + PRINT_ERRMSG(_("E5400: Callback should return list")); + goto color_cmdline_error; + } + if (tv.vval.v_list == NULL) { + goto color_cmdline_end; + } + varnumber_T prev_end = 0; + int i = 0; + TV_LIST_ITER_CONST(tv.vval.v_list, li, { + if (TV_LIST_ITEM_TV(li)->v_type != VAR_LIST) { + PRINT_ERRMSG(_("E5401: List item %i is not a List"), i); + goto color_cmdline_error; + } + const list_T *const l = TV_LIST_ITEM_TV(li)->vval.v_list; + if (tv_list_len(l) != 3) { + PRINT_ERRMSG(_("E5402: List item %i has incorrect length: %d /= 3"), + i, tv_list_len(l)); + goto color_cmdline_error; + } + bool error = false; + const varnumber_T start = ( + tv_get_number_chk(TV_LIST_ITEM_TV(tv_list_first(l)), &error)); + if (error) { + goto color_cmdline_error; + } else if (!(prev_end <= start && start < colored_ccline->cmdlen)) { + PRINT_ERRMSG(_("E5403: Chunk %i start %" PRIdVARNUMBER " not in range " + "[%" PRIdVARNUMBER ", %i)"), + i, start, prev_end, colored_ccline->cmdlen); + goto color_cmdline_error; + } else if (utf8len_tab_zero[(uint8_t)colored_ccline->cmdbuff[start]] == 0) { + PRINT_ERRMSG(_("E5405: Chunk %i start %" PRIdVARNUMBER " splits " + "multibyte character"), i, start); + goto color_cmdline_error; + } + if (start != prev_end) { + kv_push(ccline_colors->colors, ((CmdlineColorChunk) { + .start = prev_end, + .end = start, + .attr = 0, + })); + } + const varnumber_T end = tv_get_number_chk( + TV_LIST_ITEM_TV(TV_LIST_ITEM_NEXT(l, tv_list_first(l))), &error); + if (error) { + goto color_cmdline_error; + } else if (!(start < end && end <= colored_ccline->cmdlen)) { + PRINT_ERRMSG(_("E5404: Chunk %i end %" PRIdVARNUMBER " not in range " + "(%" PRIdVARNUMBER ", %i]"), + i, end, start, colored_ccline->cmdlen); + goto color_cmdline_error; + } else if (end < colored_ccline->cmdlen + && (utf8len_tab_zero[(uint8_t)colored_ccline->cmdbuff[end]] + == 0)) { + PRINT_ERRMSG(_("E5406: Chunk %i end %" PRIdVARNUMBER " splits multibyte " + "character"), i, end); + goto color_cmdline_error; + } + prev_end = end; + const char *const group = tv_get_string_chk( + TV_LIST_ITEM_TV(tv_list_last(l))); + if (group == NULL) { + goto color_cmdline_error; + } + const int id = syn_name2id((char_u *)group); + const int attr = (id == 0 ? 0 : syn_id2attr(id)); + kv_push(ccline_colors->colors, ((CmdlineColorChunk) { + .start = start, + .end = end, + .attr = attr, + })); + i++; + }); + if (prev_end < colored_ccline->cmdlen) { + kv_push(ccline_colors->colors, ((CmdlineColorChunk) { + .start = prev_end, + .end = colored_ccline->cmdlen, + .attr = 0, + })); + } + prev_prompt_errors = 0; +color_cmdline_end: + assert(!ERROR_SET(&err)); + if (can_free_cb) { + callback_free(&color_cb); + } + xfree(ccline_colors->cmdbuff); + // Note: errors “output” is cached just as well as regular results. + ccline_colors->prompt_id = colored_ccline->prompt_id; + if (arg_allocated) { + ccline_colors->cmdbuff = (char *)arg.vval.v_string; + } else { + ccline_colors->cmdbuff = xmemdupz((const char *)colored_ccline->cmdbuff, + (size_t)colored_ccline->cmdlen); + } + tv_clear(&tv); + return ret; +color_cmdline_error: + if (ERROR_SET(&err)) { + PRINT_ERRMSG(_(err_errmsg), err.msg); + api_clear_error(&err); + } + assert(printed_errmsg); + (void)printed_errmsg; + + prev_prompt_errors++; + kv_size(ccline_colors->colors) = 0; + redrawcmdline(); + ret = false; + goto color_cmdline_end; +#undef PRINT_ERRMSG +} + /* * Draw part of the cmdline at the current cursor position. But draw stars * when cmdline_star is TRUE. */ static void draw_cmdline(int start, int len) { - int i; + if (!color_cmdline(&ccline)) { + return; + } - if (cmdline_star > 0) - for (i = 0; i < len; ++i) { + if (ui_is_external(kUICmdline)) { + ccline.special_char = NUL; + ccline.redraw_state = kCmdRedrawAll; + return; + } + + if (cmdline_star > 0) { + for (int i = 0; i < len; i++) { msg_putchar('*'); - if (has_mbyte) + if (has_mbyte) { i += (*mb_ptr2len)(ccline.cmdbuff + start + i) - 1; + } } - else if (p_arshape && !p_tbidi && enc_utf8 && len > 0) { - static int buflen = 0; - char_u *p; - int j; - int newlen = 0; + } else if (p_arshape && !p_tbidi && enc_utf8 && len > 0) { + bool do_arabicshape = false; int mb_l; - int pc, pc1 = 0; - int prev_c = 0; - int prev_c1 = 0; - int u8c; - int u8cc[MAX_MCO]; - int nc = 0; + for (int i = start; i < start + len; i += mb_l) { + char_u *p = ccline.cmdbuff + i; + int u8cc[MAX_MCO]; + int u8c = utfc_ptr2char_len(p, u8cc, start + len - i); + mb_l = utfc_ptr2len_len(p, start + len - i); + if (arabic_char(u8c)) { + do_arabicshape = true; + break; + } + } + if (!do_arabicshape) { + goto draw_cmdline_no_arabicshape; + } - /* - * Do arabic shaping into a temporary buffer. This is very - * inefficient! - */ + static int buflen = 0; + + // Do arabic shaping into a temporary buffer. This is very + // inefficient! if (len * 2 + 2 > buflen) { - /* Re-allocate the buffer. We keep it around to avoid a lot of - * alloc()/free() calls. */ + // Re-allocate the buffer. We keep it around to avoid a lot of + // alloc()/free() calls. xfree(arshape_buf); buflen = len * 2 + 2; arshape_buf = xmalloc(buflen); } + int newlen = 0; if (utf_iscomposing(utf_ptr2char(ccline.cmdbuff + start))) { - /* Prepend a space to draw the leading composing char on. */ + // Prepend a space to draw the leading composing char on. arshape_buf[0] = ' '; newlen = 1; } - for (j = start; j < start + len; j += mb_l) { - p = ccline.cmdbuff + j; - u8c = utfc_ptr2char_len(p, u8cc, start + len - j); - mb_l = utfc_ptr2len_len(p, start + len - j); + int prev_c = 0; + int prev_c1 = 0; + for (int i = start; i < start + len; i += mb_l) { + char_u *p = ccline.cmdbuff + i; + int u8cc[MAX_MCO]; + int u8c = utfc_ptr2char_len(p, u8cc, start + len - i); + mb_l = utfc_ptr2len_len(p, start + len - i); if (arabic_char(u8c)) { - /* Do Arabic shaping. */ + int pc; + int pc1 = 0; + int nc = 0; + // Do Arabic shaping. if (cmdmsg_rl) { - /* displaying from right to left */ + // Displaying from right to left. pc = prev_c; pc1 = prev_c1; prev_c1 = u8cc[0]; - if (j + mb_l >= start + len) + if (i + mb_l >= start + len) { nc = NUL; - else + } else { nc = utf_ptr2char(p + mb_l); + } } else { - /* displaying from left to right */ - if (j + mb_l >= start + len) + // Displaying from left to right. + if (i + mb_l >= start + len) { pc = NUL; - else { + } else { int pcc[MAX_MCO]; - pc = utfc_ptr2char_len(p + mb_l, pcc, - start + len - j - mb_l); + pc = utfc_ptr2char_len(p + mb_l, pcc, start + len - i - mb_l); pc1 = pcc[0]; } nc = prev_c; @@ -2193,12 +2921,12 @@ static void draw_cmdline(int start, int len) u8c = arabic_shape(u8c, NULL, &u8cc[0], pc, pc1, nc); - newlen += (*mb_char2bytes)(u8c, arshape_buf + newlen); + newlen += utf_char2bytes(u8c, arshape_buf + newlen); if (u8cc[0] != 0) { - newlen += (*mb_char2bytes)(u8cc[0], arshape_buf + newlen); - if (u8cc[1] != 0) - newlen += (*mb_char2bytes)(u8cc[1], - arshape_buf + newlen); + newlen += utf_char2bytes(u8cc[0], arshape_buf + newlen); + if (u8cc[1] != 0) { + newlen += utf_char2bytes(u8cc[1], arshape_buf + newlen); + } } } else { prev_c = u8c; @@ -2208,8 +2936,139 @@ static void draw_cmdline(int start, int len) } msg_outtrans_len(arshape_buf, newlen); - } else - msg_outtrans_len(ccline.cmdbuff + start, len); + } else { +draw_cmdline_no_arabicshape: + if (kv_size(ccline.last_colors.colors)) { + for (size_t i = 0; i < kv_size(ccline.last_colors.colors); i++) { + CmdlineColorChunk chunk = kv_A(ccline.last_colors.colors, i); + if (chunk.end <= start) { + continue; + } + const int chunk_start = MAX(chunk.start, start); + msg_outtrans_len_attr(ccline.cmdbuff + chunk_start, + chunk.end - chunk_start, + chunk.attr); + } + } else { + msg_outtrans_len(ccline.cmdbuff + start, len); + } + } +} + +static void ui_ext_cmdline_show(CmdlineInfo *line) +{ + Array content = ARRAY_DICT_INIT; + if (cmdline_star) { + size_t len = 0; + for (char_u *p = ccline.cmdbuff; *p; MB_PTR_ADV(p)) { + len++; + } + char *buf = xmallocz(len); + memset(buf, '*', len); + Array item = ARRAY_DICT_INIT; + ADD(item, INTEGER_OBJ(0)); + ADD(item, STRING_OBJ(((String) { .data = buf, .size = len }))); + ADD(content, ARRAY_OBJ(item)); + } else if (kv_size(line->last_colors.colors)) { + for (size_t i = 0; i < kv_size(line->last_colors.colors); i++) { + CmdlineColorChunk chunk = kv_A(line->last_colors.colors, i); + Array item = ARRAY_DICT_INIT; + ADD(item, INTEGER_OBJ(chunk.attr)); + + ADD(item, STRING_OBJ(cbuf_to_string((char *)line->cmdbuff + chunk.start, + chunk.end-chunk.start))); + ADD(content, ARRAY_OBJ(item)); + } + } else { + Array item = ARRAY_DICT_INIT; + ADD(item, INTEGER_OBJ(0)); + ADD(item, STRING_OBJ(cstr_to_string((char *)(line->cmdbuff)))); + ADD(content, ARRAY_OBJ(item)); + } + ui_call_cmdline_show(content, line->cmdpos, + cchar_to_string((char)line->cmdfirstc), + cstr_to_string((char *)(line->cmdprompt)), + line->cmdindent, + line->level); + if (line->special_char) { + ui_call_cmdline_special_char(cchar_to_string((char)(line->special_char)), + line->special_shift, + line->level); + } +} + +void ui_ext_cmdline_block_append(int indent, const char *line) +{ + char *buf = xmallocz(indent + strlen(line)); + memset(buf, ' ', indent); + memcpy(buf + indent, line, strlen(line)); // -V575 + + Array item = ARRAY_DICT_INIT; + ADD(item, INTEGER_OBJ(0)); + ADD(item, STRING_OBJ(cstr_as_string(buf))); + Array content = ARRAY_DICT_INIT; + ADD(content, ARRAY_OBJ(item)); + ADD(cmdline_block, ARRAY_OBJ(content)); + if (cmdline_block.size > 1) { + ui_call_cmdline_block_append(copy_array(content)); + } else { + ui_call_cmdline_block_show(copy_array(cmdline_block)); + } +} + +void ui_ext_cmdline_block_leave(void) +{ + api_free_array(cmdline_block); + cmdline_block = (Array)ARRAY_DICT_INIT; + ui_call_cmdline_block_hide(); +} + +/// Extra redrawing needed for redraw! and on ui_attach +/// assumes "redrawcmdline()" will already be invoked +void cmdline_screen_cleared(void) +{ + if (!ui_is_external(kUICmdline)) { + return; + } + + if (cmdline_block.size) { + ui_call_cmdline_block_show(copy_array(cmdline_block)); + } + + int prev_level = ccline.level-1; + CmdlineInfo *line = ccline.prev_ccline; + while (prev_level > 0 && line) { + if (line->level == prev_level) { + // don't redraw a cmdline already shown in the cmdline window + if (prev_level != cmdwin_level) { + line->redraw_state = kCmdRedrawAll; + } + prev_level--; + } + line = line->prev_ccline; + } +} + +/// called by ui_flush, do what redraws neccessary to keep cmdline updated. +void cmdline_ui_flush(void) +{ + if (!ui_is_external(kUICmdline)) { + return; + } + int level = ccline.level; + CmdlineInfo *line = &ccline; + while (level > 0 && line) { + if (line->level == level) { + if (line->redraw_state == kCmdRedrawAll) { + ui_ext_cmdline_show(line); + } else if (line->redraw_state == kCmdRedrawPos) { + ui_call_cmdline_pos(line->cmdpos, line->level); + } + line->redraw_state = kCmdRedrawNone; + level--; + } + line = line->prev_ccline; + } } /* @@ -2219,33 +3078,43 @@ static void draw_cmdline(int start, int len) */ void putcmdline(int c, int shift) { - if (cmd_silent) + if (cmd_silent) { return; - msg_no_more = TRUE; - msg_putchar(c); - if (shift) - draw_cmdline(ccline.cmdpos, ccline.cmdlen - ccline.cmdpos); - msg_no_more = FALSE; + } + if (!ui_is_external(kUICmdline)) { + msg_no_more = true; + msg_putchar(c); + if (shift) { + draw_cmdline(ccline.cmdpos, ccline.cmdlen - ccline.cmdpos); + } + msg_no_more = false; + } else { + ccline.special_char = c; + ccline.special_shift = shift; + if (ccline.redraw_state != kCmdRedrawAll) { + ui_call_cmdline_special_char(cchar_to_string((char)(c)), shift, + ccline.level); + } + } cursorcmd(); + ui_cursor_shape(); } -/* - * Undo a putcmdline(c, FALSE). - */ +/// Undo a putcmdline(c, FALSE). void unputcmdline(void) { - if (cmd_silent) + if (cmd_silent) { return; - msg_no_more = TRUE; - if (ccline.cmdlen == ccline.cmdpos) + } + msg_no_more = true; + if (ccline.cmdlen == ccline.cmdpos && !ui_is_external(kUICmdline)) { msg_putchar(' '); - else if (has_mbyte) - draw_cmdline(ccline.cmdpos, - (*mb_ptr2len)(ccline.cmdbuff + ccline.cmdpos)); - else - draw_cmdline(ccline.cmdpos, 1); - msg_no_more = FALSE; + } else { + draw_cmdline(ccline.cmdpos, mb_ptr2len(ccline.cmdbuff + ccline.cmdpos)); + } + msg_no_more = false; cursorcmd(); + ui_cursor_shape(); } /* @@ -2302,18 +3171,15 @@ void put_on_cmdline(char_u *str, int len, int redraw) i = 0; c = utf_ptr2char(ccline.cmdbuff + ccline.cmdpos); while (ccline.cmdpos > 0 && utf_iscomposing(c)) { - i = (*mb_head_off)(ccline.cmdbuff, - ccline.cmdbuff + ccline.cmdpos - 1) + 1; + i = utf_head_off(ccline.cmdbuff, ccline.cmdbuff + ccline.cmdpos - 1) + 1; ccline.cmdpos -= i; len += i; c = utf_ptr2char(ccline.cmdbuff + ccline.cmdpos); } if (i == 0 && ccline.cmdpos > 0 && arabic_maycombine(c)) { - /* Check the previous character for Arabic combining pair. */ - i = (*mb_head_off)(ccline.cmdbuff, - ccline.cmdbuff + ccline.cmdpos - 1) + 1; - if (arabic_combine(utf_ptr2char(ccline.cmdbuff - + ccline.cmdpos - i), c)) { + // Check the previous character for Arabic combining pair. + i = utf_head_off(ccline.cmdbuff, ccline.cmdbuff + ccline.cmdpos - 1) + 1; + if (arabic_combine(utf_ptr2char(ccline.cmdbuff + ccline.cmdpos - i), c)) { ccline.cmdpos -= i; len += i; } else @@ -2377,9 +3243,6 @@ void put_on_cmdline(char_u *str, int len, int redraw) msg_check(); } -static struct cmdline_info prev_ccline; -static int prev_ccline_used = FALSE; - /* * Save ccline, because obtaining the "=" register may execute "normal :cmd" * and overwrite it. But get_cmdline_str() may need it, thus make it @@ -2387,15 +3250,13 @@ static int prev_ccline_used = FALSE; */ static void save_cmdline(struct cmdline_info *ccp) { - if (!prev_ccline_used) { - memset(&prev_ccline, 0, sizeof(struct cmdline_info)); - prev_ccline_used = TRUE; - } - *ccp = prev_ccline; - prev_ccline = ccline; + *ccp = ccline; + ccline.prev_ccline = ccp; ccline.cmdbuff = NULL; ccline.cmdprompt = NULL; ccline.xpc = NULL; + ccline.special_char = NUL; + ccline.level = 0; } /* @@ -2403,8 +3264,7 @@ static void save_cmdline(struct cmdline_info *ccp) */ static void restore_cmdline(struct cmdline_info *ccp) { - ccline = prev_ccline; - prev_ccline = *ccp; + ccline = *ccp; } /* @@ -2439,17 +3299,18 @@ void restore_cmdline_alloc(char_u *p) /// @returns FAIL for failure, OK otherwise static bool cmdline_paste(int regname, bool literally, bool remcr) { - long i; char_u *arg; char_u *p; - int allocated; + bool allocated; struct cmdline_info save_ccline; /* check for valid regname; also accept special characters for CTRL-R in * the command line */ if (regname != Ctrl_F && regname != Ctrl_P && regname != Ctrl_W - && regname != Ctrl_A && !valid_yank_reg(regname, false)) + && regname != Ctrl_A && regname != Ctrl_L + && !valid_yank_reg(regname, false)) { return FAIL; + } /* A register containing CTRL-R can cause an endless loop. Allow using * CTRL-C to break the loop. */ @@ -2461,9 +3322,9 @@ static bool cmdline_paste(int regname, bool literally, bool remcr) /* Need to save and restore ccline. And set "textlock" to avoid nasty * things like going to another buffer when evaluating an expression. */ save_cmdline(&save_ccline); - ++textlock; - i = get_spec_reg(regname, &arg, &allocated, TRUE); - --textlock; + textlock++; + const bool i = get_spec_reg(regname, &arg, &allocated, true); + textlock--; restore_cmdline(&save_ccline); if (i) { @@ -2480,16 +3341,11 @@ static bool cmdline_paste(int regname, bool literally, bool remcr) /* Locate start of last word in the cmd buffer. */ for (w = ccline.cmdbuff + ccline.cmdpos; w > ccline.cmdbuff; ) { - if (has_mbyte) { - len = (*mb_head_off)(ccline.cmdbuff, w - 1) + 1; - if (!vim_iswordc(mb_ptr2char(w - len))) - break; - w -= len; - } else { - if (!vim_iswordc(w[-1])) - break; - --w; + len = utf_head_off(ccline.cmdbuff, w - 1) + 1; + if (!vim_iswordc(utf_ptr2char(w - len))) { + break; } + w -= len; } len = (int)((ccline.cmdbuff + ccline.cmdpos) - w); if (p_ic ? STRNICMP(w, arg, len) == 0 : STRNCMP(w, arg, len) == 0) @@ -2520,19 +3376,19 @@ void cmdline_paste_str(char_u *s, int literally) else while (*s != NUL) { cv = *s; - if (cv == Ctrl_V && s[1]) - ++s; - if (has_mbyte) - c = mb_cptr2char_adv(&s); - else + if (cv == Ctrl_V && s[1]) { + s++; + } + if (has_mbyte) { + c = mb_cptr2char_adv((const char_u **)&s); + } else { c = *s++; + } if (cv == Ctrl_V || c == ESC || c == Ctrl_C || c == CAR || c == NL || c == Ctrl_L -#ifdef UNIX - || c == intr_char -#endif - || (c == Ctrl_BSL && *s == Ctrl_N)) + || (c == Ctrl_BSL && *s == Ctrl_N)) { stuffcharReadbuff(Ctrl_V); + } stuffcharReadbuff(c); } } @@ -2549,10 +3405,9 @@ static void cmdline_del(int from) ccline.cmdpos = from; } -/* - * this function is called when the screen size changes and with incremental - * search - */ +// This function is called when the screen size changes and with incremental +// search and in other situations where the command line may have been +// overwritten. void redrawcmdline(void) { if (cmd_silent) @@ -2561,6 +3416,7 @@ void redrawcmdline(void) compute_cmdrow(); redrawcmd(); cursorcmd(); + ui_cursor_shape(); } static void redrawcmdprompt(void) @@ -2569,17 +3425,25 @@ static void redrawcmdprompt(void) if (cmd_silent) return; - if (ccline.cmdfirstc != NUL) + if (ui_is_external(kUICmdline)) { + ccline.redraw_state = kCmdRedrawAll; + return; + } + if (ccline.cmdfirstc != NUL) { msg_putchar(ccline.cmdfirstc); + } if (ccline.cmdprompt != NULL) { - msg_puts_attr(ccline.cmdprompt, ccline.cmdattr); + msg_puts_attr((const char *)ccline.cmdprompt, ccline.cmdattr); ccline.cmdindent = msg_col + (msg_row - cmdline_row) * Columns; - /* do the reverse of set_cmdspos() */ - if (ccline.cmdfirstc != NUL) - --ccline.cmdindent; - } else - for (i = ccline.cmdindent; i > 0; --i) + // do the reverse of set_cmdspos() + if (ccline.cmdfirstc != NUL) { + ccline.cmdindent--; + } + } else { + for (i = ccline.cmdindent; i > 0; i--) { msg_putchar(' '); + } + } } /* @@ -2590,6 +3454,11 @@ void redrawcmd(void) if (cmd_silent) return; + if (ui_is_external(kUICmdline)) { + draw_cmdline(0, ccline.cmdlen); + return; + } + /* when 'incsearch' is set there may be no command line while redrawing */ if (ccline.cmdbuff == NULL) { ui_cursor_goto(cmdline_row, 0); @@ -2633,6 +3502,14 @@ static void cursorcmd(void) if (cmd_silent) return; + if (ui_is_external(kUICmdline)) { + if (ccline.redraw_state < kCmdRedrawPos) { + ccline.redraw_state = kCmdRedrawPos; + } + setcursor(); + return; + } + if (cmdmsg_rl) { msg_row = cmdline_row + (ccline.cmdspos / (int)(Columns - 1)); msg_col = (int)Columns - (ccline.cmdspos % (int)(Columns - 1)) - 1; @@ -2650,6 +3527,9 @@ static void cursorcmd(void) void gotocmdline(int clr) { + if (ui_is_external(kUICmdline)) { + return; + } msg_start(); if (cmdmsg_rl) msg_col = Columns - 1; @@ -2668,10 +3548,28 @@ void gotocmdline(int clr) */ static int ccheck_abbr(int c) { - if (p_paste || no_abbr) /* no abbreviations or in paste mode */ - return FALSE; + int spos = 0; - return check_abbr(c, ccline.cmdbuff, ccline.cmdpos, 0); + if (p_paste || no_abbr) { // no abbreviations or in paste mode + return false; + } + + // Do not consider '<,'> be part of the mapping, skip leading whitespace. + // Actually accepts any mark. + while (ascii_iswhite(ccline.cmdbuff[spos]) && spos < ccline.cmdlen) { + spos++; + } + if (ccline.cmdlen - spos > 5 + && ccline.cmdbuff[spos] == '\'' + && ccline.cmdbuff[spos + 2] == ',' + && ccline.cmdbuff[spos + 3] == '\'') { + spos += 5; + } else { + // check abbreviation from the beginning of the commandline + spos = 0; + } + + return check_abbr(c, ccline.cmdbuff, ccline.cmdpos, spos); } static int sort_func_compare(const void *s1, const void *s2) @@ -2717,47 +3615,42 @@ nextwild ( return FAIL; } - MSG_PUTS("..."); /* show that we are busy */ - ui_flush(); + if (!ui_is_external(kUIWildmenu)) { + MSG_PUTS("..."); // show that we are busy + ui_flush(); + } i = (int)(xp->xp_pattern - ccline.cmdbuff); xp->xp_pattern_len = ccline.cmdpos - i; if (type == WILD_NEXT || type == WILD_PREV) { - /* - * Get next/previous match for a previous expanded pattern. - */ + // Get next/previous match for a previous expanded pattern. p2 = ExpandOne(xp, NULL, NULL, 0, type); } else { - /* - * Translate string into pattern and expand it. - */ - if ((p1 = addstar(xp->xp_pattern, xp->xp_pattern_len, - xp->xp_context)) == NULL) - p2 = NULL; - else { - int use_options = options | - WILD_HOME_REPLACE|WILD_ADD_SLASH|WILD_SILENT; - if (escape) - use_options |= WILD_ESCAPE; - - if (p_wic) - use_options += WILD_ICASE; - p2 = ExpandOne(xp, p1, - vim_strnsave(&ccline.cmdbuff[i], xp->xp_pattern_len), - use_options, type); - xfree(p1); - /* longest match: make sure it is not shorter, happens with :help */ - if (p2 != NULL && type == WILD_LONGEST) { - for (j = 0; j < xp->xp_pattern_len; ++j) - if (ccline.cmdbuff[i + j] == '*' - || ccline.cmdbuff[i + j] == '?') - break; - if ((int)STRLEN(p2) < j) { - xfree(p2); - p2 = NULL; + // Translate string into pattern and expand it. + p1 = addstar(xp->xp_pattern, xp->xp_pattern_len, xp->xp_context); + const int use_options = ( + options + | WILD_HOME_REPLACE + | WILD_ADD_SLASH + | WILD_SILENT + | (escape ? WILD_ESCAPE : 0) + | (p_wic ? WILD_ICASE : 0)); + p2 = ExpandOne(xp, p1, vim_strnsave(&ccline.cmdbuff[i], xp->xp_pattern_len), + use_options, type); + xfree(p1); + // Longest match: make sure it is not shorter, happens with :help. + if (p2 != NULL && type == WILD_LONGEST) { + for (j = 0; j < xp->xp_pattern_len; j++) { + if (ccline.cmdbuff[i + j] == '*' + || ccline.cmdbuff[i + j] == '?') { + break; } } + if ((int)STRLEN(p2) < j) { + xfree(p2); + p2 = NULL; + } } } @@ -2871,11 +3764,17 @@ ExpandOne ( else findex = -1; } - if (p_wmnu) - win_redr_status_matches(xp, xp->xp_numfiles, xp->xp_files, - findex, cmd_showtail); - if (findex == -1) + if (p_wmnu) { + if (ui_is_external(kUIWildmenu)) { + ui_call_wildmenu_select(findex); + } else { + win_redr_status_matches(xp, xp->xp_numfiles, xp->xp_files, + findex, cmd_showtail); + } + } + if (findex == -1) { return vim_strsave(orig_save); + } return vim_strsave(xp->xp_files[findex]); } else return NULL; @@ -2959,30 +3858,19 @@ ExpandOne ( // Find longest common part if (mode == WILD_LONGEST && xp->xp_numfiles > 0) { - size_t len; - size_t mb_len = 1; - int c0; - int ci; + size_t len = 0; - for (len = 0; xp->xp_files[0][len]; len += mb_len) { - if (has_mbyte) { - mb_len = (* mb_ptr2len)(&xp->xp_files[0][len]); - c0 = (* mb_ptr2char)(&xp->xp_files[0][len]); - } else { - c0 = xp->xp_files[0][len]; - } - for (i = 1; i < xp->xp_numfiles; ++i) { - if (has_mbyte) { - ci =(* mb_ptr2char)(&xp->xp_files[i][len]); - } else { - ci = xp->xp_files[i][len]; - } + for (size_t mb_len; xp->xp_files[0][len]; len += mb_len) { + mb_len = utfc_ptr2len(&xp->xp_files[0][len]); + int c0 = utf_ptr2char(&xp->xp_files[0][len]); + for (i = 1; i < xp->xp_numfiles; i++) { + int ci = utf_ptr2char(&xp->xp_files[i][len]); if (p_fic && (xp->xp_context == EXPAND_DIRECTORIES || xp->xp_context == EXPAND_FILES || xp->xp_context == EXPAND_SHELLCMD || xp->xp_context == EXPAND_BUFFERS)) { - if (vim_tolower(c0) != vim_tolower(ci)) { + if (mb_tolower(c0) != mb_tolower(ci)) { break; } } else if (c0 != ci) { @@ -3088,9 +3976,10 @@ void ExpandEscape(expand_T *xp, char_u *str, int numfiles, char_u **files, int o #endif } #ifdef BACKSLASH_IN_FILENAME - p = vim_strsave_fnameescape(files[i], FALSE); + p = (char_u *)vim_strsave_fnameescape((const char *)files[i], false); #else - p = vim_strsave_fnameescape(files[i], xp->xp_shell); + p = (char_u *)vim_strsave_fnameescape((const char *)files[i], + xp->xp_shell); #endif xfree(files[i]); files[i] = p; @@ -3120,42 +4009,49 @@ void ExpandEscape(expand_T *xp, char_u *str, int numfiles, char_u **files, int o } } -/* - * Escape special characters in "fname" for when used as a file name argument - * after a Vim command, or, when "shell" is non-zero, a shell command. - * Returns the result in allocated memory. - */ -char_u *vim_strsave_fnameescape(char_u *fname, int shell) FUNC_ATTR_NONNULL_RET +/// Escape special characters in a file name for use as a command argument +/// +/// @param[in] fname File name to escape. +/// @param[in] shell What to escape for: if false, escapes for VimL command, +/// if true then it escapes for a shell command. +/// +/// @return [allocated] escaped file name. +char *vim_strsave_fnameescape(const char *const fname, const bool shell) + FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL { - char_u *p; #ifdef BACKSLASH_IN_FILENAME -#define PATH_ESC_CHARS ((char_u *)" \t\n*?[{`%#'\"|!<") - char_u buf[20]; +#define PATH_ESC_CHARS " \t\n*?[{`%#'\"|!<" + char_u buf[sizeof(PATH_ESC_CHARS)]; int j = 0; - /* Don't escape '[', '{' and '!' if they are in 'isfname'. */ - for (p = PATH_ESC_CHARS; *p != NUL; ++p) - if ((*p != '[' && *p != '{' && *p != '!') || !vim_isfilec(*p)) - buf[j++] = *p; + // Don't escape '[', '{' and '!' if they are in 'isfname'. + for (const char *s = PATH_ESC_CHARS; *s != NUL; s++) { + if ((*s != '[' && *s != '{' && *s != '!') || !vim_isfilec(*s)) { + buf[j++] = *s; + } + } buf[j] = NUL; - p = vim_strsave_escaped(fname, buf); + char *p = (char *)vim_strsave_escaped((const char_u *)fname, + (const char_u *)buf); #else #define PATH_ESC_CHARS ((char_u *)" \t\n*?[{`$\\%#'\"|!<") #define SHELL_ESC_CHARS ((char_u *)" \t\n*?[{`$\\%#'\"|!<>();&") - p = vim_strsave_escaped(fname, shell ? SHELL_ESC_CHARS : PATH_ESC_CHARS); + char *p = (char *)vim_strsave_escaped( + (const char_u *)fname, (shell ? SHELL_ESC_CHARS : PATH_ESC_CHARS)); if (shell && csh_like_shell()) { - /* For csh and similar shells need to put two backslashes before '!'. - * One is taken by Vim, one by the shell. */ - char_u *s = vim_strsave_escaped(p, (char_u *)"!"); + // For csh and similar shells need to put two backslashes before '!'. + // One is taken by Vim, one by the shell. + char *s = (char *)vim_strsave_escaped((const char_u *)p, + (const char_u *)"!"); xfree(p); p = s; } #endif - /* '>' and '+' are special at the start of some commands, e.g. ":edit" and - * ":write". "cd -" has a special meaning. */ + // '>' and '+' are special at the start of some commands, e.g. ":edit" and + // ":write". "cd -" has a special meaning. if (*p == '>' || *p == '+' || (*p == '-' && p[1] == NUL)) { - escape_fname(&p); + escape_fname((char_u **)&p); } return p; @@ -3224,6 +4120,15 @@ static int showmatches(expand_T *xp, int wildmenu) showtail = cmd_showtail; } + if (ui_is_external(kUIWildmenu)) { + Array args = ARRAY_DICT_INIT; + for (i = 0; i < num_files; i++) { + ADD(args, STRING_OBJ(cstr_to_string((char *)files_found[i]))); + } + ui_call_wildmenu_show(args); + return EXPAND_OK; + } + if (!wildmenu) { msg_didany = FALSE; /* lines_left will be set */ msg_start(); /* prepare for paging */ @@ -3234,12 +4139,12 @@ static int showmatches(expand_T *xp, int wildmenu) msg_start(); /* prepare for paging */ } - if (got_int) - got_int = FALSE; /* only int. the completion, not the cmd line */ - else if (wildmenu) - win_redr_status_matches(xp, num_files, files_found, 0, showtail); - else { - /* find the length of the longest file name */ + if (got_int) { + got_int = false; // only int. the completion, not the cmd line + } else if (wildmenu) { + win_redr_status_matches(xp, num_files, files_found, -1, showtail); + } else { + // find the length of the longest file name maxlen = 0; for (i = 0; i < num_files; ++i) { if (!showtail && (xp->xp_context == EXPAND_FILES @@ -3264,13 +4169,13 @@ static int showmatches(expand_T *xp, int wildmenu) lines = (num_files + columns - 1) / columns; } - attr = hl_attr(HLF_D); /* find out highlighting for directories */ + attr = HL_ATTR(HLF_D); // find out highlighting for directories if (xp->xp_context == EXPAND_TAGS_LISTFILES) { - MSG_PUTS_ATTR(_("tagname"), hl_attr(HLF_T)); + MSG_PUTS_ATTR(_("tagname"), HL_ATTR(HLF_T)); msg_clr_eos(); msg_advance(maxlen - 3); - MSG_PUTS_ATTR(_(" kind file\n"), hl_attr(HLF_T)); + MSG_PUTS_ATTR(_(" kind file\n"), HL_ATTR(HLF_T)); } /* list the files line by line */ @@ -3278,12 +4183,12 @@ static int showmatches(expand_T *xp, int wildmenu) lastlen = 999; for (k = i; k < num_files; k += lines) { if (xp->xp_context == EXPAND_TAGS_LISTFILES) { - msg_outtrans_attr(files_found[k], hl_attr(HLF_D)); + msg_outtrans_attr(files_found[k], HL_ATTR(HLF_D)); p = files_found[k] + STRLEN(files_found[k]) + 1; msg_advance(maxlen + 1); - msg_puts(p); + msg_puts((const char *)p); msg_advance(maxlen + 3); - msg_puts_long_attr(p + 2, hl_attr(HLF_D)); + msg_puts_long_attr(p + 2, HL_ATTR(HLF_D)); break; } for (j = maxlen - lastlen; --j >= 0; ) @@ -3366,7 +4271,7 @@ char_u *sm_gettail(char_u *s) t = p; had_sep = FALSE; } - mb_ptr_adv(p); + MB_PTR_ADV(p); } return t; } @@ -3433,18 +4338,20 @@ addstar ( * use with vim_regcomp(). First work out how long it will be: */ - /* For help tags the translation is done in find_help_tags(). - * For a tag pattern starting with "/" no translation is needed. */ + // For help tags the translation is done in find_help_tags(). + // For a tag pattern starting with "/" no translation is needed. if (context == EXPAND_HELP + || context == EXPAND_CHECKHEALTH || context == EXPAND_COLORS || context == EXPAND_COMPILER || context == EXPAND_OWNSYNTAX || context == EXPAND_FILETYPE || context == EXPAND_PACKADD - || (context == EXPAND_TAGS && fname[0] == '/')) + || ((context == EXPAND_TAGS_LISTFILES || context == EXPAND_TAGS) + && fname[0] == '/')) { retval = vim_strnsave(fname, len); - else { - new_len = len + 2; /* +2 for '^' at start, NUL at end */ + } else { + new_len = len + 2; // +2 for '^' at start, NUL at end for (i = 0; i < len; i++) { if (fname[i] == '*' || fname[i] == '~') new_len++; /* '*' needs to be replaced by ".*" @@ -3579,19 +4486,19 @@ static void set_expand_context(expand_T *xp) xp->xp_context = EXPAND_NOTHING; return; } - set_cmd_context(xp, ccline.cmdbuff, ccline.cmdlen, ccline.cmdpos); + set_cmd_context(xp, ccline.cmdbuff, ccline.cmdlen, ccline.cmdpos, true); } void set_cmd_context ( expand_T *xp, - char_u *str, /* start of command line */ - int len, /* length of command line (excl. NUL) */ - int col /* position of cursor */ + char_u *str, // start of command line + int len, // length of command line (excl. NUL) + int col, // position of cursor + int use_ccline // use ccline for info ) { int old_char = NUL; - char_u *nextcomm; /* * Avoid a UMR warning from Purify, only save the character if it has been @@ -3600,18 +4507,20 @@ set_cmd_context ( if (col < len) old_char = str[col]; str[col] = NUL; - nextcomm = str; + const char *nextcomm = (const char *)str; - if (ccline.cmdfirstc == '=') { - /* pass CMD_SIZE because there is no real command */ + if (use_ccline && ccline.cmdfirstc == '=') { + // pass CMD_SIZE because there is no real command set_context_for_expression(xp, str, CMD_SIZE); - } else if (ccline.input_fn) { + } else if (use_ccline && ccline.input_fn) { xp->xp_context = ccline.xp_context; xp->xp_pattern = ccline.cmdbuff; xp->xp_arg = ccline.xp_arg; - } else - while (nextcomm != NULL) + } else { + while (nextcomm != NULL) { nextcomm = set_one_cmd_context(xp, nextcomm); + } + } /* Store the string here so that call_user_expand_func() can get to them * easily. */ @@ -3670,31 +4579,60 @@ expand_cmdline ( return EXPAND_OK; } -/* - * Cleanup matches for help tags: remove "@en" if "en" is the only language. - */ - +// Cleanup matches for help tags: +// Remove "@ab" if the top of 'helplang' is "ab" and the language of the first +// tag matches it. Otherwise remove "@en" if "en" is the only language. static void cleanup_help_tags(int num_file, char_u **file) { - int i, j; - int len; + char_u buf[4]; + char_u *p = buf; + + if (p_hlg[0] != NUL && (p_hlg[0] != 'e' || p_hlg[1] != 'n')) { + *p++ = '@'; + *p++ = p_hlg[0]; + *p++ = p_hlg[1]; + } + *p = NUL; - for (i = 0; i < num_file; ++i) { - len = (int)STRLEN(file[i]) - 3; - if (len > 0 && STRCMP(file[i] + len, "@en") == 0) { - /* Sorting on priority means the same item in another language may - * be anywhere. Search all items for a match up to the "@en". */ - for (j = 0; j < num_file; ++j) + for (int i = 0; i < num_file; i++) { + int len = (int)STRLEN(file[i]) - 3; + if (len <= 0) { + continue; + } + if (STRCMP(file[i] + len, "@en") == 0) { + // Sorting on priority means the same item in another language may + // be anywhere. Search all items for a match up to the "@en". + int j; + for (j = 0; j < num_file; j++) { if (j != i && (int)STRLEN(file[j]) == len + 3 - && STRNCMP(file[i], file[j], len + 1) == 0) + && STRNCMP(file[i], file[j], len + 1) == 0) { break; - if (j == num_file) + } + } + if (j == num_file) { + // item only exists with @en, remove it file[i][len] = NUL; + } + } + } + + if (*buf != NUL) { + for (int i = 0; i < num_file; i++) { + int len = (int)STRLEN(file[i]) - 3; + if (len <= 0) { + continue; + } + if (STRCMP(file[i] + len, buf) == 0) { + // remove the default language + file[i][len] = NUL; + } } } } +typedef char_u *(*ExpandFunc)(expand_T *, int); + /* * Do the expansion based on xp->xp_context and "pat". */ @@ -3773,7 +4711,7 @@ ExpandFromContext ( /* With an empty argument we would get all the help tags, which is * very slow. Get matches for "help" instead. */ if (find_help_tags(*pat == NUL ? (char_u *)"help" : pat, - num_file, file, FALSE) == OK) { + num_file, file, false) == OK) { cleanup_help_tags(*num_file, *file); return OK; } @@ -3810,6 +4748,10 @@ ExpandFromContext ( char *directories[] = { "syntax", "indent", "ftplugin", NULL }; return ExpandRTDir(pat, 0, num_file, file, directories); } + if (xp->xp_context == EXPAND_CHECKHEALTH) { + char *directories[] = { "autoload/health", NULL }; + return ExpandRTDir(pat, 0, num_file, file, directories); + } if (xp->xp_context == EXPAND_USER_LIST) { return ExpandUserList(xp, num_file, file); } @@ -3834,39 +4776,41 @@ ExpandFromContext ( else { static struct expgen { int context; - char_u *((*func)(expand_T *, int)); + ExpandFunc func; int ic; int escaped; - } tab[] = - { - {EXPAND_COMMANDS, get_command_name, FALSE, TRUE}, - {EXPAND_BEHAVE, get_behave_arg, TRUE, TRUE}, - {EXPAND_HISTORY, get_history_arg, TRUE, TRUE}, - {EXPAND_USER_COMMANDS, get_user_commands, FALSE, TRUE}, - {EXPAND_USER_ADDR_TYPE, get_user_cmd_addr_type, FALSE, TRUE}, - {EXPAND_USER_CMD_FLAGS, get_user_cmd_flags, FALSE, TRUE}, - {EXPAND_USER_NARGS, get_user_cmd_nargs, FALSE, TRUE}, - {EXPAND_USER_COMPLETE, get_user_cmd_complete, FALSE, TRUE}, - {EXPAND_USER_VARS, get_user_var_name, FALSE, TRUE}, - {EXPAND_FUNCTIONS, get_function_name, FALSE, TRUE}, - {EXPAND_USER_FUNC, get_user_func_name, FALSE, TRUE}, - {EXPAND_EXPRESSION, get_expr_name, FALSE, TRUE}, - {EXPAND_MENUS, get_menu_name, FALSE, TRUE}, - {EXPAND_MENUNAMES, get_menu_names, FALSE, TRUE}, - {EXPAND_SYNTAX, get_syntax_name, TRUE, TRUE}, - {EXPAND_SYNTIME, get_syntime_arg, TRUE, TRUE}, - {EXPAND_HIGHLIGHT, get_highlight_name, TRUE, TRUE}, - {EXPAND_EVENTS, get_event_name, TRUE, TRUE}, - {EXPAND_AUGROUP, get_augroup_name, TRUE, TRUE}, - {EXPAND_CSCOPE, get_cscope_name, TRUE, TRUE}, - {EXPAND_SIGN, get_sign_name, TRUE, TRUE}, - {EXPAND_PROFILE, get_profile_name, TRUE, TRUE}, + } tab[] = { + { EXPAND_COMMANDS, get_command_name, false, true }, + { EXPAND_BEHAVE, get_behave_arg, true, true }, + { EXPAND_MAPCLEAR, get_mapclear_arg, true, true }, + { EXPAND_MESSAGES, get_messages_arg, true, true }, + { EXPAND_HISTORY, get_history_arg, true, true }, + { EXPAND_USER_COMMANDS, get_user_commands, false, true }, + { EXPAND_USER_ADDR_TYPE, get_user_cmd_addr_type, false, true }, + { EXPAND_USER_CMD_FLAGS, get_user_cmd_flags, false, true }, + { EXPAND_USER_NARGS, get_user_cmd_nargs, false, true }, + { EXPAND_USER_COMPLETE, get_user_cmd_complete, false, true }, + { EXPAND_USER_VARS, get_user_var_name, false, true }, + { EXPAND_FUNCTIONS, get_function_name, false, true }, + { EXPAND_USER_FUNC, get_user_func_name, false, true }, + { EXPAND_EXPRESSION, get_expr_name, false, true }, + { EXPAND_MENUS, get_menu_name, false, true }, + { EXPAND_MENUNAMES, get_menu_names, false, true }, + { EXPAND_SYNTAX, get_syntax_name, true, true }, + { EXPAND_SYNTIME, get_syntime_arg, true, true }, + { EXPAND_HIGHLIGHT, (ExpandFunc)get_highlight_name, true, true }, + { EXPAND_EVENTS, get_event_name, true, true }, + { EXPAND_AUGROUP, get_augroup_name, true, true }, + { EXPAND_CSCOPE, get_cscope_name, true, true }, + { EXPAND_SIGN, get_sign_name, true, true }, + { EXPAND_PROFILE, get_profile_name, true, true }, #ifdef HAVE_WORKING_LIBINTL - {EXPAND_LANGUAGE, get_lang_arg, TRUE, FALSE}, - {EXPAND_LOCALES, get_locales, TRUE, FALSE}, + { EXPAND_LANGUAGE, get_lang_arg, true, false }, + { EXPAND_LOCALES, get_locales, true, false }, #endif - {EXPAND_ENV_VARS, get_env_name, TRUE, TRUE}, - {EXPAND_USER, get_users, TRUE, FALSE}, + { EXPAND_ENV_VARS, get_env_name, true, true }, + { EXPAND_USER, get_users, true, false }, + { EXPAND_ARGLIST, get_arglist_name, true, false }, }; int i; @@ -3930,23 +4874,27 @@ void ExpandGeneric( // copy the matching names into allocated memory count = 0; - for (i = 0;; ++i) { + for (i = 0;; i++) { str = (*func)(xp, i); - if (str == NULL) // end of list + if (str == NULL) { // End of list. break; - if (*str == NUL) // skip empty strings + } + if (*str == NUL) { // Skip empty strings. continue; + } if (vim_regexec(regmatch, str, (colnr_T)0)) { - if (escaped) + if (escaped) { str = vim_strsave_escaped(str, (char_u *)" \t\\."); - else + } else { str = vim_strsave(str); + } (*file)[count++] = str; - if (func == get_menu_names && str != NULL) { - /* test for separator added by get_menu_names() */ + if (func == get_menu_names) { + // Test for separator added by get_menu_names(). str += STRLEN(str) - 1; - if (*str == '\001') + if (*str == '\001') { *str = '.'; + } } } } @@ -3978,7 +4926,7 @@ void ExpandGeneric( /// @param flagsarg is a combination of EW_* flags. static void expand_shellcmd(char_u *filepat, int *num_file, char_u ***file, int flagsarg) - FUNC_ATTR_NONNULL_ALL + FUNC_ATTR_NONNULL_ALL { char_u *pat; int i; @@ -4001,13 +4949,14 @@ static void expand_shellcmd(char_u *filepat, int *num_file, char_u ***file, flags |= EW_FILE | EW_EXEC | EW_SHELLCMD; bool mustfree = false; // Track memory allocation for *path. - /* For an absolute name we don't use $PATH. */ - if (path_is_absolute_path(pat)) + // For an absolute name we don't use $PATH. + if (path_is_absolute(pat)) { path = (char_u *)" "; - else if ((pat[0] == '.' && (vim_ispathsep(pat[1]) - || (pat[1] == '.' && vim_ispathsep(pat[2]))))) + } else if (pat[0] == '.' && (vim_ispathsep(pat[1]) + || (pat[1] == '.' + && vim_ispathsep(pat[2])))) { path = (char_u *)"."; - else { + } else { path = (char_u *)vim_getenv("PATH"); if (path == NULL) { path = (char_u *)""; @@ -4079,10 +5028,8 @@ static void expand_shellcmd(char_u *filepat, int *num_file, char_u ***file, } } -/* - * Call "user_expand_func()" to invoke a user defined VimL function and return - * the result (either a string or a List). - */ +/// Call "user_expand_func()" to invoke a user defined Vim script function and +/// return the result (either a string or a List). static void * call_user_expand_func(user_expand_func_T user_expand_func, expand_T *xp, int *num_file, char_u ***file) { @@ -4114,7 +5061,10 @@ static void * call_user_expand_func(user_expand_func_T user_expand_func, ccline.cmdprompt = NULL; current_SID = xp->xp_scriptID; - ret = user_expand_func(xp->xp_arg, 3, args, FALSE); + ret = user_expand_func(xp->xp_arg, + 3, + (const char_u * const *)args, + false); ccline = save_ccline; current_SID = save_current_SID; @@ -4130,36 +5080,34 @@ static void * call_user_expand_func(user_expand_func_T user_expand_func, */ static int ExpandUserDefined(expand_T *xp, regmatch_T *regmatch, int *num_file, char_u ***file) { - char_u *retstr; - char_u *s; - char_u *e; - char_u keep; - garray_T ga; + char_u *e; + garray_T ga; + + char_u *const retstr = call_user_expand_func( + (user_expand_func_T)call_func_retstr, xp, num_file, file); - retstr = call_user_expand_func(call_func_retstr, xp, num_file, file); - if (retstr == NULL) + if (retstr == NULL) { return FAIL; + } ga_init(&ga, (int)sizeof(char *), 3); - for (s = retstr; *s != NUL; s = e) { + for (char_u *s = retstr; *s != NUL; s = e) { e = vim_strchr(s, '\n'); if (e == NULL) e = s + STRLEN(s); - keep = *e; - *e = 0; + const int keep = *e; + *e = NUL; - if (xp->xp_pattern[0] && vim_regexec(regmatch, s, (colnr_T)0) == 0) { - *e = keep; - if (*e != NUL) - ++e; - continue; + const bool skip = xp->xp_pattern[0] + && vim_regexec(regmatch, s, (colnr_T)0) == 0; + *e = keep; + if (!skip) { + GA_APPEND(char_u *, &ga, vim_strnsave(s, (int)(e - s))); } - GA_APPEND(char_u *, &ga, vim_strnsave(s, (int)(e - s))); - - *e = keep; - if (*e != NUL) - ++e; + if (*e != NUL) { + e++; + } } xfree(retstr); *file = ga.ga_data; @@ -4172,23 +5120,25 @@ static int ExpandUserDefined(expand_T *xp, regmatch_T *regmatch, int *num_file, */ static int ExpandUserList(expand_T *xp, int *num_file, char_u ***file) { - list_T *retlist; - listitem_T *li; - garray_T ga; - - retlist = call_user_expand_func(call_func_retlist, xp, num_file, file); - if (retlist == NULL) + list_T *const retlist = call_user_expand_func( + (user_expand_func_T)call_func_retlist, xp, num_file, file); + if (retlist == NULL) { return FAIL; + } + garray_T ga; ga_init(&ga, (int)sizeof(char *), 3); - /* Loop over the items in the list. */ - for (li = retlist->lv_first; li != NULL; li = li->li_next) { - if (li->li_tv.v_type != VAR_STRING || li->li_tv.vval.v_string == NULL) - continue; /* Skip non-string items and empty strings */ + // Loop over the items in the list. + TV_LIST_ITER_CONST(retlist, li, { + if (TV_LIST_ITEM_TV(li)->v_type != VAR_STRING + || TV_LIST_ITEM_TV(li)->vval.v_string == NULL) { + continue; // Skip non-string items and empty strings. + } - GA_APPEND(char_u *, &ga, vim_strsave(li->li_tv.vval.v_string)); - } - list_unref(retlist); + GA_APPEND(char *, &ga, xstrdup( + (const char *)TV_LIST_ITEM_TV(li)->vval.v_string)); + }); + tv_list_unref(retlist); *file = ga.ga_data; *num_file = ga.ga_len; @@ -4247,7 +5197,7 @@ static int ExpandRTDir(char_u *pat, int flags, int *num_file, char_u ***file, char_u *e = s + STRLEN(s); if (e - s > 4 && STRNICMP(e - 4, ".vim", 4) == 0) { e -= 4; - for (s = e; s > match; mb_ptr_back(match, s)) { + for (s = e; s > match; MB_PTR_BACK(match, s)) { if (vim_ispathsep(*s)) { break; } @@ -4371,6 +5321,7 @@ static HistoryType hist_char2type(const int c) case '>': { return HIST_DEBUG; } + case NUL: case '/': case '?': { return HIST_SEARCH; @@ -4483,7 +5434,7 @@ static inline void hist_free_entry(histentry_T *hisptr) FUNC_ATTR_NONNULL_ALL { xfree(hisptr->hisstr); - list_unref(hisptr->additional_elements); + tv_list_unref(hisptr->additional_elements); clear_hist_entry(hisptr); } @@ -4539,7 +5490,7 @@ in_history ( history[type][last_i] = history[type][i]; last_i = i; } - list_unref(list); + tv_list_unref(list); history[type][i].hisnum = ++hisnum[type]; history[type][i].hisstr = str; history[type][i].timestamp = os_time(); @@ -4561,7 +5512,7 @@ in_history ( /// /// @return Any value from HistoryType enum, including HIST_INVALID. May not /// return HIST_DEFAULT unless return_default is true. -HistoryType get_histtype(const char_u *const name, const size_t len, +HistoryType get_histtype(const char *const name, const size_t len, const bool return_default) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { @@ -4615,8 +5566,8 @@ add_to_history ( * down, only lines that were added. */ if (histype == HIST_SEARCH && in_map) { - if (maptick == last_maptick) { - /* Current line is from the same mapping, remove it */ + if (maptick == last_maptick && hisidx[HIST_SEARCH] >= 0) { + // Current line is from the same mapping, remove it hisptr = &history[HIST_SEARCH][hisidx[HIST_SEARCH]]; hist_free_entry(hisptr); --hisnum[histype]; @@ -4665,13 +5616,15 @@ int get_history_idx(int histype) */ static struct cmdline_info *get_ccline_ptr(void) { - if ((State & CMDLINE) == 0) + if ((State & CMDLINE) == 0) { return NULL; - if (ccline.cmdbuff != NULL) + } else if (ccline.cmdbuff != NULL) { return &ccline; - if (prev_ccline_used && prev_ccline.cmdbuff != NULL) - return &prev_ccline; - return NULL; + } else if (ccline.prev_ccline && ccline.prev_ccline->cmdbuff != NULL) { + return ccline.prev_ccline; + } else { + return NULL; + } } /* @@ -4681,6 +5634,9 @@ static struct cmdline_info *get_ccline_ptr(void) */ char_u *get_cmdline_str(void) { + if (cmdline_star > 0) { + return NULL; + } struct cmdline_info *p = get_ccline_ptr(); if (p == NULL) @@ -4904,7 +5860,7 @@ int get_list_range(char_u **str, int *num1, int *num2) { int len; int first = false; - long num; + varnumber_T num; *str = skipwhite(*str); if (**str == '-' || ascii_isdigit(**str)) { // parse "from" part of range @@ -4954,7 +5910,7 @@ void ex_history(exarg_T *eap) while (ASCII_ISALPHA(*end) || vim_strchr((char_u *)":=@>/?", *end) != NULL) end++; - histype1 = get_histtype(arg, end - arg, false); + histype1 = get_histtype((const char *)arg, end - arg, false); if (histype1 == HIST_INVALID) { if (STRNICMP(arg, "all", end - arg) == 0) { histype1 = 0; @@ -4975,6 +5931,7 @@ void ex_history(exarg_T *eap) for (; !got_int && histype1 <= histype2; ++histype1) { STRCPY(IObuff, "\n # "); + assert(history_names[histype1] != NULL); STRCAT(STRCAT(IObuff, history_names[histype1]), " history"); MSG_PUTS_TITLE(IObuff); idx = hisidx[histype1]; @@ -5066,12 +6023,12 @@ int cmd_gchar(int offset) * Ctrl_C if it is to be abandoned * K_IGNORE if editing continues */ -static int ex_window(void) +static int open_cmdwin(void) { struct cmdline_info save_ccline; - buf_T *old_curbuf = curbuf; + bufref_T old_curbuf; + bufref_T bufref; win_T *old_curwin = curwin; - buf_T *bp; win_T *wp; int i; linenr_T lnum; @@ -5090,6 +6047,8 @@ static int ex_window(void) return K_IGNORE; } + set_bufref(&old_curbuf, curbuf); + /* Save current window sizes. */ win_size_save(&winsizes); @@ -5097,6 +6056,7 @@ static int ex_window(void) block_autocmds(); /* don't use a new tab page */ cmdmod.tab = 0; + cmdmod.noswapfile = 1; /* Create a window for the command-line buffer. */ if (win_split((int)p_cwh, WSP_BOT) == FAIL) { @@ -5105,20 +6065,21 @@ static int ex_window(void) return K_IGNORE; } cmdwin_type = get_cmdline_type(); + cmdwin_level = ccline.level; - /* Create the command-line buffer empty. */ - (void)do_ecmd(0, NULL, NULL, NULL, ECMD_ONE, ECMD_HIDE, NULL); - (void)setfname(curbuf, (char_u *)"[Command Line]", NULL, TRUE); - set_option_value((char_u *)"bt", 0L, (char_u *)"nofile", OPT_LOCAL); - set_option_value((char_u *)"swf", 0L, NULL, OPT_LOCAL); - curbuf->b_p_ma = TRUE; - curwin->w_p_fen = FALSE; + // Create empty command-line buffer. + buf_open_scratch(0, "[Command Line]"); + // Command-line buffer has bufhidden=wipe, unlike a true "scratch" buffer. + set_option_value("bh", 0L, "wipe", OPT_LOCAL); curwin->w_p_rl = cmdmsg_rl; - cmdmsg_rl = FALSE; - RESET_BINDING(curwin); + cmdmsg_rl = false; + curbuf->b_p_ma = true; + curwin->w_p_fen = false; - /* Do execute autocommands for setting the filetype (load syntax). */ + // Do execute autocommands for setting the filetype (load syntax). unblock_autocmds(); + // But don't allow switching to another buffer. + curbuf_lock++; /* Showing the prompt may have set need_wait_return, reset it. */ need_wait_return = FALSE; @@ -5129,8 +6090,9 @@ static int ex_window(void) add_map((char_u *)"<buffer> <Tab> <C-X><C-V>", INSERT); add_map((char_u *)"<buffer> <Tab> a<C-X><C-V>", NORMAL); } - set_option_value((char_u *)"ft", 0L, (char_u *)"vim", OPT_LOCAL); + set_option_value("ft", 0L, "vim", OPT_LOCAL); } + curbuf_lock--; /* Reset 'textwidth' after setting 'filetype' (the Vim filetype plugin * sets 'textwidth' to 78). */ @@ -5154,17 +6116,19 @@ static int ex_window(void) /* Replace the empty last line with the current command-line and put the * cursor there. */ - ml_replace(curbuf->b_ml.ml_line_count, ccline.cmdbuff, TRUE); + ml_replace(curbuf->b_ml.ml_line_count, ccline.cmdbuff, true); curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; curwin->w_cursor.col = ccline.cmdpos; changed_line_abv_curs(); invalidate_botline(); + if (ui_is_external(kUICmdline)) { + ccline.redraw_state = kCmdRedrawNone; + ui_call_cmdline_hide(ccline.level); + } redraw_later(SOME_VALID); - /* Save the command line info, can be used recursively. */ - save_ccline = ccline; - ccline.cmdbuff = NULL; - ccline.cmdprompt = NULL; + // Save the command line info, can be used recursively. + save_cmdline(&save_ccline); /* No Ex mode here! */ exmode_active = 0; @@ -5181,6 +6145,7 @@ static int ex_window(void) i = RedrawingDisabled; RedrawingDisabled = 0; + int save_count = save_batch_count(); /* * Call the main loop until <CR> or CTRL-C is typed. @@ -5189,8 +6154,9 @@ static int ex_window(void) normal_enter(true, false); RedrawingDisabled = i; + restore_batch_count(save_count); - int save_KeyTyped = KeyTyped; + const bool save_KeyTyped = KeyTyped; /* Trigger CmdwinLeave autocommands. */ apply_autocmds(EVENT_CMDWINLEAVE, typestr, typestr, FALSE, curbuf); @@ -5198,15 +6164,16 @@ static int ex_window(void) /* Restore KeyTyped in case it is modified by autocommands */ KeyTyped = save_KeyTyped; - /* Restore the command line info. */ - ccline = save_ccline; + // Restore the command line info. + restore_cmdline(&save_ccline); cmdwin_type = 0; + cmdwin_level = 0; exmode_active = save_exmode; /* Safety check: The old window or buffer was deleted: It's a bug when * this happens! */ - if (!win_valid(old_curwin) || !buf_valid(old_curbuf)) { + if (!win_valid(old_curwin) || !bufref_valid(&old_curbuf)) { cmdwin_result = Ctrl_C; EMSG(_("E199: Active window or buffer deleted")); } else { @@ -5215,32 +6182,33 @@ static int ex_window(void) cmdwin_result = Ctrl_C; /* Set the new command line from the cmdline buffer. */ xfree(ccline.cmdbuff); - if (cmdwin_result == K_XF1 || cmdwin_result == K_XF2) { /* :qa[!] typed */ - char *p = (cmdwin_result == K_XF2) ? "qa" : "qa!"; + if (cmdwin_result == K_XF1 || cmdwin_result == K_XF2) { // :qa[!] typed + const char *p = (cmdwin_result == K_XF2) ? "qa" : "qa!"; if (histtype == HIST_CMD) { - /* Execute the command directly. */ - ccline.cmdbuff = vim_strsave((char_u *)p); + // Execute the command directly. + ccline.cmdbuff = (char_u *)xstrdup(p); cmdwin_result = CAR; } else { - /* First need to cancel what we were doing. */ + // First need to cancel what we were doing. ccline.cmdbuff = NULL; stuffcharReadbuff(':'); - stuffReadbuff((char_u *)p); + stuffReadbuff(p); stuffcharReadbuff(CAR); } - } else if (cmdwin_result == K_XF2) { /* :qa typed */ - ccline.cmdbuff = vim_strsave((char_u *)"qa"); - cmdwin_result = CAR; } else if (cmdwin_result == Ctrl_C) { /* :q or :close, don't execute any command * and don't modify the cmd window. */ ccline.cmdbuff = NULL; } else ccline.cmdbuff = vim_strsave(get_cursor_line_ptr()); - if (ccline.cmdbuff == NULL) + if (ccline.cmdbuff == NULL) { + ccline.cmdbuff = vim_strsave((char_u *)""); + ccline.cmdlen = 0; + ccline.cmdbufflen = 1; + ccline.cmdpos = 0; cmdwin_result = Ctrl_C; - else { + } else { ccline.cmdlen = (int)STRLEN(ccline.cmdbuff); ccline.cmdbufflen = ccline.cmdlen + 1; ccline.cmdpos = curwin->w_cursor.col; @@ -5257,14 +6225,15 @@ static int ex_window(void) // Avoid command-line window first character being concealed curwin->w_p_cole = 0; wp = curwin; - bp = curbuf; + set_bufref(&bufref, curbuf); win_goto(old_curwin); - win_close(wp, TRUE); + win_close(wp, true); - /* win_close() may have already wiped the buffer when 'bh' is - * set to 'wipe' */ - if (buf_valid(bp)) - close_buffer(NULL, bp, DOBUF_WIPE, FALSE); + // win_close() may have already wiped the buffer when 'bh' is + // set to 'wipe'. + if (bufref_valid(&bufref)) { + close_buffer(NULL, bufref.br_buf, DOBUF_WIPE, false); + } /* Restore window sizes. */ win_size_restore(&winsizes); @@ -5282,47 +6251,61 @@ static int ex_window(void) return cmdwin_result; } -/* - * Used for commands that either take a simple command string argument, or: - * cmd << endmarker - * {script} - * endmarker - * Returns a pointer to allocated memory with {script} or NULL. - */ -char_u *script_get(exarg_T *eap, char_u *cmd) +/// Get script string +/// +/// Used for commands which accept either `:command script` or +/// +/// :command << endmarker +/// script +/// endmarker +/// +/// @param eap Command being run. +/// @param[out] lenp Location where length of resulting string is saved. Will +/// be set to zero when skipping. +/// +/// @return [allocated] NULL or script. Does not show any error messages. +/// NULL is returned when skipping and on error. +char *script_get(exarg_T *const eap, size_t *const lenp) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_MALLOC { - char_u *theline; - char *end_pattern = NULL; - char dot[] = "."; - garray_T ga; + const char *const cmd = (const char *)eap->arg; - if (cmd[0] != '<' || cmd[1] != '<' || eap->getline == NULL) - return NULL; - - ga_init(&ga, 1, 0x400); + if (cmd[0] != '<' || cmd[1] != '<' || eap->getline == NULL) { + *lenp = STRLEN(eap->arg); + return eap->skip ? NULL : xmemdupz(eap->arg, *lenp); + } - if (cmd[2] != NUL) - end_pattern = (char *)skipwhite(cmd + 2); - else - end_pattern = dot; + garray_T ga = { .ga_data = NULL, .ga_len = 0 }; + if (!eap->skip) { + ga_init(&ga, 1, 0x400); + } - for (;; ) { - theline = eap->getline( + const char *const end_pattern = ( + cmd[2] != NUL + ? (const char *)skipwhite((const char_u *)cmd + 2) + : "."); + for (;;) { + char *const theline = (char *)eap->getline( eap->cstack->cs_looplevel > 0 ? -1 : NUL, eap->cookie, 0); - if (theline == NULL || STRCMP(end_pattern, theline) == 0) { + if (theline == NULL || strcmp(end_pattern, theline) == 0) { xfree(theline); break; } - ga_concat(&ga, theline); - ga_append(&ga, '\n'); + if (!eap->skip) { + ga_concat(&ga, (const char_u *)theline); + ga_append(&ga, '\n'); + } xfree(theline); } - ga_append(&ga, NUL); + *lenp = (size_t)ga.ga_len; // Set length without trailing NUL. + if (!eap->skip) { + ga_append(&ga, NUL); + } - return (char_u *)ga.ga_data; + return (char *)ga.ga_data; } /// Iterate over history items @@ -5408,3 +6391,15 @@ histentry_T *hist_get_array(const uint8_t history_type, int **const new_hisidx, *new_hisnum = &(hisnum[history_type]); return history[history_type]; } + +static void set_search_match(pos_T *t) +{ + // First move cursor to end of match, then to the start. This + // moves the whole match onto the screen when 'nowrap' is set. + t->lnum += search_match_lines; + t->col = search_match_endcol; + if (t->lnum > curbuf->b_ml.ml_line_count) { + t->lnum = curbuf->b_ml.ml_line_count; + coladvance((colnr_T)MAXCOL); + } +} |