プラグイン作って覚えたことを淡々と記録するよ(アイコン編)

最近,WicketForgeが参考にしていたといわれるIntelliStripesオープンソース化してくれたので,いろいろ知れて楽しい。
やっぱりIDEAはプラグイン作りやすいぞ。ヒマと熱意があればIntelliClickとか作れそうだ。
#って作んないけどさ。:-P


しかし,この人ら,どうやってIDEAの内部情報知り得てるんだろう?intellij.netのフォーラムだけで組めているとは到底思えない。

ファイルアイコン

IntelliStripesから発見。こんな具合に特定のクラスやファイルのみアイコンを変える。
#サンプルでは,Mapのサブクラスだけアイコンを変えてみた(TestMap.javaに注目)。


やり方は簡単。application-componentにIconProviderを実装するだけでOK。
なんで,application-componentじゃないとダメとか聞かないで。だってIntelliStripesがそうやってんだもん。:-)

public class IconPlugin implements ApplicationComponent, IconProvider {
    public IconPlugin() { } 

    public void initComponent() { }
    public void disposeComponent() { }
    @NotNull public String getComponentName() { return "IconPlugin"; }

    @Nullable
    public Icon getIcon(@NotNull PsiElement element, int flags) {
        if (element instanceof PsiClass) {
            PsiClass clazz = (PsiClass) element;
            try {
                // PsiUtilsは自作ユーティリティで,OpenAPIとか関係無いです。
                if (PsiUtils.isSubclass(clazz, "java.util.Map")) {
                    return IconLoader.findIcon("/icon_small.png");
                }
            }
            catch (Exception ignore) { }
        }
        return null;

    }
}

ちなみに,IntelliStripesではgetIcon()の引数でもらうPsiElementを解析して,

  • ActionBeanを継承したJavaファイル
  • stripesのTLDを宣言しているJSPファイル

のアイコンを変更しておりました。

ガーターアイコン

ガーターとは,エディタの左側に出てくるアイコンのことね。


OpenAPIのAnnotatorってのとGutterIconRendererを組み合わせて実現するんだけど,こっちは,ちょっとアンドキュメンタブルな手順を踏む。そのアンドキュメンタブルな手順ってのが,このplugin.xmlのextensionsタグのこと。

<extensions defaultExtensionNs="com.intellij">
   <annotator language="JAVA"
              annotatorClass="com.googlecode.intellimars.icon.IconAnnotator"/>
</extensions>


一応,OpenAPIのJavadoccom.intellij.ExtensionPointsにextensionsタグに関する説明があるんだけど,どうも完全ではないみたいで,今回紹介したannotatorのほかにも,fileTemplateGroupやらfileEditorProviderやら,いろいろ宣言できるっぽい。
#DevkitにあるStrutsAssistantを見るといい。


ちょっと,そこまで極めたいとは思わないので,とりあえずannotatorだけ。Annotatorを実装したコードは,こんな感じ。見ての通り,そんな面倒なもんじゃない。

public class IconAnnotator implements Annotator {
    public void annotate(PsiElement psiElement, AnnotationHolder holder) {
        if( psiElement instanceof PsiClass) {
            PsiClass clazz = (PsiClass) psiElement;
            // PsiUtilsは自作ユーティリティで,OpenAPIとか関係無いです。
            if( PsiUtils.isSubclass(clazz, "java.util.Map") ) {
                final Annotation annotation = holder.createInfoAnnotation(clazz.getNameIdentifier(), "My Annotator");
                annotation.setGutterIconRenderer(new GotoMarkupGutter());
            }
        }
    }
}

AnnotationHolderってやつで,特定の場所のAnnotatorを作るんだけど,AnnotatorはInformation/Warning/Errorの3種類あるらしい。アノテートする場所とか種類とかは,AnnotationHolderのAPIみてくれ。


あとは,作ったAnnotatorに対して,setGutterIconRenderer()を呼びガーターアイコンを指定しておしまいとなる。

public class GotoMarkupGutter extends GutterIconRenderer {
    @NotNull
    public Icon getIcon() {
        return IconLoader.findIcon("/actions/clean.png");
    }
}


上記のコード例では何もしてないが,ガーターアイコンをクリックして,なんかしかのアクション(AnAction)を仕込むことも可っぽい。
うーん,こんだけ簡単だと,逆に拍子抜けするなぁ。

ps.
どうでもいいけど,PsiUtils.isSubclass()のソース。

public class PsiUtils {
    public static boolean isSubclass(PsiClass clazz, String superClass) {
        if (clazz == null) {
            return false;
        }
        try {
            if (clazz.getQualifiedName().equals(superClass)) {
                return true;
            }
        }
        catch (NullPointerException ignore) { }
        if (isSubclass(clazz.getSupers(), superClass)) {
            return true;
        }
        else if (isSubclass(clazz.getInterfaces(), superClass)) {
            return true;
        }

        return false;
    }

    protected static boolean isSubclass(PsiClass[] supers, String superClass) {
        for (PsiClass aSuper : supers) {
            if (isSubclass(aSuper, superClass)) {
                return true;
            }
        }
        return false;
    }
}

アニメーションアイコン

これは,蛇足だったかも。アニGIF貼っ付けるんじゃなく,AnimatedIconのサブクラスを作るのがIDEA流らしい。とりあえず,(↓)こんなアイコンは,

AsyncProcessIconを作って,どっかに張るだけ(AnimatedIconはJComponentの子どもです)。それに,AsyncProcessIconは,Plugin SDKにソース付いているから,AnimatedIconを自作するときの参考にするとヨロシ。


これまた,どうでもいい話だけど,JetConnectプラグインのメールアイコンもAnimatedIconの子どもです(EnvelopeIcon)。