vim-patch:9.1.0211: page-wise scrolling does not support smooth-scrolling

Problem:  Page-wise scrolling with Ctrl-F/Ctrl-B implements
          it's own logic to change the topline and cursor.
          In doing so, skipcol is not handled properly for
          'smoothscroll', and virtual lines.
Solution: Re-use the logic from Ctrl-E/Ctrl-Y while staying
          backward compatible as much as possible.

b9f5b95b7b
This commit is contained in:
Luuk van Baal 2024-03-26 19:06:39 +01:00
parent 19b443251f
commit 4147302f4b
9 changed files with 123 additions and 325 deletions

View File

@ -5693,8 +5693,8 @@ A jump table for the options with a short description can be found at |Q_op|.
highlighted with |hl-NonText|.
You may also want to add "lastline" to the 'display' option to show as
much of the last line as possible.
NOTE: only partly implemented, currently works with CTRL-E, CTRL-Y
and scrolling with the mouse.
NOTE: only partly implemented, currently works with CTRL-E, CTRL-Y,
CTRL-B, CTRL-F and scrolling with the mouse.
*'softtabstop'* *'sts'*
'softtabstop' 'sts' number (default 0)

View File

@ -6075,8 +6075,8 @@ vim.go.sta = vim.go.smarttab
--- highlighted with `hl-NonText`.
--- You may also want to add "lastline" to the 'display' option to show as
--- much of the last line as possible.
--- NOTE: only partly implemented, currently works with CTRL-E, CTRL-Y
--- and scrolling with the mouse.
--- NOTE: only partly implemented, currently works with CTRL-E, CTRL-Y,
--- CTRL-B, CTRL-F and scrolling with the mouse.
---
--- @type boolean
vim.o.smoothscroll = false

View File

@ -36,6 +36,7 @@
#include "nvim/message.h"
#include "nvim/mouse.h"
#include "nvim/move.h"
#include "nvim/normal.h"
#include "nvim/option.h"
#include "nvim/option_vars.h"
#include "nvim/plines.h"
@ -1552,21 +1553,6 @@ void check_topfill(win_T *wp, bool down)
win_check_anchored_floats(wp);
}
// Use as many filler lines as possible for w_topline. Make sure w_topline
// is still visible.
static void max_topfill(void)
{
int n = plines_win_nofill(curwin, curwin->w_topline, true);
if (n >= curwin->w_height_inner) {
curwin->w_topfill = 0;
} else {
curwin->w_topfill = win_get_fill(curwin, curwin->w_topline);
if (curwin->w_topfill + n > curwin->w_height_inner) {
curwin->w_topfill = curwin->w_height_inner - n;
}
}
}
// Scroll the screen one line down, but don't do it if it would move the
// cursor off the screen.
void scrolldown_clamp(void)
@ -1697,28 +1683,6 @@ static void botline_forw(win_T *wp, lineoff_T *lp)
}
}
// Switch from including filler lines below lp->lnum to including filler
// lines above loff.lnum + 1. This keeps pointing to the same line.
// When there are no filler lines nothing changes.
static void botline_topline(lineoff_T *lp)
{
if (lp->fill > 0) {
lp->lnum++;
lp->fill = win_get_fill(curwin, lp->lnum) - lp->fill + 1;
}
}
// Switch from including filler lines above lp->lnum to including filler
// lines below loff.lnum - 1. This keeps pointing to the same line.
// When there are no filler lines nothing changes.
static void topline_botline(lineoff_T *lp)
{
if (lp->fill > 0) {
lp->fill = win_get_fill(curwin, lp->lnum) - lp->fill + 1;
lp->lnum--;
}
}
// Recompute topline to put the cursor at the top of the window.
// Scroll at least "min_scroll" lines.
// If "always" is true, always set topline (for "zt").
@ -2320,241 +2284,41 @@ void cursor_correct(win_T *wp)
/// @return FAIL for failure, OK otherwise.
int onepage(Direction dir, int count)
{
int retval = OK;
lineoff_T loff;
linenr_T old_topline = curwin->w_topline;
int so = get_scrolloff_value(curwin);
int prev_topfill = curwin->w_topfill;
linenr_T prev_topline = curwin->w_topline;
colnr_T prev_skipcol = curwin->w_skipcol;
if (curbuf->b_ml.ml_line_count == 1) { // nothing to do
// Scroll 'window' or current window height lines.
count *= ((ONE_WINDOW && p_window > 0 && p_window < Rows - 1)
? (int)p_window - 2 : curwin->w_height - 1);
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;
}
int nochange = curwin->w_topline == prev_topline
&& curwin->w_topfill == prev_topfill
&& curwin->w_skipcol == prev_skipcol;
if (nochange) {
beep_flush();
return FAIL;
}
for (; count > 0; count--) {
validate_botline(curwin);
// It's an error to move a page up when the first line is already on
// the screen. It's an error to move a page down when the last line
// is on the screen and the topline is 'scrolloff' lines from the
// last line.
if (dir == FORWARD
? ((curwin->w_topline >= curbuf->b_ml.ml_line_count - so)
&& curwin->w_botline > curbuf->b_ml.ml_line_count)
: (curwin->w_topline == 1
&& curwin->w_topfill == win_get_fill(curwin, curwin->w_topline))) {
beep_flush();
retval = FAIL;
break;
}
loff.fill = 0;
if (dir == FORWARD) {
if (ONE_WINDOW && p_window > 0 && p_window < Rows - 1) {
// Vi compatible scrolling
if (p_window <= 2) {
curwin->w_topline++;
} else {
curwin->w_topline += (linenr_T)p_window - 2;
}
if (curwin->w_topline > curbuf->b_ml.ml_line_count) {
curwin->w_topline = curbuf->b_ml.ml_line_count;
}
curwin->w_cursor.lnum = curwin->w_topline;
} else if (curwin->w_botline > curbuf->b_ml.ml_line_count) {
// at end of file
curwin->w_topline = curbuf->b_ml.ml_line_count;
curwin->w_topfill = 0;
curwin->w_valid &= ~(VALID_WROW|VALID_CROW);
} else {
// For the overlap, start with the line just below the window
// and go upwards.
loff.lnum = curwin->w_botline;
loff.fill = win_get_fill(curwin, loff.lnum)
- curwin->w_filler_rows;
get_scroll_overlap(&loff, -1);
curwin->w_topline = loff.lnum;
curwin->w_topfill = loff.fill;
check_topfill(curwin, false);
curwin->w_cursor.lnum = curwin->w_topline;
curwin->w_valid &= ~(VALID_WCOL|VALID_CHEIGHT|VALID_WROW|
VALID_CROW|VALID_BOTLINE|VALID_BOTLINE_AP);
}
} else { // dir == BACKWARDS
if (curwin->w_topline == 1) {
// Include max number of filler lines
max_topfill();
continue;
}
if (ONE_WINDOW && p_window > 0 && p_window < Rows - 1) {
// Vi compatible scrolling (sort of)
if (p_window <= 2) {
curwin->w_topline--;
} else {
curwin->w_topline -= (linenr_T)p_window - 2;
}
if (curwin->w_topline < 1) {
curwin->w_topline = 1;
}
curwin->w_cursor.lnum = curwin->w_topline + (linenr_T)p_window - 1;
if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) {
curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
}
continue;
}
// Find the line at the top of the window that is going to be the
// line at the bottom of the window. Make sure this results in
// the same line as before doing CTRL-F.
loff.lnum = curwin->w_topline - 1;
loff.fill = win_get_fill(curwin, loff.lnum + 1) - curwin->w_topfill;
get_scroll_overlap(&loff, 1);
if (loff.lnum >= curbuf->b_ml.ml_line_count) {
loff.lnum = curbuf->b_ml.ml_line_count;
loff.fill = 0;
} else {
botline_topline(&loff);
}
curwin->w_cursor.lnum = loff.lnum;
// Find the line just above the new topline to get the right line
// at the bottom of the window.
int n = 0;
while (n <= curwin->w_height_inner && loff.lnum >= 1) {
topline_back(curwin, &loff);
if (loff.height == MAXCOL) {
n = MAXCOL;
} else {
n += loff.height;
}
}
if (loff.lnum < 1) { // at begin of file
curwin->w_topline = 1;
max_topfill();
curwin->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE);
} else {
// Go two lines forward again.
topline_botline(&loff);
botline_forw(curwin, &loff);
botline_forw(curwin, &loff);
botline_topline(&loff);
// We're at the wrong end of a fold now.
hasFolding(curwin, loff.lnum, &loff.lnum, NULL);
// Always scroll at least one line. Avoid getting stuck on
// very long lines.
if (loff.lnum >= curwin->w_topline
&& (loff.lnum > curwin->w_topline
|| loff.fill >= curwin->w_topfill)) {
// First try using the maximum number of filler lines. If
// that's not enough, backup one line.
loff.fill = curwin->w_topfill;
if (curwin->w_topfill < win_get_fill(curwin, curwin->w_topline)) {
max_topfill();
}
if (curwin->w_topfill == loff.fill) {
curwin->w_topline--;
curwin->w_topfill = 0;
curwin->w_valid &= ~(VALID_WROW|VALID_CROW);
}
comp_botline(curwin);
curwin->w_cursor.lnum = curwin->w_botline - 1;
curwin->w_valid &= ~(VALID_WCOL|VALID_CHEIGHT|VALID_WROW|VALID_CROW);
} else {
curwin->w_topline = loff.lnum;
curwin->w_topfill = loff.fill;
check_topfill(curwin, false);
curwin->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE);
}
}
}
}
foldAdjustCursor(curwin);
cursor_correct(curwin);
check_cursor_col(curwin);
if (retval == OK) {
} else if (!curwin->w_p_sms || curwin->w_skipcol == prev_skipcol) {
beginline(BL_SOL | BL_FIX);
}
curwin->w_valid &= ~(VALID_WCOL|VALID_WROW|VALID_VIRTCOL);
if (retval == OK && dir == FORWARD) {
// Avoid the screen jumping up and down when 'scrolloff' is non-zero.
// But make sure we scroll at least one line (happens with mix of long
// wrapping lines and non-wrapping line).
if (check_top_offset()) {
scroll_cursor_top(curwin, 1, false);
if (curwin->w_topline <= old_topline
&& old_topline < curbuf->b_ml.ml_line_count) {
curwin->w_topline = old_topline + 1;
hasFolding(curwin, curwin->w_topline, &curwin->w_topline, NULL);
}
} else if (curwin->w_botline > curbuf->b_ml.ml_line_count) {
hasFolding(curwin, curwin->w_topline, &curwin->w_topline, NULL);
}
}
redraw_later(curwin, UPD_VALID);
return retval;
}
// Decide how much overlap to use for page-up or page-down scrolling.
// This is symmetric, so that doing both keeps the same lines displayed.
// Three lines are examined:
//
// before CTRL-F after CTRL-F / before CTRL-B
// etc. l1
// l1 last but one line ------------
// l2 last text line l2 top text line
// ------------- l3 second text line
// l3 etc.
static void get_scroll_overlap(lineoff_T *lp, int dir)
{
int min_height = curwin->w_height_inner - 2;
if (lp->fill > 0) {
lp->height = 1;
} else {
lp->height = plines_win_nofill(curwin, lp->lnum, true);
}
int h1 = lp->height;
if (h1 > min_height) {
return; // no overlap
}
lineoff_T loff0 = *lp;
if (dir > 0) {
botline_forw(curwin, lp);
} else {
topline_back(curwin, lp);
}
int h2 = lp->height;
if (h2 == MAXCOL || h2 + h1 > min_height) {
*lp = loff0; // no overlap
return;
}
lineoff_T loff1 = *lp;
if (dir > 0) {
botline_forw(curwin, lp);
} else {
topline_back(curwin, lp);
}
int h3 = lp->height;
if (h3 == MAXCOL || h3 + h2 > min_height) {
*lp = loff0; // no overlap
return;
}
lineoff_T loff2 = *lp;
if (dir > 0) {
botline_forw(curwin, lp);
} else {
topline_back(curwin, lp);
}
int h4 = lp->height;
if (h4 == MAXCOL || h4 + h3 + h2 > min_height || h3 + h2 + h1 > min_height) {
*lp = loff1; // 1 line overlap
} else {
*lp = loff2; // 2 lines overlap
}
return nochange;
}
// Scroll 'scroll' lines up or down.

View File

@ -7663,8 +7663,8 @@ return {
highlighted with |hl-NonText|.
You may also want to add "lastline" to the 'display' option to show as
much of the last line as possible.
NOTE: only partly implemented, currently works with CTRL-E, CTRL-Y
and scrolling with the mouse.
NOTE: only partly implemented, currently works with CTRL-E, CTRL-Y,
CTRL-B, CTRL-F and scrolling with the mouse.
]=],
full_name = 'smoothscroll',
pv_name = 'p_sms',

View File

@ -740,7 +740,7 @@ describe('smoothscroll', function()
|
]])
exec("call setline(92, 'a'->repeat(100))")
feed('<C-B>G')
feed('<C-L><C-B>G')
-- cursor is not placed below window
screen:expect([[
{1:<<<}aaaaaaaaaaaaaaaaa |

View File

@ -1632,34 +1632,38 @@ endfunc
func Test_diff_scroll_many_filler()
20new
vnew
call setline(1, ['^^^', '^^^', '$$$', '$$$'])
call setline(1, range(1, 40))
diffthis
setlocal scrolloff=0
wincmd p
call setline(1, ['^^^', '^^^'] + repeat(['###'], 41) + ['$$$', '$$$'])
call setline(1, range(1, 20)->reverse() + ['###']->repeat(41) + range(21, 40)->reverse())
diffthis
setlocal scrolloff=0
wincmd p
redraw
" Note: need a redraw after each scroll, otherwise the test always passes.
normal! G
redraw
call assert_equal(3, winsaveview().topline)
call assert_equal(18, winsaveview().topfill)
exe "normal! \<C-B>"
redraw
call assert_equal(3, winsaveview().topline)
call assert_equal(19, winsaveview().topfill)
exe "normal! \<C-B>"
redraw
call assert_equal(2, winsaveview().topline)
call assert_equal(0, winsaveview().topfill)
exe "normal! \<C-B>"
redraw
call assert_equal(1, winsaveview().topline)
call assert_equal(0, winsaveview().topfill)
for _ in range(2)
normal! G
redraw
call assert_equal(40, winsaveview().topline)
call assert_equal(19, winsaveview().topfill)
exe "normal! \<C-B>"
redraw
call assert_equal(22, winsaveview().topline)
call assert_equal(0, winsaveview().topfill)
exe "normal! \<C-B>"
redraw
call assert_equal(4, winsaveview().topline)
call assert_equal(0, winsaveview().topfill)
exe "normal! \<C-B>"
redraw
call assert_equal(1, winsaveview().topline)
call assert_equal(0, winsaveview().topfill)
set smoothscroll
endfor
set smoothscroll&
bwipe!
bwipe!
endfunc

View File

@ -1312,15 +1312,15 @@ func Test_edit_PAGEUP_PAGEDOWN()
call feedkeys("i\<PageDown>\<esc>", 'tnix')
call assert_equal([0, 30, 1, 0], getpos('.'))
call feedkeys("A\<PageUp>\<esc>", 'tnix')
call assert_equal([0, 29, 1, 0], getpos('.'))
call assert_equal([0, 30, 1, 0], getpos('.'))
call feedkeys("A\<PageUp>\<esc>", 'tnix')
call assert_equal([0, 21, 1, 0], getpos('.'))
call assert_equal([0, 23, 1, 0], getpos('.'))
call feedkeys("A\<PageUp>\<esc>", 'tnix')
call assert_equal([0, 13, 1, 0], getpos('.'))
call assert_equal([0, 15, 1, 0], getpos('.'))
call feedkeys("A\<PageUp>\<esc>", 'tnix')
call assert_equal([0, 5, 1, 0], getpos('.'))
call assert_equal([0, 10, 1, 0], getpos('.'))
call feedkeys("A\<PageUp>\<esc>", 'tnix')
call assert_equal([0, 5, 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)
@ -1335,28 +1335,28 @@ func Test_edit_PAGEUP_PAGEDOWN()
call feedkeys("i\<S-Down>\<esc>", 'tnix')
call assert_equal([0, 30, 1, 0], getpos('.'))
call feedkeys("A\<S-Up>\<esc>", 'tnix')
call assert_equal([0, 29, 1, 0], getpos('.'))
call assert_equal([0, 30, 1, 0], getpos('.'))
call feedkeys("A\<S-Up>\<esc>", 'tnix')
call assert_equal([0, 21, 1, 0], getpos('.'))
call assert_equal([0, 23, 1, 0], getpos('.'))
call feedkeys("A\<S-Up>\<esc>", 'tnix')
call assert_equal([0, 13, 1, 0], getpos('.'))
call assert_equal([0, 15, 1, 0], getpos('.'))
call feedkeys("A\<S-Up>\<esc>", 'tnix')
call assert_equal([0, 5, 1, 0], getpos('.'))
call assert_equal([0, 10, 1, 0], getpos('.'))
call feedkeys("A\<S-Up>\<esc>", 'tnix')
call assert_equal([0, 5, 11, 0], getpos('.'))
call assert_equal([0, 10, 11, 0], getpos('.'))
set nostartofline
call cursor(30, 11)
norm! zt
call feedkeys("A\<PageUp>\<esc>", 'tnix')
call assert_equal([0, 29, 11, 0], getpos('.'))
call assert_equal([0, 30, 11, 0], getpos('.'))
call feedkeys("A\<PageUp>\<esc>", 'tnix')
call assert_equal([0, 21, 11, 0], getpos('.'))
call assert_equal([0, 23, 11, 0], getpos('.'))
call feedkeys("A\<PageUp>\<esc>", 'tnix')
call assert_equal([0, 13, 11, 0], getpos('.'))
call assert_equal([0, 15, 11, 0], getpos('.'))
call feedkeys("A\<PageUp>\<esc>", 'tnix')
call assert_equal([0, 5, 11, 0], getpos('.'))
call assert_equal([0, 10, 11, 0], getpos('.'))
call feedkeys("A\<PageUp>\<esc>", 'tnix')
call assert_equal([0, 5, 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('.'))
@ -1373,15 +1373,15 @@ func Test_edit_PAGEUP_PAGEDOWN()
call cursor(30, 11)
norm! zt
call feedkeys("A\<S-Up>\<esc>", 'tnix')
call assert_equal([0, 29, 11, 0], getpos('.'))
call assert_equal([0, 30, 11, 0], getpos('.'))
call feedkeys("A\<S-Up>\<esc>", 'tnix')
call assert_equal([0, 21, 11, 0], getpos('.'))
call assert_equal([0, 23, 11, 0], getpos('.'))
call feedkeys("A\<S-Up>\<esc>", 'tnix')
call assert_equal([0, 13, 11, 0], getpos('.'))
call assert_equal([0, 15, 11, 0], getpos('.'))
call feedkeys("A\<S-Up>\<esc>", 'tnix')
call assert_equal([0, 5, 11, 0], getpos('.'))
call assert_equal([0, 10, 11, 0], getpos('.'))
call feedkeys("A\<S-Up>\<esc>", 'tnix')
call assert_equal([0, 5, 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

@ -131,7 +131,7 @@ func Test_normal01_keymodel()
call assert_equal([0, 1, 1, 0], getpos("'<"))
call assert_equal([0, 3, 1, 0], getpos("'>"))
call feedkeys("Gz\<CR>8|\<S-PageUp>y", 'xt')
call assert_equal([0, 2, 1, 0], getpos("'<"))
call assert_equal([0, 3, 1, 0], getpos("'<"))
call assert_equal([0, 3, 8, 0], getpos("'>"))
" Test for <S-C-Home> and <S-C-End>
call cursor(2, 12)
@ -915,12 +915,10 @@ func Test_normal14_page()
set scrolloff=0
100
exe "norm! $\<c-b>"
call assert_equal('92', getline('.'))
call assert_equal([0, 92, 1, 0, 1], getcurpos())
100
set nostartofline
exe "norm! $\<c-b>"
call assert_equal('92', getline('.'))
call assert_equal([0, 92, 2, 0, v:maxcol], getcurpos())
" cleanup
set startofline
@ -3825,11 +3823,11 @@ func Test_normal_vert_scroll_longline()
call assert_equal(11, line('.'))
call assert_equal(1, winline())
exe "normal \<C-B>"
call assert_equal(10, line('.'))
call assert_equal(3, winline())
call assert_equal(11, line('.'))
call assert_equal(9, winline())
exe "normal \<C-B>\<C-B>"
call assert_equal(5, line('.'))
call assert_equal(5, winline())
call assert_equal(1, winline())
bwipe!
endfunc
@ -4182,20 +4180,30 @@ func Test_normal34_zet_large()
norm! z9765405999999999999
endfunc
" Test for { and } paragraph movements in a single line
func Test_brace_single_line()
let text =<< trim [DATA]
foobar one two three
[DATA]
" Test for { and } paragraph movements and Ctrl-B in buffer with a single line
func Test_single_line_scroll()
CheckFeature textprop
new
call setline(1, text)
call setline(1, ['foobar one two three'])
let vt = 'virt_above'
call prop_type_add(vt, {'highlight': 'IncSearch'})
call prop_add(1, 0, {'type': vt, 'text': '---', 'text_align': 'above'})
1
norm! 0}
call assert_equal([0, 1, 20, 0], getpos('.'))
norm! {
call assert_equal([0, 1, 1, 0], getpos('.'))
" Ctrl-B scrolls up with hidden "above" virtual text.
set smoothscroll
exe "normal \<C-E>"
call assert_notequal(0, winsaveview().skipcol)
exe "normal \<C-B>"
call assert_equal(0, winsaveview().skipcol)
set smoothscroll&
bw!
endfunc

View File

@ -832,7 +832,7 @@ func Test_smoothscroll_eob()
call VerifyScreenDump(buf, 'Test_smooth_eob_1', {})
" cursor is not placed below window
call term_sendkeys(buf, ":call setline(92, 'a'->repeat(100))\<CR>\<C-B>G")
call term_sendkeys(buf, ":call setline(92, 'a'->repeat(100))\<CR>\<C-L>\<C-B>G")
call VerifyScreenDump(buf, 'Test_smooth_eob_2', {})
call StopVimInTerminal(buf)
@ -1001,4 +1001,26 @@ func Test_smoothscroll_textoff_small_winwidth()
set smoothscroll& number&
endfunc
func Test_smoothscroll_page()
set smoothscroll
10split | 40vsplit
call setline(1, 'abcde '->repeat(150))
exe "norm! \<C-F>"
call assert_equal(320, winsaveview().skipcol)
exe "norm! \<C-F>"
call assert_equal(640, winsaveview().skipcol)
exe "norm! \<C-F>"
call assert_equal(880, winsaveview().skipcol)
exe "norm! \<C-B>"
call assert_equal(560, winsaveview().skipcol)
exe "norm! \<C-B>"
call assert_equal(240, winsaveview().skipcol)
exe "norm! \<C-B>"
call assert_equal(0, winsaveview().skipcol)
set smoothscroll&
endfunc
" vim: shiftwidth=2 sts=2 expandtab