前置き
(前置きは技術的なこと一切書いてありませんので、飛ばしても何ら問題ありません。
最終コードの説明はコードの説明以降に記載されていますので、コードだけ読みたい方はそちらを参照して下さい。)
約1年前にAndroid Studio移行についてエントリー書いて、最終的に完全移行をしました。
EclipseからAndroid Studio移行ではまったところメモ [gradle] on @Qiita http://t.co/qVVxCfcHOu
— Yoshinori Isogai (@shiraj_i) 2014, 7月 20
Eclipseが好きだったから始めたAndroid開発。その自分の発案で今日ついにEclipseとのお別れをしました。今までありがとー!
— Yoshinori Isogai (@shiraj_i) 2014, 7月 29
ADTの開発が終わるってことについてちょっと感傷に浸っていました
今頃だけど、ADTお疲れ様でした。http://t.co/5LB9KvAJOn)
— Yoshinori Isogai (@shiraj_i) 2015, 6月 29
よくよく考えたら、もう1年も同じツール使っていて、Androidやらないところにいかない転職しない限り、このままAndroid Studioにはお世話になるだろう、にも関わらず、中身知りません。プラグインは提供されているものしか使いません。ってそれ自分らしくないじゃんってことでソース読みつつ、プラグイン開発をしてみようと考えました。
さて、何を作ろうかと考えていたら、同僚とペアプロしていて、コード書く→ログやメモリ確認→コード書くという流れで、Editorにカーソルが当たる度にcmd+shift+F12をタイプしていて大変だなー。コーディングし始めたら勝手に最大化してくれりゃいいのに。。。!!!ということで作り始めました。一応軽くplugin見回したのですが、似たようなものがなかったので最終的に公開まで持っていきました。
(例えすでに同じようなものがあっても、自分の勉強になるからいいやってことで。。。)
このエントリーはいつもと違い、個人的な見解をふんだんに入れ、技術ネタとしてではなく、どういう流れで公開まで行き着いたのかということを主に記載しました。失敗した部分や遠回りした部分に関しても記載してあります。
参考にしたサイト
まず最初に以下がプラグイン開発で役立ったサイト・エントリーです。
IntelliJ IDEAのプラグイン開発
http://kxbmap.hatenablog.com/entry/20111102/1320169073
IntelliJ IDEAプラグインの開発 - 有用な情報とリンク集
http://d.hatena.ne.jp/fuzzhead/20120729/p1
ツイートするIntelliJプラグインを作ったよ
http://hotchemi.hateblo.jp/entry/2013/01/07/013344
IntelliJ IDEAのプラグインを作ろう!
http://qiita.com/Vexus2/items/e04a21f00e467b7ac8ad
IntelliJ IDEA Pluginの作り方
http://blog.tomorrowkey.jp/2014-02-04/how-to-make-a-plugin-for-intellij-idea/
Handling Editor Events
http://www.jetbrains.org/intellij/sdk/docs/tutorials/editor_basics/editor_events.html
Open API and Plugin Development
https://devnet.jetbrains.com/community/idea/open_api_and_plugin_development
古い記事がいっぱいですが、どのサイト・記事も今でも非常に役立ちました。
実際に開発した流れ
ということで、Hide (All/Side) Tool WindowsをEditor上でどのキーをタイプしても起動させるプラグイン[Hide Tool Windows EX] (https://plugins.jetbrains.com/plugin/7836?pr=)を作成した流れを紹介します。
ソースコードはGithubに公開してあります。
開発環境の構築
開発環境の構築は上記の参考にしたサイトに全て記載してあります。
強いて難しいと感じた点をあげるとするとProject SDKの設定をするところでかなりはまりました。
IntelliJ IDEA Pluginの作り方がスクショ付きで初心者にわかりやすく説明してありました。バージョンが古いですが適時脳内補完して下さい。
さっそく作ってみた
設定が完了したら、サンプルコードのHandling Editor Eventsを写生し、ものの数分でタイプしたら何かするというプラグインが完成しました。
そこで問題があって、TypedActionHandlerというものを使っていたのですが、タイプしたキーごとハイジャックしてしまいます。
自分が欲しかったのは、あくまで、タイプしたらToolウインドウを消すというトリガーが欲しかっただけで、タイプしたこと自体の挙動を変更したくありませんでした。
がんばって作ってみた結果、こんな感じのコードが出来上がりました。
public class MyTypedActionHandler implements TypedActionHandler {
@Override
public void execute(final Editor editor, final char c, DataContext dataContext) {
final Document document = editor.getDocument();
final Project project = editor.getProject();
Runnable runnable = new Runnable() {
@Override
public void run() {
insertCharacter(editor, document, c);
}
};
WriteCommandAction.runWriteCommandAction(project, runnable);
}
private void insertCharacter(Editor editor, Document document, char c) {
CaretModel caretModel = editor.getCaretModel();
document.insertString(caretModel.getOffset(), String.valueOf(c));
caretModel.moveToOffset(caretModel.getOffset() + 1);
}
}
Editorオブジェクトから、CaretModelを取得し、Documentにキャレットの位置にcharを挿入し、キャレットを一文字動かしています。
ハイライトしていた場合、ハイライトしていた文字列が消える挙動にはなりません。
そもそも、明らかな車輪の再開発です。どう考えてもこれじゃない感がありました。
検索しても検索しても情報が出てきません。
だいたい、他のツールでもそうですが、プラグイン作成しようとちょこっとしたカスタマイズした機能を作ろうとしても誰も作ったことがないので、(作っていればそのプラグイン使えばいい。)基本ドキュメントがありません。
初心者には荷が重く、ここで断念しかけました。
諦める前にあがいてみた
だいたい、この流れで投げ出すのが今までの自分だったのですが、今回はforumがあるということで、完全に放棄する前に、中の人に聞いてみました。1週間返答なかったら諦めるつもりでした。
Open API and Plugin Developmentでアカウント作成し、拙い英語で聞いてみました。
今みても文法ひどい・・・。TypedActionHandler with default typing behaviorがそれです。
日付みてもらえばわかると思うのですが、1日未満で最高な回答がきました。
やっぱり英語は文法じゃない!雰囲気でいける!!!(違
このタイミングで作成したアカウントでプラグインを公開しているので、どの道、アカウントは作成する必要があります。
無料ですので早めに作っておいたほうがいいです。
やっと理解する
教えてもらったTypedHandlerDelegateですが、Actionじゃねーじゃん。使い方わかんねーし!とちょっとイラっとしたのは内緒ですが、せっかく回答がきたんだし、言われたことをやってみようとゴネゴネいじくりまわしてみました。
簡単なサンプル作成まではドキュメントが充実しているのですが、Action以外でどうすればいいのかという説明がしてあるところは極端に少なくなります。
実際、TypedHandlerDelegateの日本語の説明をしているサイト見つけられませんでした。(というかドキュメント自体見つけられなかった。。。)
そこで、作っている人のコードをみて真似てみようということになり、IntelliJのcommunityバージョンのソース内にある、TypedHandlerDelegateをextendsしているコードを読んで、そのコードを使っているplugin.xmlを読みあさりました。
である程度読んだところで出来たコードが以下です。
...
<extensions defaultExtensionNs="com.intellij">
<!-- Add your extensions here -->
<typedHandler implementation="handler.MyTypedHandlerDelegate"/>
</extensions>
...
public class MyTypedHandlerDelegate extends TypedHandlerDelegate {
@Override
public Result charTyped(char c, Project project, Editor editor, PsiFile file) {
return Result.CONTINUE;
}
}
作ってみて、やっと理解したのが、どのサイトやサンプル見てもAction!Action!と説明があるのですが、Actionってそのプラグインを起動したり、設定したりする入り口であって、もちろん重要なコンセプトではあるのですが、ここだけで完結させる必要はないということでした。
拡張ポイントを作って、Actionでその拡張ポイントで使う値を設定するようにすればいいって。
間違っているかもしれませんが、自分はこの解釈ができて納得し、一気に開発が進みました。
もし、間違っているということであれば、ご指摘頂けると助かります。
できればプラグインのレポジトリのissueに記載して頂けるとありがたいです。
という感じで、すでにできているコードからガシガシ書き進めることができるようになりました。
アイデアが浮かびまくって止まらないよー
ある程度触れるようになるとガンガンアイデアが浮かんできました。
タイプしたら消える画面を設定出来ればHide Side Tool Windows機能もふくめられるじゃん。とか
消したらリストアしたいよね。とか
別にeditorに入力する前でも消したければ消せたほうがいいじゃん。とか
あれ・・・?それ、Hide All Tool Windowsの機能なわけだし、実際IntelliJの中でどうやってんだ?という疑問が浮かび上がりました。
IntelliJの中身をパクる
それでまたIntelliJ Communityのレポジトリに戻り、探してみると見つけました。HideAllToolWindowsActionうほ!ToolWindowManagerExとかこうやって使うんかい!などなどここですごい勉強しました。perfomAction丸々コピペでよくね?ってなりました。
最終的に一番良いサンプルはIntelliJ Communityのソースだと思いました。
メニュー内にある機能はプラグインと同様、AnActionを継承しているものなので、このソース舐めればだいたいやりたいことが出来るようになりました。
コードの説明
では、実際に今回作ったプラグインのソースの説明をしたいと思います。
Action
まず、actionは4つ作成しています。
最初に、設定をいじるActionは以下のクラスです。
- HideBottomToolWindowsToggleAction
- HideLeftToolWindowsToggleAction
- HideRightToolWindowsToggleAction
これらの親クラスとして、HideToolWindowsExToggleActionを作成しました。
ToggleActionというクラスを継承することにより、チェックをつけたり、外したりすることが可能になります。
HideToolWindowsExToggleActionでは、後で説明する、設定情報を変更する処理が記載されています。
最後に、HideRestoreToolWindowsActionは、hide/restore機能にキーバインドつけたいということで、上記にある通り、ほぼほぼHideAllToolWindowsActionと同じ機能を提供しました。
ActionGroupはデフォルトで提供されているもの使用しています。
popup="true"を設定することにより、サブメニューとして追加されます。
設定しなかった場合、全てのActionがadd-to-groupで設定したメニューにリストされます。
<actions>
<!-- Add your actions here -->
<group id="ActionGroup" text="Hide Tool Windows Ex" popup="true">
<action id="HideRestoreToolWindowsAction"
class="com.github.shiraji.hidetoolwindowsex.action.HideRestoreToolWindowsAction"
text="Hide/Restore"/>
<separator/>
<action id="HideRightToolWindowsToggleAction"
class="com.github.shiraji.hidetoolwindowsex.action.HideRightToolWindowsToggleAction"
text="Hide Right Tool Windows"/>
<action id="HideLeftToolWindowsToggleAction"
class="com.github.shiraji.hidetoolwindowsex.action.HideLeftToolWindowsToggleAction"
text="Hide Left Tool Windows"/>
<action id="HideBottomToolWindowsToggleAction"
class="com.github.shiraji.hidetoolwindowsex.action.HideBottomToolWindowsToggleAction"
text="Hide Bottom Tool Windows"/>
<add-to-group group-id="ToolsMenu" anchor="last"/>
</group>
</actions>
Config
configはIntelliJ IDEAのプラグインを作ろう!を真似て作りました。
当初は、この記事のまま、プロジェクト単位の設定としてやればいいかと考えていたのですが、IdeaVimというプラグインはアプリケーションベースで出来ているんだよな・・・。うん?それ見ればよくね?ってことで、VimPluginを確認しました。
上記二つのソースを合体させると、$APP_CONFIG$が見つからないというエラーに陥ります。
で、色々調査していくと、plugin.xmlにapplicationベースの設定方法だよという定義をしなくてはならないようです。
(ドキュメントではさらっと書いてあって、実際どうやればいいのかわからない。)
そして、他のプラグインを漁り、最終的に以下のコードが出来ました。
<extensions defaultExtensionNs="com.intellij">
<!-- Add your extensions here -->
<typedHandler implementation="com.github.shiraji.hidetoolwindowsex.handler.HideToolWindowsExHandler"/>
<applicationService serviceImplementation="com.github.shiraji.hidetoolwindowsex.config.HideToolWindowsExConfig"
serviceInterface="com.github.shiraji.hidetoolwindowsex.config.HideToolWindowsExConfig"/>
</extensions>
@State(
name = "HideToolWindowsExConfig",
storages = {
@Storage(id = "HideToolWindowsExConfig", file = StoragePathMacros.APP_CONFIG + "/hide_tool_windows_ex.xml")
}
)
public class HideToolWindowsExConfig implements PersistentStateComponent<HideToolWindowsExConfig> {
公開後に発覚したのですが、プロジェクトベースからアプリケーションベースの保存方法に切り替えた時、必ず、プラグインをアンインストールして、クリーンな状態からテストをして下さい。自分はアプリケーションベースで設定したけど、実はプロジェクトベースのほうでテストしていました。
Util
utilが一番処理量が多い箇所です。
publicメソッドとして、hide(Project)/hideOrRestore(Project)を提供し、設定された値を参照しつつ、特定のToolウインドウを消したり、出したりしています。
個人的にはっきりいって、こんな処理で本当に大丈夫なの?っていう箇所がUtilクラスにはいくつかあって、タイプスピードが半端ない人の速度に耐えられるのか?とか、そもそもthreadセーフじゃないコードになってることない?とか。。。
まぁ深く追求したら他の箇所でも色々出てくるし、とりあえず、公開して、みんなの目に晒して、叩かれよう。と無視することにしました。
plugin.xml
plugin.xmlにプラグインの説明や、バージョン番号などを設定しておきます。
開発中でも、runしたIntelliJ内で、Preferences > Plugins内に記載されますので、どのように見えるのか確認できます。
自分は、プラグインの説明や最新情報のフォーマットをIdeaVimから拝借しました。
出来たプラグインを公開する
なんとか形にはなったので、公開します。
その前に、上記全てのコードはテストレポジトリでゴリゴリ書いたので、パッケージなどをキレイに修正しました。
修正が完了したところで、Githubにレポジトリ作成しました。
LICENSEはしっかりやらないと後々問題になるので設定しておきます。MITで公開しておきました。
諸々の設定をしますが、プラグインとしての公開対象をplugin.xmlから設定するようにすると、Android Studioが抜けるので、手動でAndroid Studioも含めました。(どの設定をいじればよかったんだろう???)
カテゴリに関してもどこに当たるのかわからず、viewerに処理するわけだし、viewerでいいやという安易な発想でviewerに追加しました。 Code Editingのほうが近いイメージだったので、そちらにカテゴリ変更しました。
最後にLICENSEなどの情報を入力し、申請します。
ドキュメントによれば2営業日内で申請結果がわかるそうです。
このプラグインは1日で承認されました。小さなプラグインだったからかもしれません。
メールなどで通知は来ません。いつのまにか承認されていました。
アップデートに関しても同様に2営業日で結果が出ます。
課題
テスト
次に、テストです。これは本当にドキュメントが少なくて、作成するのにプラグイン作る以上に時間がかかると思い、放置しました。
このままテストなしはカッコ悪いので、がんばって書けるようにしたいと思います。
公開範囲
他のIDEにも公開すべきかどうかわからない。使ったことないので・・・テストできないし、ユースケースに合うのだろうか?
実装方法
Utilのstaticとして主だった機能を提供している。さらにstaticなメンバー変数とかある。。。
threadが複数だった場合どうなるかさっぱり。
デフォルトの値
本当に個人的な見解でデフォルトの値を決めました。デフォルトでは、左側のToolウインドウは閉じない設定になっています。これは、タイピングする直前を想定しているので、左側のToolウインドウが閉じたら、どこにカーソルがあるのか見失ってしまう恐れがあるためです。
最後に
ということで、グダグダやってきましたが、初めて、IntelliJのプラグインを公開するに至ることが出来ました。
初めてなこともあり、非常にダサいことをしまくりました。
マサカリがいっぱい飛んでくるのを覚悟でコードやこのエントリーを公開しました。
ただ、打たれ弱いので、出来れば優しく指摘を頂ければと思います。
次はAndroid Studio特有のプラグインとか作成してみたいなーと思ってます。
もし、宜しければ、Githubのレポジトリ - HideToolWindowsExにstar頂けると非常にやる気になります!