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.
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
'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
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")
/*
* 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.
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'
{"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},
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.
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
static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 445,
/**/
444,
/**/
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);
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.
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++)
{
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);
}
/*
// 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
// 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)
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.
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)
msg_row = row;
msg_col = 0;
+ if (!p_spsc)
+ win_fix_scroll(TRUE);
+
redraw_all_later(UPD_NOT_VALID);
}
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();
}
+ 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.
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();
// 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);
}
if (wp == curwin)
{
- if (get_scrolloff_value())
+ if (p_spsc && get_scrolloff_value())
update_topline();
curs_columns(FALSE); // validate w_wrow
}
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;