diff options
-rw-r--r-- | runtime/doc/options.txt | 12 | ||||
-rw-r--r-- | runtime/doc/quickref.txt | 1 | ||||
-rw-r--r-- | runtime/optwin.vim | 2 | ||||
-rw-r--r-- | src/move.c | 3 | ||||
-rw-r--r-- | src/option.h | 1 | ||||
-rw-r--r-- | src/optiondefs.h | 3 | ||||
-rw-r--r-- | src/structs.h | 2 | ||||
-rw-r--r-- | src/testdir/test_window_cmd.vim | 128 | ||||
-rw-r--r-- | src/version.c | 2 | ||||
-rw-r--r-- | src/window.c | 143 |
10 files changed, 287 insertions, 10 deletions
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index b60abc271..336d4497d 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -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 diff --git a/runtime/doc/quickref.txt b/runtime/doc/quickref.txt index 67fc28e17..2cc2358e9 100644 --- a/runtime/doc/quickref.txt +++ b/runtime/doc/quickref.txt @@ -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 diff --git a/runtime/optwin.vim b/runtime/optwin.vim index c83b06b31..6b9b5fec9 100644 --- a/runtime/optwin.vim +++ b/runtime/optwin.vim @@ -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") diff --git a/src/move.c b/src/move.c index 9ec7798b9..8660d8905 100644 --- a/src/move.c +++ b/src/move.c @@ -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. diff --git a/src/option.h b/src/option.h index ab34e82a7..97b7a6c51 100644 --- a/src/option.h +++ b/src/option.h @@ -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' diff --git a/src/optiondefs.h b/src/optiondefs.h index 2e24cdd9e..8ec012f0d 100644 --- a/src/optiondefs.h +++ b/src/optiondefs.h @@ -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}, diff --git a/src/structs.h b/src/structs.h index c12f8e45a..a477dc71f 100644 --- a/src/structs.h +++ b/src/structs.h @@ -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. diff --git a/src/testdir/test_window_cmd.vim b/src/testdir/test_window_cmd.vim index e38c8a810..a75286fae 100644 --- a/src/testdir/test_window_cmd.vim +++ b/src/testdir/test_window_cmd.vim @@ -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 diff --git a/src/version.c b/src/version.c index 8fee9776e..ec0f00b18 100644 --- a/src/version.c +++ b/src/version.c @@ -704,6 +704,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 445, +/**/ 444, /**/ 443, diff --git a/src/window.c b/src/window.c index ff55a3bf9..cbe01f04c 100644 --- a/src/window.c +++ b/src/window.c @@ -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(); } @@ -6317,6 +6347,97 @@ set_fraction(win_T *wp) } /* + * 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 @@ 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; |