2

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

1

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.