3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

IntelliJ IDEA の progress bar を 犬にする plugin

Posted at

「はてなエンジニア Advent Calendar 2024」の2025/01/07 の記事です。

イメージ

fds.gif

モデルはペットのおこげです。 ゆえに okoge-progress-barです。かわいい。

Marketplace

ソース

IntelliJ Pluginの 仕組み

IntelliJ Platform SDK

IntelliJ Platform SDKは、JetBrains製IDE(IntelliJ IDEA、PyCharm、WebStormなど)用のプラグインを開発するためのツールとAPIを提供する開発者向けキットです。

SDKを使用することで、次のようなことが実現できます:

  • カスタムUIの追加(今回のプログレスバーのような機能)
  • アクションやリスナーを通じてイベントを処理
  • メニューやツールウィンドウの拡張

intellij-platform-plugin-template

intellij-platform-plugin-template は、JetBrainsが提供するプラグイン開発のためのテンプレートリポジトリです。プラグイン開発の初期設定とサンプルコード、公開のための設定が入ってます。

plugin.xml

plugin.xmlは、プラグインの設定や動作を定義するファイルです。

IntelliJ Platform では イベント駆動型アーキテクチャを採用しているようで、さまざまなイベントに応じてリスナーを登録できます。

このファイルでは、そのListenerを登録することで、IntelliJプラットフォームのイベント(アプリケーション、プロジェクト、UI変更など)にフックしてカスタム処理を実行できます。

plugin.xml
<idea-plugin>
    <id>kk.kkhouse777.okogeprogressbar</id>
    <name>okoge-progress-bar</name>
    <vendor>kk__777</vendor>

    <depends>com.intellij.modules.platform</depends>

    <description><![CDATA[
      Pretty progress bars featuring a dog named Okoge, designed for IJ-based IDEs.
    ]]></description>

    <applicationListeners>
        <listener class="kk.kkhouse777.okogeprogressbar.listeners.OkogeProgressListener"
                  topic="com.intellij.ide.ui.LafManagerListener"/> <!--    おそらく不要    -->
        <listener class="kk.kkhouse777.okogeprogressbar.listeners.OkogeProgressListener"
                  topic="com.intellij.openapi.application.ApplicationActivationListener"/>
    </applicationListeners>
</idea-plugin>

ほかにも PluginのWindowで表示される説明文等のmetadataの設定もここで行います。

実装

素材を用意

知り合い作。3232 と 6464 で 準備。

rsz_okoge.png

サイズ感は先駆者👇にならいました。
NyanProgressBar

javax.swing.BasicProgressBarUIを拡張する

IntelliJのプログレスバーはjavax.swing.JProgressBarをベースにしているため、BasicProgressBarUIを拡張してカスタマイズします。

参考: javax.swing

import com.intellij.openapi.ui.GraphicsConfig;
import com.intellij.util.ui.GraphicsUtil;
import com.intellij.util.ui.JBUI;
import kk.kkhouse777.okogeprogressbar.okoge.OkogeProgressBarState;

import javax.swing.*;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicProgressBarUI;
import java.awt.*;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.geom.RoundRectangle2D;

public class OkogeProgressBarUI extends BasicProgressBarUI {

    @SuppressWarnings({"MethodOverridesStaticMethodOfSuperclass", "UnusedDeclaration"})
    public static ComponentUI createUI(JComponent c) {
        return new OkogeProgressBarUI();
    }

    @Override
    protected void paintIndeterminate(Graphics g2d, JComponent c) {
        // ~~略
    }

    @Override
    protected void paintDeterminate(Graphics g, JComponent c) {
        // ~~略
    }
    // ...
}

  • paintIndeterminate

    • プログレスバーが進行状態を特定できない場合に描画される

  • paintDeterminate
    • プログレスバーの進行状況が特定できる場合に描画される

描画部分は javax.swing に倣えばよいので、例えばシンプルにオレンジ色のバーを表示するならこんな感じになります。

@Override
protected void paintDeterminate(Graphics g, JComponent c) {
    Graphics2D g2 = (Graphics2D) g.create();
    try {
        // 背景の描画
        g2.setColor(Color.LIGHT_GRAY);
        g2.fillRect(0, 0, c.getWidth(), c.getHeight());

        // 進捗バーの描画
        int progress = progressBar.getValue();
        int width = (int) (c.getWidth() * (progress / 100.0));
        g2.setColor(Color.ORANGE);
        g2.fillRect(0, 0, width, c.getHeight());
    } finally {
        g2.dispose();
    }
}

拡張したProgressBar を Listener で 登録する

import com.intellij.ide.ui.LafManager
import com.intellij.ide.ui.LafManagerListener
import com.intellij.openapi.application.ApplicationActivationListener
import com.intellij.openapi.wm.IdeFrame
import kk.kkhouse777.okogeprogressbar.OkogeProgressBarUI
import javax.swing.UIManager

class OkogeProgressListener :
    LafManagerListener,
    ApplicationActivationListener {
    override fun applicationActivated(ideFrame: IdeFrame) {
        updateProgressBar()
    }

    override fun lookAndFeelChanged(p0: LafManager) {
        updateProgressBar()
    }

    companion object {
        private const val PROGRESS_BAR_UI_KEY = "ProgressBarUI"
        private const val PROGRESS_BAR_UI_CLASS_NAME = "kk.kkhouse777.okogeprogressbar.ui.OkogeProgressBarUI"

        private fun updateProgressBar() {
            UIManager.put(PROGRESS_BAR_UI_KEY, PROGRESS_BAR_UI_CLASS_NAME)
            UIManager.getDefaults().put(PROGRESS_BAR_UI_CLASS_NAME, OkogeProgressBarUI::class.java)
        }
    }
}


  • LafManagerListener

    • テーマが変更されたときに、カスタムプログレスバーの外観や設定が反映されるようにします
    • (一応 入れているのですが おそらく不要)
  • ApplicationActivationListener

    • IDEがアクティブ化された際に、UI設定を再適用することで、プログレスバーが正しく表示されるようにします

UIManagerで指定するkey は 以下を参考にしました。
https://github.com/kagof/intellij-pokemon-progress/blob/master/src/main/java/com/kagof/intellij/plugins/pokeprogress/PokemonProgressListener.java

plugin.xmlにListener を宣言する

plugin.xml
<idea-plugin>
    <id>kk.kkhouse777.okogeprogressbar</id>
    <name>okoge-progress-bar</name>
    <vendor>kk__777</vendor>

    <depends>com.intellij.modules.platform</depends>

    <description><![CDATA[
      Pretty progress bars featuring a dog named Okoge, designed for IJ-based IDEs.
    ]]></description>

    <applicationListeners>
        <listener class="kk.kkhouse777.okogeprogressbar.listeners.OkogeProgressListener"
                  topic="com.intellij.ide.ui.LafManagerListener"/> <!--    おそらく不要    -->
        <listener class="kk.kkhouse777.okogeprogressbar.listeners.OkogeProgressListener"
                  topic="com.intellij.openapi.application.ApplicationActivationListener"/>
    </applicationListeners>
</idea-plugin>

公開

intellij-platform-plugin-template が すでにいろいろ準備してくれています。

credential周り

intellij plugin の公開タスク等をまとめている gradle plugin org.jetbrains.intellij.platformが すでに入っていて以下設定があります。

build.gradle.kts
...
intellijPlatform {
    pluginConfiguration {
    ...
    signing {
        certificateChain = providers.environmentVariable("CERTIFICATE_CHAIN")
        privateKey = providers.environmentVariable("PRIVATE_KEY")
        password = providers.environmentVariable("PRIVATE_KEY_PASSWORD")
    }

この環境変数へ それぞれのcredential に設定します。

詳細は上記 を参照で良さそうですが、さっくり

  • private key を作成
openssl genpkey\
  -aes-256-cbc\
  -algorithm RSA\
  -out private_encrypted.pem\
  -pkeyopt rsa_keygen_bits:4096
  • 自己署名証明書を作成
openssl rsa -in private_encrypted.pem -out private.pem
openssl req -key private.pem -new -x509 -days 365 -out chain.crt
  • IntelliJ MarketPlaceのAPIトークンを取得

  • 環境変数(GithubAction含む)に登録する

となります。

github action

templateには ワークフローが いくつか定義されています。

  • main への 変更をフックに releaseのドラフトが作られる
  • 上記を publish する と MarketPlaceに公開される(初回のみ手動で公開する必要がある

その他必要なこと

ReadMeの修正 や MarketPlaceへのmeta dataの登録

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?