Skip to content

Commit

Permalink
Merge pull request #18194 from famiu/feat/usercmd_preview
Browse files Browse the repository at this point in the history
feat: user command "preview" (like inccommand)
  • Loading branch information
bfredl committed May 31, 2022
2 parents 5d840fa + 46536f5 commit 7380ebf
Show file tree
Hide file tree
Showing 25 changed files with 958 additions and 344 deletions.
2 changes: 2 additions & 0 deletions runtime/doc/api.txt
Expand Up @@ -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.
Expand Down
106 changes: 106 additions & 0 deletions runtime/doc/map.txt
Expand Up @@ -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*
Expand Down
10 changes: 6 additions & 4 deletions runtime/doc/options.txt
Expand Up @@ -3266,17 +3266,19 @@ 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
buffer.
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")
Expand Down
2 changes: 2 additions & 0 deletions runtime/doc/vim_diff.txt
Expand Up @@ -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|
Expand Down Expand Up @@ -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'
Expand Down
1 change: 1 addition & 0 deletions src/nvim/api/keysets.lua
Expand Up @@ -53,6 +53,7 @@ return {
"force";
"keepscript";
"nargs";
"preview";
"range";
"register";
};
Expand Down
11 changes: 10 additions & 1 deletion src/nvim/api/private/helpers.c
Expand Up @@ -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");
Expand Down Expand Up @@ -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);
Expand All @@ -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
}
Expand Down
1 change: 1 addition & 0 deletions src/nvim/api/vim.c
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src/nvim/api/vimscript.c
Expand Up @@ -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) {
Expand Down
9 changes: 6 additions & 3 deletions src/nvim/buffer_updates.c
Expand Up @@ -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,
Expand All @@ -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;

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions src/nvim/change.c
Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
}
}

Expand Down

0 comments on commit 7380ebf

Please sign in to comment.