Introduction
In this article you’ll learn how to implement code completion in your IntelliJ plugin.
Code completion is one of the most important features of your IDE. The IntelliJ help contains a lot of information about using it. But here, we’ll be looking at it from a developer’s point of view.
This article is about the extension point completion.contributor
and deals with:
- basic completion
- smart completion
- class name completion
It does not deal with:
- hippie completion – because it’s unrelated to the extension point we’re covering
- postfix completion – it’s more relevant than hippie completion, but still not covered by the extension point we’re discussing here
You’ll learn the most relevant points about the different types of completions. After that we’re going to implement word completion from a dictionary. This is what you’ll get:

Word completion from a dictionary
Completion types
There are several types of code completion: basic completion, smart completion, class name completion.
In code, these completion types are defined by the enum com.intellij.codeInsight.completion.CompletionType
.
Basic completion
The is the default completion type. It’s usually invoked by Ctrl + Space on Windows/Linux and ^ + Space on macOS.

Basic completion matches by name only
Smart completion
This is the alternative completion mode, usually invoked by Ctrl + Shift + Space on Windows/Linux and ⌃ + ⇧ + Space on macOS.
For Java code, smart completion displays only those items which match the current context.
For example, when you invoke it after a return
keyword, only variables and methods which are compatible with the return type are shown.
It’s your call if you implement smart completion and how smart it’s going to be. Usually, basic completion would return completions which are syntactically valid. Smart completion would filter these to remove the semantically invalid items.

Smart completion matches by name and expected type
Class name completion
Used for the completion of class names. Within the IntelliJ Community Edition, this is only relevant for Java code, as far as I can tell.
The only reference I could find in the sources is the
live-template function classNameComplete()
.
You won’t need to implement this, unless you want to provide additional data in this specific context.
Extension Point
As you know, plugins implement extension points to tell the IDE what they’re capable of.
The extension point for code completions is called completion.contributor
.
This is the declaration and all attributes it supports. Check out the next section to learn how to use these attributes – and by the way, IntelliJ 2019.2 includes nice code completion for the attribute values.
|
|
implementationClass
- A class implementing
com.intellij.codeInsight.completion.CompletionContributor
. Use thefactoryClass
if you want to choose the implementation at runtime. You have to provide a value for eitherimplementationClass
orfactoryClass
. factoryClass
- Class of a factory to return your implementation. It has to implement
com.intellij.openapi.extensions.ExtensionFactory
. You’ll rarely need this. Use it to choose one of several implementations at runtime. Or use it to configure the implementation you return. factoryArgument
(optional)- An optional
String
argument passed to thecreateInstance
method of your factory. language
(required)- This is the language for which your implementation provides the completions.
Most often it will provide completions that are only relevant for a certain file type, e.g.
Python
. You don’t want to show names of Python functions in Java code, for example. Use the value"any"
if you want to enable your implementation for all types of files. If you need to support multiple languages, then add the<completion.contributor … />
element multiple times. Use IntelliJ’s code completion to see the available choices for the value. order
(optional)- Contributors are called one after another.
Use this attribute to force your provider to be
first
,last
,before <id>
, orafter <id>
. You won’t need this most of the time. A use-case forfirst
is when you’d like to filter completions of other completion contributors. See Filtering completions. A use-case forbefore
andafter
is when you’d like to order two or more contributors of your plugin. id
(optional)- The ID of your completion contributor. These IDs are referenced by
before
andafter
of other completion contributors. os
(optional)- Enable your implementation only for the given operating system, e.g.
linux
. IntelliJ’s code completion tells you about the supported values.
Implementation
At this point you should decide a few things:
- which file types you want to support, i.e. the languages you’re supporting
- which completion types you want to support, i.e. basic and/or smart completion
- where completions should be shown in a file. You don’t want to suggest a class name where it’s not allowed, for example.
First, define a class which extends the base class of this extension point:
|
|
Now, you have a choice to make:
- You can override
fillCompletionVariants()
of yourCompletionContributor
. - Or you can implement a
CompletionProvider
and useextend()
in the constructor to tell IntelliJ which completion type(s) and context you’re supporting.
Both options are discussed in the subsections below.
How to implement fillCompletionVariants()
This is the generic approach.
IntelliJ’s implementation just iterates over the data which was added by extend()
.
If you don’t want to use extend()
, then override this method.
This is a bit easier to implement than a CompletionProvider
.
How to implement a CompletionProvider
This is the recommended approach. It’s a bit more complicated to understand and to debug because it is an abstraction of common use-cases of code completion.
extend
’s signature is:
|
|
type
- It defines which completion type you’re supporting. You’ll usually use
CompletionType.BASIC
orCompletionType.SMART
here. Passnull
to support all. place
- This defines the context which is supported by your provider. The SDK already comes with a lot of patterns you can use here. See below for more information on patterns and where to find them.
provider
- This is your implementation to provide completions specifically for the given type and context. If you’re using
extend()
multiple times, then each provider usually serves a subset of the possible completions.
For example, it could compute a list of all class names to show after theextends
keyword in Java. Another provider could compute a list of interface names to suggest after theimplements
keyword.
ElementPattern
Patterns are a bit hard to grasp at first. It always feels a bit over-engineered to me, but once you get it it’s a nice tool in your belt. But beware, getting used to it might take a while.
Let’s take a visual approach to understand how this thing works. Suppose you’d like to suggest a list of valid locale names in this code snippet:
|
|
A pattern starts with the PSI element at the text cursor’s position. The PSI structure of this code snippet looks like this:

PSI structure of Locale.forLanguageTag
We have to make sure that:
- we’re only completing a value in a string literal
- the literal is the first argument of
java.util.Locale#forLanguageTag
Now, a pattern which matches the innermost String
literal (highlighted in the illustration above), is
|
|
But this is matching all strings, not just the argument to Locale.forLanguageTag
.
We have to make this pattern more restrictive.
Let’s take another look at the PSI structure. The parent of the literal is a PsiLiteralExpression
. The
grandparent is a PsiExpressionList
. And the next ancestor finally is the PsiMethodCallExpression
we’re looking for.
You can use withParent(...)
on a PsiElementPattern
to check the hierarchy. You can add your own logic via with(...)
.
Chained calls of withParent()
all operate on the same element. Therefore we have to nest these calls.
With this knowledge we finally get this:
|
|
It’s still not good enough, though:
- It’s not checking that
Locale
isjava.util.Locale
. It still could be another class of the same name. - It’s not limiting the completion to the first argument.
After digging into the sources of the IntelliJ SDK, I was able to come up with this:
|
|
When I’m unable to get a pattern to work, then I usually add a call to with(...)
returning true
with a breakpoint.
If you’d like to quickly iterate on this, then add a test case and incrementally fix the pattern logic.
Common patterns in the SDK
There’s a bunch of patterns available in the JetBrains SDK:
com.intellij.patterns.StandardPatterns
- Very basic patterns. This offers the useful utilities
and()
,or()
, andnot()
. com.intellij.patterns.PlatformPatterns
- This offers PSI based patterns. Most useful are
psiElement()
andpsiFile
.
Don’t forget that the patterns returned by these methods provide chainable methods. For examplepsiElement().withParent(…)
,psiElement().inside(…)
and more. com.intellij.patterns.PsiJavaPatterns
- Most useful when you’re working with Java PSI. Before you write your own pattern for Java code, take a look at this class.
How to implement a test case
With a fixture-based test, testing completions isn’t difficult.
Here’s a simple test which tests basic completions:
|
|
Testing with automatic invocation
There’s a nice utility class CompletionAutoPopupTester
. It helps to test completions with the automatic
popup enabled.
Here’s a simple test which makes use of CompletionAutoPopupTester
:
|
|
Example: Dictionary completion
Finally, we’re ready to implement our plugin!
We’d like to complete words with items from a dictionary. IntelliJ comes with built-in dictionaries, so we’re reusing this data. Accessing this data isn’t straight-forward, though.
But let’s get started!
The result should look similar to this:

Word completion from a dictionary
For our plugin, we’d like to implement these features:
- automatic completion anywhere inside of plain text files
- automatic completion inside of string literals of any language
- manual completion at any place in any file
- support to complete upper-cased and lower-cased words, i.e. both
The
andthe
have to be supported - no completion immediately after whitespace. We always require a prefix for completion as we don’t want to add the whole dictionary to the popup.
The code is available at jansorg/intellij-code-completion on GitHub.
Update your plugin.xml
First, let’s add the extension point to the plugin.xml
file:
|
|
You now can use the quick-fix on StringLiteralDictionaryContributor
to create the missing class.
We’re using language=any
to enable it in all file types, as we want to support completions in string literals of any language.
Add a test case
Let’s do it “test-first” and start with a test case. We’ll extend this later on.
|
|
Our CompletionContributor
Let’s implement the completion contributor next.
We’re starting with completions in plain text files. So, we need to find out about the internal structure of a plain text file.
Create a text file and check out the PSI structure via Tools → View PSI Structure of Current File.
You’ll see that this type of file does not display a PSI structure. Go and dig into the IntelliJ Community Edition sources to
find out more about the plain text type. Finally, you’ll come across PlainTextParserDefinition
.
PlainTextParserDefinition
is always returning a single element which covers all of the content. Its type is PlainTextTokenTypes.PLAIN_TEXT
This means that you only need to check for this element type.
|
|
At this point you’ll have to create your DictionaryCompletionProvider
. We’re passing a flag to tell if it’s only
enabled in manual popup mode.
|
|
Accessing IntelliJ’s dictionaries
We want to access all built-in dictionaries.
After poking around in the sources of IntelliJ Community Edition, I finally encountered Dictionary.java. I’m not pasting the source here, because it’s not directly relevant. The source code is linked above.
The data is loaded at startup via a preloading activity. The implementation is available here: DictionaryLoader.java.
We want to add dictionary data to the completion result:
|
|
Complete any string literal
We’re not yet showing completions in string literals of any language.
We have to implement our own pattern, because there’s no suitable implementation in the SDK.
|
|
We’re registering this pattern with another call to extend()
:
|
|
Manual invocation
We’re still missing the last piece of the puzzle: manual invocation should always display completions, no matter where. This is easy to add: use a pattern which accepts all elements.
|
|
Source code
You’re done! Make sure that your tests are passing and play around with it in your IDE.
You can find the full set of sources on GitHub:
- Implementation
- Tests, with more test coverage
Common mistakes
Referencing originalFile
or its PsiElements
If you add originalFile
or any of its PSI elements to the result, then IntelliJ will display
invalid element
in the list of completions. This usually happens when the user modifies the file while
completions are displayed. Modifications invalidate PsiElements of your original file, which then become
unusable in the completion item you just added.
Advanced topics
DumbAware completions
By default, a completion contributor is not called when indexing is in progress.
Implement com.intellij.openapi.project.DumbAware
to provide completions during indexing. Of course, you must not access
indexes or stubs to provide completions while indexing is in progress.
Completions based on the number of invocations
Code completion can be invoked multiple times in a row. The number of invocations is passed to the extension of your plugin. This means that you’re able to return more or different results for the 2nd or 3rd invocation, for example.
The number of invocations is provided by CompletionParameters.getInvocationCount()
.
Hiding other completions
You can use the method stopHere()
of your CompletionResultSet
to exclude all following contributors. Don’t do this unless you have very good reasons.
You wouldn’t like it either, if other plugins disabled completions of your own plugin.
|
|
If you only want to show your own completions, then you typically want to run your contributor first:
|
|
Filtering other completions
You can use CompletionResultSet.runRemainingContributors(...)
to filter the completions of the remaining contributors.
Set order="first"
if you want to filter the results of all other contributors.
But this is rude. Always be nice and kind to other plugins in your runtime space 😉
Handle automatic invocation
Code completion is either automatically invoked while typing or manually by the user.
Use CompletionParameters.isAutoPopup()
to find out it was invoked automatically.
Interruptable completions
Completions are wrapped with com.intellij.openapi.progress.ProgressManager
.
If your completion contributor is doing a lot of work, then make sure to call
ProgressManager.checkCanceled
.
For example, when the user continues to type while completions are shown, the progress will be cancelled.
This method throws an exception when completions are cancelled. This exception is then caught by ProgressManager
, so you
don’t need to worry about it.
Custom presentation of items
Use LookupItem
to customize the appearance.
The easiest way to create a LookupItem
is LookupElementBuilder
. It provides chainable methods to configure
the different aspects of a LookupElement
.
This is what you can do:
|
|
This will be rendered like this:

Resources on code completion
A few more articles on code completion:
- Writing a Completion Contributor: Documentation from the IntelliJ Platform SDK DevGuide.
- CompletionContributor.java: FAQ about writing a completion contributor in the IntelliJ Community Edition sources.
Use of patterns in the IntellJ Community Edition SDK:
More on code completion in the SDK: