过去的几个月里,在设计和开发 Fklang 时,也添加了对应的 IDE 支持。由于 Intellij IDEA 插件的开发复杂,无法用文档详细描述,只能通过代码尝试。写一篇文章来记录一下这个痛苦的过程,也成为了其中的一个选择。考虑到这些只是基础的功能,所以只介绍一下:
至于其它内容,等以后有机会再写。
对了实现对于编程语言的高亮等的支持,需要使用 IDE 提供的 SDK,再次构建语言的语法树。之所以说是 “再次” 是因为在编写语言编译器时,我们已经写过一次了。与再次构建语法树相比,编写 LSP(语言服务器)也是一个选择。考虑到 LSP 的实现比较复杂,可参考的代码也比较少,所以还是采用了 JetBrains 官方推荐的方式 Grammar Kit 来开发
Grammar-Kit,一个面向语言插件开发人员的 IntelliJ IDEA 插件。并添加 BNF 语法和 JFlex 文件编辑支持,以及解析器、PSI 代码生成器。
总的来说,在编写并不复杂,也包含了对应的 PSI Viewer 插件可以 debug,还有诸如于 Erlang、Rust、D 语言等代码可以参考。不过呢,还是存在一定的差异,诸如于 private
意味着不需要生成 AST 节点,还可以通过 implements
、mixin
对 AST 节点扩展。如下代码所示:
dep_source ::= dep_node
private dep_target ::= dep_node
dep_node ::= STRING_LITERAL | IDENTIFIER
{
implements = [
"com.feakin.intellij.psi.ext.FkMandatoryReferenceElement"
]
mixin = "com.feakin.intellij.stubs.ext.misc.FkDepNodeMixin"
stubClass = "com.feakin.intellij.stubs.FkDepNodeStub"
elementTypeFactory = "com.feakin.intellij.stubs.StubImplementationsKt.factory"
}
只是简单的语法高亮等功能,只需要定义好 AST 节点即可(非 private
,诸如于 dep_source
、dep_node
等),而诸如于 stubClass
等则是在开始其它功能使用。有了语法解析之后,就可以做更多的功能:
syntaxHighlighter
)其依赖于不同的 ElementTypes
(如 dep_node
会生成 DEP_NODE
节点)来配置颜色:ATTRIBUTES[FkElementTypes.IDENTIFIER] = FkColors.IDENTIFIER.textAttributesKey
,也可以自己定义一些颜色。psiStructureViewFactory
)也是依赖于不同的 ElementTypes
后续也是通过 AST 节点来进行交互。
Fklang 作为一个语言,也是提供了基本的编译等的支持。在有了基本的高亮支持后,就需要与 SDK 进行交互了,也就是:
runLineMarkerContributor
,配置 IDE 上的 Icon 显示,并与 IDE 进行交互runConfigurationProducer
,生成对应的配置programRunner
,执行语言 SDK 的 runner,诸如于在 IDEA 中的 JUnit 也使用了类似的方式配置的。
这里的跳转指的是:跳转到定义(Goto…)与查找调用处(Find Usages),也因此我们需要实现两个不同的功能。但是,对于这两个功能而言,方式都是相似的,即遍历 AST 节点找到对应的子节点。跳转到定义(Goto…)相对比较简单,不需要考虑缓存等因素。与此同时,诸如于 class
的定义比较少,所以可以通过 StubClass
来进行缓存,以提升性能。
起先,我以为 reference 是通过 plugin.xml
配置的,后来发现可以在 Grammar Kit 中的 mixin 结合 PsiPolyVariantReference
配置。如下所示:
// 定义处
contextDeclaration ::= CONTEXT_KEYWORD IDENTIFIER contextBody
{
implements = [
"com.feakin.intellij.psi.FkNamedElement"
"com.feakin.intellij.psi.FkNameIdentifierOwner"
"com.feakin.intellij.psi.ext.FkMandatoryReferenceElement"
]
mixin = "com.feakin.intellij.stubs.ext.FkContextDeclarationImplMixin"
stubClass = "com.feakin.intellij.stubs.FkContextDeclarationStub"
elementTypeFactory = "com.feakin.intellij.stubs.StubImplementationsKt.factory"
}
// 调用处
useContextName ::= IDENTIFIER
{
implements = [
"com.feakin.intellij.psi.FkUseElement"
"com.feakin.intellij.psi.ext.FkMandatoryReferenceElement"
]
mixin = "com.feakin.intellij.stubs.ext.FkUseContextNameImplMixin"
}
我们需要在 FkContextDeclarationImplMixin
和 FkUseContextNameImplMixin
中配置 getReference,如下:
abstract class FkUseContextNameImplMixin(node: ASTNode) : FkElementImpl(node), FkUseContextName {
...
override fun getReference(): FkReference = FkUseContextNameReferenceImpl(this)
}
稍有不同的是在 contextDeclaration
里,我们可以定义 stubClass
来配置缓存,随后在 FkUseContextNameImplMixin
中通过 FkNamedElementIndex
来查找。
还有诸如编辑配置、智能感知等功能也挺有意思的,但是并不是必需的功能。
相关的参考资源:
围观我的Github Idea墙, 也许,你会遇到心仪的项目