patch 9.0.0445: when opening/closing window text moves up/down

Problem:    When opening/closing window text moves up/down.
Solution:   Add the 'splitscroll' option.  When off text will keep its
            position as much as possible.
diff --git a/src/window.c b/src/window.c
index ff55a3b..cbe01f0 100644
--- a/src/window.c
+++ b/src/window.c
@@ -25,6 +25,8 @@
 static tabpage_T *alt_tabpage(void);
 static win_T *frame2win(frame_T *frp);
 static int frame_has_win(frame_T *frp, win_T *wp);
+static void win_fix_scroll(int resize);
+static void win_fix_cursor(int normal);
 static void frame_new_height(frame_T *topfrp, int height, int topfirst, int wfh);
 static int frame_fixed_height(frame_T *frp);
 static int frame_fixed_width(frame_T *frp);
@@ -1323,6 +1325,8 @@
 	win_equal(wp, TRUE,
 		(flags & WSP_VERT) ? (dir == 'v' ? 'b' : 'h')
 		: dir == 'h' ? 'b' : 'v');
+    else if (!p_spsc)
+	win_fix_scroll(FALSE);
 
     // Don't change the window height/width to 'winheight' / 'winwidth' if a
     // size was given.
@@ -1407,6 +1411,13 @@
     newp->w_prevdir = (oldp->w_prevdir == NULL)
 				    ? NULL : vim_strsave(oldp->w_prevdir);
 
+    if (!p_spsc)
+    {
+	newp->w_botline = oldp->w_botline;
+	newp->w_prev_height = oldp->w_height - WINBAR_HEIGHT(oldp);
+	newp->w_prev_winrow = oldp->w_winrow + 2 * WINBAR_HEIGHT(oldp);
+    }
+
     // copy tagstack and folds
     for (i = 0; i < oldp->w_tagstacklen; i++)
     {
@@ -1914,6 +1925,8 @@
     win_equal_rec(next_curwin == NULL ? curwin : next_curwin, current,
 		      topframe, dir, 0, tabline_height(),
 					   (int)Columns, topframe->fr_height);
+    if (!p_spsc)
+	win_fix_scroll(TRUE);
 }
 
 /*
@@ -2725,7 +2738,11 @@
 	// only resize that frame.  Otherwise resize all windows.
 	win_equal(curwin, curwin->w_frame->fr_parent == win_frame, dir);
     else
+    {
 	win_comp_pos();
+	if (!p_spsc)
+	    win_fix_scroll(FALSE);
+    }
     if (close_curwin)
     {
 	// Pass WEE_ALLOW_PARSE_MESSAGES to decrement dont_parse_messages
@@ -4912,7 +4929,8 @@
 
     // Might need to scroll the old window before switching, e.g., when the
     // cursor was moved.
-    update_topline();
+    if (p_spsc)
+	update_topline();
 
     // may have to copy the buffer options when 'cpo' contains 'S'
     if (wp->w_buffer != curbuf)
@@ -4927,7 +4945,10 @@
     check_cursor();
     if (!virtual_active())
 	curwin->w_cursor.coladd = 0;
-    changed_line_abv_curs();	// assume cursor position needs updating
+    if (p_spsc) // assume cursor position needs updating.
+	changed_line_abv_curs();
+    else
+	win_fix_cursor(TRUE);
 
     // Now it is OK to parse messages again, which may be needed in
     // autocommands.
@@ -5458,6 +5479,9 @@
     compute_cmdrow();
     curtab->tp_ch_used = p_ch;
 
+    if (!p_spsc)
+	win_fix_scroll(TRUE);
+
 #if 0
     // Disabled: don't want making the screen smaller make a window larger.
     if (p_ea)
@@ -5662,6 +5686,9 @@
     msg_row = row;
     msg_col = 0;
 
+    if (!p_spsc)
+	win_fix_scroll(TRUE);
+
     redraw_all_later(UPD_NOT_VALID);
 }
 
@@ -6190,6 +6217,9 @@
     p_ch = MAX(Rows - cmdline_row, 1);
     curtab->tp_ch_used = p_ch;
 
+    if (!p_spsc)
+	win_fix_scroll(TRUE);
+
     redraw_all_later(UPD_SOME_VALID);
     showmode();
 }
@@ -6317,6 +6347,97 @@
 }
 
 /*
+ * Handle scroll position for 'nosplitscroll'.  Replaces scroll_to_fraction()
+ * call from win_new_height().  Instead we iterate over all windows in a
+ * tabpage and calculate the new scroll/cursor position.
+ * TODO: Ensure this also works with wrapped lines.
+ * Requires topline to be able to be set to a bufferline with some
+ * offset(row-wise scrolling/smoothscroll).
+ */
+    static void
+win_fix_scroll(int resize)
+{
+    win_T    *wp;
+    linenr_T lnum;
+
+    FOR_ALL_WINDOWS(wp)
+    {
+	// Skip when window height has not changed or when
+	// buffer has less lines than the window height.
+	if (wp->w_height != wp->w_prev_height
+		&& wp->w_height < wp->w_buffer->b_ml.ml_line_count)
+	{
+	    // Determine botline needed to avoid scrolling and set cursor.
+	    if (wp->w_winrow != wp->w_prev_winrow)
+	    {
+		lnum = wp->w_cursor.lnum;
+		wp->w_cursor.lnum = MIN(wp->w_buffer->b_ml.ml_line_count,
+			wp->w_botline - 1 + (wp->w_prev_height
+			    ? (wp->w_winrow - wp->w_prev_winrow)
+					   + (wp->w_height - wp->w_prev_height)
+			    : -WINBAR_HEIGHT(wp)));
+		// Bring the new cursor position to the bottom of the screen.
+		wp->w_fraction = FRACTION_MULT;
+		scroll_to_fraction(wp, wp->w_prev_height);
+		wp->w_cursor.lnum = lnum;
+	    }
+	    invalidate_botline_win(wp);
+	    validate_botline_win(wp);
+	}
+	wp->w_prev_height = wp->w_height;
+	wp->w_prev_winrow = wp->w_winrow;
+    }
+    // Ensure cursor is valid when not in normal mode or when resized.
+    if (!(get_real_state() & (MODE_NORMAL|MODE_CMDLINE)))
+	win_fix_cursor(FALSE);
+    else if (resize)
+	win_fix_cursor(TRUE);
+}
+
+/*
+ * Make sure the cursor position is valid for 'nosplitscroll'.
+ * 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.
+ */
+    static void
+win_fix_cursor(int normal)
+{
+    int      top = FALSE;
+    win_T    *wp = curwin;
+    long     so = get_scrolloff_value();
+    linenr_T nlnum = 0;
+
+    if (wp->w_buffer->b_ml.ml_line_count < wp->w_height)
+	return;
+
+    so = MIN(wp->w_height / 2, so);
+    // Check if cursor position is above topline or below botline.
+    if (wp->w_cursor.lnum < (wp->w_topline + so) && wp->w_topline != 1)
+	top = nlnum = MIN(wp->w_topline + so, wp->w_buffer->b_ml.ml_line_count);
+    else if (wp->w_cursor.lnum > (wp->w_botline - so - 1)
+	    && (wp->w_botline - wp->w_buffer->b_ml.ml_line_count) != 1)
+	nlnum = MAX(wp->w_botline - so - 1, 1);
+    // If cursor was invalid scroll or change cursor.
+    if (nlnum)
+    {
+	if (normal)
+	{   // Make sure cursor is closer to topline than botline.
+	    if (so == wp->w_height / 2
+			  && nlnum - wp->w_topline > wp->w_botline - 1 - nlnum)
+		nlnum--;
+	    setmark('\'');		// save cursor position
+	    wp->w_cursor.lnum = nlnum;	// change to avoid scrolling
+	    curs_columns(TRUE);		// validate w_wrow
+	}
+	else
+	{   // Ensure cursor stays visible if we are not in normal mode.
+	    wp->w_fraction = top ? 0 : FRACTION_MULT;
+	    scroll_to_fraction(wp, wp->w_prev_height);
+	}
+    }
+}
+
+/*
  * Set the height of a window.
  * "height" excludes any window toolbar.
  * This takes care of the things inside the window, not what happens to the
@@ -6336,7 +6457,7 @@
 
     if (wp->w_height > 0)
     {
-	if (wp == curwin)
+	if (wp == curwin && p_spsc)
 	    // w_wrow needs to be valid. When setting 'laststatus' this may
 	    // call win_new_height() recursively.
 	    validate_cursor();
@@ -6352,7 +6473,7 @@
 
     // There is no point in adjusting the scroll position when exiting.  Some
     // values might be invalid.
-    if (!exiting)
+    if (!exiting && p_spsc)
 	scroll_to_fraction(wp, prev_height);
 }
 
@@ -6466,7 +6587,7 @@
 
     if (wp == curwin)
     {
-	if (get_scrolloff_value())
+	if (p_spsc && get_scrolloff_value())
 	    update_topline();
 	curs_columns(FALSE);	// validate w_wrow
     }
@@ -6488,11 +6609,15 @@
     wp->w_width = width;
     wp->w_lines_valid = 0;
     changed_line_abv_curs_win(wp);
-    invalidate_botline_win(wp);
-    if (wp == curwin)
+    // Handled in win_fix_scroll()
+    if (p_spsc)
     {
-	update_topline();
-	curs_columns(TRUE);	// validate w_wrow
+	invalidate_botline_win(wp);
+	if (wp == curwin)
+	{
+	    update_topline();
+	    curs_columns(TRUE);	// validate w_wrow
+	}
     }
     redraw_win_later(wp, UPD_NOT_VALID);
     wp->w_redr_status = TRUE;