1. 程式人生 > >IDEA外掛開發——React專案外掛

IDEA外掛開發——React專案外掛

設計部分

因為平時在做專案的時候,總是會有一些重複程式碼的工作量,作為一個有追求的程式設計師,當然不會讓自己一直重複這些勞動。於是,就有了IDEA外掛開發這個方案了。IDEA外掛開發的資料非常少,大部分都要閱讀IDEA的原始碼來探索。

首先明確我的目標:根據模組和頁面名稱,自動初始化一系列的頁面,其中的變數、類名、檔名等均根據模組名稱和頁面名稱生成。

第二步就是設計互動,我初步計劃是,模組和頁面的資料夾由自己手工建立,在頁面資料夾上右鍵,點選生成,生成資料夾裡面的內容。

建立專案

新建一個IntelliJ Platform Plugin,寫好專案名稱,進入開發介面。

首先稍微介紹下目錄結構。
1. resources

目錄,資源或者配置放置的目錄,在META-INF中防止了外掛的配置,以後模板檔案也會放置在resource目錄。
2. src中放置程式碼。
3. 其他如.ideaout等與編碼無關,是IDE配置和編譯輸出檔案等。

瞭解了目錄結構,就要開始實現第一個目標。

實現選單項

在IDEA中,每個動作都是一個action,所以第一步需要建立一個action。

在我的版本的IDEA中,建立的目錄在這個位置,不同版本的IDEA位置可能不同,但是都大同小異,應該都叫做Action。
其中要填幾個欄位:
1. Action ID,也就是id了,保證唯一就可以了,隨便怎麼填,比如: My.newPage
2. Class Name,會為你建立的類的名稱,如NewPageAction。
3. Name,展示的名稱,如New Page Init。
4. Description,描述,滑鼠移動到選單上的時候,底部欄對此選單的描述,隨便寫一下就可以了。
5. Add To Group,這是最重要的一欄,首先選Group,這是代表選項出現在哪一類選單中,由於我是要讓我的選單出現在專案資料夾的右鍵選單中,所以選擇的是ProjectViewPopupMenu,旁邊的Actions欄作用是和Anchor一起的,表示選項出現在現有選項的前面還是後面。我的選項出現在最後就可以了,所以不用選擇Actions,直接在Anchor欄選擇Last。
6. Keyboard Shortcuts,快捷鍵,我不需要,所以不設定了。

在建立完之後,會自動在META-INFplugin.xml中,建立一段標籤:

<action id="My.newPage" class="com.my.plugin.action.NewPageAction" text="New Page Init" description="New Page Init">
    <add-to-group group-id="ProjectViewPopupMenu" anchor="last"/>
</action>

並且會在src目錄我們指定的包名中建立一個繼承自AnAction的類,其中實現了actionPerformed方法,這個方法代表選項被點選的時候觸發的方法。

獲取需要的資料

首先介紹下我的外掛,我的專案結構如下:

模組資料夾 ->
    頁面資料夾 ->
        action資料夾
            action檔案
            actionType檔案
        reducer資料夾
            reducer檔案
        component資料夾
            元件檔案
        主頁面檔案

所以我的外掛目標是,建立好模組資料夾和頁面資料夾之後,在頁面資料夾上點選右鍵,選擇New Page Init,就可以生成下面的所有資料夾和檔案。
那麼第一步,就是需要獲取到資料夾的名稱了。

final DataContext dataContext = e.getDataContext(); //通過事件獲取到當前的上下文
final IdeView view = LangDataKeys.IDE_VIEW.getData(dataContext); //獲取到當前IDE的物件
if (view == null) {
    return;
}
final PsiDirectory dir = view.getOrChooseDirectory(); // 拿到當前選擇的資料夾,也就是右鍵點選的資料夾

final Project project = PlatformDataKeys.PROJECT.getData(dataContext); // 拿到專案物件

if (dir == null || project == null) return;

到此,我們就拿到了需要的最基本的東西,準備工作做好了,因為頁面中可能會用到各種各樣的名稱形式,所以現在來對基礎資料做一些處理:

String[] split = dir.getName().split("-"); // 因為專案的約定,統一使用-來間隔單詞
String dashName = dir.getName(); // 帶-的名稱,如my-page
String underscoreName = StringUtil.join(split, "_"); // 帶下劃線的名稱,如my_page
String firstUpperCamelCaseName = Arrays.stream(split).map((w) -> Character.toUpperCase(w.charAt(0)) + w.substring(1).toLowerCase()).collect(Collectors.joining()); //所有首字母都大寫的駝峰名稱,如MyPage
String allUpperCaseUnderscoreName = underscoreName.toUpperCase(); // 帶下劃線的全大寫名稱,如MY_PAGE
String moduleName = "error"; // 模組名稱,稍後處理
String moduleNameCamelCase = "error"; // 駝峰模組名稱,稍後處理
String moduleUnderscoreName = "error"; // 模組下劃線名稱,稍後處理

PsiDirectory dirParent = dir.getParent(); // 獲取page資料夾的上一層,也就是模組資料夾
if (dirParent != null) {
    moduleName = dirParent.getName(); // 模組名稱
    if (dirParent.getName().contains("-")) {
        String[] parts = dirParent.getName().split("-");
        StringBuilder camelCaseString = new StringBuilder();
        for (int i = 0; i < parts.length; i++) {
            String part = parts[i];
            if (i != 0) {
                camelCaseString.append(part.substring(0, 1).toUpperCase()).append(part.substring(1).toLowerCase());
            } else {
                camelCaseString.append(part);
            }
        }
        moduleNameCamelCase = camelCaseString.toString(); // 駝峰模組名
    } else {
        moduleNameCamelCase = dirParent.getName();
    }
    moduleUnderscoreName = StringUtil.join(moduleName.split("-"), "_"); // 下劃線模組名
}

基礎資料就準備好了,可以進入下一步了。

頁面模板

IDEA中,因為內建了Velocity,所以允許我們定義一些模板,首先先在resources中,建立一個資料夾,用於存放模板檔案,名稱叫做fileTemplates.internal,注意必須使用這個名稱。
在下面新建所需要的模板檔案,帶上拓展名,並且以.ft結尾,如actions.tsx.ft
檔案的語法與Velocity相同,把最基礎的程式碼複製過來之後,將要替換的部分,使用Velocity的模板語法替換,就完成模板的編寫了。
編寫好了模板,還需要在plugin.xml中進行一些配置,讓系統能夠讀取到相應的模板。
找到extensions標籤,在標籤下加入如下標籤:

<internalFileTemplate name="actions"/>

其中name為不帶檔案拓展名的名稱,如actions.tsx.ft,就填入actions。這樣在以後使用的時候,IDEA就會將模板名稱與檔案對應起來。

模板編寫好了之後,回到原來的Action程式碼中,現在要進行的步驟是獲取模板檔案,並將值傳入模板檔案中,需要用到FileTemplateManager這個類了。

FileTemplateManager.getInstance(project).getInternalTemplate("actions"); // 獲取actions模板

在拿到模板物件之後,還需要傳入之前準備好的一些值:

Properties properties = new Properties();
properties.put("dashName", dashName);
properties.put("moduleName", moduleName);
properties.put("moduleNameCamelCase", moduleNameCamelCase);
properties.put("UpperPageName", firstUpperCamelCaseName);
properties.put("underscore_name", underscoreName);
properties.put("pageTitle", "請定義標題");
properties.put("allUpperCaseUnderscoreName", allUpperCaseUnderscoreName);

現在,就可以開始傳入值了:

PsiDirectory actionsDirectory = dir.createSubdirectory("actions"); // 先在page目錄下,建立一個目錄actions
FileTemplate actionTemplate = FileTemplateManager.getInstance(project).getInternalTemplate("actions"); // 獲取到actions的模板
try {
    FileTemplateUtil.createFromTemplate(actionTemplate, underscoreName + ".actions.tsx", properties, actionsDirectory); // 使用FileTemplateUtil的工具方法,在對應目錄下,生成檔案
} catch (Exception e1) {
    e1.printStackTrace();
}

到此,好像一個完整的編碼流程就完成,那麼執行一下吧。你會發現,執行的時候報錯了。因為IDEA將寫入有關的操作,都要放到另外的一個執行緒中執行,所以,需要告訴IDEA要執行的操作是一個寫操作。在程式碼外面包上一層:

ApplicationManager.getApplication().runWriteAction(() -> {
    // .......我們的程式碼
});

這樣,IDEA就會在寫入操作的執行緒中執行我們的程式碼了。

測試並生成外掛

開始執行的時候,需要配置一個執行環境,也就是啟動另外一個IDEA,你可以去網上下載社群版本的IDEA來進行測試。如果以後需要編寫到特殊的IDEA外掛,比如TypeScript相關或者框架相關的外掛,那麼也可以配置付費版的IDEA或者Webstorm之類的來做測試。
在執行之前,最好還要先指定一下IDEA的最低版本,在plugin.xml中,有一段被註釋的程式碼:

<!-- please see http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/build_number_ranges.html for description -->
<!-- <idea-version since-build="141.0"/>  -->            《------就是它

開啟這個註釋,就代表IDEA會支援這個版本的api,具體版本查詢,可以在上面的那行註釋連結中去找。

到此應該能夠將程式碼跑起來了,並且能夠預覽到外掛的效果了,如果還有小bug,IDEA的除錯是很方便的。接下來就是生成正式的外掛了。
生成外掛只需要點選選單上Build -> Prepare Plugin Module ‘xxx’ For Deployment,就可以生成出對應的jar包了。

結語

有些同學可能說,這些功能不寫IDEA的plugin也能寫啊。確實,功能怎麼樣都能實現,只是看怎麼樣的實現最優美。在IDEA中點選右鍵,選擇生成頁面,這就是我覺得最優美的實現,所以才這麼折騰在IDEA中實現這個功能。
並且,這個功能不是這個外掛所有的功能,後續要做的功能,應該只有在IDEA中實現才最輕鬆,比如:解析reducer樹並展示在IDE中;生成頁面的時候可選頁面初始化react元件(類似android)等等。這些功能都會依賴到js的語法解析,在IDEA中,psi直接可以訪問到解析好的語法資料,後續我應該也會出更高階的IDEA外掛教學。