ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

IntelliJ插件开发上卷指南:从Gradle工程到Action与Tool Window实战

2026/10/8 15:08:33 拓冰建站 浏览量
IntelliJ插件开发上卷指南:从Gradle工程到Action与Tool Window实战 简介这是一份面向 Intellij IDEA 插件开发者的系统手册上册基于 JetBrains Runtime 17.0.9适用于 IDEA 2023 及以上版本兼容 2024。手册从零讲解插件开发基础梳理了 IDE 插件类型、开发环境要求、开发流程与官方参考网站等预备知识随后通过“开发第一个插件”章节逐步演示工程创建、插件配置、测试调试等完整链路并深入介绍图形化插件开发可用于框架集成、代码统计、效率工具等 UI 类插件的编写。资源仅包含 1 个 PDF 文件大小 15.82MB目录层级分明方便按需查阅对应章节。上册定位为入门到进阶适合具备 Java 基础、希望扩展 IDE 功能的开发者若结合下册语言类插件与附录工具清单可继续探索代码自动完成、依赖管理、代码检查等高级收费插件方向。目前已有 383 人学习下载全书基于官方指导与项目实践整理可作为日常开发的案头参考。1. 做 IntelliJ 插件的第一个 30 分钟这份手册解决了什么很多人开始做 Idea 插件开发第一步不是写代码而是卡在环境上。SDK 加载不出来、Gradle 任务起不来、plugin.xml 满屏报红任一条都能耗掉你整个下午。这份《Intellij Platform PlugIn插件开发手册(上)》把这段最劝退的路提前趟平了从 IntelliJ Platform 工程的初始化讲起把 ActionSystem、Tool Window、PSI 这些必踩的地基按“能跑通”的顺序串了起来。它适合两类人一类是给团队做内部工具链、想少走弯路快速交出第一个可用插件的开发者另一类是写过几个插件但底子不牢、想系统理一遍 IDE 扩展机制的熟手。上卷一般不涉及打包签名和上架那部分通常留在下卷但能把上卷吃透你的调试循环就已经比大多数半路出家的人快一截。2. 插件工程怎么立起来Gradle 构建、DevKit 与版本选型2.1 为什么是 Gradle IntelliJ Plugin 而不是 DevKit早期插件开发的主流方式是用 IDE 自带的 Plugin DevKit在 Project Structure 里手动添加 IntelliJ Platform SDK把安装目录里的 lib 整个挂进来。这套方式的痛点是版本敏感切换 IDE 版本要重新配 SDK依赖下载不完整或者缓存损坏IDE 会直接报基础组件缺失比如提示需要安装 J2SE plugin 版本之类的错误和业务代码一点关系都没有纯粹是环境问题。现在官方和社区的主流做法已经统一到 Gradle IntelliJ Gradle Plugin。SDK 按intellij.version动态拉取不手工维护构建、打包、验证都有现成任务。用 Gradle 方案有四个实际收益依赖版本能写进构建脚本换机器不慌runIde、buildPlugin、verifyPlugin覆盖开发到验证全流程patchPluginXml能动态改写 plugin.xml 里的兼容版本区间本地跑一次clean build直接产出可分发的 zip。这些正是做 idea 插件开发步骤里最容易出错、也最该模板化的部分。选型时注意版本绑定关系IntelliJ Gradle Plugin 的版本、IDE 版本、JDK 版本三者互相约束。最省事的记忆方式是用“和你的主力 IDE 发布年份接近”的插件版本。比如 IDE 用 2023.2、本地 JDK 是 17那么org.jetbrains.intellij用 1.16.x 或 1.17.x 都能正常跑通。版本对不上最常见的症状是 Gradle 同步报“Could not resolve dependencies”或runIde启动后插件列表空白。2.2 最小工程文件build.gradle 与 plugin.xml新建插件项目我一般直接在一个空目录里手写build.gradle、settings.gradle、gradle-wrapper.properties再用 IntelliJ 的 Open 打开这个目录让 IDE 以 Gradle 工程识别。与其在 UI 向导里一步步点不如从官方项目模板抄一个最小结构后面改起来更可控。最小可跑的build.gradle长这样plugins { id java id org.jetbrains.intellij version 1.16.0 } group com.example version 1.0.0 repositories { mavenCentral() } java { sourceCompatibility 17 targetCompatibility 17 } intellij { version 2023.2.5 type IC plugins [com.intellij.java] } patchPluginXml { sinceBuild 232 untilBuild 242.* }逻辑说明外层plugins块是构建工具插件id java让工程能编译 Java 源码org.jetbrains.intellij负责把 IntelliJ Platform SDK 挂载到编译和运行环境。java.sourceCompatibility设为 17 是 2023.x 系列的安全选择因为 IDE 自带的 JBR 就是 17 起的。intellij.version决定当前工程面向哪个 IDE 版本构建type两个值IC是社区版IU是旗舰版旗舰版才有的类在社区版 SDK 里编译不过。plugins数组写的是 IDE 内置插件 ID比如要写 Java 语言相关功能就依赖com.intellij.java做前端方向就依赖com.intellij.javascript。patchPluginXml里的sinceBuild、untilBuild是插件兼容区间。232 对应 2023.2 的内部 build 号242 对应 2024.2。区间写得过窄会把插件拦在部分 IDE 之外写得过宽又可能在 IDE 升级后触发未知的 API 不兼容所以每次调完版本都要跑一次verifyPlugin确认边界。再往下是src/main/resources/META-INF/plugin.xml这是插件的身份证idea-plugin idcom.example.demo/id nameDemo Plugin/name vendorExample/vendor description一个演示插件带你跑通开发闭环。/description dependscom.intellij.modules.platform/depends extensions defaultExtensionNscom.intellij !-- 扩展点写在这里 -- /extensions actions !-- Action 注册写在这里 -- /actions /idea-plugin说明id在插件市场里是唯一键一旦发布不要改depends只写com.intellij.modules.platform是最小依赖代表只要编辑器核心不带 Java 或前端语言支持要用 Java PSI 就得额外依赖com.intellij.java。extensions和actions分别挂扩展点和行为SDK 按声明顺序解析这个顺序问题在避坑章节还要展开。2.3 用 runIde 跑通调试沙箱配置写完后第一件事不是写功能代码而是确认环境能起来./gradlew clean build ./gradlew runIde第一条命令做全量编译和打包产物在build/distributions下。第二条命令启动一个独立的 IDE 沙箱实例这个实例的配置目录、索引目录、插件目录全部隔离在build/idea-sandbox下不会污染你日常开发用的 IDEA。沙箱启动后随便打开一个项目到 Settings 里 Plugins 查看 Installed应该能看到 Demo Plugin 已经在列表里。如果找不到直接跳去第 4 章排查第一条。runIde还支持透传 JVM 和 IDE 属性常见写法是./gradlew runIde -Didea.config.pathbuild/config -Didea.system.pathbuild/systemidea.config.path和idea.system.path把沙箱的配置和索引目录单独划分出来。不写也能跑通但我反复调试时一定带上因为默认情况下沙箱重建索引会吃掉好几分钟这时间浪费得毫无价值。3. 把功能挂进 IDEAction、Tool Window 与事件扩展3.1 AnAction菜单、工具栏、右键菜单的三连注册IntelliJ Platform 的交互入口统一抽象为 AnAction。你要做的只有两步写一个继承 AnAction 的类在 plugin.xml 里把它注册到某个 UI 容器。先看类package com.example.demo; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages; public class HelloAction extends AnAction { Override public void actionPerformed(AnActionEvent e) { Project project e.getProject(); Messages.showMessageDialog(project, Hello from plugin, Demo, Messages.getInformationIcon()); } }逻辑说明actionPerformed是点击菜单或按下快捷键时的回调AnActionEvent携带当前上下文包括 project、文件和编辑器实例。这里通过e.getProject()拿到工程对象用 Messages 弹了个对话框这是验证 Action 是否被 IDE 正确加载的最快方式。注册到右键菜单actions action idcom.example.demo.HelloAction classcom.example.demo.HelloAction textSay Hello description弹出一个问候框 add-to-group group-idEditorPopupMenu anchorlast/ /action /actions参数说明class必须能反射到刚才写的类id在整个 IDE 范围内唯一。add-to-group决定挂载位置EditorPopupMenu是编辑器右键菜单ProjectViewPopupMenu是项目树右键MainToolBar是工具栏MainMenu是主菜单栏。anchor支持first、last、before、after用before或after时还要加relative-to-action指定锚点 Action。注册完右键编辑器菜单底部就应该出现 Say Hello。如果没出现先按 CtrlShiftA 打开 Actions 搜索框输入类名里的关键词如果能在列表里搜到说明 Action 已加载只是菜单位置不对搜不到就是 plugin.xml 解析失败。3.2 Tool Window创建一个能刷新的自定义面板Tool Window 是插件和 IDE 左侧/右侧/底部侧边栏的入口适合放树、表格、日志流这类常驻内容。实现一个 Tool Window 只需要实现ToolWindowFactorypackage com.example.demo; import com.intellij.openapi.project.Project; import com.intellij.openapi.wm.ToolWindow; import com.intellij.openapi.wm.ToolWindowFactory; import com.intellij.ui.content.Content; import com.intellij.ui.content.ContentFactory; import javax.swing.*; public class DemoToolWindowFactory implements ToolWindowFactory { Override public void createToolWindowContent(Project project, ToolWindow toolWindow) { JPanel panel new JPanel(); panel.add(new JLabel(这是插件面板)); Content content ContentFactory.getInstance() .createContent(panel, Main, false); toolWindow.getContentManager().addContent(content); } }注意ContentFactory的取法2021.3 之前是ContentFactory.SERVICE.getInstance()之后统一为ContentFactory.getInstance()。如果你抄到老代码在较新的 SDK 下会直接编译失败这是插件开发里常见的代差坑。plugin.xml 里对应的注册extensions defaultExtensionNscom.intellij toolWindow idDemoPane anchorright secondaryfalse factoryClasscom.example.demo.DemoToolWindowFactory/ /extensions参数说明anchor取值left、right、bottomsecondary为 true 时面板会和同方向已有面板合并到 tab 标签页里。factoryClass指向刚才实现的工厂类。Tool Window 最大的特点是内容面板是普通 Swing/JComponent你可以往里塞 JTable、JTree、JEditorPane也可以挂按钮和输入框它能承载的 UI 复杂度比 Action 弹窗高一个量级。3.3 扩展点与 Listener插件被 IDE 事件驱动的机制Action 是用户主动发起的Tool Window 是常驻 UI那“项目打开时自动执行”“保存文件时触发检查”这类场景怎么实现答案是扩展点与 Listener。扩展点本质上是在 plugin.xml 的extensions里声明的自定义实现SDK 在特定时机实例化并调用。常见的扩展点包括applicationService、projectService、toolWindow、editorNotificationProvider、lineMarkerProvider。监听器则面向事件驱动。以最常见的“项目打开时初始化”为例package com.example.demo; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectManagerListener; import org.jetbrains.annotations.NotNull; public class DemoProjectListener implements ProjectManagerListener { Override public void projectOpened(NotNull Project project) { System.out.println(插件感知到项目打开 project.getName()); } }plugin.xml 注册extensions defaultExtensionNscom.intellij projectManagerListener implementationcom.example.demo.DemoProjectListener/ /extensions说明projectManagerListener扩展点会让 SDK 在项目打开时回调projectOpened。这里的实现类生命周期由平台管理不要在监听器里持有重型资源因为项目打开和关闭是高频操作。更精细的监听比如文件保存、编辑器光标移动都有各自对应的扩展点核心套路一致实现接口 → 在 plugin.xml 里注册 → 在回调里消费事件。把这三步走熟插件才真正算是“跟 IDE 长在了一起”而不是一个孤立的弹窗。4. 插件开发避坑与排查从编译失败到运行翻车的 5 个高频问题4.1 现象runIde 启动后插件列表里找不到自己的插件原因最常见的是META-INF/plugin.xml放错了目录Gradle 只认src/main/resources/META-INF/plugin.xml放成resources/plugin.xml或以其他路径存在都不会被当成插件描述文件。其次是intellij.plugins里声明了当前 IDE 版本不存在的依赖插件 ID导致 SDK 加载阶段就中断整个插件被静默丢弃。解决先看build/idea-sandbox/plugins目录确认插件 jar 有没有被复制进去没有 jar 就检查build.gradle的sourceSets和资源目录结构。有 jar 但列表里不显示打开沙箱里的idea.log在build/idea-sandbox/system/log下搜插件的 id插件描述解析失败时日志会直接给异常栈。4.2 现象plugin.xml 里写了 action右键菜单却不出现原因这个坑通常不在 XML而在类加载。class属性写成了内部类或包名错误反射实例化失败后 IDE 只会在日志里记一笔另一个高频原因是把add-to-group写进了extensions而不是actionsSDK 对这两个标签的解析路径完全不同写错位置不会报错只是静默忽略。还有一种场景EditorPopupMenu 只在编辑器获得焦点时出现如果你在项目树上点右键看到的自然是 ProjectViewPopupMenu两者的菜单内容是两套。解决用 CtrlShiftA 搜索 Action 类名。能搜到就说明 Action 已注册问题在菜单分组检查 group-id 和 anchor搜不到就回到 plugin.xml 确认注册位置并检查类的全限定名是否和package一致。再不行在actionPerformed里加个System.out.println断点打上确认 IDE 是否真的实例化了这个类。4.3 现象编译报 package com.intellij.openapi.actionSystem does not exist原因SDK 没有挂到编译 classpath。常见于org.jetbrains.intellij插件版本太老、intellij.version解析失败或者本机 Gradle 处于离线模式依赖没有提前缓存在~/.gradle/caches。也见过有人把type设置成IU但网络刚好拉不下来旗舰版 SDKGradle 静默降级失败后给出成片 cannot find symbol。解决先跑./gradlew clean build --refresh-dependencies排除缓存问题确认plugins块的版本号能访问 Maven Central 或 JetBrains Plugin Repository最后把intellij.version换成你本机已安装的 IDE 版本号比如 2023.2.5通常能绕开下载障碍。这类问题一旦解决后续编译基本不会再回到这个坑。4.4 现象沙箱 IDE 直接崩溃或者启动后秒退原因多数是 JDK 版本不匹配。IntelliJ Platform 2021.1 之后要求 JDK 11 以上2022.3 之后建议 17如果你本机 Gradle JVM 是 Java 8runIde启动 JBR 时会直接崩。第二个常见原因是内存不足沙箱 IDE 默认堆太小插件加载到一半被 OOM 杀掉。第三个是本地杀毒或监控工具拦截了 JBR 的临时文件写入这类问题最玄学报错没有规律。解决在gradle.properties里显式固定 Gradle JVM 参数org.gradle.java.home/path/to/jdk17 org.gradle.jvmargs-Xmx2048m -XX:MaxMetaspaceSize1024m同时在build.gradle里给 runIde 加大堆runIde { jvmArgs -Xmx2048m, -XX:MaxMetaspaceSize1024m }如果还崩去build/idea-sandbox/system/log里看idea.log和hs_err_pid*.loghs_err 文件会把崩溃点和当前线程栈打出来大部分情况能定位到具体是哪个类触发的。4.5 现象升级 IDE 后插件失效提示版本不兼容原因sinceBuild/untilBuild区间没有覆盖新 IDE或者插件用了旧版 API新平台已经移除。IntelliJ Platform 对 API 兼容性管理严格很多接口标记Deprecated之后还会保留几个大版本但有些内部 API 说删就删编译能过不代表运行时不会抛NoSuchMethodError。解决升级插件工程时不要只在patchPluginXml里把untilBuild改大。正确路径是先把intellij.version升到目标 IDE 版本重新编译解决所有废弃 API 警告然后跑./gradlew verifyPlugin这个任务会校验插件描述、图标和版本边界。最后用buildPlugin打包在目标 IDE 版本的真实环境里做一轮冒烟测试比直接放开版本区间可靠得多。5. 上卷手册的路要怎么读从搭建环境到提交审核的验证清单5.1 上卷的章节结构按这个顺序读最省时间资源包里的这份手册是“上卷”按常见编排它会从环境准备一路讲到 PSI 基础把插件开发的地基部分覆盖完。我建议按下面这个顺序读而不是拿到就从中间翻开常见分章核心内容建议用时动手要求环境准备Gradle 工程初始化、SDK 加载30 分钟跑通 runIdeActionSystemAnAction、菜单分组、快捷键1 小时给右键菜单加一个项Tool Window面板布局、内容填充1 小时做一个带按钮的面板PSI 基础PsiFile、PsiElement、Visitor 遍历2 小时写一个类统计脚本调试与日志沙箱目录、idea.log、断点30 分钟用日志代替 print注意一个边界语言注入、框架支持、打包签名这些通常放在下卷。所以读上卷时不要指望看到发布上架的完整流程那不是缺陷是分册定位。上卷的核心目标就是让你在任何一台机器上都能把插件工程跑起来、调得动、看得见效果。5.2 把 Action 和 Tool Window 串成一个完整工程很多人读手册时每个章节都看懂了合上书还是不知道从哪下手。我的做法是准备一个空工程把手册前五章的代码一步一步合到一起形成一份最小但完整的 plugin.xml落地的效果是右键菜单弹窗 一个右侧 Tool Window 面板 项目打开时的日志打印。idea-plugin idcom.example.demo/id nameDemo Plugin/name description手册配套验证工程/description dependscom.intellij.modules.platform/depends extensions defaultExtensionNscom.intellij toolWindow idDemoPane anchorright factoryClasscom.example.demo.DemoToolWindowFactory/ projectManagerListener implementationcom.example.demo.DemoProjectListener/ /extensions actions action idcom.example.demo.HelloAction classcom.example.demo.HelloAction textSay Hello description弹窗验证 add-to-group group-idEditorPopupMenu anchorlast/ /action /actions /idea-plugin说明这份 XML 把第 3 章里三件事全部串在一起extensions里挂 Tool Window 和项目监听器actions里挂右键菜单。工程能编译、能启动、三个功能都能触达就说明你对 IntelliJ Platform 的基础扩展模型已经建立了肌肉记忆。之后每读一个新主题比如 PSI、Line Marker就把它也加进这个工程保持“边读边扩”的节奏比单独看代码样例有效得多。5.3 提交审核前的验证清单手册读完了功能也写完了别急着打包。我每次发新版本前都强制跑一遍这几项检查./gradlew verifyPlugin ./gradlew buildPlugin ./gradlew buildSearchableOptionsverifyPlugin检查插件描述、图标、依赖、版本区间的合法性buildPlugin产出待分发的 zipbuildSearchableOptions生成设置在 Settings 搜索框里的索引不跑这个用户在设置里搜不到你插件的配置项这个坑很多人压根不知道。清单也就四到五项插件描述里是否写清了适用场景图标是否同时准备了 16x16 和 32x32 两种尺寸sinceBuild、untilBuild是否覆盖了目标用户群沙箱里新建一个项目做冒烟测试idea.log里有没有非预期异常。把这些都过完插件才算真正“能交出去”。6. 进阶一步动态插件与状态持久化把手册没讲的边界补上6.1 dynamic 属性热卸载的前提手册上卷通常不会细讲动态插件机制但这个属性直接影响开发体验。在 plugin.xml 根元素上加上dynamictrue插件就支持在 IDE 运行状态下卸载和重装调试迭代时不用反复重启沙箱。代价是插件代码必须能正确释放资源监听器要取消注册、后台线程要停掉、不要持有静态引用。做不到这些卸载时 IDE 会打印泄漏警告久而久之会导致运行期状态错乱。我的习惯是第一版就把这个属性写上强制自己从一开始就养成“可回收”的写码方式。6.2 PersistentStateComponent把插件状态写进配置目录另一个容易被忽略的是状态持久化。插件里记录的配置项比如上次选中的路径、用户偏好不能每次启动都从零开始。IntelliJ Platform 提供了标准方案State(name DemoPluginState, storages Storage(demoPluginState.xml)) public class DemoPluginState implements PersistentStateComponentDemoPluginState { public String lastSelectedPath ; public boolean firstRun true; Override public NotNull DemoPluginState getState() { return this; } Override public void loadState(NotNull DemoPluginState state) { this.lastSelectedPath state.lastSelectedPath; this.firstRun state.firstRun; } }注册成 applicationServiceextensions defaultExtensionNscom.intellij applicationService serviceImplementationcom.example.demo.DemoPluginState/ /extensions说明这个类的实例本身就是状态对象字段就是持久化字段SDK 会在合适的时机把实例序列化到配置目录下的demoPluginState.xml。getState返回当前状态loadState在启动加载时把磁盘上的值塞回来。相比自己读写 properties 文件这个方案完全由平台管理 IO 时机不会在 IDE 写配置时造成文件冲突。注意所有字段都要有默认值因为老用户升级插件后XML 里可能没有新字段。这块我之前也翻过车。最初做内部工具链插件时所有配置都丢在内存里用户每次重启 IDE 都要重新选路径被组里同事吐槽了两周。后来在源码里看到PersistentStateComponent才意识到平台早就给了后悔药。从那以后我每个新插件第一版就把dynamictrue写上同时把 State 类定义好哪怕字段是空的也先占住坑。改插件、升级、再验证整条链路稳了很多沙箱里来回重启的时间也省下来了。希望这份上卷手册能帮你把同样的弯路避开。本文还有配套的精品资源点击获取