diff options
Diffstat (limited to 'src/nvim/ex_getln.c')
-rw-r--r-- | src/nvim/ex_getln.c | 385 |
1 files changed, 291 insertions, 94 deletions
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 13cfd76adf..ad3ccf2a99 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -11,7 +11,9 @@ #include <stdlib.h> #include <string.h> +#include "nvim/api/extmark.h" #include "nvim/api/private/helpers.h" +#include "nvim/api/vim.h" #include "nvim/arabic.h" #include "nvim/ascii.h" #include "nvim/assert.h" @@ -69,6 +71,7 @@ #include "nvim/syntax.h" #include "nvim/tag.h" #include "nvim/ui.h" +#include "nvim/undo.h" #include "nvim/vim.h" #include "nvim/viml/parser/expressions.h" #include "nvim/viml/parser/parser.h" @@ -230,27 +233,13 @@ static int compl_match_arraysize; static int compl_startcol; static int compl_selected; -/// |:checkhealth| completion items -/// -/// Regenerates on every new command line prompt, to accommodate changes on the -/// runtime files. -typedef struct { - garray_T names; // healthcheck names - unsigned last_gen; // last_prompt_id where names were generated -} CheckhealthComp; - -/// Cookie used when converting filepath to name -struct healthchecks_cookie { - garray_T *names; // global healthchecks - bool is_lua; // true if the current entry is a Lua healthcheck -}; - -static CheckhealthComp healthchecks = { GA_INIT(sizeof(char_u *), 10), 0 }; - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ex_getln.c.generated.h" #endif +static handle_T cmdpreview_bufnr = 0; +static long cmdpreview_ns = 0; + static int cmd_hkmap = 0; // Hebrew mapping during command line static void save_viewstate(viewstate_T *vs) @@ -295,59 +284,23 @@ static void init_incsearch_state(incsearch_state_T *s) /// @param[in] xp Not used. static char *get_healthcheck_names(expand_T *xp, int idx) { - // Generate the first time or on new prompt. - if (healthchecks.last_gen == 0 || healthchecks.last_gen != last_prompt_id) { - ga_clear_strings(&healthchecks.names); - char *patterns[3] = { "autoload/health/**.vim", "lua/**/**/health/init.lua", // NOLINT - "lua/**/**/health.lua" }; // NOLINT - for (int i = 0; i < 3; i++) { - struct healthchecks_cookie hcookie = { .names = &healthchecks.names, .is_lua = i != 0 }; - do_in_runtimepath(patterns[i], DIP_ALL, get_healthcheck_cb, &hcookie); - - if (healthchecks.names.ga_len > 0) { - ga_remove_duplicate_strings(&healthchecks.names); - } - } - // Tracked to regenerate items on next prompt. - healthchecks.last_gen = last_prompt_id; - } - return idx < healthchecks.names.ga_len - ? ((char **)(healthchecks.names.ga_data))[idx] : NULL; -} - -/// Transform healthcheck file path into it's name. -/// -/// Used as a callback for do_in_runtimepath -/// @param[in] path Expanded path to a possible healthcheck. -/// @param[out] cookie Array where names will be inserted. -static void get_healthcheck_cb(char *path, void *cookie) -{ - if (path != NULL) { - struct healthchecks_cookie *hcookie = (struct healthchecks_cookie *)cookie; - char *pattern; - char *sub = "\\1"; - char *res; - - if (hcookie->is_lua) { - // Lua: transform "../lua/vim/lsp/health.lua" into "vim.lsp" - pattern = ".*lua[\\/]\\(.\\{-}\\)[\\/]health\\([\\/]init\\)\\?\\.lua$"; - } else { - // Vim: transform "../autoload/health/provider.vim" into "provider" - pattern = ".*[\\/]\\([^\\/]*\\)\\.vim$"; - } + static Object names = OBJECT_INIT; + static unsigned last_gen = 0; - res = do_string_sub(path, pattern, sub, NULL, "g"); - if (hcookie->is_lua && res != NULL) { - // Replace slashes with dots as represented by the healthcheck plugin. - char *ares = do_string_sub(res, "[\\/]", ".", NULL, "g"); - xfree(res); - res = ares; - } + if (last_gen != last_prompt_id || last_gen == 0) { + Array a = ARRAY_DICT_INIT; + Error err = ERROR_INIT; + Object res = nlua_exec(STATIC_CSTR_AS_STRING("return vim.health._complete()"), a, &err); + api_clear_error(&err); + api_free_object(names); + names = res; + last_gen = last_prompt_id; + } - if (res != NULL) { - GA_APPEND(char *, hcookie->names, res); - } + if (names.type == kObjectTypeArray && idx < (int)names.data.array.size) { + return names.data.array.items[idx].data.string.data; } + return NULL; } // Return true when 'incsearch' highlighting is to be done. @@ -740,6 +693,8 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool init static int cmdline_level = 0; cmdline_level++; + bool save_cmdpreview = cmdpreview; + cmdpreview = false; CommandLineState state = { .firstc = firstc, .count = count, @@ -951,11 +906,6 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool init ExpandCleanup(&s->xpc); ccline.xpc = NULL; - if (s->gotesc) { - // There might be a preview window open for inccommand. Close it. - close_preview_windows(); - } - finish_incsearch_highlighting(s->gotesc, &s->is_state, false); if (ccline.cmdbuff != NULL) { @@ -998,6 +948,10 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool init set_string_option_direct("icm", -1, s->save_p_icm, OPT_FREE, SID_NONE); State = s->save_State; + if (cmdpreview != save_cmdpreview) { + cmdpreview = save_cmdpreview; // restore preview state + redraw_all_later(SOME_VALID); + } setmouse(); ui_cursor_shape(); // may show different cursor shape sb_text_end_cmdline(); @@ -2306,6 +2260,268 @@ static int empty_pattern(char_u *p) return n == 0 || (n >= 2 && p[n - 2] == '\\' && p[n - 1] == '|'); } +handle_T cmdpreview_get_bufnr(void) +{ + return cmdpreview_bufnr; +} + +long cmdpreview_get_ns(void) +{ + return cmdpreview_ns; +} + +/// Sets up command preview buffer. +/// +/// @return Pointer to command preview buffer if succeeded, NULL if failed. +static buf_T *cmdpreview_open_buf(void) +{ + buf_T *cmdpreview_buf = cmdpreview_bufnr ? buflist_findnr(cmdpreview_bufnr) : NULL; + + // If preview buffer doesn't exist, open one. + if (cmdpreview_buf == NULL) { + Error err = ERROR_INIT; + handle_T bufnr = nvim_create_buf(false, true, &err); + + if (ERROR_SET(&err)) { + return NULL; + } + + cmdpreview_buf = buflist_findnr(bufnr); + } + + // Preview buffer cannot preview itself! + if (cmdpreview_buf == curbuf) { + return NULL; + } + + // Rename preview buffer. + aco_save_T aco; + aucmd_prepbuf(&aco, cmdpreview_buf); + int retv = rename_buffer("[Preview]"); + aucmd_restbuf(&aco); + + if (retv == FAIL) { + return NULL; + } + + // Temporarily switch to preview buffer to set it up for previewing. + aucmd_prepbuf(&aco, cmdpreview_buf); + buf_clear(); + curbuf->b_p_ma = true; + curbuf->b_p_ul = -1; + curbuf->b_p_tw = 0; // Reset 'textwidth' (was set by ftplugin) + aucmd_restbuf(&aco); + cmdpreview_bufnr = cmdpreview_buf->handle; + + return cmdpreview_buf; +} + +/// Open command preview window if it's not already open. +/// Returns to original window after opening command preview window. +/// +/// @param cmdpreview_buf Pointer to command preview buffer +/// +/// @return Pointer to command preview window if succeeded, NULL if failed. +static win_T *cmdpreview_open_win(buf_T *cmdpreview_buf) +{ + win_T *save_curwin = curwin; + bool win_found = false; + + // Try to find an existing preview window. + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_buffer == cmdpreview_buf) { + win_enter(wp, false); + win_found = true; + break; + } + } + + // If an existing window is not found, create one. + if (!win_found && win_split((int)p_cwh, WSP_BOT) == FAIL) { + return NULL; + } + + win_T *preview_win = curwin; + Error err = ERROR_INIT; + + // Switch to preview buffer + try_start(); + int result = do_buffer(DOBUF_GOTO, DOBUF_FIRST, FORWARD, cmdpreview_buf->handle, 0); + if (try_end(&err) || result == FAIL) { + api_clear_error(&err); + return NULL; + } + + curwin->w_p_cul = false; + curwin->w_p_cuc = false; + curwin->w_p_spell = false; + curwin->w_p_fen = false; + + win_enter(save_curwin, false); + return preview_win; +} + +/// Closes any open command preview windows. +static void cmdpreview_close_win(void) +{ + buf_T *buf = cmdpreview_bufnr ? buflist_findnr(cmdpreview_bufnr) : NULL; + if (buf != NULL) { + close_windows(buf, false); + } +} + +/// Show 'inccommand' preview. It works like this: +/// 1. Store current undo information so we can revert to current state later. +/// 2. Execute the preview callback with the parsed command, preview buffer number and preview +/// namespace number as arguments. The preview callback sets the highlight and does the +/// changes required for the preview if needed. +/// 3. Preview callback returns 0, 1 or 2. 0 means no preview is shown. 1 means preview is shown +/// but preview window doesn't need to be opened. 2 means preview is shown and preview window +/// needs to be opened if inccommand=split. +/// 4. Use the return value of the preview callback to determine whether to +/// open the preview window or not and open preview window if needed. +/// 5. If the return value of the preview callback is not 0, update the screen while the effects +/// of the preview are still in place. +/// 6. Revert all changes made by the preview callback. +static void cmdpreview_show(CommandLineState *s) +{ + // Parse the command line and return if it fails. + exarg_T ea; + CmdParseInfo cmdinfo; + // Copy the command line so we can modify it. + char *cmdline = xstrdup((char *)ccline.cmdbuff); + char *errormsg = NULL; + + parse_cmdline(cmdline, &ea, &cmdinfo, &errormsg); + if (errormsg != NULL) { + goto end; + } + + // Swap invalid command range if needed + if ((ea.argt & EX_RANGE) && ea.line1 > ea.line2) { + linenr_T lnum = ea.line1; + ea.line1 = ea.line2; + ea.line2 = lnum; + } + + time_t save_b_u_time_cur = curbuf->b_u_time_cur; + long save_b_u_seq_cur = curbuf->b_u_seq_cur; + u_header_T *save_b_u_newhead = curbuf->b_u_newhead; + long save_b_p_ul = curbuf->b_p_ul; + int save_b_changed = curbuf->b_changed; + int save_w_p_cul = curwin->w_p_cul; + int save_w_p_cuc = curwin->w_p_cuc; + bool save_hls = p_hls; + varnumber_T save_changedtick = buf_get_changedtick(curbuf); + bool icm_split = *p_icm == 's'; // inccommand=split + buf_T *cmdpreview_buf; + win_T *cmdpreview_win; + cmdmod_T save_cmdmod = cmdmod; + + cmdpreview = true; + emsg_silent++; // Block error reporting as the command may be incomplete + msg_silent++; // Block messages, namely ones that prompt + block_autocmds(); // Block events + garray_T save_view; + win_size_save(&save_view); // Save current window sizes + save_search_patterns(); // Save search patterns + curbuf->b_p_ul = LONG_MAX; // Make sure we can undo all changes + curwin->w_p_cul = false; // Disable 'cursorline' so it doesn't mess up the highlights + curwin->w_p_cuc = false; // Disable 'cursorcolumn' so it doesn't mess up the highlights + p_hls = false; // Don't show search highlighting during live substitution + cmdmod.split = 0; // Disable :leftabove/botright modifiers + cmdmod.tab = 0; // Disable :tab modifier + cmdmod.noswapfile = true; // Disable swap for preview buffer + + // Open preview buffer if inccommand=split. + if (!icm_split) { + cmdpreview_bufnr = 0; + } else if ((cmdpreview_buf = cmdpreview_open_buf()) == NULL) { + abort(); + } + + // Setup preview namespace if it's not already set. + if (!cmdpreview_ns) { + cmdpreview_ns = (int)nvim_create_namespace((String)STRING_INIT); + } + + // Execute the preview callback and use its return value to determine whether to show preview or + // open the preview window. The preview callback also handles doing the changes and highlights for + // the preview. + Error err = ERROR_INIT; + try_start(); + int cmdpreview_type = execute_cmd(&ea, &cmdinfo, true); + if (try_end(&err)) { + api_clear_error(&err); + cmdpreview_type = 0; + } + + // If inccommand=split and preview callback returns 2, open preview window. + if (icm_split && cmdpreview_type == 2 + && (cmdpreview_win = cmdpreview_open_win(cmdpreview_buf)) == NULL) { + abort(); + } + + // If preview callback is nonzero, update screen now. + if (cmdpreview_type != 0) { + int save_rd = RedrawingDisabled; + RedrawingDisabled = 0; + update_screen(SOME_VALID); + RedrawingDisabled = save_rd; + } + + // Close preview window if it's open. + if (icm_split && cmdpreview_type == 2 && cmdpreview_win != NULL) { + cmdpreview_close_win(); + } + // Clear preview highlights. + extmark_clear(curbuf, (uint32_t)cmdpreview_ns, 0, 0, MAXLNUM, MAXCOL); + + curbuf->b_changed = save_b_changed; // Preserve 'modified' during preview + + if (curbuf->b_u_seq_cur != save_b_u_seq_cur) { + // Undo invisibly. This also moves the cursor! + while (curbuf->b_u_seq_cur != save_b_u_seq_cur) { + if (!u_undo_and_forget(1)) { + abort(); + } + } + // Restore newhead. It is meaningless when curhead is valid, but we must + // restore it so that undotree() is identical before/after the preview. + curbuf->b_u_newhead = save_b_u_newhead; + curbuf->b_u_time_cur = save_b_u_time_cur; + } + if (save_changedtick != buf_get_changedtick(curbuf)) { + buf_set_changedtick(curbuf, save_changedtick); + } + + cmdmod = save_cmdmod; // Restore cmdmod + p_hls = save_hls; // Restore 'hlsearch' + curwin->w_p_cul = save_w_p_cul; // Restore 'cursorline' + curwin->w_p_cuc = save_w_p_cuc; // Restore 'cursorcolumn' + curbuf->b_p_ul = save_b_p_ul; // Restore 'undolevels' + restore_search_patterns(); // Restore search patterns + win_size_restore(&save_view); // Restore window sizes + ga_clear(&save_view); + unblock_autocmds(); // Unblock events + msg_silent--; // Unblock messages + emsg_silent--; // Unblock error reporting + + // Restore the window "view". + curwin->w_cursor = s->is_state.save_cursor; + restore_viewstate(&s->is_state.old_viewstate); + update_topline(curwin); + + redrawcmdline(); + + // If preview callback returned 0, update screen to clear remnants of an earlier preview. + if (cmdpreview_type == 0) { + update_screen(SOME_VALID); + } +end: + xfree(cmdline); +} + static int command_line_changed(CommandLineState *s) { // Trigger CmdlineChanged autocommands. @@ -2337,7 +2553,6 @@ static int command_line_changed(CommandLineState *s) } } - // 'incsearch' highlighting. if (s->firstc == ':' && current_sctx.sc_sid == 0 // only if interactive && *p_icm != NUL // 'inccommand' is set @@ -2345,27 +2560,9 @@ static int command_line_changed(CommandLineState *s) && cmdline_star == 0 // not typing a password && cmd_can_preview((char *)ccline.cmdbuff) && !vpeekc_any()) { - // Show 'inccommand' preview. It works like this: - // 1. Do the command. - // 2. Command implementation detects MODE_CMDPREVIEW state, then: - // - Update the screen while the effects are in place. - // - Immediately undo the effects. - State |= MODE_CMDPREVIEW; - emsg_silent++; // Block error reporting as the command may be incomplete - msg_silent++; // Block messages, namely ones that prompt - do_cmdline((char *)ccline.cmdbuff, NULL, NULL, DOCMD_KEEPLINE|DOCMD_NOWAIT|DOCMD_PREVIEW); - msg_silent--; // Unblock messages - emsg_silent--; // Unblock error reporting - - // Restore the window "view". - curwin->w_cursor = s->is_state.save_cursor; - restore_viewstate(&s->is_state.old_viewstate); - update_topline(curwin); - - redrawcmdline(); - } else if (State & MODE_CMDPREVIEW) { - State = (State & ~MODE_CMDPREVIEW); - close_preview_windows(); + cmdpreview_show(s); + } else if (cmdpreview) { + cmdpreview = false; update_screen(SOME_VALID); // Clear 'inccommand' preview. } else { if (s->xpc.xp_context == EXPAND_NOTHING && (KeyTyped || vpeekc() == NUL)) { |