diff options
| author | zeertzjq <zeertzjq@outlook.com> | 2026-04-20 20:47:50 +0800 |
|---|---|---|
| committer | zeertzjq <zeertzjq@outlook.com> | 2026-04-22 10:14:52 +0800 |
| commit | 8f1e14ffa28787fc4faa8b645f378c23735f5bbe (patch) | |
| tree | 63b0ee635a1dc37f6ceac875bd0ecd4af162af64 /src | |
| parent | 1569a71c8a51287628cb5257e45d2e68f1181551 (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.h | 2 | ||||
| -rw-r--r-- | src/nvim/move.c | 56 | ||||
| -rw-r--r-- | src/nvim/option.c | 19 | ||||
| -rw-r--r-- | src/nvim/option_vars.h | 1 | ||||
| -rw-r--r-- | src/nvim/options.lua | 29 | ||||
| -rw-r--r-- | src/nvim/window.c | 1 |
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 |
