一時(shí)興起,自己動(dòng)手開發(fā)了個(gè)IDEA插件!
前言
最近工作比較閑,自己琢磨點(diǎn)東西,一時(shí)興起想自己做個(gè)IDEA插件玩玩,說不定還能賣錢,哈哈。
版本工具
- 說明JDK1.8
- IDEA2020.1
- 插件項(xiàng)目基于gradle構(gòu)建。
- 知識(shí)背景:swing
達(dá)到什么目標(biāo)?
本實(shí)例實(shí)現(xiàn)一個(gè)Idea的插件,彈出一個(gè)表單Dialog,然后點(diǎn)擊按鈕,獲取表單里輸入的內(nèi)容,然后將內(nèi)容打印在表單的上方。
成品圖展示:
項(xiàng)目初始化
新建一個(gè)gradle項(xiàng)目,修改其build.gradle文件:
- plugins {
- id 'java'
- id 'org.jetbrains.intellij' version '0.4.14' //引入intellij的gradle插件
- }
- group 'org.example'
- version '1.0' //定義jar包/zip包的版本號(hào)
- sourceCompatibility = 1.8 //限制jdk的使用版本號(hào),這里限制到8,表示生成的idea插件只能運(yùn)行在jdk8以上的環(huán)境中
- repositories {
- mavenCentral() //遠(yuǎn)程倉庫
- }
- dependencies {
- //這里引別的依賴包
- testCompile group: 'junit', name: 'junit', version: '4.12'
- }
- // See https://github.com/JetBrains/gradle-intellij-plugin/
- intellij {
- // 這里是指打插件包的時(shí)候用idea什么版本的依賴包打
- // 比如這里用2019.3打包,如果你的插件實(shí)現(xiàn)源碼里用了2019.3不存在的依賴包或類,就會(huì)報(bào)錯(cuò)
- // 一般就填當(dāng)前IDEA的版本號(hào)即可
- version "2019.3"
- }
- patchPluginXml {
- //changeNotes里的內(nèi)容展示位置參考圖14
- changeNotes """
- 1.0版本.
- 第1.0版本:初始化這個(gè)測(cè)試插件項(xiàng)目"""
- // 這個(gè)意思是說當(dāng)前定義的這個(gè)插件最早支持到什么版本的IDEA
- // 這里配置sinceBuild=191,表示插件只能被版本號(hào)大于等于2019.1版本的IDEA安裝,低于這個(gè)版本的將拋無法兼容的錯(cuò)誤
- // ↑上方參考這篇問答:https://intellij-support.jetbrains.com/hc/en-us/community/posts/360003338799-Build-compatible-plugin
- sinceBuild "191"
- }
然后Idea的右邊欄gradle將會(huì)多出intellij選項(xiàng):
“這里說下runIde,它用來調(diào)試插件,運(yùn)行它會(huì)再次啟動(dòng)一個(gè)Idea,這個(gè)Idea會(huì)自動(dòng)安裝上你當(dāng)前定義的插件包,讓你用來調(diào)試。
新增plugin.xml
這個(gè)文件非常重要,它可以指定你定義的插件出現(xiàn)在IDEA的哪個(gè)位置,可以指定具體的處理邏輯,還可以定義插件名稱、子名稱等等。
這個(gè)文件位于MATE-INF下:
配置內(nèi)容為:
- <idea-plugin>
- <!--插件的id,注意不要跟其他插件重復(fù),這個(gè)id全局唯一,盡可能復(fù)雜些-->
- <id>plugin.test</id>
- <!--插件的名稱-->
- <name>PluginTest</name>
- <vendor email="xxxx@qq.com" url="http://www.bilibili.com">你公司的名字</vendor>
- <!--插件的描述信息,支持html,展示的位置參考圖14-->
- <description><![CDATA[
- Plugin Test<br>
- 第一行:?jiǎn)渭冎皇莻€(gè)測(cè)試<br>
- 第二行:都說了只是個(gè)測(cè)試(● ̄(エ) ̄●)<br>
- <a href='https://www.bilibili.com'>你猜猜這是哪個(gè)網(wǎng)站?</a>
- <em>v1.0</em>
- ]]></description>
- <extensions defaultExtensionNs="com.intellij">
- <!-- Add your extensions here -->
- </extensions>
- <!--跟build.gradle里的sinceBuild一致即可,意義相同,必須配置-->
- <idea-version since-build="191"/>
- <actions>
- <!--下面的group是分組,分組需要有一個(gè)唯一的id標(biāo)識(shí),text用來控制分組出現(xiàn)在IDEA時(shí)呈現(xiàn)的文案,description是描述,不會(huì)展現(xiàn)出來,簡(jiǎn)單描述下分組就行-->
- <group id="PluginTest" text="插件測(cè)試組" description="插件測(cè)試描述">
- <!--add-to-group控制把該分組加到IDEA里,group-id用來描述加在哪個(gè)位置,MainMenu表示加在IDEA上方的主菜單欄里,
- anchor表示順序,last表示最后一個(gè),所以下面的配置可以描述為:將該插件加到IDEA上方主菜單欄的最后一位-->
- <add-to-group group-id="MainMenu" anchor="last"/>
- <!--這個(gè)用來指定一個(gè)分組下的觸發(fā)動(dòng)作,同樣的需要一個(gè)id,自定義;class就是用來處理這個(gè)動(dòng)作的邏輯類,具體的插件邏輯都會(huì)寫到對(duì)應(yīng)的action類里,text用來控制文案,description為描述-->
- <action id="Plugin.Test.Action"
- class="plugin.test.FromAction"
- text="表單測(cè)試" description="表單測(cè)試描述"/>
- </group>
- </actions>
- </idea-plugin>
然后定義一個(gè)Action類,記為FormAction,繼承AnAction,實(shí)現(xiàn)其抽象方法actionPerformed即可:
- public class FromAction extends AnAction {
- @Override
- public void actionPerformed(@NotNull AnActionEvent e) {
- //TODO 這里放插件邏輯
- }
- }
啟動(dòng)
現(xiàn)在雙擊runIde即可調(diào)出另外一個(gè)安裝了這個(gè)插件的IDEA界面,然后可以看運(yùn)行結(jié)果進(jìn)行調(diào)試。runIde還支持debug模式,不過運(yùn)行時(shí)要右擊選擇:
來看下調(diào)試IDEA的界面運(yùn)行效果:
定義Action
1. 定義會(huì)話框類
經(jīng)過上面三步的配置,插件的基本樣式已經(jīng)展示出來,但是點(diǎn)擊下方“表單測(cè)試”的action,并沒有什么用,因?yàn)槠浣壎ǖ腇ormAction類里沒有任何有意義的實(shí)現(xiàn)?,F(xiàn)在來實(shí)現(xiàn)開始的目標(biāo),點(diǎn)擊“表單測(cè)試”后,彈出一個(gè)自定義的表單會(huì)話框,然后點(diǎn)擊按鈕,獲取表單內(nèi)容后打印在會(huì)話框內(nèi)。
會(huì)話框(Dialog)需要定義一個(gè)繼承了IDEA的DialogWrapper抽象類的子類,這個(gè)子類就是自定義的會(huì)話框?qū)崿F(xiàn),所有的樣式定義、功能觸發(fā)都是放到這個(gè)子類里的,現(xiàn)定于如下子類:
- public class FormTestDialog extends DialogWrapper {
- private String projectName; //假如需要獲取到項(xiàng)目名,作為該類的屬性放進(jìn)來
- // DialogWrapper沒有默認(rèn)的無參構(gòu)造方法,所以需要重寫構(gòu)造方法,它提供了很多重載構(gòu)造方法,
- // 這里使用傳project類型參數(shù)的那個(gè),通過Project對(duì)象可以獲取當(dāng)前IDEA內(nèi)打開的項(xiàng)目的一些屬性,
- // 比如項(xiàng)目名,項(xiàng)目路徑等
- public FormTestDialog(@Nullable Project project) {
- super(project);
- setTitle("表單測(cè)試~~"); // 設(shè)置會(huì)話框標(biāo)題
- this.projectName = project.getName();
- }
- // 重寫下面的方法,返回一個(gè)自定義的swing樣式,該樣式會(huì)展示在會(huì)話框的最上方的位置
- @Override
- protected JComponent createNorthPanel() {
- return null;
- }
- // 重寫下面的方法,返回一個(gè)自定義的swing樣式,該樣式會(huì)展示在會(huì)話框的最下方的位置
- @Override
- protected JComponent createSouthPanel() {
- return null;
- }
- // 重寫下面的方法,返回一個(gè)自定義的swing樣式,該樣式會(huì)展示在會(huì)話框的中央位置
- @Override
- protected JComponent createCenterPanel() {
- return null;
- }
- }
2. 會(huì)話框模塊&類元素對(duì)照
找個(gè)實(shí)際的會(huì)話框?yàn)槔?,針?duì)上述中幾個(gè)方法所控制的會(huì)話框里的元素如下:
3. 會(huì)話框方法重定義
按照本文的實(shí)現(xiàn)目標(biāo),自定義的表單主體部分可以位于createCenterPanel里,然后表單的大標(biāo)題可以放到createNorthPanel里,提交按鈕可以放到createSouthPanel里,現(xiàn)在改寫如下:
- public class FormTestDialog extends DialogWrapper {
- private String projectName;
- //swing樣式類,定義在4.3.2
- private FormTestSwing formTestSwing = new FormTestSwing();
- public FormTestDialog(@Nullable Project project) {
- super(true);
- setTitle("表單測(cè)試~~"); //設(shè)置會(huì)話框標(biāo)題
- this.projectName = project.getName(); //獲取到當(dāng)前項(xiàng)目的名稱
- init(); //觸發(fā)一下init方法,否則swing樣式將無法展示在會(huì)話框
- }
- @Override
- protected JComponent createNorthPanel() {
- return formTestSwing.initNorth(); //返回位于會(huì)話框north位置的swing樣式
- }
- // 特別說明:不需要展示SouthPanel要重寫返回null,否則IDEA將展示默認(rèn)的"Cancel"和"OK"按鈕
- @Override
- protected JComponent createSouthPanel() {
- return formTestSwing.initSouth();
- }
- @Override
- protected JComponent createCenterPanel() {
- //定義表單的主題,放置到IDEA會(huì)話框的中央位置
- return formTestSwing.initCenter();
- }
- }
4. 自定義swing樣式
下面是放置swing樣式的類:
- public class FormTestSwing {
- private JPanel north = new JPanel();
- private JPanel center = new JPanel();
- private JPanel south = new JPanel();
- //為了讓位于底部的按鈕可以拿到組件內(nèi)容,這里把表單組件做成類屬性
- private JLabel r1 = new JLabel("輸出:");
- private JLabel r2 = new JLabel("NULL");
- private JLabel name = new JLabel("姓名:");
- private JTextField nameContent = new JTextField();
- private JLabel age = new JLabel("年齡:");
- private JTextField ageContent = new JTextField();
- public JPanel initNorth() {
- //定義表單的標(biāo)題部分,放置到IDEA會(huì)話框的頂部位置
- JLabel title = new JLabel("表單標(biāo)題");
- title.setFont(new Font("微軟雅黑", Font.PLAIN, 26)); //字體樣式
- title.setHorizontalAlignment(SwingConstants.CENTER); //水平居中
- title.setVerticalAlignment(SwingConstants.CENTER); //垂直居中
- north.add(title);
- return north;
- }
- public JPanel initCenter() {
- //定義表單的主體部分,放置到IDEA會(huì)話框的中央位置
- //一個(gè)簡(jiǎn)單的3行2列的表格布局
- center.setLayout(new GridLayout(3, 2));
- //row1:按鈕事件觸發(fā)后將結(jié)果打印在這里
- r1.setForeground(new Color(255, 47, 93)); //設(shè)置字體顏色
- center.add(r1);
- r2.setForeground(new Color(139, 181, 20)); //設(shè)置字體顏色
- center.add(r2);
- //row2:姓名+文本框
- center.add(name);
- center.add(nameContent);
- //row3:年齡+文本框
- center.add(age);
- center.add(ageContent);
- return center;
- }
- public JPanel initSouth() {
- //定義表單的提交按鈕,放置到IDEA會(huì)話框的底部位置
- JButton submit = new JButton("提交");
- submit.setHorizontalAlignment(SwingConstants.CENTER); //水平居中
- submit.setVerticalAlignment(SwingConstants.CENTER); //垂直居中
- south.add(submit);
- return south;
- }
- }
現(xiàn)在點(diǎn)擊下runIde按鈕,同樣的,在調(diào)試IDE里點(diǎn)擊“表單測(cè)試”,然后就會(huì)彈出如下表單框:
“除非有特殊情況需要自定義swing樣式,否則建議不加任何swing樣式,這樣自定義的swing界面是會(huì)隨著IDEA的主題改變而去自適應(yīng)的,比如將圖7中的調(diào)試IDE的主題設(shè)置成Darcula,自定義的表單也會(huì)自適應(yīng)的變成黑色背景:
5. 事件綁定
定義好了樣式,現(xiàn)在給“提交”按鈕綁定一個(gè)事件,現(xiàn)在改寫下FormTestSwing.initSouth方法:
- public JPanel initSouth() {
- //定義表單的提交按鈕,放置到IDEA會(huì)話框的底部位置
- JButton submit = new JButton("提交");
- submit.setHorizontalAlignment(SwingConstants.CENTER); //水平居中
- submit.setVerticalAlignment(SwingConstants.CENTER); //垂直居中
- south.add(submit);
- //按鈕事件綁定
- submit.addActionListener(e -> {
- //獲取到name和age
- String name = nameContent.getText();
- String age = ageContent.getText();
- //刷新r2標(biāo)簽里的內(nèi)容,替換為name和age
- r2.setText(String.format("name:%s, age:%s", name, age));
- });
- return south;
- }
現(xiàn)在再來點(diǎn)擊下“提交”按鈕,就可以輸出表單內(nèi)容了:
6. 插件綁定類:FormAction
之前講過,這個(gè)類是插件的入口,結(jié)合上面定義好的表單Dialog,來看下它是怎么寫的:
- public class FromAction extends AnAction {
- @Override
- public void actionPerformed(@NotNull AnActionEvent e) {
- FormTestDialog formTestDialog = new FormTestDialog(e.getProject());
- formTestDialog.setResizable(true); //是否允許用戶通過拖拽的方式擴(kuò)大或縮小你的表單框,我這里定義為true,表示允許
- formTestDialog.show();
- }
- }
7. 插件的打包&安裝
截止到第四步,都只是在調(diào)試IDE里查看效果,如果一個(gè)插件開發(fā)完成后,需要被實(shí)際的IDEA安裝,這個(gè)時(shí)候就需要借助打包選項(xiàng)來打包你的插件,點(diǎn)擊下面的選項(xiàng)構(gòu)建插件:
構(gòu)建完成后,查看build包下的distributions目錄,里面的zip包就可以直接安裝進(jìn)你的IDEA:
然后選擇IDEA的Preferences下的plugins選項(xiàng),彈出如下框,按照?qǐng)D里的指示選擇zip包安裝即可:
然后安裝完成,重啟IDEA即可:
各個(gè)展示模塊對(duì)應(yīng)插件項(xiàng)目里配置的來源參考下圖:
重啟后出現(xiàn)了跟調(diào)試IDEA里一樣的菜單欄,選中后運(yùn)行成功:
總結(jié)
截止到這里,一個(gè)插件的開發(fā)、調(diào)試、安裝就完成了,理論上通過這個(gè)簡(jiǎn)單的例子就可以實(shí)現(xiàn)一些實(shí)際的功能了,因?yàn)槠渫暾故玖藬?shù)據(jù)輸入到數(shù)據(jù)獲取整個(gè)過程。
因?yàn)楣ぷ鳟?dāng)中需要寫一個(gè)代碼生成器,想要以一個(gè)IDEA插件的方式提供服務(wù),所以在這里做個(gè)記錄,防止以后再次用到時(shí)從零開始。
要有一定的swing基礎(chǔ),我在開發(fā)代碼生成器的時(shí)候,就是因?yàn)閟wing基礎(chǔ)太差,布局花了非常多的時(shí)間。