IntelliJ IDE 插件开发 | (七)PSI 入门及实战(实现 MyBatis 插件的跳转功能)

这篇具有很好参考价值的文章主要介绍了IntelliJ IDE 插件开发 | (七)PSI 入门及实战(实现 MyBatis 插件的跳转功能)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

系列文章

本系列文章已收录到专栏,交流群号:689220994,也可点击链接加入。

前言

所谓 PSI(Program Structure Interface),直译过来是程序结构接口,其实就是 IntelliJ 平台给我们提供用来解析代码文件,简化对各类编程语言(Java、Kotlin、XML)操作的接口。大部分针对编程语言或者框架的便利插件其实就与此相关,本文则会先介绍关于 PSI 的一些基础知识,然后再以一些 Mybatis 插件提供的 Java 方法 和 Mapper XML 文件互相跳转的例子来说明 PSI 的实际应用,最终实现效果如下图,本文涉及的到的完整代码文件也已上传到GitHub。

intellij psi,Kotlin,ide,mybatis,kotlin,intellij-idea

PSI file(PSI 文件)

在本系列的第五篇文章中介绍了Virtual Files 和 Documents 用于处理文件的 API,而 PSI file 也是用于处理文件的 API,不过也有一些不同,具体如下:

类别 层面 范围
VF、Document 文本文件 应用级
PSI 编程语言语法树 项目级

获取文件的 PSI file 对象的方式主要有以下几种(来自官网):

Context API
Action AnActionEvent.getData(CommonDataKeys.PSI_FILE)
Document PsiDocumentManager.getPsiFile()
PSI Element PsiElement.getContainingFile()
Virtual File PsiManager.findFile(), PsiUtilCore.toPsiFiles()
File Name FilenameIndex.getVirtualFilesByName()

最后一种方式FilenameIndex.getVirtualFilesByName()得到的结果是Virtual File 对象,需要再通过倒数第二行的方式再获取到对应的 PIS file 对象。

不过通过以上方式获取到的 PsiFile 只是顶层的接口,针对不同的编程语言,我们会使用相应的实现类。例如 Java 是 PsiJavaFile,XML 是 XMLFile。

下面通过实际使用来进行介绍,首先是 PsiJavaFile,当然在使用之前需要确保build.gradle文件中将 Java 添加到插件配置中(XML 内置无需添加):

intellij {
    // 用到的插件
    plugins.set(listOf("com.intellij.java"))
}

然后在plugin.xml中加入以下配置:

<depends>com.intellij.modules.java</depends>

如果是 XML 则需要添加如下配置:

<depends>com.intellij.modules.xml</depends>

经过以上配置后,我们就可以使用 PsiJavaFile 和 XMLFile 的相关 API 了。

例如以下代码可以得到 java 文件的所属表名和类中 所有的方法名,然后展示出来:

class PsiJavaAction: AnAction() {

    override fun actionPerformed(e: AnActionEvent) {
        // 获取 PsiFile 对象
        val psiFile = e.getData(CommonDataKeys.PSI_FILE)
        // 转换为 PsiJavaFile
        val psiJavaFile = psiFile as PsiJavaFile
        // 获取类所属包
        Utils.info("当前类所属包:${psiJavaFile.packageName}")
        // 遍历获取所有的方法名
        psiJavaFile.accept(object: JavaRecursiveElementVisitor() {
            override fun visitMethod(method: PsiMethod) {
                Utils.info("查找到方法:${method.name}")
            }
        })
    }

}

intellij psi,Kotlin,ide,mybatis,kotlin,intellij-idea

在以上代码中需要注意psiJavaFile.accept()方法,其中 accept 方法是 PsiFile 所提供的方法,方法签名为void accept(@NotNull PsiElementVisitor visitor),用于遍历 PSI 文件中的各类元素,可以看到上面我们在传参时传递的是JavaRecursiveElementVisitor,这是用于遍历 Java 中各类元素(字段、方法、注解等)的一个实现类,只需要重写对应的方法即可,在上面我们重写了visitMethod方法,其实内部提供了很多方法,大家可以自行尝试,通过方法名也可以看到这里还支持遍历 break 语句,断言语句等等:

intellij psi,Kotlin,ide,mybatis,kotlin,intellij-idea

下面再说明如何遍历 XML 文件中的元素:

class PsiXMLAction: AnAction() {

    override fun actionPerformed(e: AnActionEvent) {
        // 获取 PsiFile 对象
        val psiFile = e.getData(CommonDataKeys.PSI_FILE)
        // 转换为 XmlFile
        val xmlFile = psiFile as XmlFile
        // 获取根标签名称
        Utils.info("根标签名称:${xmlFile.rootTag?.name}")
        // 遍历获取所有的元素信息
        xmlFile.accept(object: XmlRecursiveElementVisitor() {
            override fun visitXmlAttribute(attribute: XmlAttribute) {
                Utils.info("属性名称:${attribute.name}, 属性值:${attribute.value}")
            }
        })
    }

}

intellij psi,Kotlin,ide,mybatis,kotlin,intellij-idea

可以看到这里遍历使用的是XmlRecursiveElementVisitor,是 XML 对于PsiElementVisitor 的一个实现类,用于遍历 XML 文件中的各种元素:

intellij psi,Kotlin,ide,mybatis,kotlin,intellij-idea

PSI Element(PSI 元素)

在上面介绍 PSI 文件的时候多次提到元素的概念,PSI 文件则正是由一系列的 PSI Element 所组成。和 PSI file 类似,PSI Element 也属于一个顶层接口,针对不同的编程语言,会有多种 PSI 元素。以 Java 为例,有 PsiClass、PSIMethod、PsiField 等对应 Java 语法的各类元素。而 XML 中也有 XmlTag、XmlAttribute 等概念。那我们如何快速知道一个文件中有哪些 PSI 元素?如何快速知道一个我们不熟悉的编程语言中的 PSI 元素?别慌,IntelliJ平台给我提供了工具:

intellij psi,Kotlin,ide,mybatis,kotlin,intellij-idea

通过 IntelliJ 平台的工具,我们可以很方便地查看当前或者任意一种文件的 PSI 结构,下面分别以 Java 和 XML 文件为例,首先是 Java 文件:

intellij psi,Kotlin,ide,mybatis,kotlin,intellij-idea

同时点击左下的元素节点,上方还会自动对应到元素位置:

intellij psi,Kotlin,ide,mybatis,kotlin,intellij-idea

然后是 XML 文件:

intellij psi,Kotlin,ide,mybatis,kotlin,intellij-idea

当然,除了 Java 和 XML,IntelliJ 支持的编程语言远不止这些,这里展示一部分,剩下的大家可以自行探索:

intellij psi,Kotlin,ide,mybatis,kotlin,intellij-idea

上面介绍了如何快速查看 PSI 文件中的元素,下面再介绍如何去获取 PSI 元素,以下来自官网:

Context API
Action AnActionEvent.getData(CommonDataKeys.PSI_ELEMENT)Note: If an editor is currently open and the element under caret is a reference, this will return the result of resolving the reference.
PSI File PsiFile.findElementAt(offset)
Reference PsiReference.resolve()

可以看到总共有三种方式:第一种是直接获取当前光标位置的 PSI 元素;第二种是可以自己指定偏移量(如果不熟悉偏移量的概念,可以看本系列第五篇文章中讲解 CaretModel 的部分),获取指定文件指定位置的 PSI 元素;最后一种引用则使用的较少,这里不再展开介绍,大家可以查看官方文档进行了解。

除了获取某个位置的 PSI 元素,我们还可以获取其所属父元素或者子元素,下面以 Java 文件为例讲解如何使用:

class PsiJavaAction: AnAction() {

    override fun actionPerformed(e: AnActionEvent) {
        val psiFile = e.getData(CommonDataKeys.PSI_FILE)
        // 获取光标处 PSI 元素,假定该元素在方法内部
        val psiElement = e.getData(PlatformDataKeys.EDITOR)
        	?.caretModel?.let { psiFile?.findElementAt(it.offset) }
        // 获取该元素所属的方法名
        val psiMethod = PsiTreeUtil.getParentOfType(psiElement, PsiMethod::class.java)
        // 获取该元素所属的类名
        val psiClass = PsiTreeUtil.getParentOfType(psiElement, PsiClass::class.java)
        Utils.info("所属方法名:${psiMethod?.name}")
        Utils.info("所属类名:${psiClass?.name}")
    }

}

可以看到上面我们使用PsiTreeUtil::getParentOfType可以获取到一个元素的父元素,同时支持跨层级获取,既可以获取元素所属的方法,也可以获取元素所属的类。

效果如下:

intellij psi,Kotlin,ide,mybatis,kotlin,intellij-idea

相应地我们也可以通过PsiTreeUtil::getChildrenOfTypeAsList去获取某个元素的所有子元素:

class PsiJavaAction: AnAction() {

    override fun actionPerformed(e: AnActionEvent) {
        val psiFile = e.getData(CommonDataKeys.PSI_FILE)
        // 先获取光标所在处元素所属的类
        val psiElement = e.getData(PlatformDataKeys.EDITOR)
        	?.caretModel?.let { psiFile?.findElementAt(it.offset) }
        val psiClass = PsiTreeUtil.getParentOfType(psiElement, PsiClass::class.java)
        // 获取类中所有的方法
        val psiMethods = PsiTreeUtil.getChildrenOfTypeAsList(psiClass, PsiMethod::class.java)
        Utils.info("包含的方法:${psiMethods.joinToString(",") { it.name }}")
    }

}

intellij psi,Kotlin,ide,mybatis,kotlin,intellij-idea

实战

在正式实现前,先介绍一下整体的实现思路,这里只说明如何从 Java 方法跳转到 Mapper XML 文件中的节点,反向参考代码也很好理解,思路如下:

  1. 左侧图标行标记符通过实现RelatedItemLineMarkerProvider并重写collectNavigationMarkers方法设置。
  2. 判断代码行的元素类型为 PsiMethod 才进行设置,同时文件类名以 Mapper 结尾。
  3. 根据类名在项目查找同名的 Mapper XML 文件。
  4. 通过 accept 方法遍历 XML 文件所有的属性,将 id 值为对应方法名的标签所对应的元素保存到可跳转的目标。

设置行标记符号,平台给我们提供了 RelatedItemLineMarkerProvider 类进行设置,只需要自定义了自己的行标记类,然后在 plugin.xml 中添加 如下配置即可:

<codeInsight.lineMarkerProvider language="JAVA"
	implementationClass="cn.butterfly.psi.provider.JavaMapperLineMarkerProvider"/>

代码实现如下:

class JavaMapperLineMarkerProvider: RelatedItemLineMarkerProvider() {

    override fun collectNavigationMarkers(
        element: PsiElement,
        result: MutableCollection<in RelatedItemLineMarkerInfo<*>>
    ) {
        // 查找类名后缀为 Mapper 内的所有方法
        if (element !is PsiMethod) {
            return
        }
        val psiClass = PsiTreeUtil.getParentOfType(element, PsiClass::class.java) ?: return
        val className = psiClass.name ?: return
        if (!className.endsWith("Mapper")) {
            return
        }

        // 查找同名 XML 文件对应的 PSI 文件对象
        val virtualFile = FileTypeIndex.getFiles(XmlFileType.INSTANCE, GlobalSearchScope.allScope(element.project))
            .first { it.name.startsWith(className) }
        val psiFile = PsiManager.getInstance(element.project).findFile(virtualFile)

        // 遍历 XML 文件中标签 id 节点值等于 Java 方法名的元素, 然后添加可跳转的行标记符
        psiFile?.accept(object : XmlRecursiveElementVisitor() {
            override fun visitXmlAttribute(attribute: XmlAttribute) {
                if (attribute.name == "id" && attribute.value == element.name) {
                    // NavigationGutterIconBuilder 用于创建标识符
                    result.add(
                        NavigationGutterIconBuilder.create(PluginIcons.MAPPER_ICON)
                            .setTargets(setOf(attribute.navigationElement))
                            .setTooltipText("Navigation to target in mapper xml").createLineMarkerInfo(element)
                    )
                }
            }
        })
    }

}

总结

本文简单介绍了关于 PSI 文件和元素的基础知识,最后以一个 Mybatis 文件跳转的例子去演示了如何去实际运用 PSI,在下一篇文章则会介绍关于 PSI 的进阶知识,敬请期待~~文章来源地址https://www.toymoban.com/news/detail-861625.html

到了这里,关于IntelliJ IDE 插件开发 | (七)PSI 入门及实战(实现 MyBatis 插件的跳转功能)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • IntelliJ IDEA 2021版本可用的IDE Eval Reset插件

    IDEA版本说明: 如果你是最新版本或者比 2021.2.2 版本高的话,就可能存在失效或者不可用的情况,可以尝试降低版本使用。 在 idea- settings- plugins 里搜索 IDE Eval Reset (注意:是在Marketplace栏目下搜索,Installedl栏目下是你已经安装好的插件),然后点击 Install 进行安装,安装完

    2024年02月14日
    浏览(13)
  • IntelliJ IDEA安装Mybatis 插件Free Mybatis plugin

    需求描述 在开发一些Mybatis的项目,经常需要写一个Mapper接口,在找代码过程,经常需要去找对应的xml文件,所以非常的不方便。自从有了免费的free-mybatis-plugin插件之后 ,在可以实现在idea里一键跳转到对应的xml文件,反之,在xml文件也可以一键跳转到对应mapper接口。 也有其

    2024年02月15日
    浏览(57)
  • 【Java】IDE集成开发环境工具IntelliJ安装和使用

    欢迎来到《小5讲堂》 大家好,我是全栈小5。 这是《Java》序列文章,每篇文章将以博主理解的角度展开讲解, 特别是针对知识点的概念进行叙说,大部分文章将会对这些概念进行实际例子验证,以此达到加深对知识点的理解和掌握。 温馨提示:博主能力有限,理解水平有限

    2024年01月18日
    浏览(34)
  • Intellij IDEA 插件开发

    很多idea插件文档更多的是介绍如何创建一个简单的idea插件,本篇文章从开发环境、demo、生态组件、添加依赖包、源码解读、网络请求、渲染数据、页面交互等方面介绍,是一篇能够满足基本的插件开发工程要求的文章。 如有疏漏欢迎指正,如想深入了解欢迎探讨。 Intelli

    2024年02月11日
    浏览(16)
  • Delphi Professional Crack,IDE插件开发和扩展IDE

    构建具有强大视觉设计功能的单源多平台本机应用程序。 Delphi帮助您使用Object Pascal为Windows、Mac、Mobile、IoT和Linux构建和更新数据丰富、超连接、可视化的应用程序。Delphi Professional适合个人开发人员和小型团队构建桌面和移动应用程序。 Delphi功能 单一代码库-用更少的编码工

    2024年02月14日
    浏览(7)
  • IDE(集成开发环境)插件是安全开发的便捷方式之一

    开发人员每天都使用插件,插件的功能在于简化开发流程,例如自动检测所有特殊字符(如“;”、“:”)或语法合规性。创建插件的目的本身就是为了让开发人员能够在编写代码时检测漏洞,并在无需离开 IDE 环境的情况下立即修复漏洞。我们来了解一下,什么是插件以及如

    2024年03月23日
    浏览(14)
  • 十个超级实用的 IntelliJ IDEA 插件,开发更高效

    Intellij IDEA广受欢迎,被认为是最佳的IDE之一,同时加上一些优秀的插件,会让开发人员的工作更加轻松愉快。本文推荐十个Intellij IDEA优秀插件。 IDE是开发人员的必备武器,拥有一个好的IDE不仅能提高工作效率,还能让开发人员更加享受编码的乐趣。Intellij IDEA广受欢迎,被认

    2024年04月09日
    浏览(19)
  • AIGC:IntelliJ IDEA 神级插件( ChatGPT 团队开发)

    Bito是一款在IntelliJ IDEA编辑器中的插件,Bito插件是由ChatGPT团队开发的,它是ChatGPT团队为了提高开发效率而开发的一款工具。Bito插件的强大之处在于它可以帮助开发人员更快地提交代码,同时还提供了一些有用的功能,如自动补全提交信息、快速查看历史记录等。 用他自己的

    2024年02月12日
    浏览(16)
  • MyBatis第八讲:MyBatis插件机制详解与实战

    MyBatis提供了一种插件(plugin)的功能,虽然叫做插件,但其实这是拦截器功能。那么拦截器拦截MyBatis中的哪些内容呢?本文是MyBatis第8讲,对MyBatis插件机制详解。

    2024年02月13日
    浏览(22)
  • flutter开发实战-指纹、面容ID验证插件实现

    flutter开发实战-指纹、面容ID验证插件实现 在iOS开发中,经常出现需要指纹、面容ID验证的功能。 指纹、面容ID是一种基于用生物识别技术,通过扫描用户的面部特征来验证用户身份。 在iOS中实现指纹、面容ID验证功能,步骤如下 2.1 info.plist配置 在info.plist中配置允许访问FAC

    2024年02月13日
    浏览(13)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包