From e6cfa22c4cd5b3b422aa4f8350c8e73a3eb2a090 Mon Sep 17 00:00:00 2001 From: Luuk van Baal Date: Tue, 2 Apr 2024 20:58:49 +0200 Subject: [PATCH 1/3] vim-patch:9.1.0258: half-page scrolling broke backward compatibility Problem: Support for 'smoothscroll' in (half-)page scrolling broke backward compatibility and can be made to work better. (after v9.1.215) Solution: Restore the previous cursor and end-of-buffer behavior for half-page scrolling and improve 'smoothscroll' support. (Luuk van Baal) https://github.com/vim/vim/commit/cb204e688e5c9d56a78b621ef27c35d91860cb09 --- src/nvim/change.c | 3 +- src/nvim/move.c | 146 +++++++++++++++------ src/nvim/normal.c | 15 ++- test/functional/legacy/matchparen_spec.lua | 2 +- test/old/testdir/test_diffmode.vim | 3 +- test/old/testdir/test_edit.vim | 8 +- test/old/testdir/test_normal.vim | 16 +-- test/old/testdir/test_scroll_opt.vim | 72 ++++++++-- 8 files changed, 189 insertions(+), 76 deletions(-) diff --git a/src/nvim/change.c b/src/nvim/change.c index fce0de49bb..4d6c4acd0e 100644 --- a/src/nvim/change.c +++ b/src/nvim/change.c @@ -343,8 +343,7 @@ static void changed_common(buf_T *buf, linenr_T lnum, colnr_T col, linenr_T lnum || (wp->w_topline >= lnum && wp->w_topline < lnume && win_linetabsize(wp, wp->w_topline, ml_get(wp->w_topline), MAXCOL) - <= (wp->w_skipcol - + sms_marker_overlap(wp, win_col_off(wp) - win_col_off2(wp)))))) { + <= (wp->w_skipcol + sms_marker_overlap(wp, -1))))) { wp->w_skipcol = 0; } diff --git a/src/nvim/move.c b/src/nvim/move.c index e7416549f5..63505c72ab 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -190,11 +190,14 @@ static void redraw_for_cursorcolumn(win_T *wp) /// Calculates how much the 'listchars' "precedes" or 'smoothscroll' "<<<" /// marker overlaps with buffer text for window "wp". /// Parameter "extra2" should be the padding on the 2nd line, not the first -/// line. +/// line. When "extra2" is -1 calculate the padding. /// Returns the number of columns of overlap with buffer text, excluding the /// extra padding on the ledge. int sms_marker_overlap(win_T *wp, int extra2) { + if (extra2 == -1) { + extra2 = win_col_off(wp) - win_col_off2(wp); + } // There is no marker overlap when in showbreak mode, thus no need to // account for it. See wlv_put_linebuf(). if (*get_showbreak_value(wp) != NUL) { @@ -303,7 +306,7 @@ void update_topline(win_T *wp) // Check that the cursor position is visible. Add columns for // the marker displayed in the top-left if needed. getvvcol(wp, &wp->w_cursor, &vcol, NULL, NULL); - int overlap = sms_marker_overlap(wp, win_col_off(wp) - win_col_off2(wp)); + int overlap = sms_marker_overlap(wp, -1); if (wp->w_skipcol + overlap > vcol) { check_topline = true; } @@ -1382,6 +1385,9 @@ bool scrollup(win_T *wp, linenr_T line_count, bool byfold) wp->w_topline = lnum; wp->w_topfill = win_get_fill(wp, lnum); wp->w_skipcol = 0; + // Adjusting the cursor later should not adjust skipcol: + // bring it to the first screenline on this new topline. + wp->w_curswant %= width1; if (todo > 1 && do_sms) { size = linetabsize(wp, wp->w_topline); } @@ -1494,7 +1500,7 @@ void adjust_skipcol(void) } validate_virtcol(curwin); - int overlap = sms_marker_overlap(curwin, win_col_off(curwin) - win_col_off2(curwin)); + int overlap = sms_marker_overlap(curwin, -1); while (curwin->w_skipcol > 0 && curwin->w_virtcol < curwin->w_skipcol + overlap + scrolloff_cols) { // scroll a screen line down @@ -1860,10 +1866,12 @@ void scroll_cursor_bot(win_T *wp, int min_scroll, bool set_topbot) // of it. if (used < wp->w_height_inner) { int plines_offset = used + loff.height - wp->w_height_inner; + int overlap = sms_marker_overlap(wp, -1); used = wp->w_height_inner; wp->w_topfill = loff.fill; wp->w_topline = loff.lnum; wp->w_skipcol = skipcol_from_plines(wp, plines_offset); + wp->w_cursor.col = wp->w_skipcol + overlap; set_skipcol = true; } } @@ -1872,6 +1880,9 @@ void scroll_cursor_bot(win_T *wp, int min_scroll, bool set_topbot) used += loff.height; wp->w_topfill = loff.fill; } + if (wp->w_topline > wp->w_buffer->b_ml.ml_line_count) { + wp->w_topline = wp->w_buffer->b_ml.ml_line_count; + } set_empty_rows(wp, used); wp->w_valid |= VALID_BOTLINE|VALID_BOTLINE_AP; if (wp->w_topline != old_topline @@ -2341,15 +2352,57 @@ static int get_scroll_overlap(Direction dir) } } -/// Move screen "count" pages up ("dir" is BACKWARD) or down ("dir" is FORWARD) -/// and update the screen. +/// Scroll "count" lines with 'smoothscroll' in direction "dir". Adjust "count" +/// when scrolling more than "count" and return true when scrolling happened. +static bool scroll_with_sms(Direction dir, int *count) +{ + int prev_sms = curwin->w_p_sms; + colnr_T prev_skipcol = curwin->w_skipcol; + linenr_T prev_topline = curwin->w_topline; + int prev_topfill = curwin->w_topfill; + + curwin->w_p_sms = true; + scroll_redraw(dir == FORWARD, *count); + + // Not actually smoothscrolling but ended up with partially visible line. + // Continue scrolling and update "count" so that cursor can be moved + // accordingly for half-page scrolling. + if (!prev_sms && curwin->w_skipcol > 0) { + int fixdir = dir; + // Reverse the scroll direction when topline already changed. One line + // extra for scrolling backward so that consuming skipcol is symmetric. + if (labs(curwin->w_topline - prev_topline) > (dir == BACKWARD)) { + fixdir = dir * -1; + } + validate_cursor(curwin); + while (curwin->w_skipcol > 0 + && curwin->w_topline < curbuf->b_ml.ml_line_count) { + scroll_redraw(fixdir == FORWARD, 1); + *count += (fixdir == dir ? 1 : -1); + } + } + curwin->w_p_sms = prev_sms; + + return curwin->w_topline == prev_topline + && curwin->w_topfill == prev_topfill + && curwin->w_skipcol == prev_skipcol; +} + +/// Move screen "count" (half) pages up ("dir" is BACKWARD) or down ("dir" is +/// FORWARD) and update the screen. Handle moving the cursor and not scrolling +/// to reveal end of buffer lines for half-page scrolling with CTRL-D and CTRL-U. /// /// @return FAIL for failure, OK otherwise. int pagescroll(Direction dir, int count, bool half) { - int prev_topfill = curwin->w_topfill; - linenr_T prev_topline = curwin->w_topline; - colnr_T prev_skipcol = curwin->w_skipcol; + int nochange = true; + int buflen = curbuf->b_ml.ml_line_count; + colnr_T prev_col = curwin->w_cursor.col; + colnr_T prev_curswant = curwin->w_curswant; + linenr_T prev_lnum = curwin->w_cursor.lnum; + oparg_T oa = { 0 }; + cmdarg_T ca = { 0 }; + ca.oap = &oa; if (half) { // Scroll [count], 'scroll' or current window height lines. @@ -2357,45 +2410,56 @@ int pagescroll(Direction dir, int count, bool half) curwin->w_p_scr = MIN(curwin->w_height_inner, count); } count = MIN(curwin->w_height_inner, (int)curwin->w_p_scr); + // Don't scroll if we already know that it will reveal end of buffer lines. + if (dir == BACKWARD + || (curwin->w_botline - 1 < buflen) + || (curwin->w_p_sms && curwin->w_botline - 1 == buflen + && curwin->w_skipcol < linetabsize(curwin, buflen))) { + nochange = scroll_with_sms(dir, &count); + validate_botline(curwin); + // Hide any potentially revealed end of buffer lines. + if (!nochange && curwin->w_botline - 1 == buflen) { + curwin->w_cursor.lnum = buflen; + scroll_cursor_bot(curwin, 0, true); + } + } + + // Move the cursor "count" screen lines. + curwin->w_curswant = MAXCOL; + curwin->w_cursor.col = prev_col; + curwin->w_cursor.lnum = prev_lnum; + if (curwin->w_p_wrap) { + nv_screengo(&oa, dir, count); + } else if (dir == FORWARD) { + cursor_down_inner(curwin, count); + } else { + cursor_up_inner(curwin, count); + } + curwin->w_curswant = prev_curswant; + + if (get_scrolloff_value(curwin)) { + cursor_correct(curwin); + } + // Move cursor to first line of closed fold. + foldAdjustCursor(curwin); + + nochange = nochange + && prev_col == curwin->w_cursor.col + && prev_lnum == curwin->w_cursor.lnum; } else { - // Scroll 'window' or current window height lines. + // Scroll [count] times 'window' or current window height lines. count *= ((ONE_WINDOW && p_window > 0 && p_window < Rows - 1) - ? (int)p_window - 2 : get_scroll_overlap(dir)); + ? MAX(1, p_window - 2) : get_scroll_overlap(dir)); + nochange = scroll_with_sms(dir, &count); } - if (curwin->w_p_sms) { - scroll_redraw(dir == FORWARD, count); - } else { - // Scroll at least one full line without 'smoothscroll'. - count -= plines_win_nofill(curwin, curwin->w_topline, false); - scroll_redraw(dir == FORWARD, 1); - - // Temporarily set 'smoothscroll' so that scrolling count lines - // does not skip over parts of the buffer with wrapped lines. - curwin->w_p_sms = true; - if (count > 0) { - scroll_redraw(dir == FORWARD, count); - } - curwin->w_p_sms = false; - } - // Move cursor to first line of closed fold. - foldAdjustCursor(curwin); - - int nochange = curwin->w_topline == prev_topline - && curwin->w_topfill == prev_topfill - && curwin->w_skipcol == prev_skipcol; - - // Error if the viewport did not change and the cursor is already - // at the boundary. + // Error if both the viewport and cursor did not change. if (nochange) { - int prev_cursor = curwin->w_cursor.lnum; - curwin->w_cursor.lnum += (count + 1) * (dir == FORWARD ? 1 : -1); - check_cursor(curwin); - if (curwin->w_cursor.lnum == prev_cursor) { - beep_flush(); - } - } else if (!curwin->w_p_sms || curwin->w_skipcol == prev_skipcol) { + beep_flush(); + } else if (!curwin->w_p_sms) { beginline(BL_SOL | BL_FIX); + } else if (p_sol) { + nv_g_home_m_cmd(&ca); } return nochange; diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 2eb247a47e..8067bbcd4d 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -2460,7 +2460,7 @@ bool find_decl(char *ptr, size_t len, bool locally, bool thisblock, int flags_ar /// 'dist' must be positive. /// /// @return true if able to move cursor, false otherwise. -static bool nv_screengo(oparg_T *oap, int dir, int dist) +bool nv_screengo(oparg_T *oap, int dir, int dist) { int linelen = linetabsize(curwin, curwin->w_cursor.lnum); bool retval = true; @@ -5223,7 +5223,7 @@ static void nv_gv_cmd(cmdarg_T *cap) /// "g0", "g^" : Like "0" and "^" but for screen lines. /// "gm": middle of "g0" and "g$". -static void nv_g_home_m_cmd(cmdarg_T *cap) +void nv_g_home_m_cmd(cmdarg_T *cap) { int i; const bool flag = cap->nchar == '^'; @@ -5239,6 +5239,15 @@ static void nv_g_home_m_cmd(cmdarg_T *cap) if (curwin->w_virtcol >= (colnr_T)width1 && width2 > 0) { i = (curwin->w_virtcol - width1) / width2 * width2 + width1; } + + // When ending up below 'smoothscroll' marker, move just beyond it so + // that skipcol is not adjusted later. + if (curwin->w_skipcol > 0 && curwin->w_cursor.lnum == curwin->w_topline) { + int overlap = sms_marker_overlap(curwin, -1); + if (overlap > 0 && i == curwin->w_skipcol) { + i += overlap; + } + } } else { i = curwin->w_leftcol; } @@ -6402,7 +6411,7 @@ static void nv_at(cmdarg_T *cap) static void nv_halfpage(cmdarg_T *cap) { if (!checkclearop(cap->oap)) { - pagescroll(cap->cmdchar == Ctrl_D, cap->count0, true); + pagescroll(cap->cmdchar == Ctrl_D ? FORWARD : BACKWARD, cap->count0, true); } } diff --git a/test/functional/legacy/matchparen_spec.lua b/test/functional/legacy/matchparen_spec.lua index db5a24e2e0..0a21efeb7c 100644 --- a/test/functional/legacy/matchparen_spec.lua +++ b/test/functional/legacy/matchparen_spec.lua @@ -19,7 +19,7 @@ describe('matchparen', function() call cursor(5, 1) ]]) - feed('V3j') + feed('V') screen:expect([[ {17:{} | {17:}} | diff --git a/test/old/testdir/test_diffmode.vim b/test/old/testdir/test_diffmode.vim index 3e7eabf8f5..67f6aaecbb 100644 --- a/test/old/testdir/test_diffmode.vim +++ b/test/old/testdir/test_diffmode.vim @@ -1664,8 +1664,7 @@ func Test_diff_scroll_many_filler() endfor set smoothscroll& - bwipe! - bwipe! + %bwipe! endfunc " This was trying to update diffs for a buffer being closed diff --git a/test/old/testdir/test_edit.vim b/test/old/testdir/test_edit.vim index 1bfe46c4a9..abd30074d5 100644 --- a/test/old/testdir/test_edit.vim +++ b/test/old/testdir/test_edit.vim @@ -1320,7 +1320,7 @@ func Test_edit_PAGEUP_PAGEDOWN() call feedkeys("A\\", 'tnix') call assert_equal([0, 10, 1, 0], getpos('.')) call feedkeys("A\\", 'tnix') - call assert_equal([0, 2, 11, 0], getpos('.')) + call assert_equal([0, 10, 11, 0], getpos('.')) " is the same as " is the same as call cursor(1, 1) @@ -1343,7 +1343,7 @@ func Test_edit_PAGEUP_PAGEDOWN() call feedkeys("A\\", 'tnix') call assert_equal([0, 10, 1, 0], getpos('.')) call feedkeys("A\\", 'tnix') - call assert_equal([0, 2, 11, 0], getpos('.')) + call assert_equal([0, 10, 11, 0], getpos('.')) set nostartofline call cursor(30, 11) norm! zt @@ -1356,7 +1356,7 @@ func Test_edit_PAGEUP_PAGEDOWN() call feedkeys("A\\", 'tnix') call assert_equal([0, 10, 11, 0], getpos('.')) call feedkeys("A\\", 'tnix') - call assert_equal([0, 2, 11, 0], getpos('.')) + call assert_equal([0, 10, 11, 0], getpos('.')) call cursor(1, 1) call feedkeys("A\\", 'tnix') call assert_equal([0, 9, 11, 0], getpos('.')) @@ -1381,7 +1381,7 @@ func Test_edit_PAGEUP_PAGEDOWN() call feedkeys("A\\", 'tnix') call assert_equal([0, 10, 11, 0], getpos('.')) call feedkeys("A\\", 'tnix') - call assert_equal([0, 2, 11, 0], getpos('.')) + call assert_equal([0, 10, 11, 0], getpos('.')) call cursor(1, 1) call feedkeys("A\\", 'tnix') call assert_equal([0, 9, 11, 0], getpos('.')) diff --git a/test/old/testdir/test_normal.vim b/test/old/testdir/test_normal.vim index 26270fddd2..013d8959ca 100644 --- a/test/old/testdir/test_normal.vim +++ b/test/old/testdir/test_normal.vim @@ -1283,13 +1283,9 @@ func Test_vert_scroll_cmds() exe "normal \" call assert_equal(46, line('.')) exe "normal \" - call assert_equal(36, line('w0')) - call assert_equal(46, line('.')) + call assert_equal(36, line('.')) exe "normal \" - call assert_equal(1, line('w0')) - call assert_equal(40, line('.')) - exe "normal \" - call assert_equal(30, line('.')) + call assert_equal(1, line('.')) exe "normal \" call assert_equal(1, line('.')) set scroll& @@ -1310,8 +1306,9 @@ func Test_vert_scroll_cmds() call assert_equal(50, line('.')) call assert_equal(100, line('w$')) normal z. + let lnum = winline() exe "normal \" - call assert_equal(1, winline()) + call assert_equal(lnum, winline()) call assert_equal(50, line('.')) normal zt exe "normal \" @@ -3080,8 +3077,7 @@ func Test_normal42_halfpage() call assert_equal(2, &scroll) set scroll=5 exe "norm! \" - call assert_equal('3', getline('w0')) - call assert_equal('8', getline('.')) + call assert_equal('3', getline('.')) 1 set scrolloff=5 exe "norm! \" @@ -3828,7 +3824,7 @@ func Test_normal_vert_scroll_longline() call assert_equal(1, winline()) exe "normal \" call assert_equal(10, line('.')) - call assert_equal(10, winline()) + call assert_equal(4, winline()) exe "normal \\" call assert_equal(5, line('.')) call assert_equal(5, winline()) diff --git a/test/old/testdir/test_scroll_opt.vim b/test/old/testdir/test_scroll_opt.vim index 575ce0c6b5..4e551cdaad 100644 --- a/test/old/testdir/test_scroll_opt.vim +++ b/test/old/testdir/test_scroll_opt.vim @@ -552,14 +552,14 @@ func Test_smoothscroll_cursor_position() exe "normal \" call s:check_col_calc(1, 3, 41) - " Test "g0/g" + " Test "g0/g" exe "normal gg\" norm $gkg0 - call s:check_col_calc(1, 2, 21) + call s:check_col_calc(4, 1, 24) " Test moving the cursor behind the <<< display with 'virtualedit' set virtualedit=all - exe "normal \3lgkh" + exe "normal \gkh" call s:check_col_calc(3, 2, 23) set virtualedit& @@ -1020,26 +1020,72 @@ func Test_smoothscroll_page() exe "norm! \" call assert_equal(0, winsaveview().skipcol) - exe "norm! \" + " Half-page scrolling does not go beyond end of buffer and moves the cursor. + exe "norm! 0\" call assert_equal(200, winsaveview().skipcol) + call assert_equal(204, col('.')) exe "norm! \" call assert_equal(400, winsaveview().skipcol) + call assert_equal(404, col('.')) exe "norm! \" - call assert_equal(600, winsaveview().skipcol) + call assert_equal(520, winsaveview().skipcol) + call assert_equal(601, col('.')) exe "norm! \" - call assert_equal(800, winsaveview().skipcol) - exe "norm! \" - call assert_equal(880, winsaveview().skipcol) + call assert_equal(520, winsaveview().skipcol) + call assert_equal(801, col('.')) exe "norm! \" - call assert_equal(680, winsaveview().skipcol) + call assert_equal(520, winsaveview().skipcol) + call assert_equal(601, col('.')) exe "norm! \" - call assert_equal(480, winsaveview().skipcol) + call assert_equal(400, winsaveview().skipcol) + call assert_equal(404, col('.')) exe "norm! \" - call assert_equal(280, winsaveview().skipcol) - exe "norm! \" - call assert_equal(80, winsaveview().skipcol) + call assert_equal(200, winsaveview().skipcol) + call assert_equal(204, col('.')) exe "norm! \" call assert_equal(0, winsaveview().skipcol) + call assert_equal(1, col('.')) + + bwipe! +endfunc + +func Test_smoothscroll_next_topline() + call NewWindow(10, 40) + setlocal smoothscroll + call setline(1, ['abcde '->repeat(150)]->repeat(2)) + + " Scrolling a screenline that causes the cursor to move to the next buffer + " line should not skip part of that line to bring the cursor into view. + exe "norm! 22\" + call assert_equal(880, winsaveview().skipcol) + exe "norm! \" + redraw + call assert_equal(0, winsaveview().skipcol) + + " Cursor in correct place when not in the first screenline of a buffer line. + exe "norm! gg4gj20\\" + redraw + call assert_equal(2, line('w0')) + + bwipe! +endfunc + +func Test_smoothscroll_long_line_zb() + call NewWindow(10, 40) + call setline(1, 'abcde '->repeat(150)) + + " Also works without 'smoothscroll' when last line of buffer doesn't fit. + " Used to set topline to buffer line count plus one, causing an empty screen. + norm zb + redraw + call assert_equal(1, winsaveview().topline) + + " Moving cursor to bottom works on line that doesn't fit with 'smoothscroll'. + " Skipcol was adjusted later for cursor being on not visible part of line. + setlocal smoothscroll + norm zb + redraw + call assert_equal(520, winsaveview().skipcol) bwipe! endfunc From e21423bb35077fe4bbb6a8fab1000e8bfc6b6b7b Mon Sep 17 00:00:00 2001 From: Luuk van Baal Date: Wed, 3 Apr 2024 23:06:23 +0200 Subject: [PATCH 2/3] vim-patch:9.1.0260: Problems with "zb" and scrolling to new topline with 'smoothscroll' Problem: "zb" does not reveal filler lines at the start of a buffer. Scrolled cursor position with 'smoothscroll' is unpredictable, and may reset skipcol later if it is not visible (after v9.1.258) Solution: Replace confusing for loop that reaches final control value too early with while loop. Set "w_curswant" accordingly so cursor will be placed in visible part of topline. (Luuk van Baal) https://github.com/vim/vim/commit/bd28cae1f1c21c0e3743e3427c98bbd848fad237 --- src/nvim/move.c | 38 +++++++++++++------------- test/functional/legacy/normal_spec.lua | 3 +- test/old/testdir/test_normal.vim | 15 ++++++++++ test/old/testdir/test_scroll_opt.vim | 9 +++++- 4 files changed, 43 insertions(+), 22 deletions(-) diff --git a/src/nvim/move.c b/src/nvim/move.c index 63505c72ab..a1c0a02837 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -1231,6 +1231,10 @@ bool scrolldown(win_T *wp, linenr_T line_count, int byfold) wp->w_topline--; wp->w_skipcol = 0; wp->w_topfill = 0; + // Adjusting the cursor later should not adjust skipcol. + if (do_sms) { + curwin->w_curswant = MAXCOL; + } // A sequence of folded lines only counts for one logical line linenr_T first; if (hasFolding(wp, wp->w_topline, &first, NULL)) { @@ -1385,9 +1389,10 @@ bool scrollup(win_T *wp, linenr_T line_count, bool byfold) wp->w_topline = lnum; wp->w_topfill = win_get_fill(wp, lnum); wp->w_skipcol = 0; - // Adjusting the cursor later should not adjust skipcol: - // bring it to the first screenline on this new topline. - wp->w_curswant %= width1; + // Adjusting the cursor later should not adjust skipcol. + if (do_sms) { + curwin->w_curswant = 0; + } if (todo > 1 && do_sms) { size = linetabsize(wp, wp->w_topline); } @@ -1846,15 +1851,11 @@ void scroll_cursor_bot(win_T *wp, int min_scroll, bool set_topbot) bool do_sms = wp->w_p_wrap && wp->w_p_sms; if (set_topbot) { - bool set_skipcol = false; - int used = 0; wp->w_botline = cln + 1; + loff.lnum = cln + 1; loff.fill = 0; - for (wp->w_topline = wp->w_botline; - wp->w_topline > 1; - wp->w_topline = loff.lnum) { - loff.lnum = wp->w_topline; + while (true) { topline_back_winheight(wp, &loff, false); if (loff.height == MAXCOL) { break; @@ -1872,25 +1873,23 @@ void scroll_cursor_bot(win_T *wp, int min_scroll, bool set_topbot) wp->w_topline = loff.lnum; wp->w_skipcol = skipcol_from_plines(wp, plines_offset); wp->w_cursor.col = wp->w_skipcol + overlap; - set_skipcol = true; } } break; } - used += loff.height; wp->w_topfill = loff.fill; + wp->w_topline = loff.lnum; + used += loff.height; } - if (wp->w_topline > wp->w_buffer->b_ml.ml_line_count) { - wp->w_topline = wp->w_buffer->b_ml.ml_line_count; - } + set_empty_rows(wp, used); wp->w_valid |= VALID_BOTLINE|VALID_BOTLINE_AP; if (wp->w_topline != old_topline || wp->w_topfill != old_topfill - || set_skipcol + || wp->w_skipcol != old_skipcol || wp->w_skipcol != 0) { wp->w_valid &= ~(VALID_WROW|VALID_CROW); - if (set_skipcol) { + if (wp->w_skipcol != old_skipcol) { redraw_later(wp, UPD_NOT_VALID); } else { reset_skipcol(wp); @@ -2305,7 +2304,8 @@ static int get_scroll_overlap(Direction dir) int min_height = curwin->w_height_inner - 2; validate_botline(curwin); - if (dir == FORWARD && curwin->w_botline > curbuf->b_ml.ml_line_count) { + if ((dir == BACKWARD && curwin->w_topline == 1) + || (dir == FORWARD && curwin->w_botline > curbuf->b_ml.ml_line_count)) { return min_height + 2; // no overlap, still handle 'smoothscroll' } @@ -2435,7 +2435,6 @@ int pagescroll(Direction dir, int count, bool half) } else { cursor_up_inner(curwin, count); } - curwin->w_curswant = prev_curswant; if (get_scrolloff_value(curwin)) { cursor_correct(curwin); @@ -2453,12 +2452,13 @@ int pagescroll(Direction dir, int count, bool half) nochange = scroll_with_sms(dir, &count); } + curwin->w_curswant = prev_curswant; // Error if both the viewport and cursor did not change. if (nochange) { beep_flush(); } else if (!curwin->w_p_sms) { beginline(BL_SOL | BL_FIX); - } else if (p_sol) { + } else if (p_sol || curwin->w_skipcol) { nv_g_home_m_cmd(&ca); } diff --git a/test/functional/legacy/normal_spec.lua b/test/functional/legacy/normal_spec.lua index 113f7c668a..c57e51c18e 100644 --- a/test/functional/legacy/normal_spec.lua +++ b/test/functional/legacy/normal_spec.lua @@ -94,10 +94,9 @@ describe('normal', function() feed('ggG') screen:expect({ grid = [[ - foobar one two three |*8 + foobar one two three |*16 ^foobar one two three | {2:---} | - {1:~ }|*8 | ]], }) diff --git a/test/old/testdir/test_normal.vim b/test/old/testdir/test_normal.vim index 013d8959ca..10fbf4125a 100644 --- a/test/old/testdir/test_normal.vim +++ b/test/old/testdir/test_normal.vim @@ -4220,4 +4220,19 @@ func Test_single_line_scroll() call prop_type_delete(vt) endfunc +" Test for zb in buffer with a single line and filler lines +func Test_single_line_filler_zb() + call setline(1, ['', 'foobar one two three']) + diffthis + new + call setline(1, ['foobar one two three']) + diffthis + + " zb scrolls to reveal filler lines at the start of the buffer. + exe "normal \zb" + call assert_equal(1, winsaveview().topfill) + + bw! +endfunc + " vim: shiftwidth=2 sts=2 expandtab nofoldenable diff --git a/test/old/testdir/test_scroll_opt.vim b/test/old/testdir/test_scroll_opt.vim index 4e551cdaad..d381456032 100644 --- a/test/old/testdir/test_scroll_opt.vim +++ b/test/old/testdir/test_scroll_opt.vim @@ -1021,6 +1021,8 @@ func Test_smoothscroll_page() call assert_equal(0, winsaveview().skipcol) " Half-page scrolling does not go beyond end of buffer and moves the cursor. + " Even with 'nostartofline', the correct amount of lines is scrolled. + setl nostartofline exe "norm! 0\" call assert_equal(200, winsaveview().skipcol) call assert_equal(204, col('.')) @@ -1044,7 +1046,7 @@ func Test_smoothscroll_page() call assert_equal(204, col('.')) exe "norm! \" call assert_equal(0, winsaveview().skipcol) - call assert_equal(1, col('.')) + call assert_equal(40, col('.')) bwipe! endfunc @@ -1062,6 +1064,11 @@ func Test_smoothscroll_next_topline() redraw call assert_equal(0, winsaveview().skipcol) + " Also when scrolling back. + exe "norm! G\" + redraw + call assert_equal(880, winsaveview().skipcol) + " Cursor in correct place when not in the first screenline of a buffer line. exe "norm! gg4gj20\\" redraw From 8f5fd0884b4424d028feef1bccd018a48c93a59c Mon Sep 17 00:00:00 2001 From: Luuk van Baal Date: Mon, 8 Apr 2024 22:40:55 +0200 Subject: [PATCH 3/3] vim-patch:9.1.0280: several issues with 'smoothscroll' support Problem: Logic to make sure cursor is in visible part of the screen after scrolling the text with 'smoothscroll' is scattered, asymmetric and contains bugs. Solution: Adjust and create helper function for 'smoothscroll' cursor logic. (Luuk van Baal) https://github.com/vim/vim/commit/9148ba8a46baa3934c44164989cdcdec5d01d9e3 --- src/nvim/change.c | 2 +- src/nvim/move.c | 277 ++++++++++++++++----------- src/nvim/normal.c | 52 ----- src/nvim/plines.c | 3 + test/old/testdir/test_diffmode.vim | 15 ++ test/old/testdir/test_scroll_opt.vim | 38 ++-- 6 files changed, 213 insertions(+), 174 deletions(-) diff --git a/src/nvim/change.c b/src/nvim/change.c index 4d6c4acd0e..d76baaff2c 100644 --- a/src/nvim/change.c +++ b/src/nvim/change.c @@ -342,7 +342,7 @@ static void changed_common(buf_T *buf, linenr_T lnum, colnr_T col, linenr_T lnum && (last < wp->w_topline || (wp->w_topline >= lnum && wp->w_topline < lnume - && win_linetabsize(wp, wp->w_topline, ml_get(wp->w_topline), MAXCOL) + && win_linetabsize(wp, wp->w_topline, ml_get_buf(buf, wp->w_topline), MAXCOL) <= (wp->w_skipcol + sms_marker_overlap(wp, -1))))) { wp->w_skipcol = 0; } diff --git a/src/nvim/move.c b/src/nvim/move.c index a1c0a02837..675f048d09 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -78,13 +78,14 @@ int adjust_plines_for_skipcol(win_T *wp) /// Return how many lines "lnum" will take on the screen, taking into account /// whether it is the first line, whether w_skipcol is non-zero and limiting to /// the window height. -static int plines_correct_topline(win_T *wp, linenr_T lnum, linenr_T *nextp, bool *foldedp) +static int plines_correct_topline(win_T *wp, linenr_T lnum, linenr_T *nextp, bool limit_winheight, + bool *foldedp) { int n = plines_win_full(wp, lnum, nextp, foldedp, true, false); if (lnum == wp->w_topline) { n -= adjust_plines_for_skipcol(wp); } - if (n > wp->w_height_inner) { + if (limit_winheight && n > wp->w_height_inner) { return wp->w_height_inner; } return n; @@ -111,7 +112,7 @@ static void comp_botline(win_T *wp) for (; lnum <= wp->w_buffer->b_ml.ml_line_count; lnum++) { linenr_T last = lnum; bool folded; - int n = plines_correct_topline(wp, lnum, &last, &folded); + int n = plines_correct_topline(wp, lnum, &last, true, &folded); if (lnum <= wp->w_cursor.lnum && last >= wp->w_cursor.lnum) { wp->w_cline_row = done; wp->w_cline_height = n; @@ -670,7 +671,7 @@ static void curs_rows(win_T *wp) } else { linenr_T last = lnum; bool folded; - int n = plines_correct_topline(wp, lnum, &last, &folded); + int n = plines_correct_topline(wp, lnum, &last, true, &folded); lnum = last + 1; if (folded && lnum > wp->w_cursor.lnum) { break; @@ -1188,6 +1189,122 @@ void f_virtcol2col(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) rettv->vval.v_number = virtcol2col(wp, lnum, screencol); } +/// Make sure the cursor is in the visible part of the topline after scrolling +/// the screen with 'smoothscroll'. +static void cursor_correct_sms(win_T *wp) +{ + if (!wp->w_p_sms || !wp->w_p_wrap || wp->w_cursor.lnum != wp->w_topline) { + return; + } + + int so = get_scrolloff_value(wp); + int width1 = wp->w_width_inner - win_col_off(wp); + int width2 = width1 + win_col_off2(wp); + int so_cols = so == 0 ? 0 : width1 + (so - 1) * width2; + int space_cols = (wp->w_height_inner - 1) * width2; + int size = so == 0 ? 0 : win_linetabsize(wp, wp->w_topline, + ml_get_buf(wp->w_buffer, wp->w_topline), + (colnr_T)MAXCOL); + + if (wp->w_topline == 1 && wp->w_skipcol == 0) { + so_cols = 0; // Ignore 'scrolloff' at top of buffer. + } else if (so_cols > space_cols / 2) { + so_cols = space_cols / 2; // Not enough room: put cursor in the middle. + } + + // Not enough screen lines in topline: ignore 'scrolloff'. + while (so_cols > size && so_cols - width2 >= width1) { + so_cols -= width2; + } + if (so_cols >= width1 && so_cols > size) { + so_cols -= width1; + } + + // If there is no marker or we have non-zero scrolloff, just ignore it. + int overlap = (wp->w_skipcol == 0 || so_cols != 0) ? 0 : sms_marker_overlap(wp, -1); + int top = wp->w_skipcol + overlap + so_cols; + int bot = wp->w_skipcol + width1 + (wp->w_height_inner - 1) * width2 - so_cols; + + validate_virtcol(wp); + colnr_T col = wp->w_virtcol; + + if (col < top) { + if (col < width1) { + col += width1; + } + while (width2 > 0 && col < top) { + col += width2; + } + } else { + while (width2 > 0 && col >= bot) { + col -= width2; + } + } + + if (col != wp->w_virtcol) { + wp->w_curswant = col; + coladvance(wp, wp->w_curswant); + // validate_virtcol() marked various things as valid, but after + // moving the cursor they need to be recomputed + wp->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_CHEIGHT|VALID_CROW|VALID_VIRTCOL); + } +} + +/// Scroll "count" lines up or down, and redraw. +void scroll_redraw(int up, linenr_T count) +{ + linenr_T prev_topline = curwin->w_topline; + int prev_skipcol = curwin->w_skipcol; + int prev_topfill = curwin->w_topfill; + linenr_T prev_lnum = curwin->w_cursor.lnum; + + bool moved = up + ? scrollup(curwin, count, true) + : scrolldown(curwin, count, true); + + if (get_scrolloff_value(curwin) > 0) { + // Adjust the cursor position for 'scrolloff'. Mark w_topline as + // valid, otherwise the screen jumps back at the end of the file. + cursor_correct(curwin); + check_cursor_moved(curwin); + curwin->w_valid |= VALID_TOPLINE; + + // If moved back to where we were, at least move the cursor, otherwise + // we get stuck at one position. Don't move the cursor up if the + // first line of the buffer is already on the screen + while (curwin->w_topline == prev_topline + && curwin->w_skipcol == prev_skipcol + && curwin->w_topfill == prev_topfill) { + if (up) { + if (curwin->w_cursor.lnum > prev_lnum + || cursor_down(1L, false) == FAIL) { + break; + } + } else { + if (curwin->w_cursor.lnum < prev_lnum + || prev_topline == 1L + || cursor_up(1L, false) == FAIL) { + break; + } + } + // Mark w_topline as valid, otherwise the screen jumps back at the + // end of the file. + check_cursor_moved(curwin); + curwin->w_valid |= VALID_TOPLINE; + } + } + + if (moved) { + curwin->w_viewport_invalid = true; + } + + cursor_correct_sms(curwin); + if (curwin->w_cursor.lnum != prev_lnum) { + coladvance(curwin, curwin->w_curswant); + } + redraw_later(curwin, UPD_VALID); +} + /// Scroll a window down by "line_count" logical lines. "CTRL-Y" /// /// @param line_count number of lines to scroll @@ -1231,10 +1348,6 @@ bool scrolldown(win_T *wp, linenr_T line_count, int byfold) wp->w_topline--; wp->w_skipcol = 0; wp->w_topfill = 0; - // Adjusting the cursor later should not adjust skipcol. - if (do_sms) { - curwin->w_curswant = MAXCOL; - } // A sequence of folded lines only counts for one logical line linenr_T first; if (hasFolding(wp, wp->w_topline, &first, NULL)) { @@ -1247,7 +1360,7 @@ bool scrolldown(win_T *wp, linenr_T line_count, int byfold) } else { if (do_sms) { int size = win_linetabsize(wp, wp->w_topline, - ml_get(wp->w_topline), MAXCOL); + ml_get_buf(wp->w_buffer, wp->w_topline), MAXCOL); if (size > width1) { wp->w_skipcol = width1; size -= width1; @@ -1306,27 +1419,10 @@ bool scrolldown(win_T *wp, linenr_T line_count, int byfold) foldAdjustCursor(wp); coladvance(wp, wp->w_curswant); } - - if (wp->w_cursor.lnum == wp->w_topline && do_sms) { - int so = get_scrolloff_value(wp); - colnr_T scrolloff_cols = so == 0 ? 0 : width1 + (so - 1) * width2; - - // make sure the cursor is in the visible text - validate_virtcol(wp); - colnr_T col = wp->w_virtcol - wp->w_skipcol + scrolloff_cols; - int row = 0; - if (col >= width1) { - col -= width1; - row++; - } - if (col > width2 && width2 > 0) { - row += (int)col / width2; - } - if (row >= wp->w_height_inner) { - wp->w_curswant = wp->w_virtcol - (row - wp->w_height_inner + 1) * width2; - coladvance(wp, wp->w_curswant); - } + if (wp->w_cursor.lnum < wp->w_topline) { + wp->w_cursor.lnum = wp->w_topline; } + return moved; } @@ -1389,10 +1485,6 @@ bool scrollup(win_T *wp, linenr_T line_count, bool byfold) wp->w_topline = lnum; wp->w_topfill = win_get_fill(wp, lnum); wp->w_skipcol = 0; - // Adjusting the cursor later should not adjust skipcol. - if (do_sms) { - curwin->w_curswant = 0; - } if (todo > 1 && do_sms) { size = linetabsize(wp, wp->w_topline); } @@ -1432,46 +1524,6 @@ bool scrollup(win_T *wp, linenr_T line_count, bool byfold) coladvance(wp, wp->w_curswant); } - if (wp->w_cursor.lnum == wp->w_topline && do_sms && wp->w_skipcol > 0) { - int col_off = win_col_off(wp); - int col_off2 = win_col_off2(wp); - - int width1 = wp->w_width_inner - col_off; - int width2 = width1 + col_off2; - int extra2 = col_off - col_off2; - int so = get_scrolloff_value(wp); - colnr_T scrolloff_cols = so == 0 ? 0 : width1 + (so - 1) * width2; - int space_cols = (wp->w_height_inner - 1) * width2; - - // If we have non-zero scrolloff, just ignore the marker as we are - // going past it anyway. - int overlap = scrolloff_cols != 0 ? 0 : sms_marker_overlap(wp, extra2); - - // Make sure the cursor is in a visible part of the line, taking - // 'scrolloff' into account, but using screen lines. - // If there are not enough screen lines put the cursor in the middle. - if (scrolloff_cols > space_cols / 2) { - scrolloff_cols = space_cols / 2; - } - validate_virtcol(wp); - if (wp->w_virtcol < wp->w_skipcol + overlap + scrolloff_cols) { - colnr_T col = wp->w_virtcol; - - if (col < width1) { - col += width1; - } - while (col < wp->w_skipcol + overlap + scrolloff_cols) { - col += width2; - } - wp->w_curswant = col; - coladvance(wp, wp->w_curswant); - - // validate_virtcol() marked various things as valid, but after - // moving the cursor they need to be recomputed - wp->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_CHEIGHT|VALID_CROW|VALID_VIRTCOL); - } - } - bool moved = topline != wp->w_topline || botline != wp->w_botline; return moved; @@ -1867,12 +1919,10 @@ void scroll_cursor_bot(win_T *wp, int min_scroll, bool set_topbot) // of it. if (used < wp->w_height_inner) { int plines_offset = used + loff.height - wp->w_height_inner; - int overlap = sms_marker_overlap(wp, -1); used = wp->w_height_inner; wp->w_topfill = loff.fill; wp->w_topline = loff.lnum; wp->w_skipcol = skipcol_from_plines(wp, plines_offset); - wp->w_cursor.col = wp->w_skipcol + overlap; } } break; @@ -2055,6 +2105,8 @@ void scroll_cursor_bot(win_T *wp, int min_scroll, bool set_topbot) } wp->w_valid |= VALID_TOPLINE; wp->w_viewport_invalid = true; + + cursor_correct_sms(wp); } /// Recompute topline to put the cursor halfway across the window @@ -2352,9 +2404,8 @@ static int get_scroll_overlap(Direction dir) } } -/// Scroll "count" lines with 'smoothscroll' in direction "dir". Adjust "count" -/// when scrolling more than "count" and return true when scrolling happened. -static bool scroll_with_sms(Direction dir, int *count) +/// Scroll "count" lines with 'smoothscroll' in direction "dir". +static bool scroll_with_sms(Direction dir, int count) { int prev_sms = curwin->w_p_sms; colnr_T prev_skipcol = curwin->w_skipcol; @@ -2362,11 +2413,10 @@ static bool scroll_with_sms(Direction dir, int *count) int prev_topfill = curwin->w_topfill; curwin->w_p_sms = true; - scroll_redraw(dir == FORWARD, *count); + scroll_redraw(dir == FORWARD, count); // Not actually smoothscrolling but ended up with partially visible line. - // Continue scrolling and update "count" so that cursor can be moved - // accordingly for half-page scrolling. + // Continue scrolling until skipcol is zero. if (!prev_sms && curwin->w_skipcol > 0) { int fixdir = dir; // Reverse the scroll direction when topline already changed. One line @@ -2378,7 +2428,6 @@ static bool scroll_with_sms(Direction dir, int *count) while (curwin->w_skipcol > 0 && curwin->w_topline < curbuf->b_ml.ml_line_count) { scroll_redraw(fixdir == FORWARD, 1); - *count += (fixdir == dir ? 1 : -1); } } curwin->w_p_sms = prev_sms; @@ -2398,7 +2447,6 @@ int pagescroll(Direction dir, int count, bool half) int nochange = true; int buflen = curbuf->b_ml.ml_line_count; colnr_T prev_col = curwin->w_cursor.col; - colnr_T prev_curswant = curwin->w_curswant; linenr_T prev_lnum = curwin->w_cursor.lnum; oparg_T oa = { 0 }; cmdarg_T ca = { 0 }; @@ -2410,33 +2458,45 @@ int pagescroll(Direction dir, int count, bool half) curwin->w_p_scr = MIN(curwin->w_height_inner, count); } count = MIN(curwin->w_height_inner, (int)curwin->w_p_scr); - // Don't scroll if we already know that it will reveal end of buffer lines. - if (dir == BACKWARD - || (curwin->w_botline - 1 < buflen) - || (curwin->w_p_sms && curwin->w_botline - 1 == buflen - && curwin->w_skipcol < linetabsize(curwin, buflen))) { - nochange = scroll_with_sms(dir, &count); - validate_botline(curwin); - // Hide any potentially revealed end of buffer lines. - if (!nochange && curwin->w_botline - 1 == buflen) { - curwin->w_cursor.lnum = buflen; - scroll_cursor_bot(curwin, 0, true); + + int curscount = count; + // Adjust count so as to not reveal end of buffer lines. + if (dir == FORWARD) { + int n = plines_correct_topline(curwin, curwin->w_topline, NULL, false, NULL); + if (n - count < curwin->w_height_inner && curwin->w_topline < buflen) { + n += plines_m_win(curwin, curwin->w_topline + 1, buflen, true); + } + if (n - count < curwin->w_height_inner) { + count = n - curwin->w_height_inner; } } - // Move the cursor "count" screen lines. - curwin->w_curswant = MAXCOL; - curwin->w_cursor.col = prev_col; - curwin->w_cursor.lnum = prev_lnum; - if (curwin->w_p_wrap) { - nv_screengo(&oa, dir, count); - } else if (dir == FORWARD) { - cursor_down_inner(curwin, count); - } else { - cursor_up_inner(curwin, count); + // Scroll the window and determine number of lines to move the cursor. + if (count > 0) { + validate_cursor(curwin); + int prev_wrow = curwin->w_wrow; + nochange = scroll_with_sms(dir, count); + if (!nochange) { + validate_cursor(curwin); + curscount = abs(prev_wrow - curwin->w_wrow); + dir = prev_wrow > curwin->w_wrow ? FORWARD : BACKWARD; + } } - if (get_scrolloff_value(curwin)) { + int so = get_scrolloff_value(curwin); + // Move the cursor the same amount of screen lines except if + // 'scrolloff' is set and cursor was at start or end of buffer. + if (so == 0 || (prev_lnum != 1 && prev_lnum != buflen)) { + if (curwin->w_p_wrap) { + nv_screengo(&oa, dir, curscount); + } else if (dir == FORWARD) { + cursor_down_inner(curwin, curscount); + } else { + cursor_up_inner(curwin, curscount); + } + } + + if (so > 0) { cursor_correct(curwin); } // Move cursor to first line of closed fold. @@ -2448,17 +2508,16 @@ int pagescroll(Direction dir, int count, bool half) } else { // Scroll [count] times 'window' or current window height lines. count *= ((ONE_WINDOW && p_window > 0 && p_window < Rows - 1) - ? MAX(1, p_window - 2) : get_scroll_overlap(dir)); - nochange = scroll_with_sms(dir, &count); + ? MAX(1, (int)p_window - 2) : get_scroll_overlap(dir)); + nochange = scroll_with_sms(dir, count); } - curwin->w_curswant = prev_curswant; // Error if both the viewport and cursor did not change. if (nochange) { beep_flush(); } else if (!curwin->w_p_sms) { beginline(BL_SOL | BL_FIX); - } else if (p_sol || curwin->w_skipcol) { + } else if (p_sol) { nv_g_home_m_cmd(&ca); } diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 8067bbcd4d..a95965ad6a 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -2614,58 +2614,6 @@ void nv_scroll_line(cmdarg_T *cap) } } -/// Scroll "count" lines up or down, and redraw. -void scroll_redraw(bool up, linenr_T count) -{ - linenr_T prev_topline = curwin->w_topline; - int prev_skipcol = curwin->w_skipcol; - int prev_topfill = curwin->w_topfill; - linenr_T prev_lnum = curwin->w_cursor.lnum; - - bool moved = up - ? scrollup(curwin, count, true) - : scrolldown(curwin, count, true); - - if (get_scrolloff_value(curwin) > 0) { - // Adjust the cursor position for 'scrolloff'. Mark w_topline as - // valid, otherwise the screen jumps back at the end of the file. - cursor_correct(curwin); - check_cursor_moved(curwin); - curwin->w_valid |= VALID_TOPLINE; - - // If moved back to where we were, at least move the cursor, otherwise - // we get stuck at one position. Don't move the cursor up if the - // first line of the buffer is already on the screen - while (curwin->w_topline == prev_topline - && curwin->w_skipcol == prev_skipcol - && curwin->w_topfill == prev_topfill) { - if (up) { - if (curwin->w_cursor.lnum > prev_lnum - || cursor_down(1, false) == false) { - break; - } - } else { - if (curwin->w_cursor.lnum < prev_lnum - || prev_topline == 1 - || cursor_up(1, false) == false) { - break; - } - } - // Mark w_topline as valid, otherwise the screen jumps back at the - // end of the file. - check_cursor_moved(curwin); - curwin->w_valid |= VALID_TOPLINE; - } - } - if (curwin->w_cursor.lnum != prev_lnum) { - coladvance(curwin, curwin->w_curswant); - } - if (moved) { - curwin->w_viewport_invalid = true; - } - redraw_later(curwin, UPD_VALID); -} - /// Get the count specified after a 'z' command. Only the 'z', 'zl', 'zh', /// 'z', and 'z' commands accept a count after 'z'. /// @return true to process the 'z' command and false to skip it. diff --git a/src/nvim/plines.c b/src/nvim/plines.c index d90ee9c1ba..8734780415 100644 --- a/src/nvim/plines.c +++ b/src/nvim/plines.c @@ -885,6 +885,9 @@ int plines_m_win(win_T *wp, linenr_T first, linenr_T last, bool limit_winheight) count += plines_win_full(wp, first, &next, NULL, false, limit_winheight); first = next + 1; } + if (first == wp->w_buffer->b_ml.ml_line_count + 1) { + count += win_get_fill(wp, first); + } return count; } diff --git a/test/old/testdir/test_diffmode.vim b/test/old/testdir/test_diffmode.vim index 67f6aaecbb..334bb3ee32 100644 --- a/test/old/testdir/test_diffmode.vim +++ b/test/old/testdir/test_diffmode.vim @@ -1772,4 +1772,19 @@ func Test_diff_toggle_wrap_skipcol_leftcol() bwipe! endfunc +" Ctrl-D reveals filler lines below the last line in the buffer. +func Test_diff_eob_halfpage() + 5new + call setline(1, ['']->repeat(10) + ['a']) + diffthis + 5new + call setline(1, ['']->repeat(3) + ['a', 'b']) + diffthis + wincmd j + exe "norm! G\" + call assert_equal(6, line('w0')) + + %bwipe! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_scroll_opt.vim b/test/old/testdir/test_scroll_opt.vim index d381456032..cb37b09707 100644 --- a/test/old/testdir/test_scroll_opt.vim +++ b/test/old/testdir/test_scroll_opt.vim @@ -1023,30 +1023,36 @@ func Test_smoothscroll_page() " Half-page scrolling does not go beyond end of buffer and moves the cursor. " Even with 'nostartofline', the correct amount of lines is scrolled. setl nostartofline - exe "norm! 0\" + exe "norm! 15|\" call assert_equal(200, winsaveview().skipcol) - call assert_equal(204, col('.')) + call assert_equal(215, col('.')) exe "norm! \" call assert_equal(400, winsaveview().skipcol) - call assert_equal(404, col('.')) + call assert_equal(415, col('.')) exe "norm! \" call assert_equal(520, winsaveview().skipcol) - call assert_equal(601, col('.')) + call assert_equal(535, col('.')) exe "norm! \" call assert_equal(520, winsaveview().skipcol) - call assert_equal(801, col('.')) - exe "norm! \" + call assert_equal(735, col('.')) + exe "norm! \" call assert_equal(520, winsaveview().skipcol) - call assert_equal(601, col('.')) + call assert_equal(895, col('.')) exe "norm! \" - call assert_equal(400, winsaveview().skipcol) - call assert_equal(404, col('.')) + call assert_equal(320, winsaveview().skipcol) + call assert_equal(695, col('.')) exe "norm! \" - call assert_equal(200, winsaveview().skipcol) - call assert_equal(204, col('.')) + call assert_equal(120, winsaveview().skipcol) + call assert_equal(495, col('.')) exe "norm! \" call assert_equal(0, winsaveview().skipcol) - call assert_equal(40, col('.')) + call assert_equal(375, col('.')) + exe "norm! \" + call assert_equal(0, winsaveview().skipcol) + call assert_equal(175, col('.')) + exe "norm! \" + call assert_equal(0, winsaveview().skipcol) + call assert_equal(15, col('.')) bwipe! endfunc @@ -1074,6 +1080,14 @@ func Test_smoothscroll_next_topline() redraw call assert_equal(2, line('w0')) + " Cursor does not end up above topline, adjusting topline later. + setlocal nu cpo+=n + exe "norm! G$g013\" + redraw + call assert_equal(2, line('.')) + call assert_equal(0, winsaveview().skipcol) + + set cpo-=n bwipe! endfunc