zinit をしっかりと理解する
2021-12-03 追記
zdharma/zinit が吹っ飛んだので zdharma-continuum/zinit に移行しましょう。
tl; dr
Zinit Wiki の Introduction を読みながら、zinit
の書き方をキチンと理解する。
はじめに
今年の G/W は緊急事態宣言で何も予定が立てられませんでしたので、少し時間をかけて ~/.zshrc
を見直すことにしました。その過程で「俺、zinit
の書き方を全然理解できていないな」と感じたので、Zinit Wiki の Introduction をベースに、zinit
の書き方を理解していくことにしました。
実は一度も読んだことが無かった Zinit Wiki を読み進めて自分の理解を整理しただけなので、英語を読むのが全く苦ではない人は最初から Zinit Wiki を読みましょう。なにげに Zinit Wiki の日本語翻訳は無さそうなので、これから zinit
を始める人にとっては日本語で取っ付きやすいかと思ったので、ほとんど殴り書きですが公開することにしました。
隙あらば自分語り (読み飛ばして OK)
自分が SIer でインフラエンジニアとして設計・構築をやっていた頃は「いつどんな環境で作業をしてもオペレーションが出来るよう、普段使いの端末はなるべくカスタマイズしない」というポリシーでした。これは、面倒くさがりやの性格もあるし、大学卒業時に秘伝の ~/.emacs.el
を失ったことも大きかったし、大学の先輩でも前職の同僚でもあった @nizah の影響を受けたのもあります。
いずれにしても、情報系の大学にいた4年間+社会人11年半くらいは下記のようなポリシーでやってきたわけです。
-
~/.profile
や~/.bashrc
はほとんどカスタマイズしない - alias も
ls
に--color=auto --file-type
程度 - プロンプトも
git
付属のgit-prompt.sh
を利用する程度- Ubuntu のデフォルトの
~/.bashrc
で読み込まれます
- Ubuntu のデフォルトの
-
peco
とかfzf
は知ってるけど使わない -
zsh
のインストールやchsh
などもっての外
Immutable Infrastructure の初出は 2013 年らしいけど、当時はオンプレ中心にやっていた自分でもリモートで作業する必要性もなくなってきました。Ansible が 2.0 系になった頃かな、と。そこで 2016 年くらいに一度だけ zsh
を試したのですが、当時は WSL なんて便利なものもなく、Cygwin 上の zsh
は性能的に常用に堪えず、長らく bash を使い続けてきたのです。
# 正確に言うと、既に Build 2016 では発表されていて Insider Preview には来ていたかと思われるが、まさかこんなに実用的になるとは思っていなかった。
しかし、最近は WSL で実用的な性能の Linux 環境が手に入る上、転職して顧客環境における構築作業をやらなくなったこともあり、ちょうど 2020 年の夏くらいから zsh
を使い始めたのです。zsh はカスタマイズ性が高すぎてプラグインマネージャーなるものを使ってカスタマイズをするのが一般的で、その中でも zinit
というプラグインマネージャーがイケてるらしい。…ということで、zinit
の公式のサンプルを元に、ちょっとだけ誰かの dotfiles
から必要なものをコピってきたりして、比較的シンプルな ~/.zshrc
をよく分からんまま使っていたわけです。
プラグインのダウンロードと有効化
…というわけで、何はともあれ、プラグインのダウンロードと有効化から。以降、基本的に Zinit Wiki / Introduction を読み進めていきます。
zinit load zdharma/history-search-multi-word
zinit light zsh-users/zsh-syntax-highlighting
上記は GitHub 上の zdharma/history-search-multi-word や zsh-users/zsh-syntax-highlighting からプラグインを読み込みます。
下記に示す通り、プラグインの読み込み方法には 2 つのモードがあります。
-
zinit load
- トラッキング機能を有効にする。具体的には
zinit report
で一覧表示ができたり、zinit unload
でプラグインを無効化できる、等。
- トラッキング機能を有効にする。具体的には
-
zinit light
- トラッキング機能が無効になる。そのかわりに高速化されている。
zinit load
や zinit light
の後に続くのはデフォルトでは GitHub のリポジトリ名です (他の VCS の取り扱いについては…この先は君の目で確かめてくれ!)。
自分の場合、約1年くらい zinit report
は使ったことがないし、普段使いの機能は zinit unload
することもないです。唯一気になったのが zinit light
でも zinit delete --clean
で不要なプラグインを削除する機能が使えるか。これも、自分で挙動を確認した限りは不要なプラグインが zinit light
された状態ではもちろん動作しませんでした。しかし、~/.zshrc
から削除して zsh
を再起動してから zinit delete --clean
するば不要なプラグインとして削除できるようでした。自分としてはこれで必要十分です。
そのため、下記のように使い分けることにしました。
- 原則、
zinit light
を用いて~/.zshrc
を記載する - 一時的に試用するプラグインはコマンドライン上で
zinit load
で読み込む- 試用して問題がなければ
zinit light
を用いて~/.zshrc
に記載する - 問題があれば
zinit unload
してzinit delete
する
- 試用して問題がなければ
- 自動有効化/無効化(後述)を利用する場合は、例外的に
zinit load
を用いて~/.zshrc
に記載する
- 参考リンク
Oh My Zsh のプラグイン、Prezto のモジュールの再利用
Oh My Zsh と Prezto は zinit
よりも歴史も長い zsh
のプラグインマネージャーです。これらは標準的に用意されたプラグイン(Prezto の場合はモジュール)があり、自身のリポジトリ内に複数のプラグイン(モジュール)を保持しています。zinit
と比較するとプラグインマネージャーとしては起動時間は遅いですが[1]、これらのプラグイン(モジュール)にも、まだまだ使えるものが結構あります。そのため、zinit
ではこれらのプラグインが再利用できるようにしています。
zinit snippet https://github.com/robbyrussell/oh-my-zsh/raw/master/plugins/git/git.plugin.zsh
zinit snippet https://github.com/sorin-ionescu/prezto/blob/master/modules/helper/init.zsh
zinit snippet OMZ::plugins/git/git.plugin.zsh
zinit snippet PZT::modules/helper/init.zsh
前者の記法は、特定 URL のスクリプトをダウンロードして source
で取り込むだけなので、Oh My Zsh プラグインや Prezto モジュール以外の zsh
スクリプトにも使えます。
また、再利用例 1 と 2 は等価なので、Oh My Zsh や Prezto の標準的に用意されたプラグイン(モジュール)を再利用する場合は後者を使うと良いでしょう。このような Oh My Zsh や Prezto 向けのプレフィックスは他にもあるので[2]、~/.zshrc
の簡素化や可読性の観点で積極的に使用するのが良いです。
注意点として、Oh My Zsh や Prezto といったプラグインマネージャーが動作するわけではないので、スクリプト間の依存関係は ~/.zshrc
を書く人間側で解決する必要があります。例えば、再利用例に記載した git.plugin.zsh
は実際のソースコード[3]を見ると分かる通り、Oh my Zsh に含まれている lib/git.sh
に依存しています。そのため、実は再利用例そのままではエラーになってしまいます。これらを正常に動作させるためには、下記のように記述する必要があります。
zinit snippet OMZ::lib/git.zsh
zinit snippet OMZ::plugins/git/git.plugin.zsh
zinit snippet PZT::modules/helper/init.zsh
さらに、前述した Oh My Zsh や Prezto 向けのプレフィックスを積極的に使用すると下記のようにも記述できます。
zinit snippet OMZL::git.zsh
zinit snippet OMZP::git
zinit snippet PZTM::helper
依存関係の解決が面倒ではありますが、その分、Oh My Zsh や Prezto のプラグインマネージャーに依存しないので、メモリ消費量や実行時間を抑えることができます。
ちなみに、この文章を書いている時点で自分が使用しているのは OMZP::tmux
です。ZSH_TMUX_AUTOSTART=true
相当の機能を自分で実装するのが面倒だっただけなのですが。たくさんあり過ぎて、どれを使っていいものか分からんのですよねえ…。既に Oh My Zsh や Prezto を使っている人は移行の難易度が下がるでしょう。
zinit ice
の謎
多機能コマンド さて、zinit
を「さっぱり分からん」状態にしている元凶は、私は多機能すぎる zinit ice
コマンド、For 構文の 2 つにあると考えています。逆に言えば、この 2 つさえ抑えれば zinit
の設定は自ずと理解できると言ってもいいでしょう。
それでは、まず zinit ice
を解説していきます。このコマンドが何をやっているかというと、直後に行われる zinit load
、zinit light
および zinit snippet
の挙動を変更しています。
※Zinit Wiki では zinit ice svn pick"init.zsh"
を使った例を最初に提示していますが、この世は git
に飲み込まれたので、svn
については触れません。ここで行っている解説は後でやります。
書き方
では、下記の例をもとに zinit ice
の書き方と挙動について説明します。
zinit ice as"program" cp"httpstat.sh -> httpstat" pick"httpstat"
zinit light b4b4r07/httpstat
zinit ice
に渡されている as
, cp
, pick
のことを modifier と呼びます。その後、ダブルクォーテーションで囲われたものは modifier に対する引数です。zinit ice
はその他にも下記のような modifier と引数の書き方もサポートしていますが、引数がシンタックスハイライトのサポートを受けられないことから、上記の書き方が標準となっています。上と下を見比べてもらえれば一目瞭然でしょう。
zinit ice as:program cp:"httpstat.sh -> httpstat" pick:httpstat
zinit light b4b4r07/httpstat
zinit ice as=program cp="httpstat.sh -> httpstat" pick=httpstat
zinit light b4b4r07/httpstat
zinit ice --as=program --cp="httpstat.sh -> httpstat" --pick=httpstat
zinit light b4b4r07/httpstat
例における modifier の動作
zinit ice
コマンドには modifier とその引数があるということを理解したところで、実際の挙動を理解するためには、まず元のリポジトリを見てみるのが一番です。では、実際にアクセスしてみましょう。
zsh
プラグインマネージャーはプラグインを読み込む際、大雑把には git clone
してきて、<プラグイン名>.plugin.zsh
のようなファイルを探して source
します[4]。しかし、このリポジトリはドキュメント用の README.md
と demo.png
、スクリプト本体である httpstat.sh
だけで構成されています。zsh
プラグインマネージャーが読み込む <プラグイン名>.plugin.zsh
のようなファイルはありません。リポジトリの内容と README.md
を見てもらえば分かる通り、このリポジトリはいわゆる zsh
プラグインではなく、httpstat.sh
というスクリプトを提供するためのリポジトリです。
最初の as"program"
という modifier と引数は、こういった source
による読み込みが不要な、つまり zsh
プラグインではないリポジトリからスクリプト等を得る場合に使います。これは「この後に zinit load
または zinit light
するリポジトリは zsh
プラグインじゃないぞ」と zinit
に伝えている、と言い換えてもよいでしょう。
次に cp
という modifier です。この modifier は、引数によってリポジトリ内にある httpstat.sh
を httpstat
にコピーするように zinit
に伝えています。同様に mv
という modifier もありますが、後述する理由もあって cp
modifier を利用します。
最後に pick
という modifier です。この modifier は、引数で与えられた(先ほど httpstat.sh
から cp
された) httpstat
に実行権限を付け、$PATH
に追加するように zinit
に伝えています。
実際に、2 行のコマンドを実行してみてください。zinit ice
を実行した時点ではパッと見には何も起きていないように見えます。これは、zinit ice
コマンドが単体では何もしないためです。その後、zinit light b4b4r07/httpstat
をすると、リポジトリが git clone
された上で上記の処理が実行された旨の表示がされます。
デフォルトの git clone
先は ~/.zinit/plugins
です。また、リポジトリ名の /
は ---
に置換されて、~/.zinit/plugins/b4b4r07---httpstat/
となります。ls -la ~/.zinit/plugins/b4b4r07---httpstat/
を使ってアクセスすると、上記のような動作が行われていることが分かります。さらに、echo $PATH
することで $PATH
に追加されていることが分かるでしょう。
付け加えるなら、.git
ディレクトリがあることから、このディレクトリが引き続き git
管理下にあることが分かります。この状態のままリポジトリの upstream に更新があって git pull
した場合のことを想像してください。cp
であればコピーされた httpstat
が untracking file になるだけで問題なく git merge
できます。しかし、mv
modifier を利用した場合にはコンフリクトを起こしてしまう可能性があります。だから cp
modifier を利用する必要があったんですね (メガトン構文)。
ともあれ、これでユーザーは無事に httpstat
コマンドが使用できるようになりました。
mv
modifier と atpull
modifier
では mv
modifier は、必ず git
でコンフリクトを起こしてしまうので、使い物にならない modifier なのでしょうか。いいえ、そんなことはありません。こういった問題を避けるために、atpull
という modifier があります。
mv
を使った例を試す前に、先ほど zinit light
でダウンロード・有効化したプラグインを削除しておきます。
$ zinit delete b4b4r07/httpstat -y
Done (action executed, exit code: 0)
次に、下記に mv
modifier を使った例を示します。
zinit ice as"program" mv"httpstat.sh -> httpstat" pick"httpstat" \
atpull'!git reset --hard'
zinit light b4b4r07/httpstat
cp
modifier が mv
modifier に置き換えられています。また、(これはzinit の機能には関係ありませんが)行末の \
による改行を挟んで、atpull
modifier が追加されていることが分かります。
上記の 2 行のコマンドを実行した後、ls -la ~/.zinit/plugins/b4b4r07---httpstat/
でディレクトリにアクセスすると、httpstat.sh
がありません。これは cp
ではなく mv
modifier を使用したためファイル名が変更されたからです。この状態でリポジトリの upstream で更新が行われると、atpull
modifier が効果を発揮します。この modifier はその名前の通り、git pull
が行われる際に引数で指定したコマンドが実行される modifier、いわば hook です。引数の先頭に !
を付けた場合は git pull
する前、付けなかった場合は後に実行されます。この例では、git reset --hard
が git pull
する前に実行されます。そのため、zinit light
した際に mv
された httpstat.sh
および httpstat
は、git reset --hard
で元に戻されます。その後、コンフリクトを起こさずにリポジトリの upstream に追従した上で、再び mv
modifier でファイル名が変更され、pick
で実行権限を与えられます。
注意点として、インタラクティブなシェルでは !git
は直近に実行された git
から始まるコマンドに展開されてしまうため、modifier の引数がシングルクォーテーションで囲う必要があります。!
に限らず、modifier の引数で zsh
の特殊文字を利用するケースは気を付けましょう。
zinit snippet
と as
modifier
as"program"
は、zinit snippet
でも同様に使うこともできます。例では使用していませんが、この方法でも引き続き atpull
modifier が使えます。
zinit ice mv"httpstat.sh -> httpstat" pick"httpstat" as"program"
zinit snippet https://github.com/b4b4r07/httpstat/blob/master/httpstat.sh
…とはいえ、記述量や処理のシンプルさを考えると、自分の ~/.zshrc
では as"program"
の場合は cp
を使った書き方を利用するべきだと判断しました。
as"completion"
zinit ice
と zinit snippet
の組み合わせが有効なのは、もっぱら補完用スクリプトの読み込みです。下記に示すように as
modifier には引数として "completion"
を指定する記述方法で、docker
コマンド用の補完を行えるようになります。
zinit ice as"completion"
zinit snippet https://github.com/docker/cli/blob/master/contrib/completion/zsh/_docker
このファイルがそうであるように、ほとんどの補完用スクリプトは 1 つのファイルで構成されています。リポジトリごと git clone
する zinit load
や zinit load
より相性が良いでしょう。
blockf
modifier
この modifier は zsh
プラグインによる $fpath
の変更を禁止します。
古い zsh
プラグインだとプラグインマネージャーに無断で $fpath
を勝手に変更して補完機能を実装しているケースがあるらしいです。そういった動作を防いで zinit
側で完全に補完機能をコントロールするための modifier とのこと。補完系のプラグインマネージャーにはとりあえず blockf
を付けておけばよい印象(酷)。このあたりは、$fpath
や compinit
周りの動作をキチンと理解すると分かるのでしょう。
zinit ice blockf
zinit light zsh-users/zsh-completions
この後、Zinit Wiki では補完機能のコントロールについて記述がありますが、今のところ必要性を感じていないので省略します。考えなしに補完系のプラグインやスニペットをガンガン入れるとパフォーマンスに響くようなので、zsh
の起動や動作が遅いと感じるようになったら見直しましょう。
Turbo モード
wait
modifier を使った遅延読み込みのことを Turbo モードというようです。利用するためには zsh
5.3 以降が必要です。…って、いまどき、そんな古い zsh
使ってる環境なんかないでしょう。使用例では zinit load
になっていますが、zinit light
でも問題なく動作します。
PS1="READY > "
zinit ice wait'!0'
zinit load halfo/lambda-mod-zsh-theme
これ、使用例が分かりづらい上、既に instant prompt で romkatv/powerlevel10k を利用していたりすると思ったように挙動が確認できませんでした。動作を確認したい場合、zinit
をしただけの素の状態を用意すると良いです。そのためには、下記のような ~/.zshrc
にする必要があります。
### Added by Zinit's installer
if [[ ! -f $HOME/.zinit/bin/zinit.zsh ]]; then
print -P "%F{33}▓▒░ %F{220}Installing %F{33}DHARMA%F{220} Initiative Plugin Manager (%F{33}zdharma/zinit%F{220})…%f"
command mkdir -p "$HOME/.zinit" && command chmod g-rwX "$HOME/.zinit"
command git clone https://github.com/zdharma/zinit "$HOME/.zinit/bin" && \
print -P "%F{33}▓▒░ %F{34}Installation successful.%f%b" || \
print -P "%F{160}▓▒░ The clone has failed.%f%b"
fi
source "$HOME/.zinit/bin/zinit.zsh"
autoload -Uz _zinit
(( ${+_comps} )) && _comps[zinit]=_zinit
# Load a few important annexes, without Turbo
# (this is currently required for annexes)
zinit light-mode for \
zinit-zsh/z-a-rust \
zinit-zsh/z-a-as-monitor \
zinit-zsh/z-a-patch-dl \
zinit-zsh/z-a-bin-gem-node
### End of Zinit's installer chunk
実際にやってみると、PS1
を変更した時点でプロンプトが PS1
で指定したものになります。その後、zinit ice
と zinit load
を投入し、そのまま何度かエンターキーを叩いてみてください。バックグラウンドで halfo/lambda-mod-zsh-theme
をロードしながらもプロンプトは使える状態になっていることが分かります。wait
modifier の先頭の !
は、ロードが完了した後にプロンプトを再表示する、という意味です。使用例のプラグインがプロンプトを変更するものなので、読み込み完了後にプロンプトを再表示するために !
が入っているわけですね。例えば、補完プラグインのようにこっそりと遅延読み込みをしたい場合には !
は不要です。また、数字は何秒待ってから遅延読み込みをするかです。指定しない場合は 0
になるようです。
…というわけで Prezto のプロンプトの例はすっ飛ばして、次の使用例へ。
zinit ice wait lucid atload'_zsh_autosuggest_start'
zinit light zsh-users/zsh-autosuggestions
前述の通り、wait
は wait"0"
と同義です。また新しく lucid
という modifier が登場しました。これは、プラグインを読み込む際のプログレスバーの表示などを省略する modifier になります。そして atload
modifier は読み込みが完了した際に実行される関数を指定します。ここで指定された _zsh_autosuggest_start
という関数はまさに zsh-users/zsh-autosuggestions
の中で定義されており、補完を有効にするための関数です。
また、参考リンクに記載がありますが、wait"0a"
のように suffix を付けることで同じ待機時間でもプラグインの読み込み順序を明示的に指定できます。プラグイン同士に依存関係がある場合、suffix の指定をすると良いでしょう。
もう1つの難関: For 構文
zinit ice
を紹介した際に、下記のように記載しました。
zinit
を「さっぱり分からん」状態にしている元凶は、私は多機能すぎるzinit ice
コマンド、For 構文の 2 つにあると考えています
最初に紹介した zinit ice
の使用例は、zinit ice ...
した後に zinit load/light/snippet
する、というものでした。ここまでの説明で zinit ice
の動作を理解した今、実は For 構文は簡単に理解できます。なぜなら、この構文は zinit ice
した後に zinit load/light/snippet
する、という一連の流れを単に 1 つのコマンドに省略した構文だからです。先ほどの zsh-users/zsh-autosuggestions
の使用例を For 構文で書き直してみます。
zinit wait lucid atload'_zsh_autosuggest_start' light-mode for \
zsh-users/zsh-autosuggestions
最初に紹介した zinit ice
の使用例と違い、2 つの相違点があります。
-
ice
が不要になっている -
zinit light ...
を別コマンドではなく、light-mode for ...
と 1 行に続けている
この構文の良い点は、下記の 3 点です。
-
for
に続けて、複数のzsh
プラグインを書くことができる -
for
よりも前に書いた modifier はfor
に続けて書いた複数のzsh
プラグインのデフォルト値になる - その上で、
for
に続けて、プラグイン毎に modifier を上書きできる
文章を読むと難しく感じますが、実際の例を見ると簡単です。やっていきましょう。
zinit wait lucid for \
OMZ::lib/git.zsh \
atload"unalias grv" OMZ::plugins/git/git.plugin.zsh
この例では、下記のように動作します。
- 遅延読み込み (Turbo モード)で、プログレスバーなどを出力せず下記のプラグインを読み込む
OMZ::lib/git.zsh
OMZ::plugins/git/git.plugin.zsh
-
OMZ::plugins/git/git.plugin.zsh
の読み込み完了後にunalias grv
する
ね、簡単でしょう。これで、zinit
を「さっぱり分からん」状態にしている元凶の 2 つを抑えたことになります。
load
および unload
modifier によるプラグインの自動有効化・無効化
ここから先はちょっとした応用編。load
および unload
modifier を利用することで、特定条件下でのみプラグインを有効化したり無効化したりできます。
# Load when in ~/tmp
zinit ice load'![[ $PWD = */tmp* ]]' unload'![[ $PWD != */tmp* ]]' \
atload"!promptinit; prompt sprint3"
zinit load psprint/zprompts
# Load when NOT in ~/tmp
zinit ice load'![[ $PWD != */tmp* ]]' unload'![[ $PWD = */tmp* ]]'
zinit load russjohnson/angry-fly-zsh
例では For 構文は出てきていませんが、zinit ice
をちゃんとマスターしていれば、なんとなくやっていることが分かるはずです。この例では、load
および unload
modifier に指定した条件を満たした場合に、自動的にプラグインの zinit load
または zinit unload
が行われます。同じ遅延読み込みでも、wait
は ~/.zshrc
読み込み時にのみ行われますが、load
や unload
は常時判定が行われるという違いがあります。一方、load
modifier で指定した条件を満たさない限り zinit load
はされませんから、zsh
の起動速度には大きく影響を与えない、という点では wait
と同じです。
注意点としては、条件を満たした場合にのみ load
が行われるので、プラグインの内容によっては lucid
modifier を上手く組み合わせる必要があります。また、思い出してください。zinit light
と zinit load
の違いを説明した際に書いた通り、zinit unload
するには zinit light
で読み込んではいけません。
最後に
いかがだったでしょうか。この文章は Zinit Wiki の Introduction をザックリと意訳しただけです。たったそれだけの事ですが、呪文のように見えた他人の dotfiles
の ~/.zshrc
に記載された zinit
コマンドの意味が、なんとなく掴めるようになったのではないでしょうか。
この後、zinit
を更に使いこなすために、皆さんが理解するべき要素は大きく 2 つあります。
- Ice Modifiers
- Zinit Wiki、Ice Modifiers の章
-
zdharma/zinit/
内README.md
の Usage / Ice Modifiers
- Annexes プラグイン
- Zinit Wiki、Zinit Extensions 章
- 各 Annexes プラグインの README.md
- 特に、z-a-bin-gem-node は非常によく使われているので読んでおいたほうがよい
1 については言うまでもありません。ice
を制するものは zinit
を制する。ここで紹介した modifier が全てではないので、より多くの ice modifier を理解するべきです。差し当たり、多くの dotfiles
で用いられていて、この記事で説明できなかった下記の modifier の挙動は抑えておいたほうが良いでしょう。
- Cloning Options
from
bpick
- Selection of Files
src
multisrc
- Conditional Loading
if
has
- Command Execution After Cloning, Updating or Loading (hook)
atclone
atpull
atinit
atload
- Others
as"null"
id-as
2 については、Annexes の名の通り、zinit
に付属しているプラグイン群です (付属と言いつつ別のリポジトリですが、zinit
のインストーラーが導入するか質問をしてきます)。特に、zinit-zsh/z-a-bin-gem-node
は sbin
, fbin
といった ice modifier を実装しており、他人の dotfiles
を理解する上では必要になります。また、メインの開発言語として Rust を使用している場合、exa のような Rust 製ツールを Cargo で管理できる zinit-zsh/z-a-rust
にも目を通しておくと良いでしょう。
それでは、皆さんに良い zinit
ライフがあらんことを。
Discussion
New version: https://z-shell.pages.dev/ja
Help translate: https://crowdin.com/project/z-shell-zi