You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

IDEA插件开发:符号链接文件夹创建文件后视图异常解决问询

IDEA插件符号链接文件夹创建文件后显示异常的解决方案

问题说明

开发的IDEA插件支持右键点击文件夹创建代码文件,但当目标文件夹是符号链接时,新文件会错误显示在父文件夹下(实际磁盘位置正确),必须手动执行“Reload Project”才能恢复正常显示。

问题根源

IDEA的虚拟文件系统(VFS)对符号链接有特殊映射逻辑,直接通过真实磁盘路径创建文件会绕过VFS的符号链接处理,导致VFS无法将新文件关联到符号链接节点,最终出现视图显示错误。

解决方案

核心修改思路

  1. 避免直接操作真实磁盘路径,改用IDEA VFS API处理文件创建,让VFS自动管理符号链接映射
  2. 针对符号链接对应的VirtualFile进行精准刷新,确保VFS更新该节点的子文件列表
  3. 调整刷新逻辑顺序,优先触发VFS同步,再更新项目视图

修改后的代码

package coocoogame.com.et_tool;

import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.externalSystem.model.ExternalSystemDataKeys;
import com.intellij.openapi.externalSystem.model.ProjectSystemId;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowId;
import com.intellij.openapi.wm.ToolWindowManager;
import com.intellij.projectView.ProjectView;

import java.io.IOException;

public class CreateCsFileAction extends AnAction {

    // 定义字符串模板(保持原模板不变)
    private static final String TEMPLATESystem = "namespace ET${locationType} \n { \n   [FriendOf(typeof(${fileName}))] \n" +
            " [EntitySystemOf(typeof(${fileName}))] \n" +
            "public static partial class ${fileName}System  { \n" +
            "[EntitySystem] \n  private static void Awake(this ${fileName} self) \n { \n  } \n " +
            " [EntitySystem] \n   private static void Destroy(this ${fileName} self) \n { \n  } \n " +
            "} \n }";
    private static final String TEMPLATEComponent = "namespace ET${locationType} \n { " +
            "\n   //[ChildOf(typeof(${fileName}))] \n" +
            "\n   [ComponentOf(typeof(${fileName}))] \n" +
            "public  class ${fileName} : Entity, IAwake, IDestroy   { \n" +
            "\n \n" +
            "} \n }";
    private static final String TEMPLATEMessageHandler = "namespace ET${locationType} \n { " +
            "\n    [MessageHandler(SceneType.Current)] \n" +
            "public  class ${fileName} : MessageHandler<Scene, ${fileName}>   { \n" +
            " protected override async ETTask Run(Scene root, ${fileName} message) \n {"+
            "\n \n" +
            "   await ETTask.CompletedTask; \n }"+
            "} \n }";

    @Override
    public void actionPerformed(AnActionEvent e) {
        Project project = e.getData(CommonDataKeys.PROJECT);
        ProjectSystemId projectSystemId = e.getData(ExternalSystemDataKeys.EXTERNAL_SYSTEM_ID);
        if (project == null) return;

        VirtualFile[] selectedFiles = e.getData(CommonDataKeys.VIRTUAL_FILE_ARRAY);
        if (selectedFiles == null || selectedFiles.length == 0) return;

        VirtualFile targetDir = selectedFiles[0];
        if (!targetDir.isDirectory()) {
            targetDir = targetDir.getParent();
        }
        if (targetDir == null || !targetDir.isDirectory()) return;

        // 显示对话框获取文件名
        FileNameInputDialog dialog = new FileNameInputDialog(project);
        dialog.show();
        if (dialog.isOK()) {
            String fileName = dialog.getFileName();
            LocationType selectedLocationType = dialog.getSelectedLocationType();
            CSType selectedCSType = dialog.getSelectedCSType();

            if (fileName != null && !fileName.isEmpty()) {
                createFile(project, projectSystemId, targetDir, fileName, selectedLocationType, selectedCSType);
            }
        }
    }

    private void createFile(Project project, ProjectSystemId projectSystemId, VirtualFile targetDir, String fileName, LocationType locationType, CSType csType) {
        String fullFileName = fileName + ".cs";
        String fileContent = "";

        // 生成模板内容逻辑保持不变
        if (csType == CSType.System) {
            fileContent = TEMPLATESystem.replace("${fileName}", fileName)
                    .replace("${locationType}", locationType == LocationType.Client || locationType == LocationType.Server ? "." + locationType.name() : "");
        } else if (csType == CSType.Component) {
            fileContent = TEMPLATEComponent.replace("${fileName}", fileName)
                    .replace("${locationType}", locationType == LocationType.Client || locationType == LocationType.Server ? "." + locationType.name() : "");
        } else if (csType == CSType.MessageHandler) {
            fileContent = TEMPLATEMessageHandler.replace("${fileName}", fileName)
                    .replace("${locationType}", locationType == LocationType.Client || locationType == LocationType.Server ? "." + locationType.name() : "");
        }

        // 使用VFS API在写操作线程中创建文件
        ApplicationManager.getApplication().runWriteAction(() -> {
            try {
                VirtualFile newFile = targetDir.createChildData(this, fullFileName);
                VfsUtil.saveText(newFile, fileContent);

                // 精准刷新符号链接文件夹
                VfsUtil.markDirtyAndRefresh(false, true, false, targetDir);

                // 异步刷新VFS并激活项目视图
                ApplicationManager.getApplication().invokeLater(() -> {
                    VirtualFileManager.getInstance().asyncRefresh(() -> {
                        ToolWindow projectView = ToolWindowManager.getInstance(project).getToolWindow(ToolWindowId.PROJECT_VIEW);
                        if (projectView != null) {
                            projectView.activate(null);
                            // 可选:自动展开目标文件夹并选中新文件
                            ProjectView.getInstance(project).select(newFile, newFile, true);
                        }
                    });
                });

                Messages.showMessageDialog(project, "文件创建成功:" + newFile.getPath(), "成功", Messages.getInformationIcon());
            } catch (IOException ex) {
                Messages.showMessageDialog(project, "创建文件失败:" + ex.getMessage(), "错误", Messages.getErrorIcon());
            }
        });
    }
}

关键修改点说明

  1. 移除符号链接拦截逻辑:允许直接在符号链接文件夹操作,由VFS自动处理真实路径映射
  2. 改用VFS API创建文件:使用VirtualFile.createChildData和VfsUtil.saveText,确保VFS跟踪符号链接下的文件变化
  3. 精准刷新目标文件夹:直接刷新符号链接对应的VirtualFile,而非真实路径的父文件夹,让VFS更新该节点的子文件列表
  4. 优化刷新顺序:在写操作完成后立即标记文件夹为脏并刷新,再异步触发VFS全局刷新,确保视图及时同步

内容的提问来源于stack exchange,提问作者melontaro

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 06:05:57