跳转到内容

IntelliJ IDEA 插件开发全流程指南

IntelliJ IDEA 插件开发是基于 IntelliJ Platform SDK 进行的扩展开发。通过插件,可以扩展 IDE 的功能,实现代码生成、语法高亮、快捷操作、集成外部工具等丰富的自定义功能。

本指南适用范围:适用于 IntelliJ IDEA、PyCharm、WebStorm、Android Studio 等所有基于 IntelliJ Platform 的 JetBrains IDE

技术版本要求用途
IntelliJ Platform2023.2+插件运行平台
JDK17+Java运行环境
Gradle8.0+构建工具
Kotlin1.9+推荐开发语言(也可用Java)

  1. 开发环境搭建
  2. 创建插件项目
  3. 核心概念解析
  4. 扩展点详解
  5. UI开发
  6. 调试与测试
  7. 打包发布
  8. 最佳实践
  9. 实战示例:完整项目
  10. 高级主题
  11. 常见问题
  12. 学习资源
  13. 版本历史

插件开发推荐使用 IntelliJ IDEA Ultimate(旗舰版),因为旗舰版内置了完整的 IntelliJ Platform 插件开发支持。

Terminal window
# macOS (使用 Homebrew Cask)
brew install --cask intellij-idea
# 或下载安装包
# https://www.jetbrains.com/idea/download/

插件开发需要 JDK 17 或更高版本:

Terminal window
# macOS (使用 Homebrew)
brew install openjdk@17
# 验证安装
java -version
# 输出: openjdk version "17.0.x"

在 IntelliJ IDEA 中配置 JDK:

  1. File → Project Structure → SDKs
  2. 添加 JDK 17 路径

Plugin DevKit 是 IntelliJ IDEA 内置的插件开发工具包,包含:

  • 项目模板和向导
  • plugin.xml 编辑器
  • 调试运行配置
  • 构建工具集成

检查是否已启用

  1. File → Settings → Plugins
  2. 搜索 “Plugin DevKit”
  3. 确保已启用(默认内置并启用)

  1. 新建项目File → New → Project

  2. 选择模板:选择 IntelliJ Platform Plugin

    graph TD
        A[New Project] --> B[选择 IntelliJ Platform Plugin]
        B --> C[配置项目信息]
        C --> D[配置插件基础信息]
        D --> E[选择运行平台]
        E --> F[完成创建]
  3. 配置项目信息

    配置项说明示例
    Name项目名称MyFirstPlugin
    Location项目路径~/dev/MyFirstPlugin
    Language开发语言Kotlin / Java
    Build System构建系统Gradle (Kotlin DSL)
    JDKJava版本JDK 17
  4. 配置插件基础信息

    配置项说明示例
    Plugin Name插件名称My First Plugin
    Plugin ID插件唯一IDcom.example.my-first-plugin
    Group IDMaven GroupIdcom.example
    Artifact IDMaven ArtifactIdmy-first-plugin
  5. 选择运行平台:选择目标 IDE(如 IntelliJ IDEA Community)

创建完成后,项目结构如下:

MyFirstPlugin/
├── build.gradle.kts # Gradle 构建配置
├── gradle.properties # Gradle 属性配置
├── settings.gradle.kts # Gradle 设置
├── src/
│ └── main/
│ ├── resources/
│ │ └── META-INF/
│ │ └── plugin.xml # 插件描述文件
│ └── kotlin/
│ └── com.example.myplugin/
│ └── MyPluginClass.kt # 插件代码
├── build/
│ └── kotlin-compile/ # 编译缓存
└── CHANGELOG.md # 更新日志

plugin.xml 是插件的核心配置文件:

<idea-plugin>
<!-- 插件唯一标识 -->
<id>com.example.my-first-plugin</id>
<!-- 插件名称 -->
<name>My First Plugin</name>
<!-- 插件版本 -->
<version>1.0.0</version>
<!-- 供应商信息 -->
<vendor email="support@example.com" url="https://example.com">
Example Company
</vendor>
<!-- 描述信息 -->
<description><![CDATA[
这是我的第一个 IntelliJ IDEA 插件。<br>
提供<strong>示例功能</strong>和<em>实用工具</em>。
]]></description>
<!-- 变更日志 -->
<change-notes><![CDATA[
<h3>1.0.0</h3>
<ul>
<li>初始版本发布</li>
<li>实现基础功能</li>
</ul>
]]></change-notes>
<!-- 依赖和兼容性 -->
<depends>com.intellij.modules.platform</depends>
<depends>com.intellij.modules.lang</depends>
<!-- 扩展点注册 -->
<extensions defaultExtensionNs="com.intellij">
<!-- 在此注册各种扩展 -->
</extensions>
<!-- 动作注册 -->
<actions>
<!-- 在此注册菜单动作 -->
</actions>
</idea-plugin>

graph TB
    subgraph "IntelliJ Platform Core"
        A[Project Model]
        B[Virtual File System]
        C[PSI - Program Structure Interface]
        D[Editor & UI Components]
    end

    subgraph "Plugin Extensions"
        E[Extensions - 声明式扩展]
        F[Actions - 用户交互]
        G[Services - 后台服务]
    end

    A --> C
    B --> C
    C --> D
    E --> A
    F --> D
    G --> A
概念说明使用场景
ProjectIDE 项目模型访问项目配置、模块、依赖
Module项目中的模块管理代码模块、构建配置
VirtualFile虚拟文件系统文件操作、内容读写
PsiFile代码结构接口代码分析、语法树遍历
Editor编辑器组件文本编辑、高亮、注入
Application应用级别全局服务、跨项目共享
Project项目级别项目特定服务

IntelliJ Platform 有三种作用域:

// 1. Application 级别(全局唯一)
val appService = application.getService(MyApplicationService::class.java)
// 2. Project 级别(每个项目一个实例)
val projectService = project.getService(MyProjectService::class.java)
// 3. Module 级别(每个模块一个实例)
val moduleService = module.getService(MyModuleService::class.java)

Actions 是用户可以触发的操作,如菜单项、工具栏按钮、快捷键。

  1. 创建 Action 类
package com.example.myplugin
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.ui.Messages
class MyAction : AnAction() {
override fun actionPerformed(e: AnActionEvent) {
// 获取当前项目
val project = e.project ?: return
// 显示对话框
Messages.showInfoMessage(
project,
"Hello from My Plugin!",
"Greeting"
)
}
override fun update(e: AnActionEvent) {
// 控制动作是否可用
val project = e.project
e.presentation.isEnabledAndVisible = project != null
}
}
  1. 注册 Action(在 plugin.xml 中):
<actions>
<group id="MyPlugin.Group" text="My Plugin" description="My Plugin Actions">
<!-- 添加到主菜单 -->
<add-to-group group-id="ToolsMenu" anchor="last"/>
<!-- 子菜单项 -->
<action
id="MyPlugin.MyAction"
class="com.example.myplugin.MyAction"
text="Say Hello"
description="Shows a greeting message"
icon="META-INF/pluginIcon.png">
<keyboard-shortcut
keymap="$default"
first-keystroke="ctrl shift H"/>
</action>
</group>
</actions>
位置Group ID说明
主菜单-工具ToolsMenu工具菜单
主菜单-编辑EditMenu编辑菜单
主菜单-视图ViewMenu视图菜单
编辑器弹出菜单EditorPopupMenu右键菜单
工具栏MainToolBar主工具栏

Extensions 是声明式的扩展点,用于集成 IDE 核心功能。

package com.example.myplugin.services
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
@Service
class MyApplicationService {
fun doSomething() {
println("Application service doing work")
}
companion object {
fun getInstance(): MyApplicationService {
return service()
}
}
}
package com.example.myplugin.services
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project
@Service(Service.Level.PROJECT)
class MyProjectService(private val project: Project) {
fun getProjectName(): String {
return project.name
}
companion object {
fun getInstance(project: Project): MyProjectService {
return project.service()
}
}
}
<extensions defaultExtensionNs="com.intellij">
<!-- Application 级服务 -->
<applicationService
serviceImplementation="com.example.myplugin.services.MyApplicationService"/>
<!-- Project 级服务 -->
<projectService
serviceImplementation="com.example.myplugin.services.MyProjectService"/>
</extensions>
扩展点用途示例
applicationService全局服务配置管理、连接池
projectService项目服务代码分析、索引
toolWindow工具窗口自定义面板
editorFactoryListener编辑器监听文件打开/关闭
fileType文件类型自定义语言支持
completion.contributor代码补全自动完成
lineMarkerProvider行标记图标提示

工具窗口是 IDE 侧边栏的面板(如 Project、Structure)。

package com.example.myplugin.toolWindow
import com.intellij.openapi.project.Project
import com.intellij.openapi.wm.ToolWindow
import com.intellij.openapi.wm.ToolWindowFactory
import com.intellij.ui.content.ContentFactory
class MyToolWindowFactory : ToolWindowFactory {
override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) {
// 创建 UI 组件
val myToolWindowContent = MyToolWindowContent(project)
// 创建 Content
val contentFactory = ContentFactory.getInstance()
val content = contentFactory.createContent(
myToolWindowContent.getContent(),
"",
false
)
// 添加到工具窗口
toolWindow.contentManager.addContent(content)
}
}
package com.example.myplugin.toolWindow
import com.intellij.openapi.ui.SimpleToolWindowPanel
import com.intellij.ui.components.JBList
import com.intellij.ui.components.JBScrollPane
import javax.swing.JComponent
class MyToolWindowContent(private val project: com.intellij.openapi.project.Project) {
fun getContent(): JComponent {
// 创建面板
val panel = SimpleToolWindowPanel(false, true)
// 创建列表
val list = JBList.create(
listOf("Item 1", "Item 2", "Item 3")
)
val scrollPane = JBScrollPane(list)
// 添加到面板
panel.setContent(scrollPane)
return panel
}
}
<extensions defaultExtensionNs="com.intellij">
<toolWindow
id="My Plugin Tool Window"
factoryClass="com.example.myplugin.toolWindow.MyToolWindowFactory"
anchor="right"
icon="META-INF/pluginIcon.png"/>
</extensions>
package com.example.myplugin.ui
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.DialogWrapper
import com.intellij.ui.components.JBTextField
import com.intellij.uiDesigner.core.GridConstraints
import com.intellij.uiDesigner.core.GridLayoutManager
import javax.swing.JComponent
import javax.swing.JLabel
import javax.swing.JPanel
class MyDialog(project: Project) : DialogWrapper(project) {
private val textField = JBTextField()
init {
title = "My Plugin Dialog"
init()
}
override fun createCenterPanel(): JComponent {
val panel = JPanel(GridLayoutManager(2, 2))
// 添加标签
val label = JLabel("Enter something:")
panel.add(
label,
GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null)
)
// 添加文本框
panel.add(
textField,
GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null)
)
return panel
}
fun getInputText(): String = textField.text
}
package com.example.myplugin.notifications
import com.intellij.notification.Notification
import com.intellij.notification.NotificationGroupManager
import com.intellij.notification.NotificationType
import com.intellij.openapi.project.Project
object MyNotifications {
fun showInfo(project: Project, message: String) {
val notification = NotificationGroupManager.getInstance()
.getNotificationGroup("MyPlugin.NotificationGroup")
.createNotification(
"My Plugin",
message,
NotificationType.INFORMATION
)
notification.notify(project)
}
fun showError(project: Project, message: String) {
val notification = NotificationGroupManager.getInstance()
.getNotificationGroup("MyPlugin.NotificationGroup")
.createNotification(
"My Plugin Error",
message,
NotificationType.ERROR
)
notification.notify(project)
}
fun showWarning(project: Project, message: String) {
val notification = NotificationGroupManager.getInstance()
.getNotificationGroup("MyPlugin.NotificationGroup")
.createNotification(
"My Plugin Warning",
message,
NotificationType.WARNING
)
notification.notify(project)
}
}
<extensions defaultExtensionNs="com.intellij">
<notificationGroup
id="MyPlugin.NotificationGroup"
displayType="BALLOON"
key="notification.group.name"/>
</extensions>

项目创建后会自动生成运行配置 Run Plugin

运行配置说明

配置项说明
VM 参数IDE 启动参数
程序参数IDE 命令行参数
工作目录IDE 工作目录
使用类路径包含插件和 IDE 核心类
  1. 启动调试:点击 Debug 按钮(或按 Ctrl+D

  2. 设置断点:在代码中点击行号设置断点

  3. 触发断点:在测试 IDE 中执行相关操作

package com.example.myplugin
import com.intellij.testFramework.TestDataPath
import com.intellij.testFramework.fixtures.BasePlatformTestCase
import org.junit.Test
@TestDataPath("\$CONTENT_ROOT/src/test/testData")
class MyPluginTest : BasePlatformTestCase() {
override fun getTestDataPath(): String {
return "src/test/testData"
}
@Test
fun testPluginFunctionality() {
// 测试代码
val myFixture = myFixture
myFixture.configureByText(
"test.txt",
"Hello World"
)
// 执行测试逻辑
// ...
}
}
build.gradle.kts
dependencies {
testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")
testImplementation("org.jetbrains.kotlin:kotlin-test:1.9.0")
}
tasks.test {
useJUnitPlatform()
}

Terminal window
# 方式一:使用 Gradle 任务
./gradlew buildPlugin
# 方式二:使用 IntelliJ IDEA 界面
# Gradle → Tasks → intellij → buildPlugin

构建成功后,插件文件位于:

build/distributions/[PluginName]-[Version].zip
  1. 本地测试安装

    • File → Settings → Plugins → Gear Icon → Install Plugin from Disk
    • 选择构建的 .zip 文件
    • 重启 IDE
  2. 使用验证工具

Terminal window
# 运行插件验证
./gradlew runPluginVerifier
  1. 注册账号:访问 JetBrains Account
  2. 创建开发者资料:完善开发者信息
  1. 登录插件仓库

  2. 上传插件

    • 点击 Upload plugin
    • 填写插件信息:
      • 插件名称
      • 描述(支持 HTML)
      • 版本说明
      • 变更日志
      • 类别
      • 标签
    • 上传 .zip 文件
    • 选择兼容的 IDE 版本
  3. 等待审核

    • 通常 1-3 个工作日
    • 审核通过后自动发布

发布前确保 plugin.xml 包含以下字段:

<idea-plugin>
<!-- 必需字段 -->
<name>Plugin Name</name>
<version>1.0.0</version>
<vendor url="https://example.com">Company</vendor>
<description><![CDATA[详细描述...]]></description>
<change-notes><![CDATA[变更说明...]]></change-notes>
<!-- 兼容性说明 -->
<idea-version since-build="232" until-build="241.*"/>
</idea-plugin>
版本代码对应版本
232.*IntelliJ IDEA 2023.2
233.*IntelliJ IDEA 2023.3
241.*IntelliJ IDEA 2024.1
<!-- 支持多个版本 -->
<idea-version since-build="232" until-build="241.*"/>

// ❌ 错误:在主线程执行耗时操作
override fun actionPerformed(e: AnActionEvent) {
heavyComputation() // 阻塞 UI
}
// ✅ 正确:使用后台任务
override fun actionPerformed(e: AnActionEvent) {
val project = e.project ?: return
Task.Backgroundable(project, "Processing") {
override fun run(indicator: ProgressIndicator) {
heavyComputation()
}
}.queue()
}
// 使用 readAction 和 writeAction 访问 PSI
ApplicationManager.getApplication().runReadAction {
// 只读操作
val psiFile = PsiManager.getInstance(project).findFile(virtualFile)
}
ApplicationManager.getApplication().runWriteAction {
// 写操作
psiFile?.add(myElement)
}
// 使用异常处理避免插件崩溃
try {
val result = riskyOperation()
} catch (e: Exception) {
logger<MyPlugin>().error("Operation failed", e)
MyNotifications.showError(project, e.message ?: "Unknown error")
}
@State(
name = "MyPluginSettings",
storages = [Storage("MyPluginSettings.xml")]
)
class MyPluginSettings : PersistentStateComponent<MyPluginSettings.State> {
data class State(
var myProperty: String = "default"
)
private var state = State()
override fun getState(): State = state
override fun loadState(state: State) {
this.state = state
}
companion object {
fun getInstance(): MyPluginSettings {
return service()
}
}
}

创建一个简单的插件,实现以下功能:

  • 在工具菜单中添加一个 “统计代码行数” 菜单项
  • 点击后显示当前项目代码行数统计
  • 在工具窗口中显示详细统计信息
package com.example.linecounter.service
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project
import com.intellij.psi.JavaRecursiveElementVisitor
import com.intellij.psi.PsiJavaFile
import com.intellij.psi.PsiManager
@Service(Service.Level.PROJECT)
class LineCounterService(private val project: Project) {
data class FileStats(
val path: String,
val lines: Int,
val codeLines: Int
)
data class ProjectStats(
val totalFiles: Int,
val totalLines: Int,
val codeLines: Int,
val fileStats: List<FileStats>
)
fun countLines(): ProjectStats {
val psiManager = PsiManager.getInstance(project)
val rootDir = project.baseDir ?: return emptyStats()
val fileStats = mutableListOf<FileStats>()
var totalLines = 0
var codeLines = 0
VfsUtilCore.iterateChildrenRecursively(rootDir, null) { file ->
if (!file.isDirectory && file.extension == "java") {
val virtualFile = file
val psiFile = psiManager.findFile(virtualFile) as? PsiJavaFile
if (psiFile != null) {
val text = psiFile.text
val lines = text.lines().size
val code = psiFile.children
.filterIsInstance<com.intellij.psi.PsiStatement>()
.sumOf { it.text.lines().size }
totalLines += lines
codeLines += code
fileStats.add(FileStats(
path = virtualFile.path,
lines = lines,
codeLines = code
))
}
}
return true
}
return ProjectStats(
totalFiles = fileStats.size,
totalLines = totalLines,
codeLines = codeLines,
fileStats = fileStats.sortedByDescending { it.codeLines }
)
}
private fun emptyStats() = ProjectStats(0, 0, 0, emptyList())
companion object {
fun getInstance(project: Project): LineCounterService {
return project.service()
}
}
}
package com.example.linecounter.action
import com.example.linecounter.service.LineCounterService
import com.intellij.notification.NotificationGroupManager
import com.intellij.notification.NotificationType
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.Task
import com.intellij.openapi.project.Project
class CountLinesAction : AnAction() {
override fun actionPerformed(e: AnActionEvent) {
val project = e.project ?: return
object : Task.Backgroundable(project, "Counting Lines", true) {
private var stats: LineCounterService.ProjectStats? = null
override fun run(indicator: ProgressIndicator) {
stats = LineCounterService.getInstance(project).countLines()
}
override fun onSuccess() {
showResults(project, stats)
}
}.queue()
}
private fun showResults(project: Project, stats: LineCounterService.ProjectStats?) {
stats ?: return
val message = """
<b>代码统计完成</b><br>
文件总数: ${stats.totalFiles}<br>
总行数: ${stats.totalLines}<br>
代码行数: ${stats.codeLines}
""".trimIndent()
NotificationGroupManager.getInstance()
.getNotificationGroup("LineCounter.Notification")
.createNotification(
"代码统计",
message,
NotificationType.INFORMATION
)
.notify(project)
// 打开工具窗口
val toolWindow = com.intellij.openapi.wm.ToolWindowManager.getInstance(project)
.getToolWindow("Line Counter")
toolWindow?.show()
}
override fun update(e: AnActionEvent) {
e.presentation.isEnabledAndVisible = e.project != null
}
}
package com.example.linecounter.toolWindow
import com.example.linecounter.service.LineCounterService
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.SimpleToolWindowPanel
import com.intellij.openapi.wm.ToolWindow
import com.intellij.openapi.wm.ToolWindowFactory
import com.intellij.ui.components.JBList
import com.intellij.ui.components.JBScrollPane
import com.intellij.ui.content.ContentFactory
import javax.swing.JComponent
import javax.swing.JLabel
import javax.swing.JList
import javax.swing.ListSelectionModel
class LineCounterToolWindowFactory : ToolWindowFactory {
override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) {
val content = LineCounterToolWindowContent(project)
val factory = ContentFactory.getInstance()
toolWindow.contentManager.addContent(
factory.createContent(
content.getContent(),
"",
false
)
)
}
}
class LineCounterToolWindowContent(private val project: Project) {
fun getContent(): JComponent {
val panel = SimpleToolWindowPanel(false, true)
val stats = LineCounterService.getInstance(project).countLines()
val listModel = javax.swing.DefaultListModel<String>()
listModel.addElement("=== 代码统计结果 ===")
listModel.addElement("文件总数: ${stats.totalFiles}")
listModel.addElement("总行数: ${stats.totalLines}")
listModel.addElement("代码行数: ${stats.codeLines}")
listModel.addElement("")
listModel.addElement("=== Top 10 文件 ===")
stats.fileStats.take(10).forEach { file ->
listModel.addElement("${file.codeLines} 行 - ${file.path.substringAfterLast("/")}")
}
val list = JBList(listModel)
list.selectionMode = ListSelectionModel.SINGLE_SELECTION
val scrollPane = JBScrollPane(list)
panel.setContent(scrollPane)
// 刷新按钮
val refreshAction = object : com.intellij.openapi.actionSystem.AnAction(
"刷新", "刷新统计", null
) {
override fun actionPerformed(e: com.intellij.openapi.actionSystem.AnActionEvent) {
// 重新加载统计
panel.setContent(JLabel("刷新中..."))
}
}
panel.toolbar.addAction(refreshAction)
return panel
}
}
<idea-plugin>
<id>com.example.line-counter</id>
<name>Line Counter</name>
<version>1.0.0</version>
<vendor email="support@example.com">Example</vendor>
<description><![CDATA[
统计 Java 项目代码行数的插件
]]></description>
<change-notes><![CDATA[
<h3>1.0.0</h3>
<ul>
<li>初始版本</li>
<li>支持代码行数统计</li>
</ul>
]]></change-notes>
<depends>com.intellij.modules.platform</depends>
<depends>com.intellij.modules.java</depends>
<extensions defaultExtensionNs="com.intellij">
<!-- 注册服务 -->
<projectService
serviceImplementation="com.example.linecounter.service.LineCounterService"/>
<!-- 注册工具窗口 -->
<toolWindow
id="Line Counter"
factoryClass="com.example.linecounter.toolWindow.LineCounterToolWindowFactory"
anchor="right"/>
<!-- 注册通知组 -->
<notificationGroup
id="LineCounter.Notification"
displayType="BALLOON"/>
</extensions>
<actions>
<action
id="LineCounter.Count"
class="com.example.linecounter.action.CountLinesAction"
text="统计代码行数"
description="统计项目代码行数"
icon="META-INF/icon.png">
<add-to-group group-id="ToolsMenu" anchor="last"/>
<keyboard-shortcut keymap="$default" first-keystroke="ctrl shift L"/>
</action>
</actions>
</idea-plugin>
plugins {
id("java")
id("org.jetbrains.kotlin.jvm") version "1.9.0"
id("org.jetbrains.intellij") version "1.15.0"
}
group = "com.example"
version = "1.0.0"
repositories {
mavenCentral()
}
intellij {
version.set("2023.2")
type.set("IC") // IC = Community
plugins.set(listOf(/* 插件依赖 */))
}
dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib")
}
tasks {
withType<JavaCompile> {
sourceCompatibility = "17"
targetCompatibility = "17"
}
withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
kotlinOptions.jvmTarget = "17"
}
patchPluginXml {
sinceBuild.set("232")
untilBuild.set("241.*")
}
signPlugin {
certificateChain.set(System.getenv("CERTIFICATE_CHAIN"))
privateKey.set(System.getenv("PRIVATE_KEY"))
password.set(System.getenv("PRIVATE_KEY_PASSWORD"))
}
publishPlugin {
token.set(System.getenv("PUBLISH_TOKEN"))
}
}

package com.example.myplugin.completion
import com.intellij.codeInsight.completion.*
import com.intellij.codeInsight.lookup.LookupElementBuilder
import com.intellij.patterns.PlatformPatterns
import com.intellij.psi.PsiElement
import com.intellij.util.ProcessingContext
class MyCompletionContributor : CompletionContributor() {
init {
extend(
CompletionType.BASIC,
PlatformPatterns.psiElement(),
object : CompletionProvider<CompletionParameters>() {
override fun addCompletions(
parameters: CompletionParameters,
context: ProcessingContext,
result: CompletionResultSet
) {
// 添加自定义补全项
result.addElement(
LookupElementBuilder.create("myCustomSuggestion")
.withIcon(com.intellij.icons.AllIcons.Nodes.Method)
.withTypeText("Custom")
)
}
}
)
}
}
// 创建 PSI 元素
val elementFactory = JavaPsiFacade.getElementFactory(project)
// 创建新方法
val newMethod = elementFactory.createMethodFromText(
"""
public void myNewMethod() {
System.out.println("Hello");
}
""".trimIndent(),
psiClass
)
// 添加到类中
psiClass.add(newMethod)
// 使用 WriteAction
WriteCommandAction.runWriteCommandAction(project) {
psiClass.add(newMethod)
}
plugins {
id("java")
id("org.jetbrains.kotlin.jvm") version "1.9.20"
id("org.jetbrains.intellij") version "1.17.3"
id("org.jetbrains.changelog") version "2.2.0"
}
group = "com.example"
version = "1.0.0"
repositories {
mavenCentral()
}
// IntelliJ Platform 配置
intellij {
pluginName.set("My Plugin")
version.set("2023.2") // 基准版本
type.set("IC") // IC=Community, IU=Ultimate
// 依赖的插件
plugins.set(listOf(
"org.jetbrains.plugins.gradle",
"org.intellij.plugins.markdown"
))
// 沙箱目录(用于测试运行)
sandboxDir.set("${project.buildDir}/idea-sandbox")
// 插件依赖
pluginVerification {
ides.set(
listOf(
"2023.2.5",
"2023.3.5",
"2024.1.5"
)
)
}
}
dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib:1.9.20")
// 测试依赖
testImplementation("org.junit.jupiter:junit-jupiter:5.10.1")
testImplementation("org.jetbrains.kotlin:kotlin-test:1.9.20")
}
tasks {
// Java 编译配置
withType<JavaCompile> {
sourceCompatibility = "17"
targetCompatibility = "17"
options.encoding = "UTF-8"
}
// Kotlin 编译配置
withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
kotlinOptions.jvmTarget = "17"
kotlinOptions.freeCompilerArgs = listOf("-Xjvm-default=all")
}
// 测试配置
test {
useJUnitPlatform()
systemProperty("idea.tests.use.local.toolchain", "false")
}
// 插件 XML 补丁
patchPluginXml {
sinceBuild.set("232")
untilBuild.set("241.*")
// 更新 changelog
changelog.set(project.file("CHANGELOG.md").readText())
}
// 构建插件
buildSearchableOptions {
enabled = false
}
// 签名插件(可选)
signPlugin {
certificateChain.set(System.getenv("CERTIFICATE_CHAIN"))
privateKey.set(System.getenv("PRIVATE_KEY"))
password.set(System.getenv("PRIVATE_KEY_PASSWORD"))
}
// 发布到 JetBrains Marketplace
publishPlugin {
token.set(System.getenv("PUBLISH_TOKEN"))
channels.set(listOf("stable"))
}
// 运行插件验证
runPluginVerifier {
ideVersions.set(listOf("2023.2.5", "2023.3.5", "2024.1.5"))
}
}
object PlatformVersion {
fun is2024OrNewer(): Boolean {
val build = ApplicationInfo.getInstance().build
return build.baselineVersion >= 241
}
fun <T> executeCompat(
newAction: () -> T,
oldAction: () -> T
): T {
return if (is2024OrNewer()) newAction() else oldAction()
}
}
// 使用示例
val result = PlatformVersion.executeCompat(
newAction = { /* 2024+ API 调用 */ },
oldAction = { /* 旧版 API 调用 */ }
)

GitHub Actions 示例

name: Build and Test
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Build with Gradle
run: ./gradlew buildPlugin
- name: Run tests
run: ./gradlew test
- name: Verify plugin
run: ./gradlew runPluginVerifier
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: plugin-artifact
path: build/distributions/*.zip
publish:
needs: build
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Publish plugin
run: ./gradlew publishPlugin
env:
PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }}

class MyFileListener : VirtualFileListener {
override fun contentsChanged(event: VirtualFileEvent) {
println("File changed: ${event.file.name}")
}
override fun fileCreated(event: VirtualFileEvent) {
println("File created: ${event.file.name}")
}
}
// 注册监听器
VirtualFileManager.getInstance().addVirtualFileListener(MyFileListener())
val editor = FileEditorManager.getInstance(project).selectedTextEditor
val virtualFile = FileDocumentManager.getInstance().getFile(editor?.document)
val psiFile = PsiManager.getInstance(project).findFile(virtualFile)
// 遍历 PSI 元素
object : PsiElementVisitor() {
override fun visitElement(element: PsiElement) {
super.visitElement(element)
when (element) {
is PsiMethod -> println("Found method: ${element.name}")
is PsiClass -> println("Found class: ${element.name}")
}
element.acceptChildren(this)
}
}.let { psiFile.accept(it) }

build.gradle.kts 中配置:

intellij {
version.set("2023.2")
type.set("IC") // IC=Community, IU=Ultimate
plugins.set(listOf("org.jetbrains.plugins.gradle"))
}
tasks.patchPluginXml {
sinceBuild.set("232")
untilBuild.set("241.*")
}

Q5: 如何处理异步操作和进度提示?

Section titled “Q5: 如何处理异步操作和进度提示?”
// 使用 Task.Backgroundable
object : Task.Backgroundable(project, "Processing", true) {
override fun run(indicator: ProgressIndicator) {
indicator.text = "正在处理..."
indicator.fraction = 0.0
// 执行耗时操作
heavyOperation()
indicator.fraction = 1.0
}
override fun onSuccess() {
// 完成后在 UI 线程执行
showResults()
}
override fun onThrowable(error: Throwable) {
// 错误处理
showError(error.message)
}
}.queue()
  1. 查看日志Help → Show Log in Explorer
  2. 启用插件调试:在 idea.properties 中添加:
    idea.cycle.buffer.size=disabled
    idea.debug.mode=true
  3. 使用 Plugin Verifier
    Terminal window
    ./gradlew runPluginVerifier
<!-- 依赖其他插件 -->
<depends>com.intellij.java</depends>
<depends>org.jetbrains.plugins.gradle</depends>
<depends optional="true" config-file="my-plugin-with-gradle.xml">
org.jetbrains.plugins.gradle
</depends>

Q8: 如何创建自定义检查(Inspection)?

Section titled “Q8: 如何创建自定义检查(Inspection)?”
class MyInspection : AbstractBaseJavaLocalInspectionTool() {
override fun buildVisitor(
holder: ProblemsHolder,
isOnTheFly: Boolean
): PsiElementVisitor {
return object : JavaElementVisitor() {
override fun visitMethod(method: PsiMethod) {
super.visitMethod(method)
if (method.name.length > 50) {
holder.registerProblem(
method.nameIdentifier,
"方法名过长",
ProblemHighlightType.WARNING
)
}
}
}
}
}

Q9: 如何处理插件的多语言支持?

Section titled “Q9: 如何处理插件的多语言支持?”
// 创建 messages.properties
display.name=My Plugin
action.description=Perform action
// 创建 messages_zh_CN.properties
display.name=我的插件
action.description=执行操作
// 在代码中使用
val message = message("display.name")

Q10: 如何测试插件在不同 IDE 中的兼容性?

Section titled “Q10: 如何测试插件在不同 IDE 中的兼容性?”

使用 runPluginVerifier 任务:

Terminal window
# 验证特定版本
./gradlew runPluginVerifier -PverifierReleases="2023.2,2023.3,2024.1"
# 验证所有版本
./gradlew checkPluginVerifier

资源链接说明
IntelliJ Platform SDKhttps://plugins.jetbrains.com/docs/intellij/welcome.html官方开发文档
Plugin Development Guidehttps://plugins.jetbrains.com/docs/intellij/getting-started.html快速入门指南
API 文档https://javadoc.io/org.jetbrains.intellij/intellij-platform-core/latest核心 API 参考
GitHub 示例https://github.com/JetBrains/intellij-platform-plugin-template官方项目模板
Plugin DevKithttps://plugins.jetbrains.com/docs/intellij/plugin-devkit.html开发工具说明
资源链接说明
IntelliJ Platform Devhttps://intellij-support.jetbrains.com/hc/en-us/community/topics官方论坛
Stack Overflowhttps://stackoverflow.com/questions/tagged/intellij-idea+plugin问题解答
Reddit 社区https://www.reddit.com/r/intellijplugins/插件讨论
GitHub Awesome Listhttps://github.com/jychen/awesome-intellij-plugins优秀插件合集
插件GitHub学习重点
.env files supporthttps://github.com/ewsgit/env-files-support文件类型支持
String Manipulationhttps://github.com/krasa/StringManipulationAction 和快捷键
AceJumphttps://github.com/acejump/AceJump编辑器集成
Rainbow Bracketshttps://github.com/izhangzhihao/intellij-rainbow-brackets语法高亮
Key Promoter Xhttps://github.com/halirutan/KeyPromoterX事件监听
graph LR
    A[基础教程] --> B[实战项目]
    B --> C[UI 开发]
    C --> D[PSI 操作]
    D --> E[自定义语言]
    E --> F[高级特性]
  1. 入门阶段:完成官方 Hello World 示例
  2. 进阶阶段:实现一个完整的实用插件
  3. 高级阶段:学习 PSI、代码分析、自定义语言
  4. 专家阶段:贡献开源插件、参与平台开发

文档持续更新中,如有问题或建议欢迎反馈!