天天看點

IDEA 插件開發入門教程

IntelliJ IDEA 是目前最好用的 JAVA 開發 IDE,它本身的功能已經非常強大了,但是每個人的需求不一樣,有些需求 IDEA 本身無法滿足,于是我們就需要自己開發插件來解決。工欲善其事,必先利其器,想要提高開發效率,我們可以借助 IDEA 提供的插件功能來滿足我們的需求。如果沒有我需要的功能怎麼辦?很簡單,我們自己造一個!

插件能做什麼?

IDEA 的插件幾乎可以做任何事情,因為它把 IDE 本身的能力都封裝好開放出來了。主要的插件功能包含以下四種:

  • 自定義語言支援:如果有 IDEA 暫時不支援的語言,你可以自己寫一個插件來支援,例如 Go 語言原來的支援就是通過插件做的,後來單獨做了一個 Goland。官方有 自定義語言插件支援的教程
  • 架構支援:例如 Struts 2 的架構支援
  • 工具內建:可以給 IDEA 的自帶功能進行增強,例如對 Git 的操作增加 CodeReview 的功能。參考 Gerrit
  • 使用者界面:自定義的插件改變使用者界面。參考 BackgroundImage

我為了減少重複代碼的編寫,寫了一個代碼生成的插件

IDEA代碼生成插件CodeMaker

,支援自定義代碼生成的模闆。

Hello world 插件

依照慣例,我們從 Hello world 開始。

建立一個 Gradle 的插件工程

有些教程推薦用 IDEA 預設的插件工程來開始,但是我比較推薦用 Gradle 來管理整個插件工程,後面的依賴管理會很友善,否則都得靠手動管理。

點選建立工程,選擇 Gradle

IDEA 插件開發入門教程

接下來填寫項目屬性

IDEA 插件開發入門教程

配置 Gradle,用預設配置就行

IDEA 插件開發入門教程

建立完工程之後,IDEA 會自動開始解析項目依賴,因為它要下載下傳一個幾百兆的 SDK 依賴包,是以會比較久,打開科學上網能快一點。

IDEA 插件開發入門教程

Gradle 依賴解析完成之後,項目結構如下圖,其中 plugin.xml 是插件的配置,build.gradle 是項目依賴的配置(類比 pom.xml)。

IDEA 插件開發入門教程

下面就是預設生成的 plugin.xml

<idea-plugin>
    <!--插件id-->
    <id>com.xiaokai.test.demo</id>
    <!--插件名稱-->
    <name>Demo</name>
    <!--開發者資訊-->
    <vendor email="[email protected]" url="http://www.yourcompany.com">YourCompany</vendor>
    <!--插件說明-->
    <description><![CDATA[
    Enter short description for your plugin here.<br>
    <em>most HTML tags may be used</em>
    ]]></description>

    <!-- please see http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/plugin_compatibility.html
         on how to target different products -->
    <!-- uncomment to enable plugin in all products
    <depends>com.intellij.modules.lang</depends>
    -->

    <!--依賴的其他插件能力-->
    <extensions defaultExtensionNs="com.intellij">
        <!-- Add your extensions here -->
    </extensions>

    <!--插件動作-->
    <actions>
        <!-- Add your actions here -->
    </actions>
</idea-plugin>           

建立一個 Action

Action 是 IDEA 中對事件響應的處理器,它的 actionPerformed 就像是 JS 中的 onClick 方法。可以看出來,插件的開發本質上跟 web、Android 的開發沒有什麼不同,因為都是事件驅動的程式設計。

我們可以直接使用 IDEA 提供的 Action 生成器

IDEA 插件開發入門教程
IDEA 插件開發入門教程

點選 OK 之後會在 src 生成類檔案:

package com.xiaokai.test;

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

public class HelloWorldAction extends AnAction {

    @Override
    public void actionPerformed(AnActionEvent e) {
        // TODO: insert action logic here
    }
}
           

同時,動作的資訊也會注冊到 plugin.xml 中

<!--插件動作-->
    <actions>
        <!-- Add your actions here -->
        <action id="demo.hello.world" class="com.xiaokai.test.HelloWorldAction" text="HelloWorld"
                description="Say Hello World">
            <add-to-group group-id="GenerateGroup" anchor="last"/>
        </action>
    </actions>           

彈出對話框

建立完 Action 之後我們就要開始往裡面寫邏輯了,既然是 Hello World 教學,那我們就來試一下最簡單的彈出對話框。

@Override
    public void actionPerformed(AnActionEvent e) {
        //擷取目前在操作的工程上下文
        Project project = e.getData(PlatformDataKeys.PROJECT);

        //擷取目前操作的類檔案
        PsiFile psiFile = e.getData(CommonDataKeys.PSI_FILE);
        //擷取目前類檔案的路徑
        String classPath = psiFile.getVirtualFile().getPath();
        String title = "Hello World!";

        //顯示對話框
        Messages.showMessageDialog(project, classPath, title, Messages.getInformationIcon());
    }           

代碼寫完之後,打開 Gradle 的界面,點選 runIde 就會啟動一個安裝了插件的 IDEA,然後就可以進行測試。你還可以右鍵啟動 Debug 模式,這樣還能進行斷點。

IDEA 插件開發入門教程

運作的效果如下圖:

IDEA 插件開發入門教程

可以看到,我們右鍵打開 Generate 菜單之後,裡面最後一項就是我們添加的 Action,

進階的教程

如果想學習更多的原理和設計理念可以看

IntelliJ Platform SDK

的官方文檔。不過老實說,它的文檔寫的挺差的,基本上就是簡單講了一下概念和原理,沒有深入的分析。是以如果要深入研究還得靠自己。最靠譜的學習方式就是看别人寫的插件,舉個例子,你想知道怎麼樣實作自動生成代碼,你就去找支援這個功能的插件,看他的源碼是怎麼寫的。

我當時寫

CodeMaker

的時候也是靠自己啃源碼之後寫出來的。下面我簡單介紹一下我用過的一些 API,這些 API 基本都沒有文檔說明,全靠代碼相傳。

判斷目前光标選擇的元素是什麼

//擷取目前事件觸發時,光标所在的元素
        PsiElement psiElement = anActionEvent.getData(LangDataKeys.PSI_ELEMENT);
        //如果光标選擇的不是類,彈出對話框提醒
        if (psiElement == null || !(psiElement instanceof PsiClass)) {
            Messages.showMessageDialog(project, "Please focus on a class", "Generate Failed", null);
            return;
        }           

擷取目前類檔案的所有類對象

一個類檔案中可能會有内部類,是以讀取的時候傳回的是一個清單

public static List<PsiClass> getClasses(PsiElement element) {
        List<PsiClass> elements = Lists.newArrayList();
        List<PsiClass> classElements = PsiTreeUtil.getChildrenOfTypeAsList(element, PsiClass.class);
        elements.addAll(classElements);
        for (PsiClass classElement : classElements) {
            //這裡用了遞歸的方式擷取内部類
            elements.addAll(getClasses(classElement));
        }
        return elements;
    }           

格式化代碼

public static void reformatJavaFile(PsiElement theElement) {
        CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(theElement.getProject());
        try {
            codeStyleManager.reformat(theElement);
        } catch (Exception e) {
            LOGGER.error("reformat code failed", e);
        }
    }           

使用粘貼闆

CopyPasteManager.getInstance()
            .setContents(new SimpleTransferable(table.toString(), DataFlavor.allHtmlFlavor));           

更多

更多的技巧可以參考我的項目

,以及其他的開源插件。