1. 简介

近年来,JetBrains 推出的 IntelliJ IDEA 已迅速成为 Java 开发者的首选 IDE。根据最新的 Java 技术生态报告,有 61% 的开发者选择 IntelliJ,相比前一年的 55% 有显著增长。

让 IntelliJ 如此受欢迎的关键特性之一,就是 支持通过插件扩展功能或添加新能力

本文将介绍如何使用当前官方推荐的 Gradle 方式开发一个 IntelliJ 插件,展示几种扩展 IDE 的常见手段。这篇文章是对之前一篇使用 Plugin Devkit 创建插件文章的升级重制版。

2. 插件的主要类型

常见的插件类型主要包括以下几类:

  • 自定义语言支持:支持编写、解析和编译特定语言代码(如 Kotlin、Scala)
  • 框架集成:对第三方框架提供深度支持(如 Spring、MyBatis)
  • 工具集成:与外部工具联动(如 Maven、Gradle、Docker)
  • UI 扩展:新增菜单项、工具窗口、状态栏组件等

⚠️ 实际开发中,一个插件往往同时属于多个类别。例如 IntelliJ 内置的 Git 插件,它不仅提供了独立的工具窗口和右键菜单,还深度集成到项目创建流程、设置界面等多个环节。

3. 创建插件项目

目前官方支持两种插件开发方式。本文采用新项目推荐的 Gradle 构建方式,而非传统的 Plugin Devkit

使用 Gradle 创建插件项目非常简单,通过 IDE 的 New > Project 菜单即可完成:

Bildschirmfoto 2020-05-23

⚠️ 创建时注意:

  • 必须勾选 JavaIntelliJ Platform Plugin 模块
  • 当前插件开发 仅支持 JDK 8,高版本 JDK 可能导致兼容性问题

4. 实战:Stack Overflow 快捷访问插件

我们来开发一个实用小插件:在 IDE 中快速访问 Stack Overflow。功能包括:

  • ✅ 工具菜单中添加“提问”入口
  • ✅ 在编辑器和控制台中通过右键菜单搜索选中文本

4.1 创建 Action

Action 是插件与用户交互的核心机制,可通过菜单、快捷键、工具栏等方式触发。

基础结构

所有自定义 Action 都需继承 AnAction 类。我们先创建打开提问页面的简单 Action:

public class AskQuestionAction extends AnAction {
    @Override
    public void actionPerformed(AnActionEvent e) {
        BrowserUtil.browse("https://stackoverflow.com/questions/ask");
    }
}

这里使用了 SDK 提供的 BrowserUtil,它能跨平台兼容地打开浏览器,省去了手动处理不同 OS 的麻烦。

高级功能:智能搜索

搜索功能需要两个参数:

  1. 当前文件的编程语言(作为搜索标签)
  2. 用户选中的文本内容

获取语言标签(Language Tag)

通过 PSI(Program Structure Interface)API 获取当前文件的语言信息:

Optional<PsiFile> psiFile = Optional.ofNullable(e.getData(LangDataKeys.PSI_FILE));
String languageTag = psiFile.map(PsiFile::getLanguage)
  .map(Language::getDisplayName)
  .map(String::toLowerCase)
  .map(lang -> "[" + lang + "]")
  .orElse("");

PSI 是 IntelliJ 的核心解析引擎,能将代码文件转换为可编程操作的语法树。

获取选中文本

使用 Editor API 获取高亮内容:

Editor editor = e.getRequiredData(CommonDataKeys.EDITOR);
CaretModel caretModel = editor.getCaretModel();
String selectedText = caretModel.getCurrentCaret().getSelectedText();

这个方法在编辑器和控制台中都适用,API 设计很统一。

完整搜索 Action

@Override
public void actionPerformed(@NotNull AnActionEvent e) {
    Optional<PsiFile> psiFile = Optional.ofNullable(e.getData(LangDataKeys.PSI_FILE));
    String languageTag = psiFile.map(PsiFile::getLanguage)
      .map(Language::getDisplayName)
      .map(String::toLowerCase)
      .map(lang -> "[" + lang + "]")
      .orElse("");

    Editor editor = e.getRequiredData(CommonDataKeys.EDITOR);
    CaretModel caretModel = editor.getCaretModel();
    String selectedText = caretModel.getCurrentCaret().getSelectedText();

    BrowserUtil.browse("https://stackoverflow.com/search?q=" + languageTag + selectedText);
}

状态控制:动态启用/禁用

重写 update 方法实现智能状态管理:

@Override
public void update(@NotNull AnActionEvent e) {
    Editor editor = e.getRequiredData(CommonDataKeys.EDITOR);
    CaretModel caretModel = editor.getCaretModel();
    e.getPresentation().setEnabledAndVisible(caretModel.getCurrentCaret().hasSelection());
}

这样当没有选中文本时,右键菜单项会自动变灰,用户体验更自然。

4.2 注册 Action

创建完 Action 后,需要将其注册到 IDE 中。

方式一:通过 plugin.xml(推荐)

src/main/resources/META-INF/plugin.xml 中配置:

<actions>
    <action
      id="StackOverflow.AskQuestion.ToolsMenu"
      class="com.baeldung.intellij.stackoverflowplugin.AskQuestionAction"
      text="Ask Question on Stack Overflow"
      description="Ask a Question on Stack Overflow">
        <add-to-group group-id="ToolsMenu" anchor="last"/>
    </action>
    <action
      id="StackOverflow.Search.Editor"
      class="com.baeldung.intellij.stackoverflowplugin.SearchAction"
      text="Search on Stack Overflow"
      description="Search on Stack Overflow">
        <add-to-group group-id="EditorPopupMenu" anchor="last"/>
    </action>
    <action
      id="StackOverflow.Search.Console"
      class="com.baeldung.intellij.stackoverflowplugin.SearchAction"
      text="Search on Stack Overflow"
      description="Search on Stack Overflow">
        <add-to-group group-id="ConsoleEditorPopupMenu" anchor="last"/>
    </action>
</actions>

✅ 优点:启动时自动注册,配置集中
✅ 支持多位置复用同一 Action(如编辑器和控制台)

方式二:编程式注册

ActionManager.getInstance().registerAction("StackOverflow.SearchAction", new SearchAction());

⚠️ 适用场景:

  • 需要动态注册(如根据远程 API 版本加载不同功能)
  • 插件功能按需加载

❌ 缺点:需额外实现 ApplicationComponent 管理生命周期,复杂度较高

5. 插件测试

插件开发同样需要测试保障质量。

手动测试(推荐)

执行 Gradle 的 runIde 任务:

Bildschirmfoto 2020-05-25

会启动一个沙盒环境的 IntelliJ 实例,可直接验证功能。

单元测试

IntelliJ SDK 提供了无头测试环境,支持使用 JUnit 等框架编写集成测试,且能调用真实的 IDE 组件,无需 mock。

6. 插件发布

Gradle 插件提供了便捷的打包命令:

./gradlew buildPlugin

执行后会在 build/distributions 目录生成 ZIP 包,包含:

  • 编译后的 class 文件
  • 资源文件
  • plugin.xml 配置

发布方式:

  • ✅ 本地安装:通过 IDE 的 Install Plugin from Disk
  • ✅ 公开发布:上传至 JetBrains 插件仓库

最终效果展示:

Bildschirmfoto-2020-05-25

7. 总结

本文通过一个实用的 Stack Overflow 插件,演示了 IntelliJ 插件开发的核心流程。

虽然我们主要聚焦在 Action 的使用上,但 IntelliJ Plugin SDK 还提供了丰富的扩展点,如:

  • 自定义文件类型
  • 代码检查(Inspection)
  • 模板引擎(Live Templates)
  • VCS 集成

想深入学习?建议直接阅读官方 入门指南

完整代码已托管至 GitHub:https://github.com/eugenp/tutorials/tree/master/intelliJ-modules


原始标题:Writing IntelliJ IDEA Plugins Using Gradle