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

Commit: 
https://github.com/vim/vim/commit/a414630393f81c9a5b8fa4d0fcc1287155f67751
Author: McAuley Penney <[email protected]>
Date:   Wed Apr 15 19:11:12 2026 +0000

    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 <[email protected]>
    Signed-off-by: Christian Brabandt <[email protected]>

diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index 627d96540..31a9629c0 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -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
diff --git a/runtime/doc/quickref.txt b/runtime/doc/quickref.txt
index 8f6aca046..1ada2bf9b 100644
--- a/runtime/doc/quickref.txt
+++ b/runtime/doc/quickref.txt
@@ -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
diff --git a/runtime/doc/tags b/runtime/doc/tags
index 4d31cc51a..7effaf824 100644
--- a/runtime/doc/tags
+++ b/runtime/doc/tags
@@ -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'*
diff --git a/runtime/doc/version9.txt b/runtime/doc/version9.txt
index fa8c5355c..ee24e2901 100644
--- a/runtime/doc/version9.txt
+++ b/runtime/doc/version9.txt
@@ -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.
diff --git a/runtime/optwin.vim b/runtime/optwin.vim
index 73243e77f..62c2ec2e5 100644
--- a/runtime/optwin.vim
+++ b/runtime/optwin.vim
@@ -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 <[email protected]>
 
 " If there already is an option window, jump to that one.
@@ -351,6 +351,8 @@ call append("$", "  " .. s:local_to_window)
 call <SID>BinOptionL("sms")
 call <SID>AddOption("scrolloff", gettext("number of screen lines to show 
around the cursor"))
 call append("$", "     set so=" . &so)
+call <SID>AddOption("scrolloffpad", gettext("keep 'scrolloff' context even at 
end of file"))
+call append("$", "     set sop=" . &sop)
 call <SID>AddOption("wrap", gettext("long lines wrap"))
 call append("$", "     " .. s:local_to_window)
 call <SID>BinOptionL("wrap")
diff --git a/runtime/syntax/vim.vim b/runtime/syntax/vim.vim
index f213710c3..a390df78e 100644
--- a/runtime/syntax/vim.vim
+++ b/runtime/syntax/vim.vim
@@ -2,7 +2,7 @@
 " Language:       Vim script
 " Maintainer:     Hirohito Higashi <h.east.727 ATMARK gmail.com>
 "         Doug Kearns <[email protected]>
-" 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
diff --git a/src/move.c b/src/move.c
index ee9a8eb5a..1e3510130 100644
--- a/src/move.c
+++ b/src/move.c
@@ -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;
diff --git a/src/option.c b/src/option.c
index 46009341d..dc6f0966d 100644
--- a/src/option.c
+++ b/src/option.c
@@ -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.
diff --git a/src/option.h b/src/option.h
index 6bf6169bb..e5f9de8e1 100644
--- a/src/option.h
+++ b/src/option.h
@@ -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
diff --git a/src/optiondefs.h b/src/optiondefs.h
index 38962790b..0214a34b1 100644
--- a/src/optiondefs.h
+++ b/src/optiondefs.h
@@ -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}
diff --git a/src/po/vim.pot b/src/po/vim.pot
index b09ade556..936961c92 100644
--- a/src/po/vim.pot
+++ b/src/po/vim.pot
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: Vim
"
 "Report-Msgid-Bugs-To: [email protected]
"
-"POT-Creation-Date: 2026-04-09 20:33+0000
"
+"POT-Creation-Date: 2026-04-15 19:10+0000
"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE
"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>
"
 "Language-Team: LANGUAGE <[email protected]>
"
@@ -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 ""
 
diff --git a/src/popupwin.c b/src/popupwin.c
index bdf53cdc4..319d882c5 100644
--- a/src/popupwin.c
+++ b/src/popupwin.c
@@ -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)
     {
diff --git a/src/proto/option.pro b/src/proto/option.pro
index 892a6ce3b..ae586ea97 100644
--- a/src/proto/option.pro
+++ b/src/proto/option.pro
@@ -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);
diff --git a/src/structs.h b/src/structs.h
index 955b9f100..648ceec90 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -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'
diff --git a/src/testdir/dumps/Test_scrolloffpad_basic_1.dump 
b/src/testdir/dumps/Test_scrolloffpad_basic_1.dump
new file mode 100644
index 000000000..af38b016a
--- /dev/null
+++ b/src/testdir/dumps/Test_scrolloffpad_basic_1.dump
@@ -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| 
diff --git a/src/testdir/dumps/Test_scrolloffpad_basic_2.dump 
b/src/testdir/dumps/Test_scrolloffpad_basic_2.dump
new file mode 100644
index 000000000..070201946
--- /dev/null
+++ b/src/testdir/dumps/Test_scrolloffpad_basic_2.dump
@@ -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| 
diff --git a/src/testdir/dumps/Test_scrolloffpad_basic_3.dump 
b/src/testdir/dumps/Test_scrolloffpad_basic_3.dump
new file mode 100644
index 000000000..72eed1e33
--- /dev/null
+++ b/src/testdir/dumps/Test_scrolloffpad_basic_3.dump
@@ -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| 
diff --git a/src/testdir/dumps/Test_scrolloffpad_folds_1.dump 
b/src/testdir/dumps/Test_scrolloffpad_folds_1.dump
new file mode 100644
index 000000000..1c6433300
--- /dev/null
+++ b/src/testdir/dumps/Test_scrolloffpad_folds_1.dump
@@ -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| 
diff --git a/src/testdir/dumps/Test_scrolloffpad_folds_2.dump 
b/src/testdir/dumps/Test_scrolloffpad_folds_2.dump
new file mode 100644
index 000000000..63ac801b4
--- /dev/null
+++ b/src/testdir/dumps/Test_scrolloffpad_folds_2.dump
@@ -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|%| 
diff --git a/src/testdir/dumps/Test_scrolloffpad_folds_3.dump 
b/src/testdir/dumps/Test_scrolloffpad_folds_3.dump
new file mode 100644
index 000000000..1c6433300
--- /dev/null
+++ b/src/testdir/dumps/Test_scrolloffpad_folds_3.dump
@@ -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| 
diff --git a/src/testdir/dumps/Test_scrolloffpad_smoothscroll_1.dump 
b/src/testdir/dumps/Test_scrolloffpad_smoothscroll_1.dump
new file mode 100644
index 000000000..af38b016a
--- /dev/null
+++ b/src/testdir/dumps/Test_scrolloffpad_smoothscroll_1.dump
@@ -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| 
diff --git a/src/testdir/dumps/Test_scrolloffpad_smoothscroll_2.dump 
b/src/testdir/dumps/Test_scrolloffpad_smoothscroll_2.dump
new file mode 100644
index 000000000..3b66ce621
--- /dev/null
+++ b/src/testdir/dumps/Test_scrolloffpad_smoothscroll_2.dump
@@ -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| 
diff --git a/src/testdir/test_cursor_func.vim b/src/testdir/test_cursor_func.vim
index 73169649d..e541da886 100644
--- a/src/testdir/test_cursor_func.vim
+++ b/src/testdir/test_cursor_func.vim
@@ -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))
diff --git a/src/testdir/test_options.vim b/src/testdir/test_options.vim
index 828a5ca71..76a1bff34 100644
--- a/src/testdir/test_options.vim
+++ b/src/testdir/test_options.vim
@@ -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
 
diff --git a/src/testdir/test_popupwin.vim b/src/testdir/test_popupwin.vim
index a134acabd..030e9e659 100644
--- a/src/testdir/test_popupwin.vim
+++ b/src/testdir/test_popupwin.vim
@@ -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()
diff --git a/src/testdir/test_scroll_opt.vim b/src/testdir/test_scroll_opt.vim
index 99fd32a00..390a8b59a 100644
--- a/src/testdir/test_scroll_opt.vim
+++ b/src/testdir/test_scroll_opt.vim
@@ -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
diff --git a/src/testdir/util/gen_opt_test.vim 
b/src/testdir/util/gen_opt_test.vim
index 6a02c531b..bc54d272d 100644
--- a/src/testdir/util/gen_opt_test.vim
+++ b/src/testdir/util/gen_opt_test.vim
@@ -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]],
diff --git a/src/version.c b/src/version.c
index c0f826a17..0e22232f1 100644
--- a/src/version.c
+++ b/src/version.c
@@ -734,6 +734,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    356,
 /**/
     355,
 /**/
diff --git a/src/window.c b/src/window.c
index 1d0d47844..26d923d9c 100644
--- a/src/window.c
+++ b/src/window.c
@@ -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

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion visit 
https://groups.google.com/d/msgid/vim_dev/E1wD5w6-004UMX-BY%40256bit.org.

Raspunde prin e-mail lui