patch 9.2.0441: statusline: click handler not called on multi-line statusline

Commit: 
https://github.com/vim/vim/commit/8c7d824b73ff939890f13c3792e45833a651a840
Author: Hirohito Higashi <[email protected]>
Date:   Mon May 4 20:03:46 2026 +0000

    patch 9.2.0441: statusline: click handler not called on multi-line 
statusline
    
    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) <[email protected]>
    Signed-off-by: Hirohito Higashi <[email protected]>
    Signed-off-by: Christian Brabandt <[email protected]>

diff --git a/src/screen.c b/src/screen.c
index 5dfaed93c..da4d6e278 100644
--- a/src/screen.c
+++ b/src/screen.c
@@ -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();
diff --git a/src/testdir/test_statusline.vim b/src/testdir/test_statusline.vim
index 79e857cf5..6128e08b6 100644
--- a/src/testdir/test_statusline.vim
+++ b/src/testdir/test_statusline.vim
@@ -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
diff --git a/src/version.c b/src/version.c
index 34f3dd850..b64fd60b8 100644
--- a/src/version.c
+++ b/src/version.c
@@ -729,6 +729,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    441,
 /**/
     440,
 /**/

-- 
-- 
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/E1wJzh4-005tO0-Ax%40256bit.org.

Raspunde prin e-mail lui