mirror of
https://github.com/neovim/neovim.git
synced 2025-09-08 12:28:18 +00:00
fix(inccommand): parse the command to check if it is previewable
Free regprog if command isn't previewable Co-authored-by: zeertzjq <zeertzjq@outlook.com>
This commit is contained in:
@@ -13,6 +13,7 @@
|
|||||||
#include "nvim/ex_docmd.h"
|
#include "nvim/ex_docmd.h"
|
||||||
#include "nvim/lua/executor.h"
|
#include "nvim/lua/executor.h"
|
||||||
#include "nvim/ops.h"
|
#include "nvim/ops.h"
|
||||||
|
#include "nvim/regexp.h"
|
||||||
#include "nvim/window.h"
|
#include "nvim/window.h"
|
||||||
|
|
||||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||||
@@ -94,6 +95,7 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err)
|
|||||||
}
|
}
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
vim_regfree(cmdinfo.cmdmod.cmod_filter_regmatch.regprog);
|
||||||
|
|
||||||
// Parse arguments
|
// Parse arguments
|
||||||
Array args = ARRAY_DICT_INIT;
|
Array args = ARRAY_DICT_INIT;
|
||||||
|
@@ -1405,6 +1405,8 @@ bool is_cmd_ni(cmdidx_T cmdidx)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Parse command line and return information about the first command.
|
/// Parse command line and return information about the first command.
|
||||||
|
/// If parsing is done successfully, need to free cmod_filter_regmatch.regprog after calling,
|
||||||
|
/// usually done using undo_cmdmod() or execute_cmd().
|
||||||
///
|
///
|
||||||
/// @param cmdline Command line string
|
/// @param cmdline Command line string
|
||||||
/// @param[out] eap Ex command arguments
|
/// @param[out] eap Ex command arguments
|
||||||
@@ -1432,6 +1434,7 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er
|
|||||||
|
|
||||||
// Parse command modifiers
|
// Parse command modifiers
|
||||||
if (parse_command_modifiers(eap, errormsg, &cmdinfo->cmdmod, false) == FAIL) {
|
if (parse_command_modifiers(eap, errormsg, &cmdinfo->cmdmod, false) == FAIL) {
|
||||||
|
vim_regfree(cmdinfo->cmdmod.cmod_filter_regmatch.regprog);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
after_modifier = eap->cmd;
|
after_modifier = eap->cmd;
|
||||||
@@ -10040,44 +10043,6 @@ static void ex_terminal(exarg_T *eap)
|
|||||||
do_cmdline_cmd(ex_cmd);
|
do_cmdline_cmd(ex_cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if `cmd` is "previewable" (i.e. supported by 'inccommand').
|
|
||||||
///
|
|
||||||
/// @param[in] cmd Commandline to check. May start with a range or modifier.
|
|
||||||
///
|
|
||||||
/// @return true if `cmd` is previewable
|
|
||||||
bool cmd_can_preview(char *cmd)
|
|
||||||
{
|
|
||||||
if (cmd == NULL) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore additional colons at the start...
|
|
||||||
cmd = skip_colon_white(cmd, true);
|
|
||||||
|
|
||||||
// Ignore any leading modifiers (:keeppatterns, :verbose, etc.)
|
|
||||||
for (int len = modifier_len(cmd); len != 0; len = modifier_len(cmd)) {
|
|
||||||
cmd += len;
|
|
||||||
cmd = skip_colon_white(cmd, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
exarg_T ea;
|
|
||||||
memset(&ea, 0, sizeof(ea));
|
|
||||||
// parse the command line
|
|
||||||
ea.cmd = skip_range(cmd, NULL);
|
|
||||||
if (*ea.cmd == '*') {
|
|
||||||
ea.cmd = skipwhite(ea.cmd + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (find_ex_command(&ea, NULL) == NULL || 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 (ea.argt & EX_PREVIEW);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets a map of maps describing user-commands defined for buffer `buf` or
|
/// Gets a map of maps describing user-commands defined for buffer `buf` or
|
||||||
/// defined globally if `buf` is NULL.
|
/// defined globally if `buf` is NULL.
|
||||||
///
|
///
|
||||||
|
@@ -2372,7 +2372,7 @@ static void cmdpreview_close_win(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Show 'inccommand' preview. It works like this:
|
/// Show 'inccommand' preview if command is previewable. It works like this:
|
||||||
/// 1. Store current undo information so we can revert to current state later.
|
/// 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
|
/// 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
|
/// namespace number as arguments. The preview callback sets the highlight and does the
|
||||||
@@ -2385,12 +2385,15 @@ static void cmdpreview_close_win(void)
|
|||||||
/// 5. If the return value of the preview callback is not 0, update the screen while the effects
|
/// 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.
|
/// of the preview are still in place.
|
||||||
/// 6. Revert all changes made by the preview callback.
|
/// 6. Revert all changes made by the preview callback.
|
||||||
static void cmdpreview_show(CommandLineState *s)
|
///
|
||||||
|
/// @return whether preview is shown or not.
|
||||||
|
static bool cmdpreview_may_show(CommandLineState *s)
|
||||||
{
|
{
|
||||||
// Parse the command line and return if it fails.
|
// Parse the command line and return if it fails.
|
||||||
exarg_T ea;
|
exarg_T ea;
|
||||||
CmdParseInfo cmdinfo;
|
CmdParseInfo cmdinfo;
|
||||||
// Copy the command line so we can modify it.
|
// Copy the command line so we can modify it.
|
||||||
|
int cmdpreview_type = 0;
|
||||||
char *cmdline = xstrdup((char *)ccline.cmdbuff);
|
char *cmdline = xstrdup((char *)ccline.cmdbuff);
|
||||||
char *errormsg = NULL;
|
char *errormsg = NULL;
|
||||||
emsg_off++; // Block errors when parsing the command line, and don't update v:errmsg
|
emsg_off++; // Block errors when parsing the command line, and don't update v:errmsg
|
||||||
@@ -2400,6 +2403,12 @@ static void cmdpreview_show(CommandLineState *s)
|
|||||||
}
|
}
|
||||||
emsg_off--;
|
emsg_off--;
|
||||||
|
|
||||||
|
// Check if command is previewable, if not, don't attempt to show preview
|
||||||
|
if (!(ea.argt & EX_PREVIEW)) {
|
||||||
|
vim_regfree(cmdinfo.cmdmod.cmod_filter_regmatch.regprog);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
// Swap invalid command range if needed
|
// Swap invalid command range if needed
|
||||||
if ((ea.argt & EX_RANGE) && ea.line1 > ea.line2) {
|
if ((ea.argt & EX_RANGE) && ea.line1 > ea.line2) {
|
||||||
linenr_T lnum = ea.line1;
|
linenr_T lnum = ea.line1;
|
||||||
@@ -2454,7 +2463,7 @@ static void cmdpreview_show(CommandLineState *s)
|
|||||||
// the preview.
|
// the preview.
|
||||||
Error err = ERROR_INIT;
|
Error err = ERROR_INIT;
|
||||||
try_start();
|
try_start();
|
||||||
int cmdpreview_type = execute_cmd(&ea, &cmdinfo, true);
|
cmdpreview_type = execute_cmd(&ea, &cmdinfo, true);
|
||||||
if (try_end(&err)) {
|
if (try_end(&err)) {
|
||||||
api_clear_error(&err);
|
api_clear_error(&err);
|
||||||
cmdpreview_type = 0;
|
cmdpreview_type = 0;
|
||||||
@@ -2518,14 +2527,9 @@ static void cmdpreview_show(CommandLineState *s)
|
|||||||
update_topline(curwin);
|
update_topline(curwin);
|
||||||
|
|
||||||
redrawcmdline();
|
redrawcmdline();
|
||||||
|
|
||||||
// If preview callback returned 0, update screen to clear remnants of an earlier preview.
|
|
||||||
if (cmdpreview_type == 0) {
|
|
||||||
cmdpreview = false;
|
|
||||||
update_screen(SOME_VALID);
|
|
||||||
}
|
|
||||||
end:
|
end:
|
||||||
xfree(cmdline);
|
xfree(cmdline);
|
||||||
|
return cmdpreview_type != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int command_line_changed(CommandLineState *s)
|
static int command_line_changed(CommandLineState *s)
|
||||||
@@ -2564,9 +2568,9 @@ static int command_line_changed(CommandLineState *s)
|
|||||||
&& *p_icm != NUL // 'inccommand' is set
|
&& *p_icm != NUL // 'inccommand' is set
|
||||||
&& curbuf->b_p_ma // buffer is modifiable
|
&& curbuf->b_p_ma // buffer is modifiable
|
||||||
&& cmdline_star == 0 // not typing a password
|
&& cmdline_star == 0 // not typing a password
|
||||||
&& cmd_can_preview((char *)ccline.cmdbuff)
|
&& !vpeekc_any()
|
||||||
&& !vpeekc_any()) {
|
&& cmdpreview_may_show(s)) {
|
||||||
cmdpreview_show(s);
|
// 'inccommand' preview has been shown.
|
||||||
} else if (cmdpreview) {
|
} else if (cmdpreview) {
|
||||||
cmdpreview = false;
|
cmdpreview = false;
|
||||||
update_screen(SOME_VALID); // Clear 'inccommand' preview.
|
update_screen(SOME_VALID); // Clear 'inccommand' preview.
|
||||||
|
@@ -3008,3 +3008,16 @@ it('long :%s/ with inccommand does not collapse cmdline', function()
|
|||||||
AAAAAAA^ |
|
AAAAAAA^ |
|
||||||
]])
|
]])
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it("with 'inccommand' typing :filter doesn't segfault or leak memory #19057", function()
|
||||||
|
clear()
|
||||||
|
common_setup(nil, 'nosplit')
|
||||||
|
feed(':filter s')
|
||||||
|
assert_alive()
|
||||||
|
feed(' ')
|
||||||
|
assert_alive()
|
||||||
|
feed('h')
|
||||||
|
assert_alive()
|
||||||
|
feed('i')
|
||||||
|
assert_alive()
|
||||||
|
end)
|
||||||
|
Reference in New Issue
Block a user