Idea插件开发(一)——插件的分类及基础认识
Idea插件开发(二)——插件的创建打包及发布
Idea插件开发(三)——插件JSL的完整开发过程
本篇文章基于IntelliJ Platform SDK DevGuide抽取主要部分内容。官方详细开发文档请移步官方文档
我们知道JetBrains有一大堆的开发工具,比如IntelliJ IDEA,从17年开发发现这个强大的IDE后我就果断抛弃了Eclipse,然后不断给身边的小伙伴安利它,各种人性化的操作让其他Java IDE望尘莫及,它的强大有一部分也来自于丰富的插件扩展支持,本文是我不断摸索翻译官方文档自己开发完插件后总结而成,原文档有些描述生硬的地方我做了解释,如有较大出入请留言告知,以下正文。
注意:在插件开发前务必仔细阅读相关文档,大致了解插件开发的流程并且对插件开发有一个基本的认知后再着手开发将会事半功倍!(亲身体会)
为什么开发插件
使用IntelliJ Idea已经有三年多了,可以说插件大大提升了开发的效率,当然还是会遇到一些困难,所以便萌生了自己开发插件的想法,在经历一番波折后总算在插件市场发布了自己的第一个插件,可以说在这个过程中不断地受到挫折然后又不遗余力的解决问题,通过翻看官方文档翻译理解、在论坛扒帖子、Google等等,让我对写代码又有了激情,一遍一遍改代码、改注释,在提交插件之后就会有一种满足感和成就感,这就是为什么我要做这件事情。因为我已经厌倦了每天CRUD的日子,这种重复劳动让写代码变得了无生趣,我相信同为coder的我们一定能感同身受,所以我希望自己能在业余时间做些感兴趣的事情,至少让时间变得有一些意义吧。
插件的分类
首先我们要知道IntelliJ Platform支持的插件是比较通用的,很多能够在IntelliJ IDEA上运行的插件不仅能够在其他JetBrains家族IDE上运行,同时还能在Android Studio上运行,这也是为什么官方推荐使用开源版本的IntelliJ IDEA Community Edition作为指定的SDK版本,这样开发的插件通用性非常强,下图是JetBrains官方插件存储库的主页,可以看到插件数量最多的是IDEA,其次是Android Studio。
其次我们要知道插件是分很多种类的,比如ignore(git忽略提交)、lombok(简化java bean代码)、mybatis log plugin(mybatis控制台sql)、GsonFormat(json格式化)、Alibaba Java Coding Guidelines(阿里巴巴代码检查)、Codota(开源仓库拉取相同代码片段)、还有一些代码自动生成插件、切换idea背景图片的插件等这里就不一一列举了,这些应该可以说时非常常见的了,至少我用Idea做Java开发时必装这些插件,插件大致可以分为系统类、美化类、辅助类。
- 系统类的如一些获取内存、CPU参数、JVM基本信息等
- 辅助类的比如代码自动生成、代码检查等
- 美化类的比如背景图片修改、console日志颜色突出等
官方文档中提到最常见的插件类型包括:自定义语言支持、框架整合、工具整合、用户界面附加组件。
其实仔细看官方提供的组件接口就基本能划分出插件的种类,官方也在插件搜索筛选中提供了很多选项:
插件结构-Plugin Structure
接下来我们通过官方对插件开发的介绍分类进行具体的介绍,内容包括以下几点:
- 插件内容Plugin Content
- 插件类加载器Plugin Class Loaders
- 插件动作(交互)Plugin Actions
- 插件扩展Plugin Extensions
- 插件服务Plugin Services
- 插件监听器Plugin Listeners
- 插件扩展点Plugin Extension Points
- 插件组件Plugin Components
- 插件配置文件Plugin Configuration File
- 插件徽标(图标)Plugin Logo (Icon)
- 插件依赖Plugin Dependencies
插件内容
插件最终将以jar包的形式发布,该jar包必须包含以下内容:
- the configuration file (META-INF/plugin.xml) (Plugin Configuration File)
- the classes that implement the plugin functionality
- recommended: plugin logo file(s) (META-INF/pluginIcon*.svg) (Plugin Logo)
总结一下就是三点:配置文件plugin.xml、插件功能类、插件图标
无依赖插件
└── plugins
└── sample.jar
├── com/foo/...
│ ...
│ ...
└── META-INF
├── plugin.xml
├── pluginIcon.svg
└── pluginIcon_dark.svg
具有依赖项的插件
插件的.jar文件/lib与所有必需的捆绑库一起放置在插件的“根”文件夹下的文件夹中。
/lib文件夹中的所有jar都会自动添加到类路径中
└── plugins
└── sample
└── lib
├── lib_foo.jar
├── lib_bar.jar
│ ...
│ ...
└── sample.jar
├── com/foo/...
│ ...
│ ...
└── META-INF
├── plugin.xml
├── pluginIcon.svg
└── pluginIcon_dark.svg
插件类加载
默认情况下,主IDE类加载器加载在插件类加载器中找不到的类。但是,在plugin.xml文件中,您可以使用元素来指定一个插件依赖于一个或多个其他插件。在这种情况下,这些插件的类加载器将用于当前插件中找不到的类。这允许插件引用其他插件中的类。
插件动作(交互)
The IntelliJ Platform provides the concept of actions. An action is a class derived from AnAction, whose actionPerformed() method is called when its menu item or toolbar button is selected.
Actions are the most common way for a user to invoke the functionality of your plugin. An action can be invoked from a menu or a toolbar, using a keyboard shortcut, or from the Help | Find Action… lookup.
Actions are organized into groups, which, in turn, can contain other groups. A group of actions can form a toolbar or a menu. Subgroups of the group can form submenus of a menu.
总结:插件的调用(触发)方式。
大概分三种:工具栏、菜单栏、快捷键。
如图在创建插件项目时需要选择插件的触发方式,Groups包含了所有的触发形式,下方的keyboard shortcuts支持快捷键触发。
插件扩展
Extensions are the most common way for a plugin to extend the functionality of the IntelliJ Platform in a way that is not as straightforward as adding an action to a menu or toolbar.
The following are some of the most common tasks accomplished using extensions:
The com.intellij.toolWindow extension point allows plugins to add tool windows (panels displayed at the sides of the IDE user interface);
The com.intellij.applicationConfigurable and com.intellij.projectConfigurable extension points allow plugins to add pages to the Settings/Preferences dialog;
Custom language plugins use many extension points to extend various language support features in the IDE.
There are more than 1000 extension points available in the platform and the bundled plugins, allowing to customize different parts of the IDE behavior.
总结:idea的自定义插件入口(官方叫point我觉得说入口其实更通俗点)支持多到数不清。
举例:你可以扩展菜单栏(添加新的扩展菜单)、工具栏(添加新的工具窗口)…,以下全都是:
可以看到很多比较熟悉的,比如代码的高亮、搜索等等,凡是idea支持的各种花里胡哨的操作基本都支持你二次扩展。
大概数了一下有三百多个扩展点,并没有多到数不清~
dynamic=true表示该扩展插件支持动态加载,就是热加载(包括install和uninstall)
插件服务
如果你的插件内容是按需提供支持的那么你可以将这些支持写成服务。在插件运行时服务支持创建的实例唯一。插件支持以下三种级别的服务:
- applicationService: 用来声明一个application服务
- projectService: 用来声明一个project服务
- moduleService: 用来声明一个module服务
只要在扩展中引入service即可:
<extensions defaultExtensionNs="com.intellij">
<!-- Declare the application level service -->
<applicationService serviceInterface="Mypackage.MyServiceInterfaceClass" serviceImplementation="Mypackage.MyServiceImplClass">
</applicationService>
<!-- Declare the project level service -->
<projectService serviceInterface="Mypackage.MyProjectServiceInterfaceClass" serviceImplementation="Mypackage.MyProjectServiceImplClass">
</projectService>
</extensions>
服务必须指定其实现类。
插件监听器
类似消息订阅。
插件扩展点
插件扩展已经说明过了。
插件组件
插件组件是一个被抛弃了的东西。官方的文档说的非常明确,需要动态加载插件就必须抛弃组件。并且提供了原有的组件迁移形式,包括插件服务service和插件监听器listener。
插件配置文件 - plugin.xml
这是核心配置文件,每个标签都代表不同的配置,以下来自官方。
<!-- `url` specifies the URL of the plugin homepage (can be opened from "Plugins" settings dialog) -->
<idea-plugin url="https://www.jetbrains.com/idea">
<!-- Plugin name. It should be short and descriptive and in Title Case.
Displayed in the "Plugins" settings dialog and the plugin repository Web interface. -->
<name>Vss Integration</name>
<!-- Unique identifier of the plugin. Should be FQN.
Cannot be changed between the plugin versions.
If not specified, <name> will be used (not recommended). -->
<id>com.jetbrains.vssintegration</id>
<!-- Description of the plugin.
Should be short and to the point.
Start the description with a verb in present simple form such as
"integrates", "synchronizes", "adds support for" or "lets you view".
Don’t use marketing adjectives like “simple”, “lightweight”, or “professional”.
Don’t repeat the name of the plugin.
For plugins that add language/platform/framework support, the description MUST specify
the version of the corresponding language/platform/framework.
Don't mention the IDE compatibility. E.g. don't say "Adds support to IntelliJ IDEA for..."
Displayed in the "Plugins" settings dialog and the plugin repository Web interface.
Simple HTML elements can be included between <![CDATA[ ]]> tags. -->
<description>Integrates Volume Snapshot Service W10</description>
<!-- Description of changes in the latest version of the plugin.
Displayed in the "Plugins" settings dialog and the plugin repository Web interface.
Simple HTML elements can be included between <![CDATA[ ]]> tags. -->
<change-notes>Initial release of the plugin.</change-notes>
<!-- Plugin version
Recommended format is BRANCH.BUILD.FIX (MAJOR.MINOR.FIX)
Displayed in the "Plugins" settings dialog and the plugin repository Web interface. -->
<version>1.0.0</version>
<!-- The vendor of the plugin.
The optional "url" attribute specifies the URL of the vendor homepage.
The optional "email" attribute specifies the e-mail address of the vendor.
Displayed in the "Plugins" settings dialog and the plugin repository Web interface. -->
<vendor url="https://www.company.com" email="support@company.com">A Company Inc.</vendor>
<!-- Mandatory dependencies on plugins or modules.
The FQN module names in <depends> elements are used to determine IDE compatibility for the plugin.
Include at least the module shown below to indicate compatibility with IntelliJ Platform-based products.
Also include dependencies on other plugins as needed.
See "Compatibility with Multiple Products" and "Plugin Dependencies" for more information. -->
<depends>com.intellij.modules.platform</depends>
<depends>com.third.party.plugin</depends>
<!-- Optional dependency on another plugin.
If the plugin with the "com.MySecondPlugin" ID is installed, the contents of mysecondplugin.xml
(the format of this file conforms to the format of plugin.xml) will be loaded. -->
<depends optional="true" config-file="mysecondplugin.xml">com.MySecondPlugin</depends>
<!-- Minimum and maximum build of IDE compatible with the plugin -->
<idea-version since-build="183" until-build="183.*"/>
<!-- Resource bundle (/messages/MyPluginBundle.properties) to be used
with `key` attributes in extension points and implicit keys like
`action.[ActionID].text` -->
<resource-bundle>messages.MyPluginBundle</resource-bundle>
<!-- Plugin's application components (note that components are deprecated and should not be used in new plugins)
See https://www.jetbrains.org/intellij/sdk/docs/basics/plugin_structure/plugin_components.html for migration steps
-->
<application-components>
<component>
<!-- Component's interface class -->
<interface-class>com.foo.Component1Interface</interface-class>
<!-- Component's implementation class -->
<implementation-class>com.foo.impl.Component1Impl</implementation-class>
</component>
</application-components>
<!-- Plugin's project components (note that components are deprecated and should not be used in new plugins)
See https://www.jetbrains.org/intellij/sdk/docs/basics/plugin_structure/plugin_components.html for migration steps
-->
<project-components>
<component>
<!-- Interface and implementation classes are the same -->
<implementation-class>com.foo.Component2</implementation-class>
<!-- If the "workspace" option is set "true", the component
saves its state to the .iws file instead of the .ipr file.
Note that the <option> element is used only if the component
implements the JDOMExternalizable interface. Otherwise, the
use of the <option> element takes no effect. -->
<option name="workspace" value="true" />
<!-- If the "loadForDefaultProject" tag is present, the project component is instantiated also for the default project. -->
<loadForDefaultProject/>
</component>
</project-components>
<!-- Plugin's module components (note that components are deprecated and should not be used in new plugins)
See https://www.jetbrains.org/intellij/sdk/docs/basics/plugin_structure/plugin_components.html for migration steps
-->
<module-components>
<component>
<implementation-class>com.foo.Component3</implementation-class>
</component>
</module-components>
<!-- Actions -->
<actions>
<action id="VssIntegration.GarbageCollection" class="com.foo.impl.CollectGarbage" text="Collect _Garbage" description="Run garbage collector">
<keyboard-shortcut first-keystroke="control alt G" second-keystroke="C" keymap="$default"/>
</action>
</actions>
<!-- Extension points defined by the plugin.
Extension points are registered by a plugin so that other
plugins can provide this plugin with certain data.
-->
<extensionPoints>
<extensionPoint name="testExtensionPoint" beanClass="com.foo.impl.MyExtensionBean"/>
</extensionPoints>
<!-- Extensions which the plugin adds to extension points
defined by the IntelliJ Platform or by other plugins.
The "defaultExtensionNs " attribute must be set to the
ID of the plugin defining the extension point, or to
"com.intellij" if the extension point is defined by the
IntelliJ Platform. The name of the tag within the <extensions>
tag matches the name of the extension point, and the
"implementation" class specifies the name of the class
added to the extension point. -->
<extensions defaultExtensionNs="VssIntegration">
<testExtensionPoint implementation="com.foo.impl.MyExtensionImpl"/>
</extensions>
<!-- Application-level listeners -->
<applicationListeners>
<listener class="com.foo.impl.MyListener" topic="com.intellij.openapi.vfs.newvfs.BulkFileListener"/>
</applicationListeners>
<!-- Project-level listeners -->
<projectListeners>
<listener class="com.foo.impl.MyToolwindowListener" topic="com.intellij.openapi.wm.ex.ToolWindowManagerListener"/>
</projectListeners>
</idea-plugin>
描述的非常清晰,如果看起来麻烦我从网上找到一个中文版的放这里:
<idea-plugin>
<!-- 插件名称,别人在官方插件库搜索你的插件时使用的名称 -->
<name>MyPlugin</name>
<!-- 插件唯一id,不能和其他插件项目重复,所以推荐使用com.xxx.xxx的格式
插件不同版本之间不能更改,若没有指定,则与插件名称相同 -->
<id>com.example.plugin.myplugin</id>
<!-- 插件的描述 -->
<description>my plugin description</description>
<!-- 插件版本变更信息,支持HTML标签;
将展示在 settings | Plugins 对话框和插件仓库的Web页面 -->
<change-notes>Initial release of the plugin.</change-notes>
<!-- 插件版本 -->
<version>1.0</version>
<!-- 供应商主页和email-->
<vendor url="http://www.jetbrains.com" email="support@jetbrains.com" />
<!-- 插件所依赖的其他插件的id -->
<depends>MyFirstPlugin</depends>
<!-- 插件兼容IDEA的最大和最小 build 号,两个属性可以任选一个或者同时使用
官网详细介绍:http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/build_number_ranges.html-->
<idea-version since-build="3000" until-build="3999"/>
<!-- application components -->
<application-components>
<component>
<!-- 组件接口 -->
<interface-class>com.foo.Component1Interface</interface-class>
<!-- 组件的实现类 -->
<implementation-class>com.foo.impl.Component1Impl</implementation-class>
</component>
</application-components>
<!-- project components -->
<project-components>
<component>
<!-- 接口和实现类相同 -->
<interface-class>com.foo.Component2</interface-class>
</component>
</project-components>
<!-- module components -->
<module-components>
<component>
<interface-class>com.foo.Component3</interface-class>
</component>
</module-components>
<!-- Actions -->
<actions>
...
</actions>
<!-- 插件定义的扩展点,以供其他插件扩展该插件 -->
<extensionPoints>
...
</extensionPoints>
<!-- 声明该插件对IDEA core或其他插件的扩展 -->
<extensions xmlns="com.intellij">
...
</extensions>
</idea-plugin>
插件徽标(图标)
A Plugin Logo is intended to be a unique representation of a plugin’s functionality, technology, or company.
插件图标展示了你的插件功能、或者公司
因为svg是矢量图可以任意扩缩,所以官方推荐使用svg格式。具体的图像大小设置参考官方文档
根据以下约定为插件徽标文件命名:
pluginIcon.svg是默认的插件徽标。如果插件中存在用于深色UI主题的单独徽标文件,则此文件仅用于浅色UI主题,
pluginIcon_dark.svg 是一个可选的替代插件徽标,仅可用于深色IDE UI主题。
插件徽标文件必须位于META-INF插件分发文件的文件夹中,即您上传到插件存储库并安装到JetBrains IDE中的*.jar或*.zip文件。
要将Plugin Logo文件包含在您的分发文件中,请将Plugin Logo文件放入插件项目的resources/META-INF文件夹中。请注意,无论使用DevKit还是Gradle开发插件,此要求都是相同的。
上图来自官方文档
插件依赖
依赖外部插件的类涉及到外部插件的引用,本篇只做基本开发介绍不详细描述如何加载依赖型的插件。要注意如果插件存在依赖外部插件的情况,则最终打包的插件格式为ZIP而非JAR。
动态插件
动态插件指插件的热加载。
限制条件很多,一一列举:
No Use of Components
不能使用组件
Action Group Requires ID
所有的action必须声明一个唯一的ID
Use Only Dynamic Extensions
只能使用动态的扩展(上面插件扩展说明中有提到)
Mark Extension Points as Dynamic
标记引入的扩展为Dynamic动态的
Configurables Depending on Extension Points
任何依赖于动态扩展点的Configurable都必须实现Configurable.WithEpDependencies
No Use of Service Overrides
overrides="true"不允许使用声明的应用程序,项目和模块服务
结束语
本文对插件开发的基础知识做了简单阐述,看过之后会对插件开发有一个大概的了解,后面我会继续更新自己开发插件中遇到的问题和解决办法,包括插件开发中需要注意的事项等。