プロジェクトにインストールしたコマンドのシェル補完問題
はじめに
この記事はシェルにzshを利用している読者を想定しています。
mise+CLI補完の問題について説明していますが、他のプロジェクト・パッケージ管理ツールに関しても同じ問題を同様の方法で解決できます。
miseでプロジェクトにコマンドをインストールしたとき、普通の方法で.zshrc
を編集して補完を有効化すると後述の問題が発生します。
今回の記事では、miseでインストールしたコマンドのシェル補完を利用するときの個人的ベストプラクティスを紹介します。
以降、補完を利用したいmiseでインストールするコマンドの一つとして、Pythonのプロジェクト・パッケージ管理ツールuv
を例に解説していきます。他のコマンドについてはuv generate-shell-completion zsh
の部分をそのコマンドの補完関数を出力するコマンド(以降補完コマンドと呼ぶ)に置き換えて読んでください。
コマンドとその補完コマンドの例
コマンド | 補完コマンド |
---|---|
uv |
uv generate-shell-completion zsh |
uvx |
uvx --generate-shell-completion zsh |
task |
task --completion zsh |
rustup |
rustup completions zsh |
cargo |
rustup completions zsh cargo |
npm |
npm completion |
結論だけ知りたい方は、遅延評価する方法まで読み飛ばしてください。
miseとは?
mise(ミーズ)は開発ツールのバージョン管理を行うツール。以下のような特徴があります。
- プロジェクトごとに違うバージョンのツールを利用できる
- 各プロジェクトでユーザー間でツールのバージョンを共有できる
zshにおけるシェル補完について
zshでは~/.zshrc
に以下の設定を追加したあと各コマンドの補完の設定をすることでTABキーで補完が行えます。
autoload -Uz compinit
compinit
Homebrewでインストールしたコマンドなどは自動で補完の設定がされるため、上記の設定を追加するだけでほとんどのコマンドに対する自動補完の恩恵を受けられます。
通常の方法
通常、自前でインストールしたコマンドは以下の手続きで補完を有効化できます。
~/.zshrc
に以下の行を追加。
eval "$(uv generate-shell-completion zsh)"
問題点
uv
を使用するプロジェクト以外のディレクトリでシェルを起動した場合.zshrc
が読み込まれるタイミングでuv
は存在しないため、シェルを起動するときにエラーが発生。
コマンドの存在を判定してから補完を有効にする
~/.zshrc
に以下の行を追加。
if command -v "uv" &> /dev/null; then
eval "$(uv generate-shell-completion zsh)"
fi
uv
コマンドの存在を確認してから補完を有効にするため、エラーは発生しません。
問題点
uv
を使用するプロジェクト以外のディレクトリでシェルを起動した場合、uv
が使用できるディレクトリに移動しても補完が有効にならない。
site-functions以下に書き込む方法
uv
が使用できる環境で以下のコマンドを実行。
sudo mkdir -p /usr/local/share/zsh/site-functions
uv generate-shell-completion zsh | sudo tee /usr/local/share/zsh/site-functions/_uv
uv
コマンドが利用できるディレクトリで補完コマンドを事前に呼び出すため、uv
を使用するプロジェクト以外のディレクトリでシェルを起動した場合でもuv
が使用できるディレクトリに移動すれば補完が有効になります。
問題点
uv
の補完コマンドが更新される可能性があるためuv
のバージョンアップデートのたびに自分で上述のコマンドの再実行が必要。
遅延評価する方法
遅延評価を行うことで補完が必要なタイミングで補完コマンドが呼ばれるようにします。基本的に補完が必要なタイミングでは補完コマンドも利用できる状態なので前述の問題をすべて解決できます。
~/.zshrc
に以下の行を追加。
on_demand_completion() {
local cmd_name=$1
local completion_command=$2
local function_name="_${cmd_name}"
local comp_cmd_name="${completion_command%% *}"
eval "function $function_name() {
if ! command -v "$comp_cmd_name" &> /dev/null; then
return
fi
unfunction '$function_name'
eval \"\$(eval $completion_command)\"
\$_comps[$cmd_name]
}"
compdef $function_name $cmd_name
}
on_demand_completion 'uv' 'uv generate-shell-completion zsh'
以下を参考にしています。
参考元では、zshの起動の高速化のために、補完コマンドを遅延評価しています。
今回のようにディレクトリによって使えるコマンドが違う環境に応用するため以下の変更を加えました。
-
on_demand_completion
が呼ばれたタイミングでのコマンドの存在チェックを削除-
uv
の利用できないディレクトリでシェルを起動しても他の場所で補完を利用する可能性があるため
-
- 仮の補完関数が呼ばれたタイミングでコマンドの存在チェック
-
uv
の利用できないディレクトリで補完をしようとしても何も起きないように
-
- 補完コマンドが補完の対象のコマンドと違う場合に対応
- 実際に
cargo
の補完コマンドはrustup completions zsh cargo
- 実際に
最後に
mise+zshのCLI補完の組み合わせで困っている方はお試しください。
Discussion