idea plug-in development-AnAction

This chapter mainly explains the core implementation of AnAction. For more configuration, please refer to the previous chapters. The action system of Idea-IDE allows plug-ins to add Actions to the IDE menu and toolbar based on the IntelliJ platform. As shown in the figure below, Actions can be added to tools separately In the bar or right-click menu, it can also be added to the Group first, and then added in the Group mode.

  • AnAction or ActionGroup: three places that can be added to the IDE;
  • An ActionGroup can contain multiple AnActions;
  • An AnAction can be added to multiple ActionGroups;

1. AnAction

A custom Action only needs two operations in real time: 1. Configure the action definition in plugin.xml (of course, it can also be implemented by hard coding, but it is not recommended); 2. Implement the subclass of the custom AnAction.

1. Description of class AnAction

The AnAction class cannot add any type of class field during implementation to prevent memory leaks.

? To implement a custom Action, inherit the AnAction class and rewrite the actionPerformed() core method. It is recommended to also rewrite the update() method. There are three methods that are often used are explained in detail as follows:

  • AnAction.actionPerformed(): Called by the IntelliJ platform to perform the most complex operations of the plugin. It receives a parameter named AnActionEvent, this parameter can be used to access any contextual data such as project, file, selection, etc.;
  • AnAction.update(): Called by the IntelliJ platform to update the status of the action, such as whether the action is enabled, visible, etc. Be careful not to perform time-consuming operations in the rewritten code. Get the Presentation object through AnActionEvent.getPresentation() (the default Presentation object is a set of description information about menu or toolbar operations), and then execute the state of the object associated with the event context through this object. Another point is that the same Action can have different text or icons in different user interface positions, which is also achieved by cloning the Presentation object through AnAction. Replace the default text with override-text).
  • AnAction.getActionUpdateThread(): This method (only supported in 2022.3 +) returns an ActionUpdateThread object. It has two execution schemes: execute the update() method on the BGT background thread or the EDT event scheduling thread. The advantage of the former is that it can maintain access to data models such as PSI and VFS during execution, but cannot access the Swing component structure. And EDT is just the opposite.

2. Configuration instructions

<actions>
  <action
      id="com.example.impl.CollectGarbage"
      class="com.example.impl.CollectGarbage"
      text="Garbage Collector: Collect _Garbage"
      description="Run garbage collector"
      icon="icons/garbage.png">
  </action>
<actions>

Several important attribute descriptions:

  • id: a unique value within a certain range, it is recommended to have the same name as the implementation class, or refer to the source code implementation of idea implementation;
  • ?text: The text of the action displayed on the UI interface. If internationalization is used, the key attribute needs to be used instead of the text attribute
  • icon: A .png image is used in this example, but it is recommended to use all .svg format files;

Child node

Among them, synonym is used more, which defines the keyword to find this action in Help | Find Action. If searching is prohibited, searchable=”false” can be set. It also supports the internationalization of the key method.

<action
      id="VssIntegration. GarbageCollection"
      class="com.example.impl.CollectGarbage"
      text="Garbage Collector: Collect _Garbage"
      description="Run garbage collector"
      icon="icons/garbage.png">

    <!--
    The second <override-text> element uses the alternate attribute
    "use-text-of-place" to define a location (EditorPopup) to use the
    same text as is used in MainMenu. It is a way to specify the use
    of an alternate menu text in multiple discrete menu groups.
    -->
    <override-text place="MainMenu" text="Collect _Garbage"/>
    <override-text place="EditorPopup" use-text-of-place="MainMenu"/>

    <!-- Provide alternative names for searching action by name -->
    <synonym text="GC"/>

    <add-to-group
        group-id="ToolsMenu"
        relative-to-action="GenerateJavadoc"
        anchor="after"/>

    <!-- Add the first and second keystrokes to all keymaps... -->
    <keyboard-shortcut
        keymap="$default"
        first-keystroke="control alt G"
        second-keystroke="C"/>

    <!-- ...except the "Mac OS X" keymap and its children. -->
    <keyboard-shortcut
        keymap="Mac OS X"
        first-keystroke="control alt G"
        second-keystroke="C"
        remove="true"/>

    <!-- The "Mac OS X 10.5 + " keymap and its children will have only
    this keyboard shortcut for this action. -->
    <keyboard-shortcut
        keymap="Mac OS X 10.5 + "
        first-keystroke="control alt G"
        second-keystroke="C"
        replace-all="true"/>

    <mouse-shortcut
        keymap="$default"
        keystroke="control button3 double click"/>
  </action>

I18N settings

The recommended configuration method is to define a resource file for the entire plugin, but it also supports defining a separate resource file for the action. The configuration is as follows (see the end of this chapter for usage):

Define resource files for plugins

 /resources /messages /BasicActionsBundle.properties
 <resource-bundle>messages.BasicActionsBundle</resource-bundle>

Separately define resource files for actions

<actions resource-bundle="messages.MyActionsBundle">
  <!-- action/group defined here will use keys from MyActionsBundle.properties -->
</actions>

The suggested resource file format is as follows. For the value in the following code, please refer to IDEA’s code implementation and xml configuration implementation

//Actions
action.<action-id>.text=Translated Action Text
action.<action-id>.description=Translated Action Description


//Actions. override-text
action.<action-id>.<place>.text=Place-dependent Translated Action Text


//Groups
group.<group-id>.text=Translated Group Text
group.<group-id>.description=Translated Group Description

//Groups. override-text
group.<group-id>.<place>.text=Place-dependent Translated Group Text

3. Build complex UI from Actions?

If a plug-in needs to include a toolbar or popup menu in the user interface, which is built from a set of actions, it can be achieved through ActionPopupMenu and ActionToolbar. These objects can be created by calling the ActionManager.createActionPopupMenu() and createActionToolbar() methods. Call the corresponding getComponent() method to get the Swing component.

If the action toolbar needs to be attached to a specific component (for example, a panel in a tool window), call ActionToolbar.setTargetComponent() and pass an instance of the relevant component as a parameter. Setting a target ensures that the state of a toolbar button depends on the state of the related component, rather than on the current focus position within the IDE frame.

Two,

can be placed in a tag. From another perspective, can be regarded as a layer of logical structure, which can organize Action into a logical UI structure, essentially reusing Action. A visual menu can be formed by nesting of , etc. But one thing to note is that when the same action is included in multiple groups, the Action in each must have a unique identifier, that is, there must be a unique identifier in each group ID value.

When implemented through .xml, the group is static, and the group cannot be changed during the running of the IDE, but its status can be set whether it is available or not. Dynamic groups can also be created programmatically.

1. Static group

 <group
      class="com.example.impl.MyActionGroup"
      id="TestActionGroup"
      text="Test Group"
      description="Group with test actions"
      icon="icons/testgroup.png"
      popup="true"
      compact="true">

    <action
        id="VssIntegration.TestAction"
        class="com.example.impl.TestAction"
        text="My Test Action"
        description="My test action"/>

    <!-- The <separator> element defines a separator between actions.
    It can also have an <add-to-group> child element. -->
    <separator/>

    <group id="TestActionSubGroup"/>

    <!-- The <reference> element allows adding an existing action to
    the group. The mandatory "ref" attribute specifies the ID of
    the action to add. -->
    <reference ref="EditorCopy"/>

    <add-to-group
        group-id="MainMenu"
        relative-to-action="HelpMenu"
        anchor="before"/>
  </group>

Several important properties:

  • popup: Whether the operation in the group allows adding sub-actions;
  • compact: Whether operations in this group are visible when disabled

2. Dynamic group

The limitation is that cannot be configured in the .xml configuration, it needs to be implemented in the code, and there is no difference in other aspects. The implementation code is as follows, and the PopupDialogAction in the code will also perform the same operation.

public class DynamicActionGroup extends ActionGroup {
  @NotNull
  @Override
  public AnAction[] getChildren(AnActionEvent event) {
    return new AnAction[]{
        new PopupDialogAction(
            "Action Added at Runtime",
            "Dynamic Action Demo",
            SdkIcons.Sdk_default_icon)
    };
  }
}

3. Extend DefaultActionGroup?

More advanced features can be implemented, such as conditionally showing or hiding groups. This needs to extend the implementation class of DefaultActionGroup of ActionGroup. The DefaultActionGroup is used to add sub-actions and separators between them to the group. Use this class if the set of operations belonging to the group will not change at runtime.

public class CustomDefaultActionGroup extends DefaultActionGroup {
  @Override
  public void update(AnActionEvent event) {
    // Enable/disable depending on whether a 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);
  }
}

Register a custom group

<resource-bundle>messages.BasicActionsBundle</resource-bundle>

<actions>
  <group
      id="org.intellij.sdk.action.CustomDefaultActionGroup"
      class="org.intellij.sdk.action.CustomDefaultActionGroup"
      popup="true">
    <add-to-group group-id="EditorPopupMenu" anchor="first"/>
  </group>
</actions>

3. Example

In this example, an Action will be implemented, and then placed in toolWindow, group, and popup respectively through configuration.

1. Basic implementation

Implement Action

public class PopupDialogAction extends AnAction {
  //No variables can be defined here to prevent oom
  
  public PopupDialogAction() {
    super();
  }
  
  public PopupDialogAction(@Nullable String text, @Nullable String description, @Nullable Icon icon) {
    super(text, description, icon);
  }

  @Override
  public void actionPerformed(@NotNull AnActionEvent event) {
    // get the current item
    Project currentProject = event. getProject();
    // Get some description information from the Presentation context
    StringBuilder dlgMsg = new StringBuilder(event. getPresentation(). getText() + "Selected!");
    String dlgTitle = event. getPresentation(). getDescription();
    //The currently browsed object
    Navigatable nav = event.getData(CommonDataKeys.NAVIGATABLE);
    if (nav != null) {
      dlgMsg.append(String.format("\\
Selected Element: %s", nav.toString()));
    }
    //Display a Dialog
    Messages.showMessageDialog(currentProject, dlgMsg.toString(), dlgTitle, Messages.getInformationIcon());
  }

  @Override
  public void update(AnActionEvent e) {
    // Set the availability based on whether a project is open
    Project project = e. getProject();
    e.getPresentation().setEnabledAndVisible(project != null);
  }

}

Define the plugin icon

public class SdkIcons {
 //The default icon file is stored in the src/main/resources/icons/ directory
  public static final Icon Sdk_default_icon = IconLoader.getIcon("/icons/sdk_16.svg", SdkIcons.class);
}

Display 1: Add to toolWindow

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

[Note]: The usage of and in the configuration;

Display 2: add to toolWindow in group mode

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

[Note]: The value of in the configuration is the id value of ; cooperating with anchor is a positioning function, not a function reuse.

Display 3: Add to popup (refer to internationalization implementation)

<group id="org.intellij.sdk.action.CustomDefaultActionGroup"
           class="org.intellij.sdk.action.CustomDefaultActionGroup"
           popup="true">
      <add-to-group group-id="EditorPopupMenu" anchor="first"/>
      <action id="org.intellij.sdk.action.CustomGroupedAction" class="org.intellij.sdk.action.PopupDialogAction"
              icon="SdkIcons.Sdk_default_icon"/>
</group>
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);
  }

}

2. Realization of dynamic group

 <group id="org.intellij.sdk.action.DynamicActionGroup" class="org.intellij.sdk.action.DynamicActionGroup"
           popup="true" text="Dynamically Grouped Actions" description="SDK dynamically grouped action example"
           icon="SdkIcons.Sdk_default_icon">
      <add-to-group group-id="ToolsMenu" anchor="after" relative-to-action="org.intellij.sdk.action.GroupedActions"/>
    </group>

The java implementation is as follows, where the action definition must be hard-coded

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)
    };
  }

}

3. Automatic matching I18 implementation

In addition to specifying the key attribute, idea’s I18N implementation can also be implemented through the internal logic of the internationalized resource file (this method will save some code), see the configuration of CustomDefaultActionGroup above. In this configuration, the necessary attributes text, description, and key are not configured. But the program will not report an error. The reasons are as follows:

<resource-bundle>messages.BasicActionsBundle</resource-bundle>
action.org.intellij.sdk.action.CustomGroupedAction.text=A Popup Action [EN]
action.org.intellij.sdk.action.CustomGroupedAction.description=SDK popup grouped action example [EN]
group.org.intellij.sdk.action.CustomDefaultActionGroup.text=Popup Grouped Actions [EN]
group.org.intellij.sdk.action.CustomDefaultActionGroup.description=Custom defaultActionGroup demo [EN]
 <group id="org.intellij.sdk.action.CustomDefaultActionGroup"
           class="org.intellij.sdk.action.CustomDefaultActionGroup"
           popup="true">
      <add-to-group group-id="EditorPopupMenu" anchor="first"/>
      <action id="org.intellij.sdk.action.CustomGroupedAction" class="org.intellij.sdk.action.PopupDialogAction"
              icon="SdkIcons.Sdk_default_icon"/>
    </group>

For example, in the configuration line action.org.intellij.sdk.action.CustomGroupedAction.text=A Popup Action [EN], the previous action and the middle id are location information:

  • action: Indicates in the configuration file
  • org.intellij.sdk.action.CustomGroupedAction: Indicates the id attribute value of
  • text: Indicates the value to be set for the text of ;