fzfを活用してTerminalの作業効率を高める

## はじめに
今回は作業効率化をすすめるにあたって有用なfzfの利用例を紹介したいと思います。
似たようなものでpecoというのもありますが、fzfの記事が少ないと思うので、今回はfzfについて書いていきたいと思います！

(あとfzfはVimでも使えるようにサポートされているので、Vimmerの方はpecoよりもfzfかなということもあり…。)


## fzfとは
fzf
https://github.com/junegunn/fzf

fzfとはCLIでインクリメンタルに曖昧な検索が可能になるGO言語製のツールです。
標準出力をパイプでfzfコマンドで渡すだけで、標準出力の内容を対象に検索できます。

```zsh

$ find . | fzf 
```

<img width="409" alt="スクリーンショット 2017-10-19 22.02.46.png" src="https://qiita-image-store.s3.amazonaws.com/0/73446/b670e325-02cb-3f69-a4ce-cb27d760d707.png">

上部の入力箇所でインクリメンタル曖昧検索しながら、([CLIとしては慣れ親しんだキーバインド](https://qiita.com/kmszk/items/5b7f3c3eb41a3549bb4e#%E6%AC%A1%E5%80%99%E8%A3%9C%E5%89%8D%E5%80%99%E8%A3%9C)の)Ctrl-n,Ctrl-pで下部のリストから選択することができます。
(もちろんカーソルキーでもOK)


最初は『検索できるだけで、何が便利やねん』という感想を持たれた方も多いかと思いますが、CLIの環境において選択的インターフェースの提供は、工夫次第でかなり強力になります。

## まずはfzfをインストールしましょう
fzfのインストール記事は他にいくらでもあるので割愛します。
たぶん[こちらとかの記事を参考にすると良さそうです](https://qiita.com/tdrk/items/1eb27555ad65d63a6a25)。

ちなみに私の設定っぽい設定は今のところこれだけです。(←zsh)

```zsh:.zshrc
[ -f ~/.fzf.zsh ] && source ~/.fzf.zsh
export FZF_DEFAULT_COMMAND='rg --files --hidden --glob "!.git"'
export FZF_DEFAULT_OPTS='--height 40% --reverse --border'
```

## fzfでヒストリー検索
さて、fzfをちゃんとセットアップできたら、まずはコマンドヒストリーの曖昧インクリメンタル検索を使ってみましょう。


デフォルトのヒストリー検索よりも、一覧を見ながらかつ柔軟に検索できるようになるので、一度使い始めるとデフォルトのコマンドヒストリー検索には戻れません。

<img width="570" alt="スクリーンショット 2017-10-19 22.45.13.png" src="https://qiita-image-store.s3.amazonaws.com/0/73446/f6bcf4d6-55a2-fe7d-9494-db3206d4c411.png">


参考：本家README.md
https://github.com/junegunn/fzf#key-bindings-for-command-line

## fzfで実装する便利関数
さて早速、作業効率化を進めていきましょう。
まず何をすればよいかわからない方は、サンプルの宝庫である本家Wikiを見るのがおすすめです。
https://github.com/junegunn/fzf/wiki

ここではいくつか本家Wikiにある便利関数を紹介します。

### fbr
下記のような記述を.bashrcや.zshrcなどに書きます。

```zsh
# fbr - checkout git branch
fbr() {
  local branches branch
  branches=$(git branch -vv) &&
  branch=$(echo "$branches" | fzf +m) &&
  git checkout $(echo "$branch" | awk '{print $1}' | sed "s/.* //")
}
```
fbrコマンドで今ローカルに存在するbranchを選択して切り替えられるようになります。

<img width="283" alt="スクリーンショット 2017-10-19 22.13.42.png" src="https://qiita-image-store.s3.amazonaws.com/0/73446/d36690a9-60e2-b4de-ff95-e5d76dce55cb.png">
※サンプルはvimのaleのgitリポジトリが丁度良さそうだったので勝手に使わせていただいています(私自身は全く貢献してません…)

※ 本家Wikiに書いてあるサンプルは基本的にfzf+(git) branch = fbrみたいなネーミングのノリです。

さぁ、どんどん行きましょう。

### fbr
またしてもfbrという名前がついているのですが、今度はリモートブランチを含めた検索です。

```zsh
# fbr - checkout git branch (including remote branches)
fbr() {
  local branches branch
  branches=$(git branch --all | grep -v HEAD) &&
  branch=$(echo "$branches" |
           fzf-tmux -d $(( 2 + $(wc -l <<< "$branches") )) +m) &&
  git checkout $(echo "$branch" | sed "s/.* //" | sed "s#remotes/[^/]*/##")
}
```

<img width="336" alt="スクリーンショット 2017-10-19 22.21.24.png" src="https://qiita-image-store.s3.amazonaws.com/0/73446/56e099f9-4ace-afaf-8184-012c9ee17292.png">


個人的にはfbrm(fzf+branch+remote)って関数名にしてfbrとは分けて使ってます。

### fshow

```zsh
# fshow - git commit browser
fshow() {
  git log --graph --color=always \
      --format="%C(auto)%h%d %s %C(black)%C(bold)%cr" "$@" |
  fzf --ansi --no-sort --reverse --tiebreak=index --bind=ctrl-s:toggle-sort \
      --bind "ctrl-m:execute:
                (grep -o '[a-f0-9]\{7\}' | head -1 |
                xargs -I % sh -c 'git show --color=always % | less -R') << 'FZF-EOF'
                {}
FZF-EOF"
}
```

<img width="831" alt="スクリーンショット 2017-10-19 22.33.03.png" src="https://qiita-image-store.s3.amazonaws.com/0/73446/fbb8ffd6-3d0a-670b-fd14-852687cb7d13.png">

git log の--graphオプションもfzfに突っ込んでしまえば、立派なインターフェースの出来上がりです。
Enterでgit showの状態になります。

### fd
もうちょっと簡単な例を見てどうやってカスタマイズしていけばいいか理解を深めてみましょう。

fdはfindコマンドで下層までをリスト化し、標準出力をパイプでfzfに渡し、最後にcdコマンドに結果を渡しているだけということがわかります。

```zsh
# fd - cd to selected directory
fd() {
  local dir
  dir=$(find ${1:-.} -path '*/\.*' -prune \
                  -o -type d -print 2> /dev/null | fzf +m) &&
  cd "$dir"
}
```
<img width="352" alt="スクリーンショット 2017-10-19 23.04.50.png" src="https://qiita-image-store.s3.amazonaws.com/0/73446/a842c5ca-69e6-fc53-af41-22c5d2cd9eaa.png">
(※個人的にfcdって名前にしてみましたがあんまり使ってません…。)

なるほど、これなら自分でも実装できそうです。


## 自分が使っているfzfの例
git worktreeコマンドは皆さんお使いでしょうか。僕も最近使い始めて見たのですが、マルチタスクな日は便利すぎてかなりお世話になっています。 
参考： [ブランチの切り替えをしなくてよくなるコマンド git worktree がすごい！](https://qiita.com/shibukk/items/80430b54ecda7f36ca44)

でも、worktreeで作成したブランチまで移動するのがだるすぎる…。
ということでfzfを使って作業効率化してみます。

```zsh
# worktree移動
function cdworktree() {
    # カレントディレクトリがGitリポジトリ上かどうか
    git rev-parse &>/dev/null
    if [ $? -ne 0 ]; then
        echo fatal: Not a git repository.
        return
    fi

    local selectedWorkTreeDir=`git worktree list | fzf | awk '{print $1}'`

    if [ "$selectedWorkTreeDir" = "" ]; then
        # Ctrl-C.
        return
    fi

    cd ${selectedWorkTreeDir}
}
```

<img width="460" alt="スクリーンショット 2017-10-19 23.16.56.png" src="https://qiita-image-store.s3.amazonaws.com/0/73446/6e2f0e62-badb-3147-b372-8ce499086531.png">

選択した候補のpath部分をcdに渡して階層を移動しています。
長ったらしい関数名ですがcdw(tabキー)みたいな感じで使ってます。

## Vimで使う 
自分は普段使いのエディタがVimなのですが、Vimでfzfが使えるようにサポートされているところがpecoではなくfzfを使う理由の一つです。

そして本家でもvimプラグインとしても提供されています。
https://github.com/junegunn/fzf.vim

### ctrlp.vimのようなファイル検索
大きいプロジェクトだとctrlp.vimよりも遥かに高速です。

```vim
" [Replace of ctrlp.vim] ========================================
" 
nnoremap <C-p> :FZFFileList<CR>
command! FZFFileList call fzf#run({
            \ 'source': 'find . -type d -name .git -prune -o ! -name .DS_Store',
            \ 'sink': 'e'})
```

### MRU(Most Recently Used)
よくあるMRUのFZFインターフェース化

```vim
" [MRU] ========================================
"
command! Fmru FZFMru
command! FZFMru call fzf#run({
            \  'source':  v:oldfiles,
            \  'sink':    'tabe',
            \  'options': '-m -x +s',
            \  'down':    '40%'})
```

### QuickFixの検索
なにかQuickFixに送って開かせるつもりだったもの(あまり使ってない)。

```vim
" [QuickFix] ===================================
" 
command! Fq FZFQuickfix
command! FZFQuickfix call fzf#run({
            \  'source':  Get_qf_text_list(),
            \  'sink':    function('s:qf_sink'),
            \  'options': '-m -x +s',
            \  'down':    '40%'})

" QuickFix形式にqfListから文字列を生成する
function! Get_qf_text_list()
    let qflist = getqflist()
    let textList = []
    for i in qflist
        if i.valid
            let textList = add(textList, printf('%s|%d| %s',
                \       bufname(i.bufnr),
                \       i.lnum,
                \       matchstr(i.text, '\s*\zs.*\S')
                \   ))
        endif
    endfor
    return textList
endfunction

" QuickFix形式のstringからtabeに渡す
function! s:qf_sink(line)
    let parts = split(a:line, '\s')
    execute 'tabe ' . parts[0]
endfunction
```

## Qiitaなどで紹介されているfzf活用例
### fadd
[gitのdiff, addをfzf でインタラクティブに](https://qiita.com/reviry/items/e798da034955c2af84c5)

Gitのunstageなファイルを選択してどんどんstageに移せます。
個人的には特に大きいプロジェクトではtig開くよりも軽いし、かなりおすすめです。

### zコマンドと連携してディレクトリ移動を快適にする
[zshでzの結果をpecoって爆速でディレクトリ移動する](https://qiita.com/maxmellon/items/23325c22581e9187639e)
pecoの記事ですけど。

zコマンドは自分が居たことのあるディレクトリの履歴のなかから、最もマッチするディレクトリ名のパスに移動できるプラグインです。
そのままで使うと、予測してない先に飛ぶこともあるので、ｚの履歴をfzfで検索してからcdするようにしてみます。

fzfでやるならこんな感じで自分は使ってます。

```zsh
# zplugなどでzをインストールしとく
# zplug "rupa/z", use:z.sh

fzf-z-search() {
    local res=$(z | sort -rn | cut -c 12- | fzf)
    if [ -n "$res" ]; then
        BUFFER+="cd $res"
        zle accept-line
    else
        return 1
    fi
}

zle -N fzf-z-search
bindkey '^f' fzf-z-search
```

## まとめ
個人的にはfzfはシンプルかつVimサポートもあり、お陰様でCLI生活を豊かにしてくている(たぶん)ので好きです。

pecoもfzfも使ったことなかったよって人は、内容的にはbash(zsh)の部分に関してはpecoでもfzfでも同じことができると思うので、まずは自分で比較してみて好きになれそうな方を使ってみるのがおすすめです👍

## 追記
『~/.ssh/configからSSH先のホストをfzfで選んで接続する』例も書きました😊
https://qiita.com/kamykn/items/9a831862038efa4e9f8f