Add reload-sync action

Close #2816
This commit is contained in:
Junegunn Choi 2022-12-29 20:03:51 +09:00
parent 14775aa975
commit 6c37177cf5
No known key found for this signature in database
GPG Key ID: 254BC280FEF9C627
6 changed files with 124 additions and 66 deletions

View File

@ -12,63 +12,74 @@ CHANGELOG
# Send actions to the server
curl -XPOST localhost:6266 -d 'reload(seq 100)+change-prompt(hundred> )'
```
- Added `pos(...)` action to move the cursor to the numeric position
- `first` and `last` are equivalent to `pos(1)` and `pos(-1)` respectively
```sh
# Put the cursor on the 10th item
seq 100 | fzf --sync --bind 'start:pos(10)'
- New event
- Added `load` event that is triggered when the input stream is complete
and the initial processing of the list is complete.
```sh
# Change the prompt to "loaded" when the input stream is complete
(seq 10; sleep 1; seq 11 20) | fzf --prompt 'Loading> ' --bind 'load:change-prompt:Loaded> '
# Put the cursor on the 10th to last item
seq 100 | fzf --sync --bind 'start:pos(-10)'
```
- Added `load` event that is triggered when the input stream is complete and
the initial processing of the list is complete.
```sh
# Change the prompt to "loaded" when the input stream is complete
(seq 10; sleep 1; seq 11 20) | fzf --prompt 'Loading> ' --bind 'load:change-prompt:Loaded> '
# You can use it instead of 'start' event without `--sync` if asynchronous
# trigger is not an issue.
(seq 10; sleep 1; seq 11 20) | fzf --bind 'load:last'
```
- New actions
- Added `pos(...)` action to move the cursor to the numeric position
- `first` and `last` are equivalent to `pos(1)` and `pos(-1)` respectively
```sh
# Put the cursor on the 10th item
seq 100 | fzf --sync --bind 'start:pos(10)'
# You can use it instead of 'start' event without `--sync` if asynchronous
# trigger is not an issue.
(seq 10; sleep 1; seq 11 20) | fzf --bind 'load:last'
```
- Added `next-selected` and `prev-selected` actions to move between selected
items
```sh
# `next-selected` will move the pointer to the next selected item below the current line
# `prev-selected` will move the pointer to the previous selected item above the current line
seq 10 | fzf --multi --bind ctrl-n:next-selected,ctrl-p:prev-selected
# Put the cursor on the 10th to last item
seq 100 | fzf --sync --bind 'start:pos(-10)'
```
- Added `reload-sync(...)` action which replaces the current list only after
the reload process is complete. This is useful when the command takes
a while to produce the initial output and you don't want fzf to run against
an empty list while the command is running.
```sh
# You can still filter and select entries from the initial list for 3 seconds
seq 100 | fzf --bind 'load:reload-sync(sleep 3; seq 1000)+unbind(load)'
```
- Added `next-selected` and `prev-selected` actions to move between selected
items
```sh
# `next-selected` will move the pointer to the next selected item below the current line
# `prev-selected` will move the pointer to the previous selected item above the current line
seq 10 | fzf --multi --bind ctrl-n:next-selected,ctrl-p:prev-selected
# Both actions respect --layout option
seq 10 | fzf --multi --bind ctrl-n:next-selected,ctrl-p:prev-selected --layout reverse
```
- Added `change-query(...)` action that simply changes the query string to the
given static string. This can be useful when used with `--listen`.
```sh
curl localhost:6266 -d "change-query:$(date)"
```
- Added `transform-query(...)` action for transforming the query string using
an external command
```sh
# Press space to convert the query to uppercase letters
fzf --bind 'space:transform-query(tr [:lower:] [:upper:] <<< {q})'
# Both actions respect --layout option
seq 10 | fzf --multi --bind ctrl-n:next-selected,ctrl-p:prev-selected --layout reverse
```
- Added `change-query(...)` action that simply changes the query string to the
given static string. This can be useful when used with `--listen`.
```sh
curl localhost:6266 -d "change-query:$(date)"
```
- Added `transform-query(...)` action for transforming the query string using
an external command
```sh
# Press space to convert the query to uppercase letters
fzf --bind 'space:transform-query(tr [:lower:] [:upper:] <<< {q})'
# Bind it to 'change' event for automatic conversion
fzf --bind 'change:transform-query(tr [:lower:] [:upper:] <<< {q})'
# Bind it to 'change' event for automatic conversion
fzf --bind 'change:transform-query(tr [:lower:] [:upper:] <<< {q})'
# Can only type numbers
fzf --bind 'change:transform-query(sed 's/[^0-9]//g' <<< {q})'
```
- `put` action can optionally take an argument string
```sh
# a will put 'alpha' on the prompt, ctrl-b will put 'bravo'
fzf --bind 'a:put+put(lpha),ctrl-b:put(bravo)'
```
- `double-click` will behave the same as `enter` unless otherwise specified,
so you don't have to repeat the same action twice in `--bind` in most cases.
```sh
# No need to bind 'double-click' to the same action
fzf --bind 'enter:execute:less {}' # --bind 'double-click:execute:less {}'
```
# Can only type numbers
fzf --bind 'change:transform-query(sed 's/[^0-9]//g' <<< {q})'
```
- Improvements
- `put` action can optionally take an argument string
```sh
# a will put 'alpha' on the prompt, ctrl-b will put 'bravo'
fzf --bind 'a:put+put(lpha),ctrl-b:put(bravo)'
```
- `double-click` will behave the same as `enter` unless otherwise specified,
so you don't have to repeat the same action twice in `--bind` in most cases.
```sh
# No need to bind 'double-click' to the same action
fzf --bind 'enter:execute:less {}' # --bind 'double-click:execute:less {}'
```
- Added color name `preview-label` for `--preview-label` (defaults to `label`
for `--border-label`)
- Minor bug fixes and improvements

View File

@ -1019,6 +1019,7 @@ A key or an event can be bound to one or more of the following actions.
\fBrefresh-preview\fR
\fBrebind(...)\fR (rebind bindings after \fBunbind\fR)
\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)
\fBselect\fR
\fBselect-all\fR (select all matches)
@ -1122,6 +1123,16 @@ e.g.
fzf --bind "change:reload:$RG_PREFIX {q} || true" \\
--ansi --disabled --query "$INITIAL_QUERY"\fR
\fBreload-sync(...)\fR is a synchronous version of \fBreload\fR that replaces
the list only when the command is complete. This is useful when the command
takes a while to produce the initial output and you don't want fzf to run
against an empty list while the command is running.
e.g.
\fB# You can still filter and select entries from the initial list for 3 seconds
seq 100 | fzf --bind 'load:reload-sync(sleep 3; seq 1000)+unbind(load)'\fR
.SS PREVIEW BINDING
With \fBpreview(...)\fR action, you can specify multiple different preview

View File

@ -213,15 +213,6 @@ func Run(opts *Options, version string, revision string) {
clearSelection := util.Once(false)
ticks := 0
var nextCommand *string
restart := func(command string) {
reading = true
clearCache = util.Once(true)
clearSelection = util.Once(true)
chunkList.Clear()
itemIndex = 0
header = make([]string, 0, opts.HeaderLines)
go reader.restart(command)
}
eventBox.Watch(EvtReadNew)
total := 0
query := []rune{}
@ -236,6 +227,25 @@ func Run(opts *Options, version string, revision string) {
terminal.startChan <- fitpad{-1, -1}
}
}
useSnapshot := false
var snapshot []*Chunk
var prevSnapshot []*Chunk
var count int
restart := func(command string) {
reading = true
clearCache = util.Once(true)
clearSelection = util.Once(true)
// We should not update snapshot if reload is triggered again while
// the previous reload is in progress
if useSnapshot && prevSnapshot != nil {
snapshot, count = chunkList.Snapshot()
}
chunkList.Clear()
itemIndex = 0
header = make([]string, 0, opts.HeaderLines)
go reader.restart(command)
}
for {
delay := true
ticks++
@ -267,7 +277,13 @@ func Run(opts *Options, version string, revision string) {
} else {
reading = reading && evt == EvtReadNew
}
snapshot, count := chunkList.Snapshot()
if useSnapshot && evt == EvtReadFin {
useSnapshot = false
prevSnapshot = nil
}
if !useSnapshot {
snapshot, count = chunkList.Snapshot()
}
total = count
terminal.UpdateCount(total, !reading, value.(*string))
if opts.Sync {
@ -286,6 +302,9 @@ func Run(opts *Options, version string, revision string) {
case searchRequest:
sort = val.sort
command = val.command
if command != nil {
useSnapshot = val.sync
}
}
if command != nil {
if reading {
@ -296,7 +315,9 @@ func Run(opts *Options, version string, revision string) {
}
break
}
snapshot, _ := chunkList.Snapshot()
if !useSnapshot {
snapshot, _ = chunkList.Snapshot()
}
reset := clearCache()
matcher.Reset(snapshot, input(reset), true, !reading, sort, reset)
delay = false

View File

@ -892,7 +892,7 @@ const (
func init() {
executeRegexp = regexp.MustCompile(
`(?si)[:+](execute(?:-multi|-silent)?|reload|preview|change-query|change-prompt|change-preview-window|change-preview|(?:re|un)bind|pos|put|transform-query)`)
`(?si)[:+](execute(?:-multi|-silent)?|reload(?:-sync)?|preview|change-query|change-prompt|change-preview-window|change-preview|(?:re|un)bind|pos|put|transform-query)`)
splitRegexp = regexp.MustCompile("[,:]+")
actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+")
}
@ -1185,6 +1185,8 @@ func isExecuteAction(str string) actionType {
switch prefix {
case "reload":
return actReload
case "reload-sync":
return actReloadSync
case "unbind":
return actUnbind
case "rebind":

View File

@ -335,6 +335,7 @@ const (
actFirst
actLast
actReload
actReloadSync
actDisableSearch
actEnableSearch
actSelect
@ -353,6 +354,7 @@ type placeholderFlags struct {
type searchRequest struct {
sort bool
sync bool
command *string
}
@ -2540,6 +2542,7 @@ func (t *Terminal) Loop() {
}()
for looping {
var newCommand *string
var reloadSync bool
changed := false
beof := false
queryChanged := false
@ -3030,7 +3033,7 @@ func (t *Terminal) Loop() {
}
}
}
case actReload:
case actReload, actReloadSync:
t.failed = nil
valid, list := t.buildPlusList(a.a, false)
@ -3044,6 +3047,7 @@ func (t *Terminal) Loop() {
if valid {
command := t.replacePlaceholder(a.a, false, string(t.input), list)
newCommand = &command
reloadSync = a.t == actReloadSync
t.reading = true
}
case actUnbind:
@ -3173,7 +3177,7 @@ func (t *Terminal) Loop() {
t.mutex.Unlock() // Must be unlocked before touching reqBox
if changed || newCommand != nil {
t.eventBox.Set(EvtSearchNew, searchRequest{sort: t.sort, command: newCommand})
t.eventBox.Set(EvtSearchNew, searchRequest{sort: t.sort, sync: reloadSync, command: newCommand})
}
for _, event := range events {
t.reqBox.Set(event, nil)

View File

@ -2161,6 +2161,15 @@ class TestGoFZF < TestBase
tmux.until { |lines| assert_includes lines[1], '4' }
end
def test_reload_sync
tmux.send_keys "seq 100 | #{FZF} --bind 'load:reload-sync(sleep 1; seq 1000)+unbind(load)'", :Enter
tmux.until { |lines| assert_equal 100, lines.item_count }
tmux.send_keys '00'
tmux.until { |lines| assert_equal 1, lines.match_count }
# After 1 second
tmux.until { |lines| assert_equal 10, lines.match_count }
end
def test_scroll_off
tmux.send_keys "seq 1000 | #{FZF} --scroll-off=3 --bind l:last", :Enter
tmux.until { |lines| assert_equal 1000, lines.item_count }