fzfを使おう

## これはなに？

https://github.com/junegunn/fzf

インクリメンタル検索ができるGo製のツールです。同様でよく使われるツールに[peco](https://github.com/peco/peco)があります。fzfはプレビューウィンドウ表示ができるのが一番の違いでしょうか。

<script id="asciicast-231403" src="https://asciinema.org/a/231403.js" async></script>

その他、tmuxと連携する [fzf-tmux が同梱されていたり](https://github.com/junegunn/fzf#fzf-tmux-script)、 [vim と連携する fzf.vim が開発されていたり](https://github.com/junegunn/fzf.vim)するなど、関連ツールが充実しています。 

## インストール方法

fzf は homebrew 等でも配布されていますが、go製のツールなので、リポジトリをcloneしてビルドしたほうが手っ取り早いかもしれません。

### homebrew/Linuxbrewでのインストール

```shell-session
$ brew install fzf

# shellのキーバインディングへの追加やfuzzy finder(ゆるふわ検索)機能を追加したい場合は下記のコマンドを実行しましょう
$ $(brew --prefix)/opt/fzf/install
```

### git clone

```shell-session
$ git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
$ ~/.fzf/install
```

## 使い方

peco と同様に標準出力に食わせましょう。

```shell-session
$ vi `find * -type f | fzf`
```

上記は選択したファイルをviで開きます。このままだと `.git` 配下なども検索対象に含まれるので、ripgrepや、シンタックスハイライトをしてくれるbatも入れちゃいましょう。

### 一緒に入れとくと便利奴

#### ripgrep

`ripgrep`はgrepを強化したツールですが、gitでignoreしたファイルを無視できるツールなので一緒に入れてあげると良いでしょう。ripgrepはrust製ツールです。

https://github.com/BurntSushi/ripgrep

##### インストール方法

こいつも Homebrew に公開されているので下記でOKです。

```
$ brew install ripgrep
```

[他のOSの場合 release ページにDistributionに対応したパッケージがおいてあるのでそれを入れると良いでしょう。](https://github.com/BurntSushi/ripgrep/releases)

rust製ツールはrust用のパッケージマネージャcargoを使えば各OS用のパッケージを作成できます。そのせいかrust製のツールは大体パッケージも用意されているのも便利ですね。

#### bat

`bat` は rust製cat のクローンです。標準で様々な言語のシンタックスハイライトを提供してくれます。

https://github.com/sharkdp/bat

#### インストール方法

同上です。

```
$ brew install bat
```

[他のOS用パッケージ](https://github.com/sharkdp/bat/releases)


### プレビューウィンドウの起動方法

選択中の文字列を任意のコマンドにわたすことでプレビューウィンドウにこんな感じで実行結果を表示します。

```shell-session
fzf --preview [command]
```

例えば `head -100 {}` という文字列を `[command]` に入れてみましょう。選択中のファイル(文字列ですが)の先頭100行が表示されるはずです。`{}`にfzfで選択中の文字列が渡ります。例えば、fzfで`README.md`を選択しているのであれば、プレビューウィンドウには `head -100 README.md` のように渡ります。batと連携するなら、

```
fzf --preview "bat  --color=always --style=header,grid --line-range :100 {}"
```

としたらOKです。

## キーバインド

fzfをインストールするとき、キーバインドの設定を勧められます。ついでに設定しておくと便利です。

冒頭のasciinemaは `<CTRL+T>` を叩いたもので、カレントディレクトリ配下のファイルをインクリメンタル検索できます。

これをripgrepとbatを使うよう、下記のように設定しています。

```zsh:.zshrc
export FZF_CTRL_T_COMMAND='rg --files --hidden --follow --glob "!.git/*"'
export FZF_CTRL_T_OPTS='--preview "bat  --color=always --style=header,grid --line-range :100 {}"'
```

他にも用意されているキーバインドには `<CTRL+R>` と `<ALT+C>` があります。`<CTRL+R>` はコマンド履歴、 `<ALT+C>` はディレクトリ移動です。

## ** を活用する

カレントディレクトリではなく、入力している文字列からインクリメンタル検索をしたいときは `**` を使うと良いでしょう。

例えば、 `src/github.com/motemen/ghq` 配下のファイルを検索したい場合は下記のように `src/github.com/motemen/ghq/**` と入力したあとに `<TAB>` を入力すると `fzf` が起動し、補完できます。

<script id="asciicast-231405" src="https://asciinema.org/a/231405.js" async></script>

もし、batを使ってプレビューしたいのであれば、環境変数`FZF_DEFAULT_OPTS`を使って指定すると良いでしょう。fzfは環境変数で諸々指定できる点も便利です。巨大なgitリポジトリの場合、`git ls-tree -r --name-only HEAD` を使ったほうが高速にリストを作成できるようです。そういった場合は、そのリポジトリ配下のみ環境変数が有効になるように [direnv を設定しておくとよいでしょう。](https://qiita.com/kompiro/items/5fc46089247a56243a62)

### ファイル以外にも使える `**`

```zsh
# 現在実行中のプロセスをkillする
$ kill -9 **<TAB>
# 接続先のホストをインクリメンタル検索(/etc/hosts等から絞り込み)
$ ssh **<TAB>
# 環境変数をunset
$ unset **<TAB>
```

## vim連携

fzfは[vim と連携する fzf.vim が開発されている](https://github.com/junegunn/fzf.vim)と冒頭に紹介したりしました。vimから呼び出すコマンド自体はfzf本体に内包され、`fzf.vim`はそれを使ったプラグイン、という構成になっています。そのため、vimを便利にするには `fzf` と `fzf.vim` 双方を入れておくと良いでしょう。

fzfを開発している方がvimのプラグインマネージャvim-plugも開発されています。vim-plug を使ってインストールする場合は下記のように書きますが、お使いのプラグインマネージャでインストールしましょう。

```vim:vimrc
Plug '/usr/local/opt/fzf'
Plug 'junegunn/fzf.vim'
```

### fzf.vim でよく使うコマンドや設定

| コマンド | 一覧されるもの  |
|:--------|-------------:|
| Files | ファイル |
| GFiles | git ls-files の結果 |
| History | 最近使ったファイル |
| Buffers | 開いているバッファ |
| Rg <pattern> | ripgrep 結果 |

```vim:vimrc
" ripgrepで検索中、?を押すとプレビュー:
command! -bang -nargs=* Rg
  \ call fzf#vim#grep(
  \   'rg --column --line-number --no-heading --color=always --smart-case '.shellescape(<q-args>), 1,
  \   <bang>0 ? fzf#vim#with_preview('up:60%')
  \           : fzf#vim#with_preview('right:50%:hidden', '?'),
  \   <bang>0)

" Filesコマンドにもプレビューを出す
command! -bang -nargs=? -complete=dir Files
  \ call fzf#vim#files(<q-args>, fzf#vim#with_preview(), <bang>0)
```

上記の例で登場する`fzf#vim#with_preview`等のコマンドが本体で提供されているようです。vim-fzf連携は Terminal モードを使っているようで、Terminalモードの利用例としても興味深いです。

## その他便利奴

### ghq連携

scmリポジトリマネージャghqも勢いにまかせて`get`しまくると知らないリポジトリだらけになるので、READMEファイルをプレビューできるようにしておくと便利です。

```zsh:zshrc
alias gcd='ghq look `ghq list |fzf --preview "bat --color=always --style=header,grid --line-range :80 $(ghq root)/{}/README.*"`'
```

### git連携

ブランチの内容を確認しつつcheckoutしたいんや

```zsh:.zshrc
# fbr - checkout git branch
function fzf-checkout-branch() {
  local branches branch
  branches=$(git branch | sed -e 's/^\* /  /g' | sed -e 's/  //g') &&
  branch=$(echo "$branches" | fzf --preview "git show --color=always {}") &&
  git checkout $(echo "$branch")
}
zle     -N   fzf-checkout-branch
bindkey "^b" fzf-checkout-branch
```

## 最後に

以上ざっくりまとめてみましたが、プレビューウィンドウが追加されるだけで、IDEで実装しようとするといろいろ大変な諸々が、コマンドを組み合わせるだけで実現できるのがCUIというか、Unixのツールチェーンの強みだなぁ、と改めて思いました。