Fzf does not select path
Checklist
- [x] I have read through the manual page (
man fzf) - [x] I have searched through the existing issues
- [x] For bug reports, I have checked if the bug is reproducible in the latest version of fzf
Output of fzf --version
0.60.3 (brew)
OS
- [ ] Linux
- [x] macOS
- [ ] Windows
- [ ] Etc.
Shell
- [ ] bash
- [x] zsh
- [ ] fish
Problem / Steps to reproduce
As you can see from this recording, when I invoke the ** shortcut, and I select a path, this path is not copied across
https://github.com/user-attachments/assets/f16fb34b-52c2-4bfa-9dc5-a71c799f3210
This happens in 3 different terminal emulators using zsh. Do you know how to solve this issue?
From the video, it appears that everything works as expected until you make a selection. Since nothing happens, it seems you cancel the operation.
If the observation is correct, I would assume that something is interfering with the
__fzf_generic_path_completion function.
https://github.com/junegunn/fzf/blob/26bcd0c90d4c77938d2011f994d943e531322504/shell/completion.zsh#L166-L178
Is the bug reproducible in a minimal zsh environment?
command env -i "HOME=$HOME" "USER=$USER" "PATH=$PATH" "TERM=$TERM" zsh -f
source <(fzf --zsh)
cd ~/**<PRESS TAB>
If the selection works as expected, that suggests something in your shell setup is interfering with the correct functioning of the fzf completion. One way to troubleshoot is to gradually remove parts of your shell setup or make your setup public so we can identify where things go wrong.
Another way is to enable execution tracing by running:
source <(fzf --zsh)
# Enable execution tracing. For more details, refer to 'man zshbuiltins'
typeset -ft fzf-completion
# Verbose Execution trace prompt (default: '+%N:%i> '). For more details, refer to 'man zshparam/zshmisc'
PS4=$'\n%B%F{0}+ %D{%T:%3.} %2N:%I%f%b '
cd ~/**<PRESS TAB>
Please copy and paste the output here, which should look something like this:
cd ~/**
+ 16:34:25:275 fzf-completion:478 local tokens prefix trigger tail matches lbuf d_cmds cursor_pos cmd_word
+ 16:34:25:275 fzf-completion:479 setopt localoptions noshwordsplit noksh_arrays noposixbuiltins
+ 16:34:25:275 fzf-completion:483 tokens=( cd '~/**' )
+ 16:34:25:275 fzf-completion:484 [ 2 -lt 1 ']'
+ 16:34:25:275 fzf-completion:490 trigger='**'
...
Also, do you know if it was working previously and broke recently?
Thank you @LangLangBart for helping out on this. I have pinned down the issue to be related to the following lines in my .zshrc file
_fzf_compgen_dir() {
fd --type=d --hidden --exclude .git . "$1"
}
I am using fd to generate the list for directory completion. Do you see any immediate problems with this? I understand now this may not be necessarily an fzf problem, but rather a problem at the interface, but any hint would help
I have pinned down the issue to be related to the following lines in my .zshrc file
The _fzf_compgen_dir function gets evaluated and piped into fzf. The list with paths is shown correctly in your demo video, this suggests that fd works as expected, and the issue may lie somewhere else.
https://github.com/junegunn/fzf/blob/26bcd0c90d4c77938d2011f994d943e531322504/shell/completion.zsh#L156-L158
Is this issue reproducible if you execute the following commands?
command env -i "HOME=$HOME" "USER=$USER" "PATH=$PATH" "TERM=$TERM" zsh -f
_fzf_compgen_dir() {
fd --type=d --hidden --exclude .git . "$1"
}
source <(fzf --zsh)
cd ~/**<PRESS TAB>
Hi @LangLangBart, yes, the issue is reproducible with the latest commands you give.
Hi @LangLangBart, yes, the issue is reproducible with the latest commands you give.
Thanks for confirming it, but I am unable to reproduce it on my end.
- zsh version is 5.9
- fd version is 10.2.0
- fzf version 0.60.3
Can you also enable execution tracing for the __fzf_generic_path_completion and share the output ?
command env -i "HOME=$HOME" "USER=$USER" "PATH=$PATH" "TERM=$TERM" zsh -f
_fzf_compgen_dir() {
fd --type=d --hidden --exclude .git . "$1"
}
source <(fzf --zsh)
typeset -ft __fzf_generic_path_completion
cd ~/**<PRESS TAB>
Hi @LangLangBart I have the same versions of zsh, fd, and fzf. Here is the output you request (the last 3 lines are produced after I select the path /Users/daniele/.zsh/pure/
cd ~/**+__fzf_generic_path_completion:1> local base lbuf compgen fzf_opts suffix tail dir leftover matches
+__fzf_generic_path_completion:2> base='~/'
+__fzf_generic_path_completion:3> lbuf='cd '
+__fzf_generic_path_completion:4> compgen=_fzf_compgen_dir
+__fzf_generic_path_completion:5> fzf_opts=''
+__fzf_generic_path_completion:6> suffix=/
+__fzf_generic_path_completion:7> tail=''
+__fzf_generic_path_completion:9> setopt localoptions nonomatch
+__fzf_generic_path_completion:10> [[ '~/' = *\$\(* ]]
+__fzf_generic_path_completion:10> [[ '~/' = *\<\(* ]]
+__fzf_generic_path_completion:10> [[ '~/' = *\>\(* ]]
+__fzf_generic_path_completion:10> [[ '~/' = *:=* ]]
+__fzf_generic_path_completion:10> [[ '~/' = *`* ]]
+__fzf_generic_path_completion:13> eval 'base=~/'
+(eval):1> base=/Users/daniele/
+__fzf_generic_path_completion:14> [[ /Users/daniele/ = */* ]]
+__fzf_generic_path_completion:14> dir=/Users/daniele/
+__fzf_generic_path_completion:15> [ 1 ']'
+__fzf_generic_path_completion:16> [[ -z /Users/daniele/ || -d /Users/daniele/ ]]
+__fzf_generic_path_completion:17> leftover=''
+__fzf_generic_path_completion:18> leftover=''
+__fzf_generic_path_completion:19> [ -z /Users/daniele/ ']'
+__fzf_generic_path_completion:20> [ /Users/daniele/ '!=' / ']'
+__fzf_generic_path_completion:20> dir=/Users/daniele
+__fzf_generic_path_completion:21> matches=+__fzf_generic_path_completion:22> export FZF_DEFAULT_OPTS
+__fzf_generic_path_completion:23> FZF_DEFAULT_OPTS=+__fzf_generic_path_completion:23> __fzf_defaults '--reverse --scheme=path' ''
+__fzf_defaults:3> echo -E '--height 40% --min-height 20+ --bind=ctrl-z:ignore --reverse --scheme=path'
+__fzf_defaults:4> cat ''
+__fzf_defaults:5> echo -E ' '
+__fzf_generic_path_completion:23> FZF_DEFAULT_OPTS=$'--height 40% --min-height 20+ --bind=ctrl-z:ignore --reverse --scheme=path\n '
+__fzf_generic_path_completion:24> unset FZF_DEFAULT_COMMAND FZF_DEFAULT_OPTS_FILE
+__fzf_generic_path_completion:36> read -r item
+__fzf_generic_path_completion:25> declare -f _fzf_compgen_dir
+__fzf_generic_path_completion:26> __fzf_comprun cd -q ''
+__fzf_generic_path_completion:26> printf %q /Users/daniele
+__fzf_comprun:1> [[ "$(type _fzf_comprun 2>&1)" -regex-match function+__fzf_comprun:1> type _fzf_comprun
+__fzf_generic_path_completion:26> eval '_fzf_compgen_dir /Users/daniele'
+(eval):1> _fzf_compgen_dir /Users/daniele
+__fzf_comprun:1> [[ "$(type _fzf_comprun 2>&1)" -regex-match function ]]
+__fzf_comprun:3> [ -n '' ']'
+__fzf_comprun:11> shift
+_fzf_compgen_dir:1> fd '--type=d' --hidden --exclude .git . /Users/daniele
+__fzf_comprun:12> fzf -q ''
+__fzf_generic_path_completion:37> item=/Users/daniele/.zsh/pure/
+__fzf_generic_path_completion:38> echo -n -E '/Users/daniele/.zsh/pure/ '
+__fzf_generic_path_completion:36> read -r item
Here is the output you request
I get the same execution trace as you provided, except my while loop completes successfully, and I
don't understand why it doesn't for you.
...
+__fzf_generic_path_completion:37> item=/Users/paria/.dendron/
+__fzf_generic_path_completion:38> echo -n -E '/Users/paria/.dendron/ '
+__fzf_generic_path_completion:36> read -r item
+__fzf_generic_path_completion:21> matches='/Users/paria/.dendron/ '
+__fzf_generic_path_completion:41> matches=/Users/paria/.dendron/
+__fzf_generic_path_completion:42> [ -n /Users/paria/.dendron/ ']'
+__fzf_generic_path_completion:43> LBUFFER='cd /Users/paria/.dendron/'
+__fzf_generic_path_completion:45> zle reset-prompt
+__fzf_generic_path_completion:46> break
@junegunn, are you able to reproduce the issue reported by @danieleavitabile on your end?
Does the issue still occur when you modify _fzf_compgen_dir to use a different command, e.g. ls?
_fzf_compgen_dir() {
ls "$1"
}
@LangLangBart Thanks for looking into this. With the steps you provided, I'm seeing a long delay (2 or 3 seconds most of the time, sometimes longer like 5 seconds) after read -r item line.
+__fzf_generic_path_completion:37> item=/Users/jg/.tmux/
+__fzf_generic_path_completion:38> echo -n -E '/Users/jg/.tmux/ '
+__fzf_generic_path_completion:36> read -r item
But it does finish in the end.
+__fzf_generic_path_completion:21> matches='/Users/jg/.tmux/ '
+__fzf_generic_path_completion:41> matches=/Users/jg/.tmux/
+__fzf_generic_path_completion:42> [ -n /Users/jg/.tmux/ ']'
+__fzf_generic_path_completion:43> LBUFFER='cd /Users/jg/.tmux/'
+__fzf_generic_path_completion:45> zle reset-prompt
+__fzf_generic_path_completion:46> break
There is no delay if _fzf_compgen_dir is undefined and fzf is using its built-in walker. So maybe fd is not terminating in a timely manner? Need more investigation.
So maybe
fdis not terminating in a timely manner?
Thanks for testing it. Very likely.
It's just odd that it stops instantly for me and returns my terminal prompt.
time (fd --type=d --hidden --no-ignore . ~ | fzf --bind 'focus:accept')
/Users/paria/.dendron/
user=0.11s system=0.30s cpu=423% total=0.095
This adds a delay
time (sleep 5 | fzf --bind 'start:reload:seq 5' --bind 'focus:accept')
1
user=0.01s system=0.02s cpu=0% total=5.007
Kill 'sleep' process
time (sleep 5 | fzf --bind 'start:reload:seq 5; lsof -c sleep -t | xargs kill' --bind 'focus:accept')
1
user=0.02s system=0.03s cpu=83% total=0.057
It's just odd that it stops instantly for me and returns my terminal prompt.
I can explain. When fzf terminates, the standard output of fd is closed, thus fd can no longer write to it and gets "Broken pipe" which effectively causes it to stop.
A workaround is to use a command substitution like so:
fzf --bind 'start:reload:seq 5' --bind 'focus:accept' < <(sleep 5)
However, sleep 5 keeps running in the background.
So anyway, because of the "broken pipe" mechanism mentioned above, fd should normally stop as soon as it generates another output line.
A couple more observations.
fd can keep running and the script hangs indefinitely if macOS jumps in and asks for directory permissions.
No progress was made until I finally clicked "Approve (허용)".
So maybe fd is not terminating in a timely manner?
I was trying to see if that's really the case, so I ran while true; sleep 1; ps -ef | grep fd; done in another window while doing cd ~/**<tab>. And I noticed during the delay, the CPU time of fd doesn't increase.
501 55838 55835 0 2:28PM ttys002 0:02.49 fd --type=d --hidden --exclude .git . /Users/jg
501 55838 55835 0 2:28PM ttys002 0:06.19 fd --type=d --hidden --exclude .git . /Users/jg
501 55838 55835 0 2:28PM ttys002 0:07.11 fd --type=d --hidden --exclude .git . /Users/jg
501 55838 55835 0 2:28PM ttys002 0:07.57 fd --type=d --hidden --exclude .git . /Users/jg
501 55838 55835 0 2:28PM ttys002 0:07.59 fd --type=d --hidden --exclude .git . /Users/jg
501 55838 55835 0 2:28PM ttys002 0:07.59 fd --type=d --hidden --exclude .git . /Users/jg
501 55838 55835 0 2:28PM ttys002 0:07.59 fd --type=d --hidden --exclude .git . /Users/jg
501 55838 55835 0 2:28PM ttys002 0:07.59 fd --type=d --hidden --exclude .git . /Users/jg
501 55838 55835 0 2:28PM ttys002 0:07.59 fd --type=d --hidden --exclude .git . /Users/jg
501 55838 55835 0 2:28PM ttys002 0:07.59 fd --type=d --hidden --exclude .git . /Users/jg
501 55838 55835 0 2:28PM ttys002 0:07.59 fd --type=d --hidden --exclude .git . /Users/jg
501 55838 55835 0 2:28PM ttys002 0:07.59 fd --type=d --hidden --exclude .git . /Users/jg
501 55838 55835 0 2:28PM ttys002 0:07.59 fd --type=d --hidden --exclude .git . /Users/jg
501 55838 55835 0 2:28PM ttys002 0:07.59 fd --type=d --hidden --exclude .git . /Users/jg
Okay, I realized that I was running fd 9.0.0, and the delay seems to have gotten smaller after I upgraded it to 10.2.0.
EDIT: Hmm, nope. I'm still observing long delays (10 seconds) sometimes. But this just seems like an issue of fd, and there's not much we can do about it.
I have also gone back to this and I also see this is a long, very long delay (orders of dozens of seconds and more) with fd 10.2.0 and fzf 0.60.3
But this just seems like an issue of fd, and there's not much we can do about it.
Related comment: https://github.com/sharkdp/fd/issues/1479#issuecomment-1898873279
@junegunn
So you might close the issue as fzf can't do anything other than possibly giving a warning?
https://github.com/junegunn/fzf/#customizing-completion-source-for-paths-and-directories
@danieleavitabile
The built-in directory walker[^1] by fzf is super fast. If you remove _fzf_compgen_dir from your
setup, it should solve the lag issue.
[^1]: fzf/CHANGELOG.md 0.47.0
So you might close the issue as
fzfcan't do anything other than possibly giving a warning?
I was wondering if there was a way to work around the issue, like using command substitution like I mentioned above.
However, interestingly, it doesn't help in this context, possibly due to the use of eval.
# Stops immediately
fzf --bind 'start:reload:seq 5' --bind 'focus:accept' < <(sleep 5)
# Takes 5 seconds to stop
eval "fzf --bind 'start:reload:seq 5' --bind 'focus:accept'" < <(sleep 5)
(I remember fish had a similar problem with eval https://github.com/junegunn/fzf/commit/0a10d14e190eac2d3aa3d88f3b45e84d3f4431be)
I was wondering if there was a way to work around the issue, like using command substitution like I mentioned above.
What if you place the eval into the process substitution ?
--- a/shell/completion.zsh
+++ b/shell/completion.zsh
@@ -155,5 +155,5 @@ __fzf_generic_path_completion() {
unset FZF_DEFAULT_COMMAND FZF_DEFAULT_OPTS_FILE
if declare -f "$compgen" > /dev/null; then
- eval "$compgen $(printf %q "$dir")" | __fzf_comprun "$cmd_word" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover"
+ __fzf_comprun "$cmd_word" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" < <(eval "$compgen $(printf %q "$dir")")
else
if [[ $compgen =~ dir ]]; then
Well, that wasn't a great example. Anyway, I tried that version and it didn't help. You can easily reproduce the problem with this:
command env -i "HOME=$HOME" "USER=$USER" "PATH=$PATH" "TERM=$TERM" zsh -f
_fzf_compgen_dir() {
echo 'hello'
sleep 10
echo 'world'
}
source shell/completion.zsh
typeset -ft __fzf_generic_path_completion
# Select 'hello'
cd **<TAB>
Interestingly,
# fzf finishes immediately once selection is made
fzf < <(_fzf_compgen_dir)
# _fzf waits for _fzf_compgen_dir to finish
_fzf() {
fzf
}
_fzf < <(_fzf_compgen_dir)
You can easily reproduce the problem with this
That was helpful.
Possible solution[^1] ?
- Create a process group with
(...) - Run
kill -PIPE 0afterfzffinishes it, to terminate the pipeline processes in the group
--- a/shell/completion.zsh
+++ b/shell/completion.zsh
@@ -155,5 +155,10 @@ __fzf_generic_path_completion() {
unset FZF_DEFAULT_COMMAND FZF_DEFAULT_OPTS_FILE
if declare -f "$compgen" > /dev/null; then
- eval "$compgen $(printf %q "$dir")" | __fzf_comprun "$cmd_word" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover"
+ (
+ eval "$compgen $(printf %q "$dir")" | {
+ __fzf_comprun "$cmd_word" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover"
+ kill -PIPE 0
+ }
+ )
else
if [[ $compgen =~ dir ]]; then
[^1]: Re: less with subprocess
@LangLangBart Wow, thanks, the patch works great with both the "sleep" example and the original fd command we discussed. Are there any concerns with it?
Are there any concerns with it?
It worked for me as well, and I didn't encounter any issues with it. I was just hesitant because I've never used it before and only discovered it in the zsh mailing list while searching for "broken pipe". We could ping the author, @vinc17fr ^1, and see if he knows of any concerns for our use case. Otherwise, we might consider adding it.