Merge pull request #18194 from famiu/feat/usercmd_preview

feat: user command "preview" (like inccommand)
This commit is contained in:
bfredl 2022-05-31 17:44:13 +02:00 committed by GitHub
commit 7380ebfc17
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 960 additions and 346 deletions

View File

@ -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.

View File

@ -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*

View File

@ -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")

View File

@ -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'

View File

@ -53,6 +53,7 @@ return {
"force";
"keepscript";
"nargs";
"preview";
"range";
"register";
};

View File

@ -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
}

View File

@ -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)

View File

@ -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) {

View File

@ -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

View File

@ -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);
}
}

View File

@ -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) {
@ -4312,7 +4305,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) { \
@ -4368,8 +4361,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
@ -4396,7 +4388,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 {
@ -4433,34 +4425,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
}
@ -5856,52 +5837,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++) {
@ -5916,27 +5871,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
@ -5946,10 +5891,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
@ -5988,115 +5936,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;
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)
{
block_autocmds();
buf_T *buf = preview_bufnr ? buflist_findnr(preview_bufnr) : NULL;
if (buf != NULL) {
close_windows(buf, false);
}
unblock_autocmds();
}
/// :substitute command
///
/// If 'inccommand' is empty: calls do_sub().
/// If 'inccommand' is set: shows a "live" preview then removes the changes.
/// from undo history.
/// :substitute command.
void ex_substitute(exarg_T *eap)
{
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);
(void)do_sub(eap, profile_zero(), 0, 0);
return;
}
return;
/// :substitute command preview callback.
int ex_substitute_preview(exarg_T *eap, long cmdpreview_ns, handle_T cmdpreview_bufnr)
{
// 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]".

View File

@ -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',

View File

@ -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

View File

@ -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:

View File

@ -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;

View File

@ -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)) {

View File

@ -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

View File

@ -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]

View File

@ -639,6 +639,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

View File

@ -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)) {
nlua_error(lstate, _("Error executing Lua callback: %.*s"));
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;
}

View File

@ -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 {

View File

@ -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>

View File

@ -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([[

View File

@ -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)

View File

@ -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)