寫在前面
很多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,搜尋并安裝。
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="[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" 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/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>
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,如下圖:
- 若元件沒有建立 interface 類,而是直接實作了 ApplicationComponent 等接口,interface 和 implementation 可以指定為同一個類。
- 每一個元件都應該有一個唯一的名字,通過 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 在項目啟動時共同加載。
一個元件加載過程:
- 建立:調用構造方法
- 初始化:調用 initComponent() 方法
- 如果是 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 解除安裝
一個元件解除安裝過程:
- 如果是 Project 或 Module 元件,調用 projectClosed()
- 接下來 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。
- 設定 的 defaultExtensionNs 屬性若是擴充 IntelliJ Platform,設為 com.intellij若是擴充其他插件,則設為 pluginId
- 指定要擴充哪個 extension point内部的子标簽的名字必須與 extension point 的 name 屬性相同
- 如果 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/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,如圖
選擇相應 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 配置。
- 确定插件包的位置如果插件是 IDEA 捆綁的插件,那麼插件包在 IDEA 安裝目錄的 plugins/ 或 plugins//lib 下。如果插件是第三方或自己的,那麼需要先運作一次 sandbox(其實我們在運作調試插件的時候就是在運作sandbox)并從本地或插件倉庫安裝依賴插件。安裝好後,插件包會放在 sandbox 目錄下的 config/plugins/ 或 config/plugins//lib,檢視 sandbox 目錄:打開 IntelliJ Platform SDK 配置頁面,其中 Sandbox Home 就是其目錄。
-
将插件包添加到 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、四個轉換函數
- 把光标放到函數中,不能是函數内.
- 光标移動到函數體内,按下⌘+N,在彈出的Generate菜單中選擇Smart Converter;
- 插件自動生成一下四個轉換函數
- A -> B
- B -> A
- List -> List
- List -> List
4、單個抓換函數
- 在編輯器中,确定傳回值和參數,完成空轉換函數;
- public static List<UserJO> toDTOList(List<UserDTO> userDTOList) { }
- 光标移動到函數體内,按下⌘+N,在彈出的Generate菜單中選擇Smart Converter;
- 插件根據入參和出參推斷出需要轉換的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.203993552.1175576577.1685324427-1690948556.1684890471&_gl=1y6ns8zgaMTY5MDk0ODU1Ni4xNjg0ODkwNDcxga_9J976DJZ68*MTY4NTQxNDQzMS4xOS4xLjE2ODU0MTU2NTguMC4wLjA.
未配置開啟檢視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/jcomponent.html
sdk-code-samples:https://github.com/JetBrains/intellij-sdk-code-samples
十四、其他插件文檔傳送門
idea插件開發經驗總結(一):環境搭建
IDEA插件開發簡明教程
【IDEA插件開發】快速入門系列01 開發一個簡單的Idea插件
IDEA Plugin 插件怎麼開發?
作者:京東健康 馬仁喜
來源:京東雲開發者社群