IntelliJ IDEA 是目前最好用的 JAVA 开发 IDE,它本身的功能已经非常强大了,但是每个人的需求不一样,有些需求 IDEA 本身无法满足,于是我们就需要自己开发插件来解决。工欲善其事,必先利其器,想要提高开发效率,我们可以借助 IDEA 提供的插件功能来满足我们的需求。如果没有我需要的功能怎么办?很简单,我们自己造一个!
IDEA 的插件几乎可以做任何事情,因为它把 IDE 本身的能力都封装好开放出来了。主要的插件功能包含以下四种:
我为了减少重复代码的编写,写了一个代码生成的插件IDEA代码生成插件CodeMaker,支持自定义代码生成的模板。
依照惯例,我们从 Hello world 开始。
有些教程推荐用 IDEA 默认的插件工程来开始,但是我比较推荐用 Gradle 来管理整个插件工程,后面的依赖管理会很方便,否则都得靠手动管理。
新建完工程之后,IDEA 会自动开始解析项目依赖,因为它要下载一个几百兆的 SDK 依赖包,所以会比较久,打开科学上网能快一点。
Gradle 依赖解析完成之后,项目结构如下图,其中 plugin.xml 是插件的配置,build.gradle 是项目依赖的配置(类比 pom.xml)。
下面就是默认生成的 plugin.xml
com.xiaokai.test.demo
Demo
YourCompany
most HTML tags may be used
]]>
Action 是 IDEA 中对事件响应的处理器,它的 actionPerformed 就像是 JS 中的 onClick 方法。可以看出来,插件的开发本质上跟 web、Android 的开发没有什么不同,因为都是事件驱动的编程。
点击 OK 之后会在 src 生成类文件:
package com.xiaokai.test;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
public class HelloWorldAction extends AnAction {
@Override
public void actionPerformed(AnActionEvent e) {
// TODO: insert action logic here
}
}
同时,动作的信息也会注册到 plugin.xml 中
创建完 Action 之后我们就要开始往里面写逻辑了,既然是 Hello World 教学,那我们就来试一下最简单的弹出对话框。
@Override
public void actionPerformed(AnActionEvent e) {
//获取当前在操作的工程上下文
Project project = e.getData(PlatformDataKeys.PROJECT);
//获取当前操作的类文件
PsiFile psiFile = e.getData(CommonDataKeys.PSI_FILE);
//获取当前类文件的路径
String classPath = psiFile.getVirtualFile().getPath();
String title = "Hello World!";
//显示对话框
Messages.showMessageDialog(project, classPath, title, Messages.getInformationIcon());
}
代码写完之后,打开 Gradle 的界面,点击 runIde 就会启动一个安装了插件的 IDEA,然后就可以进行测试。你还可以右键启动 Debug 模式,这样还能进行断点。
运行的效果如下图:
可以看到,我们右键打开 Generate 菜单之后,里面最后一项就是我们添加的 Action,
如果想学习更多的原理和设计理念可以看IntelliJ Platform SDK的官方文档。不过老实说,它的文档写的挺差的,基本上就是简单讲了一下概念和原理,没有深入的分析。所以如果要深入研究还得靠自己。最靠谱的学习方式就是看别人写的插件,举个例子,你想知道怎么样实现自动生成代码,你就去找支持这个功能的插件,看他的源码是怎么写的。
我当时写CodeMaker的时候也是靠自己啃源码之后写出来的。下面我简单介绍一下我用过的一些 API,这些 API 基本都没有文档说明,全靠代码相传。
//获取当前事件触发时,光标所在的元素
PsiElement psiElement = anActionEvent.getData(LangDataKeys.PSI_ELEMENT);
//如果光标选择的不是类,弹出对话框提醒
if (psiElement == null || !(psiElement instanceof PsiClass)) {
Messages.showMessageDialog(project, "Please focus on a class", "Generate Failed", null);
return;
}
一个类文件中可能会有内部类,所以读取的时候返回的是一个列表
public static List getClasses(PsiElement element) {
List elements = Lists.newArrayList();
List classElements = PsiTreeUtil.getChildrenOfTypeAsList(element, PsiClass.class);
elements.addAll(classElements);
for (PsiClass classElement : classElements) {
//这里用了递归的方式获取内部类
elements.addAll(getClasses(classElement));
}
return elements;
}
public static void reformatJavaFile(PsiElement theElement) {
CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(theElement.getProject());
try {
codeStyleManager.reformat(theElement);
} catch (Exception e) {
LOGGER.error("reformat code failed", e);
}
}
CopyPasteManager.getInstance()
.setContents(new SimpleTransferable(table.toString(), DataFlavor.allHtmlFlavor));
更多的技巧可以参考我的项目CodeMaker,以及其他的开源插件。