以前、 Zshの補完について ちょっと書きましたが、 足りない所とか理解しきれてなかった所があるので追記。
Zshの補完について
取り敢えず前回のはこちら。
補完を有効にするために
autoload -U compinit
compinit
を.zshrc等で設定しておくことやcompdef
/compadd
等の
補完を作成、登録するコマンドなどについてまとめています。
上のautoload
はcompinit
を有効にさせるためのコマンド。
compinit
は指定されたパスにある補完ファイルから
補完をアップデートして、~/.zcompdumpというファイルに
コマンドとそれに使う補間関数の定義の一覧を作成します。
パスはfpath
またはFPATH
で設定されていて、
何方も同じパスが入っていますが、
fpath
の方は配列、FPATH
の方は通常のPATH
の様に:
で区切られた文字列として
登録されています。
何方かをアップデートするともう片方も自動的にアップデートされるので 何方かだけをいじればOK。
$ echo $fpath
/usr/local/share/zsh-completions /usr/local/share/zsh/site-functions /usr/local/Cellar/zsh/5.2/share/zsh/functions
$ echo $FPATH
/usr/local/share/zsh-completions:/usr/local/share/zsh/site-functions:/usr/local/Cellar/zsh/5.2/share/zsh/functions
$ fpath=(/new/path "${fpath[@]}")
$ echo $fpath
/new/path /usr/local/share/zsh-completions /usr/local/share/zsh/site-functions /usr/local/Cellar/zsh/5.2/share/zsh/functions
$ echo $FPATH
/new/path:/usr/local/share/zsh-completions:/usr/local/share/zsh/site-functions:/usr/local/Cellar/zsh/5.2/share/zsh/functions
.zcompdumpには以下の様にコマンドと関数が登録されています。
1 2 3 4 5 6 7 8 |
|
コマンドに対して補間関数が定義されていれば、 こんな感じで
'<command>' '<completion (initialization) function>'
と言った感じに書かれているはずです。 注意なのは下に書くように、この2番めは実際に補完を行う関数だけが書かれたファイルか、 または、補完の定義を行う関数(ファイル)でもOK、と言う点。
補完設定ファイルの作成
Homebrewの補助コマンドの
Brew-file
ではbrew file
コマンドに対する補完や、
さらにbrew
コマンド自体にも補完に関する強化を行っています。
で、普段Bashばかり使っているのであれでしたが、 Zshの方で実はきちんと補完が動いてませんでした。 なのでその辺のアップデートがてらの補完に関するメモです。
まず、補完の作り方は Zshの補完について で大体書いた感じですが、 そのファイルの記述で一つ忘れていたのが、 ファイルの先頭にShebangの様な形で、
#compdef brew-file
と書いておく必要があります。
#!/bin/zsh
等普通のShebangを書いてはダメ
1。
ここでcompdef
の後ろに書いてある物が対象のコマンドになります。
また、使われる関数名は記述されているファイル名になります。
このbrew-file
コマンドの場合には
/usr/local/share/zsh/site-functions/_brew-file
というファイルがfpath
の中に入っています。
この様なファイルがfpath
に入った状態でcompinit
すると
補完の定義がアップデートされ.zcompdumpが更新されます。
上にも書いたように、この_brew-file
はbrew-file
コマンドを
最初に呼んだ時に呼ばれ、補完の方法をロードするために使われます。
実際に、_brew-fileの内容は以下のようになっています2。
この中では_brew_file
という関数を定義して、
compdef _brew_file brew-file
とbrew-file
の補完を定義しています。
なので、実際に補完に使われる関数は_brew_file
(真ん中アンダーバー)になり、
_brew-file
(真ん中ハイフン)はこのファイルが
初回だけ補完の定義用に呼び出される、と言う形になっています。
ファイルの中にcompdef
等がない補完ファイルもあり、
この場合はそのファイルが補完関数として登録されます。
また、gitの補完ファイルなどは
1 2 3 4 5 6 7 8 |
|
みたいな定義になっていて、無限再帰しそうな感じもあるのですが、
この場合は関数の_git
が良しなにgit
への補完関数として登録されています。
なので書き方として、#compdef <command>
を書いた上で、
- 中でも
compdef
を使い指定のコマンドに特定の関数を補間関数として与える。 - 補完関数の内容を直接ファイルに書き込み、そのファイル自体を補間関数だとして使う。
- ファイル名と同様の関数を書き、それを最後に呼び出すような形で置いておいてその関数を補間関数として使う。
と言った書き方が出来ます。
最後の形式のものだと、最後に関数を呼ぶ際、
_git "$@"
みたいな形で引数を渡す形のものもありましたが、 そうで無いものも動いているので、この辺は古い書き方?なのかもしれません。 (昔はファイル内で定義された関数、みたいなのを上手くロードできずに ファイルの中身そのものを補完関数として渡していたために直接引数を渡す必要があった?)
man zshcompsys
とかを読んでみたもののこの辺イマイチはっきりしなかったんですが、
取り敢えず
zsh-users/zsh-completions
とかにある補完を参考にしていけばやりたいことは出来る様になるかと。
補間関数がロードされるタイミング
一つ大きく勘違いしてたのがこれらの補間関数がロードされるタイミング。
Bashの場合、補完をロードするには単に定義が書いてあるスクリプトをロードするだけです。 Bash-Completion では
source /usr/local/bash_completion
のようにbash_completionという親ファイルを読み込み、 この中から/usr/local/bash_completion.d内にある定義ファイルを 片っ端から読み込んでいく、と言う形です。
この際、関数などは勿論読み込まれ、complete
コマンドによって
各コマンドと結び付けられます。
一方、Zshの場合は上に書いたようにcompinit
を呼ぶのが
通常の補完の有効化です。
この際、起こることはコマンドと各補完初期化ファイル/関数との結びつけだけです。 実際にファイルが読み込まれたりはしません。
従って、compinit
した後だからといって該当の関数を直接使おうとすると
エラーが起こります。
但し、fpath
に含まれるファイルは、その名前の関数として、
1 2 3 4 |
|
みたいな感じで定義されています。
(where _brew-file
またはautoload
とすると全てのこの様な読み込み前関数を見れます。)
ここで、autoload -X
は、autoload
が実行されるその関数名と同じ名前のファイルをfpath
内から探して
その内容を関数の内容と置き換える、と言った操作をします
3。
つまり、compinit
時には本当に結びつけだけして、
この関数を一度呼んで初めて中身が実装されます。
他の-U
オプションはこの関数内でエイリアスを展開しないようにするオプション、
-z
はZsh形式で読み込む、と言うオプション。
通常はこれですが、-k
というksh形式で読み込むオプションもあります。
builtin
はautoload
が下手にエイリアスとかされてても元のビルトインコマンドをそのまま使うためのコマンドです。
ここで困ったのが
homebrew-file/brew-wrap
というbrew
コマンドを拡張するファイルの中で、
この補完関数を使っている所。
compinit
を下あとで、**brew-wrap**
を読みこむようにしておいても
この関数がまだキチンと定義されてないためおかしな挙動をします。
これを回避するために 以下の用に.zcompdumpの中の定義を見て、 ここにファイルが登録されていれば 関数をロードして実行(補完を定義する)、ということをする様にしました。
1 2 3 4 5 6 7 8 9 10 11 |
|
_brew-file
とファイル名が関数になったものが見つかればその関数を実行します。
これでBashの時の様に中で定義されてる_brew_file
という関数が
実際に読み込まれている状態になります。
-
Bashと共用のものにしていますが、 Bashの場合は単に
source
で読み込むだけなのでこの部分は Shebangでもどんな#
で始まる行であってもただのコメント業として無視されるので なんでも良い。 ↩ -
ここではファイル名がbrew-fileですが、実際にZshで使う際には _brew-fileと言う名前で使うために違うディレクトリにリンクが貼ってあります。
BashとZshで同じファイルを使ったほうが楽なのでそのための処置です。
-
autoload
をautoload -U _brew-file
の様に直接使うと
fpath
の中からファイルを探して来てそれをロードします。 (compinit
はfpath
の中から#compdef
が先頭にあるファイルを探してきてロードする。)この際は
compinit
で行われるのと同様、中身にbuiltin autoload
が入っただけの関数が定義されるだけです。もし、
autoload +X _brew-file
と、
+X
オプションを使うと定義づくりと実行を同時に出来るのですが、 なぜか+X
と-U
オプションは同時に使えない、と言う制限があるので もしファイルをロードして実行したい、と言う場合、 安全にエイリアスを無視したい場合は一度-U
だけで呼んで、 その後その関数を直接呼ぶ必要があります。autoload -U _brew-file _brew-file
- zsh: 9 Functions
man zshmisc