# Nonintrusive fzf with fish


[fzf]: https://github.com/junegunn/fzf
[fish]: https://fishshell.com/
[fd]: https://github.com/sharkdp/fd

<aside>(Since writing this I’ve started to use fzf’s built-in shell integration because it doesn’t make any symlinks anymore. I’m leaving this page up for historical purposes, though.)</aside>

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:

1. You send a bunch of things to `fzf`, one per line.
1. `fzf` lets you pick one (or more!) of the things that was sent to it.
1. `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, but `history | grep ssh | grep horse.example` is more typing `grep` 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, that `something` could vary depending on your OS. This might not be an issue for most fish users, but I use the same `fish` configuration on both macOS and FreeBSD computers. If the `key-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:

```fish
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”?

```fish
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.

