S-JIS[2013-02-23] 変更履歴

Eclipse JDT ハイパーリンク

Eclipseプラグイン開発JDTでのハイパーリンクについて。


概要

Javaエディターで独自のハイパーリンクを行いたい。
(Javaエディター上のハイパーリンクから自分が指定したファイルへジャンプさせたい)

しかし、以前もハイパーリンクを作ったが、この方式ではIHyperlinkDetectorのインスタンス生成をSourceViewerConfigurationで行っている為、独自テキストエディターを作る場合にしか使えない。

そこで、IHyperlinkDetectorの定義をextensionで行う。

依存プラグインは「org.eclipse.ui.workbench.texteditor」。


まず、ハイパーリンクを出すことが可能かどうかを判定するクラスを作成する。

JDTのハイパーリンクはorg.eclipse.jdt.internal.ui.javaeditor.JavaElementHyperlinkDetectorが使われているようだが、例によってこのクラスは非公開(Eclipseプラグインではたとえpublicであっても、明示的に“公開”が指定されているクラスでないと使用できない)なので直接使用することは出来ない。
でも非常に参考になる。

MyHyperlinkDetector.java:

import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeRoot;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.hyperlink.AbstractHyperlinkDetector;
import org.eclipse.jface.text.hyperlink.IHyperlink;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.texteditor.ITextEditor;
public class MyHyperlinkDetector extends AbstractHyperlinkDetector {
	@Override
	public IHyperlink[] detectHyperlinks(ITextViewer textViewer, IRegion region, boolean canShowMultipleHyperlinks) {
		// ハイパーリンクを表示するエディターを取得
		ITextEditor editor = (ITextEditor) getAdapter(ITextEditor.class);
		if (editor == null) {
			return null;
		}
		// IJavaElementを取得
		IEditorInput input = editor.getEditorInput();
		IJavaElement element = (IJavaElement) input.getAdapter(IJavaElement.class);

		// ドキュメントを取得
		IDocument document = editor.getDocumentProvider().getDocument(input);

		// マウスカーソルの指している位置の単語を取得
		int offset = region.getOffset();
		IRegion word = findWord(document, offset);
		if (word == null || word.getLength() == 0) {
			return null;
		}

		// 単語の位置のコードを取得
		IJavaElement[] codes;
		try {
			ITypeRoot root = (ITypeRoot) element.getAdapter(ITypeRoot.class);
			codes = root.codeSelect(word.getOffset(), word.getLength());
		} catch (JavaModelException e) {
			return null;
		}
		for (IJavaElement code : codes) {
			// コードの種類毎に分岐
			int elementType = code.getElementType();
			switch (elementType) {
			case IJavaElement.TYPE: //クラス
				IHyperlink[] tr = detectClassHyperlinks((IType) code, word);
				if (tr != null) {
					return tr;
				}
				break;
			case IJavaElement.FIELD: //フィールド
				IHyperlink[] fr = detectFieldHyperlinks((IField) code, word);
				if (fr != null) {
					return fr;
				}
				break;
			case IJavaElement.METHOD: //メソッド
				IHyperlink[] mr = detectMethodHyperlinks((IMethod) code, word);
				if (mr != null) {
					return mr;
				}
				break;
			}
		}
		return null;
	}
	// @see org.eclipse.jdt.internal.ui.text.JavaWordFinder#findWord()
	private static IRegion findWord(IDocument document, int offset) {
		int start = -2;
		int end = -1;
		try {
			int pos;
			for (pos = offset; pos >= 0; pos--) {
				char c = document.getChar(pos);
				if (!Character.isJavaIdentifierPart(c)) {
					break;
				}
			}

			start = pos;
			pos = offset;
			for (int length = document.getLength(); pos < length; pos++) {
				char c = document.getChar(pos);
				if (!Character.isJavaIdentifierPart(c)) {
					break;
				}
			}

			end = pos;
		} catch (BadLocationException e) {
		}
		if (start >= -1 && end > -1) {
			if (start == offset && end == offset) {
				return new Region(offset, 0);
			}
			if (start == offset) {
				return new Region(start, end - start);
			} else {
				return new Region(start + 1, end - start - 1);
			}
		} else {
			return null;
		}
	}
	private IHyperlink[] detectClassHyperlinks(IType type, IRegion region) {
		String name = type.getElementName();
		〜
		return new IHyperlink[] { new MyHyperlink(name, region) };
	}

	private IHyperlink[] detectFieldHyperlinks(IField field, IRegion region) {
		String name = field.getElementName();
		〜
		return new IHyperlink[] { new MyHyperlink(name, region) };
	}

	private IHyperlink[] detectMethodHyperlinks(IMethod method, IRegion region) {
		String name = method.getElementName();
		〜
		return new IHyperlink[] { new MyHyperlink(name, region) };
	}
}

※IJavaElementを使わず、直接ASTNodeを使えば、例えば文字列等でも判定することが出来る。


マニフェストファイルのextensionでハイパーリンクの指定を行う。

plugin.xml:

   <extension
         point="org.eclipse.ui.workbench.texteditor.hyperlinkDetectors">
      <hyperlinkDetector
            activate="true"
            class="jp.hishidama.eclipse_plugin.dmdl_editor.jdt.hyperlink.MyHyperlinkDetector"
            description="Open Declared DMDL"
            id="dmdl-editor-plugin.hyperlinkDetector.jdt.open-declaration"
            name="Open Declared DMDL"
            targetId="org.eclipse.jdt.ui.javaCode">
      </hyperlinkDetector>
   </extension>

targetIdに「org.eclipse.jdt.ui.javaCode」を指定することで、Javaエディターの場合だけ呼ばれるようになる。

独自のtargetIdを定義する方法


MyHyperlink.java:

import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.hyperlink.IHyperlink;
public class MyHyperlink implements IHyperlink {
	private String name;
	private IRegion region;
	public MyHyperlink(String name, IRegion region) {
		this.name = name;
		this.region = region;
	}
	@Override
	public IRegion getHyperlinkRegion() {
		return region;
	}
	@Override
	public String getTypeLabel() {
		return "Open Declared DMDL";
	}
	@Override
	public String getHyperlinkText() {
		return "Open Declared DMDL";
	}

Javaエディターでクラス名やフィールド名・メソッド名のハイパーリンクをしようとすると、既存のハイパーリンクと被ってしまう。
その場合はポップアップメニューが出て、動作をユーザーが選択できる。
その際のメニュー名はgetHyperlinkText()で返した文字列になる。

	@Override
	public void open() {
		〜
	}
}

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