Alex headshot

AlBlue’s Blog

Macs, Modularity and More

IntelliJ Plugin Development

2011

My attention recently turned towards IntelliJ plugin development, and since information on the subject is incredibly scarce I thought I’d jot down a few notes here. I’ll also try and relate it to plugin development with more familiar environments (Eclipse) for those that are interested in making the switch over.

Firstly, it’s worth noting that I’m writing this with IDEA 10.5 as a base. Things can change over time, so if you find this on a Google Search ten years for now you can use common sense to see if it still applies.

IntelliJ IDEA has only (relatively) recently enabled plugin development at all, so things have changed over time. One of the most notable issues is that whilst other products (Eclipse, NetBeans) have focussed on a modular architecture from the beginning, IDEA has always been shipped as a Big Bag of Hurt Jars. So both the plugin development – and the available plugins – reflect the youthfulness of the application, often with contradictory results. For example, updating IDEA itself is handled in a completely different way from updating the plugins.

Installing plugins

When IDEA installs, it creates a plugins directory at the root of the application install, which is used to store the plugins themselves. Each plugin gets its own directory, which uses the id of the plugin (and if the id is not available, the name) by default. Underneath that is a mandated lib directory, and inside that are the plugin JARs themselves.

Directory Layout
1
2
3
4
5
6
7
8
9
IDEA\
  plugins\
    MyPlugin\
      lib\
        MyPlugin.jar
        MyPluginExtra.jar
    AnotherPlugin\
      lib\
        AnotherPlugin.jar

One common way of installing plugins is to just unzip the folder into the plugins directory. The IDEA build process generates a zip file which contains the name of the project as part of the internal folder structure.

Each plugin has a single class loader, so whether it is shipped as a single JAR or as multiple JARs are a matter of convenience rather than anything else. If you consume downstream libraries then you can simply embed a copy of it in the plugin’s lib directory.

The existence of lib is somewhat mystifying; it’s as if JetBrains had other thought regarding resources which they could then put to use; or perhaps they were just following web app development structure. Either way, any other content appears to be ignored in the plugins directory.

Update sites

You can’t have update sites in IntelliJ IDEA. Well, you can’t have sites, but you can have site – hardcoded into each IntelliJ install is a constant called DEFAULT_PLUGINS_HOST, which resolves to http://plugins.intellij.net. This is similar to Eclipse’s MarketPlace and is usually the de-facto method of installing plugins into IntelliJ. If you want to have your own update site, you’re out of luck. There’s no way to host an IntelliJ update site outside of this mechanism.

There is laughably something called “Enterprise Repository support” in which you can have a list of plugins, identified with a file called updatePlugins.xml. This is nothing of the sort.

Yes, you can start IDEA with a -Didea.host.url= http://path/to/updatePlugins.xml property, and yes, it will download that file. However, when you put any plugin in this file, it will show up a dialog asking you to install all of plugins in that file.

It doesn’t integrate with the Plugin model (where people will be expecting it to be) and it forces you to install everything. Oh, and it doesn’t work either.

Instead, the update site has two URLs which it uses to convey information back to clients who ask. The first is the list of plugins that are available, which it accesses from http://plugins.intellij.net/plugins/list/. This is an XML file (not even compressed!) which contains a list of every plugin known to man (or to JetBrains, at least). The format looks like:

IntelliJ Plugin definition file
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<plugin-repository>
  <ff>"Build"</ff>
  <category name="Build">
    <idea-plugin downloads="5483" size="46833" date="1258402969000" url="http://handyedit.com/antdebugger.html">
      <name>Ant Debugger</name>
      <id>com.handyedit.AntDebugger</id>
      <description><![CDATA[...]]></description>
      <version>1.0</version>
      <vendor email="antdebugger@handyedit.com" url="http://handyedit.com/">Alexei Orischenko</vendor>
      <idea-version min="n/a" max="n/a" until-build="3999"/>
      <change-notes><![CDATA[...]]></change-notes>
    </idea-plugin>
  </category>
</plugin-repository>

Rather than telling you where you could get all of these plugins from as part of a url attribute, JetBrains makes you go back and get them via another URL, which is http://plugins.intellij.net/pluginManager. This seems to act as a redirector for plugins based on their (plugin) id, and it redirects you to a URL where you can download the file from. For example, the first entry in the IDEA repository above is the Ant Debugger, whose id is com.handyedit.AntDebugger. Add this to the URL, and you get http://plugins.intellij.net/pluginManager?action=download&id=com.handyedit.AntDebugger&build=IC-107.322, which redirects you to the JAR, and which subsequently installs the content. (The name of this file is derived from the plugin’s ID and a unique number which corresponds to the download id in the plugins.intellij.net site – it’s not related to the repository at all. Note the without the build parameter, the redirection doesn’t work – a primitive form of server-side detection of compatible version numbers.

The one ray of sunshine is that there is a way of overriding this default host; if you run IDEA with -Didea.plugins.host=http://somewhere/else then you can use a different update site to the one compiled within IntelliJ. This can do something slightly more intelligent (like; allowing you to use multiple update sites through careful redirections) but it’s a significant piece of missing functionality in IntelliJ IDEA that you have to do this.

It’s no wonder that IntelliJ users often just extract the contents of a plugin into their local install, or acquire it through the central update site. It’s almost impossible to do otherwise.

Plugin Development

In order to do any kind of plugin development in IntelliJ, you have to configure an SDK. This is non-obvious and almost all of the Google searches turn up with nothing of any use whatsoever. Fresh out of the box, IDEA doesn’t even have a Java SDK defined.

You need to set up the Java SDK first, followed by the IDEA SDK (which it can use as itself). Go into the Project Settings and find the Platform Settings, which includes SDKs. There’s a small [+] icon at the top of the second column; click it and select Java SDK first; it should auto-detect which JDK you’ve launched it from, but you can select any JDK on your system. Once that’s done, click on the [+] icon again and this time select IDEA SDK. It will prompt you for the JDK you’ve just defined and select itself as the host. Whilst it looks like it’s set everything up, unless you hit ‘OK’ down at the bottom these changes won’t be remembered.

Once you have your SDKs defined, you can create a Plugin Module project. This creates a file called META-INF/plugin.xml which defines the name, id, and version of the plugin. There are also several entries for hooking into IntelliJ:

  • Plugin metadata (name, version, id)
  • Application components – global content
  • Project components – things which are specific to a single project
  • Actions – things that show up in menus
  • Extensions – things that plug into extension points

The plugin also has a reference to something which can initialise the component (like a Plugin constructor, or an activator in OSGi/Eclipse). This appears to be called during IDEA startup, but it can block startup until it returns; so if there’s long-running operations that you need to do, consider running them in a background thread.

Swing development

My eyes! My eyes! I had really forgotten how bad Swing development can be, until I was forced back into it. Still, it’s not as bad as on some platforms but if you’re used to IntelliJ you probably see past the UI widget mess in any case. (I’m sure that people will have similar diverging opinions of Eclipse and Xcode; at some level, there’s an aspect of familiarity which means you look over warts in your own IDE of choice.)

One advantage of developing IDE plugins for IDEA over that of Eclipse is you get to use Swing. Whilst not a UI win, it is a programming win, since you don’t have to worry about dispose() or leaking resources. It’s also generally easier to find tutorials on the subject – Eclipse has always been somewhat cryptic with the way that menus and actions are contributed (not helped by the fact that the recommended way has changed every couple of releases).

In any case, it’s relatively easy to create tool windows (similar to Eclipse Views, except that they are always present in the window as either minimised entries or showing in the screen somewhere). You get handed a Container in the createToolWindowContent() method (which turns out to be a Swing container) and you can throw what you like in there, wiring it up to the mouse events to trigger actions.

Deploying the plugin is a case of doing “Build → Prepare Plugin for Deployment”. This generates a ZIP in the same folder as your plugin (no option to install it elsewhere, it seems) which contains the above folder structure with your module in. Modularity in Java has actively been harmed by IntelliJ’s awful project structure, and is the main cause for pain in leaky implementations for code primarily developed in the IDEA.

If you want to package dependent libraries as well, you can do so by going to the Project Settings and creating a new library, which you then put the JAR into (confusingly, with the ‘classes’ button). It will then get exported with your plugin when it gets built.

Hello World

With that out of the way, the process involved for a Hello World toolWindow is as follows:

META-INF/plugin.xml
1
2
3
<extensions defaultExtensionNs="com.intellij">
  <toolWindow id="HelloWorld" icon="/helloworld.png" anchor="right" factoryClass="com.example.HelloWorld"/>
</extensions>
src/com/example/HelloWorld.java
1
2
3
4
5
6
public class HelloWorld implements ToolWindowFactory {
  public void createToolWindowContent(Project project, ToolWindow toolWindow) {
    Component component = toolWindow.getComponent();
    component.getParent().add(new JLabel("Hello, World!"));
  }
}

The only thing I found significantly painful was that the runtime on OSX immediately crashed with a lack of PermGenSpace. Fortunately, “Run → Edit Configurations” whereupon you can add additional Java VM arguments (in this case, -XX:MaxPermSize=256m) which solved that problem. One other annoying feature; each time I added a key press in the -VM parameters field, I got a dialog box flash up asking whether I wanted to accept incoming connections or not. I think this is likely to be an issue with the recent Lion build (along with the error message “2011-08-12 08:34:56.286 java[10515:407] -[NSOpenPanel _setIncludeNewFolderButton:] is deprecated. Please stop calling it.” when invoking the Open dialog.

Summary

Once you get past the ugliness that is Swing, and resign yourself to manual installation of plugins, developing for IntelliJ isn’t that bad. Most of it uses vanilla Swing operations, though it does helpfully suggest some improved IntelliJ Swing classes in place of the standard Swing ones (though in my uses, it actually performed worse than the standard Swing ones did so I ignored that suggestion).

Once you have a displayable component, it’s relatively easy to pick up mouse events and respond to actions. As yet, I have not integrated with the ADT or have processed any source code which is a challenge for another day.