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.

closes: #14268

Signed-off-by: Luuk van Baal <luukvbaal@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
diff --git a/src/move.c b/src/move.c
index 9ea24c8..772b173 100644
--- a/src/move.c
+++ b/src/move.c
@@ -2047,26 +2047,6 @@
 	}
     }
 }
-
-/*
- * Use as many filler lines as possible for w_topline.  Make sure w_topline
- * is still visible.
- */
-    static void
-max_topfill(void)
-{
-    int		n;
-
-    n = plines_nofill(curwin->w_topline);
-    if (n >= curwin->w_height)
-	curwin->w_topfill = 0;
-    else
-    {
-	curwin->w_topfill = diff_check_fill(curwin, curwin->w_topline);
-	if (curwin->w_topfill + n > curwin->w_height)
-	    curwin->w_topfill = curwin->w_height - n;
-    }
-}
 #endif
 
 /*
@@ -2269,38 +2249,6 @@
     }
 }
 
-#ifdef FEAT_DIFF
-/*
- * 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 = diff_check_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 = diff_check_fill(curwin, lp->lnum) - lp->fill + 1;
-	--lp->lnum;
-    }
-}
-#endif
-
 /*
  * Recompute topline to put the cursor at the top of the window.
  * Scroll at least "min_scroll" lines.
@@ -3077,8 +3025,6 @@
     curwin->w_valid |= VALID_TOPLINE;
 }
 
-static void get_scroll_overlap(lineoff_T *lp, int dir);
-
 /*
  * Move screen "count" pages up ("dir" is BACKWARD) or down ("dir" is FORWARD)
  * and update the screen.
@@ -3088,313 +3034,48 @@
     int
 onepage(int dir, long count)
 {
-    long	n;
-    int		retval = OK;
-    lineoff_T	loff;
-    linenr_T	old_topline = curwin->w_topline;
-    long	so = get_scrolloff_value();
+#ifdef FEAT_DIFF
+    int		prev_topfill = curwin->w_topfill;
+#endif
+    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
-    {
-	beep_flush();
-	return FAIL;
-    }
+    // Scroll 'window' or current window height lines.
+    count *= ((ONE_WINDOW && p_window > 0 && p_window < Rows - 1) ?
+					    p_window : curwin->w_height) - 2;
 
-    for ( ; count > 0; --count)
-    {
-	validate_botline();
-	/*
-	 * 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
-#ifdef FEAT_DIFF
-		    && curwin->w_topfill ==
-				    diff_check_fill(curwin, curwin->w_topline)
-#endif
-		    ))
-	{
-	    beep_flush();
-	    retval = FAIL;
-	    break;
-	}
-
-#ifdef FEAT_DIFF
-	loff.fill = 0;
-#endif
-	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 += 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;
-#ifdef FEAT_DIFF
-		curwin->w_topfill = 0;
-#endif
-		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;
-#ifdef FEAT_DIFF
-		loff.fill = diff_check_fill(curwin, loff.lnum)
-						      - curwin->w_filler_rows;
-#endif
-		get_scroll_overlap(&loff, -1);
-		curwin->w_topline = loff.lnum;
-#ifdef FEAT_DIFF
-		curwin->w_topfill = loff.fill;
-		check_topfill(curwin, FALSE);
-#endif
-		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
-	{
-#ifdef FEAT_DIFF
-	    if (curwin->w_topline == 1)
-	    {
-		// Include max number of filler lines
-		max_topfill();
-		continue;
-	    }
-#endif
-	    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 -= p_window - 2;
-		if (curwin->w_topline < 1)
-		    curwin->w_topline = 1;
-		curwin->w_cursor.lnum = curwin->w_topline + 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;
-#ifdef FEAT_DIFF
-	    loff.fill = diff_check_fill(curwin, loff.lnum + 1)
-							  - curwin->w_topfill;
-#endif
-	    get_scroll_overlap(&loff, 1);
-
-	    if (loff.lnum >= curbuf->b_ml.ml_line_count)
-	    {
-		loff.lnum = curbuf->b_ml.ml_line_count;
-#ifdef FEAT_DIFF
-		loff.fill = 0;
-	    }
-	    else
-	    {
-		botline_topline(&loff);
-#endif
-	    }
-	    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.
-	    n = 0;
-	    while (n <= curwin->w_height && loff.lnum >= 1)
-	    {
-		topline_back(&loff);
-		if (loff.height == MAXCOL)
-		    n = MAXCOL;
-		else
-		    n += loff.height;
-	    }
-	    if (loff.lnum < 1)			// at begin of file
-	    {
-		curwin->w_topline = 1;
-#ifdef FEAT_DIFF
-		max_topfill();
-#endif
-		curwin->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE);
-	    }
-	    else
-	    {
-		// Go two lines forward again.
-#ifdef FEAT_DIFF
-		topline_botline(&loff);
-#endif
-		botline_forw(&loff);
-		botline_forw(&loff);
-#ifdef FEAT_DIFF
-		botline_topline(&loff);
-#endif
-#ifdef FEAT_FOLDING
-		// We're at the wrong end of a fold now.
-		(void)hasFolding(loff.lnum, &loff.lnum, NULL);
-#endif
-
-		// Always scroll at least one line.  Avoid getting stuck on
-		// very long lines.
-		if (loff.lnum >= curwin->w_topline
-#ifdef FEAT_DIFF
-			&& (loff.lnum > curwin->w_topline
-			    || loff.fill >= curwin->w_topfill)
-#endif
-			)
-		{
-#ifdef FEAT_DIFF
-		    // 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 < diff_check_fill(curwin,
-							   curwin->w_topline))
-			max_topfill();
-		    if (curwin->w_topfill == loff.fill)
-#endif
-		    {
-			--curwin->w_topline;
-#ifdef FEAT_DIFF
-			curwin->w_topfill = 0;
-#endif
-			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;
-#ifdef FEAT_DIFF
-		    curwin->w_topfill = loff.fill;
-		    check_topfill(curwin, FALSE);
-#endif
-		    curwin->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE);
-		}
-	    }
-	}
-    }
-#ifdef FEAT_FOLDING
-    foldAdjustCursor();
-#endif
-    cursor_correct();
-    check_cursor_col();
-    if (retval == OK)
-	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(1, FALSE);
-	    if (curwin->w_topline <= old_topline
-				  && old_topline < curbuf->b_ml.ml_line_count)
-	    {
-		curwin->w_topline = old_topline + 1;
-#ifdef FEAT_FOLDING
-		(void)hasFolding(curwin->w_topline, &curwin->w_topline, NULL);
-#endif
-	    }
-	}
-#ifdef FEAT_FOLDING
-	else if (curwin->w_botline > curbuf->b_ml.ml_line_count)
-	    (void)hasFolding(curwin->w_topline, &curwin->w_topline, NULL);
-#endif
-    }
-
-    redraw_later(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		h1, h2, h3, h4;
-    int		min_height = curwin->w_height - 2;
-    lineoff_T	loff0, loff1, loff2;
-
-#ifdef FEAT_DIFF
-    if (lp->fill > 0)
-	lp->height = 1;
+    if (curwin->w_p_sms)
+	scroll_redraw(dir == FORWARD, count);
     else
-	lp->height = plines_nofill(lp->lnum);
+    {
+	// Scroll at least one full line without 'smoothscroll'.
+#ifdef FEAT_DIFF
+	count -= plines_nofill(curwin->w_topline);
 #else
-    lp->height = plines(lp->lnum);
+	count -= plines(curwin->w_topline);
 #endif
-    h1 = lp->height;
-    if (h1 > min_height)
-	return;		// no overlap
+	scroll_redraw(dir == FORWARD, 1);
 
-    loff0 = *lp;
-    if (dir > 0)
-	botline_forw(lp);
-    else
-	topline_back(lp);
-    h2 = lp->height;
-    if (h2 == MAXCOL || h2 + h1 > min_height)
-    {
-	*lp = loff0;	// no overlap
-	return;
+	// 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;
     }
 
-    loff1 = *lp;
-    if (dir > 0)
-	botline_forw(lp);
-    else
-	topline_back(lp);
-    h3 = lp->height;
-    if (h3 == MAXCOL || h3 + h2 > min_height)
-    {
-	*lp = loff0;	// no overlap
-	return;
-    }
+    int nochange = curwin->w_topline == prev_topline
+#ifdef FEAT_DIFF
+	&& curwin->w_topfill == prev_topfill
+#endif
+	&& curwin->w_skipcol == prev_skipcol;
 
-    loff2 = *lp;
-    if (dir > 0)
-	botline_forw(lp);
-    else
-	topline_back(lp);
-    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
+    if (nochange)
+	beep_flush();
+    else if (!curwin->w_p_sms || curwin->w_skipcol == prev_skipcol)
+	beginline(BL_SOL | BL_FIX);
+
+    return nochange;
 }
 
 /*