IntelliJ Plugin Development introduction: ApplicationConfigurable, ProjectConfigurable

[Update 2016.5.12] I uploaded Single File Execution Plugin on github.

This post is for IntelliJ IDEA Plugin development.

Configurable – Adding a configuration menu of Plugin to the Settings dialog

If your plugin want user to allow some settings, we want to show configuration UI. We can show it on the IDEA settings dialog by implementing Configurable.

This post introduces quick implementation for ApplicationConfigurable and ProjectConfigurable. I’m summarizing it because I could not find many references for IntelliJ IDEA Plugin development, and I also might have mistake.

Ref: 

Making class implements Configurable

At first, create new java class. Here I made SingleFileExecutionConfigurable.java. Let this class implement SearchableConfigurable.

SearchableConfigurable is a subclass of Configurable, and the class which implements Configurable class can be shown on Settings dialog. You need to override following methods to implement this interface. Note that you can understand what method which needs to be override by using shortcut key for override in IntelliJ. See Configurable IDEA for more details.

  • getDisplayName()
        return the name which you want to show on the Settings dialog.
        Ex. “Single File Execution Plugin” 
  • getHelpTopic()
        Ex “preference.SingleFileExecutionConfigurable”
        
  • getId()
        return id
        Ex “preference.SingleFileExecutionConfigurable”
  • enableSearch(String s)
        It can be null. You can set some action to be performed when searched. 
  • createComponent()
         You can create GUI component here, and return JComponent.
         UI component returned here will be shown on the Settings menu.
  • isModified()
        This is to enable/disable “apply” button in the Setting dialog.
        Return true when you want to enable “apply” button.
        You need to implement a logic to check the configuration is updated or not, and decide to return true or false.
  • apply()
        It is called when “apply” or “ok” button is pressed.
        Implement a logic to update configuration.
  • reset()
        It is called when “apply” or “ok” button is pressed.
        Implement a logic to reset the configuration.
  • disposeUIResources()
        It is called when UI component becomes not necessary.
        You can implement finalization logic here to release memory.

Skeleton code after just overriding this method, and specifying display name and helptopic is the following. 

import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.options.SearchableConfigurable;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;

/**
 * This ProjectConfigurable class appears on Settings dialog,
 * to let user to configure this plugin's behavior.
 */
public class SingleFileExecutionConfigurable implements SearchableConfigurable {

    SingleFileExecutionConfigurableGUI gui;

    @Nls
    @Override
    public String getDisplayName() {
        return "Single File Execution Plugin";
    }

    @Nullable
    @Override
    public String getHelpTopic() {
        return "preference.SingleFileExecutionConfigurable";
    }

    @NotNull
    @Override
    public String getId() {
        return "preference.SingleFileExecutionConfigurable";
    }

    @Nullable
    @Override
    public Runnable enableSearch(String s) {
        return null;
    }

    @Nullable
    @Override
    public JComponent createComponent() {
        return null;
    }

    @Override
    public boolean isModified() {
        return false;
    }

    @Override
    public void apply() throws ConfigurationException {

    }

    @Override
    public void reset() {

    }

    @Override
    public void disposeUIResources() {

    }
}

Modify plugin.xml

plugin.xml is to declare your plugin’s modules, it is similar to AndroidManifest.xml of Android app. To use Configurable class, you should add following code to plugin.xml

  <extensions defaultExtensionNs="com.intellij">
    <applicationConfigurable groupId="tools" displayName="Single File Execution Plugin" id="preferences.SingleFileExecutionConfigurable" instance="SingleFileExecutionConfigurable" />
  </extensions>

  • displayName
     – Specify display name, should be same with getDisplayName().
  • id – unique id
  • instance – Specify Configurable class to be instanced.

Once it’s creating Configurable class and declaration of plugin.xml is done, you can build and run to see the result.

Your plugin configuration display appears in IntelliJ settings dialog.

We can see our configuration display appears in the settings inside Tools tab, as specified in groupId attribute of plugin.xml. For now this dialog have no UI on the right side. Because createComponent method is returning null so far. Let’s design GUI for this configurable.

Making GUI class

We want to design UI for the configuration, which can be designed by GUI form. We can generate GUI form, set of JAVA class and form, by right click src → New → GUI form. If you are not familiar with UI development for IntelliJ plugin, please check Search intell IntelliJ Plugin Development introduction: GUI form designing.  

Designing .form file. Specify field name for the root JPanel.

Then design .form file to create own GUI using GUI designer tool.

For example I put Jlabel and JTextField to let user to configure executable name. The Vertical Spacer in the last row has a value “Want Grow” with Property Vertical Size Policy, and it is used to make the other components to go top.

Once designing has done, important part here is to get root Panel’s instance by specifying “field name” property in .form file. When field name has set, IntelliJ automatically creates a reference for this component in the bound class, for example in this example we get below code

import com.intellij.uiDesigner.core.GridConstraints;
import com.intellij.uiDesigner.core.GridLayoutManager;
import com.intellij.uiDesigner.core.Spacer;

import javax.swing.*;
import java.awt.*;

/**
 * GUI for the {@link SingleFileExecutionConfigurable}
 */
public class SingleFileExecutionConfigurableGUI {
    private JPanel rootPanel;
    private JTextField exeNameTextField;

    SingleFileExecutionConfigurableGUI() {

    }

    public JPanel getRootPanel() {
        return rootPanel;
    }

    {
// GUI initializer generated by IntelliJ IDEA GUI Designer
// >>> IMPORTANT!! <<<
// DO NOT EDIT OR ADD ANY CODE HERE!
        $$$setupUI$$$();
    }

    /**
     * Method generated by IntelliJ IDEA GUI Designer
     * >>> IMPORTANT!! <<<
     * DO NOT edit this method OR call it in your code!
     *
     * @noinspection ALL
     */
    private void $$$setupUI$$$() {
        rootPanel = new JPanel();
        rootPanel.setLayout(new GridLayoutManager(3, 2, new Insets(0, 0, 0, 0), -1, -1));
        rootPanel.setRequestFocusEnabled(true);
        final JLabel label1 = new JLabel();
        label1.setText("Executable name");
        rootPanel.add(label1, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_NORTHWEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, new Dimension(80, 16), null, 0, false));
        exeNameTextField = new JTextField();
        exeNameTextField.setAutoscrolls(true);
        exeNameTextField.setEditable(true);
        exeNameTextField.setEnabled(true);
        exeNameTextField.setHorizontalAlignment(10);
        rootPanel.add(exeNameTextField, new GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_NORTH, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false));
        final JLabel label2 = new JLabel();
        label2.setText("%FILENAME% will be replaced to actual filename without extension.");
        label2.setVerticalAlignment(0);
        rootPanel.add(label2, new GridConstraints(1, 1, 1, 1, GridConstraints.ANCHOR_NORTHWEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false));
        final Spacer spacer1 = new Spacer();
        rootPanel.add(spacer1, new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_VERTICAL, 1, GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false));
        label1.setLabelFor(exeNameTextField);
    }

    /**
     * @noinspection ALL
     */
    public JComponent $$$getRootComponent$$$() {
        return rootPanel;
    }
}

Note that I only implement empty constuctor and getRootPanel method. All the other, including the declaration of rootPanel and exeNameTextField, is automatically generated.

To get this GUI from SingleFileExecutionConfigurable class, implement createComponent and disposeUIResources as follows.

public class SingleFileExecutionConfigurable implements SearchableConfigurable {

    SingleFileExecutionConfigurableGUI mGUI;

    @Nullable
    @Override
    public JComponent createComponent() {
        mGUI = new SingleFileExecutionConfigurableGUI();
        return mGUI.getRootPanel();
    }

    @Override
    public void disposeUIResources() {
        mGUI = null;
    }
}

That’s all for UI development for configurable, now you can see that the UI take effect by build and running the project. 

Getting a project instance

If you are developing ProjectConfigurable, you might want to get project instance. We can get the instance by creating a constructor with Project argument.

    public SingleFileExecutionConfigurable(@NotNull Project project) {
        // you can get project instance as an argument of constructor
        mProject = project;
        mConfig = SingleFileExecutionConfig.getInstance(project);
    }

Save the configuration using PersistentStateComponent

Final step, we need to save user’s configuration in storage. We can do so using PersistentStateComponent for IntelliJ. This is similar concept to Preference for Android platform, so that the value can be stored in xml format and we can extract these values in JAVA file.

For more details, I wrote an another post “IntelliJ Plugin Development introduction: PersistStateComponent” so please check it.

If you get lost…

There are sometimes few information for IntelliJ IDEA Plugin development, when you don’t know how to implement the feature, looking other module’s source code helps you understanding.

IntelliJ IDEA community edition source code is open at github, and you can see the “teacher” implementation. Just for one example, in the IntelliJ IDEA setting you can find “Terminal” configuration in Tools tab. This terminal implementation can be found under \intellij-community\plugins\terminal\src\org\jetbrains\plugins\terminal. 

By looking working other source code, you can get the idea for your implementation more easily.

Leave a Comment

Your email address will not be published. Required fields are marked *