]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.2.0441: statusline: click handler not called on multi-line statusline v9.2.0441
authorHirohito Higashi <h.east.727@gmail.com>
Mon, 4 May 2026 20:03:46 +0000 (20:03 +0000)
committerChristian Brabandt <cb@256bit.org>
Mon, 4 May 2026 20:03:46 +0000 (20:03 +0000)
Problem:  With a multi-line statusline clicking on a "%[FuncName]...%[]"
          or "%@FuncName@..." region defined on a row other than the
          last drawn row does not invoke the handler (Christian
          Robinson, after v9.2.0338)
Solution: In win_redr_custom() the click region table reflects only the
          last iteration of the per-row draw loop, so click regions are
          recorded only for the last row.  Move the click-region
          resolution inside the loop and append regions for each row
          using vim_realloc().  This also fixes a leak of
          clicktab[].funcname for non-last rows (Hirohito Higashi).

fixes:  #20116
closes: #20120

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/screen.c
src/testdir/test_statusline.vim
src/version.c

index 5dfaed93c1295844a77916922cca69239d44b50c..da4d6e2783a0a20036d17bd034489cc65528101f 100644 (file)
@@ -1414,6 +1414,36 @@ win_redr_custom(
     char_u *stl_tmp = (stl == NULL) ? (char_u *)"" : stl;
     int col_save = col;
 
+    // Determine where click regions for this draw will be stored, and reset
+    // the destination so each row in the loop below can append.  This must
+    // be done before the loop because for a multi-line statusline each row
+    // contributes its own click regions.
+    stl_click_region_T  **out_regions = NULL;
+    int                        *out_count = NULL;
+    int                        region_base_col = 0;
+    if (!draw_ruler)
+    {
+       if (wp != NULL)
+       {
+           out_regions = &wp->w_stl_click;
+           out_count = &wp->w_stl_click_count;
+           region_base_col = wp->w_wincol;
+       }
+       else
+       {
+           // 'tabline': store regions in global state since there is no
+           // associated window.
+           out_regions = &tabline_stl_click;
+           out_count = &tabline_stl_click_count;
+           region_base_col = firstwin->w_wincol;
+       }
+
+       for (n = 0; n < *out_count; n++)
+           vim_free((*out_regions)[n].funcname);
+       VIM_CLEAR(*out_regions);
+       *out_count = 0;
+    }
+
     for (int i = 0; i < stlh_cnt; i++)
     {
        col = col_save;
@@ -1472,6 +1502,76 @@ win_redr_custom(
                curattr = highlight_user[hltab[n].userhl - 1];
        }
        screen_puts(p, row + i, col, curattr);
+
+       // Append click regions for this row.  clicktab reflects the line
+       // just rendered, so each row of a multi-line statusline contributes
+       // its own regions.
+       if (out_regions != NULL)
+       {
+           int click_count = 0;
+           for (n = 0; clicktab[n].start != NULL; n++)
+               click_count++;
+
+           if (click_count > 0)
+           {
+               stl_click_region_T *new_arr = vim_realloc(*out_regions,
+                       sizeof(stl_click_region_T)
+                                   * (*out_count + click_count));
+               if (new_arr != NULL)
+               {
+                   char_u  *cur_funcname = NULL;
+                   int     cur_minwid = 0;
+                   int     region_start = region_base_col;
+
+                   *out_regions = new_arr;
+
+                   len = 0;
+                   p = buf;
+                   for (n = 0; clicktab[n].start != NULL; n++)
+                   {
+                       len += vim_strnsize(p,
+                                           (int)(clicktab[n].start - p));
+                       p = clicktab[n].start;
+
+                       if (cur_funcname != NULL)
+                       {
+                           stl_click_region_T *r =
+                                       &(*out_regions)[*out_count];
+                           r->row = row + i;
+                           r->col_start = region_start;
+                           r->col_end = region_base_col + len;
+                           r->funcname = vim_strsave(cur_funcname);
+                           r->minwid = cur_minwid;
+                           r->tabnr = 0;
+                           (*out_count)++;
+                       }
+
+                       cur_funcname = clicktab[n].funcname;
+                       cur_minwid = clicktab[n].minwid;
+                       region_start = region_base_col + len;
+                   }
+
+                   // Close final region if it extends to the end.
+                   if (cur_funcname != NULL)
+                   {
+                       stl_click_region_T *r =
+                                       &(*out_regions)[*out_count];
+                       r->row = row + i;
+                       r->col_start = region_start;
+                       r->col_end = region_base_col + maxwidth;
+                       r->funcname = vim_strsave(cur_funcname);
+                       r->minwid = cur_minwid;
+                       r->tabnr = 0;
+                       (*out_count)++;
+                   }
+               }
+           }
+       }
+
+       // Free the funcname strings allocated by build_stl_str_hl_local()
+       // for this line.  They have been copied into the region array above.
+       for (n = 0; clicktab[n].start != NULL; n++)
+           vim_free(clicktab[n].funcname);
     }
     ewp->w_p_crb = p_crb_save;
 
@@ -1501,110 +1601,6 @@ win_redr_custom(
            TabPageIdxs[col++] = fillchar;
     }
 
-    // Resolve click function regions for statusline or tabline.
-    if (!draw_ruler)
-    {
-       stl_click_region_T  **out_regions;
-       int                 *out_count;
-       int                 base_col;
-       int                 base_row;
-       int                 click_count = 0;
-
-       // clicktab reflects the last iteration of the draw loop above, so
-       // the regions belong to the last drawn row.
-       base_row = row + stlh_cnt - 1;
-
-       if (wp != NULL)
-       {
-           out_regions = &wp->w_stl_click;
-           out_count = &wp->w_stl_click_count;
-           base_col = wp->w_wincol;
-       }
-       else
-       {
-           // 'tabline': store regions in global state since there is no
-           // associated window.
-           out_regions = &tabline_stl_click;
-           out_count = &tabline_stl_click_count;
-           base_col = firstwin->w_wincol;
-       }
-
-       // Count the click regions.
-       for (n = 0; clicktab[n].start != NULL; n++)
-           click_count++;
-
-       // Free old click regions.
-       if (*out_regions != NULL)
-       {
-           for (n = 0; n < *out_count; n++)
-               vim_free((*out_regions)[n].funcname);
-           VIM_CLEAR(*out_regions);
-       }
-       *out_count = 0;
-
-       if (click_count > 0)
-       {
-           stl_click_region_T *regions;
-           int             rcount = 0;
-
-           regions = ALLOC_MULT(stl_click_region_T, click_count);
-           if (regions != NULL)
-           {
-               char_u  *cur_funcname = NULL;
-               int     cur_minwid = 0;
-               int     region_start = base_col;
-
-               // Walk through click records converting buffer positions
-               // to screen columns.
-               len = 0;
-               p = buf;
-               for (n = 0; clicktab[n].start != NULL; n++)
-               {
-                   len += vim_strnsize(p,
-                                      (int)(clicktab[n].start - p));
-                   p = clicktab[n].start;
-
-                   // Close previous region if there was one.
-                   if (cur_funcname != NULL)
-                   {
-                       regions[rcount].row = base_row;
-                       regions[rcount].col_start = region_start;
-                       regions[rcount].col_end = base_col + len;
-                       regions[rcount].funcname =
-                                           vim_strsave(cur_funcname);
-                       regions[rcount].minwid = cur_minwid;
-                       regions[rcount].tabnr = 0;
-                       rcount++;
-                   }
-
-                   cur_funcname = clicktab[n].funcname;
-                   cur_minwid = clicktab[n].minwid;
-                   region_start = base_col + len;
-               }
-
-               // Close final region if it extends to the end.
-               if (cur_funcname != NULL)
-               {
-                   regions[rcount].row = base_row;
-                   regions[rcount].col_start = region_start;
-                   regions[rcount].col_end = base_col + maxwidth;
-                   regions[rcount].funcname =
-                                       vim_strsave(cur_funcname);
-                   regions[rcount].minwid = cur_minwid;
-                   regions[rcount].tabnr = 0;
-                   rcount++;
-               }
-
-               *out_regions = regions;
-               *out_count = rcount;
-           }
-       }
-
-       // Free the funcname strings allocated by build_stl_str_hl_local().
-       for (n = 0; clicktab[n].start != NULL; n++)
-           vim_free(clicktab[n].funcname);
-    }
-
 theend:
     if (override_success)
        pop_highlight_overrides();
index 79e857cf5283deed4fd3958e6a9cc48c6650a2df..6128e08b661020111753d494fe6a13ee15dd69fa 100644 (file)
@@ -817,6 +817,44 @@ func Test_statusline_click_multiple_regions()
   let &laststatus = save_ls
 endfunc
 
+" Click on a region in any row of a multi-line statusline (issue #20116).
+func Test_statusline_click_multiline()
+  let save_mouse = &mouse
+  let save_stl = &statusline
+  let save_ls = &laststatus
+  let save_stlo = &statuslineopt
+  set mouse=a
+  set laststatus=2
+
+  " First row contains the click region, second row is filled with fillchar.
+  set statusline=%[StlClickTestFunc][Click]%[]%@\ \ \ \
+  set statuslineopt=maxheight:2,fixedheight
+  redraw!
+
+  let stl_row = win_screenpos(0)[0] + winheight(0)
+
+  " Click on [Click] in the first row of the multi-line statusline.
+  call test_setmouse(stl_row, 2)
+  call feedkeys("\<LeftMouse>\<LeftRelease>", 'xt')
+  call assert_true(exists('g:stl_click_info'))
+  call assert_equal(0, g:stl_click_info.minwid)
+  unlet! g:stl_click_info
+
+  " A click region on the second row should also be recognized.
+  set statusline=row1%@%2[StlClickTestFunc][Click2]%[]
+  redraw!
+  call test_setmouse(stl_row + 1, 2)
+  call feedkeys("\<LeftMouse>\<LeftRelease>", 'xt')
+  call assert_true(exists('g:stl_click_info'))
+  call assert_equal(2, g:stl_click_info.minwid)
+  unlet! g:stl_click_info
+
+  let &mouse = save_mouse
+  let &statusline = save_stl
+  let &laststatus = save_ls
+  let &statuslineopt = save_stlo
+endfunc
+
 func Test_statusline_click_region_extends_to_end()
   let save_mouse = &mouse
   let save_stl = &statusline
index 34f3dd8508260653369fe18b46df2c314317f68f..b64fd60b8249dfa8cad2c7d16288505ec5f59cc3 100644 (file)
@@ -729,6 +729,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    441,
 /**/
     440,
 /**/