fix(api): avoid side effects with nvim_parse_cmd (#19890)

Save and restore the cursor and last search pattern and do not change
search history.
This commit is contained in:
zeertzjq
2022-08-22 18:06:18 +08:00
committed by GitHub
parent 2bed0d1d97
commit 15a768eeb0
2 changed files with 74 additions and 16 deletions

View File

@@ -1361,6 +1361,13 @@ bool is_cmd_ni(cmdidx_T cmdidx)
bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **errormsg) bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **errormsg)
{ {
char *after_modifier = NULL; char *after_modifier = NULL;
bool retval = false;
// parsing the command modifiers may set ex_pressedreturn
const bool save_ex_pressedreturn = ex_pressedreturn;
// parsing the command range may require moving the cursor
const pos_T save_cursor = curwin->w_cursor;
// parsing the command range may set the last search pattern
save_last_search_pattern();
// Initialize cmdinfo // Initialize cmdinfo
CLEAR_POINTER(cmdinfo); CLEAR_POINTER(cmdinfo);
@@ -1375,13 +1382,10 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er
.cookie = NULL, .cookie = NULL,
}; };
const bool save_ex_pressedreturn = ex_pressedreturn;
// 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) {
ex_pressedreturn = save_ex_pressedreturn; goto end;
goto err;
} }
ex_pressedreturn = save_ex_pressedreturn;
after_modifier = eap->cmd; after_modifier = eap->cmd;
// Save location after command modifiers // Save location after command modifiers
@@ -1394,21 +1398,21 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er
char *p = find_ex_command(eap, NULL); char *p = find_ex_command(eap, NULL);
if (p == NULL) { if (p == NULL) {
*errormsg = _(e_ambiguous_use_of_user_defined_command); *errormsg = _(e_ambiguous_use_of_user_defined_command);
goto err; goto end;
} }
// Set command address type and parse command range // Set command address type and parse command range
set_cmd_addr_type(eap, p); set_cmd_addr_type(eap, p);
eap->cmd = cmd; eap->cmd = cmd;
if (parse_cmd_address(eap, errormsg, false) == FAIL) { if (parse_cmd_address(eap, errormsg, true) == FAIL) {
goto err; goto end;
} }
// Skip colon and whitespace // Skip colon and whitespace
eap->cmd = skip_colon_white(eap->cmd, true); eap->cmd = skip_colon_white(eap->cmd, true);
// Fail if command is a comment or if command doesn't exist // Fail if command is a comment or if command doesn't exist
if (*eap->cmd == NUL || *eap->cmd == '"') { if (*eap->cmd == NUL || *eap->cmd == '"') {
goto err; goto end;
} }
// Fail if command is invalid // Fail if command is invalid
if (eap->cmdidx == CMD_SIZE) { if (eap->cmdidx == CMD_SIZE) {
@@ -1417,7 +1421,7 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er
char *cmdname = after_modifier ? after_modifier : cmdline; char *cmdname = after_modifier ? after_modifier : cmdline;
append_command(cmdname); append_command(cmdname);
*errormsg = (char *)IObuff; *errormsg = (char *)IObuff;
goto err; goto end;
} }
// Correctly set 'forceit' for commands // Correctly set 'forceit' for commands
@@ -1456,12 +1460,12 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er
// Fail if command doesn't support bang but is used with a bang // Fail if command doesn't support bang but is used with a bang
if (!(eap->argt & EX_BANG) && eap->forceit) { if (!(eap->argt & EX_BANG) && eap->forceit) {
*errormsg = _(e_nobang); *errormsg = _(e_nobang);
goto err; goto end;
} }
// Fail if command doesn't support a range but it is given a range // Fail if command doesn't support a range but it is given a range
if (!(eap->argt & EX_RANGE) && eap->addr_count > 0) { if (!(eap->argt & EX_RANGE) && eap->addr_count > 0) {
*errormsg = _(e_norange); *errormsg = _(e_norange);
goto err; goto end;
} }
// Set default range for command if required // Set default range for command if required
if ((eap->argt & EX_DFLALL) && eap->addr_count == 0) { if ((eap->argt & EX_DFLALL) && eap->addr_count == 0) {
@@ -1471,7 +1475,7 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er
// Parse register and count // Parse register and count
parse_register(eap); parse_register(eap);
if (parse_count(eap, errormsg, false) == FAIL) { if (parse_count(eap, errormsg, false) == FAIL) {
goto err; goto end;
} }
// Remove leading whitespace and colon from next command // Remove leading whitespace and colon from next command
@@ -1487,10 +1491,15 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er
cmdinfo->magic.bar = true; cmdinfo->magic.bar = true;
} }
return true; retval = true;
err: end:
undo_cmdmod(&cmdinfo->cmdmod); if (!retval) {
return false; undo_cmdmod(&cmdinfo->cmdmod);
}
ex_pressedreturn = save_ex_pressedreturn;
curwin->w_cursor = save_cursor;
restore_last_search_pattern();
return retval;
} }
static int execute_cmd0(int *retv, exarg_T *eap, char **errormsg, bool preview) static int execute_cmd0(int *retv, exarg_T *eap, char **errormsg, bool preview)

View File

@@ -3668,6 +3668,55 @@ describe('API', function()
:^ | :^ |
]]) ]])
end) end)
it('does not move cursor or change search history/pattern #19878 #19890', function()
meths.buf_set_lines(0, 0, -1, true, {'foo', 'bar', 'foo', 'bar'})
eq({1, 0}, meths.win_get_cursor(0))
eq('', funcs.getreg('/'))
eq('', funcs.histget('search'))
feed(':') -- call the API in cmdline mode to test whether it changes search history
eq({
cmd = 'normal',
args = {'x'},
bang = true,
range = {3, 4},
count = -1,
reg = '',
addr = 'line',
magic = {
file = false,
bar = false,
},
nargs = '+',
nextcmd = '',
mods = {
browse = false,
confirm = false,
emsg_silent = false,
filter = {
pattern = "",
force = false,
},
hide = false,
keepalt = false,
keepjumps = false,
keepmarks = false,
keeppatterns = false,
lockmarks = false,
noautocmd = false,
noswapfile = false,
sandbox = false,
silent = false,
split = "",
tab = 0,
unsilent = false,
verbose = -1,
vertical = false,
}
}, meths.parse_cmd('+2;/bar/normal! x', {}))
eq({1, 0}, meths.win_get_cursor(0))
eq('', funcs.getreg('/'))
eq('', funcs.histget('search'))
end)
end) end)
describe('nvim_cmd', function() describe('nvim_cmd', function()
it('works', function () it('works', function ()