Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: user command "preview" (like inccommand) #18194

Merged
merged 1 commit into from May 31, 2022

Conversation

famiu
Copy link
Member

@famiu famiu commented Apr 20, 2022

This PR aims to add support for inccommand incremental preview functionality to user commands. It defines an incremental preview "protocol" that a user command can follow to add support for incremental live preview of that command when inccommand is enabled. This PR adds an API-only preview flag to user commands which allows a command to be incrementally previewed.

Closes #7370

runtime/doc/map.txt Outdated Show resolved Hide resolved
src/nvim/api/private/helpers.c Outdated Show resolved Hide resolved
src/nvim/api/vim.c Outdated Show resolved Hide resolved
runtime/doc/map.txt Outdated Show resolved Hide resolved
runtime/doc/map.txt Outdated Show resolved Hide resolved
runtime/doc/map.txt Outdated Show resolved Hide resolved
runtime/doc/api.txt Outdated Show resolved Hide resolved
runtime/doc/map.txt Outdated Show resolved Hide resolved
runtime/doc/map.txt Outdated Show resolved Hide resolved
runtime/doc/map.txt Outdated Show resolved Hide resolved
@justinmk
Copy link
Member

justinmk commented Apr 23, 2022

#18194 (comment)

Don't know if we want to extend the vimscript/ex-mode implementation this has been avoided in the past (#17426) favoring implementation on the API level.

💯 do not add more Vimscript syntax without a very strong reason.

I think we should make an exception in this case personally since it only has any effect when inccommand, a Neovim-specific feature is enabled, so it shouldn't conflict with Vim. But I'm free to make changes if required

I don't understand the reasoning there. Why is this worth increasing the surface area of code we need to maintain, documentation that users need to read and understand, syntax elements that need to be highlighted, errors that need to be surfaced, ...? Using :command intead of the API to define complex functions is just pointless.

src/nvim/ex_cmds_defs.h Outdated Show resolved Hide resolved
@justinmk
Copy link
Member

Does this close #7370 or at least partially address it? Please reference relevant issues.

@famiu
Copy link
Member Author

famiu commented Apr 23, 2022

Does this close #7370 or at least partially address it? Please reference relevant issues.

This does resolve most of #7370

@famiu
Copy link
Member Author

famiu commented Apr 23, 2022

#18194 (comment)

Don't know if we want to extend the vimscript/ex-mode implementation this has been avoided in the past (#17426) favoring implementation on the API level.

100 do not add more Vimscript syntax without a very strong reason.

Alright, I'll make it API-only in that case

I think we should make an exception in this case personally since it only has any effect when inccommand, a Neovim-specific feature is enabled, so it shouldn't conflict with Vim. But I'm free to make changes if required

I don't understand the reasoning there. Why is this worth increasing the surface area of code we need to maintain, documentation that users need to read and understand, syntax elements that need to be highlighted, errors that need to be surfaced, ...? Using :command intead of the API to define complex functions is just pointless.

I have no issue making it API-only. But I'm just wondering how it should be documented in that case. Putting it with other command attributes in the documentation would be weird since it'd be Lua only, and it's too much information to put in the documentation of nvim_create_user_command. Any suggestions regarding that?

@clason
Copy link
Member

clason commented Apr 23, 2022

I have no issue making it API-only. But I'm just wondering how it should be documented in that case. Putting it with other command attributes in the documentation would be weird since it'd be Lua only, and it's too much information to put in the documentation of nvim_create_user_command. Any suggestions regarding that?

There's precedent for that: the arguments passed to (only) Lua autocommand callbacks.

A more structured way of indicating different API exposure (FUNC_API_*) in the docs would be nice, though.

@justinmk
Copy link
Member

justinmk commented Apr 23, 2022

Putting it with other command attributes in the documentation would be weird since it'd be Lua only,

True, but that can be improved later. For example the list can become agnostic, and then referenced from :help :command and :help nvim_.... For now just slap a "(Nvim API only)" or something like that.

Vimscript is the "eval" part of Vim (including :function, :command, etc). The "editor DSL" is Ex and normal-mode commands, which is great for ad-hoc editing work. Vimscript is frozen.

@justinmk justinmk changed the title feat: add preview functionality to user commands feat: user command "preview" support (like inccommand) Apr 23, 2022
@justinmk justinmk changed the title feat: user command "preview" support (like inccommand) feat: user command "preview" (like inccommand) Apr 23, 2022
@famiu famiu requested a review from justinmk April 23, 2022 20:05
runtime/doc/builtin.txt Outdated Show resolved Hide resolved
runtime/doc/map.txt Outdated Show resolved Hide resolved
runtime/doc/map.txt Outdated Show resolved Hide resolved
runtime/doc/map.txt Outdated Show resolved Hide resolved
runtime/doc/map.txt Outdated Show resolved Hide resolved
runtime/doc/map.txt Outdated Show resolved Hide resolved
runtime/doc/map.txt Outdated Show resolved Hide resolved
runtime/doc/map.txt Outdated Show resolved Hide resolved
@famiu
Copy link
Member Author

famiu commented May 28, 2022

I meant just with cursor on line 1, which should mean the same thing. The amount of implicit state in ex commands still makes me nauseous...

I think that's moreso the fault of parse_cmdline only parsing the command and not setting defaults values for stuff like line2

@famiu famiu force-pushed the feat/usercmd_preview branch 3 times, most recently from 361b708 to 3c14cb1 Compare May 29, 2022 19:27
runtime/doc/map.txt Outdated Show resolved Hide resolved
runtime/doc/map.txt Outdated Show resolved Hide resolved
runtime/doc/map.txt Outdated Show resolved Hide resolved
runtime/doc/map.txt Outdated Show resolved Hide resolved
Copy link
Member

@justinmk justinmk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getting pretty close! A bit worried about the no_send_tick / changedtick buffer flag

src/nvim/buffer_defs.h Outdated Show resolved Hide resolved
local command = helpers.command

-- Implementation of a :Replace command that's used for testing user inccommand
local SetupReplaceCmd = [[
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Let's stick to one convention. all Lua code in the codebase uses snake_case.
  • This function is huge. Is it necessary for the test? Any way to reduce the code?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we could try having a completely different type of preview function entirely. I made one that mimics :substitute because that's currently the standard for how command preview should behave.

test/functional/ui/inccommand_user_spec.lua Outdated Show resolved Hide resolved
@@ -253,7 +253,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)) {
Copy link
Member

@justinmk justinmk May 30, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why did this change from a formal "editor state", to a global variable (an implicit pseudo-state)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quite a few functions reset the value of State, which caused inccommand preview for some commands to not work properly, changing it to a global state fixes that issue.

src/nvim/ex_cmds.lua Show resolved Hide resolved
src/nvim/ex_cmds_defs.h Outdated Show resolved Hide resolved
src/nvim/lua/executor.c Show resolved Hide resolved
src/nvim/ex_getln.c Outdated Show resolved Hide resolved
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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did find_ex_command have these side-effects before this PR? is that avoidable?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It did. And I think it's fine the way it is since find_ex_command also has some other side-effects, so if we wanted to remove side-effects we'd have to remove all of them, which'd be quite a hassle.

src/nvim/ex_docmd.c Show resolved Hide resolved
src/nvim/ex_getln.c Outdated Show resolved Hide resolved
runtime/doc/map.txt Outdated Show resolved Hide resolved
runtime/doc/map.txt Outdated Show resolved Hide resolved
line1 - 1,
line2,
0,
new_lines
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The example will look a lot less intimidating if we just don't wrap the function args like this. Don't worry too much about exceeding 80 chars for code examples, if it helps with readability.

runtime/doc/map.txt Outdated Show resolved Hide resolved
@@ -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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The buf == curbuf condition makes me wonder if the cmdpreview protocol should forbid switching buffers? But it is common for plugins to temporarily switch buffers, and we don't have a way of preventing it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's fine. If a plugin does something stupid, it's the plugin's fault and Neovim doesn't have to deal with that

Adds a Lua-only `preview` flag to user commands which allows the command to be incrementally previewed like `:substitute` when 'inccommand' is set.
@bfredl bfredl merged commit 7380ebf into neovim:master May 31, 2022
@famiu famiu deleted the feat/usercmd_preview branch May 31, 2022 16:04
/// 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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A key feature of 'inccommand' :substitute is that it skips the preview if the preview takes too long:

neovim/src/nvim/ex_cmds.c

Lines 4430 to 4432 in 46536f5

if (got_quit || profile_passed_limit(timeout)) { // Too slow, disable.
set_string_option_direct("icm", -1, (char_u *)"", OPT_FREE,
SID_NONE);

That should also be built into the handling of user-defined cmd preview.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another important optimization was to stop looking for matches if additional ones would not be shown to the user (either the preview buffer is full and icm=split, or they're out of the viewport of the current buffer with icm=nosplit). This PR is too complicated for me, so I could not discern if that was implemented, but I figured I'd mention it (ofc it's most helpfull in large files).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A key feature of 'inccommand' :substitute is that it skips the preview if the preview takes too long:

neovim/src/nvim/ex_cmds.c

Lines 4430 to 4432 in 46536f5

if (got_quit || profile_passed_limit(timeout)) { // Too slow, disable.
set_string_option_direct("icm", -1, (char_u *)"", OPT_FREE,
SID_NONE);

That should also be built into the handling of user-defined cmd preview.

How should we implement that though? Or should we leave that to the preview callback?

Copy link
Member

@justinmk justinmk May 31, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How should we implement that though? Or should we leave that to the preview callback?

Should be done implicitly, builtin.

Another important optimization was to stop looking for matches ... I could not discern if that was implemented,

@KillTheMule I believe that is covered by tests so I don't think there was a regression.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

inccommand: plugin support ( :[range]normal , :global, ...)
9 participants