I'm developing a custom language plugin for IntelliJ and I want to add code completion support using the CompletionContributor. The language I'm developing support for IntelliJ uses OOP, and provides the ability to use typical classes (class
) and namespaces (namespace
).
At the moment, everything is extremely clear except for one thing. I can’t understand how to call specific completion provider only at the highest level of the file scope. Below I give an example to show clearly the place where autocomplete is needed at the moment (pseudocode):
1. namespace Foo;
2.
3. class Test {
4.
5. }
6.
7. function foo() {
8.
9. }
In the above example, the completion provider should be used only on lines 1 and 2 (class scope), partially on line 3 (up to curly brace), as well as on line 6. In short, completion provider shouldn't be invoked for line 4 and 8.
Please note the file may be empty:
1.
2.
In this case, the code completion should work too.
Bellow is the boilerplate code to achieve this (Kotlin).
Contributor:
// com.some.lang.core.completion.MyCompletionContributor
package com.some.lang.core.completion
import com.intellij.codeInsight.completion.CompletionContributor
import com.some.lang.core.completion.providers.FileScopeCompletionProvider
class MyCompletionContributor : CompletionContributor() {
private val providers = listOf(
FileScopeCompletionProvider
)
init {
providers.forEach { extend(it) }
}
private fun extend(provider: MyCompletionProvider) {
extend(provider.type, provider.context, provider)
}
}
Abstract Provider:
// package com.some.lang.core.completion.MyCompletionProvider
package com.some.lang.core.completion
import com.intellij.codeInsight.completion.CompletionParameters
import com.intellij.codeInsight.completion.CompletionProvider
import com.intellij.codeInsight.completion.CompletionType
import com.intellij.patterns.ElementPattern
import com.intellij.psi.PsiElement
abstract class MyCompletionProvider : CompletionProvider<CompletionParameters>() {
abstract val context: ElementPattern<out PsiElement>
open val type: CompletionType = CompletionType.BASIC
}
File Scope Provider:
// package com.some.lang.core.completion.providers.FileScopeCompletionProvider
package com.some.lang.core.completion.providers
import com.intellij.codeInsight.completion.CompletionParameters
import com.intellij.codeInsight.completion.CompletionResultSet
import com.intellij.codeInsight.lookup.LookupElementBuilder
import com.intellij.patterns.ElementPattern
import com.intellij.patterns.PlatformPatterns
import com.intellij.psi.PsiElement
import com.intellij.util.ProcessingContext
import com.some.lang.core.Language
import com.some.lang.core.completion.MyCompletionProvider
object FileScopeCompletionProvider : MyCompletionProvider() {
override val context: ElementPattern<PsiElement>
get() = PlatformPatterns.psiElement().withLanguage(Language)
override fun addCompletions(
parameters: CompletionParameters,
processingContext: ProcessingContext,
result: CompletionResultSet
) {
result.addElement(LookupElementBuilder.create("Hello"))
}
}
Of course, this code doesn't do what is needed. However, it does show the general design that I use. I am sure that I need to fix the following lines:
override val context: ElementPattern<PsiElement>
get() = PlatformPatterns.psiElement().withLanguage(Language)
And the main question is that I don’t understand how to do it.
Update:
Relevant BNF part:
{
psiClassPrefix='My'
// ...
}
File ::= TopStatement*
private TopStatement ::= NamespaceStatement (ClassDefinition | InterfaceDefinition)
NamespaceStatement ::= 'namespace' ComplexId ';' {pin=2}
ClassDefinition ::= ClassModifier? 'class' Id SuperClass? ImplementsList? ClassBody {pin=3}
// ...
You can use with
to add your own PatternCondition
to your element patterns.
Assuming you have an isTopLevel
function defined something like this:
fun isTopLevel(elem: PsiElement): Boolean = elem.parent is MyLanguageFile
You can use this ElementPattern
to make your completion only available to top-level elements.
val context = PlatformPatterns
.psiElement()
.with(object : PatternCondition<PsiElement>("toplevel") {
override fun accepts(elem: PsiElement, context: ProcessingContext?) = isTopLevel(elem)
})
Edit: You can also use withElementType
to control which element types the completion will apply to. For example:
context = psiElement()
.andOr(
psiElement().withElementType(NAMESPACE_NAME),
psiElement().withElementType(CLASS_NAME),
//other top level stuff
)