From 61312dc086096c9fc3c0da43e476f41447be6b24 Mon Sep 17 00:00:00 2001 From: Nikolay Chashnikov Date: Wed, 10 Jul 2024 10:16:51 +0200 Subject: [PATCH] [workspace model] properly handle cases when iml file of a module is moved to another directory (IJPL-8707) The common logic which updates VirtualFileUrl instances doesn't work here because the entity source for a module points to the parent directory of its iml file (see IJPL-158284). So we need to manually check this case, and also ensure that the path in ModuleStore is updated. GitOrigin-RevId: cc518f86a9f22c4d9d57932edcb0479982907adb --- .../testSrc/ChangeModuleStorePathTest.kt | 16 ++++++++++ .../FileReferenceInWorkspaceEntityUpdater.kt | 14 +++++++++ .../watcher/VirtualFileUrlWatcher.kt | 31 ++++++++++++++++--- 3 files changed, 57 insertions(+), 4 deletions(-) diff --git a/platform/configuration-store-impl/testSrc/ChangeModuleStorePathTest.kt b/platform/configuration-store-impl/testSrc/ChangeModuleStorePathTest.kt index 60ab01435420..78f43bae88a9 100644 --- a/platform/configuration-store-impl/testSrc/ChangeModuleStorePathTest.kt +++ b/platform/configuration-store-impl/testSrc/ChangeModuleStorePathTest.kt @@ -31,6 +31,7 @@ import org.junit.Test import org.junit.rules.ExternalResource import java.nio.file.Path import kotlin.io.path.invariantSeparatorsPathString +import kotlin.io.path.isRegularFile import kotlin.io.path.readText import kotlin.properties.Delegates @@ -178,6 +179,21 @@ class ChangeModuleStorePathTest { } } + @Test + fun `move iml file`() = runBlocking { + saveProjectState() + val imlFile = module.storage.getVirtualFile()!! + val oldFile = imlFile.toNioPath() + val moduleName = module.name + writeAction { + imlFile.move(null, tempDirManager.newVirtualDirectory("newParent")) + } + val newFile = imlFile.toNioPath() + assertThat(newFile.isRegularFile()) + assertModuleFileRenamed(moduleName, oldFile) + assertThat(oldModuleNames).isEmpty() + } + @Test fun `rename module source root`() = runBlocking(Dispatchers.EDT) { saveProjectState() diff --git a/platform/lang-impl/src/com/intellij/workspaceModel/ide/impl/FileReferenceInWorkspaceEntityUpdater.kt b/platform/lang-impl/src/com/intellij/workspaceModel/ide/impl/FileReferenceInWorkspaceEntityUpdater.kt index 0e23c2661157..f8b64264f4b2 100644 --- a/platform/lang-impl/src/com/intellij/workspaceModel/ide/impl/FileReferenceInWorkspaceEntityUpdater.kt +++ b/platform/lang-impl/src/com/intellij/workspaceModel/ide/impl/FileReferenceInWorkspaceEntityUpdater.kt @@ -12,6 +12,7 @@ import com.intellij.openapi.roots.impl.storage.ClasspathStorage import com.intellij.openapi.util.io.FileUtil import com.intellij.openapi.vfs.AsyncFileListener import com.intellij.openapi.vfs.VirtualFile +import com.intellij.openapi.vfs.isFile import com.intellij.openapi.vfs.newvfs.events.VFileEvent import com.intellij.openapi.vfs.newvfs.events.VFileMoveEvent import com.intellij.openapi.vfs.newvfs.events.VFilePropertyChangeEvent @@ -24,6 +25,7 @@ import com.intellij.workspaceModel.core.fileIndex.impl.getOldAndNewUrls import com.intellij.workspaceModel.ide.impl.legacyBridge.watcher.VirtualFileUrlWatcher import java.nio.file.Path import java.nio.file.Paths +import kotlin.io.path.Path /** * This class is used to update entities in [WorkspaceModel] when files referenced from them are moved or renamed. @@ -39,6 +41,9 @@ internal class FileReferenceInWorkspaceEntityUpdater(private val project: Projec if (event is VFilePropertyChangeEvent) { collectChangedModuleStorePathsAfterDirectoryRename(event, changedModuleStorePaths) } + if (event is VFileMoveEvent) { + addChangedModuleStorePathAfterImlFileMove(event, changedModuleStorePaths) + } val (oldUrl, newUrl) = getOldAndNewUrls(event) if (oldUrl != newUrl) { changedUrlsList.add(Pair(oldUrl, newUrl)) @@ -108,5 +113,14 @@ internal class FileReferenceInWorkspaceEntityUpdater(private val project: Projec } } + private fun addChangedModuleStorePathAfterImlFileMove(event: VFileMoveEvent, changedModuleStorePaths: MutableList>) { + if (!event.file.isFile || event.requestor is StateStorage || event.file.extension != ModuleFileType.DEFAULT_EXTENSION) return + val module = ModuleManager.getInstance(project).findModuleByName(event.file.nameWithoutExtension) ?: return + + if (module.isLoaded && !module.isDisposed && module.moduleFilePath == event.oldPath) { + changedModuleStorePaths.add(Pair(module, Path(event.newPath))) + } + } + private fun String.isImlFile() = Files.getFileExtension(this) == ModuleFileType.DEFAULT_EXTENSION } \ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/workspaceModel/ide/impl/legacyBridge/watcher/VirtualFileUrlWatcher.kt b/platform/lang-impl/src/com/intellij/workspaceModel/ide/impl/legacyBridge/watcher/VirtualFileUrlWatcher.kt index c45153d5ea67..4409eaca78e8 100644 --- a/platform/lang-impl/src/com/intellij/workspaceModel/ide/impl/legacyBridge/watcher/VirtualFileUrlWatcher.kt +++ b/platform/lang-impl/src/com/intellij/workspaceModel/ide/impl/legacyBridge/watcher/VirtualFileUrlWatcher.kt @@ -1,6 +1,7 @@ // Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.workspaceModel.ide.impl.legacyBridge.watcher +import com.intellij.ide.highlighter.ModuleFileType import com.intellij.java.workspace.entities.JavaModuleSettingsEntity import com.intellij.openapi.components.service import com.intellij.openapi.project.Project @@ -150,16 +151,38 @@ private class EntitySourceFileWatcher( for ((entitySource, entities) in entitiesMap) { @Suppress("UNCHECKED_CAST") val urlFromContainer = containerToUrl(entitySource as T) - if (!FileUtil.startsWith(urlFromContainer, oldUrl)) continue - - val newVfsUrl = virtualFileManager.getOrCreateFromUrl(newUrl + urlFromContainer.substring(oldUrl.length)) - val newEntitySource = createNewSource(entitySource, newVfsUrl) + val newVfsUrl = when { + FileUtil.startsWith(urlFromContainer, oldUrl) -> newUrl + urlFromContainer.substring(oldUrl.length) + isImlFileOfModuleMoved(oldUrl, newUrl, urlFromContainer, entities) -> newUrl.substringBeforeLast('/') + else -> continue + } + + val newEntitySource = createNewSource(entitySource, virtualFileManager.getOrCreateFromUrl(newVfsUrl)) entities.forEach { entity -> diff.modifyEntity(WorkspaceEntity.Builder::class.java, entity) { this.entitySource = newEntitySource } } } } + + /** + * Detects whether [oldUrl] points of an iml file of a module which was moved to a different directory. + * We need to check this case separately because [ModuleEntity.entitySource] stores the path to the parent directory of iml file, not + * path to iml file itself (see IJPL-158284). + */ + private fun isImlFileOfModuleMoved( + oldUrl: String, + newUrl: String, + entitySourceUrl: String, + entities: List, + ): Boolean { + if (!oldUrl.endsWith(ModuleFileType.DOT_DEFAULT_EXTENSION) || oldUrl.substringBeforeLast('/') != entitySourceUrl) return false + val moduleFileName = oldUrl.substringAfterLast('/') + if (moduleFileName != newUrl.substringAfterLast('/')) return false + + val moduleNameFromUrl = moduleFileName.removeSuffix(ModuleFileType.DOT_DEFAULT_EXTENSION) + return entities.any { (it as? ModuleEntity)?.name == moduleNameFromUrl } + } } /**