
简介基于 IntelliJ IDEA 2023 与 JetBrains Runtime 17.0.9 的插件开发手册面向需要编写框架集成、代码统计、效率工具等 UI 类插件以及代码自动完成、依赖分析等语言级插件的中高级开发者。手册由上、下两册及独立附录组成上册覆盖插件开发基础与图形化插件开发下册深入语言类插件附录列出开发涉及的工具、官方参考与社区资料内容从配置 IntelliJ IDEA、创建插件工程、工程配置到测试运行逐步展开并附有目录结构、术语表和参考网站便于系统学习。压缩包内为 1 个 PDF 电子文档整体约 15.82MB已有 382 人学习下载。手册结合官方指导、作者实践与网络资料整理而成既有环境搭建路径也有 UI 与语言类插件设计思路适合在 2023、2024 版 IDE 下边读边练帮助开发者更快上手 IntelliJ Platform 插件开发。 把IntelliJ IDEA当成开发工具用的人很多但意识到它背后是一套“可编程平台”的人其实不多。IntelliJ Platform是JetBrains开放的IDE基础设施IntelliJ IDEA、PyCharm、WebStorm、Android Studio这些你熟悉的IDE全部是在这一套平台上组装出来的产品。换句话说插件开发并不是什么冷门分支你写的代码会直接运行在这些IDE的进程里通过平台预留的扩展点去增加菜单、弹窗、工具窗口、代码检查规则甚至重新定义整个编辑器的交互方式。这篇文章是IntelliJ Platform插件开发手册的上篇目标很明确带你把一个新人最容易卡住的环节——平台认知、工程搭建、第一个Action插件、服务与扩展点机制、调试打包——完整走一遍。读完你就能从零开始跑起一个能改菜单、能弹窗、能读取项目文件的插件至于ToolWindow、编辑器UI、代码分析这类进阶功能留给下篇拆解。适合刚想入坑又不知道从哪下手的开发者也适合后端转IDE工具开发的同事做参考资料。1. 先弄明白IntelliJ Platform你是在写一个运行在IDE里的程序1.1 IDE和平台的关系你说自己在做“IDEA插件开发”其实做的是IntelliJ Platform插件开发IDEA只是这套平台上最知名的一个客户端。平台层提供了编辑器、项目模型、PSI程序结构接口、搜索索引、执行环境等基础能力IDEA等IDE不过是在这些能力之上针对不同语言场景做的一套业务实现。这个层级关系会直接影响你的思维方式。刚开始容易把插件想象成“改IDEA的源码”但正确的模型是你的插件是一个独立交付的jar包运行在IDE的JVM里通过平台公开的API来做事。平台不让你碰的部分都封装在内部模块里官方也不建议用反射去绕过能让你扩展的都是平台明确定义好的扩展点Extension Point。打个比方平台像一栋精装修交付的写字楼水电网、隔间、消防都到位了插件就是入驻的公司。入驻公司不能砸承重墙、改管线但可以在规定区域摆自己的工位、加自己的招牌。扩展点就是楼里预留的接口面决定哪些位置允许你动手。搞懂这个边界后面看官方文档时你才能分清“哪些接口可以放心用、哪些只是恰好能用的黑魔法”。1.2 插件能触碰的业务边界明确了“不能砸承重墙”再看能做什么。以前两天我在项目里写的一个小插件为例它需要读取用户当前打开的文件路径统计行数再往右下角弹一个气泡通知。这在IntelliJ Platform里对应三条链路AnAction捕捉触发事件、Project和Editor对象取上下文数据、Notifications弹通知。这三步全部走平台公开API没有任何hack手段。除了这种简单交互插件还能做代码检查Inspection、意图动作Intention Action、重构步骤、格式化规则、语言注入、导航高亮等。另一个大方向是把IDE变成领域工具比如在IDE里画ER图、跑特定框架的脚手架、对接内部发布平台。不少公司内部效率工具就是这么长在IDE里的员工写代码时不需要切窗口工具链就嵌在编辑器旁边。边界在于不要试图做平台已经做得很好的事也不要去覆盖IDE的私有内部实现否则每次大版本升级你都可能踩兼容坑。1.3 Java还是Kotlin新手问得最多的就是语言选型。IntelliJ Platform本身是Java写的API文档也以Java为主Kotlin因为语法更简洁、官方插件示例用得越来越多目前在插件社区里占比很高。我的建议是如果你Java基础扎实直接Java起步没有任何问题官方SDK对Java的支持非常完整如果你本来就在写Kotlin那当然用Kotlin写起来能省不少样板代码。真正要操心的不是语言而是平台API的变化节奏。JetBrains对外部插件的兼容策略是一旦标记deprecated隔几个大版本就会移除内部API更是说变就变。所以开发前先看官方插件SDK文档里标注的稳定性等级能用公开扩展点解决的就别碰com.intellij.internal包下的方法。我见过有插件为了省事直接调了内部接口结果IDEA升级后整个功能废掉还得返工重写的例子不划算。2. 工程搭建与Gradle配置上手最容易被绊倒的地方2.1 为什么推荐Gradle搭建工程的第一选择是用Gradle IntelliJ Plugin而不是旧式DevKit向导。Gradle方案的好处一个是工程描述即代码开发环境、发布版本、依赖插件都能在build.gradle里锁定方便团队协作另一个是它的runIde、buildPlugin这些任务天然对接开发调试和最终产物构建后面接CI流水线也用得上。DevKit向导生成的工程结构不是不能用但维护迭代慢JetBrains官方也在持续把重心往Gradle迁移新项目直接用Gradle是更省心的路径。2.2 build.gradle逐行拆解我拿自己项目的build.gradle做个参考你新建项目时可以按这个骨架改plugins { id java id org.jetbrains.intellij version 1.17.4 } group com.example.myplugin version 1.0.0 repositories { mavenCentral() } dependencies { implementation org.jetbrains:annotations:24.0.0 } intellij { version.set(2023.2.5) type.set(IC) plugins.set([com.intellij.java]) } patchPluginXml { sinceBuild.set(232) untilBuild.set(243.*) }几个关键配置说下。intellij.version决定你的插件基于哪个IDE版本编译和运行type的IC代表IntelliJ IDEA CommunityIU是Ultimate。如果你要做只属于IDEA Ultimate的功能就选IU大多数纯通用插件选IC即可编译产物在多款JetBrains IDE上通用。plugins里声明的是依赖的平台插件比如要实现Java语言相关功能就需要com.intellij.java在编译期存在。patchPluginXml里的sinceBuild和untilBuild是插件兼容版本的窗口。232对应2023.2这个版本的build号243对应2024.3。写太窄会影响能安装的IDE范围写太宽又可能跑到新版本上因为API变化起不来。我的建议是先按目标IDE版本写跑过验证后再适度放开。2.3 plugin.xml最小文件工程里的plugin.xml是插件的“身份证”最终会被打进jar包的META-INF目录。最简配置大概这样idea-plugin idcom.example.myplugin/id nameMy Plugin/name version1.0.0/version vendorMy Company/vendor description插件功能描述/description dependscom.intellij.modules.platform/depends extensions defaultExtensionNscom.intellij /extensions actions /actions /idea-pluginid必须全局唯一不然安装时可能会把别的插件顶掉depends声明依赖com.intellij.modules.platform是基础平台模块几乎所有插件都依赖它。后面要用Java语言能力还要在这里加上com.intellij.modules.java。很多“插件装了没反应”的问题查到最后都是depends漏了或写错了这个比想象中常见。2.4 首次运行runIde配置没问题后执行Gradle任务./gradlew runIde。它会下载对应版本的IDE发行包在本地起一个带了你插件的开发实例。首次启动会明显久因为要解压几百MB的IDE、建立索引。如果你的网络环境下载慢可以在本地Gradle缓存目录里先放好对应版本的IDE压缩包或者把配置改为直接指向本机已安装的IDEA安装目录。启动后你会在开发IDE的控制台看到插件已经被挂载到IDE进程里了。到这个状态插件的“最小开发循环”就建立了写代码、runIde、在开发实例里验证然后继续改。后面想更快还能用HotSwap做不重启的热替换不过那属于优化阶段的事上篇先把基础循环建立起来更重要。3. 第一个Action插件菜单、快捷键与实时反馈3.1 AnAction的两个核心方法Action是插件里最常见的交互入口。它对应工具栏按钮、菜单项、快捷键你点一下触发一次动作。AnAction给我们留了两个核心方法需要重写。第一个是actionPerformed动作真正执行的地方相当于事件处理函数。第二个是update每次菜单显示或工具栏刷新时调用用来决定当前上下文里这个动作是否可见、是否可点。这里我踩过一个坑新手容易把所有逻辑都塞进actionPerformed但平台的规则是长耗时任务不能阻塞EDT事件分发线程。比如读大文件、做网络请求直接放actionPerformed里会让整个IDE界面卡顿严重时还会触发IDEA的线程检查告警。正确做法是把耗时的部分丢到后台线程去跑再回到EDT更新UI。后面示例里我会带上这个思路。3.2 在plugin.xml里注册ActionAction类写好后要注册到plugin.xml的actions节点下actions action idcom.example.fileinfo.FileInfoAction classcom.example.fileinfo.FileInfoAction text文件信息统计 description统计当前文件行数并显示路径 add-to-group group-idEditorPopupMenu anchorfirst/ keyboard-shortcut keymap$default first-keystrokectrl alt F/ /action /actionsgroup-id决定Action出现在哪里。EditorPopupMenu是编辑器右键菜单MainMenu是主菜单EditorToolbar是编辑器工具栏。anchor是插入位置first表示放在最上面。keyboard-shortcut是快捷键跟随当前keymap生效在源码里定义快捷键后用户切换keymap会自动映射到对应方案不用自己写多套配置。3.3 完整示例统计当前文件信息下面用一个完整例子把上面几个概念串起来。这个插件做的事情是在编辑器右键菜单加一项“文件信息统计”点击后弹窗显示当前文件的路径、行数和字符数。package com.example.fileinfo; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.CommonDataKeys; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.vfs.VirtualFile; import org.jetbrains.annotations.NotNull; public class FileInfoAction extends AnAction { Override public void actionPerformed(NotNull AnActionEvent e) { Project project e.getProject(); Editor editor e.getData(CommonDataKeys.EDITOR); if (project null || editor null) { return; } Document document editor.getDocument(); VirtualFile file FileDocumentManager.getInstance().getFile(document); if (file null) { // 理论上走到这里之前 update() 已经把菜单禁掉了但防御式判断还是要写 return; } String path file.getPath(); int lineCount document.getLineCount(); int textLength document.getTextLength(); Messages.showInfoMessage(project, 文件路径 path \n行数 lineCount \n字符数 textLength, 文件信息统计); } }注意e.getData(CommonDataKeys.EDITOR)与e.getRequiredData的区别。getRequiredData在取不到对应数据时会直接抛异常适合菜单必然存在上下文数据的场景getData则更稳妥返回null时自己做降级处理。右键弹菜单这种场景未必每次都有编辑器上下文所以这里用getData加判空更稳。3.4 动态控制菜单可见性很多菜单项不应该在任何地方都显示。比如“文件信息统计”只在编辑器里打开文件时有意义用户焦点在工具窗口时弹出来就很怪。这时重写update方法Override public void update(NotNull AnActionEvent e) { PsiFile psiFile e.getData(CommonDataKeys.PSI_FILE); boolean enabled psiFile ! null; e.getPresentation().setEnabledAndVisible(enabled); }PSI_FILE代表当前焦点所在的代码文件能取到就说明菜单出现在编辑器环境里。setEnabledAndVisible同时控制可见和可点击两种状态一步到位。要强调的是update里别做重计算因为菜单每次展开都会调用这个方法你要是在里面查数据库或做复杂解析整个IDE的菜单都会变卡这是新版开发者特别容易忽视的体验问题。4. 插件系统的骨架服务与扩展点的工作方式4.1 三级服务模型Action只是入口真正干活的逻辑通常放在Service里。IntelliJ Platform把服务按生命周期分成三层应用级ApplicationService全局唯一从IDE启动到退出一直存在项目级ProjectService每个项目一个实例模块级ModuleService每个模块一个实例。用哪个级别取决于数据边界跟项目配置、项目缓存相关的放项目级跟IDE全局设置相关的放应用级。获取服务的标准写法是ServiceManager.getService(YourService.class)Kotlin里还可以用serviceT()扩展函数。有一个高频坑服务的构造器里不要提前访问Project等重要上下文服务实例化时机由平台管理很多新手在这里踩空指针就是因为假设了构造时一定能拿到project。在构造函数阶段只做无副作用的初始化真正要用数据时再通过方法参数获取。4.2 常用扩展点速览插件那么多能力全靠扩展点这种“插槽”机制串起来。工程搭建后你在plugin.xml的extensions里能看到的那些标签每一个背后都对应一个平台预留的插槽。这也是理解插件开发的钥匙之一大多数能力不是靠你“主动调用”平台而是靠平台在特定时机反过来调你注册的扩展实现。上篇先按我的经验列几个最常用的applicationService / projectService注册服务实例配合ServiceManager调用。toolWindow新增一个工具窗口类似IDEA底部Terminal那种吸附面板。editorNotificationProvider在编辑器顶部展示业务通知条比如提示当前文件属于哪个项目任务单。localInspection定义一个静态代码检查项会出现在Analyze菜单和代码高亮里。intentionAction往AltEnter意图菜单里塞“一键操作”比如补充字段、生成测试方法。先知道这些名字就好工具窗口和代码检查属于下篇能展开讲的大主题。但脑子里有了“所有能力都是插槽”的意识你对后面看官方文档的confidence会高很多不会一进去就被满屏的PluginExtension列表吓到。4.3 注解注册与XML声明的关系随着SDK版本演进JetBrains推出了基于注解的服务注册方式。在类上直接标Service就等效于在plugin.xml里写applicationService serviceImplementation.../。注解方式能减少配置文件的维护量官方新示例也大多倾向这个但很多老插件和旧文档仍然是XML方式你得两种都能读懂。我建议初学阶段先把XML注册方式掌握扎实因为它更直观能看到完整依赖链上手后再切注解方式提效。不管用哪种方式平台加载扩展点时走的都是同一套容器不存在“注解注册的平台不支持”这种说法只是版本差异罢了。遇到新旧示例混着看的情况先对着一套方式跑通再对照理解另一种不会太乱。5. 调试、打包与高频报错排查5.1 调试运行与日志插件开发调试比普通Web服务方便因为runIde起的那个开发实例就是你最好的测试环境。在Gradle里执行runIde后直接打上断点操作触发插件代码就会停在断点处也可以attach到已经运行的开发实例进程做热调试。出问题时第一件事是看日志。开发实例的Help菜单里能找到Show Log in Explorer或Show Log in Finder打开idea.log查看插件相关的异常栈。插件加载失败的蛛丝马迹基本都在里面比瞎猜配置高效太多了。我排查问题时的习惯是先搜自己插件包名再往上翻看有没有PluginException关键字定位速度会比翻完整份日志快很多。5.2 打包与本地安装执行buildPlugin任务产物在build/distributions目录下生成一个zip包。要给别人用可以在目标IDE的Settings → Plugins → 齿轮 → Install Plugin from Disk选择这个zip重启IDE即完成安装。本地磁盘安装的路径比较宽松未签名插件也能直接加载但如果以后要传到JetBrains Plugin Marketplace公开发布就得按平台要求的签名和认证流程来走那是发布阶段的事上篇先不展开。5.3 高频错误对照表我把新插件开发者前两周容易遇到的报错整理成了一个表按出现的频率排序现象根因解决方案runIde启动后菜单项都不见了plugin.xml配置有误常见是depends写错或actions里的id重复清理重复id检查depends声明是否完整点击Action抛ClassNotFoundException依赖库没打进产物在build.gradle里补依赖并重新执行build调用的API方法找不到使用的SDK版本低于该API引入的版本升级intellij.version或调整sinceBuild范围界面卡死日志出现EDT超时耗时任务阻塞了主线程把重活移到后台线程再回主线程更新UI升级IDE版本后插件不可用依赖了内部API改用平台公开扩展点避免internal包这张表我建议截图存到手机或贴到工位旁边排查问题时对照看能省不少时间。尤其是第一项很多项目一上来就写了一堆action标签一旦某个id重复平台会静默跳过部分action表现就是菜单项莫名消失查日志才看到Duplicate id之类的警告。5.4 API版本兼容最后说版本策略。IntelliJ Platform每个大版本对应一组build号比如2023.1对应2312023.2对应2322024.1对应241。API的增删与build号强绑定所以插件需要声明sinceBuild/untilBuild来约束安装范围。实践上我习惯用当前最新稳定版编译等测试通过再适当放大untilBuild反过来如果你依赖了一个新API就把untilBuild限制在引入该API的大版本内避免用户装到新版本上后异常。再说一个容易忽略的点依赖其他插件时官方要求声明对应插件的最低版本因为不同版本的插件接口可能不兼容。构建脚本里intellij.plugins写的每个插件id最好都配一个明确的版本范围不要用裸id。这一点在新手期问题不大等插件功能做深了、开始依赖Python插件或Markdown插件做联动时就会回来感谢这个习惯。上篇就到这里整个链路从认知、工程、Action、服务到调试已经能支撑你写完第一个真正能跑的插件。下篇我会重点讲ToolWindow界面交互、PSI的增删改查和代码检查实现——这些都是把插件从“能弹窗”升级成“真正嵌入开发流程”的进阶能力。如果你也正在折腾IntelliJ插件有具体卡点欢迎丢出来我看到后会挑有代表性的写进下一部分。本文还有配套的精品资源点击获取