IntelliJ IDEA插件开发教程
我们在使用Android Studio开发的时候都会使用一些插件,来方便我们的开发工作,提升工作效率。IntelliJ IDEA 可能有的人已经很熟悉,强大的开源IDE。Android Studio 就是基于IDEA社区版开发的。下载安装后打开软件我们就会发现,IntelliJ IDEA的界面、菜单、状态栏以及Preference 和 安卓开发ide android studio 几乎是一模一样的。
IntelliJ IDEA 简称 IDEA,是 Jetbrains 公司旗下的一款 JAVA 开发工具,支持 Java、Scala、Groovy、kotlin 等语言的开发,同时具备支持目前主流的技术和框架,擅长于企业应用、移动应用和 Web 应用的开发,提供了丰富的功能,智能代码助手、代码自动提示、重构、J2EE支持、各类版本工具(git、svn等)、JUnit、CVS整合、代码分析、 创新的GUI设计等。
IntelliJ IDEA插件类型
在介绍IDEA插件开发流程之前,先了解下idea插件有哪些类型及其作用。
从官方文档可以看出,idea开发的插件最常见的有5种类型。分别是
1、UI主题
UI主题使设计人员能够自定义内置IDE UI元素的外观。自定义UI主题可以:
- 替代图标,
- 更改图标和UI控件的颜色,
- 更改UI控件的边框和插图,
- 提供自定义编辑器方案,
- 添加背景图像。
可供下载的UI主题 说明创造的可能性。
2、自定义语言支持
自定义语言支持提供了使用特定编程语言的基本功能。包括:
- 文件类型识别(File type recognition)
- 词法分析(Lexical analysis)
- 词法高亮显示(Syntax highlighting)
- 格式化(Formatting)
- 代码洞察和代码完成(Code insight and code completion)
- 检查和快速修复(Inspections and quick-fixes)
- 意图行动(Intention actions)
详情点击:自定义语言支持插件开发
3、框架集成(Framework Integration)
框架集成由改进的代码洞察特性组成,这些特性是给定框架的典型特征,以及直接从IDE使用特定于框架的功能的选项。有时它还包括自定义语法或DSL的语言支持元素
- 具体代码的洞察力(Specific code insight)
- 直接访问特定于框架的功能(Direct access to framework-specific functionality)
参考案例: Struts 2 plugin开发
4、工具继承(Tool Integration)
工具集成使直接从IDE操作第三方工具和组件成为可能,而无需切换上下文
这就意味着:
- 额外行动的实施(Implementation of additional actions)
- 相关的UI组件(Related UI components)
- 访问外部资源(Access to external resources)
参考案例: Gerrit integration
5、用户界面插件(User interface add-ons)
此类别中的插件对IDE的标准用户界面应用各种更改. 一些新添加的组件是交互式的,并且提供了新的功能,而另一些则仅限于可视化修改.
插件开发方式
Mac IDEA配置阿里云国内镜像
IDEA会自动装配IDEA版本的maven,所以只需要配置settings.xml
就可以实现变更国内镜像。
IDEA插件有两种开发方式:1、DevKit方式 2、Gradle方式
一、DevKit方式
Plugin DevKit_是一个捆绑的IntelliJ IDEA插件,用于使用IntelliJ IDEA为IntelliJ平台开发插件自己的构建系统. 它提供了自定义SDK类型和一组用于在IDE中构建插件的操作.早期的插件项目都是DevKit方式。
DevKit的plugin项目,项目工程的配置文件是imi文件。
Sandbox
IntelliJ IDEA 插件以 Debug/Run 模式运行时是在 SandBox 中进行的,不会影响当前的 IntelliJ IDEA;但是同一台机器同时开发多个插件时默认使用的同一个 sandbox,即在创建 IntelliJ Platform SDK 时默认指定的 Sandbox Home.
如果需要每个插件的开发环境是相互独立的,可以创建多个 IntelliJ Platform SDK,为 Sandbox Home 指定不同的目录 。
Run/Debug
新建或修改运行/调试配置,选择使用的module后,可直接点击图标运行或调试。
Prepare Plugin For Deployment
如果项目中没有任何依赖,打出来的插件包是jar包
如果项目中有依赖其他插件或Library,则打出来的插件包是zip文件
插件的配置文件是plugin.xml,是整个插件的核心,见Gradle方式中的plugin.xml说明。
二、Gradle方式
Gradle插件是构建IntelliJ插件的推荐解决方案.该插件负责处理插件项目的依赖性 - 基本IDE和插件可能依赖的其他插件. 它还提供了使用插件运行IDE并将插件发布到JetBrains插件存储库的任务.对于新项目,官方推荐Gradle方式。
Gradle的plugin项目,项目工程的配置文件是build.gradle文件
兼容性
如果依赖的插件版本和intellij版本不兼容,编译则会报如下图错误
关于兼容性,官方也作了说明
例如koltin插件,可以去JetBrains Plugins Repository查询各版本及其兼容的IDE版本
可以根据项目需要添加更多的配置
plugin.xml
plugin.xml是整个项目的核心配置文件,一些Action、Extension组件需要在这里先进行注册和声明,和Android 开发的AndroidManifest.xml注册组件类似。
<idea-plugin>
<!-- 插件名称,别人在官方插件库搜索你的插件时使用的名称 -->
<name>MyPlugin</name>
<!-- 插件唯一id,唯一性标识,上传到官方仓库如果如果有重复的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>
<!-- Targeting PhpStorm, so is dependent on the PHP plugin -->
<depends>com.jetbrains.php</depends>
<depends>com.intellij.modules.platform</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>
Action
概念
Action 用于描述一个动作、行为,可以通过快捷键、点选的方式进行触发。一个 Action 是一个 class,是 AnAction 的子类,actionPerformed 方法在菜单Item或者标题栏按钮被选中的时候会被调用。
Action 允许添加到右键菜单或者Toolbar菜单上面。Action也可以成组添加到具体的一个Group下面。
创建action
继承AnAction,并重写actionPerformed方法,此方法是action触发执行的入口。
注册action
- Action ID: action 唯一 id,推荐使用全类名
- Class Name: 要被创建的 action class 名称
- Name: menu item 的文本
- Description: action 描述,toolbar 上按钮的提示文本,可选
- Add to Group:选择新 action 要被添加到的 action group(Groups, Actions)以及相对其他 actions 的位置(Anchor),比如 EditMenu 就是顶部菜单栏的 Edit 菜单。
- Keyboard Shortcuts:指定 action 的第一和第二快捷键
Components
插件组件是插件集成的基础概念,我们插件定义的每一个组件(component) 应该在配置文件中配置相关的接口(可选)和实现类。有三种组件:
Components 类型
Components 接口类型 | 描述 |
Application Component | IDEA启动时会初始化,IDEA生命周期中仅存在一个实例。 |
Project Component | IDEA 会为每一个 Project 实例创建一个 Project 级别的component |
Module Component | IDEA 会为每一个 Project 的加载过的Module实例Module级别的component |
创建 Component
Application Component
public class TestApplication implements ApplicationComponent {
public TestApplication() {
}
@Override
public void initComponent() {
// TODO: insert component initialization logic here
}
@Override
public void disposeComponent() {
// TODO: insert component disposal logic here
}
@Override
@NotNull
public String getComponentName() {
return "TestApplication";
}
}
Project Component
public class TestProject implements ProjectComponent {
public TestProject(Project project) {
}
@Override
public void initComponent() {
}
@Override
public void disposeComponent() {
}
@Override
@NotNull
public String getComponentName() {
return "TestProject";
}
@Override
public void projectOpened() {
// called when project is opened
}
@Override
public void projectClosed() {
// called when project is being closed
}
}
Module Component
public class TestModule implements ModuleComponent {
public TestModule(Module module) {
}
@Override
public void initComponent() {
// TODO: insert component initialization logic here
}
@Override
public void disposeComponent() {
// TODO: insert component disposal logic here
}
@Override
@NotNull
public String getComponentName() {
return "TestModule";
}
@Override
public void moduleAdded() {
// Invoked when the module corresponding to this component instance has been completely
// loaded and added to the project.
}
}
注册 Components
components 需要配置在 plugin.xml 中,并指定 interface 和 implementation,interface 类用于从其他组件中检索组件,implementation 类用于实例化组件。
Application Component
<application-components>
<component>
<implementation-class>com.example.test.TestApplication</implementation-class>
</component>
</application-components>
Project Component
<project-components>
<component>
<implementation-class>com.example.test.TestProject</implementation-class>
</component>
</project-components>
Module Component
<module-components>
<component>
<implementation-class>com.example.test.TestModule</implementation-class>
</component>
</module-components>
注意:
- 一个 interface-class 不能有多个 implementation-class。
- 若组件没有创建 interface 类,而是直接实现了 ApplicationComponent 等接口,interface 和 implementation 可以指定为同一个类。
- 每一个组件都应该有一个唯一的名字,通过
getComponentName()
返回,推荐使用<plugin_name>.<component_name>
格式。
获取 Component 实例
获取 Application Component
通过 ApplicationManager获取
//获取application容器中的组件
TestApplication testApplication = ApplicationManager.getApplication().getComponent(TestApplication.class);
获取project
通过action获取
DataContext dataContext = anActionEvent.getDataContext();
// DataConstants 被标记为 @deprecated
mProject = (Project)dataContext.getData(DataConstants.PROJECT);
Project project = anActionEvent.getData(PlatformDataKeys.PROJECT);
通过PsiElement获取
/**
* The common base interface for all elements of the PSI tree.
* <p/>
* Please see <a href="https://www.jetbrains.org/intellij/sdk/docs/basics/architectural_overview.html">IntelliJ Platform Architectural Overview</a>
* for high-level overview.
*/
public interface PsiElement extends UserDataHolder, Iconable {
/**
* The empty array of PSI elements which can be reused to avoid unnecessary allocations.
*/
PsiElement[] EMPTY_ARRAY = new PsiElement[0];
ArrayFactory<PsiElement> ARRAY_FACTORY = count -> count == 0 ? EMPTY_ARRAY : new PsiElement[count];
/**
* Returns the project to which the PSI element belongs.
*
* @return the project instance.
* @throws PsiInvalidElementAccessException if this element is invalid
*/
@NotNull
@Contract(pure=true)
Project getProject() throws PsiInvalidElementAccessException;
}
获取module
通过project获取
TestModule testModule = project.getComponent(TestModule.class);
通过action获取
DataContext dataContext = anActionEvent.getDataContext();
// DataConstants 被标记为 @deprecated
mModule =(Module)dataContext.getData(DataConstants.MODULE);
Module module = anActionEvent.getData(LangDataKeys.MODULE);
@Deprecated
@SuppressWarnings({"JavadocReference"})
public interface DataConstants {
/**
* Returns {@link com.intellij.openapi.project.Project}
*
* @deprecated use {@link PlatformDataKeys#PROJECT} instead
*/
@Deprecated String PROJECT = CommonDataKeys.PROJECT.getName();
/**
* Returns {@link com.intellij.openapi.module.Module}
*
* @deprecated use {@link com.intellij.openapi.actionSystem.LangDataKeys#MODULE} instead
*/
@Deprecated @NonNls String MODULE = "module";
}
Component 生命周期
Component加载
Application 级别的 components 在 IDEA 启动时加载,Project 和 Module 级别的 components 在项目启动时共同加载。
一个组件加载过程:
- 创建:调用构造方法
- 初始化:调用
initComponent()
方法 - 如果是 Project 组件,会调用
projectOpened()
方法; 如果是 Module 组件,会依次调用moduleAdded()
和projectOpened()
方法。
ModuleComponent 的生命周期方法中比 ProjectComponent 多一个 moduleAdded()
,用于通知 module 已经被添加到 project 中。
如果 component 在加载时需要用到其他 component,我们只需在该 component 的构造方法的参数列表声明即可,在这种情况下,IntelliJ IDEA 会按正确的顺序实例化所依赖的 component。
示例:
public class MyComponent implements ApplicationComponent {
private final MyOtherComponent otherComponent;
public MyComponent(MyOtherComponent otherComponent) {
this.otherComponent = otherComponent;
}
...
}
Component 卸载
一个组件卸载过程:
- 如果是 Project 或 Module 组件,调用
projectClosed()
- 接下来
disposeComponent()
将被调用
持久化
对于IDEA插件的一些配置,一般情况下都不会希望用户每次使用插件时都要配置一遍,所以 IntelliJ Platform 提供了一些 API,来做数据的持久化。
PropertiesComponent
对于简单的 key - value 数据结构,可以使用 PropertiesComponent,用于保存 application 和 project 级别的数据。用法如下:
//获取 application 级别的 PropertiesComponent
PropertiesComponent propertiesComponent = PropertiesComponent.getInstance();
//获取 project 级别的 PropertiesComponent,指定相应的 project
PropertiesComponent propertiesComponent = PropertiesComponent.getInstance(Project);
// set & get
propertiesComponent.setValue(name, value)
propertiesComponent.getValue(name)
所有的 PropertiesComponent设置的键值对共用同一个namespance,所以需要避免key冲突。
PersistentStateComponent
对于复杂的数据结构,可以使用 PersistentStateComponent,PersistentStateComponent 可以指定持久化的存储位置。
- 需要提供一个 PersistentStateComponent 的实现类,T代表需要持久化的数据结构类型,然后重写 getState() 和 loadState() 方法。T可以是任意的类,或者是实现类自身。
- 若需要指定存储位置,则在实现类上增加 @State 注解
- 若不希望其中的某个字段被持久化,可以在该字段上增加 @Transient 注解
@State(name = "PersistentStateComponentImpl",
storages = {
@Storage(value = "PersistentStateComponentImpl.xml")
})
class PersistentStateComponentImpl implements PersistentStateComponent<State> {
State myState;
// 当组件被创建或 xml 文件被外部改变(比如git更新)时被调用
public State getState() {
return myState;
}
// 当 settings 被保存时,该方法会被调用并保存状态值。
public void loadState(State state) {
myState = state;
}
}
class State {
public State() {}
// 支持基本的数据类型、Map、Collection、enum
public String value;
@Transient
public String disableSave;
}
- 若是 application 级别的组件
-
- 运行调试时 xml 文件的位置: ~/IdeaICxxxx/system/plugins-sandbox/config/options
- 正式安装时 xml 文件的位置: ~/IdeaICxxxx/config/options
- 若是 project 级别的组件
-
- 默认为项目的 .idea/misc.xml
- 若指定为 StoragePathMacros.WORKSPACE_FILE,则会被保存在 .idea/worksapce.xml
注册持久化
持久化组件可以声明为 Service,也可以声明为 Component,声明为 Component 则与前面介绍注册与获取的方法一致,声明为Service如下:
获取方式为:
<extensions defaultExtensionNs="com.intellij">
<!-- application 级别-->
<applicationService serviceImplementation="com.yuyh.finder.PersistentStateComponentImpl1"/>
<!-- project 级别 -->
<projectService serviceImplementation="com.yuyh.finder.PersistentStateComponentImpl2"/>
</extensions>
扩展以及扩展点
Intellij 平台提供了允许一个插件与其他插件或者 IDE 交互的 extensions 以及 extension points 的概念。
extensions
如果你想要你的插件扩展其他插件或者 Intellij 平台,你必须声明一个或多个 extensions
extension points
如果你想要你的插件可以被其他插件使用,那么你必须在你的插件内声明一个或多个扩展点(extension points)。每个扩展点定义了允许访问这个点的类或者接口。
注册 extensions 以及 extension points
你可以在你的 plugin.xml 中的 <extensions> 和 <extensionPoints> 块中定义 extensions 以及 extension points。
<extensions defaultExtensionNs="com.intellij">
<checkinHandlerFactory implementation="org.example.untitled.CheckinHandlerFactory"/>
</extensions>
GUI
GUI 是 IntelliJ IDEA 提供的一个自动生成 java 布局代码的工具,它使用 JDK 中的 Swing 控件来实现 UI 界面。
面板编辑完成之后,点击Toolbar工具条那里的编译按钮。编译完成后,GUI的代码会生成在 对应的 Java文件里面。
Gradle Task
Gradle task 和 android studio的 类似,常用的也是打包、调试和发布task,只不过task 名不同。
更多说明和示例可见JetBrains/gradle-intellij-plugin
buildPlugin
buildPlugin 构建插件,常说的打包 插件包默认输出目录为build/distributions/插件名.zip
runIde
runIde 调试运行
现在双击runIde即可调出另外一个安装了这个插件的IDEA界面,然后可以看运行结果进行调试。 runIde还支持debug模式,不过运行时要右击选择:
如果setIdeDirectory设置为Android studio路径,则调试过程如下,会重新启动一个main(Android studio)
publishPlugin
publishPlugin 发布插件
上传插件市场
1、官方插件市场
官方文档:http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/publishing_plugin.html
2、自定义个人/公司插件存储服务器
Install
如果要安装的插件非JetBrains官方插件市场发布的插件,则需要配置插件存储服务器地址,才能在Marketplace搜索到然后install。
搜索但还未安装时,右侧会显示updatePlugins.xml文件中配置的插件信息;从硬盘选择安装或已安装的插件,会显示plugin.xml文件中配置的插件信息。
最后
到这里,一个IDE插件的的开发、配置、调试运行、发布、安装的基本流程就介绍完毕了。插件的应用非常广泛,可以应用所有基于IntelliJ Platform开发的IDE软件。如果想深入学习IDE插件开发,建议阅读官方文档。官方文档内容更加全面丰富,但是由于文档精炼、简洁,所以在学习过程中,很多地方需要我们去揣摩、整理和总结。