Eclipseのプラグイン開発の自作DMDLエディターでエラーマーカーを表示してみる。
|
|
|
Javaエディターでは、(コンパイル)エラーがあるとその部分に赤い波線(下線・アンダーライン)が引かれ、その行の左側や右側にエラーマークが付く。
これは「マーカー」という機能。
マーカーはリソース(ファイル)に対して付ける。
マーカーを付けると、ファイルの該当部分にマークが付く他に、タスク(問題)リストにもマーカーの情報が表示される。
エラーマーカーを設定するに当たり、どこかでコンパイルしてエラーをチェックする必要がある。
今回は(フォールディングやアウトラインと同様に)ファイル保存時にチェックすることにする。
import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarker; import org.eclipse.ui.IFileEditorInput;
public class DMDLEditor extends TextEditor { 〜
// ファイル保存時に呼ばれる private void update() { DMDLDocument document = getDocument(); ModelList models = document.getModelList(); // フォールディング範囲を最新状態に更新する foldingManager.updateFolding(document, models); // アウトラインを最新状態に更新する if (outlinePage != null) { outlinePage.refresh(models); } // エラーマーカーを更新する IFileEditorInput input = (IFileEditorInput) getEditorInput(); parse(input.getFile(), document); }
マーカーはリソース(ファイル)に対して付けるので、エディターで編集している対象ファイルのIFileを取得(input.getFile())している。
→IFileとIFileEditorInputの依存プラグイン
private void parse(IFile file, IDocument document) { // Asakusa Framework本家のDMDLパーサーを使って構文解析 DmdlParser parser = new DmdlParser(); StringReader reader = new StringReader(document.get()); URI uri = file.getFullPath().toFile().toURI(); try { parser.parse(reader, uri); } catch (DmdlSyntaxException e) { // パースエラー createErrorMarker(e, file, document); } }
private void createErrorMarker(DmdlSyntaxException e, IFile file, IDocument document) { Region region = e.getRegion(); int beginOffset = document.getLineOffset(region.beginLine - 1) + (region.beginColumn - 1); int endOffset = document.getLineOffset(region.endLine - 1) + (region.endColumn - 1) + 1; IMarker marker = file.createMarker(IMarker.PROBLEM); marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR); marker.setAttribute(IMarker.MESSAGE, e.getMessage()); // marker.setAttribute(IMarker.LINE_NUMBER, region.beginLine - 1); marker.setAttribute(IMarker.CHAR_START, beginOffset); marker.setAttribute(IMarker.CHAR_END, endOffset); marker.setAttribute(IMarker.LOCATION, String.format("%d:%d-%d:%d", region.beginLine, region.beginColumn, region.endLine, region.endColumn)); } }
Asakusa Framework本家のDmdlParserでは、構文エラーがあるとDmdlSyntaxExceptionがスローされる。
そこからgetRegion()でエラーのあった場所が取れるので、ドキュメント上のオフセットに変換している。
(DmdlSyntaxExceptionのエラーメッセージにはファイル名が含まれている…今回のようにファイル名が自明なケースでは冗長になってしまうが^^;)
そして、IFile(IResource)のcreateMarker()でマーカーを生成する。
引数がマーカーのタイプで、色々な種類のマーカーがある(自分で作ることも出来る)らしい。
「問題(エラー)」を示すマーカータイプは「eclipse.core.resources.problemmarker」だが、IMarkerに定数が定義されているので、それを使う。
setAttribute()でマーカーの情報を色々設定する。
(MarkerUtilitiesに値をセットしたり取得したりするメソッドが用意されている。[2013-02-17])
属性名 | 属性値 | 備考 |
---|---|---|
SEVERITY | 重要度 | |
MESSAGE | メッセージ | |
LINE_NUMBER | 行番号 | 省略可能 |
CHAR_START | マーカーの開始位置 | ドキュメント内のオフセット |
CHAR_END | マーカーの終了位置 | ドキュメント内のオフセット |
LOCATION | マーカーの位置 | 人が見て分かるような文言 |
TRANSIENT | true/false | trueにすると、Eclipseを閉じると消える。[2013-02-11] |
CHAR_START・CHAR_ENDを省略すると、波線(下線)が付かない。
LINE_NUMBERを指定してLOCATIONを省略した場合、タスクリストのLOCATION欄にLINE_NUMBERの内容が表示される。
リソース(ファイル)に付けられているマーカーの一覧を取得するにはfindMarkers()を使う。[2013-10-29]
IResource resource = 〜; //IFile等 IMarker[] markers = resource.findMarkers(IMarker.PROBLEM, true, IResource.DEPTH_INFINITE); for (IMarker marker : markers) { 〜 }
マーカーをクリアするにはdelete()を使う。[2013-02-11]
IMarker marker = 〜; marker.delete();
リソース(ファイル)に付けられているマーカーを全て消すにはdeleteMarkers()を使う。
IResource resource = 〜; //IFile等 resource.deleteMarkers(IMarker.PROBLEM, true, IResource.DEPTH_INFINITE);
マーカーを設定するとエディター上のその行の左端にアイコンが表示されるが、そこにマウスカーソルを当てても何も表示されない。[2013-02-11]
ホバー(ポップアップ)メッセージを表示するにはIAnnotationHoverを設定する必要がある。
import org.eclipse.jface.text.source.DefaultAnnotationHover; import org.eclipse.jface.text.source.IAnnotationHover;
public class DMDLConfiguration extends SourceViewerConfiguration { 〜
@Override public IAnnotationHover getAnnotationHover(ISourceViewer sourceViewer) { return new DefaultAnnotationHover(); } }
これで、マーカーに設定されているメッセージが(アイコンの位置の)ポップアップで表示されるようになる。
参考: stackoverflowのHover text for problem marker in Eclipse plugin
マーカーを設定するとエディター上のその位置に赤い下線(波線)が引かれるが、そこにマウスカーソルを当てても何も表示されない。[2013-02-12]
ホバー(ポップアップ)メッセージを表示するには ITextHoverを設定する必要がある。
import org.eclipse.jface.text.DefaultTextHover; import org.eclipse.jface.text.ITextHover;
public class DMDLConfiguration extends SourceViewerConfiguration { 〜
@Override public ITextHover getTextHover(ISourceViewer sourceViewer, String contentType) { return new DefaultTextHover(sourceViewer); } }
これで、マーカーに設定されているメッセージが(テキストの位置の)ポップアップで表示されるようになる。
参考: Eclipse Community ForumsのRe: How to implement Quick Fix / Quick Assist for custom editor?
DefaultTextHoverだと自分が設定したマーカー以外も出てくるようなので、ちょっと条件を付けた方が良さそう。[2013-02-16]
import org.eclipse.jface.text.source.Annotation; import org.eclipse.ui.texteditor.MarkerAnnotation;
@Override
public ITextHover getTextHover(ISourceViewer sourceViewer, String contentType) {
return new DefaultTextHover(sourceViewer) {
@Override
protected boolean isIncluded(Annotation annotation) {
if (annotation instanceof MarkerAnnotation) {
return true;
}
return false;
}
};
}
マーカーで指定された場所にジャンプする(エディター上のその単語を選択する)ことが出来る。[2013-02-17]
import org.eclipse.ui.ide.IGotoMarker;
IMarker marker = 〜; IGotoMarker target = (IGotoMarker) getAdapter(IGotoMarker.class); target.gotoMarker(marker);
ただし、(マーカーはリソース(IFile等)に対して作るが、)自動的にマーカーのリソース(ファイル)にジャンプしてくれるわけではない。
(IGotoMarkerの実装によるが、TextEditorは自分のエディター内のその場所を選択するだけ)
IFileからエディターオブジェクトを取得して、そこからgetAdapter()でIGotoMarkerを取得すればそのファイルへジャンプできる。
→IFileからエディターを取得する(開く)方法
IMarker marker = 〜; IEditorPart editor = 〜; IGotoMarker target = (IGotoMarker) editor.getAdapter(IGotoMarker.class); target.gotoMarker(marker);
nullチェック等のことを考えると、IDEクラスのgotoMarkerメソッドを使う方がいいかも。[2013-06-11]
import org.eclipse.ui.ide.IDE;
IMarker marker = 〜; IEditorPart editor = 〜; IDE.gotoMarker(editor, marker);
エラー(PROBLEM)マーカーを付けると、エディターの左側にエラーマーク、右側にエラーの印が表示される。[2013-06-05]
この左側のマークが付くエリア(列)を「vertical ruler(垂直方向ルーラー)」といい、右側の印が付くエリア(列)を「overview ruler(概説ルーラー)」という。
TextEditorのデフォルトではどちらのルーラーも表示される。
これはAbstractDecoratedTextEditor#createSourceViewer()で実装されている。
protected ISourceViewer createSourceViewer(Composite parent, IVerticalRuler ruler, int styles) { fAnnotationAccess= getAnnotationAccess(); fOverviewRuler= createOverviewRuler(getSharedColors()); ISourceViewer viewer= new SourceViewer(parent, ruler, getOverviewRuler(), isOverviewRulerVisible(), styles); // ensure decoration support has been created and configured. getSourceViewerDecorationSupport(viewer); return viewer; }
protected boolean isOverviewRulerVisible() { IPreferenceStore store= getPreferenceStore(); return store != null ? store.getBoolean(OVERVIEW_RULER) : false; }
getOverviewRuler()でnullを返したりisOverviewRulerVisible()でfalseを返したりするようにすると、overview rulerは表示されない。
(その列自体の表示領域が確保されない)
isOverviewRulerVisible()はPreferenceStoreの値を取得するようになっているが、デフォルトではtrueになっているようだ。