Kotlinで書くIntelliJ Plugin

# 概要
この記事は[JetBrains Advent Calendar 2017 10日目](https://qiita.com/advent-calendar/2017/jetbrains)の記事です。
「ただでさえ使いやすい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](https://www.jetbrains.org/intellij/sdk/docs/welcome.html)
とは言え、ここに書いてある内容を端から端まで読むのもツライので、下記のような戦略で行くのが良いと思います。

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

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

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


## 雛形を作成する
[gradle-intellij-plugin wizard](https://github.com/shiraji/intellij-plugin-with-gradle-wizard)を使って、手っ取り早く枠組みを作ることができます。

KotlinとJavaのバージョンを選択して、
(利用できるKotlinのバージョンは[kotlin-gradle-plugin](https://kotlinlang.org/docs/reference/using-gradle.html#plugin-and-versions) に準じます)
<img width="756" alt="NewProject-1" src="https://qiita-image-store.s3.amazonaws.com/0/72808/bd02a762-63a1-e7dc-f8f1-5529c22d0b54.png">


Pluginの情報を入力します。
<img width="756" alt="NewProject-2" src="https://qiita-image-store.s3.amazonaws.com/0/72808/e391aca6-a71d-b61a-176a-479a62acfc59.png">

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

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

### PluginのBuild
[buildPlugin]というタスクがデフォルトで存在するので、それを実行します。
<img width="260" alt="build-1" src="https://qiita-image-store.s3.amazonaws.com/0/72808/db1d8d79-c4b9-ec6a-1d19-112181dbf7af.png">

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

<img width="252" alt="build-2" src="https://qiita-image-store.s3.amazonaws.com/0/72808/5ac3f6ef-4b40-13bc-1586-3d3d9735a5cf.png">

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

### Pluginのデバッグ実行
[runIdea]タスクを実行すれば、作成したPluginのデバッグ実行が可能です。
<img width="256" alt="debug-1" src="https://qiita-image-store.s3.amazonaws.com/0/72808/a3e2cae8-943d-d907-d2a0-be201fd0e4f1.png">

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

起動したIntelliJのPluginリストを見ると、Pluginがインストールされた状態であることがわかります。
<img width="799" alt="debug-2" src="https://qiita-image-store.s3.amazonaws.com/0/72808/d603588e-a296-1d7e-df54-27ac72949d3b.png">

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

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

今回は、[Creating Your First Plugin](https://www.jetbrains.org/intellij/sdk/docs/basics/getting_started.html)を参考にしつつ、以下のような機能を作っていきます。

- 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を作成できます。
<img width="814" alt="action-1" src="https://qiita-image-store.s3.amazonaws.com/0/72808/98f33959-f278-b653-7694-d3c64390d707.png">

必要な項目を入力して`OK`すると、
<img width="913" alt="action-2" src="https://qiita-image-store.s3.amazonaws.com/0/72808/ceb5bdff-8184-a462-b275-5865120f79df.png">

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

↓自動生成されたTestActionクラスと`plugin.xml`への追加記述。
<img width="782" alt="action-3" src="https://qiita-image-store.s3.amazonaws.com/0/72808/a46094c9-f17b-22ac-c474-cc9db9e7acf4.png">


```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"で、
<img width="458" alt="java-to-kotlin-1" src="https://qiita-image-store.s3.amazonaws.com/0/72808/a9bb1cbd-33dc-8ac4-6266-aef4fc876a22.png">

↓こうなります。
<img width="429" alt="java-to-kotlin-2" src="https://qiita-image-store.s3.amazonaws.com/0/72808/f323f951-473e-924f-bb38-acfa496272ec.png">

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

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

```kotlin: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"メニューが表示されます。
<img width="141" alt="execute-1" src="https://qiita-image-store.s3.amazonaws.com/0/72808/3b5a2c52-8484-f67f-e68e-49fa615de126.png">

"TestAction"を選択すると入力ダイアログが表示されます。
（ショートカットキーを登録している場合、ショートカットで直接開くことができます）
<img width="483" alt="スクリーンショット 2017-12-10 17.38.47.png" src="https://qiita-image-store.s3.amazonaws.com/0/72808/40fb0fa1-4a22-9d02-8679-f9c6f0ed5ac0.png">

テキトーに名前を入力して`OK`すると、メッセージダイアログが開きます。
<img width="402" alt="スクリーンショット 2017-12-10 17.39.50.png" src="https://qiita-image-store.s3.amazonaws.com/0/72808/bf18fcb2-8c19-ef15-ee2a-c15b87c6bf68.png">


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

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

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

## ローカルでインストールする
作成したPluginはbuildしたjarファイルを用いてインストールすることができます。
<img width="794" alt="Plugins.png" src="https://qiita-image-store.s3.amazonaws.com/0/72808/d12a2963-c87c-b094-fbb5-b3aae716268a.png">

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

## Plugin Repositoryに登録する
作成したPluginを広く公開したい場合は、[JetBrains公式のPlugin Repository](https://plugins.jetbrains.com/)に登録しましょう。
登録方法は[IntelliJ Platform SDK DevGuide/Publishing a plugin
](https://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/publishing_plugin.html)に載っていますが、ざっくり訳すと以下の通りです。

### JetBrainsアカウントを取得する
[JetBrains Account Center](https://account.jetbrains.com/licenses)にアクセスし、"Create Account"からアカウントを作成します。

### PluginをIntellij Plugin Repositoryにアップロードする
登録したJetBrainsアカウントで[IntelliJ Plugin Repository](https://plugins.jetbrains.com/)にログインし、Profileページの"Add new plugin"からPluginをアップロードします。

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


# 私が実際に作ったもの
他のサンプルとして、私が実際に作ったPluginの紹介をします。
[JSON FilePath Navigator](https://plugins.jetbrains.com/plugin/10002-json-filepath-navigator)
Pluginの機能としては、以下のような仕組みを利用しています。

- `PsiElement`およびその周辺機構を利用したプロジェクト内ファイルの検索や、"Open Declaration(定義ジャンプ)"機能の拡張
- `PersistentStateComponent`を利用したデータの永続化
- Kotlinを用いたGUIパーツの実装 ([参考文献](https://github.com/JetBrains/intellij-community/blob/master/platform/platform-impl/src/com/intellij/ui/layout/readme.md))

[ソースコード](https://github.com/IsaoTakahashi/JSON-File-Navigator)を公開していますので、もし似たような機能を実装したくなったときに参照していただけると幸いです。

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

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