From fe986e5dd094b2f7e1d28e64e52ffbc5f7292191 Mon Sep 17 00:00:00 2001 From: luukvbaal Date: Mon, 20 Apr 2026 02:36:55 +0200 Subject: feat(options): add 'winpinned' to pin a window #39157 Problem: - Unable to "pin" a window to prevent closing without specifically being targeted. - :fclose closes hidden windows (even before visible windows). Solution: - Add 'winpinned' window-local option. When set, window is skipped by :fclose and :only. Pin the ui2 cmdline window (which should always be visible), so that it is not closed by :only/fclose. - Skip over hidden (and pinned) windows with :fclose. Co-authored-by: glepnir --- src/gen/gen_eval_files.lua | 1 + src/nvim/buffer_defs.h | 2 ++ src/nvim/ex_docmd.c | 4 ++-- src/nvim/option.c | 2 ++ src/nvim/options.lua | 13 +++++++++++++ src/nvim/window.c | 23 ++++++++++++++++++----- src/nvim/winfloat.c | 3 +++ 7 files changed, 41 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/gen/gen_eval_files.lua b/src/gen/gen_eval_files.lua index 4609509042..05f2d52fcf 100755 --- a/src/gen/gen_eval_files.lua +++ b/src/gen/gen_eval_files.lua @@ -667,6 +667,7 @@ local function option_scope_doc(o) 'syntax', 'winfixheight', 'winfixwidth', + 'winpinned', }, o.full_name) then r = r .. ' |local-noglobal|' diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 6b1719cc6e..546d6e5a6a 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -148,6 +148,8 @@ typedef struct { #define w_p_wfh w_onebuf_opt.wo_wfh // 'winfixheight' int wo_wfw; #define w_p_wfw w_onebuf_opt.wo_wfw // 'winfixwidth' + int wo_wp; +#define w_p_wp w_onebuf_opt.wo_wp // 'winpinned' int wo_pvw; #define w_p_pvw w_onebuf_opt.wo_pvw // 'previewwindow' OptInt wo_lhi; diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 34aef2e476..e527b38b6a 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -5329,7 +5329,7 @@ void tabpage_close(int forceit) ex_win_close(forceit, curwin, NULL); } if (!ONE_WINDOW) { - close_others(true, forceit); + close_others(true, forceit, true); } if (ONE_WINDOW) { ex_win_close(forceit, curwin, NULL); @@ -5402,7 +5402,7 @@ static void ex_only(exarg_T *eap) win_goto(wp); } } - close_others(true, eap->forceit); + close_others(true, eap->forceit, false); } static void ex_hide(exarg_T *eap) diff --git a/src/nvim/option.c b/src/nvim/option.c index 030702ccaf..51403a0fe6 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -4859,6 +4859,8 @@ void *get_varp_from(vimoption_T *p, buf_T *buf, win_T *win) return &(win->w_p_wfh); case kOptWinfixwidth: return &(win->w_p_wfw); + case kOptWinpinned: + return &(win->w_p_wp); case kOptPreviewwindow: return &(win->w_p_pvw); case kOptLhistory: diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 5ec0fe842a..a24da96883 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -10713,6 +10713,19 @@ local options = { type = 'number', varname = 'p_wmw', }, + { + abbreviation = 'wp', + defaults = false, + desc = [=[ + If enabled, the window is pinned and will not be closed by |:only| + and |:fclose|. Only commands specifically targeting the window can + close it. + ]=], + full_name = 'winpinned', + scope = { 'win' }, + short_desc = N_('prevent closing window with :only and :fclose'), + type = 'boolean', + }, { abbreviation = 'wiw', cb = 'did_set_winwidth', diff --git a/src/nvim/window.c b/src/nvim/window.c index b7d3bce5c5..a82dfa0f80 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -4248,10 +4248,12 @@ static int frame_minwidth(frame_T *topfrp, win_T *next_curwin) /// Buffers in the other windows become hidden if 'hidden' is set, or '!' is /// used and the buffer was modified. /// -/// Used by ":bdel" and ":only". +/// Used by ":tabclose" and ":only". /// -/// @param forceit always hide all other windows -void close_others(int message, int forceit) +/// @param message if true, display error messages +/// @param forceit always hide all other windows +/// @param ignore_pinned if true, also close pinned windows (for :tabclose) +void close_others(int message, int forceit, bool ignore_pinned) { win_T *const old_curwin = curwin; @@ -4280,7 +4282,8 @@ void close_others(int message, int forceit) curbuf = curwin->w_buffer; } - if (wp == curwin) { // don't close current window + // don't close current window or pinned windows + if (wp == curwin || (wp->w_p_wp && !ignore_pinned)) { continue; } @@ -4312,7 +4315,17 @@ void close_others(int message, int forceit) } if (message && !ONE_WINDOW) { - emsg(_("E445: Other window contains changes")); + // Check if remaining windows are non-pinned + bool has_non_pinned = false; + for (win_T *wp = firstwin; wp != NULL; wp = wp->w_next) { + if (wp != curwin && !wp->w_p_wp) { + has_non_pinned = true; + break; + } + } + if (has_non_pinned) { + emsg(_("E445: Other window contains changes")); + } } } diff --git a/src/nvim/winfloat.c b/src/nvim/winfloat.c index f222f6b17e..e949aaf141 100644 --- a/src/nvim/winfloat.c +++ b/src/nvim/winfloat.c @@ -298,6 +298,9 @@ void win_float_remove(bool bang, int count) { kvec_t(win_T *) float_win_arr = KV_INITIAL_VALUE; for (win_T *wp = lastwin; wp && wp->w_floating; wp = wp->w_prev) { + if (wp->w_config.hide || wp->w_p_wp) { + continue; + } kv_push(float_win_arr, wp); } if (float_win_arr.size > 0) { -- cgit v1.3-3-g829e