痛IntelliJプラグインを作った話
この記事はJetBrains Advent Calendar 2017 11日目の記事です。
前日はKotlinでプラグインを作る話でまさかの2日連続プラグインの話…!
僕は萌え系のイラストが好きなオタクエンジニアなんですが、オタクエンジニアをやっているとエディタやIDEを痛くしたくなることってありますよね…!?
EclipseにはMoeclipseという選択肢があったりします。JetBrains IDEの場合数年前までSexy Editorというプラグインでエディタの背景を変える必要がありました。
その後、本体にSet Background Image
というアクションが追加され、そこからプラグインを入れることなく背景を変更することができるようになりました。(Shift+Cmd+Aのアクション検索からしか設定に飛べないです
@いなむ先生 これで許してください pic.twitter.com/gluvtXllol
— 俺九番 (@orekyuu) 2017年4月30日
背景を痛くすることは簡単にできる時代になりましたが、痛IDEを名乗るにはまだ早いですよね。もっと痛くしましょう。
痛IDEプラグインを作ってみた
これがプロのエディタ pic.twitter.com/AsCK2PgLKv
— 俺九番 (@orekyuu) 2017年8月7日
このプラグインはエディタの背景にキャラクターを表示して操作によっていろいろなリアクションをしてくれます。
ということでJetBrainsPluginRepositoryに公開しようと思ったんですが審査でリジェクト…。頑張って絵も描いたのにどうやら公開はできなさそう…。しかし、せっかく作ったので使ってもらいたい!うちの娘を可愛がってもらいたい!
プラグインリポジトリを作る
Plugin Repositoryを各自で用意することができるようになっているので自分で用意することにしました。
ドキュメントに設定が書かれていたりするので簡単にサッっと立てることができました。
おわりに
これで最高の痛IDE環境になりました。痛IDEを作るならもうJetBrains IDEしか選択肢はないのではないでしょうか…!?
実装に関しての話は別記事に書いているので興味のある方はそちらも読んで頂けると!
いますぐダウンロード
女の子がリアクションしてくれるいんてりじぇープラグイン作りました
— 俺九番 (@orekyuu) 2017年8月7日
Plugin一覧からBrowse repositories>Manage repositoriesで出てきたダイアログにhttps://t.co/7nRml7CV9z
を追加してから"riho"で検索してください pic.twitter.com/UgvQSUwbkU
IntelliJ IDEAのプラグインの作り方
はじめに
とりあえずブログを作ってみてからかなり期間が空きましたがorekyuuです。まだこのブログの事忘れてないですよ…?
一つ前の記事を作った頃はまだ学生だったんですよね(遠い目
さて、前置きはこのへんで最近作った莉穂ちゃんプラグインを題材にIntelliJプラグインの作り方について解説してみようと思います。
これがプロのエディタ pic.twitter.com/AsCK2PgLKv
— 俺九番 (@orekyuu) 2017年8月7日
開発環境の作り方
IntelliJプラグインのプロジェクトはざっくり分けて以下の二種類があります。
- Gradleプロジェクト
- ideaプロジェクト
GradleプロジェクトはGradle Intellij pluginを使う方法です。
こちらはGradleを使っているのでライブラリの管理が楽だったりしますが、プロジェクトをImportしてもIntelliJの実行構成ができなかったり、RubyMineやPhpStormで試そうとしても分からなかったので今回は見送り。そもそもライブラリ追加しないですし。
ideaプロジェクトはIntelliJのプロジェクトテンプレートで作った構成そのままの形です。
特にライブラリを追加したりしなければこちらで良いかと思います。莉穂ちゃんプラグインはこちらの形になっています。
ideaプロジェクトにする場合は.idea以下をちゃんとgit管理下においてあげてくださいね。
依存関係の設定
プラグインを作るときに一番初めに触ることになるのが/META-INF/plugin.xmlです。
これはプラグインの情報やComponent、Extensionの設定を書くファイルです。idやnameを設定したあとはdependsの設定をします。
この設定を書かなければデフォルトではIntelliJでしか使えないプラグインになってしまいます。
莉穂ちゃんプラグインでは以下のような設定になっています。
<!-- please see http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/plugin_compatibility.html on how to target different products --> <depends>com.intellij.modules.lang</depends> <depends>com.intellij.modules.platform</depends> <depends>com.intellij.modules.xml</depends> <depends>com.intellij.modules.vcs</depends> <depends>com.intellij.modules.xdebugger</depends>
このあたりのモジュールはすべてのJetBrains製品で利用できるモジュールです。
IDEに依存するモジュールはコメントアウトされているリンクを参照してください。
キャラクタの表示
プラグインの基本的な部品のことをComponentとよんでいます。Componentには以下の3種類があります。
- Application Level Component
- Project Level Component
- Module Level Component
ApplicationはIDE起動時に初期化、ProjectはIDEのインスタンス作成時に(Projectを開いてなくても作られる!)と言った具合にだいたい名前と同じスコープで作られます。
参考: http://www.jetbrains.org/intellij/sdk/docs/basics/plugin_structure/plugin_components.html
今回はProject LevelでComponentを作りました。 plugin.xml
<project-components> <component> <implementation-class>net.orekyuu.riho.RihoPlugin</implementation-class> </component> </project-components>
RihoPlugin.java
public class RihoPlugin implements ProjectComponent { private final Project project; private IdeActionListener ideActionListener; public RihoPlugin(Project project) { this.project = project; } @Override public void initComponent() { EditorFactory.getInstance().addEditorFactoryListener(new EditorFactoryListener() { private MessageBusConnection connect; private CharacterBorder character = null; private HashMap<Editor, CharacterBorder> characterBorders = new HashMap<>(); @Override public void editorCreated(@NotNull EditorFactoryEvent editorFactoryEvent) { Editor editor = editorFactoryEvent.getEditor(); Project project = editor.getProject(); if (project == null) { return; } JComponent component = editor.getContentComponent(); try { Riho riho = new Riho(); CharacterBorder character = new CharacterBorder(component, new CharacterRenderer(riho), riho); characterBorders.put(editor, character); component.setBorder(character); connect = project.getMessageBus().connect(); connect.subscribe(RihoReactionNotifier.REACTION_NOTIFIER, riho); } catch (IOException e) { e.printStackTrace(); } } @Override public void editorReleased(@NotNull EditorFactoryEvent editorFactoryEvent) { CharacterBorder characterBorder = characterBorders.get(editorFactoryEvent.getEditor()); if (characterBorder != null) { characterBorders.remove(editorFactoryEvent.getEditor()); characterBorder.dispose(); } } }, () -> { }); MessageBusConnection connect = project.getMessageBus().connect(); connect.subscribe(Notifications.TOPIC, new NotificationListener(project)); connect.subscribe(RefactoringEventListener.REFACTORING_EVENT_TOPIC, new RefactoringListener(project)); connect.subscribe(CompilerTopics.COMPILATION_STATUS, new CompilerListener(project)); ideActionListener = new IdeActionListener(project); ActionManager.getInstance().addAnActionListener(ideActionListener); } @Override public void disposeComponent() { ActionManager.getInstance().removeAnActionListener(ideActionListener); } @Override @NotNull public String getComponentName() { return "RihoPlugin"; } @Override public void projectOpened() { } @Override public void projectClosed() { } }
Component側では初期化時にEditorFactoryにListenerを登録して、EditorのSwingコンポーネントの背景にキャラを描画するようにしています。
テストのイベントを拾う
莉穂ちゃんプラグインはユニットテストが成功すると笑ったり、失敗すると悲しんだりします。
このイベントを拾うためにExtensionsを書きます。
Extensionsは他のプラグインやapiが用意したExtensionPointがあればそれに対して拡張を書くことができます。IntelliJの拡張であればPlatformExtensionPoints.xmlから拡張したい場所を探すと良いと思います。
今回はtestStatusListenerを拡張しました。
plugin.xml
<extensions defaultExtensionNs="com.intellij"> <!-- Add your extensions here --> <testStatusListener implementation="net.orekyuu.riho.events.TestListener"/> </extensions>
TestListener.java
public class TestListener extends TestStatusListener { @Override public void testSuiteFinished(@Nullable AbstractTestProxy abstractTestProxy) { } @Override public void testSuiteFinished(@Nullable AbstractTestProxy abstractTestProxy, Project project) { // some code... } }
その他のイベントの購読、キャラクターにリアクションさせる
テスト以外に、タイピングやリファクタリングなどでも莉穂ちゃんはリアクションしてくれます。しかしExtensionPointにはそのイベントが取れそうな物がありません。
IntelliJはイベントの送信にMessagingの仕組みを用意しています。莉穂ちゃんプラグインではMessageを購読してイベントが来たときにリアクションさせています。また、リアクションさせるためのメッセージもこの仕組みを使っています。
この仕組みに出てくる登場人物は以下のとおりです。
Topic: メッセージを送るためのチャンネル
Subscriber: メッセージを受け取る人
Publisher: メッセージを送信する人
リファクタリング時にキャラクターにリアクションさせるシナリオを考えてみます。
1 リファクタリングのメッセージを購読する
RihoPlugin.java(Application Level Component)
MessageBusConnection bus = project.getMessageBus().connect();
bus.subscribe(RefactoringEventListener.REFACTORING_EVENT_TOPIC, new RefactoringListener(project));
2 メッセージを受け取ってキャラクターにメッセージを送る
RefactoringListener.java
public class RefactoringListener implements RefactoringEventListener { private final Project project; public RefactoringListener(Project project) { this.project = project; } @Override public void refactoringStarted(@NotNull String s, @Nullable RefactoringEventData refactoringEventData) { } @Override public void refactoringDone(@NotNull String s, @Nullable RefactoringEventData refactoringEventData) { RihoReactionNotifier publisher = project.getMessageBus().syncPublisher(RihoReactionNotifier.REACTION_NOTIFIER); publisher.reaction(Reaction.of(FacePattern.SMILE1, Duration.ofSeconds(3))); } @Override public void conflictsDetected(@NotNull String s, @NotNull RefactoringEventData refactoringEventData) { RihoReactionNotifier publisher = project.getMessageBus().syncPublisher(RihoReactionNotifier.REACTION_NOTIFIER); publisher.reaction(Reaction.of(FacePattern.JITO, Duration.ofSeconds(3), Emotion.MOJYA, Loop.once())); } @Override public void undoRefactoring(@NotNull String s) { RihoReactionNotifier publisher = project.getMessageBus().syncPublisher(RihoReactionNotifier.REACTION_NOTIFIER); publisher.reaction(Reaction.of(FacePattern.SYUN, Duration.ofSeconds(3))); } }
3 RihoReactionNotifier用のTopicを作る
RihoReactionNotifier.java
public interface RihoReactionNotifier { Topic<RihoReactionNotifier> REACTION_NOTIFIER = Topic.create("riho reaction", RihoReactionNotifier.class); void reaction(Reaction reaction); }
4 REACTION_NOTIFIER Topicを受け取ってリアクションさせる
public class Riho implements RihoReactionNotifier { //some code... @Override public void reaction(Reaction reaction) { reactionStartTime = Instant.now(); this.reaction = reaction; } }
プラグインの公開
JetBrains Plugin Repositoryにアップロードすれば完了です!
…なにごともなければ!
莉穂ちゃんプラグインはリジェクトされました!!!
とはいえせっかく作ったので公開したいです。ということで急遽個人Plugin Repositoryを作ることにしました。
インストール方法が面倒くさいのはこのせいなんです…。
PluginRepositoryを作る
オリジナルのPluginRepositoryを作る方法を解説したEnterprise Plugin Repositoryという記事が公式ブログにありました。
こちらを参考にupdatePlugins.xml を書きました。
<plugins> <plugin id="net.orekyuu.riho" url="http://uploader.orekyuu.net/plugin/riho/riho-1.0.3.jar" version="1.0.3"/> <plugin id="net.orekyuu.riho" url="http://uploader.orekyuu.net/plugin/riho/riho-1.0.2.jar" version="1.0.2"/> </plugins>
これのファイルとjarファイルを誰でもアクセスできるようにしてサーバーにデプロイすれば完了です!やった!
さいごに
IntelliJプラグイン解説周りの情報って少ないんですよね。誰かの役に立てばと思い書いてみた次第です。 よければ下のツイートを参考にぜひ僕の看板娘を表示する莉穂ちゃんプラグインを使ってやって下さい!
女の子がリアクションしてくれるいんてりじぇープラグイン作りました
— 俺九番 (@orekyuu) 2017年8月7日
Plugin一覧からBrowse repositories>Manage repositoriesで出てきたダイアログにhttps://t.co/7nRml7CV9z
を追加してから"riho"で検索してください pic.twitter.com/UgvQSUwbkU
また、開発に興味があればこちらのリポジトリもよろしくお願いします!ではでは〜
who’s watching
はじめに
Java Puzzlers Advent Calendar 2016最終日の記事です。
皆さんはこれまでの問題は解けたでしょうか?難しい問題ばかりで僕の正答率はガタガタでしたw
というわけで今日が最後の問題になります。1ヶ月ほど前に記事にしていたのですが、結構面白い内容だったので今回のカレンダー用に再出題してみます。
続きを読む