diff options
author | Lewis Russell <lewis6991@gmail.com> | 2024-07-17 12:23:15 +0100 |
---|---|---|
committer | Lewis Russell <me@lewisr.dev> | 2024-07-31 11:33:32 +0100 |
commit | 573a71469d37cc35f72bfc929f4ce1156833df9f (patch) | |
tree | a00230976aa453aabb2c9de073e08ccde343e400 /src | |
parent | c9b129a02ab46fc80c81f3f9cabed4040a7462c0 (diff) | |
download | rneovim-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.c | 15 | ||||
-rw-r--r-- | src/nvim/diff.c | 6 | ||||
-rw-r--r-- | src/nvim/drawline.c | 2 | ||||
-rw-r--r-- | src/nvim/ex_cmds.c | 2 | ||||
-rw-r--r-- | src/nvim/ex_docmd.c | 21 | ||||
-rw-r--r-- | src/nvim/normal.c | 53 | ||||
-rw-r--r-- | src/nvim/option.c | 3 | ||||
-rw-r--r-- | src/nvim/plines.c | 21 |
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. |