Zenn
📝

プロジェクトにインストールしたコマンドのシェル補完問題

2025/03/24に公開
3

はじめに

この記事はシェルに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キーで補完が行えます。

.zshrc
autoload -Uz compinit
compinit

Homebrewでインストールしたコマンドなどは自動で補完の設定がされるため、上記の設定を追加するだけでほとんどのコマンドに対する自動補完の恩恵を受けられます。

通常の方法

通常、自前でインストールしたコマンドは以下の手続きで補完を有効化できます。
~/.zshrcに以下の行を追加。

.zshrc
eval "$(uv generate-shell-completion zsh)"

問題点

uvを使用するプロジェクト以外のディレクトリでシェルを起動した場合.zshrcが読み込まれるタイミングでuvは存在しないため、シェルを起動するときにエラーが発生。

コマンドの存在を判定してから補完を有効にする

~/.zshrcに以下の行を追加。

.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に以下の行を追加。

.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補完の組み合わせで困っている方はお試しください。

参考

3
mutex Tech Blog

Discussion

ログインするとコメントできます