aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFamiu Haque <famiuhaque@protonmail.com>2022-04-20 17:02:18 +0600
committerFamiu Haque <famiuhaque@protonmail.com>2022-05-31 20:55:05 +0600
commit46536f53e82967dcac8d030ee3394cdb156f9603 (patch)
tree4444067831639a6a1eb1916ca9cd5002261932ec
parente9803e1de6497ee21f77f45cf2670c2fe4e8ab22 (diff)
downloadrneovim-46536f53e82967dcac8d030ee3394cdb156f9603.tar.gz
rneovim-46536f53e82967dcac8d030ee3394cdb156f9603.tar.bz2
rneovim-46536f53e82967dcac8d030ee3394cdb156f9603.zip
feat: add preview functionality to user commands
Adds a Lua-only `preview` flag to user commands which allows the command to be incrementally previewed like `:substitute` when 'inccommand' is set.
-rw-r--r--runtime/doc/api.txt2
-rw-r--r--runtime/doc/map.txt106
-rw-r--r--runtime/doc/options.txt10
-rw-r--r--runtime/doc/vim_diff.txt2
-rw-r--r--src/nvim/api/keysets.lua1
-rw-r--r--src/nvim/api/private/helpers.c11
-rw-r--r--src/nvim/api/vim.c1
-rw-r--r--src/nvim/api/vimscript.c2
-rw-r--r--src/nvim/buffer_updates.c9
-rw-r--r--src/nvim/change.c4
-rw-r--r--src/nvim/ex_cmds.c251
-rw-r--r--src/nvim/ex_cmds.lua55
-rw-r--r--src/nvim/ex_cmds_defs.h11
-rw-r--r--src/nvim/ex_docmd.c89
-rw-r--r--src/nvim/ex_docmd.h2
-rw-r--r--src/nvim/ex_getln.c302
-rw-r--r--src/nvim/fold.c5
-rw-r--r--src/nvim/generators/gen_ex_cmds.lua13
-rw-r--r--src/nvim/globals.h3
-rw-r--r--src/nvim/lua/executor.c31
-rw-r--r--src/nvim/vim.h1
-rw-r--r--test/functional/api/command_spec.lua14
-rw-r--r--test/functional/ui/float_spec.lua12
-rw-r--r--test/functional/ui/inccommand_spec.lua36
-rw-r--r--test/functional/ui/inccommand_user_spec.lua329
25 files changed, 958 insertions, 344 deletions
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt
index 965b8e6492..6c8c35486e 100644
--- a/runtime/doc/api.txt
+++ b/runtime/doc/api.txt
@@ -760,6 +760,8 @@ nvim_create_user_command({name}, {command}, {*opts})
when a Lua function is used for {command}.
• force: (boolean, default true) Override any
previous definition.
+ • preview: (function) Preview callback for
+ 'inccommand' |:command-preview|
nvim_del_current_line() *nvim_del_current_line()*
Deletes the current line.
diff --git a/runtime/doc/map.txt b/runtime/doc/map.txt
index 98da68b76a..9776304c8e 100644
--- a/runtime/doc/map.txt
+++ b/runtime/doc/map.txt
@@ -1430,6 +1430,112 @@ Possible values are (second column is the short name used in listing):
-addr=other ? other kind of range
+Incremental preview ~
+ *:command-preview* {nvim-api}
+Commands can show an 'inccommand' (as-you-type) preview by defining a preview
+handler (only from Lua, see |nvim_create_user_command()|).
+
+The preview callback must be a Lua function with this signature: >
+
+ function cmdpreview(opts, ns, buf)
+<
+where "opts" has the same form as that given to |nvim_create_user_command()|
+callbacks, "ns" is the preview namespace id for highlights, and "buf" is the
+buffer that your preview routine will directly modify to show the previewed
+results (for "inccommand=split", or nil for "inccommand=nosplit").
+
+Your command preview routine must implement this protocol:
+
+1. Modify the current buffer as required for the preview (see
+ |nvim_buf_set_text()| and |nvim_buf_set_lines()|).
+2. If preview buffer is provided, add necessary text to the preview buffer.
+3. Add required highlights to the current buffer. If preview buffer is
+ provided, add required highlights to the preview buffer as well. All
+ highlights must be added to the preview namespace which is provided as an
+ argument to the preview callback (see |nvim_buf_add_highlight()| and
+ |nvim_buf_set_extmark()| for help on how to add highlights to a namespace).
+4. Return an integer (0, 1, 2) which controls how Nvim behaves as follows:
+ 0: No preview is shown.
+ 1: Preview is shown without preview window (even with "inccommand=split").
+ 2: Preview is shown and preview window is opened (if "inccommand=split").
+ For "inccommand=nosplit" this is the same as 1.
+
+After preview ends, Nvim discards all changes to the buffer and all highlights
+in the preview namespace.
+
+Here's an example of a command to trim trailing whitespace from lines that
+supports incremental command preview:
+>
+ -- Trims trailing whitespace in the current buffer.
+ -- Also performs 'inccommand' preview if invoked as a preview callback
+ -- (preview_ns is non-nil).
+ local function trim_space(opts, preview_ns, preview_buf)
+ local line1 = opts.line1
+ local line2 = opts.line2
+ local buf = vim.api.nvim_get_current_buf()
+ local lines = vim.api.nvim_buf_get_lines(buf, line1 - 1, line2, 0)
+ local new_lines = {}
+ local preview_buf_line = 0
+
+ for i, line in ipairs(lines) do
+ local startidx, endidx = string.find(line, '%s+$')
+
+ if startidx ~= nil then
+ -- Highlight the match if in command preview mode
+ if preview_ns ~= nil then
+ vim.api.nvim_buf_add_highlight(
+ buf, preview_ns, 'Substitute', line1 + i - 2, startidx - 1,
+ endidx
+ )
+
+ -- Add lines and highlight to the preview buffer
+ -- if inccommand=split
+ if preview_buf ~= nil then
+ local prefix = string.format('|%d| ', line1 + i - 1)
+
+ vim.api.nvim_buf_set_lines(
+ preview_buf, preview_buf_line, preview_buf_line, 0,
+ { prefix .. line }
+ )
+ vim.api.nvim_buf_add_highlight(
+ preview_buf, preview_ns, 'Substitute', preview_buf_line,
+ #prefix + startidx - 1, #prefix + endidx
+ )
+
+ preview_buf_line = preview_buf_line + 1
+ end
+ end
+ end
+
+ if not preview_ns then
+ new_lines[#new_lines+1] = string.gsub(line, '%s+$', '')
+ end
+ end
+
+ -- Don't make any changes to the buffer if previewing
+ if not preview_ns then
+ vim.api.nvim_buf_set_lines(buf, line1 - 1, line2, 0, new_lines)
+ end
+
+ -- When called as a preview callback, return the value of the
+ -- preview type
+ if preview_ns ~= nil then
+ return 2
+ end
+ end
+
+ -- Create the user command
+ vim.api.nvim_create_user_command(
+ 'TrimTrailingWhitespace',
+ trim_space,
+ { nargs = '?', range = '%', addr = 'lines', preview = trim_space }
+ )
+<
+Note that in the above example, the same function is used as both the command
+callback and the preview callback, but you could instead use separate
+functions.
+
+
Special cases ~
*:command-bang* *:command-bar*
*:command-register* *:command-buffer*
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index a21d3bbce7..eebbc3f73a 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -3266,8 +3266,9 @@ A jump table for the options with a short description can be found at |Q_op|.
'inccommand' 'icm' string (default "nosplit")
global
- When nonempty, shows the effects of |:substitute|, |:smagic|, and
- |:snomagic| as you type.
+ When nonempty, shows the effects of |:substitute|, |:smagic|,
+ |:snomagic| and user commands with the |:command-preview| flag as you
+ type.
Possible values:
nosplit Shows the effects of a command incrementally in the
@@ -3275,8 +3276,9 @@ A jump table for the options with a short description can be found at |Q_op|.
split Like "nosplit", but also shows partial off-screen
results in a preview window.
- If the preview is too slow (exceeds 'redrawtime') then 'inccommand' is
- automatically disabled until |Command-line-mode| is done.
+ If the preview for built-in commands is too slow (exceeds
+ 'redrawtime') then 'inccommand' is automatically disabled until
+ |Command-line-mode| is done.
*'include'* *'inc'*
'include' 'inc' string (default "^\s*#\s*include")
diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt
index c079b83c29..8e67cb0923 100644
--- a/runtime/doc/vim_diff.txt
+++ b/runtime/doc/vim_diff.txt
@@ -183,6 +183,7 @@ Commands:
|:sign-define| accepts a `numhl` argument, to highlight the line number
|:match| can be invoked before highlight group is defined
|:source| works with Lua and anonymous (no file) scripts
+ User commands can support |:command-preview| to show results as you type
Events:
|RecordingEnter|
@@ -235,6 +236,7 @@ Options:
"horizdown", "vertleft", "vertright", "verthoriz"
'foldcolumn' supports up to 9 dynamic/fixed columns
'inccommand' shows interactive results for |:substitute|-like commands
+ and |:command-preview| commands
'laststatus' global statusline support
'pumblend' pseudo-transparent popupmenu
'scrollback'
diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua
index d4882abffe..881a83e606 100644
--- a/src/nvim/api/keysets.lua
+++ b/src/nvim/api/keysets.lua
@@ -53,6 +53,7 @@ return {
"force";
"keepscript";
"nargs";
+ "preview";
"range";
"register";
};
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index 3cccbc3cdf..6981ecc455 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -1438,6 +1438,7 @@ void create_user_command(String name, Object command, Dict(user_command) *opts,
char *rep = NULL;
LuaRef luaref = LUA_NOREF;
LuaRef compl_luaref = LUA_NOREF;
+ LuaRef preview_luaref = LUA_NOREF;
if (!uc_validate_name(name.data)) {
api_set_error(err, kErrorTypeValidation, "Invalid command name");
@@ -1592,6 +1593,14 @@ void create_user_command(String name, Object command, Dict(user_command) *opts,
goto err;
}
+ if (opts->preview.type == kObjectTypeLuaRef) {
+ argt |= EX_PREVIEW;
+ preview_luaref = api_new_luaref(opts->preview.data.luaref);
+ } else if (HAS_KEY(opts->preview)) {
+ api_set_error(err, kErrorTypeValidation, "Invalid value for 'preview'");
+ goto err;
+ }
+
switch (command.type) {
case kObjectTypeLuaRef:
luaref = api_new_luaref(command.data.luaref);
@@ -1611,7 +1620,7 @@ void create_user_command(String name, Object command, Dict(user_command) *opts,
}
if (uc_add_command(name.data, name.size, rep, argt, def, flags, compl, compl_arg, compl_luaref,
- addr_type_arg, luaref, force) != OK) {
+ preview_luaref, addr_type_arg, luaref, force) != OK) {
api_set_error(err, kErrorTypeException, "Failed to create user command");
// Do not goto err, since uc_add_command now owns luaref, compl_luaref, and compl_arg
}
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 5c3c16d6b0..8555d1bb71 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -2511,6 +2511,7 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
/// - desc: (string) Used for listing the command when a Lua function is used for
/// {command}.
/// - force: (boolean, default true) Override any previous definition.
+/// - preview: (function) Preview callback for 'inccommand' |:command-preview|
/// @param[out] err Error details, if any.
void nvim_create_user_command(String name, Object command, Dict(user_command) *opts, Error *err)
FUNC_API_SINCE(9)
diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c
index e71f1a11ec..99ab247c2a 100644
--- a/src/nvim/api/vimscript.c
+++ b/src/nvim/api/vimscript.c
@@ -1311,7 +1311,7 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
}
WITH_SCRIPT_CONTEXT(channel_id, {
- execute_cmd(&ea, &cmdinfo);
+ execute_cmd(&ea, &cmdinfo, false);
});
if (output) {
diff --git a/src/nvim/buffer_updates.c b/src/nvim/buffer_updates.c
index cb08ba0cfb..47b88945c7 100644
--- a/src/nvim/buffer_updates.c
+++ b/src/nvim/buffer_updates.c
@@ -187,7 +187,7 @@ void buf_updates_unload(buf_T *buf, bool can_reload)
}
void buf_updates_send_changes(buf_T *buf, linenr_T firstline, int64_t num_added,
- int64_t num_removed, bool send_tick)
+ int64_t num_removed)
{
size_t deleted_codepoints, deleted_codeunits;
size_t deleted_bytes = ml_flush_deleted_bytes(buf, &deleted_codepoints,
@@ -197,6 +197,9 @@ void buf_updates_send_changes(buf_T *buf, linenr_T firstline, int64_t num_added,
return;
}
+ // Don't send b:changedtick during 'inccommand' preview if "buf" is the current buffer.
+ bool send_tick = !(cmdpreview && buf == curbuf);
+
// if one the channels doesn't work, put its ID here so we can remove it later
uint64_t badchannelid = 0;
@@ -253,7 +256,7 @@ void buf_updates_send_changes(buf_T *buf, linenr_T firstline, int64_t num_added,
for (size_t i = 0; i < kv_size(buf->update_callbacks); i++) {
BufUpdateCallbacks cb = kv_A(buf->update_callbacks, i);
bool keep = true;
- if (cb.on_lines != LUA_NOREF && (cb.preview || !(State & MODE_CMDPREVIEW))) {
+ if (cb.on_lines != LUA_NOREF && (cb.preview || !cmdpreview)) {
Array args = ARRAY_DICT_INIT;
Object items[8];
args.size = 6; // may be increased to 8 below
@@ -312,7 +315,7 @@ void buf_updates_send_splice(buf_T *buf, int start_row, colnr_T start_col, bcoun
for (size_t i = 0; i < kv_size(buf->update_callbacks); i++) {
BufUpdateCallbacks cb = kv_A(buf->update_callbacks, i);
bool keep = true;
- if (cb.on_bytes != LUA_NOREF && (cb.preview || !(State & MODE_CMDPREVIEW))) {
+ if (cb.on_bytes != LUA_NOREF && (cb.preview || !cmdpreview)) {
FIXED_TEMP_ARRAY(args, 11);
// the first argument is always the buffer handle
diff --git a/src/nvim/change.c b/src/nvim/change.c
index 9fd5083fd3..fa1de69e2c 100644
--- a/src/nvim/change.c
+++ b/src/nvim/change.c
@@ -351,7 +351,7 @@ void changed_bytes(linenr_T lnum, colnr_T col)
changedOneline(curbuf, lnum);
changed_common(lnum, col, lnum + 1, 0L);
// notify any channels that are watching
- buf_updates_send_changes(curbuf, lnum, 1, 1, true);
+ buf_updates_send_changes(curbuf, lnum, 1, 1);
// Diff highlighting in other diff windows may need to be updated too.
if (curwin->w_p_diff) {
@@ -501,7 +501,7 @@ void changed_lines(linenr_T lnum, colnr_T col, linenr_T lnume, long xtra, bool d
if (do_buf_event) {
int64_t num_added = (int64_t)(lnume + xtra - lnum);
int64_t num_removed = lnume - lnum;
- buf_updates_send_changes(curbuf, lnum, num_added, num_removed, true);
+ buf_updates_send_changes(curbuf, lnum, num_added, num_removed);
}
}
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index f6bdfc6175..93ff7bd752 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -14,7 +14,6 @@
#include <string.h>
#include "nvim/api/buffer.h"
-#include "nvim/api/extmark.h"
#include "nvim/api/private/defs.h"
#include "nvim/ascii.h"
#include "nvim/buffer.h"
@@ -111,8 +110,6 @@ typedef struct {
# include "ex_cmds.c.generated.h"
#endif
-static int preview_bufnr = 0;
-
/// ":ascii" and "ga" implementation
void do_ascii(const exarg_T *const eap)
{
@@ -1013,7 +1010,7 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
disable_fold_update--;
// send update regarding the new lines that were added
- buf_updates_send_changes(curbuf, dest + 1, num_lines, 0, true);
+ buf_updates_send_changes(curbuf, dest + 1, num_lines, 0);
/*
* Now we delete the original text -- webb
@@ -1055,7 +1052,7 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
}
// send nvim_buf_lines_event regarding lines that were deleted
- buf_updates_send_changes(curbuf, line1 + extra, 0, num_lines, true);
+ buf_updates_send_changes(curbuf, line1 + extra, 0, num_lines);
return OK;
}
@@ -3438,8 +3435,8 @@ static int check_regexp_delim(int c)
/// The usual escapes are supported as described in the regexp docs.
///
/// @param do_buf_event If `true`, send buffer updates.
-/// @return buffer used for 'inccommand' preview
-static buf_T *do_sub(exarg_T *eap, proftime_T timeout, bool do_buf_event, handle_T bufnr)
+/// @return 0, 1 or 2. See show_cmdpreview() for more information on what the return value means.
+static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T cmdpreview_bufnr)
{
long i = 0;
regmmatch_T regmatch;
@@ -3467,14 +3464,10 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, bool do_buf_event, handle
char *sub_firstline; // allocated copy of first sub line
bool endcolumn = false; // cursor in last column when done
PreviewLines preview_lines = { KV_INITIAL_VALUE, 0 };
- static int pre_src_id = 0; // Source id for the preview highlight
static int pre_hl_id = 0;
- buf_T *orig_buf = curbuf; // save to reset highlighting
pos_T old_cursor = curwin->w_cursor;
int start_nsubs;
int save_ma = 0;
- int save_b_changed = curbuf->b_changed;
- bool preview = (State & MODE_CMDPREVIEW);
bool did_save = false;
@@ -3494,7 +3487,7 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, bool do_buf_event, handle
&& vim_strchr("0123456789cegriIp|\"", *cmd) == NULL) {
// don't accept alphanumeric for separator
if (check_regexp_delim(*cmd) == FAIL) {
- return NULL;
+ return 0;
}
// undocumented vi feature:
@@ -3504,7 +3497,7 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, bool do_buf_event, handle
cmd++;
if (vim_strchr("/?&", *cmd) == NULL) {
emsg(_(e_backslash));
- return NULL;
+ return 0;
}
if (*cmd != '&') {
which_pat = RE_SEARCH; // use last '/' pattern
@@ -3540,7 +3533,7 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, bool do_buf_event, handle
MB_PTR_ADV(cmd);
}
- if (!eap->skip && !preview) {
+ if (!eap->skip && !cmdpreview) {
sub_set_replacement((SubReplacementString) {
.sub = xstrdup(sub),
.timestamp = os_time(),
@@ -3550,7 +3543,7 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, bool do_buf_event, handle
} else if (!eap->skip) { // use previous pattern and substitution
if (old_sub.sub == NULL) { // there is no previous command
emsg(_(e_nopresub));
- return NULL;
+ return 0;
}
pat = NULL; // search_regcomp() will use previous pattern
sub = old_sub.sub;
@@ -3560,8 +3553,8 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, bool do_buf_event, handle
endcolumn = (curwin->w_curswant == MAXCOL);
}
- if (sub != NULL && sub_joining_lines(eap, pat, sub, cmd, !preview)) {
- return NULL;
+ if (sub != NULL && sub_joining_lines(eap, pat, sub, cmd, !cmdpreview)) {
+ return 0;
}
cmd = sub_parse_flags(cmd, &subflags, &which_pat);
@@ -3575,7 +3568,7 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, bool do_buf_event, handle
i = getdigits_long((char_u **)&cmd, true, 0);
if (i <= 0 && !eap->skip && subflags.do_error) {
emsg(_(e_zerocount));
- return NULL;
+ return 0;
}
eap->line1 = eap->line2;
eap->line2 += i - 1;
@@ -3592,26 +3585,26 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, bool do_buf_event, handle
eap->nextcmd = (char *)check_nextcmd((char_u *)cmd);
if (eap->nextcmd == NULL) {
emsg(_(e_trailing));
- return NULL;
+ return 0;
}
}
if (eap->skip) { // not executing commands, only parsing
- return NULL;
+ return 0;
}
if (!subflags.do_count && !MODIFIABLE(curbuf)) {
// Substitution is not allowed in non-'modifiable' buffer
emsg(_(e_modifiable));
- return NULL;
+ return 0;
}
- if (search_regcomp((char_u *)pat, RE_SUBST, which_pat, (preview ? 0 : SEARCH_HIS),
+ if (search_regcomp((char_u *)pat, RE_SUBST, which_pat, (cmdpreview ? 0 : SEARCH_HIS),
&regmatch) == FAIL) {
if (subflags.do_error) {
emsg(_(e_invcmd));
}
- return NULL;
+ return 0;
}
// the 'i' or 'I' flag overrules 'ignorecase' and 'smartcase'
@@ -3638,10 +3631,10 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, bool do_buf_event, handle
sub_copy = sub;
} else {
char *source = sub;
- sub = (char *)regtilde((char_u *)sub, p_magic, preview);
+ sub = (char *)regtilde((char_u *)sub, p_magic, cmdpreview);
// When previewing, the new pattern allocated by regtilde() needs to be freed
// in this function because it will not be used or freed by regtilde() later.
- sub_needs_free = preview && sub != source;
+ sub_needs_free = cmdpreview && sub != source;
}
// Check for a match on each line.
@@ -3650,7 +3643,7 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, bool do_buf_event, handle
for (linenr_T lnum = eap->line1;
lnum <= line2 && !got_quit && !aborting()
- && (!preview || preview_lines.lines_needed <= (linenr_T)p_cwh
+ && (!cmdpreview || preview_lines.lines_needed <= (linenr_T)p_cwh
|| lnum <= curwin->w_botline);
lnum++) {
long nmatch = vim_regexec_multi(&regmatch, curwin, curbuf, lnum,
@@ -3817,7 +3810,7 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, bool do_buf_event, handle
}
}
- if (subflags.do_ask && !preview) {
+ if (subflags.do_ask && !cmdpreview) {
int typed = 0;
// change State to MODE_CONFIRM, so that the mouse works
@@ -4049,7 +4042,7 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, bool do_buf_event, handle
// Save the line numbers for the preview buffer
// NOTE: If the pattern matches a final newline, the next line will
// be shown also, but should not be highlighted. Intentional for now.
- if (preview && !has_second_delim) {
+ if (cmdpreview && !has_second_delim) {
current_match.start.col = regmatch.startpos[0].col;
if (current_match.end.lnum == 0) {
current_match.end.lnum = sub_firstlnum + nmatch - 1;
@@ -4064,7 +4057,7 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, bool do_buf_event, handle
// 3. Substitute the string. During 'inccommand' preview only do this if
// there is a replace pattern.
- if (!preview || has_second_delim) {
+ if (!cmdpreview || has_second_delim) {
long lnum_start = lnum; // save the start lnum
save_ma = curbuf->b_p_ma;
if (subflags.do_count) {
@@ -4310,7 +4303,7 @@ skip:
#define PUSH_PREVIEW_LINES() \
do { \
- if (preview) { \
+ if (cmdpreview) { \
linenr_T match_lines = current_match.end.lnum \
- current_match.start.lnum +1; \
if (preview_lines.subresults.size > 0) { \
@@ -4366,8 +4359,7 @@ skip:
int64_t num_added = last_line - first_line;
int64_t num_removed = num_added - i;
- buf_updates_send_changes(curbuf, first_line, num_added, num_removed,
- do_buf_event);
+ buf_updates_send_changes(curbuf, first_line, num_added, num_removed);
}
xfree(sub_firstline); // may have to free allocated copy of the line
@@ -4394,7 +4386,7 @@ skip:
beginline(BL_WHITE | BL_FIX);
}
}
- if (!preview && !do_sub_msg(subflags.do_count) && subflags.do_ask) {
+ if (!cmdpreview && !do_sub_msg(subflags.do_count) && subflags.do_ask) {
msg("");
}
} else {
@@ -4431,34 +4423,23 @@ skip:
subflags.do_all = save_do_all;
subflags.do_ask = save_do_ask;
+ int retv = 0;
+
// Show 'inccommand' preview if there are matched lines.
- buf_T *preview_buf = NULL;
- size_t subsize = preview_lines.subresults.size;
- if (preview && !aborting()) {
+ if (cmdpreview && !aborting()) {
if (got_quit || profile_passed_limit(timeout)) { // Too slow, disable.
set_string_option_direct("icm", -1, (char_u *)"", OPT_FREE,
SID_NONE);
} else if (*p_icm != NUL && pat != NULL) {
- if (pre_src_id == 0) {
- // Get a unique new src_id, saved in a static
- pre_src_id = (int)nvim_create_namespace((String)STRING_INIT);
- }
if (pre_hl_id == 0) {
pre_hl_id = syn_check_group(S_LEN("Substitute"));
}
- curbuf->b_changed = save_b_changed; // preserve 'modified' during preview
- preview_buf = show_sub(eap, old_cursor, &preview_lines,
- pre_hl_id, pre_src_id, bufnr);
- if (subsize > 0) {
- extmark_clear(orig_buf, pre_src_id, eap->line1 - 1, 0,
- kv_last(preview_lines.subresults).end.lnum - 1, MAXCOL);
- }
+ retv = show_sub(eap, old_cursor, &preview_lines, pre_hl_id, cmdpreview_ns, cmdpreview_bufnr);
}
}
kv_destroy(preview_lines.subresults);
-
- return preview_buf;
+ return retv;
#undef ADJUST_SUB_FIRSTLNUM
#undef PUSH_PREVIEW_LINES
}
@@ -5854,52 +5835,26 @@ void ex_helpclose(exarg_T *eap)
}
}
-/// Tries to enter to an existing window of given buffer. If no existing buffer
-/// is found, creates a new split.
-///
-/// @return OK/FAIL.
-int sub_preview_win(buf_T *preview_buf)
-{
- if (preview_buf != NULL) {
- FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- if (wp->w_buffer == preview_buf) {
- win_enter(wp, false);
-
- return OK;
- }
- }
- }
- return win_split((int)p_cwh, WSP_BOT);
-}
-
/// Shows the effects of the :substitute command being typed ('inccommand').
/// If inccommand=split, shows a preview window and later restores the layout.
-static buf_T *show_sub(exarg_T *eap, pos_T old_cusr, PreviewLines *preview_lines, int hl_id,
- int src_id, handle_T bufnr)
+///
+/// @return 1 if preview window isn't needed, 2 if preview window is needed.
+static int show_sub(exarg_T *eap, pos_T old_cusr, PreviewLines *preview_lines, int hl_id,
+ long cmdpreview_ns, handle_T cmdpreview_bufnr)
FUNC_ATTR_NONNULL_ALL
{
- win_T *save_curwin = curwin;
- cmdmod_T save_cmdmod = cmdmod;
char *save_shm_p = (char *)vim_strsave(p_shm);
PreviewLines lines = *preview_lines;
buf_T *orig_buf = curbuf;
-
// We keep a special-purpose buffer around, but don't assume it exists.
- buf_T *preview_buf = bufnr ? buflist_findnr(bufnr) : 0;
- cmdmod.split = 0; // disable :leftabove/botright modifiers
- cmdmod.tab = 0; // disable :tab modifier
- cmdmod.noswapfile = true; // disable swap for preview buffer
+ buf_T *cmdpreview_buf = NULL;
+
// disable file info message
set_string_option_direct("shm", -1, (char_u *)"F", OPT_FREE,
SID_NONE);
- bool outside_curline = (eap->line1 != old_cusr.lnum
- || eap->line2 != old_cusr.lnum);
- bool preview = outside_curline && (*p_icm != 'n');
- if (preview_buf == curbuf) { // Preview buffer cannot preview itself!
- preview = false;
- preview_buf = NULL;
- }
+ // Update the topline to ensure that main window is on the correct line
+ update_topline(curwin);
// Place cursor on nearest matching line, to undo do_sub() cursor placement.
for (size_t i = 0; i < lines.subresults.size; i++) {
@@ -5914,27 +5869,17 @@ static buf_T *show_sub(exarg_T *eap, pos_T old_cusr, PreviewLines *preview_lines
// Width of the "| lnum|..." column which displays the line numbers.
linenr_T highest_num_line = 0;
int col_width = 0;
+ // Use preview window only when inccommand=split and range is not just the current line
+ bool preview = (*p_icm != 'n') && (eap->line1 != old_cusr.lnum || eap->line2 != old_cusr.lnum);
- if (preview && sub_preview_win(preview_buf) != FAIL) {
- buf_open_scratch(preview_buf ? bufnr : 0, "[Preview]");
- buf_clear();
- preview_buf = curbuf;
- curbuf->b_p_bl = false;
- curbuf->b_p_ma = true;
- curbuf->b_p_ul = -1;
- curbuf->b_p_tw = 0; // Reset 'textwidth' (was set by ftplugin)
- curwin->w_p_cul = false;
- curwin->w_p_cuc = false;
- curwin->w_p_spell = false;
- curwin->w_p_fen = false;
+ if (preview) {
+ cmdpreview_buf = buflist_findnr(cmdpreview_bufnr);
+ assert(cmdpreview_buf != NULL);
if (lines.subresults.size > 0) {
highest_num_line = kv_last(lines.subresults).end.lnum;
col_width = log10(highest_num_line) + 1 + 3;
}
- } else {
- // Failed to split the window, don't show 'inccommand' preview.
- preview_buf = NULL;
}
char *str = NULL; // construct the line to show in here
@@ -5944,10 +5889,13 @@ static buf_T *show_sub(exarg_T *eap, pos_T old_cusr, PreviewLines *preview_lines
linenr_T linenr_origbuf = 0; // last line added to original buffer
linenr_T next_linenr = 0; // next line to show for the match
+ // Temporarily switch to preview buffer
+ aco_save_T aco;
+
for (size_t matchidx = 0; matchidx < lines.subresults.size; matchidx++) {
SubResult match = lines.subresults.items[matchidx];
- if (preview_buf) {
+ if (cmdpreview_buf) {
lpos_T p_start = { 0, match.start.col }; // match starts here in preview
lpos_T p_end = { 0, match.end.col }; // ... and ends here
@@ -5986,115 +5934,50 @@ static buf_T *show_sub(exarg_T *eap, pos_T old_cusr, PreviewLines *preview_lines
// Put "|lnum| line" into `str` and append it to the preview buffer.
snprintf(str, line_size, "|%*ld| %s", col_width - 3,
next_linenr, line);
+ // Temporarily switch to preview buffer
+ aucmd_prepbuf(&aco, cmdpreview_buf);
if (linenr_preview == 0) {
ml_replace(1, str, true);
} else {
ml_append(linenr_preview, str, (colnr_T)line_size, false);
}
+ aucmd_restbuf(&aco);
linenr_preview += 1;
}
linenr_origbuf = match.end.lnum;
- bufhl_add_hl_pos_offset(preview_buf, src_id, hl_id, p_start,
- p_end, col_width);
+ bufhl_add_hl_pos_offset(cmdpreview_buf, cmdpreview_ns, hl_id, p_start, p_end, col_width);
}
- bufhl_add_hl_pos_offset(orig_buf, src_id, hl_id, match.start,
- match.end, 0);
+ bufhl_add_hl_pos_offset(orig_buf, cmdpreview_ns, hl_id, match.start, match.end, 0);
}
- xfree(str);
- redraw_later(curwin, SOME_VALID);
- win_enter(save_curwin, false); // Return to original window
- update_topline(curwin);
-
- // Update screen now.
- int save_rd = RedrawingDisabled;
- RedrawingDisabled = 0;
- update_screen(SOME_VALID);
- RedrawingDisabled = save_rd;
+ xfree(str);
set_string_option_direct("shm", -1, (char_u *)save_shm_p, OPT_FREE, SID_NONE);
xfree(save_shm_p);
- cmdmod = save_cmdmod;
-
- return preview_buf;
+ return preview ? 2 : 1;
}
-/// Closes any open windows for inccommand preview buffer.
-void close_preview_windows(void)
+/// :substitute command.
+void ex_substitute(exarg_T *eap)
{
- block_autocmds();
- buf_T *buf = preview_bufnr ? buflist_findnr(preview_bufnr) : NULL;
- if (buf != NULL) {
- close_windows(buf, false);
- }
- unblock_autocmds();
+ (void)do_sub(eap, profile_zero(), 0, 0);
+ return;
}
-/// :substitute command
-///
-/// If 'inccommand' is empty: calls do_sub().
-/// If 'inccommand' is set: shows a "live" preview then removes the changes.
-/// from undo history.
-void ex_substitute(exarg_T *eap)
+/// :substitute command preview callback.
+int ex_substitute_preview(exarg_T *eap, long cmdpreview_ns, handle_T cmdpreview_bufnr)
{
- bool preview = (State & MODE_CMDPREVIEW);
- if (*p_icm == NUL || !preview) { // 'inccommand' is disabled
- close_preview_windows();
- (void)do_sub(eap, profile_zero(), true, preview_bufnr);
-
- return;
+ // Only preview once the pattern delimiter has been typed
+ if (*eap->arg && !ASCII_ISALNUM(*eap->arg)) {
+ char *save_eap = eap->arg;
+ int retv = do_sub(eap, profile_setlimit(p_rdt), cmdpreview_ns, cmdpreview_bufnr);
+ eap->arg = save_eap;
+ return retv;
}
- block_autocmds(); // Disable events during command preview.
-
- char *save_eap = eap->arg;
- garray_T save_view;
- win_size_save(&save_view); // Save current window sizes.
- save_search_patterns();
- int save_changedtick = buf_get_changedtick(curbuf);
- time_t save_b_u_time_cur = curbuf->b_u_time_cur;
- u_header_T *save_b_u_newhead = curbuf->b_u_newhead;
- long save_b_p_ul = curbuf->b_p_ul;
- int save_w_p_cul = curwin->w_p_cul;
- int save_w_p_cuc = curwin->w_p_cuc;
-
- curbuf->b_p_ul = LONG_MAX; // make sure we can undo all changes
- curwin->w_p_cul = false; // Disable 'cursorline'
- curwin->w_p_cuc = false; // Disable 'cursorcolumn'
-
- // Don't show search highlighting during live substitution
- bool save_hls = p_hls;
- p_hls = false;
- buf_T *preview_buf = do_sub(eap, profile_setlimit(p_rdt), false,
- preview_bufnr);
- p_hls = save_hls;
-
- if (preview_buf != NULL) {
- preview_bufnr = preview_buf->handle;
- }
-
- if (save_changedtick != buf_get_changedtick(curbuf)) {
- // Undo invisibly. This also moves the cursor!
- 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;
- buf_set_changedtick(curbuf, save_changedtick);
- }
-
- curbuf->b_p_ul = save_b_p_ul;
- curwin->w_p_cul = save_w_p_cul; // Restore 'cursorline'
- curwin->w_p_cuc = save_w_p_cuc; // Restore 'cursorcolumn'
- eap->arg = save_eap;
- restore_search_patterns();
- win_size_restore(&save_view);
- ga_clear(&save_view);
- unblock_autocmds();
+ return 0;
}
/// Skip over the pattern argument of ":vimgrep /pat/[g][j]".
diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua
index 427e018141..b18bdefc2a 100644
--- a/src/nvim/ex_cmds.lua
+++ b/src/nvim/ex_cmds.lua
@@ -4,28 +4,29 @@ local module = {}
-- Description of the values below is contained in ex_cmds_defs.h file.
-- "EX_" prefix is omitted.
-local RANGE = 0x001
-local BANG = 0x002
-local EXTRA = 0x004
-local XFILE = 0x008
-local NOSPC = 0x010
-local DFLALL = 0x020
-local WHOLEFOLD = 0x040
-local NEEDARG = 0x080
-local TRLBAR = 0x100
-local REGSTR = 0x200
-local COUNT = 0x400
-local NOTRLCOM = 0x800
-local ZEROR = 0x1000
-local CTRLV = 0x2000
-local CMDARG = 0x4000
-local BUFNAME = 0x8000
-local BUFUNL = 0x10000
-local ARGOPT = 0x20000
-local SBOXOK = 0x40000
-local CMDWIN = 0x80000
-local MODIFY = 0x100000
-local FLAGS = 0x200000
+local RANGE = 0x001
+local BANG = 0x002
+local EXTRA = 0x004
+local XFILE = 0x008
+local NOSPC = 0x010
+local DFLALL = 0x020
+local WHOLEFOLD = 0x040
+local NEEDARG = 0x080
+local TRLBAR = 0x100
+local REGSTR = 0x200
+local COUNT = 0x400
+local NOTRLCOM = 0x800
+local ZEROR = 0x1000
+local CTRLV = 0x2000
+local CMDARG = 0x4000
+local BUFNAME = 0x8000
+local BUFUNL = 0x10000
+local ARGOPT = 0x20000
+local SBOXOK = 0x40000
+local CMDWIN = 0x80000
+local MODIFY = 0x100000
+local FLAGS = 0x200000
+local PREVIEW = 0x8000000
local FILES = bit.bor(XFILE, EXTRA)
local WORD1 = bit.bor(EXTRA, NOSPC)
local FILE1 = bit.bor(FILES, NOSPC)
@@ -33,6 +34,7 @@ local FILE1 = bit.bor(FILES, NOSPC)
module.flags = {
RANGE = RANGE,
DFLALL = DFLALL,
+ PREVIEW = PREVIEW
}
-- The following table is described in ex_cmds_defs.h file.
@@ -2305,9 +2307,10 @@ module.cmds = {
},
{
command='substitute',
- flags=bit.bor(RANGE, WHOLEFOLD, EXTRA, CMDWIN),
+ flags=bit.bor(RANGE, WHOLEFOLD, EXTRA, CMDWIN, PREVIEW),
addr_type='ADDR_LINES',
func='ex_substitute',
+ preview_func='ex_substitute_preview',
},
{
command='sNext',
@@ -2479,9 +2482,10 @@ module.cmds = {
},
{
command='smagic',
- flags=bit.bor(RANGE, WHOLEFOLD, EXTRA, CMDWIN),
+ flags=bit.bor(RANGE, WHOLEFOLD, EXTRA, CMDWIN, PREVIEW),
addr_type='ADDR_LINES',
func='ex_submagic',
+ preview_func='ex_submagic_preview',
},
{
command='smap',
@@ -2509,9 +2513,10 @@ module.cmds = {
},
{
command='snomagic',
- flags=bit.bor(RANGE, WHOLEFOLD, EXTRA, CMDWIN),
+ flags=bit.bor(RANGE, WHOLEFOLD, EXTRA, CMDWIN, PREVIEW),
addr_type='ADDR_LINES',
func='ex_submagic',
+ preview_func='ex_submagic_preview',
},
{
command='snoremap',
diff --git a/src/nvim/ex_cmds_defs.h b/src/nvim/ex_cmds_defs.h
index d8dd3da9e6..a5c9c6be2d 100644
--- a/src/nvim/ex_cmds_defs.h
+++ b/src/nvim/ex_cmds_defs.h
@@ -63,6 +63,7 @@
#define EX_MODIFY 0x100000 // forbidden in non-'modifiable' buffer
#define EX_FLAGS 0x200000 // allow flags after count in argument
#define EX_KEEPSCRIPT 0x4000000 // keep sctx of where command was invoked
+#define EX_PREVIEW 0x8000000 // allow incremental command preview
#define EX_FILES (EX_XFILE | EX_EXTRA) // multiple extra files allowed
#define EX_FILE1 (EX_FILES | EX_NOSPC) // 1 file, defaults to current file
#define EX_WORD1 (EX_EXTRA | EX_NOSPC) // one extra word allowed
@@ -91,6 +92,7 @@ typedef struct exarg exarg_T;
#define BAD_DROP (-2) // erase it
typedef void (*ex_func_T)(exarg_T *eap);
+typedef int (*ex_preview_func_T)(exarg_T *eap, long cmdpreview_ns, handle_T cmdpreview_bufnr);
// NOTE: These possible could be removed and changed so that
// Callback could take a "command" style string, and simply
@@ -125,10 +127,11 @@ typedef char *(*LineGetter)(int, void *, int, bool);
/// Structure for command definition.
typedef struct cmdname {
- char *cmd_name; ///< Name of the command.
- ex_func_T cmd_func; ///< Function with implementation of this command.
- uint32_t cmd_argt; ///< Relevant flags from the declared above.
- cmd_addr_T cmd_addr_type; ///< Flag for address type
+ char *cmd_name; ///< Name of the command.
+ ex_func_T cmd_func; ///< Function with implementation of this command.
+ ex_preview_func_T cmd_preview_func; ///< Preview callback function of this command.
+ uint32_t cmd_argt; ///< Relevant flags from the declared above.
+ cmd_addr_T cmd_addr_type; ///< Flag for address type.
} CommandDefinition;
// A list used for saving values of "emsg_silent". Used by ex_try() to save the
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index e6ee0046af..4b462994be 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -294,7 +294,6 @@ int do_cmdline_cmd(const char *cmd)
/// DOCMD_KEYTYPED - Don't reset KeyTyped.
/// DOCMD_EXCRESET - Reset the exception environment (used for debugging).
/// DOCMD_KEEPLINE - Store first typed line (for repeating with ".").
-/// DOCMD_PREVIEW - During 'inccommand' preview.
///
/// @param cookie argument for fgetline()
///
@@ -593,11 +592,6 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
next_cmdline = do_one_cmd(&cmdline_copy, flags, &cstack, cmd_getline, cmd_cookie);
recursive--;
- // Ignore trailing '|'-separated commands in preview-mode ('inccommand').
- if ((State & MODE_CMDPREVIEW) && (flags & DOCMD_PREVIEW)) {
- next_cmdline = NULL;
- }
-
if (cmd_cookie == (void *)&cmd_loop_cookie) {
// Use "current_line" from "cmd_loop_cookie", it may have been
// incremented when defining a function.
@@ -1578,9 +1572,11 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er
///
/// @param eap Ex-command arguments
/// @param cmdinfo Command parse information
-void execute_cmd(exarg_T *eap, CmdParseInfo *cmdinfo)
+/// @param preview Execute command preview callback instead of actual command
+int execute_cmd(exarg_T *eap, CmdParseInfo *cmdinfo, bool preview)
{
char *errormsg = NULL;
+ int retv = 0;
#define ERROR(msg) \
do { \
@@ -1698,11 +1694,17 @@ void execute_cmd(exarg_T *eap, CmdParseInfo *cmdinfo)
// Execute the command
if (IS_USER_CMDIDX(eap->cmdidx)) {
// Execute a user-defined command.
- do_ucmd(eap);
+ retv = do_ucmd(eap, preview);
} else {
- // Call the function to execute the command.
+ // Call the function to execute the command or the preview callback.
eap->errmsg = NULL;
- (cmdnames[eap->cmdidx].cmd_func)(eap);
+
+ if (preview) {
+ retv = (cmdnames[eap->cmdidx].cmd_preview_func)(eap, cmdpreview_get_ns(),
+ cmdpreview_get_bufnr());
+ } else {
+ (cmdnames[eap->cmdidx].cmd_func)(eap);
+ }
if (eap->errmsg != NULL) {
errormsg = _(eap->errmsg);
}
@@ -1718,6 +1720,7 @@ end:
if (eap->did_sandbox) {
sandbox--;
}
+ return retv;
#undef ERROR
}
@@ -2350,7 +2353,7 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter
/*
* Execute a user-defined command.
*/
- do_ucmd(&ea);
+ do_ucmd(&ea, false);
} else {
/*
* Call the function to execute the command.
@@ -5541,8 +5544,8 @@ char *uc_validate_name(char *name)
///
/// @return OK if the command is created, FAIL otherwise.
int uc_add_command(char *name, size_t name_len, char *rep, uint32_t argt, long def, int flags,
- int compl, char *compl_arg, LuaRef compl_luaref, cmd_addr_T addr_type,
- LuaRef luaref, bool force)
+ int compl, char *compl_arg, LuaRef compl_luaref, LuaRef preview_luaref,
+ cmd_addr_T addr_type, LuaRef luaref, bool force)
FUNC_ATTR_NONNULL_ARG(1, 3)
{
ucmd_T *cmd = NULL;
@@ -5597,6 +5600,7 @@ int uc_add_command(char *name, size_t name_len, char *rep, uint32_t argt, long d
XFREE_CLEAR(cmd->uc_compl_arg);
NLUA_CLEAR_REF(cmd->uc_luaref);
NLUA_CLEAR_REF(cmd->uc_compl_luaref);
+ NLUA_CLEAR_REF(cmd->uc_preview_luaref);
break;
}
@@ -5629,6 +5633,7 @@ int uc_add_command(char *name, size_t name_len, char *rep, uint32_t argt, long d
nlua_set_sctx(&cmd->uc_script_ctx);
cmd->uc_compl_arg = (char_u *)compl_arg;
cmd->uc_compl_luaref = compl_luaref;
+ cmd->uc_preview_luaref = preview_luaref;
cmd->uc_addr_type = addr_type;
cmd->uc_luaref = luaref;
@@ -5639,6 +5644,7 @@ fail:
xfree(compl_arg);
NLUA_CLEAR_REF(luaref);
NLUA_CLEAR_REF(compl_luaref);
+ NLUA_CLEAR_REF(preview_luaref);
return FAIL;
}
@@ -6071,8 +6077,7 @@ static void ex_command(exarg_T *eap)
} else if (compl > 0 && (argt & EX_EXTRA) == 0) {
emsg(_(e_complete_used_without_nargs));
} else {
- uc_add_command(name, name_len, p, argt, def, flags, compl,
- compl_arg, LUA_NOREF,
+ uc_add_command(name, name_len, p, argt, def, flags, compl, compl_arg, LUA_NOREF, LUA_NOREF,
addr_type_arg, LUA_NOREF, eap->forceit);
}
}
@@ -6092,6 +6097,7 @@ void free_ucmd(ucmd_T *cmd)
xfree(cmd->uc_compl_arg);
NLUA_CLEAR_REF(cmd->uc_compl_luaref);
NLUA_CLEAR_REF(cmd->uc_luaref);
+ NLUA_CLEAR_REF(cmd->uc_preview_luaref);
}
/// Clear all user commands for "gap".
@@ -6622,7 +6628,7 @@ size_t uc_mods(char *buf)
return result;
}
-static void do_ucmd(exarg_T *eap)
+static int do_ucmd(exarg_T *eap, bool preview)
{
char *buf;
char *p;
@@ -6643,9 +6649,14 @@ static void do_ucmd(exarg_T *eap)
cmd = USER_CMD_GA(&curbuf->b_ucmds, eap->useridx);
}
+ if (preview) {
+ assert(cmd->uc_preview_luaref > 0);
+ return nlua_do_ucmd(cmd, eap, true);
+ }
+
if (cmd->uc_luaref > 0) {
- nlua_do_ucmd(cmd, eap);
- return;
+ nlua_do_ucmd(cmd, eap, false);
+ return 0;
}
/*
@@ -6740,6 +6751,8 @@ static void do_ucmd(exarg_T *eap)
}
xfree(buf);
xfree(split_buf);
+
+ return 0;
}
static char *expand_user_command_name(int idx)
@@ -6796,7 +6809,8 @@ char *get_user_cmd_flags(expand_T *xp, int idx)
{
static char *user_cmd_flags[] = { "addr", "bang", "bar",
"buffer", "complete", "count",
- "nargs", "range", "register", "keepscript" };
+ "nargs", "range", "register",
+ "keepscript" };
if (idx >= (int)ARRAY_SIZE(user_cmd_flags)) {
return NULL;
@@ -8568,6 +8582,18 @@ static void ex_submagic(exarg_T *eap)
p_magic = magic_save;
}
+/// ":smagic" and ":snomagic" preview callback.
+static int ex_submagic_preview(exarg_T *eap, long cmdpreview_ns, handle_T cmdpreview_bufnr)
+{
+ int magic_save = p_magic;
+
+ p_magic = (eap->cmdidx == CMD_smagic);
+ int retv = ex_substitute_preview(eap, cmdpreview_ns, cmdpreview_bufnr);
+ p_magic = magic_save;
+
+ return retv;
+}
+
/// ":join".
static void ex_join(exarg_T *eap)
{
@@ -8809,7 +8835,7 @@ static void ex_redir(exarg_T *eap)
/// ":redraw": force redraw
static void ex_redraw(exarg_T *eap)
{
- if (State & MODE_CMDPREVIEW) {
+ if (cmdpreview) {
return; // Ignore :redraw during 'inccommand' preview. #9777
}
int r = RedrawingDisabled;
@@ -8843,7 +8869,7 @@ static void ex_redraw(exarg_T *eap)
/// ":redrawstatus": force redraw of status line(s) and window bar(s)
static void ex_redrawstatus(exarg_T *eap)
{
- if (State & MODE_CMDPREVIEW) {
+ if (cmdpreview) {
return; // Ignore :redrawstatus during 'inccommand' preview. #9777
}
int r = RedrawingDisabled;
@@ -10107,22 +10133,16 @@ bool cmd_can_preview(char *cmd)
if (*ea.cmd == '*') {
ea.cmd = skipwhite(ea.cmd + 1);
}
- char *end = find_ex_command(&ea, NULL);
+ find_ex_command(&ea, NULL);
- switch (ea.cmdidx) {
- case CMD_substitute:
- case CMD_smagic:
- case CMD_snomagic:
- // Only preview once the pattern delimiter has been typed
- if (*end && !ASCII_ISALNUM(*end)) {
- return true;
- }
- break;
- default:
- break;
+ if (ea.cmdidx == CMD_SIZE) {
+ return false;
+ } else if (!IS_USER_CMDIDX(ea.cmdidx)) {
+ // find_ex_command sets the flags for user commands automatically
+ ea.argt = cmdnames[(int)ea.cmdidx].cmd_argt;
}
- return false;
+ return (ea.argt & EX_PREVIEW);
}
/// Gets a map of maps describing user-commands defined for buffer `buf` or
@@ -10149,6 +10169,7 @@ Dictionary commands_array(buf_T *buf)
PUT(d, "bar", BOOLEAN_OBJ(!!(cmd->uc_argt & EX_TRLBAR)));
PUT(d, "register", BOOLEAN_OBJ(!!(cmd->uc_argt & EX_REGSTR)));
PUT(d, "keepscript", BOOLEAN_OBJ(!!(cmd->uc_argt & EX_KEEPSCRIPT)));
+ PUT(d, "preview", BOOLEAN_OBJ(!!(cmd->uc_argt & EX_PREVIEW)));
switch (cmd->uc_argt & (EX_EXTRA | EX_NOSPC | EX_NEEDARG)) {
case 0:
diff --git a/src/nvim/ex_docmd.h b/src/nvim/ex_docmd.h
index 24656f3851..dbe095ab13 100644
--- a/src/nvim/ex_docmd.h
+++ b/src/nvim/ex_docmd.h
@@ -12,7 +12,6 @@
#define DOCMD_KEYTYPED 0x08 // don't reset KeyTyped
#define DOCMD_EXCRESET 0x10 // reset exception environment (for debugging
#define DOCMD_KEEPLINE 0x20 // keep typed line for repeating with "."
-#define DOCMD_PREVIEW 0x40 // during 'inccommand' preview
// defines for eval_vars()
#define VALID_PATH 1
@@ -42,6 +41,7 @@ typedef struct ucmd {
sctx_T uc_script_ctx; // SCTX where the command was defined
char_u *uc_compl_arg; // completion argument if any
LuaRef uc_compl_luaref; // Reference to Lua completion function
+ LuaRef uc_preview_luaref; // Reference to Lua preview function
LuaRef uc_luaref; // Reference to Lua function
} ucmd_T;
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 13cfd76adf..a94d6edce2 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"
@@ -251,6 +254,9 @@ static CheckhealthComp healthchecks = { GA_INIT(sizeof(char_u *), 10), 0 };
# 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)
@@ -740,6 +746,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 +959,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 +1001,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 +2313,267 @@ 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);
+ 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 (*p_icm == 'n') {
+ 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 (*p_icm != 'n' && 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 (*p_icm != 'n' && 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.
@@ -2345,27 +2613,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)) {
diff --git a/src/nvim/fold.c b/src/nvim/fold.c
index 234c11227d..d5277b9910 100644
--- a/src/nvim/fold.c
+++ b/src/nvim/fold.c
@@ -754,8 +754,7 @@ void deleteFold(win_T *const wp, const linenr_T start, const linenr_T end, const
// the modification of the *first* line of the fold, but we send through a
// notification that includes every line that was part of the fold
int64_t num_changed = last_lnum - first_lnum;
- buf_updates_send_changes(wp->w_buffer, first_lnum, num_changed,
- num_changed, true);
+ buf_updates_send_changes(wp->w_buffer, first_lnum, num_changed, num_changed);
}
}
@@ -1614,7 +1613,7 @@ static void foldCreateMarkers(win_T *wp, pos_T start, pos_T end)
// u_save() is unable to save the buffer line, but we send the
// nvim_buf_lines_event anyway since it won't do any harm.
int64_t num_changed = 1 + end.lnum - start.lnum;
- buf_updates_send_changes(buf, start.lnum, num_changed, num_changed, true);
+ buf_updates_send_changes(buf, start.lnum, num_changed, num_changed);
}
// foldAddMarker() {{{2
diff --git a/src/nvim/generators/gen_ex_cmds.lua b/src/nvim/generators/gen_ex_cmds.lua
index 27cfe194fa..255c415a4d 100644
--- a/src/nvim/generators/gen_ex_cmds.lua
+++ b/src/nvim/generators/gen_ex_cmds.lua
@@ -65,20 +65,31 @@ for _, cmd in ipairs(defs) do
assert(cmd.addr_type ~= 'ADDR_OTHER' and cmd.addr_type ~= 'ADDR_NONE',
string.format('ex_cmds.lua:%s: Missing misplaced DFLALL\n', cmd.command))
end
+ if bit.band(cmd.flags, flags.PREVIEW) == flags.PREVIEW then
+ assert(cmd.preview_func ~= nil,
+ string.format('ex_cmds.lua:%s: Missing preview_func\n', cmd.command))
+ end
local enumname = cmd.enum or ('CMD_' .. cmd.command)
local byte_cmd = cmd.command:sub(1, 1):byte()
if byte_a <= byte_cmd and byte_cmd <= byte_z then
table.insert(cmds, cmd.command)
end
+ local preview_func
+ if cmd.preview_func then
+ preview_func = string.format("(ex_preview_func_T)&%s", cmd.preview_func)
+ else
+ preview_func = "NULL"
+ end
enumfile:write(' ' .. enumname .. ',\n')
defsfile:write(string.format([[
[%s] = {
.cmd_name = "%s",
.cmd_func = (ex_func_T)&%s,
+ .cmd_preview_func = %s,
.cmd_argt = %uL,
.cmd_addr_type = %s
},
-]], enumname, cmd.command, cmd.func, cmd.flags, cmd.addr_type))
+]], enumname, cmd.command, cmd.func, preview_func, cmd.flags, cmd.addr_type))
end
for i = #cmds, 1, -1 do
local cmd = cmds[i]
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index b0006ebaca..fb3aa91c85 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -638,6 +638,9 @@ EXTERN int motion_force INIT(=0); // motion force for pending operator
EXTERN bool exmode_active INIT(= false); // true if Ex mode is active
EXTERN bool ex_no_reprint INIT(=false); // No need to print after z or p.
+// 'inccommand' command preview state
+EXTERN bool cmdpreview INIT(= false);
+
EXTERN int reg_recording INIT(= 0); // register for recording or zero
EXTERN int reg_executing INIT(= 0); // register being executed or zero
// Flag set when peeking a character and found the end of executed register
diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c
index a826dd07d3..deff2347e0 100644
--- a/src/nvim/lua/executor.c
+++ b/src/nvim/lua/executor.c
@@ -1840,11 +1840,12 @@ cleanup:
xfree(info);
}
-void nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap)
+/// @param preview Invoke the callback as a |:command-preview| handler.
+int nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap, bool preview)
{
lua_State *const lstate = global_lstate;
- nlua_pushref(lstate, cmd->uc_luaref);
+ nlua_pushref(lstate, preview ? cmd->uc_preview_luaref : cmd->uc_luaref);
lua_newtable(lstate);
lua_pushboolean(lstate, eap->forceit == 1);
@@ -1969,7 +1970,31 @@ void nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap)
lua_setfield(lstate, -2, "smods");
- if (nlua_pcall(lstate, 1, 0)) {
+ if (preview) {
+ lua_pushinteger(lstate, cmdpreview_get_ns());
+
+ handle_T cmdpreview_bufnr = cmdpreview_get_bufnr();
+ if (cmdpreview_bufnr != 0) {
+ lua_pushinteger(lstate, cmdpreview_bufnr);
+ } else {
+ lua_pushnil(lstate);
+ }
+ }
+
+ if (nlua_pcall(lstate, preview ? 3 : 1, preview ? 1 : 0)) {
nlua_error(lstate, _("Error executing Lua callback: %.*s"));
+ return 0;
}
+
+ int retv = 0;
+
+ if (preview) {
+ if (lua_isnumber(lstate, -1) && (retv = (int)lua_tointeger(lstate, -1)) >= 0 && retv <= 2) {
+ lua_pop(lstate, 1);
+ } else {
+ retv = 0;
+ }
+ }
+
+ return retv;
}
diff --git a/src/nvim/vim.h b/src/nvim/vim.h
index 7e82af2d93..31ac5a67ff 100644
--- a/src/nvim/vim.h
+++ b/src/nvim/vim.h
@@ -70,7 +70,6 @@ enum { NUMBUFLEN = 65, };
#define MODE_EXTERNCMD 0x5000 // executing an external command
#define MODE_SHOWMATCH (0x6000 | MODE_INSERT) // show matching paren
#define MODE_CONFIRM 0x7000 // ":confirm" prompt
-#define MODE_CMDPREVIEW 0x8000 // Showing 'inccommand' command "live" preview.
/// Directions.
typedef enum {
diff --git a/test/functional/api/command_spec.lua b/test/functional/api/command_spec.lua
index d6d75e93e4..253371557a 100644
--- a/test/functional/api/command_spec.lua
+++ b/test/functional/api/command_spec.lua
@@ -16,8 +16,8 @@ local feed = helpers.feed
local funcs = helpers.funcs
describe('nvim_get_commands', function()
- local cmd_dict = { addr=NIL, bang=false, bar=false, complete=NIL, complete_arg=NIL, count=NIL, definition='echo "Hello World"', name='Hello', nargs='1', range=NIL, register=false, keepscript=false, script_id=0, }
- local cmd_dict2 = { addr=NIL, bang=false, bar=false, complete=NIL, complete_arg=NIL, count=NIL, definition='pwd', name='Pwd', nargs='?', range=NIL, register=false, keepscript=false, script_id=0, }
+ local cmd_dict = { addr=NIL, bang=false, bar=false, complete=NIL, complete_arg=NIL, count=NIL, definition='echo "Hello World"', name='Hello', nargs='1', preview=false, range=NIL, register=false, keepscript=false, script_id=0, }
+ local cmd_dict2 = { addr=NIL, bang=false, bar=false, complete=NIL, complete_arg=NIL, count=NIL, definition='pwd', name='Pwd', nargs='?', preview=false, range=NIL, register=false, keepscript=false, script_id=0, }
before_each(clear)
it('gets empty list if no commands were defined', function()
@@ -59,11 +59,11 @@ describe('nvim_get_commands', function()
end)
it('gets various command attributes', function()
- local cmd0 = { addr='arguments', bang=false, bar=false, complete='dir', complete_arg=NIL, count='10', definition='pwd <args>', name='TestCmd', nargs='1', range='10', register=false, keepscript=false, script_id=0, }
- local cmd1 = { addr=NIL, bang=false, bar=false, complete='custom', complete_arg='ListUsers', count=NIL, definition='!finger <args>', name='Finger', nargs='+', range=NIL, register=false, keepscript=false, script_id=1, }
- local cmd2 = { addr=NIL, bang=true, bar=false, complete=NIL, complete_arg=NIL, count=NIL, definition='call \128\253R2_foo(<q-args>)', name='Cmd2', nargs='*', range=NIL, register=false, keepscript=false, script_id=2, }
- local cmd3 = { addr=NIL, bang=false, bar=true, complete=NIL, complete_arg=NIL, count=NIL, definition='call \128\253R3_ohyeah()', name='Cmd3', nargs='0', range=NIL, register=false, keepscript=false, script_id=3, }
- local cmd4 = { addr=NIL, bang=false, bar=false, complete=NIL, complete_arg=NIL, count=NIL, definition='call \128\253R4_just_great()', name='Cmd4', nargs='0', range=NIL, register=true, keepscript=false, script_id=4, }
+ local cmd0 = { addr='arguments', bang=false, bar=false, complete='dir', complete_arg=NIL, count='10', definition='pwd <args>', name='TestCmd', nargs='1', preview=false, range='10', register=false, keepscript=false, script_id=0, }
+ local cmd1 = { addr=NIL, bang=false, bar=false, complete='custom', complete_arg='ListUsers', count=NIL, definition='!finger <args>', name='Finger', nargs='+', preview=false, range=NIL, register=false, keepscript=false, script_id=1, }
+ local cmd2 = { addr=NIL, bang=true, bar=false, complete=NIL, complete_arg=NIL, count=NIL, definition='call \128\253R2_foo(<q-args>)', name='Cmd2', nargs='*', preview=false, range=NIL, register=false, keepscript=false, script_id=2, }
+ local cmd3 = { addr=NIL, bang=false, bar=true, complete=NIL, complete_arg=NIL, count=NIL, definition='call \128\253R3_ohyeah()', name='Cmd3', nargs='0', preview=false, range=NIL, register=false, keepscript=false, script_id=3, }
+ local cmd4 = { addr=NIL, bang=false, bar=false, complete=NIL, complete_arg=NIL, count=NIL, definition='call \128\253R4_just_great()', name='Cmd4', nargs='0', preview=false, range=NIL, register=true, keepscript=false, script_id=4, }
source([[
let s:foo = 1
command -complete=custom,ListUsers -nargs=+ Finger !finger <args>
diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua
index ca5e269f92..fdd1504b13 100644
--- a/test/functional/ui/float_spec.lua
+++ b/test/functional/ui/float_spec.lua
@@ -3660,10 +3660,10 @@ describe('float window', function()
screen:expect{grid=[[
## grid 1
[2:----------------------------------------]|
- {5:[No Name] }|
- [5:----------------------------------------]|
- [5:----------------------------------------]|
- [5:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
{5:[Preview] }|
[3:----------------------------------------]|
## grid 2
@@ -3674,10 +3674,6 @@ describe('float window', function()
{17:f}{1:oo }|
{17:b}{1:ar }|
{1: }|
- ## grid 5
- |1| {17:f}oo |
- |2| {17:b}ar |
- {0:~ }|
]], float_pos=expected_pos}
else
screen:expect([[
diff --git a/test/functional/ui/inccommand_spec.lua b/test/functional/ui/inccommand_spec.lua
index a1ff778da1..a95cb0e83a 100644
--- a/test/functional/ui/inccommand_spec.lua
+++ b/test/functional/ui/inccommand_spec.lua
@@ -255,42 +255,6 @@ describe(":substitute, 'inccommand' preserves", function()
end)
end
- for _, case in pairs{"", "split", "nosplit"} do
- it("visual selection for non-previewable command (inccommand="..case..") #5888", function()
- local screen = Screen.new(30,10)
- common_setup(screen, case, default_text)
- feed('1G2V')
-
- feed(':s')
- screen:expect([[
- {vis:Inc substitution on} |
- t{vis:wo lines} |
- |
- {15:~ }|
- {15:~ }|
- {15:~ }|
- {15:~ }|
- {15:~ }|
- {15:~ }|
- :'<,'>s^ |
- ]])
-
- feed('o')
- screen:expect([[
- {vis:Inc substitution on} |
- t{vis:wo lines} |
- |
- {15:~ }|
- {15:~ }|
- {15:~ }|
- {15:~ }|
- {15:~ }|
- {15:~ }|
- :'<,'>so^ |
- ]])
- end)
- end
-
for _, case in ipairs({'', 'split', 'nosplit'}) do
it('previous substitute string ~ (inccommand='..case..') #12109', function()
local screen = Screen.new(30,10)
diff --git a/test/functional/ui/inccommand_user_spec.lua b/test/functional/ui/inccommand_user_spec.lua
new file mode 100644
index 0000000000..e2cd82943e
--- /dev/null
+++ b/test/functional/ui/inccommand_user_spec.lua
@@ -0,0 +1,329 @@
+local helpers = require('test.functional.helpers')(after_each)
+local Screen = require('test.functional.ui.screen')
+local clear = helpers.clear
+local exec_lua = helpers.exec_lua
+local insert = helpers.insert
+local feed = helpers.feed
+local command = helpers.command
+
+-- Implements a :Replace command that works like :substitute.
+local setup_replace_cmd = [[
+ local function show_replace_preview(buf, use_preview_win, preview_ns, preview_buf, matches)
+ -- Find the width taken by the largest line number, used for padding the line numbers
+ local highest_lnum = math.max(matches[#matches][1], 1)
+ local highest_lnum_width = math.floor(math.log10(highest_lnum))
+ local preview_buf_line = 0
+
+ vim.g.prevns = preview_ns
+ vim.g.prevbuf = preview_buf
+
+ for _, match in ipairs(matches) do
+ local lnum = match[1]
+ local line_matches = match[2]
+ local prefix
+
+ if use_preview_win then
+ prefix = string.format(
+ '|%s%d| ',
+ string.rep(' ', highest_lnum_width - math.floor(math.log10(lnum))),
+ lnum
+ )
+
+ vim.api.nvim_buf_set_lines(
+ preview_buf,
+ preview_buf_line,
+ preview_buf_line,
+ 0,
+ { prefix .. vim.api.nvim_buf_get_lines(buf, lnum - 1, lnum, false)[1] }
+ )
+ end
+
+ for _, line_match in ipairs(line_matches) do
+ vim.api.nvim_buf_add_highlight(
+ buf,
+ preview_ns,
+ 'Substitute',
+ lnum - 1,
+ line_match[1],
+ line_match[2]
+ )
+
+ if use_preview_win then
+ vim.api.nvim_buf_add_highlight(
+ preview_buf,
+ preview_ns,
+ 'Substitute',
+ preview_buf_line,
+ #prefix + line_match[1],
+ #prefix + line_match[2]
+ )
+ end
+ end
+
+ preview_buf_line = preview_buf_line + 1
+ end
+
+ if use_preview_win then
+ return 2
+ else
+ return 1
+ end
+ end
+
+ local function do_replace(opts, preview, preview_ns, preview_buf)
+ local pat1 = opts.fargs[1] or ''
+ local pat2 = opts.fargs[2] or ''
+ local line1 = opts.line1
+ local line2 = opts.line2
+
+ local buf = vim.api.nvim_get_current_buf()
+ local lines = vim.api.nvim_buf_get_lines(buf, line1 - 1, line2, 0)
+ local matches = {}
+
+ for i, line in ipairs(lines) do
+ local startidx, endidx = 0, 0
+ local line_matches = {}
+ local num = 1
+
+ while startidx ~= -1 do
+ local match = vim.fn.matchstrpos(line, pat1, 0, num)
+ startidx, endidx = match[2], match[3]
+
+ if startidx ~= -1 then
+ line_matches[#line_matches+1] = { startidx, endidx }
+ end
+
+ num = num + 1
+ end
+
+ if #line_matches > 0 then
+ matches[#matches+1] = { line1 + i - 1, line_matches }
+ end
+ end
+
+ local new_lines = {}
+
+ for _, match in ipairs(matches) do
+ local lnum = match[1]
+ local line_matches = match[2]
+ local line = lines[lnum - line1 + 1]
+ local pat_width_differences = {}
+
+ -- If previewing, only replace the text in current buffer if pat2 isn't empty
+ -- Otherwise, always replace the text
+ if pat2 ~= '' or not preview then
+ if preview then
+ for _, line_match in ipairs(line_matches) do
+ local startidx, endidx = unpack(line_match)
+ local pat_match = line:sub(startidx + 1, endidx)
+
+ pat_width_differences[#pat_width_differences+1] =
+ #vim.fn.substitute(pat_match, pat1, pat2, 'g') - #pat_match
+ end
+ end
+
+ new_lines[lnum] = vim.fn.substitute(line, pat1, pat2, 'g')
+ end
+
+ -- Highlight the matches if previewing
+ if preview then
+ local idx_offset = 0
+ for i, line_match in ipairs(line_matches) do
+ local startidx, endidx = unpack(line_match)
+ -- Starting index of replacement text
+ local repl_startidx = startidx + idx_offset
+ -- Ending index of the replacement text (if pat2 isn't empty)
+ local repl_endidx
+
+ if pat2 ~= '' then
+ repl_endidx = endidx + idx_offset + pat_width_differences[i]
+ else
+ repl_endidx = endidx + idx_offset
+ end
+
+ if pat2 ~= '' then
+ idx_offset = idx_offset + pat_width_differences[i]
+ end
+
+ line_matches[i] = { repl_startidx, repl_endidx }
+ end
+ end
+ end
+
+ for lnum, line in pairs(new_lines) do
+ vim.api.nvim_buf_set_lines(buf, lnum - 1, lnum, false, { line })
+ end
+
+ if preview then
+ local lnum = vim.api.nvim_win_get_cursor(0)[1]
+ -- Use preview window only if preview buffer is provided and range isn't just the current line
+ local use_preview_win = (preview_buf ~= nil) and (line1 ~= lnum or line2 ~= lnum)
+ return show_replace_preview(buf, use_preview_win, preview_ns, preview_buf, matches)
+ end
+ end
+
+ local function replace(opts)
+ do_replace(opts, false)
+ end
+
+ local function replace_preview(opts, preview_ns, preview_buf)
+ return do_replace(opts, true, preview_ns, preview_buf)
+ end
+
+ -- ":<range>Replace <pat1> <pat2>"
+ -- Replaces all occurences of <pat1> in <range> with <pat2>
+ vim.api.nvim_create_user_command(
+ 'Replace',
+ replace,
+ { nargs = '*', range = '%', addr = 'lines',
+ preview = replace_preview }
+ )
+]]
+
+describe("'inccommand' for user commands", function()
+ local screen
+
+ before_each(function()
+ clear()
+ screen = Screen.new(40, 17)
+ screen:set_default_attr_ids({
+ [1] = {background = Screen.colors.Yellow1},
+ [2] = {foreground = Screen.colors.Blue1, bold = true},
+ [3] = {reverse = true},
+ [4] = {reverse = true, bold = true}
+ })
+ screen:attach()
+ exec_lua(setup_replace_cmd)
+ command('set cmdwinheight=5')
+ insert[[
+ text on line 1
+ more text on line 2
+ oh no, even more text
+ will the text ever stop
+ oh well
+ did the text stop
+ why won't it stop
+ make the text stop
+ ]]
+ end)
+
+ it('works with inccommand=nosplit', function()
+ command('set inccommand=nosplit')
+ feed(':Replace text cats')
+ screen:expect([[
+ {1:cats} on line 1 |
+ more {1:cats} on line 2 |
+ oh no, even more {1:cats} |
+ will the {1:cats} ever stop |
+ oh well |
+ did the {1:cats} stop |
+ why won't it stop |
+ make the {1:cats} stop |
+ |
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ :Replace text cats^ |
+ ]])
+ end)
+
+ it('works with inccommand=split', function()
+ command('set inccommand=split')
+ feed(':Replace text cats')
+ screen:expect([[
+ {1:cats} on line 1 |
+ more {1:cats} on line 2 |
+ oh no, even more {1:cats} |
+ will the {1:cats} ever stop |
+ oh well |
+ did the {1:cats} stop |
+ why won't it stop |
+ make the {1:cats} stop |
+ |
+ {4:[No Name] [+] }|
+ |1| {1:cats} on line 1 |
+ |2| more {1:cats} on line 2 |
+ |3| oh no, even more {1:cats} |
+ |4| will the {1:cats} ever stop |
+ |6| did the {1:cats} stop |
+ {3:[Preview] }|
+ :Replace text cats^ |
+ ]])
+ end)
+
+ it('properly closes preview when inccommand=split', function()
+ command('set inccommand=split')
+ feed(':Replace text cats<Esc>')
+ screen:expect([[
+ text on line 1 |
+ more text on line 2 |
+ oh no, even more text |
+ will the text ever stop |
+ oh well |
+ did the text stop |
+ why won't it stop |
+ make the text stop |
+ ^ |
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ |
+ ]])
+ end)
+
+ it('properly executes command when inccommand=split', function()
+ command('set inccommand=split')
+ feed(':Replace text cats<CR>')
+ screen:expect([[
+ cats on line 1 |
+ more cats on line 2 |
+ oh no, even more cats |
+ will the cats ever stop |
+ oh well |
+ did the cats stop |
+ why won't it stop |
+ make the cats stop |
+ ^ |
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ :Replace text cats |
+ ]])
+ end)
+
+ it('shows preview window only when range is not current line', function()
+ command('set inccommand=split')
+ feed('gg:.Replace text cats')
+ screen:expect([[
+ {1:cats} on line 1 |
+ more text on line 2 |
+ oh no, even more text |
+ will the text ever stop |
+ oh well |
+ did the text stop |
+ why won't it stop |
+ make the text stop |
+ |
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ :.Replace text cats^ |
+ ]])
+ end)
+end)