Sign in

IntelliJ Plugin Development #1 — Understanding and modifying your first action

In the first episode, we covered the initial setup for getting your first plugin up and running, and hopefully you were able to test it with no issues. If you missed episode 0, check it out here as we setup the template for this episode. Now, we’ll cover how the plugin in episode 0 works, and modify it to change it’s functionality. We’ll also cover an erroneous scenario, and how to resolve it.

Actions

Actions are one of the key concepts in development for the IntelliJ platform. Simply, actions do exactly what you’d expect them to do — perform an operation. The operation an action performs can range from reformatting a file, to making API calls, to displaying a form or doing anything you decide. Actions are how most operations in IntelliJ are created, examples of actions include:

  • Undo
  • Save
  • Build Project
  • Git Push

As a matter of fact, any option you select from the file strip menu, and almost any button you click will perform an Action.

To create our own action, we make a new class which extends from the abstract class AnAction — this is how I created the action for the template in episode 0. There are 2 primary methods which should be overridden in the AnAction class:

  • actionPerformed (mandatory)
  • update (optional)

Taking the example from the template, you can find the action in src/main/java/MyFirstAction.java , or below.

public class MyFirstAction extends AnAction {
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
new Notification(Notifications.SYSTEM_MESSAGES_GROUP_ID,
"My first notification",
"Hello world!", NotificationType.INFORMATION)
.notify(null);
}
}

You can see in this class, we have implemented the actionPerformed method, which takes the parameter AnActionEvent e . The parameter contains information about the context of where an action was initiated from (for example, the currently open project). In this example, we do not use the parameter. In this action, we create a new Notification under the group id of System Messages (more information about group ids will be discussed in future episodes). We also supply the Notification constructor with the title, message, and NotificationType . In this case, the title is My first notification , the message is Hello world! and the notification type is INFORMATION . After creating our Notification , we need to tell IntelliJ to display it — we accomplish this by calling the Notification#notify(Project project) method. This method accepts a project as a parameter, however it’s nullable and in this simple use case, we can pass null.

IntelliJ now knows what our action is called, it knows what we want to happen when we perform the action, but how do we add the action to a menu which a user can access? We do this by registering it in the plugin.xml.

plugin.xml

The plugin.xml file (stored in src/main/resources/META-INF/plugin.xml) is how IntelliJ knows what to do with the actions that we add, and stores the plugin metadata (it’s responsible for a lot more than that, but we’ll go over that in the coming episodes).

In our example, the plugin.xml contains an <actions> section, with and <action> tag in it. The action tag (as the name would suggest) is where we define the metadata for our plugin. In our case, we define a few different properties for the action.

  • id — the id is used by IntelliJ to define the action and should be unique
  • class — the class containing our action, so intellij knows which action we are referring to
  • text — the “name” of the action, the text displayed to the user
  • description — a general description of the actions’ purpose

We also have 2 containing tags:

  • add-to-group — this tag adds the action to a particular action group (specified by group-id ). This tag also has the anchor property which allows us to change the position of the action in the group, in our case, first.
  • keyboard-shortcut — this tag is used to assign a keyboard shortcut to the action, in our case we assigned the shortcut to shift ctrl alt N . It is also possible to create a double nested shortcut, hence the name first-keystroke(for eg, shift ctrl alt N followed by shift ctrl alt S ).

Note: both containing tags are optional — they do not need to be present for the successful compilation of the project, however it is recommended to add your initiating actions to menus so that users do not have to manually search for them. Keyboard shortcuts are also generally good practice to include for commonly used operations.

Customising the Action

As a basic change to the plugin, we will use the parameter e to display the project name and project file path in the title and content of the notification instead of a static message. To do this, we use the getProject method on the action event, and then proceed to call getName() , and getProjectFilePath() respectively, as can be seen below:

@Override
public void actionPerformed(@NotNull AnActionEvent e) {
new Notification(Notifications.SYSTEM_MESSAGES_GROUP_ID,
String.format("The current project is %s", e.getProject().getName()),
String.format("The current working directory is %s", e.getProject().getProjectFilePath()), NotificationType.INFORMATION)
.notify(null);
}

A simple change — however it adds a little more complexity to the plugin, as now we have to consider, what happens if the user tries to execute the action if no project is open? Will it display null? Will it throw an exception? Will IntelliJ crash? Will nothing happen? These are the considerations we need to make.

First, let’s try executing the action with a project open, and verify that we get the result we expect.

Great! So as expected, the notification displayed the working directory, and the current project name.

Error Handling

Now, what happens if we try executing the same action with no project open? We can’t access it through the tools menu, however we can access it by pressing ctrl shift a and searching for MyFirstAction , or by using the keyboard shortcut ctrl shift alt n. When we execute the action with no project open, nothing happens — until we look at the debug window in our parent intellij.

NullPointerException on line 12— where we try to get the project name. That makes sense — if there is no project open, the current project is null, right? If we have an action which displays information about the current project, it shouldn’t be enabled when we don’t have a project open. To the end user, it will appear that the plugin did nothing, when it in fact tried to do something, and threw an exception while doing so. In this case, we have two options, either display a message to the user notifying them that they must have a project open to be able to execute the action, or we just disable the action altogether when no project is open. There will be scenarios where we will be letting the user know that something has gone wrong when they try to execute the action and it fails, however in this instance, it makes more sense to disable the action if there is no project open as it’s functionality is entirely dependent on the currently open project.

To accomplish this, we will use the update method mentioned above. In the same way as actionPerformed, it provides a parameter of AnActionEvent ewhich we can use to perform a null check on e.getProject(). The code below sets the enabled status of the presentation of the action to whether the project is null or not. In the case the project is null, then Objects.nonNull(e.getProject) will return false, which will then disable the presentation of the action. Similarly, if the project is not null, Objects.nonNull(e.getProject) will return true, and therefore enable the presentation of the action.

@Override
public void update(@NotNull AnActionEvent e) {
super.update(e);
e.getPresentation().setEnabled(Objects.nonNull(e.getProject()));
}

Opening a project, and executing the action works as it did before, and appears when searching for actions, as we expect.

When we then close the project, and then try to search for the action again, the following is displayed.

Note: if you still see the plugin displayed, this is likely because the Include disabled actions button is checked in the top right. Uncheck this box and verify it disappears. If you try to perform the action while it is disabled, nothing happens.

Great! This demonstrates that the plugin is disabled and enabled where we would expect it to be, and now the user will not be in the erroneous scenario of the project being null while executing the action.

This concludes the first episode — that was quite a lot covered so well done for making it this far! Ensure you fully understand everything described in this episode as the following episode will follow on where we have left off. In episode 2, we will begin looking at some UI components, such as Dialogs and Pop-ups. Thanks for reading!

The code for this tutorial can be found at my GitHub repository — https://github.com/JordanGibson/PluginDevelopment.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store