Terminal上でSnippetを実現する ~ zle(Zsh Line Editor) × fzf ~

f:id:rasukarusan:20200419192018p:plain:w400

ワンライナー打つのしんどい

CSV落として該当の列だけ抜き出すときにcat hoge.csv | awk ...とか、fzfでプレビューして〇〇するみたいなときにcat hoge.txt | fzf --preview...など、 大体打つコマンドって決まってくると思う。

.zshrc.shファイルなどに直接Shellを書いていくときは、エディタのコード補完が効くので特に面倒ではないが、 Terminal上でサクッとワンライナーで実行したいときに、いちいち定型文を書いていくのがとても面倒である。
「Terminalに打っている段階でもSnippetみたいな補完ができたらいいな...」と思って、その方法について考えてみる。

zle(Zsh Line Editor)とfzfの組み合わせで実現できる

こんな感じで打っている途中にfzfで補完を出して、選択したらTerminalに打ち込まれるようになる。

https://github.com/Rasukarusan/blog-assets/blob/master/terminal-snippet/demo.gif?raw=true
Snippetっぽい動きを実現

zleってなんなの?

下記のサイトが死ぬほど参考になるのでぜひ見てほしい。

ようはzshに搭載されている機能で、Terminal上の文字列やカーソル位置をShellScript上で操作できる。
Terminal上の文字列やカーソルはtputコマンドを駆使すればやれんこともないが、キーバインドに設定したりコマンド実行後に〇〇するなど、何かの動作をhookしたいときにzleを使うのが便利なので覚えておいて損なし。

もっと細かく知りたい人はman zshzleで見れる。Web版もある。

Snippetっぽいものを実現する

.zshrcに下記を記載して、source ~/.zshrcすれば終了。Terminal上でCtrl-oを押せばSnippetが出てくる。

# 自作ウィジェット
show_snippets() {
    local snippets=$(cat ~/zsh_snippet | fzf | cut -d':' -f2-)
    LBUFFER="${LBUFFER}${snippets}"
    zle reset-prompt
}
# 自作ウィジェットを登録
zle -N show_snippets
# 自作ウィジェットを`Ctrl-o`で呼び出す
bindkey '^o' show_snippets

Snippetファイルとして~/zsh_snippetを作成しておく。自分の場合はこんな感じ。

awk:'{print $1}'
fzf:--prompt ""
fzf:--preview
fzf:--preview-window=down:40%
printf:printf "\e[33m${1}\e[m\n"

このファイルをcat ~/zsh_snippet | fzf | cut -d':' -f2-で読み込んでるっていう感じ。
jsonファイルにしてjqコマンド使ってfzfのpreviewにコマンドの説明を出す、みたいにちょっとリッチにしてもいいと思う。
まああくまで補助的なものだしParser的な問題になってくるのでそんなにこだわらなくてもいいんじゃないかな。シンプルイズベスト。

ちょびっと解説

肝は$LBUFFERっていう変数。これはzshで最初から用意されているもので、Left BUFFERの略。
何が入っているかというと、現在のカーソル位置より左にある文字列が入っている。

f:id:rasukarusan:20200419190245p:plain
この状態だったらLBUFFER="cat ~/zsh_snippet"となる

なのでLBUFFER="${LBUFFER}${snippets}"の部分で
「今まで打っていたコマンド」+「fzfで選択した文字列」
を繋げてるって感じ。
他にも色々変数は用意されているので詳しくはman zshzle

あとキーバインド枯渇問題があるが、bindkey '^x^o' show_snippetsのようにすればCtrl-xoと複数の文字で登録できるので自分なりのprefixを設定すれば良さそう。
bindkeyで今設定されているものが見れるので自作のウィジェットを登録する前に要確認。

終わり

  1. 自作関数つくる
  2. ウィジェットとして登録する
  3. キーバインドに登録する

っていうめちゃくちゃ簡単な3ステップなのでスーパーベリーイージーマーケット。
当初はHyperのプラグインとして作るしかないかなあと思っていたがzleとfzfだけで十分機能を満たせた。
VimのSnippetっぽく$1に設定したところにジャンプするとか、直前の単語であらかじめsnippetを絞るとかもっとリッチにはできそうだがとりあえずこの辺に留める。