IDEA Plugin API
文件操作
Virtual File System
Virtual File System
是处理文件的一套机制, 用于处理如何加载文件, 如果保存文件, 当文件变化时如何更新缓存等.
IntelliJ Platform 将操作文件封装成了 Virtual File System
, 提供了以下几点主要功能:
- 封装处理文件的通用 API, 不论文件在磁盘, 存档, HTTP 服务器或者其他地方, 都使用同一套 API;
- 提供快照功能, 能跟踪文件的修改;
- 提供将附加持久数据与 VFS 中的文件相关联;
为了提供最后两个功能,VFS 管理用户硬盘的某些内容的持久快照。快照仅存储通过 VFS API 至少请求过一次的文件,并且异步更新以匹配磁盘上发生的更改。
快照是应用程序级别,而不是项目级别 - 因此,如果某个文件(例如,JDK 中的某个类)被多个项目引用,则其内容的一个副本将存储在 VFS 中。
所有 VFS 访问操作都通过快照。
如果通过 VFS API
请求某些信息但快照中没有这些信息,则会从磁盘加载并存储到快照中。如果快照中有可用信息,则返回快照数据。仅当访问了特定信息时,文件的内容和目录中的文件列表才存储在快照中 -
否则,仅存储名称,长度,时间戳,属性等文件元数据。
这意味着 IntelliJ Platform UI 中显示的文件系统状态和文件内容来自快照,快照可能并不总是与磁盘的实际内容相匹配。
例如, 在某些情况下,在 IntelliJ 平台选择删除之前,已删除的文件仍可在 UI 中显示一段时间。
在刷新操作期间从磁盘更新快照,这通常是异步发生的。通过 VFS 进行的所有写操作都是同步的 - 即内容立即保存到磁盘。
刷新操作将 VFS 的一部分状态与实际磁盘内容同步。IntelliJ 平台或插件代码显式调用刷新操作 - 即在 IDE 运行时在磁盘上更改文件时,VFS 不会立即获取更改。VFS
将在下一次刷新操作期间更新,其中包括其范围内的文件。
Virtual File
用于表示 Virtual File System
中的一个具体文件, 相当于本地系统文件, 也用于表示 jar 包中的文件中的类, 还可以表示版本管理中的旧文件.
VFS
仅处理二进制内容
获取 VirtualFile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| private void getVirtualFile(AnActionEvent e) { VirtualFile virtualFile = e.getData(PlatformDataKeys.VIRTUAL_FILE); VirtualFile[] virtualFiles = e.getData(PlatformDataKeys.VIRTUAL_FILE_ARRAY); VirtualFile virtualFileFromLocalFileSystem = LocalFileSystem.getInstance().findFileByIoFile(new File("path")); PsiFile psiFile = e.getData(CommonDataKeys.PSI_FILE); if (psiFile != null) { psiFile.getVirtualFile(); } Document document = Objects.requireNonNull(e.getData(PlatformDataKeys.EDITOR)).getDocument(); VirtualFile virtualFileFromDocument = FileDocumentManager.getInstance().getFile(document); }
|
遍历文件系统
遍历文件可以使用 File 的 API, 我们也可以通过 VFS
提供的 API 来实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| private void iterateChildrenRecursively(VirtualFile virtualFile) {
VfsUtilCore.iterateChildrenRecursively(virtualFile, new VirtualFileFilter() { @Override public boolean accept(VirtualFile file) { boolean allowAccept = file.isDirectory()&& !file.getName().equals(NODE_MODULES_FILE); if(allowAccept || file.getName().endsWith(MARKDOWN_FILE_TYPE)){ log.trace("accept = {}", file.getPath()); return true; } return false; } }, new ContentIterator() { @Override public boolean processFile(@NotNull VirtualFile fileOrDir) { if(!fileOrDir.isDirectory()){ log.trace("processFile = {}", fileOrDir.getName()); } return true; } }); }
|
ContentIterator
接口表示处理文件的具体方式, 需要自己实现.
由于在过滤器中已经将非 markdown 文件过滤掉, 因此此处只需要实现处理 markdown 文件的逻辑即可.
Document
Document 是可编辑的 Unicode 字符序列, 对应的是 VirtualFile
中的文本内容.
文档中的换行符 始终 为 \n
.
可以通过 Document 对文件做任何操作.
获取 Document
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| private void getDocument(AnActionEvent e){ Document documentFromEditor = Objects.requireNonNull(e.getData(PlatformDataKeys.EDITOR)).getDocument(); VirtualFile virtualFile = e.getData(PlatformDataKeys.VIRTUAL_FILE); if (virtualFile != null) { Document documentFromVirtualFile = FileDocumentManager.getInstance().getDocument(virtualFile); Document documentFromVirtualFileCache = FileDocumentManager.getInstance().getCachedDocument(virtualFile);
Project project = e.getProject(); if (project != null) { PsiFile psiFile = PsiManager.getInstance(project).findFile(virtualFile); psiFile = e.getData(CommonDataKeys.PSI_FILE); if (psiFile != null) { Document documentFromPsi = PsiDocumentManager.getInstance(project).getDocument(psiFile); Document documentFromPsiCache = PsiDocumentManager.getInstance(project).getCachedDocument(psiFile); } } } }
|
对 Document 操作时一定要注意内存泄漏的问题, 因为每次访问文件的 Document 对象是, 都是一个新的实例, 使用完成后一定要记得释放引用.
创建 Document
如果需要在磁盘上创建新文件, 不要直接创建 Document
, 而是先创建 PSI
文件,
然后获取它的 Document
.
如果需要创建一个没有绑定的 Document
的实例, 可以使用 EditorFactory.createDocument
.
Document Listener
- 接收有关特定
Document
实例中的更改的通知
1
| Document.addDocumentListener
|
1
| EditorFactory.getEventMulticaster().addDocumentListener
|
write Document
SDK 规定所有写操作必须通过异步执行, 因此需要将写操作包装到 command 中
1
| CommandProcessor.getInstance().executeCommand()
|
比如:
1 2 3 4 5 6
| WriteCommandAction.runWriteCommandAction(project, () -> { document.setText(string); psiDocumentManager.doPostponedOperationsAndUnblockDocument(document); psiDocumentManager.commitDocument(document); FileDocumentManager.getInstance().saveDocument(document); });
|
Editor
Editor 相关 API
editor-ui-api package,Editor.java,EditorImpl.java.CommonDataKeys.java,DataKey.java,AnActionEvent,DataContext
PSI
获取 PSI
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| private void getPsiFile(AnActionEvent e){ PsiFile psiFileFromAction = e.getData(LangDataKeys.PSI_FILE); Project project = e.getProject(); if (project != null) { VirtualFile virtualFile = e.getData(PlatformDataKeys.VIRTUAL_FILE); if (virtualFile != null) { PsiFile psiFileFromVirtualFile = PsiManager.getInstance(project).findFile(virtualFile);
Document documentFromEditor = Objects.requireNonNull(e.getData(PlatformDataKeys.EDITOR)).getDocument(); PsiFile psiFileFromDocument = PsiDocumentManager.getInstance(project).getPsiFile(documentFromEditor);
FilenameIndex.getFilesByName(project, "fileName", GlobalSearchScope.projectScope(project)); } } }
|
如果我知道它的名字但不知道路径,我如何找到文件?
FilenameIndex.getFilesByName()
如何找到特定 PSI 元素的使用位置?
ReferencesSearch.search()
如何重命名 PSI 元素?
RefactoringFactory.createRename()
如何重建虚拟文件的 PSI?
FileContentUtil.reparseFiles()
Java 特定
如何找到类的所有继承者?
ClassInheritorsSearch.search()
如何通过限定名称查找课程?
JavaPsiFacade.findClass()
如何通过短名称找到一个班级?
PsiShortNamesCache.getInstance().getClassesByName()
如何找到 Java 类的超类?
PsiClass.getSuperClass()
如何获取对 Java 类的包含的引用?
1 2
| PsiJavaFile javaFile = (PsiJavaFile) psiClass.getContaningFile(); PsiPackage pkg = JavaPsiFacade.getInstance(project).findPackage(javaFile.getPackageName());
|
如何找到覆盖特定方法的方法
OverridingMethodsSearch.search()