電通総研 テックブログ

電通総研が運営する技術ブログ

IntelliJプラグイン開発の始め方~ラインマーカー編~

こんにちは。電通総研ITの寺尾です。 プラグイン開発の機能実装について、今回はラインマーカーの実装についてお話します。

前回はこちら:IntelliJプラグイン開発の始め方~アクション機能編~

ラインマーカーとは

エディタ画面上のガター(行番号の表示される列)に表示されるアイコンを、IntelliJではラインマーカーガターアイコンと呼びます。
インタフェースの実装や、オーバーライドメソッドの実装先へのジャンプで、アイコンをクリックする方も多いのではないでしょうか。

アクションによるファイルジャンプとは別に、今回はラインマーカーによるファイルジャンプ機能の実装をご紹介します。

公式ドキュメント ラインマーカープロバイダー

実装前に知っておくこと

前回と同様に、本記事でもKotlinでの実装例を提示します。

Kotlinプロジェクトに関する内容は、アクション機能編の実装前に知っておくことをご参照ください。

実装

ゴール

今回はラインマーカーのクリックで、DAOメソッドからSQLファイルへのジャンプ機能の実装を目指します。

DemoFileJunpGutter

事前準備

ラインマーカーはプロバイダークラスを実装して実現します。
まずは以下の準備から始めていきましょう。

  • RelatedItemLineMarkerProviderのサブクラス
  • プラグイン設定登録

今回のラインマーカー処理は、RelatedItemLineMarkerProviderのサブクラスで実装します。
plugin.xmlには、以下のように<codeInsight.lineMarkerProvider>タグを使って登録します。

プロバイダークラスはIntelliJの拡張ポイントとして登録するため、タグ内に記述します。

  • codeInsight.lineMarkerProvider: ラインマーカーのプロバイダークラスを登録するタグ
  • language: ラインマーカー表示処理対象とするファイルタイプ
  • implementationClass: 実装したプロバイダークラスの完全修飾クラス名
<extensions defaultExtensionNs="com.intellij">
    <codeInsight.lineMarkerProvider language="JAVA" implementationClass="org.domaframework.doma.intellij.gutter.dao.DaoMethodProvider"/>
</extensions>

それでは、実装を始めていきましょう!
各オーバーライドメソッドでは、ガターに表示するアイコン設定とラインマーカーの登録処理を実装します。

ラインマーカーのアイコン

ラインマーカーに表示するアイコンは、デフォルトで用意されているIconや独自に用意した画像のアイコンタイプから設定できます。

getIcon

デフォルトで用意されているアイコンや任意のアイコンを以下のように設定します。

// デフォルトで用意されているアイコン設定例
override fun getIcon(): Icon = AllIcons.General.Mouse

参考:独自のアイコンを登録する

独自に用意した画像をアイコンとして使用する場合、ファイルタイプとアイコンを表すオブジェクト、クラスを用意します。

Doma Tools」では、カスタム言語としてSQLファイルを扱うための実装の一環で、ファイルタイプ定義を生成しています。詳細は以下公式ドキュメントをご参照ください。

class SqlFileType private constructor() : LanguageFileType(SqlLanguage.INSTANCE) {
    override fun getName(): String = "DomaSql"

    override fun getDescription(): String = "Doma sql template"

    override fun getDefaultExtension(): String = "sql"

    override fun getIcon(): Icon = SqlIcon.FILE

    companion object {
        @JvmField
        val INSTANCE: SqlFileType = SqlFileType()
    }
}

次にアイコンタイプを表すオブジェクトを定義します。

object SqlIcon {
    @JvmField
    val FILE: Icon = getIcon("/icons/SqlFile.svg", SqlFileType::class.java)
}

独自で用意したアイコンも同じように、Icon型プロパティで設定できます。

override fun getIcon(): Icon = SqlIcon.FILE

ナビゲーション機能を持つラインマーカーの登録

collectNavigationMarkersでは、ラインマーカーがクリックされた時に呼び出される処理ではなく、
どのような機能を持つラインマーカーを登録するかを実装します。

メソッドに渡されるファイル内のPSI要素の条件をチェックして、対象にラインマーカーを紐づけた情報を生成します。

今回は簡単に「メソッド要素(PsiMethod)である」という条件を満たす要素に、「SQLファイルにジャンプする」ラインマーカーを生成します。
アクション機能と異なり、このプロバイダークラスは「Javaファイルを対象に動作する」設定で登録しているため、ファイルタイプチェックの実装はしません。

collectNavigationMarkers

// PsiElement型引数eの型をチェック
val method = e as? PsiMethod
if(method != null){
 // ラインマーカー生成処理
}

引数eがラインマーカーの対象であることを確認後、DAOメソッドからSQLファイルにジャンプするラインマーカーを生成します。

if(method != null){
    // 一意な要素にラインマーカーを生成するため
    // 要素の識別子トークンを持つPsiNameIdentifierOwnerに変換
    val owner = e as? PsiNameIdentifierOwner ?: return
    val identifier = owner.nameIdentifier ?: return
    
    val sqlFile : PsiElement? = // アクション機能と同じように、ジャンプ先のSQLファイル情報を取得
    
    // ラインマーカービルダーオブジェクトの生成
    val builder: NavigationGutterIconBuilder<PsiElement> =
                    NavigationGutterIconBuilder
                        .create(icon) // getIcon()から値を取得
                        .setTooltipText("SQLへ移動")
                        .setTarget(sqlFile)
    
    // ラインマーカー情報を保持する引数`result`にラインマーカーオブジェクトを設定
    result.add(builder.createLineMarkerInfo(identifier))
}

参考:ナビゲーション以外の機能を持たせる

上記の例では、単純に指定した対象へのファイルジャンプを行うナビゲーション処理を行っています。
アイコンクリック時にファイルジャンプ以外の処理を実行したい場合、生成するラインマーカーオブジェクトをRelatedItemLineMarkerInfo型として作成します。

val marker =
    RelatedItemLineMarkerInfo(
        identifier,
        identifier.textRange,
        icon,
        { "SQLへ移動" },
        getHandler(e.project, sqlFile), // クリック時処理のハンドラーを設定
        GutterIconRenderer.Alignment.RIGHT,
    ) {
        // 必要に応じて関連要素のCollectiopn<GotoRelatedItem?>を返す処理を実装
    }
result.add(marker)

クリック時に実行する処理のハンドラーは、以下のように実装できます。

 private fun  getHandler(project: Project ,sqlFile: PsiFile): GutterIconNavigationHandler<PsiElement> =
        GutterIconNavigationHandler { _: MouseEvent?, _: PsiElement? ->
            // 任意の処理とファイルジャンプ処理
            FileEditorManager.getInstance(project).openFile(sqlFile.virtualFile, true)
        }

動かしてみる

ラインマーカーの表示とクリック時の挙動を確認してみましょう。

デバッグ起動方法は前回と同じく、デバッグ起動するで環境を起動し、DAOファイルを開いてラインマーカーの表示を確認します。

アイコンが表示されたら、クリックしてSQLファイルへのジャンプを試してみましょう!

参考:「Doma Tools」実装コード

Doma Tools」の同様のラインマーカーは以下コードで実装しています。

DaoMethodProvider

さいごに

今回はアクションと同じくファイルジャンプをラインマーカーで実装する方法をご紹介しました。
独自のアイコンを表示させると、「自分で作ったプラグイン感」を一層強く感じます。

ワンクリックで処理を呼び出せるラインマーカーで、いろいろな便利機能を実装してみてください!

レビューも投稿していただけますと大変励みになります🙇‍♀️

Doma Tools マーケットプレイスページ

プラグインOSSとしてDomaコミュニティへ寄贈されています。
不具合修正や機能要望、ディスカッションにもぜひご参加ください。

採用ページ

執筆:@terao.haruka、レビュー:@nakamura.toshihiro
Shodoで執筆されました