zshでpecoと連携するためのanyframeというプラグインを作った

![10.gif](https://qiita-image-store.s3.amazonaws.com/0/204/b730db31-cea5-b8f2-043f-5d6194d0626c.gif)

## これは何?
zshの操作で、[peco](https://github.com/peco/peco)とか[percol](https://github.com/mooz/percol)とか、Anything風インターフェースで選択するやつがよく使われてる。これを使うと、例えばコマンドライン履歴からインクリメンタルに検索して、それを実行、とかできるようになる。

でも、pecoとかpercolはシェルとは関係なくて、単に「インクリメンタルに絞り込む」ってところだけしかやってくれない。それだけでは役に立たなくて、「選んだ結果を実行する」とかのシェルの処理が必要になる。

それで、そういうシェルの処理を自分で書きたくない人向けに[anyframe](https://github.com/mollifier/anyframe)というのを作った。

- [https://github.com/mollifier/anyframe](https://github.com/mollifier/anyframe)

これはzsh用のプラグインで、これを使うと自分でシェルの関数を書かなくても、キーバインドとかaliasを設定するだけでpeco/percolの便利なやつをすぐ使えるようになる。

## 特徴

- 普通の人が使いたいと思う関数がだいたい入ってる
- [peco](https://github.com/peco/peco)、[percol](https://github.com/mooz/percol)、[fzf](https://github.com/junegunn/fzf)に対応してる
- aliasでもbindkey(キーボード ショートカットから呼び出すやつ)でも、どちらからでも使える
- 選んだ結果をすぐに実行するのも、コマンドラインに挿入するのもできる
- 拡張しやすい

## インストール
まずは先に[peco](https://github.com/peco/peco)、[percol](https://github.com/mooz/percol)、[fzf](https://github.com/junegunn/fzf)をインストールしておく。どれかひとつ好きなやつでOK。その後[anyframe](https://github.com/mollifier/anyframe)を次の方法でインストールする。

### 1. 手動でインストールする場合
手動でインストールする場合、$fpathの通ったところに[anyframe](https://github.com/mollifier/anyframe)以下のファイルを全部配置する。$HOME/.zsh/の下にインストールする場合の例は以下。

```
# ディレクトリがまだない場合は作成する
% mkdir $HOME/.zsh/
% cd $HOME/.zsh/
% git clone git@github.com:mollifier/anyframe.git
```

そのあと~/.zshrcに次の設定を追加する

```zsh
fpath=($HOME/.zsh/anyframe(N-/) $fpath)
autoload -Uz anyframe-init
anyframe-init
```

### 2. Antigenでインストールする場合
[Antigen](https://github.com/zsh-users/antigen)を使ってる人は、~/.zshrcに1行追加するだけでOK。

```zsh
if [[ -f ~/.zsh/antigen/antigen.zsh ]]; then
  source ~/.zsh/antigen/antigen.zsh
  antigen bundle mollifier/anyframe # <= これを追加
  antigen apply
fi
```

## 使い方
適当に`source ~/.zshrc`すると[anyframe-functions/widgets](https://github.com/mollifier/anyframe/tree/master/anyframe-functions/widgets)にある関数(**anyframe-widget**と呼ぶことにする)が全部使えるようになる。こいつに対してbindkeyで好きなキーバインドを設定する。あとはそのキーを押せばOK。

僕が使っているキーバインドはこんな感じ。

```zsh
bindkey '^xb' anyframe-widget-cdr
bindkey '^x^b' anyframe-widget-checkout-git-branch

bindkey '^xr' anyframe-widget-execute-history
bindkey '^x^r' anyframe-widget-execute-history

bindkey '^xp' anyframe-widget-put-history
bindkey '^x^p' anyframe-widget-put-history

bindkey '^xg' anyframe-widget-cd-ghq-repository
bindkey '^x^g' anyframe-widget-cd-ghq-repository

bindkey '^xk' anyframe-widget-kill
bindkey '^x^k' anyframe-widget-kill

bindkey '^xi' anyframe-widget-insert-git-branch
bindkey '^x^i' anyframe-widget-insert-git-branch

bindkey '^xf' anyframe-widget-insert-filename
bindkey '^x^f' anyframe-widget-insert-filename
```

あとは、aliasで短い名前をつけてそれを呼び出しても良い。

```zsh
alias aw=anyframe-widget-select-widget
```

もっと言うと、`anyframe-widget-select-widget`とか直接打ち込んで呼び出しても良い。

## anyframe-widget一覧
[anyframe-functions/widgets](https://github.com/mollifier/anyframe/tree/master/anyframe-functions/widgets)にある関数一覧とその使い方は次の通り。

|関数名|内容|
|:-----|:---|
|anyframe-widget-execute-history|コマンドライン履歴から選んで実行する|
|anyframe-widget-put-history|コマンドライン履歴から選んでコマンドラインに挿入する|
|anyframe-widget-checkout-git-branch|Gitブランチを切り替える|
|anyframe-widget-insert-git-branch|Gitブランチ名をコマンドラインに挿入する|
|anyframe-widget-cdr|過去に移動したことのあるディレクトリに移動する(cdrが必要)|
|anyframe-widget-kill|プロセスをkillする|
|anyframe-widget-cd-ghq-repository|[ghq](https://github.com/motemen/ghq)コマンドで管理しているリポジトリに移動する([ghq](https://github.com/motemen/ghq)が必要)|
|anyframe-widget-insert-filename|ファイル名をコマンドラインに挿入する|
|anyframe-widget-tmux-attach|tmuxセッションを選んでアタッチする|
|anyframe-widget-select-widget|anyframe-widgetから選んでそれを実行する|

少し説明が必要な奴があるので、補足しておく。

### anyframe-widget-cdr
過去に移動したことのあるディレクトリに移動する。cdrが必要。

cdrは、zsh標準で添付されている、ディレクトリ移動の履歴を管理して最近移動したディレクトリに移動するためのプラグイン。これを使うには、最低限次の2行を~/.zshrcに書いておく。

```zsh
autoload -Uz chpwd_recent_dirs cdr add-zsh-hook
add-zsh-hook chpwd chpwd_recent_dirs
```

参考 : [zshでcdの履歴管理に標準添付のcdrを使う - @znz blog](http://blog.n-z.jp/blog/2013-11-12-zsh-cdr.html)

### anyframe-widget-cd-ghq-repository
[ghq](https://github.com/motemen/ghq)コマンドで管理しているリポジトリに移動する。別途[ghq](https://github.com/motemen/ghq)が必要。ghqは、Gitリポジトリをクローンして、その一覧を管理するためのコマンド。

### anyframe-widget-select-widget
anyframe-widgetから選んでそれを実行する。つまり、こいつを実行すると

```
cd-ghq-repository
cdr
checkout-git-branch
... 以下省略 ...
```

というふうにanyframe-widget一覧が表示されてpeco/percolで選択できる。で、それを選ぶとその選んだanyframe-widgetが実行される。使用頻度が低くてわざわざキーバインドを設定する程でもないanyframe-widgetを呼び出すときに便利。


普通の人はここまでの設定で便利に使えると思う。

## カスタマイズする
anyframeは、peco、percol、fzfに関してカスタマイズができるようになってるので、それについて解説する。

anyframeでは、インクリメンタルに検索するコマンドのことを**selector**と呼ぶことにしてる。今のところselectorとしてはpeco、percol、fzfに対応してて、そのなかでインストールされているやつを自動的に使うようになってる。それで、複数インストールしている人は、どれを使いたいか明示的に指定することもできる。

pecoを使うと明示的に指定するときは、次の行を~/.zshrcに書く。

```zsh
zstyle ":anyframe:selector:" use peco
```

percolを使うと指定したいときはこんな感じ。

```zsh
zstyle ":anyframe:selector:" use percol
```

fzfを使うと指定したいときは、こう書く。

```zsh
zstyle ":anyframe:selector:" use fzf
```

どれかひとつしかインストールしていない人は、別にこれを書かなくても大丈夫。

あとは、peco/percol/fzfコマンドのパスとコマンドライン引数も指定できる。これは、パスの通ってないところにインストールしたpeco/percol/fzfを使うときとか、オプションを追加したいときに使う。

```zsh
# 例1: pecoで--no-ignore-caseオプションを指定する
zstyle ":anyframe:selector:peco:" command 'peco --no-ignore-case'
```

```zsh
# 例2: percolで--case-sensitiveオプションを指定する
zstyle ":anyframe:selector:percol:" command 'percol --case-sensitive'
```

```zsh
# 例3: fzfで--extendedオプションを指定する
zstyle ":anyframe:selector:fzf:" command 'fzf --extended'
```

僕の場合は`${HOME}/.peco_config.json`というところにpecoの設定ファイルを置いていて、次のようにしてそれを設定ファイルとして使うように指定してる。

```zsh
zstyle ":anyframe:selector:peco:" command "peco --rcfile=${HOME}/.peco_config.json"
```

## 自分で独自の関数を追加する

anyframeは自分でanyframe-widgetとかの関数を追加して拡張することを考慮している。自分でzshの関数を書くことになるんだけど、そのときでもanyframeに含まれている関数が使えるので、一から書くよりだいぶ簡単になる。

書く場所として2つある。まず、~/.zshrcに独自の関数を足していくのがお手軽な方法。そうやっていっぱい追加すると~/.zshrcが大きくなってわけわかんなくなるので、別の場所に切り出して配置したい、という人もいると思う。そういうこともできるので、最後にその方法についても解説する。

### ~/.zshrcに独自のanyframe-widgetを追加する
まずはお手軽な、~/.zshrcに関数を追加して呼び出す方法について解説する。

anyframeでは、peco/percol/fzfで選択した結果をどうするか、という動作として

1. 選択した結果をzshですぐに実行する
2. 選択した結果で現在のコマンドラインを置き換える
3. 選択した結果をコマンドラインのカーソル位置に挿入する

という3つのパターンに対応している。

そして、1.のパターンのときは、基本的には次のような形の関数を書く。

```zsh
# 1. 選択した結果をzshですぐに実行する関数のひな形
function 関数名 () {
  <(a)選択する候補を標準出力に出力する処理> \
    | anyframe-selector-auto \
    | <(b)選択結果に対するフィルタ(必要なときだけ)> \
    | anyframe-action-execute
}
```

「(a)選択する候補を標準出力に出力する処理」は、まさにそのままで、選びたい候補を単純に標準出力に出力するコマンドとか関数を書く。`history`コマンドでも`git log`でもファイルを`cat`で出力したやつでも、なんでも良い。

それをパイプラインで繋いで`anyframe-selector-auto`に渡すと、peco/percolが起動する。選んだ結果はまた標準出力に出力される。
すぐにzshで実行したいときは、その結果をさらにパイプラインで繋いで`anyframe-action-execute`に渡せばOK。

peco/percolで選んだ結果に対してさらに絞り込みとか変換とか書けたいときは「(b)選択結果に対するフィルタ」の位置にフィルタプログラムを書く。ここも普通のフィルタなので、grepでもsedでもrubyでもなんでも好きなように書ける。


例えば、標準で入っているanyframe-widget-execute-history(コマンドライン履歴から選んで実行する)を自分で書くとすると、こんな感じ。

```zsh
function anyframe-widget-execute-history () {
  history -n -r 1 \
    | anyframe-selector-auto \
    | anyframe-action-execute
}
```

あとは、aliasから呼び出したいときは単純にaliasで別名を付ける。

```zsh
alias ah=anyframe-widget-execute-history
```

これで`ah`というaliasで呼び出せるようになる。

キーボードショートカットから呼び出したいときは、次の2行を~/.zshrcに書く。

```zsh
zle -N anyframe-widget-execute-history
bindkey '^xr' anyframe-widget-execute-history
```

これでCtrl+x rで呼び出せるようになる。

本当は「peco、percol、fzfのうちインストールされている方を起動する」とか「選んだ結果を今のコマンドラインに挿入してそれを実行する」とか、そういう処理が必要なんだけど、そういうのは全部anyframe-selector-autoとanyframe-action-executeがやってくれてる。

処理の内容も意図して標準入力/標準出力だけを使うようにしてる。変数とか`$()`とか、そういういかにもシェル特有っぽい書き方は必要ない。なので他のコマンドと簡単に組み合わせれるし、シェルスクリプトが苦手な人でも書きやすいと思う。



「1. 選択した結果をzshですぐに実行する」の派生系として、「選択した結果を特定のコマンドの引数に渡して実行する」というのをしたいことがある。そういうときは最後の`anyframe-action-execute`の後に実行したいコマンドを書く。

例えば、標準で入っているanyframe-widget-kill(プロセスをkillする)を自分で書くとすると、こんな感じ。

```zsh
function anyframe-widget-kill () {
  ps -u $USER -o pid,stat,%cpu,%mem,cputime,command \
    | anyframe-selector-auto \
    | awk '{print $1}' \
    | anyframe-action-execute kill
}

zle -N anyframe-widget-kill
bindkey '^xk' anyframe-widget-kill
```

最後の`anyframe-action-execute kill`のところは「標準入力で受け取った文字列を引数としてkillコマンドを実行する」という意味になる。

ここまでが「1. 選択した結果をzshですぐに実行する」の書き方。これが「2. 選択した結果で現在のコマンドラインを置き換える」「3. 選択した結果をコマンドラインのカーソル位置に挿入する」になると、それぞれ次のようになる。

```zsh
# 2. 選択した結果で現在のコマンドラインを置き換える関数のひな形
function 関数名 () {
  <(a)選択する候補を標準出力に出力する処理> \
    | anyframe-selector-auto \
    | <(b)選択結果に対するフィルタ(必要なときだけ)> \
    | anyframe-action-put
}
```

```zsh
# 3. 選択した結果をコマンドラインのカーソル位置に挿入する関数のひな形
function 関数名 () {
  <(a)選択する候補を標準出力に出力する処理> \
    | anyframe-selector-auto \
    | <(b)選択結果に対するフィルタ(必要なときだけ)> \
    | anyframe-action-insert
}
```

書き方はほとんど同じで、最後の`anyframe-action-execute`が`anyframe-action-put`、`anyframe-action-insert`に変わっただけ。

### 特定のディレクトリに独自のanyframe-widgetを配置する
~/.zshrcに関数をいっぱい追加すると、ファイルが大きくなりすぎて分かりにくいし、将来anyframeとかpecoを使うのをやめるときにどこを消せばよいのか分かりにくいので、別のファイルとして書きたいという人もいると思う。anyframeはそういうこともできるようになっていて、$fpathの通ったところにanyframe-functions/widgetsというディレクトリを作って、その下に自分の作ったanyframe-widgetを置けば良い。

$HOME/.zsh/anyframe-customの下に配置する場合の例は以下。

```
# ディレクトリがまだない場合は作成する
% mkdir -p $HOME/.zsh/anyframe-custom # <= このディレクトリ名は何でも良い
% cd $HOME/.zsh/anyframe-custom
% mkdir -p anyframe-functions/widgets # <= このディレクトリ名は固定
```

そして、anyframe-functions/widgetsの下にファイルを作って、独自のanyframe-widgetの中身を書く。ファイル名は何でも良くて、その名前が関数名になる。

例えば、標準で入っているanyframe-widget-execute-historyを自分で追加するとすると、次の内容のファイルを$HOME/.zsh/anyframe-custom/anyframe-functions/widgetsの下にanyframe-widget-execute-historyという名前で作成する。

```zsh:anyframe-widget-execute-history
history -n -r 1 \
  | anyframe-selector-auto \
  | anyframe-action-execute
```

`function`とかは書かずに、`{}`の中身だけ書くことに注意する。

使い方としては、これを~/.zshrcで、**`anyframe-init`を呼び出す前に**$fpathに指定して読み込む。

```zsh
fpath=($HOME/.zsh/anyframe(N-/) $fpath)
fpath=($HOME/.zsh/anyframe-custom(N-/) $fpath) # <= これを追加
autoload -Uz anyframe-init
anyframe-init
```

Antigenでインストールした人は**`antigen bundle mollifier/anyframe`の前に**$fpathに指定して読み込む。

```zsh
if [[ -f ~/.zsh/antigen/antigen.zsh ]]; then
  source ~/.zsh/antigen/antigen.zsh
  fpath=($HOME/.zsh/anyframe-custom(N-/) $fpath) # <= これを追加
  antigen bundle mollifier/anyframe
  antigen apply
fi
```

`$HOME/.zsh/anyframe-custom/anyframe-functions/widgets`ではなくて`$HOME/.zsh/anyframe-custom`をfpathに追加することに注意。

あとはaliasかbindkeyを設定する。

```zsh
alias ah=anyframe-widget-execute-history
# または
bindkey '^xr' anyframe-widget-execute-history
```

このときは`zle -N anyframe-widget-execute-history`というのは必要ない(anyframe-init内でそれをやってる)。

## おわりに
anyframeの使い方とカスタマイズ方法について解説しました。後半は少し難しいお話になったけど、単に使うだけならカスタマイズしなくてもキーバインド設定するだけで大丈夫なので、pecoとかpercolとか使って簡単に便利になりたい人は、ぜひ試してみてください!