Fix incompatibility of adaptive height and 'start:reload'

This command would cause a deadlock and make fzf crash:

  fzf --bind 'start:reload:ls' --height ~100%

Because,

1. 'start' event is handled by Terminal
2. When 'reload' is bound to 'start', fzf avoids starting the initial reader
3. Terminal waits for the initial input to find the right height when
   adaptive height is used
4. Because the initial reader is not started, Terminal never gets the
   initial list
5. No chance to trigger 'start:reload', hence deadlock

This commit fixes the above problem by extracting the reload command
bound to 'start' event and starting the initial reader with that command
instead of letting Terminal start it.

This commit also makes the environment variables available to
$FZF_DEFAULT_COMMAND.

  FZF_DEFAULT_COMMAND='echo $FZF_QUERY' fzf --query foo

Fix #3944
This commit is contained in:
Junegunn Choi 2024-07-27 10:38:08 +09:00
parent b896e0d314
commit 587df594b8
No known key found for this signature in database
GPG Key ID: 254BC280FEF9C627
6 changed files with 45 additions and 29 deletions

View File

@ -1,6 +1,14 @@
CHANGELOG CHANGELOG
========= =========
0.54.3
------
- Fixed incompatibility of adaptive height and 'start:reload'
- Environment variables are now available to `$FZF_DEFAULT_COMMAND`
```sh
FZF_DEFAULT_COMMAND='echo $FZF_QUERY' fzf --query foo
```
0.54.2 0.54.2
------ ------
- Fixed incorrect syntax highlighting of truncated multi-line entries - Fixed incorrect syntax highlighting of truncated multi-line entries

View File

@ -58,7 +58,6 @@ const (
const ( const (
EvtReadNew util.EventType = iota EvtReadNew util.EventType = iota
EvtReadFin EvtReadFin
EvtReadNone
EvtSearchNew EvtSearchNew
EvtSearchProgress EvtSearchProgress
EvtSearchFin EvtSearchFin

View File

@ -146,8 +146,23 @@ func Run(opts *Options) (int, error) {
// Process executor // Process executor
executor := util.NewExecutor(opts.WithShell) executor := util.NewExecutor(opts.WithShell)
// Terminal I/O
var terminal *Terminal
var err error
var initialEnv []string
initialReload := opts.extractReloadOnStart()
if opts.Filter == nil {
terminal, err = NewTerminal(opts, eventBox, executor)
if err != nil {
return ExitError, err
}
initialEnv = terminal.environ()
var temps []string
initialReload, temps = terminal.replacePlaceholderInInitialCommand(initialReload)
defer removeFiles(temps)
}
// Reader // Reader
reloadOnStart := opts.reloadOnStart()
streamingFilter := opts.Filter != nil && !sort && !opts.Tac && !opts.Sync streamingFilter := opts.Filter != nil && !sort && !opts.Tac && !opts.Sync
var reader *Reader var reader *Reader
if !streamingFilter { if !streamingFilter {
@ -155,12 +170,7 @@ func Run(opts *Options) (int, error) {
return chunkList.Push(data) return chunkList.Push(data)
}, eventBox, executor, opts.ReadZero, opts.Filter == nil) }, eventBox, executor, opts.ReadZero, opts.Filter == nil)
if reloadOnStart { go reader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip, initialReload, initialEnv)
// reload or reload-sync action is bound to 'start' event, no need to start the reader
eventBox.Set(EvtReadNone, nil)
} else {
go reader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip)
}
} }
// Matcher // Matcher
@ -212,7 +222,7 @@ func Run(opts *Options) (int, error) {
} }
return false return false
}, eventBox, executor, opts.ReadZero, false) }, eventBox, executor, opts.ReadZero, false)
reader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip) reader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip, initialReload, initialEnv)
} else { } else {
eventBox.Unwatch(EvtReadNew) eventBox.Unwatch(EvtReadNew)
eventBox.WaitFor(EvtReadFin) eventBox.WaitFor(EvtReadFin)
@ -234,8 +244,7 @@ func Run(opts *Options) (int, error) {
} }
// Synchronous search // Synchronous search
sync := opts.Sync && !reloadOnStart if opts.Sync {
if sync {
eventBox.Unwatch(EvtReadNew) eventBox.Unwatch(EvtReadNew)
eventBox.WaitFor(EvtReadFin) eventBox.WaitFor(EvtReadFin)
} }
@ -244,18 +253,14 @@ func Run(opts *Options) (int, error) {
go matcher.Loop() go matcher.Loop()
defer matcher.Stop() defer matcher.Stop()
// Terminal I/O // Handling adaptive height
terminal, err := NewTerminal(opts, eventBox, executor)
if err != nil {
return ExitError, err
}
maxFit := 0 // Maximum number of items that can fit on screen maxFit := 0 // Maximum number of items that can fit on screen
padHeight := 0 padHeight := 0
heightUnknown := opts.Height.auto heightUnknown := opts.Height.auto
if heightUnknown { if heightUnknown {
maxFit, padHeight = terminal.MaxFitAndPad() maxFit, padHeight = terminal.MaxFitAndPad()
} }
deferred := opts.Select1 || opts.Exit0 || sync deferred := opts.Select1 || opts.Exit0 || opts.Sync
go terminal.Loop() go terminal.Loop()
if !deferred && !heightUnknown { if !deferred && !heightUnknown {
// Start right away // Start right away
@ -322,9 +327,6 @@ func Run(opts *Options) (int, error) {
err = quitSignal.err err = quitSignal.err
stop = true stop = true
return return
case EvtReadNone:
reading = false
terminal.UpdateCount(0, false, nil)
case EvtReadNew, EvtReadFin: case EvtReadNew, EvtReadFin:
if evt == EvtReadFin && nextCommand != nil { if evt == EvtReadFin && nextCommand != nil {
restart(*nextCommand, nextEnviron) restart(*nextCommand, nextEnviron)

View File

@ -2964,17 +2964,18 @@ func ParseOptions(useDefaults bool, args []string) (*Options, error) {
return opts, nil return opts, nil
} }
func (opts *Options) reloadOnStart() bool { func (opts *Options) extractReloadOnStart() string {
// Not compatible with --filter cmd := ""
if opts.Filter != nil {
return false
}
if actions, prs := opts.Keymap[tui.Start.AsEvent()]; prs { if actions, prs := opts.Keymap[tui.Start.AsEvent()]; prs {
filtered := []*action{}
for _, action := range actions { for _, action := range actions {
if action.t == actReload || action.t == actReloadSync { if action.t == actReload || action.t == actReloadSync {
return true cmd = action.a
} else {
filtered = append(filtered, action)
} }
} }
opts.Keymap[tui.Start.AsEvent()] = filtered
} }
return false return cmd
} }

View File

@ -111,18 +111,20 @@ func (r *Reader) readChannel(inputChan chan string) bool {
} }
// ReadSource reads data from the default command or from standard input // ReadSource reads data from the default command or from standard input
func (r *Reader) ReadSource(inputChan chan string, root string, opts walkerOpts, ignores []string) { func (r *Reader) ReadSource(inputChan chan string, root string, opts walkerOpts, ignores []string, initCmd string, initEnv []string) {
r.startEventPoller() r.startEventPoller()
var success bool var success bool
if inputChan != nil { if inputChan != nil {
success = r.readChannel(inputChan) success = r.readChannel(inputChan)
} else if len(initCmd) > 0 {
success = r.readFromCommand(initCmd, initEnv)
} else if util.IsTty(os.Stdin) { } else if util.IsTty(os.Stdin) {
cmd := os.Getenv("FZF_DEFAULT_COMMAND") cmd := os.Getenv("FZF_DEFAULT_COMMAND")
if len(cmd) == 0 { if len(cmd) == 0 {
success = r.readFiles(root, opts, ignores) success = r.readFiles(root, opts, ignores)
} else { } else {
// We can't export FZF_* environment variables to the default command // We can't export FZF_* environment variables to the default command
success = r.readFromCommand(cmd, nil) success = r.readFromCommand(cmd, initEnv)
} }
} else { } else {
success = r.readFromStdin() success = r.readFromStdin()

View File

@ -2946,6 +2946,10 @@ type replacePlaceholderParams struct {
executor *util.Executor executor *util.Executor
} }
func (t *Terminal) replacePlaceholderInInitialCommand(template string) (string, []string) {
return t.replacePlaceholder(template, false, string(t.input), []*Item{nil, nil})
}
func (t *Terminal) replacePlaceholder(template string, forcePlus bool, input string, list []*Item) (string, []string) { func (t *Terminal) replacePlaceholder(template string, forcePlus bool, input string, list []*Item) (string, []string) {
return replacePlaceholder(replacePlaceholderParams{ return replacePlaceholder(replacePlaceholderParams{
template: template, template: template,