最近 relastle/pmy というツールにハマっています。
— キラキラの虚無感 (@Ryooooooga) 2020年12月25日
(邪悪な正規表現を代償に)いろいろできる pic.twitter.com/Q0GMzD76zT
— キラキラの虚無感 (@Ryooooooga) 2020年12月25日
Zshの補完機能の欠点
zshの補完機能は強力ですが、カスタマイズ性や拡張の容易さに難があります。
また、やろうとおもえば補完関数内でfzfなどのFuzzy Finderを用いてインタラクティブな補完を実現できますが、fzfの--multi
オプションなどを用いた複数結果の展開などはできません。
デフォルトの補完機能 (ZLEのexpand-or-complete
)を上書きするような形でそれらも実現はできますが、やはり記述の複雑さや保守性の面からつらいものがあります。
そこで、pmyというCLIツールとfzfを組み合わせてインタラクティブな補完を実現しました。 利用時の様子は上のツイートのとおりです。
fzf
fzfはGo製のFuzzy Finderです。
日本では同じくGo製のFuzzy Finderであるpecoの方が知名度が高い印象がありますが、preview windowはpecoにはない魅力です。
preview windowは、任意のコマンドなどを用いて検索候補のプレビューを表示できる機能です。
上の図ではbatを用いてシンタックスハイライト付きでファイルの中身を表示しています。他にもls
やtree
のようなコマンドを使ってディレクトリの構造を表示する、あるいはgit show
でコミットの情報を表示するなど、用途の幅が広い便利な機能です。
私の場合は以下のような用途にfzfを用いていました。
history
を使ったコマンドヒストリーの検索chpwd_recent_dirs
を使ったディレクトリ移動履歴の検索ghq
を使ったリポジトリ間の移動- テキストエディタを開く際のファイルの選択
docker rm
やdocker rmi
の際に削除するイメージの選択
1 ~ 3 はFuzzy Finderの利用例で頻繁に挙げられる用途なので特筆すべきことはありません。
4 に関しては以下のような$EDITOR
(私の場合はnvim) のラッパー関数 e
を用意していました。
e() { if [ $# -eq 0 ]; then local selected="$(fd --hidden --color=always --exclude='.git' --type=f | fzf --exit-0 --multi --preview="fzf-preview-file '{}'" --preview-window="right:60%")" [ -n "$selected" ] && "$EDITOR" -- ${(f)selected} else command "$EDITOR" "$@" fi }
これは、e
に引数が指定されていた場合は通常通りに $EDITOR
を起動し、引数なしで呼び出された場合にはカレントディレクトリ以下のファイルから fzf で選択したものを $EDITOR
の引数に与えるものです (かなり便利)。
これまでの不満点
上のような fzf の活用は、実際に便利で重宝していたのですが、若干の物足りなさを感じていました。 具体的には以下のような点です。
e
で選択したファイルがコマンドヒストリーに記録されない上の関数ではどのようなファイルを選択してもコマンドヒストリーに
e
しか残りません。 そのため、以前に開いたファイルを再び開こうと思った場合にも再度検索を行わなければならず、煩わしさを感じていました。git switch
などの既存のコマンドの補完にfzfを用いたいGitなどの既存のコマンドやそのサブコマンドの補完にfzfを用いたい場合、逐一ラッパースクリプトを用意しなければならず面倒でした。
余談
fzfには補完用のスクリプトが用意されており、これを使用することでfzfを使った補完が可能になっていました。
参考: https://github.com/junegunn/fzf#fuzzy-completion-for-bash-and-zsh
また、この機構を利用してGitなどの補完を実現したプロジェクトに chitoku-k/fzf-zsh-completions があります。
今回、これらのような既存のものを利用しなかったのは以下の理由のためです。
- 自分用の環境に合わせてカスタマイズするのが面倒
- 補完のトリガーである
**<TAB>
というシーケンスに慣れない
pmy
というわけで本題です。
pmyはFuzzy Finderを用いたzshの補完を実現するためのCLIツールです。
YAML (もしくはJSON) で設定ファイルを書くことにより、冒頭で示したようなfuzzyな検索と補完が可能になります。
インストール方法
go get
を用いたビルド方法
$ go get -u github.com/relastle/pmy $ eval "$(pmy init)"
- zinit を用いたプリビルドバイナリのダウンロード
zinit ice lucid wait"0" as"program" from"gh-r" \ pick"pmy*/pmy" \ atload'eval "$(pmy init)"' zinit light 'relastle/pmy'
設定ファイルの編集
pmyは、デフォルトでは ~/.pmy/rules
以下に配置された設定ファイルを参照します。
例えば、冒頭のツイートのような補完は下の設定で再現できます。
# ~/.pmy/rules/_.yaml - description: editor regexp-left: ^\s*(?P<cmd>(nvim|vim|vi))\s+(?P<args>\S+\s+)*)(?P<query>\S*)$ cmd-groups: - stmt: fd --color=always --hidden --type=f after: paste -s -d ' ' - fuzzy-finder-cmd: fzf --multi --preview="bat {}" --query=<query> buffer-left: '<cmd> <args>' buffer-right: '[]'
上の場合、ファイルパスやその一部を雑に入力しただけで候補に出現するため、通常のzsh補完よりも効率的に目的のファイルにたどり着けます。
他にも以下のような設定でpmyを運用しています。
https://github.com/Ryooooooga/dotfiles/tree/main/config/pmy/rules
設定ファイルの参照ディレクトリなどの変更
pmy はデフォルトでは ~/.pmy
以下を作業ディレクトリとして用います。
しかし、私はホームディレクトリが雑多なdotfilesで汚れることをあまり好ましく思っていません。
幸いにも、pmyは環境変数によって作業ディレクトリを変えられるため、XDG風のファイルの配置に変更しました。 また、補完のトリガーになるキー(デフォルトではCtrl+Space)も変えられます。
# 補完のトリガーを変更 (Ctrl+P) export PMY_TRIGGER_KEY="^P" # ルールファイルの参照先を変更 export PMY_RULE_PATH="$XDG_CONFIG_HOME/pmy/rules" export PMY_SNIPPET_PATH="$XDG_CONFIG_HOME/pmy/snippets" # ログファイルの出力先を変更 export PMY_LOG_PATH="$XDG_CACHE_HOME/pmy/log.txt" eval "$(pmy init)"
pmyの利点
pmyを利用することには以下のような利点があります。
pmyの欠点
一方で、pmyもまた万能ではありません。 しばらく利用してみて感じた欠点を以下に挙げます。
pmyはかなり独特でおもしろいツールなので、fzfがすきなzshユーザの方は一回試してみるといいんじゃないでしょうか。