Add support for an action that replaces fzf
jlebon opened this issue · 6 comments
- I have read through the manual page (
man fzf
) - I have the latest version of fzf
- I have searched through the existing issues
Info
- OS
- Linux
- Mac OS X
- Windows
- Etc.
- Shell
- bash
- zsh
- fish
Problem / Steps to reproduce
Right now, we have the execute()
action, which is useful for spawning programs, but the original fzf process is still running so that we can go back to it. In some use cases, a better fit would be to replace the fzf process with the target invocation. For example:
- a pattern I've started using recently in my fzf-powered tools is to have it recursively call itself on a slightly modified version of the command (for example, a file browser with a binding to go up by one directory is implemented by respawning itself passing the parent directory as argument)
- some bindings are intended to spawn executables that "take ownership" of the fzf results; the user in that case has no intention to return to fzf since its job is done; a common example is spawning a text editor
My current approach is to do execute(...)+abort
but it keeps fzf running unnecessarily until the program exits. In the case of the first use case above, recursively calling back into fzf leads to a continuously growing process stack. Another approach is to wrap fzf and call the program on the results that it returns (e.g. vim $(my-fzf-powered-file-finder)
), but (1) this doesn't work if you want to be able to bind multiple hotkeys to different executables, and (2) since stdout is being captured, it breaks anything fzf spawns that needs raw terminal access (e.g. less
).
Proposal
Introduce a new exec()
action which causes fzf to exec(3)
into the invocation rather than fork and exec.
And now searching closed issues, I see #816 which is similar and mentions --expect
which I missed before. I think --expect
could be a cleaner workaround for this, but requires support from the wrapping script. It seems like #816 lead to the creation of the +abort
pattern, but I think what we really want there is to just call exec(3)
to not have to wait around just to exit once the invocation returns.
@jlebon +1 for this, I had this problem just yesterday where I want an exec-and-exit. "run()" would be a good name for it.
I'm not sure this workaround fits your use case but I'm using a shell function wrapper. This doesn't help your 1) and 2) though.
function hi(){ echo "Your word is: $(cat /usr/share/dict/words | fzf -q "$1" --prompt "hi> ")";};
Example:
https://github.com/jknight/package-json-fzf/blob/main/README.md
I think
--expect
could be a cleaner workaround for this, but requires support from the wrapping script.
So the suggested feature is essentially a shortcut? execute
provides a functionality that is not otherwise possible, but what we're discussing here is already achievable with the existing options. fzf was designed to become a good citizen in the Unix toolchain and it's more natural to use it in a shell script with other tools rather than to use it alone.
Introduce a new
exec()
action which causes fzf toexec(3)
into the invocation rather than fork and exec.
While we know that exec
is the right name for the feature, not everyone should be aware of the system call and the name's too similar to execute
that it can be misleading. I think something like become(...)
should be more appropriate.
- have it recursively call itself on a slightly modified version of the command
Aren't you bothered by the flicking of the screen in between the processes? Using reload
bindings can be a good alternative although you'll have to manage the state in an external file.
FZF_DEFAULT_COMMAND="pwd | tee /tmp/pwd; ls --color=always" \
fzf --reverse --ansi --header-lines 1 \
--bind 'backward-eof:reload:cd "$(cat /tmp/pwd)/.."; eval "$FZF_DEFAULT_COMMAND"' \
--bind 'enter:reload(cd "$(cat /tmp/pwd)"/{} || cd "$(cat /tmp/pwd)"; eval "$FZF_DEFAULT_COMMAND")+top'
Hi @junegunn, first I want to say thank you for fzf. It's rare that I come across a tool that so significantly changes my development experience. The ability to create with just a tiny shell script an interactive interface tailor-made for your needs is incredibly powerful. Thank you.
Introduce a new
exec()
action which causes fzf toexec(3)
into the invocation rather than fork and exec.While we know that
exec
is the right name for the feature, not everyone should be aware of the system call and the name's too similar toexecute
that it can be misleading. I think something likebecome(...)
should be more appropriate.
SGTM!
I think
--expect
could be a cleaner workaround for this, but requires support from the wrapping script.So the suggested feature is essentially a shortcut?
execute
provides a functionality that is not otherwise possible, but what we're discussing here is already achievable with the existing options. fzf was designed to become a good citizen in the Unix toolchain and it's more natural to use it in a shell script with other tools rather than to use it alone.
I don't think --expect
fixes this part though:
(2) since stdout is being captured, it breaks anything fzf spawns that needs raw terminal access (e.g.
less
).
I think what happens there is that less
senses that stdout is not a terminal and goes into cat
mode.
I guess another approach is adding an --output FILE
switch to have fzf write its result to a separate file instead of stdout. And then the wrapping script wouldn't have to redirect fzf output.
But also I suspect that a new become(...)
would simplify a lot of scripts that currently use --expect
.
- have it recursively call itself on a slightly modified version of the command
Aren't you bothered by the flicking of the screen in between the processes? Using
reload
bindings can be a good alternative although you'll have to manage the state in an external file.FZF_DEFAULT_COMMAND="pwd | tee /tmp/pwd; ls --color=always" \ fzf --reverse --ansi --header-lines 1 \ --bind 'backward-eof:reload:cd "$(cat /tmp/pwd)/.."; eval "$FZF_DEFAULT_COMMAND"' \ --bind 'enter:reload(cd "$(cat /tmp/pwd)"/{} || cd "$(cat /tmp/pwd)"; eval "$FZF_DEFAULT_COMMAND")+top'
Yeah, changing the original invocation is the tricky part, and using state files is an interesting approach though a bit awkward. I actually had some ideas around that, but I'll keep that for a separate RFE. :)
I don't think
--expect
fixes this part though:(2) since stdout is being captured, it breaks anything fzf spawns that needs raw terminal access (e.g.
less
).
FYI, you can workaround the issue by redirecting the stdout to /dev/tty
lines=$(fzf --bind 'alt-l:execute:less {} > /dev/tty' --expect=ctrl-l)
Thank you for the quick turnaround on this! It's working great.