patch 9.2.0356: Cannot apply 'scrolloff' context lines at end of file

Problem:  Cannot apply 'scrolloff' context lines at end of file
Solution: Add the 'scrolloffpad' option to keep 'scrolloff' context even
          when at the end of the file (McAuley Penney).

closes: #19040

Signed-off-by: McAuley Penney <jacobmpenney@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
McAuley Penney
2026-04-15 19:11:12 +00:00
committed by Christian Brabandt
parent 490b737f3e
commit a414630393
29 changed files with 1035 additions and 38 deletions
+21 -4
View File
@@ -1,4 +1,4 @@
*options.txt* For Vim version 9.2. Last change: 2026 Apr 14
*options.txt* For Vim version 9.2. Last change: 2026 Apr 15
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -7577,8 +7577,8 @@ A jump table for the options with a short description can be found at |Q_op|.
Minimal number of screen lines to keep above and below the cursor.
This will make some context visible around where you are working. If
you set it to a very large value (999) the cursor line will always be
in the middle of the window (except at the start or end of the file or
when long lines wrap).
in the middle of the window (except at the start or end of the file,
see 'scrolloffpad', or when long lines wrap).
After using the local value, go back the global value with one of
these two: >
setlocal scrolloff<
@@ -7586,7 +7586,24 @@ A jump table for the options with a short description can be found at |Q_op|.
< For scrolling horizontally see 'sidescrolloff'.
NOTE: This option is set to 0 when 'compatible' is set.
*'scrollopt'* *'sbo'*
*'scrolloffpad'* *'sop'*
'scrolloffpad' 'sop' number (default 0)
global or local to window |global-local|
When 'scrolloff' and 'scrolloffpad' are greater than zero, allow
the cursor to remain centered when at the end of the file.
Normally, 'scrolloff' will not keep the cursor centered at the
end of the file.
A value of 0 disables this feature. Any value above 0 enables it.
For a window-local value, -1 means to use the global value.
Values below -1 are invalid.
After using the local value, go back the global value with one of
these two: >
setlocal scrolloffpad<
setlocal scrolloffpad=-1
< *'scrollopt'* *'sbo'*
'scrollopt' 'sbo' string (default "ver,jump")
global
This is a comma-separated list of words that specifies how
+2 -1
View File
@@ -1,4 +1,4 @@
*quickref.txt* For Vim version 9.2. Last change: 2026 Apr 09
*quickref.txt* For Vim version 9.2. Last change: 2026 Apr 15
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -895,6 +895,7 @@ Short explanation of each option: *option-list*
'scrollfocus' 'scf' scroll wheel applies to window under pointer
'scrolljump' 'sj' minimum number of lines to scroll
'scrolloff' 'so' minimum nr. of lines above and below cursor
'scrolloffpad' 'sop' keep 'scrolloff' context at end of file
'scrollopt' 'sbo' how 'scrollbind' should behave
'sections' 'sect' nroff macros that separate sections
'secure' secure mode for reading .vimrc in current dir
+2
View File
@@ -953,6 +953,7 @@ $quote eval.txt /*$quote*
'scrollfocus' options.txt /*'scrollfocus'*
'scrolljump' options.txt /*'scrolljump'*
'scrolloff' options.txt /*'scrolloff'*
'scrolloffpad' options.txt /*'scrolloffpad'*
'scrollopt' options.txt /*'scrollopt'*
'scs' options.txt /*'scs'*
'sect' options.txt /*'sect'*
@@ -1011,6 +1012,7 @@ $quote eval.txt /*$quote*
'so' options.txt /*'so'*
'softtabstop' options.txt /*'softtabstop'*
'sol' options.txt /*'sol'*
'sop' options.txt /*'sop'*
'sourceany' vi_diff.txt /*'sourceany'*
'sp' options.txt /*'sp'*
'spc' options.txt /*'spc'*
+2 -1
View File
@@ -1,4 +1,4 @@
*version9.txt* For Vim version 9.2. Last change: 2026 Apr 14
*version9.txt* For Vim version 9.2. Last change: 2026 Apr 15
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -52654,6 +52654,7 @@ Options: ~
'modelinestrict' Only allow safe options to be set from a modeline.
'pumopt' Additional options for the popup menu
'scrolloffpad' Keep 'scrolloff' context at end of file
'statuslineopt' Extra window-local options for the 'statusline', to
configure the height.
't_BS' Begin synchronized update.
+3 -1
View File
@@ -1,7 +1,7 @@
" These commands create the option window.
"
" Maintainer: The Vim Project <https://github.com/vim/vim>
" Last Change: 2026 Apr 09
" Last Change: 2026 Apr 15
" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" If there already is an option window, jump to that one.
@@ -351,6 +351,8 @@ call append("$", "\t" .. s:local_to_window)
call <SID>BinOptionL("sms")
call <SID>AddOption("scrolloff", gettext("number of screen lines to show around the cursor"))
call append("$", " \tset so=" . &so)
call <SID>AddOption("scrolloffpad", gettext("keep 'scrolloff' context even at end of file"))
call append("$", " \tset sop=" . &sop)
call <SID>AddOption("wrap", gettext("long lines wrap"))
call append("$", "\t" .. s:local_to_window)
call <SID>BinOptionL("wrap")
+6 -6
View File
@@ -2,7 +2,7 @@
" Language: Vim script
" Maintainer: Hirohito Higashi <h.east.727 ATMARK gmail.com>
" Doug Kearns <dougkearns@gmail.com>
" Last Change: 2026 Apr 14
" Last Change: 2026 Apr 15
" Former Maintainer: Charles E. Campbell
" DO NOT CHANGE DIRECTLY.
@@ -69,8 +69,8 @@ syn keyword vimOption contained co columns com comments cms commentstring cp com
syn keyword vimOption contained efm errorformat ek esckeys ei eventignore eiw eventignorewin et expandtab ex exrc fenc fileencoding fencs fileencodings ff fileformat ffs fileformats fic fileignorecase ft filetype fcs fillchars ffu findfunc fixeol fixendofline fcl foldclose fdc foldcolumn fen foldenable fde foldexpr fdi foldignore fdl foldlevel fdls foldlevelstart fmr foldmarker fdm foldmethod fml foldminlines fdn foldnestmax fdo foldopen fdt foldtext fex formatexpr flp formatlistpat fo formatoptions fp formatprg fs fsync gd gdefault gfm grepformat gp grepprg gcr guicursor gfn guifont gfs guifontset gfw guifontwide ghr guiheadroom gli guiligatures go guioptions guipty gtl guitablabel gtt guitabtooltip hf helpfile hh helpheight hlg helplang hid hidden hl highlight skipwhite nextgroup=vimSetEqual,vimSetMod
syn keyword vimOption contained hi history hk hkmap hkp hkmapp hls hlsearch icon iconstring ic ignorecase imaf imactivatefunc imak imactivatekey imc imcmdline imd imdisable imi iminsert ims imsearch imsf imstatusfunc imst imstyle inc include inex includeexpr is incsearch inde indentexpr indk indentkeys inf infercase im insertmode isf isfname isi isident isk iskeyword isp isprint js joinspaces jop jumpoptions key kmp keymap km keymodel kpc keyprotocol kp keywordprg lmap langmap lm langmenu lnr langnoremap lrm langremap ls laststatus lz lazyredraw lhi lhistory lbr linebreak lines lsp linespace lisp lop lispoptions lw lispwords list lcs listchars lpl loadplugins luadll magic mef makeef menc makeencoding mp makeprg mps matchpairs mat matchtime mco maxcombine mfd maxfuncdepth skipwhite nextgroup=vimSetEqual,vimSetMod
syn keyword vimOption contained mmd maxmapdepth mm maxmem mmp maxmempattern mmt maxmemtot msc maxsearchcount mis menuitems mopt messagesopt msm mkspellmem ml modeline mle modelineexpr mls modelines mlst modelinestrict ma modifiable mod modified more mouse mousef mousefocus mh mousehide mousem mousemodel mousemev mousemoveevent mouses mouseshape mouset mousetime mzq mzquantum mzschemedll mzschemegcdll nf nrformats nu number nuw numberwidth ofu omnifunc odev opendevice opfunc operatorfunc ost osctimeoutlen pp packpath para paragraphs paste pt pastetoggle pex patchexpr pm patchmode pa path perldll pi preserveindent pvh previewheight pvp previewpopup pvw previewwindow pdev printdevice penc printencoding pexpr printexpr pfn printfont pheader printheader pmbcs printmbcharset skipwhite nextgroup=vimSetEqual,vimSetMod
syn keyword vimOption contained pmbfn printmbfont popt printoptions prompt pb pumborder ph pumheight pmw pummaxwidth pumopt pw pumwidth pythondll pythonhome pythonthreedll pythonthreehome pyx pyxversion qftf quickfixtextfunc qe quoteescape ro readonly rdt redrawtime re regexpengine rnu relativenumber remap rop renderoptions report rs restorescreen ri revins rl rightleft rlc rightleftcmd rubydll ru ruler ruf rulerformat rtp runtimepath scr scroll scb scrollbind scf scrollfocus sj scrolljump so scrolloff sbo scrollopt sect sections secure sel selection slm selectmode ssop sessionoptions sh shell shcf shellcmdflag sp shellpipe shq shellquote srr shellredir ssl shellslash stmp shelltemp st shelltype sxe shellxescape sxq shellxquote sr shiftround sw shiftwidth shm shortmess skipwhite nextgroup=vimSetEqual,vimSetMod
syn keyword vimOption contained sn shortname sbr showbreak sc showcmd sloc showcmdloc sft showfulltag sm showmatch smd showmode stal showtabline stpl showtabpanel ss sidescroll siso sidescrolloff scl signcolumn scs smartcase si smartindent sta smarttab sms smoothscroll sts softtabstop spell spc spellcapcheck spf spellfile spl spelllang spo spelloptions sps spellsuggest sb splitbelow spk splitkeep spr splitright sol startofline stl statusline stlo statuslineopt su suffixes sua suffixesadd swf swapfile sws swapsync swb switchbuf smc synmaxcol syn syntax tcl tabclose tal tabline tpm tabpagemax tpl tabpanel tplo tabpanelopt ts tabstop tbs tagbsearch tc tagcase tfu tagfunc tl taglength tr tagrelative tag tags tgst tagstack tcldll term tbidi termbidi tenc termencoding skipwhite nextgroup=vimSetEqual,vimSetMod
syn keyword vimOption contained pmbfn printmbfont popt printoptions prompt pb pumborder ph pumheight pmw pummaxwidth pumopt pw pumwidth pythondll pythonhome pythonthreedll pythonthreehome pyx pyxversion qftf quickfixtextfunc qe quoteescape ro readonly rdt redrawtime re regexpengine rnu relativenumber remap rop renderoptions report rs restorescreen ri revins rl rightleft rlc rightleftcmd rubydll ru ruler ruf rulerformat rtp runtimepath scr scroll scb scrollbind scf scrollfocus sj scrolljump so scrolloff sop scrolloffpad sbo scrollopt sect sections secure sel selection slm selectmode ssop sessionoptions sh shell shcf shellcmdflag sp shellpipe shq shellquote srr shellredir ssl shellslash stmp shelltemp st shelltype sxe shellxescape sxq shellxquote sr shiftround sw shiftwidth skipwhite nextgroup=vimSetEqual,vimSetMod
syn keyword vimOption contained shm shortmess sn shortname sbr showbreak sc showcmd sloc showcmdloc sft showfulltag sm showmatch smd showmode stal showtabline stpl showtabpanel ss sidescroll siso sidescrolloff scl signcolumn scs smartcase si smartindent sta smarttab sms smoothscroll sts softtabstop spell spc spellcapcheck spf spellfile spl spelllang spo spelloptions sps spellsuggest sb splitbelow spk splitkeep spr splitright sol startofline stl statusline stlo statuslineopt su suffixes sua suffixesadd swf swapfile sws swapsync swb switchbuf smc synmaxcol syn syntax tcl tabclose tal tabline tpm tabpagemax tpl tabpanel tplo tabpanelopt ts tabstop tbs tagbsearch tc tagcase tfu tagfunc tl taglength tr tagrelative tag tags tgst tagstack tcldll term tbidi termbidi tenc termencoding skipwhite nextgroup=vimSetEqual,vimSetMod
syn keyword vimOption contained tgc termguicolors trz termresize tsy termsync twk termwinkey twsl termwinscroll tws termwinsize twt termwintype terse ta textauto tx textmode tw textwidth tsr thesaurus tsrfu thesaurusfunc top tildeop to timeout tm timeoutlen title titlelen titleold titlestring tb toolbar tbis toolbariconsize ttimeout ttm ttimeoutlen tbi ttybuiltin tf ttyfast ttym ttymouse tsl ttyscroll tty ttytype udir undodir udf undofile ul undolevels ur undoreload uc updatecount ut updatetime vsts varsofttabstop vts vartabstop vbs verbose vfile verbosefile vdir viewdir vop viewoptions vi viminfo vif viminfofile ve virtualedit vb visualbell warn wiv weirdinvert ww whichwrap wc wildchar wcm wildcharm wig wildignore wic wildignorecase wmnu wildmenu wim wildmode skipwhite nextgroup=vimSetEqual,vimSetMod
syn keyword vimOption contained wop wildoptions wak winaltkeys wcr wincolor wi window wfb winfixbuf wfh winfixheight wfw winfixwidth wh winheight whl winhighlight wmh winminheight wmw winminwidth winptydll wiw winwidth wse wlseat wst wlsteal wtm wltimeoutlen wrap wm wrapmargin ws wrapscan write wa writeany wb writebackup wd writedelay xtermcodes skipwhite nextgroup=vimSetEqual,vimSetMod
@@ -108,9 +108,9 @@ syn keyword vimOptionVarName contained co columns com comments cms commentstring
syn keyword vimOptionVarName contained efm errorformat ek esckeys ei eventignore eiw eventignorewin et expandtab ex exrc fenc fileencoding fencs fileencodings ff fileformat ffs fileformats fic fileignorecase ft filetype fcs fillchars ffu findfunc fixeol fixendofline fcl foldclose fdc foldcolumn fen foldenable fde foldexpr fdi foldignore fdl foldlevel fdls foldlevelstart fmr foldmarker fdm foldmethod fml foldminlines fdn foldnestmax fdo foldopen fdt foldtext fex formatexpr flp formatlistpat fo formatoptions fp formatprg fs fsync gd gdefault gfm grepformat gp grepprg gcr guicursor gfn guifont gfs guifontset gfw guifontwide ghr guiheadroom gli guiligatures go guioptions guipty gtl guitablabel gtt guitabtooltip hf helpfile hh helpheight hlg helplang hid hidden hl highlight
syn keyword vimOptionVarName contained hi history hk hkmap hkp hkmapp hls hlsearch icon iconstring ic ignorecase imaf imactivatefunc imak imactivatekey imc imcmdline imd imdisable imi iminsert ims imsearch imsf imstatusfunc imst imstyle inc include inex includeexpr is incsearch inde indentexpr indk indentkeys inf infercase im insertmode isf isfname isi isident isk iskeyword isp isprint js joinspaces jop jumpoptions key kmp keymap km keymodel kpc keyprotocol kp keywordprg lmap langmap lm langmenu lnr langnoremap lrm langremap ls laststatus lz lazyredraw lhi lhistory lbr linebreak lines lsp linespace lisp lop lispoptions lw lispwords list lcs listchars lpl loadplugins luadll magic mef makeef menc makeencoding mp makeprg mps matchpairs mat matchtime mco maxcombine
syn keyword vimOptionVarName contained mfd maxfuncdepth mmd maxmapdepth mm maxmem mmp maxmempattern mmt maxmemtot msc maxsearchcount mis menuitems mopt messagesopt msm mkspellmem ml modeline mle modelineexpr mls modelines mlst modelinestrict ma modifiable mod modified more mouse mousef mousefocus mh mousehide mousem mousemodel mousemev mousemoveevent mouses mouseshape mouset mousetime mzq mzquantum mzschemedll mzschemegcdll nf nrformats nu number nuw numberwidth ofu omnifunc odev opendevice opfunc operatorfunc ost osctimeoutlen pp packpath para paragraphs paste pt pastetoggle pex patchexpr pm patchmode pa path perldll pi preserveindent pvh previewheight pvp previewpopup pvw previewwindow pdev printdevice penc printencoding pexpr printexpr pfn printfont pheader printheader
syn keyword vimOptionVarName contained pmbcs printmbcharset pmbfn printmbfont popt printoptions prompt pb pumborder ph pumheight pmw pummaxwidth pumopt pw pumwidth pythondll pythonhome pythonthreedll pythonthreehome pyx pyxversion qftf quickfixtextfunc qe quoteescape ro readonly rdt redrawtime re regexpengine rnu relativenumber remap rop renderoptions report rs restorescreen ri revins rl rightleft rlc rightleftcmd rubydll ru ruler ruf rulerformat rtp runtimepath scr scroll scb scrollbind scf scrollfocus sj scrolljump so scrolloff sbo scrollopt sect sections secure sel selection slm selectmode ssop sessionoptions sh shell shcf shellcmdflag sp shellpipe shq shellquote srr shellredir ssl shellslash stmp shelltemp st shelltype sxe shellxescape sxq shellxquote sr shiftround
syn keyword vimOptionVarName contained sw shiftwidth shm shortmess sn shortname sbr showbreak sc showcmd sloc showcmdloc sft showfulltag sm showmatch smd showmode stal showtabline stpl showtabpanel ss sidescroll siso sidescrolloff scl signcolumn scs smartcase si smartindent sta smarttab sms smoothscroll sts softtabstop spell spc spellcapcheck spf spellfile spl spelllang spo spelloptions sps spellsuggest sb splitbelow spk splitkeep spr splitright sol startofline stl statusline stlo statuslineopt su suffixes sua suffixesadd swf swapfile sws swapsync swb switchbuf smc synmaxcol syn syntax tcl tabclose tal tabline tpm tabpagemax tpl tabpanel tplo tabpanelopt ts tabstop tbs tagbsearch tc tagcase tfu tagfunc tl taglength tr tagrelative tag tags tgst tagstack tcldll
syn keyword vimOptionVarName contained term tbidi termbidi tenc termencoding tgc termguicolors trz termresize tsy termsync twk termwinkey twsl termwinscroll tws termwinsize twt termwintype terse ta textauto tx textmode tw textwidth tsr thesaurus tsrfu thesaurusfunc top tildeop to timeout tm timeoutlen title titlelen titleold titlestring tb toolbar tbis toolbariconsize ttimeout ttm ttimeoutlen tbi ttybuiltin tf ttyfast ttym ttymouse tsl ttyscroll tty ttytype udir undodir udf undofile ul undolevels ur undoreload uc updatecount ut updatetime vsts varsofttabstop vts vartabstop vbs verbose vfile verbosefile vdir viewdir vop viewoptions vi viminfo vif viminfofile ve virtualedit vb visualbell warn wiv weirdinvert ww whichwrap wc wildchar wcm wildcharm wig wildignore
syn keyword vimOptionVarName contained pmbcs printmbcharset pmbfn printmbfont popt printoptions prompt pb pumborder ph pumheight pmw pummaxwidth pumopt pw pumwidth pythondll pythonhome pythonthreedll pythonthreehome pyx pyxversion qftf quickfixtextfunc qe quoteescape ro readonly rdt redrawtime re regexpengine rnu relativenumber remap rop renderoptions report rs restorescreen ri revins rl rightleft rlc rightleftcmd rubydll ru ruler ruf rulerformat rtp runtimepath scr scroll scb scrollbind scf scrollfocus sj scrolljump so scrolloff sop scrolloffpad sbo scrollopt sect sections secure sel selection slm selectmode ssop sessionoptions sh shell shcf shellcmdflag sp shellpipe shq shellquote srr shellredir ssl shellslash stmp shelltemp st shelltype sxe shellxescape sxq shellxquote
syn keyword vimOptionVarName contained sr shiftround sw shiftwidth shm shortmess sn shortname sbr showbreak sc showcmd sloc showcmdloc sft showfulltag sm showmatch smd showmode stal showtabline stpl showtabpanel ss sidescroll siso sidescrolloff scl signcolumn scs smartcase si smartindent sta smarttab sms smoothscroll sts softtabstop spell spc spellcapcheck spf spellfile spl spelllang spo spelloptions sps spellsuggest sb splitbelow spk splitkeep spr splitright sol startofline stl statusline stlo statuslineopt su suffixes sua suffixesadd swf swapfile sws swapsync swb switchbuf smc synmaxcol syn syntax tcl tabclose tal tabline tpm tabpagemax tpl tabpanel tplo tabpanelopt ts tabstop tbs tagbsearch tc tagcase tfu tagfunc tl taglength tr tagrelative tag tags tgst tagstack
syn keyword vimOptionVarName contained tcldll term tbidi termbidi tenc termencoding tgc termguicolors trz termresize tsy termsync twk termwinkey twsl termwinscroll tws termwinsize twt termwintype terse ta textauto tx textmode tw textwidth tsr thesaurus tsrfu thesaurusfunc top tildeop to timeout tm timeoutlen title titlelen titleold titlestring tb toolbar tbis toolbariconsize ttimeout ttm ttimeoutlen tbi ttybuiltin tf ttyfast ttym ttymouse tsl ttyscroll tty ttytype udir undodir udf undofile ul undolevels ur undoreload uc updatecount ut updatetime vsts varsofttabstop vts vartabstop vbs verbose vfile verbosefile vdir viewdir vop viewoptions vi viminfo vif viminfofile ve virtualedit vb visualbell warn wiv weirdinvert ww whichwrap wc wildchar wcm wildcharm wig wildignore
syn keyword vimOptionVarName contained wic wildignorecase wmnu wildmenu wim wildmode wop wildoptions wak winaltkeys wcr wincolor wi window wfb winfixbuf wfh winfixheight wfw winfixwidth wh winheight whl winhighlight wmh winminheight wmw winminwidth winptydll wiw winwidth wse wlseat wst wlsteal wtm wltimeoutlen wrap wm wrapmargin ws wrapscan write wa writeany wb writebackup wd writedelay xtermcodes
" GEN_SYN_VIM: vimOption term output code variable, START_STR='syn keyword vimOptionVarName contained', END_STR=''
syn keyword vimOptionVarName contained t_AB t_AF t_AU t_AL t_al t_bc t_BE t_BD t_cd t_ce t_Ce t_CF t_cl t_cm t_Co t_CS t_Cs t_cs t_CV t_da t_db t_DL t_dl t_ds t_Ds t_EC t_EI t_fs t_fd t_fe t_GP t_IE t_IS t_ke t_ks t_le t_mb t_md t_me t_mr t_ms t_nd t_op t_RF t_RB t_RC t_RI t_Ri t_RK t_RS t_RT t_RV t_Sb t_SC t_se t_Sf t_SH t_SI t_Si t_so t_SR t_sr t_ST t_Te t_te t_TE t_ti t_TI t_Ts t_ts t_u7 t_ue t_us t_Us t_ut t_vb t_ve t_vi t_VS t_vs t_WP t_WS t_XM t_xn t_xs t_ZH t_ZR t_8f t_8b t_8u t_xo t_BS t_ES
+63 -17
View File
@@ -279,6 +279,31 @@ update_topline_redraw(void)
update_screen(0);
}
/*
* Return true when 'scrolloffpad' may augment 'scrolloff'.
* This only applies to automatic cursor visibility correction.
* For now 'scrolloffpad' is treated as boolean: 0 disables, > 0 enables.
*/
static bool
use_scrolloffpad(void)
{
return get_scrolloff_value() > 0 && get_scrolloffpad_value() > 0;
}
/*
* Return TRUE when there are not enough real buffer lines below "lnum" to
* satisfy the requested "so" context.
*/
static bool
scrolloffpad_eof_pressure(linenr_T lnum, long so)
{
if (!use_scrolloffpad() || so <= 0)
return false;
// Use subtraction to avoid signed overflow in "lnum + so".
return lnum > curbuf->b_ml.ml_line_count - so;
}
/*
* Update curwin->w_topline to move the cursor onto the screen.
*/
@@ -295,6 +320,7 @@ update_topline(void)
int check_botline = FALSE;
long *so_ptr = curwin->w_p_so >= 0 ? &curwin->w_p_so : &p_so;
int save_so = *so_ptr;
bool eof_pressure;
// Cursor is updated instead when this is TRUE for 'splitkeep'.
if (skip_update_topline)
@@ -318,6 +344,7 @@ update_topline(void)
// When dragging with the mouse, don't scroll that quickly
if (mouse_dragging > 0)
*so_ptr = mouse_dragging - 1;
eof_pressure = scrolloffpad_eof_pressure(curwin->w_cursor.lnum, *so_ptr);
linenr_T old_topline = curwin->w_topline;
#ifdef FEAT_DIFF
@@ -405,11 +432,21 @@ update_topline(void)
// cursor in the middle of the window. Otherwise put the cursor
// near the top of the window.
if (n >= halfheight)
scroll_cursor_halfway(FALSE, FALSE);
{
if (eof_pressure)
scroll_cursor_halfway(TRUE, TRUE);
else
scroll_cursor_halfway(FALSE, FALSE);
}
else
{
scroll_cursor_top(scrolljump_value(), FALSE);
check_botline = TRUE;
if (eof_pressure)
scroll_cursor_halfway(TRUE, TRUE);
else
{
scroll_cursor_top(scrolljump_value(), FALSE);
check_botline = TRUE;
}
}
}
@@ -436,7 +473,7 @@ update_topline(void)
if (!(curwin->w_valid & VALID_BOTLINE_AP))
validate_botline();
if (curwin->w_botline <= curbuf->b_ml.ml_line_count)
if (curwin->w_botline <= curbuf->b_ml.ml_line_count || use_scrolloffpad())
{
if (curwin->w_cursor.lnum < curwin->w_botline)
{
@@ -452,7 +489,7 @@ update_topline(void)
// Cursor is (a few lines) above botline, check if there are
// 'scrolloff' window lines below the cursor. If not, need to
// scroll.
n = curwin->w_empty_rows;
n = eof_pressure ? 0 : curwin->w_empty_rows;
loff.lnum = curwin->w_cursor.lnum;
#ifdef FEAT_FOLDING
// In a fold go to its last line.
@@ -473,14 +510,14 @@ update_topline(void)
if (n >= *so_ptr)
break;
botline_forw(&loff);
}
if (n >= *so_ptr && !eof_pressure)
// sufficient context, no need to scroll
check_botline = FALSE;
}
if (n >= *so_ptr)
else
// sufficient context, no need to scroll
check_botline = FALSE;
}
else
// sufficient context, no need to scroll
check_botline = FALSE;
}
if (check_botline)
{
@@ -506,9 +543,14 @@ update_topline(void)
line_count = curwin->w_cursor.lnum - curwin->w_botline
+ 1 + *so_ptr;
if (line_count <= curwin->w_height + 1)
scroll_cursor_bot(scrolljump_value(), FALSE);
{
if (eof_pressure)
scroll_cursor_halfway(TRUE, TRUE);
else
scroll_cursor_bot(scrolljump_value(), FALSE);
}
else
scroll_cursor_halfway(FALSE, FALSE);
scroll_cursor_halfway(eof_pressure, eof_pressure);
}
}
}
@@ -2350,9 +2392,11 @@ botline_forw(lineoff_T *lp)
else
#ifdef FEAT_FOLDING
if (hasFolding(lp->lnum, NULL, &lp->lnum))
// Add a closed fold
lp->height = 1;
else
{
// Add a closed fold.
lp->height = 1;
}
else
#endif
lp->height = PLINES_NOFILL(lp->lnum);
}
@@ -2804,8 +2848,9 @@ scroll_cursor_bot(int min_scroll, int set_topbot)
* Scroll up if the cursor is off the bottom of the screen a bit.
* Otherwise put it at 1/2 of the screen.
*/
bool eof_pressure = scrolloffpad_eof_pressure(cln, so);
if (line_count >= curwin->w_height && line_count > min_scroll)
scroll_cursor_halfway(FALSE, TRUE);
scroll_cursor_halfway(eof_pressure, TRUE);
else if (line_count > 0)
{
if (do_sms)
@@ -3046,7 +3091,8 @@ cursor_correct(void)
if (curwin->w_botline == curbuf->b_ml.ml_line_count + 1
&& mouse_dragging == 0)
{
below_wanted = 0;
if (!use_scrolloffpad())
below_wanted = 0;
max_off = (curwin->w_height - 1) / 2;
if (above_wanted > max_off)
above_wanted = max_off;
+34 -4
View File
@@ -804,8 +804,9 @@ set_option_default(
long def_val = (long)(long_i)options[opt_idx].def_val[dvi];
if ((long *)varp == &curwin->w_p_so
|| (long *)varp == &curwin->w_p_siso)
// 'scrolloff' and 'sidescrolloff' local values have a
|| (long *)varp == &curwin->w_p_siso
|| (long *)varp == &curwin->w_p_sop)
// 'scrolloff', 'sidescrolloff', and 'scrolloffpad' local values have a
// different default value than the global default.
*(long *)varp = -1;
else
@@ -2581,8 +2582,9 @@ do_set_option_numeric(
value = NO_LOCAL_UNDOLEVEL;
else if (opt_flags == OPT_LOCAL
&& ((long *)varp == &curwin->w_p_siso
|| (long *)varp == &curwin->w_p_so))
// for 'scrolloff'/'sidescrolloff' -1 means using the global value
|| (long *)varp == &curwin->w_p_so
|| (long *)varp == &curwin->w_p_sop))
// for 'scrolloff'/'sidescrolloff'/'scrolloffpad' -1 means using the global value
value = -1;
else
value = *(long *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL);
@@ -2624,6 +2626,12 @@ do_set_option_numeric(
else if (op == OP_REMOVING)
value = *(long *)varp - value;
if ((long *)varp == &curwin->w_p_sop && value < -1)
{
errmsg = e_invalid_argument;
goto skip;
}
errmsg = set_num_option(opt_idx, varp, value, errbuf, errbuflen,
opt_flags);
@@ -5401,6 +5409,11 @@ check_num_option_bounds(
errmsg = e_argument_must_be_positive;
p_so = 0;
}
if (p_sop < 0 && full_screen)
{
errmsg = e_invalid_argument;
p_sop = 0;
}
if (p_siso < 0 && full_screen)
{
errmsg = e_argument_must_be_positive;
@@ -6818,6 +6831,9 @@ unset_global_local_option(char_u *name, void *from)
case PV_SO:
curwin->w_p_so = -1;
break;
case PV_SOP:
curwin->w_p_sop = -1;
break;
# ifdef FEAT_FIND_ID
case PV_DEF:
clear_string_option(&buf->b_p_def);
@@ -6959,6 +6975,7 @@ get_varp_scope(struct vimoption *p, int scope)
case PV_TC: return (char_u *)&(curbuf->b_p_tc);
case PV_SISO: return (char_u *)&(curwin->w_p_siso);
case PV_SO: return (char_u *)&(curwin->w_p_so);
case PV_SOP: return (char_u *)&(curwin->w_p_sop);
#ifdef FEAT_FIND_ID
case PV_DEF: return (char_u *)&(curbuf->b_p_def);
case PV_INC: return (char_u *)&(curbuf->b_p_inc);
@@ -7044,6 +7061,8 @@ get_varp(struct vimoption *p)
? (char_u *)&(curwin->w_p_siso) : p->var;
case PV_SO: return curwin->w_p_so >= 0
? (char_u *)&(curwin->w_p_so) : p->var;
case PV_SOP: return curwin->w_p_sop != -1
? (char_u *)&(curwin->w_p_sop) : p->var;
#ifdef FEAT_FIND_ID
case PV_DEF: return *curbuf->b_p_def != NUL
? (char_u *)&(curbuf->b_p_def) : p->var;
@@ -7445,6 +7464,7 @@ copy_winopt(winopt_T *from, winopt_T *to)
to->wo_crb_save = from->wo_crb_save;
to->wo_siso = from->wo_siso;
to->wo_so = from->wo_so;
to->wo_sop = from->wo_sop;
#ifdef FEAT_SPELL
to->wo_spell = from->wo_spell;
#endif
@@ -9072,6 +9092,16 @@ get_scrolloff_value(void)
return curwin->w_p_so < 0 ? p_so : curwin->w_p_so;
}
/*
* Return the effective 'scrolloffpad' value for the current window, using the
* global value when appropriate.
*/
long
get_scrolloffpad_value(void)
{
return curwin->w_p_sop == -1 ? p_sop : curwin->w_p_sop;
}
/*
* Return the effective 'sidescrolloff' value for the current window, using the
* global value when appropriate.
+2
View File
@@ -901,6 +901,7 @@ EXTERN long p_sj; // 'scrolljump'
EXTERN int p_scf; // 'scrollfocus'
#endif
EXTERN long p_so; // 'scrolloff'
EXTERN long p_sop; // 'scrolloffpad'
EXTERN char_u *p_sbo; // 'scrollopt'
EXTERN char_u *p_sections; // 'sections'
EXTERN int p_secure; // 'secure'
@@ -1378,6 +1379,7 @@ enum
, WV_SMS
, WV_SISO
, WV_SO
, WV_SOP
#ifdef FEAT_SPELL
, WV_SPELL
#endif
+4
View File
@@ -209,6 +209,7 @@
#define PV_SMS OPT_WIN(WV_SMS)
#define PV_SISO OPT_BOTH(OPT_WIN(WV_SISO))
#define PV_SO OPT_BOTH(OPT_WIN(WV_SO))
#define PV_SOP OPT_BOTH(OPT_WIN(WV_SOP))
#ifdef FEAT_SPELL
# define PV_SPELL OPT_WIN(WV_SPELL)
#endif
@@ -2266,6 +2267,9 @@ static struct vimoption options[] =
{"scrolloff", "so", P_NUM|P_VI_DEF|P_VIM|P_RALL,
(char_u *)&p_so, PV_SO, NULL, NULL,
{(char_u *)0L, (char_u *)0L} SCTX_INIT},
{"scrolloffpad", "sop", P_NUM|P_VI_DEF|P_VIM|P_RALL,
(char_u *)&p_sop, PV_SOP, NULL, NULL,
{(char_u *)0L, (char_u *)0L} SCTX_INIT},
{"scrollopt", "sbo", P_STRING|P_VI_DEF|P_ONECOMMA|P_NODUP,
(char_u *)&p_sbo, PV_NONE, did_set_scrollopt, expand_set_scrollopt,
{(char_u *)"ver,jump", (char_u *)0L}
+4 -1
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Vim\n"
"Report-Msgid-Bugs-To: vim-dev@vim.org\n"
"POT-Creation-Date: 2026-04-09 20:33+0000\n"
"POT-Creation-Date: 2026-04-15 19:10+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -9383,6 +9383,9 @@ msgstr ""
msgid "number of screen lines to show around the cursor"
msgstr ""
msgid "keep 'scrolloff' context even at end of file"
msgstr ""
msgid "long lines wrap"
msgstr ""
+1
View File
@@ -2399,6 +2399,7 @@ popup_create(typval_T *argvars, typval_T *rettv, create_type_T type)
}
wp->w_p_wrap = TRUE; // 'wrap' is default on
wp->w_p_so = 0; // 'scrolloff' zero
wp->w_p_sop = 0; // 'scrolloffpad' zero
if (tp != NULL)
{
+1
View File
@@ -145,6 +145,7 @@ int option_was_set(char_u *name);
int reset_option_was_set(char_u *name);
int can_bs(int what);
long get_scrolloff_value(void);
long get_scrolloffpad_value(void);
long get_sidescrolloff_value(void);
unsigned int get_bkc_flags(buf_T *buf);
char_u *get_flp_value(buf_T *buf);
+2
View File
@@ -349,6 +349,8 @@ typedef struct
#define w_p_siso w_onebuf_opt.wo_siso // 'sidescrolloff' local value
long wo_so;
#define w_p_so w_onebuf_opt.wo_so // 'scrolloff' local value
long wo_sop;
#define w_p_sop w_onebuf_opt.wo_sop // 'scrolloffpad' local value
#ifdef FEAT_TERMINAL
char_u *wo_twk;
# define w_p_twk w_onebuf_opt.wo_twk // 'termwinkey'
+20
View File
@@ -0,0 +1,20 @@
|l+0&#ffffff0|i|n|e| |9|1| @70
|l|i|n|e| |9|2| @70
|l|i|n|e| |9|3| @70
|l|i|n|e| |9|4| @70
|l|i|n|e| |9|5| @70
|l|i|n|e| |9|6| @70
|l|i|n|e| |9|7| @70
|l|i|n|e| |9|8| @70
|l|i|n|e| |9@1| @70
>l|i|n|e| |1|0@1| @69
|~+0#4040ff13&| @76
|~| @76
|~| @76
|~| @76
|~| @76
|~| @76
|~| @76
|~| @76
|~| @76
| +0#0000000&@59|1|0@1|,|1| @8|B|o|t|
+20
View File
@@ -0,0 +1,20 @@
>l+0&#ffffff0|i|n|e| |1| @71
|l|i|n|e| |2| @71
|l|i|n|e| |3| @71
|l|i|n|e| |4| @71
|l|i|n|e| |5| @71
|l|i|n|e| |6| @71
|l|i|n|e| |7| @71
|l|i|n|e| |8| @71
|l|i|n|e| |9| @71
|l|i|n|e| |1|0| @70
|l|i|n|e| |1@1| @70
|l|i|n|e| |1|2| @70
|l|i|n|e| |1|3| @70
|l|i|n|e| |1|4| @70
|l|i|n|e| |1|5| @70
|l|i|n|e| |1|6| @70
|l|i|n|e| |1|7| @70
|l|i|n|e| |1|8| @70
|l|i|n|e| |1|9| @70
@60|1|,|1| @10|T|o|p|
+20
View File
@@ -0,0 +1,20 @@
|l+0&#ffffff0|i|n|e| |8|2| @70
|l|i|n|e| |8|3| @70
|l|i|n|e| |8|4| @70
|l|i|n|e| |8|5| @70
|l|i|n|e| |8|6| @70
|l|i|n|e| |8|7| @70
|l|i|n|e| |8@1| @70
|l|i|n|e| |8|9| @70
|l|i|n|e| |9|0| @70
|l|i|n|e| |9|1| @70
|l|i|n|e| |9|2| @70
|l|i|n|e| |9|3| @70
|l|i|n|e| |9|4| @70
|l|i|n|e| |9|5| @70
|l|i|n|e| |9|6| @70
|l|i|n|e| |9|7| @70
|l|i|n|e| |9|8| @70
|l|i|n|e| |9@1| @70
>l|i|n|e| |1|0@1| @69
@60|1|0@1|,|1| @8|B|o|t|
+20
View File
@@ -0,0 +1,20 @@
|l+0&#ffffff0|i|n|e| |1@2| @69
|l|i|n|e| |1@1|2| @69
|l|i|n|e| |1@1|3| @69
|l|i|n|e| |1@1|4| @69
|l|i|n|e| |1@1|5| @69
|l|i|n|e| |1@1|6| @69
|l|i|n|e| |1@1|7| @69
|l|i|n|e| |1@1|8| @69
|l|i|n|e| |1@1|9| @69
>l|i|n|e| |1|2|0| @69
|~+0#4040ff13&| @76
|~| @76
|~| @76
|~| @76
|~| @76
|~| @76
|~| @76
|~| @76
|~| @76
| +0#0000000&@59|1|2|0|,|1| @8|B|o|t|
+20
View File
@@ -0,0 +1,20 @@
|l+0&#ffffff0|i|n|e| |5|1| @70
|l|i|n|e| |5|2| @70
|l|i|n|e| |5|3| @70
|l|i|n|e| |5|4| @70
|l|i|n|e| |5@1| @70
|l|i|n|e| |5|6| @70
|l|i|n|e| |5|7| @70
|l|i|n|e| |5|8| @70
|l|i|n|e| |5|9| @70
>++0#0000e05#a8a8a8255|-@1| |5|1| |l|i|n|e|s|:| |l|i|n|e| |6|0|-@56
|l+0#0000000#ffffff0|i|n|e| |1@2| @69
|l|i|n|e| |1@1|2| @69
|l|i|n|e| |1@1|3| @69
|l|i|n|e| |1@1|4| @69
|l|i|n|e| |1@1|5| @69
|l|i|n|e| |1@1|6| @69
|l|i|n|e| |1@1|7| @69
|l|i|n|e| |1@1|8| @69
|l|i|n|e| |1@1|9| @69
@60|6|0|,|1| @9|9|8|%|
+20
View File
@@ -0,0 +1,20 @@
|l+0&#ffffff0|i|n|e| |1@2| @69
|l|i|n|e| |1@1|2| @69
|l|i|n|e| |1@1|3| @69
|l|i|n|e| |1@1|4| @69
|l|i|n|e| |1@1|5| @69
|l|i|n|e| |1@1|6| @69
|l|i|n|e| |1@1|7| @69
|l|i|n|e| |1@1|8| @69
|l|i|n|e| |1@1|9| @69
>l|i|n|e| |1|2|0| @69
|~+0#4040ff13&| @76
|~| @76
|~| @76
|~| @76
|~| @76
|~| @76
|~| @76
|~| @76
|~| @76
| +0#0000000&@59|1|2|0|,|1| @8|B|o|t|
+20
View File
@@ -0,0 +1,20 @@
|l+0&#ffffff0|i|n|e| |9|1| @70
|l|i|n|e| |9|2| @70
|l|i|n|e| |9|3| @70
|l|i|n|e| |9|4| @70
|l|i|n|e| |9|5| @70
|l|i|n|e| |9|6| @70
|l|i|n|e| |9|7| @70
|l|i|n|e| |9|8| @70
|l|i|n|e| |9@1| @70
>l|i|n|e| |1|0@1| @69
|~+0#4040ff13&| @76
|~| @76
|~| @76
|~| @76
|~| @76
|~| @76
|~| @76
|~| @76
|~| @76
| +0#0000000&@59|1|0@1|,|1| @8|B|o|t|
+20
View File
@@ -0,0 +1,20 @@
|l+0&#ffffff0|i|n|e| |9|2| @70
|l|i|n|e| |9|3| @70
|l|i|n|e| |9|4| @70
|l|i|n|e| |9|5| @70
|l|i|n|e| |9|6| @70
|l|i|n|e| |9|7| @70
|l|i|n|e| |9|8| @70
|l|i|n|e| |9@1| @70
|L|O|N|G| |L|O|N|G| |L|O|N|G| |L|O|N|G| |L|O|N|G| |L|O|N|G| |L|O|N|G| |L|O|N|G| >L|O|N|G| |L|O|N|G| |L|O|N|G| |L|O|N|G| |L|O|N|G| |L|O|N|G| |L|O|N|G| |L|O|N
|G| |L|O|N|G| |L|O|N|G| |L|O|N|G| |L|O|N|G| |L|O|N|G| |L|O|N|G| |L|O|N|G| |L|O|N|G| |L|O|N|G| |L|O|N|G| |L|O|N|G| |L|O|N|G| |L|O|N|G| |L|O|N|G| @6
|~+0#4040ff13&| @76
|~| @76
|~| @76
|~| @76
|~| @76
|~| @76
|~| @76
|~| @76
|~| @76
| +0#0000000&@59|1|0@1|,|4|1| @7|B|o|t|
+2 -1
View File
@@ -124,7 +124,8 @@ func Test_screenpos()
setlocal nonumber display=lastline so=0
exe "normal G\<C-Y>\<C-Y>"
redraw
call assert_equal({'row': winrow + wininfo.height - 1,
let winbar_height = get(wininfo, 'winbar', 0)
call assert_equal({'row': winrow + wininfo.height - 1 + winbar_height,
\ 'col': wincol + 7,
\ 'curscol': wincol + 7,
\ 'endcol': wincol + 7}, winid->screenpos(line('$'), 8))
+39
View File
@@ -1495,6 +1495,45 @@ func Test_local_scrolloff()
set siso&
endfunc
func Test_local_scrolloffpad()
let save_g_sop = &g:sop
let save_l_sop = &l:sop
set sop=0
call assert_equal(0, &g:sop)
call assert_equal(-1, &l:sop)
call assert_equal(0, &sop)
setglobal sop=1
call assert_equal(1, &g:sop)
call assert_equal(1, &sop)
split
call assert_equal(1, &g:sop)
call assert_equal(-1, &l:sop)
call assert_equal(1, &sop)
setlocal sop=0
call assert_equal(0, &l:sop)
call assert_equal(0, &sop)
call assert_equal(1, &g:sop)
wincmd p
call assert_equal(1, &sop)
wincmd p
setlocal sop<
call assert_equal(-1, &l:sop)
call assert_equal(1, &sop)
setlocal sop=2
call assert_equal(2, &l:sop)
call assert_equal(2, &sop)
setlocal sop=-1
call assert_equal(-1, &l:sop)
call assert_equal(1, &sop) " Uses global value because local is -1
call assert_fails("setlocal sop=-2", 'E474:')
call assert_equal(-1, &l:sop)
call assert_equal(1, &sop)
call assert_fails("setlocal sop=foo", 'E521:')
close
let &g:sop = save_g_sop
let &l:sop = save_l_sop
endfunc
func Test_writedelay()
CheckFunction reltimefloat
+31 -2
View File
@@ -1412,6 +1412,18 @@ endfunc
func Test_popup_option_values()
new
" Save old values
let save_g_so = &g:so
let save_l_so = &l:so
let save_g_sop = &g:sop
let save_l_sop = &l:sop
let save_nu = &l:nu
let save_wrap = &l:wrap
let save_ofu = &l:ofu
let save_path = &l:path
let save_stl = &l:stl
" window-local
setlocal number
setlocal nowrap
@@ -1421,6 +1433,8 @@ func Test_popup_option_values()
setlocal path=/there
" global/window-local
setlocal statusline=2
set scrolloff=5
set scrolloffpad=1
let winid = popup_create('hello', {})
call assert_equal(0, getwinvar(winid, '&number'))
@@ -1429,12 +1443,27 @@ func Test_popup_option_values()
call assert_equal(&g:path, getwinvar(winid, '&path'))
call assert_equal(&g:statusline, getwinvar(winid, '&statusline'))
" 'scrolloff' is reset to zero
" 'scrolloff' and 'scrolloffpad' are reset to zero
call assert_equal(5, &scrolloff)
call assert_equal(0, getwinvar(winid, '&scrolloff'))
call assert_equal(1, &scrolloffpad)
call assert_equal(0, getwinvar(winid, '&scrolloffpad'))
call popup_close(winid)
bwipe
" Restore old values
let &g:so = save_g_so
let &l:so = save_l_so
let &g:sop = save_g_sop
let &l:sop = save_l_sop
let &l:nu = save_nu
let &l:wrap = save_wrap
let &l:ofu = save_ofu
let &l:path = save_path
let &l:stl = save_stl
bwipe!
endfunc
func Test_popup_atcursor()
+651
View File
@@ -1439,4 +1439,655 @@ func Test_smoothscroll_listchars_eol()
bwipe!
endfunc
" scrolloffpad contract:
" - augment scrolloff only under EOF pressure (insufficient real lines below);
" - do not change explicit "z" viewport placement command semantics;
" - current scope is EOF-only, so BOF behavior remains unchanged.
func Test_scrolloffpad_zb_keeps_bottom_command_semantics()
new
resize 12
setlocal scrolloff=10
call setline(1, map(range(1, 300), 'printf("line %d", v:val)'))
setlocal scrolloffpad=0
normal! gg150Gzb
let baseline = [line('.'), line('w$'), winline()]
setlocal scrolloffpad=1
normal! gg150Gzb
call assert_equal(baseline, [line('.'), line('w$'), winline()])
bwipe!
endfunc
func Test_scrolloffpad_zminus_keeps_bottom_beginline_semantics()
new
resize 12
setlocal scrolloff=10
call setline(1, map(range(1, 300), 'printf(" line %d", v:val)'))
setlocal scrolloffpad=0
normal! gg150Gz-
let baseline = [line('.'), line('w$'), winline(), col('.')]
call assert_equal(match(getline('.'), '\S') + 1, col('.'))
setlocal scrolloffpad=1
normal! gg150Gz-
call assert_equal(baseline, [line('.'), line('w$'), winline(), col('.')])
call assert_equal(match(getline('.'), '\S') + 1, col('.'))
bwipe!
endfunc
func Test_scrolloffpad_zb_is_one_shot_then_scrolloff_reapplies()
new
resize 12
setlocal scrolloff=10
call setline(1, map(range(1, 300), 'printf("line %d", v:val)'))
let after_zb = {}
let after_j = {}
for sop in [0, 1]
let &l:scrolloffpad = sop
normal! gg150Gzb
let after_zb[sop] = [line('.'), line('w$'), winline(), winsaveview().topline]
normal! j
let after_j[sop] = [line('.'), line('w$'), winline(), winsaveview().topline]
call assert_notequal(after_zb[sop][3], after_j[sop][3])
call assert_true(line('.') < line('w$'))
endfor
call assert_equal(after_zb[0], after_zb[1])
call assert_equal(after_j[0], after_j[1])
bwipe!
endfunc
func Test_scrolloffpad_has_no_mid_buffer_effect()
new
resize 12
setlocal scrolloff=10 scrolloffpad=0
call setline(1, map(range(1, 500), 'printf("line %d", v:val)'))
normal! gg150G
let topline_without_pad = winsaveview().topline
setlocal scrolloffpad=1
normal! gg150G
let topline_with_pad = winsaveview().topline
call assert_equal(topline_without_pad, topline_with_pad)
bwipe!
endfunc
func Test_scrolloffpad_changes_eof_pressure_only()
new
resize 12
setlocal scrolloff=10 scrolloffpad=0
call setline(1, map(range(1, 200), 'printf("line %d", v:val)'))
normal! ggG
let view_without_pad = winsaveview()
let cursor_without_pad = line('.')
let row_without_pad = winline()
setlocal scrolloffpad=1
normal! ggG
let view_with_pad = winsaveview()
let row_with_pad = winline()
call assert_equal(line('$'), line('.'))
call assert_equal(cursor_without_pad, line('.'))
call assert_notequal(view_without_pad.topline, view_with_pad.topline)
call assert_true(row_with_pad < row_without_pad)
bwipe!
endfunc
func Test_scrolloffpad_large_scrolloff_no_overflow()
new
resize 12
call setline(1, map(range(1, 200), 'printf("line %d", v:val)'))
setlocal scrolloff=2147483647 scrolloffpad=0
normal! ggG
let view_without_pad = winsaveview()
let row_without_pad = winline()
setlocal scrolloffpad=1
normal! ggG
let view_with_pad = winsaveview()
let row_with_pad = winline()
call assert_equal(line('$'), line('.'))
call assert_notequal(view_without_pad.topline, view_with_pad.topline)
call assert_true(row_with_pad < row_without_pad)
bwipe!
endfunc
func Test_scrolloffpad_boolean_gate_values()
new
resize 12
setlocal scrolloff=10
call setline(1, map(range(1, 200), 'printf("line %d", v:val)'))
let views = {}
let rows = {}
for sop in [0, 1, 2]
let &l:scrolloffpad = sop
normal! ggG
let views[sop] = winsaveview()
let rows[sop] = winline()
call assert_equal(line('$'), line('.'))
endfor
call assert_equal(views[1].topline, views[2].topline)
call assert_equal(rows[1], rows[2])
call assert_notequal(views[0].topline, views[1].topline)
call assert_true(rows[1] < rows[0])
bwipe!
endfunc
func Test_scrolloffpad_requires_scrolloff_nonzero()
new
resize 12
call setline(1, map(range(1, 200), 'printf("line %d", v:val)'))
let states = {}
for so in [0, 10]
let states[so] = {}
for sop in [0, 1]
let &l:scrolloff = so
let &l:scrolloffpad = sop
normal! ggG
let states[so][sop] = [line('.'), line('w0'), line('w$'), winline()]
call assert_equal(line('$'), line('.'))
endfor
endfor
call assert_equal(states[0][0], states[0][1])
call assert_notequal(states[10][0], states[10][1])
call assert_true(states[10][1][3] < states[10][0][3])
bwipe!
endfunc
func Test_scrolloffpad_search_to_eof()
new
resize 12
setlocal scrolloff=10
call setline(1, map(range(1, 200), 'printf("line %d", v:val)'))
call setline(line('$'), 'EOF TARGET')
let states = {}
for sop in [0, 1]
let &l:scrolloffpad = sop
normal! gg
call assert_true(search('EOF TARGET') > 0)
let states[sop] = [line('.'), line('w0'), line('w$'), winline()]
call assert_equal(line('$'), line('.'))
endfor
call assert_notequal(states[0], states[1])
call assert_true(states[1][3] < states[0][3])
bwipe!
endfunc
func Test_scrolloffpad_paging_to_eof()
new
resize 12
setlocal scrolloff=10
call setline(1, map(range(1, 240), 'printf("line %d", v:val)'))
let states = {}
for sop in [0, 1]
let &l:scrolloffpad = sop
normal! gg
let prev = -1
for _ in range(1, 200)
execute "normal! \<C-D>"
if line('.') == prev
break
endif
let prev = line('.')
endfor
let states[sop] = [line('.'), line('w0'), line('w$'), winline()]
call assert_equal(line('$'), line('w$'))
endfor
call assert_notequal(states[0], states[1])
call assert_true(states[1][3] < states[0][3])
bwipe!
endfunc
func Test_scrolloffpad_autocmd_append_at_eof()
let states = {}
for sop in [0, 1]
new
resize 12
setlocal scrolloff=10
let &l:scrolloffpad = sop
call setline(1, map(range(1, 120), 'printf("line %d", v:val)'))
let b:scrolloffpad_appended = 0
augroup ScrolloffpadAppendAtEof
autocmd!
autocmd CursorMoved <buffer> if b:scrolloffpad_appended == 0 && line('.') == line('$') | call append('$', 'appended') | let b:scrolloffpad_appended = 1 | endif
augroup END
normal! ggG
doautocmd <nomodeline> CursorMoved
let states[sop] = [
\ line('.'),
\ line('$'),
\ line('w0'),
\ line('w$'),
\ winline(),
\ b:scrolloffpad_appended,
\ ]
call assert_equal(1, b:scrolloffpad_appended)
call assert_equal(states[sop][1] - 1, states[sop][0])
augroup ScrolloffpadAppendAtEof
autocmd!
augroup END
bwipe!
endfor
call assert_notequal(states[0], states[1])
call assert_true(states[1][4] < states[0][4])
endfunc
func Test_scrolloffpad_eof_no_reverse_scroll_on_j()
new
resize 20
setlocal scrolloff=20 scrolloffpad=1
call setline(1, map(range(1, 80), 'printf("line %d", v:val)'))
normal! gg
let prev_topline = winsaveview().topline
for lnum in range(2, line('$'))
normal! j
let cur_topline = winsaveview().topline
call assert_true(
\ cur_topline >= prev_topline,
\ printf('topline moved backwards at line %d: %d -> %d',
\ lnum, prev_topline, cur_topline))
let prev_topline = cur_topline
endfor
bwipe!
endfunc
func Test_scrolloffpad_bof_unchanged()
new
resize 12
setlocal scrolloff=10 scrolloffpad=0
call setline(1, map(range(1, 200), 'printf("line %d", v:val)'))
normal! Ggg
let view_without_pad = winsaveview()
let w0_without_pad = line('w0')
setlocal scrolloffpad=1
normal! Ggg
let view_with_pad = winsaveview()
let w0_with_pad = line('w0')
call assert_equal(1, w0_without_pad)
call assert_equal(1, w0_with_pad)
call assert_equal(view_without_pad.topline, view_with_pad.topline)
bwipe!
endfunc
func Test_scrolloffpad_mouse_drag_uses_drag_scrolloff()
CheckFeature mouse
let save_mouse = &mouse
set mouse=a
new
resize 20
call setline(1, map(range(1, 240), 'printf("line %d", v:val)'))
setlocal scrolloff=50
let after_drag = {}
for sop in [0, 1]
let &l:scrolloffpad = sop
normal! gg160Gzt
normal! v
call test_setmouse(2, 1)
call feedkeys("\<LeftMouse>", 'xt')
call test_setmouse(3, 1)
call feedkeys("\<LeftDrag>", 'xt')
let after_drag[sop] = [winsaveview().topline, line('.'), winline()]
call feedkeys("\<Esc>", 'xt')
endfor
call assert_equal(after_drag[0], after_drag[1])
bwipe!
let &mouse = save_mouse
endfunc
func Test_scrolloffpad_basic()
CheckScreendump
CheckRunVimInTerminal
let save_termwinsize = &termwinsize
set termwinsize=
let lines =<< trim END
set scrolloff=10
set scrolloffpad=5
enew!
call setline(1, map(range(1, 100), 'printf("line %d", v:val)'))
normal! gg
END
call writefile(lines, 'XScrolloffpadBasic', 'D')
let buf = RunVimInTerminal('-S XScrolloffpadBasic', {'rows': 20, 'cols': 78})
" Enabled: scrolloffpad > 0, expect EOF centering/padding
call term_sendkeys(buf, "\<Esc>:\<C-U>normal! G\<CR>")
call term_sendkeys(buf, "\<C-L>")
call TermWait(buf)
call VerifyScreenDump(buf, 'Test_scrolloffpad_basic_1', {})
" Beginning-of-file is unchanged (Top)
call term_sendkeys(buf, "\<Esc>:\<C-U>normal! gg\<CR>")
call term_sendkeys(buf, "\<C-L>")
call TermWait(buf)
call VerifyScreenDump(buf, 'Test_scrolloffpad_basic_2', {})
" Gating: disable scrolloffpad, then go to EOF again
" Expect normal EOF behavior (no extra centering/padding)
call term_sendkeys(buf, "\<Esc>:\<C-U>set scrolloffpad=0\<CR>")
call term_sendkeys(buf, "\<Esc>:\<C-U>normal! G\<CR>")
call term_sendkeys(buf, "\<C-L>")
call TermWait(buf)
call VerifyScreenDump(buf, 'Test_scrolloffpad_basic_3', {})
call StopVimInTerminal(buf)
let &termwinsize = save_termwinsize
endfunc
func Test_scrolloffpad_smoothscroll()
CheckScreendump
CheckRunVimInTerminal
let save_termwinsize = &termwinsize
set termwinsize=
let lines =<< trim END
set smoothscroll scrolloff=10 scrolloffpad=1
enew!
call setline(1, map(range(1, 100), 'printf("line %d", v:val)'))
normal! gg
END
call writefile(lines, 'XScrolloffpadSmoothscroll', 'D')
let buf = RunVimInTerminal('-S XScrolloffpadSmoothscroll', #{rows: 20, cols: 78})
call term_sendkeys(buf, "\<Esc>:\<C-U>normal! G\<CR>")
call term_sendkeys(buf, "\<C-L>")
call TermWait(buf)
call VerifyScreenDump(buf, 'Test_scrolloffpad_smoothscroll_1', {})
call term_sendkeys(buf, "\<Esc>:\<C-U>call setline(line('$'), repeat('LONG ', 30))\<CR>")
call term_sendkeys(buf, "\<Esc>:\<C-U>normal! 41|\<CR>")
call term_sendkeys(buf, "\<C-L>")
call TermWait(buf)
call VerifyScreenDump(buf, 'Test_scrolloffpad_smoothscroll_2', {})
call StopVimInTerminal(buf)
let &termwinsize = save_termwinsize
endfunc
func Test_scrolloffpad_insert_eof()
let save_so = &scrolloff
let save_sop = &scrolloffpad
set scrolloff=10 scrolloffpad=1
enew!
call setline(1, map(range(1, 200), 'printf("line %d", v:val)'))
normal! G
let topline_before = winsaveview().topline
call feedkeys("i\<Esc>", 'xt')
call assert_equal(topline_before, winsaveview().topline)
exe "normal! \<C-E>"
let topline_after = winsaveview().topline
call feedkeys("i\<Esc>", 'xt')
call assert_equal(topline_after, winsaveview().topline)
let &scrolloff = save_so
let &scrolloffpad = save_sop
bwipe!
endfunc
func Test_scrolloffpad_in_diff_mode()
CheckFeature diff
let save_so = &scrolloff
let save_sop = &scrolloffpad
let save_splitright = &splitright
set nosplitright
set scrolloff=10
set scrolloffpad=0
enew
call setline(1, map(range(1, 100), {_, v -> 'line ' .. v}))
diffthis
vnew
call setline(1, map(range(1, 100), {_, v -> 'line ' .. v}))
" Make buffers minimally different to avoid diff folding everything.
call setline(50, 'DIFF LINE 50')
diffthis
windo normal! zR
windo normal! gg
wincmd =
let rows_without = []
let rows_with = []
let near_states = []
let eof_states = []
for sop in [0, 1]
let &scrolloffpad = sop
" Near EOF with real text visible in both windows.
windo normal! 99G
for w in range(1, winnr('$'))
execute w .. 'wincmd w'
let state = [line('.'), line('w0'), line('w$'), winline()]
call assert_equal(99, state[0])
call assert_equal(100, state[2])
if sop == 0
call add(near_states, state)
endif
endfor
call assert_equal(near_states[0], near_states[1])
" EOF in both windows: scrolloffpad should raise the cursor row.
windo normal! G
for w in range(1, winnr('$'))
execute w .. 'wincmd w'
let state = [line('.'), line('w0'), line('w$'), winline()]
call assert_equal(line('$'), state[0])
if sop == 0
call add(eof_states, state)
call add(rows_without, state[3])
else
call add(rows_with, state[3])
endif
endfor
call assert_equal(eof_states[0], eof_states[1])
endfor
call assert_true(rows_with[0] < rows_without[0])
call assert_true(rows_with[1] < rows_without[1])
windo diffoff
%bwipe!
let &scrolloff = save_so
let &scrolloffpad = save_sop
let &splitright = save_splitright
endfunc
func Test_scrolloffpad_diff_eof_filler_behavior()
CheckFeature diff
let save_so = &scrolloff
let save_sop = &scrolloffpad
let save_diffopt = &diffopt
let save_splitright = &splitright
set diffopt+=filler
set scrolloff=10
set scrolloffpad=0
set nosplitright
20new
call setline(1, map(range(1, 100), {_, v -> 'left ' .. v}))
diffthis
let short_wid = win_getid()
vnew
call setline(1, map(range(1, 120), {_, v -> 'right ' .. v}))
diffthis
let long_wid = win_getid()
call assert_true(win_gotoid(short_wid))
let short_height = winheight(0)
call assert_true(win_gotoid(long_wid))
let long_height = winheight(0)
call assert_equal(short_height, long_height)
call assert_equal(20, short_height)
let ordered_diff_wids = [long_wid, short_wid]
let states = {}
for sop in [0, 1]
execute 'set scrolloffpad=' .. sop
for wid in ordered_diff_wids
call assert_true(win_gotoid(wid))
normal! gg
endfor
for wid in ordered_diff_wids
call assert_true(win_gotoid(wid))
normal! G
endfor
call assert_true(win_gotoid(short_wid))
let short_view = winsaveview()
let short_state = [
\ line('.'),
\ line('$'),
\ winline(),
\ short_view.topline,
\ short_view.topfill,
\ diff_filler(line('$') + 1),
\ ]
call assert_equal(short_state[1], short_state[0])
call assert_true(short_state[5] > 0)
call assert_true(win_gotoid(long_wid))
let long_view = winsaveview()
let long_state = [
\ line('.'),
\ line('$'),
\ winline(),
\ long_view.topline,
\ long_view.topfill,
\ ]
call assert_true(long_state[0] > 0 && long_state[0] <= long_state[1])
call assert_equal(short_state[0], long_state[0])
let states[sop] = [short_state, long_state]
endfor
let short_without = states[0][0]
let short_with = states[1][0]
" Environment/layout can shift direction of movement; require only that
" scrolloffpad changes the short-window viewport state under EOF filler.
call assert_true(short_with[2] != short_without[2]
\ || short_with[3] != short_without[3]
\ || short_with[4] != short_without[4])
windo diffoff
call assert_true(win_gotoid(short_wid))
only!
%bwipe!
let &scrolloff = save_so
let &scrolloffpad = save_sop
let &diffopt = save_diffopt
let &splitright = save_splitright
endfunc
func Test_scrolloffpad_with_folds()
CheckScreendump
CheckRunVimInTerminal
CheckFeature folding
let save_termwinsize = &termwinsize
set termwinsize=
let lines =<< trim END
set scrolloff=10
set scrolloffpad=1
enew
call setline(1, map(range(1, 120), {_, v -> 'line ' . v}))
" Create a large fold near the end of the file.
" Fold lines 60-110, leaving 111-120 visible after the fold.
set foldmethod=manual
set foldenable
normal! gg
normal! 60G
normal! zf50j
normal! gg
END
call writefile(lines, 'XScrolloffpadFolds', 'D')
let buf = RunVimInTerminal('-S XScrolloffpadFolds', #{rows: 20, cols: 78})
" Case 1: Jump to end-of-file
" With folds present, scrolloffpad should still
" keep the cursor positioned with padding below EOF
call term_sendkeys(buf, "\<Esc>:\<C-U>normal! G\<CR>")
call term_sendkeys(buf, "\<C-L>")
call TermWait(buf)
call VerifyScreenDump(buf, 'Test_scrolloffpad_folds_1', {})
" Case 2: Move to the folded line to ensure the fold is actually in view
call term_sendkeys(buf, "\<Esc>:\<C-U>normal! 60G\<CR>")
call term_sendkeys(buf, "\<C-L>")
call TermWait(buf)
call VerifyScreenDump(buf, 'Test_scrolloffpad_folds_2', {})
" Case 3: Close the fold explicitly and go to EOF again
" Behavior should remain stable with closed folds
call term_sendkeys(buf, "\<Esc>:\<C-U>normal! zc\<CR>")
call term_sendkeys(buf, "\<Esc>:\<C-U>normal! G\<CR>")
call term_sendkeys(buf, "\<C-L>")
call TermWait(buf)
call VerifyScreenDump(buf, 'Test_scrolloffpad_folds_3', {})
call StopVimInTerminal(buf)
let &termwinsize = save_termwinsize
endfunc
" vim: shiftwidth=2 sts=2 expandtab
+2
View File
@@ -24,6 +24,7 @@ while search("^'[^']*'.*\\n.*|global-local", 'W')
endwhile
call extend(global_locals, #{
\ scrolloff: -1,
\ scrolloffpad: -1,
\ sidescrolloff: -1,
\ undolevels: -123456,
\})
@@ -93,6 +94,7 @@ let test_values = {
\ 'scroll': [[0, 1, 2, 15], [-1, 999]],
\ 'scrolljump': [[-100, -1, 0, 1, 2, 15], [-101, 999]],
\ 'scrolloff': [[0, 1, 8, 999], [-1]],
\ 'scrolloffpad': [[0, 1, 2, 3], [-1]],
\ 'shiftwidth': [[0, 1, 8, 999], [-1]],
\ 'showtabpanel': [[0, 1, 2], []],
\ 'sidescroll': [[0, 1, 8, 999], [-1]],
+2
View File
@@ -734,6 +734,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
356,
/**/
355,
/**/
+1
View File
@@ -5974,6 +5974,7 @@ win_alloc(win_T *after, int hidden)
// use global option value for global-local options
new_wp->w_allbuf_opt.wo_so = new_wp->w_p_so = -1;
new_wp->w_allbuf_opt.wo_sop = new_wp->w_p_sop = -1;
new_wp->w_allbuf_opt.wo_siso = new_wp->w_p_siso = -1;
// We won't calculate w_fraction until resizing the window