summaryrefslogtreecommitdiffhomepage
path: root/vhosts/blog/content/posts/2024-04-29/zsh-file-completion-for-composer-custom-commands.ndoc
blob: 33ef19d2e4f57d6eb95e8fe0a0e2b084831e6a51 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
---
[article]
uuid = "9b26c1ed-45c3-4cad-9476-cbf2cf2e4de7"
title = "【Zsh】 Composer のカスタムコマンドに対する Zsh 補完で引数にファイルを補完させる"
description = "Zsh の Composer に対する補完はカスタムコマンドやその引数を補完しない。カスタムコマンドの引数としてファイルを補完させる方法を調べた。"
tags = [
  "composer",
  "php",
  "zsh",
]

[[article.revisions]]
date = "2024-04-29"
remark = "公開"
---
<article>
  <section id="version-info">
    <h>バージョン情報</h>
    <ul>
      <li>Composer: 2.7.4</li>
      <li>PHP: 8.3.6</li>
      <li>Zsh: 5.9</li>
    </ul>
  </section>
  <section id="intro">
    <h>はじめに</h>
    <p>
      <a href="https://getcomposer.org/">Composer</a> は PHP のデファクトスタンダードなパッケージマネージャである。
      Zsh では、<code>composer</code> コマンドに対する補完が提供されており、<code>composer</code> と入力してタブキーを押すと、利用可能なコマンドやオプションが補完される。
      Zsh の補完はシェル関数の形で実装されており、<code>composer</code> コマンドに対応した補完をおこなうのは <code>_composer</code> である。
      <a href="https://github.com/zsh-users/zsh/blob/a66e92918568881af110a3e2e3018b317c054e4a/Completion/Unix/Command/_composer">記事執筆時点での補完関数の定義は、GitHub のミラーリポジトリから参照できる。</a>
    </p>
  </section>
  <section id="problem">
    <h>発生していた問題</h>
    <p>
      <code>composer</code> コマンドはカスタムコマンド (<code>composer.json</code> の <code>scripts</code> で定義されたコマンド) に対して補完をおこなわない。
      つまり、途中まで入力されたカスタムコマンドを補完しないし、カスタムコマンドの引数も補完しない。
      例えば、PHPUnit を呼び出す <code>phpunit</code> というカスタムコマンドを定義し <code>composer phpu</code> まで打ってタブキーを押しても、<code>composer phpunit</code> にはならない。
      また、<code>composer phpunit -- --</code> まで打ってタブキーを押しても、<code>phpunit</code> コマンドのオプションは補完されない。
    </p>
    <p>
      このことは、先ほどリンクを載せた <code>_composer</code> 関数を定義しているファイルの冒頭にも書かれている。
    </p>
    <codeblock language="zsh">
      <![CDATA[
      # - @todo We don't complete custom commands (including script aliases). This is
      #   easy to do in the general case, but it probably requires some clever caching
      #   to avoid introducing a noticeable lag to every completion operation, due to
      #   the way command resolution works and the fact that discovering custom
      #   commands requires making slow calls to Composer
      ]]>
    </codeblock>
  </section>
  <section id="what-i-want-to-achive">
    <h>やりたいこと</h>
    <p>
      確かに、カスタムコマンドに対して完全な補完を提供するのは不可能か、あるいは実現できても遅くなりすぎるだろう。
      しかし、不完全なフォールバックを提供するくらいなら可能なはずだ。
    </p>
    <p>
      この記事では、これらのカスタムコマンドについて、Zsh が提供するデフォルトのファイル・ディレクトリ補完を適用する。
      つまり、<code>composer phpunit -- tests/</code> まで打ってタブキーを押すと、<code>tests</code> ディレクトリの下にあるテストファイルまたはディレクトリが補完される。
    </p>
  </section>
  <section id="solution">
    <h>解決策</h>
    <p>
      まずは、Zsh で補完関数を提供する場合のボイラープレートコードを書く。
      以下は <code>~/.zshrc</code> にすべて書く前提だが、<code>autoload</code> を設定するなどすれば別ファイルに分離できる (詳細な手順は割愛)。
    </p>
    <codeblock language="zsh">
      <![CDATA[
      compdef _my_composer composer composer.phar
      ]]>
    </codeblock>
    <p>
      <code>compdef</code> は Zsh が用意している関数で、第一引数に補完関数の名前、第二引数以降に補完を適用するコマンド名を並べる。
      この場合は、<code>composer</code> コマンドや <code>composer.phar</code> コマンドに対して <code>_my_composer</code> を使って補完をおこなうよう定義している。
    </p>
    <p>
      次に <code>_my_composer</code> を定義する。基本的にはデフォルトの <code>composer</code> コマンドの補完関数 (つまり <code>_composer</code> 関数) を使い、それが何も返さなかった場合に限り、Zsh のファイル・ディレクトリ補完へフォールバックする。
    </p>
    <codeblock language="zsh">
      <![CDATA[
      function _my_composer() {
          _composer "$@" || _files "$@"
      }
      ]]>
    </codeblock>
    <p>
      <code>_composer</code> コマンドは何も補完候補がなかったとき非ゼロな exit status で終了するので、そうであったなら <code>_files</code> を呼び出す。
      <code>_files</code> は、Zsh がデフォルトで用意しているファイル・ディレクトリの補完をおこなう関数である。
    </p>
  </section>
  <section id="conclusion">
    <h>まとめ</h>
    <p>
      これらの設定をおこなうことで、部分的ながら Composer のカスタムコマンドに対して補完をおこなうことができる。
      特に、PHPUnit や PHPStan などの対象ファイル・ディレクトリを引数に取るようなコマンドを使う場合に有用であろう。
    </p>
  </section>
</article>