Introduction to IntelliJ Plugin Development  General

Introduction to IntelliJ Plugin Development

An introduction to plugin development for the IntelliJ platform. It lists best practices and recommendations to build and work on plugins.

Introduction

Products based on the IntelliJ platform are expandable by plugins. With the help of plugins the products can be enhanced with things like new languages, additional code completion, expanded documentation content, support for new frameworks and the like. Much of the built-in functionality is already implemented with the help of plugins.

Plugins can be installed, uninstalled, enabled and disabled by the user. Extensions shipped with the product itself may be disabled, but can not be uninstalled.

You probably are planning to build and deploy your own plugin. It’s a great idea! This post helps you to get started and to learn about best practices to work on it.

After reading this article (and the further resources it links to) you should be able to get started with the development of your first plugin. Please let me know if you miss information about certain areas of the development process. I’ll update this page from time to time to make sure that it’s up-to-date and as helpful as possible.

This post is intended for experienced Java developers. I assume general knowledge of Java, Swing, JUnit, XML and Gradle and also expect that you (if you want to add support for a new language) know about lexers, parser grammars and the related concepts. Source code won’t be explained unless it’s related to IntelliJ. It is assumed that you use IntelliJ IDEA Community or Ultimate as your development environment of choice.

When I’m referring to IntelliJ then the other products of the same family are also meant, of course. This includes IntelliJ IDEA Ultimate and Community, WebStorm, PHPStorm, PyCharm Community / Pro / Edu, CLion, DataGrip, AppCode and Gogland.

Please note: ReSharper and ReSharper C++ are not based on the IntelliJ platform and are therefore not compatible with plugins targeting this environment.

Many of IntelliJ’s featuers are available in all of these products and may be used or extended by your plugin. Some functionality is limited to certain products, though. For example, JavaScript support is not available in Community editions of IntelliJ or PyCharm but is part of JetBrains’ commercial offerings.

Development

Use IntelliJ, of course! The Community edition provides all you need to get started. Make sure to enable the plugin Plugin DevKit to get useful actions and inspections which support you to work on your pluign.

IntelliJ comes in many different editions and versions.

Choose your target environment

IntelliJ major build

At first you should choose the minimum version of the platform you would like to support. JetBrains releases about 3 new major versions of its products each year. Each major version has its own major build number. The details of the naming schema are documented in the developer wiki.

Your choice will depend on your target audience, the features you would like to implement and the time you’d like to spend to support older versions of the products. You may choose to drop support for older versions in the future, supporting newer versions is usually a lot easier than to extend your plugin’s compatibility to older builds.

At the time when the 145.x build was released JetBrains switched to a subscription based licensing model. Users are expected to upgrade to newer versions more quickly. If you don’t have much time then stick to the current build of IntelliJ, e.g. 172.x (i.e. 2017.2) and stay compatible with the newer releases. When binary incompatibilites arise with newer builds or when you need new features which are only available there you may choose to drop the support of older builds.

If you’re targetting an enterprise environment or if you’d like to support as many users as possible support 145.x / 2016.1 and later. If you need to provide support for earlier versions it’s best to do right from the beginning. Backporting your plugin to older APIs won’t be pretty.

At this time I’d advise to not support builds earlier than 145.x, i.e. 2016.1. At this time all builds since 145.x use Java 8. Previous versions are require Java 6.

Overall it’s not as bad as it sounds, though. A few years ago JetBrains introduced binary incompatibilites or backwards-incompatible changes to API. This has been noticeably improved. These kinds of incompatibilies are rarely observed with the more recent builds. At times JetBrains needs to make a cut to drop support of the old APIs, though. When that happens you need to decide whether you’d like to support both build ranges or just the newer versions.

See below to learn about the different ways to handle this.

Java runtime environment

Each major build requires a certain version of the Java runtime environment. 145.x and later are all based on Java 8. JetBrains even ships packages which include their own, patched version of the JDK to fix issues on the different operating systems.

The choice is easy:

  • if you support 145.x / 2016.1 or later, then stick with Java 8
  • if you support earlier version you need to compile with Java 6.

Operating system

The IntelliJ products are all, with the exception of AppCode, cross-platform compatible. Your plugin should not target any specific operating system. If you add your own UI elements, like dialogs or custom Swing components, then make sure that you test your plugin on Mac OS X, Windows and Linux. The different systems may exhibit distinct behavior of font rendering, etc. Of course, the usual guidelines for cross-platform development apply: stick to Java’s filesystem access, don’t assume that certain programs are available, etc.

Programming language

For most developers the choice is easy: use the Java language to build your plugin.

If you prefer it’s also possible to use other languages based on the JVM, e.g. Kotlin or Scala. You’re on your own here, though. There are a few other plugins doing this and JetBrains is using Kotlin itself, but you need to handle any additional complexity or incompatibility incurred by your language of choice.

Developing

Basic concepts

The following paragraphs explain the concepts which are frequently used while developing an IntelliJ extension.

Dependency management
IntelliJ implements dependency injection by using PicoContainer.
Application
The center of the IntelliJ runtime environment. There’s only a single instance for a process. It’s accessible by com.intellij.openapi.application.ApplicationManager#getInstance(). You could also declare an Application contructor parameter to get a reference to it.
Project
Basically a project represents a main window and everything it manages (i.e. modules, settings, editor windows, …). There’s a getProject() method available on most classes which are managed in project scope.

The single instance of an Application is linked to several projects. It may have zero, one or more projects opened at the same time. Plugins can offer functionality which use an Application or a Project.

Module
You can think of a module as an element which consists of a directory with attached configuration. Modules are visible in the project tree in IntelliJ but are hidden in the smaller IDE’s like WebStorm etc. which have just one module in each project.
Facet
A facet is sub-structure of a module. For example, it’s to implement the configuration of the Spring framework in a module. Facets are only displayed in IntelliJ but are inaccessible in the smaller IDEs. Don’t use facets if you develop for more than IntelliJ.
plugin.xml
The central configuration file of a plugin. It contains the metadata like name, description, author, changelog and also lists the different extensions which are implemented by a plugin.
Component
A type of extensions which is made available by a plugin. A component is either tied to an Application (i.e. an ApplicationComponent) or to a Project (i.e. a ProjectComponent). A component is available during the lifetime of an Application or Project instance. Each instance of Project has its own instances of a ProjectComponent. Application components can take an Application in the constructor. Project components may have both Project and Application as constructor parameters.
Note: Avoid components, if possible. They slow down startup because they’re not lazily initialized. They take up memory during the whole lifetime of their context (i.e. Application / Project).
Service
A service is the kind of the newer version of a component. It’s initialized when it accessed for the first time and is recommended instead of components.
Extension point
An ExtensionPoint is a very generic extension. IntelliJ’s core features offer certain points, where plugin may offer additional logic and/or data. This is done using extension points. A plugin declares that it implements a certain extension point. IntelliJ then retrieves all implementations of that extension point and takes them to implement a certain feature.
For example: Code completion relies on data which is specific for the programming language of the current file. The BashSupport plugin implements an extension point to offer the right suggestions in the code completion popup. When code completion is invoked in IntelliJ, it looks at the current file (a Bash file, in that case) and calls all extension point implementations which offer code completion for Bash files.

Custom Language Development

A plugin is able to add a new language to IntelliJ. Language in this context could be a programming language like Scala, Fortran or Bash but could also be a markup language like Markdown or even a lexer definition.

I highly recommend to read the relevant section in the official DevGuide before you continue here.

Don’t underestimate the effort which is needed to support a new language. Full support of all available features takes a lot of time. But, of course, it’s possible to start small and add features from time to time.

Lexer and Parser

If there is a formal definition of the grammar: checkout GrammarKit, a great plugin to generate a lexer and parser suitable for IntelliJ. GrammarKit’s documentation is available at github.com. It uses Parsing Expression Grammar, you may have to rewrite your grammar into this PEG format. Then use the GrammarKit plugin to generate lexer and parser for your language.

If there is no formal definition available you should still use GrammarKit and write it from scratch.

The alternative to GrammarKit is to write your own lexer and parser. The IntelliJ SDK supports JFlex as lexer generator. Just make sure that you use the patched version shipped by JetBrains (see github.com/jetbrains). JFlexAdapter should be used to turn a JFlex generated lexer into a IntelliJ lexer.

Tests

You’ll make your life a lot easier if you add test cases for your lexer and parser. Testing them is much more time consuming and difficult if you do that in a live IDE. Both, lexer and parser, are fundamental parts of your plugin and should have good coverage.

IntelliJ’s Java support

IntelliJ’s support for the Java language is an example of a grown-up parser which is not generated. It uses a JFlex lexer (_JavaLexer.flex) and a hand-crafted parser. Please note that this is a rather unusual case which needs to support many different versions of the Java language.

Extending existing plugins

There are several ways to extend existing plugins:

  • Implement extension points
  • Add new inspections
  • Add new Intentions

Your plugin needs to declare a dependency on the 3rd party plugin before it’s able to use it.

Extension points

Plugins can offer their own extension points. Extending these plugins works in the same way as extending the base functionality. If a plugin does not offer custom extension points then it’s not possible to customize its behavior in most cases. We’ll assume that you want to extend a plugin which offers extension points.

Open source plugins

If the source code of the plugin is available then it’s often easy to extend it. Checkout the source, look into the plugin.xml file for the <extensionPoints> section and read the documentation and/or source code of the extension point interfaces.

Closed source plugins

If it’s closed-source then it’s going to be a lot harder. You can still look into the plugin.xml but you have to guess how the <extensionPoints> are supposed to be used. You have to rely on a Java decompiler to read the source code of an extension point interface and of its existing implementations.

For example, the JavaScript plugin, which is shipped with the commercial editions, offers custom extension points. Look at the file META-INF/plugin.xml contained in the plugins/JavaScriptLanguage/lib/JavaScriptLanguage.jar JAR file which is located in your IDE’s installation folder. I once implemented the JavaScript.predefinedLibraryProvider extension point, but I had to guess how it should be used. It’s a lot of guesswork and may need some time to get it right.

Of course, you may ask the plugin’s author to provide information about the extension points. JetBrains has a few details about the PHPStorm extension points in the dev guide: PHP Open API.

Inspections & Intentions

You are able to use another plugin’s PSI classes to add new inspections and intentions.

Declare your inspection using the other plugin’s language ID. IntelliJ’s code completion already suggests the available IDs if you call it with your caret inside of the language attribute of a localInspection tag.

An Intention does not need any special configuration as it does not link to a certain language. Make sure to implement your isAvailable(...) method in a way that it’s only enabled in files of the other plugin.

Building

You can build your plugin’s distribution file using IntelliJ or using Gradle. Gradle is currently the only officially supported build system to build plugins. JetBrains’ gradle plugin allows you to build and test with one or more SDKs of your choice.

A template for a new plugin build by Gradle is available at github.com/jansorg/intellij-plugin-base.

Testing

Continuous integration

It’s easy to add if you use Gradle, GitHub and Travis. If you use another CI tool please consult its available documentation.

Travis
Checkout the intellij-plugin-base template for a Travis configuration. The Readme file contains a link to the build status badge.

Documentation

Official Dev Guide (jetbrains.org) Must-read
JetBrains’ Dev Guide is a must-read for anyone interested in plugin development. Make sure to work through the Tutorial, to look at the FAQ and to study the Architecture Reference Guide.
Build ranges explained
IntellJ’s build ranges are explained on this page.
Notes on Plugin development, by Terence Parr (github.com)
Terence Parr, the author of the famous ANTLR parser generator, published his notes on IntelliJ plugin development. He is, of course, mostly writing about custom language plugin development. He’s very thorough and it’s definitely worth reading!

Relevant projects

GrammarKit (github.com)
GrammarKit is a lexer and parser generator for IntelliJ plugins.

Community

Official Developer Forum (jetbrains.com)
The official forum is a bit hard to find, but nevertheless a good place to ask your questions and to search through the older threads to learn about plugin development.
Gitter channel (gitter.im)
A channel managed by the community. Ask your questions here, there are experienced plugin developers helping out.

Source code

IntelliJ community repository (github.com)
The official code repository of the IntelliJ Community edition. If you need to dig into IntelliJ’s source it’s worth to have a look. Beware, it’s huge: more 3 million lines of code according to SLOCCount. The public API is properly documented, some of the internal implementation classes are lacking in good documentation.
Plugins with their respective source code repositories (github.com)
A auto-generated list of all available plugins with their source code repositories. A great reference to learn from plugin which are similar to yours.
Plugin template with Gradle build (github.com)
A template to start with a new plugin which uses Gradle as build system.