diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b33f917..081547b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,17 @@ CHANGELOG ========= +0.74.0 (WIP) +------------ +- Added `result-final` event, a variant of `result` that is not triggered while the input stream is still open (#4835) + - Use it for one-shot, per-query actions that would otherwise re-fire on every intermediate snapshot during loading + ```sh + # 'result' fires per intermediate snapshot (header keeps updating during load); + # 'result-final' fires once after the stream closes (footer shows the final count) + (seq 100; sleep 1; seq 100) | fzf --query 1 \ + --bind 'result:transform-header(echo result: $FZF_MATCH_COUNT),result-final:transform-footer(echo final: $FZF_MATCH_COUNT)' + ``` + 0.73.1 ------ - Bug fixes diff --git a/man/man1/fzf.1 b/man/man1/fzf.1 index c5d330d2..85c965c4 100644 --- a/man/man1/fzf.1 +++ b/man/man1/fzf.1 @@ -226,7 +226,7 @@ Enable processing of ANSI color codes Synchronous search for multi-staged filtering. If specified, fzf will launch the finder only after the input stream is complete and the initial filtering and the associated actions (bound to any of \fBstart\fR, \fBload\fR, -\fBresult\fR, or \fBfocus\fR) are complete. +\fBresult\fR, \fBresult\-final\fR, or \fBfocus\fR) are complete. .RS e.g. \fB# Avoid rendering both fzf instances at the same time @@ -1855,6 +1855,20 @@ e.g. # * Note that you can't use 'change' event in this case because the second position may not be available fzf \-\-sync \-\-bind 'result:transform:[[ \-z {q} ]] && echo "pos(2)"'\fR .RE + +\fIresult\-final\fR +.RS +Same as \fIresult\fR, but suppressed while the input stream is still open. Use +this when you want a one-shot action per query instead of one per intermediate +snapshot during loading. + +e.g. + \fB# 'result' fires per intermediate snapshot (header keeps updating during load); + # 'result-final' fires once after the stream closes (footer shows the final count) + (seq 100; sleep 1; seq 100) | fzf \-\-query 1 \\ + \-\-bind 'result:transform\-header(echo result: $FZF_MATCH_COUNT),result\-final:transform\-footer(echo final: $FZF_MATCH_COUNT)'\fR +.RE + \fIchange\fR .RS Triggered whenever the query string is changed diff --git a/src/options.go b/src/options.go index 85a21144..6e2c933a 100644 --- a/src/options.go +++ b/src/options.go @@ -1063,6 +1063,8 @@ func parseKeyChords(str string, message string) (map[tui.Event]string, []tui.Eve add(tui.Focus) case "result": add(tui.Result) + case "result-final": + add(tui.ResultFinal) case "resize": add(tui.Resize) case "one": diff --git a/src/terminal.go b/src/terminal.go index 1b3a60b2..65a46606 100644 --- a/src/terminal.go +++ b/src/terminal.go @@ -1152,7 +1152,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor bgSemaphore: make(chan struct{}, maxBgProcesses), bgSemaphores: make(map[action]chan struct{}), keyChan: make(chan tui.Event), - eventChan: make(chan tui.Event, 6), // start | (load + result + zero|one) | (focus) | (resize) + eventChan: make(chan tui.Event, 7), // start | (load + result + result-final + zero|one) | (focus) | (resize) timerChan: make(chan tui.Event), // unbuffered: every() ticks coalesce when main loop is busy tui: renderer, ttyDefault: opts.TtyDefault, @@ -1345,6 +1345,9 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor } _, t.hasStartActions = t.keymap[tui.Start.AsEvent()] _, t.hasResultActions = t.keymap[tui.Result.AsEvent()] + if _, prs := t.keymap[tui.ResultFinal.AsEvent()]; prs { + t.hasResultActions = true + } _, t.hasFocusActions = t.keymap[tui.Focus.AsEvent()] _, t.hasLoadActions = t.keymap[tui.Load.AsEvent()] @@ -2022,8 +2025,18 @@ func (t *Terminal) UpdateList(result MatchResult) { } } if t.hasResultActions { - t.pendingReqList = true - t.eventChan <- tui.Result.AsEvent() + result := tui.Result.AsEvent() + if _, prs := t.keymap[result]; prs { + t.pendingReqList = true + t.eventChan <- result + } + if !t.reading { + resultFinal := tui.ResultFinal.AsEvent() + if _, prs := t.keymap[resultFinal]; prs { + t.pendingReqList = true + t.eventChan <- resultFinal + } + } } updateList := !t.trackBlocked && !t.pendingReqList updatePrompt := trackWasBlocked && !t.trackBlocked diff --git a/src/tui/eventtype_string.go b/src/tui/eventtype_string.go index 67bb43ae..466d74a7 100644 --- a/src/tui/eventtype_string.go +++ b/src/tui/eventtype_string.go @@ -164,11 +164,12 @@ func _() { _ = x[ClickFooter-153] _ = x[Multi-154] _ = x[Every-155] + _ = x[ResultFinal-156] } -const _EventType_name = "RuneCtrlACtrlBCtrlCCtrlDCtrlECtrlFCtrlGCtrlHTabCtrlJCtrlKCtrlLEnterCtrlNCtrlOCtrlPCtrlQCtrlRCtrlSCtrlTCtrlUCtrlVCtrlWCtrlXCtrlYCtrlZEscCtrlSpaceCtrlBackSlashCtrlRightBracketCtrlCaretCtrlSlashShiftTabBackspaceDeletePageUpPageDownUpDownLeftRightHomeEndInsertShiftUpShiftDownShiftLeftShiftRightShiftDeleteShiftHomeShiftEndShiftPageUpShiftPageDownF1F2F3F4F5F6F7F8F9F10F11F12AltBackspaceAltUpAltDownAltLeftAltRightAltDeleteAltHomeAltEndAltPageUpAltPageDownAltShiftUpAltShiftDownAltShiftLeftAltShiftRightAltShiftDeleteAltShiftHomeAltShiftEndAltShiftPageUpAltShiftPageDownCtrlUpCtrlDownCtrlLeftCtrlRightCtrlHomeCtrlEndCtrlBackspaceCtrlDeleteCtrlPageUpCtrlPageDownAltCtrlAltCtrlAltUpCtrlAltDownCtrlAltLeftCtrlAltRightCtrlAltHomeCtrlAltEndCtrlAltBackspaceCtrlAltDeleteCtrlAltPageUpCtrlAltPageDownCtrlShiftUpCtrlShiftDownCtrlShiftLeftCtrlShiftRightCtrlShiftHomeCtrlShiftEndCtrlShiftDeleteCtrlShiftPageUpCtrlShiftPageDownCtrlAltShiftUpCtrlAltShiftDownCtrlAltShiftLeftCtrlAltShiftRightCtrlAltShiftHomeCtrlAltShiftEndCtrlAltShiftDeleteCtrlAltShiftPageUpCtrlAltShiftPageDownMouseDoubleClickLeftClickRightClickSLeftClickSRightClickScrollUpScrollDownSScrollUpSScrollDownPreviewScrollUpPreviewScrollDownInvalidFatalBracketedPasteBeginBracketedPasteEndResizeChangeBackwardEOFStartLoadFocusOneZeroResultJumpJumpCancelClickHeaderClickFooterMultiEvery" +const _EventType_name = "RuneCtrlACtrlBCtrlCCtrlDCtrlECtrlFCtrlGCtrlHTabCtrlJCtrlKCtrlLEnterCtrlNCtrlOCtrlPCtrlQCtrlRCtrlSCtrlTCtrlUCtrlVCtrlWCtrlXCtrlYCtrlZEscCtrlSpaceCtrlBackSlashCtrlRightBracketCtrlCaretCtrlSlashShiftTabBackspaceDeletePageUpPageDownUpDownLeftRightHomeEndInsertShiftUpShiftDownShiftLeftShiftRightShiftDeleteShiftHomeShiftEndShiftPageUpShiftPageDownF1F2F3F4F5F6F7F8F9F10F11F12AltBackspaceAltUpAltDownAltLeftAltRightAltDeleteAltHomeAltEndAltPageUpAltPageDownAltShiftUpAltShiftDownAltShiftLeftAltShiftRightAltShiftDeleteAltShiftHomeAltShiftEndAltShiftPageUpAltShiftPageDownCtrlUpCtrlDownCtrlLeftCtrlRightCtrlHomeCtrlEndCtrlBackspaceCtrlDeleteCtrlPageUpCtrlPageDownAltCtrlAltCtrlAltUpCtrlAltDownCtrlAltLeftCtrlAltRightCtrlAltHomeCtrlAltEndCtrlAltBackspaceCtrlAltDeleteCtrlAltPageUpCtrlAltPageDownCtrlShiftUpCtrlShiftDownCtrlShiftLeftCtrlShiftRightCtrlShiftHomeCtrlShiftEndCtrlShiftDeleteCtrlShiftPageUpCtrlShiftPageDownCtrlAltShiftUpCtrlAltShiftDownCtrlAltShiftLeftCtrlAltShiftRightCtrlAltShiftHomeCtrlAltShiftEndCtrlAltShiftDeleteCtrlAltShiftPageUpCtrlAltShiftPageDownMouseDoubleClickLeftClickRightClickSLeftClickSRightClickScrollUpScrollDownSScrollUpSScrollDownPreviewScrollUpPreviewScrollDownInvalidFatalBracketedPasteBeginBracketedPasteEndResizeChangeBackwardEOFStartLoadFocusOneZeroResultJumpJumpCancelClickHeaderClickFooterMultiEveryResultFinal" -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, 157, 173, 182, 191, 199, 208, 214, 220, 228, 230, 234, 238, 243, 247, 250, 256, 263, 272, 281, 291, 302, 311, 319, 330, 343, 345, 347, 349, 351, 353, 355, 357, 359, 361, 364, 367, 370, 382, 387, 394, 401, 409, 418, 425, 431, 440, 451, 461, 473, 485, 498, 512, 524, 535, 549, 565, 571, 579, 587, 596, 604, 611, 624, 634, 644, 656, 659, 666, 675, 686, 697, 709, 720, 730, 746, 759, 772, 787, 798, 811, 824, 838, 851, 863, 878, 893, 910, 924, 940, 956, 973, 989, 1004, 1022, 1040, 1060, 1065, 1076, 1085, 1095, 1105, 1116, 1124, 1134, 1143, 1154, 1169, 1186, 1193, 1198, 1217, 1234, 1240, 1246, 1257, 1262, 1266, 1271, 1274, 1278, 1284, 1288, 1298, 1309, 1320, 1325, 1330} +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, 157, 173, 182, 191, 199, 208, 214, 220, 228, 230, 234, 238, 243, 247, 250, 256, 263, 272, 281, 291, 302, 311, 319, 330, 343, 345, 347, 349, 351, 353, 355, 357, 359, 361, 364, 367, 370, 382, 387, 394, 401, 409, 418, 425, 431, 440, 451, 461, 473, 485, 498, 512, 524, 535, 549, 565, 571, 579, 587, 596, 604, 611, 624, 634, 644, 656, 659, 666, 675, 686, 697, 709, 720, 730, 746, 759, 772, 787, 798, 811, 824, 838, 851, 863, 878, 893, 910, 924, 940, 956, 973, 989, 1004, 1022, 1040, 1060, 1065, 1076, 1085, 1095, 1105, 1116, 1124, 1134, 1143, 1154, 1169, 1186, 1193, 1198, 1217, 1234, 1240, 1246, 1257, 1262, 1266, 1271, 1274, 1278, 1284, 1288, 1298, 1309, 1320, 1325, 1330, 1341} 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 bc6e094e..de0b62bb 100644 --- a/src/tui/tui.go +++ b/src/tui/tui.go @@ -234,6 +234,7 @@ const ( ClickFooter Multi Every + ResultFinal ) func (t EventType) AsEvent() Event { diff --git a/test/test_core.rb b/test/test_core.rb index 91eac672..56fd1f64 100644 --- a/test/test_core.rb +++ b/test/test_core.rb @@ -1405,6 +1405,17 @@ class TestCore < TestInteractive tmux.until { |lines| assert_includes lines, '> 1' } end + def test_result_final_event + tmux.send_keys %[(seq 100; sleep 1; seq 100) | #{FZF} \\ + --query 1 \\ + --bind 'result:transform-header(echo "R=$FZF_MATCH_COUNT")' \\ + --bind 'result-final:transform-footer(echo "F=$FZF_MATCH_COUNT")'], :Enter + tmux.until { |lines| assert lines.any_include?('R=20') } + tmux.until { |lines| refute lines.any_include?('F=20') } + tmux.until { |lines| assert lines.any_include?('R=40') } + tmux.until { |lines| assert lines.any_include?('F=40') } + end + def test_every_event tmux.send_keys %(seq 100 | fzf --bind 'every(0.2):transform-prompt(cat #{tempname})'), :Enter tmux.until { |lines| assert_equal 100, lines.match_count }