Add 'inccommand' option for live :substitute preview#19772
Add 'inccommand' option for live :substitute preview#19772jparise wants to merge 3 commits intovim:masterfrom
Conversation
0a64448 to
4537bef
Compare
| // Command type set by parse_pattern_and_range(). | ||
| typedef enum { | ||
| PPR_UNKNOWN = 0, | ||
| PPR_SUBSTITUTE, // :substitute, :smagic, :snomagic | ||
| PPR_GLOBAL, // :global, :vglobal | ||
| PPR_SORT, // :sort, :uniq | ||
| PPR_VIMGREP // :vimgrep, :vimgrepadd, :lvimgrep, etc. | ||
| } ppr_cmd_T; |
There was a problem hiding this comment.
I thought this would be a better abstraction that a single is_sub output flag, but it's also a bit speculative given that we only have a real use case for PPR_SUBSTITUTE.
(This is all to avoid re-parsing the command in the inccommand path to determine if we're dealing with a substitution.)
| return FALSE; | ||
| } | ||
|
|
||
| first_line = search_first_line == 0 ? 1 : search_first_line; |
There was a problem hiding this comment.
Is it possible to instead only do substution preview on buffer lines that are visible?
E.g. Loop through every window that is showing this buffer, find topline and botline, and find the min and max range that we must substitute preview
There was a problem hiding this comment.
Good idea! That's a nice optimization for large buffers. I implemented this across all windows in the current tab that are displaying the current buffer.
| if (state->match != NULL) | ||
| { | ||
| state->match->mit_hlg_id = | ||
| syn_namen2id((char_u *)"Substitute", |
There was a problem hiding this comment.
I think we should make the highlighting for substitution preview use the 'highlight' option, see the hlf_T enum. Then we can make that occasion use the "Substitute" group by default (see HIGHLIGHT_INIT in optiondefs.h)
| continue; | ||
|
|
||
| // Skip multi-line matches. | ||
| if (regmatch.endpos[0].lnum > 0) |
There was a problem hiding this comment.
I can get the expression replacement side effects, but what side effects would multi-lime matching have? Neovim supports it
There was a problem hiding this comment.
I restricted this in the first version to simplify things. I should have called that out explicitly.
Multi-line matches complicate the preview implementation/restore because they can add or remove lines, but there's otherwise no reason not to support them. I'll do that if it looks like this feature is heading in the right direction.
|
anybody, please provide feedback |
|
I think that this feature, as it stands, is overly specific. If interactive edit preview is going to be provided then it should be available for every command. I don't think there's anything special about |
|
By the way, Neovim seems to have forgotten the (Added:) |
Perhaps something like neovim's |
The new 'inccommand' boolean option shows a live preview of :substitute replacements as you type on the command line. When enabled alongside 'incsearch', typing :%s/foo/bar/ will temporarily modify the buffer to show "bar" in place of "foo", updating on each keystroke. Pressing Escape restores the original buffer; pressing Enter executes the substitution normally. This is a frequently requested feature (vim#14868, vim#10205). Neovim has had 'inccommand' since 2017, and the traces.vim plugin provides similar functionality. This implementation brings the core functionality natively to Vim, using a boolean option rather than Neovim's "nosplit"/"split" approach. The "split" preview window could be added later. The replacement text is highlighted using the new Substitute highlight group, which links to IncSearch by default. This makes it easy to distinguish what changed at a glance, and colorschemes can customize it independently. For non-substitute commands like :global, :vglobal, and :sort, the option enhances the existing incsearch behavior by highlighting all pattern matches in the range with IncSearch, not just the first one. This feature works by saving original lines, applying the substitutions, redrawing, and then restoring. We share some logic with ex_substitute(), but there's a bit of code duplication that could be revisited. Expression replacements (\=) and multi-line matches are skipped during preview since they have side effects or require more complex handling. The command still executes normally when Enter is pressed. Signed-off-by: Jon Parise <jon@indelible.org>
|
Based on the "Principle of Least Astonishment," I am opposed to adding this option. Even with highlighting, it would temporarily overwrite the normal buffer display. |
|
If there was any principle that only buffer content characters may be displayed, it has been broken since the introduction of |
|
I don't use flashy "conceal" features in my source code, and I don't think any such runtimes are bundled. Even if they exist, they shouldn't be enabled by default. Are you going to bring up :h textprop next? (bitter smile) Sure, you can do anything with map, abbrev, or autocmd if you really want to. But we don't include such "weird" settings by default, do we? Are you trying to say, "Don't worry, 'inccommand' is off by default"? Hmm... I feel this doesn't align with (what I consider) the Vim philosophy. The fact that it hasn't been ported to Vim until now seems to speak for itself. |
|
I am not against this, when this is off per default In fact, I think this improves usability a bit, because one can see what effect the :s command can have, so I can see definitely merit of such an enhancement. |
I believe this is quite a useful improvement and I agree it would be good to have for other related commands, e.g. |
That would be nice, but I think implementing it only for native commands would be fine, at least for a first pass. My main point was that this is currently an ad-hoc feature available for a single command that is really introducing a new feature class, being interactive edit preview. It makes no sense to me as a user for this to work for Unless it's implemented as a full edit preview feature it just feels like the inclusion of "plugin of the month" to me, something that has usually been carefully avoided. |
Isn't this exactly what plugins are for? We already have traces.vim which looks like it's more functional (assuming it works as advertised) than this PR. I think "usability improvement" is such a low bar that there's probably many plugins ahead in the queue. If the primary obstacle to having these included in core is a working PR, and we're willing to accept LLM generated PRs, we're on a very slippery slope. |
|
That is a fair point. We have now 2 maintainers voting against inclusion as of now. let's give it a bit more discussion time. |
|
I worked on this for a few reasons: it was previously discussed with (nice-to-have) support on vim-dev; it's been natively available in neovim for a number of years; and That being said, I also understand the arguments against including this directly in vim, especially when plugins like traces.vim already exist. There does appear to be some positive sentiment that an optional (and generalized) preview mode could be useful, so maybe that's a place to steer this discussion: a separate preview buffer, something like neovim's |
The new 'inccommand' boolean option shows a live preview of :substitute replacements as you type on the command line. When enabled alongside 'incsearch', typing :%s/foo/bar/ will temporarily modify the buffer to show "bar" in place of "foo", updating on each keystroke. Pressing Escape restores the original buffer; pressing Enter executes the substitution normally.
This is a frequently requested feature (#14868, #10205). Neovim has had 'inccommand' since 2017, and the traces.vim plugin provides similar functionality. This implementation brings the core functionality natively to Vim, using a boolean option rather than Neovim's "nosplit"/"split" approach. The "split" preview window could be added later.
The replacement text is highlighted using the new Substitute highlight group, which links to IncSearch by default. This makes it easy to distinguish what changed at a glance, and colorschemes can customize it independently.
For non-substitute commands like :global, :vglobal, and :sort, the option enhances the existing incsearch behavior by highlighting all pattern matches in the range with IncSearch, not just the first one.
This feature works by saving original lines, applying the substitutions, redrawing, and then restoring. We share some logic with ex_substitute(), but there's a bit of code duplication that could be revisited.
Expression replacements (=) and multi-line matches are skipped during preview since they have side effects or require more complex handling. The command still executes normally when Enter is pressed.
Resolves: #14868
See also: #10205
See also: https://groups.google.com/g/vim_dev/c/FiUC3_mSv_o
I used a good amount of AI assistance (Claude Code) for the initial research and in building the core functions because I hadn't used the buffer internals before. It also helped generate the test cases.