読者です 読者をやめる 読者になる 読者になる

EDIT MODE

モバイルアプリ開発に関することを書いています

ToolWindow タイプの Intellij Plugin 作成の Tips

初めて Intellij Plugin を作ってみました。

どんな Plugin を作ったかは、前の記事に書いたので興味あるかたは読んでみてください。 また作成した Plugin のコードGitHub で公開しています。

Intellij Plugin を作ってみて感じこと

ちょっとググると分かりますが、言語を限定しなくても関連する情報が少ないです。

後述するブログやサイトと、その中で紹介されてるブログやサイトでほぼめぼしい情報が底をつきます。

あとはもう、OSS として公開されてる Intellij Plugin のコードを参考にするか、Intellij IDEA CE のコードを参考にするしかありませんでした。

とはいえ、メインの技術は Java Swing という枯れた技術なので、そこは情報量豊富でした。 そのため調べながら作るとしたら、以下のような組み合わせを繰り返す感じになると思います。

  • Plugin 作成の流れや基本 >> 公式サイトや Plugin 作成について書かれたブログを見る
  • UI まわりの基本 >> Swing 関連のサイトやブログを見る
  • ピンポイントでの実装方法 >> OSS の Plugin のコードを読む
  • 上記でも解決できない問題 >> Intellij IDEA CE のコードを読む

Plugin のタイプ

AndroidStudio や Intellij IDEA などを実際に使ってる人には説明不要かと思いますが、Plugin をざっくり3タイプに分けるとこんな感じだと思ってます。

  1. ほぼ UI を持たないサポート系
  2. 特定のタイミングで機能するダイアログ系
  3. 常駐?して機能するツール

1 は CodeGenerator とか Editor 系の Plugin を指してます。

2 はそのまんまですが Importer 系とか FileGenerator とかいろいろありますね。

3 は ProjectTree とか BuildTool 系で、いわゆる画面の左端や右端、もしくは下部に表示/非表示を切り替えられるタイプの Plugin です。

ToolWindow の作成

Plugin プロジェクトの作成方法などは、後述するサイトなどで詳しく紹介されているのでここでは触れません。 まずは ToolWindow を表示するために必要な手順を書いていこうと思います。

1. ToolWindow にコンテンツをセットするクラスを作る

以下のように ToolWindow に表示する UI をセットするためのクラスを追加します。ポイントは、ToolWindowFactory インターフェースを実装する点です。

public class AndroidDrawableViewerToolWindowFactory implements ToolWindowFactory {

    @Override
    public void createToolWindowContent(Project project, ToolWindow toolWindow) {
        JLabel label = new JLabel("Hello World");

        final ContentManager contentManager = toolWindow.getContentManager();
        final Content content = contentManager.getFactory().createContent(label, null, false);
        contentManager.addContent(content);
    }
}

createContent(JComponent, String, boolean) メソッドで、第一引数に Swing の UIConponent をセットでき、主にそれが ToolWindow 内に表示されます。

2. plugin.xml に ToolWindow を生成する宣言を記述する

作成する Plugin の初期化時に、1 で作成した処理が動くように宣言する必要があります。 記述するのは、 <extension> タグの中です。

  <extensions defaultExtensionNs="com.intellij">
    <!-- Add your extensions here -->
    <toolWindow anchor="right" canCloseContents="true" id="DrawableViewer"
                factoryClass="com.androhi.androiddrawableviewer.AndroidDrawableViewerToolWindowFactory" />
  </extensions>

各属性について詳しくは公式サイトの ここ に書かれていますが、補足すると factoryClass の指定は完全修飾名(パッケージ名込み)で書かないとダメでした。

3. ToolWindow を表示する

1 と 2 を作成して実行すると、1 で createContent(JComponent, String, boolean) メソッドに渡した JComponent(ここだと"Hello World"と表示するラベル)が表示される ToolWindow を表示できると思います。

ToolWindow の表示切り替えは、メニューから行う方法と ToolButtons で行う方法があります。

メニューでの切り替え

View > ToolWindowsにPlugin名

f:id:androhi:20150723231446p:plain

ToolButtons での切り替え

View > Tool Buttons がオンの状態で plugin.xml で指定した anchor の場所のボタン(ここではWindow右端)

f:id:androhi:20150723231510p:plain

ToolWindow に ToolBar を設置する

下図のように ToolWindow 内には、何かしらのボタン郡があるものが多いと思います。この領域は ToolBar と呼ばれるものらしく、コンテンツ部分とは独立して配置されるようです。

f:id:androhi:20150723233841p:plain

このパターンの ToolWindow には、SimpleToolWindowPanel という JPanel のサブクラスを使うのが一般的らしいです。(主に Intellij CE のコードを見て知りました。)

public class DrawableViewer extends SimpleToolWindowPanel {

    public DrawableViewer(final Project project) {
        super(true, true);
        this.project = project;

        setToolbar(createToolbarPanel());
        setContent(createContentPanel());
    }

    private JComponent createToolbarPanel() {
        final DefaultActionGroup actionGroup = new DefaultActionGroup();
        actionGroup.add(new AnAction());
        final ActionToolbar actionToolbar = ActionManager.getInstance().createActionToolbar("AndroidDrawableViewer", actionGroup, true);
        return actionToolbar.getComponent();
    }

    private JScrollPane createContentPanel() {
        String projectPath = project.getBasePath();
        ...
    }
    ....
}

上のコードで、setToolBar(JComponent) メソッドで ToolBar 領域の UI を、setContent(JComponent) メソッドで Content 領域の UI をそれぞれセットする仕組みになっています。

setContent() の方は、割といろいろな JComponent がセットできますが、setToolBar() の方は AnAction クラスもしくはサブクラス(要はアイコンボタン)のグループを内包した ActionToolbar の Component をセットするのがセオリーなようです。

ここでは見やすいように new Action() をセットしていますが、実際には固有のアイコンや処理を定義するので、AnAction のサブクラスのインスタンスをセットします。そのあたりは、GitHubのコード で全体を見ていただいた方が分かり易いかもしれません。

ToolWindow の表示更新

これがかなり悩みました。

以下に書いた内容も正しいか自信がありません。もっとよい方法をご存知の方は、ぜひ教えてください。

やりたかったことは、Action があったときに ToolWindow のコンテンツを新しいデータを反映した後、再描画するという内容です。

デコンパイルした Intellij 側のコード見たりいろいろと試したのですが再描画できず、結局コンテンツを再生成してセットし直すことで、期待した動きになりました。

以下は先程の ToolBar のボタンをクリックしたときのアクションクラスです。

public class EditTargetResDirAction extends AnAction {

    public EditTargetResDirAction() {
        super("Edit directory", "Edit target resource directory", AllIcons.General.Settings);
    }

    @Override
    public void actionPerformed(AnActionEvent anActionEvent) {
        Project project = anActionEvent.getProject();
        ...
        final VirtualFile file = FileChooser.chooseFile(descriptor, project, selectDir);
        if (file != null) {
            ...
            resetContent(project);
        }
    }

    private void resetContent(Project project) {
        DrawableViewer drawableViewer = new DrawableViewer(project);
        ContentManager contentManager = ToolWindowManager.getInstance(project)
                .getToolWindow(DrawableViewer.TOOL_WINDOW_ID).getContentManager();
        Content content = contentManager.getFactory().createContent(drawableViewer, null, false);

        contentManager.removeAllContents(true);
        contentManager.addContent(content);
    }

getToolWindow(String) メソッドに plugin.xml に記述した ToolWindow の ID を渡すと同じ ToolWindow が取得できるので、それに対して再生成した JComponent インスタンス(=drawableViewer)をセットし直しています。 セットするときは、Add するのでその前に古い方は Remove しています。

まとめ

ToolWindow まわりは Swing では無く Intellij の仕組みなので、特に公開されてる情報が少なく途中で心が折れかけました。

でも ToolWindow で実現する Plugin は、そのコンテンツを見ながらコードを書けたりするので、いろいろと楽しい Plugin が作れるんじゃないかなーと感じました。

加えて Intellij IDEA 14 から実装されたデコンパイル機能が、ものすごく重宝しました。これのおかげで公開まで辿りつけた気がします。

そしてめでたく JetBrains Plugin Repository に公開されたので、もし興味ある方は感想などいただけると嬉しいです。

AndroidDrawableViewer f:id:androhi:20150723230925p:plain

参考にしたサイト