资料
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






![[附源码]Python计算机毕业设计SSM基于java旅游信息分享网站(程序+LW)](https://img-blog.csdnimg.cn/bcf5916bca2a44e68b7008f457011cdb.png)












![[附源码]Node.js计算机毕业设计高校图书服务系统Express](https://img-blog.csdnimg.cn/60197477b0aa427790fdb6b48af7c4cc.png)