简介:面向初学者的 IntelliJ IDEA 插件开发源码示例,适合希望快速掌握编辑器右键菜单、弹出框以及鼠标事件处理能力的 Java 开发者。压缩包共16个文件,大小约10KB,以 Java 源码和 XML 配置为主体:5个 Java 类实现核心交互逻辑,5个 XML 文件负责 plugin.xml 插件注册、项目模块及工作区配置,2个 SVG 提供明暗主题图标,另有 .name、.gitignore 与 iml 等辅助文件;src 目录集中存放 Action 与监听器代码,resources 下保存 META-INF 配置和图标资源,.idea 目录记录调试运行环境。已有785人浏览学习,具备较高参考价值。通过学习可完整理解插件项目结构、Action 机制、事件监听、Dialog/Popup 构建、资源引用及项目配置方式,例如在右键菜单中插入自定义操作、用弹出框收集用户输入后触发回调;结合源码注释能快速搭建自己的插件框架,并利用 IntelliJ IDEA 内置调试工具验证交互效果。这份示例代码体量小巧、目录清晰,特别适合在短时间内完成插件开发入门并延伸到 IDE 定制场景。
1. 一个带完整源码的 demo 压缩包:IntelliJ IDEA 插件开发最值得先打开的东西
你下载了idea插件详细源码demo.zip,解压之后先别急着翻plugin.xml,也别盯着build.gradle发呆。我最建议你做的第一件事是:先把runIde跑起来,让一个全新的 IDEA 沙箱窗口出现在屏幕上。那一刻你才算真正理解插件开发是怎么闭环的——你写的代码不是在某个测试类里跑,而是直接作为 IDE 的一部分活在一个干净的 IntelliJ IDEA 实例里。这个 demo 之所以叫“详细源码”,是因为它把插件工程最常见的骨架都摆了出来:Action、工具窗口、事件监听、持久化状态,外加一整套构建配置。它解决的最实际问题不是“怎么写插件”,而是“从零搭出一个能编译、能启动、能调试的插件工程”——这一步劝退了最多新手。这篇笔记适合两类人:刚装好 IDEA、想搞懂插件怎么落地的新手,以及能写 Java/Kotlin 但还没碰过 IntelliJ 平台的老手。
2. 解压到跑通:把 idea 插件 demo 工程完整启动起来
2.1 先看目录结构,理解插件工程的基本盘
拿到idea插件详细源码demo.zip之后,先解压到纯英文路径下,比如D:\plugin-demo或者~/workspace/plugin-demo。中文路径本身一般没问题,但后续 Gradle 下载依赖、IDE 缓存索引时偶尔会出现编码类报错,没必要为这个增加变量。常见的插件工程目录长这样:
demo-plugin/ ├── build.gradle ├── settings.gradle ├── gradle.properties ├── gradlew ├── gradlew.bat ├── gradle/ │ └── wrapper/ │ └── gradle-wrapper.properties └── src/ └── main/ ├── java/com/example/ │ ├── DemoAction.java │ ├── DemoListener.java │ └── DemoWindowFactory.java └── resources/ ├── META-INF/ │ └── plugin.xml └── icons/ └── demo-icon.svg这个目录骨架背后是一条最基本的原理:IntelliJ 平台插件本质上是“一个包含类和资源的 jar 包 + 一个声明插件结构的plugin.xml”。IDEA 启动时并不扫描所有 class,而是先读plugin.xml,按里面声明的扩展点、Action、监听器去实例化对应类。所以拆 demo 时,真正的主干只有三条线:build.gradle决定怎么构建,plugin.xml决定 IDE 如何识别插件的结构,src/main/java下的类决定插件的行为。
2.2 导入工程并完成构建,先让 demo 能编译
用 IntelliJ IDEA 打开解压后的目录,注意要选build.gradle而不是直接打开文件夹。IDEA 会问你是用 Gradle 方式导入还是当成普通 Java 工程,这里必须选 Gradle。导入后第一件事是检查 Gradle JVM 设置:打开Settings -> Build, Execution, Deployment -> Build Tools -> Gradle,把 Gradle JVM 指向 JDK 17 或更高版本。当前主流 IntelliJ 平台基于 JDK 17 构建,如果你的本机 JDK 是 8 或 11,会出现类文件版本冲突,报错信息里常见Unsupported class file major version。
同步完成后,先执行一次干净的构建,确认 demo 本身没有问题:
./gradlew clean buildWindows 下用gradlew.bat clean build。这个命令会编译src/main/java下的所有源码,把资源文件打进 jar,并生成最终插件包。如果这一步通过,说明工程的基础依赖和源码都是完整的;如果失败,优先看是不是网络原因导致 Gradle 依赖没有下载完,国内环境常见,多执行几次或者更换 Maven 镜像即可。构建产物会出现在build/libs/目录下,文件名一般是demo-plugin-1.0.0.zip或者.jar,这个包其实已经可以被 IDEA 安装,但我们现在要做的是直接启动一个调试用的 IDE。
2.3 跑 runIde,看沙箱 IDEA 如何加载你的插件
runIde是插件开发里最重要的一个 Gradle 任务,它会在本地启动一个全新的 IDEA 实例,这个实例和日常使用的 IDE 完全隔离,只加载当前工程的插件。执行命令:
./gradlew runIde首次运行会下载对应版本的 IntelliJ IDEA 运行时,体积较大,耐心等待。启动后你会看到一个干净得像刚装完一样的 IDEA,侧边栏和菜单里会出现 demo 里的 Action 或工具窗口入口。这里要理解一件事:这个沙箱 IDEA 的配置目录、插件目录、日志目录都是独立的,不会影响你日常使用的 IDE,也不会丢失你自己的配置。这就是插件开发里“黑匣子”最透明的一个环节——你能直接看到插件加载前后的差异。
运行runIde时 debug 端口默认是5005,你可以随时用远程调试的方式挂上去,后面我会在第 4 章具体讲。
3. 把 demo 源码拆开看:plugin.xml、Action 与 ToolWindow 三条主线
3.1 plugin.xml 是所有插件的中枢,先读懂它
无论 demo 的功能多复杂,它最后都会被 IDEA 通过src/main/resources/META-INF/plugin.xml这个文件识别。一个最小可用的plugin.xml是这样:
<idea-plugin> <id>com.example.demo-plugin</id> <name>Demo Plugin</name> <version>1.0.0</version> <vendor email="support@example.com" url="https://example.com">Example</vendor> <depends>com.intellij.modules.platform</depends> <extensions defaultExtensionNs="com.intellij"> <toolWindow id="DemoWindow" anchor="right" factoryClass="com.example.DemoWindowFactory" icon="/icons/demo-icon.svg"/> </extensions> <actions> <action id="com.example.DemoAction" class="com.example.DemoAction" text="Demo Action" description="这是一个 demo 动作"> <add-to-group group-id="ToolsMenu" anchor="last"/> </action> </actions> </idea-plugin>这里的每个参数都不是摆设。id是全插件唯一的标识,建议用com.你的域名.功能名的反域名格式,避免和其他插件冲突,光这一条就能避开后面“Action 注册了但被别的插件顶掉”的深坑。depends声明依赖的模块,com.intellij.modules.platform是基础模块,只要做普通插件基本都依赖它;如果你的插件要操作编辑器或项目文件,还需要加com.intellij.modules.java。extensions段声明扩展点,比如表格里的toolWindow会在右侧创建一个工具窗口;actions段则声明菜单和工具栏上的动作。注意defaultExtensionNs="com.intellij",这表示扩展点的命名空间,写错一个字母插件就会静默加载失败,而日志里只会留一句让人摸不着头脑的Cannot find declaration to goto。
3.2 Action 的注册与响应逻辑:用户点一下发生了什么
Action 是插件里最常见的交互入口,demo 里的DemoAction.java本质上只需要做两件事:继承AnAction,重写actionPerformed。源码一般长这样:
package com.example; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.ui.Messages; import org.jetbrains.annotations.NotNull; public class DemoAction extends AnAction { @Override public void actionPerformed(@NotNull AnActionEvent e) { Messages.showInfoMessage( "Demo 插件运行正常,当前项目:" + e.getProject(), "来自 Demo" ); } }这段代码的关键在于AnActionEvent——它承载了这次动作触发的全部上下文。e.getProject()返回当前打开的项目,可能是null,所以真实代码里一定要判空;e.getDataContext()可以拿到光标位置、选中的文件、编辑器实例等数据。这个机制是 IntelliJ 平台的核心设计:Action 不直接操作全局状态,而是通过 DataContext 获取当前上下文里的数据,也就是业界常说的DataProvider体系。
一个常见的误用是:新手把需要项目信息的逻辑直接写在 Action 构造函数里,然后发现getProject()返回 null。原因很简单——AnAction对象在 IDE 启动时就被实例化了,那时候还没有任何项目打开。所以如果需要项目数据,只能在actionPerformed里拿,Action 类本身要保持无状态。
3.3 ToolWindow 与持久化状态:让插件的界面真正“活”起来
demo 里如果包含工具窗口,它展示的才是插件从“菜单弹窗”进化到“常驻界面”的形态。ToolWindow 的注册方式已经在plugin.xml里写好了,对应的DemoWindowFactory是这样的:
package com.example; 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 org.jetbrains.annotations.NotNull; import javax.swing.*; public class DemoWindowFactory implements ToolWindowFactory { @Override public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) { JPanel panel = new JPanel(); panel.add(new JLabel("这是 Demo 工具窗口的内容区域")); Content content = ContentFactory.getInstance() .createContent(panel, "Demo", false); toolWindow.getContentManager().addContent(content); } }工具窗口的价值不只是展示一个面板,它还意味着插件有了自己的生命周期:窗口打开、关闭、项目切换、IDE 启动和退出。demo 里如果还有状态保存逻辑,十有八九用的是PropertiesComponent,这是 IntelliJ 平台提供的最轻量持久化方案:
PropertiesComponent.getInstance(project).setValue("demo.lastTimestamp", String.valueOf(System.currentTimeMillis()));它把配置存到 IDE 的配置目录,不需要你自己管数据库或文件路径。这里要提醒一句:PropertiesComponent适合存少量业务设置,如果数据量大、结构复杂,用它就是自找麻烦,后面我会讲到什么场景该换 PersistentStateComponent。
4. build.gradle 与调试手法:决定 demo 能不能变成产品的细节
4.1 build.gradle 里决定成败的 5 个配置项
idea插件详细源码demo.zip里的构建脚本通常是基于 Gradle 插件org.jetbrains.intellij的写法。这个插件帮你完成了下载 IDE 依赖、准备沙箱环境、打包插件等一系列任务。核心配置大致是这样:
plugins { id 'java' id 'org.jetbrains.intellij' version '1.x.x' // 以 demo 工程锁定的版本为准 } group = 'com.example' version = '1.0.0' repositories { mavenCentral() } dependencies { testImplementation 'junit:junit:4.13.2' } intellij { version = '2023.1' type = 'IC' plugins = ['com.intellij.java'] } patchPluginXml { sinceBuild = '231' untilBuild = '241.*' }这五个配置项,每一个翻车都能让你卡上一整天。第一,org.jetbrains.intellij的版本不要照抄网上的用法,要和你本地 Gradle 版本匹配,否则会报方法签名错误。第二,intellij.version决定你基于哪个 IDE 版本开发,这个版本会影响 API 的可用性——有些新 API 在老版本里不存在,编译直接失败。第三,type表示 IDE 发行版,IC是社区版、IU是旗舰版,如果你用了旗舰版才有的 API,运行时就会报NoClassDefFoundError。第四,plugins字段用于声明附带的插件依赖,比如开发 Java 相关功能必须加com.intellij.java。第五,patchPluginXml的sinceBuild和untilBuild直接决定这个插件能装到哪些 IDEA 版本上,这个范围写得过宽,插件可能加载后行为异常;写得太窄,用户升级 IDE 后就装不上了。
4.2 调试插件源码的三层手段
runIde跑起来之后,插件代码就在另一个 IDEA 进程里运行了。第一层调试手段也是最常用的,是直接打日志:com.intellij.openapi.diagnostic.Logger。在你自己的类里声明一个静态 Logger 实例,然后调用logger.info(...)、logger.warn(...)。日志会输出到沙箱 IDE 的日志文件里,路径一般在:
~/Library/Logs/JetBrains/IntelliJIdea<版本号>/idea.log (macOS) %LOCALAPPDATA%\JetBrains\IntelliJIdea<版本号>\log\idea.log (Windows)第二层手段是断点调试。runIde默认启动时会开启调试端口,你可以在 IDEA 里新建一个Remote JVM Debug运行配置,host 填 localhost,端口填5005,然后点 Debug 按钮。挂上之后,在 demo 源码里打断点,沙箱 IDE 里触发对应操作,调试器就会停在断点上。这是定位插件问题最高效的方式,比打日志循环验证快得多。
第三层手段是查看 IDE 自带的错误报告。当插件抛出异常时,沙箱 IDEA 会弹出Fatal Errors对话框,里面会列出异常堆栈。很多新手忽略这个对话框直接点掉,然后去源码里瞎猜。实际上堆栈里已经把出错的类、行号、调用链都写清楚了。我一般会先复制堆栈内容,用类名去定位源码,而不是在代码里到处加日志。调试插件有一个关键观念要转过弯:你写的代码跑在 IDE 进程里,IDE 本身也是 Java 程序,所以 OOM、死锁、类加载冲突这些问题它全都会有,只是发生时机和普通 Web 应用完全不同。
5. 避坑:插件 demo 从导入到发布最常见的 6 个翻车现场
5.1 第一类翻车:导入环节就卡住的两条坑
现象:Gradle 同步报错,提示Unsupported class file major version 61或者Failed to apply plugin 'org.jetbrains.intellij'。
原因:这是本机 JDK 版本太老。IDEA 2021.2 之后的平台基于 JDK 17 构建,而不少开发机默认的JAVA_HOME还是 JDK 8。Gradle 进程用 JDK 8 启动,去加载要求 JDK 17 的插件和平台类,自然就翻车了。
解决:到Settings -> Build, Execution, Deployment -> Build Tools -> Gradle,把Gradle JVM切到 JDK 17 或更高。如果你本机没装 JDK 17,直接用 IDEA 自带的 JBR(JetBrains Runtime)也可以,它本质上就是一个 JDK 17。注意改完设置后重启 Gradle 同步,否则缓存里还是旧 JVM。
现象:导入之后 IDEA 提示Plugin 'Plugin DevKit' is required,或者菜单里找不到任何插件开发相关的入口。
原因:旧版 IDEA 把插件开发工具做成了独立插件Plugin DevKit,默认没有安装。如果 demo 的构建方式是基于 DevKit 而不是纯 Gradle,这个插件缺失会导致工程无法识别为插件项目。
解决:打开Settings -> Plugins,在 Marketplace 搜索Plugin DevKit并安装。不过我更推荐直接走 Gradle 方式——org.jetbrains.intellij插件不强制依赖 DevKit,而且打包、sandbox 管理都更方便,这也是如今官方主推的方式。
5.2 第二类翻车:运行期失灵的三个典型症状
现象:runIde启动成功了,但在菜单里找不到 Action,或者点击报错Cannot find class ...。
原因:最常见的是plugin.xml里的class属性写错了全限定名,或者 Action 类没有public构造。IDEA 加载插件时不会在启动阶段就实例化所有 Action,它只做“懒加载”——真正点击菜单时才去反射创建对象。所以类名写错不会让runIde启动失败,只会让按钮在点击时才报错。
解决:确认plugin.xml里 class 的全限定名和实际包路径一致;打开沙箱 IDE 的日志文件,搜索Action或插件 id 相关关键字,定位具体的 ClassNotFoundException。然后顺手养成一个习惯:Action 类里声明一个无参构造器,别依赖任何有参构造。
现象:改了源码之后重新runIde,发现改动没有生效,还是旧行为。
原因:你大概率没有重新构建就直接跑沙箱,而沙箱进程是旧代码启动的。或者你在同一个沙箱进程里开了热部署(动态插件加载),但当前类不支持动态加载。
解决:每次改动代码以后,先执行一次./gradlew build再runIde。如果为了提高效率想用热部署,需要满足两个前提:一是 IntelliJ 平台开启动态插件加载(默认支持部分场景),二是你的插件类不能出现静态初始化逻辑锁死类加载器。最可靠的做法是重启沙箱,别把热部署想得太神,它对于 ToolWindow 这类 UI 组件经常失效。
现象:插件打包出来,在另一台电脑的 IDEA 上安装时提示Plugin requires IDE version X or earlier。
原因:patchPluginXml里untilBuild写得太死,而新版本 IDE 的 build 号超过了声明上限。比如你在 2023.1 上开发,把untilBuild写成了231.0,别人用 2023.2 就装不上。
解决:把sinceBuild设为你开发时所用 IDE 的主版本号,untilBuild写成通配符形式,比如241.*表示允许 2024.1 系列的所有小版本。要测试最低兼容版本,需要换一个老版本 IDE 打开工程重新runIde,这是最费时间的环节,也是插件发布前绝对值得投入的部分。
5.3 第三类翻车:打包与发布前的隐蔽坑
现象:打包后的插件安装时 IDEA 提示Plugin is not compatible with the current IDE,但版本范围明明是对的。
原因:插件依赖了某个特定depends模块,而这个模块不在目标 IDE 里。典型的例子是基于社区版IC开发,但代码里用了旗舰版才有的com.intellij.modules.ultimateAPI。
解决:在plugin.xml的depends里明确声明你依赖的模块,同时把目标type对齐。如果你希望插件同时兼容社区版和旗舰版,就不要用平台类里标注了@Internal或属于旗舰版命名空间的 API,这些类名在编译期可能通过,运行期才炸。
现象:插件发布后功能正常,但启动 IDE 时非常慢,日志里出现大段的Loading plugin took xxx ms。
原因:插件的plugin.xml里注册了大量全局组件,导致 IDE 启动时全部要初始化。全局组件实例和项目无关,却在每个项目打开时都要加载,拖慢整体速度。
解决:只保留必须的 Action 和 ToolWindow 注册,全局监听器改成按需激活方式。比如用ProjectManagerListener注册时,在projectOpened后再做实际初始化,而不是在组件构造时把整个服务的依赖全部拉起。这条优化不会体现在 demo 的功能表面,但在真实产品里或许是用户给出差评的主要原因之一。
6. 从 demo 到能用的插件:三个进阶验证技巧
第一个验证技巧是日志定位法。runIde启动的沙箱 IDEA 会生成一个独立的idea.log文件,里面记录了插件加载、Action 注册、扩展点发现的全部过程。每当你改完plugin.xml不确定某个组件是否注册成功时,直接查日志里Plugin "Demo Plugin" ...这一段,它会明确写出加载耗时的组件的全限定名。看到PluginError级别的内容就该停下来补课,而不是继续写业务代码。
第二个验证技巧是把插件安装到一个“干净”的 IDEA 里测试。不要一直在runIde的沙箱里验证,沙箱环境是全新的,不代表现实场景。更贴近用户的做法是:用./gradlew buildPlugin打出 zip 包,然后在另一台电脑或另一个用户目录下启动一个正式版 IDEA,通过Settings -> Plugins -> Install Plugin from Disk安装。这一步能测出依赖缺失和版本范围两大问题,也最接近用户的第一感。
第三个技巧是兼容性矩阵验证。你开发时的 IDE 版本越新,你的插件对旧版的兼容性越差,这是插件开发里绕不开的规律。我的习惯是维护两张表:一张记录每个 API 用到的 IDE 版本,一张记录目标用户的 IDE 分布。确定最小的sinceBuild之后,用那个旧版本重新跑一遍runIde,把所有功能过一遍。早期我写过一个插件,自测全通过,发布后大量用户反馈菜单消失,最后发现是用了新版本才有的AnActionEvent.getDataContext()返回值判断方式。从那以后我给自己定了一条死规矩:任何一次发布前,至少用两个不同大版本的 IDE 跑一遍核心流程。这条规矩让我后来少收了很多条“插件挂了”的邮件,希望也能帮到你。
本文还有配套的精品资源,点击获取