In my humble opinion, IntelliJ is the best Java IDE out there. But even the best IDE can be improved. In this blog I will tell you why I decided to create a plugin and how this went down.
Let’s backtrack a few steps to understand why I decided to write a plugin. I was preparing to give a talk at Devoxx France. It is a live coding session that shows off features of the debugger. Every time when I prepare to give a talk, I try to update the talk. This time I decided to use the new UI of IntelliJ. No sooner said than done I applied the change and started practicing. During the first test run I noticed that there was a bug in the new UI. In the old UI I could shift click in the gutter to get a logging breakpoint. This did not work in the new UI. Furthermore I was looking for a solution to get a logging breakpoint without the use of the mouse. I was not able to find a solution for this problem. Therefore I decided to kill two birds with one stone. I decided to create a plugin that solves both problems.
So my first thought was to just create a plugin, because how difficult can that be? Those were famous last words, because it turned out to be much harder than I expected. My initial idea was to create a “proper” plugin. That is a plugin that can easily be installed by others by downloading it from the Jetbrains Marketplace. Within an hour it was clear that creating a proper plugin is hard and takes a lot of time. Luckily IntelliJ offers a simpler solution in the form of a live plugin. You can directly test your plugin in the IDE without complicated steps. This plugin does not end up in the marketplace, but is far simpler to develop. Far simpler does not mean it is simple. It still took several evenings to get it to work properly.
So the goal is to create a live plugin that will create a logging breakpoint without using the mouse. The latter part is easily solved by binding the action of the plugin to a keyboard shortcut. Creating a logging breakpoint in code is a bit harder, but before we dive into that let’s explain what a logging breakpoint is and how you create one manually. A logging breakpoint is a breakpoint that only logs something and does not suspend. Both these breakpoint options (logging and non-suspending) are relatively unknown. But you can easily create a logging breakpoint by first creating a simple line breakpoint and then changing its properties. You have to make the breakpoint non-suspending (so it should not pause the execution of your program) and it should have a logging expression. The screenshot below shows how this would look like in IntelliJ.
Picture 1 - A logging breakpoint in IntelliJ
So doing it manually is pretty straightforward and now it is time to do this with a live plugin. The first step is to install the live-plugin plugin from the marketplace. This enables the live plugin functionality. The second step is to write the live plugin itself. You can choose between Groovy or Kotlin when creating one. I decided to go with the latter, because I am more familiar with that language.
After looking at some example plugins I decided it was time to start writing my own. I broke the problem down in the following sub-problems:
Let’s start with the first, showing a notification when pressing a keyboard shortcut. I copied an existing plugin from a tutorial that did exactly this and started modifying it. The only modification that it needed was changing the keyboard shortcut. This was relatively easy and within fifteen minutes I had this sub-problem solved. On to the next one!
The goal here is to set a simple breakpoint when pressing the right key combination. My plugin already reacted to the correct key combination, so I only have to create a breakpoint. This is where it became pretty complicated. I had no clue which API to use. So I spend several hours browsing through the IntelliJ API documentation to get a vague direction. Then I browsed a lot of classes in the com.intellij.xdebugger package to find the first glimmers of hope. I found the class XDebuggerUtilImpl and it had a method called toggleAndReturnLineBreakpoint. This seems like the right place to start! Unfortunately the method took six parameters. I will not bore you with all the details, but let’s focus on one. The method needs a list of breakpoint types. This sounds logical, because it needs to know the possible types of breakpoints it can set. But the type of parameter looks like this: List<XLineBreakpointType<P extends XBreakpointProperties> extends XBreakpointType<XLineBreakpoint<P>, P>>. Okay, this is not for the faint of heart. Several hours later I got the hang of it and I was able to create a simple breakpoint by pressing the right keyboard shortcut. Hooray!
The next step is to modify this breakpoint so it will not suspend the execution of the program and it will log something. This part was pretty straightforward. The methods mentioned above returned a promise with a breakpoint in there. After using two simple setters the simple breakpoint became a logging breakpoint. The sweet smell of victory is almost mine!
Next up is using the current selection to determine what will be logged. The idea is that the user selects a variable and the breakpoint will log the name and the value of the variable. It should also work when selecting an expression. In that case it will print the expression and its outcome. It is pretty simple to get the selected text and to use this to create the desired logline. Only one subproblem remaining!
The last one is putting the breakpoint on the next line. This needs a little bit of explaining, because initially I thought that putting the breakpoint on the same line would be fine. It turns out that this does not work well when creating a variable. When you set a breakpoint it will pause the execution at the beginning of the line, so before it creates the variable. If the variable does not exist yet, it is impossible to read and log its value. Therefore I decided to put the breakpoint on the next line. This turned out to be too simplistic, because the next line could be a whiteline and those do not support breakpoints. The final solution is to put the breakpoint on the next line that supports breakpoints. I learned a lot about caret models, cursor offsets and source positions, but several hours later I had a working prototype!
So with this plugin I do not need to use the mouse when creating a logging breakpoint. Furthermore I can quickly create logging breakpoints while using the new UI. Just selecting a variable and pressing the right keys on the board is enough.
The final result can be found on Github: https://github.com/BoukeNijhuis/devoxxfr2023/blob/main/.live-plugins/plugin.kts. The file plugin.kts is in the .live-plugins folder so the live-plugin plugin will automatically load the plugin whenever the project is opened in IntelliJ.
So after some evenings of writing a live plugin it is time to create a “proper” plugin which can be easily installed from the JetBrains marketplace. But this is a story for another time. Once this time arrives I will write another blog about it.