patch 9.2.0328: Cannot handle mouseclicks in the statusline

Commit: 
https://github.com/vim/vim/commit/d42b047f78e743bcfab8e76c00c357c0471d3308
Author: Yasuhiro Matsumoto <[email protected]>
Date:   Thu Apr 9 21:15:30 2026 +0000

    patch 9.2.0328: Cannot handle mouseclicks in the statusline
    
    Problem:  Cannot handle mouseclicks in the statusline
    Solution: Add the %[FuncName] statusline item to define clickable
              regions with a callback function. (Yasuhiro Matsumoto)
    
    closes: #19841
    
    Signed-off-by: Yasuhiro Matsumoto <[email protected]>
    Signed-off-by: Christian Brabandt <[email protected]>

diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index 03f77c519..300eebbc2 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 07
+*options.txt*  For Vim version 9.2.  Last change: 2026 Apr 09
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -8624,6 +8624,51 @@ A jump table for the options with a short description 
can be found at |Q_op|.
        @ -   Inserts a newline.  This only takes effect when the "maxheight"
              value of 'statuslineopt' is greater than 1, or for |tabpanel|.
 
+                                               *stl-%[FuncName]*
+       %[ defines clickable regions in the statusline.  When the user clicks
+       on a region with the mouse, the specified function is called.
+
+         %[FuncName]   Start of a clickable region.  "FuncName" is the name
+                       of a Vim function to call when the region is clicked.
+         %[]           End of the clickable region.  If omitted, the region
+                       extends to the end of the statusline or to the start
+                       of the next clickable region.
+
+       A {minwid} value can be used to pass an identifier to the callback:
+         %3[FuncName]  Starts a clickable region with minwid 3.
+
+       The function receives a single |Dictionary| argument with these
+       entries:
+         "minwid"      The minwid value from %N[Func] (0 if not specified).
+         "nclicks"     Number of clicks: 1, 2, or 3.
+         "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.
+
+       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.
+
+       Example: >
+           func! ClickFile(info)
+               if a:info.button ==# 'l' && a:info.nclicks == 2
+                   browse edit
+               endif
+               return 0
+           endfunc
+           set statusline=%[ClickFile]%f%[]\ %l:%c
+<      This makes the filename in the statusline clickable.  Double-clicking
+       it opens the file browser.
+
+       Use `has('statusline_click')` to check if this feature is available.
+       This is useful for backward compatibility: >
+           if has('statusline_click')
+               set statusline=%[ClickFile]%f%[]\ %l:%c
+           else
+               set statusline=%f\ %l:%c
+           endif
+<
        When displaying a flag, Vim removes the leading comma, if any, when
        that flag comes right after plaintext.  This will make a nice display
        when flags are used like in the examples below.
diff --git a/runtime/doc/tags b/runtime/doc/tags
index 3da46d2fd..6505d1b88 100644
--- a/runtime/doc/tags
+++ b/runtime/doc/tags
@@ -1524,6 +1524,7 @@ $quote    eval.txt        /*$quote*
 +spell various.txt     /*+spell*
 +startuptime   various.txt     /*+startuptime*
 +statusline    various.txt     /*+statusline*
++statusline_click      various.txt     /*+statusline_click*
 +sun_workshop  various.txt     /*+sun_workshop*
 +syntax        various.txt     /*+syntax*
 +system()      various.txt     /*+system()*
@@ -10547,6 +10548,7 @@ status-line     windows.txt     /*status-line*
 statusmsg-variable     eval.txt        /*statusmsg-variable*
 stl-%! options.txt     /*stl-%!*
 stl-%@ options.txt     /*stl-%@*
+stl-%[FuncName]        options.txt     /*stl-%[FuncName]*
 stl-%{ options.txt     /*stl-%{*
 str2blob()     builtin.txt     /*str2blob()*
 str2float()    builtin.txt     /*str2float()*
diff --git a/runtime/doc/various.txt b/runtime/doc/various.txt
index 518f6caab..9de661452 100644
--- a/runtime/doc/various.txt
+++ b/runtime/doc/various.txt
@@ -1,4 +1,4 @@
-*various.txt*  For Vim version 9.2.  Last change: 2026 Feb 14
+*various.txt*  For Vim version 9.2.  Last change: 2026 Apr 06
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -496,6 +496,7 @@ N  *+spell*         spell checking support, see |spell|
 N  *+startuptime*      |--startuptime| argument
 N  *+statusline*       Options 'statusline', 'rulerformat' and special
                        formats of 'titlestring' and 'iconstring'
+N  *+statusline_click* Click handlers in 'statusline' |stl-%[FuncName]|
 -  *+sun_workshop*     Removed: |workshop|
 N  *+syntax*           Syntax highlighting |syntax|
    *+system()*         Unix only: opposite of |+fork|
diff --git a/runtime/doc/version9.txt b/runtime/doc/version9.txt
index acce37226..e0976453d 100644
--- a/runtime/doc/version9.txt
+++ b/runtime/doc/version9.txt
@@ -1,4 +1,4 @@
-*version9.txt* For Vim version 9.2.  Last change: 2026 Apr 07
+*version9.txt* For Vim version 9.2.  Last change: 2026 Apr 09
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -52613,6 +52613,8 @@ Other ~
   pairs individually (e.g. 'listchars', 'fillchars', 'diffopt').
 - |system()| and |systemlist()| functions accept a list as first argument,
   bypassing the shell completely.
+- Allow mouse clickable regions in the |status-line| using the
+  |stl-%[FuncName]| atom.
 
 Platform specific ~
 -----------------
diff --git a/src/buffer.c b/src/buffer.c
index 6c99acd0a..cbee49e1a 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -49,7 +49,8 @@ static int    value_changed(char_u *str, char_u **last);
 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, int *lbreaks);
+               stl_hlrec_T **hltab, stl_hlrec_T **tabtab,
+               stl_clickrec_T **clicktab, int *lbreaks);
 #endif
 static int     append_arg_number(win_T *wp, char_u *buf, size_t buflen, int 
add_file);
 static void    free_buffer(buf_T *);
@@ -4080,7 +4081,7 @@ maketitle(void)
            if (stl_syntax & STL_IN_TITLE)
                build_stl_str_hl(curwin, title_str, sizeof(buf), p_titlestring,
                                    (char_u *)"titlestring", 0,
-                                   0, maxlen, NULL, NULL);
+                                   0, maxlen, NULL, NULL, NULL);
            else
 #endif
                title_str = p_titlestring;
@@ -4251,7 +4252,8 @@ maketitle(void)
 #ifdef FEAT_STL_OPT
            if (stl_syntax & STL_IN_ICON)
                build_stl_str_hl(curwin, icon_str, sizeof(buf), p_iconstring,
-                                (char_u *)"iconstring", 0, 0, 0, NULL, NULL);
+                                (char_u *)"iconstring", 0, 0, 0, NULL, NULL,
+                                NULL);
            else
 #endif
                icon_str = p_iconstring;
@@ -4347,8 +4349,10 @@ typedef struct
        Separate,
        Highlight,
        TabPage,
+       ClickFunc,
        Trunc
     }          stl_type;
+    char_u     *stl_clickfunc; // function name for ClickFunc items
 } stl_item_T;
 
 static size_t          stl_items_len = 20; // Initial value, grows as needed.
@@ -4356,6 +4360,7 @@ static stl_item_T      *stl_items = NULL;
 static int            *stl_groupitem = NULL;
 static stl_hlrec_T     *stl_hltab = NULL;
 static stl_hlrec_T     *stl_tabtab = NULL;
+static stl_clickrec_T  *stl_clicktab = NULL;
 static int             *stl_separator_locations = NULL;
 
 /*
@@ -4383,10 +4388,12 @@ build_stl_str_hl(
     int                fillchar,
     int                maxwidth,
     stl_hlrec_T **hltab,       // return: HL attributes (can be NULL)
-    stl_hlrec_T **tabtab)      // return: tab page nrs (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)
 {
     return build_stl_str_hl_local(STL_MODE_SINGLE, wp, out, outlen, &fmt,
-           opt_name, opt_scope, fillchar, maxwidth, hltab, tabtab, NULL);
+           opt_name, opt_scope, fillchar, maxwidth, hltab, tabtab, clicktab,
+           NULL);
 }
 
     int
@@ -4400,10 +4407,12 @@ build_stl_str_hl_mline(
     int                fillchar,
     int                maxwidth,
     stl_hlrec_T **hltab,       // return: HL attributes (can be NULL)
-    stl_hlrec_T **tabtab)      // return: tab page nrs (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)
 {
     return build_stl_str_hl_local(STL_MODE_MULTI, wp, out, outlen, fmt,
-           opt_name, opt_scope, fillchar, maxwidth, hltab, tabtab, NULL);
+           opt_name, opt_scope, fillchar, maxwidth, hltab, tabtab, clicktab,
+           NULL);
 }
 
 # ifdef ENABLE_STL_MODE_MULTI_NL
@@ -4418,10 +4427,12 @@ build_stl_str_hl_mline_nl(
     int                fillchar,
     int                maxwidth,
     stl_hlrec_T **hltab,       // return: HL attributes (can be NULL)
-    stl_hlrec_T **tabtab)      // return: tab page nrs (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)
 {
     return build_stl_str_hl_local(STL_MODE_MULTI_NL, wp, out, outlen, fmt,
-           opt_name, opt_scope, fillchar, maxwidth, hltab, tabtab, NULL);
+           opt_name, opt_scope, fillchar, maxwidth, hltab, tabtab, clicktab,
+           NULL);
 }
 # endif
 
@@ -4442,7 +4453,7 @@ 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, &rendered_height);
+           opt_name, opt_scope, 0, 0, NULL, NULL, NULL, &rendered_height);
     --emsg_off;
     return rendered_height;
 }
@@ -4477,6 +4488,7 @@ build_stl_str_hl_local(
     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)
     int                *rendered_height)   // return: stl rendered height (can 
be NULL)
 {
     linenr_T   lnum;
@@ -4538,6 +4550,7 @@ build_stl_str_hl_local(
        // end of the list.
        stl_hltab  = ALLOC_MULT(stl_hlrec_T, stl_items_len + 1);
        stl_tabtab = ALLOC_MULT(stl_hlrec_T, stl_items_len + 1);
+       stl_clicktab = ALLOC_MULT(stl_clickrec_T, stl_items_len + 1);
 
        stl_separator_locations = ALLOC_MULT(int, stl_items_len);
     }
@@ -4632,6 +4645,12 @@ build_stl_str_hl_local(
                break;
            stl_tabtab = new_hlrec;
 
+           stl_clickrec_T *new_clickrec = vim_realloc(stl_clicktab,
+                                     sizeof(stl_clickrec_T) * (new_len + 1));
+           if (new_clickrec == NULL)
+               break;
+           stl_clicktab = new_clickrec;
+
            int *new_separator_locs = vim_realloc(stl_separator_locations,
                                            sizeof(int) * new_len);
            if (new_separator_locs == NULL)
@@ -4641,6 +4660,8 @@ build_stl_str_hl_local(
            stl_items_len = new_len;
        }
 
+       stl_items[curitem].stl_clickfunc = NULL;
+
        if (*s != '%')
            prevchar_isflag = prevchar_isitem = FALSE;
 
@@ -4675,8 +4696,39 @@ build_stl_str_hl_local(
        if (*s == NUL)  // ignore trailing %
            break;
 
+       if (*s == STL_CLICKFUNC)
+       {
+           // %[] - end click region
+           if (s[1] == ']')
+           {
+               stl_items[curitem].stl_type = ClickFunc;
+               stl_items[curitem].stl_start = p;
+               stl_items[curitem].stl_minwid = 0;
+               stl_items[curitem].stl_clickfunc = NULL;
+               s += 2;
+               curitem++;
+               continue;
+           }
+           // %[FuncName] - start click region
+           if (ASCII_ISALPHA(s[1]) || s[1] == '_')
+           {
+               char_u *rb = vim_strchr(s + 1, ']');
+               if (rb != NULL)
+               {
+                   stl_items[curitem].stl_type = ClickFunc;
+                   stl_items[curitem].stl_start = p;
+                   stl_items[curitem].stl_minwid = 0;
+                   stl_items[curitem].stl_clickfunc =
+                                         vim_strnsave(s + 1, rb - s - 1);
+                   s = rb + 1;
+                   curitem++;
+                   continue;
+               }
+           }
+       }
        if (*s == STL_LINEBREAK)
        {
+           // Plain %@ - line break
            if (mode == STL_MODE_MULTI
 # ifdef ENABLE_STL_MODE_MULTI_NL
                    || mode == STL_MODE_MULTI_NL
@@ -5233,6 +5285,40 @@ build_stl_str_hl_local(
                    ++s;
                continue;
            }
+
+       case STL_CLICKFUNC:
+           // %N[] - end click region (with minwid, minwid is ignored)
+           if (*s == ']')
+           {
+               stl_items[curitem].stl_type = ClickFunc;
+               stl_items[curitem].stl_start = p;
+               stl_items[curitem].stl_minwid = 0;
+               stl_items[curitem].stl_clickfunc = NULL;
+               s++;
+               curitem++;
+               continue;
+           }
+           // %N[FuncName] with minwid
+           if (ASCII_ISALPHA(*s) || *s == '_')
+           {
+               char_u *rb = vim_strchr(s, ']');
+               if (rb != NULL)
+               {
+                   stl_items[curitem].stl_type = ClickFunc;
+                   stl_items[curitem].stl_start = p;
+                   stl_items[curitem].stl_minwid = minwid;
+                   stl_items[curitem].stl_clickfunc =
+                                             vim_strnsave(s, rb - s);
+                   s = rb + 1;
+                   curitem++;
+                   continue;
+               }
+           }
+           continue;
+
+       case STL_LINEBREAK:
+           // %N@ - line break (already handled above, fallback)
+           continue;
        }
 
        stl_items[curitem].stl_start = p;
@@ -5388,6 +5474,10 @@ find_linebreak:
 
     if (mode == STL_MODE_GET_RENDERED_HEIGHT)
     {
+       // Free click function names that were allocated during parsing.
+       for (l = 0; l < itemcnt; l++)
+           if (stl_items[l].stl_type == ClickFunc)
+               vim_free(stl_items[l].stl_clickfunc);
        if (rendered_height != NULL)
            *rendered_height = rheight;
        return 0;
@@ -5561,6 +5651,34 @@ find_linebreak:
        sp->userhl = 0;
     }
 
+    // Store the info about click function regions.
+    if (clicktab != NULL)
+    {
+       stl_clickrec_T *cp;
+
+       *clicktab = stl_clicktab;
+       cp = stl_clicktab;
+       for (l = 0; l < itemcnt; l++)
+       {
+           if (stl_items[l].stl_type == ClickFunc)
+           {
+               cp->start = stl_items[l].stl_start;
+               cp->funcname = stl_items[l].stl_clickfunc;
+               cp->minwid = stl_items[l].stl_minwid;
+               cp++;
+           }
+       }
+       cp->start = NULL;
+       cp->funcname = NULL;
+    }
+    else
+    {
+       // Free click function names when caller doesn't need them.
+       for (l = 0; l < itemcnt; l++)
+           if (stl_items[l].stl_type == ClickFunc)
+               vim_free(stl_items[l].stl_clickfunc);
+    }
+
     redraw_not_allowed = save_redraw_not_allowed;
 
     // A user function may reset KeyTyped, restore it.
diff --git a/src/evalfunc.c b/src/evalfunc.c
index b4ea12c3c..f61135f77 100644
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -7509,6 +7509,13 @@ f_has(typval_T *argvars, typval_T *rettv)
                1
 #else
                0
+#endif
+               },
+       {"statusline_click",
+#ifdef FEAT_STL_OPT
+               1
+#else
+               0
 #endif
                },
        {"netbeans_intg",
diff --git a/src/gui.c b/src/gui.c
index 4238b8b73..1d60cd7f3 100644
--- a/src/gui.c
+++ b/src/gui.c
@@ -3841,7 +3841,7 @@ get_tabline_label(
 
        // Can't use NameBuff directly, build_stl_str_hl() uses it.
        build_stl_str_hl(curwin, res, MAXPATHL, *opt, opt_name, 0,
-                                                0, (int)Columns, NULL, NULL);
+                                          0, (int)Columns, NULL, NULL, NULL);
        STRCPY(NameBuff, res);
 
        // Back to the original curtab.
diff --git a/src/hardcopy.c b/src/hardcopy.c
index 55d438418..e87caf302 100644
--- a/src/hardcopy.c
+++ b/src/hardcopy.c
@@ -489,7 +489,8 @@ prt_header(
        printer_page_num = pagenum;
 
        build_stl_str_hl(curwin, tbuf, (size_t)(width + IOSIZE), p_header,
-                          (char_u *)"printheader", 0, ' ', width, NULL, NULL);
+                        (char_u *)"printheader", 0, ' ', width, NULL, NULL,
+                        NULL);
 
        // Reset line numbers
        curwin->w_cursor.lnum = tmp_lnum;
diff --git a/src/mouse.c b/src/mouse.c
index 3f2f86dc8..6bff3d7c2 100644
--- a/src/mouse.c
+++ b/src/mouse.c
@@ -19,6 +19,8 @@
  */
 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);
 
     void
 mouse_set_vert_scroll_step(long step)
@@ -760,6 +762,22 @@ do_mouse(
     in_status_line = (jump_flags & IN_STATUS_LINE);
     in_sep_line = (jump_flags & IN_SEP_LINE);
 
+    // Check for statusline click handler early, before visual mode or
+    // other button-specific handling can interfere.
+    if (in_status_line && is_click && !is_drag
+           && stl_click_handler(dragwin, mouse_col,
+                                               which_button, mod_mask))
+    {
+#ifdef FEAT_MOUSESHAPE
+       if (!drag_status_line)
+       {
+           drag_status_line = TRUE;
+           update_mouseshape(-1);
+       }
+#endif
+       return FALSE;
+    }
+
 #ifdef FEAT_NETBEANS_INTG
     if (isNetbeansBuffer(curbuf)
                            && !(jump_flags & (IN_STATUS_LINE | IN_SEP_LINE)))
@@ -1621,7 +1639,106 @@ mouse_model_popup(void)
     return (p_mousem[0] == 'p');
 }
 
-static win_T *dragwin = NULL;  // window being dragged
+/*
+ * Call a statusline click handler function.
+ * 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)
+{
+#ifdef FEAT_EVAL
+    int                n;
+    int                nclicks;
+    char_u     button_str[2];
+    char_u     mods_str[4];
+    int                mi = 0;
+    dict_T     *info;
+    typval_T   argvars[2];
+    typval_T   rettv;
+    funcexe_T  funcexe;
+    int                col = mcol;
+
+    if (wp == NULL || wp->w_stl_click == NULL || wp->w_stl_click_count == 0)
+       return FALSE;
+
+    // Find the click region at the given column.
+    for (n = 0; n < wp->w_stl_click_count; n++)
+    {
+       if (col >= wp->w_stl_click[n].col_start
+                                         && col < wp->w_stl_click[n].col_end)
+           break;
+    }
+    if (n >= wp->w_stl_click_count || wp->w_stl_click[n].funcname == NULL)
+       return FALSE;
+
+    // Build the info dictionary.
+    info = dict_alloc();
+    if (info == NULL)
+       return FALSE;
+
+    dict_add_number(info, "minwid", wp->w_stl_click[n].minwid);
+
+    // Determine number of clicks.
+    // MOD_MASK_2CLICK=0x20, MOD_MASK_3CLICK=0x40, MOD_MASK_4CLICK=0x60
+    nclicks = ((mods & MOD_MASK_MULTI_CLICK) >> 5) + 1;
+    if (nclicks > 3)
+       nclicks = 3;
+    dict_add_number(info, "nclicks", nclicks);
+
+    // Button.
+    if (which_button == MOUSE_LEFT)
+       button_str[0] = 'l';
+    else if (which_button == MOUSE_RIGHT)
+       button_str[0] = 'r';
+    else
+       button_str[0] = 'm';
+    button_str[1] = NUL;
+    dict_add_string(info, "button", button_str);
+
+    // Modifiers.
+    if (mods & MOD_MASK_SHIFT)
+       mods_str[mi++] = 's';
+    if (mods & MOD_MASK_CTRL)
+       mods_str[mi++] = 'c';
+    if (mods & MOD_MASK_ALT)
+       mods_str[mi++] = 'a';
+    mods_str[mi] = NUL;
+    dict_add_string(info, "mods", mods_str);
+
+    dict_add_number(info, "winid", wp->w_id);
+
+    // Call the function with the info dict as argument.
+    argvars[0].v_type = VAR_DICT;
+    argvars[0].vval.v_dict = info;
+    ++info->dv_refcount;
+    argvars[1].v_type = VAR_UNKNOWN;
+
+    rettv.v_type = VAR_NUMBER;
+    rettv.vval.v_number = 0;
+
+    CLEAR_FIELD(funcexe);
+    funcexe.fe_evaluate = TRUE;
+    (void)call_func(wp->w_stl_click[n].funcname, -1,
+                                           &rettv, 1, argvars, &funcexe);
+
+    n = (int)rettv.vval.v_number;
+    clear_tv(&rettv);
+    dict_unref(info);
+
+    if (n != 0)
+       redraw_statuslines();
+
+    return TRUE;
+#else
+    (void)wp;
+    (void)mcol;
+    (void)which_button;
+    (void)mods;
+    return FALSE;
+#endif
+}
+
+// dragwin is declared near the top of the file
 
 /*
  * Reset the window being dragged.  To be called when switching tab page.
diff --git a/src/option.h b/src/option.h
index adafbd8d3..7165ea80c 100644
--- a/src/option.h
+++ b/src/option.h
@@ -364,9 +364,10 @@ typedef enum {
 #define STL_USER_HL    '*'             // highlight from (User)1..9 or 0
 #define STL_HIGHLIGHT  '#'             // highlight name
 #define STL_LINEBREAK  '@'             // insert a line break
+#define STL_CLICKFUNC  '['             // click handler region
 #define STL_TABPAGENR  'T'             // tab page label nr
 #define STL_TABCLOSENR 'X'             // tab page close nr
-#define STL_ALL                ((char_u *) "fFtcvVlLknoObBrRhHmYyWwMqpPaNS{#@")
+#define STL_ALL                ((char_u *) 
"fFtcvVlLknoObBrRhHmYyWwMqpPaNS{#@[")
 
 // flags used for parsed 'wildmode'
 #define WIM_FULL       0x01
diff --git a/src/optionstr.c b/src/optionstr.c
index 9647f7c01..e82c2b78c 100644
--- a/src/optionstr.c
+++ b/src/optionstr.c
@@ -671,8 +671,30 @@ check_stl_option(char_u *s)
        if (!*s)
            break;
        s++;
+       if (*s == STL_CLICKFUNC)
+       {
+           if (s[1] == ']')
+           {
+               // %[] - end click region
+               s += 2;
+               continue;
+           }
+           if (ASCII_ISALPHA(s[1]) || s[1] == '_')
+           {
+               // %[FuncName] - start click region
+               char_u *rb = vim_strchr(s + 2, ']');
+               if (rb != NULL)
+               {
+                   s = rb + 1;
+                   continue;
+               }
+           }
+           // Bare %[ is invalid
+           return illegal_char(errbuf, errbuflen, *s);
+       }
        if (*s == STL_LINEBREAK)
        {
+           // Plain %@ - line break
            s++;
            continue;
        }
@@ -694,6 +716,32 @@ check_stl_option(char_u *s)
            s++;
        if (*s == STL_USER_HL)
            continue;
+       if (*s == STL_CLICKFUNC)
+       {
+           // %N[FuncName] or %N[]
+           if (s[1] == ']')
+           {
+               s += 2;
+               continue;
+           }
+           if (ASCII_ISALPHA(s[1]) || s[1] == '_')
+           {
+               char_u *rb = vim_strchr(s + 2, ']');
+               if (rb != NULL)
+               {
+                   s = rb + 1;
+                   continue;
+               }
+           }
+           // Bare %N[ is invalid
+           return illegal_char(errbuf, errbuflen, *s);
+       }
+       if (*s == STL_LINEBREAK)
+       {
+           // %N@ - line break
+           s++;
+           continue;
+       }
        if (*s == '.')
        {
            s++;
diff --git a/src/proto/buffer.pro b/src/proto/buffer.pro
index 438ddb1a8..13c273d6a 100644
--- a/src/proto/buffer.pro
+++ b/src/proto/buffer.pro
@@ -49,9 +49,9 @@ int col_print(char_u *buf, size_t buflen, int col, int vcol);
 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);
-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);
-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);
+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 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);
diff --git a/src/screen.c b/src/screen.c
index 04c1f237a..311735d82 100644
--- a/src/screen.c
+++ b/src/screen.c
@@ -1296,6 +1296,7 @@ win_redr_custom(
     int                opt_scope = 0;
     stl_hlrec_T *hltab;
     stl_hlrec_T *tabtab;
+    stl_clickrec_T *clicktab;
     win_T      *ewp;
     int                p_crb_save;
     bool       override_success = false;
@@ -1396,7 +1397,8 @@ win_redr_custom(
        width = build_stl_str_hl_mline(ewp, buf, sizeof(buf),
                        &stl_tmp,
                        opt_name, opt_scope,
-                       fillchar, maxwidth, &hltab, &tabtab);
+                       fillchar, maxwidth, &hltab, &tabtab,
+                       &clicktab);
 
        // Make all characters printable.
        p = transstr(buf);
@@ -1475,6 +1477,83 @@ win_redr_custom(
            TabPageIdxs[col++] = fillchar;
     }
 
+    // Resolve click function regions for statusline.
+    if (wp != NULL && !draw_ruler)
+    {
+       int     click_count = 0;
+
+       // Count the click regions.
+       for (n = 0; clicktab[n].start != NULL; n++)
+           click_count++;
+
+       // Free old click regions.
+       if (wp->w_stl_click != 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);
+       }
+       wp->w_stl_click_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 = wp->w_wincol;
+
+               // 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].col_start = region_start;
+                       regions[rcount].col_end = wp->w_wincol + len;
+                       regions[rcount].funcname =
+                                           vim_strsave(cur_funcname);
+                       regions[rcount].minwid = cur_minwid;
+                       rcount++;
+                   }
+
+                   cur_funcname = clicktab[n].funcname;
+                   cur_minwid = clicktab[n].minwid;
+                   region_start = wp->w_wincol + 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].funcname =
+                                       vim_strsave(cur_funcname);
+                   regions[rcount].minwid = cur_minwid;
+                   rcount++;
+               }
+
+               wp->w_stl_click = regions;
+               wp->w_stl_click_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/structs.h b/src/structs.h
index 5bb51dd54..955b9f100 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -1436,6 +1436,25 @@ typedef struct
     int                userhl;         // 0: no HL, 1-9: User HL, < 0 for syn 
ID
 } stl_hlrec_T;
 
+/*
+ * Used for statusline click function regions.
+ */
+typedef struct {
+    char_u     *start;         // position in output buffer where region starts
+    char_u     *funcname;      // function name (NULL = end/close marker)
+    int                minwid;         // minwid value from %N@Func@
+} stl_clickrec_T;
+
+/*
+ * Per-window resolved click regions (screen column based).
+ */
+typedef struct {
+    int                col_start;      // screen column where region starts
+    int                col_end;        // screen column where region ends
+    char_u     *funcname;      // function name (allocated copy)
+    int                minwid;         // minwid value
+} stl_click_region_T;
+
 
 /*
  * Syntax items - usually buffer-specific.
@@ -4117,6 +4136,8 @@ struct window_S
     int                w_prev_height;      // previous height used for 
'splitkeep'
     int                w_stl_rendered_height; // rendered height of 
window-local 'stl'
                                    // (number of "%@" + 1)
+    stl_click_region_T *w_stl_click;  // statusline click regions
+    int                w_stl_click_count;  // number of click regions
     int                w_status_height;    // number of status lines.
                                    // If 'statuslineopt' was changed, this
                                    // member holds the previous value until
diff --git a/src/tabpanel.c b/src/tabpanel.c
index 2a6dbe831..51929a8b1 100644
--- a/src/tabpanel.c
+++ b/src/tabpanel.c
@@ -507,7 +507,8 @@ do_by_tplmode(
 #endif
                        (args.cwp, buf, sizeof(buf),
                        &usefmt, opt_name, opt_scope, TPL_FILLCHAR,
-                       args.col_end - args.col_start, &hltab, &tabtab);
+                       args.col_end - args.col_start, &hltab, &tabtab,
+                       NULL);
 
                args.prow = &row;
                args.pcol = &col;
diff --git a/src/testdir/test_statusline.vim b/src/testdir/test_statusline.vim
index 174c1a475..673a6ed12 100644
--- a/src/testdir/test_statusline.vim
+++ b/src/testdir/test_statusline.vim
@@ -721,4 +721,156 @@ func Test_statusline_singlebyte_negative()
   let [&columns, &ls, &stl, &enc] = [_columns, _ls, _stl, _enc]
 endfunc
 
+func g:StlClickTestFunc(info)
+  let g:stl_click_info = a:info
+  return 0
+endfunc
+
+func g:StlClickReturn1(info)
+  let g:stl_click_info = a:info
+  return 1
+endfunc
+
+func Test_statusline_click_handler()
+  let save_mouse = &mouse
+  let save_stl = &statusline
+  let save_ls = &laststatus
+  set mouse=a
+  set laststatus=2
+
+  " Basic click handler
+  set statusline=%[StlClickTestFunc][Click]%[]\ %f
+  redraw!
+
+  " Click on the [Click] region
+  let stl_row = win_screenpos(0)[0] + winheight(0)
+  call test_setmouse(stl_row, 2)
+  call feedkeys("\<LeftMouse>", 'xt')
+  call feedkeys("\<LeftRelease>", '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(0, g:stl_click_info.minwid)
+  call assert_equal(win_getid(), g:stl_click_info.winid)
+  unlet! g:stl_click_info
+
+  " Click outside click region (on the filename part)
+  call test_setmouse(stl_row, 20)
+  call feedkeys("\<LeftMouse>", 'xt')
+  call feedkeys("\<LeftRelease>", 'xt')
+  call assert_false(exists('g:stl_click_info'))
+
+  " Test with minwid
+  set statusline=%42[StlClickTestFunc][Click]%[]\ %f
+  redraw!
+  call test_setmouse(stl_row, 2)
+  call feedkeys("\<LeftMouse>", 'xt')
+  call feedkeys("\<LeftRelease>", 'xt')
+  call assert_true(exists('g:stl_click_info'))
+  call assert_equal(42, g:stl_click_info.minwid)
+  unlet! g:stl_click_info
+
+  " Test middle click
+  call test_setmouse(stl_row, 2)
+  call feedkeys("\<MiddleMouse>", 'xt')
+  call feedkeys("\<MiddleRelease>", 'xt')
+  call assert_true(exists('g:stl_click_info'))
+  call assert_equal('m', g:stl_click_info.button)
+  unlet! g:stl_click_info
+  let &mouse = save_mouse
+  let &statusline = save_stl
+  let &laststatus = save_ls
+endfunc
+
+func Test_statusline_click_multiple_regions()
+  let save_mouse = &mouse
+  let save_stl = &statusline
+  let save_ls = &laststatus
+  set mouse=a
+  set laststatus=2
+
+  " Two adjacent click regions with different minwid
+  set statusline=%1[StlClickTestFunc][AAA]%[]%2[StlClickTestFunc][BBB]%[]
+  redraw!
+
+  let stl_row = win_screenpos(0)[0] + winheight(0)
+
+  " Click on [AAA] region (col 2)
+  call test_setmouse(stl_row, 2)
+  call feedkeys("\<LeftMouse>", 'xt')
+  call feedkeys("\<LeftRelease>", 'xt')
+  call assert_true(exists('g:stl_click_info'))
+  call assert_equal(1, g:stl_click_info.minwid)
+  unlet! g:stl_click_info
+
+  " Click on [BBB] region (col 7)
+  call test_setmouse(stl_row, 7)
+  call feedkeys("\<LeftMouse>", 'xt')
+  call feedkeys("\<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
+endfunc
+
+func Test_statusline_click_region_extends_to_end()
+  let save_mouse = &mouse
+  let save_stl = &statusline
+  let save_ls = &laststatus
+  set mouse=a
+  set laststatus=2
+
+  " Click region without %[] extends to end of statusline
+  set statusline=xxx%[StlClickTestFunc]Clickable
+  redraw!
+
+  let stl_row = win_screenpos(0)[0] + winheight(0)
+
+  " Click near the end of the statusline
+  call test_setmouse(stl_row, 15)
+  call feedkeys("\<LeftMouse>", 'xt')
+  call feedkeys("\<LeftRelease>", 'xt')
+  call assert_true(exists('g:stl_click_info'))
+  unlet! g:stl_click_info
+
+  " Click on "xxx" (before the click region)
+  call test_setmouse(stl_row, 1)
+  call feedkeys("\<LeftMouse>", 'xt')
+  call feedkeys("\<LeftRelease>", 'xt')
+  call assert_false(exists('g:stl_click_info'))
+
+  let &mouse = save_mouse
+  let &statusline = save_stl
+  let &laststatus = save_ls
+endfunc
+
+func Test_statusline_click_option_validation()
+  " Valid formats should not produce errors
+  let save_stl = &statusline
+  set statusline=%[Func]text%[]
+  set statusline=%3[Func]text%[]
+  set statusline=%[Func]text
+  set statusline=%[Func_Name]text%[]
+  " %@ alone is still valid (line break)
+  set statusline=%@
+  let &statusline = save_stl
+endfunc
+
+func Test_statusline_click_linebreak_still_works()
+  " Ensure %@ without FuncName still works as line break
+  let save_stl = &statusline
+  let save_ls = &laststatus
+  set laststatus=2
+
+  " This should not error - %@ is line break
+  set statusline=line1%@line2
+  redraw!
+
+  let &statusline = save_stl
+  let &laststatus = save_ls
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/version.c b/src/version.c
index 483f55d9f..14387f650 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 */
+/**/
+    328,
 /**/
     327,
 /**/
diff --git a/src/window.c b/src/window.c
index 38bc4677e..b42845ecf 100644
--- a/src/window.c
+++ b/src/window.c
@@ -6045,6 +6045,14 @@ win_free(
     remove_highlight_overrides(wp->w_hl);
     vim_free(wp->w_hl);
 
+    // Free statusline click regions.
+    if (wp->w_stl_click != NULL)
+    {
+       for (i = 0; i < wp->w_stl_click_count; i++)
+           vim_free(wp->w_stl_click[i].funcname);
+       vim_free(wp->w_stl_click);
+    }
+
     clear_winopt(&wp->w_onebuf_opt);
     clear_winopt(&wp->w_allbuf_opt);
 

-- 
-- 
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/E1wAwwv-00CFEz-BN%40256bit.org.

Raspunde prin e-mail lui