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

  • 133
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

10.gif

これは何?

zshの操作で、pecoとかpercolとか、Anything風インターフェースで選択するやつがよく使われてる。これを使うと、例えばコマンドライン履歴からインクリメンタルに検索して、それを実行、とかできるようになる。

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

それで、そういうシェルの処理を自分で書きたくない人向けにanyframeというのを作った。

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

特徴

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

インストール

まずは先にpecopercolfzfをインストールしておく。どれかひとつ好きなやつでOK。その後anyframeを次の方法でインストールする。

1. 手動でインストールする場合

手動でインストールする場合、$fpathの通ったところにanyframe以下のファイルを全部配置する。$HOME/.zsh/の下にインストールする場合の例は以下。

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

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

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

2. Antigenでインストールする場合

Antigenを使ってる人は、~/.zshrcに1行追加するだけでOK。

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

使い方

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

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

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で短い名前をつけてそれを呼び出しても良い。

alias aw=anyframe-widget-select-widget

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

anyframe-widget一覧

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コマンドで管理しているリポジトリに移動する(ghqが必要)
anyframe-widget-insert-filename ファイル名をコマンドラインに挿入する
anyframe-widget-tmux-attach tmuxセッションを選んでアタッチする
anyframe-widget-select-widget anyframe-widgetから選んでそれを実行する

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

anyframe-widget-cdr

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

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

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

参考 : zshでcdの履歴管理に標準添付のcdrを使う - @znz blog

anyframe-widget-cd-ghq-repository

ghqコマンドで管理しているリポジトリに移動する。別途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に書く。

zstyle ":anyframe:selector:" use peco

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

zstyle ":anyframe:selector:" use percol

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

zstyle ":anyframe:selector:" use fzf

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

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

# 例1: pecoで--no-ignore-caseオプションを指定する
zstyle ":anyframe:selector:peco:" command 'peco --no-ignore-case'
# 例2: percolで--case-sensitiveオプションを指定する
zstyle ":anyframe:selector:percol:" command 'percol --case-sensitive'
# 例3: fzfで--extendedオプションを指定する
zstyle ":anyframe:selector:fzf:" command 'fzf --extended'

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

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.のパターンのときは、基本的には次のような形の関数を書く。

# 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(コマンドライン履歴から選んで実行する)を自分で書くとすると、こんな感じ。

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

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

alias ah=anyframe-widget-execute-history

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

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

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する)を自分で書くとすると、こんな感じ。

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. 選択した結果をコマンドラインのカーソル位置に挿入する」になると、それぞれ次のようになる。

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

書き方はほとんど同じで、最後のanyframe-action-executeanyframe-action-putanyframe-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という名前で作成する。

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

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

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

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に指定して読み込む。

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を設定する。

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

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

おわりに

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