idea源码学习记录-vfs

参考 https://plugins.jetbrains.com/docs/intellij/virtual-file-system.html

注: 我写笔记用的源码版本是232.8660.185 我的idea版本为241.17011.79 当前的官方文档用的版本是242.23339.11

vfs是idea的虚拟文件系统(Virtual File System)

The Virtual File System (VFS) is a component of the IntelliJ Platform that encapsulates most of its activity for working with files represented as Virtual File.
虚拟文件系统 (VFS) 是 IntelliJ 平台的一个组件,它封装了其大部分活动,用于处理表示为虚拟文件的文件。

It serves the following main purposes:
它的主要用途如下:

  • Providing a universal API for working with files regardless of their actual location (on disk, in an archive, on an HTTP server, etc.)
    提供一个通用 API,用于处理文件,无论其实际位置如何(在磁盘上、在存档中、在 HTTP 服务器上等)
  • Tracking file modifications and providing both old and new versions of the file content when a change is detected.
    跟踪文件修改,并在检测到更改时提供文件内容的旧版本和新版本。
  • Providing a possibility to associate additional persistent data with a file in the VFS.
    提供将其他持久数据与 VFS 中的文件相关联的可能性。

To provide the last two features, the VFS manages a persistent snapshot of some of the user's hard disk contents. The snapshot stores only those files which have been requested at least once through the VFS API, and is asynchronously updated to match the changes happening on the disk.
为了提供最后两项功能,VFS 管理用户某些硬盘内容的持久快照。快照仅存储那些通过 VFS API 至少请求过一次的文件,并异步更新以匹配磁盘上发生的更改。

The snapshot is application level, not project level - so, if some file (for example, a class in the JDK) is referenced by multiple projects, only one copy of its contents will be stored in the VFS.
快照是应用程序级别,而不是项目级别 - 因此,如果多个项目引用了某个文件(例如,JDK 中的类),则 VFS 中将仅存储其内容的一个副本。

All VFS access operations go through the snapshot.
所有 VFS 访问操作都通过快照。

必要的时候需要进行强制刷新文件操作 VirtualFile.refresh()

获取文件系统的方法

StandardFileSystems

com.intellij.openapi.vfs.StandardFileSystems

提供了以下两个静态方法

StandardFileSystems.local() 获取file://前缀的文件系统

StandardFileSystems.jar() 获取jar://前缀的文件系统

均是通过com.intellij.openapi.vfs.VirtualFileManager#getFileSystem获取对应的文件系统

VirtualFileManager的实例 目前看是用com.intellij.openapi.vfs.PlatformVirtualFileManager的实例

由于idea版本和源码版本这里关于通过Application获取com.intellij.openapi.vfs.PlatformVirtualFileManager的实例方式有变化无法判断这里的获取细节

不过看起来都是通过ApplicationService进行定义的类名 这里通过xml直接指定了实现

<!--intellij-community-idea-232.8660.185\platform\platform-resources\src\META-INF\PlatformExtensions.xml-->
 <applicationService serviceInterface="com.intellij.openapi.vfs.VirtualFileManager"
                     serviceImplementation="com.intellij.openapi.vfs.PlatformVirtualFileManager"/>

插一个补充 Services的概念

https://plugins.jetbrains.com/docs/intellij/plugin-services.html

A service is a plugin component loaded on demand when your plugin calls the getService() method of corresponding ComponentManager instance (see Types). The IntelliJ Platform ensures that only one instance of a service is loaded even though it is called several times. Services are used to encapsulate logic operating on a set of related classes or to provide some reusable functionality that can be used across the plugin project. Conceptually, they don't differ from the service classes in other languages or frameworks.
服务是当你的插件调用相应 ComponentManager 实例的 getService() 方法时,按需加载的插件组件(参见 类型)。IntelliJ Platform 确保仅加载一个服务实例,即使它被多次调用也是如此。服务用于封装对一组相关类进行操作的逻辑,或者提供一些可在整个插件项目中使用的可重用功能。从概念上讲,它们与其他语言或框架中的服务类没有区别。

A service must have an implementation class used for service instantiation. A service may also have an interface class used to obtain the service instance and provide the service's API.
服务必须具有用于服务实例化的 implementation 类。服务还可以具有用于获取服务实例和提供服务的 API 的接口类。

A service needing a shutdown hook/cleanup routine can implement Disposable and perform necessary work in dispose() (see Automatically Disposed Objects).
需要关闭钩子/清理例程的服务可以实现 Disposable 并在 dispose() 中执行必要的工作(参见 自动释放的对象)。

com.intellij.openapi.application.Application就是ComponentManager实例

并且可以通过ApplicationManager.getApplication()获取实例

其他文件系统

通过VirtualFileSystem.EP_NAME.extensionList可以查到目前我的idea用到的文件系统有

jar: com.intellij.openapi.vfs.impl.jar.JarFileSystemImpl@59c3d23a
temp: com.intellij.openapi.vfs.ex.temp.TempFileSystem@d3d86d9
dummy: com.intellij.openapi.vfs.ex.dummy.DummyFileSystem@1c521ba9
http: com.intellij.openapi.vfs.impl.http.HttpFileSystemImpl@4e68c190
https: com.intellij.openapi.vfs.impl.http.HttpsFileSystem@2b57e18d
vcs: com.intellij.openapi.vcs.vfs.VcsFileSystem@1801f8a2
vcs-log: com.intellij.vcs.log.ui.editor.VcsLogVirtualFileSystem@37788ed4
lazyAttachVfs: com.intellij.xdebugger.attach.fs.LazyAttachVirtualFS@390d0f97
uml: com.intellij.uml.UmlVirtualFileSystem@14868efe
nosql: org.bruce.tools.nosql.commons.view.editor.NoSqlDatabaseFileSystem@a1a7522
remoteDeploymentFS: com.jetbrains.plugins.webDeployment.remoteEdit.fs.RemoteDeploymentFileSystem@2de939d9
helm-tgz: com.intellij.kubernetes.helm.tgz.TgzFileSystem@7e06d211
git-compare-branches: git4idea.branch.GitCompareBranchesVirtualFileSystem@677ed8ac
gitIndexFs: git4idea.index.vfs.GitIndexFileSystem@5886686c
gitlabmr: org.jetbrains.plugins.gitlab.mergerequest.file.GitLabVirtualFileSystem@4582ce45
jrt: com.intellij.openapi.vfs.impl.jrt.JrtFileSystemImpl@60ce3de1
javaeeDummy: com.intellij.javaee.module.components.FrameworkVirtualFileSystem@4c39a557
testdata: org.jetbrains.idea.devkit.testAssistant.vfs.TestDataGroupFileSystem@14eceed4
gzip: com.github.camork.filesystem.gz.GZFileSystemImpl@81a67ea
tar: com.github.camork.filesystem.tar.TarFileSystemImpl@2c0bf518
7z: com.github.camork.filesystem.sevenzip.SevenZipFileSystemImpl@6b0dd1bc
zip: com.github.camork.filesystem.zip.ZipFileSystemImpl@4806d696
zst: com.github.camork.filesystem.zstd.ZstdFileSystem@222482f3
maven-properties: org.jetbrains.idea.maven.vfs.MavenPropertiesVirtualFileSystem@5c684247
ghpr: org.jetbrains.plugins.github.pullrequest.GHPRVirtualFileSystem@21da4512
vmscript: com.intellij.javascript.debugger.scripts.VmScriptFileSystem@778069c0
das: com.intellij.database.vfs.DatabaseVirtualFileSystem@27fd206
dbSrc: com.intellij.database.dataSource.srcStorage.DbSrcFileSystem@3b432676
android-dummy: com.android.tools.idea.editors.AndroidFakeFileSystem@163469e1
apk: com.android.tools.idea.apk.viewer.ApkFileSystem@6d598714
jupyter-remote: org.jetbrains.plugins.notebooks.jupyter.remote.vfs.JupyterRemoteFileSystemImpl@74c5d18f
notebook: org.jetbrains.plugins.notebooks.core.impl.file.NotebookVirtualFileSystem@eb2d65f
bined: org.exbin.bined.intellij.BinEdFileSystem@1ba312e0

查找对应类名可以发现是在此处定义的

intellij-community-idea-232.8660.185\platform\platform-resources\src\META-INF\PlatformExtensions.xml

    <virtualFileSystem implementationClass="com.intellij.openapi.vfs.impl.local.LocalFileSystemImpl" key="file" physical="true"/>
    <virtualFileSystem implementationClass="com.intellij.openapi.vfs.impl.jar.JarFileSystemImpl" key="jar" physical="true"/>
    <virtualFileSystem implementationClass="com.intellij.openapi.vfs.ex.temp.TempFileSystem" key="temp" physical="true"/>    
	<virtualFileSystem key="dummy" implementationClass="com.intellij.openapi.vfs.ex.dummy.DummyFileSystem"/>
    <virtualFileSystem key="http" implementationClass="com.intellij.openapi.vfs.impl.http.HttpFileSystemImpl"/>
    <virtualFileSystem key="https" implementationClass="com.intellij.openapi.vfs.impl.http.HttpsFileSystem"/>

intellij-community-idea-232.8660.185\platform\core-impl\resources\META-INF\CoreImpl.xml中定义了virtualFileSystem表示的什么扩展点

<extensionPoint name="virtualFileSystem" beanClass="com.intellij.openapi.vfs.impl.VirtualFileManagerImpl$VirtualFileSystemBean" dynamic="true">
 <with attribute="implementationClass" implements="com.intellij.openapi.vfs.VirtualFileSystem"/>
</extensionPoint>

参考扩展点相关内容

https://plugins.jetbrains.com/docs/intellij/plugin-extensions.html

https://plugins.jetbrains.com/docs/intellij/plugin-extension-points.html

https://plugins.jetbrains.com/docs/intellij/intellij-platform-extension-point-list.html

其中的beanClass

The beanClass attribute sets a bean class that specifies one or several properties annotated with the @Attribute annotation. Note that bean classes do not follow the JavaBean standard. Implement PluginAware to obtain information about the plugin providing the actual extension (see Error Handling).
beanClass 属性设置一个 Bean 类,该类指定一个或多个使用 @Attribute 注释注释的属性。请注意,Bean 类不遵循 JavaBean 标准。实现 PluginAware 以获取有关提供实际扩展的插件的信息(请参阅 错误处理)。

因此上述使用<virtualFileSystem/>的时候 指定了implementationClass key physical

这里是扩展点创建的定义

// intellij-community-idea-232.8660.185\platform\core-api\src\com\intellij\openapi\vfs\VirtualFileSystem.java
public static final ExtensionPointName<KeyedLazyInstance<VirtualFileSystem>> EP_NAME = ExtensionPointName.create("com.intellij.virtualFileSystem");

VirtualFileManagerImpl动态处理扩展为例

// intellij-community-idea-232.8660.185\platform\core-impl\src\com\intellij\openapi\vfs\impl\VirtualFileManagerImpl.java
private final KeyedExtensionCollector<VirtualFileSystem, String> myCollector = new KeyedExtensionCollector<>(VirtualFileSystem.EP_NAME);

public VirtualFileManagerImpl(@NotNull List<? extends VirtualFileSystem> preCreatedFileSystems, @NotNull MessageBus bus) {
   myPreCreatedFileSystems = new ArrayList<>(preCreatedFileSystems);

   for (VirtualFileSystem fileSystem : preCreatedFileSystems) {
     // 添加到扩展列表
     myCollector.addExplicitExtension(fileSystem.getProtocol(), fileSystem);
     if (!(fileSystem instanceof CachingVirtualFileSystem)) {
       fileSystem.addVirtualFileListener(myVirtualFileListenerMulticaster.getMulticaster());
     }
   }

   addVirtualFileListener(new LoggingListener());

   bus.connect().subscribe(VFS_CHANGES, new BulkVirtualFileListenerAdapter(myVirtualFileListenerMulticaster.getMulticaster()));
 }


public @NotNull List<VirtualFileSystem> getPhysicalFileSystems() {
   List<VirtualFileSystem> physicalFileSystems = new ArrayList<>(myPreCreatedFileSystems);
	
   // 获取扩展列表
   ExtensionPoint<KeyedLazyInstance<VirtualFileSystem>> point = myCollector.getPoint();
   if (point != null) {
     for (KeyedLazyInstance<VirtualFileSystem> bean : point.getExtensionList()) {
       if (((VirtualFileSystemBean)bean).physical) {
         VirtualFileSystem fileSystem = bean.getInstance();
         physicalFileSystems.add(fileSystem);
       }
     }
   }

   return physicalFileSystems;
 }


获取文件系统

// 通过虚拟文件管理根据key获取vfs
val vfsFile: VirtualFileSystem = VirtualFileManager.getInstance().getFileSystem("file")
// 通过已经预定义好的标准文件系统获取vfs 这里只能获取file和jar的文件系统
val vfsLocal: VirtualFileSystem = StandardFileSystems.local()

使用文件系统

以下是 VirtualFileSystem 类中所有 public 方法的列表:

  1. @NotNull String getProtocol(): 返回该文件系统的协议名称

  2. @Nullable VirtualFile findFileByPath(@NotNull @NonNls String path): 根据给定的路径查找文件 如果路径不存在则返回null

  3. @NotNull String extractPresentableUrl(@NotNull String path): 获取给定路径的可展示 URL

  4. void refresh(boolean asynchronous): 刷新文件系统中所有文件的缓存信息

  5. @Nullable VirtualFile refreshAndFindFileByPath(@NotNull String path): 刷新文件系统中与给定路径相关的部分,并查找该路径的文件

  6. void addVirtualFileListener(@NotNull VirtualFileListener listener): 添加文件系统的监听器

  7. void addVirtualFileListener(@NotNull VirtualFileListener listener, @NotNull Disposable disposable): 添加文件系统的监听器,并在 disposable 被释放时自动移除监听器

  8. void removeVirtualFileListener(@NotNull VirtualFileListener listener): 移除文件系统的监听器

  9. boolean isReadOnly(): 检查文件系统是否为只读

  10. boolean isCaseSensitive(): 检查文件系统是否区分大小写

  11. boolean isValidName(@NotNull String name): 检查给定的名称是否有效

  12. @Nullable Path getNioPath(@NotNull VirtualFile file): 返回与给定虚拟文件相关的 Path

通过文件系统获取虚拟文件文件夹

/*
 * 获取/创建 虚拟文件文件夹
 * @param [path] 文件夹路径
 * @param [vfs] 虚拟文件系统
 * @return [VirtualFile] 虚拟文件文件夹
 */
fun getOrMakeVirtualFileDir(path: String, vfs: VirtualFileSystem): VirtualFile {
  var dir = vfs.findFileByPath(path)
  if (dir == null) {
    // 如果路径不出错 这里是可以创建VirtualFile的
    dir = VfsUtil.createDirectoryIfMissing(vfs, path)!!
  }
  return dir
}
// 使用writeAction处理io写操作
fun getOrMakeVirtualFileDir(path: String, vfs: VirtualFileSystem): VirtualFile {
  var dir = vfs.findFileByPath(path)
  if (dir == null) {
    // 如果路径不出错 这里是可以创建VirtualFile的
    dir = WriteAction.computeAndWait<VirtualFile, IOException> {
      VfsUtil.createDirectoryIfMissing(vfs, path)
    }!!
  }
  return dir
}
// 或者用runWriteAction 看起来是WriteAction.computeAndWait的简化版本
fun getOrMakeVirtualFileDir(path: String, vfs: VirtualFileSystem): VirtualFile {
  var dir = vfs.findFileByPath(path)
  if (dir == null) {
    // 如果路径不出错 这里是可以创建VirtualFile的
    dir = runWriteAction {
      VfsUtil.createDirectoryIfMissing(vfs, path)!!
    }
  }
  return dir
}

// 如果不涉及其他文件系统的话 可以用vfsUtil直接处理
fun getOrMakeVirtualFileDirWithLocal(path: String): VirtualFile {
  var dir = VfsUtil.findFileByIoFile(File(path), true)
  if (dir == null) {
    // 如果路径不出错 这里是可以创建VirtualFile的
    dir = VfsUtil.createDirectories(path)
  }
  return dir as VirtualFile
}

虚拟文件VirtualFile

通过文件系统或者VfsUtil都可以获取到虚拟文件 虚拟文件的操作和ioFile很像

https://plugins.jetbrains.com/docs/intellij/virtual-file.html

A VirtualFile (VF) is the IntelliJ Platform's representation of a file in a Virtual File System (VFS).
VirtualFile (VF) 是 IntelliJ 平台在虚拟文件系统 (VFS) 中表示的文件。

Most commonly, a virtual file is a file in a local file system. However, the IntelliJ Platform supports multiple pluggable file system implementations, so virtual files can also represent classes in a JAR file, old revisions of files loaded from a version control repository, and so on.
最常见的是,虚拟文件是本地文件系统中的文件。但是,IntelliJ Platform 支持多个可插入文件系统实现,因此虚拟文件还可以表示 JAR 文件中的类、从版本控制存储库加载的文件的旧版本等。

The VFS level deals only with binary content. Contents of a VirtualFile are treated as a stream of bytes, but concepts like encodings and line separators are handled on higher system levels.
VFS 级别仅处理二进制内容。VirtualFile 的内容被视为字节流,但编码和行分隔符等概念在更高的系统级别上处理。

官方手册提供的获取方式是

Context API
Action AnActionEvent.getData(PlatformDataKeys.VIRTUAL_FILE)AnActionEvent.getData(PlatformDataKeys.VIRTUAL_FILE_ARRAY) for multiple selection
Document FileDocumentManager.getFile()
PSI File PsiFile.getVirtualFile() (may return null if the PSI file exists only in memory)
File Name FilenameIndex.getVirtualFilesByName()
Local File System Path LocalFileSystem.findFileByIoFile()VirtualFileManager.findFileByNioPath()/refreshAndFindFileByNioPath() (2020.2+)

可能用的到的一些方法

VirtualFile 类中,有许多常用的公共方法,这些方法可以被外部使用。以下是一些常用的公共方法,包括方法名、参数、返回值以及方法的功能描述:

以下是 VirtualFile 类中的 public 方法列表:

  1. getName()

    • 获取文件的名称。
  2. getNameSequence()

    • 获取文件名称的字符序列。
  3. getFileSystem()

    • 获取文件所属的 VirtualFileSystem
  4. getPath()

    • 获取文件的路径。
  5. toNioPath()

    • 获取与此文件相关的 Path
  6. getUrl()

    • 获取文件的 URL。
  7. getPresentableUrl()

    • 获取文件的可展示 URL。
  8. getExtension()

    • 获取文件的扩展名。
  9. getNameWithoutExtension()

    • 获取不带扩展名的文件名。
  10. rename(Object requestor, String newName)

    • 重命名文件。
  11. isWritable()

    • 检查文件是否可写。
  12. setWritable(boolean writable)

    • 设置文件的可写性。
  13. isDirectory()

    • 检查文件是否是目录。
  14. is(VFileProperty property)

    • 检查文件是否具有特定属性。
  15. getCanonicalPath()

    • 获取文件的规范路径。
  16. getCanonicalFile()

    • 获取文件的规范文件。
  17. isValid()

    • 检查文件是否有效。
  18. getParent()

    • 获取父文件。
  19. getChildren()

    • 获取子文件。
  20. findChild(String name)

    • 查找具有给定名称的子文件。
  21. findOrCreateChildData(Object requestor, String name)

    • 查找或创建子文件。
  22. getFileType()

    • 获取文件的 FileType
  23. findFileByRelativePath(String relPath)

    • 根据相对路径查找文件。
  24. createChildDirectory(Object requestor, String name)

    • 创建子目录。
  25. createChildData(Object requestor, String name)

    • 创建子文件。
  26. delete(Object requestor)

    • 删除文件。
  27. move(Object requestor, VirtualFile newParent)

    • 移动文件到另一个目录。
  28. copy(Object requestor, VirtualFile newParent, String copyName)

    • 复制文件到另一个目录。
  29. getCharset()

    • 获取文件的字符集。
  30. setCharset(Charset charset)

    • 设置文件的字符集。
  31. setCharset(Charset charset, Runnable whenChanged)

    • 设置文件的字符集,并在更改时执行操作。
  32. setCharset(Charset charset, Runnable whenChanged, boolean fireEventsWhenChanged)

    • 设置文件的字符集,并在更改时执行操作和触发事件。
  33. isCharsetSet()

    • 检查文件的字符集是否已设置。
  34. setBinaryContent(byte[] content)

    • 设置文件的二进制内容。
  35. setBinaryContent(byte[] content, long newModificationStamp, long newTimeStamp)

    • 设置文件的二进制内容,并更新修改时间戳。
  36. setBinaryContent(byte[] content, long newModificationStamp, long newTimeStamp, Object requestor)

    • 设置文件的二进制内容,并更新修改时间戳和请求者。

还有intellij-community-idea-232.8660.185\platform\testFramework\src\com\intellij\testFramework\utils\vfs\VirtualFileUtil.ktintellij-community-idea-232.8660.185\platform\core-api\src\com\intellij\openapi\vfs\VirtualFileUtil.kt对VirtualFile扩展的方法

  1. isFile

    • 描述: 检查 VirtualFile 是否是一个有效的文件(不是目录)。
    • 用法: val isFile: Boolean
  2. readText()

    • 描述: 读取 VirtualFile 的文本内容。
    • 返回值: String
    • 用法: fun VirtualFile.readText(): String
  3. writeText(content: String)

    • 描述: 将文本内容写入 VirtualFile
    • 参数: content: String
    • 用法: @RequiresWriteLock fun VirtualFile.writeText(content: String)
  4. readBytes()

    • 描述: 读取 VirtualFile 的字节内容。
    • 返回值: ByteArray
    • 用法: fun VirtualFile.readBytes(): ByteArray
  5. writeBytes(content: ByteArray)

    • 描述: 将字节内容写入 VirtualFile
    • 参数: content: ByteArray
    • 用法: @RequiresWriteLock fun VirtualFile.writeBytes(content: ByteArray)
  6. toNioPathOrNull()

    • 描述: 尝试将 VirtualFile 转换为 Path,如果失败则返回 null
    • 返回值: Path?
    • 用法: fun VirtualFile.toNioPathOrNull(): Path?
  7. findDocument()

    • 描述: 查找与 VirtualFile 关联的 Document
    • 返回值: Document?
    • 用法: @RequiresReadLock fun VirtualFile.findDocument(): Document?
  8. findPsiFile(project: Project)

    • 描述: 查找与 VirtualFile 关联的 PsiFile
    • 参数: project: Project
    • 返回值: PsiFile?
    • 用法: @RequiresReadLock fun VirtualFile.findPsiFile(project: Project): PsiFile?
  9. findFileOrDirectory(relativePath: String)

    • 描述: 根据相对路径查找文件或目录。
    • 参数: relativePath: String
    • 返回值: VirtualFile?
    • 用法: @RequiresReadLock fun VirtualFile.findFileOrDirectory(relativePath: String): VirtualFile?
  10. findFile(relativePath: String)

    • 描述: 根据相对路径查找文件。
    • 参数: relativePath: String
    • 返回值: VirtualFile?
    • 用法: @RequiresReadLock fun VirtualFile.findFile(relativePath: String): VirtualFile?
  11. findDirectory(relativePath: String)

    • 描述: 根据相对路径查找目录。
    • 参数: relativePath: String
    • 返回值: VirtualFile?
    • 用法: @RequiresReadLock fun VirtualFile.findDirectory(relativePath: String): VirtualFile?
  12. findOrCreateFile(relativePath: String)

    • 描述: 根据相对路径查找或创建文件。
    • 参数: relativePath: String
    • 返回值: VirtualFile
    • 用法: @RequiresWriteLock fun VirtualFile.findOrCreateFile(relativePath: String): VirtualFile
  13. findOrCreateDirectory(relativePath: String)

    • 描述: 根据相对路径查找或创建目录。
    • 参数: relativePath: String
    • 返回值: VirtualFile
    • 用法: @RequiresWriteLock fun VirtualFile.findOrCreateDirectory(relativePath: String): VirtualFile
  14. getDocument()

    • 描述: 获取与 VirtualFile 关联的 Document,如果不存在则抛出异常。
    • 返回值: Document
    • 用法: @RequiresReadLock fun VirtualFile.getDocument(): Document
  15. getPsiFile(project: Project)

    • 描述: 获取与 VirtualFile 关联的 PsiFile
    • 参数: project: Project
    • 返回值: PsiFile
    • 用法: @RequiresReadLock fun VirtualFile.getPsiFile(project: Project): PsiFile
  16. getFile(relativePath: String)

    • 描述: 根据相对路径获取文件,如果不存在则抛出异常。
    • 参数: relativePath: String
    • 返回值: VirtualFile
    • 用法: @RequiresReadLock fun VirtualFile.getFile(relativePath: String): VirtualFile
  17. getDirectory(relativePath: String)

    • 描述: 根据相对路径获取目录,如果不存在则抛出异常。
    • 参数: relativePath: String
    • 返回值: VirtualFile
    • 用法: @RequiresReadLock fun VirtualFile.getDirectory(relativePath: String): VirtualFile
  18. createFile(relativePath: String)

    • 描述: 根据相对路径创建文件,如果文件已存在则抛出异常。
    • 参数: relativePath: String
    • 返回值: VirtualFile
    • 用法: @RequiresWriteLock fun VirtualFile.createFile(relativePath: String): VirtualFile
  19. createDirectory(relativePath: String)

    • 描述: 根据相对路径创建目录,如果目录已存在则抛出异常。
    • 参数: relativePath: String
    • 返回值: VirtualFile
    • 用法: @RequiresWriteLock fun VirtualFile.createDirectory(relativePath: String): VirtualFile
  20. deleteRecursively()

    • 描述: 递归删除 VirtualFile
    • 用法: @RequiresWriteLock fun VirtualFile.deleteRecursively()
  21. deleteChildrenRecursively(predicate: (VirtualFile) -> Boolean)

    • 描述: 递归删除满足条件的子文件。
    • 参数: predicate: (VirtualFile) -> Boolean
    • 用法: @RequiresWriteLock fun VirtualFile.deleteChildrenRecursively(predicate: (VirtualFile) -> Boolean)
  22. deleteRecursively(relativePath: String)

    • 描述: 根据相对路径递归删除文件或目录。
    • 参数: relativePath: String
    • 用法: @RequiresWriteLock fun VirtualFile.deleteRecursively(relativePath: String)
  23. deleteChildrenRecursively(relativePath: String, predicate: (VirtualFile) -> Boolean)

    • 描述: 根据相对路径递归删除满足条件的子文件。
    • 参数: relativePath: String, predicate: (VirtualFile) -> Boolean
    • 用法: @RequiresWriteLock fun VirtualFile.deleteChildrenRecursively(relativePath: String, predicate: (VirtualFile) -> Boolean)
  24. refreshAndGetVirtualFile()

    • 描述: 刷新并获取 Path 对应的 VirtualFile,如果不存在则抛出异常。
    • 返回值: VirtualFile
    • 用法: fun Path.refreshAndGetVirtualFile(): VirtualFile
  25. refreshAndGetVirtualDirectory()

    • 描述: 刷新并获取 Path 对应的 VirtualDirectory,如果不存在则抛出异常。
    • 返回值: VirtualFile
    • 用法: fun Path.refreshAndGetVirtualDirectory(): VirtualFile

代码片段

var vDir: VirtualFile = VfsUtil.findFileByIoFile(File(myPathStr),true)!!
val myFilePath = "test.txt"
// 读文件内容
runReadAction {
  val vfile = vDir.findOrCreateFile(myFilePath)
  val text = vfile.readText()
  // 输出到控制台
  PluginUtil.showInConsole(
    """
  | vfm:${vfm},vfsLocal:${vfsLocal}
  | 
  | ${text}
  |
""".trimMargin(), project!!
  )
}
// 写入文件内容
runWriteActionAndWait {
  val vfile = vDir.findOrCreateFile(myFilePath)
  vfile.writeText("hello world")
}

整理一下

通过VirtualFileManager根据扩展点加载所有的VirtualFileSystem

FileSystem看起来是整体上去对文件进行管理的 提供的常用方法是用来查询IO文件的 刷新IO文件的 以及一些文件操作的监听(VirtualFileListener)

以一个虚拟文件的创建为例 在虚拟文件的文件夹中创建一个文件

com.intellij.testFramework.utils.vfs.VirtualFileUtilKt#createFile方法的调用

这里虚拟文件系统应该是com.intellij.openapi.vfs.impl.local.LocalFileSystemImpl

虚拟文件的实现类应该是com.intellij.openapi.vfs.newvfs.impl.VirtualFileImpl

虚拟文件文件夹的实现类com.intellij.openapi.vfs.newvfs.impl.VirtualDirectoryImpl

这里需要一个虚拟文件文件夹 this应该是VirtualDirectoryImpl 以下的逻辑梳理基于这个最初的this

@RequiresWriteLock
fun VirtualFile.createFile(relativePath: @SystemIndependent String): VirtualFile {
  // 查询文件是否存在 不存在才能进行创建文件
  val file = findFile(relativePath)
  if (file != null) {
    throw IOException("""
      |File already exists: $file
      |  basePath = $path
      |  relativePath = $relativePath
    """.trimMargin())
  }
    // 调用文件创建方法
  return findOrCreateFile(relativePath)
}

先看查询文件是否存在的逻辑findFile

@RequiresReadLock
fun VirtualFile.findFile(relativePath: @SystemIndependent String): VirtualFile? {
  val file = findFileOrDirectory(relativePath) ?: return null
  if (!file.isFile) {
    throw IOException("""
      |Expected file instead of directory: $file
      |  basePath = $path
      |  relativePath = $relativePath
    """.trimMargin())
  }
  return file
}

@RequiresReadLock
fun VirtualFile.findFileOrDirectory(relativePath: @SystemIndependent String): VirtualFile? {
  return getResolvedVirtualFile(relativePath) { name, _ ->
    findChild(name) ?: return null // return from findFileOrDirectory
  }
}

/**
 * 获取解析后的虚拟文件
 * @param [relativePath] 相对路径
 * @param [getChild] 获取child方法
 * @return [VirtualFile] 虚拟文件
 */
private inline fun VirtualFile.getResolvedVirtualFile(
  relativePath: String,
  getChild: VirtualFile.(String, Boolean) -> VirtualFile
): VirtualFile {
  // 处理路径获取文件夹和标准化的相对路径
  val (baseVirtualFile, normalizedRelativePath) = relativizeToClosestAncestor(relativePath)
  var virtualFile = baseVirtualFile
  if (normalizedRelativePath.pathString.isNotEmpty()) {
    val names = normalizedRelativePath.map { it.pathString }
    for ((i, name) in names.withIndex()) {
      if (!virtualFile.isDirectory) {
        throw IOException("""
          |Expected directory instead of file: $virtualFile
          |  basePath = $path
          |  relativePath = $relativePath
        """.trimMargin())
      }
      // 用getChild去查找子文件
      virtualFile = virtualFile.getChild(name, i == names.lastIndex)
    }
  }
  return virtualFile
}

  /**
   * Finds child of this file with the given name. The returned file is guaranteed to be valid, if the method is called in a read action.
   * 查找具有给定名称的此文件的子项。如果在读取操作中调用该方法,则返回的文件保证有效。
   *
   * @param name the file name to search by
   * @return the file if found any, {@code null} otherwise
   * @throws InvalidVirtualFileAccessException if this method is called inside read action on an invalid file
   */
  public @Nullable VirtualFile findChild(@NotNull @NonNls String name) {
    VirtualFile[] children = getChildren();
    if (children == null) return null;
    for (VirtualFile child : children) {
      if (child.nameEquals(name)) {
        return child;
      }
    }
    return null;
  }

  /*
   * VirtualDirectoryImpl#getChildren
   */
  @Override
  public VirtualFile @NotNull [] getChildren() {
    // 处理无效目录
    if (!isValid()) {
      return handleInvalidDirectory(EMPTY_ARRAY);
    }
    // 所有的已经被加载(疑似和缓存相关)
    //  缓存应该是在 VfsData.DirectoryData myData 属性中
    if (allChildrenLoaded()) {
      return getArraySafely(true);
    }
    // 加载所有的children(涉及遍历目录和缓存处理 逻辑复杂不详细记录了)
    return loadAllChildren();
  }

 

注: com.intellij.openapi.vfs.newvfs.impl.VirtualDirectoryImpl#createChildImpl创建一个虚拟文件对象用的方法 内部实际调用了com.intellij.openapi.vfs.newvfs.impl.VfsData#getFileById去获取虚拟文件对象

这里会根据是否是文件夹 返回VirtualDirectoryImpl或者VirtualFileImpl 但是这里看起来没有IO交互 可能是因为com.intellij.openapi.vfs.newvfs.impl.VirtualDirectoryImpl#loadAllChildren调用的 此时应该已经和IO交互过了 这里传参看起来都是int类型的id 疑似和缓存有关系 看起来和IO交互的时候都会涉及FileSystem的使用

VfsData是个相关的核心类 看起来虚拟文件的信息数据都在其中记录和缓存
也可能是做了一个对象存储设计

再看文件创建逻辑findOrCreateFile

@RequiresWriteLock
fun VirtualFile.findOrCreateFile(relativePath: @SystemIndependent String): VirtualFile {
  // 解析虚拟文件
  val file = getResolvedVirtualFile(relativePath) { name, isLast ->
    findChild(name) ?: when (isLast) {
      true -> createChildData(fileSystem, name) // 创建文件
      else -> createChildDirectory(fileSystem, name) // 创建文件夹
    }
  }
  if (!file.isFile) {
    throw IOException("""
      |Expected file instead of directory: $file
      |  basePath = $path
      |  relativePath = $relativePath
    """.trimMargin())
  }
  return file
}

  /**
   * Creates a new file in this directory. This method should be only called within write-action.
   * 在此目录中创建新文件。此方法只能在写入操作中调用。
   * See {@link Application#runWriteAction}.
   *
   * @param requestor any object to control who called this method. Note that
   *                  it is considered to be an external change if {@code requestor} is {@code null}.
   *                  See {@link VirtualFileEvent#getRequestor}
   * @return {@code VirtualFile} representing the created file
   * @throws IOException if file failed to be created
   */
  public @NotNull VirtualFile createChildData(Object requestor, @NotNull @NonNls String name) throws IOException {
    if (!isDirectory()) {
      throw new IOException(CoreBundle.message("file.create.wrong.parent.error"));
    }

    if (!isValid()) {
      throw new IOException(CoreBundle.message("invalid.directory.create.files"));
    }

    if (!getFileSystem().isValidName(name)) {
      throw new IOException(CoreBundle.message("file.invalid.name.error", name));
    }

    if (findChild(name) != null) {
      throw new IOException(CoreBundle.message("file.create.already.exists.error", getUrl(), name));
    }
	// 通过文件系统创建文件 私有方法外部无法直接调用
    return getFileSystem().createChildFile(requestor, this, name);
  }

 /**
   * LocalFileSystemBase#createChildFile
   */
  @Override
  public @NotNull VirtualFile createChildFile(Object requestor, @NotNull VirtualFile parent, @NotNull String name) throws IOException {
    if (!isValidName(name)) {
      throw new IOException(CoreBundle.message("file.invalid.name.error", name));
    }

    if (!parent.exists() || !parent.isDirectory()) {
      throw new IOException(IdeCoreBundle.message("vfs.target.not.directory.error", parent.getPath()));
    }

    // 文件IO
    File ioParent = convertToIOFile(parent);
    if (!ioParent.isDirectory()) {
      throw new IOException(IdeCoreBundle.message("target.not.directory.error", ioParent.getPath()));
    }

    if (!auxCreateFile(parent, name)) {
      // 通过IOFile创建文件
      File ioFile = new File(ioParent, name);
      VirtualFile existing = parent.findChild(name);
      boolean created = FileUtilRt.createIfNotExists(ioFile);
      if (!created) {
        if (existing != null) {
          throw new IOException(IdeCoreBundle.message("vfs.target.already.exists.error", parent.getPath() + "/" + name));
        }

        throw new IOException(IdeCoreBundle.message("new.file.failed.error", ioFile.getPath()));
      }
      else if (existing != null) {
        // wow, IO created file successfully even though it already existed in VFS. Maybe we got dir case sensitivity wrong?
        boolean oldCaseSensitive = parent.isCaseSensitive();
        FileAttributes.CaseSensitivity actualSensitivity = FileSystemUtil.readParentCaseSensitivity(new File(existing.getPath()));
        // 处理异步刷新
        if ((actualSensitivity == FileAttributes.CaseSensitivity.SENSITIVE) != oldCaseSensitive) {
          // we need to update case sensitivity
          VFilePropertyChangeEvent event = VirtualDirectoryImpl.generateCaseSensitivityChangedEvent(parent, actualSensitivity);
          if (event != null) {
            RefreshQueue.getInstance().processEvents(false, List.of(event));
          }
        }
      }
    }

    auxNotifyCompleted(handler -> handler.createFile(parent, name));

    // 伪虚拟文件
    return new FakeVirtualFile(parent, name);
  }

posted @ 2024-10-05 21:00  Yao_xi  阅读(15)  评论(0编辑  收藏  举报