跳转至

PSI:代码结构模型

PSI,全称 Program Structure Interface,是 IntelliJ Platform 对源代码结构的抽象模型。它基于文件内容构建语法和语义结构,是补全、跳转、查找用法、检查、重构、格式化等能力的基础。

PSI、Document、VirtualFile 的关系

概念 表示什么 常见用途
VirtualFile 虚拟文件系统中的文件 路径、文件类型、刷新、二进制/文本区分
Document 编辑器里的文本内容 文本修改、光标位置、行列信息
PsiFile / PsiElement 代码结构树 语法结构、引用、解析、重构

常见转换:

val psiFile = event.getData(CommonDataKeys.PSI_FILE)
val virtualFile = psiFile?.virtualFile
val document = psiFile?.let {
    PsiDocumentManager.getInstance(project).getDocument(it)
}

读取 PSI

后台线程读取 PSI 时要放在 Read Action 中:

val name = com.intellij.openapi.application.ReadAction.compute<String, Throwable> {
    psiFile.name
}

Kotlin 且目标平台较新时,优先使用协程风格的 readAction API。

修改 PSI

修改 PSI、VFS 或项目模型必须在 Write Action 中执行,并且通常需要从安全的 EDT 上下文调度。

com.intellij.openapi.command.WriteCommandAction.runWriteCommandAction(project) {
    element.replace(newElement)
}

如果修改来自用户命令,使用 WriteCommandAction 通常比裸 WriteAction 更合适,因为它会接入撤销栈。

PSI Element 与引用

PsiElement 表示语法树节点。引用通常由 PsiReference 提供:

  • resolve():跳转到声明。
  • getVariants():提供基础补全候选。
  • handleElementRename():支持重命名。

语言插件通常会围绕 PSI Element、Reference、Scope Processor 和 Index 构建核心能力。

调试 PSI

开发时建议使用:

  • PSI Viewer:查看当前文件的 PSI 树。
  • Internal Mode:打开更多 IDE 内部诊断功能。
  • Debugger:观察 PsiElement 类型、文本范围、父子结构。

常见原则

  • 不要长期缓存 PsiElement;跨 Read Action 后要重新校验 isValid
  • 尽量缓存可恢复的轻量键,例如 SmartPsiElementPointer、文件 URL、FQN。
  • 修改 PSI 前先用工厂创建合法节点,例如 Java 使用 PsiElementFactory
  • 不要在 UI renderer、Action update() 或未受控线程里修改 PSI。

参考来源