ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

IntelliJ Platform插件开发入门:环境搭建到第一个Action

IntelliJ Platform插件开发入门:环境搭建到第一个Action 简介面向Intellij IDEA插件开发者的系统学习手册基于JetBrains Runtime 17.0.9兼容IDEA 2023及2024版本适合具备一定Java基础、希望进入插件开发领域的读者。上册围绕插件开发基础与图形化插件开发展开从平台术语、IDE插件类型、开发环境要求、开发流程与参考网站入手详细演示了第一个插件工程的创建与配置并涉及工程测试等关键环节同时为下册的语言类插件开发与附录的工具资料规划了清晰路径便于读者按需选择框架集成、代码统计、效率工具或代码自动完成、代码检查等方向的深入学习。资源为1个PDF文件大小15.82MB目录分级明确知识点间配有示例与操作说明可有效降低上手门槛。目前已有383人学习下载适合希望通过系统手册快速搭建Intellij平台插件开发知识体系的Java开发者。1. Intellij Platform PlugIn 插件开发手册“上”册环境、骨架与第一次运行我见过不少团队在 idea插件开发 上卡住的第一个晚上不是卡在写那个 AnAction 类而是卡在“手册看完了runIde 却一直起不来”的翻车现场。Intellij Platform PlugIn 插件开发手册的“上”册从标题也能猜到它的任务把环境、工程骨架、plugin.xml、第一个 Action 和第一次打包串成一条可复现的路。它适合有 Java 或 Kotlin 基础的从业者目标不是让你成为 PSI 专家而是让你在三天内从零跑通一个能被 IDE 加载、能弹菜单、能打包分发的最小插件。如果你目标更激进比如要改编辑器渲染或做语言服务集成这份“上”册只负责打底不负责带你进深渊。2. 搭好 IntelliJ Platform 插件开发环境JDK、Gradle 与 IDE 三者的版本对齐开发 idea 插件步骤里最容易劝退新人的不是 API 复杂度而是三件套版本对不上IDE 自带的 JBR、编译插件用的 JDK、以及 Gradle 插件自身的版本。手册“上”册通常会直接从 IntelliJ SDK 讲起但我建议你先把下面这张经验表贴在终端旁边再开始动手。IDE 版本自带 JBR推荐编译 JDK推荐 Gradle说明2021.3JBR 11JDK 11Gradle 6.8老工程常见组合2022.3JBR 17JDK 17Gradle 7.5大量教程默认环境2023.2JBR 17JDK 17Gradle 8.0我常用组合2024.1JBR 21JDK 21Gradle 8.5新项目可以直接跟进别把这张表当成官方兼容矩阵它只是个人项目经验值方向上参考。真正重要的是理解为什么需要对齐插件最终跑在 IDE 自带 JBR 里而不是跑在你系统安装的 JDK 上。2.1 三个版本前提JBR、JDK 与 Gradle 为什么必须对齐IDE 2023.x 这个区间自带的 JBR 是 17。插件字节码最终要加载进这个 JBR所以编译目标不能比它新。你本地如果装的是 JDK 21那就把编译 target 设成 17直接 target 21启动阶段就会报 UnsupportedClassVersionError。反过来IDE 还是 2021.x 时代JBR 11却在 build.gradle.kts 里把依赖指向了 2023.1 的 Platform SDK构建能过启动时接口二进制不兼容NoSuchMethodError 满天飞这才是新手眼里最典型的“玄学”问题。Gradle 版本影响的是 org.jetbrains.intellij 插件能否被正常加载。老项目常见 Groovy DSL 加 apply 语法在 Gradle 8 下会碰到 apply method 被禁用的硬报错这个坑我放到第 5 章专门说。我这边能稳定用的组合是 IDEA 2023.2、JDK 17、Gradle 8.0、org.jetbrains.intellij 1.15.0。如果你开新项目可以去看看官方最新的 IntelliJ Platform Gradle Plugin 2.x配置入口和 1.x 不一样但“上”册里大量工程还是 1.x先按 1.x 学之后迁移成本不大。2.2 搭建最小 Gradle 工程build.gradle.kts 里的关键参数先建一个空目录然后放两个文件。第一个是 settings.gradle.kts用于指定插件仓库和工程名pluginManagement { repositories { gradlePluginPortal() mavenCentral() } } rootProject.name my-first-ide-plugin这段配置解决的是插件本身从哪下载。公司内网环境下如果默认源拉不到 org.jetbrains.intellij就在这里加内部 mirror不要改 build.gradle.kts 里的 repositories否则会绕过 pluginManagement 直接走全局源行为更不可控。第二个是 build.gradle.kts核心内容如下plugins { java id(org.jetbrains.intellij) version 1.15.0 } group com.example version 0.1.0 intellij { pluginName.set(my-first-ide-plugin) version.set(2023.1) type.set(IC) downloadSources.set(true) } tasks.patchPluginXml { sinceBuild.set(231) untilBuild.set(241.*) }intellij 块里最值得盯的是 version 和 type。version 是插件要下载的 IDE SDK 版本我这里用 2023.1type 是发行版型号IC 是社区版IU 是旗舰版常见还有 PC、PY、GO。个人学习阶段用 IC 最轻量但如果你要依赖旗舰版才有的模块type 就必须设成 IU否则编译期找不到对应类。downloadSources 默认打开这样断点能进 IDE 自己的实现类代价是首次同步时间长初次跑通时可以先关掉。patchPluginXml 的 sinceBuild 和 untilBuild 直接决定打包出来的 zip 能被哪些 IDE 版本安装。sinceBuild 设 231意思是只认 2023.1 起的 IDEuntilBuild 设 241.*表示 2024.1 全版本。范围写太窄团队里别人装不上写太宽用到的 API 在老 IDE 上不存在启动直接挂。2.3 目录结构别自己发明存放位置插件工程和普通 Java 工程的区别只在两个地方。src/main/java 放插件代码src/main/resources/META-INF/plugin.xml 放清单。IDE 加载插件时先读这个 XML再反射创建类文件名和路径是默认约定Gradle 插件不会帮你换位置。你如果自己创建一个 resources/plugin.xml 放在别的地方构建能过运行起来 IDE 完全不认识。第一次跑通之前还建议在 gradle.properties 里加一行 org.gradle.jvmargs-Xmx2g。原因是 runIde 会拉起整个 IDE内存不够会闪退。很多新手把这里当成构建无关配置直接跳过结果 runIde 一启动 IDEA 就报 low memory然后回头改各种虚拟机参数绕一大圈。先 2G不够再加这是最稳的起点。3. 认识插件骨架plugin.xml、AnAction 与扩展点如何协作“上”册最核心的章节我认为是“清单驱动”这一节。Intellij Platform PlugIn 插件没有 main()入口是一份 plugin.xml。IDE 加载插件时先解析这个 XML把 Action 类塞进菜单把扩展实现挂到平台钩子上不认识的节点就跳过并写警告。所以插件开发的第一定律宁可 Java 代码写朴素一点也要让 plugin.xml 完全对着官方 schema 写。3.1 plugin.xml插件如何被 IDE 识别我一般会先写一个最小的 plugin.xml确保能加载再往里填 Action 和扩展点。一个能弹菜单的最小清单长这样idea-plugin idcom.example.my-first-ide-plugin/id nameMy First IDE Plugin/name version0.1.0/version vendor emaildevexample.comYour Team/vendor description一个用于验证插件链路的最小工程/description dependscom.intellij.modules.platform/depends dependscom.intellij.modules.java/depends actions action idcom.example.ShowTimeAction classcom.example.ShowTimeAction textShow Current Time description在状态栏显示当前时间 add-to-group group-idToolsMenu anchorfirst/ /action /actions /idea-pluginid 最好用公司域名反写避免和 Marketplace 上既有插件撞名。撞名不会影响编译但同一个沙箱里同时装两个同名插件后装的那个不会被加载而且 idea.log 里只有很隐晦的提示。vendor 不是必填但企业内部分发时联系人邮箱能省掉不少沟通成本。depends 是新手最容易漏的。com.intellij.modules.platform 是基础依赖几乎每个插件都要加一旦你的代码要操作 Java 工程的 PSI比如读类名、拿 import 列表就必须加 com.intellij.modules.java否则运行时会告诉你类不存在。少依赖的症状是启动报错或功能没反应而不是编译器报警所以特别难查。actions 节点写在 XML 而不是注解里这是 IntelliJ 的标准做法。IDE 根据 id 识别菜单项class 必须指向一个继承 AnAction 的类text 是菜单显示文字。不要在 Java 代码里再次设置文本两边不一致会让人困惑。3.2 写第一个 AnAction跟手菜单的完整过程AnAction 是插件交互的基石。下面这个类对应上面的清单点击菜单后弹一个消息框package com.example; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.ui.Messages; public class ShowTimeAction extends AnAction { Override public void actionPerformed(AnActionEvent e) { Messages.showInfoMessage( Now: java.time.LocalTime.now(), Current Time); } Override public void update(AnActionEvent e) { e.getPresentation().setEnabledAndVisible(true); } }actionPerformed 是唯一的业务入口参数 e 能拿到当前项目 Project、编辑器 Editor、选中文本等数据。先用 Messages 弹窗验证最省事弹窗能出现说明类和 XML 都被正确加载了后面再逐步换成真实功能。update 方法会在菜单每次显示前被调用用于控制按钮是否置灰。初学阶段最安全的行为是直接 setEnabledAndVisible(true)什么条件都不加。把重逻辑挪进 update 是典型自杀式写法——IDE 每次弹菜单都会执行一遍用户会直观感受到 UI 卡顿。如果你要用当前编辑器的上下文去做判断比如只在 Java 文件里启用再把 update 里的逻辑收紧。和 Chrome 插件开发里注册 browser action 的感觉类似AnAction 只需要“类 注册点”就能挂到菜单不需要手动为按钮画界面。3.3 扩展点更隐蔽的插件形态Action 面向用户主动点击扩展点面向平台主动回调。光标移动、文件保存、编译完成这些事件发生时IDE 会调用你注册的实现类。扩展点同样写在 plugin.xml 的 extensions 节点上格式如下extensions defaultExtensionNscom.intellij toolWindow idMyToolWindow anchorright factoryClasscom.example.MyToolWindowFactory/ /extensionstoolWindow 是最容易肉眼确认的扩展点之一IDE 右侧会出现一个 MyToolWindow 页签页面内容由 factoryClass 创建。扩展点名称是平台写死的不能自创。开发时打开 IDEA 自己的 Actions 面板搜 Extensions能看到当前 IDE 支持的所有扩展点清单比凭记忆写可靠得多。注册扩展点最容易犯的错误是类存在、XML 也写了但忘记在实现类上实现对应接口或者类名少写一个字母。IDE 加载时会尝试把 factoryClass 强转成 ToolWindowFactory转不成就抛异常然后 idea.log 里出现一大段 stack trace。这跟 AnAction 的注册逻辑是两套体系不要混在一起记。下面这张表适合贴在笔记里区分两类注册方式维度AnActionExtension触发方式用户点击菜单或快捷键平台事件回调注册位置actionsextensions实现要求继承 AnAction实现扩展点接口典型用途菜单动作、快捷键工具窗、行标记、补全新学的时候Action 的感知成本低扩展点需要一点反向思考但它是插件能力的上限。手册“上”册一般只要求你认识它能注册一个 toolWindow 就算过关。4. 把插件跑起来runIde、断点日志与 buildPlugin 全流程前两章把纸面东西搭好了接下来进入真正的开发 idea 插件步骤——本地运行。整个流程只有三条命令runIde、Debug runIde、buildPlugin。下面按顺序拆。4.1 runIde 最小命令与首次下载在项目根目录执行./gradlew runIde这条命令背后会做三件事解析 intellij.version 并下载对应 IDE SDK把插件工程编译成类合并 plugin.xml然后启动一个沙箱 IDE。沙箱的配置目录是 build/idea-sandbox/config日志目录是 build/idea-sandbox/system/log和你日常开发用的 IDE 配置互不干扰这一点非常重要——在沙箱里调试插件不会污染你平时的工作环境。如果本地已经装了同版本 IDE可以指定 ideaPath 跳过下载./gradlew runIde -PideaPath/Applications/IntelliJ IDEA.app/Contents/MacOS/ideaWindows 下就写 idea64.exe 的完整路径。这样可以省掉下载时间但代价是拿不到 Platform SDK 的源码断点进不了 IDE 内部实现。我一般建议第一次跑通用下载方式之后为了速度再用本地安装路径。首次 runIde 会下载全部依赖耗时较长别急着反复停任务。如果下载失败优先检查网络源或换一个更常见的 IDE 版本而不是马上重试否则容易留下残缺缓存后面每次构建都卡在同一处。4.2 断点与日志插件排错的左右手在 Gradle 工具窗里右键 runIde选择 DebugIDEA 会以调试模式启动沙箱。由于 runIde 本质是启动另一个 JVM断点能否命中取决于 Gradle 是否把调试参数传给沙箱正常情况下是可以的但要确认断点打在插件类里不是打在平台类里。如果发现断点一直不生效别急着换断点位置先看 idea.log。日志路径稳定在 build/idea-sandbox/system/log/idea.log类加载、Action 注册失败、扩展点转换失败都有记录。推荐在 Action 里加一段日志观察调用时机import com.intellij.openapi.diagnostic.Logger; public class ShowTimeAction extends AnAction { private static final Logger LOG Logger.getInstance(ShowTimeAction.class); Override public void update(AnActionEvent e) { LOG.info(update: e.getPresentation().getText()); e.getPresentation().setEnabledAndVisible(true); } }日志会输出到 idea.log也可以用终端 tail 实时看。System.out 不是不能用但插件类加载器在某些场景会吞掉标准输出排查起来不如 Logger 干净。用 Logger 还支持按类名过滤比在控制台里翻乱码快很多。4.3 buildPlugin 打包到安装本地验证通过后执行./gradlew buildPlugin产物在 build/distributions/my-first-ide-plugin-0.1.0.zip。注意不要把这个 zip 解压后再压缩IDE 的插件安装器期待一个带 META-INF/plugin.xml 的顶层目录你重新压缩成别的结构安装时会提示 Invalid plugin descriptor。安装路径是日常 IDE 的 Settings → Plugins → 齿轮 → Install Plugin from Disk → 选择 zip。如果 IDE 提示版本不兼容大概率是 sinceBuild/untilBuild 没对齐回到 patchPluginXml 改。团队分发还可以用菜单里的 Export、Import 或自建 update site那是“上”册以外的话题先不用碰。还有一个容易被忽略的点runIde 沙箱和日常 IDE 是两个环境沙箱里能跑的插件日常 IDE 不一定能装。每次发版前一定用 buildPlugin 装到日常 IDE 里点一次这是底线操作。5. 插件开发避坑笔记四个高频问题与修复路径跑通基础流程之后剩下的就是血泪经验。以下四类问题每条我都至少见过一次有的直到现在偶尔还会踩到。5.1 Gradle 8 下 apply 老写法直接报错现象build.gradle 第一行写着 apply plugin: org.jetbrains.intellij执行任何 Gradle 任务时抛错提示 You are applying a plugin imperatively using the apply method后面的 exit code 是 1。原因Gradle 8.x 开始收紧旧式 apply 方法org.jetbrains.intellij 属于第三方插件不能再用命令式加载。网上大量 2020 年的博客都是这种写法抄的时候很容易翻车。这个报错不是 IntelliJ 特有的只要用 Gradle 8 升级旧第三方插件的工程都会遇到。解决把 apply 写法改成 plugins 块plugins { id(org.jetbrains.intellij) version 1.15.0 }同时保留 settings.gradle.kts 里的 pluginManagement 仓库。改完后先执行 ./gradlew clean再执行 runIde避免旧的构建缓存把问题掩盖掉。5.2 JDK 与 JBR 不匹配弹 J2SE 版本警告现象runIde 或真实 IDE 打开插件时IDE 弹窗提示 in order to access this application, you must install the J2SE plugin version 17或者更常见的是 java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException。原因插件用高版本 JDK 编译后字节码里的类版本高于运行时 IDE 自带的 JBR。另一种情况是 JDK 11 之后标准库移除了 JAXB老插件代码里还在 import于是 ClassNotFound。解决分两步。第一步在 build.gradle.kts 里固定 java toolchainjava { toolchain { languageVersion.set(JavaLanguageVersion.of(17)) } }第二步把 intellij.version、sinceBuild、untilBuild 三者调整到同一个大版本区间比如 2023.1 231 241.*。改完别只重跑 build要 clean 一次再 runIde否则 Gradle 缓存里还是旧 class 文件。5.3 菜单不显示或一直置灰现象Action 类编译通过plugin.xml 也写了但菜单里找不到或者找到了却是灰色。原因三个方向排查。一是 class 全限定名写错idea.log 里会有一条 ClassNotFoundException二是 add-to-group 的 group-id 不对IDE 只会把 Action 放进真实存在的菜单组三是 update 方法里 setEnabledAndVisible(false)又被某次事件上下文触发把按钮关掉了。解决先用最保守的写法。在 plugin.xml 里 anchor 改成 first在 update 里直接 setEnabledAndVisible(true)重跑 runIde 看菜单。别在 update 里判断太多 Editor 上下文等链路确认通了再加条件控制。如果还是看不见打开 IDE 的 Plugins 设置确认插件处于 enabled 状态。5.4 打包出来的 zip 装不进目标 IDE现象成功 buildPlugin但在另一台相同版本 IDE 上安装失败提示插件描述文件缺失或版本不兼容。原因常见是 patchPluginXml 没写untilBuild 默认为空某些 IDE 版本严格模式拒绝安装或者 zip 内部结构不对Gradle 生成的 zip 是插件目录为顶层手工改过的 zip 可能破坏层级。解决显式配置 sinceBuild 和 untilBuild。团队统一 IDE 版本时可以写死一个区间比如 231-241别写太宽。另一点是尽量不要手工改 zip用 Gradle 重新 build避免结构变化。“上”册能做到这里插件已经具备分发价值了。5.5 插件崩溃NoClassDefFoundError 指向自己的类现象runIde 刚起来控制台或 idea.log 出现大段 stack traceCaused by: java.lang.NoClassDefFoundError类名指向你自己写的类有时还会弹出插件 crash 对话框。原因插件引用了 IDE 模块之外的第三方库比如 commons-io但没有把依赖打进产物或者 plugin.xml 里 class 字段对应的 jar 没有进入插件 classpath。解决在 build.gradle.kts 里把第三方依赖声明成 implementationIntellij Gradle 插件会把它复制进 lib 目录dependencies { implementation(commons-io:commons-io:2.11.0) }不要用 compileOnly那意味着编译期可见、运行期缺失。出包后用任意解压工具查看 zip确认 lib/ 目录存在并且包含对应 jar基本就能避开这个坑。6. 用三个小实验验证“上”册学到的整套链路读完“上”册最重要的不是记住 API而是形成验证闭环。我建议你花一个下午做下面三个实验每一步都是前面章节的组合练习。第一个实验把 ShowTimeAction 的弹窗改成 DialogWrapper 子类接收用户输入后写进当前工程目录。这要求你同时改动 Java 类和 plugin.xml验证 Action 注册链路仍然完整。如果弹窗能被打开说明从 plugin.xml 到类加载再到 UI 的整条路径是通的。改动时不要动 plugin.xml 里的 id只改 class 内容能少踩一个注册冲突。第二个实验把第 3.3 节里的 toolWindow 扩展点实现出来。factoryClass 里返回一个简单 JLabel 面板并在 createToolWindowContent 方法里打一条日志。启动后右侧出现页签日志出现对应行这条扩展链路就是通的。这个实验能帮你建立“扩展点视图”的肌肉记忆后面学 LineMarkerProvider、CompletionContributor 都是同一套“接口 XML 注册”的逻辑。第三个实验跑一次 buildPlugin把 zip 装到你日常开发用的同一个版本 IDE 里连续用几天。期间刻意去点菜单、开工具窗看有没有闪退或异常输出。只有在日常 IDE 里跑过一周的插件才算真正“上”册毕业。平时顺手把 idea.log 里和插件相关的 WARN 都处理掉比学更多扩展点重要得多。我自己的教训是第一次做插件时觉得 plugin.xml 只是配一下而已于是跳过它直接写 Action 代码结果菜单一直不出现。后来发现 XML 里 class 的包名写错一位IDE 连日志都懒得告诉你是哪个类找不到只能靠逐行排查。现在我会先让 plugin.xml 最小化再逐步加 Action、加扩展点每加一个就 runIde 一次。把 runIde 当成单元测试工具而不是启动器是“上”册之后我最推荐的姿势。希望帮到你。本文还有配套的精品资源点击获取
返回列表