Writing my first Intellij plugin

As of today, I am happy to announce the first version of my first plugin is available for free to download. The Java Builder Guided Completion Intellij plugin has its home on Github, as an open-source project, and you can visit it here for more info. Alternatively, you can download directly from the JetBrains marketplace by clicking here or just search for it in the Intellij IDE.

Due to work and family obligations, this took more time than expected, but still, quite a big chunk of time was spent on figuring out how some things work in the Intellij platform and how to set up things correctly, rather than actual development. Because of this, I decided to write this article where I will give you some tips that would have helped me big time if I knew them beforehand.

I will follow up this article with part 2 where I will explain more the implementation details of the plugin I developed. I am keeping it separate from this one since that will mostly contain information specific to the project, while in this one I will be presenting more general information.

Solving a problem

Firstly, if you are planning on writing a plugin(or any piece of software actually) you should be solving a problem, either one that does not have any solutions yet or one that has bad solutions. In the following I will be describing the problem I was trying to solve, then I will continue with some technical advice on developing an Intellij plugin that will most likely save you a lot of precious time and make you avoid frustrations that might persuade you to give up on your idea altogether.

Builders

If you are coming from the Java or C# world(and not only) I assume you are familiar with the Builder pattern.

It always irked me the wrong way that, while being a lot better than the alternative telescopic constructor, the builder pattern construct is not very friendly to the users of the API exposing it. Using the Builder class in my IDE, I do not know what parameters are absolutely required for successfully building the target object, which ones are optional, and maybe which ones are mutually incompatible. Of course, I can figure it out at runtime, but that would result in a lot of trial and error attempts. Another way of finding this kind of info is by reading the documentation, but many times there is no documentation and still, it is quite inconvenient.

Solutions

My first attempt at solving this problem was to define a series of Builder interfaces that would all be implemented by the Builder class. Each interface would define a mandatory method that would return the Builder object as the next interface in the chain. I found out I was not the only one to come up with this concept and it actually has a name: the Step Builder Pattern. Here you can read more details about it and see some examples.

In practice, it turns out that the Step Builder pattern is not ideal as this post explains. Essentially it makes things quite inflexible and there are a lot of corner cases that cannot be easily tackled with this approach.

The previously mentioned post actually was the inspiration for my final solution. I decided to go on with their suggestion and implement a plugin for Intellij that would mostly meet the requirements described by the Lombok developer. Actually part of the roadmap is to integrate with the Lombok plugin. For now bellow there is an example on what the plugin does.

Guided Completion Example

Technical Tips

Now let us get started with some Intellij platform quirks that you should know in order to save time and focus on your actual development.

1. Extension Points Help

Extension Points are quite a central concept in Intellij plugin development. They basically allow you to modify the behavior of the IDE in quite sophisticated ways. The list is quite long and most of the time you have no idea what you are supposed to do to make stuff work with an extension point.
I discovered it quite late, but luckily there is a tool that JetBrains has provided that allows anyone to search for a particular Extension Point and it will return a list of open-source plugins that have implemented it, and even more, it will point you to the specific file in the repository of the project containing the implementation.
You can access it here. My recommendations are:

  1. get from here a list of all extension points
  2. search for the one that you think will probably help you to accomplish your objective
  3. look at the related platform class which may have some documentation
  4. look for other plugins implementing it since that will most likely answer a lot of the questions you have.

2. Enable Java functionality

My plugin is in Java developed for Java. I had a lot of headaches when looking at some code examples that I wanted to use in my plugin and when trying them out they did not work at all. Eventually, I discovered that the Java functionality has been extracted out from the core Intellij platform. In order to access that functionality you need to do the following:

  • declare in your plugin.xml file the following:
<depends>com.intellij.modules.java</depends>
  • if you are using the gradle-intellij-plugin(as you should) then you also need to tell gradle about this dependency:
intellij {
  plugins = ['com.intellij.java']
  // ...
}

The above is for groovy based graddle. For kotlin based graddle use:

intellij {
  setPlugins("java")
  // ...
}

PsiViewer

In the Intellij platform, the contents of a file are structured under a construct called the PSI(Program Structure Interface) Tree. For your plugin you might need to interact with this PSI tree either to read its contents or even modify it.
A very helpful tool that will allow you to experiment and discover how these trees and their elements look like is the PsiViewer plugin. It lets you have a live view of the tree structure of a file you have open.

Here there is also a small quirk that I had to figure out. After you install the plugin, in order to have it available you need to set in the bin/idea.properties file the following and then restart the IDE:

idea.is.internal=true

Debugging

For sure you will want to debug your plugin in order to solve some issue or just to discover how things work internally in Intellij.
First, you should be happy to know that you do not need to create any special Intellij configuration or task, assuming you are using the gradle-intellij-plugin. If you check your gradle tasks under the Intellij directory you have the runIde task. Executing this task will launch a new Intellij session containing any modification you did in your project(so including whatever your plugin implemented). If you want to debug you need to execute the task with Debug instead of Run.

If you try to do this as-is, you will probably encounter quite fast when debugging a ProcessCanceledException which will result in you unable to continue with your debugging session. Had to dig a little on how to get rid of it but in the end, I found out I need to basically start the test IDE with the following two options

  • -Didea.auto.reload.plugins=false
  • -Didea.ProcessCanceledException=disabled

The way I managed to do this was with the following configuration in my kotlin based build.gradle:

 withType<org.jetbrains.intellij.tasks.RunIdeTask> {
        jvmArgs("-Didea.auto.reload.plugins=false")
        jvmArgs("-Didea.ProcessCanceledException=disabled")
    }

To be continued

This was a fun experience for me and will try to improve on the current version of the plugin so please try it out and give any feedback. Continue to follow this blog if you want to know more about the actual plugin implementation in my next post.