Skip to content

Add clickable regions to statusline with %[FuncName] item#19841

Closed
mattn wants to merge 8 commits intovim:masterfrom
mattn:statusline-click
Closed

Add clickable regions to statusline with %[FuncName] item#19841
mattn wants to merge 8 commits intovim:masterfrom
mattn:statusline-click

Conversation

@mattn
Copy link
Copy Markdown
Member

@mattn mattn commented Mar 27, 2026

Add support for clickable regions in the statusline using %[FuncName] syntax. When a user clicks on a region, the specified Vim function is called with a dictionary argument. The function can return non-zero to trigger a statusline redraw.

%[FuncName] starts a clickable region, %[] ends it. If %[] is omitted, the region extends to the end of the statusline. %N[FuncName] passes an identifier N to the callback as minwid.

The callback receives a Dictionary with:

  • minwid: identifier from %N[Func] (0 if not specified)
  • nclicks: number of clicks (1, 2, or 3)
  • button: "l" (left), "m" (middle), "r" (right)
  • mods: modifier keys, combination of "s" (shift), "c" (ctrl), "a" (alt)
  • winid: window-ID of the clicked statusline

Use has('statusline_click') to check availability.

func! ClickFile(info)
    if a:info.button ==# 'l' && a:info.nclicks == 2
        browse edit
    endif
    return 0
endfunc
set statusline=%[ClickFile]%f%[]\ %l:%c
2026-03-26.231437.mp4

mattn added 2 commits March 25, 2026 23:48
Add support for clickable regions in the statusline using the %@funcName@
syntax. When a user clicks on a region, the specified Vim function is called
with a dictionary containing click details (button, nclicks, modifiers, minwid,
winid). The function can return non-zero to trigger a statusline redraw.

- Add %@funcName@ and %@@ (end region) parsing in build_stl_str_hl()
- Add stl_click_handler() in mouse.c to dispatch clicks to callbacks
- Store resolved click regions per window (w_stl_click)
- Add 'statusline_click' feature flag for has() detection
- Add 'statuslineclick' option to statuslineopt for enable/disable
- Add documentation and tests
Use %[FuncName] to start a clickable region and %[] to end it,
instead of %@funcName@ and %@@. This avoids conflict with the
existing %@ line break notation and works with :set without
needing to escape special characters.
@mattn mattn force-pushed the statusline-click branch from 233a453 to ee84edf Compare March 27, 2026 03:30
mattn added 2 commits March 27, 2026 12:31
Rename parameters mouse_col and mod_mask to mcol and mods to avoid
shadowing the global variables with the same names.
@mattn mattn force-pushed the statusline-click branch from ee84edf to 27370a6 Compare March 27, 2026 03:32
@mattn
Copy link
Copy Markdown
Member Author

mattn commented Mar 27, 2026

I plan to add clickable region support for tabpanel as well in a follow-up.

- Fix -Wshadow in win_free(): remove inner 'int i' that shadows outer
- Fix -Wunused-parameter in stl_click_handler(): cast params to void
  when FEAT_EVAL is not defined (tiny build)
@chrisbra
Copy link
Copy Markdown
Member

Thanks, unfortunately ASAN detected a memory leak.

@chrisbra
Copy link
Copy Markdown
Member

thanks, but I think we should try to be compatible with Neovim here and I believe they are using the %@ atom?

@zeertzjq
Copy link
Copy Markdown
Member

zeertzjq commented Mar 29, 2026

But the arguments passed to the function is also different:

	@ N   Start of execute function label. Use %X or %T to end the label,
	      e.g.: %10@SwitchBuffer@foo.c%X.  Clicking this label runs the
	      specified function: in the example when clicking once using left
	      mouse button on "foo.c", a `SwitchBuffer(10, 1, 'l', '    ')`
	      expression will be run.  The specified function receives the
	      following arguments in order:
	      1. minwid field value or zero if no N was specified
	      2. number of mouse clicks to detect multiple clicks
	      3. mouse button used: "l", "r" or "m" for left, right or middle
	         button respectively; one should not rely on third argument
	         being only "l", "r" or "m": any other non-empty string value
	         that contains only ASCII lower case letters may be expected
	         for other mouse buttons
	      4. modifiers pressed: string which contains "s" if shift
	         modifier was pressed, "c" for control, "a" for alt and "m"
	         for meta; currently if modifier is not pressed string
	         contains space instead, but one should not rely on presence
	         of spaces or specific order of modifiers: use |stridx()| to
	         test whether some modifier is present; string is guaranteed
	         to contain only ASCII letters and spaces, one letter per
	         modifier; "?" modifier may also be present, but its presence
	         is a bug that denotes that new mouse button recognition was
	         added without modifying code that reacts on mouse clicks on
	         this label.

So using the same atom doesn't really help (and may lead to user mistakes).

@mattn
Copy link
Copy Markdown
Member Author

mattn commented Mar 29, 2026

Because statusline treats %@ as a newline character, it was not possible to make it have the same interface as neovim. #18871 #19123 #19622

@mattn
Copy link
Copy Markdown
Member Author

mattn commented Mar 29, 2026

Note that Neovim's statusline click handler uses %@FuncName@...%X syntax, which conflicts with Vim in two places:

  • %@ — Vim uses this for line breaks (STL_LINEBREAK), Neovim uses it to start a clickable region
  • %X — Vim uses this for tab page close button number (STL_TABCLOSENR), Neovim uses it to end a clickable region

The %[FuncName] syntax proposed here avoids both conflicts, which is nice.

@mattn
Copy link
Copy Markdown
Member Author

mattn commented Mar 29, 2026

Also, the callback currently receives a single Dictionary argument with no room for additional parameters in the future. It might be worth considering whether the signature should be extensible (e.g., allowing additional arguments to be added later without breaking existing callbacks).

@chrisbra
Copy link
Copy Markdown
Member

I see, makes sense. But I don't understand the comment about extending the dictionary in the future. We can simply add new keys in the future, right? So it should be fine as is.

@mattn
Copy link
Copy Markdown
Member Author

mattn commented Mar 29, 2026

Yes, exactly. My point was that Neovim's positional arguments approach would require changing the interface when extending, whereas the dictionary approach we use here allows adding new keys without breaking existing callbacks. So we're on the same page.

@chrisbra
Copy link
Copy Markdown
Member

chrisbra commented Apr 9, 2026

Thanks, I think this is fine now

@chrisbra chrisbra closed this in d42b047 Apr 9, 2026
@chrisbra
Copy link
Copy Markdown
Member

@mattn I was looking into adding support to vim-airline, but how does the function know the field value for which it has been defined? E.g. for Neovim I know the minwid which is the value I have to use to find out the buffer number to switch to.

@mattn
Copy link
Copy Markdown
Member Author

mattn commented Apr 10, 2026

@chrisbra Same idea as Neovim — use %N[FuncName]. The number N before [ is delivered to the callback as a:info.minwid, so you can identify which field was clicked exactly as you do on the Neovim side.

@chrisbra
Copy link
Copy Markdown
Member

ah, makes sense. Thanks

@chrisbra
Copy link
Copy Markdown
Member

but it doesn't seem to work in the tabline?

chrisbra added a commit that referenced this pull request Apr 10, 2026
related: #19841

Signed-off-by: Christian Brabandt <cb@256bit.org>
@mattn
Copy link
Copy Markdown
Member Author

mattn commented Apr 11, 2026

Confirmed — the tabline path in win_redr_custom() discards the parsed click regions (the save step is guarded by wp != NULL), and tabline clicks are dispatched via TabPageIdxs[] which doesn't know about %[FuncName]. So this is an implementation gap. I'll wire it up in a follow-up.

chrisbra pushed a commit that referenced this pull request Apr 11, 2026
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 <mattn.jp@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants