IntelliJ IDEA插件开发教程

IntelliJ IDEA插件开发教程

image.png

我们在使用Android Studio开发的时候都会使用一些插件,来方便我们的开发工作,提升工作效率。IntelliJ IDEA 可能有的人已经很熟悉,强大的开源IDE。Android Studio 就是基于IDEA社区版开发的。下载安装后打开软件我们就会发现,IntelliJ IDEA的界面、菜单、状态栏以及Preference 和 安卓开发ide android studio 几乎是一模一样的。

   image.png

IntelliJ IDEA 简称 IDEA,是 Jetbrains 公司旗下的一款 JAVA 开发工具,支持 Java、Scala、Groovy、kotlin 等语言的开发,同时具备支持目前主流的技术和框架,擅长于企业应用、移动应用和 Web 应用的开发,提供了丰富的功能,智能代码助手、代码自动提示、重构、J2EE支持、各类版本工具(git、svn等)、JUnit、CVS整合、代码分析、 创新的GUI设计等。

IntelliJ IDEA插件类型

在介绍IDEA插件开发流程之前,先了解下idea插件有哪些类型及其作用。

image.png

从官方文档可以看出,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方式。

image.png

DevKit的plugin项目,项目工程的配置文件是imi文件。

image.png

Sandbox

IntelliJ IDEA 插件以 Debug/Run 模式运行时是在 SandBox 中进行的,不会影响当前的 IntelliJ IDEA;但是同一台机器同时开发多个插件时默认使用的同一个 sandbox,即在创建 IntelliJ Platform SDK 时默认指定的 Sandbox Home.

如果需要每个插件的开发环境是相互独立的,可以创建多个 IntelliJ Platform SDK,为 Sandbox Home 指定不同的目录 。

image.png

Run/Debug

新建或修改运行/调试配置,选择使用的module后,可直接点击图标运行或调试。

image.png

Prepare Plugin For Deployment

如果项目中没有任何依赖,打出来的插件包是jar包

image.png

如果项目中有依赖其他插件或Library,则打出来的插件包是zip文件

image.png

插件的配置文件是plugin.xml,是整个插件的核心,见Gradle方式中的plugin.xml说明。

二、Gradle方式

Gradle插件是构建IntelliJ插件的推荐解决方案.该插件负责处理插件项目的依赖性 - 基本IDE和插件可能依赖的其他插件.  它还提供了使用插件运行IDE并将插件发布到JetBrains插件存储库的任务.对于新项目,官方推荐Gradle方式。

image.png

Gradle的plugin项目,项目工程的配置文件是build.gradle文件

image.png

gradle插件源码及版本信息

image.png

兼容性

image.png

如果依赖的插件版本和intellij版本不兼容,编译则会报如下图错误

image.png

关于兼容性,官方也作了说明

image.png

例如koltin插件,可以去JetBrains Plugins Repository查询各版本及其兼容的IDE版本

image.png

image.png

可以根据项目需要添加更多的配置

image.png

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触发执行的入口。

image.png

注册action

image.png

  • 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 ComponentIDEA启动时会初始化,IDEA生命周期中仅存在一个实例。
Project ComponentIDEA 会为每一个 Project 实例创建一个 Project 级别的component
Module ComponentIDEA 会为每一个 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>

注意:

  1. 一个 interface-class 不能有多个 implementation-class。
  2. 若组件没有创建 interface 类,而是直接实现了 ApplicationComponent 等接口,interface 和 implementation 可以指定为同一个类。
  3. 每一个组件都应该有一个唯一的名字,通过 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 在项目启动时共同加载。

一个组件加载过程:

  1. 创建:调用构造方法
  2. 初始化:调用 initComponent() 方法
  3. 如果是 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 卸载

一个组件卸载过程:

  1. 如果是 Project 或 Module 组件,调用 projectClosed()
  2. 接下来 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 界面。

image.png

image.png

image.png

面板编辑完成之后,点击Toolbar工具条那里的编译按钮。编译完成后,GUI的代码会生成在 对应的 Java文件里面。

image.png

Gradle Task

Gradle task 和 android studio的 类似,常用的也是打包、调试和发布task,只不过task 名不同。

image.png

更多说明和示例可见JetBrains/gradle-intellij-plugin

buildPlugin

buildPlugin  构建插件,常说的打包 插件包默认输出目录为build/distributions/插件名.zip

image.png

runIde

runIde 调试运行

现在双击runIde即可调出另外一个安装了这个插件的IDEA界面,然后可以看运行结果进行调试。 runIde还支持debug模式,不过运行时要右击选择:

image.png

如果setIdeDirectory设置为Android studio路径,则调试过程如下,会重新启动一个main(Android studio)

 

publishPlugin

publishPlugin 发布插件

上传插件市场

1、官方插件市场

官方文档:http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/publishing_plugin.html

image.png

2、自定义个人/公司插件存储服务器

image.png

   image.png

Install

如果要安装的插件非JetBrains官方插件市场发布的插件,则需要配置插件存储服务器地址,才能在Marketplace搜索到然后install。

image.png

搜索但还未安装时,右侧会显示updatePlugins.xml文件中配置的插件信息;从硬盘选择安装或已安装的插件,会显示plugin.xml文件中配置的插件信息。

image.png

最后

到这里,一个IDE插件的的开发、配置、调试运行、发布、安装的基本流程就介绍完毕了。插件的应用非常广泛,可以应用所有基于IntelliJ Platform开发的IDE软件。如果想深入学习IDE插件开发,建议阅读官方文档。官方文档内容更加全面丰富,但是由于文档精炼、简洁,所以在学习过程中,很多地方需要我们去揣摩、整理和总结。

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值