コードアシストを利用したeclipseプラグインを作りたい人へ

eclipse で便利な機能といえばコードアシスト(コンテンツアシスト)。Ctrl + Space でコードの補完ができるアレだ。これを自作プラグインで拡張したいと思う人も多いだろうが、情報も少なく私自身もかなり苦戦したのでここに説明しておく。

拡張ポイント

そもそも eclipse のプラグイン作成は自分が拡張したい機能の拡張ポイントを探すことから始まる。拡張ポイントを選択し、その拡張ポイントについて正しく設定を行い(正しく plugin.xml を記述し)、その拡張ポイントに必要なクラスを書く、これが eclipse プラグインの作り方の簡単な流れだ。

コードアシストを拡張するための拡張ポイントはズバリ「org.eclipse.jdt.ui.javaCompletionProposalComputer」である。

まずは適当なプラグインプロジェクトを作り(そのときテンプレートは使用しなくてよい)、拡張タブの[追加]でこの拡張ポイントを追加してほしい。

次にこの拡張ポイントの設定だ。plugin.xml を直接書き換えることもできるが、拡張タブからGUIで行えるのでそこで行うことにする。

先ほど追加した拡張ポイントを右クリックし[新規]-[javaCompletionProposalComputer]を選択し、この新しい項目の拡張要素詳細の activate を true に変更する。次に、新しい項目を右クリックし[新規]-[partition]を選択し、type を「__dtfl_partition_content_type」にする。

これで一通り拡張ポイントの設定は完了だ。なお、最後の type は「どこで拡張したコードアシストを有効にするか」の設定である。他の設定の詳細は「拡張ポイント記述の表示」を読んでもらいたい(英語だが)。

クラス・メソッド作成

作成しなければならないクラスは、先ほど追加した項目「(javaCompletionProposalComputer)」のclassに記述された名前のクラスである。「class*:」をクリックすると、必要な名前やインターフェイスが入力された状態でクラス作成画面が開くので、そのまま完了しよう。
コードアシストを拡張するのに最低限必要なのは、computeCompletionProposals メソッドを実装することだ。このメソッドで返すべきインスタンスを返せば、その内容をコードアシストに表示することができる。

クラスを作った時点で、クラス名の部分にエラーが発生していると思う。そこをマウスオーバーして「実装されていないメソッドの追加」を選択すると、エラーは増えるが computeCompletionProposals メソッドが記述されるだろう。

増えたエラーは全てインポートクラスを発見できていないことによるものである。eclipse プラグインでは、インポートしたいクラスのパッケージを全て依存関係に追加しなければならない。「org.eclipse.jdt.ui」は拡張ポイントを追加したときに依存関係に追加されたのだが、他のパッケージは依存関係に存在しない。そこで MANIFEST.MF の依存関係タブを開き「org.eclipse.core.runtime」「org.eclipse.jface.text」の2つを依存関係に追加すればエラーは解消される。

メソッド実装

では最後に computeCompletionProposals メソッドを実装しよう。

このメソッドでは List (実際は ICompletionProposal の実装である CompletionProposal のリスト)を返すことになっている。1つの CompletionProposal インスタンスがコードアシスト1項目に対応する。

とりあえず私が作ったサンプルの computeCompletionProposals メソッドを見てもらおう。

public List<ICompletionProposal> computeCompletionProposals(ContentAssistInvocationContext context, IProgressMonitor monitor) {
    List<ICompletionProposal> propList = new ArrayList<ICompletionProposal>();
    int offset = context.getInvocationOffset();
    String str;
    CompletionProposal proposal;

    // 文字列"akisute"をコードアシストリストに登録する
    str = "akisute";
    proposal  =  new CompletionProposal(str, offset, 10, str.length());
    propList.add(proposal);
    // 文字列"kashitsune"をコードアシストリストに登録する
    str = "kashitsune";
    proposal =  new CompletionProposal(str, offset, 0, str.length());
    propList.add(proposal);

    return propList;
}

これを記入してプラグインを実行し、適当なファイルで Ctrl + Space を押すと「akisute」と「kashitsune」が表示され、項目を選択することでファイルに文字列を挿入できる。

このコードで説明した方がいいのは offset と proposal くらいだろう。

まず、このメソッドの引数 context というのはコードアシストを実行したファイルの情報が含まれていて各メソッドで取得できる。その1つが getInvocationOffset() であり、これによってオフセット、すなわちカーソルの位置を得ることができる。

proposal は CompletionProposal のインスタンスである。CompletionProposal のコンストラクタは「CompletionProposal(String replacementString, int replacementOffset, int replacementLength, int cursorPosition) 」といった4つの引数を取り、それぞれの役割は以下の通り。

replacementString
置換後の文字列
replacementOffset
置換を開始するカーソル位置
replacementLength
置換前の(削除する)文字列の長さ
cursorPosition
置換後のカーソル位置(replacementOffsetからの距離)

「挿入」ではなく「置換」となってるのは、例えば「aki」と書いてる状態でコードアシストを使用して「akisute」としたいとき、これは「aki」を「akisute」に置換したと考えるためである。この例であれば、

replacementString
"akisute"
replacementOffset
context.getInvocationOffset() - "aki".length()
replacementLength
"aki".length()
cursorPosition
(置換後にakisuteの後にカーソルを置きたいなら)"akisute".length()

となる。上手く引数を書けば、任意の文字数の記述途中の文字列を補完することももちろん可能だ。

まとめ

長々と説明してきたが、言いたかったことは「コードアシストを利用したプラグインを作成するための拡張ポイントは "org.eclipse.jdt.ui.javaCompletionProposalComputer" である」ということだ。

これさえわかれば、拡張ポイント説明文書を読んだりクラスやメソッドをググることで自分の作りたいものに近づくことができるのだが、ここにたどり着くのに多くの人はかなり苦戦すると思う。

この記事が私のように悩んでいる人の助けになれば幸いである。