summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJunegunn Choi <junegunn.c@gmail.com>2025-07-22 23:15:10 +0900
committerJunegunn Choi <junegunn.c@gmail.com>2025-07-22 23:24:23 +0900
commit7941129cc406a07da2b748f41c19b040224585e4 (patch)
treeb540410de72d1bf57fb88c1080be28e0d954702a
parent069d71a8401530028e2ea407904462ef8c435d88 (diff)
downloadfzf-7941129cc406a07da2b748f41c19b040224585e4.tar.gz
Add 'click-footer' event
-rw-r--r--CHANGELOG.md21
-rw-r--r--man/man1/fzf.18
-rw-r--r--src/options.go2
-rw-r--r--src/terminal.go48
-rw-r--r--src/tui/eventtype_string.go7
-rw-r--r--src/tui/tui.go1
6 files changed, 84 insertions, 3 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3f489606..c0e2a872 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,27 @@
CHANGELOG
=========
+0.65.0
+------
+- Added `click-footer` event that is triggered when the footer section is clicked. When the event is triggered, the following environment variables are set:
+ - `$FZF_CLICK_FOOTER_COLUMN` - clicked column (1-based)
+ - `$FZF_CLICK_FOOTER_LINE` - clicked line (1-based)
+ - `$FZF_CLICK_FOOTER_WORD` - the word under the cursor
+ ```sh
+ fzf --footer $'[Edit] [View]\n[Copy to clipboard]' \
+ --with-shell 'bash -c' \
+ --bind 'click-footer:transform:
+ [[ $FZF_CLICK_FOOTER_WORD =~ Edit ]] && echo "execute:vim \{}"
+ [[ $FZF_CLICK_FOOTER_WORD =~ View ]] && echo "execute:view \{}"
+ (( FZF_CLICK_FOOTER_LINE == 2 )) && (( FZF_CLICK_FOOTER_COLUMN < 20 )) &&
+ echo "execute-silent(echo -n \{} | pbcopy)+bell"
+ '
+ ```
+- Added support for `{*n}` and `{*nf}` placeholder.
+ - `{*n}` evaluates to the zero-based ordinal index of all matched items.
+ - `{*nf}` evaluates to the temporary file containing that.
+- Bug fixes
+
0.64.0
------
- Added `multi` event that is triggered when the multi-selection has changed.
diff --git a/man/man1/fzf.1 b/man/man1/fzf.1
index 5ec02a76..0553fc02 100644
--- a/man/man1/fzf.1
+++ b/man/man1/fzf.1
@@ -1682,6 +1682,14 @@ e.g.
)'\fR
.RE
+\fIclick\-footer\fR
+.RS
+Triggered when a mouse click occurs within the footer. Sets
+\fBFZF_CLICK_FOOTER_LINE\fR and \fBFZF_CLICK_FOOTER_COLUMN\fR environment
+variables starting from 1. It optionally sets \fBFZF_CLICK_FOOTER_WORD\fR
+if clicked on a word.
+.RE
+
.SS AVAILABLE ACTIONS:
A key or an event can be bound to one or more of the following actions.
diff --git a/src/options.go b/src/options.go
index 6cc13c46..3de1eefa 100644
--- a/src/options.go
+++ b/src/options.go
@@ -1008,6 +1008,8 @@ func parseKeyChordsImpl(str string, message string) (map[tui.Event]string, error
add(tui.JumpCancel)
case "click-header":
add(tui.ClickHeader)
+ case "click-footer":
+ add(tui.ClickFooter)
case "multi":
add(tui.Multi)
case "alt-enter", "alt-return":
diff --git a/src/terminal.go b/src/terminal.go
index 95977614..de134356 100644
--- a/src/terminal.go
+++ b/src/terminal.go
@@ -422,6 +422,8 @@ type Terminal struct {
forcePreview bool
clickHeaderLine int
clickHeaderColumn int
+ clickFooterLine int
+ clickFooterColumn int
proxyScript string
numLinesCache map[int32]numLinesCacheValue
}
@@ -1259,7 +1261,10 @@ func (t *Terminal) environImpl(forPreview bool) []string {
env = append(env, fmt.Sprintf("FZF_POS=%d", util.Min(t.merger.Length(), t.cy+1)))
env = append(env, fmt.Sprintf("FZF_CLICK_HEADER_LINE=%d", t.clickHeaderLine))
env = append(env, fmt.Sprintf("FZF_CLICK_HEADER_COLUMN=%d", t.clickHeaderColumn))
+ env = append(env, fmt.Sprintf("FZF_CLICK_FOOTER_LINE=%d", t.clickFooterLine))
+ env = append(env, fmt.Sprintf("FZF_CLICK_FOOTER_COLUMN=%d", t.clickFooterColumn))
env = t.addClickHeaderWord(env)
+ env = t.addClickFooterWord(env)
// Add preview environment variables if preview is enabled
pwindowSize := t.pwindowSize()
@@ -1634,6 +1639,8 @@ func (t *Terminal) changeFooter(footer string) {
lines = strings.Split(strings.TrimSuffix(footer, "\n"), "\n")
}
t.footer = lines
+ t.clickFooterLine = 0
+ t.clickFooterColumn = 0
}
// UpdateHeader updates the header
@@ -4803,6 +4810,35 @@ func (t *Terminal) addClickHeaderWord(env []string) []string {
return env
}
+func (t *Terminal) addClickFooterWord(env []string) []string {
+ clickFooterLine := t.clickFooterLine - 1
+ if clickFooterLine < 0 || clickFooterLine >= len(t.footer) {
+ // Never clicked on the footer
+ return env
+ }
+
+ // NOTE: Unlike in click-header, we don't use --delimiter here, since we're
+ // only interested in the word, not nth. Does this make sense?
+ words := Tokenize(t.footer[clickFooterLine], Delimiter{})
+ colNum := t.clickFooterColumn - 1
+ for _, token := range words {
+ prefixWidth := int(token.prefixLength)
+ word := token.text.ToString()
+ trimmed := strings.TrimRightFunc(word, unicode.IsSpace)
+ trimWidth, _ := util.RunesWidth([]rune(trimmed), prefixWidth, t.tabstop, math.MaxInt32)
+
+ // Find the position of the first non-space character in the word
+ minPos := strings.IndexFunc(trimmed, func(r rune) bool {
+ return !unicode.IsSpace(r)
+ })
+ if colNum >= minPos && colNum >= prefixWidth && colNum < prefixWidth+trimWidth {
+ env = append(env, fmt.Sprintf("FZF_CLICK_FOOTER_WORD=%s", trimmed))
+ return env
+ }
+ }
+ return env
+}
+
// Loop is called to start Terminal I/O
func (t *Terminal) Loop() error {
// prof := profile.Start(profile.ProfilePath("/tmp/"))
@@ -6380,6 +6416,18 @@ func (t *Terminal) Loop() error {
return doActions(actionsFor(tui.ClickHeader))
}
+ // Inside the footer window
+ if clicked && t.footerWindow != nil && t.footerWindow.Enclose(my, mx) {
+ mx -= t.footerWindow.Left() + t.headerIndent(t.footerBorderShape)
+ my -= t.footerWindow.Top()
+ if mx < 0 {
+ break
+ }
+ t.clickFooterLine = my + 1
+ t.clickFooterColumn = mx + 1
+ return doActions(actionsFor(tui.ClickFooter))
+ }
+
// Ignored
if !t.window.Enclose(my, mx) && !barDragging {
break
diff --git a/src/tui/eventtype_string.go b/src/tui/eventtype_string.go
index b4e82f4a..9d4aa77d 100644
--- a/src/tui/eventtype_string.go
+++ b/src/tui/eventtype_string.go
@@ -110,12 +110,13 @@ func _() {
_ = x[Jump-99]
_ = x[JumpCancel-100]
_ = x[ClickHeader-101]
- _ = x[Multi-102]
+ _ = x[ClickFooter-102]
+ _ = x[Multi-103]
}
-const _EventType_name = "RuneCtrlACtrlBCtrlCCtrlDCtrlECtrlFCtrlGCtrlHTabCtrlJCtrlKCtrlLEnterCtrlNCtrlOCtrlPCtrlQCtrlRCtrlSCtrlTCtrlUCtrlVCtrlWCtrlXCtrlYCtrlZEscCtrlSpaceCtrlDeleteCtrlBackSlashCtrlRightBracketCtrlCaretCtrlSlashShiftTabBackspaceDeletePageUpPageDownUpDownLeftRightHomeEndInsertShiftUpShiftDownShiftLeftShiftRightShiftDeleteF1F2F3F4F5F6F7F8F9F10F11F12AltBackspaceAltUpAltDownAltLeftAltRightAltShiftUpAltShiftDownAltShiftLeftAltShiftRightAltCtrlAltInvalidFatalBracketedPasteBeginBracketedPasteEndMouseDoubleClickLeftClickRightClickSLeftClickSRightClickScrollUpScrollDownSScrollUpSScrollDownPreviewScrollUpPreviewScrollDownResizeChangeBackwardEOFStartLoadFocusOneZeroResultJumpJumpCancelClickHeaderMulti"
+const _EventType_name = "RuneCtrlACtrlBCtrlCCtrlDCtrlECtrlFCtrlGCtrlHTabCtrlJCtrlKCtrlLEnterCtrlNCtrlOCtrlPCtrlQCtrlRCtrlSCtrlTCtrlUCtrlVCtrlWCtrlXCtrlYCtrlZEscCtrlSpaceCtrlDeleteCtrlBackSlashCtrlRightBracketCtrlCaretCtrlSlashShiftTabBackspaceDeletePageUpPageDownUpDownLeftRightHomeEndInsertShiftUpShiftDownShiftLeftShiftRightShiftDeleteF1F2F3F4F5F6F7F8F9F10F11F12AltBackspaceAltUpAltDownAltLeftAltRightAltShiftUpAltShiftDownAltShiftLeftAltShiftRightAltCtrlAltInvalidFatalBracketedPasteBeginBracketedPasteEndMouseDoubleClickLeftClickRightClickSLeftClickSRightClickScrollUpScrollDownSScrollUpSScrollDownPreviewScrollUpPreviewScrollDownResizeChangeBackwardEOFStartLoadFocusOneZeroResultJumpJumpCancelClickHeaderClickFooterMulti"
-var _EventType_index = [...]uint16{0, 4, 9, 14, 19, 24, 29, 34, 39, 44, 47, 52, 57, 62, 67, 72, 77, 82, 87, 92, 97, 102, 107, 112, 117, 122, 127, 132, 135, 144, 154, 167, 183, 192, 201, 209, 218, 224, 230, 238, 240, 244, 248, 253, 257, 260, 266, 273, 282, 291, 301, 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 333, 336, 339, 351, 356, 363, 370, 378, 388, 400, 412, 425, 428, 435, 442, 447, 466, 483, 488, 499, 508, 518, 528, 539, 547, 557, 566, 577, 592, 609, 615, 621, 632, 637, 641, 646, 649, 653, 659, 663, 673, 684, 689}
+var _EventType_index = [...]uint16{0, 4, 9, 14, 19, 24, 29, 34, 39, 44, 47, 52, 57, 62, 67, 72, 77, 82, 87, 92, 97, 102, 107, 112, 117, 122, 127, 132, 135, 144, 154, 167, 183, 192, 201, 209, 218, 224, 230, 238, 240, 244, 248, 253, 257, 260, 266, 273, 282, 291, 301, 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 333, 336, 339, 351, 356, 363, 370, 378, 388, 400, 412, 425, 428, 435, 442, 447, 466, 483, 488, 499, 508, 518, 528, 539, 547, 557, 566, 577, 592, 609, 615, 621, 632, 637, 641, 646, 649, 653, 659, 663, 673, 684, 695, 700}
func (i EventType) String() string {
if i < 0 || i >= EventType(len(_EventType_index)-1) {
diff --git a/src/tui/tui.go b/src/tui/tui.go
index a91e93e2..3f5d4282 100644
--- a/src/tui/tui.go
+++ b/src/tui/tui.go
@@ -132,6 +132,7 @@ const (
Jump
JumpCancel
ClickHeader
+ ClickFooter
Multi
)