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.
This commit is contained in:
Luuk van Baal
2022-09-11 16:59:53 +01:00
committed by Bram Moolenaar
parent 9510d22463
commit 29ab524358
10 changed files with 287 additions and 10 deletions

View 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

View 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

View 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")

View 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.

View 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'

View 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},

View 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.

View 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

View File

@ -703,6 +703,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
445,
/**/
444,
/**/

View 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;