mirror of
https://github.com/junegunn/fzf.git
synced 2026-03-18 12:32:40 +08:00
Replace --track=NTH with --id-nth for cross-reload item identity
Separate item identity from cursor tracking: - Add --id-nth=NTH to define item identity fields for cross-reload ops - --track reverts to a simple boolean flag - track-current action no longer accepts nth argument - With --multi, selections are preserved across reload-sync by matching identity keys in the reloaded list Close #4718 Close #4701 Close #4483 Close #4409 Close #3460 Close #2441
This commit is contained in:
18
CHANGELOG.md
18
CHANGELOG.md
@@ -3,16 +3,16 @@ CHANGELOG
|
||||
|
||||
0.71.0
|
||||
------
|
||||
- Cross-reload tracking
|
||||
- `--track` now accepts an optional nth expression (`--track=NTH`) for field-based tracking across `reload`s
|
||||
- `--track` without `NTH` retains the existing index-based tracking behavior (does not persist across reloads)
|
||||
- `--track=..` tracks by the entire line across reloads
|
||||
- `--track=1` tracks by the first field across reloads
|
||||
- When a `reload` is triggered, fzf searches for the tracked item by its nth field in the new list.
|
||||
- Cross-reload item identity with `--id-nth`
|
||||
- Added `--id-nth=NTH` to define item identity fields for cross-reload operations
|
||||
- When a `reload` is triggered with tracking enabled, fzf searches for the tracked item by its identity fields in the new list.
|
||||
- `--track --id-nth ..` tracks by the entire line
|
||||
- `--track --id-nth 1` tracks by the first field
|
||||
- `--track` without `--id-nth` retains the existing index-based tracking behavior
|
||||
- The UI is temporarily blocked (prompt dimmed, input disabled) until the item is found or loading completes.
|
||||
- Press `Escape` or `Ctrl-C` to cancel the blocked state without quitting
|
||||
- Info line shows `+T*` / `+t*` while searching
|
||||
- `track-current` action now also accepts an optional nth argument: `track-current(1)`
|
||||
- Press `Escape` or `Ctrl-C` to cancel the blocked state without quitting
|
||||
- Info line shows `+T*` / `+t*` while searching
|
||||
- With `--multi`, selected items are preserved across `reload-sync` by matching their identity fields
|
||||
- Performance improvements
|
||||
- The search performance now scales linearly with the number of CPU cores, as we dropped static partitioning to allow better load balancing across threads.
|
||||
```
|
||||
|
||||
@@ -617,28 +617,18 @@ Disable multi-line display of items when using \fB\-\-read0\fR
|
||||
.B "\-\-raw"
|
||||
Enable raw mode where non-matching items are also displayed in a dimmed color.
|
||||
.TP
|
||||
.BI "\-\-track" "[=NTH]"
|
||||
.BI "\-\-track"
|
||||
Make fzf track the current selection when the result list is updated.
|
||||
This can be useful when browsing logs using fzf with sorting disabled. It is
|
||||
not recommended to use this option with \fB\-\-tac\fR as the resulting behavior
|
||||
can be confusing.
|
||||
|
||||
When an nth expression is explicitly given (e.g. \fB\-\-track=..\fR or
|
||||
\fB\-\-track=1\fR), fzf enables field\-based tracking across \fBreload\fRs.
|
||||
On reload, fzf extracts the tracking key from the current item using the nth
|
||||
expression and searches for a matching item in the reloaded list. While
|
||||
searching, the UI is blocked (query input and cursor movement are disabled, and
|
||||
the prompt is dimmed). With \fBreload\fR, the blocked state clears as soon as
|
||||
the match is found in the stream. With \fBreload\-sync\fR, the blocked state
|
||||
persists until the entire stream is complete. Press \fBEscape\fR or
|
||||
\fBCtrl\-C\fR to cancel the blocked state without quitting fzf.
|
||||
When \fB\-\-id\-nth\fR is also set, fzf enables field\-based tracking across
|
||||
\fBreload\fRs. See \fB\-\-id\-nth\fR for details.
|
||||
|
||||
Without the nth expression, \fB\-\-track\fR uses index\-based tracking that
|
||||
Without \fB\-\-id\-nth\fR, \fB\-\-track\fR uses index\-based tracking that
|
||||
does not persist across reloads.
|
||||
|
||||
The info line shows \fB+T*\fR (or \fB+t*\fR for one\-off tracking) while
|
||||
the search is in progress.
|
||||
|
||||
.RS
|
||||
e.g.
|
||||
\fB# Index\-based tracking (does not persist across reloads)
|
||||
@@ -646,10 +636,36 @@ e.g.
|
||||
fzf \-\-ansi \-\-track \-\-no\-sort \-\-layout=reverse\-list\fR
|
||||
|
||||
\fB# Track by first field (e.g. pod name) across reloads
|
||||
kubectl get pods | fzf \-\-track=1 \-\-header\-lines=1 \\
|
||||
kubectl get pods | fzf \-\-track \-\-id\-nth 1 \-\-header\-lines=1 \\
|
||||
\-\-bind 'ctrl\-r:reload:kubectl get pods'\fR
|
||||
.RE
|
||||
.TP
|
||||
.BI "\-\-id\-nth=" "N[,..]"
|
||||
Define item identity fields for cross\-reload operations. When set, fzf
|
||||
uses the specified fields to identify items across \fBreload\fR and
|
||||
\fBreload\-sync\fR.
|
||||
|
||||
With \fB\-\-track\fR, fzf extracts the tracking key from the current item
|
||||
using the nth expression and searches for a matching item in the reloaded list.
|
||||
While searching, the UI is blocked (query input and cursor movement are
|
||||
disabled, and the prompt is dimmed). With \fBreload\fR, the blocked state
|
||||
clears as soon as the match is found in the stream. With \fBreload\-sync\fR,
|
||||
the blocked state persists until the entire stream is complete. Press
|
||||
\fBEscape\fR or \fBCtrl\-C\fR to cancel the blocked state without quitting fzf.
|
||||
|
||||
The info line shows \fB+T*\fR (or \fB+t*\fR for one\-off tracking) while
|
||||
the search is in progress.
|
||||
|
||||
With \fB\-\-multi\fR, selected items are preserved across \fBreload\-sync\fR
|
||||
by matching their identity fields in the reloaded list.
|
||||
|
||||
.RS
|
||||
e.g.
|
||||
\fB# Track and preserve selections by pod name across reloads
|
||||
kubectl get pods | fzf \-\-multi \-\-track \-\-id\-nth 1 \-\-header\-lines=1 \\
|
||||
\-\-bind 'ctrl\-r:reload\-sync:kubectl get pods'\fR
|
||||
.RE
|
||||
.TP
|
||||
.B "\-\-tac"
|
||||
Reverse the order of the input
|
||||
|
||||
@@ -2027,7 +2043,6 @@ A key or an event can be bound to one or more of the following actions.
|
||||
\fBtoggle+down\fR \fIctrl\-i (tab)\fR
|
||||
\fBtoggle+up\fR \fIbtab (shift\-tab)\fR
|
||||
\fBtrack\-current\fR (track the current item; automatically disabled if focus changes)
|
||||
\fBtrack\-current(...)\fR (track the current item using the given nth expression as the tracking key)
|
||||
\fBtransform(...)\fR (transform states using the output of an external command)
|
||||
\fBtransform\-border\-label(...)\fR (transform border label using an external command)
|
||||
\fBtransform\-ghost(...)\fR (transform ghost text using an external command)
|
||||
|
||||
@@ -101,6 +101,7 @@ Usage: fzf [options]
|
||||
--no-multi-line Disable multi-line display of items when using --read0
|
||||
--raw Enable raw mode (show non-matching items)
|
||||
--track Track the current selection when the result is updated
|
||||
--id-nth=N[,..] Define item identity fields for cross-reload operations
|
||||
--tac Reverse the order of the input
|
||||
--gap[=N] Render empty lines between each item
|
||||
--gap-line[=STR] Draw horizontal line on each gap using the string
|
||||
@@ -594,7 +595,7 @@ type Options struct {
|
||||
Sort int
|
||||
Raw bool
|
||||
Track trackOption
|
||||
TrackNth []Range
|
||||
IdNth []Range
|
||||
Tac bool
|
||||
Tail int
|
||||
Criteria []criterion
|
||||
@@ -1631,7 +1632,7 @@ const (
|
||||
|
||||
func init() {
|
||||
argActionRegexp = regexp.MustCompile(
|
||||
`(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|bg-transform|transform)-(?:query|prompt|(?:border|list|preview|input|header|footer)-label|header-lines|header|footer|search|with-nth|nth|pointer|ghost)|bg-transform|transform|change-(?:preview-window|preview|multi)|(?:re|un|toggle-)bind|pos|put|print|search|trigger|track(?:-current)?)`)
|
||||
`(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|bg-transform|transform)-(?:query|prompt|(?:border|list|preview|input|header|footer)-label|header-lines|header|footer|search|with-nth|nth|pointer|ghost)|bg-transform|transform|change-(?:preview-window|preview|multi)|(?:re|un|toggle-)bind|pos|put|print|search|trigger)`)
|
||||
splitRegexp = regexp.MustCompile("[,:]+")
|
||||
actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+")
|
||||
}
|
||||
@@ -1955,12 +1956,6 @@ func parseActionList(masked string, original string, prevActions []*action, putA
|
||||
if _, _, err := parseKeyChords(actionArg, spec[0:offset]+" target required"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case actTrackCurrent:
|
||||
if len(actionArg) > 0 {
|
||||
if _, err := splitNth(actionArg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
case actChangePreviewWindow:
|
||||
opts := previewOpts{}
|
||||
for _, arg := range strings.Split(actionArg, "|") {
|
||||
@@ -2166,8 +2161,6 @@ func isExecuteAction(str string) actionType {
|
||||
return actTrigger
|
||||
case "search":
|
||||
return actSearch
|
||||
case "track", "track-current":
|
||||
return actTrackCurrent
|
||||
}
|
||||
return actIgnore
|
||||
}
|
||||
@@ -2818,18 +2811,18 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
|
||||
opts.Raw = false
|
||||
case "--track":
|
||||
opts.Track = trackEnabled
|
||||
if ok, str := optionalNextString(); ok {
|
||||
nth, err := splitNth(str)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
opts.TrackNth = nth
|
||||
} else {
|
||||
opts.TrackNth = nil
|
||||
}
|
||||
case "--no-track":
|
||||
opts.Track = trackDisabled
|
||||
opts.TrackNth = nil
|
||||
case "--id-nth":
|
||||
str, err := nextString("nth expression required")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if opts.IdNth, err = splitNth(str); err != nil {
|
||||
return err
|
||||
}
|
||||
case "--no-id-nth":
|
||||
opts.IdNth = nil
|
||||
case "--tac":
|
||||
opts.Tac = true
|
||||
case "--no-tac":
|
||||
|
||||
@@ -314,11 +314,12 @@ type Terminal struct {
|
||||
sort bool
|
||||
toggleSort bool
|
||||
track trackOption
|
||||
trackNth []Range
|
||||
idNth []Range
|
||||
trackKey string
|
||||
trackBlocked bool
|
||||
trackSync bool
|
||||
trackKeyCache map[int32]bool
|
||||
pendingSelections map[string]selectedItem
|
||||
targetIndex int32
|
||||
delimiter Delimiter
|
||||
expect map[tui.Event]string
|
||||
@@ -1049,7 +1050,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
||||
sort: opts.Sort > 0,
|
||||
toggleSort: opts.ToggleSort,
|
||||
track: opts.Track,
|
||||
trackNth: opts.TrackNth,
|
||||
idNth: opts.IdNth,
|
||||
targetIndex: minItem.Index(),
|
||||
delimiter: opts.Delimiter,
|
||||
expect: opts.Expect,
|
||||
@@ -1857,7 +1858,14 @@ func (t *Terminal) UpdateList(result MatchResult) {
|
||||
}
|
||||
if t.revision != newRevision {
|
||||
if !t.revision.compatible(newRevision) {
|
||||
// Reloaded: clear selection
|
||||
// Reloaded: capture selection keys for restoration, then clear
|
||||
if len(t.idNth) > 0 && t.multi > 0 && len(t.selected) > 0 {
|
||||
t.pendingSelections = make(map[string]selectedItem, len(t.selected))
|
||||
for _, sel := range t.selected {
|
||||
key := t.trackKeyFor(sel.item, t.idNth)
|
||||
t.pendingSelections[key] = sel
|
||||
}
|
||||
}
|
||||
t.selected = make(map[int32]selectedItem)
|
||||
t.clearNumLinesCache()
|
||||
} else {
|
||||
@@ -1912,7 +1920,7 @@ func (t *Terminal) UpdateList(result MatchResult) {
|
||||
idx := item.Index()
|
||||
match, ok := t.trackKeyCache[idx]
|
||||
if !ok {
|
||||
match = t.trackKeyFor(item, t.trackNth) == t.trackKey
|
||||
match = t.trackKeyFor(item, t.idNth) == t.trackKey
|
||||
t.trackKeyCache[idx] = match
|
||||
}
|
||||
if match {
|
||||
@@ -1943,6 +1951,18 @@ func (t *Terminal) UpdateList(result MatchResult) {
|
||||
t.cy = count - min(count, t.maxItems()) + pos
|
||||
}
|
||||
}
|
||||
// Restore selections by id-nth key after reload completes
|
||||
if !t.reading && t.pendingSelections != nil {
|
||||
for i := 0; i < t.merger.Length() && len(t.pendingSelections) > 0; i++ {
|
||||
item := t.merger.Get(i).item
|
||||
key := t.trackKeyFor(item, t.idNth)
|
||||
if sel, found := t.pendingSelections[key]; found {
|
||||
t.selected[item.Index()] = selectedItem{sel.at, item}
|
||||
delete(t.pendingSelections, key)
|
||||
}
|
||||
}
|
||||
t.pendingSelections = nil
|
||||
}
|
||||
needActivation := false
|
||||
if !t.reading {
|
||||
switch t.resultMerger.Length() {
|
||||
@@ -7104,12 +7124,6 @@ func (t *Terminal) Loop() error {
|
||||
if !t.track.Global() {
|
||||
t.track = trackCurrent(t.currentIndex())
|
||||
}
|
||||
// Parse optional nth argument: track-current(1) or track-current(1,3)
|
||||
if len(a.a) > 0 {
|
||||
if nth, err := splitNth(a.a); err == nil {
|
||||
t.trackNth = nth
|
||||
}
|
||||
}
|
||||
req(reqInfo)
|
||||
case actUntrackCurrent:
|
||||
if t.track.Current() {
|
||||
@@ -7469,9 +7483,9 @@ func (t *Terminal) Loop() error {
|
||||
t.reading = true
|
||||
|
||||
// Capture tracking key before reload
|
||||
if !t.track.Disabled() && len(t.trackNth) > 0 {
|
||||
if !t.track.Disabled() && len(t.idNth) > 0 {
|
||||
if item := t.currentItem(); item != nil {
|
||||
t.trackKey = t.trackKeyFor(item, t.trackNth)
|
||||
t.trackKey = t.trackKeyFor(item, t.idNth)
|
||||
t.trackKeyCache = make(map[int32]bool)
|
||||
t.trackBlocked = true
|
||||
t.trackSync = reloadSync
|
||||
|
||||
@@ -1660,8 +1660,8 @@ class TestCore < TestInteractive
|
||||
end
|
||||
|
||||
def test_track_nth_reload_whole_line
|
||||
# --track=.. should track by entire line across reloads
|
||||
tmux.send_keys "seq 1000 | #{FZF} --track=.. --bind 'ctrl-r:reload:seq 1000 | sort -R'", :Enter
|
||||
# --track --id-nth .. should track by entire line across reloads
|
||||
tmux.send_keys "seq 1000 | #{FZF} --track --id-nth .. --bind 'ctrl-r:reload:seq 1000 | sort -R'", :Enter
|
||||
tmux.until { |lines| assert_equal 1000, lines.match_count }
|
||||
|
||||
# Move to item 555
|
||||
@@ -1683,8 +1683,8 @@ class TestCore < TestInteractive
|
||||
end
|
||||
|
||||
def test_track_nth_reload_field
|
||||
# --track=1 should track by first field across reloads
|
||||
tmux.send_keys "printf '1 apple\\n2 banana\\n3 cherry\\n' | #{FZF} --track=1 --bind 'ctrl-r:reload:printf \"1 apricot\\n2 blueberry\\n3 cranberry\\n\"'", :Enter
|
||||
# --track --id-nth 1 should track by first field across reloads
|
||||
tmux.send_keys "printf '1 apple\\n2 banana\\n3 cherry\\n' | #{FZF} --track --id-nth 1 --bind 'ctrl-r:reload:printf \"1 apricot\\n2 blueberry\\n3 cranberry\\n\"'", :Enter
|
||||
tmux.until do |lines|
|
||||
assert_equal 3, lines.match_count
|
||||
assert_includes lines, '> 1 apple'
|
||||
@@ -1704,7 +1704,7 @@ class TestCore < TestInteractive
|
||||
|
||||
def test_track_nth_reload_no_match
|
||||
# When tracked item is not found after reload, cursor stays at current position
|
||||
tmux.send_keys "printf 'alpha\\nbeta\\ngamma\\n' | #{FZF} --track=.. --bind 'ctrl-r:reload:printf \"delta\\nepsilon\\nzeta\\n\"'", :Enter
|
||||
tmux.send_keys "printf 'alpha\\nbeta\\ngamma\\n' | #{FZF} --track --id-nth .. --bind 'ctrl-r:reload:printf \"delta\\nepsilon\\nzeta\\n\"'", :Enter
|
||||
tmux.until { |lines| assert_equal 3, lines.match_count }
|
||||
tmux.send_keys :Up
|
||||
tmux.until { |lines| assert_includes lines, '> beta' }
|
||||
@@ -1721,7 +1721,7 @@ class TestCore < TestInteractive
|
||||
|
||||
def test_track_nth_blocked_indicator
|
||||
# +T* should appear during reload and disappear when match is found
|
||||
tmux.send_keys "seq 100 | #{FZF} --track=.. --bind 'ctrl-r:reload:sleep 1; seq 100 | sort -R'", :Enter
|
||||
tmux.send_keys "seq 100 | #{FZF} --track --id-nth .. --bind 'ctrl-r:reload:sleep 1; seq 100 | sort -R'", :Enter
|
||||
tmux.until do |lines|
|
||||
assert_equal 100, lines.match_count
|
||||
assert_includes lines[-2], '+T'
|
||||
@@ -1741,7 +1741,7 @@ class TestCore < TestInteractive
|
||||
|
||||
def test_track_nth_abort_unblocks
|
||||
# Escape during track-blocked state should unblock, not quit
|
||||
tmux.send_keys "seq 100 | #{FZF} --track=.. --bind 'ctrl-r:reload:sleep 3; seq 100'", :Enter
|
||||
tmux.send_keys "seq 100 | #{FZF} --track --id-nth .. --bind 'ctrl-r:reload:sleep 3; seq 100'", :Enter
|
||||
tmux.until do |lines|
|
||||
assert_equal 100, lines.match_count
|
||||
assert_includes lines[-2], '+T'
|
||||
@@ -1763,7 +1763,7 @@ class TestCore < TestInteractive
|
||||
# With async reload, +T* should clear as soon as the match streams in,
|
||||
# even while loading is still in progress.
|
||||
# sleep 1 first so +T* is observable, then the match arrives, then more items after a delay.
|
||||
tmux.send_keys "seq 5 | #{FZF} --track=.. --bind 'ctrl-r:reload:sleep 1; echo 1; sleep 2; seq 2 10'", :Enter
|
||||
tmux.send_keys "seq 5 | #{FZF} --track --id-nth .. --bind 'ctrl-r:reload:sleep 1; echo 1; sleep 2; seq 2 10'", :Enter
|
||||
tmux.until do |lines|
|
||||
assert_equal 5, lines.match_count
|
||||
assert_includes lines, '> 1'
|
||||
@@ -1784,7 +1784,7 @@ class TestCore < TestInteractive
|
||||
def test_track_nth_reload_sync_blocks_until_complete
|
||||
# With reload-sync, +T* should stay until the entire stream is complete,
|
||||
# even though the match arrives early in the stream.
|
||||
tmux.send_keys "seq 5 | #{FZF} --track=.. --bind 'ctrl-r:reload-sync:sleep 1; echo 1; sleep 2; seq 2 10'", :Enter
|
||||
tmux.send_keys "seq 5 | #{FZF} --track --id-nth .. --bind 'ctrl-r:reload-sync:sleep 1; echo 1; sleep 2; seq 2 10'", :Enter
|
||||
tmux.until do |lines|
|
||||
assert_equal 5, lines.match_count
|
||||
assert_includes lines, '> 1'
|
||||
@@ -1810,7 +1810,7 @@ class TestCore < TestInteractive
|
||||
|
||||
def test_track_nth_toggle_track_unblocks
|
||||
# toggle-track during track-blocked state should unblock and disable tracking
|
||||
tmux.send_keys "seq 100 | #{FZF} --track=.. --bind 'ctrl-r:reload:sleep 5; seq 100' --bind 'ctrl-t:toggle-track'", :Enter
|
||||
tmux.send_keys "seq 100 | #{FZF} --track --id-nth .. --bind 'ctrl-r:reload:sleep 5; seq 100' --bind 'ctrl-t:toggle-track'", :Enter
|
||||
tmux.until do |lines|
|
||||
assert_equal 100, lines.match_count
|
||||
assert_includes lines[-2], '+T'
|
||||
@@ -1830,7 +1830,7 @@ class TestCore < TestInteractive
|
||||
def test_track_nth_reload_async_no_match
|
||||
# With async reload, when tracked item is not found, cursor stays at
|
||||
# current position after stream completes
|
||||
tmux.send_keys "printf 'alpha\\nbeta\\ngamma\\n' | #{FZF} --track=.. --bind 'ctrl-r:reload:sleep 1; printf \"delta\\nepsilon\\nzeta\\n\"'", :Enter
|
||||
tmux.send_keys "printf 'alpha\\nbeta\\ngamma\\n' | #{FZF} --track --id-nth .. --bind 'ctrl-r:reload:sleep 1; printf \"delta\\nepsilon\\nzeta\\n\"'", :Enter
|
||||
tmux.until { |lines| assert_equal 3, lines.match_count }
|
||||
tmux.send_keys :Up
|
||||
tmux.until { |lines| assert_includes lines, '> beta' }
|
||||
@@ -1846,12 +1846,12 @@ class TestCore < TestInteractive
|
||||
end
|
||||
end
|
||||
|
||||
def test_track_action_with_nth
|
||||
# track(1) action should track by first field
|
||||
tmux.send_keys "printf '1 apple\\n2 banana\\n3 cherry\\n' | #{FZF} --bind 'ctrl-t:track(1),ctrl-r:reload:printf \"1 apricot\\n2 blueberry\\n3 cranberry\\n\"'", :Enter
|
||||
def test_track_action_with_id_nth
|
||||
# track-current with --id-nth should track by specified field
|
||||
tmux.send_keys "printf '1 apple\\n2 banana\\n3 cherry\\n' | #{FZF} --id-nth 1 --bind 'ctrl-t:track-current,ctrl-r:reload:printf \"1 apricot\\n2 blueberry\\n3 cranberry\\n\"'", :Enter
|
||||
tmux.until { |lines| assert_equal 3, lines.match_count }
|
||||
|
||||
# Move to "2 banana" and activate field tracking
|
||||
# Move to "2 banana" and activate tracking
|
||||
tmux.send_keys :Up
|
||||
tmux.until { |lines| assert_includes lines, '> 2 banana' }
|
||||
tmux.send_keys 'C-t'
|
||||
@@ -1865,6 +1865,30 @@ class TestCore < TestInteractive
|
||||
end
|
||||
end
|
||||
|
||||
def test_id_nth_preserve_multi_selection
|
||||
# --id-nth with --multi should preserve selections across reload-sync
|
||||
File.write(tempname, "1 apricot\n2 blueberry\n3 cranberry\n")
|
||||
tmux.send_keys "printf '1 apple\\n2 banana\\n3 cherry\\n' | #{fzf("--multi --id-nth 1 --bind 'ctrl-r:reload-sync:cat #{tempname}'")}", :Enter
|
||||
tmux.until { |lines| assert_equal 3, lines.match_count }
|
||||
|
||||
# Select first item (1 apple) and third item (3 cherry)
|
||||
tmux.send_keys :Tab
|
||||
tmux.send_keys :Up, :Up, :Tab
|
||||
tmux.until { |lines| assert_includes lines[-2], '(2)' }
|
||||
|
||||
# Reload — selections should be preserved by id-nth key
|
||||
tmux.send_keys 'C-r'
|
||||
tmux.until do |lines|
|
||||
assert_equal 3, lines.match_count
|
||||
assert_includes lines[-2], '(2)'
|
||||
assert(lines.any? { |l| l.include?('apricot') })
|
||||
end
|
||||
|
||||
# Accept and verify the correct items were preserved
|
||||
tmux.send_keys :Enter
|
||||
assert_equal ['1 apricot', '3 cranberry'], fzf_output_lines
|
||||
end
|
||||
|
||||
def test_one_and_zero
|
||||
tmux.send_keys "seq 10 | #{FZF} --bind 'zero:preview(echo no match),one:preview(echo {} is the only match)'", :Enter
|
||||
tmux.send_keys '1'
|
||||
|
||||
Reference in New Issue
Block a user