fix(cmdatom): insert-session entered by feedkeys() ignores typed input #41518

Problem:
An insert-session entered by a scheduled `feedkeys('i','n')` is
classified on entry (as "not typed"), and not reevaluated after that,
thus user input following it is not captured.

Repro: an `:imap` that does `feedkeys('<esc>','n')` and schedules
re-entering insert, emits one CmdAtom for the first press, then nothing
else.

Solution:
Sample `maptick` (ticked by `gotchars()` on typed input, including
mappings) at session start; if it advanced by session end, the session
is user input.
This commit is contained in:
Justin M. Keyes
2026-08-27 10:48:33 -04:00
committed by GitHub
parent b7145490fc
commit 61df463c5e
3 changed files with 40 additions and 4 deletions

View File

@@ -194,7 +194,7 @@ CmdSpec atom_cmd_spec(const cmdarg_T *cap)
static CmdOrigin atom_origin(void)
{
CmdOrigin origin = { .win = curwin, .pos = curwin->w_cursor,
.tick = buf_get_changedtick(curbuf) };
.tick = buf_get_changedtick(curbuf), .maptick = maptick };
set_bufref(&origin.buf, curbuf);
return origin;
}
@@ -1167,9 +1167,12 @@ InsSession atom_ins_start(int cmd, long count, VisualIns vis, bool vblock)
void atom_ins_end(const InsSession *session, bool busy)
{
bool visual = session->vis != kVInsNone;
if (!session->typed || busy || restart_edit != 0 || !atom_buf_has_consumers()
bool user_input = session->typed
// A session is user input, if user input occurred during it. #41516
|| maptick != session->origin.maptick;
if (!user_input || busy || restart_edit != 0 || !atom_buf_has_consumers()
|| (visual && session->vis != kVInsKeys)) {
if (session->typed && (busy || restart_edit != 0) && atom_composite_active()) {
if (user_input && (busy || restart_edit != 0) && atom_composite_active()) {
// Incomplete session (i_CTRL-O): its resolution is never captured.
composite.lossy = true;
}

View File

@@ -36,6 +36,7 @@ typedef struct {
const win_T *win; ///< Window.
pos_T pos; ///< Cursor position. Stored here bc the window might be closed.
varnumber_T tick; ///< b:changedtick.
int maptick; ///< Advances on typed input (globals.h:maptick).
} CmdOrigin;
/// How an insert-session was entered from Visual mode.

View File

@@ -939,7 +939,7 @@ describe('CmdAtom', function()
)
end)
it('an insert session atom captures its text', function()
it('insert-session atom captures its text', function()
fn.setline(1, { 'aaa' })
feed('gg0')
atoms_start()
@@ -966,6 +966,38 @@ describe('CmdAtom', function()
eq({ type = 'insert', count = 3, text = 'Z' }, pick(atom_last(), 'type', 'count', 'text'))
end)
it('insert-session entered programmatically, still captures *user* input #41516', function()
atoms_start()
n.exec_lua([[
vim.keymap.set('i', '<C-j>', function()
vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes('<Esc>', true, false, true), 'n', false)
vim.schedule(function()
vim.api.nvim_feedkeys('i', 'n', false)
end)
end)
]])
local before = #atoms()
feed('i')
n.exec_lua('vim.wait(50)')
feed('<C-j>')
n.exec_lua('vim.wait(50)')
feed('<Esc>')
local evs = atoms()
eq(before + 2, #evs)
eq({ k('1i<Esc>'), k('1i<Esc>') }, { evs[#evs - 1].keys, evs[#evs].keys })
-- Typed text within such a session lands in its atom...
n.exec_lua([[vim.schedule(function() vim.api.nvim_feedkeys('i', 'n', false) end)]])
n.exec_lua('vim.wait(50)')
feed('hi<Esc>')
eq({ text = 'hi', keys = k('1ihi<Esc>') }, pick(atom_last(), 'text', 'keys'))
-- ...but with NO typed input within it, the session emits nothing.
before = #atoms()
n.exec_lua([[vim.schedule(function() vim.api.nvim_feedkeys('i', 'n', false) end)]])
n.exec_lua('vim.wait(50)')
n.exec_lua([[vim.api.nvim_feedkeys('\27', 'n', false)]])
eq(before, #atoms())
end)
it("operatorfunc atom includes the getchar()'d payload", function()
n.exec(t_atom.minisurround_vim)
fn.setline(1, { 'alpha beta' })