mirror of
https://github.com/junegunn/fzf.git
synced 2025-11-08 11:23:47 -05:00
Implement asynchronous transform actions (#4419)
Close #4418 Example: fzf --bind 'focus:bg-transform-header(sleep 2; date; echo {})'
This commit is contained in:
18
CHANGELOG.md
18
CHANGELOG.md
@@ -1,6 +1,24 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.63.0
|
||||||
|
------
|
||||||
|
- Added background variants of transform actions with `bg-` prefix that run asynchronously in the background
|
||||||
|
```sh
|
||||||
|
GETTER='curl -s http://metaphorpsum.com/sentences/1'
|
||||||
|
fzf --style full --border --preview : \
|
||||||
|
--bind "focus:bg-transform-header:$GETTER" \
|
||||||
|
--bind "focus:+bg-transform-footer:$GETTER" \
|
||||||
|
--bind "focus:+bg-transform-border-label:$GETTER" \
|
||||||
|
--bind "focus:+bg-transform-preview-label:$GETTER" \
|
||||||
|
--bind "focus:+bg-transform-input-label:$GETTER" \
|
||||||
|
--bind "focus:+bg-transform-list-label:$GETTER" \
|
||||||
|
--bind "focus:+bg-transform-header-label:$GETTER" \
|
||||||
|
--bind "focus:+bg-transform-footer-label:$GETTER" \
|
||||||
|
--bind "focus:+bg-transform-ghost:$GETTER" \
|
||||||
|
--bind "focus:+bg-transform-prompt:$GETTER"
|
||||||
|
```
|
||||||
|
|
||||||
0.62.0
|
0.62.0
|
||||||
------
|
------
|
||||||
- Relaxed the `--color` option syntax to allow whitespace-separated entries (in addition to commas), making multi-line definitions easier to write and read
|
- Relaxed the `--color` option syntax to allow whitespace-separated entries (in addition to commas), making multi-line definitions easier to write and read
|
||||||
|
|||||||
@@ -1805,6 +1805,9 @@ A key or an event can be bound to one or more of the following actions.
|
|||||||
\fBup\fR \fIctrl\-k ctrl\-p up\fR
|
\fBup\fR \fIctrl\-k ctrl\-p up\fR
|
||||||
\fByank\fR \fIctrl\-y\fR
|
\fByank\fR \fIctrl\-y\fR
|
||||||
|
|
||||||
|
Each \fBtransform*\fR action has a corresponding \fBbg\-transform*\fR
|
||||||
|
variant that runs the command in the background.
|
||||||
|
|
||||||
.SS ACTION COMPOSITION
|
.SS ACTION COMPOSITION
|
||||||
|
|
||||||
Multiple actions can be chained using \fB+\fR separator.
|
Multiple actions can be chained using \fB+\fR separator.
|
||||||
@@ -1929,6 +1932,14 @@ e.g.
|
|||||||
echo "change\-header:Invalid selection"'
|
echo "change\-header:Invalid selection"'
|
||||||
\fR
|
\fR
|
||||||
|
|
||||||
|
.SS TRANSFORM IN THE BACKGROUND
|
||||||
|
|
||||||
|
Transform actions are synchronous, meaning fzf becomes unresponsive while the
|
||||||
|
command runs. To avoid this, each \fBtransform*\fR action has a corresponding
|
||||||
|
\fBbg\-transform*\fR variant that runs in the background. Unless you need to
|
||||||
|
chain multiple transform actions where later ones depend on earlier results,
|
||||||
|
prefer using the \fBbg\fR variant.
|
||||||
|
|
||||||
.SS PREVIEW BINDING
|
.SS PREVIEW BINDING
|
||||||
|
|
||||||
With \fBpreview(...)\fR action, you can specify multiple different preview
|
With \fBpreview(...)\fR action, you can specify multiple different preview
|
||||||
|
|||||||
@@ -113,48 +113,64 @@ func _() {
|
|||||||
_ = x[actTransformPrompt-102]
|
_ = x[actTransformPrompt-102]
|
||||||
_ = x[actTransformQuery-103]
|
_ = x[actTransformQuery-103]
|
||||||
_ = x[actTransformSearch-104]
|
_ = x[actTransformSearch-104]
|
||||||
_ = x[actSearch-105]
|
_ = x[actBgTransform-105]
|
||||||
_ = x[actPreview-106]
|
_ = x[actBgTransformBorderLabel-106]
|
||||||
_ = x[actPreviewTop-107]
|
_ = x[actBgTransformGhost-107]
|
||||||
_ = x[actPreviewBottom-108]
|
_ = x[actBgTransformHeader-108]
|
||||||
_ = x[actPreviewUp-109]
|
_ = x[actBgTransformFooter-109]
|
||||||
_ = x[actPreviewDown-110]
|
_ = x[actBgTransformHeaderLabel-110]
|
||||||
_ = x[actPreviewPageUp-111]
|
_ = x[actBgTransformFooterLabel-111]
|
||||||
_ = x[actPreviewPageDown-112]
|
_ = x[actBgTransformInputLabel-112]
|
||||||
_ = x[actPreviewHalfPageUp-113]
|
_ = x[actBgTransformListLabel-113]
|
||||||
_ = x[actPreviewHalfPageDown-114]
|
_ = x[actBgTransformNth-114]
|
||||||
_ = x[actPrevHistory-115]
|
_ = x[actBgTransformPointer-115]
|
||||||
_ = x[actPrevSelected-116]
|
_ = x[actBgTransformPreviewLabel-116]
|
||||||
_ = x[actPrint-117]
|
_ = x[actBgTransformPrompt-117]
|
||||||
_ = x[actPut-118]
|
_ = x[actBgTransformQuery-118]
|
||||||
_ = x[actNextHistory-119]
|
_ = x[actBgTransformSearch-119]
|
||||||
_ = x[actNextSelected-120]
|
_ = x[actSearch-120]
|
||||||
_ = x[actExecute-121]
|
_ = x[actPreview-121]
|
||||||
_ = x[actExecuteSilent-122]
|
_ = x[actPreviewTop-122]
|
||||||
_ = x[actExecuteMulti-123]
|
_ = x[actPreviewBottom-123]
|
||||||
_ = x[actSigStop-124]
|
_ = x[actPreviewUp-124]
|
||||||
_ = x[actFirst-125]
|
_ = x[actPreviewDown-125]
|
||||||
_ = x[actLast-126]
|
_ = x[actPreviewPageUp-126]
|
||||||
_ = x[actReload-127]
|
_ = x[actPreviewPageDown-127]
|
||||||
_ = x[actReloadSync-128]
|
_ = x[actPreviewHalfPageUp-128]
|
||||||
_ = x[actDisableSearch-129]
|
_ = x[actPreviewHalfPageDown-129]
|
||||||
_ = x[actEnableSearch-130]
|
_ = x[actPrevHistory-130]
|
||||||
_ = x[actSelect-131]
|
_ = x[actPrevSelected-131]
|
||||||
_ = x[actDeselect-132]
|
_ = x[actPrint-132]
|
||||||
_ = x[actUnbind-133]
|
_ = x[actPut-133]
|
||||||
_ = x[actRebind-134]
|
_ = x[actNextHistory-134]
|
||||||
_ = x[actToggleBind-135]
|
_ = x[actNextSelected-135]
|
||||||
_ = x[actBecome-136]
|
_ = x[actExecute-136]
|
||||||
_ = x[actShowHeader-137]
|
_ = x[actExecuteSilent-137]
|
||||||
_ = x[actHideHeader-138]
|
_ = x[actExecuteMulti-138]
|
||||||
_ = x[actBell-139]
|
_ = x[actSigStop-139]
|
||||||
_ = x[actExclude-140]
|
_ = x[actFirst-140]
|
||||||
_ = x[actExcludeMulti-141]
|
_ = x[actLast-141]
|
||||||
|
_ = x[actReload-142]
|
||||||
|
_ = x[actReloadSync-143]
|
||||||
|
_ = x[actDisableSearch-144]
|
||||||
|
_ = x[actEnableSearch-145]
|
||||||
|
_ = x[actSelect-146]
|
||||||
|
_ = x[actDeselect-147]
|
||||||
|
_ = x[actUnbind-148]
|
||||||
|
_ = x[actRebind-149]
|
||||||
|
_ = x[actToggleBind-150]
|
||||||
|
_ = x[actBecome-151]
|
||||||
|
_ = x[actShowHeader-152]
|
||||||
|
_ = x[actHideHeader-153]
|
||||||
|
_ = x[actBell-154]
|
||||||
|
_ = x[actExclude-155]
|
||||||
|
_ = x[actExcludeMulti-156]
|
||||||
|
_ = x[actAsync-157]
|
||||||
}
|
}
|
||||||
|
|
||||||
const _actionType_name = "actIgnoreactStartactClickactInvalidactBracketedPasteBeginactBracketedPasteEndactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeGhostactChangeHeaderactChangeFooteractChangeHeaderLabelactChangeFooterLabelactChangeInputLabelactChangeListLabelactChangeMultiactChangeNthactChangePointeractChangePreviewactChangePreviewLabelactChangePreviewWindowactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactToggleMultiLineactToggleHscrollactTrackCurrentactToggleInputactHideInputactShowInputactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformGhostactTransformHeaderactTransformFooteractTransformHeaderLabelactTransformFooterLabelactTransformInputLabelactTransformListLabelactTransformNthactTransformPointeractTransformPreviewLabelactTransformPromptactTransformQueryactTransformSearchactSearchactPreviewactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactToggleBindactBecomeactShowHeaderactHideHeaderactBellactExcludeactExcludeMulti"
|
const _actionType_name = "actIgnoreactStartactClickactInvalidactBracketedPasteBeginactBracketedPasteEndactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeGhostactChangeHeaderactChangeFooteractChangeHeaderLabelactChangeFooterLabelactChangeInputLabelactChangeListLabelactChangeMultiactChangeNthactChangePointeractChangePreviewactChangePreviewLabelactChangePreviewWindowactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactToggleMultiLineactToggleHscrollactTrackCurrentactToggleInputactHideInputactShowInputactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformGhostactTransformHeaderactTransformFooteractTransformHeaderLabelactTransformFooterLabelactTransformInputLabelactTransformListLabelactTransformNthactTransformPointeractTransformPreviewLabelactTransformPromptactTransformQueryactTransformSearchactBgTransformactBgTransformBorderLabelactBgTransformGhostactBgTransformHeaderactBgTransformFooteractBgTransformHeaderLabelactBgTransformFooterLabelactBgTransformInputLabelactBgTransformListLabelactBgTransformNthactBgTransformPointeractBgTransformPreviewLabelactBgTransformPromptactBgTransformQueryactBgTransformSearchactSearchactPreviewactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactToggleBindactBecomeactShowHeaderactHideHeaderactBellactExcludeactExcludeMultiactAsync"
|
||||||
|
|
||||||
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, 1557, 1567, 1580, 1596, 1608, 1622, 1638, 1656, 1676, 1698, 1712, 1727, 1735, 1741, 1755, 1770, 1780, 1796, 1811, 1821, 1829, 1836, 1845, 1858, 1874, 1889, 1898, 1909, 1918, 1927, 1940, 1949, 1962, 1975, 1982, 1992, 2007}
|
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}
|
||||||
|
|
||||||
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) {
|
||||||
|
|||||||
@@ -29,6 +29,10 @@ const (
|
|||||||
maxPatternLength = 1000
|
maxPatternLength = 1000
|
||||||
maxMulti = math.MaxInt32
|
maxMulti = math.MaxInt32
|
||||||
|
|
||||||
|
// Background processes
|
||||||
|
maxBgProcesses = 30
|
||||||
|
maxBgProcessesPerAction = 3
|
||||||
|
|
||||||
// Matcher
|
// Matcher
|
||||||
numPartitionsMultiplier = 8
|
numPartitionsMultiplier = 8
|
||||||
maxPartitions = 32
|
maxPartitions = 32
|
||||||
|
|||||||
@@ -1435,7 +1435,7 @@ const (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
executeRegexp = regexp.MustCompile(
|
executeRegexp = regexp.MustCompile(
|
||||||
`(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|transform)-(?:query|prompt|(?:border|list|preview|input|header|footer)-label|header|footer|search|nth|pointer|ghost)|transform|change-(?:preview-window|preview|multi)|(?:re|un|toggle-)bind|pos|put|print|search)`)
|
`(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|bg-transform|transform)-(?:query|prompt|(?:border|list|preview|input|header|footer)-label|header|footer|search|nth|pointer|ghost)|bg-transform|transform|change-(?:preview-window|preview|multi)|(?:re|un|toggle-)bind|pos|put|print|search)`)
|
||||||
splitRegexp = regexp.MustCompile("[,:]+")
|
splitRegexp = regexp.MustCompile("[,:]+")
|
||||||
actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+")
|
actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+")
|
||||||
}
|
}
|
||||||
@@ -1892,6 +1892,36 @@ func isExecuteAction(str string) actionType {
|
|||||||
return actTransformQuery
|
return actTransformQuery
|
||||||
case "transform-search":
|
case "transform-search":
|
||||||
return actTransformSearch
|
return actTransformSearch
|
||||||
|
case "bg-transform":
|
||||||
|
return actBgTransform
|
||||||
|
case "bg-transform-list-label":
|
||||||
|
return actBgTransformListLabel
|
||||||
|
case "bg-transform-border-label":
|
||||||
|
return actBgTransformBorderLabel
|
||||||
|
case "bg-transform-preview-label":
|
||||||
|
return actBgTransformPreviewLabel
|
||||||
|
case "bg-transform-input-label":
|
||||||
|
return actBgTransformInputLabel
|
||||||
|
case "bg-transform-header-label":
|
||||||
|
return actBgTransformHeaderLabel
|
||||||
|
case "bg-transform-footer-label":
|
||||||
|
return actBgTransformFooterLabel
|
||||||
|
case "bg-transform-footer":
|
||||||
|
return actBgTransformFooter
|
||||||
|
case "bg-transform-header":
|
||||||
|
return actBgTransformHeader
|
||||||
|
case "bg-transform-ghost":
|
||||||
|
return actBgTransformGhost
|
||||||
|
case "bg-transform-nth":
|
||||||
|
return actBgTransformNth
|
||||||
|
case "bg-transform-pointer":
|
||||||
|
return actBgTransformPointer
|
||||||
|
case "bg-transform-prompt":
|
||||||
|
return actBgTransformPrompt
|
||||||
|
case "bg-transform-query":
|
||||||
|
return actBgTransformQuery
|
||||||
|
case "bg-transform-search":
|
||||||
|
return actBgTransformSearch
|
||||||
case "search":
|
case "search":
|
||||||
return actSearch
|
return actSearch
|
||||||
}
|
}
|
||||||
@@ -2857,7 +2887,13 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if opts.ListBorderShape == tui.BorderLine {
|
if opts.ListBorderShape == tui.BorderLine {
|
||||||
return errors.New("list border cannot be 'line'")
|
if hasArg {
|
||||||
|
// '--list-border line' is not allowed
|
||||||
|
return errors.New("list border cannot be 'line'")
|
||||||
|
}
|
||||||
|
// This is when '--style full:line' is previously specified and
|
||||||
|
// '--list-border' is specified without an argument.
|
||||||
|
opts.ListBorderShape = tui.BorderRounded
|
||||||
}
|
}
|
||||||
case "--no-list-border":
|
case "--no-list-border":
|
||||||
opts.ListBorderShape = tui.BorderNone
|
opts.ListBorderShape = tui.BorderNone
|
||||||
|
|||||||
465
src/terminal.go
465
src/terminal.go
@@ -388,6 +388,10 @@ type Terminal struct {
|
|||||||
startChan chan fitpad
|
startChan chan fitpad
|
||||||
killChan chan bool
|
killChan chan bool
|
||||||
serverInputChan chan []*action
|
serverInputChan chan []*action
|
||||||
|
callbackChan chan func()
|
||||||
|
bgQueue map[action][]func()
|
||||||
|
bgSemaphore chan struct{}
|
||||||
|
bgSemaphores map[action]chan struct{}
|
||||||
keyChan chan tui.Event
|
keyChan chan tui.Event
|
||||||
eventChan chan tui.Event
|
eventChan chan tui.Event
|
||||||
slab *util.Slab
|
slab *util.Slab
|
||||||
@@ -489,6 +493,7 @@ const (
|
|||||||
actBackwardDeleteCharEof
|
actBackwardDeleteCharEof
|
||||||
actBackwardWord
|
actBackwardWord
|
||||||
actCancel
|
actCancel
|
||||||
|
|
||||||
actChangeBorderLabel
|
actChangeBorderLabel
|
||||||
actChangeGhost
|
actChangeGhost
|
||||||
actChangeHeader
|
actChangeHeader
|
||||||
@@ -505,6 +510,7 @@ const (
|
|||||||
actChangePreviewWindow
|
actChangePreviewWindow
|
||||||
actChangePrompt
|
actChangePrompt
|
||||||
actChangeQuery
|
actChangeQuery
|
||||||
|
|
||||||
actClearScreen
|
actClearScreen
|
||||||
actClearQuery
|
actClearQuery
|
||||||
actClearSelection
|
actClearSelection
|
||||||
@@ -561,6 +567,7 @@ const (
|
|||||||
actHidePreview
|
actHidePreview
|
||||||
actTogglePreview
|
actTogglePreview
|
||||||
actTogglePreviewWrap
|
actTogglePreviewWrap
|
||||||
|
|
||||||
actTransform
|
actTransform
|
||||||
actTransformBorderLabel
|
actTransformBorderLabel
|
||||||
actTransformGhost
|
actTransformGhost
|
||||||
@@ -576,6 +583,23 @@ const (
|
|||||||
actTransformPrompt
|
actTransformPrompt
|
||||||
actTransformQuery
|
actTransformQuery
|
||||||
actTransformSearch
|
actTransformSearch
|
||||||
|
|
||||||
|
actBgTransform
|
||||||
|
actBgTransformBorderLabel
|
||||||
|
actBgTransformGhost
|
||||||
|
actBgTransformHeader
|
||||||
|
actBgTransformFooter
|
||||||
|
actBgTransformHeaderLabel
|
||||||
|
actBgTransformFooterLabel
|
||||||
|
actBgTransformInputLabel
|
||||||
|
actBgTransformListLabel
|
||||||
|
actBgTransformNth
|
||||||
|
actBgTransformPointer
|
||||||
|
actBgTransformPreviewLabel
|
||||||
|
actBgTransformPrompt
|
||||||
|
actBgTransformQuery
|
||||||
|
actBgTransformSearch
|
||||||
|
|
||||||
actSearch
|
actSearch
|
||||||
actPreview
|
actPreview
|
||||||
actPreviewTop
|
actPreviewTop
|
||||||
@@ -613,6 +637,7 @@ const (
|
|||||||
actBell
|
actBell
|
||||||
actExclude
|
actExclude
|
||||||
actExcludeMulti
|
actExcludeMulti
|
||||||
|
actAsync
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a actionType) Name() string {
|
func (a actionType) Name() string {
|
||||||
@@ -623,10 +648,34 @@ func processExecution(action actionType) bool {
|
|||||||
switch action {
|
switch action {
|
||||||
case actTransform,
|
case actTransform,
|
||||||
actTransformBorderLabel,
|
actTransformBorderLabel,
|
||||||
|
actTransformGhost,
|
||||||
actTransformHeader,
|
actTransformHeader,
|
||||||
|
actTransformFooter,
|
||||||
|
actTransformHeaderLabel,
|
||||||
|
actTransformFooterLabel,
|
||||||
|
actTransformInputLabel,
|
||||||
|
actTransformListLabel,
|
||||||
|
actTransformNth,
|
||||||
|
actTransformPointer,
|
||||||
actTransformPreviewLabel,
|
actTransformPreviewLabel,
|
||||||
actTransformPrompt,
|
actTransformPrompt,
|
||||||
actTransformQuery,
|
actTransformQuery,
|
||||||
|
actTransformSearch,
|
||||||
|
actBgTransform,
|
||||||
|
actBgTransformBorderLabel,
|
||||||
|
actBgTransformGhost,
|
||||||
|
actBgTransformHeader,
|
||||||
|
actBgTransformFooter,
|
||||||
|
actBgTransformHeaderLabel,
|
||||||
|
actBgTransformFooterLabel,
|
||||||
|
actBgTransformInputLabel,
|
||||||
|
actBgTransformListLabel,
|
||||||
|
actBgTransformNth,
|
||||||
|
actBgTransformPointer,
|
||||||
|
actBgTransformPreviewLabel,
|
||||||
|
actBgTransformPrompt,
|
||||||
|
actBgTransformQuery,
|
||||||
|
actBgTransformSearch,
|
||||||
actPreview,
|
actPreview,
|
||||||
actChangePreview,
|
actChangePreview,
|
||||||
actRefreshPreview,
|
actRefreshPreview,
|
||||||
@@ -773,7 +822,7 @@ func mayTriggerPreview(opts *Options) bool {
|
|||||||
for _, actions := range opts.Keymap {
|
for _, actions := range opts.Keymap {
|
||||||
for _, action := range actions {
|
for _, action := range actions {
|
||||||
switch action.t {
|
switch action.t {
|
||||||
case actPreview, actChangePreview, actTransform:
|
case actPreview, actChangePreview, actTransform, actBgTransform:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -987,6 +1036,10 @@ 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),
|
||||||
|
bgQueue: make(map[action][]func()),
|
||||||
|
bgSemaphore: make(chan struct{}, maxBgProcesses),
|
||||||
|
bgSemaphores: make(map[action]chan struct{}),
|
||||||
keyChan: make(chan tui.Event),
|
keyChan: make(chan tui.Event),
|
||||||
eventChan: make(chan tui.Event, 6), // start | (load + result + zero|one) | (focus) | (resize)
|
eventChan: make(chan tui.Event, 6), // start | (load + result + zero|one) | (focus) | (resize)
|
||||||
tui: renderer,
|
tui: renderer,
|
||||||
@@ -2578,7 +2631,9 @@ func (t *Terminal) printPrompt() {
|
|||||||
|
|
||||||
before, after := t.updatePromptOffset()
|
before, after := t.updatePromptOffset()
|
||||||
if len(before) == 0 && len(after) == 0 && len(t.ghost) > 0 {
|
if len(before) == 0 && len(after) == 0 && len(t.ghost) > 0 {
|
||||||
w.CPrint(tui.ColGhost, t.ghost)
|
maxWidth := util.Max(1, w.Width()-t.promptLen-1)
|
||||||
|
runes, _ := t.trimRight([]rune(t.ghost), maxWidth)
|
||||||
|
w.CPrint(tui.ColGhost, string(runes))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4291,6 +4346,75 @@ func (t *Terminal) captureLines(template string) string {
|
|||||||
return t.executeCommand(template, false, true, true, false, "")
|
return t.executeCommand(template, false, true, true, false, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) captureAsync(a action, firstLineOnly bool, callback func(string)) {
|
||||||
|
_, list := t.buildPlusList(a.a, false)
|
||||||
|
command, tempFiles := t.replacePlaceholder(a.a, false, string(t.input), list)
|
||||||
|
item := func() {
|
||||||
|
cmd := t.executor.ExecCommand(command, false)
|
||||||
|
cmd.Env = t.environ()
|
||||||
|
|
||||||
|
out, _ := cmd.StdoutPipe()
|
||||||
|
reader := bufio.NewReader(out)
|
||||||
|
var output string
|
||||||
|
if err := cmd.Start(); err == nil {
|
||||||
|
if firstLineOnly {
|
||||||
|
output, _ = reader.ReadString('\n')
|
||||||
|
output = strings.TrimRight(output, "\r\n")
|
||||||
|
} else {
|
||||||
|
bytes, _ := io.ReadAll(reader)
|
||||||
|
output = string(bytes)
|
||||||
|
}
|
||||||
|
cmd.Wait()
|
||||||
|
}
|
||||||
|
removeFiles(tempFiles)
|
||||||
|
|
||||||
|
t.callbackChan <- func() { callback(output) }
|
||||||
|
}
|
||||||
|
queue, prs := t.bgQueue[a]
|
||||||
|
if !prs {
|
||||||
|
queue = []func(){}
|
||||||
|
}
|
||||||
|
queue = append(queue, item)
|
||||||
|
t.bgQueue[a] = queue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) dispatchAsync() {
|
||||||
|
Loop:
|
||||||
|
for a, queue := range t.bgQueue {
|
||||||
|
delete(t.bgQueue, a)
|
||||||
|
if len(queue) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
semaphore, prs := t.bgSemaphores[a]
|
||||||
|
if !prs {
|
||||||
|
semaphore = make(chan struct{}, maxBgProcessesPerAction)
|
||||||
|
t.bgSemaphores[a] = semaphore
|
||||||
|
}
|
||||||
|
for _, item := range queue {
|
||||||
|
select {
|
||||||
|
// Acquire local semaphore
|
||||||
|
case semaphore <- struct{}{}:
|
||||||
|
default:
|
||||||
|
// Failed to acquire local semaphore, putting only the last one back to the queue
|
||||||
|
t.bgQueue[a] = queue[len(queue)-1:]
|
||||||
|
continue Loop
|
||||||
|
}
|
||||||
|
todo := item
|
||||||
|
go func() {
|
||||||
|
// Acquire global semaphore
|
||||||
|
t.bgSemaphore <- struct{}{}
|
||||||
|
|
||||||
|
todo()
|
||||||
|
// Release local semaphore
|
||||||
|
<-semaphore
|
||||||
|
// Release global semaphore
|
||||||
|
<-t.bgSemaphore
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Terminal) executeCommand(template string, forcePlus bool, background bool, capture bool, firstLineOnly bool, info string) string {
|
func (t *Terminal) executeCommand(template string, forcePlus bool, background bool, capture bool, firstLineOnly bool, info string) string {
|
||||||
line := ""
|
line := ""
|
||||||
valid, list := t.buildPlusList(template, forcePlus)
|
valid, list := t.buildPlusList(template, forcePlus)
|
||||||
@@ -5089,11 +5213,27 @@ func (t *Terminal) Loop() error {
|
|||||||
barrier <- true
|
barrier <- true
|
||||||
needBarrier = false
|
needBarrier = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// These variables are defined outside the loop to be accessible from closures
|
||||||
|
events := []util.EventType{}
|
||||||
|
changed := false
|
||||||
|
var newNth *[]Range
|
||||||
|
req := func(evts ...util.EventType) {
|
||||||
|
for _, event := range evts {
|
||||||
|
events = append(events, event)
|
||||||
|
if event == reqClose || event == reqQuit {
|
||||||
|
looping = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The main event loop
|
||||||
for loopIndex := int64(0); looping; loopIndex++ {
|
for loopIndex := int64(0); looping; loopIndex++ {
|
||||||
var newCommand *commandSpec
|
var newCommand *commandSpec
|
||||||
var newNth *[]Range
|
|
||||||
var reloadSync bool
|
var reloadSync bool
|
||||||
changed := false
|
events = []util.EventType{}
|
||||||
|
changed = false
|
||||||
|
newNth = nil
|
||||||
beof := false
|
beof := false
|
||||||
queryChanged := false
|
queryChanged := false
|
||||||
denylist := []int32{}
|
denylist := []int32{}
|
||||||
@@ -5110,6 +5250,7 @@ func (t *Terminal) Loop() error {
|
|||||||
|
|
||||||
var event tui.Event
|
var event tui.Event
|
||||||
actions := []*action{}
|
actions := []*action{}
|
||||||
|
callbacks := []func(){}
|
||||||
select {
|
select {
|
||||||
case event = <-t.keyChan:
|
case event = <-t.keyChan:
|
||||||
needBarrier = true
|
needBarrier = true
|
||||||
@@ -5141,6 +5282,20 @@ func (t *Terminal) Loop() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case callback := <-t.callbackChan:
|
||||||
|
event = tui.Invalid.AsEvent()
|
||||||
|
actions = append(actions, &action{t: actAsync})
|
||||||
|
callbacks = append(callbacks, callback)
|
||||||
|
DrainCallback:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case callback = <-t.callbackChan:
|
||||||
|
callbacks = append(callbacks, callback)
|
||||||
|
continue DrainCallback
|
||||||
|
default:
|
||||||
|
break DrainCallback
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
t.mutex.Lock()
|
t.mutex.Lock()
|
||||||
@@ -5155,15 +5310,6 @@ func (t *Terminal) Loop() error {
|
|||||||
previousInput := t.input
|
previousInput := t.input
|
||||||
previousCx := t.cx
|
previousCx := t.cx
|
||||||
t.lastKey = event.KeyName()
|
t.lastKey = event.KeyName()
|
||||||
events := []util.EventType{}
|
|
||||||
req := func(evts ...util.EventType) {
|
|
||||||
for _, event := range evts {
|
|
||||||
events = append(events, event)
|
|
||||||
if event == reqClose || event == reqQuit {
|
|
||||||
looping = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updatePreviewWindow := func(forcePreview bool) {
|
updatePreviewWindow := func(forcePreview bool) {
|
||||||
t.resizeWindows(forcePreview, false)
|
t.resizeWindows(forcePreview, false)
|
||||||
req(reqPrompt, reqList, reqInfo, reqHeader, reqFooter)
|
req(reqPrompt, reqList, reqInfo, reqHeader, reqFooter)
|
||||||
@@ -5238,9 +5384,29 @@ func (t *Terminal) Loop() error {
|
|||||||
// actions to allow changing the query even when the input is hidden
|
// actions to allow changing the query even when the input is hidden
|
||||||
// e.g. fzf --no-input --bind 'space:show-input+change-query(foo)+hide-input'
|
// e.g. fzf --no-input --bind 'space:show-input+change-query(foo)+hide-input'
|
||||||
currentInput := t.input
|
currentInput := t.input
|
||||||
|
capture := func(firstLineOnly bool, callback func(string)) {
|
||||||
|
if a.t >= actBgTransform {
|
||||||
|
// bg-transform-*
|
||||||
|
t.captureAsync(*a, firstLineOnly, callback)
|
||||||
|
} else if a.t >= actTransform {
|
||||||
|
// transform-*
|
||||||
|
if firstLineOnly {
|
||||||
|
callback(t.captureLine(a.a))
|
||||||
|
} else {
|
||||||
|
callback(t.captureLines(a.a))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// change-*
|
||||||
|
callback(a.a)
|
||||||
|
}
|
||||||
|
}
|
||||||
Action:
|
Action:
|
||||||
switch a.t {
|
switch a.t {
|
||||||
case actIgnore, actStart, actClick:
|
case actIgnore, actStart, actClick:
|
||||||
|
case actAsync:
|
||||||
|
for _, callback := range callbacks {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
case actBecome:
|
case actBecome:
|
||||||
valid, list := t.buildPlusList(a.a, false)
|
valid, list := t.buildPlusList(a.a, false)
|
||||||
if valid {
|
if valid {
|
||||||
@@ -5333,15 +5499,17 @@ func (t *Terminal) Loop() error {
|
|||||||
t.previewed.version = 0
|
t.previewed.version = 0
|
||||||
req(reqPreviewRefresh)
|
req(reqPreviewRefresh)
|
||||||
}
|
}
|
||||||
case actTransformPrompt:
|
case actTransformPrompt, actBgTransformPrompt:
|
||||||
prompt := t.captureLine(a.a)
|
capture(true, func(prompt string) {
|
||||||
t.promptString = prompt
|
t.promptString = prompt
|
||||||
t.prompt, t.promptLen = t.parsePrompt(prompt)
|
t.prompt, t.promptLen = t.parsePrompt(prompt)
|
||||||
req(reqPrompt)
|
req(reqPrompt)
|
||||||
case actTransformQuery:
|
})
|
||||||
query := t.captureLine(a.a)
|
case actTransformQuery, actBgTransformQuery:
|
||||||
t.input = []rune(query)
|
capture(true, func(query string) {
|
||||||
t.cx = len(t.input)
|
t.input = []rune(query)
|
||||||
|
t.cx = len(t.input)
|
||||||
|
})
|
||||||
case actToggleSort:
|
case actToggleSort:
|
||||||
t.sort = !t.sort
|
t.sort = !t.sort
|
||||||
changed = true
|
changed = true
|
||||||
@@ -5399,119 +5567,102 @@ func (t *Terminal) Loop() error {
|
|||||||
}
|
}
|
||||||
t.multi = multi
|
t.multi = multi
|
||||||
req(reqList, reqInfo)
|
req(reqList, reqInfo)
|
||||||
case actChangeNth, actTransformNth:
|
case actChangeNth, actTransformNth, actBgTransformNth:
|
||||||
expr := a.a
|
capture(true, func(expr string) {
|
||||||
if a.t == actTransformNth {
|
// Split nth expression
|
||||||
expr = t.captureLine(a.a)
|
tokens := strings.Split(expr, "|")
|
||||||
}
|
if nth, err := splitNth(tokens[0]); err == nil {
|
||||||
|
// Changed
|
||||||
// Split nth expression
|
newNth = &nth
|
||||||
tokens := strings.Split(expr, "|")
|
} else {
|
||||||
if nth, err := splitNth(tokens[0]); err == nil {
|
// The default
|
||||||
// Changed
|
newNth = &t.nth
|
||||||
newNth = &nth
|
}
|
||||||
} else {
|
// Cycle
|
||||||
// The default
|
if len(tokens) > 1 {
|
||||||
newNth = &t.nth
|
a.a = strings.Join(append(tokens[1:], tokens[0]), "|")
|
||||||
}
|
}
|
||||||
// Cycle
|
if !compareRanges(t.nthCurrent, *newNth) {
|
||||||
if len(tokens) > 1 {
|
changed = true
|
||||||
a.a = strings.Join(append(tokens[1:], tokens[0]), "|")
|
t.nthCurrent = *newNth
|
||||||
}
|
t.forceRerenderList()
|
||||||
if !compareRanges(t.nthCurrent, *newNth) {
|
}
|
||||||
changed = true
|
})
|
||||||
t.nthCurrent = *newNth
|
|
||||||
t.forceRerenderList()
|
|
||||||
}
|
|
||||||
case actChangeQuery:
|
case actChangeQuery:
|
||||||
t.input = []rune(a.a)
|
t.input = []rune(a.a)
|
||||||
t.cx = len(t.input)
|
t.cx = len(t.input)
|
||||||
case actChangeHeader, actTransformHeader:
|
case actChangeHeader, actTransformHeader, actBgTransformHeader:
|
||||||
header := a.a
|
capture(false, func(header string) {
|
||||||
if a.t == actTransformHeader {
|
if t.changeHeader(header) {
|
||||||
header = t.captureLines(a.a)
|
if t.headerWindow != nil {
|
||||||
}
|
// Need to resize header window
|
||||||
if t.changeHeader(header) {
|
req(reqFullRedraw)
|
||||||
if t.headerWindow != nil {
|
} else {
|
||||||
// Need to resize header window
|
req(reqHeader, reqList, reqPrompt, reqInfo)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
req(reqHeader)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
case actChangeFooter, actTransformFooter, actBgTransformFooter:
|
||||||
|
capture(false, func(footer string) {
|
||||||
|
if t.changeFooter(footer) {
|
||||||
req(reqFullRedraw)
|
req(reqFullRedraw)
|
||||||
} else {
|
} else {
|
||||||
req(reqHeader, reqList, reqPrompt, reqInfo)
|
req(reqFooter)
|
||||||
}
|
}
|
||||||
} else {
|
})
|
||||||
req(reqHeader)
|
case actChangeHeaderLabel, actTransformHeaderLabel, actBgTransformHeaderLabel:
|
||||||
}
|
capture(true, func(label string) {
|
||||||
case actChangeFooter, actTransformFooter:
|
t.headerLabelOpts.label = label
|
||||||
footer := a.a
|
t.headerLabel, t.headerLabelLen = t.ansiLabelPrinter(label, &tui.ColHeaderLabel, false)
|
||||||
if a.t == actTransformFooter {
|
req(reqRedrawHeaderLabel)
|
||||||
footer = t.captureLines(a.a)
|
})
|
||||||
}
|
case actChangeFooterLabel, actTransformFooterLabel, actBgTransformFooterLabel:
|
||||||
if t.changeFooter(footer) {
|
capture(true, func(label string) {
|
||||||
req(reqFullRedraw)
|
t.footerLabelOpts.label = label
|
||||||
} else {
|
t.footerLabel, t.footerLabelLen = t.ansiLabelPrinter(label, &tui.ColFooterLabel, false)
|
||||||
req(reqFooter)
|
req(reqRedrawFooterLabel)
|
||||||
}
|
})
|
||||||
case actChangeHeaderLabel, actTransformHeaderLabel:
|
case actChangeInputLabel, actTransformInputLabel, actBgTransformInputLabel:
|
||||||
label := a.a
|
capture(true, func(label string) {
|
||||||
if a.t == actTransformHeaderLabel {
|
t.inputLabelOpts.label = label
|
||||||
label = t.captureLine(a.a)
|
if t.inputBorder != nil {
|
||||||
}
|
t.inputLabel, t.inputLabelLen = t.ansiLabelPrinter(label, &tui.ColInputLabel, false)
|
||||||
t.headerLabelOpts.label = label
|
req(reqRedrawInputLabel)
|
||||||
t.headerLabel, t.headerLabelLen = t.ansiLabelPrinter(label, &tui.ColHeaderLabel, false)
|
}
|
||||||
req(reqRedrawHeaderLabel)
|
})
|
||||||
case actChangeFooterLabel, actTransformFooterLabel:
|
case actChangeListLabel, actTransformListLabel, actBgTransformListLabel:
|
||||||
label := a.a
|
capture(true, func(label string) {
|
||||||
if a.t == actTransformFooterLabel {
|
t.listLabelOpts.label = label
|
||||||
label = t.captureLine(a.a)
|
if t.wborder != nil {
|
||||||
}
|
t.listLabel, t.listLabelLen = t.ansiLabelPrinter(label, &tui.ColListLabel, false)
|
||||||
t.footerLabelOpts.label = label
|
req(reqRedrawListLabel)
|
||||||
t.footerLabel, t.footerLabelLen = t.ansiLabelPrinter(label, &tui.ColFooterLabel, false)
|
}
|
||||||
req(reqRedrawFooterLabel)
|
})
|
||||||
case actChangeInputLabel, actTransformInputLabel:
|
case actChangeBorderLabel, actTransformBorderLabel, actBgTransformBorderLabel:
|
||||||
label := a.a
|
capture(true, func(label string) {
|
||||||
if a.t == actTransformInputLabel {
|
t.borderLabelOpts.label = label
|
||||||
label = t.captureLine(a.a)
|
if t.border != nil {
|
||||||
}
|
t.borderLabel, t.borderLabelLen = t.ansiLabelPrinter(label, &tui.ColBorderLabel, false)
|
||||||
t.inputLabelOpts.label = label
|
req(reqRedrawBorderLabel)
|
||||||
if t.inputBorder != nil {
|
}
|
||||||
t.inputLabel, t.inputLabelLen = t.ansiLabelPrinter(label, &tui.ColInputLabel, false)
|
})
|
||||||
req(reqRedrawInputLabel)
|
case actChangePreviewLabel, actTransformPreviewLabel, actBgTransformPreviewLabel:
|
||||||
}
|
capture(true, func(label string) {
|
||||||
case actChangeListLabel, actTransformListLabel:
|
t.previewLabelOpts.label = label
|
||||||
label := a.a
|
if t.pborder != nil {
|
||||||
if a.t == actTransformListLabel {
|
t.previewLabel, t.previewLabelLen = t.ansiLabelPrinter(label, &tui.ColPreviewLabel, false)
|
||||||
label = t.captureLine(a.a)
|
req(reqRedrawPreviewLabel)
|
||||||
}
|
}
|
||||||
t.listLabelOpts.label = label
|
})
|
||||||
if t.wborder != nil {
|
case actTransform, actBgTransform:
|
||||||
t.listLabel, t.listLabelLen = t.ansiLabelPrinter(label, &tui.ColListLabel, false)
|
capture(false, func(body string) {
|
||||||
req(reqRedrawListLabel)
|
if actions, err := parseSingleActionList(strings.Trim(body, "\r\n")); err == nil {
|
||||||
}
|
// NOTE: We're not properly passing the return value here
|
||||||
case actChangeBorderLabel, actTransformBorderLabel:
|
doActions(actions)
|
||||||
label := a.a
|
}
|
||||||
if a.t == actTransformBorderLabel {
|
})
|
||||||
label = t.captureLine(a.a)
|
|
||||||
}
|
|
||||||
t.borderLabelOpts.label = label
|
|
||||||
if t.border != nil {
|
|
||||||
t.borderLabel, t.borderLabelLen = t.ansiLabelPrinter(label, &tui.ColBorderLabel, false)
|
|
||||||
req(reqRedrawBorderLabel)
|
|
||||||
}
|
|
||||||
case actChangePreviewLabel, actTransformPreviewLabel:
|
|
||||||
label := a.a
|
|
||||||
if a.t == actTransformPreviewLabel {
|
|
||||||
label = t.captureLine(a.a)
|
|
||||||
}
|
|
||||||
t.previewLabelOpts.label = label
|
|
||||||
if t.pborder != nil {
|
|
||||||
t.previewLabel, t.previewLabelLen = t.ansiLabelPrinter(label, &tui.ColPreviewLabel, false)
|
|
||||||
req(reqRedrawPreviewLabel)
|
|
||||||
}
|
|
||||||
case actTransform:
|
|
||||||
body := t.captureLines(a.a)
|
|
||||||
if actions, err := parseSingleActionList(strings.Trim(body, "\r\n")); err == nil {
|
|
||||||
return doActions(actions)
|
|
||||||
}
|
|
||||||
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)
|
||||||
@@ -5933,10 +6084,12 @@ func (t *Terminal) Loop() error {
|
|||||||
override := []rune(a.a)
|
override := []rune(a.a)
|
||||||
t.inputOverride = &override
|
t.inputOverride = &override
|
||||||
changed = true
|
changed = true
|
||||||
case actTransformSearch:
|
case actTransformSearch, actBgTransformSearch:
|
||||||
override := []rune(t.captureLine(a.a))
|
capture(true, func(query string) {
|
||||||
t.inputOverride = &override
|
override := []rune(query)
|
||||||
changed = true
|
t.inputOverride = &override
|
||||||
|
changed = true
|
||||||
|
})
|
||||||
case actEnableSearch:
|
case actEnableSearch:
|
||||||
t.paused = false
|
t.paused = false
|
||||||
changed = true
|
changed = true
|
||||||
@@ -6276,30 +6429,26 @@ func (t *Terminal) Loop() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case actChangeGhost, actTransformGhost:
|
case actChangeGhost, actTransformGhost, actBgTransformGhost:
|
||||||
ghost := a.a
|
capture(true, func(ghost string) {
|
||||||
if a.t == actTransformGhost {
|
t.ghost = ghost
|
||||||
ghost = t.captureLine(a.a)
|
if len(t.input) == 0 {
|
||||||
}
|
req(reqPrompt)
|
||||||
t.ghost = ghost
|
|
||||||
if len(t.input) == 0 {
|
|
||||||
req(reqPrompt)
|
|
||||||
}
|
|
||||||
case actChangePointer, actTransformPointer:
|
|
||||||
pointer := a.a
|
|
||||||
if a.t == actTransformPointer {
|
|
||||||
pointer = t.captureLine(a.a)
|
|
||||||
}
|
|
||||||
length := uniseg.StringWidth(pointer)
|
|
||||||
if length <= 2 {
|
|
||||||
if length != t.pointerLen {
|
|
||||||
t.forceRerenderList()
|
|
||||||
}
|
}
|
||||||
t.pointer = pointer
|
})
|
||||||
t.pointerLen = length
|
case actChangePointer, actTransformPointer, actBgTransformPointer:
|
||||||
t.pointerEmpty = strings.Repeat(" ", t.pointerLen)
|
capture(true, func(pointer string) {
|
||||||
req(reqList)
|
length := uniseg.StringWidth(pointer)
|
||||||
}
|
if length <= 2 {
|
||||||
|
if length != t.pointerLen {
|
||||||
|
t.forceRerenderList()
|
||||||
|
}
|
||||||
|
t.pointer = pointer
|
||||||
|
t.pointerLen = length
|
||||||
|
t.pointerEmpty = strings.Repeat(" ", t.pointerLen)
|
||||||
|
req(reqList)
|
||||||
|
}
|
||||||
|
})
|
||||||
case actChangePreview:
|
case actChangePreview:
|
||||||
if t.previewOpts.command != a.a {
|
if t.previewOpts.command != a.a {
|
||||||
t.previewOpts.command = a.a
|
t.previewOpts.command = a.a
|
||||||
@@ -6451,6 +6600,10 @@ func (t *Terminal) Loop() error {
|
|||||||
if reload {
|
if reload {
|
||||||
reloadRequest = &searchRequest{sort: t.sort, sync: reloadSync, nth: newNth, command: newCommand, environ: t.environ(), changed: changed, denylist: denylist, revision: t.merger.Revision()}
|
reloadRequest = &searchRequest{sort: t.sort, sync: reloadSync, nth: newNth, command: newCommand, environ: t.environ(), changed: changed, denylist: denylist, revision: t.merger.Revision()}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dispatch queued background requests
|
||||||
|
t.dispatchAsync()
|
||||||
|
|
||||||
t.mutex.Unlock() // Must be unlocked before touching reqBox
|
t.mutex.Unlock() // Must be unlocked before touching reqBox
|
||||||
|
|
||||||
if reload {
|
if reload {
|
||||||
|
|||||||
@@ -1939,4 +1939,29 @@ class TestCore < TestInteractive
|
|||||||
tmux.send_keys %(echo -en "foo\n" | fzf --read0 --no-multi-line), :Enter
|
tmux.send_keys %(echo -en "foo\n" | fzf --read0 --no-multi-line), :Enter
|
||||||
tmux.until { |lines| assert_includes lines, '> foo␊' }
|
tmux.until { |lines| assert_includes lines, '> foo␊' }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_async_transform
|
||||||
|
time = Time.now
|
||||||
|
tmux.send_keys %(
|
||||||
|
seq 100 | #{FZF} --style full --border --preview : \
|
||||||
|
--bind 'focus:bg-transform-header(sleep 0.5; echo th.)' \
|
||||||
|
--bind 'focus:+bg-transform-footer(sleep 0.5; echo tf.)' \
|
||||||
|
--bind 'focus:+bg-transform-border-label(sleep 0.5; echo tbl.)' \
|
||||||
|
--bind "focus:+bg-transform-preview-label(sleep 0.5; echo tpl.)" \
|
||||||
|
--bind 'focus:+bg-transform-input-label(sleep 0.5; echo til.)' \
|
||||||
|
--bind 'focus:+bg-transform-list-label(sleep 0.5; echo tll.)' \
|
||||||
|
--bind 'focus:+bg-transform-header-label(sleep 0.5; echo thl.)' \
|
||||||
|
--bind 'focus:+bg-transform-footer-label(sleep 0.5; echo tfl.)' \
|
||||||
|
--bind 'focus:+bg-transform-prompt(sleep 0.5; echo tp.)' \
|
||||||
|
--bind 'focus:+bg-transform-ghost(sleep 0.5; echo tg.)'
|
||||||
|
).strip, :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert lines.any_include?('100/100')
|
||||||
|
%w[th tf tbl tpl til tll thl tfl tp tg].each do
|
||||||
|
assert lines.any_include?("#{it}.")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elapsed = Time.now - time
|
||||||
|
assert elapsed < 2
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user