3

I have the following function in my .zshrc

function foo() {
    filename="$(fzf)"
}
zle -N foo
bindkey '^X' foo

now if I call foo by typing it, it works as expected, but if I call it via the key-binding with ^X, the fzf buffer is empty.

Can anyone reproduce this? How can I fix this?

1 Answer 1

3

If the point is to insert the output of fzf at the current cursor position, properly quoted, it should be something like:

insert-quoted-fzf-output() {
  local output
  output=$(fzf</dev/tty) && LBUFFER+=${(q-)output}
}

zle -N insert-quoted-fzf-output
bindkey '^X' insert-quoted-fzf-output

That is you need to insert that output at the end of $LBUFFER (the part of the editing buffer left of the cursor), quoted (here using the q- parameter expansion flag to minimise the amount of quoting), and you need fzf to take its input from the terminal. You probably want the widget to return a non-zero exit status if fzf fails.

Your specific problem about empty fzf buffer here was about the missing </dev/tty redirection.

You'll notice the same behaviour of fzf if you run it as fzf < /dev/null.

If you run info zsh widgets (assuming you have the zsh documentation available in info format; on some systems, you may need to install a zsh-doc package), you'll see (emphasis mine):

18.5 User-Defined Widgets

User-defined widgets, being implemented as shell functions, can execute any normal shell command. They can also run other widgets (whether built-in or user-defined) using the zle builtin command. The standard input of the function is redirected from /dev/null to prevent external commands from unintentionally blocking ZLE by reading from the terminal, but read -k or read -q can be used to read characters.

When its stdin is not a terminal, fzf reads it to construct the list of choices to offer to its user. /dev/null is a special device where any attempt to read anything returns with nothing, which explains why you get an empty buffer. Restoring a terminal there makes it switch back to the mode where instead of reading stdin for the list of choices, it reads the output of a $FZF_DEFAULT_COMMAND command (some find command by default).

Another option would be to actually run that find command by yourself. If you're on a GNU system, you could even improve on it to add a bit more of reliability and efficiency:

output=$(
  LC_ALL=C find -L . -name . -o '(' \
    -name '.*' -o \
    -fstype sysfs -o \
    -fstype devfs -o \
    -fstype devtmpfs -o \
    -fstype proc ')' -prune -o -type f -printf '%P\0' 2> /dev/null |
    fzf --read0 --print0
) && output=${output%$'\0'}
2
  • The part I needed was just </dev/tty. Could you explain what that does and why no output is shown without it?
    – noibe
    Jun 27, 2020 at 14:54
  • @noibe, see edit. Jun 27, 2020 at 15:59

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .