Contents

IntelliJ 插件

1. 简介

在过去几年中,来自 JetBrains 的IntelliJ 迅速成为 Java 开发人员的顶级 IDE。在我们最近的Java 现状报告 中,IntelliJ 是 55% 受访者的首选 IDE,高于一年前的 48%。

使 IntelliJ 如此吸引 Java 开发人员的一个特性是能够使用插件扩展和创建新功能。在本教程中,我们将着眼于编写一个 IntelliJ 插件来演示扩展 IDE 的一些方法。

请注意,虽然本文重点介绍 IntelliJ 插件,但所有 JetBrains IDE 都共享通用代码。因此,**此处使用的许多技术可以应用于其他 JetBrain 的 IDE,**例如 PyCharm、RubyMine 等。

2. 插件功能

IntelliJ 的插件功能通常属于 4 类之一:

  • 自定义语言:编写、解释和编译用不同语言编写的代码的能力
  • 框架:支持 3rd 方框架,例如 Spring
  • 工具:与外部工具集成,例如 Gradle
  • 用户界面附加组件:新菜单项、工具窗口和按钮等

插件通常会分为多个类别。例如, IntelliJ 附带的Git 插件与系统上安装的 git 可执行文件交互。 该插件提供其工具窗口和弹出菜单项,同时还集成到项目创建工作流程、首选项窗口等中。

3. 创建一个插件

开始使用 IntelliJ 插件的最简单方法是使用他们的Plugin DevKit 。这可以从New > Project菜单访问:

/uploads/intellij_new_custom_plugin/1.jpg

请注意,我们必须使用 JetBrains JDK来确保所需的插件类在类路径中可用。默认情况下,IntelliJ 应该带有合适的 JDK,但如果没有,我们可以从这里 下载一个。

在撰写本文时,我们只能使用 Java 8 来编写 IntelliJ 插件。这是因为 JetBrains 当前不为 Java 9 或更高版本提供官方 JDK。

4. 示例插件

为了演示编写 IntelliJ 插件,我们将创建一个插件,它提供从 IDE 中的多个区域快速访问流行的 Stack Overflow 网站。我们将添加:

  • 用于访问“提问”页面的“工具”菜单项
  • 文本编辑器和控制台输出中的弹出菜单项,用于在 Stack Overflow 中搜索突出显示的文本。

4.1. 创建动作

Actions 是用于编写 IntelliJ 插件的核心组件。操作由 IDE 中的事件触发,例如单击菜单项或工具栏按钮。

创建动作的第一步是创建一个扩展AnAction的 Java 类。对于我们的 Stack Overflow 插件,我们将创建 2 个操作。

第一个操作在新的浏览器窗口中打开提问页面:

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

我们使用内置的BrowserUtil类,因为它处理在不同操作系统和浏览器上打开网页的所有细微差别。

第二个操作打开 Stack Overflow 搜索页面并将搜索文本作为查询字符串传递。这次我们将实现两种方法。

我们实现的第一个方法就像我们的第一个动作一样,处理打开网络浏览器。

不过,首先,我们需要为 StackOverflow 收集两个值。一个是语言标签,另一个是要搜索的文本。

要获取语言标签,我们将使用Program Structure Interface此 API 解析项目中的所有文件,并提供一种编程方式来检查它们。

在这种情况下,我们使用 PSI 来确定文件的编程语言:

PsiFile file = e.getData(CommonDataKeys.PSI_FILE);
Language lang = e.getData(CommonDataKeys.PSI_FILE).getLanguage();
String languageTag = "+[" + lang.getDisplayName().toLowerCase() + "]";

请注意,PSI 还提供有关文件的特定于语言的详细信息。例如,我们可以使用 PSI 来查找 Java 类中的所有公共方法。

为了获取要搜索的文本,我们将使用 Editor API 来检索屏幕上突出显示的文本:

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

尽管此操作对于编辑器窗口和控制台窗口都是相同的,但访问所选文本的方式是相同的。

现在,我们可以将所有这些放在一个 actionPerformed声明中:

@Override
public void actionPerformed(AnActionEvent e) {
    PsiFile file = e.getData(CommonDataKeys.PSI_FILE);
    Language lang = e.getData(CommonDataKeys.PSI_FILE).getLanguage();
    String languageTag = "+[" + lang.getDisplayName().toLowerCase() + "]";
    Editor editor = e.getRequiredData(CommonDataKeys.EDITOR);
    CaretModel caretModel = editor.getCaretModel();
    String selectedText = caretModel.getCurrentCaret().getSelectedText()
    String query = selectedText.replace(' ', '+') + languageTag;
    BrowserUtil.browse("https://stackoverflow.com/search?q=" + query);
}

此操作还覆盖了名为update的第二个方法。这允许我们在不同条件下启用或禁用操作。

在这种情况下,我们在没有选定文本时禁用搜索操作:

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

4.2. 注册动作

编写完操作后,我们需要将它们注册到 IDE 中。有两种方法可以做到这一点。

第一种方法是使用plugin.xml 文件,它是在我们开始一个新项目时为我们创建的。

默认情况下,该文件将有一个空的<actions>元素,这是我们将添加我们的动作的地方:

<actions>
    <action 
      id="StackOverflow.AskQuestion.ToolsMenu"
      class="com.blogdemo.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.blogdemo.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.blogdemo.intellij.stackoverflowplugin.SearchAction"
      text="Search on Stack Overflow"
      description="Search on Stack Overflow">
        <add-to-group group-id="ConsoleEditorPopupMenu" anchor="last"/>
    </action>
</actions>

使用 XML 文件注册操作将确保它们在 IDE 启动期间注册,这通常是更可取的。

第二种注册动作的方法是以编程方式使用ActionManager类:

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

这具有让我们动态注册操作的优势。例如,如果我们编写一个插件来与远程 API 集成,我们可能希望根据调用的 API 版本注册一组不同的操作。

这种方法的缺点是动作不会在启动时注册。我们必须创建一个ApplicationComponent的实例来管理操作,这需要更多的编码和 XML 配置。

5. 测试插件

与任何程序一样,编写 IntelliJ 插件需要测试。对于像我们编写的插件这样的小插件,确保插件编译并且我们创建的操作在我们单击它们时按预期工作就足够了。

我们可以使用插件运行配置手动测试(和调试)我们的插件:

/uploads/intellij_new_custom_plugin/3.jpg

这将启动一个新的 IntelliJ 实例,并激活我们的插件。这使我们能够单击我们创建的不同菜单项,并确保打开正确的 Stack Overflow 页面。

如果您希望进行更传统的单元测试,IntelliJ 提供了一个环境 来运行单元测试。我们可以使用我们想要的任何测试框架编写测试,并且测试使用来自 IDE 的真实的、未模拟的组件运行。

6. 部署插件

插件 DevKit 提供了一种打包插件的简单方法,因此我们可以安装和分发它们。只需右键单击插件项目并选择“为部署准备插件模块”。这将在项目目录中生成一个 JAR 文件。

生成的 JAR 文件包含加载到 IntelliJ 中所需的代码和配置文件。您可以在本地安装它,也可以将它发布到插件存储库 以供其他人使用。

下面的屏幕截图显示了正在运行的新 Stack Overflow 菜单项之一:

/uploads/intellij_new_custom_plugin/5.jpg