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)

cb204e688e
This commit is contained in:
Luuk van Baal 2024-04-02 20:58:49 +02:00
parent 7035125b2b
commit e6cfa22c4c
8 changed files with 189 additions and 76 deletions

View File

@ -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;
}

View File

@ -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;

View File

@ -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);
}
}

View File

@ -19,7 +19,7 @@ describe('matchparen', function()
call cursor(5, 1)
]])
feed('V<c-d><c-d>3j')
feed('V<c-d><c-d>')
screen:expect([[
{17:{} |
{17:}} |

View File

@ -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

View File

@ -1320,7 +1320,7 @@ func Test_edit_PAGEUP_PAGEDOWN()
call feedkeys("A\<PageUp>\<esc>", 'tnix')
call assert_equal([0, 10, 1, 0], getpos('.'))
call feedkeys("A\<PageUp>\<esc>", 'tnix')
call assert_equal([0, 2, 11, 0], getpos('.'))
call assert_equal([0, 10, 11, 0], getpos('.'))
" <S-Up> is the same as <PageUp>
" <S-Down> is the same as <PageDown>
call cursor(1, 1)
@ -1343,7 +1343,7 @@ func Test_edit_PAGEUP_PAGEDOWN()
call feedkeys("A\<S-Up>\<esc>", 'tnix')
call assert_equal([0, 10, 1, 0], getpos('.'))
call feedkeys("A\<S-Up>\<esc>", '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\<PageUp>\<esc>", 'tnix')
call assert_equal([0, 10, 11, 0], getpos('.'))
call feedkeys("A\<PageUp>\<esc>", 'tnix')
call assert_equal([0, 2, 11, 0], getpos('.'))
call assert_equal([0, 10, 11, 0], getpos('.'))
call cursor(1, 1)
call feedkeys("A\<PageDown>\<esc>", 'tnix')
call assert_equal([0, 9, 11, 0], getpos('.'))
@ -1381,7 +1381,7 @@ func Test_edit_PAGEUP_PAGEDOWN()
call feedkeys("A\<S-Up>\<esc>", 'tnix')
call assert_equal([0, 10, 11, 0], getpos('.'))
call feedkeys("A\<S-Up>\<esc>", 'tnix')
call assert_equal([0, 2, 11, 0], getpos('.'))
call assert_equal([0, 10, 11, 0], getpos('.'))
call cursor(1, 1)
call feedkeys("A\<S-Down>\<esc>", 'tnix')
call assert_equal([0, 9, 11, 0], getpos('.'))

View File

@ -1283,13 +1283,9 @@ func Test_vert_scroll_cmds()
exe "normal \<C-D>"
call assert_equal(46, line('.'))
exe "normal \<C-U>"
call assert_equal(36, line('w0'))
call assert_equal(46, line('.'))
call assert_equal(36, line('.'))
exe "normal \<C-U>"
call assert_equal(1, line('w0'))
call assert_equal(40, line('.'))
exe "normal \<C-U>"
call assert_equal(30, line('.'))
call assert_equal(1, line('.'))
exe "normal \<C-U>"
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 \<C-D>"
call assert_equal(1, winline())
call assert_equal(lnum, winline())
call assert_equal(50, line('.'))
normal zt
exe "normal \<C-D>"
@ -3080,8 +3077,7 @@ func Test_normal42_halfpage()
call assert_equal(2, &scroll)
set scroll=5
exe "norm! \<c-u>"
call assert_equal('3', getline('w0'))
call assert_equal('8', getline('.'))
call assert_equal('3', getline('.'))
1
set scrolloff=5
exe "norm! \<c-d>"
@ -3828,7 +3824,7 @@ func Test_normal_vert_scroll_longline()
call assert_equal(1, winline())
exe "normal \<C-B>"
call assert_equal(10, line('.'))
call assert_equal(10, winline())
call assert_equal(4, winline())
exe "normal \<C-B>\<C-B>"
call assert_equal(5, line('.'))
call assert_equal(5, winline())

View File

@ -552,14 +552,14 @@ func Test_smoothscroll_cursor_position()
exe "normal \<C-Y>"
call s:check_col_calc(1, 3, 41)
" Test "g0/g<Home>"
" Test "g0/g<Home>"
exe "normal gg\<C-E>"
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 \<C-E>3lgkh"
exe "normal \<C-E>gkh"
call s:check_col_calc(3, 2, 23)
set virtualedit&
@ -1020,26 +1020,72 @@ func Test_smoothscroll_page()
exe "norm! \<C-B>"
call assert_equal(0, winsaveview().skipcol)
exe "norm! \<C-D>"
" Half-page scrolling does not go beyond end of buffer and moves the cursor.
exe "norm! 0\<C-D>"
call assert_equal(200, winsaveview().skipcol)
call assert_equal(204, col('.'))
exe "norm! \<C-D>"
call assert_equal(400, winsaveview().skipcol)
call assert_equal(404, col('.'))
exe "norm! \<C-D>"
call assert_equal(600, winsaveview().skipcol)
call assert_equal(520, winsaveview().skipcol)
call assert_equal(601, col('.'))
exe "norm! \<C-D>"
call assert_equal(800, winsaveview().skipcol)
exe "norm! \<C-D>"
call assert_equal(880, winsaveview().skipcol)
call assert_equal(520, winsaveview().skipcol)
call assert_equal(801, col('.'))
exe "norm! \<C-U>"
call assert_equal(680, winsaveview().skipcol)
call assert_equal(520, winsaveview().skipcol)
call assert_equal(601, col('.'))
exe "norm! \<C-U>"
call assert_equal(480, winsaveview().skipcol)
call assert_equal(400, winsaveview().skipcol)
call assert_equal(404, col('.'))
exe "norm! \<C-U>"
call assert_equal(280, winsaveview().skipcol)
exe "norm! \<C-U>"
call assert_equal(80, winsaveview().skipcol)
call assert_equal(200, winsaveview().skipcol)
call assert_equal(204, col('.'))
exe "norm! \<C-U>"
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\<C-E>"
call assert_equal(880, winsaveview().skipcol)
exe "norm! \<C-E>"
redraw
call assert_equal(0, winsaveview().skipcol)
" Cursor in correct place when not in the first screenline of a buffer line.
exe "norm! gg4gj20\<C-D>\<C-D>"
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