轩辕李的博客 轩辕李的博客
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • HTML&CSS
  • JavaScript
  • 分布式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

轩辕李

勇猛精进,星辰大海
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • HTML&CSS
  • JavaScript
  • 分布式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Java

  • Spring

  • 其他语言

  • 工具

    • 从Eclipse到IDEA,金字塔到太空堡垒
    • 开发自己的IDEA插件
      • 一、为什么要开发IDEA插件
      • 二、插件开发准备工作
        • 1、安装 IntelliJ IDEA
        • 2、学习 IntelliJ Platform SDK 文档
        • 3、配置开发环境
      • 三、创建插件项目
        • 1、新建插件项目
        • 2、配置插件项目
        • 3、插件项目结构
      • 四、插件开发基础
        • 1、编写插件代码
        • 1.1、常用组件类型
        • 2、注册插件功能
        • 3、使用Gradle构建插件
        • 4、打包与发布插件
        • 4.1、本地打包
        • 4.2、发布到JetBrains Marketplace
      • 五、高级功能开发
        • 1、PSI(Program Structure Interface)操作
        • 2、代码生成与模板
        • 3、自定义Inspection(代码检查)
      • 六、插件开发实践与技巧
        • 1、了解IntelliJ Platform SDK的API
        • 2、参考其他插件的源代码
        • 3、内置插件的应用
        • 4、使用调试功能
        • 4.1、调试技巧
        • 5、优化插件性能
        • 5.1、性能优化最佳实践
        • 6、关注用户体验
        • 7、测试插件
        • 8、LivePlugin
      • 七、常见问题与解决方案
        • 1、Q1: 插件在新版本IDEA中不兼容
        • 2、Q2: 获取不到项目或编辑器实例
        • 3、Q3: PSI修改不生效
        • 4、Q4: 插件启动缓慢
      • 八、学习资源
      • 九、总结
    • Maven实战
    • Git使用技巧
  • 后端
  • 工具
轩辕李
2022-04-01
目录

开发自己的IDEA插件

IntelliJ IDEA对于Java开发者来说是一款友好且强大的集成开发环境(IDE)。
IntelliJ IDEA提供了丰富的插件扩展,可以帮助开发者在不同的场景下提高生产力。
在工具篇中我介绍IDEA的常用操作,不过当你使用了自己的基础框架,这时候就需要自定义的插件了。
本文将详细介绍IDEA插件开发的过程和技巧,帮助你快速入门IDEA插件开发。

# 一、为什么要开发IDEA插件

开发IDEA插件可以带来以下好处:

  • 提高开发效率:自动化重复性任务,如代码生成、模板创建等
  • 集成第三方工具:将外部工具和服务集成到IDE中,实现无缝工作流
  • 定制化功能:为团队或项目定制专属功能,如代码规范检查、框架支持等
  • 学习平台架构:深入了解IntelliJ Platform的设计理念和架构模式

# 二、插件开发准备工作

# 1、安装 IntelliJ IDEA

为了进行插件开发,你需要安装IntelliJ IDEA。你可以选择社区版(免费)或者旗舰版(收费)。插件开发主要依赖于IntelliJ Platform SDK,这个SDK在社区版和旗舰版中都包含。

下载地址:Download (opens new window)

# 2、学习 IntelliJ Platform SDK 文档

IntelliJ Platform SDK文档是学习插件开发的重要资源。你可以在JetBrains官方网站找到相关文档:Docs (opens new window)

这里包含了插件开发的基础知识、插件项目结构、API使用示例等内容。强烈建议你在开始插件开发前通读相关文档。

# 3、配置开发环境

开发IDEA插件需要配置IntelliJ Platform Plugin SDK:

  1. 打开File > Project Structure
  2. 选择SDKs,点击+号,选择IntelliJ Platform Plugin SDK
  3. 选择IDEA安装目录,SDK会自动配置
  4. 设置JDK版本(建议使用JDK 17或更高版本)

# 三、创建插件项目

# 1、新建插件项目

打开IntelliJ IDEA,选择File > New > Project。在新建项目的对话框中,选择IntelliJ Platform Plugin,然后点击Next。

# 2、配置插件项目

在项目配置页面,你需要输入插件的基本信息,包括插件名称、存储位置等。在SDK选项中,选择之前配置好的IntelliJ Platform Plugin SDK。

# 3、插件项目结构

创建好插件项目后,你可以看到以下目录结构:

my-plugin/
├── src/
│   └── main/
│       ├── java/           # Java源代码
│       └── resources/       # 资源文件
│           └── META-INF/
│               └── plugin.xml  # 插件配置文件
├── build.gradle.kts        # Gradle构建脚本
└── gradle.properties       # Gradle配置

关键文件说明:

  • plugin.xml: 插件描述文件,定义插件的元信息、扩展点、依赖等
  • build.gradle.kts: 使用Gradle IntelliJ Plugin进行构建配置
  • 源代码目录:包含Action、Service、Extension等实现类

# 四、插件开发基础

# 1、编写插件代码

插件的核心代码位于src目录下。你可以根据需求创建Java或Kotlin类,实现插件的功能。

# 1.1、常用组件类型

  1. Action类:用于执行插件功能的主要代码
public class MyAction extends AnAction {
    @Override
    public void actionPerformed(@NotNull AnActionEvent e) {
        // 获取当前项目
        Project project = e.getProject();
        if (project == null) return;
        
        // 显示通知
        NotificationGroupManager.getInstance()
            .getNotificationGroup("MyPlugin.Notification")
            .createNotification("Hello from MyPlugin!", NotificationType.INFORMATION)
            .notify(project);
    }
}
  1. Service类:提供全局或项目级别的服务
@Service
public final class MyProjectService {
    private final Project myProject;
    
    public MyProjectService(Project project) {
        this.myProject = project;
    }
    
    public void doSomething() {
        // 服务逻辑
    }
    
    public static MyProjectService getInstance(Project project) {
        return project.getService(MyProjectService.class);
    }
}
  1. ToolWindow类:创建工具窗口
public class MyToolWindowFactory implements ToolWindowFactory {
    @Override
    public void createToolWindowContent(@NotNull Project project, 
                                       @NotNull ToolWindow toolWindow) {
        MyToolWindow myToolWindow = new MyToolWindow(project);
        ContentFactory contentFactory = ContentFactory.SERVICE.getInstance();
        Content content = contentFactory.createContent(
            myToolWindow.getContent(), "", false);
        toolWindow.getContentManager().addContent(content);
    }
}
  1. FileType类:支持新的文件类型
public class MyFileType extends LanguageFileType {
    public static final MyFileType INSTANCE = new MyFileType();
    
    private MyFileType() {
        super(MyLanguage.INSTANCE);
    }
    
    @NotNull
    @Override
    public String getName() {
        return "My File";
    }
    
    @NotNull
    @Override
    public String getDescription() {
        return "My custom file type";
    }
    
    @NotNull
    @Override
    public String getDefaultExtension() {
        return "myext";
    }
    
    @Nullable
    @Override
    public Icon getIcon() {
        return MyIcons.FILE;
    }
}

# 2、注册插件功能

在plugin.xml文件中,你需要注册插件的各种功能。以下是一个完整的配置示例:

<idea-plugin>
    <id>com.example.myplugin</id>
    <name>My Plugin</name>
    <vendor>Your Name</vendor>
    <version>1.0.0</version>
    
    <!-- 插件描述 -->
    <description><![CDATA[
        This plugin provides custom functionality for...
    ]]></description>
    
    <!-- 兼容性设置 -->
    <depends>com.intellij.modules.platform</depends>
    <depends optional="true">com.intellij.java</depends>
    
    <!-- 扩展点注册 -->
    <extensions defaultExtensionNs="com.intellij">
        <!-- 注册服务 -->
        <projectService serviceImplementation="com.example.MyProjectService"/>
        
        <!-- 注册工具窗口 -->
        <toolWindow id="MyToolWindow" 
                   anchor="right" 
                   factoryClass="com.example.MyToolWindowFactory"/>
        
        <!-- 注册文件类型 -->
        <fileType name="My File" 
                 implementationClass="com.example.MyFileType" 
                 fieldName="INSTANCE" 
                 language="MyLang" 
                 extensions="myext"/>
        
        <!-- 注册通知组 -->
        <notificationGroup id="MyPlugin.Notification" 
                          displayType="BALLOON"/>
    </extensions>
    
    <!-- Action注册 -->
    <actions>
        <action id="MyPlugin.MyAction" 
               class="com.example.MyAction" 
               text="My Custom Action" 
               description="Execute my custom action">
            <add-to-group group-id="ToolsMenu" anchor="last"/>
            <keyboard-shortcut keymap="$default" 
                             first-keystroke="ctrl alt M"/>
        </action>
        
        <!-- Action组 -->
        <group id="MyPlugin.ActionGroup" 
              text="My Plugin" 
              popup="true">
            <add-to-group group-id="MainMenu" anchor="last"/>
            <action id="MyPlugin.Action1" 
                   class="com.example.Action1" 
                   text="Action 1"/>
            <separator/>
            <action id="MyPlugin.Action2" 
                   class="com.example.Action2" 
                   text="Action 2"/>
        </group>
    </actions>
</idea-plugin>

# 3、使用Gradle构建插件

现代IDEA插件开发推荐使用Gradle进行构建。配置build.gradle.kts文件:

plugins {
    id("java")
    id("org.jetbrains.intellij") version "1.16.0"
}

group = "com.example"
version = "1.0.0"

repositories {
    mavenCentral()
}

// 配置IntelliJ Platform
intellij {
    version.set("2023.3")
    type.set("IC") // IC为社区版,IU为旗舰版
    plugins.set(listOf("java"))
}

tasks {
    // 设置JVM兼容性
    withType<JavaCompile> {
        sourceCompatibility = "17"
        targetCompatibility = "17"
    }
    
    patchPluginXml {
        sinceBuild.set("223")
        untilBuild.set("233.*")
    }
    
    signPlugin {
        certificateChain.set(System.getenv("CERTIFICATE_CHAIN"))
        privateKey.set(System.getenv("PRIVATE_KEY"))
        password.set(System.getenv("PRIVATE_KEY_PASSWORD"))
    }
    
    publishPlugin {
        token.set(System.getenv("PUBLISH_TOKEN"))
    }
}

# 4、打包与发布插件

# 4.1、本地打包

# 构建插件
./gradlew buildPlugin

# 运行IDE进行调试
./gradlew runIde

# 验证插件
./gradlew verifyPlugin

# 4.2、发布到JetBrains Marketplace

  1. 在JetBrains账户 (opens new window)中创建API Token
  2. 配置环境变量或gradle.properties
  3. 执行发布命令:
./gradlew publishPlugin

插件发布后,用户可以在IDEA的Settings > Plugins > Marketplace中搜索安装。

# 五、高级功能开发

# 1、PSI(Program Structure Interface)操作

PSI是IntelliJ Platform的核心概念,用于表示源代码的结构:

public class PsiExample 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;
        
        // 获取当前文件的PSI
        PsiFile psiFile = PsiDocumentManager.getInstance(project)
            .getPsiFile(editor.getDocument());
        
        if (psiFile instanceof PsiJavaFile) {
            PsiJavaFile javaFile = (PsiJavaFile) psiFile;
            
            // 遍历所有类
            for (PsiClass psiClass : javaFile.getClasses()) {
                // 获取所有方法
                for (PsiMethod method : psiClass.getMethods()) {
                    System.out.println("Method: " + method.getName());
                }
            }
        }
    }
}

# 2、代码生成与模板

创建代码生成器,自动生成样板代码:

public class CodeGenerator {
    public static void generateGetterSetter(PsiClass psiClass, PsiField field) {
        Project project = psiClass.getProject();
        PsiElementFactory factory = JavaPsiFacade.getInstance(project)
            .getElementFactory();
        
        // 生成Getter方法
        String getterName = "get" + capitalize(field.getName());
        PsiMethod getter = factory.createMethod(getterName, field.getType());
        getter.getModifierList().setModifierProperty(PsiModifier.PUBLIC, true);
        getter.getBody().add(factory.createStatementFromText(
            "return " + field.getName() + ";", null));
        
        // 生成Setter方法
        String setterName = "set" + capitalize(field.getName());
        PsiMethod setter = factory.createMethod(setterName, PsiType.VOID);
        setter.getModifierList().setModifierProperty(PsiModifier.PUBLIC, true);
        PsiParameter parameter = factory.createParameter(
            field.getName(), field.getType());
        setter.getParameterList().add(parameter);
        setter.getBody().add(factory.createStatementFromText(
            "this." + field.getName() + " = " + field.getName() + ";", null));
        
        // 添加到类中
        WriteCommandAction.runWriteCommandAction(project, () -> {
            psiClass.add(getter);
            psiClass.add(setter);
        });
    }
}

# 3、自定义Inspection(代码检查)

创建自定义代码检查规则:

public class MyInspection extends AbstractBaseJavaLocalInspectionTool {
    @NotNull
    @Override
    public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, 
                                         boolean isOnTheFly) {
        return new JavaElementVisitor() {
            @Override
            public void visitMethod(PsiMethod method) {
                super.visitMethod(method);
                
                // 检查方法名是否符合规范
                String methodName = method.getName();
                if (!methodName.matches("[a-z][a-zA-Z0-9]*")) {
                    holder.registerProblem(
                        method.getNameIdentifier(),
                        "Method name should start with lowercase letter",
                        ProblemHighlightType.WARNING,
                        new RenameQuickFix()
                    );
                }
            }
        };
    }
    
    private static class RenameQuickFix implements LocalQuickFix {
        @Nls
        @NotNull
        @Override
        public String getFamilyName() {
            return "Rename to follow naming convention";
        }
        
        @Override
        public void applyFix(@NotNull Project project, 
                            @NotNull ProblemDescriptor descriptor) {
            PsiElement element = descriptor.getPsiElement();
            if (element != null) {
                // 触发重命名重构
                RefactoringFactory.getInstance(project)
                    .createRename(element, suggestNewName(element.getText()))
                    .run();
            }
        }
    }
}

# 六、插件开发实践与技巧

# 1、了解IntelliJ Platform SDK的API

熟练使用IntelliJ Platform SDK的API是插件开发的关键。你应该深入了解SDK提供的各种类和方法,以便在实际开发中快速找到合适的API。

# 2、参考其他插件的源代码

阅读和分析其他插件的源代码是学习插件开发的好方法。你可以在GitHub或JetBrains Plugin Repository中找到许多开源插件,这些插件涵盖了不同的功能和技术领域,可以为你的插件开发提供灵感和借鉴。

# 3、内置插件的应用

IDEA本身提供了丰富的内置插件,这些插件支撑了IDEA的强大功能。
比如Freemarker插件、Spring插件、JSP插件等。但是这些插件的API不显于官方文档之中,如果你要用到其中的PsiElement或PsiFile的实现,是让人困扰的。
这里没有什么好办法,先把Platform SDK基础阅读一遍,然后根据实际进行调试,才能对他们进行娴熟的应用。

# 4、使用调试功能

IntelliJ IDEA提供了丰富的调试功能,可以帮助你快速定位和解决插件中的问题。

# 4.1、调试技巧

  1. 运行调试IDE:使用./gradlew runIde启动带有插件的IDEA实例
  2. 内部动作:启用Internal Actions(Help > Edit Custom Properties,添加idea.is.internal=true)
  3. PSI Viewer:使用Tools > View PSI Structure查看代码结构
  4. UI Inspector:使用Tools > Internal Actions > UI > UI Inspector检查UI组件

# 5、优化插件性能

插件的性能对用户体验至关重要。在开发插件时,你应该关注性能优化,确保插件在各种场景下都能运行得足够快。

# 5.1、性能优化最佳实践

  1. 使用缓存机制
public class CachedDataService {
    private final Map<String, Object> cache = new ConcurrentHashMap<>();
    
    public Object getData(String key) {
        return cache.computeIfAbsent(key, k -> {
            // 耗时的计算
            return expensiveComputation(k);
        });
    }
    
    // 使用CachedValuesManager进行更智能的缓存
    public static PsiMethod[] getCachedMethods(PsiClass psiClass) {
        return CachedValuesManager.getCachedValue(psiClass, () -> {
            PsiMethod[] methods = psiClass.getMethods();
            return CachedValueProvider.Result.create(
                methods, 
                psiClass // 依赖项,当psiClass改变时缓存失效
            );
        });
    }
}
  1. 异步处理与后台任务
public class AsyncOperations {
    // 在后台线程执行
    public static void runInBackground(Project project, Runnable task) {
        ProgressManager.getInstance().run(
            new Task.Backgroundable(project, "Processing...", true) {
                @Override
                public void run(@NotNull ProgressIndicator indicator) {
                    indicator.setText("Loading data...");
                    task.run();
                }
            }
        );
    }
    
    // 使用ReadAction/WriteAction
    public static void safeRead(Runnable readTask) {
        ApplicationManager.getApplication().runReadAction(readTask);
    }
    
    public static void safeWrite(Project project, Runnable writeTask) {
        WriteCommandAction.runWriteCommandAction(project, writeTask);
    }
}
  1. 延迟初始化
@Service
public final class LazyService {
    private volatile ExpensiveResource resource;
    
    public ExpensiveResource getResource() {
        if (resource == null) {
            synchronized (this) {
                if (resource == null) {
                    resource = new ExpensiveResource();
                }
            }
        }
        return resource;
    }
}
  1. 避免EDT阻塞
public class EdtSafeOperations {
    public static void performSafely(Runnable task) {
        if (ApplicationManager.getApplication().isDispatchThread()) {
            // 已在EDT,直接执行
            task.run();
        } else {
            // 不在EDT,切换到EDT执行
            ApplicationManager.getApplication().invokeLater(task);
        }
    }
}

# 6、关注用户体验

良好的用户体验是插件成功的关键。在开发插件时,你应该关注以下几个方面的用户体验:

  • 易用性:插件的功能应该容易上手,用户能够快速理解和使用。你可以通过设计简洁的界面、提供详细的帮助文档等方式提高插件的易用性。
  • 可配置性:插件应该允许用户根据自己的需求进行配置。你可以在插件中提供配置界面,让用户自定义插件的行为、外观等属性。
  • 反馈与支持:插件应该提供充分的反馈信息,帮助用户了解插件的运行状态。同时,你应该为插件提供良好的支持,如及时回应用户问题、修复bug等。

# 7、测试插件

编写单元测试和集成测试确保插件质量:

public class MyActionTest extends BasePlatformTestCase {
    public void testActionExecution() {
        // 创建测试项目
        Project project = getProject();
        
        // 创建测试文件
        PsiFile file = createFile("Test.java", 
            "public class Test { }\n");
        
        // 创建Action事件
        AnActionEvent event = createTestActionEvent(project, file);
        
        // 执行Action
        MyAction action = new MyAction();
        action.actionPerformed(event);
        
        // 验证结果
        assertEquals("Expected result", getResultFromProject(project));
    }
    
    private AnActionEvent createTestActionEvent(Project project, PsiFile file) {
        DataContext dataContext = dataId -> {
            if (CommonDataKeys.PROJECT.is(dataId)) return project;
            if (CommonDataKeys.PSI_FILE.is(dataId)) return file;
            return null;
        };
        return AnActionEvent.createFromDataContext(
            "test", null, dataContext);
    }
}

# 8、LivePlugin

LivePlugin是一种IntelliJ IDEA插件,它允许您在应用程序运行时编写和运行Groovy脚本。具体来说,它为您提供了一个面板,您可以在其中编写脚本,并在IDEA中运行它们,而无需停止和重新启动应用程序。

通过使用LivePlugin,您可以更轻松地对应用程序进行实验和调试,因为您可以立即在应用程序中测试和执行脚本,而无需在应用程序中重新编译和构建代码。

此外,LivePlugin还支持自定义工具栏按钮和快捷键,以便更轻松地执行脚本和其他操作。

但是这个LivePlugin的使用也会用到IntelliJ Platform SDK API,所以需要你对插件体系有一个基础认知。

下面是一个把width替换为style的示例,主要解决的问题是旧项目中td使用了过期属性width,要快捷替换为style width:

package width2style

import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.editor.SelectionModel

import static liveplugin.PluginUtil.*

registerAction("width2style","ctrl alt Q", "EditorPopupMenu","width2style") { AnActionEvent event ->
    def project = event?.project
    def editor = CommonDataKeys.EDITOR.getData(event.dataContext)
    if (project == null || editor == null) return

    // 获取选中的文本
    SelectionModel selectionModel = editor.getSelectionModel();
    String selectedText = selectionModel.getSelectedText();

    String searchText = 'width="([^"]*)"'
    String replacementText = 'style="width:$1;"'
    String replacedText = selectedText.replaceAll(searchText,replacementText)

    int start = selectionModel.getSelectionStart();
    int end = selectionModel.getSelectionEnd();

    // Document modifications must be done inside "commands" which will support undo/redo functionality.
    runDocumentWriteAction(event.project, editor.document, "width2style") { document ->
        // 用替换后的文本更新选中的内容
        document.replaceString(start, end, replacedText);
    }

}

可以看到,LivePlugin提供了注册Action的便捷方式,闭包中的代码就是实现功能的代码。

LivePlugin适合编写轻量级的IDEA插件。

# 七、常见问题与解决方案

# 1、Q1: 插件在新版本IDEA中不兼容

解决方案:在plugin.xml中正确配置版本范围:

<idea-version since-build="223" until-build="241.*"/>

# 2、Q2: 获取不到项目或编辑器实例

解决方案:始终进行空值检查:

Project project = e.getProject();
if (project == null) {
    Messages.showErrorDialog("No project found", "Error");
    return;
}

# 3、Q3: PSI修改不生效

解决方案:确保在WriteCommandAction中执行修改:

WriteCommandAction.runWriteCommandAction(project, () -> {
    // PSI修改代码
});

# 4、Q4: 插件启动缓慢

解决方案:

  • 使用<depends optional="true">声明可选依赖
  • 延迟初始化重量级组件
  • 使用ProjectManagerListener延迟加载项目相关功能

# 八、学习资源

  • 官方文档:IntelliJ Platform SDK (opens new window)
  • 示例代码:IntelliJ Platform Plugin Template (opens new window)
  • 社区论坛:JetBrains Developer Community (opens new window)
  • 视频教程:JetBrains TV (opens new window)
  • 开源插件:在GitHub搜索intellij-plugin标签查看优秀插件源码

# 九、总结

IntelliJ IDEA插件开发是一个有趣且具有挑战性的领域,它可以帮助你更深入地了解IntelliJ IDEA这款强大的开发工具,并为其他开发者提供有价值的功能。

通过学习本文的知识和技巧,你应该能够迅速入门IDEA插件开发,掌握插件项目的创建、编写、调试、发布等全过程。重要的是要:

  • 深入理解PSI和Virtual File System等核心概念
  • 熟练使用Action、Service、Extension Point等组件
  • 注重性能优化,避免阻塞EDT
  • 编写测试保证插件质量
  • 关注用户体验,提供友好的交互

希望你能在插件开发的道路上取得成功,为IntelliJ IDEA的生态贡献力量。

祝你变得更强!

编辑 (opens new window)
#IDEA插件
上次更新: 2025/08/15
从Eclipse到IDEA,金字塔到太空堡垒
Maven实战

← 从Eclipse到IDEA,金字塔到太空堡垒 Maven实战→

最近更新
01
AI时代的编程心得
09-11
02
Claude Code与Codex的协同工作
09-01
03
Claude Code实战之供应商切换工具
08-18
更多文章>
Theme by Vdoing | Copyright © 2018-2025 京ICP备2021021832号-2 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式