Ohhnews

分类导航

$ cd ..
Jetbrains Blog原文

构建带可选内容模块的IntelliJ插件

#intellij插件#插件模型v2#可选内容模块#gradle构建#远程开发

如果插件的某个部分只应在特定IDE功能可用时才加载,该如何实现? Plugin Model v2 现已作为实验性功能推出,用于结构化、打包和构建插件,并以面向未来的方式支持此类场景。其主要用例是 拆分模式(远程开发) 插件。

在本文中,我们将创建一个插件,将 CSS PSI 相关功能移至可选的插件内容模块中。

IDE 包含一个提供 CSS 支持的捆绑插件内容模块,我们的可选插件内容模块依赖于它。在 IntelliJ IDEA 2026.1 中(CSS PSI 已免费开放,无需订阅),此内容模块将自动可用。在 IntelliJ IDEA 2025.3 中,仅当用户拥有订阅时才会加载。 [LOADING...] 具有一个依赖于 IDE 插件内容模块的插件内容模块的插件

新插件

使用 IDE Plugin 生成器创建插件,该生成器位于 IntelliJ IDEA 2026.1 的 File | New Project(如果已安装 Plugin DevKit)。移除样板代码(如注释和依赖项),并设置正确的插件描述符元数据。

更重要的是,将 Gradle 构建脚本设置为依赖于 IntelliJ IDEA 2025.3。在此版本中,Plugin Model v2 仍为实验性,但对第三方插件普遍可用。

$ kotlin
dependencies {
    intellijPlatform {
        intellijIdea("2025.3")
    }
}

为模块化构建准备 Gradle 构建脚本

IntelliJ Platform Gradle Plugin 2.16.0 及更高版本为对应于 IntelliJ 插件内容模块的 Gradle 子模块提供了精简配置。

每个这样的 Gradle 子模块需要两个 Gradle 插件:Kotlin 和插件内容模块支持。

build.gradle.kts 中启用此功能。

$ kotlin
subprojects {
    apply(plugin = "org.jetbrains.kotlin.jvm")
    apply(plugin = "org.jetbrains.intellij.platform.module")
}

为模块化构建准备插件描述符

在模块化插件中,插件描述符 plugin.xml 是最小化的。甚至 com.intellij.modules.platform 依赖也不再需要,因为它会自动提供。

操作、扩展和监听器不再属于此处。它们在相应的插件内容模块描述符中声明。

创建插件内容模块

在 IDE 中,创建一个空的 Kotlin 模块 css,并使用 Gradle 构建。它同时映射到一个 Gradle 子项目和一个插件内容模块。最初,其构建脚本应缩减为空文件。所有必要的配置将由父构建脚本及其 Gradle 插件提供。

要使其工作,请在主构建脚本中将此 Gradle 子项目声明为依赖项。

$ kotlin
dependencies {
    intellijPlatform {
        // ...
    }
    implementation(project(":css"))
}

然后,设置插件内容模块描述符。注意其命名和位置。与通常的 plugin.xml 不同,插件内容模块描述符位于 src/main/resources 的类路径根目录下。描述符名称派生自父项目名称。换句话说,创建一个 src/main/resources/mincssrel.css.xml 文件,其中包含空的 <idea-plugin> 元素。

准备好此插件内容模块描述符后,最后一个配置步骤是在插件描述符中声明插件内容模块。在 plugin.xml 中,使用 loading 属性将此模块声明为可选。

$ xml
<idea-plugin>
    <!-- 为简洁起见省略 -->
    <content>
        <module name="mincssrel.css" loading="optional" />
    </content>
</idea-plugin>

loading 属性可以省略,但建议显式指定以避免混淆。

依赖 PSI 功能

CSS PSI 功能位于 IDE 插件内容模块 intellij.css 中。在两个地方将此依赖项添加到 css 插件内容模块:首先,在插件内容模块 Gradle 构建脚本中;其次,在插件内容模块描述符中。这些声明有两个方面:Gradle 决定编译内容,插件内容模块描述符决定加载内容。

css/build.gradle.kts 文件现在获得适当内容。使用 IntelliJ Platform Gradle Plugin 提供的 bundledModule 表示法在 intellijPlatform 块中添加依赖项。

$ kotlin
dependencies {
    intellijPlatform {
        bundledModule("intellij.css")
    }
}

src/main/resources/mincssrel.css.xml 描述符不再为空。通过引用完整的模块名称(包括其前缀)来声明对此捆绑插件内容模块的依赖。

$ xml
<idea-plugin>
    <dependencies>
        <module name="intellij.css" />
    </dependencies>
</idea-plugin>

插件内容模块依赖项是 Plugin Model v2 的一部分。每个 <module> 元素声明一个必需的非可选依赖项。如果此依赖项不可用,则 mincssrel.css 模块不会加载。

作为一般规则,如果插件没有可用的插件内容模块,则它将被禁用。

CSS PSI

为了演示功能有效,创建一个 CssAction 动作,并在插件内容模块描述符 mincssrel.css.xml 中声明它。

$ xml
<actions>
    <action id="org.intellij.sdk.css.CssAction"
            class="org.intellij.sdk.css.CssAction"
            text="Invoke CSS Action"
    />
</actions>

然后,提供源代码。从静态样式表字符串创建一个内存中的 CSS 文件,在后台线程的 readAction 下读取 PSI,遍历规则集,收集 CSS 选择器名称,并在事件分派线程(EDT)上的对话框中显示它们。

$ kotlin
import com.intellij.lang.css.CSSLanguage
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.application.*
import com.intellij.openapi.progress.currentThreadCoroutineScope
import com.intellij.openapi.project.*
import com.intellij.openapi.ui.Messages
import com.intellij.psi.PsiFileFactory
import com.intellij.psi.css.*
import com.intellij.util.concurrency.annotations.RequiresReadLock
import kotlinx.coroutines.*
import org.intellij.lang.annotations.Language

@Language("CSS")
private const val SAMPLE_STYLESHEET = """
body {
  font-family: sans-serif;
}

h1 {
  font-size: 2.5em;
}    
"""

class CssAction : DumbAwareAction() {
    override fun actionPerformed(e: AnActionEvent) {
        val project = e.project ?: return
        currentThreadCoroutineScope().launch {
            val selectorNames = readAction {
                project.createSampleCssPsiFile()?.getSelectorNames() ?: emptyList()
            }
            val selectorsMessage = selectorNames.joinToString(", ")
            withContext(Dispatchers.EDT) {
                Messages.showInfoMessage(selectorsMessage, "CSS Selector List")
            }
        }
    }

    private fun Project.createSampleCssPsiFile(): CssFile? {
        val psiFile = PsiFileFactory
            .getInstance(this)
            .createFileFromText(CSSLanguage.INSTANCE, SAMPLE_STYLESHEET)
        return psiFile as? CssFile
    }

    @RequiresReadLock
    private fun CssFile.getSelectorNames() = stylesheet.rulesetList.rulesets.flatMap {
        it.selectors.toList()
    }.map {
        it.presentableText
    }
}

运行插件

如果 IDE 中存在 intellij.css 插件内容模块,则 CSS 操作可以访问 CSS PSI。如果缺失,则 mincssrel.css 插件内容模块不会加载。

在 IntelliJ IDEA 2025.3 中,此 CSS PSI 功能需通过订阅使用。在 IntelliJ IDEA 2026.1 及更高版本中,CSS PSI 甚至无需订阅即可使用。

为了演示,在主构建脚本 build.gradle.kts 中添加一个专用的 Gradle 运行任务。

$ kotlin
import org.jetbrains.intellij.platform.gradle.IntelliJPlatformType.IntellijIdea
// ...
// Gradle 构建脚本内容省略
// ...
val runIde261 by intellijPlatformTesting.runIde.registering {
    type = IntellijIdea
    version = "2026.1"
}

运行 runIde261 任务,打开 Search Everywhere,然后调用 CSS 操作。这展示了由 intellij.css IDE 插件内容模块和 CSS PSI 支持的可选功能。

总结

插件可以声明一个可选的插件内容模块,以隔离特定于平台的功能,其依赖项精确决定了何时可以加载。插件内容模块的数量没有限制。有关更复杂的示例,请参阅 multi-module-plugin 仓库,其中展示了两个插件内容模块:一个必需和一个可选,两者之间具有 API 依赖关系。

如需本文的视觉总结,请观看 Gradle Setup Powering Multi-module IntelliJ Plugins。若要继续了解远程开发,请阅读后续文章 Make Your Plugin Remote Development-Ready