changeset 30624:f2f35161d75a v9.0.0647

patch 9.0.0647: the 'splitscroll' option is not a good name Commit: https://github.com/vim/vim/commit/13ece2ae1d09009d3fb8acf858c288e7848ecdac Author: Luuk van Baal <luukvbaal@gmail.com> Date: Mon Oct 3 15:28:08 2022 +0100 patch 9.0.0647: the 'splitscroll' option is not a good name Problem: The 'splitscroll' option is not a good name. Solution: Rename 'splitscroll' to 'splitkeep' and make it a string option, also supporting "topline". (Luuk van Baal, closes #11258)
author Bram Moolenaar <Bram@vim.org>
date Mon, 03 Oct 2022 16:30:04 +0200
parents 22b985817601
children 33ed996584e5
files runtime/doc/options.txt runtime/doc/quickref.txt runtime/optwin.vim src/globals.h src/move.c src/option.h src/optiondefs.h src/optionstr.c src/structs.h src/testdir/dumps/Test_nosplitscroll_callback_1.dump src/testdir/dumps/Test_nosplitscroll_callback_2.dump src/testdir/dumps/Test_nosplitscroll_callback_3.dump src/testdir/dumps/Test_nosplitscroll_callback_4.dump src/testdir/dumps/Test_nosplitscroll_fold_1.dump src/testdir/dumps/Test_nosplitscroll_fold_2.dump src/testdir/dumps/Test_nosplitscroll_fold_3.dump src/testdir/dumps/Test_nosplitscroll_fold_4.dump src/testdir/dumps/Test_splitkeep_callback_1.dump src/testdir/dumps/Test_splitkeep_callback_2.dump src/testdir/dumps/Test_splitkeep_callback_3.dump src/testdir/dumps/Test_splitkeep_callback_4.dump src/testdir/dumps/Test_splitkeep_fold_1.dump src/testdir/dumps/Test_splitkeep_fold_2.dump src/testdir/dumps/Test_splitkeep_fold_3.dump src/testdir/dumps/Test_splitkeep_fold_4.dump src/testdir/gen_opt_test.vim src/testdir/test_window_cmd.vim src/version.c src/window.c
diffstat 29 files changed, 214 insertions(+), 202 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -7518,24 +7518,28 @@ A jump table for the options with a shor
 	When on, splitting a window will put the new window below the current
 	one. |:split|
 
+			*'splitkeep'* *'spk'
+'splitkeep' 'spk'	string	(default "cursor")
+			global
+	The value of this option determines the scroll behavior when opening,
+	closing or resizing horizontal splits.
+
+	Possible values are:
+	  cursor	Keep the same relative cursor position.
+	  screen	Keep the text on the same screen line.
+	  topline	Keep the topline the same.
+
+	For the "screen" and "topline" values, the cursor position will be
+	changed when necessary. In this case, the jumplist will be populated
+	with the previous cursor position. For "screen", the text cannot always
+	be kept on the same screen line	when 'wrap' is enabled.
+
 			*'splitright'* *'spr'* *'nosplitright'* *'nospr'*
 'splitright' 'spr'	boolean	(default off)
 			global
 	When on, splitting a window will put the new window right of the
 	current one. |:vsplit|
 
-			*'splitscroll'* *'spsc'* *'nosplitscroll'* *'nospsc'*
-'splitscroll' 'spsc'	boolean	(default on)
-			global
-	The value of this option determines the scroll behavior when opening,
-	closing or resizing horizontal splits. When "on", splitting a window
-	horizontally will keep the same relative cursor position in the old and
-	new window, as well windows that are resized. When "off", scrolling
-	will be avoided to stabilize the window content. Instead, the cursor
-	position will be changed when necessary. In this case, the jumplist
-	will be populated with the previous cursor position. Scrolling cannot
-	be guaranteed to be avoided when 'wrap' is enabled.
-
 			   *'startofline'* *'sol'* *'nostartofline'* *'nosol'*
 'startofline' 'sol'	boolean	(default on)
 			global
--- a/runtime/doc/quickref.txt
+++ b/runtime/doc/quickref.txt
@@ -919,8 +919,8 @@ Short explanation of each option:		*opti
 'spelloptions'	  'spo'     options for spell checking
 'spellsuggest'	  'sps'     method(s) used to suggest spelling corrections
 'splitbelow'	  'sb'	    new window from split is below the current one
+'splitkeep'	  'spk'     determines scroll behavior for split windows
 'splitright'	  'spr'     new window is put right of the current one
-'splitscroll'	  'spsc'    determines scroll behavior for split windows
 'startofline'	  'sol'     commands move cursor to first non-blank in line
 'statusline'	  'stl'     custom format for the status line
 'suffixes'	  'su'	    suffixes that are ignored with multiple match
--- a/runtime/optwin.vim
+++ b/runtime/optwin.vim
@@ -516,10 +516,10 @@ call <SID>AddOption("switchbuf", gettext
 call <SID>OptionG("swb", &swb)
 call <SID>AddOption("splitbelow", gettext("a new window is put below the current one"))
 call <SID>BinOptionG("sb", &sb)
+call <SID>AddOption("splitkeep", gettext("determines scroll behavior for split windows"))
+call <SID>BinOptionG("spk", &spk)
 call <SID>AddOption("splitright", gettext("a new window is put right of the current one"))
 call <SID>BinOptionG("spr", &spr)
-call <SID>AddOption("splitscroll", gettext("determines scroll behavior for split windows"))
-call <SID>BinOptionG("spsc", &spsc)
 call <SID>AddOption("scrollbind", gettext("this window scrolls together with other bound windows"))
 call append("$", "\t" .. s:local_to_window)
 call <SID>BinOptionL("scb")
--- a/src/globals.h
+++ b/src/globals.h
@@ -1975,10 +1975,10 @@ EXTERN int channel_need_redraw INIT(= FA
 EXTERN optmagic_T magic_overruled INIT(= OPTION_MAGIC_NOT_SET);
 
 #ifdef FEAT_CMDWIN
-// Skip win_fix_cursor() call for 'nosplitscroll' when cmdwin is closed.
+// Skip win_fix_cursor() call for 'splitkeep' when cmdwin is closed.
 EXTERN int skip_win_fix_cursor INIT(= FALSE);
 #endif
-// Skip win_fix_scroll() call for 'nosplitscroll' when closing tab page.
+// Skip win_fix_scroll() call for 'splitkeep' when closing tab page.
 EXTERN int skip_win_fix_scroll INIT(= FALSE);
 // Skip update_topline() call while executing win_fix_scroll().
 EXTERN int skip_update_topline INIT(= FALSE);
--- a/src/move.c
+++ b/src/move.c
@@ -219,6 +219,10 @@ update_topline(void)
     long	*so_ptr = curwin->w_p_so >= 0 ? &curwin->w_p_so : &p_so;
     int		save_so = *so_ptr;
 
+    // Cursor is updated instead when this is TRUE for 'splitkeep'.
+    if (skip_update_topline)
+	return;
+
     // If there is no valid screen and when the window height is zero just use
     // the cursor line.
     if (!screen_valid(TRUE) || curwin->w_height == 0)
@@ -1027,8 +1031,7 @@ curs_columns(
     /*
      * First make sure that w_topline is valid (after moving the cursor).
      */
-    if (!skip_update_topline)
-	update_topline();
+    update_topline();
 
     /*
      * Next make sure that w_cline_row is valid.
--- a/src/option.h
+++ b/src/option.h
@@ -924,11 +924,11 @@ EXTERN char_u	*p_spo;		// 'spelloptions'
 EXTERN char_u	*p_sps;		// 'spellsuggest'
 #endif
 EXTERN int	p_spr;		// 'splitright'
-EXTERN int	p_spsc;		// 'splitscroll'
 EXTERN int	p_sol;		// 'startofline'
 EXTERN char_u	*p_su;		// 'suffixes'
 EXTERN char_u	*p_sws;		// 'swapsync'
 EXTERN char_u	*p_swb;		// 'switchbuf'
+EXTERN char_u	*p_spk;		// 'splitkeep'
 EXTERN unsigned	swb_flags;
 // Keep in sync with p_swb_values in optionstr.c
 #define SWB_USEOPEN		0x001
--- a/src/optiondefs.h
+++ b/src/optiondefs.h
@@ -2350,12 +2350,12 @@ static struct vimoption options[] =
     {"splitbelow",  "sb",   P_BOOL|P_VI_DEF,
 			    (char_u *)&p_sb, PV_NONE,
 			    {(char_u *)FALSE, (char_u *)0L} SCTX_INIT},
+    {"splitkeep",   "spk",  P_STRING,
+			    (char_u *)&p_spk, PV_NONE,
+			    {(char_u *)"cursor", (char_u *)"cursor"} SCTX_INIT},
     {"splitright",  "spr",  P_BOOL|P_VI_DEF,
 			    (char_u *)&p_spr, PV_NONE,
 			    {(char_u *)FALSE, (char_u *)0L} SCTX_INIT},
-    {"splitscroll", "spsc", P_BOOL,
-			    (char_u *)&p_spsc, PV_NONE,
-			    {(char_u *)TRUE, (char_u *)TRUE} SCTX_INIT},
     {"startofline", "sol",  P_BOOL|P_VI_DEF|P_VIM,
 			    (char_u *)&p_sol, PV_NONE,
 			    {(char_u *)TRUE, (char_u *)0L} SCTX_INIT},
--- a/src/optionstr.c
+++ b/src/optionstr.c
@@ -46,6 +46,7 @@ static char *(p_ssop_values[]) = {"buffe
 #endif
 // Keep in sync with SWB_ flags in option.h
 static char *(p_swb_values[]) = {"useopen", "usetab", "split", "newtab", "vsplit", "uselast", NULL};
+static char *(p_spk_values[]) = {"cursor", "screen", "topline", NULL};
 static char *(p_tc_values[]) = {"followic", "ignore", "match", "followscs", "smart", NULL};
 #if defined(FEAT_TOOLBAR) && !defined(FEAT_GUI_MSWIN)
 static char *(p_toolbar_values[]) = {"text", "icons", "tooltips", "horiz", NULL};
@@ -1683,6 +1684,13 @@ did_set_string_option(
 	    errmsg = e_invalid_argument;
     }
 
+    // 'splitkeep'
+    else if (varp == &p_spk)
+    {
+	if (check_opt_strings(p_spk, p_spk_values, FALSE) != OK)
+	    errmsg = e_invalid_argument;
+    }
+
     // 'debug'
     else if (varp == &p_debug)
     {
@@ -1722,7 +1730,7 @@ did_set_string_option(
 	int	is_spellfile = varp == &(curwin->w_s->b_p_spf);
 
 	if ((is_spellfile && !valid_spellfile(*varp))
-	    || (!is_spellfile && !valid_spelllang(*varp)))
+		|| (!is_spellfile && !valid_spelllang(*varp)))
 	    errmsg = e_invalid_argument;
 	else
 	    errmsg = did_set_spell_option(is_spellfile);
--- a/src/structs.h
+++ b/src/structs.h
@@ -3621,8 +3621,8 @@ struct window_S
     int		w_winrow;	    // first row of window in screen
     int		w_height;	    // number of rows in window, excluding
 				    // status/command/winbar line(s)
-    int		w_prev_winrow;	    // previous winrow used for 'splitscroll'
-    int		w_prev_height;	    // previous height used for 'splitscroll'
+    int		w_prev_winrow;	    // previous winrow used for 'splitkeep'
+    int		w_prev_height;	    // previous height used for 'splitkeep'
 
     int		w_status_height;    // number of status lines (0 or 1)
     int		w_wincol;	    // Leftmost column of window in screen.
rename from src/testdir/dumps/Test_nosplitscroll_callback_1.dump
rename to src/testdir/dumps/Test_splitkeep_callback_1.dump
rename from src/testdir/dumps/Test_nosplitscroll_callback_2.dump
rename to src/testdir/dumps/Test_splitkeep_callback_2.dump
rename from src/testdir/dumps/Test_nosplitscroll_callback_3.dump
rename to src/testdir/dumps/Test_splitkeep_callback_3.dump
rename from src/testdir/dumps/Test_nosplitscroll_callback_4.dump
rename to src/testdir/dumps/Test_splitkeep_callback_4.dump
rename from src/testdir/dumps/Test_nosplitscroll_fold_1.dump
rename to src/testdir/dumps/Test_splitkeep_fold_1.dump
rename from src/testdir/dumps/Test_nosplitscroll_fold_2.dump
rename to src/testdir/dumps/Test_splitkeep_fold_2.dump
rename from src/testdir/dumps/Test_nosplitscroll_fold_3.dump
rename to src/testdir/dumps/Test_splitkeep_fold_3.dump
rename from src/testdir/dumps/Test_nosplitscroll_fold_4.dump
rename to src/testdir/dumps/Test_splitkeep_fold_4.dump
--- a/src/testdir/gen_opt_test.vim
+++ b/src/testdir/gen_opt_test.vim
@@ -134,6 +134,7 @@ let test_values = {
       \ 'spelllang': [['', 'xxx', 'sr@latin'], ['not&lang', "that\\\rthere"]],
       \ 'spelloptions': [['', 'camel'], ['xxx']],
       \ 'spellsuggest': [['', 'best', 'double,33'], ['xxx']],
+      \ 'splitkeep': [['cursor', 'screen', 'topline'], ['xxx']],
       \ 'switchbuf': [['', 'useopen', 'split,newtab'], ['xxx']],
       \ 'tagcase': [['smart', 'match'], ['', 'xxx', 'smart,match']],
       \ 'term': [[], []],
--- a/src/testdir/test_window_cmd.vim
+++ b/src/testdir/test_window_cmd.vim
@@ -1632,31 +1632,29 @@ func Test_win_equal_last_status()
   set laststatus&
 endfunc
 
-" Ensure no scrolling happens with 'nosplitscroll' for a sequence of
+" Test "screen" and "cursor" values for 'splitkeep' with a sequence of
 " split operations for various options: with and without a winbar,
 " tabline, for each possible value of 'laststatus', 'scrolloff',
-" 'equalalways', and regardless of the cursor position.
-func Test_nosplitscroll_options()
-  set nowrap
-  set nosplitscroll
-
+" 'equalalways', and with the cursor at the top, middle and bottom.
+func Test_splitkeep_options()
   " disallow window resizing
   let save_WS = &t_WS
   set t_WS=
 
   let gui = has("gui_running")
   inoremap <expr> c "<cmd>copen<bar>wincmd k<CR>"
-  for run in range(0, 10)
-    tabnew | tabonly! | redraw    
-    let tabline = (gui ? 0 : ((run % 5) ? 1 : 0))
-    let winbar_sb = (run % 2) && (run % 3)
-    execute 'set scrolloff=' . (!(run % 4) ? 0 : run)
-    execute 'set laststatus=' . (run % 3)
-    execute 'set ' . ((run % 2) ? 'equalalways' : 'noequalalways')
-    execute 'set ' . ((run % 3) ? 'splitbelow' : 'nosplitbelow')
+  for run in range(0, 20)
+    let &splitkeep = run > 10 ? 'topline' : 'screen'
+    let &scrolloff = (!(run % 4) ? 0 : run)
+    let &laststatus = (run % 3)
+    let &splitbelow = (run % 3)
+    let &equalalways = (run % 2)
+    let wsb = (run % 2) && &splitbelow
+    let tl = (gui ? 0 : ((run % 5) ? 1 : 0))
+    let pos = !(run % 3) ? 'H' : ((run % 2) ? 'M' : 'L')
+    tabnew | tabonly! | redraw
     execute (run % 5) ? 'tabnew' : ''
     execute (run % 2) ? 'nnoremenu 1.10 WinBar.Test :echo' : ''
-    let pos = !(run % 3) ? 'H' : ((run % 2) ? 'M' : 'L')
     call setline(1, range(1, 256))
     " No scroll for restore_snapshot
     norm G
@@ -1672,17 +1670,17 @@ func Test_nosplitscroll_options()
     call assert_equal(1, line("w0"))
     call assert_equal(&scroll, winheight(0) / 2)
     wincmd j
-    call assert_equal(win_screenpos(0)[0] - tabline - winbar_sb, line("w0"))
+    call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0"))
 
     " No scroll when resizing windows
-    wincmd k | resize +2
+    wincmd k | resize +2 | redraw
     call assert_equal(1, line("w0"))
     wincmd j
-    call assert_equal(win_screenpos(0)[0] - tabline - winbar_sb, line("w0"))
+    call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0"))
 
     " No scroll when dragging statusline
     call win_move_statusline(1, -3)
-    call assert_equal(win_screenpos(0)[0] - tabline - winbar_sb, line("w0"))
+    call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0"))
     wincmd k
     call assert_equal(1, line("w0"))
 
@@ -1690,9 +1688,9 @@ func Test_nosplitscroll_options()
     set lines+=2
     call assert_equal(1, line("w0"))
     wincmd j
-    call assert_equal(win_screenpos(0)[0] - tabline - winbar_sb, line("w0"))
+    call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0"))
     set lines-=2
-    call assert_equal(win_screenpos(0)[0] - tabline - winbar_sb, line("w0"))
+    call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0"))
     wincmd k
     call assert_equal(1, line("w0"))
 
@@ -1700,23 +1698,23 @@ func Test_nosplitscroll_options()
     wincmd =
     call assert_equal(1, line("w0"))
     wincmd j
-    call assert_equal(win_screenpos(0)[0] - tabline - winbar_sb, line("w0"))
+    call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0"))
     wincmd k
     call assert_equal(1, line("w0"))
 
     " No scroll in windows split multiple times
     vsplit | split | 4wincmd w
-    call assert_equal(win_screenpos(0)[0] - tabline - winbar_sb, line("w0"))
+    call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0"))
     1wincmd w | quit | wincmd l | split
-    call assert_equal(win_screenpos(0)[0] - tabline - winbar_sb, line("w0"))
+    call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0"))
     wincmd j
-    call assert_equal(win_screenpos(0)[0] - tabline - winbar_sb, line("w0"))
+    call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0"))
 
     " No scroll in small window
     2wincmd w | only | 5split | wincmd k
     call assert_equal(1, line("w0"))
     wincmd j
-    call assert_equal(win_screenpos(0)[0] - tabline - winbar_sb, line("w0"))
+    call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0"))
 
     " No scroll for vertical split
     quit | vsplit | wincmd l
@@ -1725,8 +1723,8 @@ func Test_nosplitscroll_options()
     call assert_equal(1, line("w0"))
 
     " No scroll in windows split and quit multiple times
-    quit | redraw | split | redraw | split | redraw | quit | redraw
-    call assert_equal(win_screenpos(0)[0] - tabline - winbar_sb, line("w0"))
+    quit | redraw | split | split | quit | redraw
+    call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0"))
 
     " No scroll for new buffer
     1wincmd w | only | copen | wincmd k
@@ -1734,7 +1732,7 @@ func Test_nosplitscroll_options()
     only
     call assert_equal(1, line("w0"))
     above copen | wincmd j
-    call assert_equal(win_screenpos(0)[0] - tabline, line("w0"))
+    call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl, line("w0"))
 
     " No scroll when opening cmdwin, and no cursor move when closing cmdwin.
     only | norm ggL
@@ -1751,22 +1749,21 @@ func Test_nosplitscroll_options()
     only | execute "norm gg5\<C-e>" | split | wincmd k
     call assert_equal(6, line("w0"))
     wincmd j
-    call assert_equal(5 + win_screenpos(0)[0] - tabline - winbar_sb, line("w0"))
+    call assert_equal(&spk == 'topline' ? 6 : 5 + win_screenpos(0)[0] - tl - wsb, line("w0"))
   endfor
 
   tabnew | tabonly! | %bwipeout!
   iunmap c
-  set wrap&
   set scrolloff&
   set splitbelow&
   set laststatus&
   set equalalways&
-  set splitscroll&
+  set splitkeep&
   let &t_WS = save_WS
 endfunc
 
-function Test_nosplitscroll_cmdwin_cursor_position()
-  set nosplitscroll
+function Test_splitkeep_cmdwin_cursor_position()
+  set splitkeep=screen
   call setline(1, range(&lines))
 
   " No scroll when cursor is at near bottom of window and cusor position
@@ -1789,11 +1786,11 @@ function Test_nosplitscroll_cmdwin_curso
   call assert_equal(1, col('.'))
 
   %bwipeout!
-  set splitscroll&
+  set splitkeep&
 endfunction
 
-function Test_nosplitscroll_misc()
-  set nosplitscroll
+function Test_splitkeep_misc()
+  set splitkeep=screen
   set splitbelow
 
   call setline(1, range(1, &lines))
@@ -1815,67 +1812,68 @@ function Test_nosplitscroll_misc()
 
   %bwipeout!
   set splitbelow&
-  set splitscroll&
+  set splitkeep&
 endfunc
 
-function Test_nosplitscroll_callback()
+function Test_splitkeep_callback()
   CheckScreendump
   let lines =<< trim END
-    set nosplitscroll
+    set splitkeep=screen
     call setline(1, range(&lines))
-    function WincmdCb(a, b)
+    function C1(a, b)
       split | wincmd p
     endfunction
-    function TermCb(a, b)
+    function C2(a, b)
       close | split
     endfunction
-    nnoremap t <cmd>call popup_create(term_start(&shell, { 'hidden': 1, 'exit_cb': 'TermCb' }), {})<CR>
-    nnoremap j <cmd>call job_start([&shell, &shellcmdflag, "echo"], { 'exit_cb': 'WincmdCb' })<CR>
+    nn j <cmd>call job_start([&sh, &shcf, "true"], { 'exit_cb': 'C1' })<CR>
+    nn t <cmd>call popup_create(term_start([&sh, &shcf, "true"],
+          \ { 'hidden': 1, 'exit_cb': 'C2' }), {})<CR>
   END
-  call writefile(lines, 'XTestNosplitscrollCallback', 'D')
-  let buf = RunVimInTerminal('-S XTestNosplitscrollCallback', #{rows: 8})
+  call writefile(lines, 'XTestSplitkeepCallback', 'D')
+  let buf = RunVimInTerminal('-S XTestSplitkeepCallback', #{rows: 8})
 
   call term_sendkeys(buf, "j")
-  call VerifyScreenDump(buf, 'Test_nosplitscroll_callback_1', {})
+  call VerifyScreenDump(buf, 'Test_splitkeep_callback_1', {})
 
-  call term_sendkeys(buf, ":quit\<CR>Htexit\<CR>")
-  call VerifyScreenDump(buf, 'Test_nosplitscroll_callback_2', {})
+  call term_sendkeys(buf, ":quit\<CR>Ht")
+  call VerifyScreenDump(buf, 'Test_splitkeep_callback_2', {})
 
   call term_sendkeys(buf, ":set sb\<CR>:quit\<CR>Gj")
-  call VerifyScreenDump(buf, 'Test_nosplitscroll_callback_3', {})
+  call VerifyScreenDump(buf, 'Test_splitkeep_callback_3', {})
 
-  call term_sendkeys(buf, ":quit\<CR>Gtexit\<CR>")
-  call VerifyScreenDump(buf, 'Test_nosplitscroll_callback_4', {})
+  call term_sendkeys(buf, ":quit\<CR>Gt")
+  call VerifyScreenDump(buf, 'Test_splitkeep_callback_4', {})
 endfunc
 
-function Test_nosplitscroll_fold()
-CheckScreendump
+function Test_splitkeep_fold()
+  CheckScreendump
 
-let lines =<< trim END
-  set nosplitscroll
-  set foldmethod=marker
-  set number
-  let line = 1
-  for n in range(1, &lines)
-    call setline(line, ['int FuncName() {/*{{{*/', 1, 2, 3, 4, 5, '}/*}}}*/',
-          \ 'after fold'])
-    let line += 8
-  endfor
-END
-  call writefile(lines, 'XTestNosplitscrollFold', 'D')
-  let buf = RunVimInTerminal('-S XTestNosplitscrollFold', #{rows: 10})
+  let lines =<< trim END
+    set splitkeep=screen
+    set foldmethod=marker
+    set number
+    let line = 1
+    for n in range(1, &lines)
+      call setline(line, ['int FuncName() {/*{{{*/', 1, 2, 3, 4, 5, '}/*}}}*/',
+            \ 'after fold'])
+      let line += 8
+    endfor
+  END
+  call writefile(lines, 'XTestSplitkeepFold', 'D')
+  let buf = RunVimInTerminal('-S XTestSplitkeepFold', #{rows: 10})
 
   call term_sendkeys(buf, "L:wincmd s\<CR>")
-  call VerifyScreenDump(buf, 'Test_nosplitscroll_fold_1', {})
+  call VerifyScreenDump(buf, 'Test_splitkeep_fold_1', {})
 
   call term_sendkeys(buf, ":quit\<CR>")
-  call VerifyScreenDump(buf, 'Test_nosplitscroll_fold_2', {})
+  call VerifyScreenDump(buf, 'Test_splitkeep_fold_2', {})
 
   call term_sendkeys(buf, "H:below split\<CR>")
-  call VerifyScreenDump(buf, 'Test_nosplitscroll_fold_3', {})
+  call VerifyScreenDump(buf, 'Test_splitkeep_fold_3', {})
 
   call term_sendkeys(buf, ":wincmd k\<CR>:quit\<CR>")
-  call VerifyScreenDump(buf, 'Test_nosplitscroll_fold_4', {})
+  call VerifyScreenDump(buf, 'Test_splitkeep_fold_4', {})
 endfunction
 
 " 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 */
 /**/
+    647,
+/**/
     646,
 /**/
     645,
--- a/src/window.c
+++ b/src/window.c
@@ -1325,7 +1325,7 @@ win_split_ins(
 	win_equal(wp, TRUE,
 		(flags & WSP_VERT) ? (dir == 'v' ? 'b' : 'h')
 		: dir == 'h' ? 'b' : 'v');
-    else if (!p_spsc && wp != aucmd_win)
+    else if (*p_spk != 'c' && wp != aucmd_win)
 	win_fix_scroll(FALSE);
 
     // Don't change the window height/width to 'winheight' / 'winwidth' if a
@@ -1411,7 +1411,7 @@ win_init(win_T *newp, win_T *oldp, int f
     newp->w_prevdir = (oldp->w_prevdir == NULL)
 				    ? NULL : vim_strsave(oldp->w_prevdir);
 
-    if (!p_spsc)
+    if (*p_spk != 'c')
     {
 	newp->w_botline = oldp->w_botline;
 	newp->w_prev_height = oldp->w_height - WINBAR_HEIGHT(oldp);
@@ -1925,7 +1925,7 @@ win_equal(
     win_equal_rec(next_curwin == NULL ? curwin : next_curwin, current,
 		      topframe, dir, 0, tabline_height(),
 					   (int)Columns, topframe->fr_height);
-    if (!p_spsc && next_curwin != aucmd_win)
+    if (*p_spk != 'c' && next_curwin != aucmd_win)
 	win_fix_scroll(TRUE);
 }
 
@@ -2733,7 +2733,7 @@ win_close(win_T *win, int free_buf)
     else
     {
 	win_comp_pos();
-	if (!p_spsc)
+	if (*p_spk != 'c')
 	    win_fix_scroll(FALSE);
     }
     if (close_curwin)
@@ -4931,7 +4931,7 @@ win_enter_ext(win_T *wp, int flags)
 
     // Might need to scroll the old window before switching, e.g., when the
     // cursor was moved.
-    if (p_spsc)
+    if (*p_spk == 'c')
 	update_topline();
 
     // may have to copy the buffer options when 'cpo' contains 'S'
@@ -4947,7 +4947,7 @@ win_enter_ext(win_T *wp, int flags)
     check_cursor();
     if (!virtual_active())
 	curwin->w_cursor.coladd = 0;
-    if (p_spsc) // assume cursor position needs updating.
+    if (*p_spk == 'c')		// assume cursor position needs updating
 	changed_line_abv_curs();
     else
 	win_fix_cursor(TRUE);
@@ -5068,7 +5068,7 @@ static int last_win_id = LOWEST_WIN_ID -
  * FALSE.
  */
     static win_T *
-win_alloc(win_T *after UNUSED, int hidden UNUSED)
+win_alloc(win_T *after, int hidden)
 {
     win_T	*new_wp;
 
@@ -5480,7 +5480,7 @@ shell_new_rows(void)
     compute_cmdrow();
     curtab->tp_ch_used = p_ch;
 
-    if (!p_spsc && !skip_win_fix_scroll)
+    if (*p_spk != 'c' && !skip_win_fix_scroll)
 	win_fix_scroll(TRUE);
 
 #if 0
@@ -5687,7 +5687,7 @@ win_setheight_win(int height, win_T *win
     msg_row = row;
     msg_col = 0;
 
-    if (!p_spsc)
+    if (*p_spk != 'c')
 	win_fix_scroll(TRUE);
 
     redraw_all_later(UPD_NOT_VALID);
@@ -6218,7 +6218,7 @@ win_drag_status_line(win_T *dragwin, int
     p_ch = MAX(Rows - cmdline_row, 1);
     curtab->tp_ch_used = p_ch;
 
-    if (!p_spsc)
+    if (*p_spk != 'c')
 	win_fix_scroll(TRUE);
 
     redraw_all_later(UPD_SOME_VALID);
@@ -6348,7 +6348,7 @@ set_fraction(win_T *wp)
 }
 
 /*
- * Handle scroll position for 'nosplitscroll'.  Replaces scroll_to_fraction()
+ * Handle scroll position for 'splitkeep'.  Replaces scroll_to_fraction()
  * call from win_new_height().  Instead we iterate over all windows in a
  * tabpage and calculate the new scroll position.
  * TODO: Ensure this also works with wrapped lines.
@@ -6362,14 +6362,14 @@ win_fix_scroll(int resize)
     win_T	*wp;
     linenr_T	lnum;
 
-    skip_update_topline = TRUE;  // avoid scrolling in curs_columns()
+    skip_update_topline = TRUE;
     FOR_ALL_WINDOWS(wp)
     {
 	// Skip when window height has not changed.
 	if (wp->w_height != wp->w_prev_height)
 	{
 	    // If window has moved update botline to keep the same screenlines.
-	    if (wp->w_winrow != wp->w_prev_winrow)
+	    if (*p_spk == 's' && wp->w_winrow != wp->w_prev_winrow)
 	    {
 		lnum = wp->w_cursor.lnum;
 		diff = (wp->w_winrow - wp->w_prev_winrow)
@@ -6403,7 +6403,7 @@ win_fix_scroll(int resize)
 }
 
 /*
- * Make sure the cursor position is valid for 'nosplitscroll'.
+ * Make sure the cursor position is valid for 'splitkeep'.
  * If it is not, put the cursor position in the jumplist and move it.
  * If we are not in normal mode, scroll to make valid instead.
  */
@@ -6413,9 +6413,9 @@ win_fix_cursor(int normal)
     long	so = get_scrolloff_value();
     win_T	*wp = curwin;
     linenr_T	nlnum = 0;
-    linenr_T    lnum = wp->w_cursor.lnum;
-    linenr_T    bot;
-    linenr_T    top;
+    linenr_T	lnum = wp->w_cursor.lnum;
+    linenr_T	bot;
+    linenr_T	top;
 
     if (wp->w_buffer->b_ml.ml_line_count < wp->w_height)
 	return;
@@ -6429,25 +6429,23 @@ win_fix_cursor(int normal)
     top = cursor_down_inner(wp, so);
     wp->w_cursor.lnum = wp->w_botline - 1;
     bot = cursor_up_inner(wp, so);
+    wp->w_cursor.lnum = lnum;
     // Check if cursor position is above or below valid cursor range.
     if (lnum > bot && (wp->w_botline - wp->w_buffer->b_ml.ml_line_count) != 1)
 	nlnum = bot;
     else if (lnum < top && wp->w_topline != 1)
 	nlnum = (so == wp->w_height / 2) ? bot : top;
 
-    wp->w_cursor.lnum = lnum;
-
     if (nlnum)  // Cursor is invalid for current scroll position.
     {
 	if (normal)  // Save to jumplist and set cursor to avoid scrolling.
 	{
 	    setmark('\'');
 	    wp->w_cursor.lnum = nlnum;
-	    curs_columns(TRUE);		// validate w_wrow
 	}
 	else  // Scroll instead when not in normal mode.
 	{
-	    wp->w_fraction = 0.5 * FRACTION_MULT;
+	    wp->w_fraction = (nlnum == bot) ? FRACTION_MULT : 0;
 	    scroll_to_fraction(wp, wp->w_prev_height);
 	    validate_botline_win(curwin);
 	}
@@ -6474,7 +6472,7 @@ win_new_height(win_T *wp, int height)
 
     if (wp->w_height > 0)
     {
-	if (wp == curwin && p_spsc)
+	if (wp == curwin && *p_spk == 'c')
 	    // w_wrow needs to be valid. When setting 'laststatus' this may
 	    // call win_new_height() recursively.
 	    validate_cursor();
@@ -6490,7 +6488,7 @@ win_new_height(win_T *wp, int height)
 
     // There is no point in adjusting the scroll position when exiting.  Some
     // values might be invalid.
-    if (!exiting && p_spsc)
+    if (!exiting && *p_spk == 'c')
 	scroll_to_fraction(wp, prev_height);
 }
 
@@ -6604,7 +6602,7 @@ scroll_to_fraction(win_T *wp, int prev_h
 
     if (wp == curwin)
     {
-	if (p_spsc && get_scrolloff_value())
+	if (get_scrolloff_value())
 	    update_topline();
 	curs_columns(FALSE);	// validate w_wrow
     }
@@ -6627,15 +6625,13 @@ win_new_width(win_T *wp, int width)
     wp->w_width = width < 0 ? 0 : width;
     wp->w_lines_valid = 0;
     changed_line_abv_curs_win(wp);
-    // Handled in win_fix_scroll()
-    if (p_spsc)
-    {
-	invalidate_botline_win(wp);
-	if (wp == curwin)
-	{
-	    update_topline();
-	    curs_columns(TRUE);	// validate w_wrow
-	}
+    invalidate_botline_win(wp);
+    if (wp == curwin)
+    {
+	skip_update_topline = (*p_spk != 'c');
+	update_topline();
+	curs_columns(TRUE);	// validate w_wrow
+	skip_update_topline = FALSE;
     }
     redraw_win_later(wp, UPD_NOT_VALID);
     wp->w_redr_status = TRUE;