PSI
程序结构接口,通常简称为 PSI,是 IntelliJ 平台中的一个层,负责解析文件和创建语法和语义代码模型,为平台的许多功能提供支持。
PSI files
PSI(程序结构接口)文件是结构的根,将文件内容表示为特定编程语言中元素的层次结构。
PsiFile 类是所有 PSI 文件的公共基类,而特定语言的文件通常由其子类表示。
例如,PsiJavaFile 类表示一个 Java 文件,而 XmlFile 类表示一个 XML 文件。
与Virtual Files和Documents有应用范围(即使打开多个项目,每个文件都由同一个VirtualFile实例表示)不同,PSI有项目范围:
如果文件属于多个项目,则同一个文件由多个PsiFile实例表示 同时打开。
chat
详细解释下 idea 插件开发中的 PSI file
在 IntelliJ IDEA 插件开发中,PSI(Program Structure Interface)文件是一种核心概念,它代表了源代码的抽象语法树(Abstract Syntax Tree,AST)。
PSI 文件提供了一种统一的方式来解析、访问和修改源代码,无论是 Java、Kotlin、JavaScript 还是其他语言。
PSI 文件包含了代码的结构和语义信息,可以表示类、方法、变量、注释等各种代码元素。通过使用 PSI 文件,插件可以深入理解代码的结构和含义,并对其进行分析、操作和扩展。
在 IntelliJ IDEA 插件开发中,可以使用 PSI 文件来完成许多任务,例如:
-
导航和查找:通过分析 PSI 文件,可以实现跳转到定义、查找引用等导航功能。插件可以定位代码中的特定元素,并在编辑器中进行高亮显示或进行其他操作。
-
代码分析:通过遍历 PSI 树,可以执行静态代码分析。插件可以检测代码中的错误、潜在问题或编码风格违规,并提供相应的警告或建议。
-
重构和代码生成:PSI 文件提供了修改代码结构的能力。插件可以使用 PSI 文件来执行重构操作,例如重命名变量、提取方法、内联方法等。此外,还可以基于代码模板生成新的代码。
-
代码生成和自动完成:插件可以利用 PSI 文件生成代码片段,并将其插入到编辑器中。这对于自动完成功能非常有用,可以根据上下文为用户提供代码补全建议。
要在 IntelliJ IDEA 插件中使用 PSI 文件,通常需要使用该插件提供的 API 来访问和操作 PSI 树。IntelliJ IDEA 提供了丰富的 PSI API,可以让开发者轻松地处理源代码的结构和语义信息。
总而言之,PSI 文件是 IntelliJ IDEA 插件开发中的关键组件,它提供了一种统一的方式来解析、访问和修改源代码。通过使用 PSI 文件,插件可以实现各种功能,包括导航、代码分析、重构和代码生成等。
java 文件 PSI file 的使用入门例子
package com.github.houbb.database.migrate.idea.plugin.action;
import com.intellij.execution.MethodListDlg;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
public class GenerateTestAction extends AnAction {
private static final Logger LOG = Logger.getInstance(GenerateTestAction.class);
@Override
public void actionPerformed(AnActionEvent event) {
Project project = event.getProject();
VirtualFile file = event.getDataContext().getData(CommonDataKeys.VIRTUAL_FILE);
if (project != null && file != null && file.getName().endsWith(".java")) {
PsiFile psiFile = PsiManager.getInstance(project).findFile(file);
if (psiFile instanceof PsiJavaFile) {
PsiJavaFile javaFile = (PsiJavaFile) psiFile;
// 访问类和方法
PsiClass[] classes = javaFile.getClasses();
for (PsiClass psiClass : classes) {
System.out.println("Class: " + psiClass.getName());
PsiMethod[] methods = psiClass.getMethods();
for (PsiMethod method : methods) {
System.out.println("Method: " + method.getName());
}
PsiField[] psiFields = psiClass.getFields();
for (PsiField psiField : psiFields) {
System.out.println("Field: " + psiField.getName());
}
}
}
}
}
}
展示方法列表弹框
Editor editor = event.getDataContext().getData(CommonDataKeys.EDITOR);
MethodListDlg methodListDlg = new MethodListDlg(psiClass, new Condition<PsiMethod>() {
@Override
public boolean value(PsiMethod psiMethod) {
return true;
}
}, editor.getComponent());
methodListDlg.show();
详细介绍一下 PsiCodeBlock
PsiCodeBlock
是 IntelliJ IDEA 插件开发中用于表示代码块的 PSI 元素。它代表了一段代码的范围,通常是在方法、构造函数或其他语句块中使用。
PsiCodeBlock
继承自 PsiElement
,它是 PSI 树中的一个节点,可以包含其他 PSI 元素作为其子元素。代码块可以包含多个语句、局部变量、内部类、注释等。
下面是一些常见的操作,可以通过 PsiCodeBlock
进行操作和获取信息:
- 获取代码块中的语句:
PsiCodeBlock codeBlock = ...; // 获取 PsiCodeBlock 对象 PsiStatement[] statements = codeBlock.getStatements(); // 获取代码块中的语句数组
- 遍历代码块中的语句:
PsiCodeBlock codeBlock = ...; // 获取 PsiCodeBlock 对象 for (PsiStatement statement : codeBlock.getStatements()) { // 在此处处理每个语句 }
- 获取代码块的起始位置和结束位置:
PsiCodeBlock codeBlock = ...; // 获取 PsiCodeBlock 对象 int startOffset = codeBlock.getTextRange().getStartOffset(); // 获取代码块的起始偏移量 int endOffset = codeBlock.getTextRange().getEndOffset(); // 获取代码块的结束偏移量
- 获取代码块的父元素:
PsiCodeBlock codeBlock = ...; // 获取 PsiCodeBlock 对象 PsiElement parent = codeBlock.getParent(); // 获取代码块的父元素,例如方法或构造函数
- 在代码块中插入语句:
PsiCodeBlock codeBlock = ...; // 获取 PsiCodeBlock 对象 PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project); // 获取 PsiElementFactory PsiStatement newStatement = elementFactory.createStatementFromText("System.out.println(\"Hello, World!\");", codeBlock); // 创建新的语句 codeBlock.add(newStatement); // 在代码块末尾添加新的语句
这些只是 PsiCodeBlock
提供的一些常见操作和用法示例。根据您的需求,您还可以使用其他方法和组合操作来处理和修改代码块的内容。
请注意,在使用 PsiCodeBlock
时,需要了解 PSI 元素树的结构和相关的 PSI 类。可以使用 PSI Viewer 或调试器来检查代码块的结构,以便更好地了解如何操作和获取所需的信息。
详细介绍一下 PsiStatement
PsiStatement
是 IntelliJ IDEA 插件开发中的一个 PSI 元素,用于表示 Java 代码中的语句。
它是 PsiCodeBlock
中的子元素,代表了一个独立的语句,可以是表达式、声明、控制流语句等。
PsiStatement
继承自 PsiElement
,因此它是 PSI 树中的一个节点。它提供了一些方法来访问和操作语句的内容和属性。
下面是一些常见的操作,可以通过 PsiStatement
进行操作和获取信息:
- 获取语句的文本:
PsiStatement statement = ...; // 获取 PsiStatement 对象 String text = statement.getText(); // 获取语句的文本
- 获取语句的类型:
PsiStatement statement = ...; // 获取 PsiStatement 对象 if (statement instanceof PsiExpressionStatement) { // 处理表达式语句 } else if (statement instanceof PsiDeclarationStatement) { // 处理声明语句 } else if (statement instanceof PsiIfStatement) { // 处理 if 语句 } else { // 处理其他类型的语句 }
- 获取语句的父元素:
PsiStatement statement = ...; // 获取 PsiStatement 对象 PsiElement parent = statement.getParent(); // 获取语句的父元素,例如代码块或循环语句
- 获取语句所在的方法或构造函数:
PsiStatement statement = ...; // 获取 PsiStatement 对象 PsiMethod containingMethod = PsiTreeUtil.getParentOfType(statement, PsiMethod.class); // 获取包含语句的方法或构造函数
- 在语句之前或之后插入新的语句:
PsiStatement statement = ...; // 获取 PsiStatement 对象 PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project); // 获取 PsiElementFactory PsiStatement newStatement = elementFactory.createStatementFromText("System.out.println(\"Hello, World!\");", statement); // 创建新的语句 statement.getParent().addBefore(newStatement, statement); // 在语句之前插入新的语句
这些只是 PsiStatement
提供的一些常见操作和用法示例。根据您的需求,您还可以使用其他方法和组合操作来处理和修改语句的内容。
请注意,在使用 PsiStatement
时,需要了解 PSI 元素树的结构和相关的 PSI 类。可以使用 PSI Viewer 或调试器来检查语句的结构,以便更好地了解如何操作和获取所需的信息。
更多
这里其实理解每一个方法的每一句话在执行什么。
GlobalSearchScope
入门
详细解释一下
GlobalSearchScope searchScope = GlobalSearchScope.fileScope(psiClass.getContainingFile());
在 IDEA 插件开发中,GlobalSearchScope
是一个类,用于指定搜索的范围。
searchScope
是一个变量,其类型为 GlobalSearchScope
,用于表示搜索的范围。
在给定的代码片段中,searchScope
被初始化为 GlobalSearchScope.fileScope(psiClass.getContainingFile())
。
让我们逐步解释这段代码的含义:
-
首先,
psiClass
是一个代表代码中的类的PsiClass
对象。它是 PSI 的一种元素类型,用于表示源代码中的类。 -
psiClass.getContainingFile()
是PsiClass
接口中的一个方法,用于获取包含该类的文件的PsiFile
对象。PsiFile
是 PSI 的另一种元素类型,用于表示源代码文件。 -
GlobalSearchScope.fileScope(psiClass.getContainingFile())
是一个静态方法调用,用于创建一个表示特定文件范围的GlobalSearchScope
对象。它接受一个PsiFile
参数,表示要搜索的文件。在这种情况下,我们使用psiClass.getContainingFile()
返回的文件作为搜索范围。
综上所述,GlobalSearchScope.fileScope(psiClass.getContainingFile())
的作用是创建一个搜索范围,该范围限定在包含给定 PsiClass
的文件中。这意味着在后续的代码中,使用 searchScope
变量进行搜索操作时,将仅在包含该类的文件中进行搜索,而不是整个项目或其他范围。这有助于提高搜索效率并减少不必要的搜索结果。
SearchUtils
问题
详细解释一下
Iterable<PsiReference> psiReferenceIterable = SearchUtils.findAllReferences(psiMethod, searchScope)`;
在这段代码中,Iterable<PsiReference> psiReferenceIterable
是一个变量,用于存储搜索到的对给定 psiMethod
的引用。
SearchUtils.findAllReferences(psiMethod, searchScope)
是一个方法调用,用于执行搜索操作。
让我们逐步解释这段代码的含义:
-
psiMethod
是一个代表代码中方法的PsiMethod
对象。它是 PSI 的一种元素类型,用于表示源代码中的方法。 -
searchScope
是一个GlobalSearchScope
对象,用于指定搜索的范围。它确定了搜索操作在哪些文件或区域中进行。 -
SearchUtils.findAllReferences(psiMethod, searchScope)
是一个方法调用,它调用名为findAllReferences
的方法,并传入psiMethod
和searchScope
作为参数。SearchUtils
是一个自定义的工具类或实用程序类,其中定义了执行搜索操作的逻辑。 -
findAllReferences
方法执行搜索操作,查找给定方法psiMethod
的所有引用。它使用searchScope
来限定搜索范围。 -
返回的结果是一个
Iterable<PsiReference>
对象,表示搜索到的对给定方法的引用。Iterable
是 Java 中的一个接口,表示可迭代的集合。PsiReference
是 PSI 的元素类型之一,表示代码中的引用关系。
因此,Iterable<PsiReference> psiReferenceIterable = SearchUtils.findAllReferences(psiMethod, searchScope);
的作用是执行搜索操作,查找给定方法 psiMethod
的所有引用,并将结果存储在 psiReferenceIterable
变量中。
开发者可以使用 psiReferenceIterable
对象来遍历和处理这些引用,进一步分析代码或执行其他操作。
PsiTreeUtil
详细解释一下 PsiTreeUtil.getParentOfType(psiElement, PsiMethod.class)
在 IDEA 插件开发中,PsiTreeUtil.getParentOfType(psiElement, PsiMethod.class)
是一个方法调用,用于查找给定 psiElement
的父级中的特定类型的元素(在这种情况下是 PsiMethod
)。
让我们逐步解释这段代码的含义:
-
psiElement
是一个代表代码中某个元素的PsiElement
对象。PsiElement
是 PSI 的基本元素类型,用于表示源代码中的不同部分,例如类、方法、语句等。 -
PsiMethod.class
是一个表示PsiMethod
类的对象。PsiMethod
是 PSI 的元素类型,用于表示源代码中的方法。 -
PsiTreeUtil.getParentOfType(psiElement, PsiMethod.class)
是一个静态方法调用,它调用名为getParentOfType
的方法,并传入psiElement
和PsiMethod.class
作为参数。PsiTreeUtil
是 IDEA 提供的一个实用程序类,其中定义了许多用于处理 PSI 树的工具方法。 -
getParentOfType
方法执行向上遍历父级的操作,查找给定psiElement
的父级中的特定类型的元素(在这种情况下是PsiMethod
)。它会沿着 PSI 树从psiElement
开始向上遍历,直到找到满足条件的父级元素,或者到达树的顶部(例如文件级别)。 -
返回的结果是找到的满足条件的父级元素,如果没有找到,则返回
null
。
因此,PsiTreeUtil.getParentOfType(psiElement, PsiMethod.class)
的作用是在给定 psiElement
的父级中查找特定类型(PsiMethod
)的元素。
这对于需要根据代码结构进行导航、查找或修改的插件功能非常有用。开发者可以根据实际需求使用返回的父级元素进行进一步的操作。
把搜索范围限定在一个 JavaFile 中
要将搜索范围限定在一个 JavaFile
中,你可以使用 LocalSearchScope
类来创建一个本地搜索范围。以下是实现的步骤:
-
获取目标
JavaFile
对应的PsiFile
对象。PsiFile psiFile = ...; // 获取目标 JavaFile 对应的 PsiFile 对象
-
创建一个
LocalSearchScope
对象,将目标PsiFile
作为参数传递进去。LocalSearchScope searchScope = new LocalSearchScope(psiFile);
LocalSearchScope
是 PSI 中的一个类,用于表示局部搜索范围,即限定在特定文件内的范围。 -
使用
PsiSearchHelper
类的findReferencesTo
方法来搜索对目标PsiField
的引用,并将搜索结果存储在一个数组或列表中。PsiSearchHelper searchHelper = PsiSearchHelper.getInstance(project); PsiReference[] references = searchHelper.findReferencesTo(psiField, searchScope);
这里的
project
是指插件的项目对象,你需要替换成你实际的项目对象。 -
遍历搜索结果,筛选出引用所在的
PsiMethod
对象。for (PsiReference reference : references) { PsiElement referenceElement = reference.getElement(); PsiMethod psiMethod = PsiTreeUtil.getParentOfType(referenceElement, PsiMethod.class); // 对获取到的 PsiMethod 进行处理 }
在遍历结果时,可以使用
PsiTreeUtil.getParentOfType
方法获取引用所在的父级元素,即PsiMethod
对象。对获取到的PsiMethod
进行处理,如记录、打印或执行其他操作,根据你的具体需求进行处理。
通过以上步骤,你可以将搜索范围限定在一个 JavaFile
中,并获取到 PsiField
被哪些 PsiMethod
引用,并对这些引用进行进一步的操作或分析。
意外发现 PsiDocComment
可以拿到文档,所以直接生成对应的文档,非常的方便。
How do I get a PSI file?
Context | API |
---|---|
Action | AnActionEvent.getData(CommonDataKeys.PSI_FILE) |
Document | PsiDocumentManager.getPsiFile() |
PSI Element | PsiElement.getContainingFile() (may return null if the PSI element is not contained in a file) |
Virtual File | PsiManager.findFile(), PsiUtilCore.toPsiFiles() |
File Name | FilenameIndex.getVirtualFilesByName() and locate via PsiManager.findFile() or PsiUtilCore.toPsiFiles() |
我可以用 PSI 文件做什么?
大多数有趣的修改操作是在单个 PSI 元素的级别上执行的,而不是作为一个整体的文件。
要遍历文件中的元素,请使用
psiFile.accept(new PsiRecursiveElementWalkingVisitor() {
// visitor implementation ...
});
PSI 文件来自哪里?
由于 PSI 依赖于语言,因此 PSI 文件是使用 Language 实例创建的:
LanguageParserDefinitions.INSTANCE
.forLanguage(MyLanguage.INSTANCE)
.createFile(fileViewProvider);
PSI 文件保留多长时间?
与文档一样,PSI 文件是从相应的 VirtualFile 实例中弱引用的,如果没有被任何人引用,则可以被垃圾收集。
如何创建 PSI 文件?
PsiFileFactory.createFileFromText() 创建具有指定内容的内存中 PSI 文件。
要将 PSI 文件保存到磁盘,请使用 PsiDirectory.add()。
当 PSI 文件更改时,我如何收到通知?
PsiManager.addPsiTreeChangeListener() 允许您接收有关项目 PSI 树的所有更改的通知。
或者,在 com.intellij.psi.treeChangeListener 扩展点中注册 PsiTreeChangeListener。
如何扩展 PSI?
可以通过自定义语言插件扩展 PSI 以支持其他语言。 有关开发自定义语言插件的更多详细信息,请参阅自定义语言支持参考指南。
与 PSI 合作有哪些规则?
对 PSI 文件内容所做的任何更改都会反映在文档中,因此所有处理文档的规则(读/写操作、命令、只读状态处理)都有效。
如何系统的学习 psi 接口。给出学习资料,包含网址
要系统地学习 PSI(Program Structure Interface)接口以及相关概念和使用方法,以下是一些建议的学习资料和资源:
-
JetBrains 官方文档:IntelliJ Platform SDK Documentation - PSI (https://www.jetbrains.org/intellij/sdk/docs/reference_guide/custom_language_support/psi.html) 是 JetBrains 官方提供的关于 PSI 的详细文档。它包含了 PSI 的概述、基本概念、API 文档和示例代码,是学习 PSI 的权威参考资料。
-
IntelliJ IDEA 插件开发教程:如果你想结合实际开发来学习 PSI,可以参考 IntelliJ IDEA 插件开发的相关教程和示例。官方提供了一系列教程和示例,帮助你了解如何使用 PSI 来开发功能丰富的插件。可以从官方插件开发文档(https://plugins.jetbrains.com/docs/intellij/welcome.html)开始,逐步学习插件开发和 PSI 的相关内容。
-
书籍和博客文章:有一些书籍和博客文章专门介绍了 IntelliJ IDEA 插件开发和 PSI 的使用。例如,《IntelliJ IDEA插件开发权威指南》(IntelliJ IDEA Plugin Development: Beginner’s Guide)一书涵盖了 PSI 的内容。此外,一些开发者也在个人博客上分享了他们的经验和教程,你可以搜索相关博客文章来获取更多学习资源。
-
示例项目和开源插件:研究一些开源的 IntelliJ IDEA 插件项目可以帮助你学习 PSI 的实际应用。可以浏览 GitHub 上的一些知名插件项目,如 JetBrains 官方的插件、Lombok 插件等。阅读其源代码可以帮助你理解 PSI 的使用方式和最佳实践。
-
社区论坛和讨论组:加入 IntelliJ IDEA 插件开发的社区论坛和讨论组,与其他开发者交流经验和学习资源。JetBrains 官方提供了插件开发者社区论坛(https://intellij-support.jetbrains.com/hc/en-us/community/topics/200366979-IntelliJ-IDEA-Open-API-and-Plugin-Development),你可以在这里提问和寻求帮助。
通过结合阅读官方文档、学习教程、阅读源代码和参与社区讨论,你可以逐步深入学习 PSI 接口并掌握其使用方法。不断实践和开发小项目也是提高技能的关键。
参考资料
https://plugins.jetbrains.com/docs/intellij/psi.html