S-JIS[2013-01-24] 変更履歴

EclipseプラグインDMDLエディター(アウトライン)

Eclipseプラグイン開発自作DMDLエディターでアウトラインを表示できるようにしてみる。


概要

アウトライン(アウトラインページ)は、Javaエディターで言えば(Eclipse3.7の右側に出ている)クラスやフィールド・メソッドの一覧のやつ。

アウトラインは要するにツリーなので、データとしてツリー構造を用意する。


依存パッケージ

アウトラインを作るには、org.eclipse.ui.views.contentoutline.ContentOutlinePageクラスを使用する。
これをimport文に指定して“ContentOutlinePageが見つからない”というコンパイルエラーになる場合は、「依存関係」の「インストール済みパッケージ」に「org.eclipse.ui.views.contentoutline」を追加する。
もしくは、「依存関係」の「必須プラグイン」で「org.eclipse.ui.views」を追加する。


アウトラインを作るには、ドキュメント(エディター上のテキスト)を構文解析してアウトラインに表示するものを決める必要がある。

DMDLエディターの場合は、データモデル名と、その中で定義されているプロパティー名を出すのが良さそう。
↓こんなイメージ

なお、ここでは詳細は割愛するが、アウトラインやフォールディングで使えるように、簡単なパーサー(DMDLSimpleParser)を作成した。
アウトラインのツリー構造を作るのに このパーサーを利用する。


OutlinePage.java

中核となるアウトラインページクラス。
インスタンス生成箇所

import jp.hishidama.eclipse_plugin.dmdl_editor.editors.DMDLEditor;
import jp.hishidama.eclipse_plugin.dmdl_editor.parser.DMDLSimpleParser;
import jp.hishidama.eclipse_plugin.dmdl_editor.parser.token.ModelList;

import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.views.contentoutline.ContentOutlinePage;
public class OutlinePage extends ContentOutlinePage {
	protected RootData root = new RootData();
	protected DMDLEditor editor;

	public OutlinePage(DMDLEditor editor) {
		this.editor = editor;
	}

コンストラクターでDMDLEditorのインスタンスを保持しておく。

	@Override
	public void createControl(Composite parent) {
		super.createControl(parent);

		TreeViewer viewer = getTreeViewer();
		viewer.setContentProvider(new DMDLContentProvider());
		viewer.setLabelProvider(new DMDLLabelProvider());
		viewer.addSelectionChangedListener(new OutlineSelectionChangedListener(editor));
		viewer.setInput(root);

		IDocument document = editor.getDocumentProvider().getDocument(editor.getEditorInput());
		DMDLSimpleParser parser = new DMDLSimpleParser();
		ModelList models = parser.parse(document);
		refresh(models);
	}

createControl()でアウトライン関連クラスの初期化を行う。
viewer.setInput()でアウトラインのツリーのデータを管理するオブジェクトを指定する。

	public void refresh(ModelList models) {
		root.element = models;
		getTreeViewer().refresh();
	}
}

refresh()は、アウトラインのツリーの再描画を行うメソッド。
外部から呼べるようにpublicにしておく。


RootData.java

アウトラインのデータ(ツリーのルート)を保持する為のクラスを用意する。

import jp.hishidama.eclipse_plugin.dmdl_editor.parser.token.ModelList;
public class RootData {
	public ModelList element;
}

element要素を持っているだけ。非常にシンプル(笑)

(ModelListは、自作したDMDLSimpleParserが返すクラス。構文解析結果を保持している)


DMDLContentProvider.java

ContentProviderは、アウトラインのツリーの内容を管理するクラス。
OutlinePageにてviewer.setContentProvider()で設定する。

import java.util.List;

import jp.hishidama.eclipse_plugin.dmdl_editor.parser.token.ModelToken;
import jp.hishidama.eclipse_plugin.dmdl_editor.parser.token.PropertyToken;

import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.Viewer;
public class DMDLContentProvider implements ITreeContentProvider {

	@Override
	public Object[] getElements(Object inputElement) {
		RootData root = (RootData) inputElement;
		if (root.element == null) {
			return new Object[] {};
		}

		return root.element.getModelList().toArray();
	}

getElements()でツリーのルート直下に表示されるデータ一覧を返すようにする。
ここに渡ってくるinputElementは、OutlinePage#createControl()にてviewer.setInput()でセットしたもの。

	@Override
	public boolean hasChildren(Object element) {
		if (element instanceof ModelToken) {
			ModelToken model = (ModelToken) element;
			List<PropertyToken> list = model.getPropertyList();
			return !list.isEmpty();
		}
		return false;
	}

hasChildren()は、引数のelementの子要素があるかどうかを返す。

	@Override
	public Object[] getChildren(Object parentElement) {
		if (parentElement instanceof ModelToken) {
			ModelToken model = (ModelToken) parentElement;
			List<PropertyToken> list = model.getPropertyList();
			return list.toArray();
		}
		return null;
	}

getChildren()は、引数のparentElementの子要素一覧を返す。
hasChildren()の引数名はelementなのに、なんでgetChildren()の引数名はparentElementなんだ^^;

	@Override
	public Object getParent(Object element) {
		return null;
	}

	@Override
	public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
	}

	@Override
	public void dispose() {
	}
}

DMDLLabelProvider.java

LabelProviderは、ツリーの要素を実際に表示する際の文言やイメージ(画像)を管理するクラス。
OutlinePageにてviewer.setLabelProvider()で設定する。

import java.net.URL;

import jp.hishidama.eclipse_plugin.dmdl_editor.parser.token.ModelToken;
import jp.hishidama.eclipse_plugin.dmdl_editor.parser.token.PropertyToken;

import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.swt.graphics.Image;
public class DMDLLabelProvider extends LabelProvider {
	@Override
	public Image getImage(Object element) {
		return null;
	}

画像を表示する方法

	@Override
	public String getText(Object element) {
		if (element instanceof ModelToken) {
			ModelToken token = (ModelToken) element;
			return token.getModelName();
		}
		if (element instanceof PropertyToken) {
			PropertyToken token = (PropertyToken) element;
			String name = token.getPropertyName();
			String type = token.getDataType();
			if (type == null) {
				return name;
			}
			return name + " : " + type;
		}
		return element.toString();
	}
}

DMDLエディターでは、ツリーの要素がデータモデルを表している場合はモデル名、プロパティーの場合は「プロパティー名 : データ型」を表示するようにしてみた。

文字に色を付ける方法


OutlineSelectionChangedListener.java

SelectionChangedListenerは、アウトライン上でデータ(要素・ノード)がクリックされたときに呼ばれるリスナークラス。
OutlinePageにてviewer.addSelectionChangedListener()で設定する。

import jp.hishidama.eclipse_plugin.dmdl_editor.editors.DMDLEditor;
import jp.hishidama.eclipse_plugin.dmdl_editor.parser.token.DMDLToken;
import jp.hishidama.eclipse_plugin.dmdl_editor.parser.token.ModelToken;
import jp.hishidama.eclipse_plugin.dmdl_editor.parser.token.PropertyToken;

import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
public class OutlineSelectionChangedListener implements ISelectionChangedListener {
	protected DMDLEditor editor;

	public OutlineSelectionChangedListener(DMDLEditor editor) {
		this.editor = editor;
	}
	@Override
	public void selectionChanged(SelectionChangedEvent event) {
		IStructuredSelection sel = (IStructuredSelection) event.getSelection();
		Object element = sel.getFirstElement();

		DMDLToken token;
		if (element instanceof ModelToken) {
			ModelToken model = (ModelToken) element;
			token = model.getModelNameToken();
		} else if (element instanceof PropertyToken) {
			PropertyToken prop = (PropertyToken) element;
			token = prop.getPropertyNameToken();
		} else {
			token = (DMDLToken) element;
		}
		if (token != null) {
			int offset = token.getStart();
			int length = token.getLength();
			editor.selectAndReveal(offset, length);
		}
	}
}

アウトライン上でデータがクリックされるとselectionChanged()が呼ばれる。

  1. クリック(選択)されたデータ(element)を取得する。
  2. 該当データの、ドキュメント上の位置と長さ(offsetとlength)を取得する。
  3. エディター上でその範囲を選択する。(editor.selectAndReveal())

ちなみに、selectAndReveal()のlengthに0を指定するとカーソルがその位置に移動するだけとなる。


DMDLEditor.java

DMDLEditorでアウトラインの設定を行う。

import jp.hishidama.eclipse_plugin.dmdl_editor.editors.folding.FoldingManager;
import jp.hishidama.eclipse_plugin.dmdl_editor.editors.outline.OutlinePage;
import jp.hishidama.eclipse_plugin.dmdl_editor.parser.DMDLSimpleParser;
public class DMDLEditor extends TextEditor implements IPropertyChangeListener {
	protected OutlinePage outlinePage;
	@Override
	public Object getAdapter(@SuppressWarnings("rawtypes") Class adapter) {
		if (IContentOutlinePage.class.equals(adapter)) {
			if (outlinePage == null) {
				outlinePage = new OutlinePage(this);
			}
			return outlinePage;
		}

		// フォールディング
		Object obj = foldingManager.getAdapter(adapter);
		if (obj != null) {
			return obj;
		}

		return super.getAdapter(adapter);
	}

アウトラインでは、getAdapter()でContentOutlinePageインスタンスを生成して返すようにする。

	@Override
	public void doSaveAs() {
		super.doSaveAs();

		update();
	}

	@Override
	public void doSave(IProgressMonitor progressMonitor) {
		super.doSave(progressMonitor);

		update();
	}

	private void update() {
		IDocument document = getDocumentProvider().getDocument(getEditorInput());
		DMDLSimpleParser parser = new DMDLSimpleParser();
		ModelList models = parser.parse(document);

		// フォールディング範囲を最新状態に更新する
		foldingManager.updateFolding(document, models);

		// アウトラインを最新状態に更新する
		if (outlinePage != null) {
			outlinePage.refresh(models);
		}
	}

フォールディング処理と同様に、 ファイル保存時にアウトラインを再描画することにする。


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