To: vim_dev@googlegroups.com Subject: Patch 9.0.0445 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ 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 BinOptionG("sb", &sb) call AddOption("splitright", gettext("a new window is put right of the current one")) call BinOptionG("spr", &spr) + call AddOption("splitscroll", gettext("determines scroll behavior when spliting windows")) + call BinOptionG("spsc", &spsc) call AddOption("scrollbind", gettext("this window scrolls together with other bound windows")) call append("$", "\t" .. s:local_to_window) call 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 :copen + 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\" | 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 -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// \\\ \\\ sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///