From: Yasuhiro Matsumoto Date: Sat, 11 Apr 2026 15:22:24 +0000 (+0000) Subject: patch 9.2.0338: Cannot handle mouseclicks in the tabline X-Git-Tag: v9.2.0338^0 X-Git-Url: http://git.ipfire.org/gitweb/?a=commitdiff_plain;h=0802e00f2a57fb62a0012a2f15a718e72ed806fd;p=thirdparty%2Fvim.git patch 9.2.0338: Cannot handle mouseclicks in the tabline Problem: Cannot handle mouseclicks in the tabline Solution: Support %[FuncName] click regions in 'tabline', add "area" key to the click info dict (Yasuhiro Matsumoto). The previous implementation resolved and stored click regions only for per-window statuslines; the tabline path in win_redr_custom() (wp==NULL) parsed %[FuncName] but discarded the regions, and tabline clicks were dispatched via TabPageIdxs[] which didn't know about them. Add a global tabline_stl_click array populated from the tabline path, refactor stl_click_handler() to take the regions directly, and dispatch matching clicks from do_mouse() before falling through to tab selection. The winid entry in the callback dict is 0 for tabline clicks. related: #19841 closes: #19950 Supported by AI. Signed-off-by: Yasuhiro Matsumoto Signed-off-by: Christian Brabandt --- diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 300eebbc22..f083d6ff10 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -1,4 +1,4 @@ -*options.txt* For Vim version 9.2. Last change: 2026 Apr 09 +*options.txt* For Vim version 9.2. Last change: 2026 Apr 11 VIM REFERENCE MANUAL by Bram Moolenaar @@ -8626,7 +8626,8 @@ A jump table for the options with a short description can be found at |Q_op|. *stl-%[FuncName]* %[ defines clickable regions in the statusline. When the user clicks - on a region with the mouse, the specified function is called. + on a region with the mouse, the specified function is called. The + same syntax can also be used in 'tabline'. %[FuncName] Start of a clickable region. "FuncName" is the name of a Vim function to call when the region is clicked. @@ -8644,11 +8645,17 @@ A jump table for the options with a short description can be found at |Q_op|. "button" Mouse button: "l" (left), "m" (middle), "r" (right). "mods" Modifier keys: combination of "s" (shift), "c" (ctrl), "a" (alt). Empty string if no modifiers. - "winid" |window-ID| of the window whose statusline was clicked. + "winid" |window-ID| of the window whose statusline was clicked, + or 0 when the click was in 'tabline'. + "area" "statusline" or "tabline". Indicates which option the + clicked region belongs to. Useful when a single + callback is shared between 'statusline' and 'tabline'. If the function returns non-zero, the statusline is redrawn. Dragging the statusline to resize the window still works even when - click handlers are defined. + click handlers are defined. When used in 'tabline', clicks in + %[FuncName] regions are dispatched to the callback instead of the + default tab-selection behavior. Example: > func! ClickFile(info) diff --git a/src/globals.h b/src/globals.h index b7533c6322..7d873eea4e 100644 --- a/src/globals.h +++ b/src/globals.h @@ -104,6 +104,10 @@ EXTERN int redrawing_for_callback INIT(= 0); */ EXTERN short *TabPageIdxs INIT(= NULL); +// Click regions for 'tabline' (%[FuncName]). +EXTERN stl_click_region_T *tabline_stl_click INIT(= NULL); +EXTERN int tabline_stl_click_count INIT(= 0); + #ifdef FEAT_PROP_POPUP // Array with size Rows x Columns containing zindex of popups. EXTERN short *popup_mask INIT(= NULL); diff --git a/src/mouse.c b/src/mouse.c index 6bff3d7c28..1077c5ee72 100644 --- a/src/mouse.c +++ b/src/mouse.c @@ -21,6 +21,9 @@ static long mouse_hor_step = 6; static long mouse_vert_step = 3; static win_T *dragwin = NULL; // window being dragged static int stl_click_handler(win_T *wp, int mcol, int which_button, int mods); +static int stl_click_handler_regions(stl_click_region_T *regions, + int region_count, int winid, int mcol, + int which_button, int mods); void mouse_set_vert_scroll_step(long step) @@ -502,6 +505,16 @@ do_mouse( // Check for clicking in the tab page line. if (TabPageIdxs != NULL && mouse_row == 0 && firstwin->w_winrow > 0) { + // Dispatch 'tabline' %[FuncName] click regions before falling through + // to tab-page selection. On drag events fall through to the normal + // tab-drag handling. + if (is_click && !is_drag + && stl_click_handler_regions(tabline_stl_click, + tabline_stl_click_count, + 0, mouse_col, which_button, + mod_mask)) + return FALSE; + tp_label.just_in = true; tp_label.nr = TabPageIdxs[mouse_col]; @@ -1640,11 +1653,19 @@ mouse_model_popup(void) } /* - * Call a statusline click handler function. + * Call a click-region callback function. + * "regions"/"region_count" describe the resolved click regions, + * "winid" is stored as the "winid" key in the info dict (0 for tabline). * Returns TRUE if the function was called and handled the click. */ static int -stl_click_handler(win_T *wp, int mcol, int which_button, int mods) +stl_click_handler_regions( + stl_click_region_T *regions, + int region_count, + int winid, + int mcol, + int which_button, + int mods) { #ifdef FEAT_EVAL int n; @@ -1658,17 +1679,16 @@ stl_click_handler(win_T *wp, int mcol, int which_button, int mods) funcexe_T funcexe; int col = mcol; - if (wp == NULL || wp->w_stl_click == NULL || wp->w_stl_click_count == 0) + if (regions == NULL || region_count == 0) return FALSE; // Find the click region at the given column. - for (n = 0; n < wp->w_stl_click_count; n++) + for (n = 0; n < region_count; n++) { - if (col >= wp->w_stl_click[n].col_start - && col < wp->w_stl_click[n].col_end) + if (col >= regions[n].col_start && col < regions[n].col_end) break; } - if (n >= wp->w_stl_click_count || wp->w_stl_click[n].funcname == NULL) + if (n >= region_count || regions[n].funcname == NULL) return FALSE; // Build the info dictionary. @@ -1676,7 +1696,7 @@ stl_click_handler(win_T *wp, int mcol, int which_button, int mods) if (info == NULL) return FALSE; - dict_add_number(info, "minwid", wp->w_stl_click[n].minwid); + dict_add_number(info, "minwid", regions[n].minwid); // Determine number of clicks. // MOD_MASK_2CLICK=0x20, MOD_MASK_3CLICK=0x40, MOD_MASK_4CLICK=0x60 @@ -1705,7 +1725,13 @@ stl_click_handler(win_T *wp, int mcol, int which_button, int mods) mods_str[mi] = NUL; dict_add_string(info, "mods", mods_str); - dict_add_number(info, "winid", wp->w_id); + dict_add_number(info, "winid", winid); + + // "area": which option the clicked region belongs to. Lets a shared + // dispatcher distinguish 'statusline' from 'tabline' (and future areas) + // without having to overload winid == 0. + dict_add_string(info, "area", + winid == 0 ? (char_u *)"tabline" : (char_u *)"statusline"); // Call the function with the info dict as argument. argvars[0].v_type = VAR_DICT; @@ -1718,7 +1744,7 @@ stl_click_handler(win_T *wp, int mcol, int which_button, int mods) CLEAR_FIELD(funcexe); funcexe.fe_evaluate = TRUE; - (void)call_func(wp->w_stl_click[n].funcname, -1, + (void)call_func(regions[n].funcname, -1, &rettv, 1, argvars, &funcexe); n = (int)rettv.vval.v_number; @@ -1726,11 +1752,20 @@ stl_click_handler(win_T *wp, int mcol, int which_button, int mods) dict_unref(info); if (n != 0) + { + // Make sure the tabline gets redrawn too when the callback asks for + // a redraw (redraw_statuslines() only redraws the tabline when + // redraw_tabline is set). + if (winid == 0) + redraw_tabline = TRUE; redraw_statuslines(); + } return TRUE; #else - (void)wp; + (void)regions; + (void)region_count; + (void)winid; (void)mcol; (void)which_button; (void)mods; @@ -1738,6 +1773,19 @@ stl_click_handler(win_T *wp, int mcol, int which_button, int mods) #endif } +/* + * Call a statusline click handler function for window "wp". + * Returns TRUE if the function was called and handled the click. + */ + static int +stl_click_handler(win_T *wp, int mcol, int which_button, int mods) +{ + if (wp == NULL) + return FALSE; + return stl_click_handler_regions(wp->w_stl_click, wp->w_stl_click_count, + wp->w_id, mcol, which_button, mods); +} + // dragwin is declared near the top of the file /* diff --git a/src/screen.c b/src/screen.c index f667e6eb24..ed28ac91ab 100644 --- a/src/screen.c +++ b/src/screen.c @@ -1486,23 +1486,41 @@ win_redr_custom( TabPageIdxs[col++] = fillchar; } - // Resolve click function regions for statusline. - if (wp != NULL && !draw_ruler) + // Resolve click function regions for statusline or tabline. + if (!draw_ruler) { - int click_count = 0; + stl_click_region_T **out_regions; + int *out_count; + int base_col; + int click_count = 0; + + 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 (wp->w_stl_click != NULL) + if (*out_regions != NULL) { - for (n = 0; n < wp->w_stl_click_count; n++) - vim_free(wp->w_stl_click[n].funcname); - VIM_CLEAR(wp->w_stl_click); + for (n = 0; n < *out_count; n++) + vim_free((*out_regions)[n].funcname); + VIM_CLEAR(*out_regions); } - wp->w_stl_click_count = 0; + *out_count = 0; if (click_count > 0) { @@ -1514,7 +1532,7 @@ win_redr_custom( { char_u *cur_funcname = NULL; int cur_minwid = 0; - int region_start = wp->w_wincol; + int region_start = base_col; // Walk through click records converting buffer positions // to screen columns. @@ -1530,7 +1548,7 @@ win_redr_custom( if (cur_funcname != NULL) { regions[rcount].col_start = region_start; - regions[rcount].col_end = wp->w_wincol + len; + regions[rcount].col_end = base_col + len; regions[rcount].funcname = vim_strsave(cur_funcname); regions[rcount].minwid = cur_minwid; @@ -1539,22 +1557,22 @@ win_redr_custom( cur_funcname = clicktab[n].funcname; cur_minwid = clicktab[n].minwid; - region_start = wp->w_wincol + len; + region_start = base_col + len; } // Close final region if it extends to the end. if (cur_funcname != NULL) { regions[rcount].col_start = region_start; - regions[rcount].col_end = wp->w_wincol + maxwidth; + regions[rcount].col_end = base_col + maxwidth; regions[rcount].funcname = vim_strsave(cur_funcname); regions[rcount].minwid = cur_minwid; rcount++; } - wp->w_stl_click = regions; - wp->w_stl_click_count = rcount; + *out_regions = regions; + *out_count = rcount; } } diff --git a/src/testdir/test_statusline.vim b/src/testdir/test_statusline.vim index 673a6ed125..27fef946af 100644 --- a/src/testdir/test_statusline.vim +++ b/src/testdir/test_statusline.vim @@ -752,6 +752,7 @@ func Test_statusline_click_handler() call assert_equal(1, g:stl_click_info.nclicks) call assert_equal(0, g:stl_click_info.minwid) call assert_equal(win_getid(), g:stl_click_info.winid) + call assert_equal('statusline', g:stl_click_info.area) unlet! g:stl_click_info " Click outside click region (on the filename part) @@ -873,4 +874,60 @@ func Test_statusline_click_linebreak_still_works() let &laststatus = save_ls endfunc +func Test_tabline_click_handler() + let save_mouse = &mouse + let save_tal = &tabline + let save_stal = &showtabline + if has('gui') + let save_go = &guioptions + set guioptions-=e + endif + set mouse=a + set showtabline=2 + + " Two adjacent click regions in 'tabline' with different minwid. + set tabline=%1[StlClickTestFunc][AAA]%[]%2[StlClickTestFunc][BBB]%[] + redraw! + + " Click on [AAA] region (tabline is row 1). + call test_setmouse(1, 2) + call feedkeys("\\", 'xt') + call assert_true(exists('g:stl_click_info')) + call assert_equal('l', g:stl_click_info.button) + call assert_equal(1, g:stl_click_info.nclicks) + call assert_equal(1, g:stl_click_info.minwid) + " winid is 0 for tabline clicks (no associated window). + call assert_equal(0, g:stl_click_info.winid) + call assert_equal('tabline', g:stl_click_info.area) + unlet! g:stl_click_info + + " Click on [BBB] region. + call test_setmouse(1, 7) + call feedkeys("\\", 'xt') + call assert_true(exists('g:stl_click_info')) + call assert_equal(2, g:stl_click_info.minwid) + unlet! g:stl_click_info + + " Middle click on [AAA]. + call test_setmouse(1, 2) + call feedkeys("\\", 'xt') + call assert_true(exists('g:stl_click_info')) + call assert_equal('m', g:stl_click_info.button) + unlet! g:stl_click_info + + " Click outside any %[...] region: no callback, no error. + set tabline=xxx%1[StlClickTestFunc][YYY]%[] + redraw! + call test_setmouse(1, 1) + call feedkeys("\\", 'xt') + call assert_false(exists('g:stl_click_info')) + + let &mouse = save_mouse + let &tabline = save_tal + let &showtabline = save_stal + if has('gui') + let &guioptions = save_go + endif +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/version.c b/src/version.c index f4e5ef7ec2..15f52077eb 100644 --- a/src/version.c +++ b/src/version.c @@ -734,6 +734,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 338, /**/ 337, /**/