m/fzf
1
0
mirror of https://github.com/junegunn/fzf.git synced 2025-11-15 23:03:47 -05:00

Add bg-cancel action to ignore running background transforms

Close #4430

Example:

  # Implement popup that disappears after 1 second
  #   * Use footer as the popup
  #   * Use `bell` to ring the terminal bell
  #   * Use `bg-transform-footer` to clear the footer after 1 second
  #   * Use `bg-cancel` to ignore currently running background transform actions
  fzf --multi --list-border \
      --bind 'enter:execute-silent(echo -n {+} | pbcopy)+bell' \
      --bind 'enter:+transform-footer(echo Copied {} to clipboard)' \
      --bind 'enter:+bg-cancel+bg-transform-footer(sleep 1)'
This commit is contained in:
Junegunn Choi
2025-06-21 17:21:03 +09:00
parent c35d9cff7d
commit c36ddce36f
5 changed files with 96 additions and 49 deletions

View File

@@ -33,8 +33,19 @@ CHANGELOG
seq 10000 | fzf --preview "awk '{sum += \$1} END {print sum}' {*f}" seq 10000 | fzf --preview "awk '{sum += \$1} END {print sum}' {*f}"
``` ```
- Use this with caution, as it can make fzf sluggish for large lists. - Use this with caution, as it can make fzf sluggish for large lists.
- Added background variants of transform actions with `bg-` prefix that run asynchronously in the background - Added asynchronous transform actions with `bg-` prefix that run asynchronously in the background, along with `bg-cancel` action to ignore currently running `bg-transform` actions.
```sh ```sh
# Implement popup that disappears after 1 second
# * Use footer as the popup
# * Use `bell` to ring the terminal bell
# * Use `bg-transform-footer` to clear the footer after 1 second
# * Use `bg-cancel` to ignore currently running background transform actions
fzf --multi --list-border \
--bind 'enter:execute-silent(echo -n {+} | pbcopy)+bell' \
--bind 'enter:+transform-footer(echo Copied {} to clipboard)' \
--bind 'enter:+bg-cancel+bg-transform-footer(sleep 1)'
# It's okay for the commands to take a little while because they run in the background
GETTER='curl -s http://metaphorpsum.com/sentences/1' GETTER='curl -s http://metaphorpsum.com/sentences/1'
fzf --style full --border --preview : \ fzf --style full --border --preview : \
--bind "focus:bg-transform-header:$GETTER" \ --bind "focus:bg-transform-header:$GETTER" \
@@ -48,6 +59,8 @@ CHANGELOG
--bind "focus:+bg-transform-ghost:$GETTER" \ --bind "focus:+bg-transform-ghost:$GETTER" \
--bind "focus:+bg-transform-prompt:$GETTER" --bind "focus:+bg-transform-prompt:$GETTER"
``` ```
- SSH completion enhancements by @akinomyoga
- Bug fixes and improvements
0.62.0 0.62.0
------ ------

View File

@@ -128,49 +128,50 @@ func _() {
_ = x[actBgTransformPrompt-117] _ = x[actBgTransformPrompt-117]
_ = x[actBgTransformQuery-118] _ = x[actBgTransformQuery-118]
_ = x[actBgTransformSearch-119] _ = x[actBgTransformSearch-119]
_ = x[actSearch-120] _ = x[actBgCancel-120]
_ = x[actPreview-121] _ = x[actSearch-121]
_ = x[actPreviewTop-122] _ = x[actPreview-122]
_ = x[actPreviewBottom-123] _ = x[actPreviewTop-123]
_ = x[actPreviewUp-124] _ = x[actPreviewBottom-124]
_ = x[actPreviewDown-125] _ = x[actPreviewUp-125]
_ = x[actPreviewPageUp-126] _ = x[actPreviewDown-126]
_ = x[actPreviewPageDown-127] _ = x[actPreviewPageUp-127]
_ = x[actPreviewHalfPageUp-128] _ = x[actPreviewPageDown-128]
_ = x[actPreviewHalfPageDown-129] _ = x[actPreviewHalfPageUp-129]
_ = x[actPrevHistory-130] _ = x[actPreviewHalfPageDown-130]
_ = x[actPrevSelected-131] _ = x[actPrevHistory-131]
_ = x[actPrint-132] _ = x[actPrevSelected-132]
_ = x[actPut-133] _ = x[actPrint-133]
_ = x[actNextHistory-134] _ = x[actPut-134]
_ = x[actNextSelected-135] _ = x[actNextHistory-135]
_ = x[actExecute-136] _ = x[actNextSelected-136]
_ = x[actExecuteSilent-137] _ = x[actExecute-137]
_ = x[actExecuteMulti-138] _ = x[actExecuteSilent-138]
_ = x[actSigStop-139] _ = x[actExecuteMulti-139]
_ = x[actFirst-140] _ = x[actSigStop-140]
_ = x[actLast-141] _ = x[actFirst-141]
_ = x[actReload-142] _ = x[actLast-142]
_ = x[actReloadSync-143] _ = x[actReload-143]
_ = x[actDisableSearch-144] _ = x[actReloadSync-144]
_ = x[actEnableSearch-145] _ = x[actDisableSearch-145]
_ = x[actSelect-146] _ = x[actEnableSearch-146]
_ = x[actDeselect-147] _ = x[actSelect-147]
_ = x[actUnbind-148] _ = x[actDeselect-148]
_ = x[actRebind-149] _ = x[actUnbind-149]
_ = x[actToggleBind-150] _ = x[actRebind-150]
_ = x[actBecome-151] _ = x[actToggleBind-151]
_ = x[actShowHeader-152] _ = x[actBecome-152]
_ = x[actHideHeader-153] _ = x[actShowHeader-153]
_ = x[actBell-154] _ = x[actHideHeader-154]
_ = x[actExclude-155] _ = x[actBell-155]
_ = x[actExcludeMulti-156] _ = x[actExclude-156]
_ = x[actAsync-157] _ = x[actExcludeMulti-157]
_ = x[actAsync-158]
} }
const _actionType_name = "actIgnoreactStartactClickactInvalidactBracketedPasteBeginactBracketedPasteEndactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeGhostactChangeHeaderactChangeFooteractChangeHeaderLabelactChangeFooterLabelactChangeInputLabelactChangeListLabelactChangeMultiactChangeNthactChangePointeractChangePreviewactChangePreviewLabelactChangePreviewWindowactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactToggleMultiLineactToggleHscrollactTrackCurrentactToggleInputactHideInputactShowInputactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformGhostactTransformHeaderactTransformFooteractTransformHeaderLabelactTransformFooterLabelactTransformInputLabelactTransformListLabelactTransformNthactTransformPointeractTransformPreviewLabelactTransformPromptactTransformQueryactTransformSearchactBgTransformactBgTransformBorderLabelactBgTransformGhostactBgTransformHeaderactBgTransformFooteractBgTransformHeaderLabelactBgTransformFooterLabelactBgTransformInputLabelactBgTransformListLabelactBgTransformNthactBgTransformPointeractBgTransformPreviewLabelactBgTransformPromptactBgTransformQueryactBgTransformSearchactSearchactPreviewactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactToggleBindactBecomeactShowHeaderactHideHeaderactBellactExcludeactExcludeMultiactAsync" const _actionType_name = "actIgnoreactStartactClickactInvalidactBracketedPasteBeginactBracketedPasteEndactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeGhostactChangeHeaderactChangeFooteractChangeHeaderLabelactChangeFooterLabelactChangeInputLabelactChangeListLabelactChangeMultiactChangeNthactChangePointeractChangePreviewactChangePreviewLabelactChangePreviewWindowactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactToggleMultiLineactToggleHscrollactTrackCurrentactToggleInputactHideInputactShowInputactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformGhostactTransformHeaderactTransformFooteractTransformHeaderLabelactTransformFooterLabelactTransformInputLabelactTransformListLabelactTransformNthactTransformPointeractTransformPreviewLabelactTransformPromptactTransformQueryactTransformSearchactBgTransformactBgTransformBorderLabelactBgTransformGhostactBgTransformHeaderactBgTransformFooteractBgTransformHeaderLabelactBgTransformFooterLabelactBgTransformInputLabelactBgTransformListLabelactBgTransformNthactBgTransformPointeractBgTransformPreviewLabelactBgTransformPromptactBgTransformQueryactBgTransformSearchactBgCancelactSearchactPreviewactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactToggleBindactBecomeactShowHeaderactHideHeaderactBellactExcludeactExcludeMultiactAsync"
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 57, 77, 84, 92, 110, 118, 127, 144, 165, 180, 201, 225, 240, 249, 269, 283, 298, 313, 333, 353, 372, 390, 404, 416, 432, 448, 469, 491, 506, 520, 534, 547, 564, 572, 585, 601, 613, 621, 635, 649, 660, 671, 689, 706, 713, 732, 744, 758, 767, 782, 794, 807, 818, 829, 841, 855, 876, 891, 904, 922, 938, 953, 967, 979, 991, 1008, 1015, 1020, 1029, 1040, 1051, 1064, 1079, 1090, 1103, 1118, 1125, 1138, 1151, 1168, 1183, 1196, 1210, 1224, 1240, 1260, 1272, 1295, 1312, 1330, 1348, 1371, 1394, 1416, 1437, 1452, 1471, 1495, 1513, 1530, 1548, 1562, 1587, 1606, 1626, 1646, 1671, 1696, 1720, 1743, 1760, 1781, 1807, 1827, 1846, 1866, 1875, 1885, 1898, 1914, 1926, 1940, 1956, 1974, 1994, 2016, 2030, 2045, 2053, 2059, 2073, 2088, 2098, 2114, 2129, 2139, 2147, 2154, 2163, 2176, 2192, 2207, 2216, 2227, 2236, 2245, 2258, 2267, 2280, 2293, 2300, 2310, 2325, 2333} var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 57, 77, 84, 92, 110, 118, 127, 144, 165, 180, 201, 225, 240, 249, 269, 283, 298, 313, 333, 353, 372, 390, 404, 416, 432, 448, 469, 491, 506, 520, 534, 547, 564, 572, 585, 601, 613, 621, 635, 649, 660, 671, 689, 706, 713, 732, 744, 758, 767, 782, 794, 807, 818, 829, 841, 855, 876, 891, 904, 922, 938, 953, 967, 979, 991, 1008, 1015, 1020, 1029, 1040, 1051, 1064, 1079, 1090, 1103, 1118, 1125, 1138, 1151, 1168, 1183, 1196, 1210, 1224, 1240, 1260, 1272, 1295, 1312, 1330, 1348, 1371, 1394, 1416, 1437, 1452, 1471, 1495, 1513, 1530, 1548, 1562, 1587, 1606, 1626, 1646, 1671, 1696, 1720, 1743, 1760, 1781, 1807, 1827, 1846, 1866, 1877, 1886, 1896, 1909, 1925, 1937, 1951, 1967, 1985, 2005, 2027, 2041, 2056, 2064, 2070, 2084, 2099, 2109, 2125, 2140, 2150, 2158, 2165, 2174, 2187, 2203, 2218, 2227, 2238, 2247, 2256, 2269, 2278, 2291, 2304, 2311, 2321, 2336, 2344}
func (i actionType) String() string { func (i actionType) String() string {
if i < 0 || i >= actionType(len(_actionType_index)-1) { if i < 0 || i >= actionType(len(_actionType_index)-1) {

View File

@@ -1703,6 +1703,8 @@ func parseActionList(masked string, original string, prevActions []*action, putA
appendAction(actExclude) appendAction(actExclude)
case "exclude-multi": case "exclude-multi":
appendAction(actExcludeMulti) appendAction(actExcludeMulti)
case "bg-cancel":
appendAction(actBgCancel)
default: default:
t := isExecuteAction(specLower) t := isExecuteAction(specLower)
if t == actIgnore { if t == actIgnore {

View File

@@ -230,6 +230,11 @@ type Status struct {
Selected []StatusItem `json:"selected"` Selected []StatusItem `json:"selected"`
} }
type versionedCallback struct {
version int64
callback func()
}
// Terminal represents terminal input/output // Terminal represents terminal input/output
type Terminal struct { type Terminal struct {
initDelay time.Duration initDelay time.Duration
@@ -371,6 +376,7 @@ type Terminal struct {
selected map[int32]selectedItem selected map[int32]selectedItem
version int64 version int64
revision revision revision revision
bgVersion int64
reqBox *util.EventBox reqBox *util.EventBox
initialPreviewOpts previewOpts initialPreviewOpts previewOpts
previewOpts previewOpts previewOpts previewOpts
@@ -388,7 +394,7 @@ type Terminal struct {
startChan chan fitpad startChan chan fitpad
killChan chan bool killChan chan bool
serverInputChan chan []*action serverInputChan chan []*action
callbackChan chan func() callbackChan chan versionedCallback
bgQueue map[action][]func() bgQueue map[action][]func()
bgSemaphore chan struct{} bgSemaphore chan struct{}
bgSemaphores map[action]chan struct{} bgSemaphores map[action]chan struct{}
@@ -601,6 +607,8 @@ const (
actBgTransformQuery actBgTransformQuery
actBgTransformSearch actBgTransformSearch
actBgCancel
actSearch actSearch
actPreview actPreview
actPreviewTop actPreviewTop
@@ -1038,7 +1046,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
startChan: make(chan fitpad, 1), startChan: make(chan fitpad, 1),
killChan: make(chan bool), killChan: make(chan bool),
serverInputChan: make(chan []*action, 100), serverInputChan: make(chan []*action, 100),
callbackChan: make(chan func(), maxBgProcesses), callbackChan: make(chan versionedCallback, maxBgProcesses),
bgQueue: make(map[action][]func()), bgQueue: make(map[action][]func()),
bgSemaphore: make(chan struct{}, maxBgProcesses), bgSemaphore: make(chan struct{}, maxBgProcesses),
bgSemaphores: make(map[action]chan struct{}), bgSemaphores: make(map[action]chan struct{}),
@@ -4360,10 +4368,10 @@ func (t *Terminal) captureLines(template string) string {
func (t *Terminal) captureAsync(a action, firstLineOnly bool, callback func(string)) { func (t *Terminal) captureAsync(a action, firstLineOnly bool, callback func(string)) {
_, list := t.buildPlusList(a.a, false) _, list := t.buildPlusList(a.a, false)
command, tempFiles := t.replacePlaceholder(a.a, false, string(t.input), list) command, tempFiles := t.replacePlaceholder(a.a, false, string(t.input), list)
item := func() { version := t.bgVersion
cmd := t.executor.ExecCommand(command, false) cmd := t.executor.ExecCommand(command, false)
cmd.Env = t.environ() cmd.Env = t.environ()
item := func() {
out, _ := cmd.StdoutPipe() out, _ := cmd.StdoutPipe()
reader := bufio.NewReader(out) reader := bufio.NewReader(out)
var output string var output string
@@ -4379,7 +4387,7 @@ func (t *Terminal) captureAsync(a action, firstLineOnly bool, callback func(stri
} }
removeFiles(tempFiles) removeFiles(tempFiles)
t.callbackChan <- func() { callback(output) } t.callbackChan <- versionedCallback{version, func() { callback(output) }}
} }
queue, prs := t.bgQueue[a] queue, prs := t.bgQueue[a]
if !prs { if !prs {
@@ -5318,12 +5326,16 @@ func (t *Terminal) Loop() error {
case callback := <-t.callbackChan: case callback := <-t.callbackChan:
event = tui.Invalid.AsEvent() event = tui.Invalid.AsEvent()
actions = append(actions, &action{t: actAsync}) actions = append(actions, &action{t: actAsync})
callbacks = append(callbacks, callback) if callback.version == t.bgVersion {
callbacks = append(callbacks, callback.callback)
}
DrainCallback: DrainCallback:
for { for {
select { select {
case callback = <-t.callbackChan: case callback = <-t.callbackChan:
callbacks = append(callbacks, callback) if callback.version == t.bgVersion {
callbacks = append(callbacks, callback.callback)
}
continue DrainCallback continue DrainCallback
default: default:
break DrainCallback break DrainCallback
@@ -5696,6 +5708,8 @@ func (t *Terminal) Loop() error {
doActions(actions) doActions(actions)
} }
}) })
case actBgCancel:
t.bgVersion++
case actChangePrompt: case actChangePrompt:
t.promptString = a.a t.promptString = a.a
t.prompt, t.promptLen = t.parsePrompt(a.a) t.prompt, t.promptLen = t.parsePrompt(a.a)

View File

@@ -1964,4 +1964,21 @@ class TestCore < TestInteractive
elapsed = Time.now - time elapsed = Time.now - time
assert elapsed < 2 assert elapsed < 2
end end
def test_bg_cancel
tmux.send_keys %(seq 0 1 | #{FZF} --bind 'space:bg-cancel+bg-transform-header(sleep {}; echo [{}])'), :Enter
tmux.until { assert_equal 2, it.match_count }
tmux.send_keys '1'
tmux.until { assert_equal 1, it.match_count }
tmux.send_keys :Space
tmux.send_keys :BSpace
tmux.until { assert_equal 2, it.match_count }
tmux.send_keys :Space
tmux.until { |lines| assert lines.any_include?('[0]') }
sleep 2
tmux.until do |lines|
assert lines.any_include?('[0]')
refute lines.any_include?('[1]')
end
end
end end