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

7 views
Skip to first unread message

Christian Brabandt

unread,
May 4, 2026, 4:15:15 PMMay 4
to vim...@googlegroups.com
patch 9.2.0441: statusline: click handler not called on multi-line statusline

Commit: https://github.com/vim/vim/commit/8c7d824b73ff939890f13c3792e45833a651a840
Author: Hirohito Higashi <h.eas...@gmail.com>
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) <nor...@anthropic.com>
Signed-off-by: Hirohito Higashi <h.eas...@gmail.com>
Signed-off-by: Christian Brabandt <c...@256bit.org>

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,
/**/
Reply all
Reply to author
Forward
0 new messages