minimize

事業拡大のため、新しい仲間を募集しています。
→詳しくはこちら

ここでは、Eclipse Plugin を自作するときにまとめたメモ等を書いていきます。
僕自身、まだ始めたばかりなので大した情報はありませんが
これから始めようとしている人は参考にしてみて下さい。
Eclipse2と3の相違点なども記述してあります。

まずは作ってみる

まずはEclipse Pluginを作ってみましょう。
メニューからFile -> New -> Other... を選択、
ダイアログからPlug-in Development -> Plug-in Project を選択します。
後はWizardに従って進めていけばOKです。

とりあえず全てデフォルトで作成すれば一通りのものが作られるはずです。
プロジェクト構成は、以下のようになります。

plugin.xml はデフォルトでPlug-in Manifest Editorという専用エディタに
関連付けられています。
このエディタは、複数のタブに分かれていてややこしいですが
とりあえずSourceタブを見ればテキストで表示されるので
ここを見れば大体の構成はわかります。

とりあえず確認

作成されたPluginをEclipseに組み込む方法は後で紹介しますが、
その前にまずは確認だけしてみましょう。
メニューからRun -> Run As -> Run-time Workbench とすると、
今作成したPluginを組み込んだEclipseが起動します。

一見変化が無いように見えますが、
試しにメニューからWindow -> Preferences を選択してみましょう。
Sample Preferencesという項目が追加されているはずです。
他にも、サンプル用のViewやAction(コマンド)等も追加されているので
確認してみて下さい。

確認が終わったら、今起動したEclipseウィンドーを閉じましょう(右上の×)。
メインのEclipseを終了しないように気を付けて下さい。

とりあえず自分でサンプルコードを書いてみましょう。
SampleAction.run(action) が既に用意されているので、
ここに記述していくのがいいと思います。
あらかじめ用意されたwindowフィールド(IWorkbenchWindowクラス)の中身を
色々調べてみると先は開ける……はず(笑)。

Preferenceから値を取得する

通常Eclipseではユーザー毎の設定はPreferenceダイアログで行います。
ここで設定した値を取得する方法を紹介します。
Preferenceの項目毎にクラスが存在するので、まずはこれを探さなくてはいけません。
例えば「Workbench/Editors/Text Editor」に対応するクラスは
org.eclipse.ui.internal.editors.text.EditorsPlugin になります。

XXXPluginというクラス名で統一してあるので、探すのは比較的簡単なはずです。
Navigate -> Open Type で "*Plugin" とすればある程度絞られるのでこの中から探しましょう。
目的のPluginクラスが見つかったら、例えば以下のように記述します。

IPreferenceStore store = EditorsPlugin.getDefault().getPreferenceStore();
int value = store.getInt(PreferenceConstants.EDITOR_PRINT_MARGIN_COLUMN);

これで、「Workbench/Editors/Text Editor」にある「Print margin column」の値が取得できます。
コンパイルエラーになるようなら、次を読んでみて下さい。

プロジェクトで必要なPluginを追加する

上で使用しているPreferenceConstantsクラスはorg.eclipse.jdt.uiパッケージに存在していて
そのままではプロジェクトから参照できません。

plugin.xml を開いてDependenciesタブを開くと「Required Plug-ins」という項目があります。
ここにorg.eclipse.jdt.uiを追加すれば、
プロジェクトからこのパッケージを参照することが可能になります。

ファイルの文字エンコーディングを取得する

Eclipseではファイルの文字エンコーディングは
Preference -> Workbench/Editors で指定できますが、この値が何故かPluginから取得できません。
これを取得するには、以下のように記述します。

String encoding = ResourcesPlugin.getEncoding();

なんでこんな作りになっているのかは知りませんが…

ファイルを参照する

現在開いているファイルを参照する場合には、以下のようにします。

IEditorPart editorPart = window.getActivePage().getActiveEditor();
IEditorInput input = activeEditor.getEditorInput();
if (input instanceof IFileEditorInput) {
    IFileEditorInput fileInput = (IFileEditorInput) input;
    IFile ifile = fileInput.getFile();

    // Type1. InputStreamを取得する
    InputStream inputStream = ifile.getContents();

    // Type2. Fileオブジェクトを取得する
    File file = ((Path)ifile.getLocation()).toFile();

    // Type3. ファイルの絶対パスを取得する
    ifile.getLocation().toString();

    // Type3. ファイルの(プロジェクトrootからの)相対パスを取得する
    ifile.getFullPath().toString();
}

4つほど紹介しましたが、どの方法にしてもとにかく面倒です。
実際に使う際には自作のメソッドを用意して処理をまとめた方が良いでしょう。
ちなみに、開いている全てのファイルを参照する場合は以下のようにします。

IEditorReference[] references = page.getEditorReferences();
IEditorPart editorPart = references[index].getEditor(true);

ファイルが修正されているか調べる

現在開いているファイルが修正されているかどうかを調べるには、
IEditorPart.isDirty() を使用します。
ファイルを保存するにはIEditorPart.doSave(IProgressMonitor monitor) を使用します。
monitorには進行モニタobjを指定しますが、進行状況を表示しないのならばnullでOKです。

IProgressMonitorを取得する。

で、今話に出てきたIProgressMonitor。
通常はEclipseウィンドウのステータスバーに表示することが多いと思うので
これを取得する方法を紹介します。

IWorkbench workbench = window.getWorkbench();
WorkbenchWindow workbenchWindow = (WorkbenchWindow)workbench.getActiveWorkbenchWindow();
IActionBars bars = workbenchWindow.getActionBars();
IStatusLineManager lineManager = bars.getStatusLineManager();
IProgressMonitor progressMonitor = lineManager.getProgressMonitor();

…な、長い。しかしEclipse Pluginの開発を行う限り
こういう事は日常茶飯事だと思われますので、徐々に慣れるようにしましょう(笑)。

Problemsビューに項目を追加する

Eclipse標準のJavaエディタでは、Problems(Eclipse2ではTask)ビューにコンパイラエラーを表示してくれます。
自分でエディタを作る場合も、文法チェック等を行って間違いがあったら表示されると便利です。
Eclipseではこれらを「マーカー(Marker)」と呼んでいます。
マーカーはリソース単位で設定する仕組みになっていて、以下のように使います。

IMarker marker = IResource.createMarker(String type);

IResourceのサブインターフェイスとしてはIProjectやIFileなどがあるので
ファイル単位のマーカーやプロジェクト単位のマーカー等が設定できます。
typeにはマーカーの種類を指定します。IMarker.PROBLEM 等の定数が用意されているのでこれを使いましょう。

取得したmarkerオブジェクトには追加属性が設定できます。
ファイルの行番号や優先順位など、設定できる項目はマーカー種別により異なります。
以下に例を挙げます。

// Problemsマーカー
marker.setAttribute(IMarker.MESSAGE, "コンパイルエラーです。");
marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
marker.setAttribute(IMarker.LINE_NUMBER, new Integer(150));

// Taskマーカー
marker.setAttribute(IMarker.MESSAGE, "コンパイルエラーです。");
marker.setAttribute(IMarker.DONE, false);
marker.setAttribute(IMarker.PRIORITY, IMarker.PRIORITY_HIGH);
marker.setAttribute(IMarker.LINE_NUMBER, new Integer(150));

サンプルを活用しよう

Eclipse Pluginを使いこなすには、相当数のライブラリを理解する必要があります。
ここ等にJavadocがあるのでこれを参考にする訳ですが、
それだけではとてもじゃありませんが使いこなすのは大変です。
ここは、Eclipse本家にあるサンプルを活用しましょう。

Eclipse本体と同じページにExampleが置いてあるので、これを落としてきて展開します。
サンプルプログラムのソースは、展開したフォルダの
eclipse\plugins\org.eclipse.sdk.examples.source_3.0.0\src
以下のディレクトリにzipファイルとして存在するので、これを展開すればかなり参考になるはずです。
もちろん、Eclipseが自動生成してくれるサンプルソースも参考になります。

…いや、別に自分で説明するのが面倒くさくなった訳じゃありません(笑)。
サンプルを見てもわからない情報などは今後もここに載せていくつもりです。

独自Outlineページを作ってみる

サンプルプログラムのJavaEditorには、独自のOutlineページを作る方法が載っています。
ソースを読めば大体わかると思いますが、ざっと必要な手順を並べます。
サンプルと見比べながら確認して下さい。

…ふぅ。ざっとこんな感じです。
一度やってしまえば後は簡単ですので是非チャレンジしてみましょう。

Outlineに独自イメージを付ける

さて、もう一歩踏み込んでみます。
サンプルを見るだけだと、Outlineの要素にイメージを付けることができません。
が、その方法は意外なところから発見できました。Eclipseが自動生成してくれるSample Viewです。
では、その方法です。

Imageの取得方法

Imageに限らず、自作Pluginで使用するリソースを取得する方法は以下の通りです。

URL url = SampleEclipsePlugin.getDefault().find(new Path("icons/test.png"));

Pathコンストラクタの引数は、Pluginフォルダからの相対パスを記述します。
デバッグ中ならばPluginプロジェクトのルートパスからの相対パスになり、
組み込み時には plugin/sample_plugin/ からの相対パスになります。
Imageを取得するには、ここからさらに

new Image(url.openStream());

とすればOKです。

TextEditorの背景色を変える方法

これが意外と厄介でした(僕だけ?)。
TextEditorの背景色を変えるには、

(Abstract)TextEditor.getSourceViewer().getTextWidget().setBackground(Color)

を使うんですが、このロジックを呼び出すタイミングが重要です。
TextEditorのコンストラクタやinitializeEditorでこの処理を行おうとしても
この段階ではまだsourceViewerが生成されていないのでエラーになってしまいます。
次のように、createPartControlメソッドをオーバーライドしましょう。

public void createPartControl(Composite parent) {
    super.createPartControl(parent);
    getSourceViewer().getTextWidget().setBackground(color);
}

エディタに独自ポップアップメニューを追加する

エディタ内で右クリックを押したときのポップアップメニューに項目を追加するには、
TextEditorのサブクラスでeditorContextMenuAboutToShowメソッドを実装します。

protected void editorContextMenuAboutToShow(IMenuManager menu) {
    super.editorContextMenuAboutToShow(menu);
    menu.add(new Separator());
    menu.add(new MyAction("action name"));
}

こんな感じです。MyActionクラスはActionのサブクラスにします。

テキストに色を付ける

テキストエディタの標準文字色を変えたい場合は、
上の例でsetForeGroundメソッドを使えばOKですが
ここでは特定のキーワードに色を付ける方法を紹介します。
これもサンプルプログラムのJavaEditorにほぼ完璧な形で載っているので簡単に説明します。

RuleBasedScannerサブクラスの作成

このクラスでは、テキストに色を付けるルールを設定します。
実装するのはコンストラクタのみです。
以下、このクラスを MyCodeScanner と表記します。

コンストラクタの処理

ここで行うことは、ルール(の配列)を生成してsetRules(IRule[])を呼び出すことが全てです。
サンプルでは、一度Listに詰めてから
最終的にtoArrayで配列に変換する方式をとっています。

本題に戻ります。
IRuleを実装したクラスはあらかじめいくつか用意されているのでこれを使いましょう。
一つ例を挙げます。Javaの行末コメントを色付けするルールです。
IRule rule = new EndOfLineRule( "//", new Token(new TextAttribute(color)) );
EndOfLineRuleの第一引数(String)はコメント開始文字列、
第二引数(Itoken)はこのルールが適用されたときの文字装飾方法を指定します。
ITokenという名前から、文字列を意味するものかと思ってしまいますが
単なる文字色を表していると理解した方がわかりやすいでしょう。
TextAtributeコンストラクタの引数を変えれば背景色や文字の太さ等も変えられます。

SourceViewerConfigurationサブクラスの作成

実装するメソッドは一つだけです。

public IPresentationReconciler getPresentationReconciler(ISourceViewer sourceViewer) {
    PresentationReconciler reconciler = new PresentationReconciler();
    DefaultDamagerRepairer dr = new DefaultDamagerRepairer(new MyCodeScanner());
    reconciler.setDamager(dr, IDocument.DEFAULT_CONTENT_TYPE);
    reconciler.setRepairer(dr, IDocument.DEFAULT_CONTENT_TYPE);
    return reconciler;
}

このクラスは、エディタの表示方法をカスタマイズするメソッドを提供します。
他にもContent Assistを実装するときなど様々な場面で使いますので
常にEditorとセットで考えるとわかりやすいと思います。
作成したら、MyEditor クラスに initializeEditor メソッドを以下のように実装します。

protected void initializeEditor() {
    super.initializeEditor();
    setSourceViewerConfiguration(new MySourceViewerConfiguration());
}

これにより、MyEditor -> MySourceViewerConfiguration -> MyCodeScanner が関連付けられます。

独自のルールを作る

IRuleを実装したクラスを自作するのは簡単です。
実装するメソッドは以下の二つです。

ちなみに、複数のルールに当てはまる文字列があった場合は
(setRulesで格納した配列の)先頭に近いルールが適用されます。

最後に。独自のRuleクラスを作成した場合、内部ポインタを扱いを間違えると
他のルールにまで影響が及ぼされる可能性があります。
具体的には、他のルールに適用されるべき文字列に色付けがされなくなってしまいます。
充分にテストを行いましょう…僕のようにならない為にも(笑)。

自作コマンドにアクセラレータキーを割り当てる

色々と準備が必要なので順に説明します。

plugin.xmlは、Eclipse3になって変更された箇所がいくつかあります。

属性名が変わっているものが結構あります(command→commandId)が、
互換性の為に昔の属性名もそのまま使えるようになっている(Eclipse3.0M4)ので
エラーが発生しない限りそのままでも構いません。
ただ、要素構成が変わっているものは修正が必要です。
例えば今回だと、scope要素がcontext要素に置き換わっているので
この部分は修正(要素名の変更+親要素の変更)が必要です。
extensionの作成

plugin.xmlを開き、Extensionsタブを選択します。
ここのルート要素にorg.eclipse.ui.commandsがあるか確認します。
無ければ新規で作成しましょう。
右側にある「Add...」ボタンを押して、Generic Wizards -> Schema-based Extensionから
org.eclipse.ui.commandsを選択します。

Categoryの作成

続いて、コマンドが属するカテゴリーを決定します。
カテゴリーとは、Eclipseのキー割当ダイアログに表示される「Edit」や「File」等を指します。
Extensionsタブからorg.eclipse.ui.commandsを右クリックします。
New -> category を選択すると、先程作成したcommandsの子要素にカテゴリーが作成されます。

作成された要素をダブルクリックすると、Propertiesビューが表示されるはずです。
ここから項目を埋めていきましょう。
最低限必要なのは2つです。

id

このカテゴリーに付けるIDを設定します。
任意ですが、既存のものを見ると「org.eclipse.ui.category.edit」のように付けているので
これに倣って付けるのがいいでしょう。

name

キー割当ダイアログで表示されるカテゴリー名です。

Scopeの作成

使用するエディタに応じてキー割当を変えることができます。
これを制御するのがスコープという概念です。
デフォルトでは「Global」「Java Editor」「text Editor」というスコープが存在します。
この項目は、Eclipse2と3で若干異なります。

Eclipse 2の場合

commandsの子要素にscopeを作成します。

id

このスコープに付けるIDを設定します。

name

キー割当ダイアログで表示されるスコープ名です。

parent

基準とするスコープを記述します。
空(デフォルト)だとGlobalを基準にします。
テキストエディタを基準にしたい場合は org.eclipse.ui.textEditorScope と記述します。

Eclipse 3の場合

ルートにorg.eclipse.ui.contextsというextensionを作成して、その中にcontext要素を作成します。

id

このスコープに付けるIDを設定します。

name

キー割当ダイアログで表示されるスコープ名です。

parentId

基準とするスコープを記述します。
空(デフォルト)だとGlobalを基準にします。
テキストエディタを基準にしたい場合は org.eclipse.ui.textEditorScope と記述します。

Editorとスコープを関連付ける

Editorとスコープを関連付けるには、自作したTextEditorのサブクラスで
initializeKeyBindingScopesメソッドをオーバーライドします。

protected void initializeKeyBindingScopes() {
    setKeyBindingScopes(new String[] { "scope id" });
}

commandの作成

コマンド(アクション)とカテゴリーを関連付けます。
commandsの子要素にcommandを作成します。

category

カテゴリーIDを指定します。

id

コマンド(アクション)IDを指定します。
このIDはactionSetsの子要素にあるaction要素のdefinitionIdと合わせる必要があります。

name

コマンド名を記述します。

keyBindingの作成

コマンドにキーを割り当てます。
commandsの子要素にkeyBindingを作成します。

command

コマンドIDを指定します。

configuration

org.eclipse.ui.defaultAcceleratorConfigurationと記述して下さい。

scope

スコープIDを指定します。

string

キーを指定します。
「Ctrl+L」「Ctrl+X Ctrl+L」「Alt+Shift+ARROW_UP」のような記述方法にします。

actionの作成

最後にアクション(コマンド)を作成します。
Extensionsタブのルート要素にactionSetsを(もし無ければ)作成して下さい。
その下にactionSet、さらにその下にactionを作成します。
actionで最低限必要な項目を挙げます。

class

コマンドに対応するJavaクラスを指定します。
既にあるクラスを選択するか、新しくJavaクラスを生成します。

definitionId

command要素のidと同じものを記述します。

Actionの実装方法

作成したActionクラスのrunメソッドに処理ロジックを記述します。
大抵の場合、initメソッドを以下のように実装します。

private IWorkbenchWindow window;
public void init(IWorkbenchWindow window) {
    this.window = window;
}

こうすることによって、アクションを実行するときにIWorkbenchWindowを利用できます。

プラグインのバージョンを取得する

以下のようにします。

String pluginId = "something plugin id";
IPluginRegistry registry = Platform.getPluginRegistry();
IPluginDescriptor descriptor = registry.getPluginDescriptor(pluginId);
int major = descriptor.getVersionIdentifier().getMajorComponent();
int minor = descriptor.getVersionIdentifier().getMinorComponent();
int service = descriptor.getVersionIdentifier().getServiceComponent();

バージョンが「1.2.3」の場合、majorが1、minorが2、serviceが3となります。

テキストに色を付ける(ネスト編)

テキストに色を付ける」で、キーワードに色付けする方法は紹介しました。
しかし、この方法では一歩踏み込んだ色付けが出来ません。
具体的に言えば、例えばRubyなどでは文字列リテラルの中で「式展開」することが可能です。

string = "this is #{obj.name}."

このとき、Emacsのruby-modeでは 色付けの実際例 のように
式展開された部分は違う色付けがされます。
これをEclipseで行うには・・・そう、もう一つクラスを作ればいいんです(笑)。

作成するクラスはDefaultDamagerRepairerのサブクラス(以下MyDamagerRepairer)です。
MySourceViewerConfiguration 内にDefaultDamagerRepairerの記述があるはずです。
ここをMyDamagerRepairerに置き換えましょう。

DefaultDamageRepairerとは何か?

ソースを見ると、このクラスはIPresentationDamagerIPresentationRepairerという
二つのインターフェイスを実装したクラスだということがわかります。
前者はとりあえず放っておいて、ここではIPresentationRepairerだけ理解しましょう。
簡単に言えば、このインターフェイスは「文字列を解析して、表示文字列を生成する」為のものです。

メソッドは以下の二つです。

void setDocument(IDocument document)

documentを解析文字列として設定します。

void createPresentation(TextPresentation presentation, ITypedRegion damage)

damageを解析して、その結果をpresentationに格納します。
ここで文字列を解析するのに
RuleBasedScannerサブクラスの作成で自作したscannerが使われるという仕組みです。

createPresentationメソッドをオーバーライドすれば、
Editorに表示する文字列を自由に制御できます。
具体的な方法は、ちょっと長くなるのでまた今度(・・・と言って逃げる)。