跳转至

自定义语言支持

IntelliJ Platform 的语言能力由平台通用部分和语言特定部分共同组成。为一门新语言开发插件时,你主要实现语言特定部分,平台会提供编辑器、项目模型、检查框架、补全框架、导航框架等基础设施。

开发顺序

一个自定义语言插件通常按这个顺序推进:

  1. 注册文件类型。
  2. 实现 Lexer。
  3. 实现 Parser 与 PSI。
  4. 支持语法高亮与错误高亮。
  5. 实现引用解析与导航。
  6. 实现补全。
  7. 实现格式化、查找用法、重命名、安全删除。
  8. 增加检查、意图动作、文档、结构视图、Inlay Hint 等编辑器体验。

不要一开始就做所有 IDE 能力。先让文件被识别、能解析、能定位 PSI,再逐步叠加功能。

注册文件类型

文件类型让 IDE 知道哪些文件属于你的语言:

<extensions defaultExtensionNs="com.intellij">
  <fileType
      name="Example"
      language="Example"
      implementationClass="com.example.ExampleFileType"
      fieldName="INSTANCE"
      extensions="example"/>
</extensions>

文件类型类通常包含名称、描述、默认扩展名和图标。

Lexer、Parser 与 PSI

语言插件的核心是把文本变成结构:

  • Lexer:把字符流拆成 token。
  • Parser:把 token 组织成语法树。
  • PSI:在语法树之上提供语义化 API。

简单语言可以使用 Grammar-Kit 生成 Parser 和 PSI 样板。复杂语言可能需要手写部分解析逻辑或桥接已有解析器。

引用解析

引用解析决定 IDE 能否从使用位置跳到声明位置:

class ExampleReference(element: PsiElement, range: TextRange) :
    PsiReferenceBase<PsiElement>(element, range) {

    override fun resolve(): PsiElement? {
        return findDeclaration(element.project, value)
    }

    override fun getVariants(): Array<Any> {
        return collectDeclarations(element.project).toTypedArray()
    }
}

resolve() 支持跳转和重命名基础能力;getVariants() 可为基础补全提供候选项。

补全与导航

基础补全可以来自 Reference,复杂补全使用 CompletionContributor。导航能力通常围绕:

  • PsiNamedElement
  • PsiReference
  • ChooseByNameContributor
  • GotoDeclarationHandler
  • Stub Index

当项目变大后,不要全项目遍历 PSI 查声明,应通过索引查找。

重构支持

最常见的重构能力:

  • Rename:声明和引用都要正确更新。
  • Find Usages:引用查找结果要准确。
  • Safe Delete:删除前检查是否仍被引用。

这些能力通常建立在稳定的 PSI、Reference 和索引之上。

编辑器体验

可逐步补充:

  • Syntax Highlighting:词法级颜色。
  • Annotator / ExternalAnnotator:语义级高亮。
  • Formatter:格式化和缩进。
  • DocumentationProvider:悬浮文档。
  • StructureViewBuilder:结构视图。
  • ParameterInfoHandler:参数提示。
  • InlayHintsProvider:内嵌提示。

与 LSP 的关系

如果已有成熟 Language Server,可以考虑接入 LSP;如果你需要深度集成 IntelliJ 的重构、索引、项目模型、检查和 UI,则原生语言插件通常更可控。

参考来源