-*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
@ - 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.
+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()*
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()*
-*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
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|
-*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
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 ~
-----------------
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 *);
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;
#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;
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.
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;
/*
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
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
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
++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;
}
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;
// 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);
}
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)
stl_items_len = new_len;
}
+ stl_items[curitem].stl_clickfunc = NULL;
+
if (*s != '%')
prevchar_isflag = prevchar_isitem = FALSE;
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
++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;
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;
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.
1
#else
0
+#endif
+ },
+ {"statusline_click",
+#ifdef FEAT_STL_OPT
+ 1
+#else
+ 0
#endif
},
{"netbeans_intg",
// 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.
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;
*/
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)
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)))
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.
#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
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;
}
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++;
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);
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;
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);
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();
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.
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
#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;
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
static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 328,
/**/
327,
/**/
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);