Brief description: the previous two days to write a picture compression plug-in with Kotlin – guide, now the plug-in foundation, yes this article is to teach you how to write a plug-in step by step from scratch, including plug-in project construction, operation, debugging to the final online release of the whole process. This article is for you if you are a beginner of plugins, and it is also the basis for the next one.

This is a news flash.

ImageSlimming image compression plug-in development completed, immediately recommended it to the team internal staff to use, in the weekly meeting there is a colleague put forward a demand, is in the AndroidStudio project, you can arbitrarily select the RES directory under one or more pictures, and then directly right-click, you can achieve picture compression. Then I thought about it for a while. I thought about it in my heart and realized it tonight. The effect is roughly as follows:

After implementing this function, the V1.1 version of the code has made a great structural adjustment, removed some common top-level functions and extension functions, now this function code has been updated to GitHub, please check the feature-image-slimming-v1.2 branch.

What is the IDE plugin for JetBrains

The IDE plug-in utilizes jetBrains’ open source IntelliJ Platform SDK(Java language) to develop a functional component that can be installed independently in an editor like IDEA. IDE plug-in is based on the IntelliJ IDEA development tool development, which integrates the construction of the plug-in project. Using Java language development and IntelliJ SDK combined development. And the plugins developed can be used not only in AndroidStudio, but also in the whole family bucket tool of jetBrains editor. It can be found that Intellij Idea has a large number of built-in plug-ins through the source code. It can be said that most of the functions of the Intellij Idea development tool are combined by plug-ins.

Start building your first plug-in project

Note: There are two main ways to build plug-in projects:

One is to directly create plug-in projects built into IDEA.

The other option is to build a plugin project by building a Gradle project, adding plugin.xml configuration and IDEA ERP dependencies (the whole process is the same as developing an Android project). Of course, this process can be implemented by referring to the official Gradle-Intellij-Plugin project. However, in the latest version after 2018.1.1, IDEA also provides internal entry to build grale plug-in project. Specifically, you can download the new version of Intellij IDEA.

  • 1. (Here we use the first type as an example) Open the installed IntelliJ IDEA and create New Project. Select an IntelliJ Platform Plugin project. Note that the SDK of IntelliJ IDEA needs to be introduced

  • 2. After selecting the SDK, you only need to create the project step by step. The structure of the project is as follows:

  • 3. As you can see, a plugin.xml file is generated. This file is the configuration file of the plug-in project.
<idea-plugin>
  <id>com.your.company.unique.plugin.id</id>
  <name>Plugin display name here</name>
  <version>1.0</version>
  <vendor email="[email protected]" url="http://www.yourcompany.com">YourCompany</vendor>

  <description><! [CDATA[ Enter short description for your plugin here.<br> <em>most HTML tags may be used</em> ]]></description>

  <change-notes><! [CDATA[ Add change notes here.<br> <em>most HTML tags may be used</em> ]]></change-notes>

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

  <! -- please see http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/plugin_compatibility.html on how to target different products -->
  <! -- uncomment to enable plugin in all products <depends>com.intellij.modules.lang</depends> -->

  <extensions defaultExtensionNs="com.intellij">
    <! -- Add your extensions here -->
  </extensions>

  <actions>
    <! -- Add your actions here -->
  </actions>

</idea-plugin>
Copy the code

Id tag: Identifies the plugin project, similar to the Package feature in Android projects. Uniquely identifies a plug-in project.

The name tag: the name of the plug-in that is published to the jetBrains Plugin repository.

Version tag: The plug-in version number, which identifies the plug-in version and is generally used to update the plug-in version id in the JetBrains Plugins repository.

Vendor tag: developer information, email and personal home page, company name or individual developer name, used to display the plugin information in the plugin repository.

Description label: describes the functions of the plug-in. Supports embedded HTML tags within tags.

ChangNote tag: Generally used for information about plug-in version changes. Supports embedded HTML tags within tags.

Idea-version tag: This version tag determines that the plugin can run in the lowest version of IDEA. If incorrectly configured, the plugin will fail to be installed. This is similar to the setting of the lowest compatible Android version in Androidmanifest.xml.

The depends tags: For example, if you need to implement a git-like plug-in, you can introduce Git4Idea through the depends tag.

Git4Idea
In fact, the built-in GitHub plug-in is realized by relying on the internal Git4Idea plug-in, and the current code cloud Git tool plug-in is also realized by relying on the Git4Idea built-in plug-in

Extension tag: Plug-ins interact with other plug-ins or with the IDE itself. (The default is IDEA.) If you want your plug-in to extend the functionality of other plug-ins or the IntelliJ Platform, you must declare one or more extensions.

  <extensions defaultExtensionNs="com.intellij">
    <appStarter implementation="MyTestPackage.MyTestExtension1" />
    <applicationConfigurable implementation="MyTestPackage.MyTestExtension2" />
  </extensions>
Copy the code

The Action tag: This tag is very important because it determines where and in what order your plug-ins will appear on the IDE, and the binding of the plug-in’s click events to the plug-in’s action implementation class.

  • 4. Create an Action class. In the IDEA plug-in project, IDEA clicking on an Item or button or an icon will trigger an Action in the plug-in.

The first is to create an Action directly through a portal provided by IDEA, and it automatically registers the event bindings in plugin.xml for you

Note # 1: It is best to add defined actions to a built-in group in the IDE so that plug-ins can be easily found and run in the corresponding group. May be asked, in which I listed so many z know which place corresponding to run the IDEA and have little skill to see the corresponding set of small and medium-sized the description content in parentheses, and then selected a group, and see what the inside group, corresponding IDEA can probably guess where, under the most stupid way is to test run, suggested that the test results recorded, The follow-up is convenient.

Note 2: In addition to adding a defined action to a built-in group, you can also add it to a custom group. The second method describes how to customize a group, but you still need to add a custom group to a built-in group, so you usually need to add the action directly or indirectly to a built-in group.

Note 3: Action can also be configured with icon, that is, it is common to click the icon to execute the plug-in. How to configure icon will be introduced in the second method below.

The second option is to manually create AnAction class, then inherit the AnAction class or DumbAwareAction class, and then unregister the Action class with the event binding in the Action tag in plugin.xml

Create Action class:

package com.mikyou.plugins.demo

import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.ui.Messages// Import com.intellij. Openapi

class DemoAction: AnAction() {
    override fun actionPerformed(p0: AnActionEvent?). {
        Messages.showInfoMessage("Just a Test "."From DemoAction hint")}}Copy the code

Register the action class binding in plugin.xml

 <actions>
    <! -- Add your actions here -->
    <action id="com.mikyou.plugins.demo.DemoAction" class="com.mikyou.plugins.demo.DemoAction" text="DemoAction"
            description="just a test demo">
      <add-to-group group-id="ToolbarRunGroup" anchor="last"/><! Add to ToolbarRunGroup built-in group -->
    </action>
 </actions>
Copy the code

To configure the plugin icon in plugin.xml, either create an image directory in the plugin project’s Resource directory or simply copy the icon to the directory and specify the icon property in the Action tag

  <actions>
    <! -- Add your actions here -->
    <action id="com.mikyou.plugins.demo.DemoAction" class="com.mikyou.plugins.demo.DemoAction" text="DemoAction"
            description="just a test demo" icon="/image/icon_pic_demo.png"><! -- Specify the icon -->
      <add-to-group group-id="ToolbarRunGroup" anchor="last"/><! Add to ToolbarRunGroup built-in group -->
    </action>
  </actions>
Copy the code

Configure custom groups in plugin.xml and add the custom groups to the built-in groups.

    <group id="com.mikyou.plugins.group.demo" text="Demo" description="just a demo group"><! Id: unique identifier of the group, text: display name of the group, description: description name of the group -->
        <add-to-group group-id="MainMenu" anchor="last"/><! Add groups to built-in groups -->
        <action id="com.mikyou.plugins.demo.DemoAction" class="com.mikyou.plugins.demo.DemoAction" text="DemoAction" description="just a test demo" icon="/image/icon_pic_demo.png"><! -- Specify the icon -->
          <add-to-group group-id="ToolbarRunGroup" anchor="last"/><! Add to ToolbarRunGroup built-in group -->
        </action>
    </group>
Copy the code
  • 5, after the configuration is OK, you can now run the plug-in, after the successful operation will start a new Intellij IDEA, this IDE is to install the development of the plug-in, and then you can debug your plug-in function inside.

  • 6. Click Run to test

  • 7, You can break the point, click debug, and then you can break the code.

  • Finally, package the plug-in and publish it. Select Build on the top toolbar and click “Prepare Plugin Module ‘Demo’ For Deployment” to generate a JAR or ZIP package in the current working directory. To publish the plugin, just upload your package to jetBrains Plugins Repository and wait for jetBrains to approve it. Then you can search the Plugins Repository in the IDE.

AnAction from source code analysis plug-in

  • 1. The AnAction class in the plug-in

One of the most important plug-in development is the Action class, it can be said that it is an entrance to the plug-in function, write AnAction class, generally will inherit AnAction class, AnAction is an abstract class, must implement the actionPerformed method, This method is called back after the user triggers the plug-in’s click event, so similar to opening a dialog box, the logic to perform a function can be written in it, and so on. From a plug-in development perspective (except for the plug-in life cycle), think of it as the main function in the program.

  • 2. The actionPerformed method in the AnAction class in the plug-in

Start by creating a DemoAction that inherits from AnAction

class DemoAction: AnAction() {
    override fun actionPerformed(p0: AnActionEvent?). {
        Messages.showInfoMessage("Just a Test "."From DemoAction hint")}}Copy the code

Then look at the third constructor of the AnAction overload and get the object of the Presentation class. Specifically, this object holds the visibility and availability of the plug-in, the Icon of the plug-in, and the appearance and control information that the plug-in displays in the IDE.

public AnAction(a) {
        this.myShortcutSet = CustomShortcutSet.EMPTY;
        this.myIsDefaultIcon = true;
    }

    public AnAction(Icon icon) {
        this((String)null, (String)null, icon);
    }

    public AnAction(@Nullable String text) {
        this(text, (String)null, (Icon)null);
    }

    public AnAction(@Nullable String text, @Nullable String description, @Nullable Icon icon) {
        this.myShortcutSet = CustomShortcutSet.EMPTY;
        this.myIsDefaultIcon = true;
        Presentation presentation = this.getTemplatePresentation();
        presentation.setText(text);// Set the plug-in to display text
        presentation.setDescription(description);// Set the plug-in description file information
        presentation.setIcon(icon);// Set the icon of the plug-in
    }
Copy the code

The external caller will trigger the actionPerformed method. Note that the actionPerformed method has an AnActionEvent object that has a getData method that gets many window objects from IDEA. But it’s actually done internally by delegating getData to its dataContext member object, which is important to represent the Context, the equivalent of Android development Context, The getData method in its internal dataContext can be used to obtain each window object of IDEA interface and the object of each window to achieve some specific functions. For example, the Project object, the VirtualFile object, the Editor object, the PsiFile persistent file object, and so on. It is no exaggeration to say that the subsequent plug-in function development is around it, which will be described in detail below.

  • Update method in the AnAction class of the plugin
class DemoAction: AnAction() {
    override fun actionPerformed(p0: AnActionEvent?). {
        Messages.showInfoMessage("Just a Test "."From DemoAction hint")}override fun update(e: AnActionEvent?). {
        super.update(e)
    }
}
Copy the code

The update method is called back when the status of the Action changes. When the status of the Action changes, the update function is called back by IDEA and the AnActionEvent object parameter is passed. The AnAction object encapsulates the context of the current Action. If an action group is displayed, the update method in the action group will be called back. Therefore, the update method in a plug-in will be executed before the actionPerformed method. It is also possible to execute multiple times, meaning that a plugin must first be visible and actionable before it is clicked to trigger an action event. Therefore, the application of a scene is that a careful partner will find that sometimes the item in the menu on the right is a gray dot, sometimes it can be displayed, sometimes it is not displayed, and sometimes it can be displayed. The logic for these judgments is typically performed in the UPDATE method.

  • 3. AnActionEvent in the AnAction class in the plugin

Both actionPerformed and Update methods carry an AnActionEvent object, which acts as a medium for the plug-in to communicate with IDEA. The corresponding DataKey object is passed through the getData method of dataContext inside AnActionEvent to obtain the corresponding window object

 @Nullable
    public <T> T getData(@NotNull DataKey<T> key) {
        if (key == null{? $reportNull? $0(28);
        }

        return this.getDataContext().getData(key);// Delegate to the DataContext object's getData method implementation
}
Copy the code
  • 4, AnActionEvent obtains the current Project object, and leads to CommonDataKeys
    @Nullable
    public Project getProject(a) {
        return (Project)this.getData(CommonDataKeys.PROJECT);
    }
Copy the code

AnActionEvent. GetData: commonDataKeys. PROJECT = commonDataKeys. PROJECT Then you’ll see that there are many object keys, such as Editor, VirtualFile, PsiFile objects, and so on.

public class CommonDataKeys {
    public static final DataKey<Project> PROJECT = DataKey.create("project");
    public static final DataKey<Editor> EDITOR = DataKey.create("editor");
    public static final DataKey<Editor> HOST_EDITOR = DataKey.create("host.editor");
    public static final DataKey<Caret> CARET = DataKey.create("caret");
    public static final DataKey<Editor> EDITOR_EVEN_IF_INACTIVE = DataKey.create("editor.even.if.inactive");
    public static final DataKey<Navigatable> NAVIGATABLE = DataKey.create("Navigatable");
    public static final DataKey<Navigatable[]> NAVIGATABLE_ARRAY = DataKey.create("NavigatableArray");
    public static final DataKey<VirtualFile> VIRTUAL_FILE = DataKey.create("virtualFile");
    public static final DataKey<VirtualFile[]> VIRTUAL_FILE_ARRAY = DataKey.create("virtualFileArray");
    public static final DataKey<PsiElement> PSI_ELEMENT = DataKey.create("psi.Element");
    public static final DataKey<PsiFile> PSI_FILE = DataKey.create("psi.File");
    public static final DataKey<Boolean> EDITOR_VIRTUAL_SPACE = DataKey.create("editor.virtual.space");

    public CommonDataKeys(a) {}}Copy the code
  • 5. Continue to dig into CommonDataKeys to see if it has any subclasses, and maybe find more key sets and get more object corresponding keys, which means that you can use more API to develop IDEA plug-in, and realize demand development faster and better. Here I will show you how to use upSource to view the source of IDEA online, and to view the CommonDataKeys subclass.

CommonDataKeys has a subclass of PlatformDataKeys, and PlatformDataKeys has a subclass of LangDataKeys.

public class PlatformDataKeys extends CommonDataKeys {
  public static final DataKey<FileEditor> FILE_EDITOR = DataKey.create("fileEditor");
  public static final DataKey<String> FILE_TEXT = DataKey.create("fileText");
  public static final DataKey<Boolean> IS_MODAL_CONTEXT = DataKey.create("isModalContext");
  public static final DataKey<DiffViewer> DIFF_VIEWER = DataKey.create("diffViewer");
  public static final DataKey<DiffViewer> COMPOSITE_DIFF_VIEWER = DataKey.create("compositeDiffViewer");
  public static final DataKey<String> HELP_ID = DataKey.create("helpId");
  public static final DataKey<Project> PROJECT_CONTEXT = DataKey.create("context.Project");
  public static final DataKey<Component> CONTEXT_COMPONENT = DataKey.create("contextComponent");
  public static final DataKey<CopyProvider> COPY_PROVIDER = DataKey.create("copyProvider");
  public static final DataKey<CutProvider> CUT_PROVIDER = DataKey.create("cutProvider");
  public static final DataKey<PasteProvider> PASTE_PROVIDER = DataKey.create("pasteProvider");
  public static final DataKey<DeleteProvider> DELETE_ELEMENT_PROVIDER = DataKey.create("deleteElementProvider");
  public static final DataKey<Object> SELECTED_ITEM = DataKey.create("selectedItem");
  public static final DataKey<Object[]> SELECTED_ITEMS = DataKey.create("selectedItems");
  public static final DataKey<Rectangle> DOMINANT_HINT_AREA_RECTANGLE = DataKey.create("dominant.hint.rectangle");
  public static final DataKey<ContentManager> CONTENT_MANAGER = DataKey.create("contentManager");
  public static final DataKey<ToolWindow> TOOL_WINDOW = DataKey.create("TOOL_WINDOW");
  public static final DataKey<TreeExpander> TREE_EXPANDER = DataKey.create("treeExpander");
  public static final DataKey<ExporterToTextFile> EXPORTER_TO_TEXT_FILE = DataKey.create("exporterToTextFile");
  public static final DataKey<VirtualFile> PROJECT_FILE_DIRECTORY = DataKey.create("context.ProjectFileDirectory");
  public static final DataKey<Disposable> UI_DISPOSABLE = DataKey.create("ui.disposable");
  public static final DataKey<ContentManager> NONEMPTY_CONTENT_MANAGER = DataKey.create("nonemptyContentManager");
  public static final DataKey<ModalityState> MODALITY_STATE = DataKey.create("ModalityState");
  public static final DataKey<Boolean> SOURCE_NAVIGATION_LOCKED = DataKey.create("sourceNavigationLocked");
  public static final DataKey<String> PREDEFINED_TEXT = DataKey.create("predefined.text.value");
  public static final DataKey<String> SEARCH_INPUT_TEXT = DataKey.create("search.input.text.value");
  public static final DataKey<Object> SPEED_SEARCH_COMPONENT = DataKey.create("speed.search.component.value");
  public static final DataKey<Point> CONTEXT_MENU_POINT = DataKey.create("contextMenuPoint");
  @Deprecated
  public static final DataKey<Comparator<? super AnAction>> ACTIONS_SORTER = DataKey.create("actionsSorter");
}

public class LangDataKeys extends PlatformDataKeys {
  public static final DataKey<Module> MODULE = DataKey.create("module");
  public static final DataKey<Module> MODULE_CONTEXT = DataKey.create("context.Module");
  public static final DataKey<Module[]> MODULE_CONTEXT_ARRAY = DataKey.create("context.Module.Array");
  public static final DataKey<ModifiableModuleModel> MODIFIABLE_MODULE_MODEL = DataKey.create("modifiable.module.model");
  public static final DataKey<Language> LANGUAGE = DataKey.create("Language");
  public static final DataKey<Language[]> CONTEXT_LANGUAGES = DataKey.create("context.Languages");
  public static final DataKey<PsiElement[]> PSI_ELEMENT_ARRAY = DataKey.create("psi.Element.array");
  public static final DataKey<IdeView> IDE_VIEW = DataKey.create("IDEView");
  public static final DataKey<Boolean> NO_NEW_ACTION = DataKey.create("IDEview.no.create.element.action");
  public static final DataKey<Condition<AnAction>> PRESELECT_NEW_ACTION_CONDITION = DataKey.create("newElementAction.preselect.id");
  public static final DataKey<PsiElement> TARGET_PSI_ELEMENT = DataKey.create("psi.TargetElement");
  public static final DataKey<Module> TARGET_MODULE = DataKey.create("module.TargetModule");
  public static final DataKey<PsiElement> PASTE_TARGET_PSI_ELEMENT = DataKey.create("psi.pasteTargetElement");
  public static final DataKey<ConsoleView> CONSOLE_VIEW = DataKey.create("consoleView");
  public static final DataKey<JBPopup> POSITION_ADJUSTER_POPUP = DataKey.create("chooseByNameDropDown");
  public static final DataKey<JBPopup> PARENT_POPUP = DataKey.create("chooseByNamePopup");
  public static final DataKey<Library> LIBRARY = DataKey.create("project.model.library");
  public static final DataKey<RunProfile> RUN_PROFILE = DataKey.create("runProfile");
  public static final DataKey<ExecutionEnvironment> EXECUTION_ENVIRONMENT = DataKey.create("executionEnvironment");
  public static final DataKey<RunContentDescriptor> RUN_CONTENT_DESCRIPTOR = DataKey.create("RUN_CONTENT_DESCRIPTOR");
}
Copy the code

4. Some suggestions for plug-in development

  • 1. It is recommended to check the official API documentation, although I think it is not very well written, but it is a quick way to learn more about plug-in development.
  • 2, suggest that more than a look at the source of the IDE plug-ins, is the best way I think further study plug-in development, such as the git plug-in Git4Idea built-in, deep into its source code, you will find the IDE a pull, a push, checkout, branch each function concrete realization. As an added bonus, you’ll mimic some of the apis used by built-in plug-ins, such as how to perform background threading tasks and how to manipulate the file system (internal plug-in files).
  • 3. The last and most important point is your idea. Plug-in development is just a tool, and the most important thing is the idea

Five, plug-in development of some resources

  • The official documentation
  • Plug-in resource collection
  • IntelliJ IDEA Architecture Overview (for Plug-in Developers)
  • Code cloud Intellij Idea Git plug-in tool source code

Finally here, the plug-in development foundation is over, the next chapter is this series of end combat development, welcome to continue to pay attention to ~~~

Welcome to the Kotlin Developer Association, where the latest Kotlin technical articles are published, and a weekly Kotlin foreign technical article is translated from time to time. If you like Kotlin, welcome to join us ~~~