patch 9.2.0467: multi-line statusline loses highlighting attributes

Problem:  In a multi-line statusline (and 'tabpanel'), %#XX# / %N*
          set on one row do not persist on subsequent rows.
          build_stl_str_hl_local() rebuilds stl_items[] from scratch
          on every line break ("%@" or "\n"), so the highlight is
          reset at each row boundary even though within a row it
          stays until %* (or another %# / %*).
Solution: Carry the last Highlight item's stl_minwid across line
          breaks via a new in/out int* parameter "carry_hl".  At the
          start of each row, pre-insert a Highlight item from the
          carried value so the row begins under the same highlight;
          before returning, update the carried value with the row's
          final Highlight item.  Apply the same carry to the
          tabpanel rendering loop (Hirohito Higashi).

related: #19123
closes:  #20180

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Hirohito Higashi <h.east.727@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
Hirohito Higashi
2026-05-10 18:14:01 +00:00
committed by Christian Brabandt
parent 3a8b9e09b2
commit 5ef1eec5c5
9 changed files with 126 additions and 12 deletions
+38 -8
View File
@@ -50,7 +50,7 @@ static int build_stl_str_hl_local(stl_mode_T mode, win_T *wp,
char_u *out, size_t outlen, char_u **fmt_arg,
char_u *opt_name, int opt_scope, int fillchar, int maxwidth,
stl_hlrec_T **hltab, stl_hlrec_T **tabtab,
stl_clickrec_T **clicktab, int *lbreaks);
stl_clickrec_T **clicktab, int *lbreaks, int *carry_hl);
#endif
static int append_arg_number(win_T *wp, char_u *buf, size_t buflen, int add_file);
static void free_buffer(buf_T *);
@@ -4393,7 +4393,7 @@ build_stl_str_hl(
{
return build_stl_str_hl_local(STL_MODE_SINGLE, wp, out, outlen, &fmt,
opt_name, opt_scope, fillchar, maxwidth, hltab, tabtab, clicktab,
NULL);
NULL, NULL);
}
int
@@ -4408,11 +4408,13 @@ build_stl_str_hl_mline(
int maxwidth,
stl_hlrec_T **hltab, // return: HL attributes (can be NULL)
stl_hlrec_T **tabtab, // return: tab page nrs (can be NULL)
stl_clickrec_T **clicktab) // return: click func regions (can be NULL)
stl_clickrec_T **clicktab, // return: click func regions (can be NULL)
int *carry_hl) // (in/out) %# / %* highlight carried across
// line breaks (can be NULL)
{
return build_stl_str_hl_local(STL_MODE_MULTI, wp, out, outlen, fmt,
opt_name, opt_scope, fillchar, maxwidth, hltab, tabtab, clicktab,
NULL);
NULL, carry_hl);
}
# ifdef ENABLE_STL_MODE_MULTI_NL
@@ -4428,11 +4430,13 @@ build_stl_str_hl_mline_nl(
int maxwidth,
stl_hlrec_T **hltab, // return: HL attributes (can be NULL)
stl_hlrec_T **tabtab, // return: tab page nrs (can be NULL)
stl_clickrec_T **clicktab) // return: click func regions (can be NULL)
stl_clickrec_T **clicktab, // return: click func regions (can be NULL)
int *carry_hl) // (in/out) %# / %* highlight carried across
// line breaks (can be NULL)
{
return build_stl_str_hl_local(STL_MODE_MULTI_NL, wp, out, outlen, fmt,
opt_name, opt_scope, fillchar, maxwidth, hltab, tabtab, clicktab,
NULL);
NULL, carry_hl);
}
# endif
@@ -4453,7 +4457,8 @@ get_stl_rendered_height(
++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, NULL, &rendered_height);
opt_name, opt_scope, 0, 0, NULL, NULL, NULL, &rendered_height,
NULL);
--emsg_off;
return rendered_height;
}
@@ -4489,7 +4494,9 @@ build_stl_str_hl_local(
stl_hlrec_T **hltab, // return: HL attributes (can be NULL)
stl_hlrec_T **tabtab, // return: tab page nrs (can be NULL)
stl_clickrec_T **clicktab, // return: click func regions (can be NULL)
int *rendered_height) // return: stl rendered height (can be NULL)
int *rendered_height, // return: stl rendered height (can be NULL)
int *carry_hl) // (in/out) %# / %* highlight carried across
// line breaks (can be NULL)
{
linenr_T lnum;
colnr_T len;
@@ -4614,6 +4621,18 @@ build_stl_str_hl_local(
# endif
p = out;
curitem = 0;
// Pre-insert a Highlight item from carry_hl so that %# / %* set on a
// previous multi-line statusline row continues to apply on this row.
if (carry_hl != NULL && *carry_hl != 0)
{
stl_items[curitem].stl_type = Highlight;
stl_items[curitem].stl_start = p;
stl_items[curitem].stl_minwid = *carry_hl;
stl_items[curitem].stl_clickfunc = NULL;
curitem++;
}
prevchar_isflag = TRUE;
prevchar_isitem = FALSE;
for (s = usefmt; *s != NUL; )
@@ -5446,6 +5465,17 @@ find_linebreak:
outputlen = (size_t)(p - out);
itemcnt = curitem;
// Remember the most recent %# / %* highlight so the next row of a
// multi-line statusline can resume it.
if (carry_hl != NULL)
{
int last_hl = 0;
for (l = 0; l < itemcnt; l++)
if (stl_items[l].stl_type == Highlight)
last_hl = stl_items[l].stl_minwid;
*carry_hl = last_hl;
}
if (mode == STL_MODE_MULTI
# ifdef ENABLE_STL_MODE_MULTI_NL
|| mode == STL_MODE_MULTI_NL
+2 -2
View File
@@ -50,8 +50,8 @@ void maketitle(void);
void resettitle(void);
void free_titles(void);
int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, char_u *opt_name, int opt_scope, int fillchar, int maxwidth, stl_hlrec_T **hltab, stl_hlrec_T **tabtab, stl_clickrec_T **clicktab);
int build_stl_str_hl_mline(win_T *wp, char_u *out, size_t outlen, char_u **fmt, char_u *opt_name, int opt_scope, int fillchar, int maxwidth, stl_hlrec_T **hltab, stl_hlrec_T **tabtab, stl_clickrec_T **clicktab);
int build_stl_str_hl_mline_nl(win_T *wp, char_u *out, size_t outlen, char_u **fmt, char_u *opt_name, int opt_scope, int fillchar, int maxwidth, stl_hlrec_T **hltab, stl_hlrec_T **tabtab, stl_clickrec_T **clicktab);
int build_stl_str_hl_mline(win_T *wp, char_u *out, size_t outlen, char_u **fmt, char_u *opt_name, int opt_scope, int fillchar, int maxwidth, stl_hlrec_T **hltab, stl_hlrec_T **tabtab, stl_clickrec_T **clicktab, int *carry_hl);
int build_stl_str_hl_mline_nl(win_T *wp, char_u *out, size_t outlen, char_u **fmt, char_u *opt_name, int opt_scope, int fillchar, int maxwidth, stl_hlrec_T **hltab, stl_hlrec_T **tabtab, stl_clickrec_T **clicktab, int *carry_hl);
int get_stl_rendered_height(win_T *wp, char_u *fmt, char_u *opt_name, int opt_scope);
int get_rel_pos(win_T *wp, char_u *buf, int buflen);
char_u *fix_fname(char_u *fname);
+3 -1
View File
@@ -1479,6 +1479,7 @@ win_redr_custom(
*out_count = 0;
}
int carry_hl = 0;
for (int i = 0; i < stlh_cnt; i++)
{
col = col_save;
@@ -1487,7 +1488,8 @@ win_redr_custom(
&stl_tmp,
opt_name, opt_scope,
fillchar, maxwidth, &hltab, &tabtab,
&clicktab);
&clicktab,
&carry_hl);
// Make all characters printable.
p = transstr(buf);
+4 -1
View File
@@ -688,6 +688,8 @@ do_by_tplmode(
if (usefmt != NULL && *usefmt != NUL)
{
int carry_hl = 0;
while (*usefmt != NUL)
{
char_u buf[IOSIZE];
@@ -708,7 +710,8 @@ do_by_tplmode(
(args.cwp, buf, sizeof(buf),
&usefmt, opt_name, opt_scope, TPL_FILLCHAR,
args.col_end - args.col_start, &hltab, &tabtab,
tplmode == TPLMODE_REDRAW ? &clicktab : NULL);
tplmode == TPLMODE_REDRAW ? &clicktab : NULL,
&carry_hl);
args.prow = &row;
args.pcol = &col;
+9
View File
@@ -0,0 +1,9 @@
> +0&#ffffff0@74
|~+0#4040ff13&| @73
|L+3#0000000&|1|A| @68|L+0&#ffff4012|1|B
|L|2| |c|a|r@1|i|e|d| |S|e|a|r|c|h| @57
|L+3&#ffffff0|3| |r|e|s|e|t| @66
|L+0#ffff4012#4040ff13|4| |u|s|e|r|2| @66
|L|5| |c|a|r@1|i|e|d| |u|s|e|r|2| @58
|L+3#0000000#ffffff0|6| |r|e|s|e|t| @66
| +0&&@74
+9
View File
@@ -0,0 +1,9 @@
|L+2&#ffffff0|1|A| @16> +0&&@39
|L+0&#ffff4012|1|B| @16|~+0#4040ff13#ffffff0| @38
|L+0#0000000#ffff4012|2| |c|a|r@1|i|e|d| |S|e|a|r|c|h| @2|~+0#4040ff13#ffffff0| @38
|L+2#0000000&|3| |r|e|s|e|t| @11|~+0#4040ff13&| @38
|L+0#ffff4012#4040ff13|4| |u|s|e|r|2| @11|~+0#4040ff13#ffffff0| @38
|L+0#ffff4012#4040ff13|5| |c|a|r@1|i|e|d| @9|~+0#4040ff13#ffffff0| @38
|L+2#0000000&|6| |r|e|s|e|t| @11|~+0#4040ff13&| @38
| +1#0000000&@19|~+0#4040ff13&| @38
| +1#0000000&@19| +0&&@21|0|,|0|-|1| @8|A|l@1|
+29
View File
@@ -235,6 +235,35 @@ func Test_multistatusline_highlight()
call StopVimInTerminal(buf)
endfunc
func Test_multistatusline_carry_hl()
CheckScreendump
" %#XX# / %N* set on one row should persist on subsequent rows until %*
" (or another %# / %*) changes it.
let lines =<< trim END
func MyStatusLine()
return 'L1A%=%#Search#L1B%@'
\ .. 'L2 carried Search%@'
\ .. '%*L3 reset%@'
\ .. '%2*L4 user2%@'
\ .. 'L5 carried user2%@'
\ .. '%*L6 reset'
endfunc
hi User2 ctermfg=Yellow ctermbg=Blue
set laststatus=2
set statuslineopt=maxheight:6
set statusline=%!MyStatusLine()
END
call writefile(lines, 'XTest_multistatusline_carry_hl', 'D')
let buf = g:RunVimInTerminal('-S XTest_multistatusline_carry_hl', {'rows': 9})
call term_sendkeys(buf, "\<C-L>")
call VerifyScreenDump(buf, 'Test_multistatusline_carry_hl_01', {})
call StopVimInTerminal(buf)
endfunc
func Test_statuslineopt_default_stl()
CheckScreendump
+30
View File
@@ -1129,6 +1129,36 @@ func Test_tabpanel_empty()
set tabpanel&
endfunc
func Test_tabpanel_carry_hl()
CheckScreendump
" %#XX# / %N* set on one row of a tabpanel should persist on subsequent
" rows until %* (or another %# / %*) changes it. Both "%@" and "\n" are
" accepted as line breaks in 'tabpanel'.
let lines =<< trim END
func MyTabPanel()
return "L1A\n"
\ .. "%#Search#L1B\n"
\ .. "L2 carried Search\n"
\ .. "%*L3 reset\n"
\ .. "%2*L4 user2\n"
\ .. "L5 carried\n"
\ .. "%*L6 reset"
endfunc
hi User2 ctermfg=Yellow ctermbg=Blue
set showtabpanel=2
set tabpanelopt=columns:20
set tabpanel=%!MyTabPanel()
END
call writefile(lines, 'XTest_tabpanel_carry_hl', 'D')
let buf = RunVimInTerminal('-S XTest_tabpanel_carry_hl', {'rows': 9, 'cols': 60})
call VerifyScreenDump(buf, 'Test_tabpanel_carry_hl_01', {})
call StopVimInTerminal(buf)
endfunc
func Test_tabpanel_getinfo_and_scroll()
CheckScreendump
+2
View File
@@ -729,6 +729,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
467,
/**/
466,
/**/