资料
https://zhaojian.blog.csdn.net/article/details/127882946
Plugin Configuration File https://plugins.jetbrains.com/docs/intellij/plugin-configuration-file.html
今天分享的主要内容:
了解插件能够做什么
如何开发一个插件
阅读两个常用的插件源码
intellij的窗口样式,及学习插件的方法
官方案例源码品读
从一个ScrcpyController插件开始
安卓控制软件scrcpy
终端输入命令 scrcpy -s 98KAY15DVD(设备号)
多个设备还得需要加上设备号,太繁琐.
有没有更好的方法,有,ScrcpyController, 一个IDE的插件
图1 ScrcpyController
把所有的功能,通过UI的方式显示出来,并且列出了所有的设备号.
Android Studio是Intellij+插件的形式
在Android Studio的软件目录, 找到plugin文件夹.
会发现有很多的android插件
创建一个plugin项目
新建New
创建的目录结构
配置run命令
正常的run和debug程序
像开发安卓程序一样打断点Debug就行.
plugin.xml中都有什么
从官网查询
Plugin Configuration File
https://plugins.jetbrains.com/docs/intellij/plugin-configuration-file.html
ScrcpyController源码解析
目录结构
重要组成
创建要显示的界面
GUI Form, .form和java文件同时创建,在编辑的时候,也会生成句柄
此插件的界面显示有哪些:
刚开头的时候,说了他的主界面,还有settings中的配置文件.
settings文件
图2 ScrcpyControllerSettingsComponent
图3 TextDialog 通用弹窗
plugin.xml中配置
持久化数据
Persisting State of Components
https://plugins.jetbrains.com/docs/intellij/persisting-state-of-components.html
extend PersistentStateComponent
mark a service as implementing the PersistentStateComponent interface
define the state class
specify the storage location using @com.intellij.openapi.components.State
<idea-plugin>
<extensions defaultExtensionNs="com.intellij">
<applicationService serviceImplementation="com.codertainment.scrcpy.controller.model.ScrcpyProps"/>
</extensions>
</idea-plugin>
此时,完整的界面就完成了.
执行命令
再看一个GsonFormatPlus插件
目录结构
plugin.xml文件
GenerateGroup是generate菜单栏的group-id
<actions>
<action id="GsonFormatPlus" class="com.foxsteps.gsonformat.MainAction" text="GsonFormatPlus">
<!--Generage弹出菜单-->
<add-to-group group-id="GenerateGroup" anchor="last"/>
<keyboard-shortcut keymap="$default" first-keystroke="alt s"/>
</action>
</actions>
主界面
JsonDialog.java
JsonDialog.form
Setting界面
SettingDialog.java
SettingDialog.form
Model界面
FieldsDialog.java
FieldsDialog.form
{
“cat” : “feline”,
“dog” : “canine”,
“rat” : “murine”
}
生成代码
- 通过PsiFile, PsiClass读取类信息 ConvertBridge#parseJson
- ClassEntity编辑类 ConvertBridge#handleVirgoMode
- DataWriter写入文件 FieldsDialog#run
如何依赖插件(主要解决依赖问题)
如何找到依赖插件ID
1.找到官网Dependencies文档(https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html#jetbrains-marketplace)有一些使用的插件
2.在插件市场,找到你想用的插件,在任意version中,点击展开, 左上角就有plugin id (https://plugins.jetbrains.com/plugin/8097-graphql)
IntelliJ都包括布局结构
官网文档 https://www.jetbrains.com/help/idea/getting-started.html
Tool Windows https://www.jetbrains.com/help/idea/tool-windows.html
几种常见的Tool Window
更多的官方案例-便于快速入手
git地址 git@github.com:AdrianAndroid/intellij-sdk-code-samples.git
<action id="org.intellij.sdk.action.PopupDialogAction" class="org.intellij.sdk.action.PopupDialogAction"
text="Action Basics Plugin: Pop Dialog Action" description="SDK action example"
icon="SdkIcons.Sdk_default_icon">
<add-to-group group-id="ToolsMenu" anchor="first"/>
<override-text place="MainMenu" text="Pop Dialog Action"/>
<keyboard-shortcut first-keystroke="control alt A" second-keystroke="C" keymap="$default"/>
<mouse-shortcut keystroke="control button3 doubleClick" keymap="$default"/>
</action>
public class PopupDialogAction extends AnAction {
/**
* Gives the user feedback when the dynamic action menu is chosen.
* Pops a simple message dialog. See the psi_demo plugin for an
* example of how to use {@link AnActionEvent} to access data.
*
* @param event Event received when the associated menu item is chosen.
*/
public void actionPerformed( AnActionEvent event) {
// Using the event, create and show a dialog
Project currentProject = event.getProject();
StringBuilder dlgMsg = new StringBuilder(event.getPresentation().getText() + " Selected!");
String dlgTitle = event.getPresentation().getDescription();
// If an element is selected in the editor, add info about it.
Navigatable nav = event.getData(CommonDataKeys.NAVIGATABLE);
if (nav != null) {
dlgMsg.append(String.format("\nSelected Element: %s", nav.toString()));
}
Messages.showMessageDialog(currentProject, dlgMsg.toString(), dlgTitle, Messages.getInformationIcon());
}
/**
* Determines whether this menu item is available for the current context.
* Requires a project to be open.
*
* @param e Event received when the associated group-id menu is chosen.
*/
public void update(AnActionEvent e) {
// Set the availability based on whether a project is open
Project project = e.getProject();
e.getPresentation().setEnabledAndVisible(project != null);
}
}
GroupedActions
<group id="org.intellij.sdk.action.GroupedActions"
text="Static Grouped Actions" description="SDK statically grouped action example"
popup="true" icon="SdkIcons.Sdk_default_icon">
<add-to-group group-id="ToolsMenu" anchor="after" relative-to-action="org.intellij.sdk.action.PopupDialogAction"/>
<action id="org.intellij.sdk.action.GroupPopDialogAction" class="org.intellij.sdk.action.PopupDialogAction"
text="A Group Action" description="SDK static grouped action example"
icon="SdkIcons.Sdk_default_icon">
</action>
</group>
DynamicActionGroup
relative-to-action相对于某一个选项的位置
通过代码动态添加
public class DynamicActionGroup extends ActionGroup {
@Override
public AnAction @NotNull [] getChildren(AnActionEvent e) {
return new AnAction[]{
new PopupDialogAction(“Action Added at Runtime”, “Dynamic Action Demo”, SdkIcons.Sdk_default_icon)
};
}
}
CustomDefaultActionGroup
group-id是EditorPopupMenu,右键弹出的菜单栏
public class CustomDefaultActionGroup extends DefaultActionGroup {
@Override
public void update(AnActionEvent event) {
// Enable/disable depending on whether user is editing
Editor editor = event.getData(CommonDataKeys.EDITOR);
event.getPresentation().setEnabled(editor != null); // 设置弹出的菜单是否可以点击
// Take this opportunity to set an icon for the group.
event.getPresentation().setIcon(SdkIcons.Sdk_default_icon);
}
}
@Override
public void actionPerformed(@NotNull final AnActionEvent e) {
MyLog.log(this, “actionPerformed”);
// Get access to the editor and caret model. update() validated editor’s existence.
final Editor editor = e.getRequiredData(CommonDataKeys.EDITOR);
final CaretModel caretModel = editor.getCaretModel();
// Getting the primary caret ensures we get the correct one of a possible many.
final Caret primaryCaret = caretModel.getPrimaryCaret();
// Get the caret information
LogicalPosition logicalPos = primaryCaret.getLogicalPosition();
VisualPosition visualPos = primaryCaret.getVisualPosition();
int caretOffset = primaryCaret.getOffset();
// Build and display the caret report.
String report = logicalPos.toString() + “\n” + visualPos.toString() + “\n” + "Offset: " + caretOffset;
Messages.showInfoMessage(report, “Caret Parameters Inside The Editor”);
}
*Editor Add Caret
@Override
public void actionPerformed(@NotNull final AnActionEvent e) {
MyLog.log(this, “actionPerformed”);
// Editor is known to exist from update, so it’s not null
final Editor editor = e.getRequiredData(CommonDataKeys.EDITOR);
// Get the action manager in order to get the necessary action handler…
final EditorActionManager actionManager = EditorActionManager.getInstance();
// Get the action handler registered to clone carets
final EditorActionHandler actionHandler = actionManager.getActionHandler(IdeActions.ACTION_EDITOR_CLONE_CARET_BELOW);
// Clone one caret below the active caret
actionHandler.execute(editor, editor.getCaretModel().getPrimaryCaret(), e.getDataContext());
}
- Editor Replace Text 替换原有的字符串
System.out.println(“sdfdfsdfsdfadsfsad”);
System.out.println(“editor_basics”);
@Override
public void actionPerformed(@NotNull final AnActionEvent e) {
MyLog.log(this, “actionPerformed”);
// Get all the required data from data keys
// Editor and Project were verified in update(), so they are not null.
final Editor editor = e.getRequiredData(CommonDataKeys.EDITOR);
final Project project = e.getRequiredData(CommonDataKeys.PROJECT);
final Document document = editor.getDocument();
// Work off of the primary caret to get the selection info
Caret primaryCaret = editor.getCaretModel().getPrimaryCaret();
int start = primaryCaret.getSelectionStart();
int end = primaryCaret.getSelectionEnd();
// Replace the selection with a fixed string.
// Must do this document change in a write action context.
WriteCommandAction.runWriteCommandAction(project, () ->
document.replaceString(start, end, “editor_basics”)
);
// De-select the text range that was just replaced
primaryCaret.removeSelection();
}
*MyTypedHandler 监听打字输入到(0,0)的位置
class MyTypedHandler extends TypedHandlerDelegate {
@NotNull
@Override
public Result charTyped(char c, @NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) {
MyLog.log(this, "charTyped -> " + c);
// Get the document and project
final Document document = editor.getDocument();
// Construct the runnable to substitute the string at offset 0 in the document
Runnable runnable = () -> document.insertString(0, “editor_basics\n”);
// Make the document change in the context of a write action.
WriteCommandAction.runWriteCommandAction(project, runnable);
return Result.STOP;
}
}
最多能打开几个项目(max_opend_project)
全局监听的Listener
IDE层级的注册
应用服务
public class ProjectOpenCloseListener implements ProjectManagerListener {
@Override
public void projectOpened(@NotNull Project project) {
// Ensure this isn’t part of testing
if (ApplicationManager.getApplication().isUnitTestMode()) {
return;
}
// Get the counting service
ProjectCountingService projectCountingService = ApplicationManager.getApplication().getService(ProjectCountingService.class);
// Increment the project count
projectCountingService.incrProjectCount();
// See if the total # of projects violates the limit.
if (projectCountingService.projectLimitExceeded()) {
// Transitioned to outside the limit
String title = String.format(“Opening Project “%s””, project.getName());
String message = “
The number of open projects exceeds the SDK plugin max_opened_projects limit.
” +
“This is not an error
”;
Messages.showMessageDialog(project, message, title, Messages.getInformationIcon());
}
}
@Override
public void projectClosed(@NotNull Project project) {
// Ensure this isn’t part of testing
if (ApplicationManager.getApplication().isUnitTestMode()) {
return;
}
// Get the counting service
ProjectCountingService projectCountingService =
ApplicationManager.getApplication().getService(ProjectCountingService.class);
// Decrement the count because a project just closed
projectCountingService.decrProjectCount();
}
}
获取项目环境信息(project_model)
Project https://plugins.jetbrains.com/docs/intellij/project.html
SDK https://plugins.jetbrains.com/docs/intellij/sdk.html
Library https://plugins.jetbrains.com/docs/intellij/library.html
Show Source Roots
@Override
public void actionPerformed(@NotNull final AnActionEvent event) {
Project project = event.getProject();
if (project == null) {
return;
}
String projectName = project.getName();
StringBuilder sourceRootsList = new StringBuilder();
VirtualFile[] vFiles = ProjectRootManager.getInstance(project).getContentSourceRoots();
for (VirtualFile file : vFiles) {
sourceRootsList.append(file.getUrl()).append(“\n”);
}
Messages.showInfoMessage(
“Source roots for the " + projectName + " plugin:\n” + sourceRootsList.toString(),
“Project Properties”
);
}
Show Sdk Info
@Override
public void actionPerformed(@NotNull final AnActionEvent event) {
Project project = event.getProject();
if (project != null) {
Sdk sdk = ProjectRootManager.getInstance(project).getProjectSdk();
if (sdk != null) {
String projectSDKName = sdk.getName();
String newProjectSdkName = “New Sdk Name”;
ProjectRootManager.getInstance(project).setProjectSdkName(newProjectSdkName, sdk.getSdkType().getName());
Messages.showInfoMessage(projectSDKName + " has changed to " + newProjectSdkName, “Project Sdk Info”);
}
}
}
FileProjectIndex in Action
@Override
public void actionPerformed(@NotNull final AnActionEvent event) {
Project project = event.getProject();
final Editor editor = event.getData(CommonDataKeys.EDITOR);
if (project == null || editor == null) {
return;
}
Document document = editor.getDocument();
FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance();
VirtualFile virtualFile = fileDocumentManager.getFile(document);
ProjectFileIndex projectFileIndex = ProjectRootManager.getInstance(project).getFileIndex();
if (virtualFile != null) {
Module module = projectFileIndex.getModuleForFile(virtualFile);
String moduleName;
moduleName = module != null ? module.getName() : “No module defined for file”;
VirtualFile moduleContentRoot = projectFileIndex.getContentRootForFile(virtualFile);
boolean isLibraryFile = projectFileIndex.isLibraryClassFile(virtualFile);
boolean isInLibraryClasses = projectFileIndex.isInLibraryClasses(virtualFile);
boolean isInLibrarySource = projectFileIndex.isInLibrarySource(virtualFile);
Messages.showInfoMessage("Module: " + moduleName + "\n" +
"Module content root: " + moduleContentRoot + "\n" +
"Is library file: " + isLibraryFile + "\n" +
"Is in library classes: " + isInLibraryClasses +
", Is in library source: " + isInLibrarySource,
"Main File Info for" + virtualFile.getName());
}
Project Modification in Action
Actions https://plugins.jetbrains.com/docs/intellij/basic-action-system.html
CommonDataKeys https://dploeger.github.io/intellij-api-doc/com/intellij/openapi/actionSystem/CommonDataKeys.html#HOST_EDITOR
@Override
public void actionPerformed(@NotNull final AnActionEvent event) {
Project project = event.getProject();
if (project == null) {
return;
}
Navigatable element = event.getData(CommonDataKeys.NAVIGATABLE);
if (element instanceof PsiClass) {
PsiFile file = ((PsiClass) element).getContainingFile();
if (file == null) {
return;
}
final VirtualFile virtualFile = file.getVirtualFile();
if (virtualFile == null) {
return;
}
final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex();
final Module module = fileIndex.getModuleForFile(virtualFile);
if (module == null) {
return;
}
if (!ModuleRootManager.getInstance(module).getFileIndex().isInContent(virtualFile)) {
ModuleRootModificationUtil.addModuleLibrary(module, virtualFile.getUrl());
}
}
}
Libraries for File
类文件的信息(psi_demo)
PSI
Navigating the PSI https://plugins.jetbrains.com/docs/intellij/navigating-psi.html
Program Structure Interface (PSI) https://plugins.jetbrains.com/docs/intellij/psi.html
PSI Files https://plugins.jetbrains.com/docs/intellij/psi-files.html
Unlike VirtualFile and Document, which have application scope (even if multiple projects are open, each file is represented by the same VirtualFile instance),
PSI has project scope: the same file is represented by multiple PsiFile instances if the file belongs to multiple projects open at the same time.
拓展: Document
Documents https://plugins.jetbrains.com/docs/intellij/documents.html
A Document is an editable sequence of Unicode characters, typically corresponding to the text contents of a virtual file.
拓展:Virutal File
Virtual File System https://plugins.jetbrains.com/docs/intellij/virtual-file-system.html
Virtual Files https://plugins.jetbrains.com/docs/intellij/virtual-file.html
A VirtualFile (VF) is the IntelliJ Platform’s representation of a file in a Virtual File System (VFS).
设置页面配置(settings)
public class AppSettingsConfigurable implements Configurable {
private AppSettingsComponent mySettingsComponent;
// A default constructor with no arguments is required because this implementation
// is registered as an applicationConfigurable EP
@Nls(capitalization = Nls.Capitalization.Title)
@Override
public String getDisplayName() {
return "SDK: Application Settings Example";
}
@Override
public JComponent getPreferredFocusedComponent() {
return mySettingsComponent.getPreferredFocusedComponent();
}
@Nullable
@Override
public JComponent createComponent() {
mySettingsComponent = new AppSettingsComponent();
return mySettingsComponent.getPanel();
}
@Override
public boolean isModified() {
AppSettingsState settings = AppSettingsState.getInstance();
boolean modified = !mySettingsComponent.getUserNameText().equals(settings.userId);
modified |= mySettingsComponent.getIdeaUserStatus() != settings.ideaStatus;
return modified;
}
@Override
public void apply() {
AppSettingsState settings = AppSettingsState.getInstance();
settings.userId = mySettingsComponent.getUserNameText();
settings.ideaStatus = mySettingsComponent.getIdeaUserStatus();
}
@Override
public void reset() {
AppSettingsState settings = AppSettingsState.getInstance();
mySettingsComponent.setUserNameText(settings.userId);
mySettingsComponent.setIdeaUserStatus(settings.ideaStatus);
}
@Override
public void disposeUIResources() {
mySettingsComponent = null;
}
}
public class AppSettingsComponent {
private final JPanel myMainPanel;
private final JBTextField myUserNameText = new JBTextField();
private final JBCheckBox myIdeaUserStatus = new JBCheckBox("Do you use IntelliJ IDEA? ");
public AppSettingsComponent() {
myMainPanel = FormBuilder.createFormBuilder()
.addLabeledComponent(new JBLabel("Enter user name: "), myUserNameText, 1, false)
.addComponent(myIdeaUserStatus, 1)
.addComponentFillVertically(new JPanel(), 0)
.getPanel();
}
public JPanel getPanel() {
return myMainPanel;
}
public JComponent getPreferredFocusedComponent() {
return myUserNameText;
}
@NotNull
public String getUserNameText() {
return myUserNameText.getText();
}
public void setUserNameText(@NotNull String newText) {
myUserNameText.setText(newText);
}
public boolean getIdeaUserStatus() {
return myIdeaUserStatus.isSelected();
}
public void setIdeaUserStatus(boolean newStatus) {
myIdeaUserStatus.setSelected(newStatus);
}
}
@State(
name = “org.intellij.sdk.settings.AppSettingsState”,
storages = @Storage(“SdkSettingsPlugin.xml”)
)
public class AppSettingsState implements PersistentStateComponent {
public String userId = "John Q. Public";
public boolean ideaStatus = false;
public static AppSettingsState getInstance() {
return ApplicationManager.getApplication().getService(AppSettingsState.class);
}
@Nullable
@Override
public AppSettingsState getState() {
return this;
}
@Override
public void loadState(@NotNull AppSettingsState state) {
XmlSerializerUtil.copyBean(state, this);
}
}
工具窗口ToolWindow
public class MyToolWindowFactory implements ToolWindowFactory {
/**
* Create the tool window content.
*
* @param project current project
* @param toolWindow current tool window
*/
public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) {
MyToolWindow myToolWindow = new MyToolWindow(toolWindow);
ContentFactory contentFactory = ContentFactory.SERVICE.getInstance();
Content content = contentFactory.createContent(myToolWindow.getContent(), "", false);
toolWindow.getContentManager().addContent(content);
}
}
public class MyToolWindow {
private JButton refreshToolWindowButton;
private JButton hideToolWindowButton;
private JLabel currentDate;
private JLabel currentTime;
private JLabel timeZone;
private JPanel myToolWindowContent;
public MyToolWindow(ToolWindow toolWindow) {
hideToolWindowButton.addActionListener(e -> toolWindow.hide(null));
refreshToolWindowButton.addActionListener(e -> currentDateTime());
this.currentDateTime();
}
public void currentDateTime() {
// Get current date and time
Calendar instance = Calendar.getInstance();
currentDate.setText(instance.get(Calendar.DAY_OF_MONTH) + "/" + (instance.get(Calendar.MONTH) + 1) + "/" + instance.get(Calendar.YEAR)
);
currentDate.setIcon(new ImageIcon(getClass().getResource("/toolWindow/Calendar-icon.png")));
int min = instance.get(Calendar.MINUTE);
String strMin = min < 10 ? "0" + min : String.valueOf(min);
currentTime.setText(instance.get(Calendar.HOUR_OF_DAY) + ":" + strMin);
currentTime.setIcon(new ImageIcon(getClass().getResource("/toolWindow/Time-icon.png")));
// Get time zone
long gmt_Offset = instance.get(Calendar.ZONE_OFFSET); // offset from GMT in milliseconds
String str_gmt_Offset = String.valueOf(gmt_Offset / 3600000);
str_gmt_Offset = (gmt_Offset > 0) ? "GMT + " + str_gmt_Offset : "GMT - " + str_gmt_Offset;
timeZone.setText(str_gmt_Offset);
timeZone.setIcon(new ImageIcon(getClass().getResource("/toolWindow/Time-zone-icon.png")));
}
public JPanel getContent() {
return myToolWindowContent;
}
}
开发Intellij 插件,还能干什么
学习Android编译框架的原理
开发过程中的便捷工具
热门Android Studio 插件,这里是Top 20! https://juejin.cn/post/6854573213104472071