feat(window/ui): add splitkeep option (#19243)

vim-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.
29ab524358

vim-patch:9.0.0455: a few problems with 'splitscroll'

Problem:    A few problems with 'splitscroll'.
Solution:   Fix 'splitscroll' problems. (Luuk van Baal, closes vim/vim#11117)
5ed391708a

vim-patch:9.0.0461: 'scroll' is not always updated

Problem:    'scroll' is not always updated.
Solution:   Call win_init_size() at the right place.
470a14140b

vim-patch:9.0.0465: cursor moves when cmdwin is closed when 'splitscroll' is off

Problem:    Cursor moves when cmdwin is closed when 'splitscroll' is off.
Solution:   Temporarily set 'splitscroll' when jumping back to the original
            window. (closes vim/vim#11128)
e697d48890

vim-patch:9.0.0469: cursor moves if cmdwin is closed when 'splitscroll' is off

Problem:    Cursor moves if cmdwin is closed when 'splitscroll' is off.
Solution:   Skip win_fix_cursor if called when cmdwin is open or closing.
            (Luuk van Baal, closes vim/vim#11134)
3735f11050

vim-patch:9.0.0478: test for 'splitscroll' takes too much time

Problem:    Test for 'splitscroll' takes too much time.
Solution:   Only test some of the combinations. (Luuk van Baal, closes vim/vim#11139)
594f9e09cd

vim-patch:9.0.0486: text scrolled with 'nosplitscroll', autocmd win and help

Problem:    Text scrolled with 'nosplitscroll', autocmd win opened and help
            window closed.
Solution:   Skip win_fix_scroll() in more situations. (Luuk van Baal,
            closes vim/vim#11150)
d5bc762dea

vim-patch:9.0.0505: various problems with 'nosplitscroll'

Problem:    Various problems with 'nosplitscroll'.
Solution:   Fix 'nosplitscroll' problems. (Luuk van Baal, closes vim/vim#11166)
faf1d412f5

vim-patch:9.0.0555: scrolling with 'nosplitscroll' in callback changing curwin

Problem:    Scrolling with 'nosplitscroll' in callback changing curwin.
Solution:   Invalidate w_cline_row in the right place. (Luuk van Baal,
            closes vim/vim#11185)
20e58561ab

vim-patch:9.0.0603: with 'nosplitscroll' folds are not handled correctly

Problem:    With 'nosplitscroll' folds are not handled correctly.
Solution:   Take care of closed folds when moving the cursor. (Luuk van Baal,
            closes vim/vim#11234)
7c1cbb6cd4

vim-patch:9.0.0605: dump file missing

Problem:    Dump file missing.
Solution:   Add the missing dump file. (issue vim/vim#11234)
439a2ba174

vim-patch:9.0.0647: the 'splitscroll' option is not a good name

Problem:    The 'splitscroll' option is not a good name.
Solution:   Rename 'splitscroll' to 'splitkeep' and make it a string option,
            also supporting "topline". (Luuk van Baal, closes vim/vim#11258)
13ece2ae1d

vim-patch:9.0.0667: ml_get error when 'splitkeep' is "screen"

Problem:    ml_get error when 'splitkeep' is "screen". (Marius Gedminas)
Solution:   Check the botline is not too large. (Luuk van Baal,
            closes vim/vim#11293, closes vim/vim#11292)
346823d3e5
This commit is contained in:
luukvbaal 2022-10-06 08:57:52 +02:00 committed by GitHub
parent 4bfbac05c9
commit 5acf52e19b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 733 additions and 80 deletions

View File

@ -5961,6 +5961,22 @@ 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 below the current
one. |:split|
*'splitkeep'* *'spk'*
'splitkeep' 'spk' string (default "cursor")
global
The value of this option determines the scroll behavior when opening,
closing or resizing horizontal splits.
Possible values are:
cursor Keep the same relative cursor position.
screen Keep the text on the same screen line.
topline Keep the topline the same.
For the "screen" and "topline" values, the cursor position will be
changed when necessary. In this case, the jumplist will be populated
with the previous cursor position. For "screen", the text cannot always
be kept on the same screen line when 'wrap' is enabled.
*'splitright'* *'spr'* *'nosplitright'* *'nospr'*
'splitright' 'spr' boolean (default off)
global

View File

@ -890,6 +890,7 @@ Short explanation of each option: *option-list*
'spelloptions' 'spo' options for spell checking
'spellsuggest' 'sps' method(s) used to suggest spelling corrections
'splitbelow' 'sb' new window from split is below the current one
'splitkeep' 'spk' determines scroll behavior for split windows
'splitright' 'spr' new window is put right of the current one
'startofline' 'sol' commands move cursor to first non-blank in line
'statusline' 'stl' custom format for the status line

View File

@ -511,6 +511,8 @@ call append("$", "\tto a buffer")
call <SID>OptionG("swb", &swb)
call append("$", "splitbelow\ta new window is put below the current one")
call <SID>BinOptionG("sb", &sb)
call append("$", "splitkeep\ta determines scroll behavior for split windows")
call <SID>BinOptionG("spk", &spk)
call append("$", "splitright\ta new window is put right of the current one")
call <SID>BinOptionG("spr", &spr)
call append("$", "scrollbind\tthis window scrolls together with other bound windows")

View File

@ -1202,6 +1202,8 @@ struct window_S {
int w_winrow; // first row of window in screen
int w_height; // number of rows in window, excluding
// status/command line(s)
int w_prev_winrow; // previous winrow used for 'splitkeep'
int w_prev_height; // previous height used for 'splitkeep'
int w_status_height; // number of status lines (0 or 1)
int w_winbar_height; // number of window bars (0 or 1)
int w_wincol; // Leftmost column of window in screen.

View File

@ -2541,97 +2541,112 @@ int oneleft(void)
return OK;
}
/// @oaram upd_topline When true: update topline
/// Move the cursor up "n" lines in window "wp".
/// Takes care of closed folds.
/// Returns the new cursor line or zero for failure.
linenr_T cursor_up_inner(win_T *wp, long n)
{
linenr_T lnum = wp->w_cursor.lnum;
// This fails if the cursor is already in the first line.
if (lnum <= 1) {
return 0;
}
if (n >= lnum) {
lnum = 1;
} else if (hasAnyFolding(wp)) {
// Count each sequence of folded lines as one logical line.
// go to the start of the current fold
(void)hasFoldingWin(wp, lnum, &lnum, NULL, true, NULL);
while (n--) {
// move up one line
lnum--;
if (lnum <= 1) {
break;
}
// If we entered a fold, move to the beginning, unless in
// Insert mode or when 'foldopen' contains "all": it will open
// in a moment.
if (n > 0 || !((State & MODE_INSERT) || (fdo_flags & FDO_ALL))) {
(void)hasFoldingWin(wp, lnum, &lnum, NULL, true, NULL);
}
}
if (lnum < 1) {
lnum = 1;
}
} else {
lnum -= (linenr_T)n;
}
wp->w_cursor.lnum = lnum;
return lnum;
}
/// @param upd_topline When true: update topline
int cursor_up(long n, int upd_topline)
{
linenr_T lnum;
if (n > 0) {
lnum = curwin->w_cursor.lnum;
// This fails if the cursor is already in the first line.
if (lnum <= 1) {
return FAIL;
}
if (n >= lnum) {
lnum = 1;
} else if (hasAnyFolding(curwin)) {
// Count each sequence of folded lines as one logical line.
// go to the start of the current fold
(void)hasFolding(lnum, &lnum, NULL);
while (n--) {
// move up one line
lnum--;
if (lnum <= 1) {
break;
}
// If we entered a fold, move to the beginning, unless in
// Insert mode or when 'foldopen' contains "all": it will open
// in a moment.
if (n > 0 || !((State & MODE_INSERT) || (fdo_flags & FDO_ALL))) {
(void)hasFolding(lnum, &lnum, NULL);
}
}
if (lnum < 1) {
lnum = 1;
}
} else {
lnum -= (linenr_T)n;
}
curwin->w_cursor.lnum = lnum;
if (n > 0 && cursor_up_inner(curwin, n) == 0) {
return FAIL;
}
// try to advance to the column we want to be at
coladvance(curwin->w_curswant);
if (upd_topline) {
update_topline(curwin); // make sure curwin->w_topline is valid
update_topline(curwin); // make sure curwin->w_topline is valid
}
return OK;
}
/// Cursor down a number of logical lines.
///
/// Move the cursor down "n" lines in window "wp".
/// Takes care of closed folds.
/// Returns the new cursor line or zero for failure.
linenr_T cursor_down_inner(win_T *wp, long n)
{
linenr_T lnum = wp->w_cursor.lnum;
linenr_T line_count = wp->w_buffer->b_ml.ml_line_count;
// Move to last line of fold, will fail if it's the end-of-file.
(void)hasFoldingWin(wp, lnum, NULL, &lnum, true, NULL);
// This fails if the cursor is already in the last line.
if (lnum >= line_count) {
return FAIL;
}
if (lnum + n >= line_count) {
lnum = line_count;
} else if (hasAnyFolding(wp)) {
linenr_T last;
// count each sequence of folded lines as one logical line
while (n--) {
if (hasFoldingWin(wp, lnum, NULL, &last, true, NULL)) {
lnum = last + 1;
} else {
lnum++;
}
if (lnum >= line_count) {
break;
}
}
if (lnum > line_count) {
lnum = line_count;
}
} else {
lnum += (linenr_T)n;
}
wp->w_cursor.lnum = lnum;
return lnum;
}
/// @param upd_topline When true: update topline
int cursor_down(long n, int upd_topline)
{
linenr_T lnum;
if (n > 0) {
lnum = curwin->w_cursor.lnum;
// Move to last line of fold, will fail if it's the end-of-file.
(void)hasFolding(lnum, NULL, &lnum);
// This fails if the cursor is already in the last line.
if (lnum >= curbuf->b_ml.ml_line_count) {
return FAIL;
}
if (lnum + n >= curbuf->b_ml.ml_line_count) {
lnum = curbuf->b_ml.ml_line_count;
} else if (hasAnyFolding(curwin)) {
linenr_T last;
// count each sequence of folded lines as one logical line
while (n--) {
if (hasFolding(lnum, NULL, &last)) {
lnum = last + 1;
} else {
lnum++;
}
if (lnum >= curbuf->b_ml.ml_line_count) {
break;
}
}
if (lnum > curbuf->b_ml.ml_line_count) {
lnum = curbuf->b_ml.ml_line_count;
}
} else {
lnum += (linenr_T)n;
}
curwin->w_cursor.lnum = lnum;
if (n > 0 && cursor_down_inner(curwin, n) == 0) {
return FAIL;
}
// try to advance to the column we want to be at

View File

@ -4359,6 +4359,7 @@ static int open_cmdwin(void)
// First go back to the original window.
wp = curwin;
set_bufref(&bufref, curbuf);
skip_win_fix_cursor = true;
win_goto(old_curwin);
// win_goto() may trigger an autocommand that already closes the
@ -4375,6 +4376,7 @@ static int open_cmdwin(void)
// Restore window sizes.
win_size_restore(&winsizes);
skip_win_fix_cursor = false;
}
ga_clear(&winsizes);

View File

@ -1071,4 +1071,11 @@ EXTERN char windowsVersion[20] INIT(= { 0 });
EXTERN int exit_need_delay INIT(= 0);
///< Skip win_fix_cursor() call for 'splitkeep' when cmdwin is closed.
EXTERN bool skip_win_fix_cursor INIT(= false);
///< Skip win_fix_scroll() call for 'splitkeep' when closing tab page.
EXTERN bool skip_win_fix_scroll INIT(= false);
///< Skip update_topline() call while executing win_fix_scroll().
EXTERN bool skip_update_topline INIT(= false);
#endif // NVIM_GLOBALS_H

View File

@ -346,6 +346,7 @@ int main(int argc, char **argv)
ui_builtin_start();
}
TIME_MSG("done waiting for UI");
firstwin->w_prev_height = firstwin->w_height; // may have changed
}
// prepare screen now

View File

@ -140,6 +140,11 @@ void update_topline(win_T *wp)
long *so_ptr = wp->w_p_so >= 0 ? &wp->w_p_so : &p_so;
long save_so = *so_ptr;
// Cursor is updated instead when this is true for 'splitkeep'.
if (skip_update_topline) {
return;
}
// If there is no valid screen and when the window height is zero just use
// the cursor line.
if (!default_grid.chars || wp->w_height_inner == 0) {

View File

@ -729,6 +729,7 @@ EXTERN unsigned int tpf_flags; ///< flags from 'termpastefilter'
EXTERN char *p_tfu; ///< 'tagfunc'
EXTERN char *p_spc; ///< 'spellcapcheck'
EXTERN char *p_spf; ///< 'spellfile'
EXTERN char *p_spk; ///< 'splitkeep'
EXTERN char *p_spl; ///< 'spelllang'
EXTERN char *p_spo; // 'spelloptions'
EXTERN unsigned int spo_flags;

View File

@ -2356,6 +2356,13 @@ return {
varname='p_sb',
defaults={if_true=false}
},
{
full_name='splitkeep', abbreviation='spk',
short_desc=N_("determines scroll behavior for split windows"),
type='string', scope={'global'},
varname='p_spk',
defaults={if_true='cursor'}
},
{
full_name='splitright', abbreviation='spr',
short_desc=N_("new window is put right of the current one"),

View File

@ -75,6 +75,7 @@ static char *(p_ssop_values[]) = { "buffers", "winpos", "resize", "winsize", "lo
// Keep in sync with SWB_ flags in option_defs.h
static char *(p_swb_values[]) = { "useopen", "usetab", "split", "newtab", "vsplit", "uselast",
NULL };
static char *(p_spk_values[]) = { "cursor", "screen", "topline", NULL };
static char *(p_tc_values[]) = { "followic", "ignore", "match", "followscs", "smart", NULL };
static char *(p_ve_values[]) = { "block", "insert", "all", "onemore", "none", "NONE", NULL };
static char *(p_wop_values[]) = { "tagfile", "pum", NULL };
@ -1095,6 +1096,10 @@ char *did_set_string_option(int opt_idx, char **varp, char *oldval, char *errbuf
if (opt_strings_flags(p_swb, p_swb_values, &swb_flags, true) != OK) {
errmsg = e_invarg;
}
} else if (varp == &p_spk) { // 'splitkeep'
if (check_opt_strings(p_spk, p_spk_values, false) != OK) {
errmsg = e_invarg;
}
} else if (varp == &p_debug) { // 'debug'
if (check_opt_strings(p_debug, p_debug_values, true) != OK) {
errmsg = e_invarg;

View File

@ -1,5 +1,8 @@
" Tests for window cmd (:wincmd, :split, :vsplit, :resize and etc...)
source check.vim
source screendump.vim
func Test_window_cmd_ls0_with_split()
set ls=0
set splitbelow
@ -1486,5 +1489,258 @@ func Test_win_equal_last_status()
set laststatus&
endfunc
" Test "screen" and "cursor" values for 'splitkeep' with a sequence of
" split operations for various options: with and without a winbar,
" tabline, for each possible value of 'laststatus', 'scrolloff',
" 'equalalways', and with the cursor at the top, middle and bottom.
func Test_splitkeep_options()
" disallow window resizing
" let save_WS = &t_WS
" set t_WS=
let gui = has("gui_running")
inoremap <expr> c "<cmd>copen<bar>wincmd k<CR>"
for run in range(0, 20)
let &splitkeep = run > 10 ? 'topline' : 'screen'
let &scrolloff = (!(run % 4) ? 0 : run)
let &laststatus = (run % 3)
let &splitbelow = (run % 3)
let &equalalways = (run % 2)
" Nvim: both windows have a winbar after splitting
" let wsb = (run % 2) && &splitbelow
let wsb = 0
let tl = (gui ? 0 : ((run % 5) ? 1 : 0))
let pos = !(run % 3) ? 'H' : ((run % 2) ? 'M' : 'L')
tabnew | tabonly! | redraw
execute (run % 5) ? 'tabnew' : ''
" execute (run % 2) ? 'nnoremenu 1.10 WinBar.Test :echo' : ''
let &winbar = (run % 2) ? '%f' : ''
call setline(1, range(1, 256))
" No scroll for restore_snapshot
norm G
try
copen | close | colder
catch /E380/
endtry
call assert_equal(257 - winheight(0), line("w0"))
" No scroll for firstwin horizontal split
execute 'norm gg' . pos
split | redraw | wincmd k
call assert_equal(1, line("w0"))
call assert_equal(&scroll, winheight(0) / 2)
wincmd j
call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0"))
" No scroll when resizing windows
wincmd k | resize +2 | redraw
call assert_equal(1, line("w0"))
wincmd j
call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0"))
" No scroll when dragging statusline
call win_move_statusline(1, -3)
call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, 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(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0"))
set lines-=2
call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, 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(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0"))
wincmd k
call assert_equal(1, line("w0"))
" No scroll in windows split multiple times
vsplit | split | 4wincmd w
call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0"))
1wincmd w | quit | wincmd l | split
call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0"))
wincmd j
call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0"))
" No scroll in small window
2wincmd w | only | 5split | wincmd k
call assert_equal(1, line("w0"))
wincmd j
call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, 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 | redraw | split | split | quit | redraw
call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, 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(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl, line("w0"))
" No scroll when opening cmdwin, and no cursor move when closing cmdwin.
only | norm ggL
let curpos = getcurpos()
norm q:
call assert_equal(1, line("w0"))
call assert_equal(curpos, getcurpos())
" Scroll when cursor becomes invalid in insert mode
norm Lic
call assert_equal(curpos, getcurpos())
" No scroll when topline not equal to 1
only | execute "norm gg5\<C-e>" | split | wincmd k
call assert_equal(6, line("w0"))
wincmd j
call assert_equal(&spk == 'topline' ? 6 : 5 + win_screenpos(0)[0] - tl - wsb, line("w0"))
endfor
tabnew | tabonly! | %bwipeout!
iunmap c
set scrolloff&
set splitbelow&
set laststatus&
set equalalways&
set splitkeep&
" let &t_WS = save_WS
endfunc
function Test_splitkeep_cmdwin_cursor_position()
set splitkeep=screen
call setline(1, range(&lines))
" No scroll when cursor is at near bottom of window and cusor position
" recompution (done by line('w0') in this test) happens while in cmdwin.
normal! G
let firstline = line('w0')
autocmd CmdwinEnter * ++once autocmd WinEnter * ++once call line('w0')
execute "normal! q:\<C-w>q"
redraw!
call assert_equal(firstline, line('w0'))
" User script can change cursor position successfully while in cmdwin and it
" shouldn't be changed when closing cmdwin.
execute "normal! Gq:\<Cmd>call win_execute(winnr('#')->win_getid(), 'call cursor(1, 1)')\<CR>\<C-w>q"
call assert_equal(1, line('.'))
call assert_equal(1, col('.'))
execute "normal! Gq:\<Cmd>autocmd WinEnter * ++once call cursor(1, 1)\<CR>\<C-w>q"
call assert_equal(1, line('.'))
call assert_equal(1, col('.'))
%bwipeout!
set splitkeep&
endfunction
function Test_splitkeep_misc()
set splitkeep=screen
set splitbelow
call setline(1, range(1, &lines))
norm Gzz
let top = line('w0')
" No scroll when aucmd_win is opened
call setbufvar(bufnr("test", 1) , '&buftype', 'nofile')
call assert_equal(top, line('w0'))
" No scroll when tab is changed/closed
tab help | close
call assert_equal(top, line('w0'))
" No scroll when help is closed and buffer line count < window height
norm ggdG
call setline(1, range(1, &lines - 10))
norm G
let top = line('w0')
help | quit
call assert_equal(top, line('w0'))
" No error when resizing window in autocmd and buffer length changed
autocmd FileType qf exe "resize" line('$')
cexpr getline(1, '$')
copen
wincmd p
norm dd
cexpr getline(1, '$')
%bwipeout!
set splitbelow&
set splitkeep&
endfunc
function Test_splitkeep_callback()
CheckScreendump
let lines =<< trim END
set splitkeep=screen
call setline(1, range(&lines))
function C1(a, b)
split | wincmd p
endfunction
function C2(a, b)
close | split
endfunction
nn j <cmd>call job_start([&sh, &shcf, "true"], { 'exit_cb': 'C1' })<CR>
nn t <cmd>call popup_create(term_start([&sh, &shcf, "true"],
\ { 'hidden': 1, 'exit_cb': 'C2' }), {})<CR>
END
call writefile(lines, 'XTestSplitkeepCallback', 'D')
let buf = RunVimInTerminal('-S XTestSplitkeepCallback', #{rows: 8})
call term_sendkeys(buf, "j")
call VerifyScreenDump(buf, 'Test_splitkeep_callback_1', {})
call term_sendkeys(buf, ":quit\<CR>Ht")
call VerifyScreenDump(buf, 'Test_splitkeep_callback_2', {})
call term_sendkeys(buf, ":set sb\<CR>:quit\<CR>Gj")
call VerifyScreenDump(buf, 'Test_splitkeep_callback_3', {})
call term_sendkeys(buf, ":quit\<CR>Gt")
call VerifyScreenDump(buf, 'Test_splitkeep_callback_4', {})
endfunc
function Test_splitkeep_fold()
CheckScreendump
let lines =<< trim END
set splitkeep=screen
set foldmethod=marker
set number
let line = 1
for n in range(1, &lines)
call setline(line, ['int FuncName() {/*{{{*/', 1, 2, 3, 4, 5, '}/*}}}*/',
\ 'after fold'])
let line += 8
endfor
END
call writefile(lines, 'XTestSplitkeepFold', 'D')
let buf = RunVimInTerminal('-S XTestSplitkeepFold', #{rows: 10})
call term_sendkeys(buf, "L:wincmd s\<CR>")
call VerifyScreenDump(buf, 'Test_splitkeep_fold_1', {})
call term_sendkeys(buf, ":quit\<CR>")
call VerifyScreenDump(buf, 'Test_splitkeep_fold_2', {})
call term_sendkeys(buf, "H:below split\<CR>")
call VerifyScreenDump(buf, 'Test_splitkeep_fold_3', {})
call term_sendkeys(buf, ":wincmd k\<CR>:quit\<CR>")
call VerifyScreenDump(buf, 'Test_splitkeep_fold_4', {})
endfunction
" vim: shiftwidth=2 sts=2 expandtab

View File

@ -1487,6 +1487,8 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
// equalize the window sizes.
if (do_equal || dir != 0) {
win_equal(wp, true, (flags & WSP_VERT) ? (dir == 'v' ? 'b' : 'h') : (dir == 'h' ? 'b' : 'v'));
} else if (*p_spk != 'c' && wp != aucmd_win) {
win_fix_scroll(false);
}
// Don't change the window height/width to 'winheight' / 'winwidth' if a
@ -1558,6 +1560,12 @@ static void win_init(win_T *newp, win_T *oldp, int flags)
newp->w_prevdir = (oldp->w_prevdir == NULL)
? NULL : xstrdup(oldp->w_prevdir);
if (*p_spk != 'c') {
newp->w_botline = oldp->w_botline;
newp->w_prev_height = oldp->w_height;
newp->w_prev_winrow = oldp->w_winrow;
}
// copy tagstack and folds
for (i = 0; i < oldp->w_tagstacklen; i++) {
taggy_T *tag = &newp->w_tagstack[i];
@ -2066,6 +2074,9 @@ void win_equal(win_T *next_curwin, bool current, int dir)
win_equal_rec(next_curwin == NULL ? curwin : next_curwin, current,
topframe, dir, 0, tabline_height(),
Columns, topframe->fr_height);
if (*p_spk != 'c' && next_curwin != aucmd_win) {
win_fix_scroll(true);
}
}
/// Set a frame to a new position and height, spreading the available room
@ -2858,6 +2869,9 @@ int win_close(win_T *win, bool free_buf, bool force)
win_equal(curwin, curwin->w_frame->fr_parent == win_frame, dir);
} else {
(void)win_comp_pos();
if (*p_spk != 'c') {
win_fix_scroll(false);
}
}
}
@ -3944,6 +3958,7 @@ static void new_frame(win_T *wp)
void win_init_size(void)
{
firstwin->w_height = (int)ROWS_AVAIL;
firstwin->w_prev_height = (int)ROWS_AVAIL;
firstwin->w_height_inner = firstwin->w_height - firstwin->w_winbar_height;
firstwin->w_height_outer = firstwin->w_height;
firstwin->w_winrow_off = firstwin->w_winbar_height;
@ -4050,6 +4065,7 @@ int win_new_tabpage(int after, char_u *filename)
win_init_size();
firstwin->w_winrow = tabline_height();
firstwin->w_prev_winrow = firstwin->w_winrow;
win_comp_scroll(curwin);
newtp->tp_topframe = topframe;
@ -4407,6 +4423,7 @@ void goto_tabpage_tp(tabpage_T *tp, bool trigger_enter_autocmds, bool trigger_le
// Don't repeat a message in another tab page.
set_keep_msg(NULL, 0);
skip_win_fix_scroll = true;
if (tp != curtab && leave_tabpage(tp->tp_curwin->w_buffer,
trigger_leave_autocmds) == OK) {
if (valid_tabpage(tp)) {
@ -4417,6 +4434,7 @@ void goto_tabpage_tp(tabpage_T *tp, bool trigger_enter_autocmds, bool trigger_le
trigger_leave_autocmds);
}
}
skip_win_fix_scroll = false;
}
/// Go to the last accessed tab page, if there is one.
@ -4747,7 +4765,9 @@ static void win_enter_ext(win_T *const wp, const int flags)
// Might need to scroll the old window before switching, e.g., when the
// cursor was moved.
update_topline(curwin);
if (*p_spk == 'c') {
update_topline(curwin);
}
// may have to copy the buffer options when 'cpo' contains 'S'
if (wp->w_buffer != curbuf) {
@ -4764,7 +4784,11 @@ static void win_enter_ext(win_T *const wp, const int flags)
if (!virtual_active()) {
curwin->w_cursor.coladd = 0;
}
changed_line_abv_curs(); // assume cursor position needs updating
if (*p_spk == 'c') {
changed_line_abv_curs(); // assume cursor position needs updating
} else {
win_fix_cursor(true);
}
fix_current_dir();
@ -5238,6 +5262,10 @@ void win_new_screen_rows(void)
win_reconfig_floats(); // The size of floats might change
compute_cmdrow();
curtab->tp_ch_used = p_ch;
if (*p_spk != 'c' && !skip_win_fix_scroll) {
win_fix_scroll(true);
}
}
/// Called from win_new_screensize() after Columns changed.
@ -5435,6 +5463,11 @@ void win_setheight_win(int height, win_T *win)
curtab->tp_ch_used = p_ch;
msg_row = row;
msg_col = 0;
if (*p_spk != 'c') {
win_fix_scroll(true);
}
redraw_all_later(UPD_NOT_VALID);
redraw_cmdline = true;
}
@ -5921,6 +5954,11 @@ void win_drag_status_line(win_T *dragwin, int offset)
cmdline_row = row;
p_ch = MAX(Rows - cmdline_row, p_ch_was_zero ? 0 : 1);
curtab->tp_ch_used = p_ch;
if (*p_spk != 'c') {
win_fix_scroll(true);
}
redraw_all_later(UPD_SOME_VALID);
showmode();
}
@ -6043,6 +6081,99 @@ void set_fraction(win_T *wp)
}
}
/// Handle scroll position for 'splitkeep'. Replaces scroll_to_fraction()
/// call from win_set_inner_size(). Instead we iterate over all windows in a
/// tabpage and calculate the new scroll position.
/// TODO(luukvbaal): 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).
void win_fix_scroll(int resize)
{
linenr_T lnum;
skip_update_topline = true;
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
// Skip when window height has not changed or when floating.
if (!wp->w_floating && wp->w_height != wp->w_prev_height) {
// If window has moved update botline to keep the same screenlines.
if (*p_spk == 's' && wp->w_winrow != wp->w_prev_winrow
&& wp->w_botline - 1 <= wp->w_buffer->b_ml.ml_line_count) {
lnum = wp->w_cursor.lnum;
int diff = (wp->w_winrow - wp->w_prev_winrow)
+ (wp->w_height - wp->w_prev_height);
wp->w_cursor.lnum = wp->w_botline - 1;
// Add difference in height and row to botline.
if (diff > 0) {
cursor_down_inner(wp, diff);
} else {
cursor_up_inner(wp, -diff);
}
// 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;
} else if (wp == curwin) {
wp->w_valid &= ~VALID_CROW;
}
invalidate_botline_win(wp);
validate_botline(wp);
}
win_comp_scroll(wp);
wp->w_prev_height = wp->w_height;
wp->w_prev_winrow = wp->w_winrow;
}
skip_update_topline = false;
// Ensure cursor is valid when not in normal mode or when resized.
if (!(get_real_state() & (MODE_NORMAL|MODE_CMDLINE|MODE_TERMINAL))) {
win_fix_cursor(false);
} else if (resize) {
win_fix_cursor(true);
}
}
/// Make sure the cursor position is valid for 'splitkeep'.
/// 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)
{
win_T *wp = curwin;
long so = get_scrolloff_value(wp);
linenr_T nlnum = 0;
linenr_T lnum = wp->w_cursor.lnum;
if (wp->w_buffer->b_ml.ml_line_count < wp->w_height
|| skip_win_fix_cursor) {
return;
}
// Determine valid cursor range.
so = MIN(wp->w_height_inner / 2, so);
wp->w_cursor.lnum = wp->w_topline;
linenr_T top = cursor_down_inner(wp, so);
wp->w_cursor.lnum = wp->w_botline - 1;
linenr_T bot = cursor_up_inner(wp, so);
wp->w_cursor.lnum = lnum;
// Check if cursor position is above or below valid cursor range.
if (lnum > bot && (wp->w_botline - wp->w_buffer->b_ml.ml_line_count) != 1) {
nlnum = bot;
} else if (lnum < top && wp->w_topline != 1) {
nlnum = (so == wp->w_height / 2) ? bot : top;
}
if (nlnum) { // Cursor is invalid for current scroll position.
if (normal) {
// Save to jumplist and set cursor to avoid scrolling.
setmark('\'');
wp->w_cursor.lnum = nlnum;
} else {
// Scroll instead when not in normal mode.
wp->w_fraction = (nlnum == bot) ? FRACTION_MULT : 0;
scroll_to_fraction(wp, wp->w_prev_height);
validate_botline(curwin);
}
}
}
// 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
@ -6181,7 +6312,7 @@ void win_set_inner_size(win_T *wp, bool valid_cursor)
if (height != prev_height) {
if (height > 0 && valid_cursor) {
if (wp == curwin) {
if (wp == curwin && *p_spk == 'c') {
// w_wrow needs to be valid. When setting 'laststatus' this may
// call win_new_height() recursively.
validate_cursor();
@ -6198,7 +6329,7 @@ void win_set_inner_size(win_T *wp, bool valid_cursor)
// There is no point in adjusting the scroll position when exiting. Some
// values might be invalid.
if (!exiting && valid_cursor) {
if (valid_cursor && !exiting && *p_spk == 'c') {
scroll_to_fraction(wp, prev_height);
}
redraw_later(wp, UPD_SOME_VALID);
@ -6211,8 +6342,10 @@ void win_set_inner_size(win_T *wp, bool valid_cursor)
changed_line_abv_curs_win(wp);
invalidate_botline_win(wp);
if (wp == curwin) {
skip_update_topline = (*p_spk != 'c');
update_topline(wp);
curs_columns(wp, true); // validate w_wrow
skip_update_topline = false;
}
}
redraw_later(wp, UPD_NOT_VALID);
@ -6252,7 +6385,7 @@ void win_comp_scroll(win_T *wp)
{
const long old_w_p_scr = wp->w_p_scr;
wp->w_p_scr = wp->w_height / 2;
wp->w_p_scr = wp->w_height_inner / 2;
if (wp->w_p_scr == 0) {
wp->w_p_scr = 1;
}
@ -6606,6 +6739,10 @@ static void last_status_rec(frame_T *fr, bool statusline, bool is_stl_global)
}
comp_col();
}
// Set prev_height when difference is due to 'laststatus'.
if (abs(wp->w_height - wp->w_prev_height) == 1) {
wp->w_prev_height = wp->w_height;
}
} else if (wp->w_status_height != 0 && is_stl_global) {
// If statusline is global and the window has a statusline, replace it with a horizontal
// separator

View File

@ -0,0 +1,196 @@
local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
local clear = helpers.clear
local exec = helpers.exec
local exec_lua = helpers.exec_lua
local feed = helpers.feed
describe('splitkeep', function()
local screen = Screen.new()
before_each(function()
clear('--cmd', 'set splitkeep=screen')
screen:attach()
end)
-- oldtest: Test_splitkeep_callback()
it('does not scroll when split in callback', function()
exec([[
call setline(1, range(&lines))
function C1(a, b, c)
split | wincmd p
endfunction
function C2(a, b, c)
close | split
endfunction
]])
exec_lua([[
vim.api.nvim_set_keymap("n", "j", "", { callback = function()
vim.cmd("call jobstart([&sh, &shcf, 'true'], { 'on_exit': 'C1' })")
end
})]])
exec_lua([[
vim.api.nvim_set_keymap("n", "t", "", { callback = function()
vim.api.nvim_set_current_win(
vim.api.nvim_open_win(vim.api.nvim_create_buf(false, {}), false, {
width = 10,
relative = "cursor",
height = 4,
row = 0,
col = 0,
}))
vim.cmd("call termopen([&sh, &shcf, 'true'], { 'on_exit': 'C2' })")
end
})]])
feed('j')
screen:expect([[
0 |
1 |
2 |
3 |
4 |
5 |
[No Name] [+] |
^7 |
8 |
9 |
10 |
11 |
[No Name] [+] |
|
]])
feed(':quit<CR>Ht')
screen:expect([[
^0 |
1 |
2 |
3 |
4 |
5 |
[No Name] [+] |
7 |
8 |
9 |
10 |
11 |
[No Name] [+] |
:quit |
]])
feed(':set sb<CR>:quit<CR>Gj')
screen:expect([[
1 |
2 |
3 |
4 |
^5 |
[No Name] [+] |
7 |
8 |
9 |
10 |
11 |
12 |
[No Name] [+] |
:quit |
]])
feed(':quit<CR>Gt')
screen:expect([[
1 |
2 |
3 |
4 |
5 |
[No Name] [+] |
7 |
8 |
9 |
10 |
11 |
^12 |
[No Name] [+] |
:quit |
]])
end)
-- oldtest: Test_splitkeep_fold()
it('does not scroll when window has closed folds', function()
exec([[
set splitkeep=screen
set foldmethod=marker
set number
let line = 1
for n in range(1, &lines)
call setline(line, ['int FuncName() {/*{{{*/', 1, 2, 3, 4, 5, '}/*}}}*/',
\ 'after fold'])
let line += 8
endfor
]])
feed('L:wincmd s<CR>')
screen:expect([[
1 +-- 7 lines: int FuncName() {···················|
8 after fold |
9 +-- 7 lines: int FuncName() {···················|
16 after fold |
17 +-- 7 lines: int FuncName() {···················|
24 ^after fold |
[No Name] [+] |
32 after fold |
33 +-- 7 lines: int FuncName() {···················|
40 after fold |
41 +-- 7 lines: int FuncName() {···················|
48 after fold |
[No Name] [+] |
:wincmd s |
]])
feed(':quit<CR>')
screen:expect([[
1 +-- 7 lines: int FuncName() {···················|
8 after fold |
9 +-- 7 lines: int FuncName() {···················|
16 after fold |
17 +-- 7 lines: int FuncName() {···················|
24 after fold |
25 +-- 7 lines: int FuncName() {···················|
32 after fold |
33 +-- 7 lines: int FuncName() {···················|
40 after fold |
41 +-- 7 lines: int FuncName() {···················|
48 after fold |
49 ^+-- 7 lines: int FuncName() {···················|
:quit |
]])
feed('H:below split<CR>')
screen:expect([[
1 +-- 7 lines: int FuncName() {···················|
8 after fold |
9 +-- 7 lines: int FuncName() {···················|
16 after fold |
17 +-- 7 lines: int FuncName() {···················|
[No Name] [+] |
25 ^+-- 7 lines: int FuncName() {···················|
32 after fold |
33 +-- 7 lines: int FuncName() {···················|
40 after fold |
41 +-- 7 lines: int FuncName() {···················|
48 after fold |
[No Name] [+] |
:below split |
]])
feed(':wincmd k<CR>:quit<CR>')
screen:expect([[
1 +-- 7 lines: int FuncName() {···················|
8 after fold |
9 +-- 7 lines: int FuncName() {···················|
16 after fold |
17 +-- 7 lines: int FuncName() {···················|
24 after fold |
25 ^+-- 7 lines: int FuncName() {···················|
32 after fold |
33 +-- 7 lines: int FuncName() {···················|
40 after fold |
41 +-- 7 lines: int FuncName() {···················|
48 after fold |
49 +-- 7 lines: int FuncName() {···················|
:quit |
]])
end)
end)