ここでは、Eclipse Plugin を自作するときにまとめたメモ等を書いていきます。
僕自身、まだ始めたばかりなので大した情報はありませんが
これから始めようとしている人は参考にしてみて下さい。
Eclipse2と3の相違点なども記述してあります。
まずはEclipse Pluginを作ってみましょう。
メニューからFile -> New -> Other... を選択、
ダイアログからPlug-in Development -> Plug-in Project を選択します。
後はWizardに従って進めていけばOKです。
とりあえず全てデフォルトで作成すれば一通りのものが作られるはずです。
プロジェクト構成は、以下のようになります。
Javaクラスファイル
Javaソースファイル
設定ファイル
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クラス)の中身を
色々調べてみると先は開ける……はず(笑)。
通常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」の値が取得できます。
コンパイルエラーになるようなら、次を読んでみて下さい。
上で使用している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。
通常はEclipseウィンドウのステータスバーに表示することが多いと思うので
これを取得する方法を紹介します。
IWorkbench workbench = window.getWorkbench(); WorkbenchWindow workbenchWindow = (WorkbenchWindow)workbench.getActiveWorkbenchWindow(); IActionBars bars = workbenchWindow.getActionBars(); IStatusLineManager lineManager = bars.getStatusLineManager(); IProgressMonitor progressMonitor = lineManager.getProgressMonitor();
…な、長い。しかしEclipse Pluginの開発を行う限り
こういう事は日常茶飯事だと思われますので、徐々に慣れるようにしましょう(笑)。
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が自動生成してくれるサンプルソースも参考になります。
…いや、別に自分で説明するのが面倒くさくなった訳じゃありません(笑)。
サンプルを見てもわからない情報などは今後もここに載せていくつもりです。
サンプルプログラムのJavaEditorには、独自のOutlineページを作る方法が載っています。
ソースを読めば大体わかると思いますが、ざっと必要な手順を並べます。
サンプルと見比べながら確認して下さい。
当然これが必要です。TextEditorかMultiPageEditorPartの派生クラスを使うと良いでしょう。
そして、getAdapterメソッドを実装する必要もあります。
Outlineビューに表示する内容の実体です。ContentOutlinePageの派生クラスにします。
実装するメソッドもその内容も一筋縄ではいきません。サンプルをほぼそのままコピーしましょう。
ファイルの内容をparseして、outlineに渡すオブジェクト(階層)を作成するのがこのクラスです。
一番重要なロジックといえるでしょう。
まずはサンプルをコピーして、parseメソッドを自作すればOKです。
必要に応じてgetElements, hasChildren, getParent, getChildrenメソッドも書き換えましょう。
メソッドの処理内容は、メソッド名そのままなので特に解説は要らないと思います。
サンプルではこのContentProviderがinner classになっていますが、正直見にくいので
通常のクラスに変換しましょう。Convert Member Type to Top Levelを使えば楽勝です。
…ふぅ。ざっとこんな感じです。
一度やってしまえば後は簡単ですので是非チャレンジしてみましょう。
さて、もう一歩踏み込んでみます。
サンプルを見るだけだと、Outlineの要素にイメージを付けることができません。
が、その方法は意外なところから発見できました。Eclipseが自動生成してくれるSample Viewです。
では、その方法です。
まずはLabelProviderの派生クラスを作成します。
実装するメソッドはgetImageだけでOKです。
getTextメソッドもありますが、実装しなくてもLabelProvider.getTextがtoStringを返してくれます。
LabelProvider.getImageは常にnullを返すので下位クラスで実装する必要があります。
先程作成したContentOutlinePageの派生クラスにあるcreateControlメソッド。
ここに、
viewer.setLabelProvider(...);
という部分があります。ここに自作のLabelProviderを指定しましょう。
さて、肝心のgetImageの中身はどうしましょう。
Eclipseのシステムイメージを使うだけなら
PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJ_ELEMENT);
などとすれば可能ですが、せっかくですから自分で作成したImageを使うと良いでしょう。
EclipseにはImageRegistry というイメージ管理クラスが用意されているので、
これを使うと自作の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の背景色を変えるには、
(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にほぼ完璧な形で載っているので簡単に説明します。
このクラスでは、テキストに色を付けるルールを設定します。
実装するのはコンストラクタのみです。
以下、このクラスを MyCodeScanner と表記します。
ここで行うことは、ルール(の配列)を生成してsetRules(IRule[])を呼び出すことが全てです。
サンプルでは、一度Listに詰めてから
最終的にtoArrayで配列に変換する方式をとっています。
今回に限らず、この手法は知っていた方がいいでしょう。
「変換するのが面倒だから初めからListを引数にしてくれればいいのに」という人もいるでしょうが、
Listではなく配列にしている理由がちゃんとあるのです。
スピード、メモリ使用量の改善という事もありますが
僕が考える一番の理由は「プログラムの可読性と開発効率の向上」です。
何でもかんでもListに詰めてしまうと、
その中に何のオブジェクトが入っているのかが一目でわからなくなります。
特に、後からソースを読んだ人が判断するのは至難の技です。
通常、Listからオブジェクトを取り出すときはキャストしてから使います。
このとき、間違ったクラスのインスタンスを詰めていた場合に例外が発生してしまいます。
配列にしておけば、格納するインスタンスのクラスを静的に決定できるので
間違いも起きません(間違っていればコンパイルエラーになる為)。
Listを配列に変換するにはtoArrayメソッドを使います。
このとき、引数無しのメソッド呼び出しをしてしまうとObject[]が生成されてしまうので
サンプルのように面倒でもまずr = new IRule[size]としてからtoArray(r)と引数付きのメソッドを呼び出せば
ArrayList -> IRule[] の変換が行えます。
Eclipseならば toarray と入力して Content Assist コマンドを実行すれば
テンプレートが挿入されるのでこれを利用しましょう。
本題に戻ります。
IRuleを実装したクラスはあらかじめいくつか用意されているのでこれを使いましょう。
一つ例を挙げます。Javaの行末コメントを色付けするルールです。
IRule rule = new EndOfLineRule( "//", new Token(new TextAttribute(color)) );EndOfLineRuleの第一引数(String)はコメント開始文字列、
第二引数(Itoken)はこのルールが適用されたときの文字装飾方法を指定します。ITokenという名前から、文字列を意味するものかと思ってしまいますが
単なる文字色を表していると理解した方がわかりやすいでしょう。TextAtributeコンストラクタの引数を変えれば背景色や文字の太さ等も変えられます。
実装するメソッドは一つだけです。
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を実装したクラスを自作するのは簡単です。
実装するメソッドは以下の二つです。
ex. MyRule(IToken token)
これは必ずしも必要ではありませんが、使用するIToken(文字装飾方法)くらいは
ユーザが自由に変更できるのが望ましいので是非作りましょう。
public IToken evaluate(ICharacterScanner scanner)
ここがメインロジックです。
エディタで表示される文字列がこのルールに適用されるのかを確認するためにEclipseが呼び出します。
まずは、引数と戻り値の説明です。
引数として与えられるのはこれだけです。
簡単に言えば、文字列の情報を扱うインターフェイスです。
主に利用するメソッドは以下の3つです。
文字列から一文字を取り出します。
さらに、内部ポインタを一つ後ろに移動させます。
つまり、もう一度このメソッドを呼び出すと次の一文字を取り出すことになります。
内部ポインタを一つ前に移動させます。
内部ポインタが指す文字が、エディタ上で何カラム目にあるかを返します。
例えば、行頭なら0を返します。
内部ポインタは変化しません。
ルールが適用される場合に、そのときの文字装飾方法を返します。
ルールが適用されない場合にはToken.UNDEFINEDを返すようにします。
ここで重要なことは、引数として与えられるscannerの扱いです。
一般的なロジックを紹介します。
まずscannerから一文字取り出し、それがルールに適用されるかどうか調べる。
適用されなかったら、scanner.unread()をcall してからToken.UNDEFINEDを返す。
適用される可能性があったら、続いて文字を取り出していく。
適用される事が確定したら、適用される文字の後ろまで内部ポインタを移動させてITokenを返す。
途中まで来て「適用されない」と判断されたら、
内部ポインタをこのメソッドが呼び出された状態まで戻してからToken.UNDEFINEDを返す。
以上をまとめると、
適用が終了する場所まで内部ポインタを移動して、IToken(文字装飾方法)を返す。
メソッドが呼び出された状態まで内部ポインタを戻して、IToken.UNDEFINEDを返す。
という流れになります。
ルールが適用されない場合に内部ポインタを戻すのは、
その場合に「内部ポインタの位置を保ったまま」他のルールの適用チェックを行う為です。
ちなみに、複数のルールに当てはまる文字列があった場合は
(setRulesで格納した配列の)先頭に近いルールが適用されます。
最後に。独自のRuleクラスを作成した場合、内部ポインタを扱いを間違えると
他のルールにまで影響が及ぼされる可能性があります。
具体的には、他のルールに適用されるべき文字列に色付けがされなくなってしまいます。
充分にテストを行いましょう…僕のようにならない為にも(笑)。
色々と準備が必要なので順に説明します。
plugin.xmlは、Eclipse3になって変更された箇所がいくつかあります。 属性名が変わっているものが結構あります(command→commandId)が、 互換性の為に昔の属性名もそのまま使えるようになっている(Eclipse3.0M4)ので エラーが発生しない限りそのままでも構いません。 ただ、要素構成が変わっているものは修正が必要です。 例えば今回だと、scope要素がcontext要素に置き換わっているので この部分は修正(要素名の変更+親要素の変更)が必要です。
plugin.xmlを開き、Extensionsタブを選択します。
ここのルート要素にorg.eclipse.ui.commandsがあるか確認します。
無ければ新規で作成しましょう。
右側にある「Add...」ボタンを押して、Generic Wizards -> Schema-based Extensionから
org.eclipse.ui.commandsを選択します。
続いて、コマンドが属するカテゴリーを決定します。
カテゴリーとは、Eclipseのキー割当ダイアログに表示される「Edit」や「File」等を指します。
Extensionsタブからorg.eclipse.ui.commandsを右クリックします。
New -> category を選択すると、先程作成したcommandsの子要素にカテゴリーが作成されます。
作成された要素をダブルクリックすると、Propertiesビューが表示されるはずです。
ここから項目を埋めていきましょう。
最低限必要なのは2つです。
このカテゴリーに付けるIDを設定します。
任意ですが、既存のものを見ると「org.eclipse.ui.category.edit」のように付けているので
これに倣って付けるのがいいでしょう。
キー割当ダイアログで表示されるカテゴリー名です。
使用するエディタに応じてキー割当を変えることができます。
これを制御するのがスコープという概念です。
デフォルトでは「Global」「Java Editor」「text Editor」というスコープが存在します。
この項目は、Eclipse2と3で若干異なります。
commandsの子要素にscopeを作成します。
このスコープに付けるIDを設定します。
キー割当ダイアログで表示されるスコープ名です。
基準とするスコープを記述します。
空(デフォルト)だとGlobalを基準にします。
テキストエディタを基準にしたい場合は org.eclipse.ui.textEditorScope と記述します。
ルートにorg.eclipse.ui.contextsというextensionを作成して、その中にcontext要素を作成します。
このスコープに付けるIDを設定します。
キー割当ダイアログで表示されるスコープ名です。
基準とするスコープを記述します。
空(デフォルト)だとGlobalを基準にします。
テキストエディタを基準にしたい場合は org.eclipse.ui.textEditorScope と記述します。
Editorとスコープを関連付けるには、自作したTextEditorのサブクラスで
initializeKeyBindingScopesメソッドをオーバーライドします。
protected void initializeKeyBindingScopes() {
setKeyBindingScopes(new String[] { "scope id" });
}
コマンド(アクション)とカテゴリーを関連付けます。
commandsの子要素にcommandを作成します。
カテゴリーIDを指定します。
コマンド(アクション)IDを指定します。
このIDはactionSetsの子要素にあるaction要素のdefinitionIdと合わせる必要があります。
コマンド名を記述します。
コマンドにキーを割り当てます。
commandsの子要素にkeyBindingを作成します。
コマンドIDを指定します。
org.eclipse.ui.defaultAcceleratorConfigurationと記述して下さい。
スコープIDを指定します。
キーを指定します。
「Ctrl+L」「Ctrl+X Ctrl+L」「Alt+Shift+ARROW_UP」のような記述方法にします。
最後にアクション(コマンド)を作成します。
Extensionsタブのルート要素にactionSetsを(もし無ければ)作成して下さい。
その下にactionSet、さらにその下にactionを作成します。
actionで最低限必要な項目を挙げます。
コマンドに対応するJavaクラスを指定します。
既にあるクラスを選択するか、新しくJavaクラスを生成します。
command要素のidと同じものを記述します。
作成した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に置き換えましょう。
ソースを見ると、このクラスはIPresentationDamagerとIPresentationRepairerという
二つのインターフェイスを実装したクラスだということがわかります。
前者はとりあえず放っておいて、ここではIPresentationRepairerだけ理解しましょう。
簡単に言えば、このインターフェイスは「文字列を解析して、表示文字列を生成する」為のものです。
メソッドは以下の二つです。
documentを解析文字列として設定します。
damageを解析して、その結果をpresentationに格納します。
ここで文字列を解析するのに
RuleBasedScannerサブクラスの作成で自作したscannerが使われるという仕組みです。
createPresentationメソッドをオーバーライドすれば、
Editorに表示する文字列を自由に制御できます。
具体的な方法は、ちょっと長くなるのでまた今度(・・・と言って逃げる)。