Patch 9.0.0445
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.
Files:      runtime/doc/options.txt, runtime/doc/quickref.txt,
            runtime/optwin.vim, src/move.c, src/option.h, src/optiondefs.h,
            src/structs.h, src/window.c, src/testdir/test_window_cmd.vim


*** ../vim-9.0.0444/runtime/doc/options.txt     2022-08-31 14:46:07.903016994 
+0100
--- runtime/doc/options.txt     2022-09-11 16:43:40.824700636 +0100
***************
*** 7472,7477 ****
--- 7499,7516 ----
        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
*** ../vim-9.0.0444/runtime/doc/quickref.txt    2022-06-28 11:21:06.000000000 
+0100
--- runtime/doc/quickref.txt    2022-09-11 16:49:42.148013392 +0100
***************
*** 919,924 ****
--- 919,925 ----
  'spellsuggest'          'sps'     method(s) used to suggest spelling 
corrections
  'splitbelow'    'sb'      new window from split is below the current one
  'splitright'    'spr'     new window is put right of the current one
+ 'splitscroll'   'spsc'    determines scroll behavior when splitting 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
*** ../vim-9.0.0444/runtime/optwin.vim  2022-04-07 12:17:51.000000000 +0100
--- runtime/optwin.vim  2022-09-11 16:50:06.551966954 +0100
***************
*** 515,520 ****
--- 515,522 ----
  call <SID>BinOptionG("sb", &sb)
  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 when 
spliting 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")
*** ../vim-9.0.0444/src/move.c  2022-09-10 20:00:31.121468657 +0100
--- src/move.c  2022-09-11 16:43:40.824700636 +0100
***************
*** 981,987 ****
      /*
       * First make sure that w_topline is valid (after moving the cursor).
       */
!     update_topline();
  
      /*
       * Next make sure that w_cline_row is valid.
--- 981,988 ----
      /*
       * First make sure that w_topline is valid (after moving the cursor).
       */
!     if (p_spsc)
!       update_topline();
  
      /*
       * Next make sure that w_cline_row is valid.
*** ../vim-9.0.0444/src/option.h        2022-08-26 16:58:46.139489368 +0100
--- src/option.h        2022-09-11 16:43:40.824700636 +0100
***************
*** 924,929 ****
--- 924,930 ----
  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'
*** ../vim-9.0.0444/src/optiondefs.h    2022-08-27 21:24:22.709002324 +0100
--- src/optiondefs.h    2022-09-11 16:43:40.824700636 +0100
***************
*** 2349,2354 ****
--- 2349,2357 ----
      {"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},
*** ../vim-9.0.0444/src/structs.h       2022-09-10 20:00:31.117468669 +0100
--- src/structs.h       2022-09-11 16:51:07.075851730 +0100
***************
*** 3570,3575 ****
--- 3570,3577 ----
      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_status_height;    // number of status lines (0 or 1)
      int               w_wincol;           // Leftmost column of window in 
screen.
*** ../vim-9.0.0444/src/window.c        2022-09-07 14:42:46.099802275 +0100
--- src/window.c        2022-09-11 16:57:26.366536524 +0100
***************
*** 25,30 ****
--- 25,32 ----
  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,1328 ****
--- 1325,1332 ----
        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,1412 ****
--- 1411,1423 ----
      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,1919 ****
--- 1925,1932 ----
      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,2731 ****
--- 2738,2748 ----
        // 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,4918 ****
  
      // Might need to scroll the old window before switching, e.g., when the
      // cursor was moved.
!     update_topline();
  
      // may have to copy the buffer options when 'cpo' contains 'S'
      if (wp->w_buffer != curbuf)
--- 4929,4936 ----
  
      // Might need to scroll the old window before switching, e.g., when the
      // cursor was moved.
!     if (p_spsc)
!       update_topline();
  
      // may have to copy the buffer options when 'cpo' contains 'S'
      if (wp->w_buffer != curbuf)
***************
*** 4927,4933 ****
      check_cursor();
      if (!virtual_active())
        curwin->w_cursor.coladd = 0;
!     changed_line_abv_curs();  // assume cursor position needs updating
  
      // Now it is OK to parse messages again, which may be needed in
      // autocommands.
--- 4945,4954 ----
      check_cursor();
      if (!virtual_active())
        curwin->w_cursor.coladd = 0;
!     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,5463 ****
--- 5479,5487 ----
      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,5667 ****
--- 5686,5694 ----
      msg_row = row;
      msg_col = 0;
  
+     if (!p_spsc)
+       win_fix_scroll(TRUE);
+ 
      redraw_all_later(UPD_NOT_VALID);
  }
  
***************
*** 6190,6195 ****
--- 6217,6225 ----
      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,6322 ****
--- 6347,6443 ----
  }
  
  /*
+  * 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,6342 ****
  
      if (wp->w_height > 0)
      {
!       if (wp == curwin)
            // w_wrow needs to be valid. When setting 'laststatus' this may
            // call win_new_height() recursively.
            validate_cursor();
--- 6457,6463 ----
  
      if (wp->w_height > 0)
      {
!       if (wp == curwin && p_spsc)
            // w_wrow needs to be valid. When setting 'laststatus' this may
            // call win_new_height() recursively.
            validate_cursor();
***************
*** 6352,6358 ****
  
      // There is no point in adjusting the scroll position when exiting.  Some
      // values might be invalid.
!     if (!exiting)
        scroll_to_fraction(wp, prev_height);
  }
  
--- 6473,6479 ----
  
      // There is no point in adjusting the scroll position when exiting.  Some
      // values might be invalid.
!     if (!exiting && p_spsc)
        scroll_to_fraction(wp, prev_height);
  }
  
***************
*** 6466,6472 ****
  
      if (wp == curwin)
      {
!       if (get_scrolloff_value())
            update_topline();
        curs_columns(FALSE);    // validate w_wrow
      }
--- 6587,6593 ----
  
      if (wp == curwin)
      {
!       if (p_spsc && get_scrolloff_value())
            update_topline();
        curs_columns(FALSE);    // validate w_wrow
      }
***************
*** 6488,6498 ****
      wp->w_width = width;
      wp->w_lines_valid = 0;
      changed_line_abv_curs_win(wp);
!     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;
--- 6609,6623 ----
      wp->w_width = 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
!       }
      }
      redraw_win_later(wp, UPD_NOT_VALID);
      wp->w_redr_status = TRUE;
*** ../vim-9.0.0444/src/testdir/test_window_cmd.vim     2022-09-07 
14:42:46.099802275 +0100
--- src/testdir/test_window_cmd.vim     2022-09-11 16:43:40.828700627 +0100
***************
*** 1631,1635 ****
--- 1631,1763 ----
    set laststatus&
  endfunc
  
+ " Ensure no scrolling happens with 'nosplitscroll' with and without a
+ " winbar, tabline, for each possible value of 'laststatus', 'scrolloff',
+ " 'equalalways', and regardless of the cursor position.
+ func Test_splitscroll_with_splits()
+   set nowrap
+   set nosplitscroll
+   let gui = has("gui_running")
+   inoremap c <cmd>:copen<CR>
+   for winbar in [0, 1]
+     for sb in [0, 1]
+       for ea in [0, 1]
+         for tab in [0, 1]
+           for so in [0, 5]
+             for ls in range(0, 2)
+               for pos in ["H", "M", "L"]
+               let tabline = (gui ? 0 : (tab ? 1 : 0))
+               let winbar_sb = (sb ? winbar : 0)
+               execute 'set scrolloff=' . so
+               execute 'set laststatus=' . ls
+               execute 'set ' . (ea ? 'equalalways' : 'noequalalways')
+               execute 'set ' . (sb ? 'splitbelow' : 'nosplitbelow')
+               execute tab ? 'tabnew' : ''
+               execute winbar ? 'nnoremenu 1.10 WinBar.Test :echo' : ''
+               call setline(1, range(1, 256))
+               execute 'norm gg' . pos
+               " No scroll for vertical split and quit
+               vsplit | quit
+               call assert_equal(1, line("w0"))
+ 
+               " No scroll for horizontal split
+               split | redraw! | wincmd k
+               call assert_equal(1, line("w0"))
+ 
+               " No scroll when resizing windows
+               resize +2
+               call assert_equal(1, line("w0"))
+               wincmd j
+               call assert_equal(win_screenpos(0)[0] - tabline - winbar_sb, 
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"))
+               wincmd k
+               call assert_equal(1, line("w0"))
+ 
+               " No scroll when changing shellsize
+               set lines+=2
+               call assert_equal(1, line("w0"))
+               wincmd j
+               call assert_equal(win_screenpos(0)[0] - tabline - winbar_sb, 
line("w0"))
+               set lines-=2
+               call assert_equal(win_screenpos(0)[0] - tabline - winbar_sb, 
line("w0"))
+               wincmd k
+               call assert_equal(1, line("w0"))
+ 
+               " No scroll when equalizing windows
+               wincmd =
+               call assert_equal(1, line("w0"))
+               wincmd j
+               call assert_equal(win_screenpos(0)[0] - tabline - winbar_sb, 
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"))
+               1wincmd w | quit | wincmd l | split
+               call assert_equal(win_screenpos(0)[0] - tabline - winbar_sb, 
line("w0"))
+               wincmd j
+               call assert_equal(win_screenpos(0)[0] - tabline - winbar_sb, 
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"))
+ 
+               " No scroll for vertical split
+               quit | vsplit | wincmd l
+               call assert_equal(1, line("w0"))
+               wincmd h
+               call assert_equal(1, line("w0"))
+ 
+               " No scroll in windows split and quit multiple times
+               quit | split | split | quit
+               call assert_equal(win_screenpos(0)[0] - tabline - winbar_sb, 
line("w0"))
+ 
+               " No scroll for new buffer
+               1wincmd w | only | copen | wincmd k
+               call assert_equal(1, line("w0"))
+               only
+               call assert_equal(1, line("w0"))
+               above copen | wincmd j
+               call assert_equal(win_screenpos(0)[0] - tabline, line("w0"))
+ 
+               " No scroll when opening cmdwin
+               only | norm ggLq:
+               call assert_equal(1, line("w0"))
+ 
+               " Scroll when cursor becomes invalid in insert mode
+               norm Lic
+               wincmd k | only
+               call assert_notequal(1, line("w0"))
+ 
+               " No scroll when topline not equal to 1
+               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"))
+               only
+               endfor
+             endfor
+           endfor
+           tabonly!
+         endfor
+       endfor
+     endfor
+   endfor
+ 
+   tabnew | tabonly! | %bwipeout!
+   iunmap c
+   set wrap&
+   set scrolloff&
+   set splitbelow&
+   set laststatus&
+   set equalalways&
+   set splitscroll&
+ endfunc
  
  " vim: shiftwidth=2 sts=2 expandtab
*** ../vim-9.0.0444/src/version.c       2022-09-11 15:14:00.551020049 +0100
--- src/version.c       2022-09-11 16:45:28.040496825 +0100
***************
*** 705,706 ****
--- 705,708 ----
  {   /* Add new patch number below this line */
+ /**/
+     445,
  /**/

-- 
Light travels faster than sound.  This is why some people
appear bright until you hear them speak

 /// Bram Moolenaar -- [email protected] -- http://www.Moolenaar.net   \\\
///                                                                      \\\
\\\        sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ ///
 \\\            help me help AIDS victims -- http://ICCF-Holland.org    ///

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/vim_dev/20220911160137.947D41C0CFD%40moolenaar.net.

Raspunde prin e-mail lui