Eclipseプラグイン実践テクニック(2)
高機能なXMLエディタをプラグインとして作る


NTTデータ先端技術 竹添直樹
NTTデータ 基盤システム事業本部 岡本隆史
2006/9/28

テキストエディタの拡張

 「作って覚えるEclipseプラグイン」の第3回ではPDEのサンプルとして用意されているXMLエディタを題材に、テキストエディタの基本について解説しました。しかしこのエディタは、強調表示機能を備えているだけの非常にシンプルなエディタでした。本連載の第2回では、このXMLエディタに以下のような機能を追加してみようと思います。なお、本稿で作成したサンプルコードはここからダウンロードできます。

  • アウトライン表示機能
  • コードアシスト機能
  • ダブルクリック時の動作の改善
  • ハイパーリンク機能
アウトラインの実装

 Eclipseには標準でアウトライン・ビューが用意されており、Javaソースコードなどを編集する際にはソースコードに含まれるメソッド等をツリー状に表示することができます。また、このアウトラインをマウスでクリックするとエディタ上でそのメソッドの位置にジャンプすることができます。XMLエディタでも編集中のXML要素の構造をアウトライン・ビューに表示できるようにしてみましょう。

 アウトライン・ビューはアクティブなエディタのgetAdapter()メソッドを介してorg.eclipse.ui.views.contentoutline.IContentOutlinePageインターフェイスを実装したオブジェクト(アウトラインページ)を取得します。このアウトラインページがアウトライン・ビューに表示するコンテンツを提供します。

図1 アウトライン・ビューとエディタの関係

 エディタ名、プリファレンスページ名はともに英語での表記になっていますので、この部分を外部化してみます。プラグインのルートディレクトリにplugin.properties、plugin_ja.propertiesを作成し、それぞれ以下のように記述しておきます。

リスト1
public class XMLEditor extends TextEditor ... {

  // IContentOutlinePageの実装クラス
  private XMLOutlinePage outlinePage;

  ...

  public void dispose() {
    if(outlinePage!=null){
      outlinePage.dispose();
    }

      ...
    }
  public Object getAdapter(Class adapter) {
    if(IContentOutlinePage.class.equals(adapter)){
      if(outlinePage==null){
        outlinePage = new XMLOutlinePage(this);
      }
      return outlinePage;
    }
    return super.getAdapter(adapter);
  }


  ...
}

 肝心のアウトラインページの実装ですが、JFaceのTreeViewerを使用してツリー形式のアウトラインを表示する場合はContentOutlinePageを継承して必要な部分のみオーバーライドすることで実装することが可能です(アウトライン・ビューにはツリー以外の内容も表示することは可能です)。なお、今回はXMLのツリー構造を取得するために通常のXMLパーサではなくFuzzyXMLというライブラリを使用しました。FuzzyXMLは以下のような特徴を持つ簡易XMLパーサで、DOM風のAPIを提供しています。

  • 整形式でないXMLでもパースできる。
  • パース後のDOM ノードがテキスト上の位置情報を保持している。
  • DOM ノードへの操作と元のテキストとの同期を取るための機能を備えている。

 アウトライン・ビューで要素をクリックした際にソースエディタの該当位置にジャンプできるようにしたいので「DOMノードがテキスト上の位置情報を保持している」という特徴が必要になってくるのです。また、入力途中で整形式になっていないXMLでもアウトラインを表示できるというメリットもあります。

 以下がFuzzyXMLを使用してXMLのDOM構造をTreeViewerで表示するアウトラインページの実装です。

リスト2
public class XMLOutlinePage extends ContentOutlinePage {

  private RootModel root = new RootModel();
  private XMLEditor editor = null;
  public XMLOutlinePage(XMLEditor editor){
  this.editor = editor;
  }

  public void createControl(Composite parent) { // …… (1)
    super.createControl(parent);

    TreeViewer viewer = getTreeViewer();
    viewer.setContentProvider(new XMLContentProvider());
    viewer.setLabelProvider(new XMLLabelProvider());

    viewer.addSelectionChangedListener
    (new IselectionChangedListener(){ // …… (2)
      public void selectionChanged
      (SelectionChangedEvent event) {
        IStructuredSelection sel =
        (IStructuredSelection)event.getSelection();
        Object element = sel.getFirstElement();
        if(element instanceof FuzzyXMLNode){
          int offset = ((FuzzyXMLNode)element).getOffset();
          editor.selectAndReveal(offset, 0);
        }
      }
    });

    viewer.setInput(root);

    refresh();
  }

  public void refresh(){ // …… (3)
    // エディタからソースを取得
    IDocumentProvider provider = editor.getDocumentProvider();
    IDocument document =
    provider.getDocument(editor.getEditorInput());
    String source = document.get();
    // FuzzyXMLでパース
    FuzzyXMLDocument doc = new FuzzyXMLParser().parse(source);
    // ルートモデルにルート要素をセット
    root.documentElement = doc.getDocumentElement();
    // ツリービューアをリフレッシュ
    getTreeViewer().refresh();
  }

  /** ツリービューアのルートモデル */
  private class RootModel {
    private FuzzyXMLElement documentElement;
  }

  ...
}

 上記のコードで行っていることを解説していきましょう。

 (1)のcreateControl()メソッドでTreeViewerの設定を行っています。コンテンツプロバイダとラベルプロバイダの実装については誌面の都合上割愛します。詳細については実際にサンプルコードをダウンロードしてご覧ください。また、TreeViewerの使用方法については「作って覚えるEclipseプラグイン」第2回で詳しく解説していますので併せてご覧ください。

 (2)のaddSelectionChangedListener()で登録しているリスナはツリーの要素が選択された場合に呼び出されるもので、エディタの該当個所へジャンプする処理を実装しています。

 (3)のrefresh()メソッドで実際にFuzzyXMLを使用してXMLをパースし、TreeViewerに表示するデータを生成しています。このメソッドは外部からも呼び出せるようにpublicメソッドとして切り出しておきます。

 この状態でランタイム・ワークベンチを起動し、適当なXMLファイルを開くと、以下のようなツリーがアウトライン・ビューに表示されます。また、アウトライン・ビューの各要素をクリックするとエディタ上の該当する部分にジャンプするはずです。

図2 完成したアウトライン・ビュー

 さて、これだけではXMLを編集してもアウトライン・ビューには反映されず、いつまでたっても最初に表示された状態のままです。どこかのタイミングでエディタでの編集内容をアウトラインページに反映する必要があります。EclipseのJavaエディタではソースコードの編集中にリアルタイムにアウトラインにも反映が行われますが、ここでは話を簡単にするためにエディタの保存時に反映することにします。エディタクラスのdoSave()メソッドとdoSaveAs()メソッドを以下のようにオーバーライドします。

リスト3
public void doSave(IProgressMonitor progressMonitor) {
  super.doSave(progressMonitor);
  outlinePage.refresh();
}

public void doSaveAs() {
    super.doSaveAs();
    outlinePage.refresh();
}

 これでエディタを保存するたびにアウトラインページの内容が更新されるようになります。

コラム
IAdaptable インターフェイス
Eclipseプラグイン開発でも頻繁に利用されるインターフェイスの1つにIAdaptableというインターフェイスがあります。 このインターフェイスはObject getAdapter(Class adapter)というメソッドを1つだけ定義しており、Eclipseの提供するさまざまなクラスがこのインターフェイスを実装しています。
それではこのgetAdapter()メソッドはどのようなときに使用するのでしょうか。今回のサンプルではアウトライン・ビューがエディタからアウトラインページ
(IContentOutlinePageの実装クラス)を取得するためにgetAdapter()メソッドを使用しました(アウトラインをサポートしていないエディタの場合はnullが返却されます)。このように、IAdaptableインターフェイスを使用すると、エディタ自身がIContentOutlinePageインターフェイスを実装しなくても、getAdapter()メソッドを介してアウトラインページを提供することができるのです。
特にエディタのようなコンポーネントでは非常に多くの機能を提供する必要がありますが、このような場合でもエディタ自身の実装をシンプルに保ったまま機能の追加を行うことが容易になり、それぞれの機能を再利用しやすい形で実装することができます。またサポートする型を動的に追加するといったことも可能になります。


  1/4

 INDEX

第2回 高機能なXMLエディタをプラグインとして作る

Page1
テキストエディタの拡張
  Page2
コードアシストの実装
  Page3
ダブルクリック時の動作のカスタマイズ
  Page4
ハイパーリンク機能



Java Solution全記事一覧



Java Agile フォーラム 新着記事
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Java Agile 記事ランキング

本日 月間