跳转至

Code Insight 细节专题:Inlay、Documentation、Intentions 与 Templates

代码补全、检查与 Quick Fix 覆盖了 Code Insight 的主入口。本章补齐官方目录中更细的能力:Inlay Hints、Documentation、Intentions、Inspection Options、Live Templates 和 Postfix Templates。它们共同决定插件是否像 IDE 原生能力,而不是只在编辑器里显示一条 warning。

核对日期:2026-06-18。

能力选择

目标 首选能力 适合场景
在代码旁显示轻量信息 Inlay Hints / Code Vision 参数名、推断类型、引用数、作者、指标
展示符号说明 Documentation Target API Quick Documentation、hover doc、文档工具窗口
提供上下文操作 Intention Action 光标处可触发的代码转换或辅助动作
报告代码问题并修复 Inspection + Quick Fix 可持续分析、可配置 severity 和 scope
插入重复代码模式 Live Templates 用户主动输入缩写或 Surround With
基于表达式后缀展开 Postfix Templates expr.ifexpr.varexpr.notnull 一类代码生成

不要把所有编辑器增强都做成 inspection。Inspection 会进入检查配置、批量分析和 problem view;只在光标处出现的动作更适合 intention;只提供视觉提示的能力更适合 inlay 或 code vision;纯代码片段生成更适合 templates。

Inlay Hints

Inlay hints 是编辑器中的轻量内嵌信息。官方按展示方式分为:

  • inline:显示在代码 token 之间。
  • block:显示在代码块上方或行末,常用于 Code Vision。

常见 API 选择:

API 状态与用途
InlayParameterHintsProvider 简单参数名提示,只能展示字符串 inline hint
Declarative InlayHintsProvider 2023.1+ 推荐的 inline hint API,UI 无关,适配不同 frontend
DaemonBoundCodeVisionProvider Code Vision 与 PSI 相关时使用,PSI 变化后自动重新计算
CodeVisionProvider Code Vision 与 PSI 无关时使用,例如 VCS 或外部指标
Legacy InlayHintsProvider 需要自定义 presentation/behavior 时使用;inline 场景优先考虑 declarative API

Declarative inlay provider 注册思路:

<extensions defaultExtensionNs="com.intellij">
  <codeInsight.declarativeInlayProvider
      language="JAVA"
      providerId="example.implicit.types"
      implementationClass="com.example.ExampleInlayProvider"/>
</extensions>

预览文件必须放在资源目录:

src/main/resources/inlayProviders/example.implicit.types/preview.java

其中目录名要和 providerId 一致。预览里的提示用官方 markup:

var value /*<# String #>*/ = "text";

实现原则:

  • Inlay 内容必须短,不替代 Quick Documentation。
  • 不在 provider 中做昂贵全项目搜索;需要引用数这类指标时使用 Code Vision,并做好缓存/取消。
  • 对 PSI 相关 Code Vision 使用 DaemonBoundCodeVisionProvider
  • Settings | Editor | Inlay Hints 中要有可理解的名称、说明和 preview。
  • 用 UI Inspector 检查已有 inlay 的 provider 和设置项。

Documentation Target API

自 IntelliJ Platform 2023.1 起,官方推荐使用 Documentation Target API。旧 DocumentationProvider API 已被标为 deprecated,但自定义语言教程仍保留旧 API 以兼容迁移。

三类入口:

EP 用途
DocumentationTargetProvider 根据当前 editor offset 和 PsiFile 返回文档目标
PsiDocumentationTargetProvider 根据 PSI element 返回文档目标
SymbolDocumentationTargetProvider 根据 Symbol API 返回文档目标

核心对象是 DocumentationTarget

  • computeDocumentation() 返回 HTML 文档,可异步。
  • computeDocumentationHint() 返回 hover 或 quick navigate 的短提示。
  • createPointer() 用于跨 read action 恢复目标;PSI 失效时应返回 null

旧 API 对照:

DocumentationProvider 新 API 思路
generateDoc() DocumentationTarget.computeDocumentation()
getQuickNavigateInfo() computeDocumentationHint()
getCustomDocumentationElement() Target provider 中选择正确目标
generateHoverDoc() target 的 hint/doc 分流
getDocumentationElementForLookupItem() completion lookup item 应能映射到 PSI/Symbol target

注册旧 API 时常见形式:

<extensions defaultExtensionNs="com.intellij">
  <lang.documentationProvider
      language="Example"
      implementationClass="com.example.ExampleDocumentationProvider"/>
</extensions>

若要跨语言提供文档,例如在 Java string literal 里解析自定义 key,需要使用更通用的 com.intellij.documentationProvider,并在 getCustomDocumentationElement() 或新 target provider 中自行解析引用。

文档内容建议:

  • 第一屏展示类型/签名/定义位置。
  • 详细说明和示例放在 content/sections。
  • 外部文档 URL 可以提供,但不要让网络失败影响本地 Quick Documentation。
  • HTML 要简洁,避免依赖复杂脚本或外部资源。
  • 大文档或远程文档使用异步 DocumentationResult

Intentions 与 Preview

Intention 是用户按 Alt+Enter 时出现的上下文动作。它和 Quick Fix 的差别在于:Intention 不一定来自问题报告,也不一定表示代码有错。

典型实现:

class ConvertExampleIntention : PsiElementBaseIntentionAction() {
    override fun getFamilyName(): String = "Convert example"
    override fun getText(): String = "Convert example syntax"

    override fun isAvailable(project: Project, editor: Editor?, element: PsiElement): Boolean {
        return findTarget(element) != null
    }

    override fun invoke(project: Project, editor: Editor?, element: PsiElement) {
        val target = findTarget(element) ?: return
        WriteCommandAction.runWriteCommandAction(project) {
            // Replace target PSI with new PSI.
        }
    }
}

资源文件约定:

src/main/resources/intentionDescriptions/ConvertExampleIntention/description.html
src/main/resources/intentionDescriptions/ConvertExampleIntention/before.example.template
src/main/resources/intentionDescriptions/ConvertExampleIntention/after.example.template

如果类名会被混淆,或多模块打包可能造成资源重名,应在 plugin.xml<intentionAction> 中声明 descriptionDirectoryName。不需要 before/after preview 时可设置 skipBeforeAfter

Preview 要点:

  • 平台会在非物理文件副本和 headless editor 中尝试执行 intention/quick fix,生成 diff。
  • 默认 preview 适合只修改当前文件、无需用户输入、无外部副作用的动作。
  • 如果修改其他文件、弹出 UI、启动外部进程或依赖真实 editor,应 override generatePreview()
  • 可返回 IntentionPreviewInfo.HtmlCustomDiffDIFFEMPTY
  • 使用 checkPreviewAndLaunchAction()checkIntentionPreviewHtml()getIntentionPreviewText() 测试 preview。

常见 preview 失败原因:

  • action 持有真实 PsiElement 并修改它,而不是 remap 到文件副本。
  • 在 preview 中调用 Application.invokeLater()
  • 依赖 PsiDocumentManager.getDocument(psiFile),但非物理文件不支持该路径。
  • 修改当前文件之外的 references/search 结果。
  • 对 mock editor 未实现的能力做假设,例如复杂 folding 操作。

Inspection Options

如果 inspection 需要配置项,2023.1+ 优先使用 declarative options,而不是 Swing panel。

推荐入口:

class ExampleInspection : LocalInspectionTool() {
    @JvmField
    var ignoreGeneratedCode: Boolean = true

    override fun getOptionsPane(): OptPane {
        return pane(
            checkbox("ignoreGeneratedCode", "Ignore generated code")
        )
    }
}

Declarative options 的优势:

  • 平台统一渲染,符合 UI guidelines。
  • 可以在 inspection panel 以外的地方读取和修改。
  • 对不同 frontend 更友好。
  • bind id 可被 DevKit resolve,减少字段重命名后的隐藏错误。

复杂场景可以通过 OptionController 自定义读写逻辑。仍需要 Swing 时,使用 createOptionsPanel(),但注意:从 2023.1 起,如果 getOptionsPane() 返回非空面板,createOptionsPanel() 会被忽略。

Live Templates

Live Templates 适合用户主动输入缩写后展开代码,或在 Surround With 中包裹选区。可由用户创建、导出,再随插件打包。

典型资源:

<templateSet group="Example">
  <template
      name="ex"
      value="example($ARG$)$END$"
      description="Create example call"
      toReformat="true"
      toShortenFQNames="true">
    <variable name="ARG" expression="complete()" defaultValue="&quot;value&quot;" alwaysStopAt="true"/>
    <context>
      <option name="EXAMPLE_CODE" value="true"/>
    </context>
  </template>
</templateSet>

注册:

<extensions defaultExtensionNs="com.intellij">
  <defaultLiveTemplates file="/liveTemplates/Example.xml"/>
  <liveTemplateContext
      contextId="EXAMPLE_CODE"
      implementation="com.example.ExampleTemplateContext"/>
</extensions>

TemplateContextType 决定模板在哪里可用:

class ExampleTemplateContext : TemplateContextType("EXAMPLE_CODE", "Example code") {
    override fun isInContext(templateActionContext: TemplateActionContext): Boolean {
        return templateActionContext.file.fileType == ExampleFileType
    }
}

Live Template XML 关键属性:

属性 作用
name 用户输入的缩写
value 展开内容,变量用 $NAME$
shortcut TABSPACEENTERCUSTOMNONE
toReformat 插入后按 code style 格式化
toShortenFQNames 插入后尝试缩短全限定名/导入
resource-bundle + key 本地化 description

自定义函数本质是 macro。实现 MacroBase 并注册:

<extensions defaultExtensionNs="com.intellij">
  <liveTemplateMacro implementation="com.example.TitleCaseMacro"/>
</extensions>

实现建议:

  • 先在 IDE 中手工创建和导出模板,再打包进插件,减少 XML 错误。
  • 优先复用内置 template contexts,不要为已有语言重复定义上下文。
  • description 应说明用户看到的动作,不写实现细节。
  • 多模块打包时确保 template 文件路径唯一。

Postfix Templates

Postfix Templates 在 completion 过程中出现。用户输入表达式后缀,例如 value.notnull,平台询问当前语言的 PostfixTemplateProvider,筛选适用模板并展开。

注册:

<extensions defaultExtensionNs="com.intellij">
  <codeInsight.template.postfixTemplateProvider
      language="Example"
      implementationClass="com.example.ExamplePostfixTemplateProvider"/>
</extensions>

简单模板可直接继承 PostfixTemplate,实现:

  • isApplicable():当前 context 是否可用。
  • expand():如何改写 editor/document/PSI。

更复杂场景:

需求 推荐基类/机制
多个表达式可选 PostfixTemplateWithExpressionSelector
可用 Live Template 语法表达 StringBasedPostfixTemplate
用户可在 Settings 中编辑 editable postfix template 相关基类
复用 Surround With SurroundPostfixTemplateBase

资源文件必须提供描述和 before/after 示例:

src/main/resources/postfixTemplates/IntroduceVariablePostfixTemplate/description.html
src/main/resources/postfixTemplates/IntroduceVariablePostfixTemplate/before.example.template
src/main/resources/postfixTemplates/IntroduceVariablePostfixTemplate/after.example.template

示例中可以用 <spot> 标记重点区域:

<spot>items</spot>.notnull

也可以用 $key 表示当前 postfix key。多模块打包时同样要避免 description 目录重名。

测试矩阵

能力 最低测试
Inlay Hints provider 开关、preview 文件、PSI 改变后刷新、Dumb Mode 行为
Documentation offset/PSI/Symbol target、hover、lookup item documentation、失效 PSI pointer
Intentions isAvailable、invoke、description、before/after、preview
Inspection Options 默认值、持久化、options UI、quick fix 修改配置
Live Templates context 生效、变量跳转顺序、reformat/shorten、Surround With
Postfix Templates applicability、多个表达式选择、description 示例、展开后 caret

参考来源