From 72051773642e6324af2577956b1cf1fcff638b51 Mon Sep 17 00:00:00 2001 From: TheLeoP Date: Wed, 28 Jan 2026 10:35:26 -0500 Subject: [PATCH] fix(operators): correctly handle newlines at start/end of textobject --- lua/mini/operators.lua | 15 +++++++++++++++ tests/test_operators.lua | 23 +++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/lua/mini/operators.lua b/lua/mini/operators.lua index 42007500..c4e40920 100644 --- a/lua/mini/operators.lua +++ b/lua/mini/operators.lua @@ -375,9 +375,14 @@ MiniOperators.exchange = function(mode) H.cache.exchange.step_two = H.exchange_set_region_extmark(mode, false) if H.cache.exchange.step_two == nil then return end + local cache_virtualedit = vim.o.virtualedit + vim.o.virtualedit = 'onemore' + -- Do exchange H.exchange_do() + vim.o.virtualedit = cache_virtualedit + -- Stop exchange H.exchange_stop() end @@ -420,6 +425,9 @@ MiniOperators.multiply = function(mode) if data == nil then return end local mark_from, mark_to, submode = data.mark_from, data.mark_to, data.submode + local cache_virtualedit = vim.o.virtualedit + vim.o.virtualedit = 'onemore' + H.with_temp_context({ registers = { 'x', '"' } }, function() -- Yank to temporary "x" register local yank_data = { mark_from = mark_from, mark_to = mark_to, submode = submode, mode = mode, register = 'x' } @@ -442,6 +450,8 @@ MiniOperators.multiply = function(mode) -- already is at first non-blank, while this moves to first column. if submode ~= 'V' then vim.cmd('normal! `[') end end) + + vim.o.virtualedit = cache_virtualedit end --- Replace text with register @@ -484,8 +494,13 @@ MiniOperators.replace = function(mode) data.register = register data.reindent_linewise = H.get_config().replace.reindent_linewise + local cache_virtualedit = vim.o.virtualedit + vim.o.virtualedit = 'onemore' + H.replace_do(data) + vim.o.virtualedit = cache_virtualedit + return '' end diff --git a/tests/test_operators.lua b/tests/test_operators.lua index 9c87a588..e2ffce7e 100644 --- a/tests/test_operators.lua +++ b/tests/test_operators.lua @@ -1205,6 +1205,17 @@ T['Exchange']['works with `make_mappings()`'] = function() validate_edit1d('aa bb', 0, { 'viwcx', 'w', 'viwcx' }, 'bb aa', 3) end +T['Exchange']['works on visual selection with leading newline'] = function() + set_lines({ 'a(', ' b', ')', 'c{', ' d,', '}' }) + + set_cursor(2, 2) + type_keys('vk', 'gx') + set_cursor(5, 2) + type_keys('vk', 'gx') + eq(get_lines(), { 'a(', ' d', ')', 'c{', ' b,', '}' }) + eq(get_cursor(), { 4, 1 }) +end + T['Exchange']['respects `selection=exclusive`'] = function() child.lua([[vim.keymap.set('o', 'ie', function() vim.cmd('normal! \22j') end)]]) child.o.selection = 'exclusive' @@ -1586,6 +1597,10 @@ T['Multiply']['works with `make_mappings()`'] = function() validate_edit1d('aa bb', 0, { 'viw', 'cm' }, 'aaaa bb', 2) end +T['Multiply']['works on visual selection with leading newline'] = function() + validate_edit({ 'a(', ' b', ')' }, { 2, 2 }, { 'vk', 'gm' }, { 'a(', ' b', ' b', ')' }, { 2, 2 }) +end + T['Multiply']['respects `selection=exclusive`'] = function() child.o.selection = 'exclusive' @@ -2103,6 +2118,14 @@ T['Replace']['works with `make_mappings()`'] = function() validate_edit1d('aa bb', 0, { 'yiw', 'w', 'viw', 'gR' }, 'aa aa', 3) end +T['Replace']['works on visual selection with leading newline'] = function() + validate_edit({ 'a(', ' b', ')' }, { 2, 2 }, { 'vky', 'gvgr' }, { 'a(', ' b', ')' }, { 1, 1 }) +end + +T['Replace']['works on visual selection with trailing newline'] = function() + validate_edit({ 'a(', ' b', ')' }, { 2, 2 }, { 'vkoly', 'gvgr' }, { 'a(', ' b', '', ')' }, { 1, 1 }) +end + T['Replace']['respects `selection=exclusive`'] = function() child.lua([[vim.keymap.set('o', 'ie', function() vim.cmd('normal! \22j') end)]]) child.o.selection = 'exclusive'