Edited at
zshDay 2

おい、Antigen もいいけど zplug 使えよ

注意

この記事はv1の情報となっております。

最新の情報は日本語ドキュメントを参考にしてください。


zsh のプラグインマネージャといえば Antigen です。zsh 界隈ではプラグイン文化がそこまで強くない印象を受けます。便利なプラグイン1やコマンド2がたくさんあるのに、Vim のそれほど盛り上がっていないのはプラグインマネージャが弱いからではないでしょうか。


Antigen とその弱点

Antigen はよく使われるし実際便利ですが、いくつかの欠点があります。


  1. プラグインの読み込みが遅いこと

  2. プラグインの管理が最低限しかできないこと

  3. プラグインが antigen に対応していないといけないこと

これらの問題点の幾つかは重厚なる zsh 使いの @mollifier さんも以下のスライドで言及しています。


第二の Antigen

1つ目の問題点は Antigen ユーザの中でも結構上がってきているようで、Antigen alternative として作られたほとんどのプラグインマネージャはここを改良してきています。

一方で、2つ目の問題は未だに解決されてないように感じます。Vim のプラグインマネージャーができるように zsh でも依存関係を保ったインストールやブランチロック・リビジョンロックしたいですよね。

そして、3つ目も問題です。基本的に、リポジトリルートに *.plugin.zshinit.zsh がなければ Antigen は読みに行きません。たとえば、holman/spark 3など bash で書かれたコマンドやプラグインは(zsh 上でも動くのに)このようなファイルを置いていないことが多く(開発者が悪いわけではない)、結局はそれらについてユーザが別個に手動で管理する他ありません。また、プラグイン開発者もこれらのファイルを作成することを強いられます。

以下の Pull Request は emojify という bash 製ツールについて、「Antigen でも管理できるように *.plugin.zsh を作ってくれ」というものです。

現状ではこのようにプラグイン/コマンド開発者が対応を迫られている状態です(それに、bash ツールなのにプロジェクトルートに *.zsh なファイル置くのって嫌じゃないですか)。


プラグインマネージャとは


そもそもプラグインを管理するにあたり、ユーザは何をしたいか


自分なりのポイントですが、大まかには以下のことだと思っています。


  • クローン・インストールを簡単にしたい

  • すべてのマシンで同じプラグインの状態を保ちたい(再現性)

  • 特定のバージョンで固定したい(安定版ブランチ)

  • プラグインを作るときに、プラグインマネージャのことを意識しなくてよい

  • 全体的に楽したい

特に最後ですね。楽をしたい(*.plugin.zsh なんて作りたくない!)、それに尽きる気がします。そのためにはプラグインマネージャはユーザにとことん使われる設計でなくてはならないはずです。

現状 Antigen (とその alternative)しかありません(超重量級な oh-my-zsh などは除く。むしろあれはプラグインマネージャというより「ぼくのかんがえたさいきょうのせってい」集4)。zsh 界の殺伐としたプラグインマネージャを革新するプラグインマネージャを誰かが、重い腰を上げてやってやる必要があります。私の腰は軽いのでやってみました。


zplug

そこで今回、できる限り万能に近い・完全に新しいプラグインマネージャをイチから開発しました(既存のプラグインマネージャはあくまでも Antigen alternative で根本的に改善されていなかったため)。

新しいプラグインマネージャを作るにあたり、Antigen とその alternative はもちろん、NeoBundle や vim-plug といった Vim プラグインマネージャの実装も参考にしました。

次の gif アニメはプラグインをインストールするときの様子です。

ほとんど同じ時間でクローニングできていて、一番時間を食っている最後の junegunn/fzf-bin ですが、バイナリである(サイズが他より大きい)ため他より時間がかかっているだけです。それでも、トータルタイムは約8秒です。

本来ならば一つ一つクローンするため、プラグイン数が増えるにつれてダウンロード時間も増えていきます。しかし、zplug では非同期でインストールしているため、ここまで高速に処理を完了させることができます。


特徴


  • 何でも管理できる(コマンド、Gist、oh-my-zsh のプラグイン、GitHub Releases のバイナリ)

  • 非同期インストール/アップデート

  • ブランチロック・リビジョンロック

  • インストール後の コマンド実行 hook あり

  • oh-my-zsh などの外部プラグインをサポート

  • バイナリを管理できる(GitHub Releases)

  • shallow clone できる(オン・オフ)

  • 依存関係の記述ができる

  • ユーザはプラグインマネージャのことを考えなくていい(*.plugin.zsh なにそれ)

  • 選択的インターフェイスとの連携(fzf, peco, percol, zaw)

など


インストール

シングルファイルの構成のため、curlwgen などで簡単・高速にダウンロードできます。git.io/zplug は zplug の raw.github.com へのリダイレクトです。

$ curl -fLo ~/.zplug/zplug --create-dirs git.io/zplug


設定例

こんな感じで .zshrc に書いてください。Vim などと似たような感じです。

source ~/.zplug/zplug

# 「ユーザ名/リポジトリ名」で記述し、ダブルクォートで見やすく括る(括らなくてもいい)
zplug "zsh-users/zsh-syntax-highlighting"
zplug "zsh-users/zsh-history-substring-search"

# junegunn/dotfiles にある bin の中の vimcat をコマンドとして管理する
zplug "junegunn/dotfiles", as:command, of:bin/vimcat

# tcnksm/docker-alias にある zshrc をプラグインとして管理する
# as: のデフォルトは plugin なので省力もできる
zplug "tcnksm/docker-alias", of:zshrc, as:plugin

# frozen: を指定すると全体アップデートのときアップデートしなくなる(デフォルトは0)
zplug "k4rthik/git-cal", as:command, frozen:1

# from: で特殊ケースを扱える
# gh-r を指定すると GitHub Releases から取ってくる
# of: で amd64 とかするとそれを持ってくる(指定しないかぎりOSにあったものを自動で選ぶ)
# コマンド化するときに file: でリネームできる(この例では fzf-bin を fzf にしてる)
zplug "junegunn/fzf-bin", \
as:command, \
from:gh-r, \
file:fzf

# from: では gh-r の他に oh-my-zsh と gist が使える
# oh-my-zsh を指定すると oh-my-zsh のリポジトリにある plugin/ 以下を
# コマンド/プラグインとして管理することができる
zplug "plugins/git", from:oh-my-zsh

# ビルド用 hook になっていて、この例ではクローン成功時に make install する
# シェルコマンドなら何でも受け付けるので "echo OK" などでも可
zplug "tj/n", do:"make install"

# ブランチロック・リビジョンロック
# at: はブランチとタグをサポートしている
zplug "b4b4r07/enhancd", at:v1
zplug "mollifier/anyframe", commit:4c23cb60

# if: を指定すると真のときのみロードを行う(クローンはする)
zplug "hchbaw/opp.zsh", if:"(( ${ZSH_VERSION%%.*} < 5 ))"

# from: では gist を指定することができる
# gist のときもリポジトリと同様にタグを使うことができる
zplug "b4b4r07/79ee61f7c140c63d2786", \
from:gist, \
as:command, \
of:get_last_pane_path.sh

# パイプで依存関係を表現できる
# 依存関係はパイプの流れのまま
# この例では emoji-cli は jq に依存する
zplug "stedolan/jq", \
as:command, \
file:jq, \
from:gh-r \
| zplug "b4b4r07/emoji-cli"

# check コマンドで未インストール項目があるかどうか verbose にチェックし
# false のとき(つまり未インストール項目がある)y/N プロンプトで
# インストールする
if ! zplug check --verbose; then
printf "Install? [y/N]: "
if read -q; then
echo; zplug install
fi
fi

# プラグインを読み込み、コマンドにパスを通す
zplug load --verbose


zplug のコマンドについて

コマンド
説明
オプション

install
非同期でインストールする

--verbose,--select

load
インストールしたものをロードする
--select

list
インストールしたものをリストする

--verbose,--select

update
非同期でアップデートする

--self,--select

check
未インストール項目があるかどうかチェックする

--verbose,--select

status
各リポジトリが最新かどうかチェックする
--select


zplug のタグについて

タグ(tags)というものを用いて、プラグインごとの属性情報を設定します。使用できるタグは現在(v0.1.9)、以下のとおりです。

タグ
説明
値 (デフォルト値)

as
コマンドかプラグインかを指定する

plugin,command (plugin)
as:command

of

source するファイルへの相対パスかパスを通すコマンドへの相対パスを指定する(glob パターンでも可)
- (-)

of:bin,of:*.zsh

from
外部からの取得を行う

gh-r,gist,oh-my-zsh (-)
from:gh-r

at
ブランチ/タグを指定したインストールをサポートする
ブランチかタグの名前 (master)
at:v1.5.6

file
リネームしたい名前(コマンド時に有用)
好きなファイル名 (-)
file:fzf

dir
インストール先のディレクトリパス
READ ONLY
-

if
真のときダウンロードしたコマンド/プラグインを有効化する
真か偽 (-)
if:"[ -d ~/.zsh ]"

do
インストール後に実行するコマンド
コマンド (-)
do:make install

frozen
直接指定しないかぎりアップデートを禁止する
0か1 (0)
frozen:1

commit
コミットを指定してインストールする ($ZPLUG_SHALLOW が真かどうかに関わらず)
コミットのハッシュ値 (-)
commit:4428d48

on
依存関係
READ ONLY
-

これらのタグを巧みに設定することで、プラグインがどのような形式で用意されていてもクローンし、読み込みことが可能です。

また、プラグインとしてインストールした場合、source されます。コマンドとしてインストールされた場合は以下のようなルールでロードされます。


  • リポジトリ(例えば hogehoge)と同じ名前のコマンドを探す

  • なければ of タグで指定されたディレクトリにある hogehoge コマンドを探す


  • of で指定されたのがディレクトリでなければ指定されたファイルをコマンドとする

  • それもなければリポジトリ自体をコマンドと疑う(バイナリインストールの場合)

要するによしなにコマンドファイルを探してくれます。探すことができた場合、そのファイルを $ZPLUG_HOME/bin にシンボリックリンクし、bin をパスに追加します。

$ZPLUG_HOME

|-- bin
| `-- some_command -> ../repos/username_A/reponame1/some_command
`-- repos
|-- username_A
| |-- reponame1
| | |-- README.md
| | `-- some_command
| `-- reponame2
| |-- README.md
| `-- some_plugin.zsh
`-- username_B
`-- reponame1


zplug をコマンドラインで利用する

zplug が設定ファイル(.zshrc)に書かれたプラグインなどを対象に扱います。しかし、「プラグインを新しく追加するたびにエディタで .zshrc 開くのめんどくせー」という物臭な方のために、コマンドラインからも利用できるようになっています。

実際にこっちのほうがコマンドライン補完を利用して、zplug の記述方法を覚えていなくとも簡単に設定することができるので便利です。そして、打ち込んだ設定は $ZPLUG_EXTERNAL に保存されています。これはコマンドラインから登録したときにその内容を吐き出すために使用されるファイルになっていて、zplug load のときに一緒に読み込まれます。


zplug の環境変数について



  • $ZPLUG_HOME~/.zplug

    ダウンロードされるディレクトリです。すべてのソースは一度 repos にダウンロードされ、同階層の bin にはシンボリックリンクとしたコマンドが配置されます。




  • $ZPLUG_THREADS(16)

    並列処理する際のサブプロセス数を設定します。大きくすればするほどプロセスが起動し、それだけ同時に処理が実行されます。




  • $ZPLUG_PROTOCOL(HTTPS)

    git clone するときに使用するプロトコルを設定します。HTTPS か SSH が使用できますが、GitHub でも推奨されている HTTPS をデフォルトとしています。




  • $ZPLUG_SHALLOW(true)

    shallow clone をするかどうかです。プラグイン開発者でない限り、過去のコミットを遡ることは少ないためデフォルトではオンです。また shallow clone にしておくと一般に高速でクローンできます。また現在、プラグインごとの shallow clone はできません。




  • ZPLUG_FILTER (fzf-tmux:fzf:peco:percol:zaw)

    --select が指定されたときに有効なオプションで、コロンで区切られたパスに存在する最初の要素(fzf-tmux があればそれを、なければ次を…)をインタラクティブフィルタとして扱います。コロン区切りでさえあれば、fzf-tmux -d "10%":/path/to/peco:my peco こんなふうに書くコマンドのオプションやフルパス指定なども可能です。




  • ZPLUG_EXTERNAL ($ZPLUG_HOME/init.zsh)

    コマンドラインからプラグインを追加したときに、その設定内容を書き溜めるためのファイルです。




最後に

次世代のプラグインマネージャー、zplug を紹介しました。

一通りの実装が終わったところです。今後、破壊的なアップデートは極力行わない予定です。バグフィックス/要望などについて issue で報告いただけたら幸いです。


zplug の今後


  • 新機能の追加などは現状では考えておらず、保守と互換性を重視していこうと思います


  • of: タグについてグロブによるパターン(例えば *.shzsh/*.zsh など)を受け付けますが、ブラケット(bin/{aCmd,bCmd})などの展開は受理しません(これはパースの問題で、タグのデリミタをカンマとし単純にスプリットしている ${(s:,:)... のが原因です。ここらへんの使い勝手の向上を図りたいです。


参考





  1. awesome-zsh-plugins 



  2. awesome-shell 



  3. spark は brew install spark できるので例としては良くなかった 



  4. zsh 初心者が入門するには良いツールだと思う(個人的には好まない)