赞
踩
idea这些插件都是怎么开发的?本文手把手带你开发
IDEA插件开发,注意JDK版本:
IDEA 2020.3 以上版本插件开发时,需要使用 Java 11。
IDEA 2022.2 及更高版本插件开发时,需要使用 Java 17
1、在新建时配置idea SDK
配完成sdk,点下一步填写项目名就可以新建项目了
2、在项目新建完成之后也可以配置SDK
项目目录结构如下:
plugin.xml 中的内容,在插件安装后的对应位置,如下
IntlliJ的插件由一个个Action组成,而我们编程的入口也是这个Action。
这里我们写一个弹出框试试。
新建完成后,plugin.xml里面会生成一个action标签,与该插件安装后对应关系如下
除了plugin.xml有变动,还会生成一个HelloBoy 类,我们给他添加如下代码
public class HelloBoy extends AnAction {
@Override
public void actionPerformed(AnActionEvent e) {
Project project = e.getData(PlatformDataKeys.PROJECT);
String title = "boy";
String msg = "Hello,boy";
Messages.showMessageDialog(project, msg, title, Messages.getInformationIcon());
}
}
点击运行
在弹出的idea项目中,点击菜单栏上面的Tools
打包,项目更目录下会生成jar包
安装插件
安装完成,可能需要重启一下idea,然后就能顺利看到这个弹筐了
Action官方文档: idea Action官方文档
plugin.xml action示例
<idea-plugin> <id>com.idea.hello</id> <name>插件名字写在这里</name> <version>1.0</version> <vendor email="邮箱@qq.com" url="http://www.666.com">666有限公司</vendor> <description> <![CDATA[ 这里的内容是插件描述,<b>支持HTML标签</b>,<br>这里你最少要写40个字知道不,否则它就会有红色的波浪线 ]]> </description> <change-notes> <![CDATA[ 这里是更新提示,<b>这里也支持HTML标签</b>,<br>这里也要至少有40个字,否则就会有红色波浪线 ]]> </change-notes> <!-- 支持的idea版本 --> <idea-version since-build="173.0"/> <!-- 依赖 --> <depends>com.intellij.modules.platform</depends> <extensions defaultExtensionNs="com.intellij"> <!-- Add your extensions here --> </extensions> <actions> <group popup="true" id="HelloWorld" text="sayMenu"> <add-to-group group-id="EditorPopupMenu" anchor="last" /> </group> <action class="HelloBoy" id="helloAction" text="sayHello"> <add-to-group group-id="MainMenu" anchor="first" /> </action> </actions> </idea-plugin>
plugin.xml扩展点示例
<!-- 可选,"url"属性指定了到插件主页的链接,它显示在市场的插件页面上 --> <idea-plugin url="https://example.com/my-plugin-site"> <id>com.example.myplugin</id> <name>插件名</name> <version>1.0.0</version> <!-- 供应商名称或组织ID(如果已创建)。 属性: "url"(可选)-指定到供应商主页的链接 "email"(可选)—指定供应商的电子邮件地址 在“插件”页面显示。 --> <vendor url="https://www.66.com" email="demo@example.com">公司名</vendor> <!-- 收费插件使用改标签,你也可以在付费插件中启用免费功能。在销售插件指南:https://plugins.jetbrains.com/build-and-market --> <product-descriptor code="PMYPLUGIN" release-date="20221111" release-version="20221" optional="true"/> <!-- 兼容idea的最大、最小版本 --> <idea-version since-build="193" until-build="193.*"/> <description> <![CDATA[ 这里的内容是插件描述,<b>支持HTML标签</b>,<br>这里你最少要写40个字知道不,否则它就会有红色的波浪线 ]]> </description> <change-notes><![CDATA[ 这里是更新提示,<b>这里也支持HTML标签</b>,<br>这里也要至少有40个字,否则就会有红色波浪线 ]]> </change-notes> <!-- 依赖,更多查阅:https://plugins.jetbrains.com/docs/intellij/plugin-compatibility.html --> <depends>com.intellij.modules.platform</depends> <depends>com.example.third-party-plugin</depends> <!-- 依赖于 已安装插件 "com.example.my-second-plugin" --> <depends optional="true" config-file="mysecondplugin.xml">com.example.my-second-plugin</depends> <!-- 绑定资源 (/messages/MyPluginBundle.properties) ,可以被用在扩展点和隐式主键上,如 "action.[ActionID].text|description" --> <resource-bundle>messages.MyPluginBundle</resource-bundle> <!-- 扩展点 --> <extensionPoints> <extensionPoint name="testExtensionPoint" beanClass="com.example.impl.MyExtensionBean"/> <applicationService serviceImplementation="com.example.impl.MyApplicationService"/> <projectService serviceImplementation="com.example.impl.MyProjectService"/> </extensionPoints> <!-- Application-level 监听器 --> <applicationListeners> <listener class="com.example.impl.MyListener" topic="com.intellij.openapi.vfs.newvfs.BulkFileListener"/> </applicationListeners> <!-- Project-level 监听器 --> <projectListeners> <listener class="com.example.impl.MyToolwindowListener" topic="com.intellij.openapi.wm.ex.ToolWindowManagerListener"/> </projectListeners> <actions> <action id="VssIntegration.GarbageCollection" class="com.example.impl.CollectGarbage" text="Collect _Garbage" description="Run garbage collector"> <keyboard-shortcut first-keystroke="control alt G" second-keystroke="C" keymap="$default"/> </action> </actions> <!-- 扩展点声明 --> <extensions defaultExtensionNs="VssIntegration"> <myExtensionPoint implementation="com.example.impl.MyExtensionImpl"/> </extensions> <!-- 不要在新插件中使用 --> <application-components> <component> <!-- Component's interface class --> <interface-class>com.example.Component1Interface</interface-class> <!-- Component's implementation class --> <implementation-class>com.example.impl.Component1Impl</implementation-class> </component> </application-components> <!-- 不要在新插件中使用 --> <project-components> <component> <implementation-class>com.example.Component2</implementation-class> <option name="workspace" value="true"/> <loadForDefaultProject/> </component> </project-components> <!-- 不要在新插件中使用 --> <module-components> <component> <implementation-class>com.example.Component3</implementation-class> </component> </module-components> </idea-plugin>
单个菜单
<actions>
<action id="com.idea.hello" class="com.HelloBoy" text="hello" description="打个招呼">
<add-to-group group-id="EditorPopupMenu" anchor="first"/>
</action>
</actions>
属性 | 说明 |
---|---|
id | 当前操作的id,可自定义填写,或写当前绑定的AnAction对象的包名.类名/类名 |
class | 绑定的AnAction对象 |
text | 当前这个菜单要显示什么内容 |
description | 当前菜单的介绍 |
icon | 菜单旁边要显示什么图标 |
设置action所在的组
<add-to-group group-id="EditorPopupMenu" anchor="first"/>
系统默认有一些组,如下是常用的group-id:
idea菜单组 | 说明 |
---|---|
MainMenu | 主菜单栏(只能group标签依赖) |
ToolsMenu | Tools菜单栏 |
FileMenu | File菜单栏(idea界面最顶层菜单栏都是它们的名字+Menu) |
EditorPopupMenu | 代码编辑页面右击出现 |
我们也可以自己创建菜单组(属性和单个菜单一样),popup=“true” 表示例弹出
<actions>
<group popup="true" id="HelloWorld" text="sayMenu">
<add-to-group group-id="MainMenu" anchor="last" />
</group>
<action class="HelloBoy" id="helloAction" text="sayHello">
<add-to-group group-id="HelloWorld" anchor="first" />
</action>
</actions>
扩展是插件最常用的一种扩展IDEA功能的方式, 只是这种方式没有直接把Action加到菜单或者工具栏那么直接。扩展功能是通过IDEA自带的或者其它插件提供的一些扩展点实现的。
<!-- defaultExtensionNs属性只有两种值: 1. com.intellij 使用 IntelliJ Platform核心扩展点 2. {ID of a plugin} 使用其它插件的扩展点,必须要配置其它插件的依赖 子标签的名称必须和扩展点的名称相同 扩展点有两种类型 3. 扩展点使用interface 属性声明, 则设置扩展点标签的implementation属性, 属性值设置实现了interface对应接口的实现类的全限定名 4. 扩展点使用beanClass属性声明,则可以设置beanClass对应的扩展点类中@Attribute注解标注的属性。 可以使用view | quick document 查看扩展点帮助 --> <extensions defaultExtensionNs="com.intellij"> <appStarter implementation="com.example.MyAppStarter"/> <projectTemplatesFactory implementation="com.example.MyProjectTemplatesFactory"/> </extensions> <extensions defaultExtensionNs="another.plugin"> <myExtensionPoint key="keyValue" implementationClass="com.example.MyExtensionPointImpl"/> </extensions>
插件显示在settings中和工具栏下方,示例:
<extensions defaultExtensionNs="com.intellij">
<!-- 显示在file-settings-tools中 -->
<applicationConfigurable groupId="tools" instance="cn.luojunhui.touchfish.config.BookSettingsConfigurable" order="last"/>
<applicationService serviceInterface="cn.luojunhui.touchfish.config.BookSettingsState" serviceImplementation="cn.luojunhui.touchfish.config.BookSettingsState"/>
<!-- 显示在idea最下方的工具栏中 -->
<toolWindow id="Touch Fish" secondary="false" anchor="bottom" icon="/icons/fish_14.png" factoryClass="cn.luojunhui.touchfish.windwos.BookWindowFactory">
</toolWindow>
</extensions>
Extension Points 声明扩展点
有两种类型的扩展点:
1、Interface 其它插件提供该接口的实现类
2、Bean 其它插件提供子类, 这种方式其它插件可以设置一些扩展点类的属性值
<idea-plugin>
<id>my.plugin</id>
<extensionPoints>
<extensionPoint
name="myExtensionPoint1"
beanClass="com.example.MyBeanClass"/>
<extensionPoint
name="myExtensionPoint2"
interface="com.example.MyInterface"/>
</extensionPoints>
</idea-plugin>
扩展点类
public class MyBeanClass extends AbstractExtensionPointBean { @Attribute("key") public String key; @Attribute("implementationClass") public String implementationClass; public String getKey() { return key; } public String getClass() { return implementationClass; } }
其他插件使用改扩展点
<idea-plugin> <id>another.plugin</id> <!-- Declare dependency on plugin defining extension point: --> <depends>my.plugin</depends> <!-- Use "my.plugin" namespace: --> <extensions defaultExtensionNs="my.plugin"> <myExtensionPoint1 key="someKey" implementationClass="another.some.implementation.class"/> <myExtensionPoint2 implementation="another.MyInterfaceImpl"/> </extension> </idea-plugin>
声明扩展点的类使用扩展点
public class MyExtensionUsingService {
private static final ExtensionPointName<MyBeanClass> EP_NAME =
ExtensionPointName.create("my.plugin.myExtensionPoint1");
public void useExtensions() {
for (MyBeanClass extension : EP_NAME.getExtensionList()) {
String key = extension.getKey();
String clazz = extension.getClass();
// ...
}
}
}
public class JsonFormatAction extends AnAction { @Override public void actionPerformed(AnActionEvent event) { // 获取当前project对象 Project project = event.getData(PlatformDataKeys.PROJECT); // 获取当前编辑的文件, 可以进而获取 PsiClass, PsiField 对象 PsiFile psiFile = event.getData(CommonDataKeys.PSI_FILE); Editor editor = event.getData(CommonDataKeys.EDITOR); // 获取Java类或者接口 PsiClass psiClass = getTargetClass(editor, psiFile); // 创建并调起 DialogWrapper DialogWrapper dialog = new JsonFormat(project, psiFile, editor, psiClass); dialog.show(); }
其他形式
// 获取project. 外部调用 getData(CommonDataKeys.PROJECT) = getDataContext().getData(CommonDataKeys.PROJECT)
Project project = e.getProject();
// 获取数据上下文
DataContext dataContext = e.getDataContext();
// context能够也获取到其余信息, 入参为 PlatformDataKeys 定义的字段
Project project1 = dataContext.getData(PlatformDataKeys.PROJECT);
Editor editor = dataContext.getData(PlatformDataKeys.EDITOR);
PsiFile psiFile = dataContext.getData(PlatformDataKeys.PSI_FILE);
PsiElement psiElement = dataContext.getData(PlatformDataKeys.PSI_ELEMENT);
// 虚构文件
VirtualFile virtualFile = dataContext.getData(PlatformDataKeys.VIRTUAL_FILE);
获取PsiClass
@Nullable
protected PsiClass getTargetClass(Editor editor, PsiFile file) {
int offset = editor.getCaretModel().getOffset();
PsiElement element = file.findElementAt(offset);
if (element == null) {
return null;
} else {
PsiClass target = PsiTreeUtil.getParentOfType(element, PsiClass.class);
return target instanceof SyntheticElement ? null : target;
}
}
// 获取全类名 String qualifiedName = aClass.getQualifiedName(); // 获取所有字段 PsiField[] fields = aClass.getFields(); // 获取字段名 String name = psiField.getName() // PsiClass和PsiField都实现了PsiElement // 删除 element.delete() // 添加元素, 向一个类中添加方法, 字段等, 也可以调用 addBefore, addAfter add(PsiElement element) // PsiType支持常用基本类型, 但是当创建对象时则不支持.需要自己创建 PsiElementFactory psiElementFactory = JavaPsiFacade.getElementFactory(project); // String 类型 PsiType stringPsiType = psiElementFactory.createTypeFromText("java.lang.String", null) // list PsiType listPsiType = psiElementFactory.createTypeFromText("java.util.List<String>", null); // 自定义list PsiType typeFromText = psiElementFactory.createTypeFromText("java.util.List<" + className + ">", null);
以 Mapper.xml 举例声明接口,继承 DomElement,并配合 @Attribute、@SubTag 、@SubTagsList 注解定义一个 xml model,其中需要注意 @SubTagsList 方法要使用复数形式。
public interface Mapper extends DomElement { @Attribute("namespace") GenericAttributeValue<String> getNamespace(); /** * 增删改查对应的节点 */ @SubTagsList({"select", "insert", "update", "delete"}) List<Statement> getStatements(); @SubTagList("select") List<Select> getSelects(); @SubTagList("insert") List<Insert> getInserts(); @SubTagList("update") List<Update> getUpdates(); @SubTagList("delete") List<Delete> getDeletes(); }
搜索文件。
比如想搜索项目中的所有 xml 文件,上面使用 Mapper 接口定义了 Mapper.xml 的结构,就可以利用 DomService 搜索所有的 Mapper.xml:
// 当前项目的所有元素 mapper, 分别填入类型, 作用域 GlobalSearchScope
List<DomFileElement<Mapper>> fileElements = DomService.getInstance().getFileElements(Mapper.class, project, GlobalSearchScope.allScope(project));
写入文件。
需要调用WriteCommandAction进行异步写入
WriteCommandAction.runWriteCommandAction(project, () -> {
doGenerate(psiClass, jsonObject);
});
通知。
在操作成功之后,在 IDEA 右下角通知用户,使用 NotificationGroup 类即可。
// 静态属性
private static final NotificationGroup NOTIFICATION_GROUP = new NotificationGroup("Java2Json.NotificationGroup", NotificationDisplayType.BALLOON, true);
public void actionPerformed(@NotNull AnActionEvent e) {
// 在方法中调用
Notification success = NOTIFICATION_GROUP.createNotification(message, NotificationType.INFORMATION);
Notifications.Bus.notify(success, project);
}
也可以定义为工具类,如下
/** * 进行消息通知工具类 */ public class NotificationUtils { private static NotificationGroup notificationGroup = new NotificationGroup("ApiDoc.NotificationGroup", NotificationDisplayType.BALLOON, true); public static void warnNotify(String message, Project project) { Notifications.Bus.notify(notificationGroup.createNotification(message, NotificationType.WARNING), project); } public static void infoNotify(String message, Project project) { Notifications.Bus.notify(notificationGroup.createNotification(message, NotificationType.INFORMATION), project); } public static void errorNotify(String message, Project project) { Notifications.Bus.notify(notificationGroup.createNotification(message, NotificationType.ERROR), project); } }
Service是插件的一个组件, 是为了把公共的逻辑放到一起,Service的实例是单例的。
有三种类型的Service, 应用级别的,项目级别的,模块级别的。
声明Service有两种方式
1、类上加@Service注解,service不需要被重写的时候可以使用这种方式
2、在plugin.xml中声明,通过applicationService,projectService扩展点声明
<extensions defaultExtensionNs="com.intellij">
<!-- application-level service -->
<applicationService
serviceInterface="mypackage.MyApplicationService"
serviceImplementation="mypackage.MyApplicationServiceImpl"/>
<!-- project-level service -->
<projectService
serviceInterface="mypackage.MyProjectService"
serviceImplementation="mypackage.MyProjectServiceImpl"/>
</extensions>
获取Service
MyApplicationService applicationService = ApplicationManager.getApplication()
.getService(MyApplicationService.class);
MyProjectService projectService = project.getService(MyProjectService.class);
实现类可以封装静态方法,方便获取
MyApplicationService applicationService = MyApplicationService.getInstance();
MyProjectService projectService = MyProjectService.getInstance(project);
和其它Service交互
@Service public final class ProjectService { private final Project myProject; public ProjectService(Project project) { myProject = project; } public void someServiceMethod(String parameter) { AnotherService anotherService = myProject.getService(AnotherService.class); String result = anotherService.anotherServiceMethod(parameter, false); // do some more stuff } }
使用gradle创建项目,除了打包方式,依赖引入不同,其他的都差不多
项目结构(第一创建可能会下载很多依赖,加载时间会长一点)
intellij 2020 编译的插件,不能安装在idea 2022上。
解决办法 :添加 updateSinceUntilBuild false (设置各版本兼容)
intellij {
version '2020.2.4'
updateSinceUntilBuild false
}
解决办法:添加 options.encoding = “UTF-8”
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。