IntelliJ IDEA 架构概述(面向插件开发者)

2023-05-16

原文未定稿(2014/11)


这篇文章的目的是从插件开发者的视角描述IntelliJ IDEA的结构。文章将以一种任务驱动的方式组织:相比列出所有你可以对各组件对象进行的操作并描述这些操作它们的实现方式,这篇文章将尽可能回答“我能用这个对象做些什么”、“我如何能得到这个对象”这样的问题。

这篇文章假定读者已经熟悉IntelliJ IDEA插件开发的基本观念。如果你对插件开发还一无所知,你应该先从live demo和入门教程开始着手,之后再回到这里阅读这篇文章。

这篇文章包含以下主题:

  • 通用线程规则
  • 虚拟文件
  • 文档
  • PSI文件
  • 文件视图Providers
  • PSI元素

通用线程规则

通常,IntelliJ IDEA中的数据结构会被加上“可多读/单写入”的锁。来自任何线程的数据读取都是允许的。从UI线程中读取数据并不要求任何特殊请求,不过,从其他线程中进行的读取操作必需被封装在一个read action(ApplicationManager.getApplication().runReadAction())中。只允许UI线程进行写入数据操作,并且写操作始终需要封装在一个write action(ApplicationManager.getApplication().runWriteAction())中。

为了将控制权从后台线程移交到事件处理线程,插件应该调用ApplicationManager.getApplication().invokeLater()方法,而不是(Swing GUI编程中)标准的SwingUtilities.invokeLater()方法。前者的API允许为调用指定模式状态——调用被允许在一组模式对话框下执行。传递值ModalityState.NON_MODAL表示操作将在所有模式对话框关闭后被执行,而值ModalityState.stateForComponent() 表示操作可以在指定的组件(某个对话框的部分)仍可访问时就被执行。


虚拟文件

一个虚拟文件(com.intellij.openapi.vfs.VirtualFile)是一个文件系统(VFS)中一个文件的IntelliJ IDEA的(对应)代理对象。最常见的虚拟文件实例是你本地文件系统中的一个文件。然而,IDEA支持多种可插入的文件系统实现,所以虚拟文件也可以代理一个JAR包文件中的class、来自CVS仓库中加载的旧版本文件等等。

VFS水平只由二进制内容对应。你可以将虚拟文件内容当作字节流进行获取和设置,但像编码和换行符这类概念是由系统高层处理的。

How do I get one?

  • 从action中:e.getData(PlatformDataKeys.VIRTUAL_FILE)。如果你关心多选,你也可以用e.getData(PlatformDataKeys.VIRTUAL_FILE_ARRAY)。
  • 从本地文件系统路径中:LocalFileSystem.getInstance().findFileByIoFile()
  • 从一个PSI文件:psiFile.getVirtualFile()(如果PSI文件只存在于内存中,可能返回 null)
  • 从一个文档中:FileDocumentManager.getInstance().getFile()

What can I do with one?

在文件系统中移动、获取文件内容、重命名、移动、删除等典型的文件操作

递归遍历操作应使用VfsUtilCore.iterateChildrenRecursively进行,防止因递归符号链接产生的无限循环。

Where does it come from?

从项目目录根部开始通过来回扫描(各层)文件系统目录,VFS会被增量构建。文件系统中新出现的文件会通过VFS的refreshes被探测到。一次重新刷新操作可以使用VirtualFileManager.getInstance().refresh() 或VirtualFile.refresh()程序性的被引发。VFS的重新刷新也可以由文件系统监视器接收到的文件系统变更通知来引发(Windows和Mac操作系统可用)。

作为一个插件开发者,如果你需要访问一个刚刚被外部工具创建的文件,你可能需要通过IDEA的API手动地触发一次VFS重新刷新操作。

How long does it live?

要提供一个定制的文件系统实现(例如FTP文件系统),实现VirtualFileSystem类(你很可能也需要实现VirtualFile类)并将你的实现注册为一个application组件。要在本地文件系统中注入操作钩子(例如,如果你要开发一个需要自定义重命名/移动操作的版本控制系统整合插件),实现LocalFileOperationsHandler接口并通过LocalFileSystem.registerAuxiliaryFileOperationsHandler方法注册。

What are the rules for working with it?

查看IntelliJ IDEA Virtual File System以获得VFS架构与使用规则的详细描述。

Samples

一个说明如何使用虚拟文件的示例插件项目可以在<%IDEA project directory%>/community/samples/vfs和<%IDEA project directory%>/community/samples/textEditor文件夹下得到。

文档

一篇文档是一串可编辑的Unicode字符序列,典型的对应虚拟文件的字符内容。文档中的断行总是被标准化为\n。IntelliJ IDEA会在加载和保存文档时透明化地处理编码和换行符转换。

How do I get one?

  • 从action中:e.getData(PlatformDataKeys.EDITOR).getDocument()
  • 从一个虚拟文件中:FileDocumentManager.getDocument()。如果文档内容之前未加载,此调用会强制文档从磁盘加载内容;如果你只关心已经打开的文档或者文档可能已经被修改了,使用FileDocumentManager.getCachedDocument()替代前面的调用。
  • 从一个PSI文件中:PsiDocumentManager.getInstance().getDocument()或PsiDocumentManager.getInstance().getCachedDocument()

What can I do with one?

用来访问和修改“plain text”水平的文件内容(作为字符串而不是Java元素树)的任何操作。

Where does it come from?

文档实例在一些需要访问文件的文本内容的时候被创建(特别地,为文件构建PSI时需要创建文档实例)。另外,文档实例不会和任何可临时创建的虚拟文件关联,例如(文档不会)代理一个对话框的文本编辑域的内容。

How long does it live?

文档实例被对应的虚拟文件实例微弱的引用。因此,如果不再被引用,一篇未修改的文档实例可以被垃圾回收,而如果文档内容之后又再次被访问,新的实例将被创建。将文档引用保存在你的插件的持久数据结构中将导致内存泄漏。

How do I create one?

如果你需要在磁盘创建一个新文件,你不会创建一个文档:你创建一个PSI文件然后取出它的文档。如果你需要创建一个不需要绑定到任何对象上的文档实例,你可以使用EditorFactory.createDocument。

How do I get notified when it changes?

Document.addDocumentListener允许你接收指定文档实例的变更通知。

EditorFactory.getEventMulticaster().addDocumentListener允许你接收所有打开文档的变更通知。

FileDocumentManager.addFileDocumentManagerListener允许你接收任何文档保存到磁盘或从磁盘重新加载的通知。

What are the rules of working with it?

使用常规的读写操作规则。作为补充,任何修改文档内容的操作必须被封装成一个命令(CommandProcessor.getInstance().executeCommand())。executeCommand()调用可以被嵌套,而且最外层的executeCommand调用被添加到回退列表中。如果多个文档在一条命令中被修改了,回退这条命令将默认向用户展示一个确认对话框。

如果对应文档的文件是只读的(例如,未从版本控制系统中检出),文档修改将会失败。因此,在修改文档前,有必要调用ReadonlyStatusHandler.getInstance(project).ensureFilesWritable()来进行必要的文件检出。

所有传递给文档修改方法(setText, insertString, replaceString)的文本字符串必须使用\n作为换行符。

Samples

一个说明如何使用文档文件的示例插件项目可以在<%IDEA project directory%>/community/samples/textEditor文件夹下得到。更多信息,查阅Sample Text File Editor

PSI文件

一个PSI(Program Structure Interface)文件是将文件内容按照特定编程语言的元素层次结构相对应进行代理的根结构。PsiFile类是所有PSI文件的通用基类,而特定语言的文件常常由它的子类进行代理。例如,PsiJavaFile类代理一个Java文件,而XmlFile类代理一个XML文件。

与有application作用范围(一个即使在多个工程中打开,各展示实例也由同一个虚拟文件实例代理。注:此处原文直译:即使打开了多个工程,每个文件文件也由相同的虚拟文件实例代理。应该是原文表述有误)的虚拟文件和文档不同,PSI拥有project作用范围(如果一个文件所属的不同项目同时打开,这个文件会被不同的PsiFile实例代理)。

How do I get one?

  • 从action中:e.getData(LangDataKeys.PSI_FILE)
  • 从虚拟文件中:PsiManager.getInstance(project).findFile()
  • 从文档中:PsiDocumentManager.getInstance(project).getPsiFile()
  • 从文件中的一个元素:psiElement.getContainingFile()
  • 使用FilenameIndex.getFilesByName(project, name, scope)方法从项目任意位置找到一个有指定名称的文件

What can I do with one?

由于PSI是基于语言的,PSI文件是通过Language对象使用LanguageParserDefinitions.INSTANCE.forLanguage(language).createFile(fileViewProvider)方法创建的。

与文档相似,PSI文件是在访问特定文件时按需创建的。

How long does it live?

与文档相似,PSI文件被对应的虚拟文件实例微弱的引用,而当无人引用时会被垃圾回收。

How do I create one?

PsiFileFactory.getInstance(project).createFileFromText()方法使用指定内容创建一个内存PSI文件。使用PsiDirectory.add()方法将PSI文件保存到磁盘。

How do I get notified when it changes?

PsiManager.getInstance(project).addPsiTreeChangeListener()方法允许你接收项目中PSI树的所有变更通知。

How do I extend it?

PSI可以被在自定义语言插件中继承以支持额外的语言。开发这样的插件将在另一篇文章中展开讨论。

What are the rules for working with it?

所有对PSI文件内容的变更都作用到文档上,所以可以应用所有文档操作的规则(读写动作、命令、只读状态处理等)。

文件视图Provider

文件视图provider(参见类FileViewProvider)是IntelliJ IDEA 6.0版本中引入的一个新概念。它的主要目的是管理访问单一文件中不同的PSI树。例如,一个JSPX页面文件中包含:一个独立蝗Java代码PSI树(PsiJavaFile)、一个独立的XML代码结构树(XmlFile)、一个独立的全面的JSP结构树(JspFile)。每一个PSI树都覆盖了文件的全部内容,并且各自的视图中在包含不同语言的片段处都可以找到特别的“外部语言元素”。

一个FileViewProvider实例对应一个单独的虚拟文件、单独的文档,并用于取回多种PsiFile实例。

How do I get one?

  • 从虚拟文件中:PsiManager.getInstance(project).findViewProvider()
  • 从PSI文件中:psiFile.getViewProvider()

What can I do with one?

获取文件中存在的所有语言的PSI树列表:fileViewProvider.getLanguages()

获取指定语言的PSI树:fileViewProvider.getPsi(language),其中language参数可以取StdLanguages类中定义的Language类型候选值。例如,要获取XML的PSI树,使用代码:fileViewProvider.getPsi(StdLanguages.XML)。

要获取文件特定位置(偏移量)特定语言的元素:fileViewProvider.findElementAt(offset,language)

How do I extend it?

要创建一种内容穿插分布了不同语言结构树的文件类型,你的插件必需包含一个IntelliJ IDEA内核可访问的fileType.fileViewProviderFactory扩展点类型的扩展。这种扩展点使用FileTypeExtensionPoint类型的bean来声明。要访问声明的扩展点,创建一个Java类继承FileViewProviderFactory接口,在这个类中,重载(实现)createFileViewProvider方法。

要声明fileType.fileViewProviderFactory扩展点类型的扩展,在plugin.xml文件中的<extensions>小节增加如下语法化代码块:

 <fileType.fileViewProviderFactory filetype=%file type% implementationClass=%class name%>
 </fileType.fileViewProviderFactory>
其中%file type%指出要创建的文件类型(如JFS),然后%class name%指定你实现FileViewProviderFactory接口的Java类名称。

PSI元素

一个PSI文件代理PSI元素的层次结构(也被称作PSI树)。在一种特定的编程语言中,一个独立的PSI文件可能包含多个PSI树。一个PSI元素,反过来说,可能存在子PSI元素。

PSI元素和各个PSI元素层面上的操作被用来探索IntelliJ IDEA所解释的源代码的内部结构。例如,你可以用PSI元素进行像代码检查或代码修正猜测这样的代码分析。

PsiElement类是PSI元素的通用基类。

How do I get a PSI element?

  • 从Action中:e.getData(LangDataKeys.PSI_ELEMENT)注意:如果一个编辑器正被打开(焦点状态)而且光标下的元素是一个引用,这将返回引用对象的结果。这可能不是你需要的。
  • 从文件的偏移量:PsiFile.findElementAt()。注意:这将返回指定偏移位置的最底层元素,这常常是一个语法块。你很可能需要使用PsiTreeUtil.getParentOfType()方法找出你真正想要的元素。
  • 通过遍历一个PSI文件:使用一个PsiRecursiveElementWalkingVisitor实例
  • 通过解析一个引用:PsiReference.resolve()
  • 另可参见:PSI Cookbook

What can I do with one?

参见:PSI Cookbook


原文待续

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

IntelliJ IDEA 架构概述(面向插件开发者) 的相关文章

随机推荐

  • Linux下安装jdk配置报错:-bash: java: command not found

    可能是jdk解压有问题 xff0c 重新解压然后source etc profile xff1a 使配置生效 再 java version来查看配置成功没
  • IP地址段范围写法

    A类IP段 0 0 0 0 到127 255 255 255 B类IP段 128 0 0 0 到191 255 255 255 C类IP段 192 0 0 0 到223 255 255 255 XP默认分配的子网掩码每段只有255或0 xf
  • 有时间细读这些书

    1 Windows程序设计 第5版 珍藏版 xff1a 这是很经典的一本介绍Win32 API编程的书了 xff0c 基本介绍到了大多数关于Windows程序设计的基本内容 2 Windows程序设计 王艳平版 xff1a 这本和上一本的区
  • linux中systemctl详细理解及常用命令

    一 systemctl理解 Linux 服务管理两种方式service和systemctl systemd是Linux系统最新的初始化系统 init 作用是提高系统的启动速度 xff0c 尽可能启动较少的进程 xff0c 尽可能更多进程并发
  • Linux查看网速命令

    watch 34 ifconfig eth0 grep byte 34
  • 软件正在改变世界,程序员应该得到足够尊重

    软件无处不在 xff0c 越来越多的人离不开软件 xff0c 你打开电脑 xff0c 你使用手机 xff0c 你购物娱乐 软件一直在帮你 xff0c 软件已经渗透到我们的工作 生活 娱乐的方方面面 xff0c 软件每一天都在改变着这个世界
  • apache2.4 配置多个版本的 php7,php8)

    不多说 xff0c 直接上配置修改 httpd conf lt IfDefine php7 gt Listen 82 LoadFile 34 D php 7 2 34 libssh2 dll 34 LoadModule php7 modul
  • 叉乘怎么记忆,计算

    以一个例子直观记忆叉乘 xff1a 引用自 向量积 百度百科 baidu com 在这个式子中 xff0c 我们可以清楚地看到三项分别是i xff0c j xff0c k 前面则是他们的系数 我们可以直接把i xff0c j xff0c k
  • 接口防重方案设计

    幂等性原理 xff1a 前台的多次请求 xff0c 对于后台 xff0c 也是同一次请求 xff1b 通常接口设计方式 xff1a 1 前端的页面提交按钮置灰 xff0c 防止用户重复点击 xff1b 2 对前端提交的token进行校验 x
  • Spark Streaming 与 Kafka 集成分析

    前言 Spark Streaming 诞生于2013年 xff0c 成为Spark平台上流式处理的解决方案 xff0c 同时也给大家提供除Storm 以外的另一个选择 这篇内容主要介绍Spark Streaming 数据接收流程模块中与Ka
  • 微信小程序-轮播图实现

    好久不见 xff0c 今天小h来分享一下如何实现一个微信小程序的轮播图实现方式 xff1a 前提条件是具有微信开发者工具 xff0c 还有对应的开发者ID xff0c 这些基础条件我这边就直接跳过了哈 xff0c 直接进入正题 xff1a
  • 所以,到底什么是微服务?

    1 微服务是一种软件架构 xff0c 是聚焦在单一的职责和业务功能 xff0c 具有独立的进程 xff0c 能够单独运行的服务 xff0c 并且与外部服务是通过HTTP进行交互通信的服务 2 微服务比较常见的特性是 xff0c 具有单一职责
  • 关于云服务Bmob的使用方法(上)——上传数据

    关于第三方云服务平台Bmob是怎样使用的 xff1f 我们从两个方面来写 xff0c 一个是传输数据 xff0c 一个是传输文件 第一个是关于bmob传输数据的 xff0c 首先我们在官网http www bmob cn 上面注册我们自己的
  • 关于云服务Bmob的使用方法(下)——上传文件

    上一篇我们说了如何传输数据 xff0c 那么这一篇我们进阶一下 xff0c 来谈谈如何传输文件 xff0c 比如图片 关于如何在bmob上注册和申请 xff0c 上一篇已经有说明 xff0c 不懂的读者可以去看看 xff0c 然后我们直接进
  • 使用栈模拟递归的算法

    这一篇笔者要讲的是如何用栈来模拟递归 xff0c 或者说替代递归的算法 xff0c 现在我们假如要算从三角形数的叠加 xff0c 比如输入10 xff0c 输出是55 xff0c 输入是100 xff0c 输出是5050 xff0c 等等
  • java集合篇(一)——ArrayList扩容原理

    相信大家都对ArrayList相当熟悉了 xff0c 今天笔者就对ArrayList的源码进行解读 xff0c 讲解一下对ArrayList扩容的基本原理 虽然大家都有用过 xff0c 但还是简单介绍一下吧 xff0c ArrayList实
  • 怎样快速开发一个 Dubbo 应用?

    背景 本文将以 Dubbo 为例 xff0c 介绍如何快速开发一个 Dubbo 应用 为了便于读者理解 xff1a 首先会介绍一下传统的 RMI 的基本概念 然后比较下现代的 RPC 框架与 RMI 的区别 再基于 Dubbo 提供的 AP
  • 百度历届笔试题(1)

    题目描述 牛牛和妞妞正在玩一个猜数游戏 xff0c 妞妞心里想两个不相等的正数 xff0c 把这两个正数的和y告诉牛牛 妞妞声称这两个数都不超过x xff0c 让牛牛猜这两个数是多少 牛牛每猜一次 xff0c 妞妞会告诉他猜对了还是猜错了
  • systemd service 配置自启动,配置多个环境变量,最大打开文件数

    一 创建service文件 样例 vim usr lib systemd system nacos service Unit Description 61 nacos After 61 network target Service Type
  • IntelliJ IDEA 架构概述(面向插件开发者)

    原文未定稿 2014 11 这篇文章的目的是从插件开发者的视角描述IntelliJ IDEA的结构 文章将以一种任务驱动的方式组织 xff1a 相比列出所有你可以对各组件对象进行的操作并描述这些操作它们的实现方式 xff0c 这篇文章将尽可