changeset 30610:6c6ac189a05f v9.0.0640

patch 9.0.0640: cannot scroll by screen line if a line wraps Commit: https://github.com/vim/vim/commit/f6196f424474e2a9c160f2a995fc2691f82b58f9 Author: Bram Moolenaar <Bram@vim.org> Date: Sun Oct 2 21:29:55 2022 +0100 patch 9.0.0640: cannot scroll by screen line if a line wraps Problem: Cannot scroll by screen line if a line wraps. Solution: Add the 'smoothscroll' option. Only works for CTRL-E and CTRL-Y so far.
author Bram Moolenaar <Bram@vim.org>
date Sun, 02 Oct 2022 22:30:15 +0200
parents 0473728b3784
children 621f1de0ecb7
files runtime/doc/options.txt runtime/doc/quickref.txt runtime/optwin.vim src/drawline.c src/move.c src/option.c src/optiondefs.h src/structs.h src/testdir/dumps/Test_smoothscroll_1.dump src/testdir/dumps/Test_smoothscroll_2.dump src/testdir/dumps/Test_smoothscroll_3.dump src/testdir/dumps/Test_smoothscroll_4.dump src/testdir/dumps/Test_smoothscroll_5.dump src/testdir/dumps/Test_smoothscroll_6.dump src/testdir/dumps/Test_smoothscroll_7.dump src/testdir/dumps/Test_smoothscroll_8.dump src/testdir/test_scroll_opt.vim src/version.c
diffstat 18 files changed, 313 insertions(+), 30 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -7302,6 +7302,14 @@ A jump table for the options with a shor
 	reset.
 	NOTE: This option is reset when 'compatible' is set.
 
+			*'smoothscroll'* *'sms'* *'nosmoothscroll'* *'nosms'*
+'smoothscroll' 'sms'	boolean  (default off)
+			local to window
+	Scrolling works with screen lines.  When 'wrap' is set and the first
+	line in the window wraps part of it may not be visible, as if it is
+	above the window.
+	NOTE: only partly implemented, works with CTRL-E and CTRL-Y.
+
 					*'softtabstop'* *'sts'*
 'softtabstop' 'sts'	number	(default 0)
 			local to buffer
--- a/runtime/doc/quickref.txt
+++ b/runtime/doc/quickref.txt
@@ -910,6 +910,7 @@ Short explanation of each option:		*opti
 'smartcase'	  'scs'     no ignore case when pattern has uppercase
 'smartindent'	  'si'	    smart autoindenting for C programs
 'smarttab'	  'sta'     use 'shiftwidth' when inserting <Tab>
+'smoothscroll'	  'sms'     scroll by screen lines when 'wrap' is set
 'softtabstop'	  'sts'     number of spaces that <Tab> uses while editing
 'spell'			    enable spell checking
 'spellcapcheck'   'spc'     pattern to locate end of a sentence
--- a/runtime/optwin.vim
+++ b/runtime/optwin.vim
@@ -343,6 +343,9 @@ call <SID>Header(gettext("displaying tex
 call <SID>AddOption("scroll", gettext("number of lines to scroll for CTRL-U and CTRL-D"))
 call append("$", "\t" .. s:local_to_window)
 call <SID>OptionL("scr")
+call <SID>AddOption("smoothscroll", gettext("scroll by screen line"))
+call append("$", "\t" .. s:local_to_window)
+call <SID>BinOptionL("sms")
 call <SID>AddOption("scrolloff", gettext("number of screen lines to show around the cursor"))
 call append("$", " \tset so=" . &so)
 call <SID>AddOption("wrap", gettext("long lines wrap"))
--- a/src/drawline.c
+++ b/src/drawline.c
@@ -387,7 +387,7 @@ handle_lnum_col(
 	      }
 
 	      sprintf((char *)wlv->extra, fmt, number_width(wp), num);
-	      if (wp->w_skipcol > 0)
+	      if (wp->w_skipcol > 0 && wlv->startrow == 0)
 		  for (wlv->p_extra = wlv->extra; *wlv->p_extra == ' ';
 			  ++wlv->p_extra)
 		      *wlv->p_extra = '-';
@@ -492,7 +492,8 @@ handle_breakindent(win_T *wp, winlinevar
 		if (wlv->n_extra < 0)
 		    wlv->n_extra = 0;
 	    }
-	    if (wp->w_skipcol > 0 && wp->w_p_wrap && wp->w_briopt_sbr)
+	    if (wp->w_skipcol > 0 && wlv->startrow == 0
+					   && wp->w_p_wrap && wp->w_briopt_sbr)
 		wlv->need_showbreak = FALSE;
 	    // Correct end of highlighted area for 'breakindent',
 	    // required when 'linebreak' is also set.
@@ -540,7 +541,7 @@ handle_showbreak_and_filler(win_T *wp, w
 	wlv->c_extra = NUL;
 	wlv->c_final = NUL;
 	wlv->n_extra = (int)STRLEN(sbr);
-	if (wp->w_skipcol == 0 || !wp->w_p_wrap)
+	if ((wp->w_skipcol == 0 && wlv->startrow == 0) || !wp->w_p_wrap)
 	    wlv->need_showbreak = FALSE;
 	wlv->vcol_sbr = wlv->vcol + MB_CHARLEN(sbr);
 	// Correct end of highlighted area for 'showbreak',
@@ -750,7 +751,7 @@ draw_screen_line(win_T *wp, winlinevars_
 
     // Highlight 'cursorcolumn' & 'colorcolumn' past end of the line.
     if (wp->w_p_wrap)
-	v = wp->w_skipcol;
+	v = wlv->startrow == 0 ? wp->w_skipcol : 0;
     else
 	v = wp->w_leftcol;
 
@@ -1411,7 +1412,7 @@ win_line(
     // 'nowrap' or 'wrap' and a single line that doesn't fit: Advance to the
     // first character to be displayed.
     if (wp->w_p_wrap)
-	v = wp->w_skipcol;
+	v = startrow == 0 ? wp->w_skipcol : 0;
     else
 	v = wp->w_leftcol;
     if (v > 0 && !number_only)
@@ -3219,9 +3220,8 @@ win_line(
 	// special character (via 'listchars' option "precedes:<char>".
 	if (lcs_prec_todo != NUL
 		&& wp->w_p_list
-		&& (wp->w_p_wrap ?
-		    (wp->w_skipcol > 0  && wlv.row == 0) :
-		    wp->w_leftcol > 0)
+		&& (wp->w_p_wrap ? (wp->w_skipcol > 0 && wlv.row == 0)
+				 : wp->w_leftcol > 0)
 #ifdef FEAT_DIFF
 		&& wlv.filler_todo <= 0
 #endif
--- a/src/move.c
+++ b/src/move.c
@@ -36,6 +36,32 @@ static void topline_back(lineoff_T *lp);
 static void botline_forw(lineoff_T *lp);
 
 /*
+ * Reduce "n" for the screen lines skipped with "wp->w_skipcol".
+ */
+    static int
+adjust_plines_for_skipcol(win_T *wp, int n)
+{
+    if (wp->w_skipcol == 0)
+	return n;
+
+    int off = 0;
+    int width = wp->w_width - win_col_off(wp);
+    if (wp->w_skipcol >= width)
+    {
+	++off;
+	int skip = wp->w_skipcol - width;
+	width -= win_col_off2(wp);
+	while (skip >= width)
+	{
+	    ++off;
+	    skip -= width;
+	}
+    }
+    wp->w_valid &= ~VALID_WROW;
+    return n - off;
+}
+
+/*
  * Compute wp->w_botline for the current wp->w_topline.  Can be called after
  * wp->w_topline changed.
  */
@@ -78,12 +104,16 @@ comp_botline(win_T *wp)
 	}
 	else
 #endif
+	{
 #ifdef FEAT_DIFF
 	    if (lnum == wp->w_topline)
 		n = plines_win_nofill(wp, lnum, TRUE) + wp->w_topfill;
 	    else
 #endif
 		n = plines_win(wp, lnum, TRUE);
+	    if (lnum == wp->w_topline)
+		n = adjust_plines_for_skipcol(wp, n);
+	}
 	if (
 #ifdef FEAT_FOLDING
 		lnum <= wp->w_cursor.lnum && last >= wp->w_cursor.lnum
@@ -778,13 +808,18 @@ curs_rows(win_T *wp)
 	    }
 	    else
 #endif
+	    {
+		int n;
 #ifdef FEAT_DIFF
 		if (lnum == wp->w_topline)
-		    wp->w_cline_row += plines_win_nofill(wp, lnum++, TRUE)
-							      + wp->w_topfill;
+		    n = plines_win_nofill(wp, lnum, TRUE) + wp->w_topfill;
 		else
 #endif
-		    wp->w_cline_row += plines_win(wp, lnum++, TRUE);
+		    n = plines_win(wp, lnum, TRUE);
+		if (lnum++ == wp->w_topline)
+		    n = adjust_plines_for_skipcol(wp, n);
+		wp->w_cline_row += n;
+	    }
 	}
     }
 
@@ -1237,7 +1272,7 @@ curs_columns(
 	else if (extra < 0)
 	    win_del_lines(curwin, 0, -extra, FALSE, FALSE, 0);
     }
-    else
+    else if (!curwin->w_p_sms)
 	curwin->w_skipcol = 0;
     if (prev_skipcol != curwin->w_skipcol)
 	redraw_later(UPD_NOT_VALID);
@@ -1422,6 +1457,14 @@ scrolldown(
     long	done = 0;	// total # of physical lines done
     int		wrow;
     int		moved = FALSE;
+    int		width1 = 0;
+    int		width2 = 0;
+
+    if (curwin->w_p_wrap && curwin->w_p_sms)
+    {
+	width1 = curwin->w_width - curwin_col_off();
+	width2 = width1 - curwin_col_off2();
+    }
 
 #ifdef FEAT_FOLDING
     linenr_T	first;
@@ -1442,25 +1485,57 @@ scrolldown(
 	else
 #endif
 	{
-	    if (curwin->w_topline == 1)
+	    if (curwin->w_topline == 1 && curwin->w_skipcol < width1)
 		break;
-	    --curwin->w_topline;
+	    if (curwin->w_p_wrap && curwin->w_p_sms
+						  && curwin->w_skipcol >= width1)
+	    {
+		if (curwin->w_skipcol >= width1 + width2)
+		    curwin->w_skipcol -= width2;
+		else
+		    curwin->w_skipcol -= width1;
+		redraw_later(UPD_NOT_VALID);
+		++done;
+	    }
+	    else
+	    {
+		--curwin->w_topline;
+		curwin->w_skipcol = 0;
 #ifdef FEAT_DIFF
-	    curwin->w_topfill = 0;
+		curwin->w_topfill = 0;
 #endif
 #ifdef FEAT_FOLDING
-	    // A sequence of folded lines only counts for one logical line
-	    if (hasFolding(curwin->w_topline, &first, NULL))
-	    {
-		++done;
-		if (!byfold)
-		    line_count -= curwin->w_topline - first - 1;
-		curwin->w_botline -= curwin->w_topline - first;
-		curwin->w_topline = first;
+		// A sequence of folded lines only counts for one logical line
+		if (hasFolding(curwin->w_topline, &first, NULL))
+		{
+		    ++done;
+		    if (!byfold)
+			line_count -= curwin->w_topline - first - 1;
+		    curwin->w_botline -= curwin->w_topline - first;
+		    curwin->w_topline = first;
+		}
+		else
+#endif
+		if (curwin->w_p_wrap && curwin->w_p_sms)
+		{
+		    int size = win_linetabsize(curwin, curwin->w_topline,
+				   ml_get(curwin->w_topline), (colnr_T)MAXCOL);
+		    if (size > width1)
+		    {
+			curwin->w_skipcol = width1;
+			size -= width1;
+			redraw_later(UPD_NOT_VALID);
+		    }
+		    while (size > width2)
+		    {
+			curwin->w_skipcol += width2;
+			size -= width2;
+		    }
+		    ++done;
+		}
+		else
+		    done += PLINES_NOFILL(curwin->w_topline);
 	    }
-	    else
-#endif
-		done += PLINES_NOFILL(curwin->w_topline);
 	}
 	--curwin->w_botline;		// approximate w_botline
 	invalidate_botline();
@@ -1565,6 +1640,41 @@ scrollup(
     }
     else
 #endif
+    if (curwin->w_p_wrap && curwin->w_p_sms)
+    {
+	int off1 = curwin_col_off();
+	int off2 = off1 + curwin_col_off2();
+	int add;
+	int size = win_linetabsize(curwin, curwin->w_topline,
+				   ml_get(curwin->w_topline), (colnr_T)MAXCOL);
+	linenr_T prev_topline = curwin->w_topline;
+
+	// 'smoothscroll': increase "w_skipcol" until it goes over the end of
+	// the line, then advance to the next line.
+	for (int todo = line_count; todo > 0; --todo)
+	{
+	    add = curwin->w_width - (curwin->w_skipcol > 0 ? off2 : off1);
+	    curwin->w_skipcol += add;
+	    if (curwin->w_skipcol >= size)
+	    {
+		if (curwin->w_topline == curbuf->b_ml.ml_line_count)
+		{
+		    curwin->w_skipcol -= add;
+		    break;
+		}
+		++curwin->w_topline;
+		++curwin->w_botline;	// approximate w_botline
+		curwin->w_skipcol = 0;
+		if (todo > 1)
+		    size = win_linetabsize(curwin, curwin->w_topline,
+				   ml_get(curwin->w_topline), (colnr_T)MAXCOL);
+	    }
+	}
+	if (curwin->w_topline == prev_topline)
+	    // need to redraw even though w_topline didn't change
+	    redraw_later(UPD_NOT_VALID);
+    }
+    else
     {
 	curwin->w_topline += line_count;
 	curwin->w_botline += line_count;	// approximate w_botline
--- a/src/option.c
+++ b/src/option.c
@@ -2964,6 +2964,15 @@ set_bool_option(
     }
 #endif
 
+    else if ((int *)varp == &curwin->w_p_sms)
+    {
+	if (!curwin->w_p_sms)
+	{
+	    curwin->w_skipcol = 0;
+	    changed_line_abv_curs();
+	}
+    }
+
     // when 'textmode' is set or reset also change 'fileformat'
     else if ((int *)varp == &curbuf->b_p_tx)
     {
@@ -5436,6 +5445,7 @@ get_varp(struct vimoption *p)
 	case PV_RLC:	return (char_u *)&(curwin->w_p_rlc);
 #endif
 	case PV_SCROLL:	return (char_u *)&(curwin->w_p_scr);
+	case PV_SMS:	return (char_u *)&(curwin->w_p_sms);
 	case PV_WRAP:	return (char_u *)&(curwin->w_p_wrap);
 #ifdef FEAT_LINEBREAK
 	case PV_LBR:	return (char_u *)&(curwin->w_p_lbr);
--- a/src/optiondefs.h
+++ b/src/optiondefs.h
@@ -194,6 +194,7 @@
 #endif
 #define PV_SCBIND	OPT_WIN(WV_SCBIND)
 #define PV_SCROLL	OPT_WIN(WV_SCROLL)
+#define PV_SMS		OPT_WIN(WV_SMS)
 #define PV_SISO		OPT_BOTH(OPT_WIN(WV_SISO))
 #define PV_SO		OPT_BOTH(OPT_WIN(WV_SO))
 #ifdef FEAT_SPELL
@@ -2282,6 +2283,9 @@ static struct vimoption options[] =
     {"smarttab",    "sta",  P_BOOL|P_VI_DEF|P_VIM,
 			    (char_u *)&p_sta, PV_NONE,
 			    {(char_u *)FALSE, (char_u *)0L} SCTX_INIT},
+    {"smoothscroll", "sms", P_BOOL|P_VI_DEF|P_RWIN,
+			    (char_u *)VAR_WIN, PV_SMS,
+			    {(char_u *)FALSE, (char_u *)0L} SCTX_INIT},
     {"softtabstop", "sts",  P_NUM|P_VI_DEF|P_VIM,
 			    (char_u *)&p_sts, PV_STS,
 			    {(char_u *)0L, (char_u *)0L} SCTX_INIT},
--- a/src/structs.h
+++ b/src/structs.h
@@ -262,6 +262,8 @@ typedef struct
 #endif
     long	wo_scr;
 #define w_p_scr w_onebuf_opt.wo_scr	// 'scroll'
+    int		wo_sms;
+#define w_p_sms w_onebuf_opt.wo_sms	// 'smoothscroll'
 #ifdef FEAT_SPELL
     int		wo_spell;
 # define w_p_spell w_onebuf_opt.wo_spell // 'spell'
@@ -3592,11 +3594,12 @@ struct window_S
 				    // below w_topline (at end of file)
     int		w_old_botfill;	    // w_botfill at last redraw
 #endif
-    colnr_T	w_leftcol;	    // window column number of the left most
+    colnr_T	w_leftcol;	    // screen column number of the left most
 				    // character in the window; used when
 				    // 'wrap' is off
-    colnr_T	w_skipcol;	    // starting column when a single line
-				    // doesn't fit in the window
+    colnr_T	w_skipcol;	    // starting screen column for the first
+				    // line in the window; used when 'wrap' is
+				    // on
 
     int		w_empty_rows;	    // number of ~ rows in window
 #ifdef FEAT_DIFF
new file mode 100644
--- /dev/null
+++ b/src/testdir/dumps/Test_smoothscroll_1.dump
@@ -0,0 +1,12 @@
+|w+0&#ffffff0|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| 
+|w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| 
+|w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| @20
+|l|i|n|e| |t|h|r|e@1| @29
+|l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| 
+|l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| @10
+>l|i|n|e| @35
+|l|i|n|e| @35
+|l|i|n|e| @35
+|~+0#4040ff13&| @38
+|~| @38
+| +0#0000000&@21|5|,|1| @10|B|o|t| 
new file mode 100644
--- /dev/null
+++ b/src/testdir/dumps/Test_smoothscroll_2.dump
@@ -0,0 +1,12 @@
+|w+0&#ffffff0|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| 
+|w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| @20
+|l|i|n|e| |t|h|r|e@1| @29
+|l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| 
+|l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| @10
+>l|i|n|e| @35
+|l|i|n|e| @35
+|l|i|n|e| @35
+|~+0#4040ff13&| @38
+|~| @38
+|~| @38
+| +0#0000000&@21|5|,|1| @10|B|o|t| 
new file mode 100644
--- /dev/null
+++ b/src/testdir/dumps/Test_smoothscroll_3.dump
@@ -0,0 +1,12 @@
+|w+0&#ffffff0|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| @20
+|l|i|n|e| |t|h|r|e@1| @29
+|l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| 
+|l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| @10
+>l|i|n|e| @35
+|l|i|n|e| @35
+|l|i|n|e| @35
+|~+0#4040ff13&| @38
+|~| @38
+|~| @38
+|~| @38
+| +0#0000000&@21|5|,|1| @10|B|o|t| 
new file mode 100644
--- /dev/null
+++ b/src/testdir/dumps/Test_smoothscroll_4.dump
@@ -0,0 +1,12 @@
+|l+0&#ffffff0|i|n|e| |t|h|r|e@1| @29
+|l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| 
+|l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| @10
+|l|i|n|e| @35
+|l|i|n|e| @35
+>l|i|n|e| @35
+|~+0#4040ff13&| @38
+|~| @38
+|~| @38
+|~| @38
+|~| @38
+| +0#0000000&@21|7|,|1| @10|B|o|t| 
new file mode 100644
--- /dev/null
+++ b/src/testdir/dumps/Test_smoothscroll_5.dump
@@ -0,0 +1,12 @@
+|w+0&#ffffff0|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| @20
+|l|i|n|e| |t|h|r|e@1| @29
+|l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| 
+|l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| @10
+|l|i|n|e| @35
+|l|i|n|e| @35
+>l|i|n|e| @35
+|~+0#4040ff13&| @38
+|~| @38
+|~| @38
+|~| @38
+| +0#0000000&@21|7|,|1| @10|B|o|t| 
new file mode 100644
--- /dev/null
+++ b/src/testdir/dumps/Test_smoothscroll_6.dump
@@ -0,0 +1,12 @@
+|w+0&#ffffff0|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| 
+|w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| @20
+|l|i|n|e| |t|h|r|e@1| @29
+|l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| 
+|l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| @10
+|l|i|n|e| @35
+|l|i|n|e| @35
+>l|i|n|e| @35
+|~+0#4040ff13&| @38
+|~| @38
+|~| @38
+| +0#0000000&@21|7|,|1| @10|B|o|t| 
new file mode 100644
--- /dev/null
+++ b/src/testdir/dumps/Test_smoothscroll_7.dump
@@ -0,0 +1,12 @@
+|w+0&#ffffff0|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| 
+|w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| 
+|w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| @20
+|l|i|n|e| |t|h|r|e@1| @29
+|l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| 
+|l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| @10
+|l|i|n|e| @35
+|l|i|n|e| @35
+>l|i|n|e| @35
+|~+0#4040ff13&| @38
+|~| @38
+| +0#0000000&@21|7|,|1| @10|B|o|t| 
new file mode 100644
--- /dev/null
+++ b/src/testdir/dumps/Test_smoothscroll_8.dump
@@ -0,0 +1,12 @@
+|l+0&#ffffff0|i|n|e| |o|n|e| @31
+|w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| 
+|w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| 
+|w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| @20
+|l|i|n|e| |t|h|r|e@1| @29
+|l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| 
+|l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| @10
+|l|i|n|e| @35
+|l|i|n|e| @35
+>l|i|n|e| @35
+|~+0#4040ff13&| @38
+| +0#0000000&@21|7|,|1| @10|A|l@1| 
--- a/src/testdir/test_scroll_opt.vim
+++ b/src/testdir/test_scroll_opt.vim
@@ -1,4 +1,7 @@
-" Test for reset 'scroll'
+" Test for reset 'scroll' and 'smoothscroll'
+
+source check.vim
+source screendump.vim
 
 func Test_reset_scroll()
   let scr = &l:scroll
@@ -34,4 +37,47 @@ func Test_reset_scroll()
   quit!
 endfunc
 
+func Test_smoothscroll_CtrlE_CtrlY()
+  CheckScreendump
+
+  let lines =<< trim END
+      vim9script
+      setline(1, [
+        'line one',
+        'word '->repeat(20),
+        'line three',
+        'long word '->repeat(7),
+        'line',
+        'line',
+        'line',
+      ])
+      set smoothscroll
+      :5
+  END
+  call writefile(lines, 'XSmoothScroll', 'D')
+  let buf = RunVimInTerminal('-S XSmoothScroll', #{rows: 12, cols: 40})
+
+  call term_sendkeys(buf, "\<C-E>")
+  call VerifyScreenDump(buf, 'Test_smoothscroll_1', {})
+  call term_sendkeys(buf, "\<C-E>")
+  call VerifyScreenDump(buf, 'Test_smoothscroll_2', {})
+  call term_sendkeys(buf, "\<C-E>")
+  call VerifyScreenDump(buf, 'Test_smoothscroll_3', {})
+  call term_sendkeys(buf, "\<C-E>")
+  call VerifyScreenDump(buf, 'Test_smoothscroll_4', {})
+
+  call term_sendkeys(buf, "\<C-Y>")
+  call VerifyScreenDump(buf, 'Test_smoothscroll_5', {})
+  call term_sendkeys(buf, "\<C-Y>")
+  call VerifyScreenDump(buf, 'Test_smoothscroll_6', {})
+  call term_sendkeys(buf, "\<C-Y>")
+  call VerifyScreenDump(buf, 'Test_smoothscroll_7', {})
+  call term_sendkeys(buf, "\<C-Y>")
+  call VerifyScreenDump(buf, 'Test_smoothscroll_8', {})
+
+  call StopVimInTerminal(buf)
+endfunc
+
+
+
 " vim: shiftwidth=2 sts=2 expandtab
--- a/src/version.c
+++ b/src/version.c
@@ -700,6 +700,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    640,
+/**/
     639,
 /**/
     638,