From b781e95a24bd2d201f4afbe4729635114893cdf5 Mon Sep 17 00:00:00 2001 From: Yanuo Ma <41042490+esmuellert@users.noreply.github.com> Date: Thu, 27 Aug 2026 10:04:33 -0400 Subject: [PATCH] fix(scrollbind): window jumps back and forth with tall virt_lines block Problem: Scrolling a 'scrollbind' window whose peer has a virt_lines block taller than the window makes the peer jump back and forth. Solution: After scrollup()/scrolldown(), sync w_scbind_pos to the actual get_vtopline() when the target was not clamped to buffer bounds. Ref: esmuellert/codediff.nvim#519 AI-assisted --- src/nvim/normal.c | 6 ++ test/functional/ui/scrollbind_spec.lua | 84 ++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) diff --git a/src/nvim/normal.c b/src/nvim/normal.c index b5cd75e663..7f877367f8 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -2185,6 +2185,12 @@ void check_scrollbind(linenr_T vtopline_diff, int leftcol_diff) } else { scrolldown(curwin, -y, false); } + + // The scroll may not reach new_vtopline (tall virt_lines block). + // Sync w_scbind_pos to the actual position, else the miss repeats. + if (curwin->w_scbind_pos == new_vtopline) { + curwin->w_scbind_pos = get_vtopline(curwin); + } } redraw_later(curwin, UPD_VALID); diff --git a/test/functional/ui/scrollbind_spec.lua b/test/functional/ui/scrollbind_spec.lua index 54239fec1c..9081ee491e 100644 --- a/test/functional/ui/scrollbind_spec.lua +++ b/test/functional/ui/scrollbind_spec.lua @@ -439,4 +439,88 @@ describe('Scrollbind', function() ]], }) end) + + it('does not jump back and forth with a virt_lines block taller than the window', function() + n.exec_lua(function() + local lines = {} --- @type string[] + for i = 1, 20 do + lines[i] = tostring(i) + end + + -- Bound window: one virt_lines block of 15 rows, window is 10 rows. + vim.api.nvim_buf_set_lines(0, 0, -1, false, lines) + vim.bo.buftype = 'nofile' + local virt_lines = {} --- @type table[] + for i = 1, 15 do + virt_lines[i] = { { 'v' .. i } } + end + vim.api.nvim_buf_set_extmark(0, vim.api.nvim_create_namespace('test'), 4, 0, { + virt_lines = virt_lines, + }) + vim.wo.scrollbind = true + + -- Scrolled window: plain buffer. + vim.cmd.vnew() + vim.api.nvim_buf_set_lines(0, 0, -1, false, lines) + vim.bo.buftype = 'nofile' + vim.wo.scrollbind = true + end) + + n.feed('5') + + screen:expect({ + grid = [[ + ^6 │v7 | + 7 │v8 | + 8 │v9 | + 9 │v10 | + 10 │v11 | + 11 │v12 | + 12 │v13 | + 13 │v14 | + 14 │v15 | + 15 │6 | + {3:[Scratch] }{2:[Scratch] }| + | + ]], + }) + + n.feed('') + + screen:expect({ + grid = [[ + ^7 │v8 | + 8 │v9 | + 9 │v10 | + 10 │v11 | + 11 │v12 | + 12 │v13 | + 13 │v14 | + 14 │v15 | + 15 │6 | + 16 │7 | + {3:[Scratch] }{2:[Scratch] }| + | + ]], + }) + + n.feed('') + + screen:expect({ + grid = [[ + ^8 │v9 | + 9 │v10 | + 10 │v11 | + 11 │v12 | + 12 │v13 | + 13 │v14 | + 14 │v15 | + 15 │6 | + 16 │7 | + 17 │8 | + {3:[Scratch] }{2:[Scratch] }| + | + ]], + }) + end) end)