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() { } }
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引数にはコンテンツタイプ(パーティションスキャナーで判別した範囲の名前)を指定する。
整形を実行する切っ掛けとなるコマンドを定義する。
<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を表す。
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クラスが呼ばれる)
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()は使わないので実装するものは何も無いのだが、インターフェースで定義されているメソッドなので空実装しておく。
public class DMDLConfiguration extends SourceViewerConfiguration { 〜
@Override public IContentFormatter getContentFormatter(ISourceViewer sourceViewer) { ContentFormatter formatter = new DMDLContentFormatter(); return formatter; } }