S-JIS[2013-02-04/2013-02-09] 変更履歴

EclipseプラグインDMDLエディター(整形)

Eclipseプラグイン開発自作DMDLエディターでソースの整形が出来るようにしてみる。


概要

ソースの整形(フォーマッティング)とは、一定のルールに従って空白や改行を入れて綺麗に並べること。

整形を実施するクラスを用意し、コマンド(もしくは旧来のアクション)で呼び出すようにする。


基本的な例

整形を行うクラスはIFormattingStrategyを実装する。

import org.eclipse.jface.text.formatter.IFormattingStrategy;
public class DMDLFormattingStrategy implements IFormattingStrategy {
	@Override
	public void formatterStarts(String initialIndentation) {
	}
	@Override
	public String format(String content, boolean isLineStart, String indentation, int[] positions) {
		String formatted = contentを整形;
		return formatted;
	}
	@Override
	public void formatterStops() {
	}
}

DMDLConfiguration

SourceViewerConfigurationクラスIFormattingStrategyクラスを登録する。

import org.eclipse.jface.text.formatter.ContentFormatter;
public class DMDLConfiguration extends SourceViewerConfiguration {
〜
	@Override
	public IContentFormatter getContentFormatter(ISourceViewer sourceViewer) {
		ContentFormatter formatter = new ContentFormatter();
		formatter.setFormattingStrategy(new DMDLFormattingStrategy(), DMDLPartitionScanner.DMDL_BLOCK);
		return formatter;
	}
}

formatterインスタンスを生成し、FormattingStrategyをセットする。第2引数にはコンテンツタイプ(パーティションスキャナーで判別した範囲の名前)を指定する。


コマンド

整形を実行する切っ掛けとなるコマンドを定義する。

plugin.xml:

   <extension
         point="org.eclipse.ui.commands">
      <category
            id="dmdl-editor-plugin.category"
            name="category-name">
      </category>
      <command
            categoryId="dmdl-editor-plugin.category"
            id="dmdl-editor-plugin.formatting-command"
            name="formatting-command">
      </command>
   </extension>
   <extension
         point="org.eclipse.ui.handlers">
      <handler
            class="jp.hishidama.eclipse_plugin.dmdl_editor.editors.style.format.FormattingHandler"
            commandId="dmdl-editor-plugin.formatting-command">
      </handler>
   </extension>
   <extension
         point="org.eclipse.ui.bindings">
      <key
            commandId="dmdl-editor-plugin.formatting-command"
            contextId="org.eclipse.ui.contexts.window"
            schemeId="org.eclipse.ui.defaultAcceleratorConfiguration"
            sequence="M1+M2+F">
      </key>
   </extension>

commandsでコマンドIDを定義し、
handlersでコマンドIDに対して実際に実行するクラス(ハンドラー)を定義する。
bindingsでコマンドIDに対してキーバインドを定義する。Windows/UNIXではM1がCtrlでM2がShiftなので、M1+M2+FはCtrl+Shift+Fを表す。

FormattingHandler.java:

import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.jface.text.ITextOperationTarget;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.handlers.HandlerUtil;
public class FormattingHandler extends AbstractHandler {
	@Override
	public Object execute(ExecutionEvent event) throws ExecutionException {
		IEditorPart editor = HandlerUtil.getActiveEditor(event);
		ITextOperationTarget target = (ITextOperationTarget) editor.getAdapter(ITextOperationTarget.class);
		target.doOperation(ISourceViewer.FORMAT);

		return null;
	}
}

HandlerUtilを使って、対象のエディター(今回の場合、実体はTextEditor)を取得する。
そして、エディターのgetAdapter()を使ってITextOperationTargetを取得する。

ITextOperationTargetの実体はTextEditor#createSourceViewer()で生成されたISourceViewerインスタンス。
したがって、IEditorPartをTextEditorにキャストしてgetSourceViewer()で取得すればgetAdapter()を使う必要は無い。
のだが、getSourceViewer()はpublicメソッドではないので、呼べないのだった(苦笑)

で、ITextOperationTarget#doOperation()にFORMATを渡すと整形処理が実行される。(最終的に、登録したIFormattingStrategyクラスが呼ばれる)


IContentFormatterの例

IFormattingStrategyを使う方法には致命的な問題がある。[2013-02-09]
それは、format()メソッドには整形元となる文字列しか入ってこないということ。

ソース整形では、ユーザーが範囲指定をすることによって、その範囲だけを整形することが出来る。
しかしformat()メソッドには選択された文字列だけしか渡ってこない為、それが(文法的に)どの部分なのかを判別することが出来ない。
せめてDocumentオブジェクトも渡してくれればそこから判断できるのだが…。

というわけで、IFormattingStrategyを使わず、その呼び出し元であるIContentFormatterを直接使えばDocumentを扱うことが出来る。

import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.formatter.IContentFormatter;
import org.eclipse.jface.text.formatter.IFormattingStrategy;
public class DMDLContentFormatter implements IContentFormatter {
	@Override
	public void format(IDocument document, IRegion region) {
		// ソースを整形する範囲
		int start  = region.getOffset();
		int length = region.getLength();

		String formattedText = ソースを整形

		// ソースを置換
		try {
			document.replace(start, length, formattedText);
		} catch (BadLocationException e) {
			ILog log = Activator.getDefault().getLog();
			log.log(new Status(Status.WARNING, Activator.PLUGIN_ID, "DMDLContentFormatter bad location.", e));
		}
	}

org.eclipse.jface.text.formatter.ContentFormatterがIContentFormatterの基本的な実装なので、参考になる。
ContentFormatterでは置換(document.replace()呼び出し)の前後でUpdaterとかいうやつを使ってごちょごちょやっているが、とりあえずはそれは無くても大丈夫そう。

	@Override
	public IFormattingStrategy getFormattingStrategy(String contentType) {
		return null; // 使用しない
	}
}

getFormattingStrategy()は使わないので実装するものは何も無いのだが、インターフェースで定義されているメソッドなので空実装しておく。


DMDLConfiguration.java:

public class DMDLConfiguration extends SourceViewerConfiguration {
〜
	@Override
	public IContentFormatter getContentFormatter(ISourceViewer sourceViewer) {
		ContentFormatter formatter = new DMDLContentFormatter();
		return formatter;
	}
}

Eclipseプラグインへ戻る / Eclipseへ戻る / 技術メモへ戻る
メールの送信先:ひしだま