淺嘗輒止:Interllij Idea 插件Action開(kāi)發(fā)

一、Interllij Idea下載
idea下載地址:https://www.jetbrains.com/idea
intellij-community sdk源碼地址:https://github.com/JetBrains/intellij-community
二、環(huán)境搭建
相信大家都會(huì),不多說(shuō)~
三、新建插件項(xiàng)目

1.png
  • 一路Next即可,建完如圖
2.png
  • 新建Action:MobRenameAction.java
3.png
  • 選擇Group為ProjectViewPopupMenu的Action類(lèi)型,自定義id、類(lèi)名等
4.png
  • 建完如下
5.png

到這里可以正式開(kāi)發(fā)自己的插件功能了。

  • ProjectViewPopupMenu:project窗口內(nèi)容右鍵彈出的窗口;
  • AnAction:功能實(shí)現(xiàn)的父類(lèi);

一鍵查找、一鍵替換我們想到的肯定是Find in Path...、Replace in Path...,但我們的場(chǎng)景常常是修改某個(gè)資源文件名->替換代碼中引用資源文件名的硬編碼,Rename我們也熟悉,但Rename只能一鍵修改id的引用,并不能修改硬編碼的引用,如何一步到位使用Rename+Replace的功能呢?那就是定制化我們的Action。

今天我們是輕度定制化,不可避免要利用RenameFileAction和ReplaceInPathAction已實(shí)現(xiàn)的功能。
這次不講Plugin SDK源碼,只講相關(guān)實(shí)現(xiàn),看代碼

public class MobRenameAction extends AnAction {

    @Override
    public void actionPerformed(AnActionEvent e) {

        DataContext dataContext = e.getDataContext();
        //獲取Project實(shí)例
        Project project = CommonDataKeys.PROJECT.getData(dataContext);
        //獲取右鍵選中的文件元素
       PsiElement element = e.getData(CommonDataKeys.PSI_ELEMENT);
       PsiFile file = e.getData(CommonDataKeys.PSI_FILE);
       //顯示操作彈窗
       MobRenameDialog dialog = new MobRenameDialog(project, element, dataContext, file);
       dialog.show();

    }
}

  • What is the PSI?

Program Structure Interface (PSI)
The Program Structure Interface, commonly referred to as just PSI, is the layer in the IntelliJ Platform that is responsible for parsing files and creating the syntactic and semantic code model that powers so many of the platform’s features.

(程序結(jié)構(gòu)接口(通常稱(chēng)為PSI)是IntelliJ平臺(tái)中的一層,負(fù)責(zé)分析文件并創(chuàng)建支持平臺(tái)許多功能的語(yǔ)法和語(yǔ)義代碼模型。)

今天實(shí)現(xiàn)一個(gè)簡(jiǎn)單功能,當(dāng)修改layout下的某個(gè)文件名時(shí)同步修改R.layout.idxx引用以及使用ResHelper.getLayoutRes硬編碼名稱(chēng),最終實(shí)現(xiàn)效果如下:

GIF.gif

彈窗代碼實(shí)現(xiàn):


package com.mob.plugintest;

public class MobRenameDialog extends RefactoringDialog {
    private JLabel myNameLabel;
    private final JLabel myNewNamePrefix;
    private PsiElement myPsiElement;
    private NameSuggestionsField myNameSuggestionsField;
    private SuggestedNameInfo mySuggestedNameInfo;
    private String myOldName;
    private final Editor myEditor;
    private NameSuggestionsField.DataChanged myNameChangedListener;
    private DataContext dataContext;
    private Project project;
    private PsiFile psiFile;

    protected MobRenameDialog(@NotNull Project project, PsiElement psiElement, 
                    DataContext dataContext, PsiFile psiFile) {
        super(project, true);
        this.myPsiElement = psiElement;
        this.myNewNamePrefix = new JLabel("");
        this.myEditor = null;
        this.dataContext = dataContext;
        this.project = project;
        this.psiFile = psiFile;

        setTitle("layout文件重命名");
        this.createNewNameComponent();
        init();
        this.myNameLabel.setText(XmlStringUtil.wrapInHtml(
              XmlTagUtilBase.escapeString(this.getLabelText(), false)));
    }

    @Override
    protected void doAction() {
        close(0);
        ReplaceInProjectManager replaceManager = ReplaceInProjectManager.getInstance(project);

        String originalFileName = psiFile.getName().substring(0, psiFile.getName().indexOf("."));

        String name = getNewName();
        String newName = name.substring(0, name.indexOf("."));
        //從RenameFileAction復(fù)制的重命名工具類(lèi)
        RenamePsiElementProcessor elementProcessor = RenamePsiElementProcessor.forElement(this.myPsiElement);
        elementProcessor.setToSearchInComments(this.myPsiElement, false);
        RenameProcessor processor = new RenameProcessor(project, this.myPsiElement, 
name, GlobalSearchScope.projectScope(project), false, false);;
        processor.run();

        if (replaceManager.isEnabled()) {
            FindManager findManager = FindManager.getInstance(project);
            FindModel findModel = findManager.getFindInProjectModel().clone();
            //定義查找字符串,使用正則表達(dá)式查找
            findModel.setStringToFind("ResHelper.getLayoutRes\\(([a-zA-Z0-9\\.\\(\\)\\s]+,\\s*)\"" 
                  + originalFileName + "\"\\);");
            System.out.println(">>>>setStringToFind>> " + findModel.getStringToFind());
            findModel.setReplaceState(true);
            //定義替換字符串,使用正則表達(dá)式替換$1=([a-zA-Z0-9\\.\\(\\)\\s]+,\\s*)
            findModel.setStringToReplace("ResHelper.getLayoutRes\\($1\"" + newName + "\"\\);");
            FindInProjectUtil.setDirectoryName(findModel, dataContext);
            FindInProjectUtil.initStringToFindFromDataContext(findModel, dataContext);
            replaceManager.replaceInPath(findModel);
        }
    }
    public String[] getSuggestedNames() {
        LinkedHashSet<String> result = new LinkedHashSet();
        String initialName = VariableInplaceRenameHandler.getInitialName();
        if (initialName != null) {
            result.add(initialName);
        }

        result.add(UsageViewUtil.getShortName(this.myPsiElement));
        Iterator var3 = NameSuggestionProvider.EP_NAME.getExtensionList().iterator();

        while(var3.hasNext()) {
            NameSuggestionProvider provider = (NameSuggestionProvider)var3.next();
            SuggestedNameInfo info = provider.getSuggestedNames(this.myPsiElement,
 this.myPsiElement, result);
            if (info != null) {
                this.mySuggestedNameInfo = info;
                if (provider instanceof PreferrableNameSuggestionProvider 
          && !((PreferrableNameSuggestionProvider)provider).shouldCheckOthers()) {
                    break;
                }
            }
        }

        return ArrayUtilRt.toStringArray(result);
    }

    protected void createNewNameComponent() {
        String[] suggestedNames = this.getSuggestedNames();
        this.myOldName = UsageViewUtil.getShortName(this.myPsiElement);
        this.myNameSuggestionsField = new NameSuggestionsField(suggestedNames, 
        this.myProject, FileTypes.PLAIN_TEXT, this.myEditor) {
            protected boolean shouldSelectAll() {
                return MobRenameDialog.this.myEditor == null
 || MobRenameDialog.this.myEditor.getSettings().isPreselectRename();
            }
        };
        if (this.myPsiElement instanceof PsiFile && this.myEditor == null) {
            this.myNameSuggestionsField.selectNameWithoutExtension();
        }

        this.myNameChangedListener = () -> {
//            this.processNewNameChanged();
        };
        this.myNameSuggestionsField.addDataChangedListener(this.myNameChangedListener);
    }

    @NotNull
    public String getNewName() {
        String newName = this.myNameSuggestionsField.getEnteredName().trim();

        return newName;
    }

    @NotNull
    protected String getLabelText() {
        String fileName = RefactoringBundle.message("rename.0.and.its.usages.to",
 new Object[]{this.getFullName()});
        return fileName;
    }

    protected String getFullName() {
        String name = DescriptiveNameUtil.getDescriptiveName(this.myPsiElement);
        String type = UsageViewUtil.getType(this.myPsiElement);
        return StringUtil.isEmpty(name) ? type : type + " '" + name + "'";
    }

    @Nullable
    @Override
    protected JComponent createNorthPanel() {
        //自定義彈窗顯示控件
        JPanel panel = new JPanel(new GridBagLayout());
        GridBagConstraints gbConstraints = new GridBagConstraints();
        gbConstraints.insets = JBUI.insetsBottom(4);
        gbConstraints.weighty = 0.0D;
        gbConstraints.weightx = 1.0D;
        gbConstraints.gridwidth = 1;
        gbConstraints.gridheight = 1;
        gbConstraints.fill = 1;
        this.myNameLabel = new JLabel();
        panel.add(this.myNameLabel, gbConstraints);
        gbConstraints.insets = JBUI.insets(0, 0, 4, StringUtil.isEmpty(this.myNewNamePrefix.getText()) ? 0 : 1);
        gbConstraints.gridwidth = 1;
        gbConstraints.gridheight = 1;
        gbConstraints.fill = 0;
        gbConstraints.weightx = 0.0D;
        gbConstraints.gridx = 0;
        gbConstraints.anchor = 17;
        panel.add(this.myNewNamePrefix, gbConstraints);
        gbConstraints.insets = JBUI.insetsBottom(8);
        gbConstraints.gridwidth = 2;
        gbConstraints.fill = 1;
        gbConstraints.weightx = 1.0D;
        gbConstraints.gridx = 0;
        gbConstraints.weighty = 1.0D;
        panel.add(this.myNameSuggestionsField.getComponent(), gbConstraints);
        return panel;
    }

    @Nullable
    @Override
    protected JComponent createCenterPanel() {
        return null;
    }
}

以上代碼實(shí)際只有正則表達(dá)式為自定義,其它都來(lái)自RenameFileAction、ReplaceInPathAction,至此輕度定制重命名插件就完成了。
一般情況下不會(huì)需要定制化插件,RenameFileAction、ReplaceInPathAction已經(jīng)足夠強(qiáng)大,這離不開(kāi)正則表達(dá)式,可能會(huì)對(duì)設(shè)置的查找和替換表達(dá)式感到疑惑,說(shuō)明如下:

6.png

以上需要關(guān)注的正則共有兩個(gè)可提取的匹配([a-zA-Z0-9.()\s]+,\s),(test2),在使用表達(dá)式時(shí)我們可以使用括號(hào)分別匹配不同的字符,在替換時(shí)按順序使用$1到$99可得到括號(hào)中匹配的實(shí)際值, 如上圖在本例中只需要替換(test2),保留([a-zA-Z0-9.()\s]+,\s)匹配的任意字符,在replace則可以使用$1代替([a-zA-Z0-9.()\s]+,\s*)。

參考資料與延伸:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請(qǐng)通過(guò)簡(jiǎn)信或評(píng)論聯(lián)系作者。

友情鏈接更多精彩內(nèi)容