为文件顶层实现 IntelliJ Completion Contributor

Implementing IntelliJ Completion Contributor for the file top level

我正在开发 custom language plugin for IntelliJ and I want to add code completion support using the CompletionContributor。我正在开发支持 IntelliJ 的语言使用 OOP,并提供使用典型 classes (class) 和命名空间 (namespace).

的能力

此刻,除了一件事,一切都无比清晰。我无法理解如何在文件范围的最高级别调用特定的完成提供程序 only。下面我举个例子来说明一下目前需要自动补全的地方(伪代码):

1. namespace Foo;
2.
3. class Test {
4. 
5. }
6.
7. function foo() {
8. 
9. }

在上面的示例中,完成提供程序应该 仅在第 1 行和第 2 行(class 范围) 上使用,部分在第 3 行上使用(直到花括号),以及第 6 行。简而言之,不应为第 4 行和第 8 行调用完成提供程序

请注意文件可能为空:

1.
2.

在这种情况下,代码补全 应该也可以工作

Bellow 是实现此目的的样板代码 (Kotlin)。

贡献者:

// 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)
    }
}

抽象提供者:

// 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
}

文件范围提供者:

// 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"))
    }
}

当然,这段代码并不能满足需要。但是,它确实显示了我使用的一般设计。我确定我需要修复以下几行:

    override val context: ElementPattern<PsiElement>
        get() = PlatformPatterns.psiElement().withLanguage(Language)

而且主要问题是我不明白怎么做。

更新:

相关 BNF 部分:

{
    psiClassPrefix='My'

    // ...
}

File ::= TopStatement*

private TopStatement ::= NamespaceStatement (ClassDefinition | InterfaceDefinition)

NamespaceStatement ::= 'namespace' ComplexId ';' {pin=2}

ClassDefinition ::= ClassModifier? 'class' Id SuperClass? ImplementsList? ClassBody {pin=3}

// ...

您可以使用 with 将您自己的 PatternCondition 添加到您的元素模式中。

假设您有一个 isTopLevel 函数定义如下:

fun isTopLevel(elem: PsiElement): Boolean = elem.parent is MyLanguageFile

您可以使用此 ElementPattern 使您的完成仅对顶级元素可用。

val context = PlatformPatterns
  .psiElement()
  .with(object : PatternCondition<PsiElement>("toplevel") {
    override fun accepts(elem: PsiElement, context: ProcessingContext?) = isTopLevel(elem)
  })

编辑:您还可以使用 withElementType 来控制补全将应用于哪些元素类型。例如:

context = psiElement()
  .andOr(
    psiElement().withElementType(NAMESPACE_NAME),
    psiElement().withElementType(CLASS_NAME),
    //other top level stuff
  )