3

Intellij IDEA Plugin DEV (九)

 3 years ago
source link: https://dong4j.github.io/views/tools/2019/03191253.html#java%E7%89%B9%E5%AE%9A
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

IDEA Plugin API

# 文件操作

# Virtual File System

Virtual File System 是处理文件的一套机制, 用于处理如何加载文件, 如果保存文件, 当文件变化时如何更新缓存等.

IntelliJ Platform 将操作文件封装成了 Virtual File System, 提供了以下几点主要功能:

  1. 封装处理文件的通用 API, 不论文件在磁盘, 存档, HTTP 服务器或者其他地方, 都使用同一套 API;
  2. 提供快照功能, 能跟踪文件的修改;
  3. 提供将附加持久数据与 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

private void getVirtualFile(AnActionEvent e) {
    // 获取 VirtualFile 方式一:
    VirtualFile virtualFile = e.getData(PlatformDataKeys.VIRTUAL_FILE);
    // 获取多个 VirtualFile
    VirtualFile[] virtualFiles = e.getData(PlatformDataKeys.VIRTUAL_FILE_ARRAY);
    // 方式二: 从本地文件系统路径获取
    VirtualFile virtualFileFromLocalFileSystem = LocalFileSystem.getInstance().findFileByIoFile(new File("path"));
    // 方式三: 从 PSI 文件 (如果 PSI 文件仅存在内存中, 则可能返回 null)
    PsiFile psiFile = e.getData(CommonDataKeys.PSI_FILE);
    if (psiFile != null) {
        psiFile.getVirtualFile();
    }
    // 方式四: 从 document 中获取
    Document document = Objects.requireNonNull(e.getData(PlatformDataKeys.EDITOR)).getDocument();
    VirtualFile virtualFileFromDocument = FileDocumentManager.getInstance().getFile(document);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

-w1256

# 遍历文件系统

遍历文件可以使用 File 的 API, 我们也可以通过 VFS 提供的 API 来实现

private void iterateChildrenRecursively(VirtualFile virtualFile) {
    /**
     * 递归遍历子文件
     *
     * @param root     the root         父文件
     * @param filter   the filter       过滤器
     * @param iterator the iterator     处理方式
     * @return the boolean
     */
    VfsUtilCore.iterateChildrenRecursively(
       virtualFile,
       new VirtualFileFilter() {
           @Override
           public boolean accept(VirtualFile file) {
               // todo-dong4j : (2019年03月15日 13:02) [从 .gitignore 中获取忽略的文件]
               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) {
               // todo-dong4j : (2019年03月15日 13:04) [处理 markdown 逻辑实现]
               if(!fileOrDir.isDirectory()){
                   log.trace("processFile = {}", fileOrDir.getName());
               }
               return true;
           }
       });
}
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
34

ContentIterator 接口表示处理文件的具体方式, 需要自己实现.

由于在过滤器中已经将非 markdown 文件过滤掉, 因此此处只需要实现处理 markdown 文件的逻辑即可.

-w1165

# Document

Document 是可编辑的 Unicode 字符序列, 对应的是 VirtualFile 中的文本内容.

文档中的换行符 始终\n.

可以通过 Document 对文件做任何操作.

# 获取 Document

private void getDocument(AnActionEvent e){
    // 从当前编辑器中获取
    Document documentFromEditor = Objects.requireNonNull(e.getData(PlatformDataKeys.EDITOR)).getDocument();
    // 从 VirtualFile 获取 (如果之前未加载文档内容, 则此调用会强制从磁盘加载文档内容)
    VirtualFile virtualFile = e.getData(PlatformDataKeys.VIRTUAL_FILE);
    if (virtualFile != null) {
        Document documentFromVirtualFile = FileDocumentManager.getInstance().getDocument(virtualFile);
        // 从缓存中获取
        Document documentFromVirtualFileCache = FileDocumentManager.getInstance().getCachedDocument(virtualFile);

        // 从 PSI 中获取
        Project project = e.getProject();
        if (project != null) {
            // 获取 PSI (一)
            PsiFile psiFile = PsiManager.getInstance(project).findFile(virtualFile);
            // 获取 PSI (二)
            psiFile = e.getData(CommonDataKeys.PSI_FILE);
            if (psiFile != null) {
                Document documentFromPsi = PsiDocumentManager.getInstance(project).getDocument(psiFile);
                // 从缓存中获取
                Document documentFromPsiCache = PsiDocumentManager.getInstance(project).getCachedDocument(psiFile);
            }
        }
    }
}
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

-w1188

对 Document 操作时一定要注意内存泄漏的问题, 因为每次访问文件的 Document 对象是, 都是一个新的实例, 使用完成后一定要记得释放引用.

# 创建 Document

如果需要在磁盘上创建新文件, 不要直接创建 Document, 而是先创建 PSI文件, 然后获取它的 Document.

如果需要创建一个没有绑定的 Document 的实例, 可以使用EditorFactory.createDocument.

# Document Listener

  • 接收有关特定 Document 实例中的更改的通知
Document.addDocumentListener
  • 接收有关所有打开文档中的更改的通知
EditorFactory.getEventMulticaster().addDocumentListener

# write Document

SDK 规定所有写操作必须通过异步执行, 因此需要将写操作包装到 command 中

CommandProcessor.getInstance().executeCommand()
WriteCommandAction.runWriteCommandAction(project, () -> {
    document.setText(string);
    psiDocumentManager.doPostponedOperationsAndUnblockDocument(document);
    psiDocumentManager.commitDocument(document);
    FileDocumentManager.getInstance().saveDocument(document);
});
1
2
3
4
5
6

# Editor

Editor 相关 API

editor-ui-api packageEditor.javaEditorImpl.javaCommonDataKeys.javaDataKey.javaAnActionEventDataContext

# PSI

# 获取 PSI

private void getPsiFile(AnActionEvent e){
    // 从 action 中获取
    PsiFile psiFileFromAction = e.getData(LangDataKeys.PSI_FILE);
    Project project = e.getProject();
    if (project != null) {
        VirtualFile virtualFile = e.getData(PlatformDataKeys.VIRTUAL_FILE);
        if (virtualFile != null) {
            // 从 VirtualFile 获取
            PsiFile psiFileFromVirtualFile = PsiManager.getInstance(project).findFile(virtualFile);

            // 从 document
            Document documentFromEditor = Objects.requireNonNull(e.getData(PlatformDataKeys.EDITOR)).getDocument();
            PsiFile psiFileFromDocument = PsiDocumentManager.getInstance(project).getPsiFile(documentFromEditor);

            // 在 project 范围内查找特定 PsiFile
            FilenameIndex.getFilesByName(project, "fileName", GlobalSearchScope.projectScope(project));
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 如果我知道它的名字但不知道路径, 我如何找到文件?

FilenameIndex.getFilesByName()

# 如何找到特定PSI元素的使用位置?

ReferencesSearch.search()

# 如何重命名PSI元素?

RefactoringFactory.createRename()

# 如何重建虚拟文件的PSI?

FileContentUtil.reparseFiles()

# Java特定

# 如何找到类的所有继承者?

ClassInheritorsSearch.search()

# 如何通过限定名称查找课程?

JavaPsiFacade.findClass()

# 如何通过短名称找到一个班级?

PsiShortNamesCache.getInstance().getClassesByName()

# 如何找到Java类的超类?

PsiClass.getSuperClass()

# 如何获取对Java类的包含的引用?

PsiJavaFile javaFile = (PsiJavaFile) psiClass.getContaningFile();
PsiPackage pkg = JavaPsiFacade.getInstance(project).findPackage(javaFile.getPackageName());

# 如何找到覆盖特定方法的方法

OverridingMethodsSearch.search()


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK