summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ADVANCED.md47
-rw-r--r--CHANGELOG.md21
-rw-r--r--man/man1/fzf.16
-rw-r--r--src/actiontype_string.go75
-rw-r--r--src/options.go12
-rw-r--r--src/terminal.go48
-rw-r--r--test/test_core.rb18
7 files changed, 173 insertions, 54 deletions
diff --git a/ADVANCED.md b/ADVANCED.md
index bafe9708..2636b15f 100644
--- a/ADVANCED.md
+++ b/ADVANCED.md
@@ -1,8 +1,8 @@
Advanced fzf examples
======================
-* *Last update: 2024/06/24*
-* *Requires fzf 0.54.0 or later*
+* *Last update: 2025/01/26*
+* *Requires fzf 0.59.0 or later*
---
@@ -22,6 +22,7 @@ Advanced fzf examples
* [Switching to fzf-only search mode](#switching-to-fzf-only-search-mode)
* [Switching between Ripgrep mode and fzf mode](#switching-between-ripgrep-mode-and-fzf-mode)
* [Switching between Ripgrep mode and fzf mode using a single key binding](#switching-between-ripgrep-mode-and-fzf-mode-using-a-single-key-binding)
+ * [Controlling Ripgrap search and fzf search simultaneously](#controlling-ripgrap-search-and-fzf-search-simultaneously)
* [Log tailing](#log-tailing)
* [Key bindings for git objects](#key-bindings-for-git-objects)
* [Files listed in `git status`](#files-listed-in-git-status)
@@ -500,6 +501,48 @@ fzf --ansi --disabled --query "$INITIAL_QUERY" \
--bind 'enter:become(vim {1} +{2})'
```
+### Controlling Ripgrap search and fzf search simultaneously
+
+fzf 0.59.0 added `search` action that allows you to trigger an fzf search
+with an arbitrary query string. This means fzf is no longer restricted to the
+exact query entered in the prompt.
+
+In the example below, `transform` action is used to conditionally trigger
+either `reload` for ripgrep or `search` for fzf. The first word of the query
+initiates the Ripgrep process to generate the initial results, while the
+remainder of the query is passed to fzf for secondary filtering.
+
+```sh
+#!/usr/bin/env bash
+
+# Switch between Ripgrep mode and fzf filtering mode (CTRL-T)
+RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
+INITIAL_QUERY="${*:-}"
+TRANSFORMER='
+ words=($FZF_QUERY)
+
+ # If $FZF_QUERY contains multiple words, drop the first word,
+ # and trigger fzf search with the rest
+ if [[ ${#words[@]} -gt 1 ]]; then
+ echo "search:${FZF_QUERY#* }"
+
+ # Otherwise, if the query does not end with a space,
+ # restart ripgrep and reload the list
+ elif ! [[ $FZF_QUERY =~ \ $ ]]; then
+ echo "reload:sleep 0.1; $RG_PREFIX \"${words[0]}\" || true"
+ fi
+'
+fzf --ansi --disabled --query "$INITIAL_QUERY" \
+ --with-shell 'bash -c' \
+ --bind "start:transform:$TRANSFORMER" \
+ --bind "change:transform:$TRANSFORMER" \
+ --color "hl:-1:underline,hl+:-1:underline:reverse" \
+ --delimiter : \
+ --preview 'bat --color=always {1} --highlight-line {2}' \
+ --preview-window 'up,60%,border-bottom,+{2}+3/3,~3' \
+ --bind 'enter:become(vim {1} +{2})'
+```
+
Log tailing
-----------
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a808a5b7..ab795978 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,27 @@ CHANGELOG
--bind 'ctrl-r:reload(ps -ef)' --header 'Press CTRL-R to reload' \
--header-lines-border bottom --no-list-border
```
+- Added `search(...)` and `transform-search(...)` action to trigger an fzf search with an arbitrary query string. This can be used to extend the search syntax of fzf. In the following example, fzf will use the first word of the query to trigger ripgrep search, and use the rest of the query to perform fzf search within the result.
+ ```sh
+ TRANSFORMER='
+ words=($FZF_QUERY)
+
+ # If $FZF_QUERY contains multiple words, drop the first word,
+ # and trigger fzf search with the rest
+ if [[ ${#words[@]} -gt 1 ]]; then
+ echo "search:${FZF_QUERY#* }"
+
+ # Otherwise, if the query does not end with a space,
+ # restart ripgrep and reload the list
+ elif ! [[ $FZF_QUERY =~ \ $ ]]; then
+ echo "reload:rg --column --color=always --smart-case \"${words[0]}\""
+ fi
+ '
+ fzf --ansi --disabled \
+ --with-shell 'bash -c' \
+ --bind "start:transform:$TRANSFORMER" \
+ --bind "change:transform:$TRANSFORMER"
+ ```
- Added `bell` action to ring the terminal bell
```sh
# Press CTRL-Y to copy the current line to the clipboard and ring the bell
diff --git a/man/man1/fzf.1 b/man/man1/fzf.1
index 2377ba03..89883b4b 100644
--- a/man/man1/fzf.1
+++ b/man/man1/fzf.1
@@ -94,8 +94,8 @@ more weight to the chronological ordering. This also sets
.RS
fzf chooses \fBpath\fR scheme when the input is a TTY device, where fzf would
start its built-in walker or run \fB$FZF_DEFAULT_COMMAND\fR, and there is no
-\fBreload\fR action bound to \fBstart\fR event. Otherwise, it chooses
-\fBdefault\fR scheme.
+\fBreload\fR or \fBtransform\fR action bound to \fBstart\fR event. Otherwise,
+it chooses \fBdefault\fR scheme.
.RE
.TP
@@ -1609,6 +1609,7 @@ A key or an event can be bound to one or more of the following actions.
\fBreload(...)\fR (see below for the details)
\fBreload\-sync(...)\fR (see below for the details)
\fBreplace\-query\fR (replace query string with the current selection)
+ \fBsearch(...)\fR (trigger fzf search with the given string)
\fBselect\fR
\fBselect\-all\fR (select all matches)
\fBshow\-header\fR
@@ -1639,6 +1640,7 @@ A key or an event can be bound to one or more of the following actions.
\fBtransform\-preview\-label(...)\fR (transform preview label using an external command)
\fBtransform\-prompt(...)\fR (transform prompt string using an external command)
\fBtransform\-query(...)\fR (transform query string using an external command)
+ \fBtransform\-search(...)\fR (trigger fzf search with the output of an external command)
\fBunbind(...)\fR (unbind bindings)
\fBunix\-line\-discard\fR \fIctrl\-u\fR
\fBunix\-word\-rubout\fR \fIctrl\-w\fR
diff --git a/src/actiontype_string.go b/src/actiontype_string.go
index 4459e251..143e02b6 100644
--- a/src/actiontype_string.go
+++ b/src/actiontype_string.go
@@ -96,45 +96,48 @@ func _() {
_ = x[actTransformPreviewLabel-85]
_ = x[actTransformPrompt-86]
_ = x[actTransformQuery-87]
- _ = x[actPreview-88]
- _ = x[actChangePreview-89]
- _ = x[actChangePreviewWindow-90]
- _ = x[actPreviewTop-91]
- _ = x[actPreviewBottom-92]
- _ = x[actPreviewUp-93]
- _ = x[actPreviewDown-94]
- _ = x[actPreviewPageUp-95]
- _ = x[actPreviewPageDown-96]
- _ = x[actPreviewHalfPageUp-97]
- _ = x[actPreviewHalfPageDown-98]
- _ = x[actPrevHistory-99]
- _ = x[actPrevSelected-100]
- _ = x[actPrint-101]
- _ = x[actPut-102]
- _ = x[actNextHistory-103]
- _ = x[actNextSelected-104]
- _ = x[actExecute-105]
- _ = x[actExecuteSilent-106]
- _ = x[actExecuteMulti-107]
- _ = x[actSigStop-108]
- _ = x[actFirst-109]
- _ = x[actLast-110]
- _ = x[actReload-111]
- _ = x[actReloadSync-112]
- _ = x[actDisableSearch-113]
- _ = x[actEnableSearch-114]
- _ = x[actSelect-115]
- _ = x[actDeselect-116]
- _ = x[actUnbind-117]
- _ = x[actRebind-118]
- _ = x[actBecome-119]
- _ = x[actShowHeader-120]
- _ = x[actHideHeader-121]
+ _ = x[actTransformSearch-88]
+ _ = x[actSearch-89]
+ _ = x[actPreview-90]
+ _ = x[actChangePreview-91]
+ _ = x[actChangePreviewWindow-92]
+ _ = x[actPreviewTop-93]
+ _ = x[actPreviewBottom-94]
+ _ = x[actPreviewUp-95]
+ _ = x[actPreviewDown-96]
+ _ = x[actPreviewPageUp-97]
+ _ = x[actPreviewPageDown-98]
+ _ = x[actPreviewHalfPageUp-99]
+ _ = x[actPreviewHalfPageDown-100]
+ _ = x[actPrevHistory-101]
+ _ = x[actPrevSelected-102]
+ _ = x[actPrint-103]
+ _ = x[actPut-104]
+ _ = x[actNextHistory-105]
+ _ = x[actNextSelected-106]
+ _ = x[actExecute-107]
+ _ = x[actExecuteSilent-108]
+ _ = x[actExecuteMulti-109]
+ _ = x[actSigStop-110]
+ _ = x[actFirst-111]
+ _ = x[actLast-112]
+ _ = x[actReload-113]
+ _ = x[actReloadSync-114]
+ _ = x[actDisableSearch-115]
+ _ = x[actEnableSearch-116]
+ _ = x[actSelect-117]
+ _ = x[actDeselect-118]
+ _ = x[actUnbind-119]
+ _ = x[actRebind-120]
+ _ = x[actBecome-121]
+ _ = x[actShowHeader-122]
+ _ = x[actHideHeader-123]
+ _ = x[actBell-124]
}
-const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeListLabelactChangeInputLabelactChangeHeaderactChangeHeaderLabelactChangeMultiactChangePreviewLabelactChangePromptactChangeQueryactChangeNthactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactToggleMultiLineactToggleHscrollactTrackCurrentactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformListLabelactTransformInputLabelactTransformHeaderactTransformHeaderLabelactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactShowHeaderactHideHeader"
+const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeListLabelactChangeInputLabelactChangeHeaderactChangeHeaderLabelactChangeMultiactChangePreviewLabelactChangePromptactChangeQueryactChangeNthactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactToggleMultiLineactToggleHscrollactTrackCurrentactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformListLabelactTransformInputLabelactTransformHeaderactTransformHeaderLabelactTransformPreviewLabelactTransformPromptactTransformQueryactTransformSearchactSearchactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactShowHeaderactHideHeaderactBell"
-var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 245, 264, 279, 299, 313, 334, 349, 363, 375, 389, 402, 419, 427, 440, 456, 468, 476, 490, 504, 515, 526, 544, 561, 568, 587, 599, 613, 622, 637, 649, 662, 673, 684, 696, 710, 731, 746, 759, 777, 793, 808, 825, 832, 837, 846, 857, 868, 881, 896, 907, 920, 935, 942, 955, 968, 985, 1000, 1013, 1027, 1041, 1057, 1077, 1089, 1112, 1133, 1155, 1173, 1196, 1220, 1238, 1255, 1265, 1281, 1303, 1316, 1332, 1344, 1358, 1374, 1392, 1412, 1434, 1448, 1463, 1471, 1477, 1491, 1506, 1516, 1532, 1547, 1557, 1565, 1572, 1581, 1594, 1610, 1625, 1634, 1645, 1654, 1663, 1672, 1685, 1698}
+var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 245, 264, 279, 299, 313, 334, 349, 363, 375, 389, 402, 419, 427, 440, 456, 468, 476, 490, 504, 515, 526, 544, 561, 568, 587, 599, 613, 622, 637, 649, 662, 673, 684, 696, 710, 731, 746, 759, 777, 793, 808, 825, 832, 837, 846, 857, 868, 881, 896, 907, 920, 935, 942, 955, 968, 985, 1000, 1013, 1027, 1041, 1057, 1077, 1089, 1112, 1133, 1155, 1173, 1196, 1220, 1238, 1255, 1273, 1282, 1292, 1308, 1330, 1343, 1359, 1371, 1385, 1401, 1419, 1439, 1461, 1475, 1490, 1498, 1504, 1518, 1533, 1543, 1559, 1574, 1584, 1592, 1599, 1608, 1621, 1637, 1652, 1661, 1672, 1681, 1690, 1699, 1712, 1725, 1732}
func (i actionType) String() string {
if i < 0 || i >= actionType(len(_actionType_index)-1) {
diff --git a/src/options.go b/src/options.go
index 9d233b57..2b9a5196 100644
--- a/src/options.go
+++ b/src/options.go
@@ -1332,7 +1332,7 @@ const (
func init() {
executeRegexp = regexp.MustCompile(
- `(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|transform)-(?:query|prompt|(?:border|list|preview|input|header)-label|header)|transform|change-(?:preview-window|preview|multi|nth)|(?:re|un)bind|pos|put|print)`)
+ `(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|transform)-(?:query|prompt|(?:border|list|preview|input|header)-label|header|search)|transform|change-(?:preview-window|preview|multi|nth)|(?:re|un)bind|pos|put|print|search)`)
splitRegexp = regexp.MustCompile("[,:]+")
actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+")
}
@@ -1744,6 +1744,10 @@ func isExecuteAction(str string) actionType {
return actTransformPrompt
case "transform-query":
return actTransformQuery
+ case "transform-search":
+ return actTransformSearch
+ case "search":
+ return actSearch
}
return actIgnore
}
@@ -3252,7 +3256,7 @@ func ParseOptions(useDefaults bool, args []string) (*Options, error) {
// 1. explicitly set --scheme=default,
// 2. or replace $FZF_DEFAULT_COMMAND with an equivalent 'start:reload'
// binding, which is the new preferred way.
- if !opts.hasReloadOnStart() && util.IsTty(os.Stdin) {
+ if !opts.hasReloadOrTransformOnStart() && util.IsTty(os.Stdin) {
opts.Scheme = "path"
}
_, opts.Criteria, _ = parseScheme(opts.Scheme)
@@ -3267,10 +3271,10 @@ func ParseOptions(useDefaults bool, args []string) (*Options, error) {
return opts, nil
}
-func (opts *Options) hasReloadOnStart() bool {
+func (opts *Options) hasReloadOrTransformOnStart() bool {
if actions, prs := opts.Keymap[tui.Start.AsEvent()]; prs {
for _, action := range actions {
- if action.t == actReload || action.t == actReloadSync {
+ if action.t == actReload || action.t == actReloadSync || action.t == actTransform {
return true
}
}
diff --git a/src/terminal.go b/src/terminal.go
index 30523de3..4d58ca8c 100644
--- a/src/terminal.go
+++ b/src/terminal.go
@@ -277,6 +277,7 @@ type Terminal struct {
xoffset int
yanked []rune
input []rune
+ inputOverride *[]rune
multi int
multiLine bool
sort bool
@@ -533,6 +534,8 @@ const (
actTransformPreviewLabel
actTransformPrompt
actTransformQuery
+ actTransformSearch
+ actSearch
actPreview
actChangePreview
actChangePreviewWindow
@@ -1354,7 +1357,13 @@ func (t *Terminal) getScrollbar() (int, int) {
func (t *Terminal) Input() (bool, []rune) {
t.mutex.Lock()
defer t.mutex.Unlock()
- return t.paused, copySlice(t.input)
+ paused := t.paused
+ src := t.input
+ if t.inputOverride != nil {
+ paused = false
+ src = *t.inputOverride
+ }
+ return paused, copySlice(src)
}
// UpdateCount updates the count information
@@ -3837,6 +3846,14 @@ func (t *Terminal) fullRedraw() {
t.printAll()
}
+func (t *Terminal) captureLine(template string) string {
+ return t.executeCommand(template, false, true, true, true, "")
+}
+
+func (t *Terminal) captureLines(template string) string {
+ return t.executeCommand(template, false, true, true, false, "")
+}
+
func (t *Terminal) executeCommand(template string, forcePlus bool, background bool, capture bool, firstLineOnly bool, info string) string {
line := ""
valid, list := t.buildPlusList(template, forcePlus)
@@ -4751,12 +4768,12 @@ func (t *Terminal) Loop() error {
req(reqPreviewRefresh)
}
case actTransformPrompt:
- prompt := t.executeCommand(a.a, false, true, true, true, "")
+ prompt := t.captureLine(a.a)
t.promptString = prompt
t.prompt, t.promptLen = t.parsePrompt(prompt)
req(reqPrompt)
case actTransformQuery:
- query := t.executeCommand(a.a, false, true, true, true, "")
+ query := t.captureLine(a.a)
t.input = []rune(query)
t.cx = len(t.input)
case actToggleSort:
@@ -4840,7 +4857,7 @@ func (t *Terminal) Loop() error {
case actChangeHeader, actTransformHeader:
header := a.a
if a.t == actTransformHeader {
- header = t.executeCommand(a.a, false, true, true, false, "")
+ header = t.captureLines(a.a)
}
if t.changeHeader(header) {
req(reqHeader, reqList, reqPrompt, reqInfo)
@@ -4878,40 +4895,40 @@ func (t *Terminal) Loop() error {
req(reqRedrawPreviewLabel)
}
case actTransform:
- body := t.executeCommand(a.a, false, true, true, false, "")
+ body := t.captureLines(a.a)
if actions, err := parseSingleActionList(strings.Trim(body, "\r\n")); err == nil {
return doActions(actions)
}
case actTransformHeaderLabel:
- label := t.executeCommand(a.a, false, true, true, true, "")
+ label := t.captureLine(a.a)
t.headerLabelOpts.label = label
if t.headerBorder != nil {
t.headerLabel, t.headerLabelLen = t.ansiLabelPrinter(label, &tui.ColHeaderLabel, false)
req(reqRedrawHeaderLabel)
}
case actTransformInputLabel:
- label := t.executeCommand(a.a, false, true, true, true, "")
+ label := t.captureLine(a.a)
t.inputLabelOpts.label = label
if t.inputBorder != nil {
t.inputLabel, t.inputLabelLen = t.ansiLabelPrinter(label, &tui.ColInputLabel, false)
req(reqRedrawInputLabel)
}
case actTransformListLabel:
- label := t.executeCommand(a.a, false, true, true, true, "")
+ label := t.captureLine(a.a)
t.listLabelOpts.label = label
if t.wborder != nil {
t.listLabel, t.listLabelLen = t.ansiLabelPrinter(label, &tui.ColListLabel, false)
req(reqRedrawListLabel)
}
case actTransformBorderLabel:
- label := t.executeCommand(a.a, false, true, true, true, "")
+ 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 actTransformPreviewLabel:
- label := t.executeCommand(a.a, false, true, true, true, "")
+ label := t.captureLine(a.a)
t.previewLabelOpts.label = label
if t.pborder != nil {
t.previewLabel, t.previewLabelLen = t.ansiLabelPrinter(label, &tui.ColPreviewLabel, false)
@@ -5309,6 +5326,14 @@ func (t *Terminal) Loop() error {
t.track = trackDisabled
}
req(reqInfo)
+ case actSearch:
+ override := []rune(a.a)
+ t.inputOverride = &override
+ changed = true
+ case actTransformSearch:
+ override := []rune(t.captureLine(a.a))
+ t.inputOverride = &override
+ changed = true
case actEnableSearch:
t.paused = false
changed = true
@@ -5734,6 +5759,9 @@ func (t *Terminal) Loop() error {
}
t.truncateQuery()
queryChanged = string(previousInput) != string(t.input)
+ if queryChanged {
+ t.inputOverride = nil
+ }
changed = changed || queryChanged
if onChanges, prs := t.keymap[tui.Change.AsEvent()]; queryChanged && prs && !doActions(onChanges) {
continue
diff --git a/test/test_core.rb b/test/test_core.rb
index 1524a885..0d80644b 100644
--- a/test/test_core.rb
+++ b/test/test_core.rb
@@ -1069,6 +1069,24 @@ class TestCore < TestInteractive
tmux.until { |lines| assert_equal 'up', lines[-1] }
end
+ def test_search
+ tmux.send_keys %(seq 100 | #{FZF} --query 0 --bind space:search:1), :Enter
+ tmux.until { |lines| assert_equal 10, lines.match_count }
+ tmux.send_keys :Space
+ tmux.until { |lines| assert_equal 20, lines.match_count }
+ tmux.send_keys '0'
+ tmux.until { |lines| assert_equal 1, lines.match_count }
+ end
+
+ def test_transform_search
+ tmux.send_keys %(seq 1000 | #{FZF} --bind 'change:transform-search:echo {q}{q}'), :Enter
+ tmux.until { |lines| assert_equal 1000, lines.match_count }
+ tmux.send_keys '1'
+ tmux.until { |lines| assert_equal 28, lines.match_count }
+ tmux.send_keys :BSpace, '0'
+ tmux.until { |lines| assert_equal 10, lines.match_count }
+ end
+
def test_clear_selection
tmux.send_keys %(seq 100 | #{FZF} --multi --bind space:clear-selection), :Enter
tmux.until { |lines| assert_equal 100, lines.match_count }