AI-generated image
Development

How to Add a Custom Language to Your JetBrains IDE

2 min read

I needed Freemarker support in IntelliJ and it did not exist. The built-in highlighting was wrong half the time, there was no completion, and navigating between templates was guessing. So I wrote a plugin. That was plugin number one. Twenty-one more followed.

To add a custom language to a JetBrains IDE you need four things: a lexer, a parser, a PSI tree, and at least one useful feature on top. Everything else is optional but makes the plugin worth paying for.

Start with BNF

JetBrains provides Grammar-Kit, a BNF-based parser generator that produces the PSI tree structure. You write your language grammar in a .bnf file, Grammar-Kit generates the parser and the element types. The generated code is verbose but correct.

The lexer is a JFlex file. It tokenizes your source into keywords, identifiers, strings, comments. Grammar-Kit consumes those tokens and builds the tree. This part takes about 80% of the initial effort - getting the grammar right for a language you did not design is harder than it sounds.

For Flexible Freemarker, I had to handle a template language embedded in HTML. Two grammars, interleaved. That was the moment I realized this infrastructure would need to be reusable.

PSI is your foundation

The PSI (Program Structure Interface) tree is the abstract syntax tree that IntelliJ uses for everything. Every feature - completion, navigation, refactoring, inspections - reads from this tree. If your PSI structure is wrong, every feature on top of it is wrong too.

Get PSI right first. Add features second. I made the mistake of building completion before the tree was stable and rewrote it three times.

What makes a plugin useful

Syntax highlighting gets you downloads. Code completion gets you paying users. In my experience the feature priority is: highlighting, completion, navigation (go-to-definition, find-usages), then inspections (warnings and quick-fixes).

Inspections are where it gets interesting. For Flexible Solidity I wrote reentrancy-guard inspections that catch a class of smart-contract bugs at edit time. That is the kind of thing a generic linter cannot do because it needs the full PSI tree.

What nobody tells you upfront

The IntelliJ Platform API is powerful but maybe 30% documented. You will spend more time reading the IntelliJ Community Edition source code than reading docs. JetBrains is getting better at this, slowly. But “read the source” is still the real documentation for anything beyond the basics.

If you are thinking about it - start with a language you use yourself. The first plugin is three months. The second one is three weeks. After that it is a weekend per language, if your infrastructure is solid. I wrote about how that shared codebase works.