patch 9.2.0144: 'statuslineopt' is a global only option
Commit:
https://github.com/vim/vim/commit/2d14d62c508e91b5533f1dad0882938c06cbf6b5
Author: Hirohito Higashi <[email protected]>
Date: Thu Mar 12 18:49:38 2026 +0000
patch 9.2.0144: 'statuslineopt' is a global only option
Problem: 'statuslineopt' is a global only option and configuring the
line height is limited.
Solution: Make 'statuslineopt' global-local to a window and allow to
configure a fixed-height height statusline per window
(Hirohito Higashi).
closes: #19622
Signed-off-by: Hirohito Higashi <[email protected]>
Signed-off-by: Christian Brabandt <[email protected]>
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index bc4c68abe..3542eb345 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -8532,8 +8532,8 @@ A jump table for the options with a short description can
be found at |Q_op|.
windows.
The number N must be between 1 and 9. See |hl-User1..9|
*stl-%@*
- @ - Insert a line break (for the |tabpanel| or when the status line
- 'statuslineopt' has a "maxheight" value greater than 1).
+ @ - Inserts a newline. This only takes effect when the "maxheight"
+ value of 'statuslineopt' is greater than 1, or for |tabpanel|.
When displaying a flag, Vim removes the leading comma, if any, when
that flag comes right after plaintext. This will make a nice display
@@ -8594,31 +8594,48 @@ A jump table for the options with a short description
can be found at |Q_op|.
<
*'statuslineopt'* *'stlo'*
'statuslineopt' 'stlo' string (default "")
- global
+ global or local to window |global-local|
{not available when compiled without the |+statusline|
feature}
Optional settings for |status-line|. It can consist of the following
items. All are optional. Items must be separated by a comma.
- maxheight:{n} Set the maximum status line height to {n}.
- {n} must be 1 or greater.
- If not present, 1 is used.
- Check whether {n} is applicable to all windows
- on all tab pages, and if not, set it to the
- closest possible value.
- The option value is updated to the actual
- applied value. For example, setting
- maxheight:999 may result in a smaller value
- depending on the available space.
- When {n} is set to 2 or more, you can use "@"
- in 'statusline' to display information on
- multiple lines in the status line.
- See |stl-%@|.
-
+ fixedheight Fix the status line height to "maxheight".
+ Without this, the height varies from 1 to
+ "maxheight" based on the number of newline
+ |stl-%@| in 'statusline'.
+
+ maxheight:{n} Set the maximum status line height to {n}.
+ {n} must be 1 or greater. If not specified, the
+ height is 1.
+ When {n} is 2 or more, you can use newline
+ |stl-%@| in 'statusline' to display information on
+ multiple lines.
+
+ If {n} cannot be set due to insufficient window
+ height or other constraints, a best-effort
+ approach will be taken to set the closest possible
+ value that does not exceed {n}. No error will be
+ shown even if the setting cannot be changed.
+
+ For the global value, a value applicable to all
+ windows on all tab pages is used. For a
+ window-local value, a value applicable to that
+ window is used.
+ Note: When the applied value differs from {n}, the
+ option is updated to reflect it.
+ Example of updated options: >vim
+ :set statuslineopt=maxheight:999,fixedheight
+ " Only 20 lines could be applied, so
+ " maxheight is updated to 20.
+ :set statuslineopt?
+ statuslineopt=maxheight:20,fixedheight
+<
Examples: >
+ :set statuslineopt=fixedheight
:set stlo=
- :set stlo=maxheight:2
- :set stlo+=maxheight:3
+ :set stlo=maxheight:2,fixedheight
+ :set stlo-=fixedheight
<
*'suffixes'* *'su'*
'suffixes' 'su' string (default ".bak,~,.o,.h,.info,.swp,.obj")
@@ -8805,7 +8822,7 @@ A jump table for the options with a short description can
be found at |Q_op|.
You can use |g:actual_curtabpage| within a function assigned to
tabpanel. |g:actual_curtabpage| represents current tab's label number.
- You can use "%@" or "
" to insert a new line: >
+ You can use "%@" or "
" to insert a newline: >
set tabpanel=%!TabPanel()
function! TabPanel() abort
diff --git a/runtime/doc/version9.txt b/runtime/doc/version9.txt
index 6bbb700e9..1e7dcf70c 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 Mar 11
+*version9.txt* For Vim version 9.2. Last change: 2026 Mar 12
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -52612,8 +52612,8 @@ Autocommands: ~
Options: ~
-'statuslineopt' Extra options for the 'statusline', e.g. use the
- "maxheight" suboption to use several lines.
+'statuslineopt' Extra window-local options for the
'statusline', to
+ configure the height.
't_BS' Begin synchronized update.
't_ES' End synchronized update.
'termresize' Method for handling terminal resize events.
diff --git a/runtime/doc/windows.txt b/runtime/doc/windows.txt
index 37b7289b2..f27cc6b05 100644
--- a/runtime/doc/windows.txt
+++ b/runtime/doc/windows.txt
@@ -122,10 +122,9 @@ when the last window also has a status line:
'laststatus' = 1 status line if there is more than one window
'laststatus' = 2 always a status line
-You can change the contents of the status line with the 'statusline' option.
-This option can be local to the window, so that you can have a different
-status line in each window.
-You can change the height of the status line with the 'statuslineopt' option.
+You can change the contents and height of the status line with the
+'statusline' and 'statuslineopt' options. Both can be local to the window,
+allowing each window to have a unique status line appearance and height.
Normally, inversion is used to display the status line. This can be changed
with the 's' character in the 'highlight' option. For example, "sb" sets it
diff --git a/src/buffer.c b/src/buffer.c
index f2d3a6271..fa0119785 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -3347,6 +3347,10 @@ get_winopts(buf_T *buf)
win_T *wp = wip->wi_win;
copy_winopt(&wp->w_onebuf_opt, &curwin->w_onebuf_opt);
+#ifdef FEAT_STL_OPT
+ // w_stl_rendered_height is not in winvar_T; copy it explicitly.
+ curwin->w_stl_rendered_height = wp->w_stl_rendered_height;
+#endif
#ifdef FEAT_FOLDING
curwin->w_fold_manual = wp->w_fold_manual;
curwin->w_foldinvalid = TRUE;
@@ -3372,6 +3376,24 @@ get_winopts(buf_T *buf)
// Set 'foldlevel' to 'foldlevelstart' if it's not negative.
if (p_fdls >= 0)
curwin->w_p_fdl = p_fdls;
+#endif
+#ifdef FEAT_STL_OPT
+ // Update rendered height for window-local 'statusline'.
+ if (*curwin->w_p_stl != NUL)
+ update_stl_rendered_height(curwin);
+ // If the effective statuslineopt changed, re-adjust all window heights.
+ if (curwin->w_status_height > 0
+ && curwin->w_status_height != statusline_height(curwin))
+ {
+ frame_change_statusline_height();
+ // win_split_ins() estimated the new window's stlh from the splitting
+ // window, which may differ from the actual stlh after option
+ // inheritance is resolved here. frame_change_statusline_height()
+ // redistributes space within the stale fr_height values set during the
+ // split, so the content rows may be unequal. Re-equalize if needed.
+ if (p_ea)
+ win_equal(curwin, TRUE, 'v');
+ }
#endif
after_copy_winopt(curwin);
}
@@ -4423,9 +4445,15 @@ get_stl_rendered_height(
int rendered_height = 0;
char_u buf[MAXPATHL] = {0};
+ // Suppress errors while computing height: expressions (%!expr, %{expr})
+ // are evaluated here only to count rendered lines, not for their side
+ // effects. Any errors (e.g. E48 in sandbox) will surface properly when
+ // the statusline is actually drawn.
+ ++emsg_off;
(void)build_stl_str_hl_local(STL_MODE_GET_RENDERED_HEIGHT,
wp, buf, sizeof(buf), &fmt,
opt_name, opt_scope, 0, 0, NULL, NULL, &rendered_height);
+ --emsg_off;
return rendered_height;
}
diff --git a/src/option.c b/src/option.c
index 68de5b557..2228f90e3 100644
--- a/src/option.c
+++ b/src/option.c
@@ -6593,6 +6593,9 @@ unset_global_local_option(char_u *name, void *from)
case PV_STL:
clear_string_option(&((win_T *)from)->w_p_stl);
break;
+ case PV_STLO:
+ clear_string_option(&((win_T *)from)->w_p_stlo);
+ break;
# endif
case PV_UL:
buf->b_p_ul = NO_LOCAL_UNDOLEVEL;
@@ -6686,6 +6689,7 @@ get_varp_scope(struct vimoption *p, int scope)
#endif
#ifdef FEAT_STL_OPT
case PV_STL: return (char_u *)&(curwin->w_p_stl);
+ case PV_STLO: return (char_u *)&(curwin->w_p_stlo);
#endif
case PV_UL: return (char_u *)&(curbuf->b_p_ul);
case PV_LW: return (char_u *)&(curbuf->b_p_lw);
@@ -6801,6 +6805,8 @@ get_varp(struct vimoption *p)
#ifdef FEAT_STL_OPT
case PV_STL: return *curwin->w_p_stl != NUL
? (char_u *)&(curwin->w_p_stl) : p->var;
+ case PV_STLO: return *curwin->w_p_stlo != NUL
+ ? (char_u *)&(curwin->w_p_stlo) : p->var;
#endif
case PV_UL: return curbuf->b_p_ul != NO_LOCAL_UNDOLEVEL
? (char_u *)&(curbuf->b_p_ul) : p->var;
@@ -7050,6 +7056,11 @@ win_copy_options(win_T *wp_from, win_T *wp_to)
{
copy_winopt(&wp_from->w_onebuf_opt, &wp_to->w_onebuf_opt);
copy_winopt(&wp_from->w_allbuf_opt, &wp_to->w_allbuf_opt);
+#ifdef FEAT_STL_OPT
+ // w_stl_rendered_height is in win_T directly, not in winvar_T, so it is
+ // not copied by copy_winopt(). Copy it explicitly here.
+ wp_to->w_stl_rendered_height = wp_from->w_stl_rendered_height;
+#endif
after_copy_winopt(wp_to);
}
@@ -7119,6 +7130,9 @@ copy_winopt(winopt_T *from, winopt_T *to)
#endif
#ifdef FEAT_STL_OPT
to->wo_stl = copy_option_val(from->wo_stl);
+ to->wo_stlo = copy_option_val(from->wo_stlo);
+ to->wo_stlo_fh = from->wo_stlo_fh;
+ to->wo_stlo_mh = from->wo_stlo_mh;
#endif
to->wo_wrap = from->wo_wrap;
#ifdef FEAT_DIFF
@@ -7241,6 +7255,7 @@ check_winopt(winopt_T *wop UNUSED)
#endif
#ifdef FEAT_STL_OPT
check_string_option(&wop->wo_stl);
+ check_string_option(&wop->wo_stlo);
#endif
#ifdef FEAT_SYN_HL
check_string_option(&wop->wo_culopt);
@@ -7295,6 +7310,7 @@ clear_winopt(winopt_T *wop UNUSED)
#endif
#ifdef FEAT_STL_OPT
clear_string_option(&wop->wo_stl);
+ clear_string_option(&wop->wo_stlo);
#endif
#ifdef FEAT_SYN_HL
clear_string_option(&wop->wo_culopt);
diff --git a/src/option.h b/src/option.h
index 966b0bdea..305a92ad8 100644
--- a/src/option.h
+++ b/src/option.h
@@ -1389,6 +1389,7 @@ enum
#endif
#ifdef FEAT_STL_OPT
, WV_STL
+ , WV_STLO
#endif
, WV_WFB
, WV_WFH
diff --git a/src/optiondefs.h b/src/optiondefs.h
index d89b12523..40733fdff 100644
--- a/src/optiondefs.h
+++ b/src/optiondefs.h
@@ -223,6 +223,7 @@
#endif
#ifdef FEAT_STL_OPT
# define PV_STL OPT_BOTH(OPT_WIN(WV_STL))
+# define PV_STLO OPT_BOTH(OPT_WIN(WV_STLO))
#endif
#define PV_UL OPT_BOTH(OPT_BUF(BV_UL))
#define PV_WFB OPT_WIN(WV_WFB)
@@ -2522,7 +2523,7 @@ static struct vimoption options[] =
{"statuslineopt" ,"stlo", P_STRING|P_VI_DEF|P_ALLOCED|P_RSTAT|P_MLE
|P_ONECOMMA|P_COLON|P_NODUP,
#ifdef FEAT_STL_OPT
- (char_u *)&p_stlo, PV_NONE, did_set_statuslineopt,
expand_set_statuslineopt,
+ (char_u *)&p_stlo, PV_STLO, did_set_statuslineopt,
expand_set_statuslineopt,
#else
(char_u *)NULL, PV_NONE, NULL, NULL,
#endif
diff --git a/src/optionstr.c b/src/optionstr.c
index aefb78ee8..4f180d22a 100644
--- a/src/optionstr.c
+++ b/src/optionstr.c
@@ -97,7 +97,7 @@ static char *(p_ssop_values[]) = {"buffers", "winpos",
"resize", "winsize",
NULL};
#endif
#if defined(FEAT_STL_OPT)
-static char *(p_stlo_values[]) = {"maxheight:", NULL};
+static char *(p_stlo_values[]) = {"fixedheight", "maxheight:", NULL};
#endif
// Keep in sync with SWB_ flags in option.h
static char *(p_swb_values[]) = {"useopen", "usetab", "split", "newtab",
"vsplit", "uselast", NULL};
@@ -4215,15 +4215,89 @@ expand_set_splitkeep(optexpand_T *args, int
*numMatches, char_u ***matches)
char *
did_set_statusline(optset_T *args)
{
- char *ret = parse_statustabline_rulerformat(args, FALSE);
+ char_u **varp = (char_u **)args->os_varp;
+ char *ret = parse_statustabline_rulerformat(args, FALSE);
if (ret != NULL)
return ret;
+ update_stl_rendered_height(varp == &curwin->w_p_stl ? curwin : NULL);
frame_change_statusline_height();
return NULL;
}
+/*
+ * Rewrite the 'statuslineopt' string: replace the last "maxheight:N" with
+ * actual_stlh and remove earlier duplicates. Keyword order is preserved.
+ */
+ static void
+update_stlo_maxheight(char_u **varp, int actual_stlh)
+{
+ const char_u *src = *varp;
+ const char_u *last_mh = NULL;
+ const char_u *p;
+
+ // Find the last "maxheight:" token.
+ p = src;
+ while (*p != NUL)
+ {
+ if (STRNCMP(p, "maxheight:", 10) == 0)
+ last_mh = p;
+ while (*p != ',' && *p != NUL)
+ ++p;
+ if (*p == ',')
+ ++p;
+ }
+
+ if (last_mh == NULL)
+ return;
+
+ size_t bufsize = STRLEN(src) + NUMBUFLEN + 1;
+ char_u *buf = alloc(bufsize);
+ if (buf == NULL)
+ return;
+
+ int len = 0;
+ bool need_comma = false;
+
+ p = src;
+ while (*p != NUL)
+ {
+ const char_u *tok = p;
+ int tok_len;
+
+ while (*p != ',' && *p != NUL)
+ ++p;
+ tok_len = (int)(p - tok);
+ if (*p == ',')
+ ++p;
+
+ if (STRNCMP(tok, "maxheight:", 10) == 0)
+ {
+ if (tok == last_mh)
+ {
+ // Replace the last occurrence with the actual value.
+ if (need_comma)
+ buf[len++] = ',';
+ len += vim_snprintf((char *)buf + len,
+ bufsize - len, "maxheight:%d", actual_stlh);
+ need_comma = true;
+ }
+ }
+ else
+ {
+ if (need_comma)
+ buf[len++] = ',';
+ mch_memmove(buf + len, tok, tok_len);
+ len += tok_len;
+ need_comma = true;
+ }
+ }
+ buf[len] = NUL;
+ free_string_option(*varp);
+ *varp = buf;
+}
+
/*
* The 'statuslineopt' option is changed.
*/
@@ -4231,20 +4305,49 @@ did_set_statusline(optset_T *args)
did_set_statuslineopt(optset_T *args)
{
char_u **varp = (char_u **)args->os_varp;
+ win_T *wp = varp == &curwin->w_p_stlo ? curwin : NULL;
- if (statuslineopt_changed(*varp) == FAIL)
+ if (statuslineopt_changed(*varp, wp) == FAIL)
return e_invalid_argument;
- frame_change_statusline_height();
+ // Sync stl_rendered_height with the current statusline.
+ update_stl_rendered_height(wp);
+ // Update the maxheight value to the actual value set.
+ // Note: Must be changed if p_stlo_values are changed.
if (*varp != empty_option)
{
- // Update the maxheight value to the actual value set.
- // Note: Must be changed if p_stlo_values are changed.
- free_string_option(*varp);
- vim_snprintf((char *)IObuff, IOSIZE, "maxheight:%d",
- statusline_height(NULL));
- *varp = vim_strsave(IObuff);
+ int actual_stlh;
+
+ if (wp != NULL)
+ {
+ frame_change_statusline_height();
+ actual_stlh = MIN(wp->w_p_stlo_mh,
+ wp->w_height + wp->w_status_height - p_wmh);
+ }
+ else
+ actual_stlh = frame_change_statusline_height();
+
+ // Only re-serialize when the window actually has a status line shown.
+ if (actual_stlh > 0)
+ {
+ update_stlo_maxheight(varp, actual_stlh);
+ // Update the parsed maxheight member directly.
+ if (wp != NULL)
+ wp->w_p_stlo_mh = actual_stlh;
+ else
+ set_stlo_mh(actual_stlh);
+ }
+ }
+ else
+ frame_change_statusline_height();
+
+ if ((args->os_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0)
+ {
+ // :set clears the local values.
+ clear_string_option(&curwin->w_p_stlo);
+ curwin->w_p_stlo_fh = -1;
+ curwin->w_p_stlo_mh = -1;
}
return NULL;
diff --git a/src/proto/window.pro b/src/proto/window.pro
index 7bc2c114c..cf060c20d 100644
--- a/src/proto/window.pro
+++ b/src/proto/window.pro
@@ -90,8 +90,10 @@ void win_new_width(win_T *wp, int width);
void win_comp_scroll(win_T *wp);
void command_height(void);
void last_status(int morewin);
-void frame_change_statusline_height(void);
-int statuslineopt_changed(char_u *stlopt);
+void update_stl_rendered_height(win_T *wp);
+int frame_change_statusline_height(void);
+void set_stlo_mh(int mh);
+int statuslineopt_changed(char_u *stlopt, win_T *wp);
int statusline_height(win_T *wp);
int tabline_height(void);
int last_stl_height(int morewin);
diff --git a/src/structs.h b/src/structs.h
index 2e0aaa31a..5f4e1fc16 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -312,6 +312,12 @@ typedef struct
#ifdef FEAT_STL_OPT
char_u *wo_stl;
# define w_p_stl w_onebuf_opt.wo_stl // 'statusline'
+ char_u *wo_stlo;
+# define w_p_stlo w_onebuf_opt.wo_stlo // 'statuslineopt'
+ int wo_stlo_fh;
+# define w_p_stlo_fh w_onebuf_opt.wo_stlo_fh // "fixedheight" of
'statuslineopt'
+ int wo_stlo_mh;
+# define w_p_stlo_mh w_onebuf_opt.wo_stlo_mh // "maxheight:" of 'statuslineopt'
#endif
int wo_scb;
#define w_p_scb w_onebuf_opt.wo_scb // 'scrollbind'
@@ -4066,11 +4072,12 @@ struct window_S
// status/command/winbar line(s)
int w_prev_winrow; // previous winrow used for
'splitkeep'
int w_prev_height; // previous height used for
'splitkeep'
+ int w_stl_rendered_height; // rendered height of
window-local 'stl'
+ // (number of "%@" + 1)
int w_status_height; // number of status lines.
// If 'statuslineopt' was changed, this
- // member will be the previous value until
- // call function
- // frame_change_statusline_height().
+ // member holds the previous value until
+ // frame_change_statusline_height() calls.
int w_wincol; // Leftmost column of window in
screen.
int w_width; // Width of window, excluding
separation.
int w_vsep_width; // Number of separator columns (0
or 1).
diff --git a/src/testdir/dumps/Test_statuslineopt_new_split_01.dump
b/src/testdir/dumps/Test_statuslineopt_new_split_01.dump
new file mode 100644
index 000000000..a91ee64da
--- /dev/null
+++ b/src/testdir/dumps/Test_statuslineopt_new_split_01.dump
@@ -0,0 +1,20 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|S+3#0000000&|E|G|1| @70
+| +0&&@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|S+1#0000000&|E|G|1| @70
+|S|E|G|2| @70
+|S|E|G|3| @70
+|S|E|G|4| @70
+|S|E|G|5| @70
+| +0&&@74
diff --git a/src/testdir/dumps/Test_statuslineopt_sp_foo_01.dump
b/src/testdir/dumps/Test_statuslineopt_sp_foo_01.dump
new file mode 100644
index 000000000..a91ee64da
--- /dev/null
+++ b/src/testdir/dumps/Test_statuslineopt_sp_foo_01.dump
@@ -0,0 +1,20 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|S+3#0000000&|E|G|1| @70
+| +0&&@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|S+1#0000000&|E|G|1| @70
+|S|E|G|2| @70
+|S|E|G|3| @70
+|S|E|G|4| @70
+|S|E|G|5| @70
+| +0&&@74
diff --git a/src/testdir/dumps/Test_statuslineopt_sp_split_01.dump
b/src/testdir/dumps/Test_statuslineopt_sp_split_01.dump
new file mode 100644
index 000000000..383286437
--- /dev/null
+++ b/src/testdir/dumps/Test_statuslineopt_sp_split_01.dump
@@ -0,0 +1,15 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|S+3#0000000&|E|G|1| @70
+|S|E|G|2| @70
+|S|E|G|3| @70
+|S|E|G|4| @70
+|S|E|G|5| @70
+| +0&&@74
+|~+0#4040ff13&| @73
+|S+1#0000000&|E|G|1| @70
+|S|E|G|2| @70
+|S|E|G|3| @70
+|S|E|G|4| @70
+|S|E|G|5| @70
+| +0&&@74
diff --git a/src/testdir/dumps/Test_statuslineopt_wincmd_eq_01.dump
b/src/testdir/dumps/Test_statuslineopt_wincmd_eq_01.dump
new file mode 100644
index 000000000..f0884413d
--- /dev/null
+++ b/src/testdir/dumps/Test_statuslineopt_wincmd_eq_01.dump
@@ -0,0 +1,12 @@
+> +0&#ffffff0@74
+|L+3&&|A| @72
+|L|B| @72
+|L|C| @72
+|L|D| @72
+|L|E| @72
+| +0&&@74
+|~+0#4040ff13&| @73
+|G+1#0000000&|A| @72
+|G|B| @72
+|G|C| @72
+| +0&&@74
diff --git a/src/testdir/dumps/Test_statuslineopt_wincmd_underscore_01.dump
b/src/testdir/dumps/Test_statuslineopt_wincmd_underscore_01.dump
new file mode 100644
index 000000000..5a189a25b
--- /dev/null
+++ b/src/testdir/dumps/Test_statuslineopt_wincmd_underscore_01.dump
@@ -0,0 +1,14 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|G+3#0000000&|A| @72
+|G|B| @72
+|G|C| @72
+| +0&&@74
+|G+1&&|A| @72
+|G|B| @72
+|G|C| @72
+| +0&&@74
diff --git a/src/testdir/test_options.vim b/src/testdir/test_options.vim
index bca393b1a..8097a3f04 100644
--- a/src/testdir/test_options.vim
+++ b/src/testdir/test_options.vim
@@ -2736,6 +2736,9 @@ func Test_set_option_window_global_local_all()
elseif opt == 'listchars'
exe 'setl ' .. opt .. '=tab:>>'
exe 'setg ' .. opt .. '=tab:++'
+ elseif opt == 'statuslineopt'
+ exe 'setl ' .. opt .. '=maxheight:4'
+ exe 'setg ' .. opt .. '=maxheight:5,fixedheight'
elseif opt == 'virtualedit'
exe 'setl ' .. opt .. '=all'
exe 'setg ' .. opt .. '=block'
@@ -2755,6 +2758,8 @@ func Test_set_option_window_global_local_all()
call assert_equal('vert:+,fold:+', eval('&g:' .. opt), 'option:' ..
opt)
elseif opt == 'listchars'
call assert_equal('tab:++', eval('&g:' .. opt), 'option:' .. opt)
+ elseif opt == 'statuslineopt'
+ call assert_equal('maxheight:5,fixedheight', eval('&g:' .. opt),
'option:' .. opt)
elseif opt == 'virtualedit'
call assert_equal('block', eval('&g:' .. opt), 'option:' .. opt)
else
diff --git a/src/testdir/test_statuslineopt.vim
b/src/testdir/test_statuslineopt.vim
index 791a65ae5..6454dbff8 100644
--- a/src/testdir/test_statuslineopt.vim
+++ b/src/testdir/test_statuslineopt.vim
@@ -39,6 +39,31 @@ def Test_statuslineopt()
set statuslineopt=maxheight:2
&statusline = "AAA"
var wid = win_getid()
+ s:Assert_match_statusline(wid, 1, ['^AAA *'])
+ &statusline = "AAA%@BBB"
+ s:Assert_match_statusline(wid, 2, ['^AAA *', '^BBB *'])
+ &statusline = "AAA%@BBB%@CCC"
+ s:Assert_match_statusline(wid, 2, ['^AAA *', '^BBB *'])
+
+ set statuslineopt=maxheight:3
+ s:Assert_match_statusline(wid, 3, ['^AAA *', '^BBB *', '^CCC *'])
+ &statusline = "AAA%@BBB"
+ s:Assert_match_statusline(wid, 2, ['^AAA *', '^BBB *'])
+
+ # Best effort
+ &statusline = "AAA%@BBB%@CCC"
+ set statuslineopt=maxheight:999
+ s:Assert_match_statusline(wid, 3, ['^AAA *', '^BBB *', '^CCC *'])
+
+ # Single line
+ set statuslineopt=maxheight:1
+ s:Assert_match_statusline(wid, 1, ['^AAA *'])
+enddef
+
+def Test_statuslineopt_fixedheight()
+ set statuslineopt=maxheight:2,fixedheight
+ &statusline = "AAA"
+ var wid = win_getid()
var stlh = 2
s:Assert_match_statusline(wid, stlh, ['^AAA *', '^ *'])
&statusline = "AAA%@BBB"
@@ -46,7 +71,7 @@ def Test_statuslineopt()
&statusline = "AAA%@BBB%@CCC"
s:Assert_match_statusline(wid, stlh, ['^AAA *', '^BBB *'])
- set statuslineopt=maxheight:3
+ set statuslineopt=maxheight:3,fixedheight
stlh = 3
s:Assert_match_statusline(wid, stlh, ['^AAA *', '^BBB *', '^CCC *'])
&statusline = "AAA%@BBB"
@@ -54,7 +79,7 @@ def Test_statuslineopt()
# Best effort
&statusline = "AAA%@BBB%@CCC"
- set statuslineopt=maxheight:999
+ set statuslineopt=maxheight:999,fixedheight
stlh = &lines - &ch - 1
s:Assert_match_statusline(wid, stlh, ['^AAA *', '^BBB *', '^CCC *', '^ *'])
@@ -89,12 +114,19 @@ def Test_statuslineopt_multi_win()
s:Assert_match_statusline(wid2, stlh, ['^CCC *', '^DDD *'])
s:Assert_match_statusline(wid3, stlh, ['^EEE *', '^FFF *'])
- # Best effort
- set statuslineopt=maxheight:999
- stlh = (&lines - &ch - 3) / 3
- s:Assert_match_statusline(wid1, stlh, ['^AAA *', '^BBB *', '^BB3 *', '^ *'])
- s:Assert_match_statusline(wid2, stlh, ['^CCC *', '^DDD *', '^DD3 *', '^ *'])
- s:Assert_match_statusline(wid3, stlh, ['^EEE *', '^FFF *', '^FF3 *', '^ *'])
+ # Best effort (fixedheight fills all available space)
+ # Window equalization may not give exactly equal frame heights depending on
+ # the terminal size, so allow a difference of 1 relative to wid1.
+ set statuslineopt=maxheight:999,fixedheight
+ var h1 = getwininfo(wid1)[0].height
+ assert_inrange(h1 - 1, h1 + 1, getwininfo(wid2)[0].height)
+ assert_inrange(h1 - 1, h1 + 1, getwininfo(wid3)[0].height)
+ s:Assert_match_statusline(wid1, getwininfo(wid1)[0].status_height,
+ ['^AAA *', '^BBB *', '^BB3 *', '^ *'])
+ s:Assert_match_statusline(wid2, getwininfo(wid2)[0].status_height,
+ ['^CCC *', '^DDD *', '^DD3 *', '^ *'])
+ s:Assert_match_statusline(wid3, getwininfo(wid3)[0].status_height,
+ ['^EEE *', '^FFF *', '^FF3 *', '^ *'])
# Single line
set statuslineopt=maxheight:1
@@ -104,6 +136,58 @@ def Test_statuslineopt_multi_win()
s:Assert_match_statusline(wid3, stlh, ['^EEE *'])
enddef
+def Test_statuslineopt_winlocal()
+ # Test window-local 'statuslineopt':
+ # wid1 uses global statuslineopt (maxheight:1)
+ # wid2 uses local statuslineopt (maxheight:3), both using the global
statusline
+ &statusline = "AAA%@BBB%@CCC"
+ set statuslineopt=maxheight:1
+ var wid1 = win_getid()
+ new ddd
+ var wid2 = win_getid()
+ setlocal statuslineopt=maxheight:3
+
+ s:Assert_match_statusline(wid1, 1, ['^AAA *'])
+ s:Assert_match_statusline(wid2, 3, ['^AAA *', '^BBB *', '^CCC *'])
+
+ # After clearing the local statuslineopt, wid2 reverts to global
(maxheight:1)
+ setlocal statuslineopt<
+ s:Assert_match_statusline(wid1, 1, ['^AAA *'])
+ s:Assert_match_statusline(wid2, 1, ['^AAA *'])
+
+ # Window with local statusline and local statuslineopt (fixedheight)
+ &l:statusline = "DDD%@EEE"
+ setlocal statuslineopt=maxheight:3,fixedheight
+ s:Assert_match_statusline(wid2, 3, ['^DDD *', '^EEE *', '^ *'])
+
+ # wid1 (global settings) is unaffected by wid2's local changes
+ s:Assert_match_statusline(wid1, 1, ['^AAA *'])
+enddef
+
+def Test_statuslineopt_split_inherit()
+ # setl stlo follows the same inheritance rules as setl stl:
+ # :sp - local stlo IS inherited (new window shows same buffer)
+ # :new - local stlo is NOT inherited (cleared when entering new buffer)
+ set statuslineopt=maxheight:1
+ &statusline = "SEG1%@SEG2%@SEG3"
+ setlocal statuslineopt=maxheight:3
+ var wid1 = win_getid()
+ s:Assert_match_statusline(wid1, 3, ['^SEG1 *', '^SEG2 *', '^SEG3 *'])
+
+ # :sp splits the same buffer - local stlo is inherited
+ split
+ var wid2 = win_getid()
+ s:Assert_match_statusline(wid2, 3, ['^SEG1 *', '^SEG2 *', '^SEG3 *'])
+ quit
+
+ # :new opens a new empty buffer - local stlo is NOT inherited, falls back to
+ # global (maxheight:1)
+ new
+ var wid3 = win_getid()
+ s:Assert_match_statusline(wid3, 1, ['^SEG1 *'])
+ quit
+enddef
+
let g:StloStatusVar = ''
def g:StloStatusLine(): string
return g:StloStatusVar
@@ -154,14 +238,14 @@ endfunc
func Test_statuslineopt_default_stl()
CheckScreendump
+ " fixedheight with no custom stl: status area is fixed at 4 rows, filled
+ " with fillchar and must not bleed through buffer content.
let lines =<< trim END
set laststatus=2
- set statuslineopt=maxheight:4
+ set statuslineopt=maxheight:4,fixedheight
END
call writefile(lines, 'XTest_statuslineopt_default_stl', 'D')
- " With no custom statusline, the extra status rows must be filled with
- " fillchar and must not bleed through buffer content.
let buf = g:RunVimInTerminal('-S XTest_statuslineopt_default_stl', {'rows':
8})
call term_sendkeys(buf, "\<C-L>")
call VerifyScreenDump(buf, 'Test_statuslineopt_default_stl_01', {})
@@ -169,4 +253,163 @@ func Test_statuslineopt_default_stl()
call StopVimInTerminal(buf)
endfunc
+def Test_statuslineopt_default_stl_maxheight()
+ # maxheight without fixedheight: status area adapts to rendered content.
+ # Default statusline is always single-line, so status area must be 1 row
+ # regardless of maxheight value.
+ set statuslineopt=maxheight:9
+ var wid = win_getid()
+ assert_equal(1, getwininfo(wid)[0].status_height)
+
+ # After :new the new window also has stlo=maxheight:9 but w_height should
+ # be equalized (both windows get roughly half the available content rows).
+ new
+ var wid2 = win_getid()
+ var h1 = getwininfo(wid)[0].height
+ var h2 = getwininfo(wid2)[0].height
+ # Both status areas must be 1 row (not 9)
+ assert_equal(1, getwininfo(wid)[0].status_height)
+ assert_equal(1, getwininfo(wid2)[0].status_height)
+ # Content heights must be nearly equal (differ by at most 1 due to p_wh)
+ assert_true(abs(h1 - h2) <= 1, $'h1={h1} h2={h2} should be equal')
+ only
+enddef
+
+func Test_statuslineopt_new_split()
+ CheckScreendump
+
+ " Test :new — local stlo is NOT inherited (new buffer), so the new window
+ " uses global stlo (maxheight:1, single-line) while the original keeps
+ " fixedheight,maxheight:5.
+ let lines =<< trim END
+ set laststatus=2
+ set statuslineopt=maxheight:1
+ set statusline=SEG1%@SEG2%@SEG3%@SEG4%@SEG5
+ setlocal statuslineopt=fixedheight,maxheight:5
+ END
+ call writefile(lines, 'XTest_statuslineopt_new_split', 'D')
+
+ let buf = g:RunVimInTerminal('-S XTest_statuslineopt_new_split', {'rows':
20})
+ call term_sendkeys(buf, ":new\<CR>\<C-L>")
+ call VerifyScreenDump(buf, 'Test_statuslineopt_new_split_01', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_statuslineopt_sp_split()
+ CheckScreendump
+
+ " Test :sp — local stlo IS inherited (same buffer), so both windows use
+ " fixedheight,maxheight:5.
+ let lines =<< trim END
+ set laststatus=2
+ set statusline=SEG1%@SEG2%@SEG3%@SEG4%@SEG5
+ setlocal statuslineopt=fixedheight,maxheight:5
+ END
+ call writefile(lines, 'XTest_statuslineopt_sp_split', 'D')
+
+ let buf = g:RunVimInTerminal('-S XTest_statuslineopt_sp_split', {'rows': 15})
+ call term_sendkeys(buf, ":sp\<CR>\<C-L>")
+ call VerifyScreenDump(buf, 'Test_statuslineopt_sp_split_01', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_statuslineopt_sp_foo()
+ CheckScreendump
+
+ " Test :sp foo — local stlo is NOT inherited (different file), so the new
+ " window uses global stlo (maxheight:1, single-line).
+ let lines =<< trim END
+ set laststatus=2
+ set statuslineopt=maxheight:1
+ set statusline=SEG1%@SEG2%@SEG3%@SEG4%@SEG5
+ setlocal statuslineopt=fixedheight,maxheight:5
+ END
+ call writefile(lines, 'XTest_statuslineopt_sp_foo', 'D')
+
+ let buf = g:RunVimInTerminal('-S XTest_statuslineopt_sp_foo', {'rows': 20})
+ call term_sendkeys(buf, ":sp Xfoo\<CR>\<C-L>")
+ call VerifyScreenDump(buf, 'Test_statuslineopt_sp_foo_01', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_statuslineopt_wincmd_eq()
+ CheckScreendump
+
+ " Test CTRL-W_= with mixed global stlo (maxheight:3) and local stlo
+ " (fixedheight,maxheight:5); equalization assigns equal content rows to
+ " both windows despite their different status-line heights.
+ let lines =<< trim END
+ set laststatus=2
+ set statuslineopt=maxheight:3
+ set statusline=GA%@GB%@GC
+ new
+ setlocal statuslineopt=fixedheight,maxheight:5
+ setlocal statusline=LA%@LB%@LC%@LD%@LE
+ END
+ call writefile(lines, 'XTest_statuslineopt_wincmd_eq', 'D')
+
+ let buf = g:RunVimInTerminal('-S XTest_statuslineopt_wincmd_eq', {'rows':
12})
+ call term_sendkeys(buf, "\<C-W>=\<C-L>")
+ call VerifyScreenDump(buf, 'Test_statuslineopt_wincmd_eq_01', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_statuslineopt_wincmd_underscore()
+ CheckScreendump
+
+ " Test CTRL-W__ with global stlo maxheight:3 and multi-line statusline.
+ " After maximizing, the large window should keep stlh=3, not collapse to 1
+ " because the minimized other window constrained global_stlh.
+ let lines =<< trim END
+ set laststatus=2
+ set statuslineopt=maxheight:3
+ set statusline=GA%@GB%@GC
+ new
+ END
+ call writefile(lines, 'XTest_statuslineopt_wincmd_underscore', 'D')
+
+ let buf = g:RunVimInTerminal('-S XTest_statuslineopt_wincmd_underscore',
{'rows': 14})
+ call term_sendkeys(buf, "\<C-W>_\<C-L>")
+ call VerifyScreenDump(buf, 'Test_statuslineopt_wincmd_underscore_01', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+def Test_statuslineopt_besteff_order()
+ # Test the best-effort option update: keyword order is preserved and
+ # duplicate maxheight: removed.
+
+ # "fixedheight" before "maxheight:" - order must be preserved.
+ :5split
+ set statuslineopt=fixedheight,maxheight:9
+ assert_equal('fixedheight,maxheight:5', &statuslineopt)
+ set statuslineopt&
+ only
+
+ # "maxheight:" before "fixedheight" - order must be preserved.
+ :5split
+ set statuslineopt=maxheight:9,fixedheight
+ assert_equal('maxheight:5,fixedheight', &statuslineopt)
+ set statuslineopt&
+ only
+
+ # set stlo+=maxheight:N: last maxheight wins, earlier one is removed.
+ :5split
+ set statuslineopt=fixedheight,maxheight:2
+ set statuslineopt+=maxheight:9
+ assert_equal('fixedheight,maxheight:5', &statuslineopt)
+ set statuslineopt&
+ only
+
+ # Duplicate maxheight: in one assignment: last wins, earlier removed.
+ :5split
+ set statuslineopt=maxheight:2,fixedheight,maxheight:9
+ assert_equal('fixedheight,maxheight:5', &statuslineopt)
+ only
+enddef
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/version.c b/src/version.c
index 4b6e592b3..d539758f5 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 */
+/**/
+ 144,
/**/
143,
/**/
diff --git a/src/window.c b/src/window.c
index 2925fbe23..f624e9e95 100644
--- a/src/window.c
+++ b/src/window.c
@@ -42,6 +42,7 @@ static int leave_tabpage(buf_T *new_curbuf, int
trigger_leave_autocmds);
static void enter_tabpage(tabpage_T *tp, buf_T *old_curbuf, int
trigger_enter_autocmds, int trigger_leave_autocmds);
static void frame_fix_height(win_T *wp);
static int frame_minheight(frame_T *topfrp, win_T *next_curwin);
+static int frame_wincount_in_height(frame_T *topfrp);
static int may_open_tabpage(void);
static int win_enter_ext(win_T *wp, int flags);
static void win_free(win_T *wp, tabpage_T *tp);
@@ -54,8 +55,8 @@ static void win_goto_hor(int left, long count);
static void frame_add_height(frame_T *frp, int n);
static void last_status_rec(frame_T *fr, int statusline);
#if defined(FEAT_STL_OPT)
-static void frame_change_statusline_height_rec(frame_T *frp,
- bool actual_change);
+static int frame_find_global_stlh_rec(frame_T *frp, int h);
+static void frame_change_statusline_height_rec(frame_T *frp, int global_stlh);
#endif
static void frame_flatten(frame_T *frp);
static void winframe_restore(win_T *wp, int dir, frame_T *unflat_altfr);
@@ -99,8 +100,10 @@ static int close_disallowed = 0;
static int frame_locked = 0;
#if defined(FEAT_STL_OPT)
-static int stlo_mh = STATUS_HEIGHT;
-static int stlh_effort;
+// rendered height of global 'stl' (number of "%@" + 1)
+static int stl_rendered_height = 1;
+static int stlo_fh = FALSE; // "fixedheight" flag cache of global 'stlo'
+static int stlo_mh = STATUS_HEIGHT; // "maxheight" value cache of global 'stlo'
#endif
/*
@@ -1191,20 +1194,18 @@ win_split_ins(
oldwin->w_status_height = statusline_height(oldwin);
oldwin_height -= oldwin->w_status_height;
}
+ // Estimated stl height for the new window.
+ int new_win_stlh = need_status != 0 ? need_status
+ : statusline_height(new_wp != NULL ? new_wp : curwin);
if (new_size == 0)
- {
- if (lastwin == firstwin && p_ls == 0)
- new_size = (oldwin_height - statusline_height(oldwin) + 1) / 2;
- else
- new_size = (oldwin_height - oldwin->w_status_height + 1) / 2;
- }
- if (new_size > available - minheight - statusline_height(oldwin))
- new_size = available - minheight - statusline_height(oldwin);
+ new_size = (oldwin_height - new_win_stlh + 1) / 2;
+ if (new_size > available - minheight - new_win_stlh)
+ new_size = available - minheight - new_win_stlh;
if (new_size < wmh1)
new_size = wmh1;
// if it doesn't fit in the current window, need win_equal()
- if (oldwin_height - new_size - statusline_height(oldwin) < p_wmh)
+ if (oldwin_height - new_size - new_win_stlh < p_wmh)
do_equal = TRUE;
// We don't like to take lines for the new window from a
@@ -1486,6 +1487,11 @@ win_split_ins(
/*
* equalize the window sizes.
+ * Note: this win_equal() runs before get_winopts() changes the new
+ * window's statusline height (stlh). If the new window's stlh differs
+ * from the estimate used for new_size (e.g. because it won't inherit a
+ * local 'statuslineopt' from the current window), get_winopts() will call
+ * win_equal() again after adjusting the stlh.
*/
if (do_equal || dir != 0)
win_equal(wp, TRUE,
@@ -2232,7 +2238,7 @@ win_equal_rec(
else
// These windows don't use up room.
totwincount -= (n + (fr->fr_next == NULL
- ? extra_sep : 0)) / (p_wmw + 1);
+ ? extra_sep : 0)) / (p_wmw + 1);
room -= new_size - n;
if (room < 0)
{
@@ -2328,15 +2334,7 @@ win_equal_rec(
if (dir != 'h') // equalize frame heights
{
- // Compute maximum number of windows vertically in this frame.
- n = frame_minheight(topfr, NOWIN);
- // add one for the bottom window if it doesn't have a statusline
- if (row + height == cmdline_row && p_ls == 0)
- extra_sep = statusline_height(NULL);
- else
- extra_sep = 0;
- totwincount = (n + extra_sep) / (p_wmh
- + statusline_height(NULL));
+ totwincount = frame_wincount_in_height(topfr);
has_next_curwin = frame_has_win(topfr, next_curwin);
/*
@@ -2369,14 +2367,14 @@ win_equal_rec(
{
room += p_wh - p_wmh;
next_curwin_size = 0;
- if (new_size < p_wh)
- new_size = p_wh;
+ // n + (p_wh - p_wmh) = frame_minheight(fr,
+ // next_curwin): min height for curwin.
+ if (new_size < n + (p_wh - p_wmh))
+ new_size = n + (p_wh - p_wmh);
}
else
// These windows don't use up room.
- totwincount -= (n + (fr->fr_next == NULL
- ? extra_sep : 0))
- / (p_wmh + statusline_height(NULL));
+ totwincount -= frame_wincount_in_height(fr);
room -= new_size - n;
if (room < 0)
{
@@ -2426,8 +2424,7 @@ win_equal_rec(
{
// Compute the maximum number of windows vert. in "fr".
n = frame_minheight(fr, NOWIN);
- wincount = (n + (fr->fr_next == NULL ? extra_sep : 0))
- / (p_wmh + statusline_height(NULL));
+ wincount = frame_wincount_in_height(fr);
m = frame_minheight(fr, next_curwin);
if (has_next_curwin)
hnc = frame_has_win(fr, next_curwin);
@@ -4398,6 +4395,40 @@ frame_minheight(frame_T *topfrp, win_T *next_curwin)
return m;
}
+/*
+ * Compute the window count in height for frame "topfrp".
+ */
+static int
+frame_wincount_in_height(frame_T *topfrp)
+{
+ frame_T *frp;
+ int c = 0;
+ int n;
+
+ if (topfrp->fr_win != NULL)
+ c = 1;
+ else if (topfrp->fr_layout == FR_ROW)
+ {
+ // get the max window count across all frames in this row
+ c = 0;
+ FOR_ALL_FRAMES(frp, topfrp->fr_child)
+ {
+ n = frame_wincount_in_height(frp);
+ if (n > c)
+ c = n;
+ }
+ }
+ else
+ {
+ // Sum the window counts for all frames in this column.
+ c = 0;
+ FOR_ALL_FRAMES(frp, topfrp->fr_child)
+ c += frame_wincount_in_height(frp);
+ }
+
+ return c;
+}
+
/*
* Compute the minimal width for frame "topfrp".
* When "next_curwin" isn't NULL, use p_wiw for this window.
@@ -7637,28 +7668,61 @@ last_status_rec(frame_T *fr, int statusline)
#if defined(FEAT_STL_OPT)
/*
- * Set a status line height to windows at the bottom of "frp".
- * Note: Does not check if there is room!
+ * Update the cached rendered height of 'statusline'.
+ * If "wp" is NULL, update the global stl_rendered_height using p_stl.
+ * If "wp" is non-NULL, update wp->w_stl_rendered_height using wp->w_p_stl.
*/
void
-frame_change_statusline_height(void)
+update_stl_rendered_height(win_T *wp)
{
- tabpage_T *tp;
+ char_u *opt_name = (char_u *)"statusline";
+ char_u *stl_val = (wp == NULL) ? p_stl : wp->w_p_stl;
+ int opt_flags = (wp == NULL) ? 0 : OPT_LOCAL;
+ int *height = (wp == NULL) ? &stl_rendered_height
+ : &wp->w_stl_rendered_height;
- stlh_effort = stlo_mh;
- FOR_ALL_TABPAGES(tp)
- frame_change_statusline_height_rec(tp->tp_topframe, false);
+ *height = get_stl_rendered_height(wp == NULL ? curwin : wp, stl_val,
+ opt_name, opt_flags);
+}
- stlo_mh = stlh_effort;
- FOR_ALL_TABPAGES(tp)
- frame_change_statusline_height_rec(tp->tp_topframe, true);
+/*
+ * First pass: clamp the global statusline height against available space.
+ * Returns the space-constrained height.
+ */
+ static int
+frame_find_global_stlh_rec(frame_T *frp, int h)
+{
+ if (frp->fr_layout == FR_LEAF)
+ {
+ win_T *wp = frp->fr_win;
- comp_col();
- redraw_all_later(UPD_SOME_VALID);
+ // Only consider windows with a status line that use global stlo.
+ // Exclude windows at minimum height (w_height <= p_wmh): they can
+ // only afford 1 status line row anyway, and should not constrain
+ // the global stlh for larger windows (e.g. after CTRL-W__).
+ if (wp->w_height > p_wmh && wp->w_status_height > 0
+ && *wp->w_p_stlo == NUL)
+ {
+ int win_free_height = frp->fr_height - WINBAR_HEIGHT(wp);
+
+ if (win_free_height - p_wmh < h)
+ h = win_free_height - p_wmh;
+ }
+ }
+ else
+ {
+ for (frp = frp->fr_child; frp != NULL; frp = frp->fr_next)
+ h = frame_find_global_stlh_rec(frp, h);
+ }
+ return h;
}
+/*
+ * Second pass: apply statusline heights.
+ * "global_stlh" is the space-constrained height for windows using global stlo.
+ */
static void
-frame_change_statusline_height_rec(frame_T *frp, bool actual_change)
+frame_change_statusline_height_rec(frame_T *frp, int global_stlh)
{
if (frp->fr_layout == FR_LEAF)
{
@@ -7668,36 +7732,70 @@ frame_change_statusline_height_rec(frame_T *frp, bool
actual_change)
{
int win_free_height = frp->fr_height - WINBAR_HEIGHT(wp);
- if (actual_change)
- {
- wp->w_status_height = stlo_mh;
- if (wp->w_status_height > win_free_height - p_wmh)
- {
- wp->w_status_height = win_free_height - p_wmh;
- }
- win_new_height(wp, win_free_height - wp->w_status_height);
- }
- else
+ wp->w_status_height = statusline_height(wp);
+ if (wp->w_p_stlo == NULL || *wp->w_p_stlo == NUL)
{
- if (win_free_height - p_wmh < stlh_effort)
- stlh_effort = win_free_height - p_wmh;
+ // Global stlo: cap at the uniform space-constrained height so
+ // that all windows share the same status line height.
+ if (wp->w_status_height > global_stlh)
+ wp->w_status_height = global_stlh;
}
+ if (wp->w_status_height > win_free_height - p_wmh)
+ wp->w_status_height = win_free_height - p_wmh;
+ // Preserve the buffer view: statusline height changes should
+ // not scroll the window. win_new_height() calls
+ // scroll_to_fraction() which adjusts w_topline; restore it
+ // and let update_topline() handle cursor visibility for
+ // curwin during redraw.
+ linenr_T saved_topline = wp->w_topline;
+ int saved_topfill = wp->w_topfill;
+ win_new_height(wp, win_free_height - wp->w_status_height);
+ wp->w_topline = saved_topline;
+ wp->w_topfill = saved_topfill;
+ wp->w_valid &= ~(VALID_WROW | VALID_CROW
+ | VALID_BOTLINE | VALID_BOTLINE_AP);
}
}
- else if (frp->fr_layout == FR_ROW)
- {
- // Handle all the frames in the row.
- for (frp = frp->fr_child; frp != NULL; frp = frp->fr_next)
- frame_change_statusline_height_rec(frp, actual_change);
- }
- else // frp->fr_layout == FR_COL
+ else
{
- // Handle all the frames in the column.
for (frp = frp->fr_child; frp != NULL; frp = frp->fr_next)
- frame_change_statusline_height_rec(frp, actual_change);
+ frame_change_statusline_height_rec(frp, global_stlh);
}
}
+/*
+ * Adjust status line heights for all windows across all tab pages.
+ * Uses two passes: first finds the space-constrained global height,
+ * then applies appropriate heights to each window.
+ */
+ int
+frame_change_statusline_height(void)
+{
+ tabpage_T *tp;
+ int global_stlh;
+
+ // First pass: find space-constrained global height.
+ global_stlh = stlo_mh;
+ FOR_ALL_TABPAGES(tp)
+ global_stlh = frame_find_global_stlh_rec(tp->tp_topframe, global_stlh);
+
+ // Second pass: apply heights.
+ FOR_ALL_TABPAGES(tp)
+ frame_change_statusline_height_rec(tp->tp_topframe, global_stlh);
+ comp_col();
+ redraw_all_later(UPD_SOME_VALID);
+ return global_stlh;
+}
+
+/*
+ * Update the global maxheight cache for 'statuslineopt'.
+ */
+ void
+set_stlo_mh(int mh)
+{
+ stlo_mh = mh;
+}
+
/*
* Check "stlopt" as 'statuslineopt' and update the members of "wp".
* This is called when 'statuslineopt' is changed.
@@ -7705,13 +7803,17 @@ frame_change_statusline_height_rec(frame_T *frp, bool
actual_change)
*/
int
statuslineopt_changed(
- char_u *stlopt
- )
+ char_u *stlopt, // when NULL: use "wp->w_p_stlo"
+ win_T *wp) // when NULL: update global stlo_fh/stlo_mh
{
char_u *p;
+ int l_stlo_fh = FALSE;
int l_stlo_mh = 1;
- p = stlopt;
+ if (stlopt != NULL)
+ p = stlopt;
+ else
+ p = wp->w_p_stlo;
while (*p != NUL)
{
@@ -7723,34 +7825,75 @@ statuslineopt_changed(
if (l_stlo_mh < 1)
return FAIL;
}
+ else if (STRNCMP(p, "fixedheight", 11) == 0)
+ {
+ p += 11;
+ l_stlo_fh = TRUE;
+ }
if (*p != ',' && *p != NUL)
return FAIL;
if (*p == ',')
++p;
}
- stlo_mh = l_stlo_mh;
+ if (wp == NULL)
+ {
+ stlo_fh = l_stlo_fh;
+ stlo_mh = l_stlo_mh;
+ }
+ else
+ {
+ wp->w_p_stlo_fh = l_stlo_fh;
+ wp->w_p_stlo_mh = l_stlo_mh;
+ }
return OK;
}
#endif
/*
- * Return the number of lines used by the status line.
- * "wp" is not used currently because 'statuslineopt' is a global option.
- * NULL is passed when called from layout calculations (e.g. 'laststatus')
- * where no specific window context is available.
+ * Return the effective statusline height for window "wp".
+ * Uses the window-local 'statuslineopt' if set, otherwise the global one.
+ * When "wp" is NULL, always uses the global 'statuslineopt' settings.
*/
int
statusline_height(win_T *wp UNUSED)
{
- return
+ int stl_height = 1;
#if defined(FEAT_STL_OPT)
- stlo_mh
-#else
- STATUS_HEIGHT
+ bool fixed;
+ int l_stl_rendered_height;
+
+ if (wp != NULL && *wp->w_p_stlo != NUL)
+ {
+ stl_height = wp->w_p_stlo_mh;
+ fixed = !!wp->w_p_stlo_fh;
+ l_stl_rendered_height = (*wp->w_p_stl != NUL)
+ ? wp->w_stl_rendered_height
+ : stl_rendered_height;
+ }
+ else
+ {
+ stl_height = stlo_mh;
+ fixed = !!stlo_fh;
+ l_stl_rendered_height = (wp != NULL && *wp->w_p_stl != NUL)
+ ? wp->w_stl_rendered_height
+ : stl_rendered_height;
+ }
+
+ // When not fixed, clamp to rendered height (1 for default statusline).
+ if (!fixed)
+ {
+ int has_custom_stl = (*p_stl != NUL)
+ || (wp != NULL && *wp->w_p_stl != NUL);
+ if (has_custom_stl)
+ stl_height = MAX(1, MIN(l_stl_rendered_height, stl_height));
+ else
+ stl_height = 1;
+ }
#endif
- ;
+
+ return stl_height;
}
/*
--
--
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/E1w0lGP-0029tn-26%40256bit.org.