From 6cbde6a95f2da043ef8cb66b7e51daedd3bacffb Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 6 Sep 2026 14:50:48 -0400 Subject: [PATCH] fix(multicursor): Visual-mode mapping loses its commands #41741 Problem: A Visual-mode mapping that leaves Visual mode ("xmap I Q0i") does not replay its motions/edits at the extra cursors, though an insert session it starts does. atom_map_start() skips while Visual is active, so the mapping has no `composite`, and atom_capturable() then rejects its commands. Solution: Open the `composite` for Visual-mode mappings too. Bonus: the mapping atom is now reported as: before: type=visual, keys="viwcFOO" after: type=mapping, keys="viwciFOO", lhs=",c" --- src/nvim/input_cmdatom.c | 3 +-- test/functional/editor/mcursor_spec.lua | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/nvim/input_cmdatom.c b/src/nvim/input_cmdatom.c index 958b6c33b7..bd470ee15a 100644 --- a/src/nvim/input_cmdatom.c +++ b/src/nvim/input_cmdatom.c @@ -873,8 +873,7 @@ void atom_stuff_start(const cmdarg_T *cap) void atom_map_start(const char *lhs, size_t len, bool peeked) { if (!atom_has_consumers() || mc_replaying() - || reg_executing != 0 || ex_normal_busy != 0 || !(State & MODE_NORMAL) - || Visual.active) { + || reg_executing != 0 || ex_normal_busy != 0 || !(State & MODE_NORMAL)) { return; } if (atom_composite_active()) { diff --git a/test/functional/editor/mcursor_spec.lua b/test/functional/editor/mcursor_spec.lua index 69152744d1..53f619b53d 100644 --- a/test/functional/editor/mcursor_spec.lua +++ b/test/functional/editor/mcursor_spec.lua @@ -514,6 +514,20 @@ describe('multicursor', function() end) end) + describe('composite/mapping', function() + it('Visual-mode mapping that creates cursors (Q) #41694', function() + command('xmap I Q0i') + cursors({ 'test', 'nvim', '', 'test', 'nvim' }, '') + feed('gg$Qjjj') + atoms_start() + feed('VjI') + feed('bad') + eq({ 'badtest', 'nvim', '', 'badtest', 'badnvim' }, get_lines()) + -- "Q" is excluded: it is cursor-management, not part of the edit. + eq({ '01i\27ibad\27' }, atoms_tail(1)) + end) + end) + describe('. (dot-repeat)', function() it('repeats operators, pre-cursor edits, inserts and changes at all cursors', function() -- Operator.