oh-my-fishのプラグインで色々展開＆ディレクトリ移動を便利に！

##TL;DR
- `fish`のプラグイン [oh-my-fish/plugin-expand](https://github.com/oh-my-fish/plugin-expand) を使うと，`Tab`で色んなものを展開できるようになります！
- それを利用したディレクトリ移動系プラグイン [oh-my-fish/marlin](https://github.com/oh-my-fish/marlin) もあります
- でもパスに日本語が混じると使えなかったので，[enhancd](https://github.com/b4b4r07/enhancd)ライクな`plugin-expand`設定を書くことにしました

## oh-my-fish/plugin-expand
`oh-my-fish`の公式プラグインを物色していたところ，非常に面白いものを見つけました．

[oh-my-fish/plugin-expand](https://github.com/oh-my-fish/plugin-expand) - Provides interactive word expansions in real-time

これは`Tab`にキーバインドして，カーソル上の単語を指定したルールに従って展開できるようにしてくれるものです．

READMEにも例はあるのですが，ここでは`zsh`のグローバルエイリアスもどきを定義してみます．

```fish:~/.config/fish/config.fish
## カーソル上の単語が正規表現'^G$'にマッチするとき，'echo "| grep "'の実行結果でその単語を置き換える
expand-word -p '^G$' -e 'echo "| grep "'

## 同じ正規表現に複数の展開方法を指定できる
expand-word -p '^L$' -e 'echo "| less "'
expand-word -p '^L$' -e 'echo "| vimpager "'

expand-word -p '^_$' -e 'echo ">/dev/null"' # エラー読みたい
expand-word -p '^_$' -e 'echo "^/dev/null"' # エラーいらない
expand-word -p '^_$' -e 'echo ">/dev/null ^/dev/null"' # 黙ってやれ
```

![plugin-expand.mov.gif](https://qiita-image-store.s3.amazonaws.com/0/124405/03a21954-21e2-8c3e-4074-8330fbfec319.gif)

展開のルールを登録するには`expand-word`コマンドを使います．`-p, --pettern`オプションで正規表現を，`-e, --expander`オプションで式を渡すと，`Tab`を押したときに単語の展開をしてくれるようになります．

カーソル下の単語が登録された複数の正規表現にマッチしたり，一つの正規表現に複数の展開方法が登録されていたりする場合は，**`fzf`, `peco`などのインタラクティブフィルタリングツールが起動し**，選択を促されます[^filter]．

## oh-my-fish/marlin

工夫次第で色々な展開ができそうなプラグインですが，`oh-my-fish`にもこれを利用したプラグイン`oh-my-fish/marlin`があります[^name]．

これはカンマ`,`に続けて正義表現を入力すると，過去のディレクトリ移動履歴からそれにマッチするものを見つけて展開してくれるプラグインです．
展開候補が複数ある場合にフィルタリングツールが起動する，という`plugin-expand`の特性が非常に有効に働きます．

![omf-marlin.mov.gif](https://qiita-image-store.s3.amazonaws.com/0/124405/29dfbde1-f788-7c6d-27f4-71357e213e93.gif)

上記のデモでは仮想のパスを使ってますが，実在するパスならそのままエンターを押すことでディレクトリ移動できます．

トリガーがカンマ`,`で手軽に使える上，ディレクトリ移動履歴の作成も勝手にやってもらえるのでかなり便利です．
`zsh`から`fish`に移行した際に[enhancd](https://github.com/b4b4r07/enhancd)という強力なプラグインを失い，**移動系プラグイン難民**となっていたため，`merlin`にはかなり期待できそうでした．


...が，どうもパスに空白や日本語が混じっていたりする場合にうまく扱えないことが判明しました．普段そういったパスを使わない方なら便利に使えると思いますが，残念．

## enhancdライクなplugin-expandの設定

しかし`plugin-expand`は応用すればディレクトリ移動にも使える，と分かったので，せっかくなのでenhancdっぽい使い方ができる設定を書いてみようと思いました．

```fish:~/.config/fish/config.fish
## 設定
set -x cdhist_file "$HOME/.cdhist"
set -x cdhist_size 100
touch $cdhist_file

## 移動履歴記録
## PWD変数を監視して，値をファイルに追記
function __cd_add_log --on-variable PWD
    echo $PWD >> $cdhist_file
end

## 移動履歴取得 & 取得時にファイル更新
function __cd_get_log
    set -l logs (cat $cdhist_file)
    if set -q logs[2]
        string join \n $logs[-1..1] # 新しいものを上に
    end \
    | awk '!a[$0]++' \ # 要素を一意に
    | head -n $cdhist_size \ # 要素数制限
    | read -z logs

    string join \n $logs | string escape -n # 新しいものを上にして出力へ

    string join \n $logs | tail -r > $cdhist_file # 新しいものを下にしてファイルへ
end

## 親ディレクトリ一覧の取得
function __cd_parents
    set -l dirs (string split '/' "$PWD")

    for i in (seq (math (count $dirs) -1) 1)
        string join '/' $dirs[2..$i]
    end
    echo "/"
end

## 展開式を取得
expand-word -c "test (commandline -p) = 'cd -'" -e __cd_get_log
expand-word -c "test (commandline -p) = 'cd ..'" -e __cd_parents
```

ディレクトリ移動の記録は`marlin`と同じく変数`PWD`を監視する方法を採用．[enhancdの実装に関する記事](http://www.tellme.tokyo/entry/2015/08/16/092849)を参考に，履歴をひっくり返したり戻したりしながら履歴の出力と更新を行っています．

使い方としては, `cd -`と打ってタブを押すとこれまでの検索履歴，`cd ..`と打ってタブを押すとルートまでの親ディレクトリ一覧が表示されます．

ただ，`enhancd`には移動履歴がどんどん蓄積する仕組みがあって便利だったのですが，そこまではとても実現できそうにありません．
ので，以下のように対処．

```
cat ~/.enhancd/enhancd.log >> ~/.cdhist 
```

## おわりに
以前[fishで{1..9}を展開する記事](http://qiita.com/ryotako/items/70827643a20fa60b2547)を書きましたけど，やっぱり同じようなことを考える方はいるようで，`oh-my-fish`にそのものズバリなプラグインがありました．

今回はそのプラグインを使い，`marlin`をヒントに`enhancd`っぽい設定を作りました．簡素ですが個人的には大満足です．

`plugin-expand`，他にも色々応用が考えられそうです．

## 補足
...とここまで書いてきて今更なのですけど，私の環境で`plugin-expand`を入れると，普通に補完をするときの`Tab`がうまく働かなくなってしまいました．

なので，[プラグインに少し手を加えたもの](https://github.com/ryotako/plugin-expand/tree/fix_for_usual_tab_completion)を使っています．

`Tab`にキーバインドする関数を自作していたり，色々なプラグインマネージャを試してプラグインを入れたり消したりしていたので何かが干渉しているのかも.
本当に`Tab`が効かなくなるのなら，プルリクを出したいところですが...

[^filter]: フィルタリングツールを指定するには`FILTER`変数を使います．フィルタリングツールが見つからなかった場合や，`set FILTER ''`のように明示的に指定されていない場合は，`Tab`を連打することで複数の候補を切り替えられます．むしろこちらの挙動の方が好きな人もいるかもしれません．

[^name]: `oh-my-fish`公式なのに名前が`plugin-`でも`thema-`でも始まらない稀有なプラグインです．単なるミスなのか，何かの気概を表しているのか... なおmarlinはカジキのことだそうです．

## 追記 (2017/4/14)
`__cd_get_log`の中で`tail`を使っていたのを`head`に改めました．
## 追記 (2017/4/27)
`__cd_get_log`の中で履歴を逆順にできていなかったのを修正．