From cc4e0c8d60ede60d2a48efe322a9bd1f71198b8a Mon Sep 17 00:00:00 2001 From: Qiming zhao Date: Wed, 30 Sep 2026 22:16:27 +0800 Subject: fix(input): end Visual mode when aborting redo #42151 Problem: Skipping buffer flushing in Visual mode breaks map-error and leaves failed redo motions that raise errors stuck in Visual mode. Solution: Keep flushing on errors and exit Visual mode when pending redo is discarded. Cover failed motions, unset marks, macros, and mappings. AI-assisted --- test/functional/editor/cmdatom_spec.lua | 48 +++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) (limited to 'test/functional/editor/cmdatom_spec.lua') diff --git a/test/functional/editor/cmdatom_spec.lua b/test/functional/editor/cmdatom_spec.lua index 4d805a794e..7c70a2d07b 100644 --- a/test/functional/editor/cmdatom_spec.lua +++ b/test/functional/editor/cmdatom_spec.lua @@ -54,6 +54,54 @@ describe('dot-repeat', function() eq({ 'acbzacbonexy', 'czacbtwo' }, get_lines()) end) + it('ends Visual mode when a replayed motion fails #41896', function() + fn.setline(1, { '1', '2', '3' }) + feed('jI1') + eq({ '11', '12', '3' }, get_lines()) + feed('j.') + eq({ '11', '112', '13' }, get_lines()) + feed('j.') + eq({ '11', '112', '13' }, get_lines()) + eq('n', fn.mode(1)) + -- A failed repeat does not replace the last change. + feed('k0.') + eq({ '11', '1112', '113' }, get_lines()) + eq('n', fn.mode(1)) + end) + + for _, errorbells in ipairs({ 'noerrorbells', 'errorbells' }) do + it('ends Visual mode when a replayed motion raises an error with ' .. errorbells, function() + command('set ' .. errorbells) + fn.setline(1, { 'one', 'two' }) + feed('majV`a~') + eq({ 'ONE', 'TWO' }, get_lines()) + command('delmarks a') + feed('.') + eq('E20: Mark not set', api.nvim_get_vvar('errmsg')) + eq({ 'ONE', 'TWO' }, get_lines()) + eq('n', fn.mode(1)) + end) + end + + it('still stops macros and mappings on failed Visual motions', function() + fn.setline(1, { 'one', 'two', 'three' }) + fn.setreg('q', 'Vjd') + feed('2@q') + eq({ 'three' }, get_lines()) + eq('V', fn.mode(1)) + feed('') + command('nnoremap Q Vjd') + feed('Q') + eq({ 'three' }, get_lines()) + eq('V', fn.mode(1)) + feed('') + -- An ordinary failed motion still leaves the selection active. + feed('vj') + eq('v', fn.mode(1)) + feed('d') + eq({ 'hree' }, get_lines()) + end) + it('of a visual op does not churn showcmd', function() local screen = Screen.new(40, 8, { ext_messages = true }) command('set showcmd') -- cgit