fix(api): adjust Visual position after nvim_buf_set_text #30690

Problem:
Visual selection could end up in the wrong place after
nvim_buf_set_text or nvim_buf_set_lines. In some delete cases,
Visual.lnum was already clamped before the line shift happened, so the
adjustment got skipped.

Solution:
Split fix_cursor_cols into reusable fix_pos_col logic and reuse it
for Visual updates. Also adjust Visual.lnum before changed_lines so
the shift uses the original position before final clamping.
This commit is contained in:
glepnir
2026-05-19 01:08:26 +08:00
committed by GitHub
parent 4791444953
commit 450ba41436
2 changed files with 198 additions and 60 deletions

View File

@@ -1021,6 +1021,110 @@ describe('api/buf', function()
eq({ 1, 4 }, api.nvim_win_get_cursor(win2))
end)
it('keep visual select position #29558', function()
insert([[1234]])
api.nvim_win_set_cursor(0, { 1, 1 })
feed('vl')
exec_lua([[
vim.defer_fn(function() vim.api.nvim_buf_set_text(0, 0, 0, 0, 0, { '0' }) end, 50)
vim.wait(80)
]])
local mode = api.nvim_get_mode().mode
eq({ '23' }, fn.getregion(fn.getpos('.'), fn.getpos('v'), { type = mode }))
eq({ 'v', { '01234' } }, { mode, api.nvim_buf_get_lines(0, 0, -1, false) })
feed('<ESC>')
api.nvim_buf_set_lines(0, 0, -1, false, { '1', '2', '3' })
api.nvim_win_set_cursor(0, { 2, 0 })
feed('v')
exec_lua([[
vim.defer_fn(function() vim.api.nvim_buf_set_text(0, 0, 0, 0, 0, { '0', '' }) end, 50)
vim.wait(80)
]])
mode = api.nvim_get_mode().mode
eq({ '2' }, fn.getregion(fn.getpos('.'), fn.getpos('v'), { type = mode }))
eq({ 'v', { '0', '1', '2', '3' } }, { mode, api.nvim_buf_get_lines(0, 0, -1, false) })
feed('<ESC>')
api.nvim_buf_set_lines(0, 0, -1, false, { '1', '2' })
api.nvim_win_set_cursor(0, { 1, 0 })
feed('vj')
exec_lua([[
vim.defer_fn(function() vim.api.nvim_buf_set_text(0, 0, 0, 0, 0, { '0' }) end, 50)
vim.wait(80)
]])
mode = api.nvim_get_mode().mode
eq({ '1', '2' }, fn.getregion(fn.getpos('.'), fn.getpos('v'), { type = mode }))
eq({ 'v', { '01', '2' } }, { mode, api.nvim_buf_get_lines(0, 0, -1, false) })
feed('<ESC>')
api.nvim_buf_set_lines(0, 0, -1, false, { '123' })
api.nvim_win_set_cursor(0, { 1, 1 })
feed('v')
exec_lua([[
vim.defer_fn(function() vim.api.nvim_buf_set_text(0, 0, 0, 0, 0, { '', '' }) end, 50)
vim.wait(80)
]])
mode = api.nvim_get_mode().mode
eq({ '2' }, fn.getregion(fn.getpos('.'), fn.getpos('v'), { type = mode }))
eq({ 'v', { '', '123' } }, { mode, api.nvim_buf_get_lines(0, 0, -1, false) })
feed('<ESC>')
-- Visual block mode
api.nvim_buf_set_lines(0, 0, -1, false, { '123', '456' })
api.nvim_win_set_cursor(0, { 1, 0 })
feed('<C-v>jl')
exec_lua([[
vim.defer_fn(function() vim.api.nvim_buf_set_text(0, 0, 0, 0, 0, { '0' }) end, 50)
vim.wait(80)
]])
mode = api.nvim_get_mode().mode
eq({ '01', '45' }, fn.getregion(fn.getpos('.'), fn.getpos('v'), { type = mode }))
eq(
{ string.char(0x16), { '0123', '456' } },
{ mode, api.nvim_buf_get_lines(0, 0, -1, false) }
)
feed('<ESC>')
-- also test nvim_buf_set_lines inserts line above visual selection
api.nvim_buf_set_lines(0, 0, -1, false, { '1', '2', '3' })
api.nvim_win_set_cursor(0, { 2, 0 })
feed('v')
exec_lua([[
vim.defer_fn(function() vim.api.nvim_buf_set_lines(0, 0, 0, false, { 'new' }) end, 50)
vim.wait(80)
]])
mode = api.nvim_get_mode().mode
eq({ '2' }, fn.getregion(fn.getpos('.'), fn.getpos('v'), { type = mode }))
eq({ 'v', { 'new', '1', '2', '3' } }, { mode, api.nvim_buf_get_lines(0, 0, -1, false) })
feed('<ESC>')
api.nvim_buf_set_lines(0, 0, -1, false, { '1234' })
api.nvim_win_set_cursor(0, { 1, 1 })
feed('vl')
exec_lua([[
vim.defer_fn(function()
vim.api.nvim_buf_set_text(0, 0, 0, 0, 0, { '0', 'foo' })
end, 50)
vim.wait(80)
]])
mode = api.nvim_get_mode().mode
eq({ '23' }, fn.getregion(fn.getpos('.'), fn.getpos('v'), { type = mode }))
eq({ 'v', { '0', 'foo1234' } }, { mode, api.nvim_buf_get_lines(0, 0, -1, false) })
feed('<ESC>')
api.nvim_buf_set_lines(0, 0, -1, false, { '1', '2', '3', '4', '5', '6', '7', '8', '9', '10' })
api.nvim_win_set_cursor(0, { 8, 0 })
feed('v')
exec_lua([[
vim.defer_fn(function() vim.api.nvim_buf_set_lines(0, 0, 5, false, {}) end, 50)
vim.wait(80)
]])
mode = api.nvim_get_mode().mode
eq({ '8' }, fn.getregion(fn.getpos('.'), fn.getpos('v'), { type = mode }))
eq({ 'v', { '6', '7', '8', '9', '10' } }, { mode, api.nvim_buf_get_lines(0, 0, -1, false) })
feed('<ESC>')
end)
describe('when text is being added right at cursor position #22526', function()
it('updates the cursor position in NORMAL mode', function()
insert([[