aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLewis Russell <lewis6991@gmail.com>2024-07-17 12:23:15 +0100
committerLewis Russell <me@lewisr.dev>2024-07-31 11:33:32 +0100
commit573a71469d37cc35f72bfc929f4ce1156833df9f (patch)
treea00230976aa453aabb2c9de073e08ccde343e400 /src
parentc9b129a02ab46fc80c81f3f9cabed4040a7462c0 (diff)
downloadrneovim-573a71469d37cc35f72bfc929f4ce1156833df9f.tar.gz
rneovim-573a71469d37cc35f72bfc929f4ce1156833df9f.tar.bz2
rneovim-573a71469d37cc35f72bfc929f4ce1156833df9f.zip
fix(scrollbind): properly take filler/virtual lines into account
Problem: `'scrollbind'` does not work properly if the window being scrolled automatically contains any filler/virtual lines (except for diff filler lines). This is because when the scrollbind check is done, the logic only considers changes to topline which are represented as line numbers. Solution: Write the logic for determine the scroll amount to take into account filler/virtual lines. Fixes #29751
Diffstat (limited to 'src')
-rw-r--r--src/nvim/decoration.c15
-rw-r--r--src/nvim/diff.c6
-rw-r--r--src/nvim/drawline.c2
-rw-r--r--src/nvim/ex_cmds.c2
-rw-r--r--src/nvim/ex_docmd.c21
-rw-r--r--src/nvim/normal.c53
-rw-r--r--src/nvim/option.c3
-rw-r--r--src/nvim/plines.c21
8 files changed, 79 insertions, 44 deletions
diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c
index fb6a92025f..70696f1f03 100644
--- a/src/nvim/decoration.c
+++ b/src/nvim/decoration.c
@@ -887,7 +887,8 @@ bool decor_redraw_eol(win_T *wp, DecorState *state, int *eol_attr, int eol_col)
static const uint32_t lines_filter[4] = {[kMTMetaLines] = kMTFilterSelect };
-int decor_virt_lines(win_T *wp, linenr_T lnum, VirtLines *lines)
+/// @param apply_folds Only count virtual lines that are not in folds.
+int decor_virt_lines(win_T *wp, int start_row, int end_row, VirtLines *lines, bool apply_folds)
{
buf_T *buf = wp->w_buffer;
if (!buf_meta_total(buf, kMTMetaLines)) {
@@ -896,15 +897,14 @@ int decor_virt_lines(win_T *wp, linenr_T lnum, VirtLines *lines)
return 0;
}
- assert(lnum > 0);
- int row = lnum - 1;
-
MarkTreeIter itr[1] = { 0 };
- if (!marktree_itr_get_filter(buf->b_marktree, MAX(row - 1, 0), 0, row + 1, 0,
+ if (!marktree_itr_get_filter(buf->b_marktree, MAX(start_row - 1, 0), 0, end_row, 0,
lines_filter, itr)) {
return 0;
}
+ assert(start_row >= 0);
+
int virt_lines = 0;
while (true) {
MTKey mark = marktree_itr_current(itr);
@@ -915,7 +915,8 @@ int decor_virt_lines(win_T *wp, linenr_T lnum, VirtLines *lines)
bool above = vt->flags & kVTLinesAbove;
int mrow = mark.pos.row;
int draw_row = mrow + (above ? 0 : 1);
- if (draw_row == row && !hasFolding(wp, mrow + 1, NULL, NULL)) {
+ if (draw_row >= start_row && draw_row < end_row
+ && (!apply_folds || !hasFolding(wp, mrow + 1, NULL, NULL))) {
virt_lines += (int)kv_size(vt->data.virt_lines);
if (lines) {
kv_splice(*lines, vt->data.virt_lines);
@@ -926,7 +927,7 @@ int decor_virt_lines(win_T *wp, linenr_T lnum, VirtLines *lines)
}
}
- if (!marktree_itr_next_filter(buf->b_marktree, itr, row + 1, 0, lines_filter)) {
+ if (!marktree_itr_next_filter(buf->b_marktree, itr, end_row, 0, lines_filter)) {
break;
}
}
diff --git a/src/nvim/diff.c b/src/nvim/diff.c
index 0b9bdb6181..6d5c301e81 100644
--- a/src/nvim/diff.c
+++ b/src/nvim/diff.c
@@ -2143,7 +2143,11 @@ int diff_check_with_linestatus(win_T *wp, linenr_T lnum, int *linestatus)
return 0;
}
- if (!dp->is_linematched && diff_linematch(dp)) {
+ // Don't run linematch when lnum is offscreen.
+ // Useful for scrollbind calculations which need to count all the filler lines
+ // above the screen.
+ if (lnum >= wp->w_topline && lnum < wp->w_botline
+ && !dp->is_linematched && diff_linematch(dp)) {
run_linematch_algorithm(dp);
}
diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c
index 47d84e6539..8a948716e5 100644
--- a/src/nvim/drawline.c
+++ b/src/nvim/drawline.c
@@ -1156,7 +1156,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
area_highlighting = true;
}
VirtLines virt_lines = KV_INITIAL_VALUE;
- wlv.n_virt_lines = decor_virt_lines(wp, lnum, &virt_lines);
+ wlv.n_virt_lines = decor_virt_lines(wp, lnum - 1, lnum, &virt_lines, true);
wlv.filler_lines += wlv.n_virt_lines;
if (lnum == wp->w_topline) {
wlv.filler_lines = wp->w_topfill;
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index 5cc9a8c106..f170fd0762 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -2695,7 +2695,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum
*so_ptr = 999; // force cursor to be vertically centered in the window
}
update_topline(curwin);
- curwin->w_scbind_pos = curwin->w_topline;
+ curwin->w_scbind_pos = plines_m_win_fill(curwin, 1, curwin->w_topline);
*so_ptr = n;
redraw_curbuf_later(UPD_NOT_VALID); // redraw this buffer later
}
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index 2defd580fc..e384627fec 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -81,6 +81,7 @@
#include "nvim/os/os_defs.h"
#include "nvim/os/shell.h"
#include "nvim/path.h"
+#include "nvim/plines.h"
#include "nvim/popupmenu.h"
#include "nvim/pos_defs.h"
#include "nvim/profile.h"
@@ -5580,39 +5581,43 @@ static void ex_swapname(exarg_T *eap)
/// (1998-11-02 16:21:01 R. Edward Ralston <eralston@computer.org>)
static void ex_syncbind(exarg_T *eap)
{
- linenr_T topline;
+ linenr_T vtopline; // Target topline (including fill)
+
linenr_T old_linenr = curwin->w_cursor.lnum;
setpcmark();
- // determine max topline
+ // determine max (virtual) topline
if (curwin->w_p_scb) {
- topline = curwin->w_topline;
+ vtopline = get_vtopline(curwin);
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
if (wp->w_p_scb && wp->w_buffer) {
- topline = MIN(topline, wp->w_buffer->b_ml.ml_line_count - get_scrolloff_value(curwin));
+ linenr_T y = plines_m_win_fill(wp, 1, wp->w_buffer->b_ml.ml_line_count)
+ - get_scrolloff_value(curwin);
+ vtopline = MIN(vtopline, y);
}
}
- topline = MAX(topline, 1);
+ vtopline = MAX(vtopline, 1);
} else {
- topline = 1;
+ vtopline = 1;
}
// Set all scrollbind windows to the same topline.
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
if (wp->w_p_scb) {
- int y = topline - wp->w_topline;
+ int y = vtopline - get_vtopline(wp);
if (y > 0) {
scrollup(wp, y, true);
} else {
scrolldown(wp, -y, true);
}
- wp->w_scbind_pos = topline;
+ wp->w_scbind_pos = vtopline;
redraw_later(wp, UPD_VALID);
cursor_correct(wp);
wp->w_redr_status = true;
}
}
+
if (curwin->w_p_scb) {
did_syncbind = true;
checkpcmark();
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index 88b8ccbb85..3931ae3ee9 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -2092,17 +2092,23 @@ static void display_showcmd(void)
grid_line_flush();
}
+int get_vtopline(win_T *wp)
+{
+ return plines_m_win_fill(wp, 1, wp->w_topline) - wp->w_topfill;
+}
+
/// When "check" is false, prepare for commands that scroll the window.
/// When "check" is true, take care of scroll-binding after the window has
/// scrolled. Called from normal_cmd() and edit().
void do_check_scrollbind(bool check)
{
static win_T *old_curwin = NULL;
- static linenr_T old_topline = 0;
- static int old_topfill = 0;
+ static linenr_T old_vtopline = 0;
static buf_T *old_buf = NULL;
static colnr_T old_leftcol = 0;
+ int vtopline = get_vtopline(curwin);
+
if (check && curwin->w_p_scb) {
// If a ":syncbind" command was just used, don't scroll, only reset
// the values.
@@ -2115,10 +2121,9 @@ void do_check_scrollbind(bool check)
if ((curwin->w_buffer == old_buf
|| curwin->w_p_diff
)
- && (curwin->w_topline != old_topline
- || curwin->w_topfill != old_topfill
+ && (vtopline != old_vtopline
|| curwin->w_leftcol != old_leftcol)) {
- check_scrollbind(curwin->w_topline - old_topline, curwin->w_leftcol - old_leftcol);
+ check_scrollbind(vtopline - old_vtopline, curwin->w_leftcol - old_leftcol);
}
} else if (vim_strchr(p_sbo, 'j')) { // jump flag set in 'scrollopt'
// When switching between windows, make sure that the relative
@@ -2129,14 +2134,13 @@ void do_check_scrollbind(bool check)
// resync is performed, some of the other 'scrollbind' windows may
// need to jump so that the current window's relative position is
// visible on-screen.
- check_scrollbind(curwin->w_topline - (linenr_T)curwin->w_scbind_pos, 0);
+ check_scrollbind(vtopline - curwin->w_scbind_pos, 0);
}
- curwin->w_scbind_pos = curwin->w_topline;
+ curwin->w_scbind_pos = vtopline;
}
old_curwin = curwin;
- old_topline = curwin->w_topline;
- old_topfill = curwin->w_topfill;
+ old_vtopline = vtopline;
old_buf = curwin->w_buffer;
old_leftcol = curwin->w_leftcol;
}
@@ -2144,20 +2148,18 @@ void do_check_scrollbind(bool check)
/// Synchronize any windows that have "scrollbind" set, based on the
/// number of rows by which the current window has changed
/// (1998-11-02 16:21:01 R. Edward Ralston <eralston@computer.org>)
-void check_scrollbind(linenr_T topline_diff, int leftcol_diff)
+void check_scrollbind(linenr_T vtopline_diff, int leftcol_diff)
{
win_T *old_curwin = curwin;
buf_T *old_curbuf = curbuf;
int old_VIsual_select = VIsual_select;
int old_VIsual_active = VIsual_active;
colnr_T tgt_leftcol = curwin->w_leftcol;
- linenr_T topline;
- linenr_T y;
// check 'scrollopt' string for vertical and horizontal scroll options
- bool want_ver = (vim_strchr(p_sbo, 'v') && topline_diff != 0);
- want_ver |= old_curwin->w_p_diff;
- bool want_hor = (vim_strchr(p_sbo, 'h') && (leftcol_diff || topline_diff != 0));
+ bool want_ver = old_curwin->w_p_diff
+ || (vim_strchr(p_sbo, 'v') && vtopline_diff != 0);
+ bool want_hor = (vim_strchr(p_sbo, 'h') && (leftcol_diff || vtopline_diff != 0));
// loop through the scrollbound windows and scroll accordingly
VIsual_select = VIsual_active = 0;
@@ -2174,16 +2176,19 @@ void check_scrollbind(linenr_T topline_diff, int leftcol_diff)
if (old_curwin->w_p_diff && curwin->w_p_diff) {
diff_set_topline(old_curwin, curwin);
} else {
- curwin->w_scbind_pos += topline_diff;
- topline = (linenr_T)curwin->w_scbind_pos;
- if (topline > curbuf->b_ml.ml_line_count) {
- topline = curbuf->b_ml.ml_line_count;
- }
- if (topline < 1) {
- topline = 1;
- }
+ curwin->w_scbind_pos += vtopline_diff;
+ int curr_vtopline = get_vtopline(curwin);
+
+ // Perf: reuse curr_vtopline to reduce the time in plines_m_win_fill().
+ // Equivalent to:
+ // int max_vtopline = plines_m_win_fill(curwin, 1, curbuf->b_ml.ml_line_count);
+ int max_vtopline = curr_vtopline + curwin->w_topfill
+ + plines_m_win_fill(curwin, curwin->w_topline + 1,
+ curbuf->b_ml.ml_line_count);
+
+ int new_vtopline = MAX(MIN((linenr_T)curwin->w_scbind_pos, max_vtopline), 1);
- y = topline - curwin->w_topline;
+ int y = new_vtopline - curr_vtopline;
if (y > 0) {
scrollup(curwin, y, false);
} else {
diff --git a/src/nvim/option.c b/src/nvim/option.c
index 8fb97ed979..e7d8bb91ac 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -88,6 +88,7 @@
#include "nvim/os/os.h"
#include "nvim/os/os_defs.h"
#include "nvim/path.h"
+#include "nvim/plines.h"
#include "nvim/popupmenu.h"
#include "nvim/pos_defs.h"
#include "nvim/regexp.h"
@@ -2474,7 +2475,7 @@ static const char *did_set_scrollbind(optset_T *args)
return NULL;
}
do_check_scrollbind(false);
- win->w_scbind_pos = win->w_topline;
+ win->w_scbind_pos = get_vtopline(win);
return NULL;
}
diff --git a/src/nvim/plines.c b/src/nvim/plines.c
index 4409b14ae1..e51e9bf8c3 100644
--- a/src/nvim/plines.c
+++ b/src/nvim/plines.c
@@ -712,7 +712,7 @@ bool win_may_fill(win_T *wp)
/// @return Number of filler lines above lnum
int win_get_fill(win_T *wp, linenr_T lnum)
{
- int virt_lines = decor_virt_lines(wp, lnum, NULL);
+ int virt_lines = decor_virt_lines(wp, lnum - 1, lnum, NULL, true);
// be quick when there are no filler lines
if (diffopt_filler()) {
@@ -906,6 +906,25 @@ int plines_m_win(win_T *wp, linenr_T first, linenr_T last, int max)
return MIN(max, count);
}
+/// Return number of window lines a physical line range will occupy.
+/// Only considers real and filler lines.
+///
+/// Mainly used for calculating scrolling offsets.
+int plines_m_win_fill(win_T *wp, linenr_T first, linenr_T last)
+{
+ int count = last - first + 1 + decor_virt_lines(wp, first - 1, last, NULL, false);
+
+ if (diffopt_filler()) {
+ for (int lnum = first; lnum <= last; lnum++) {
+ // Note: this also considers folds.
+ int n = diff_check(wp, lnum);
+ count += MAX(n, 0);
+ }
+ }
+
+ return MAX(count, 0);
+}
+
/// Get the number of screen lines a range of text will take in window "wp".
///
/// @param[in] start_lnum Starting line number, 1-based inclusive.