プラグインの拡張¶
このガイドでは、確立されたパターンに従ってプラグインに新機能を追加する方法を説明します。
一般的なワークフロー¶
src/main/kotlin/com/rescript/plugin/配下の適切なパッケージに新しい Kotlin ファイルを作成IntelliJ Platform の Extension Point インターフェースを実装
正しい Extension Point の下に
plugin.xmlで登録src/test/kotlin/com/rescript/plugin/にテストを追加ビルドとテスト:
./gradlew buildPlugin
一般的な Extension Point パターン¶
新しいインスペクションの追加¶
インスペクションはコードを分析し、問題を報告します。
package com.rescript.plugin.analysis
import com.intellij.codeInspection.LocalInspectionTool
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.PsiElementVisitor
class RescriptMyInspection : LocalInspectionTool() {
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
// Return a visitor that checks PSI elements
}
}
plugin.xml で登録:
<localInspection
language="ReScript"
groupName="ReScript"
displayName="My inspection"
enabledByDefault="true"
level="WARNING"
implementationClass="com.rescript.plugin.analysis.RescriptMyInspection"/>
新しいインテンションアクションの追加¶
インテンションは Alt+Enter でクイックアクションを提供します。
package com.rescript.plugin.intention
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiFile
class RescriptMyIntention : IntentionAction {
override fun getText(): String = "My action"
override fun getFamilyName(): String = "ReScript"
override fun isAvailable(project: Project, editor: Editor?, file: PsiFile?): Boolean { ... }
override fun invoke(project: Project, editor: Editor?, file: PsiFile?) { ... }
override fun startInWriteAction(): Boolean = true
}
plugin.xml で登録:
<intentionAction>
<language>ReScript</language>
<className>com.rescript.plugin.intention.RescriptMyIntention</className>
<category>ReScript</category>
</intentionAction>
新しいアクションの追加¶
アクションはメニューに表示され、キーボードショートカットを割り当てることができます。
package com.rescript.plugin.navigation
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
class RescriptMyAction : AnAction() {
override fun actionPerformed(e: AnActionEvent) { ... }
override fun update(e: AnActionEvent) { ... }
}
plugin.xml で登録:
<action id="Rescript.MyAction"
class="com.rescript.plugin.navigation.RescriptMyAction"
text="My Action"
description="Description of my action">
<add-to-group group-id="EditorPopupMenu" anchor="last"/>
<keyboard-shortcut keymap="$default" first-keystroke="alt shift M"/>
</action>
新しいレクサートークンの追加¶
レクサーに新しいトークン型を追加するには:
Rescript.flexを編集 — トークンルールを追加RescriptTokenTypes.ktを編集 —IElementType定数を定義し、適切なTokenSetに追加RescriptSyntaxHighlighter.ktを編集 — トークンをTextAttributesKeyにマッピングビルド —
./gradlew buildPluginでレクサーを再生成
Postfix テンプレートの追加¶
// Add to RescriptPostfixTemplateProvider.kt
class MyPostfixTemplate(provider: PostfixTemplateProvider) :
PostfixTemplateWithExpressionSelector(
"mytemplate",
"expr.mytemplate",
"Description",
RescriptExpressionSelector(),
provider
) {
override fun expandForChooseExpression(expression: PsiElement, editor: Editor) {
// Transform the expression
}
}
Code Vision プロバイダーの追加¶
Code Vision プロバイダーは、コード要素の上にインラインアノテーション(例: 型シグネチャ)を表示します。
package com.rescript.plugin.codevision
import com.intellij.codeInsight.hints.codeVision.DaemonBoundCodeVisionProvider
import com.intellij.codeInsight.hints.codeVision.CodeVisionAnchorKind
import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiFile
class RescriptMyCodeVisionProvider : DaemonBoundCodeVisionProvider {
override val id: String = "rescript.myVision"
override val name: String = "My Vision"
override val defaultAnchor: CodeVisionAnchorKind = CodeVisionAnchorKind.Top
override fun computeForEditor(
editor: Editor,
file: PsiFile,
): List<Pair<TextRange, CodeVisionEntry>> {
// Compute entries to display above code elements
return listOf(
Pair(range, TextCodeVisionEntry("display text", id))
)
}
}
plugin.xml で登録:
<codeInsight.daemonBoundCodeVisionProvider
implementation="com.rescript.plugin.codevision.RescriptMyCodeVisionProvider"/>
ツールウィンドウの追加¶
ツールウィンドウは、IDE のサイドバーまたはボトムバーに永続的な UI パネルを提供します。
package com.rescript.plugin.typeinfo
import com.intellij.openapi.project.DumbAware
import com.intellij.openapi.project.Project
import com.intellij.openapi.wm.ToolWindow
import com.intellij.openapi.wm.ToolWindowFactory
class RescriptMyToolWindowFactory : ToolWindowFactory, DumbAware {
override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) {
val panel = MyPanel(project)
val content = toolWindow.contentManager.factory
.createContent(panel, null, false)
toolWindow.contentManager.addContent(content)
}
override fun shouldBeAvailable(project: Project): Boolean = true
}
plugin.xml で登録:
<toolWindow id="My Tool Window"
anchor="bottom"
secondary="true"
factoryClass="com.rescript.plugin.typeinfo.RescriptMyToolWindowFactory"
icon="/icons/rescript-file.svg"/>
PSI スタブインデックスの追加¶
スタブインデックスは、ファイル全体を解析せずに高速なシンボル検索を可能にします。プラグインは名前ベースのインデックスに StringStubIndexExtension を、コンテンツベースのインデックスに FileBasedIndexExtension を使用します。
名前インデックス(名前によるシンボル検索):
package com.rescript.plugin.indexing
import com.intellij.psi.stubs.StringStubIndexExtension
import com.intellij.psi.stubs.StubIndexKey
class RescriptMyIndex : StringStubIndexExtension<RescriptDeclarationPsiElement>() {
companion object {
val KEY: StubIndexKey<String, RescriptDeclarationPsiElement> =
StubIndexKey.createIndexKey("rescript.my.index")
}
override fun getKey() = KEY
override fun getVersion(): Int = 1
}
plugin.xml で登録:
<stubIndex implementation="com.rescript.plugin.indexing.RescriptMyIndex"/>
ファイルベースインデックス(クエリ用のファイルコンテンツインデックス):
class RescriptMyFileIndex : FileBasedIndexExtension<String, Void>() {
companion object {
val NAME: ID<String, Void> = ID.create("rescript.my.file.index")
}
override fun getName() = NAME
override fun getIndexer(): DataIndexer<String, Void, FileContent> {
return DataIndexer { inputData ->
// Scan file content and return key-value map
mapOf("key" to null)
}
}
override fun getKeyDescriptor() = EnumeratorStringDescriptor()
override fun getValueExternalizer() = VoidDataExternalizer()
override fun getVersion(): Int = 1
override fun getInputFilter() = FileBasedIndex.InputFilter { file ->
file.fileType == RescriptFileType.INSTANCE
}
override fun dependsOnFileContent(): Boolean = true
}
plugin.xml で登録:
<fileBasedIndex implementation="com.rescript.plugin.indexing.RescriptMyFileIndex"/>
LSP カスタムリクエストの追加¶
@JsonRequest アノテーションを使用して、LSP サーバーインターフェースにカスタムリクエストメソッドを追加します。
package com.rescript.plugin.lsp
import org.eclipse.lsp4j.TextDocumentIdentifier
import org.eclipse.lsp4j.jsonrpc.services.JsonRequest
import org.eclipse.lsp4j.services.LanguageServer
import java.util.concurrent.CompletableFuture
interface RescriptLanguageServer : LanguageServer {
@JsonRequest("textDocument/createInterface")
fun createInterface(params: TextDocumentIdentifier): CompletableFuture<TextDocumentIdentifier>
@JsonRequest("textDocument/openCompiled")
fun openCompiled(params: TextDocumentIdentifier): CompletableFuture<TextDocumentIdentifier>
}
カスタムリクエストはインターフェースメソッドとして定義され、plugin.xml には登録しません。このインターフェースは LspServerDescriptor 経由で LSP サーバープロキシを取得する際に使用されます:
val server = lspServerDescriptor.getServer() as? RescriptLanguageServer
val result = server?.createInterface(params)?.get()
ペーストプロセッサの追加¶
ペーストプロセッサは、エディタへのペースト時にクリップボードの内容を変換します。
package com.rescript.plugin.paste
import com.intellij.codeInsight.editorActions.CopyPastePostProcessor
import com.intellij.codeInsight.editorActions.TextBlockTransferableData
import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiFile
import java.awt.datatransfer.Transferable
class RescriptMyPasteProcessor : CopyPastePostProcessor<TextBlockTransferableData>() {
override fun collectTransferableData(
file: PsiFile, editor: Editor,
startOffsets: IntArray, endOffsets: IntArray,
): List<TextBlockTransferableData> = emptyList()
override fun extractTransferableData(
content: Transferable,
): List<TextBlockTransferableData> {
// Check clipboard content and return data if applicable
}
override fun processTransferableData(
project: Project, editor: Editor,
bounds: RangeMarker, caretOffset: Int,
indented: Ref<in Boolean>,
values: MutableList<out TextBlockTransferableData>,
) {
// Transform the pasted content
WriteCommandAction.runWriteCommandAction(project) {
editor.document.replaceString(bounds.startOffset, bounds.endOffset, transformed)
}
}
}
plugin.xml で登録:
<copyPastePostProcessor
implementation="com.rescript.plugin.paste.RescriptMyPasteProcessor"/>
ファイル命名規則¶
クラス:
Rescript<Feature><Type>.kt(例:RescriptFoldingBuilder.kt)テスト:
Rescript<Feature><Type>Test.kt(例:RescriptFoldingBuilderTest.kt)パッケージ: 機能カテゴリに合わせる (例:
folding/,navigation/,analysis/)
KDoc 要件¶
すべてのクラスと非自明な公開メソッドには英語で KDoc コメントを記述する必要があります。詳細は Contributing Guide を参照してください。