]> granicus.if.org Git - vim/commitdiff
patch 9.0.0445: when opening/closing window text moves up/down v9.0.0445
authorLuuk van Baal <luukvbaal@gmail.com>
Sun, 11 Sep 2022 15:59:53 +0000 (16:59 +0100)
committerBram Moolenaar <Bram@vim.org>
Sun, 11 Sep 2022 15:59:53 +0000 (16:59 +0100)
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.

runtime/doc/options.txt
runtime/doc/quickref.txt
runtime/optwin.vim
src/move.c
src/option.h
src/optiondefs.h
src/structs.h
src/testdir/test_window_cmd.vim
src/version.c
src/window.c

index b60abc271d35167f301b3fc976569848ad54d466..336d4497d5e1fbe0273c900f714d656c774cb864 100644 (file)
@@ -7499,6 +7499,18 @@ A jump table for the options with a short description can be found at |Q_op|.
        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
index 67fc28e17a0291a29a2e9434510b14cc687c08b9..2cc2358e9cdf6de7263760e206ff7ffd3af166b5 100644 (file)
@@ -919,6 +919,7 @@ Short explanation of each option:           *option-list*
 '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
index c83b06b31ac491bfc625b9b41d897191e76532b9..6b9b5fec95a48ddcc35c85cfd606d46cf13a3246 100644 (file)
@@ -515,6 +515,8 @@ call <SID>AddOption("splitbelow", gettext("a new window is put below the current
 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")
index 9ec7798b9f2ef529c5ef994ba07ee6c8d82c7873..8660d89055eb409d38cad7bff857e3adfe2957e0 100644 (file)
@@ -981,7 +981,8 @@ curs_columns(
     /*
      * First make sure that w_topline is valid (after moving the cursor).
      */
-    update_topline();
+    if (p_spsc)
+       update_topline();
 
     /*
      * Next make sure that w_cline_row is valid.
index ab34e82a7af7db0348d5ab2c7b7202eb7ba49985..97b7a6c51196fc19b9e4582dfa88d5eb450ee0d1 100644 (file)
@@ -924,6 +924,7 @@ 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'
index 2e24cdd9e2a04e46d8a8c320a21c5e6ee922e39c..8ec012f0d668feb4297177155afd7a5635a59664 100644 (file)
@@ -2349,6 +2349,9 @@ static struct vimoption options[] =
     {"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},
index c12f8e45af64591e00d19578aebabceb0b6e6555..a477dc71fe7907fe852ab5824e318b5059be9e83 100644 (file)
@@ -3570,6 +3570,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_status_height;    // number of status lines (0 or 1)
     int                w_wincol;           // Leftmost column of window in screen.
index e38c8a8107897f20cf65a74918cea658d40ccdca..a75286fae3646b5c86aae78ab1f32dca43729645 100644 (file)
@@ -1631,5 +1631,133 @@ func Test_win_equal_last_status()
   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
index 8fee9776eca033d394b08131097c5a9eef80b019..ec0f00b182acc2909cb1f40dbeea2594d881ddf4 100644 (file)
@@ -703,6 +703,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    445,
 /**/
     444,
 /**/
index ff55a3bf9fc6083f94c5368ca2a85da9ad3b6776..cbe01f04cac42dd304a01b5e8df403484a5f4992 100644 (file)
@@ -25,6 +25,8 @@ static frame_T *win_altframe(win_T *win, tabpage_T *tp);
 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_split_ins(
        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 @@ win_init(win_T *newp, win_T *oldp, int flags UNUSED)
     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(
     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 @@ win_close(win_T *win, int free_buf)
        // 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 @@ win_enter_ext(win_T *wp, int flags)
 
     // 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 @@ win_enter_ext(win_T *wp, int flags)
     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 @@ shell_new_rows(void)
     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 @@ win_setheight_win(int height, win_T *win)
     msg_row = row;
     msg_col = 0;
 
+    if (!p_spsc)
+       win_fix_scroll(TRUE);
+
     redraw_all_later(UPD_NOT_VALID);
 }
 
@@ -6190,6 +6217,9 @@ win_drag_status_line(win_T *dragwin, int offset)
     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();
 }
@@ -6316,6 +6346,97 @@ set_fraction(win_T *wp)
                                     + FRACTION_MULT / 2) / (long)wp->w_height;
 }
 
+/*
+ * 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.
@@ -6336,7 +6457,7 @@ win_new_height(win_T *wp, int height)
 
     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 @@ 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)
+    if (!exiting && p_spsc)
        scroll_to_fraction(wp, prev_height);
 }
 
@@ -6466,7 +6587,7 @@ scroll_to_fraction(win_T *wp, int prev_height)
 
     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 @@ win_new_width(win_T *wp, int width)
     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;