主頁 > 後端開發 > Intellij IDEA 插件開發

Intellij IDEA 插件開發

2023-06-28 10:45:37 後端開發

寫在前面

很多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-ranges.html?from=jetbrains.org#intellij-platform-based-products-of-recent-ide-versions
接下來需要找idea對應版本的gradle版本
https://www.jetbrains.com/legal/third-party-software/?product=iic&version=2020.1.1
最后就是找 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-web

2、啟用Plugin DevKit

Plugin DevKit 是 IntelliJ 的一個插件,它使用 IntelliJ IDEA 自己的構建系統來為開發 IDEA 插件提供支持,開發 IDEA 插件之前需要安裝并啟用 Plugin DevKit ,
打開 IDEA,導航到 Settings | Plugins,若插件串列中沒有 Plugin DevKit,點擊 Install JetBrains plugin,搜索并安裝,

image.png

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 的安裝路徑,如圖

image.png

創建好 IntelliJ Platform Plugin SDK 后,選擇左側欄 Project Settings 下的 Projects,在 Project SDK 下選擇剛創建的 IntelliJ Platform Plugin SDK,

image.png

4、設定原始碼路徑(可選)

4.1查看 build 號:打開 IDEA,Help | About,查看版本號及 build 號

4.2IDEA Community 原始碼(https://github.com/JetBrains/intellij-community/):切換到與 build 號相同的分支,點擊 Clone or download 按鈕,選擇 Download ZIP

image.png

4.3選擇工程結構設定后選擇SDKs->選中之前在第3步添加的sdk點擊SourcePath后按如下1點擊添加一個sourcePath,選擇上面下載額原始碼后點擊OK、點擊Applay

image.png

4.4未安裝原始碼時點擊某一個action(NewModuleAction)會看到如下所示閱讀起來會比較晦澀難懂,

image.png

5、Sandbox

IntelliJ IDEA 插件以 Debug/Run 模式運行時是在 SandBox 中進行的,不會影響當前的 IntelliJ IDEA;但是同一臺機器同時開發多個插件時默認使用的同一個 sandbox,即在創建 IntelliJ Platform SDK 時默認指定的 Sandbox Home

image.png

如果需要每個插件的開發環境是相互獨立的,可以創建多個 IntelliJ Platform SDK,為 Sandbox Home 指定不同的目錄 ,

三、開發一個簡單插件

插件的創建、配置、運行、打包流程,以及 action

1、創建一個插件工程

選擇 File | New | Project,左側欄中選擇 IntelliJ Platform Plugin 工程型別

image.png

點擊 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="[email protected]" />
  <!-- 插件所依賴的其他插件的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"  text="Text Boxes" description="A test menu item" />
  </group>
</actions>


  • 元素會定義一個 action,指定 action 的 id、實作類、顯示文本、描述

  • 元素會定義一個 action group(多個action),設定 action group 的 id、文本、描述

  • 元素指定其外部 action 或 action group 被添加到的位置

上面示例會定義一個被添加到 IDEA 主選單的最后面的 “SampleMenu” 的選單,點擊該選單將彈出一個 “Text Boxes” item,如圖

image.png

4.3、快速創建 Action

IntelliJ Platform 提供了 New Action 向導,它會幫助我們創建 action class 并配置 plugin.xml 檔案:

在目標 package 上右鍵,選擇 New | Plugin DevKit | Action:

image.png

  • 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 的第一和第二快捷鍵

image.png

注意:該向導只能向主選單中已存在的 action group 或工具列上添加 action,若要創建新的 action group,請參考前面的內容,

5、運行除錯插件

運行/除錯插件可直接在 IntelliJ IDEA 進行,選擇 Run | Edit Configurations...,若左側欄沒有 Plugin 型別的 Configuration, 點擊右上角 + 按鈕,選擇 Plugin 型別, 如圖

image.png

Use classpath of module 選擇要除錯的 module,其余配置一般默認即可;切換到 Logs 選項卡,如果勾選了 idea.log,運行插件時 idea.log 檔案的內容將輸出到 idea.log console,

運行插件點擊工具列上運行按鈕Run

image.png
image.png

6、打包安裝插件

6.1、打包插件

選擇 Build | Prepare Plugin Module ‘module name’ for Deployment 來打包插件:

image.png

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...

image.png

  • 選擇插件包的位置,點擊 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"  text="Text Boxes" description="A test menu item" />
  </group>
</actions>


2、進入PlatformActions.xml如下圖,這個時候不難看出這里就是主選單的第一列子選單

image.png

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"  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" />
            <action id="ImportProject" />
            <action id="ProjectFromVersionControl"
/>
            <separator/>
            <action id="NewModule" />
            <action id="ImportModule" />
        </group>
    </actions>


3.2實作自定義的打開檔案

其實是通過下面的action配置的OpenFileAction找到原始碼

<action id="OpenFile"  icon="AllIcons.Actions.Menu_open"/>


在將原始碼拷貝出來粘貼到自己的action內,這樣就可以實作自己的主選單File下的Open子選單

3.3這個時候有人會有疑問我不知道去哪找New對應的action呀?

這個時候我們通過界面可以看到Project from Existing Sources...,這里我們就可以去搜這個文本呀,既然顯示在頁面上,必然有地方定義了它, ActionBundle.properties

image.png

這個時候我們在根據對應的action定義的文本在去搜索對應的action,com.intellij.ide.actions.ImportProjectAction

image.png

3.4這個時候我們將對應的action拷貝到自己的插件定義的配置上也就形成了3.1的一級和二級選單

image.png

2.添加主工具列MainToolBar

添加主工具列MainToolBar(如果不清楚哪里是主選單、主工具列、導航欄、背景關系選單、彈出選單參考https://www.w3cschool.cn/intellij\\_idea\\_doc/intellij\\_idea\\_doc-34852d55.html)

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


image.png

3、添加背景關系選單ProjectViewPopupMenu

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


image.png

4、添加彈出選單EditorPopupMenu

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


image.png

5、添加列印ConsoleEditorPopupMenu

    <!--添加到控制臺列印右鍵-->
        <group>
            <add-to-group group-id="ConsoleEditorPopupMenu" anchor="before" relative-to-action="CutCopyPasteGroup"/>
            <reference ref="MainMenuActionGroup"></reference>
        </group>


image.png

6、右鍵新建action時也可以直接選擇添加的位置,

1.篩選后查找要添加的group
2.選擇對應的action
3.選擇要添加到這個action的某個位置

image.png

五、Components(已不建議使用)

IntelliJ IDEA 的組件模型是基于 PicoContainer 的,組件都包含在這些容器中,但容器有三種級別:application container,project container 以及 module container,application container 可以包含多個 project container,而 project container 可以包含多個 module container,

1、Components 型別

Components 是插件開發的基礎,Components 有三種型別:

image.png

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,如下圖:

image.png

  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運行后的預期是先執行應用層組件,在執行工程級組件,在執行模塊級組件

image.png

六、Extensions and Extension Points

如果插件需要擴展 IDEA Platform 或 其他插件的功能,或為其他插件提供可以擴展自己的介面,那么就要用到 extensions 和 extension points,用于與 IDEA 和其他插件互動,

1、Extension points 擴展點

extension point 用于資料資訊擴展,使其他插件可以擴展本插件的功能,可通過plugin.xml 的 元素宣告,如下示例:

<extensionPoints>
    <!--使用beanClass宣告-->
    <extensionPoint name="MyExtensionPoint1" bean 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" implementation></MyExtensionPoint1>
  </extensions>


插件的 service 的實作就是擴展 IDEA Platform 的 applicationService 或 projectService 兩個 extension points

3、獲取 extension points

IntelliJ Platform 的部分 extension points

 <extensionPoints>
    <extensionPoint name="languageBundle" bean/>
    <!--suppress PluginXmlValidity -->
    <extensionPoint name="applicationService" bean dynamic="true"/>
    <!--suppress PluginXmlValidity -->
    <extensionPoint name="projectService" bean dynamic="true"/>
    <!--suppress PluginXmlValidity -->
    <extensionPoint name="moduleService" bean 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" bean dynamic="true">
      <with attribute="implementationClass" implements="com.intellij.lang.ParserDefinition"/>
    </extensionPoint>
    <extensionPoint name="lang.elementManipulator" bean dynamic="true">
      <with attribute="implementationClass" implements="com.intellij.psi.ElementManipulator"/>
    </extensionPoint>
    <!--suppress PluginXmlValidity -->
    <extensionPoint name="stubElementTypeHolder" bean 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/extensions

七、Service

參考:https://plugins.jetbrains.com/docs/intellij/plugin-services.html#examples

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,如圖

image.png

選擇相應 service,彈出如下對話框,填寫 interface 類和 implementation 類,若不勾選 Separate interface from implementation,只需填寫 implementation 類,

image.png
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 = "https://www.cnblogs.com/Jcloud/archive/2023/06/28/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,

image.png

配置 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 代碼,因為建好布局后可能需要修改代碼,其他默認即可,

image.png

2.創建 form

創建 form 檔案form 檔案用于記錄界面布局,在相應的 package 上右鍵,選擇 New | GUI Form,如圖,輸入 form 檔案名,一般與 java 檔案名相同,點擊 OK 創建 form 與 java 檔案,

image.png

3.面板介紹

編輯界面打開 form 檔案,如圖,通過拖拽控制元件來搭建布局,每個form檔案布局的 root 控制元件都是一個 JPanel,可將該 root 物件傳給需要該布局的類,注意:左下角的屬性面板,只有當填寫了 field name 屬性時該控制元件的物件才會被當成成員變數,否則為區域變數,

image.png

4.構建

生成 java 代碼搭建好布局后,點擊 build

編譯按鈕,即可生成 java 的原始碼檔案,

GUI 生成的方法名前后都有三個 \(標識,當再次修改布局時,GUI 只會修改\) 標識的方法,

image.png

十一、原始碼分析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,

image.png

5、插件特色

插件自動從轉換函式的引數和回傳值推斷出轉換POJO;

支持List之間的轉換,

如果存在單個轉換的函式,則直接使用

如果不存在單個轉換的函式,創建單個轉換函式

支持嵌套轉換

image.png

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" 
                    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>


image.png

這個時候我們不難看出并沒有地方參考這個組,這個時候我們不防從使用的地方入手,我們是右鍵點擊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" />


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);


image.png

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.203993552.1175576577.1685324427-1690948556.1684890471&_gl=1y6ns8zgaMTY5MDk0ODU1Ni4xNjg0ODkwNDcxga_9J976DJZ68*MTY4NTQxNDQzMS4xOS4xLjE2ODU0MTU2NTguMC4wLjA.

未配置開啟查看PIS結構時如下圖

image.png

開啟查看PIS結構 找到idea安裝路徑下的bin目錄下的idea.properties配置如下

idea.is.internal=true


image.png

開啟后顯示了View PSI Structure 和View PSI Structure of Current File

image.png

進入要查看結構的檔案后點擊View PSI Structure of Current File
查看某一個檔案的psi結構

image.png

2.3查看插件原始碼

進入ConvertGeneratorAction的點擊事件方法不難看到如下的根據PSI獲取當前類和方法的代碼

image.png

2.4繼續跟蹤生成方法轉換代碼

這里主要是根據回傳型別獲取到了一個MethodGenerator并執行對應的generateCode方法

image.png

2.5MethodGenerator下的generateCode

MethodGenerator下的generateCode主要獲取了當前方法的入參fromClass與toClass,并進行了字串的組裝和生成代碼塊,

image.png
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"  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());
    }
}


觸發驗證

image.png

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();
    }
}


image.png

3.4亂碼處理

像上圖的標題等直接賦值漢字時會有亂碼,重新編碼進行處理(這種方式簡單的漢字和漢字較少時可以)

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


image.png

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();
    }


image.png

測驗選中內容和回顯內容如下圖

image.png

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服務獲取資料后回顯

image.png

3.7獲取控制元件內資料

image.png

3.8保存資料到檔案

可以使用java本身的流進行讀寫,也可以使用模板引擎進行,這里使用freemarker模版引擎
3.8.1獲取按鈕點擊事件后彈出目錄選擇框選擇要保存的檔案夾,首先需要改造彈窗的構造器傳入當前action的事件Event,從event獲取當前的工程

image.png

3.8.2按鈕點擊事件創建檔案選擇器

image.png

有人會有疑問,為什么這樣就彈出了檔案選擇器,其實最后是一個FileChooser->FileChooserDialog

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


3.8.3引入freemarker模版引擎依賴并進行檔案創建保存

image.png

組織資料、獲取模版、創建檔案、執行創建檔案

image.png

模版代碼創建并獲取上圖中的組織資料model下的內容

???????image.png

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" factory>
        </toolWindow>
    </extensions>


image.png

十三、參考檔案:

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/jcomponent.html

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

十四、其他插件檔案傳送門

idea插件開發經驗總結(一):環境搭建

IDEA插件開發簡明教程

【IDEA插件開發】快速入門系列01 開發一個簡單的Idea插件

IDEA Plugin 插件怎么開發?

作者:京東健康 馬仁喜

來源:京東云開發者社區

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/556191.html

標籤:其他

上一篇:說說設計模式~命令模式(command)

下一篇:返回列表

標籤雲
其他(161774) Python(38255) JavaScript(25515) Java(18268) C(15238) 區塊鏈(8273) C#(7972) AI(7469) 爪哇(7425) MySQL(7271) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5875) 数组(5741) R(5409) Linux(5347) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4607) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2438) ASP.NET(2404) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) .NET技术(1985) HtmlCss(1976) 功能(1967) Web開發(1951) C++(1942) python-3.x(1918) 弹簧靴(1913) xml(1889) PostgreSQL(1881) .NETCore(1863) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Intellij IDEA 插件開發

    很多idea插件檔案更多的是介紹如何創建一個簡單的idea插件,本篇文章從開發環境、demo、生態組件、添加依賴包、原始碼解讀、網路請求、渲染資料、頁面互動等方面介紹,是一篇能夠滿足基本的插件開發工程要求的文章。 ......

    uj5u.com 2023-06-28 10:45:37 more
  • 說說設計模式~命令模式(command)

    [回到目錄](https://www.cnblogs.com/lori/p/3896484.html) # 概述 命令模式(Command Pattern)是一種行為型設計模式,它將請求封裝成一個物件,從而允許我們根據不同的請求將客戶端引數化,并且能夠將請求排隊或記錄請求日志、支持撤銷操作等。該模式 ......

    uj5u.com 2023-06-28 10:45:30 more
  • [滲透測驗]—5.3 網路滲透測驗技術和工具

    在本章節中,我們將學習一些常用的網路滲透測驗技術和工具,如Wireshark、Ettercap等。我們會盡量將內容講解得詳細、通俗易懂,并提供盡可能多的實體。 ### 5.1 Wireshark Wireshark是一款免費的開源資料包分析器,可以實時或離線捕獲、分析和解碼網路資料包。Wiresha ......

    uj5u.com 2023-06-28 10:45:23 more
  • 一文了解Go語言的I/O介面設計

    # 1. 引言 I/O 操作在編程中扮演著至關重要的角色。它涉及程式與外部世界之間的資料交換,允許程式從外部,如鍵盤、檔案、網路等地方讀取資料,也能夠將外界輸入的資料重新寫入到目標位置中。使得程式能夠與外部環境進行資料交換、與用戶進行互動、實作資料持久化和檔案操作、進行網路通信等。因此,了解和掌握I ......

    uj5u.com 2023-06-28 10:45:18 more
  • Intellij IDEA 插件開發

    很多idea插件檔案更多的是介紹如何創建一個簡單的idea插件,本篇文章從開發環境、demo、生態組件、添加依賴包、原始碼解讀、網路請求、渲染資料、頁面互動等方面介紹,是一篇能夠滿足基本的插件開發工程要求的文章。 ......

    uj5u.com 2023-06-28 10:44:21 more
  • 說說設計模式~命令模式(command)

    [回到目錄](https://www.cnblogs.com/lori/p/3896484.html) # 概述 命令模式(Command Pattern)是一種行為型設計模式,它將請求封裝成一個物件,從而允許我們根據不同的請求將客戶端引數化,并且能夠將請求排隊或記錄請求日志、支持撤銷操作等。該模式 ......

    uj5u.com 2023-06-28 10:39:06 more
  • 一文了解Go語言的I/O介面設計

    # 1. 引言 I/O 操作在編程中扮演著至關重要的角色。它涉及程式與外部世界之間的資料交換,允許程式從外部,如鍵盤、檔案、網路等地方讀取資料,也能夠將外界輸入的資料重新寫入到目標位置中。使得程式能夠與外部環境進行資料交換、與用戶進行互動、實作資料持久化和檔案操作、進行網路通信等。因此,了解和掌握I ......

    uj5u.com 2023-06-28 10:38:47 more
  • JavaCV人臉識別三部曲之一:視頻中的人臉保存為圖片

    ### 歡迎訪問我的GitHub > 這里分類和匯總了欣宸的全部原創(含配套原始碼):[https://github.com/zq2599/blog_demos](https://github.com/zq2599/blog_demos) ### 關于人臉識別 - 本文是《JavaCV人臉識別三部曲》 ......

    uj5u.com 2023-06-28 09:08:58 more
  • C++面試八股文:用過std::set/std::map嗎?

    某日二師兄參加XXX科技公司的C++工程師開發崗位第27面: > 面試官:用過`std::set/std::map`嗎? > > 二師兄:用過。 > > 面試官:能介紹一下二者嗎? > > 二師兄:`std::set`是一個有序的集合,其中的元素是唯一的,即每個元素只能出現一次。一般用于去重和自動排 ......

    uj5u.com 2023-06-28 09:08:47 more
  • 執行緒池

    # 執行緒池 ## 簡介 執行緒池是一種基于池化思想的執行緒管理工具。 ## 優點 - 降低資源消耗 - 提高回應速度 - 提高執行緒的可管理性 - 提供可擴展性 ## 使用場景 - 連接池 - 執行緒隔離 - 開發中需要創建5個以上的執行緒就可以考慮使用執行緒池 ## 執行緒池核心引數 | 引數名 | 型別 | ......

    uj5u.com 2023-06-28 09:08:44 more