# HG changeset patch # User Christian Brabandt # Date 1712609103 -7200 # Node ID 8e38ceda0822d046998a4c5fa2c84b967f133693 # Parent 6ff5ddfde8852dea274a03becd729133462baa73 patch 9.1.0280: several issues with 'smoothscroll' support Commit: https://github.com/vim/vim/commit/9148ba8a46baa3934c44164989cdcdec5d01d9e3 Author: Luuk van Baal Date: Mon Apr 8 22:27:41 2024 +0200 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) closes: #14410 Signed-off-by: Luuk van Baal Signed-off-by: Christian Brabandt diff --git a/src/misc1.c b/src/misc1.c --- a/src/misc1.c +++ b/src/misc1.c @@ -519,6 +519,8 @@ plines_m_win(win_T *wp, linenr_T first, #endif { #ifdef FEAT_DIFF + if (first == wp->w_buffer->b_ml.ml_line_count) + count += diff_check_fill(wp, first + 1); if (first == wp->w_topline) count += plines_win_nofill(wp, first, limit_winheight) + wp->w_topfill; diff --git a/src/move.c b/src/move.c --- a/src/move.c +++ b/src/move.c @@ -59,7 +59,7 @@ adjust_plines_for_skipcol(win_T *wp) * the window height. */ static int -plines_correct_topline(win_T *wp, linenr_T lnum) +plines_correct_topline(win_T *wp, linenr_T lnum, int limit_winheight) { int n; #ifdef FEAT_DIFF @@ -70,7 +70,7 @@ plines_correct_topline(win_T *wp, linenr n = plines_win(wp, lnum, FALSE); if (lnum == wp->w_topline) n -= adjust_plines_for_skipcol(wp); - if (n > wp->w_height) + if (limit_winheight && n > wp->w_height) n = wp->w_height; return n; } @@ -119,7 +119,7 @@ comp_botline(win_T *wp) else #endif { - n = plines_correct_topline(wp, lnum); + n = plines_correct_topline(wp, lnum, TRUE); } if ( #ifdef FEAT_FOLDING @@ -921,7 +921,7 @@ curs_rows(win_T *wp) else #endif { - wp->w_cline_row += plines_correct_topline(wp, lnum); + wp->w_cline_row += plines_correct_topline(wp, lnum, TRUE); ++lnum; } } @@ -1602,6 +1602,127 @@ f_virtcol2col(typval_T *argvars UNUSED, #endif /* + * Make sure the cursor is in the visible part of the topline after scrolling + * the screen with 'smoothscroll'. + */ +static void cursor_correct_sms(void) +{ + if (!curwin->w_p_sms ||!curwin->w_p_wrap + || curwin->w_cursor.lnum != curwin->w_topline) + return; + + long so = get_scrolloff_value(); + int width1 = curwin->w_width - curwin_col_off(); + int width2 = width1 + curwin_col_off2(); + int so_cols = so == 0 ? 0 : width1 + (so - 1) * width2; + int space_cols = (curwin->w_height - 1) * width2; + int size = so == 0 ? 0 : win_linetabsize(curwin, curwin->w_topline, + ml_get(curwin->w_topline), (colnr_T)MAXCOL); + + if (curwin->w_topline == 1 && curwin->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 = (curwin->w_skipcol == 0 || so_cols != 0) ? 0 + : sms_marker_overlap(curwin, -1); + int top = curwin->w_skipcol + overlap + so_cols; + int bot = curwin->w_skipcol + width1 + (curwin->w_height - 1) * width2 + - so_cols; + validate_virtcol(); + colnr_T col = curwin->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 != curwin->w_virtcol) + { + curwin->w_curswant = col; + coladvance(curwin->w_curswant); + // validate_virtcol() marked various things as valid, but after + // moving the cursor they need to be recomputed + curwin->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, long count) +{ + linenr_T prev_topline = curwin->w_topline; + int prev_skipcol = curwin->w_skipcol; +#ifdef FEAT_DIFF + int prev_topfill = curwin->w_topfill; +#endif + linenr_T prev_lnum = curwin->w_cursor.lnum; + + if (up) + scrollup(count, TRUE); + else + scrolldown(count, TRUE); + if (get_scrolloff_value() > 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(); + 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 +#ifdef FEAT_DIFF + && curwin->w_topfill == prev_topfill +#endif + ) + { + 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; + } + } + + cursor_correct_sms(); + if (curwin->w_cursor.lnum != prev_lnum) + coladvance(curwin->w_curswant); + redraw_later(UPD_VALID); +} + +/* * Scroll the current window down by "line_count" logical lines. "CTRL-Y" */ void @@ -1663,9 +1784,6 @@ scrolldown( #ifdef FEAT_DIFF curwin->w_topfill = 0; #endif - // Adjusting the cursor later should not adjust skipcol. - if (do_sms) - curwin->w_curswant = MAXCOL; #ifdef FEAT_FOLDING // A sequence of folded lines only counts for one logical line if (hasFolding(curwin->w_topline, &first, NULL)) @@ -1749,35 +1867,8 @@ scrolldown( #endif coladvance(curwin->w_curswant); } - - if (curwin->w_cursor.lnum == curwin->w_topline && do_sms) - { - long so = get_scrolloff_value(); - int scrolloff_cols = so == 0 ? 0 : width1 + (so - 1) * width2; - - // make sure the cursor is in the visible text - validate_virtcol(); - int col = curwin->w_virtcol - curwin->w_skipcol + scrolloff_cols; - int row = 0; - if (col >= width1) - { - col -= width1; - ++row; - } - if (col > width2 && width2 > 0) - { - row += col / width2; - // even so col is not used anymore, - // make sure it is correct, just in case - col = col % width2; - } - if (row >= curwin->w_height) - { - curwin->w_curswant = curwin->w_virtcol - - (row - curwin->w_height + 1) * width2; - coladvance(curwin->w_curswant); - } - } + if (curwin->w_cursor.lnum < curwin->w_topline) + curwin->w_cursor.lnum = curwin->w_topline; } /* @@ -1859,9 +1950,6 @@ scrollup( curwin->w_topfill = diff_check_fill(curwin, lnum); # endif curwin->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(curwin, curwin->w_topline); } @@ -1902,47 +1990,6 @@ scrollup( ~(VALID_WROW|VALID_WCOL|VALID_CHEIGHT|VALID_CROW|VALID_VIRTCOL); coladvance(curwin->w_curswant); } - if (curwin->w_cursor.lnum == curwin->w_topline - && do_sms && curwin->w_skipcol > 0) - { - int col_off = curwin_col_off(); - int col_off2 = curwin_col_off2(); - - int width1 = curwin->w_width - col_off; - int width2 = width1 + col_off2; - int extra2 = col_off - col_off2; - long so = get_scrolloff_value(); - int scrolloff_cols = so == 0 ? 0 : width1 + (so - 1) * width2; - int space_cols = (curwin->w_height - 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(curwin, 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(); - if (curwin->w_virtcol < curwin->w_skipcol + overlap + scrolloff_cols) - { - colnr_T col = curwin->w_virtcol; - - if (col < width1) - col += width1; - while (col < curwin->w_skipcol + overlap + scrolloff_cols) - col += width2; - curwin->w_curswant = col; - coladvance(curwin->w_curswant); - - // validate_virtcol() marked various things as valid, but after - // moving the cursor they need to be recomputed - curwin->w_valid &= - ~(VALID_WROW|VALID_WCOL|VALID_CHEIGHT|VALID_CROW|VALID_VIRTCOL); - } - } } /* @@ -2489,7 +2536,6 @@ scroll_cursor_bot(int min_scroll, int se { int plines_offset = used + loff.height - curwin->w_height; - int overlap = sms_marker_overlap(curwin, -1); used = curwin->w_height; #ifdef FEAT_DIFF curwin->w_topfill = loff.fill; @@ -2497,7 +2543,6 @@ scroll_cursor_bot(int min_scroll, int se curwin->w_topline = loff.lnum; curwin->w_skipcol = skipcol_from_plines( curwin, plines_offset); - curwin->w_cursor.col = curwin->w_skipcol + overlap; } } break; @@ -2722,6 +2767,8 @@ scroll_cursor_bot(int min_scroll, int se curwin->w_valid = old_valid; } curwin->w_valid |= VALID_TOPLINE; + + cursor_correct_sms(); } /* @@ -3094,10 +3141,10 @@ static int get_scroll_overlap(int dir) } /* - * Scroll "count" lines with 'smoothscroll' in direction "dir". Adjust "count" - * when scrolling more than "count" and return TRUE when scrolling happened. + * Scroll "count" lines with 'smoothscroll' in direction "dir". Return TRUE + * when scrolling happened. */ -static int scroll_with_sms(int dir, long *count) +static int scroll_with_sms(int dir, long count) { int prev_sms = curwin->w_p_sms; colnr_T prev_skipcol = curwin->w_skipcol; @@ -3107,11 +3154,10 @@ static int scroll_with_sms(int dir, long #endif 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; @@ -3122,10 +3168,7 @@ static int scroll_with_sms(int dir, long validate_cursor(); 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; @@ -3149,7 +3192,6 @@ pagescroll(int dir, long count, int 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 }; @@ -3162,34 +3204,45 @@ pagescroll(int dir, long count, int half curwin->w_p_scr = MIN(curwin->w_height, count); count = MIN(curwin->w_height, 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))) + int curscount = count; + // Adjust count so as to not reveal end of buffer lines. + if (dir == FORWARD) { - nochange = scroll_with_sms(dir, &count); - validate_botline(); - // Hide any potentially revealed end of buffer lines. - if (!nochange && curwin->w_botline - 1 == buflen) + int n = plines_correct_topline(curwin, curwin->w_topline, FALSE); + if (n - count < curwin->w_height && curwin->w_topline < buflen) + n += plines_m_win(curwin, curwin->w_topline + 1, buflen, TRUE); + if (n - count < curwin->w_height) + count = n - curwin->w_height; + } + + // Scroll the window and determine number of lines to move the cursor. + if (count > 0) + { + validate_cursor(); + int prev_wrow = curwin->w_wrow; + nochange = scroll_with_sms(dir, count); + if (!nochange) { - curwin->w_cursor.lnum = buflen; - scroll_cursor_bot(0, TRUE); + validate_cursor(); + curscount = abs(prev_wrow - curwin->w_wrow); + dir = prev_wrow > curwin->w_wrow ? FORWARD : BACKWARD; } } - // 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); + int so = get_scrolloff_value(); + // 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 (get_scrolloff_value()) + if (so > 0) cursor_correct(); #ifdef FEAT_FOLDING // Move cursor to first line of closed fold. @@ -3205,16 +3258,15 @@ pagescroll(int dir, long count, int half // 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); + 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); return nochange; diff --git a/src/normal.c b/src/normal.c --- a/src/normal.c +++ b/src/normal.c @@ -2474,65 +2474,6 @@ nv_scroll_line(cmdarg_T *cap) } /* - * Scroll "count" lines up or down, and redraw. - */ - void -scroll_redraw(int up, long count) -{ - linenr_T prev_topline = curwin->w_topline; - int prev_skipcol = curwin->w_skipcol; -#ifdef FEAT_DIFF - int prev_topfill = curwin->w_topfill; -#endif - linenr_T prev_lnum = curwin->w_cursor.lnum; - - if (up) - scrollup(count, TRUE); - else - scrolldown(count, TRUE); - if (get_scrolloff_value() > 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(); - 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 -#ifdef FEAT_DIFF - && curwin->w_topfill == prev_topfill -#endif - ) - { - 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 (curwin->w_cursor.lnum != prev_lnum) - coladvance(curwin->w_curswant); - redraw_later(UPD_VALID); -} - -/* * Get the count specified after a 'z' command. Only the 'z', 'zl', 'zh', * 'z', and 'z' commands accept a count after 'z'. * Returns TRUE to process the 'z' command and FALSE to skip it. diff --git a/src/proto/move.pro b/src/proto/move.pro --- a/src/proto/move.pro +++ b/src/proto/move.pro @@ -37,6 +37,7 @@ void f_screenpos(typval_T *argvars, typv void f_virtcol2col(typval_T *argvars, typval_T *rettv); void scrolldown(long line_count, int byfold); void scrollup(long line_count, int byfold); +void scroll_redraw(int up, long count); void adjust_skipcol(void); void check_topfill(win_T *wp, int down); void scrolldown_clamp(void); diff --git a/src/proto/normal.pro b/src/proto/normal.pro --- a/src/proto/normal.pro +++ b/src/proto/normal.pro @@ -25,7 +25,6 @@ int find_decl(char_u *ptr, int len, int void nv_g_home_m_cmd(cmdarg_T *cap); int nv_screengo(oparg_T *oap, int dir, long dist); void nv_scroll_line(cmdarg_T *cap); -void scroll_redraw(int up, long count); void handle_tabmenu(void); void do_nv_ident(int c1, int c2); int get_visual_text(cmdarg_T *cap, char_u **pp, int *lenp); diff --git a/src/testdir/dumps/Test_smooth_long_6.dump b/src/testdir/dumps/Test_smooth_long_6.dump --- a/src/testdir/dumps/Test_smooth_long_6.dump +++ b/src/testdir/dumps/Test_smooth_long_6.dump @@ -3,4 +3,4 @@ |t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o |f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e |x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w -| @21|3|,|9|0| @9|6@1|%| +|:|s|e|t| |s|c|r|o|l@1|o| @9|3|,|9|0| @9|6@1|%| diff --git a/src/testdir/dumps/Test_smooth_long_7.dump b/src/testdir/dumps/Test_smooth_long_7.dump --- a/src/testdir/dumps/Test_smooth_long_7.dump +++ b/src/testdir/dumps/Test_smooth_long_7.dump @@ -3,4 +3,4 @@ |t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o |f| |t|e|x|t| |w|i>t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e |x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w -| @21|3|,|1|7|0| @8|6@1|%| +|:|s|e|t| |s|c|r|o|l@1|o| @9|3|,|1|7|0| @8|6@1|%| diff --git a/src/testdir/test_diffmode.vim b/src/testdir/test_diffmode.vim --- a/src/testdir/test_diffmode.vim +++ b/src/testdir/test_diffmode.vim @@ -2023,4 +2023,19 @@ func Test_diff_toggle_wrap_skipcol_leftc 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/src/testdir/test_scroll_opt.vim b/src/testdir/test_scroll_opt.vim --- a/src/testdir/test_scroll_opt.vim +++ b/src/testdir/test_scroll_opt.vim @@ -1020,30 +1020,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(735, col('.')) exe "norm! \" call assert_equal(520, winsaveview().skipcol) - call assert_equal(801, 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 @@ -1071,6 +1077,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 diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -705,6 +705,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 280, +/**/ 279, /**/ 278,