Kotlinで書くIntelliJ Plugin


概要

この記事はJetBrains Advent Calendar 2017 10日目の記事です。
「ただでさえ使いやすいIntelliJをもっと使いやすくしたい❗」ってことでPluginを作ってみた経験をもとに、作り方をまとめてみました。

Kotlinの方に書こうかと思ったけど、既に埋まってたしKotlin成分よりIntelliJ成分のほうが多かったので、こちらで書きました。

ちなみにIntelliJ Pluginと言っていますが、他のJetBrains製品(WebStormとかRubyMineとか)でも使えるようなユニバーサルなPluginも作れます。
(というか、製品固有の機能を使わない限りは大体ユニバーサルなものになります)

この記事における開発環境

  • IntelliJ Version: 2017.3 Build: 173.3727.127
  • Kotlin 1.2.0

作り方

JetBrains公式がIntelliJのSDKについての情報を公開しています。 → IntelliJ Platform SDK DevGuide
とは言え、ここに書いてある内容を端から端まで読むのもツライので、下記のような戦略で行くのが良いと思います。

  1. チュートリアル(Creating Your First Plugin)をこなして、plugin開発の概観を把握する。
  2. 作りたいものに必要なコンポーネントのドキュメントを読んで学ぶ
    • IntelliJ Platform SDK DevGuideに大体載っている
    • どのコンポーネントをどう使えばいいかサッパリな場合、「自分が作りたいものに近い既存プラグイン」のソースを読むのが一番手っ取り早い。
      (Community Editionのソース見りゃ良いっちゃ良いんだけど、最初に読むにはコード量が大きすぎるので、慣れるまではオススメしない)
  3. 作る(後ほど詳述)
  4. Plugin Repositoryに登録(公開したい場合)

というわけで、「3. 作る」の過程について順を追って説明していきます。

Pluginの作成手順

Pluginの作成は、Ultimate/Community Editionのどちらでも同じ流れで作成が可能です。
(以下のスクリーンショットはすべてCommunity Editionのものです)

雛形を作成する

gradle-intellij-plugin wizardを使って、手っ取り早く枠組みを作ることができます。

KotlinとJavaのバージョンを選択して、
(利用できるKotlinのバージョンはkotlin-gradle-plugin に準じます)

Pluginの情報を入力します。

ここで入力した内容はplugin.xmlbuild.gradleに反映されます。
もし入力漏れやミスがあったとしても、後から上記ファイルを編集すれば問題ありません。

雛形のみでの実行

雛形ができたら、一度コードを何も書かない状態でpluginをbuildしてみて、設定に問題がないことを確認しましょう。

PluginのBuild

[buildPlugin]というタスクがデフォルトで存在するので、それを実行します。

依存関係にIntelliJ Community Editionが入っているため、最初のbuildにはかなり時間がかかります。

BUILD SUCCESSFULが表示されればOKで、build/libsディレクトリ配下にPluginのjarファイルが生成されます。

Pluginのデバッグ実行

[runIdea]タスクを実行すれば、作成したPluginのデバッグ実行が可能です。

[runIdea]によるデバッグ実行では、開発に使っているIntelliJ本体とは別に「対象Pluginがインストールされた状態のIntelliJ(Community Edition)」が立ち上がり、そのプロセス上でPluginの挙動を確かめることができます。

起動したIntelliJのPluginリストを見ると、Pluginがインストールされた状態であることがわかります。

[runIdea]のタスクを終了すると、起動したIntelliJのプロセスも自動的に終了します。

コードを書く

というわけで、いよいよ本筋であるPlugin用のコードを書いていきましょう。
IntelliJのPlugin DevKitには多くのActionクラスを定義されており、それを継承したクラスを実装することで、「コンテキストメニューに独自メニューを追加」したり「ショートカットキーで何かを実行」することができます。

今回は、Creating Your First Pluginを参考にしつつ、以下のような機能を作っていきます。

  • Main Menuに独自メニューを追加
  • メニューを選択すると入力ダイアログを表示し、名前入力を促す
  • 入力した名前をもとにメッセージダイアログを表示する

手順としては、上記チュートリアルにある通り、

  1. 記述内容に応じたクラスを実装する(チュートリアルには"Java code"とありますが、Kotlinでも問題ありません)
  2. Actionの設定をplugin.xmlに記述する

の2ステップです。
ただ、Plugin DevKit > ActionメニューからActionを追加することで、自動的にAction設定がplugin.xmlに登録されるので、実際は、

  1. Actionを作る
  2. 生成されたクラスにコードを記述する

という流れになります。

Actionの作成

通常のJavaやKotlinのファイルを作成する流れと同様、Newメニューから Plugin DevKit > Actionと辿ることで新規Actionを作成できます。

必要な項目を入力してOKすると、

入力した名前のActionクラスが生成され、plugin.xmlにActionが登録されます。

↓自動生成されたTestActionクラスとplugin.xmlへの追加記述。

plugin.xml(抜粋)
<actions>
    <action id="Test.TestAction" class="test.intellij.plugin.test.TestAction" text="TestAction"
            description="Action for Test">
        <add-to-group group-id="MainMenu" anchor="first"/>
        <keyboard-shortcut keymap="$default" first-keystroke="ctrl alt M"/>
    </action>
</actions>

Java -> Kotlin 変換

次に、TestActionクラスの中身を実装していきます…が、その前にTestActionクラスをJavaからKotlinに変換しておきましょう。
(Javaのままやりたい場合は、このステップは不要です)

"Convert Java File to Kotlin File"で、

↓こうなります。

これはPlugin開発に限ったことではないのですが、手動でJavaファイルをKotlinに書き変える必要がありません。
この他に、JavaのコードをKotlinファイルにコピペすると「Kotlinのコードに変換しますか?」という確認ダイアログとともにKotlin化されたコードを貼り付けることができます。
「Javaは書けるけど、それをどうやってKotlinで表現すればよいか分からない」というときにとても重宝します。
(ただし、濫用するといつまで経ってもKotlinの文法を覚えられませんので気をつけましょう)

Actionの中身を実装する

チュートリアルに載っているTextBoxesクラスのコードをKotlin風に書き直しつつTestActionクラスを実装します。

TestAction.kt
class TestAction() : AnAction() {

    override fun actionPerformed(e: AnActionEvent) {
        val project = e.getData(PlatformDataKeys.PROJECT)
        val txt = Messages.showInputDialog(project,
                "What is your name?", "Input your name", Messages.getQuestionIcon())
        Messages.showMessageDialog(project,
                "Hello, $txt!\n I am glad to see you.", "Information", Messages.getInformationIcon())
    }
}

デバッグ実行

実装が終わったので、いよいよPluginの検証をしてみましょう。
雛形のときと同様に[runIdea]タスクを実行すると、別プロセスでIntelliJが立ち上がります。

今回のPluginはMain Menuにメニューを追加しているので、Main Menuを開くと"TestAction"メニューが表示されます。

"TestAction"を選択すると入力ダイアログが表示されます。
(ショートカットキーを登録している場合、ショートカットで直接開くことができます)

テキトーに名前を入力してOKすると、メッセージダイアログが開きます。

このシンプルな例だけで、

  • Main Menuへの独自メニュー追加
  • 入力ダイアログの表示
  • メッセージダイアログの表示
  • ショートカットキーによるActionの駆動

を試すことができました💫

ローカルでインストールする

作成したPluginはbuildしたjarファイルを用いてインストールすることができます。

そのPluginを利用するのが自分(もしくは周囲の数人程度)であれば、直接jarを配布してローカルでインストールするのが手っ取り早いです。

Plugin Repositoryに登録する

作成したPluginを広く公開したい場合は、JetBrains公式のPlugin Repositoryに登録しましょう。
登録方法はIntelliJ Platform SDK DevGuide/Publishing a plugin
に載っていますが、ざっくり訳すと以下の通りです。

JetBrainsアカウントを取得する

JetBrains Account Centerにアクセスし、"Create Account"からアカウントを作成します。

PluginをIntellij Plugin Repositoryにアップロードする

登録したJetBrainsアカウントでIntelliJ Plugin Repositoryにログインし、Profileページの"Add new plugin"からPluginをアップロードします。

アップロード直後にPluginが登録されるわけではなく、JetBrainsのレビュー(1〜2営業日ほど)を経て登録されます。

私が実際に作ったもの

他のサンプルとして、私が実際に作ったPluginの紹介をします。
JSON FilePath Navigator
Pluginの機能としては、以下のような仕組みを利用しています。

  • PsiElementおよびその周辺機構を利用したプロジェクト内ファイルの検索や、"Open Declaration(定義ジャンプ)"機能の拡張
  • PersistentStateComponentを利用したデータの永続化
  • Kotlinを用いたGUIパーツの実装 (参考文献)

ソースコードを公開していますので、もし似たような機能を実装したくなったときに参照していただけると幸いです。

最後に

IntelliJやそのPlugin群は非常に質が高く、それらを組み合わせるだけでも十分快適な開発環境を構築することができます。
しかしながら、「こうした方がもっと良いのに」「需要はないかもしれないけれど、自分にとってはあると滅茶苦茶便利になる機能がある」というような場面にも少なからず遭遇すると思います。
そんなとき、既存のPluginを改良してプルリクを送ったり、自前でPluginを作ったりすることで、よりオレオレなカスタマイズ環境を実現することができるでしょう。

というわけで、みなさんもPlugin開発してイイ感じな開発環境を目指しましょう👍