]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.2.0467: multi-line statusline loses highlighting attributes v9.2.0467
authorHirohito Higashi <h.east.727@gmail.com>
Sun, 10 May 2026 18:14:01 +0000 (18:14 +0000)
committerChristian Brabandt <cb@256bit.org>
Sun, 10 May 2026 18:14:01 +0000 (18:14 +0000)
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>
src/buffer.c
src/proto/buffer.pro
src/screen.c
src/tabpanel.c
src/testdir/dumps/Test_multistatusline_carry_hl_01.dump [new file with mode: 0644]
src/testdir/dumps/Test_tabpanel_carry_hl_01.dump [new file with mode: 0644]
src/testdir/test_statuslineopt.vim
src/testdir/test_tabpanel.vim
src/version.c

index 44e504c5373bf7f5a5d04930c7aa743408374ccf..43cba3ad8cdc78be074c4fecd7c1932ff817d975 100644 (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
index 13c273d6aab1a39189a81abbf3bcec9787e5b8aa..7c29256426f13fae524174463ad066e9fec9eed0 100644 (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);
index 57b041d40d7a4331a7d762fd43baf049cdc1dcaf..9c76b8763b387bb071e4a9976b9d7ae986a8c331 100644 (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);
index 1e833c7b65bd80d86336b7609fbc3f2843d3b754..f7889a5e59b59a077cbaa280b185adf10bd1a2e3 100644 (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;
diff --git a/src/testdir/dumps/Test_multistatusline_carry_hl_01.dump b/src/testdir/dumps/Test_multistatusline_carry_hl_01.dump
new file mode 100644 (file)
index 0000000..724d844
--- /dev/null
@@ -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
diff --git a/src/testdir/dumps/Test_tabpanel_carry_hl_01.dump b/src/testdir/dumps/Test_tabpanel_carry_hl_01.dump
new file mode 100644 (file)
index 0000000..87a130f
--- /dev/null
@@ -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| 
index 6454dbff878f9d5d2999acc1ab4ec3c524b7ce44..653529e3566451a7c462c6a2b0251da9b548ed5f 100644 (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
 
index b5bf678a103110902b0177479924b839b19a857d..3b5fe390f443754239d18aacf7350e1918604e0d 100644 (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
 
index 705339b3443464aede551279f2c3d9bbfb9ddcde..a5ba4c7960a5e406bdcd3bb7dbde0966528679bc 100644 (file)
@@ -729,6 +729,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    467,
 /**/
     466,
 /**/