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;
}
}