一妒貌、Interllij Idea下載
idea下載地址:https://www.jetbrains.com/idea
intellij-community sdk源碼地址:https://github.com/JetBrains/intellij-community
二、環(huán)境搭建
相信大家都會(huì)梅惯,不多說(shuō)~
三、新建插件項(xiàng)目
- 一路Next即可,建完如圖
- 新建Action:MobRenameAction.java
- 選擇Group為ProjectViewPopupMenu的Action類型卖擅,自定義id、類名等
- 建完如下
到這里可以正式開(kāi)發(fā)自己的插件功能了墨技。
- ProjectViewPopupMenu:project窗口內(nèi)容右鍵彈出的窗口惩阶;
- AnAction:功能實(shí)現(xiàn)的父類;
一鍵查找扣汪、一鍵替換我們想到的肯定是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)接口(通常稱為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硬編碼名稱,最終實(shí)現(xiàn)效果如下:
彈窗代碼實(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ù)制的重命名工具類
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ō)明如下:
以上需要關(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*)读宙。
參考資料與延伸:
- http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started.html
- https://mp.weixin.qq.com/s/zfiw4a17VCKw43EfKpysgA
- http://www.reibang.com/p/7fce3f10ea17
- https://github.com/AweiLoveAndroid/The-pit-of-the-Android-Studio/blob/master/readme/Android%20Studio%E5%A5%BD%E7%94%A8%E7%9A%84%E6%8F%92%E4%BB%B6.md