I’ve been getting into fzf recently. In case what it does isn’t immediately obvious to you (it wasn’t to me), here’s what it does:
- You send a bunch of things to
fzf
, one per line. fzf
lets you pick one (or more!) of the things that was sent to it.fzf
prints, to standard output, that thing or those things that you picked.
This has a couple obvious use cases:
- “I want to
cd
to a directory, but I only want to type the unique end of the path, not the entire path from here to there.” - “I want to
ssh
into this one server again, buthistory | grep ssh | grep horse.example
is more typinggrep
than I want to do.”
If you install fzf
by hand, you’ll get an opportunity to let it install its own things into your shell’s configuration. If you let it, it’ll set up C-t, C-r, and M-c so that they run a “file widget”, “history widget”, and a “cd
widget”, respectively. Pretty handy, but:
- This installation process puts a symlink from your fish-config directory (likely
~/.config/fish/
) to/usr/local/something/fzf/key-bindings.fish
. What’s worse, thatsomething
could vary depending on your OS. This might not be an issue for most fish users, but I use the samefish
configuration on both macOS and FreeBSD computers. If thekey-bindings.fish
file is in different places on both macOS and FreeBSD, I was gonna have a bad time. - My primary OS is macOS. While the Meta key may be easily used on every other OS, the only hardware Meta key I have is the Option key, and it’s being used to make curly quotes and ellipses, even in Terminal.app. Sure, I can press Escape and then press C, it’s not nearly as pleasant as pressing Alt-C.
I then tried to see if I could get 95% of what I wanted with 0% of the invasive code in my ~/.config/fish/
.
Searching through history
Normally, when I want to search through my history, I run hgrep foo
, where hgrep
is short for history | grep
.
I ended up with this:
function hfzf
set -l to_run (history | fzf)
if test -n "$to_run"
echo "$to_run"
eval "$to_run"
end
end
Note that fish
won’t let you just run (history | fzf)
. It’ll give you an error of fish: Command substitutions not allowed
. I’m not sure, but I think this is to keep fish
users from shooting themselves in the foot. However, if you explicitly set that command to a variable and then run eval
on it, fish
will let you do what you want.
The echo
line is there because it feels weird to run a command that hasn’t been printed to the console in some form. bash
will print out lines that have had command-substitution magic (!!, etc.) applied to them; I wanted the same thing in fish
with fzf
.
Running it is easy. I just type hfzf
, pick the history item from the list, and then hit Enter to run it.
Changing directories
fish
already has cdh
for “change to a recently-visited directory”, so what about cdf
for “fuzzy cd”?
function cdf
set -l whither
if command -sq fd
set whither (fd --type d | fzf)
else
set whither (find . -type d | fzf)
end
test -n "$whither"; and cd "$whither"
end
After declaring a local variable, this tests to see if fd is installed. fd
is a Rust-based reimplementation of find
. Most importantly, it ignores hidden files by default, so it won’t waste time and disk reads by trawling through .git/objects/
directories looking for directories to change to.
I don’t have fd
installed on all my machines, so I have a fallback case that uses find
.
The final interesting line tests to make sure $whither
actually has something in it before changing directories. If I quit out of fzf without picking anything, I don’t want to run cd
with no arguments. That’ll just drag me back to my home directory, which likely isn’t what I want.
Wrapup
Two functions in two files gives me most of what I want out of fzf
without deeply integrating it into my shell configuration in weird ways. If you dislike it when your utilities get their hooks into your shell, maybe you’ll find all this useful.