[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
This commit is contained in:
Nikolay Chashnikov
2024-07-10 10:16:51 +02:00
committed by intellij-monorepo-bot
parent 02196cd0f5
commit 61312dc086
3 changed files with 57 additions and 4 deletions

View File

@@ -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<Unit>(Dispatchers.EDT) {
saveProjectState()

View File

@@ -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<Pair<Module, Path>>) {
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
}

View File

@@ -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<T : EntitySource>(
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<WorkspaceEntity>,
): 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 }
}
}
/**