乐趣区

关于intellij-idea:Intellij-IDEA-插件开发-京东云技术团队

写在后面

很多 idea 插件文档更多的是介绍如何创立一个简略的 idea 插件,本篇文章从开发环境、demo、生态组件、增加依赖包、源码解读、网络申请、渲染数据、页面交互等方面介绍,是一篇可能满足根本的插件开发工程要求的文章。

如有疏漏欢送斧正,如想深刻理解欢送探讨。

一、简介

IntelliJ IDEA 与 IntelliJ Platform

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

IntelliJ Platform 是一个构建 IDE 的开源平台,基于它构建的 IDE 有 IntelliJ IDEA、WebStorm、DataGrip、以及 Android Studio 等等。IDEA 插件也是基于 IntelliJ Platform 开发的。

二、开发环境搭建

留神各软件版本要对应

1、开发工具

IDEA 2020.1 各版本下载地址:https://www.jetbrains.com/idea/download/other.html\
gradle 6.1 各版本下载地址:https://gradle.org/releases/\
org.jetbrains.intellij 0.4.22\
jdk 1.8\
首先看一下目前 idea 版本的变动,找到本人以后 idea 对应的版本须要的 jdk 版本 \
https://plugins.jetbrains.com/docs/intellij/build-number-rang…\
接下来须要找 idea 对应版本的 gradle 版本 \
https://www.jetbrains.com/legal/third-party-software/?product…\
最初就是找 gradle 版本对应的 org.jetbrains.intellij 插件版本,在 gradle-intellij-plugin 插件的 releases 页面,这里会在形容中指出有各个插件版本对应最低的 gradle 版本。\
https://github.com/JetBrains/gradle-intellij-plugin/releases?page=1\
增加依赖到 gradel 配置文件,获取相干依赖配置 \
https://mvnrepository.com/artifact/org.springframework/spring…

2、启用 Plugin DevKit

Plugin DevKit 是 IntelliJ 的一个插件,它应用 IntelliJ IDEA 本人的构建零碎来为开发 IDEA 插件提供反对。开发 IDEA 插件之前须要装置并启用 Plugin DevKit。\
关上 IDEA,导航到 Settings | Plugins,若插件列表中没有 Plugin DevKit,点击 Install JetBrains plugin,搜寻并装置。

3、配置 IntelliJ Platform Plugin SDK

IntelliJ Platform Plugin SDK 就是开发 IntelliJ 平台插件的 SDK, 是基于 JDK 之上运行的,相似于开发 Android 利用须要 Android SDK。\
3.1 导航到 File | Project Structure,抉择对话框左侧栏 Platform Settings 下的 SDKs

3.2 点击 + 按钮,先抉择 JDK,指定 JDK 的门路;再创立 IntelliJ Platform Plugin SDK,指定 home path 为 IDEA 的装置门路,如图

创立好 IntelliJ Platform Plugin SDK 后,抉择左侧栏 Project Settings 下的 Projects,在 Project SDK 下抉择刚创立的 IntelliJ Platform Plugin SDK。

4、设置源码门路(可选)

4.1 查看 build 号:关上 IDEA,Help | About,查看版本号及 build 号

4.2IDEA Community 源码(https://github.com/JetBrains/intellij-community/):切换到与 build 号雷同的分支,点击 Clone or download 按钮,抉择 Download ZIP

4.3 抉择工程构造设置后抉择 SDKs-> 选中之前在第 3 步增加的 sdk 点击 SourcePath 后按如下 1 点击增加一个 sourcePath,抉择下面下载额源码后点击 OK、点击 Applay

4.4 未装置源码时点击某一个 action(NewModuleAction)会看到如下所示浏览起来会比拟艰涩难懂。

5、Sandbox

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

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

三、开发一个简略插件

插件的创立、配置、运行、打包流程,以及 action

1、创立一个插件工程

抉择 File | New | Project,左侧栏中抉择 IntelliJ Platform Plugin 工程类型

点击 Next,设置工程名称及地位,点击 Finish 实现创立。能够到 File | Project Structure 来自定义工程设置。

除了在 idea 创立插件我的项目外,咱们还能够下载 github 模板代码进行批改:https://github.com/JetBrains/intellij-platform-plugin-template

2、插件工程构造

插件工程内容:

PluginDemo/
    resources/
      META-INF/
        plugin.xml
    src/
      com/foo/...
      ...
      ...

  • src 实现插件性能的 classes
  • resources/META-INF/plugin.xml 插件的配置文件,指定插件名称、形容、版本号、反对的 IntelliJ IDEA 版本、插件的 components 和 actions 以及软件商等信息。

3、plugin.xml

上面示例形容了可在 plugin.xml 文件配置的次要元素:

<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.plugin.demo.Component1Interface</interface-class>
      <!-- 组件的实现类 -->
      <implementation-class>com.plugin.demo.impl.Component1Impl</implementation-class>
    </component>
  </application-components>
  <!-- project components -->
  <project-components>
    <component>
      <!-- 接口和实现类雷同 -->
      <interface-class>com.plugin.demo.impl.Component2</interface-class>
    </component>
  </project-components>
  <!-- module components -->
  <module-components>
    <component>
      <interface-class>com.plugin.demo.impl.Component3</interface-class>
    </component>
  </module-components>
  <!-- Actions -->
  <actions>
    ...
  </actions>
  <!-- 插件定义的扩大点,以供其余插件扩大该插件 -->
  <extensionPoints>
    ...
  </extensionPoints>
  <!-- 申明该插件对 IDEA core 或其余插件的扩大 -->
  <extensions xmlns="com.intellij">
    ...
  </extensions>
</idea-plugin>

4、创立 Action

Action 是实现插件性能的类,一个 Action 类须要继承 AnAction 并且实现 actionPerformed 办法。当用户点击菜单或者工具栏按钮,按快捷键,或者通过 Help | Find Action 点击时,IntelliJ Platform 零碎会回调对应 Action 的 actionPerformed 办法。\
一个 Action 示意 IDEA 菜单里的一个 menu item 或工具栏上的一个按钮,通过继承 AnAction class 实现,当抉择一个 menu item 或点击工具栏上的按钮时,就会调用 AnAction 类的 actionPerformed 办法。\
实现自定义 Action 分两步:

  • 定义一个或多个 action
  • 注册 action,将 item 增加到菜单或工具栏上

4.1、定义 Action

定义一个 Java class,继承 AnAction 类,并重写 actionPerformed 办法,如

public class ActionDemo extends AnAction {public void actionPerformed(AnActionEvent event) {Project project = event.getData(PlatformDataKeys.PROJECT);
        Messages.showInputDialog(
          project,
          "What is your name?",
          "Input your name",
          Messages.getQuestionIcon());
    }
}

4.2、注册 Action

在 plugin.xml 文件的 <actions> 元素内注册

<actions>
  <group id="MyPlugin.SampleMenu" text="Sample Menu" description="Sample menu">
    <add-to-group group-id="MainMenu" anchor="last"  />
       <action id="Myplugin.ActionDemo" class="Mypackage.ActionDemo" text="Text Boxes" description="A test menu item" />
  </group>
</actions>

  • <action> 元素会定义一个 action,指定 action 的 id、实现类、显示文本、形容
  • <group> 元素会定义一个 action group(多个 action),设置 action group 的 id、文本、形容
  • <add-to-group> 元素指定其内部 action 或 action group 被增加到的地位

下面示例会定义一个被增加到 IDEA 主菜单的最初面的“SampleMenu”的菜单,点击该菜单将弹出一个“Text Boxes”item,如图

4.3、疾速创立 Action

IntelliJ Platform 提供了 New Action 向导,它会帮忙咱们创立 action class 并配置 plugin.xml 文件:

在指标 package 上右键,抉择 New | Plugin DevKit | Action:

  • Action ID: action 惟一 id,举荐 format: PluginName.ID
  • Class Name: 要被创立的 action class 名称
  • Name: menu item 的文本
  • Description: action 形容,toolbar 上按钮的提醒文本,可选
  • Add to Group:抉择新 action 要被增加到的 action group(Groups, Actions)以及绝对其余 actions 的地位(Anchor)
  • Keyboard Shortcuts:指定 action 的第一和第二快捷键

留神:该向导只能向主菜单中已存在的 action group 或工具栏上增加 action,若要创立新的 action group,请参考后面的内容。

5、运行调试插件

运行 / 调试插件可间接在 IntelliJ IDEA 进行,抉择 Run | Edit Configurations…,若左侧栏没有 Plugin 类型的 Configuration, 点击右上角 + 按钮,抉择 Plugin 类型, 如图

Use classpath of module 抉择要调试的 module,其余配置个别默认即可;切换到 Logs 选项卡,如果勾选了 idea.log,运行插件时 idea.log 文件的内容将输入到 idea.log console。

运行插件点击工具栏上运行按钮 Run

\

6、打包装置插件

6.1、打包插件

抉择 Build | Prepare Plugin Module‘module name’for Deployment 来打包插件:

jar 类型的插件包:

    PluginDemo.jar/
      com/xxx/...
      ...
      ...
      META-INF/
        plugin.xml

zip 类型的插件包:

PluginDemo.zip/
  lib/
    libxxx.jar
    libbar.jar
    PluginDemo.jar/
      com/xxx/...
      ...
      ...
      META-INF/
        plugin.xml

6.2、装置插件

导航到 File | Settings | Plugins 页面,点击 Install plugin from disk…

  • 抉择插件包的地位,点击 OK
  • 在插件列表中,勾选插件名字前面的 check-box 来启用插件,点击 OK
  • 重启 IDEA\
    Install JetBrains plugin… 从 JetBrains 仓库(https://plugins.jetbrains.com/)中装置插件 \
    Browse repositories… 增加并治理本人的仓库

四、Action 容许增加的地位

这个时候咱们理解的都比拟通俗还停留在 demo 层面,如何进行深刻的理解呢?

eg: 咱们怎么晓得都有哪些 action 或 action group 能够被咱们增加呢?

1. 增加主菜单 MainMenu

1、咱们能够点击配置 group-id=”MainMenu” 下的 MainMenu

<actions>
  <group id="MyPlugin.SampleMenu" text="Sample Menu" description="Sample menu">
    <add-to-group group-id="MainMenu" anchor="last"  />
       <action id="Myplugin.Textboxes" class="Mypackage.TextBoxes" text="Text Boxes" description="A test menu item" />
  </group>
</actions>

2、进入 PlatformActions.xml 如下图,这个时候不难看出这里就是主菜单的第一列子菜单

3. 这个时候如果咱们想新建个相似与 File–>New 和 Open 的菜单该怎么做呢?

3.1 咱们应该先实现布局,增加主菜单 MainMenu

  <!-- Actions -->
    <actions>
        <group id="MainMenuActionGroup" text="MainMenuActionGroup" description="MainMenuActionGroup" popup="true">
            <add-to-group group-id="MainMenu" anchor="after" relative-to-action="HelpMenu"/>
            <action id="OpenFile" class="com.plugin.demo.action.MainMenuOpenFileAction" text="Open"
                    description="主菜单 File 下的 Open 子菜单"/>
            <separator/>
        </group>
        <group id="JavaNewProjectOrModuleGroup" text="一级菜单" popup="true">
            <add-to-group group-id="MainMenuActionGroup" anchor="before" relative-to-action="OpenFile"/>
            <action id="NewProject" class="com.intellij.ide.actions.NewProjectAction"/>
            <action id="ImportProject" class="com.intellij.ide.actions.ImportProjectAction"/>
            <action id="ProjectFromVersionControl"
class="com.intellij.openapi.wm.impl.welcomeScreen.ProjectFromVersionControlAction"/>
            <separator/>
            <action id="NewModule" class="com.intellij.openapi.roots.ui.configuration.actions.NewModuleAction"/>
            <action id="ImportModule" class="com.intellij.ide.actions.ImportModuleAction"/>
        </group>
    </actions>

3.2 实现自定义的关上文件

其实是通过上面的 action 配置的 OpenFileAction 找到源码

<action id="OpenFile" class="com.intellij.ide.actions.OpenFileAction" icon="AllIcons.Actions.Menu_open"/>

在将源码拷贝进去粘贴到本人的 action 内。这样就能够实现本人的主菜单 File 下的 Open 子菜单

3.3 这个时候有人会有疑难我不晓得去哪找 New 对应的 action 呀?

这个时候咱们通过界面能够看到 Project from Existing Sources…,这里咱们就能够去搜这个文本呀。既然显示在页面上。必然有中央定义了它。ActionBundle.properties

这个时候咱们在依据对应的 action 定义的文本在去搜寻对应的 action,com.intellij.ide.actions.ImportProjectAction

3.4 这个时候咱们将对应的 action 拷贝到本人的插件定义的配置上也就造成了 3.1 的一级和二级菜单

2. 增加主工具栏 MainToolBar

增加主工具栏 MainToolBar(如果不分明哪里是主菜单、主工具栏、导航栏、上下文菜单、弹出菜单参考 https://www.w3cschool.cn/intellij6f1017d5aa6296e6c3bb8c666b1a…)

   <group>
            <add-to-group group-id="MainToolBar" anchor="before" relative-to-action="SearchEverywhere"/>
            <reference ref="MainMenuActionGroup"></reference>
        </group>

3、增加上下文菜单 ProjectViewPopupMenu

   <group>
            <add-to-group group-id="ProjectViewPopupMenu" anchor="before" relative-to-action="WeighingNewGroup"/>
            <reference ref="MainMenuActionGroup"></reference>
        </group> 

4、增加弹出菜单 EditorPopupMenu

 <!-- 增加到弹出框右键 -->
        <group>
            <add-to-group group-id="EditorPopupMenu" anchor="before" relative-to-action="ShowIntentionsGroup"/>
            <reference ref="MainMenuActionGroup"></reference>
        </group>

5、增加打印 ConsoleEditorPopupMenu

    <!-- 增加到控制台打印右键 -->
        <group>
            <add-to-group group-id="ConsoleEditorPopupMenu" anchor="before" relative-to-action="CutCopyPasteGroup"/>
            <reference ref="MainMenuActionGroup"></reference>
        </group>

6、右键新建 action 时也能够间接抉择增加的地位。

1. 筛选后查找要增加的 group\
2. 抉择对应的 action\
3. 抉择要增加到这个 action 的某个地位

五、Components(已不倡议应用)

IntelliJ IDEA 的组件模型是基于 PicoContainer 的,组件都蕴含在这些容器中,但容器有三种级别:application container,project container 以及 module container。application container 能够蕴含多个 project container,而 project container 能够蕴含多个 module container。

1、Components 类型

Components 是插件开发的根底,Components 有三种类型:

2、注册 Components

components 须要配置在 plugin.xml 中,并指定 interface 和 implementation,interface 类用于从其余组件中检索组件,implementation 类用于实例化组件。示例:

// 创立一个 application level component
public interface Component1 extends ApplicationComponent {
}

public class Component1Impl implements Component1 {

    @Override
    public String getComponentName() {return "PluginDemo.Component1";}
}

plugin.xml

<application-components>
    <component>
      <interface-class>com.example.test.Component1</interface-class>
      <implementation-class>com.example.test.Component1Impl</implementation-class>
    </component>
 </application-components>

留神:一个 interface-class 不能有多个 implementation-class,如下图:

  1. 若组件没有创立 interface 类,而是间接实现了 ApplicationComponent 等接口,interface 和 implementation 能够指定为同一个类。
  2. 每一个组件都应该有一个惟一的名字,通过 getComponentName() 返回,举荐应用 \<plugin\_name>.\<component\_name> 格局。

3、Component 周期办法

ApplicationComponent 的生命周期办法:

// 构造方法
public constructor(){}
// 初始化
public void initComponent() {}

public void disposeComponent() {}

ProjectComponent 的生命周期办法:

// 构造方法
public constructor(){}
// 告诉一个 project 曾经实现加载
public void projectOpened() {}

public void projectClosed() {}
// 执行初始化操作以及与其余 components 的通信
public void initComponent() {}
// 开释系统资源或执行其余清理
public void disposeComponent() {}

ModuleComponent 的生命周期办法:

ModuleComponent 的生命周期办法中比 ProjectComponent 多一个 moduleAdded(),用于告诉 module 曾经被增加到 project 中。

4、Component 加载

Application 级别的 components 在 IDEA 启动时加载,Project 和 Module 级别的 components 在我的项目启动时独特加载。

一个组件加载过程:

  1. 创立:调用构造方法
  2. 初始化:调用 initComponent() 办法
  3. 如果是 Project 组件,会调用 projectOpened() 办法;如果是 Module 组件,会顺次调用 moduleAdded() 和 projectOpened() 办法

如果 component 在加载时须要用到其余 component,咱们只需在该 component 的构造方法的参数列表申明即可,在这种状况下,IntelliJ IDEA 会按正确的程序实例化所依赖的 component。

示例:

public class MyComponent implements ApplicationComponent {
    private final MyOtherComponent otherComponent;

    public MyComponent(MyOtherComponent otherComponent) {this.otherComponent = otherComponent;}
    ...
}

5、Component 卸载

一个组件卸载过程:

  1. 如果是 Project 或 Module 组件,调用 projectClosed()
  2. 接下来 disposeComponent() 将被调用

6、Component 容器

后面咱们提到有三种不同的容器,application container 实现 Application 接口; project container 实现 Project 接口;

module container 实现 Module 接口。每一个容器都有本人的办法去获取容器内的 component。

获取 application 容器及其外部的组件:

/ 获取 application 容器
Application application = ApplicationManager.getApplication();
// 获取 application 容器中的组件
MyComponent myComponent = application.getComponent(MyComponent.class);

获取 project / module 容器及其外部的组件:

在 component 构造方法的参数列表中申明:

public class MyComponent implements ProjectComponent {
Project project;
public MyComponent(Project project){this.project = project;}

public void initComponent() {OtherComponent otherComponent = project.getComponent(OtherComponent.class);
}
}

在这个例子中,组件在构造方法中获取了容器对象,将其保留,而后在 component 其余中央进行援用。

7、各组件应用机会

7.1 创立一个 ApplicationComponent

package com.plugin.demo.component;

import com.intellij.openapi.components.ApplicationComponent;

// 创立一个 application level component
public interface ApplicationComponentDemo extends ApplicationComponent {
}


package com.plugin.demo.component;

import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;

public class ApplicationComponentDemoImpl implements ApplicationComponentDemo {

    @Override
    public String getComponentName() {System.out.println("ApplicationComponentDemoImpl =" +this.getClass().getName());
        return this.getClass().getName();
    }

    // 初始化
    public void initComponent() {System.out.println("ApplicationComponentDemoImpl initComponent");
    }

    public void disposeComponent() {
        // 获取 application 容器
        Application application = ApplicationManager.getApplication();
        // 获取 application 容器中的组件
        ApplicationComponentDemoImpl myComponent = application.getComponent(ApplicationComponentDemoImpl.class);
        System.out.println("disposeComponent =" + myComponent.getComponentName());
    }
}

7.2 创立一个 ProjectComponent

package com.plugin.demo.component;

import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.ProjectComponent;
import org.jetbrains.annotations.NotNull;

public class ProjectComponentDemo implements ProjectComponent {
    @NotNull
    @Override
    public String getComponentName() {
        // 获取 application 容器
        Application application = ApplicationManager.getApplication();
        // 获取 application 容器中的组件
        ApplicationComponentDemoImpl myComponent = application.getComponent(ApplicationComponentDemoImpl.class);
        System.out.println("ProjectComponentDemo =" + myComponent.getComponentName());
        return myComponent.getComponentName();}

    @Override
    public void initComponent() {
//        获取 application 容器
        Application application = ApplicationManager.getApplication();
//        获取 application 容器中的组件
        ApplicationComponentDemo component = application.getComponent(ApplicationComponentDemo.class);
        System.out.println("ApplicationComponentDemoImpl initComponent =" + component.getComponentName());
        System.out.println("ProjectComponentDemo initComponent");
    }

    @Override
    public void disposeComponent() {
        // 获取 application 容器
        Application application = ApplicationManager.getApplication();
        // 获取 application 容器中的组件
        ApplicationComponentDemoImpl myComponent = application.getComponent(ApplicationComponentDemoImpl.class);
        System.out.println("disposeComponent =" + myComponent.getComponentName());
    }
}

7.3 创立一个 ModuleComponent

package com.plugin.demo.component;

import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.ApplicationComponent;
import com.intellij.openapi.module.ModuleComponent;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import org.jetbrains.annotations.NotNull;

public class ModuleComponentDemo implements ModuleComponent {


    @NotNull
    @Override
    public String getComponentName() {
        // 获取 application 容器
        Application application = ApplicationManager.getApplication();
        // 获取 application 容器中的组件
        ApplicationComponentDemoImpl myComponent = application.getComponent(ApplicationComponentDemoImpl.class);
        System.out.println("ApplicationComponentDemoImpl initComponent =" + myComponent.getComponentName());
        ProjectManager projectManager = ProjectManager.getInstance();
        Project defaultProject = projectManager.getDefaultProject();
        ProjectComponentDemo component = defaultProject.getComponent(ProjectComponentDemo.class);
        System.out.println("ProjectComponentDemo initComponent" + component.getComponentName());
        return myComponent.getComponentName();}

    @Override
    public void initComponent() {System.out.println("ModuleComponentDemo initComponent");
    }

    @Override
    public void disposeComponent() {
        // 获取 application 容器
        Application application = ApplicationManager.getApplication();
        // 获取 application 容器中的组件
        ApplicationComponentDemoImpl myComponent = application.getComponent(ApplicationComponentDemoImpl.class);
        System.out.println("disposeComponent =" + myComponent.getComponentName());
    }
}

7.4 注册配置 Component

 <application-components>   
        <component>   
             <interface-class>com.plugin.demo.component.ApplicationComponentDemo</interface-class>  
             <implementation-class>com.plugin.demo.component.ApplicationComponentDemoImpl</implementation-class>    
        </component>
    </application-components>
    <project-components>
        <component>   
             <interface-class>com.plugin.demo.component.ProjectComponentDemo</interface-class>  
             <implementation-class>com.plugin.demo.component.ProjectComponentDemo</implementation-class>    
        </component>
    </project-components>
    <module-components>
        <component>   
             <interface-class>com.plugin.demo.component.ModuleComponentDemo</interface-class>  
             <implementation-class>com.plugin.demo.component.ModuleComponentDemo</implementation-class>    
        </component>
    </module-components>

7.5 运行后的预期是先执行应用层组件,在执行工程级组件,在执行模块级组件

六、Extensions and Extension Points

如果插件须要扩大 IDEA Platform 或 其余插件的性能,或为其余插件提供能够扩大本人的接口,那么就要用到 extensions 和 extension points,用于与 IDEA 和其余插件交互。

1、Extension points 扩大点

extension point 用于数据信息扩大,使其余插件能够扩大本插件的性能,可通过 plugin.xml 的 元素申明,如下示例:

<extensionPoints>
    <!-- 应用 beanClass 申明 -->
    <extensionPoint name="MyExtensionPoint1" beanClass="MyPackage.MyBeanClass" area="IDEA_APPLICATION">
        <with attribute="implementationClass" implements="MyPackage.MyAbstractClass"/>
    </extensionPoint>
    <!-- 应用 interface 申明 -->
    <extensionPoint name="MyExtensionPoint2" interface="MyPlugin.MyInterface" area="IDEA_PROJECT" />
</extensionPoints>

  • name 指定 extension point 的名字,当其余插件扩大该 extensionPoint 时,须要指定该 name
  • area 有三种值,IDEAAPPLICATION,IDEAPROJECT,IDEA\_MODULE,指定 extension point 的级别
  • interface 指定须要扩大此 extension point 的插件必须要实现的接口
  • beanClass 指定一个类,该类有一个或多个被 @Attribute 注解的属性
  • 申明 extension point 有两种形式,指定 beanClass 或 interface
  • 如果某个属性须要是某个类的子类,或某个接口的实现类,须要通过 指明类名或接口名。

示例上述代码中的 MyExtensionPoint1 的 beanClass:

public class MyBeanClass extends AbstractExtensionPointBean {@Attribute("key")
  public String key;

  @Attribute("implementationClass")
  public String implementationClass;

  ...
}

2、Extension 扩大其余插件性能

如果插件须要扩大 IntelliJ Platform 或其余插件的性能,须要申明一个或多个 extension。

  1. 设置 的 defaultExtensionNs 属性若是扩大 IntelliJ Platform,设为 com.intellij 若是扩大其余插件,则设为 pluginId
  2. 指定要扩大哪个 extension point 外部的子标签的名字必须与 extension point 的 name 属性雷同
  3. 如果 extension point
  • 是通过 interface 申明的,那么应用 implementation 属性指明 interface 的实现类
  • 是通过 beanClass 申明的,那么就要为 beanClass 中被 @Attribute 注解的属性指定属性值

示例:

<!-- 扩大 interface 申明的 extensionPoint -->
  <extensions defaultExtensionNs="com.intellij">
    <appStarter implementation="MyPackage.MyExtension1" />
    <applicationConfigurable implementation="MyPackage.MyExtension2" />
  </extensions>

  <!-- 扩大 beanClass 申明的 extensionPoint -->
  <extensions defaultExtensionNs="pluginId">
     <MyExtensionPoint1 key="keyValue" implementationClass="MyPackage.MyClassImpl"></MyExtensionPoint1>
  </extensions>

插件的 service 的实现就是扩大 IDEA Platform 的 applicationService 或 projectService 两个 extension points

3、获取 extension points

IntelliJ Platform 的局部 extension points

 <extensionPoints>
    <extensionPoint name="languageBundle" beanClass="com.intellij.DynamicBundle$LanguageBundleEP"/>
    <!--suppress PluginXmlValidity -->
    <extensionPoint name="applicationService" beanClass="com.intellij.openapi.components.ServiceDescriptor" dynamic="true"/>
    <!--suppress PluginXmlValidity -->
    <extensionPoint name="projectService" beanClass="com.intellij.openapi.components.ServiceDescriptor" dynamic="true"/>
    <!--suppress PluginXmlValidity -->
    <extensionPoint name="moduleService" beanClass="com.intellij.openapi.components.ServiceDescriptor" dynamic="true"/>
    <extensionPoint name="virtualFileManagerListener" interface="com.intellij.openapi.vfs.VirtualFileManagerListener" dynamic="true"/>
    <extensionPoint name="vfs.asyncListener" interface="com.intellij.openapi.vfs.AsyncFileListener" dynamic="true"/>

    <!-- only bundled plugin can define startupActivity -->
    <extensionPoint name="startupActivity" interface="com.intellij.openapi.startup.StartupActivity"/>
    <extensionPoint name="postStartupActivity" interface="com.intellij.openapi.startup.StartupActivity" dynamic="true"/>
    <extensionPoint name="backgroundPostStartupActivity" interface="com.intellij.openapi.startup.StartupActivity" dynamic="true"/>

    <extensionPoint name="fileTypeDetector" interface="com.intellij.openapi.fileTypes.FileTypeRegistry$FileTypeDetector" dynamic="true"/>
    <extensionPoint name="editorFactoryDocumentListener" interface="com.intellij.openapi.editor.event.DocumentListener" dynamic="true"/>
    <extensionPoint name="multiHostInjector" interface="com.intellij.lang.injection.MultiHostInjector" area="IDEA_PROJECT" dynamic="true"/>
    <extensionPoint name="writingAccessProvider" area="IDEA_PROJECT" interface="com.intellij.openapi.vfs.WritingAccessProvider" dynamic="true"/>
    <extensionPoint name="metaLanguage" interface="com.intellij.lang.MetaLanguage"/>
    <extensionPoint name="lang.parserDefinition" beanClass="com.intellij.lang.LanguageExtensionPoint" dynamic="true">
      <with attribute="implementationClass" implements="com.intellij.lang.ParserDefinition"/>
    </extensionPoint>
    <extensionPoint name="lang.elementManipulator" beanClass="com.intellij.openapi.util.ClassExtensionPoint" dynamic="true">
      <with attribute="implementationClass" implements="com.intellij.psi.ElementManipulator"/>
    </extensionPoint>
    <!--suppress PluginXmlValidity -->
    <extensionPoint name="stubElementTypeHolder" beanClass="com.intellij.psi.stubs.StubElementTypeHolderEP" dynamic="true"/>
  </extensionPoints>
  <extensions defaultExtensionNs="com.intellij">
    <applicationService serviceInterface="com.intellij.util.messages.MessageBusFactory"
                        serviceImplementation="com.intellij.util.messages.impl.MessageBusFactoryImpl"/>
  </extensions>

其余能够从被扩大插件的 plugin.xml 文件中获取 \
https://plugins.jetbrains.com/intellij-platform-explorer/exte…

七、Service

参考:https://plugins.jetbrains.com/docs/intellij/plugin-services.h…

Service 也是一种按需加载的 component,在调用 ServiceManager.getService(Class)时才会加载,且程序中只有一个实例。

Service 是插件的一个组件,是为了把公共的逻辑放到一起,Service 的实例是单例的。

Serivce 在 IntelliJ IDEA 中是以 extension point 模式提供的,实现本人的 service 须要扩大相应 extension point。

  • applicationService: application level service
  • projectService: project level service
  • moduleService: module level service

申明 service 时必须蕴含 serviceImplementation 属性用于实例化 service,serviceInterface 属性是可选的,可用于获取 service 实例。

1、创立 Service

在须要搁置 service 的 package 上右键,New | Plugin DevKit | xxxxService,如图

抉择相应 service,弹出如下对话框,填写 interface 类和 implementation 类,若不勾选 Separate interface from implementation,只需填写 implementation 类。

\
IntelliJ IDEA 会主动创立相应类并配置 plugin.xml 文件。\
示例:plugin.xml:

 <extensions defaultExtensionNs="com.intellij">
        <applicationService serviceInterface="com.plugin.demo.service.ApplicationServiceDemo"
                            serviceImplementation="com.plugin.demo.service.impl.ApplicationServiceDemoImpl"/>
        <projectService serviceInterface="com.plugin.demo.service.ProjectServiceDemo"
                        serviceImplementation="com.plugin.demo.service.impl.ProjectServiceDemoImpl"/>
        <moduleService serviceInterface="com.plugin.demo.service.ModuleServiceDemo"
                       serviceImplementation="com.plugin.demo.service.impl.ModuleServiceDemoImpl"/>
    </extensions>

生成的 service 类:

public interface ApplicationServiceDemo {static ApplicationServiceDemo getInstance() {return ServiceManager.getService(ApplicationServiceDemo.class);
    }
}
public interface ProjectServiceDemo {static ProjectServiceDemo getInstance(@NotNull Project project) {return ServiceManager.getService(project, ProjectServiceDemo.class);
    }
}
public interface ModuleServiceDemo {static ModuleServiceDemo getInstance(@NotNull Module module) {return module.getService(ModuleServiceDemo.class);
    }
}

public class ApplicationServiceDemoImpl implements ApplicationServiceDemo {public ApplicationServiceDemoImpl() {System.out.println("ApplicationServiceDemoImpl =");
    }
}
public class ProjectServiceDemoImpl implements ProjectServiceDemo {public ProjectServiceDemoImpl(Project project) {System.out.println("ProjectServiceDemoImpl =" + project);
    }
}
public class ModuleServiceDemoImpl implements ModuleServiceDemo {public ModuleServiceDemoImpl(Module project) {System.out.println("ModuleServiceDemoImpl =" + project);
    }
}

2、获取 Service

MyApplicationService applicationService = ServiceManager.getService(MyApplicationService.class);

// 获取 project 级别的 service,须要提供 project 对象
MyProjectService projectService = ServiceManager.getService(project, MyProjectService.class);

// 获取 module 级别的 service,须要提供 module 对象
MyModuleService moduleService = ModuleServiceManager.getService(module, MyModuleService.class);

八、长久化状态

咱们在应用 IDE 开始开发工作之前,总是要先在 settings 页面进行一些设置,且每次从新关上 IDE 后这些设置依然保留着,那么这些设置是如何保留下来的呢?

IntelliJ Platform 提供了一些 API,能够使 components 或 services 在每次关上 IDE 时依然应用之前的数据,即长久化其状态。

1、PropertiesComponent

对于一些简略大量的值,咱们能够应用 PropertiesComponent,它能够保留 application 级别和 project 级别的值。

上面办法用于获取 PropertiesComponent 对象:

// 获取 application 级别的 PropertiesComponent
PropertiesComponent.getInstance()
// 获取 project 级别的 PropertiesComponent,指定相应的 project
PropertiesComponent.getInstance(Project)

propertiesComponent.setValue(name, value)
propertiesComponent.getValue(name)

PropertiesComponent 保留的是键值对,因为所有插件应用的是同一个 namespace,强烈建议应用前缀来命名 name,比方应用 plugin id。

2、PersistentStateComponent

PersistentStateComponent 用于长久化比较复杂的 components 或 services,能够指定须要长久化的值、值的格局以及存储地位。

要应用 PersistentStateComponent 长久化状态:

  • 须要提供一个 PersistentStateComponent 接口的实现类(component 或 service),指定类型参数,重写 getState() 和 loadState() 办法
  • 类型参数就是要被长久化的类,它能够是一个 bean class,也能够是 PersistentStateComponent 实现类自身。
  • 在 PersistentStateComponent 的实现类上,通过 @com.intellij.openapi.components.State 注解指定存储的地位

上面通过两个例子进行阐明:

class MyService implements PersistentStateComponent<MyService.State> {
  // 这里 state 是一个 bean class
  static class State {
    public String value;
    ...
  }

  // 用于保留以后的状态
  State myState;

  // 从以后对象里获取状态
  public State getState() {return myState;}
  // 从内部加载状态,设置给以后对象的相应字段
  public void loadState(State state) {myState = state;}
}

// 这里的 state 就是实现类自身
class MyService implements PersistentStateComponent<MyService> {
  public String stateValue;
  ...

  public MyService getState() {return this;}

  public void loadState(MyService state) {XmlSerializerUtil.copyBean(state, this);
  }
}

2.1、实现 State 类

a、字段要求

state 类中可能有多个字段,但不是所有字段都能够被长久化,能够被长久化的字段:

  • public 字段
  • bean 属性:提供 getter 和 setter 办法
  • 被注解的公有字段:应用 @Tag, @Attribute, @Property, @MapAnnotation, @AbstractCollection 等注解来自定义存储格局,个别在实现向后兼容时才思考应用这些注解

这些字段也有类型要求:

  • 数字(包含根底类型,如 int,和封装类型,如 Integer)
  • 布尔值
  • 字符串
  • 汇合
  • map
  • 枚举

如果不心愿某个字段被长久化,能够应用 @com.intellij.util.xmlb.annotations.Transient 注解。

b、结构器要求

state 类必须有一个默认结构器,这个结构器返回的 state 对象被认为是默认状态,只有当以后状态与默认状态不同时,状态才会被长久化。

2.2、定义存储地位

咱们能够应用 @State 注解来定义存储地位

@State(name = "PersistentDemo", storages = {@Storage(value = "PluginDemo.xml")})
public class PersistentDemo implements PersistentStateComponent<PersistentDemo> {...}

name:定义 xml 文件根标签的名称

storages:一个或多个 @Storage,定义存储的地位

  • 若是 application 级别的组件运行调试时 xml 文件的地位:\~/IdeaICxxxx/system/plugins-sandbox/config/options 正式环境时 xml 文件的地位:\~/IdeaICxxxx/config/options
  • 若是 project 级别的组件,默认为我的项目的 .idea/misc.xml,若指定为 StoragePathMacros.WORKSPACE\_FILE,则会被保留在 .idea/worksapce.xml

2.3、生命周期

  • loadState() 当组件被创立或 xml 文件被内部扭转(比方被版本控制系统更新)时被调用
  • getState() 当 settings 被保留(比方 settings 窗口失去焦点,敞开 IDE)时,该办法会被调用并保留状态值。如果 getState() 返回的状态与默认状态雷同,那么什么都不会被保留。
  • noStateLoaded() 该办法不是必须实现的,当初始化组件,然而没有状态被长久化时会被调用

2.4、组件申明

长久化组件能够申明为 component,也能够申明为 service

申明为 service,plugin.xml 文件如下配置:

<extensions defaultExtensionNs="com.intellij">
    <applicationService serviceImplementation="com.example.test.persisting.PersistentDemo"/>
    <projectService serviceImplementation="com.example.test.persisting.PersistentDemo2"/>
  </extensions>

代码中获取状态与获取 service 的形式一样:

PersistentDemo persistDemo = ServiceManager.getService(PersistentDemo.class);
PersistentDemo2 persistDemo2 = ServiceManager.getService(project,PersistentDemo.class);

申明为 component,plugin.xml 文件如下配置:

<application-components>
  <!-- 将长久化组件申明为 component-->
  <component>
    <implementation-class>com.example.persistentdemo.PersistentComponent</implementation-class>
  </component>
</application-components>

获取状态与获取 component 的形式一样:

public static PersistentComponent getInstance() {return ApplicationManager.getApplication().getComponent(PersistentComponent.class);
}
public static PersistentComponent getInstance(Project project) {return project.getComponent(PersistentComponent.class);
}

九、插件依赖

开发插件时可能会用到其余插件,可能是 IDEA 绑定的,也可能是第三方的插件。

配置插件依赖须要将插件包增加到 SDK 的 classpath 中,并在 plugin.xml 配置。

  1. 确定插件包的地位如果插件是 IDEA 捆绑的插件,那么插件包在 IDEA 装置目录的 plugins/ 或 plugins//lib 下。如果插件是第三方或本人的,那么须要先运行一次 sandbox(其实咱们在运行调试插件的时候就是在运行 sandbox)并从本地或插件仓库装置依赖插件。装置好后,插件包会放在 sandbox 目录下的 config/plugins/ 或 config/plugins//lib,查看 sandbox 目录:关上 IntelliJ Platform SDK 配置页面,其中 Sandbox Home 就是其目录。
  2. 将插件包增加到 SDK 的 classpath 中导航到 File | Project Structure | SDKs,抉择插件应用的 IntelliJ Platform SDK,点击右侧 + 号,在弹出的文件抉择框中抉择要依赖的插件包,点击 OK。

配置 plugin.xml 在 plugin.xml 的 局部增加所依赖插件的 id。

 org.jetbrains.kotlin

plugin id 能够从插件包的 plugin.xml 文件查看。

十、GUI 介绍

GUI 是 IntelliJ IDEA 提供的一个主动生成 java 布局代码的工具,它应用 JDK 中的 Swing 控件来实现 UI 界面。

应用步骤:

1. 配置

配置 GUI 首先关上 Settings 对话框,抉择 Editor | GUI Designer,如图,在 Generate GUI into: 有两个选项,生成 class 文件或 java 代码,咱们抉择生成 java 代码,因为建好布局后可能须要批改代码。其余默认即可。

2. 创立 form

创立 form 文件 form 文件用于记录界面布局。在相应的 package 上右键,抉择 New | GUI Form,如图,输出 form 文件名,个别与 java 文件名雷同,点击 OK 创立 form 与 java 文件。

3. 面板介绍

编辑界面关上 form 文件,如图,通过拖拽控件来搭建布局。每个 form 文件布局的 root 控件都是一个 JPanel, 可将该 root 对象传给须要该布局的类。留神:左下角的属性面板,只有当填写了 field name 属性时该控件的对象才会被当成成员变量,否则为局部变量。

4. 构建

生成 java 代码搭建好布局后,点击 build

编译按钮,即可生成 java 的源码文件。

GUI 生成的办法名前后都有三个 $ 标识,当再次批改布局时,GUI 只会批改 $ 标识的办法。

十一、源码剖析 SmartConverter

SmartConverter — POJO Object Converter

我的项目地址:https://github.com/zitiger/smartconverter

1、我的项目背景

在分层开发中,咱们总是面临着各种 POJO(DTO,DO,JO,VO)对象之间的互相转换。当对象比较复杂时,编写转换代码耗时较多,且非常容易出错。以至于可能会呈现写一天代码,半天在写各种 convert 的囧境。

为了实现主动转换,呈现了 BeanUtil 和 ModelMapper 等解决方案。这些计划,在大量对象转换时,性能损耗能够疏忽,然而当转换数量达到一定量级时,这种损耗会对性能产生影响。

本插件能够主动生成 POJO 之间的转换代码,省去手工转换的麻烦,也不会损失性能。

2、装置

下载 SmartConverter.zip,并在 Intellij Idea 中装置;

3、四个转换函数

  1. 把光标放到函数中,不能是函数内.
  2. 光标挪动到函数体内,按下⌘+N,在弹出的 Generate 菜单中选择 Smart Converter;
  3. 插件主动生成一下四个转换函数
  • A -> B
  • B -> A
  • List-> List
  • List**-> List**

4、单个抓换函数

  1. 在编辑器中,确定返回值和参数,实现空转换函数;

     public static List<UserJO> toDTOList(List<UserDTO> userDTOList) { }
    
    
  2. 光标挪动到函数体内,按下⌘+N,在弹出的 Generate 菜单中选择 Smart Converter;
  3. 插件依据入参和出参推断出须要转换的 POJO。

5、插件特色

插件主动从转换函数的参数和返回值推断出转换 POJO;

反对 List 之间的转换。

如果存在单个转换的函数,则间接应用

如果不存在单个转换的函数,创立单个转换函数

反对嵌套转换

6、源码解读

6.1. 如何将 ConvertGeneratorAction 增加到菜单

因为应用 SmartConvert 是应用 alt+insert 弹出或者右键点击 Generate 显示 SmartConvertAction, 所以依据前文的增加地位不难推断增加在弹出菜单 EditorPopupMenu 下,这个时候咱们能够从两个方向找他增加的地位。

首先从我的项目的配置文件进入找到 plugin.xml 下配置的 action。由此不难看出它理论是增加在了 GenerateGroup 这个组上的

 <actions>
        <group id="com.zitiger.plugin.converter.generate.group" popup="true">
            <separator/>
            <!-- Add your actions here -->
            <action id="com.zitiger.plugin.converter.action.generator" class="com.zitiger.plugin.converter.action.ConvertGeneratorAction"
                    text="Smart Converter" description="Smart Converter">
                <keyboard-shortcut keymap="$default" first-keystroke="shift meta N"/>
            </action>
            <add-to-group group-id="GenerateGroup" anchor="last"/>
        </group>
    </actions>

这个时候咱们不难看出并没有中央援用这个组,这个时候咱们不防从应用的中央动手,咱们是右键点击 Generate 或者 alt+insert 弹出的 EditorLangPopupMenu 下的 Generate 的组。这个时候咱们去全局搜寻 EditorPopupMenu

发现这里有一个增加到右键菜单下的

 <group id="EditorLangPopupMenu">
      <separator/>
      <group id="EditorPopupMenu.GoTo" popup="true">
        <reference ref="ShowNavBar"/>
        <reference ref="GotoDeclaration"/>
        <reference ref="GotoImplementation"/>
        <reference ref="GotoTypeDeclaration"/>
        <reference ref="GotoSuperMethod"/>
        <reference ref="GotoTest"/>
      </group>
      <reference ref="Generate"/>
      <separator/>
      <group id="EditorPopupMenu.Run">
        <reference ref="RunContextPopupGroup"/>
      </group>
      <separator/>
      <reference ref="VersionControlsGroup"/>
      <separator/>
      <reference ref="ExternalToolsGroup"/>
      <add-to-group group-id="EditorPopupMenu" relative-to-action="CompareClipboardWithSelection" anchor="before"/>
    </group>

点击后跳转的是

 <action id="Generate" class="com.intellij.codeInsight.generation.actions.GenerateAction"/>

GenerateAction 的点击办法 actionPerformed 内动静生成了 ActionGroup

JBPopupFactory.getInstance().createActionGroupPopup(CodeInsightBundle.message("generate.list.popup.title"), wrapGroup(getGroup(),dataContext,project),dataContext,JBPopupFactory.ActionSelectionAid.SPEEDSEARCH, false);

而 getGroup() 通过指定 groupid 获取到 GenerateGroup 的 Action 组

return (DefaultActionGroup)ActionManager.getInstance().getAction(IdeActions.GROUP_GENERATE);

6.2. 如何实现实体 \~\~\~\~ 转换

2.1Program Structure Interface (PSI)

https://plugins.jetbrains.com/docs/intellij/psi-files.html

程序结构接口,通常简称为 PSI,负责解析文件并创立语法和语义代码模型,为平台的泛滥性能提供反对。

PSI 文件是构造的根,将文件内容示意为特定编程语言中元素的层次结构

PsiFile 是所有 PSI 文件的公共基类,而特定语言的文件通常由其子类示意。例如 PsiJavaFile 类代表一个 Java 文件,类 XmlFile 代表一个 XML 文件。

2.2 查看某一个文件的 PSI 构造

参考文档:PSI Viewer

https://www.jetbrains.com/help/idea/psi-viewer.html?_ga=2.203….

未配置开启查看 PIS 构造时如下图

开启查看 PIS 构造 找到 idea 装置门路下的 bin 目录下的 idea.properties 配置如下

idea.is.internal=true

开启后显示了 View PSI Structure 和 View PSI Structure of Current File

进入要查看构造的文件后点击 View PSI Structure of Current File\
查看某一个文件的 psi 构造

2.3 查看插件源码

进入 ConvertGeneratorAction 的点击事件办法不难看到如下的依据 PSI 获取以后类和办法的代码

2.4 持续跟踪生成办法转换代码

这里次要是依据返回类型获取到了一个 MethodGenerator 并执行对应的 generateCode 办法

2.5MethodGenerator 下的 generateCode

MethodGenerator 下的 generateCode 次要获取了以后办法的入参 fromClass 与 toClass, 并进行了字符串的组装和生成代码块。

\
PsiCodeBlock codeBlock = elementFactory.\
createCodeBlockFromText(“{” + String.join(“\n”, statementList) + “}”, psiClass);\
源码剖析就到这里,如果有趣味的同学能够自行深入分析并欢送补充。

十二、武魂交融

1. 定位

想编写一个什么样的插件(性能)

插件要实现的能力是什么,eg:进行办法入参疾速转为出参、获取抉择的文本增加为笔记、idea 激活弹出框、数据库 Database… 等。

2. 拆解

实现插件须要具备哪些能力(性能拆解)

须要页面操作交互能力(java swing)

须要发送 http 申请能力(增加依赖的能力)

须要增加 action 的能力(插件须要放在哪里,插件的生命周期是什么等级的等。)

须要读写文件的能里(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file),”utf-8″));)

3. 落地

3.1 增加一个 Action 到右键 EditorPopupMenu

创立一个 action 并继承 AnAction

package com.test.action;

import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;

public class testAction extends AnAction {
@Override
public void actionPerformed(AnActionEvent e) {
    // TODO: insert action logic here
    System.out.println("action 点击触发办法 =" + e);
}

}

 <actions>
    <!-- Add your actions here -->
    <action id="testAction" class="com.test.action.testAction" text="testAction" description="testAction">
      <add-to-group group-id="EditorPopupMenu" anchor="before" relative-to-action="ShowIntentionsGroup"/>
    </action>
  </actions>

3.2 发动网络申请获取数据

增加 spring 相干依赖到 gradle

dependencies {
    testCompile group: 'junit', name: 'junit', version: '4.12'
    // https://mvnrepository.com/artifact/org.springframework/spring-web
    implementation 'org.springframework:spring-web:5.1.13.RELEASE'
    // https://mvnrepository.com/artifact/org.springframework/spring-core
    implementation 'org.springframework:spring-core:5.1.13.RELEASE'
    // https://mvnrepository.com/artifact/org.springframework/spring-beans
    implementation 'org.springframework:spring-beans:5.1.13.RELEASE'
}

应用 spring-web 下的 RestTemplate 创立网络申请工具(也能够间接应用 RestTemplate)

package com.test.http;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

public class HttpUtil {public static ResponseEntity<Map> get(String url){RestTemplate restTemplate = new RestTemplate();
        try {ResponseEntity<Map> forEntity = new RestTemplate().getForEntity(url, Map.class);
            return forEntity;
        } catch (Exception e) {e.printStackTrace();
            return  null;
        }
    }
}

在需的中央触发网络申请获取数据

public class testAction extends AnAction {

    @Override
    public void actionPerformed(AnActionEvent e) {
        // TODO: insert action logic here
        System.out.println("action 点击触发办法 =" + e);
        ResponseEntity<Map> mapResponseEntity = HttpUtil.get("http://localhost:22200/getPerson");
        System.out.println("action 点击触发网络申请 =" + mapResponseEntity.toString());
    }
}

触发验证

3.3 回显到 idea 界面

首先创立一个回显显示的界面

package com.test.view;

import com.intellij.openapi.ui.DialogWrapper;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import java.awt.*;


public class testDialog extends DialogWrapper {
    JLabel label;

    public testDialog(boolean canBeParent) {super(canBeParent);
        init();// 初始化 dialog
        setTitle("题目");
    }

    @Override
    protected @Nullable JComponent createCenterPanel() {
        /* 创立一个面板,设置其布局为边界布局 */
        JPanel centerPanel = new JPanel(new BorderLayout());
        /* 创立一个文字标签,来承载内容 */
        String text = "aaa11111 测试回显内容";
        label = new JLabel(text);
        /* 设置首先大小 */
        label.setPreferredSize(new Dimension(100, 100));
        /* 将文字标签增加的面板的正中间 */
        centerPanel.add(label, BorderLayout.CENTER);
        return centerPanel;
    }

    public void setLabelText(String text) {label.setText(text);
    }
}

在 action 内触发申请网络获取内容并设置到显示的面板上。

public class testAction extends AnAction {

    @Override
    public void actionPerformed(AnActionEvent e) {
        // TODO: insert action logic here
        System.out.println("action 点击触发办法 =" + e);
        ResponseEntity<Map> mapResponseEntity = HttpUtil.get("http://localhost:22200/getPerson");
        System.out.println("action 点击触发网络申请 =" + mapResponseEntity.getBody());
        testDialog testDialog=new testDialog(true);
        testDialog.setLabelText(mapResponseEntity.getBody().toString());
        testDialog.show();}
}

3.4 乱码解决

像上图的题目等间接赋值汉字时会有乱码,从新编码进行解决(这种形式简略的汉字和汉字较少时能够)

 String encodeTitle = new String("题目".getBytes("gbk"), "UTF-8");
 title = new EditorTextField(encodeTitle);

3.5 获取选中的内容并回显

咱们从 action 中获取 editor 对象,在通过 editor 获取 SelectionModel,在获取选中的文本。

弹窗提供一个从新设置抉择文本的办法 testDialog.setContent(selectedText);

 @Override
    public void actionPerformed(AnActionEvent e) {
        // TODO: insert action logic here
        testDialog testDialog = new testDialog(true);
        // 获取以后编辑器对象
        Editor editor = e.getRequiredData(CommonDataKeys.EDITOR);
        // 获取抉择的数据模型
        SelectionModel selectionModel = editor.getSelectionModel();
        // 获取以后抉择的文本
        String selectedText = selectionModel.getSelectedText();
        System.out.println(selectedText);
        testDialog.setContent(selectedText);
        testDialog.show();}

测试选中内容和回显内容如下图

3.6 按钮响应

@Override
    protected JComponent createSouthPanel() {JPanel panel = new JPanel(new FlowLayout());
        try {String encodeTitle = new String("题目".getBytes("gbk"), "UTF-8");
            JLabel title = new JLabel(encodeTitle);
            String encodeBtnAdd = new String("按钮点击".getBytes("gbk"), "UTF-8");
            JButton btnAdd = new JButton(encodeBtnAdd);
            // 按钮点击事件处理
            btnAdd.addActionListener(e -> {
                // 获取题目
                String titleStr = title.getText();
                // 获取内容
                String contentStr = content.getText();
                System.out.println("titleStr" + ":" + titleStr);
                System.out.println("contentStr" + ":" + contentStr);
                label.setText(getHttpText());
            });
            panel.add(title, BorderLayout.NORTH);
            /* 设置首先大小 */
            btnAdd.setPreferredSize(new Dimension(200, 100));
            panel.add(btnAdd, BorderLayout.CENTER);
        } catch (UnsupportedEncodingException e) {e.printStackTrace();
        }
        return panel;
    }

    private String getHttpText() {ResponseEntity<Map> mapResponseEntity = HttpUtil.get("http://localhost:22200/getPerson");
        return mapResponseEntity.toString();}

如图所示点击按钮拜访本地 http 服务获取数据后回显

3.7 获取控件内数据

3.8 保留数据到文件

能够应用 java 自身的流进行读写,也能够应用模板引擎进行, 这里应用 freemarker 模版引擎 \
3.8.1 获取按钮点击事件后弹出目录抉择框抉择要保留的文件夹, 首先须要革新弹窗的结构器传入以后 action 的事件 Event,从 event 获取以后的工程

3.8.2 按钮点击事件创立文件选择器

有人会有疑难,为什么这样就弹出了文件选择器, 其实最初是一个 FileChooser->FileChooserDialog

final FileChooserDialog chooser = FileChooserFactory.getInstance().createFileChooser(descriptor, project, parent);
    return chooser.choose(project, toSelect);

3.8.3 引入 freemarker 模版引擎依赖并进行文件创建保留

组织数据、获取模版、创立文件、执行创立文件

模版代码创立并获取上图中的组织数据 model 下的内容

​​​​​​​

3.9 告诉(当有谬误或胜利是弹出告诉事件 –IDEA 的 Event Log)

                       NotificationGroup notificationGroup = new NotificationGroup("testId", NotificationDisplayType.BALLOON, true);
                        /**
                         * content :  告诉内容
                         * type:告诉的类型,warning,info,error
                         */
                        Notification notification = notificationGroup.createNotification("测试告诉保留胜利", MessageType.INFO);
                        Notifications.Bus.notify(notification);

3.10 扩大某一个扩大点

增加一个自定义 ToolWindow\
3.10.1 创立一个 toolwindow

package com.test.view;
import com.intellij.openapi.wm.ToolWindow;
import javax.swing.*;
import java.text.SimpleDateFormat;
import java.util.Date;

public class MyToolWindow  {
    private JButton hideButton;
    private JLabel datetimeLabel;
    private JPanel myToolWindowContent;
    public MyToolWindow(ToolWindow toolWindow) {init();
        hideButton.addActionListener(e -> toolWindow.hide(null));
    }

    private void init() {datetimeLabel = new JLabel();
        datetimeLabel.setText(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        hideButton = new JButton("勾销");
        myToolWindowContent = new JPanel();
        myToolWindowContent.add(datetimeLabel);
        myToolWindowContent.add(hideButton);
    }
    public JPanel getContent() {return myToolWindowContent;}
}

3.10.2 创立 ToolWindowFactory 的实现类

package com.test.view;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowFactory;
import com.intellij.ui.content.Content;
import com.intellij.ui.content.ContentFactory;
import org.jetbrains.annotations.NotNull;

public class toolWindowExt implements ToolWindowFactory {
    @Override
    public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) {MyToolWindow myToolWindow = new MyToolWindow(toolWindow);
        // 获取内容工厂的实例
        ContentFactory contentFactory = ContentFactory.SERVICE.getInstance();
        // 获取用于 toolWindow 显示的内容
        Content content = contentFactory.createContent(myToolWindow.getContent(), "自定义 tool window", false);
        // 给 toolWindow 设置内容
        toolWindow.getContentManager().addContent(content);
    }
}

3.10.3 申明扩大点对应的扩大

    <extensions defaultExtensionNs="com.intellij">
        <!-- Add your extensions here -->
        <toolWindow id="增加 toolWindow"
                    secondary="false"
                    anchor="right" factoryClass="com.test.view.toolWindowExt">
        </toolWindow>
    </extensions>

十三、参考文档:

idea 插件官网文档:https://plugins.jetbrains.com/docs/intellij/welcome.html

gradle 官网文档:https://docs.gradle.org/current/userguide/userguide.html

freemarker:https://freemarker.apache.org/docs/

京东技术:https://cloud.tencent.com/developer/article/1348741

javaSwing:https://docs.oracle.com/javase/tutorial/uiswing/components/jc…

sdk-code-samples:https://github.com/JetBrains/intellij-sdk-code-samples

十四、其余插件文档传送门

idea 插件开发经验总结(一):环境搭建

IDEA 插件开发扼要教程

【IDEA 插件开发】疾速入门系列 01 开发一个简略的 Idea 插件

IDEA Plugin 插件怎么开发?

作者:京东衰弱 马仁喜

起源:京东云开发者社区

退出移动版