Eclipseのプラグイン開発の自作DMDLエディターでコメント色付けを行ってみる。
DMDLではコメントを書くことが出来るので、コメントに色を付けてみる。
/* アイテム */ item = { code : LONG; -- 商品コード id : TEXT; // name : TEXT; };
DMDLのコメントの定義は以下の通り。
まず、簡単なところから。
色を管理するクラスを用意する。(XMLEditorで生成されたクラスをちょっとだけ修正(ジェネリクス対応)してそのまま使用する)
package jp.hishidama.eclipse_plugin.dmdl_editor.editors; import java.util.HashMap; import java.util.Map; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.widgets.Display;
public class ColorManager {
	protected Map<RGB, Color> fColorTable = new HashMap<RGB, Color>();
	public Color getColor(RGB rgb) {
		Color color = fColorTable.get(rgb);
		if (color == null) {
			color = new Color(Display.getCurrent(), rgb);
			fColorTable.put(rgb, color);
		}
		return color;
	}
	public void dispose() {
		for (Color color : fColorTable.values()) {
			color.dispose();
		}
		fColorTable.clear();
	}
}
色はRGBクラスで管理するが、テキストエディター上ではColorクラスで扱う。その為、変換用のマップを用意している。
また、最終的にはColorオブジェクトをdisposeする必要があるようなので、そのメソッドも用意している。
ちなみに、Eclipseプラグインではフィールド(変数)名の先頭に「f」を付ける命名ルールになっているようだ。
DMDLEditorでColorManagerを保持する。
また、構文解析に関係するDocumentProviderとConfigurationクラスを新設し、それを設定する。
package jp.hishidama.eclipse_plugin.dmdl_editor.editors; import org.eclipse.ui.editors.text.TextEditor;
public class DMDLEditor extends TextEditor {
	private ColorManager colorManager = new ColorManager();
	/**
	 * コンストラクター.
	 */
	public DMDLEditor() {
		setDocumentProvider(new DMDLDocumentProvider());
		setSourceViewerConfiguration(new DMDLConfiguration(colorManager));
	}
	@Override
	public void dispose() {
		colorManager.dispose();
		super.dispose();
	}
}
XMLEditorのサンプルでこのようなColorManagerを使っているが、TextEditorの場合はColorManagerの代わりにISharedTextColorsが使えそう。[2013-06-05]
import org.eclipse.jface.text.source.ISharedTextColors;
public class DMDLEditor extends TextEditor {
	public DMDLEditor() {
		setDocumentProvider(new DMDLDocumentProvider());
		ISharedTextColors colors = getSharedColors();
		setSourceViewerConfiguration(new DMDLConfiguration(colors));
	}
}
getSharedColors()によって取得されたISharedTextColorsは全エディターで共有されているので、自分でdispose()する必要は無い。
DocumentProviderは、ドキュメント(IDocumentインターフェース)オブジェクトを生成する為のクラス。
package jp.hishidama.eclipse_plugin.dmdl_editor.editors; import org.eclipse.core.runtime.CoreException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IDocumentPartitioner; import org.eclipse.jface.text.rules.FastPartitioner; import org.eclipse.ui.editors.text.FileDocumentProvider;
public class DMDLDocumentProvider extends FileDocumentProvider {
	@Override
	protected IDocument createDocument(Object element) throws CoreException {
		IDocument document = super.createDocument(element);
		if (document != null) {
			IDocumentPartitioner partitioner = new FastPartitioner(
				new DMDLPartitionScanner(),
				new String[] { DMDLPartitionScanner.DMDL_COMMENT });
			partitioner.connect(document);
			document.setDocumentPartitioner(partitioner);
		}
		return document;
	}
}
createDocument()をオーバーライドし、親メソッドを呼んでドキュメントオブジェクトを生成してから、Partitioner(パーティショナー)を登録している。
DMDLPartitionScannerも自作する。
Partitionerは、ドキュメント(全テキスト)を解析してパーティションに分割するクラス。
FastPartitionerでは、使用するパーティション名一覧も登録する。
パーティション分割(ドキュメントの構文解析)およびパーティション名の定義をPartitionScannerで行う。
PartitionScannerは、ドキュメントを構文解析してパーティションに分割するクラス。
とりあえず今回のDMDLの例では、コメント部分とコメント以外の部分に分割する。
分割された各パーティションには名前を付ける(例:DMDL_COMMENT)。
package jp.hishidama.eclipse_plugin.dmdl_editor.editors; import org.eclipse.jface.text.rules.EndOfLineRule; import org.eclipse.jface.text.rules.IPredicateRule; import org.eclipse.jface.text.rules.IToken; import org.eclipse.jface.text.rules.MultiLineRule; import org.eclipse.jface.text.rules.RuleBasedPartitionScanner; import org.eclipse.jface.text.rules.Token;
public class DMDLPartitionScanner extends RuleBasedPartitionScanner {
	public final static String DMDL_COMMENT = "__dmdl_comment";
	/**
	 * コンストラクター.
	 */
	public DMDLPartitionScanner() {
		IToken commentToken = new Token(DMDL_COMMENT);
		IPredicateRule[] rules = {
			new EndOfLineRule("--", commentToken),
			new EndOfLineRule("//", commentToken),
			new MultiLineRule("/*", "*/", commentToken),
		};
		setPredicateRules(rules);
	}
}
コンストラクターで、各パーティションの構文のルールを登録する。
今回は、特定の文字列から行末までを表すEndOfLineRuleと、特定の文字列で囲まれた範囲を表すMultiLineRuleを使っている。
SourceViewerConfigurationは、エディターの表示に関する設定を行うクラス。
今回はパーティション名に対して色を付ける設定を行う。
package jp.hishidama.eclipse_plugin.dmdl_editor.editors; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.TextAttribute; import org.eclipse.jface.text.presentation.IPresentationReconciler; import org.eclipse.jface.text.presentation.PresentationReconciler; import org.eclipse.jface.text.rules.DefaultDamagerRepairer; import org.eclipse.jface.text.rules.RuleBasedScanner; import org.eclipse.jface.text.rules.Token; import org.eclipse.jface.text.source.ISourceViewer; import org.eclipse.jface.text.source.SourceViewerConfiguration; import org.eclipse.swt.graphics.RGB;
public class DMDLConfiguration extends SourceViewerConfiguration {
	private ColorManager colorManager;
	/**
	 * コンストラクター.
	 *
	 * @param colorManager
	 */
	public DMDLConfiguration(ColorManager colorManager) {
		this.colorManager = colorManager;
	}
	@Override
	public String[] getConfiguredContentTypes(ISourceViewer sourceViewer) {
		return new String[] {
			IDocument.DEFAULT_CONTENT_TYPE,
			DMDLPartitionScanner.DMDL_COMMENT,
		};
	}
getConfiguredContentTypes()でコンテンツタイプの一覧を返す。
(ドキュメントのデフォルトタイプと、DMDLPartitionScannerで定義したパーティション名)
	@Override
	public IPresentationReconciler getPresentationReconciler(ISourceViewer sourceViewer) {
		PresentationReconciler reconciler = new PresentationReconciler();
		{ // デフォルトの色の設定
			RGB c = new RGB(0, 0, 0);
			RuleBasedScanner scanner = new RuleBasedScanner();
			scanner.setDefaultReturnToken(new Token(new TextAttribute(colorManager.getColor(c))));
			DefaultDamagerRepairer dr = new DefaultDamagerRepairer(scanner);
			reconciler.setDamager(dr, IDocument.DEFAULT_CONTENT_TYPE);
			reconciler.setRepairer(dr, IDocument.DEFAULT_CONTENT_TYPE);
		}
		{ // コメントの色の設定
			RGB c = new RGB(0, 192, 0);
			NonRuleBasedDamagerRepairer dr = new NonRuleBasedDamagerRepairer(new TextAttribute(colorManager.getColor(c)));
			reconciler.setDamager(dr, DMDLPartitionScanner.DMDL_COMMENT);
			reconciler.setRepairer(dr, DMDLPartitionScanner.DMDL_COMMENT);
		}
		return reconciler;
	}
}
getPresentationReconciler()でPresentationReconciler(リコンサイラー:調停者)を返す。
reconcilerには、コンテンツタイプ毎のDamager(ダメージャー)・Repairer(リペアラー)を設定する。
Damagerは、ドキュメント(テキスト)が変更された際に、変更範囲を算出するクラス。
Repairerは、変更範囲を修復する(今回だと、色を付ける)クラス。
DamagerとRepairerは密接な関係があるので、DamagerRepairerという両方を実装したクラスが使われる。
| DefaultDamagerRepairer | NonRuleBasedDamagerRepairer | 
|---|---|
| Eclipseプラグインで提供されているクラス。 | XMLEditorテンプレートで生成されるクラスだが、そのまま他でも使える。 | 
| パーティションの中をさらに構文によってスタイル(色とか)を変えたい場合は、その構文解析用のScannerを作り、DefaultDamagerRepairerを使う。 | パーティションの中に違いが無い(例えばコメントの中は特に構文など無い)場合は、NonRuleBasedDamagerRepairerを使う。 | 
| 今回の例では、ScannerにRuleBasedScannerをそのまま使っている。 setDefaultReturnToken()によって、ルール(構文)に合致しない際のスタイル(デフォルト値)を指定している。 RuleBasedScannerそのままだとルールが空なので、必ずデフォルト値が返ることになる。  | 
		
DMDL_COMMENTに対するDamagerRepairerだけ定義してDEFAULT_CONTENT_TYPEに対するDamagerRepairerを定義しなかった場合は、
ドキュメント(テキスト)上でコメントを付けるとコメントの色に変わるが、コメントを外した時に色が変わらない(コメント色のままになる)。
ただ、今のままだと、「/*」「*/」でくくった範囲に行を追加したり削除したりした時に色がコメントにならない事があるんだよなぁ。
何か考慮漏れがあるんじゃないかと思うが…。