开发自己的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
:
- 打开
File
>Project Structure
- 选择
SDKs
,点击+
号,选择IntelliJ Platform Plugin SDK
- 选择IDEA安装目录,SDK会自动配置
- 设置
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、常用组件类型
- 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);
}
}
- 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);
}
}
- 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);
}
}
- 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
- 在JetBrains账户 (opens new window)中创建API Token
- 配置环境变量或
gradle.properties
- 执行发布命令:
./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、调试技巧
- 运行调试IDE:使用
./gradlew runIde
启动带有插件的IDEA实例 - 内部动作:启用
Internal Actions
(Help
>Edit Custom Properties
,添加idea.is.internal=true
) - PSI Viewer:使用
Tools
>View PSI Structure
查看代码结构 - UI Inspector:使用
Tools
>Internal Actions
>UI
>UI Inspector
检查UI组件
# 5、优化插件性能
插件的性能对用户体验至关重要。在开发插件时,你应该关注性能优化,确保插件在各种场景下都能运行得足够快。
# 5.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改变时缓存失效
);
});
}
}
- 异步处理与后台任务
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);
}
}
- 延迟初始化
@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;
}
}
- 避免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的生态贡献力量。
祝你变得更强!