summaryrefslogtreecommitdiffstatshomepage
path: root/src
diff options
context:
space:
mode:
authorzeertzjq <zeertzjq@outlook.com>2026-04-20 20:47:50 +0800
committerzeertzjq <zeertzjq@outlook.com>2026-04-22 10:14:52 +0800
commit8f1e14ffa28787fc4faa8b645f378c23735f5bbe (patch)
tree63b0ee635a1dc37f6ceac875bd0ecd4af162af64 /src
parent1569a71c8a51287628cb5257e45d2e68f1181551 (diff)
vim-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: vim/vim#19040 https://github.com/vim/vim/commit/a414630393f81c9a5b8fa4d0fcc1287155f67751 Co-authored-by: McAuley Penney <jacobmpenney@gmail.com>
Diffstat (limited to 'src')
-rw-r--r--src/nvim/buffer_defs.h2
-rw-r--r--src/nvim/move.c56
-rw-r--r--src/nvim/option.c19
-rw-r--r--src/nvim/option_vars.h1
-rw-r--r--src/nvim/options.lua29
-rw-r--r--src/nvim/window.c1
6 files changed, 96 insertions, 12 deletions
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index e3ed8a67c7..72e67d4fd6 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -204,6 +204,8 @@ typedef struct {
#define w_p_siso w_onebuf_opt.wo_siso // 'sidescrolloff' local value
OptInt wo_so;
#define w_p_so w_onebuf_opt.wo_so // 'scrolloff' local value
+ OptInt wo_sop;
+#define w_p_sop w_onebuf_opt.wo_sop // 'scrolloffpad' local value
char *wo_winhl;
#define w_p_winhl w_onebuf_opt.wo_winhl // 'winhighlight'
char *wo_lcs;
diff --git a/src/nvim/move.c b/src/nvim/move.c
index a1ef9634d2..99b1e73e32 100644
--- a/src/nvim/move.c
+++ b/src/nvim/move.c
@@ -246,6 +246,26 @@ static void reset_skipcol(win_T *wp)
redraw_later(wp, UPD_SOME_VALID);
}
+/// 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(win_T *wp)
+{
+ return get_scrolloff_value(wp) > 0 && get_scrolloffpad_value(wp) > 0;
+}
+
+/// Return true when there are not enough real buffer lines below "lnum" to
+/// satisfy the requested "so" context.
+static bool scrolloffpad_eof_pressure(win_T *wp, linenr_T lnum, OptInt so)
+{
+ if (!use_scrolloffpad(wp) || so <= 0) {
+ return false;
+ }
+
+ // Use subtraction to avoid signed overflow in "lnum + so".
+ return lnum > wp->w_buffer->b_ml.ml_line_count - so;
+}
+
// Update wp->w_topline to move the cursor onto the screen.
void update_topline(win_T *wp)
{
@@ -278,6 +298,7 @@ void update_topline(win_T *wp)
if (mouse_dragging > 0) {
*so_ptr = mouse_dragging - 1;
}
+ bool eof_pressure = scrolloffpad_eof_pressure(wp, wp->w_cursor.lnum, *so_ptr);
linenr_T old_topline = wp->w_topline;
int old_topfill = wp->w_topfill;
@@ -350,10 +371,18 @@ void update_topline(win_T *wp)
// cursor in the middle of the window. Otherwise put the cursor
// near the top of the window.
if (n >= halfheight) {
- scroll_cursor_halfway(wp, false, false);
+ if (eof_pressure) {
+ scroll_cursor_halfway(wp, true, true);
+ } else {
+ scroll_cursor_halfway(wp, false, false);
+ }
} else {
- scroll_cursor_top(wp, scrolljump_value(wp), false);
- check_botline = true;
+ if (eof_pressure) {
+ scroll_cursor_halfway(wp, true, true);
+ } else {
+ scroll_cursor_top(wp, scrolljump_value(wp), false);
+ check_botline = true;
+ }
}
} else {
// Make sure topline is the first line of a fold.
@@ -374,7 +403,7 @@ void update_topline(win_T *wp)
}
assert(wp->w_buffer != 0);
- if (wp->w_botline <= wp->w_buffer->b_ml.ml_line_count) {
+ if (wp->w_botline <= wp->w_buffer->b_ml.ml_line_count || use_scrolloffpad(wp)) {
if (wp->w_cursor.lnum < wp->w_botline) {
if ((wp->w_cursor.lnum >= wp->w_botline - *so_ptr || win_lines_concealed(wp))) {
lineoff_T loff;
@@ -382,7 +411,7 @@ void update_topline(win_T *wp)
// Cursor is (a few lines) above botline, check if there are
// 'scrolloff' window lines below the cursor. If not, need to
// scroll.
- int n = wp->w_empty_rows;
+ int n = eof_pressure ? 0 : wp->w_empty_rows;
loff.lnum = wp->w_cursor.lnum;
// In a fold go to its last line.
hasFolding(wp, loff.lnum, NULL, &loff.lnum);
@@ -397,7 +426,7 @@ void update_topline(win_T *wp)
}
botline_forw(wp, &loff);
}
- if (n >= *so_ptr) {
+ if (n >= *so_ptr && !eof_pressure) {
// sufficient context, no need to scroll
check_botline = false;
}
@@ -424,9 +453,13 @@ void update_topline(win_T *wp)
n = wp->w_cursor.lnum - wp->w_botline + 1 + *so_ptr;
}
if (n <= wp->w_view_height + 1) {
- scroll_cursor_bot(wp, scrolljump_value(wp), false);
+ if (eof_pressure) {
+ scroll_cursor_halfway(wp, true, true);
+ } else {
+ scroll_cursor_bot(wp, scrolljump_value(wp), false);
+ }
} else {
- scroll_cursor_halfway(wp, false, false);
+ scroll_cursor_halfway(wp, eof_pressure, eof_pressure);
}
}
}
@@ -2111,8 +2144,9 @@ void scroll_cursor_bot(win_T *wp, int min_scroll, bool 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(wp, cln, so);
if (line_count >= wp->w_view_height && line_count > min_scroll) {
- scroll_cursor_halfway(wp, false, true);
+ scroll_cursor_halfway(wp, eof_pressure, true);
} else if (line_count > 0) {
if (do_sms) {
scrollup(wp, scrolled, true); // TODO(vim):
@@ -2289,7 +2323,9 @@ void cursor_correct(win_T *wp)
validate_botline_win(wp);
if (wp->w_botline == wp->w_buffer->b_ml.ml_line_count + 1
&& mouse_dragging == 0) {
- below_wanted = 0;
+ if (!use_scrolloffpad(wp)) {
+ below_wanted = 0;
+ }
int max_off = (wp->w_view_height - 1) / 2;
above_wanted = MIN(above_wanted, max_off);
}
diff --git a/src/nvim/option.c b/src/nvim/option.c
index 2318625720..0e33292f77 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -3111,6 +3111,12 @@ static const char *validate_num_option(OptIndex opt_idx, OptInt *newval, char *e
return e_positive;
}
break;
+ case kOptScrolloffpad:
+ // if (value < 0 && full_screen) {
+ if (value < 0) {
+ return e_invarg;
+ }
+ break;
case kOptSidescrolloff:
if (value < 0 && full_screen) {
return e_positive;
@@ -3600,6 +3606,7 @@ static OptVal get_option_unset_value(OptIndex opt_idx)
case kOptFsync:
return BOOLEAN_OPTVAL(kNone);
case kOptScrolloff:
+ case kOptScrolloffpad:
case kOptSidescrolloff:
return NUMBER_OPTVAL(-1);
case kOptUndolevels:
@@ -4673,6 +4680,8 @@ void *get_varp_scope_from(vimoption_T *p, int opt_flags, buf_T *buf, win_T *win)
return &(win->w_p_siso);
case kOptScrolloff:
return &(win->w_p_so);
+ case kOptScrolloffpad:
+ return &(win->w_p_sop);
case kOptDefine:
return &(buf->b_p_def);
case kOptInclude:
@@ -4760,6 +4769,8 @@ void *get_varp_from(vimoption_T *p, buf_T *buf, win_T *win)
return win->w_p_siso >= 0 ? &(win->w_p_siso) : p->var;
case kOptScrolloff:
return win->w_p_so >= 0 ? &(win->w_p_so) : p->var;
+ case kOptScrolloffpad:
+ return win->w_p_sop >= 0 ? &(win->w_p_sop) : p->var;
case kOptBackupcopy:
return *buf->b_p_bkc != NUL ? &(buf->b_p_bkc) : p->var;
case kOptDefine:
@@ -5121,6 +5132,7 @@ void 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;
to->wo_spell = from->wo_spell;
to->wo_cuc = from->wo_cuc;
to->wo_cul = from->wo_cul;
@@ -6560,6 +6572,13 @@ int64_t get_scrolloff_value(win_T *wp)
return wp->w_p_so < 0 ? p_so : wp->w_p_so;
}
+/// Return the effective 'scrolloffpad' value for the current window, using the
+/// global value when appropriate.
+int64_t get_scrolloffpad_value(win_T *wp)
+{
+ return wp->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.
int64_t get_sidescrolloff_value(win_T *wp)
diff --git a/src/nvim/option_vars.h b/src/nvim/option_vars.h
index a8f26f4730..c57020b553 100644
--- a/src/nvim/option_vars.h
+++ b/src/nvim/option_vars.h
@@ -472,6 +472,7 @@ EXTERN char *p_rtp; ///< 'runtimepath'
EXTERN OptInt p_scbk; ///< 'scrollback'
EXTERN OptInt p_sj; ///< 'scrolljump'
EXTERN OptInt p_so; ///< 'scrolloff'
+EXTERN OptInt p_sop; ///< 'scrolloffpad'
EXTERN char *p_sbo; ///< 'scrollopt'
EXTERN char *p_sections; ///< 'sections'
EXTERN int p_secure; ///< 'secure'
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
index cb3db201bd..d749aefa00 100644
--- a/src/nvim/options.lua
+++ b/src/nvim/options.lua
@@ -7237,8 +7237,8 @@ local options = {
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: >vim
setlocal scrolloff<
@@ -7252,6 +7252,31 @@ local options = {
varname = 'p_so',
},
{
+ abbreviation = 'sop',
+ defaults = 0,
+ desc = [=[
+ 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: >vim
+ setlocal scrolloffpad<
+ setlocal scrolloffpad=-1
+ <
+ ]=],
+ full_name = 'scrolloffpad',
+ scope = { 'global', 'win' },
+ short_desc = N_("keep 'scrolloff' context even at end of file"),
+ type = 'number',
+ varname = 'p_sop',
+ },
+ {
abbreviation = 'sbo',
defaults = 'ver,jump',
values = { 'ver', 'hor', 'jump' },
diff --git a/src/nvim/window.c b/src/nvim/window.c
index 5f9082f6e6..40f471a9fb 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -5532,6 +5532,7 @@ win_T *win_alloc(win_T *after, bool hidden)
// use global option 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