IntelliJ IDEAのプラグインを作ろう！


【追記】この記事の情報はかなり古くなっています。
2019年現在では、公式にプラグイン作成関連のドキュメントがある程度あるので、そちらを参考にすることをおすすめします。
[https://www.jetbrains.org/intellij/sdk/docs/basics/getting_started.html](https://www.jetbrains.org/intellij/sdk/docs/basics/getting_started.html)

--------------------------------------------------


## このエントリで紹介していること
- IntelliJ IDEAプラグインの事前知識
- プラグインの開発方法
- プラグインデバッグ手法、Tips
- プラグインの公開の仕方
- 開発にあたっての便利リンク集

## このエントリの目標
IntelliJ IDEAプラグインでNotificationメッセージに”Hello Plugin”を表示させる
[オプション] プロジェクト内に永続変数/クラスを持つ

## IntelliJ IDEA系のプラグインについて
IntelliJ IDEA系の製品(PhpStorm/RubyMine他)で使えるプラグインです。どのIDEで利用可能とするかは任意で選択出来ます。
プラグインの公開は無料ですが、JetBrainsアカウントのユーザ登録が必須となります。
プラグインについてはこちらのサイトで公開されています。
http://plugins.jetbrains.com/

## 事前知識として
以下の項目が必須となります。

- エディタとしてIntelliJ IDEA
- 言語知識としてJava(もしくはGroovy / Scala / KotlinなどのJVM言語)

IntelliJ系のプラグインの開発にはIntelliJ IDEAが必須となります。
また、開発にあたっては基本的にJavaで書くことになります。上述したように他のJVM言語でも書けますが、今回はJavaでのみ解説します。
IntelliJ IDEAで[php-openapi]なるものがあるのでよく勘違いされる方いますが、PhpStormではプラグイン開発はできません。

## つくってみる
今回の解説ではIntelliJ IDEA13.0(Build #IU-133.193)を使用します。

### プロジェクトの作成
以下の手順で新しいプロジェクトを作成します。

- [New Project] - [IntelliJ Platform Plugin]

Project locationはどこでも任意の場所で大丈夫です。プロジェクト名は[HelloPlugin]にしてみます。


![New_Project_と_GlobalNodeProperties.java_-__global-variable-string-parameter__-_global-variable-string-parameter_-____Dropbox_100_Programming_java_global-variable-string-parameter-plugin_.png](https://qiita-image-store.s3.amazonaws.com/0/20074/891d8b03-6317-fa59-df24-4368f74c738d.png "New_Project_と_GlobalNodeProperties.java_-__global-variable-string-parameter__-_global-variable-string-parameter_-____Dropbox_100_Programming_java_global-variable-string-parameter-plugin_.png")

設定ができたら[Finish]でプロジェクトを作成します。


### アクションを作成

基本的に[アクション]という単位で動作を作成していきます。
srcディレクトリを右クリックで以下の手順でアクションを作成します。

- [New] - [Action]

Action IDはプラグイン内で一意にします。基本的にはクラス名と一緒にすれば良いです。ここでは[HelloPluginAction]にします。
Nameはアクション名として表示されます。ここでは[Say Hello]とします。

Groupsは、作成したアクションがどの処理グループに属すかを設定します。ここでは簡易ツールとしたいので[ToolsMenu (Tools)]を選択します。だいぶ下の方です。

下のKeyboard Shortcutsはそのアクションを実行するショートカットキーを設定出来ます。ここではFirstに[Cmd+Shift+F12]を設定します。Winの場合は[Ctrl+Shift+F12]とかで良いと思います。


![New_Action_と_HelloPluginAction.java_-__HelloPlugin__-_HelloPlugin_-____Dropbox_100_Programming_java_HelloPlugin_.png](https://qiita-image-store.s3.amazonaws.com/0/20074/c1b26747-9d2b-d405-4904-de1af4ee49f5.png "New_Action_と_HelloPluginAction.java_-__HelloPlugin__-_HelloPlugin_-____Dropbox_100_Programming_java_HelloPlugin_.png")

とりあえずこんな感じで入れたら[OK]を押しましょう。そうするとHelloPluginActionクラスが作成され、plugin.xmlに<action>が追加されます。

- HelloPluginAction.java

![HelloPluginAction.java_-__HelloPlugin__-_HelloPlugin_-____Dropbox_100_Programming_java_HelloPlugin_.png](https://qiita-image-store.s3.amazonaws.com/0/20074/d90e959a-6241-b8f0-ebde-a331814ad791.png "HelloPluginAction.java_-__HelloPlugin__-_HelloPlugin_-____Dropbox_100_Programming_java_HelloPlugin_.png")


- plugin.xml

![plugin.xml_-__HelloPlugin__-_HelloPlugin_-____Dropbox_100_Programming_java_HelloPlugin_-2.png](https://qiita-image-store.s3.amazonaws.com/0/20074/8ad040c6-af25-0ebf-5af8-eaa73986a7ca.png "plugin.xml_-__HelloPlugin__-_HelloPlugin_-____Dropbox_100_Programming_java_HelloPlugin_-2.png")


実行されるアクションはactionPerformed()に入ってきます。main関数のようなイメージです。基本的な処理はここに書いていきます。

ついでに、plugin.xmlの中身も見てみましょう。
plugin.xmlはプラグイン本体のマニフェスト情報やショートカット情報を管理している設定ファイルです。
上のアクションを作成することで赤枠の中が追加されました。テキストやショートカットキーの変更などは、こちらを直に書き換えてしまっても大丈夫です。

簡単に他の項目も説明しておきます。
上部のid, name, version, vendorはプラグインの情報です。プラグイン名やID、作成者情報を設定出来ます。
その下のdescriptionはプラグインの概要、change-notesは更新履歴を書いていきます。この中ではHTMLタグが利用出来ます。

extensionsは永続化設定やReferenceやCompletionなどを定義します。後半でちょっとだけ触ります。

HelloPluginActionに戻ります。
actionPerformed()の中身を以下のように書き換えてみます。

```java
  public void actionPerformed(AnActionEvent e) {
    Notifications.Bus.notify(
        new Notification("sample", "Hello Plugin!", "Hello! This is Sample Plugin.", NotificationType.INFORMATION)
    );
  }
```

試しにこれでデバッグしてみましょう。

### プラグインを動かしてみる

ツールバーの[Run] - [Debug]から実行します。
Edit Configurationの設定を言われる場合は、[+]から[Plugin]を追加して以下のような感じで[Use classpath of module]の欄を設定して、[Debug]を押します。

![Debug_-_HelloPlugin.png](https://qiita-image-store.s3.amazonaws.com/0/20074/4585ddbf-2f38-90a7-8b6e-10e78d4dc4cf.png "Debug_-_HelloPlugin.png")

コンパイル後、IntelliJ IDEAがもう1プロセス立ち上がります。こちらがデバッグ用のエディタです。

立ち上がった方のIntelliJ IDEA(以下デバッグエディタ)はまっさらな状態です。とりあえず設定はそのままで、適当なプロジェクトを開いてみます。

開き終わったら先ほど設定したショートカット(Cmd+Shift+F12)を押してみます。

そうすると、こんな感じで右上にNotification通知がされると思います。

![GlobalNodeProperties.java_-__jenkins-chatwork-plugin__-_jenkins-chatwork-plugin_-____Dropbox_100_Programming_java_jenkins-chatwork-plugin_.png](https://qiita-image-store.s3.amazonaws.com/222/20074/db80d984-b54f-1987-2829-a593eb6178bc.png "GlobalNodeProperties.java_-__jenkins-chatwork-plugin__-_jenkins-chatwork-plugin_-____Dropbox_100_Programming_java_jenkins-chatwork-plugin_.png")

ざっくりな動かし方はこんな感じです。いかがでしたでしょうか。
実質コードはほとんど書いていないので、全然物足りないという方は以下のオプションも読んでみてください。

### プロジェクト内に永続値を持つ
エディタを閉じた後もプロジェクト固有の変数や定数を永続的に保持したい場合は数多くあると思います。それにはPersistStateComponentを使います。

まずはPersistStateComponentを継承したモデルクラスを作成します。
永続化しておきたい変数や定数、メソッドを格納したModelをプロジェクト固有で持つような感じになります。
クラス名は任意ですが、ここでは[PluginConfig]とします。
src配下右クリックから[New] - [Java Class] で、普通にクラスを作ります。

ここではHelloPluginのアクションが実行された回数を保持しておいて、実行されるたびにインクリメントするようにしてみます。

中身はこんな感じにしました。

- HelloPluginAction.java

```java
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;

public class HelloPluginAction extends AnAction {
  public void actionPerformed(AnActionEvent e) {

    PluginConfig config = PluginConfig.getInstance(e.getProject());

    if(config.isEmpty()) {
      config.init();
    }

    int count = config.getCount();

    Notifications.Bus.notify(
        new Notification("sample", "Hello Plugin!", String.format("This action has been performed %d times", count), NotificationType.INFORMATION)
    );

    config.increment();
  }
}

```

- PluginConfig.java

```java
import com.intellij.openapi.components.*;
import com.intellij.openapi.project.Project;
import com.intellij.util.xmlb.XmlSerializerUtil;
import org.jetbrains.annotations.Nullable;


@State(
    name = "HelloPluginConfig",
    reloadable = true,
    storages = {
        @Storage(id = "default", file = "$PROJECT_FILE$"),
        @Storage(id = "dir", file = "$PROJECT_CONFIG_DIR$/hello_plugin.xml", scheme = StorageScheme.DIRECTORY_BASED)
    }
)

public class PluginConfig implements PersistentStateComponent<PluginConfig> {
  private Integer count;

  @Nullable
  @Override
  public PluginConfig getState() {
    return this;
  }

  @Override
  public void loadState(PluginConfig config) {
    XmlSerializerUtil.copyBean(config, this);
  }

  @Nullable
  public static PluginConfig getInstance(Project project) {
    return ServiceManager.getService(project, PluginConfig.class);
  }

  public void setCount(Integer count) {
    this.count = count;
  }

  public Integer getCount() {
    return count;
  }

  public boolean isEmpty() {
    return count == null;
  }

  public void init() {
    this.count = 0;
  }

  public void increment() {
    this.count++;
  }

}
```

- plugin.xml(一部抜粋)

```xml
  <extensions defaultExtensionNs="com.intellij">
    <projectService serviceInterface="PluginConfig" serviceImplementation="PluginConfig"/>
  </extensions>
```

![GlobalNodeProperties.java_-__jenkins-chatwork-plugin__-_jenkins-chatwork-plugin_-____Dropbox_100_Programming_java_jenkins-chatwork-plugin_.png](https://qiita-image-store.s3.amazonaws.com/222/20074/e54bb637-4f19-562a-40a2-38b8a669499e.png "GlobalNodeProperties.java_-__jenkins-chatwork-plugin__-_jenkins-chatwork-plugin_-____Dropbox_100_Programming_java_jenkins-chatwork-plugin_.png")

これでアクションを実行する度にカウントがインクリメントされるようになりました。
プロジェクトごとの固有にしてあるので、エディタを開きなおしてもこの数字は保持されています。


PluginConfigがPersistentStateComponentを継承しているところがポイントです。
@State()アノテーションの中身でこの設定クラスを保存する保存先を指定しています。ここではプロジェクトディレクトリの設定ディレクトリ(.idea/)にhello_plugin.xmlという名前で保存するようにしています。
オプションや、これ以上の詳しい使い方についてはこちらに書いてあります。
[Persisting State of Components](http://confluence.jetbrains.com/display/IDEADEV/Persisting+State+of+Components)

### プラグインをデバッグする

[Debug]として開始していれば、任意の箇所でブレイクポイントを張るだけで止まってくれます。

![HelloPluginAction.java_-__HelloPlugin__-_HelloPlugin_-____Dropbox_100_Programming_java_HelloPlugin_.png](https://qiita-image-store.s3.amazonaws.com/222/20074/c0c9c2ce-b8d7-0982-e268-2df437a15236.png "HelloPluginAction.java_-__HelloPlugin__-_HelloPlugin_-____Dropbox_100_Programming_java_HelloPlugin_.png")


APIのドキュメントなどがないため、渡されたクラスにどのようなメソッドがあるか、そのメソッドがどのような値を返すかをデバッグしながら開発することは非常に有効です。
とはいえコードを書き換えて毎回デバッグ起動するのではキリがないので、ここではEvaluate Expression(Ctrl+F8)を使用します。

![全画面_2013_12_10_19_44-2.png](https://qiita-image-store.s3.amazonaws.com/222/20074/c2c30cf7-fe38-0bcd-52d5-fb1a48a9a7e4.png "全画面_2013_12_10_19_44-2.png")


上記のようにブレイクポイントで止めた状態でEvaluate Expressionを押すとウィンドウが開きます。ここで実行したいコードを入力するとその場で動的実行して、その結果を表示してくれます。

例えば、8行目で渡されてきたAnActionEvent eがどのようなメソッドを持っているか試したい場合はこのように

![全画面_2013_12_10_19_46-2.png](https://qiita-image-store.s3.amazonaws.com/222/20074/fdc8be67-01ef-ec49-d975-c61bda7e3ae1.png "全画面_2013_12_10_19_46-2.png")

e.getProject().getBasePath()で返される値がどんな感じか試したいときは

![全画面_2013_12_10_19_47-2.png](https://qiita-image-store.s3.amazonaws.com/222/20074/96993ae3-8fcb-8b1d-2ace-4fb09b901fe4.png "全画面_2013_12_10_19_47-2.png")

入力してEvaluateを実行すると、実行結果がその場で返されます。

特にプラグイン開発においてはこの機能を使いこなせるかどうかで開発効率が桁違いに変わってくるので、是非試してみてください。

### プラグインを公開する。
上述の通り、プラグインの公開にはJetBrainsアカウントが必要になります。アカウントはプラグイン作成ページからも登録出来ます。

アップロードする前にプラグインのjarファイルを作成する必要があります。

[Build] - [Prepare Plugin Module “Hello Plugin” For Deployment]
を実行すると、プロジェクトのディレクトリにjarファイルが作成されます。

http://plugins.jetbrains.com/
jarファイルが用意出来たら、ログインした状態で右上の[Add new plugin]から登録を行います。
アップロードフォームがあるので先ほどのjarを添付し、プラグインのカテゴリを選択します。

次のページでプラグインの概要やライセンス、開発者、プラグインを配布対象のIDEなどを選択します。
最後に保存すると公開準備は完了です。

審査が終わると1日程度で公開され、IntelliJ IDEAのプラグインセンターからダウンロード出来るようになります。

## ハマったときは？

ドキュメントのFAQで解決することも多いので、まずはFAQをじっくり見ます。
[Plugin Development FAQ](http://confluence.jetbrains.com/display/IDEADEV/Plugin+Development+FAQ)

そもそもどういう風に動かしているのか分からない場合、同じことをやっているような/やっていそうなプラグインを探してきて、ソースコードを真似るのが一番いいと思います。
ローカルに落としてきてデバッグしながらコードを追うのも有効です。

[intellij-community](https://github.com/JetBrains/intellij-community)のCoreのソースコードを落としてきて類似処理を探すのも有効です。中規模以上のプラグインを作ることになったらお世話になることは多いです。
コードだけで1GB以上あるので追いかけるのはちょっと大変ですが。

どうしても解決しなければフォーラムで質問するのもいいと思います。1〜2日くらいで中の人が答えてくれる場合が多いです。
[Open API and Plugin Development Forum](http://devnet.jetbrains.com/community/idea/open_api_and_plugin_development)


[@vexus2](http://twitter.com/vexus2)宛てにTwitterでリプライもらえれば力になれる場合もあるかもしれません。


## 最後に

APIドキュメントがなく、公式ドキュメントもかなり少ない＆日本語情報はほぼ皆無なため結構苦労することが多いです。が、ハードルが高いからこそ得られる達成感もあります。
また、自分が好きなエディタを更に自分好みにカスタマイズ出来るというのは非常にメリットかと思います。

ぜひIntelliJ IDEAのプラグイン開発に挑戦してみてはいかがでしょうか。


## 参考リンク
- [IntelliJ Plugin Development](http://confluence.jetbrains.com/display/IDEADEV/PluginDevelopment)
- [Open API and Plugin Development Forum](http://devnet.jetbrains.com/community/idea/open_api_and_plugin_development)
- [ツイートするIntelliJプラグインを作ったよ](http://hotchemi.hateblo.jp/entry/2013/01/07/013344)
- [IntelliJ IDEAプラグインの開発 - 有用な情報とリンク集Add Star](http://d.hatena.ne.jp/fuzzhead/20120729/p1)
- [IntelliJ IDEAのプラグイン開発](http://kxbmap.hatenablog.com/entry/20111102/1320169073)