diff --git a/plugins/compose/intellij.compose.ide.plugin.resources/src/com/intellij/compose/ide/plugin/resources/psi/ComposeResourcesPsiChangesListener.kt b/plugins/compose/intellij.compose.ide.plugin.resources/src/com/intellij/compose/ide/plugin/resources/psi/ComposeResourcesPsiChangesListener.kt index e2e3360c96ed..9b97766fd1d9 100644 --- a/plugins/compose/intellij.compose.ide.plugin.resources/src/com/intellij/compose/ide/plugin/resources/psi/ComposeResourcesPsiChangesListener.kt +++ b/plugins/compose/intellij.compose.ide.plugin.resources/src/com/intellij/compose/ide/plugin/resources/psi/ComposeResourcesPsiChangesListener.kt @@ -9,6 +9,7 @@ import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.toNioPathOrNull import com.intellij.psi.PsiDirectory +import com.intellij.psi.PsiElement import com.intellij.psi.PsiErrorElement import com.intellij.psi.PsiFile import com.intellij.psi.PsiTreeChangeAdapter @@ -20,6 +21,7 @@ import com.intellij.psi.xml.XmlComment import com.intellij.psi.xml.XmlTag import com.intellij.psi.xml.XmlText import com.intellij.psi.xml.XmlToken +import com.intellij.psi.impl.PsiTreeChangeEventImpl import org.jetbrains.kotlin.asJava.namedUnwrappedElement import org.jetbrains.kotlin.idea.KotlinLanguage @@ -30,7 +32,10 @@ private val PsiTreeChangeEvent.isIgnorable: Boolean get() { if (file?.language == KotlinLanguage.INSTANCE) return true val parentName = getParentName() ?: return true - if (!parentName.isValidInnerComposeResourcesDirName) return true + + val oldParentValidComposeResourceDir = oldParent?.namedUnwrappedElement?.name?.isValidInnerComposeResourcesDirName ?: false + + if (!parentName.isValidInnerComposeResourcesDirName && !oldParentValidComposeResourceDir) return true // We can ignore edits in whitespace, XML error nodes, and modification in comments. // (Note that editing text in an attribute value, including whitespace characters, @@ -53,6 +58,9 @@ private val PsiTreeChangeEvent.isIgnorable: Boolean class ComposeResourcesPsiChangesListener(private val project: Project) : PsiTreeChangeAdapter() { private var ignoreChildrenChanged = false + private val composeResourcesGenerationService by lazy { project.service() } + + /** propagating file rename events */ override fun propertyChanged(event: PsiTreeChangeEvent) { ignoreChildrenChanged = true @@ -138,17 +146,29 @@ class ComposeResourcesPsiChangesListener(private val project: Project) : PsiTree private fun notice(event: PsiTreeChangeEvent) { if (project.isDisposed) return if (event.isIgnorable) return + val virtualFile = event.getVirtualFile() ?: return - val composeResourcesGenerationService = project.service() val path = virtualFile.toNioPathOrNull() ?: return val composeResourcesDir = project.findComposeResourcesDirFor(path) ?: return + composeResourcesGenerationService.tryEmit(composeResourcesDir) } - private fun PsiTreeChangeEvent.getVirtualFile(): VirtualFile? = file?.virtualFile /* for string values */ - ?: (this.parent as? PsiDirectory)?.virtualFile /* for files */ - ?: (this.child as? PsiFile)?.virtualFile /* CHILD_MOVED case */ + private fun PsiTreeChangeEvent.getVirtualFile(): VirtualFile? { + val code = (this as? PsiTreeChangeEventImpl)?.code ?: return null + return when (code) { + PsiTreeChangeEventImpl.PsiEventType.CHILD_MOVED -> { + oldParent.getComposeResourcesVirtualFile() ?: newParent.getComposeResourcesVirtualFile() + } + else -> { + file?.virtualFile /* for string values */ ?: (parent as? PsiDirectory)?.virtualFile /* for files */ + } + } + } + private fun PsiElement.getComposeResourcesVirtualFile(): VirtualFile? = (this as? PsiDirectory) + ?.takeIf { it.name.isValidInnerComposeResourcesDirName } + ?.virtualFile } private fun PsiTreeChangeEvent.getParentName(): String? = file?.parent?.name /* for string values */ diff --git a/plugins/compose/intellij.compose.ide.plugin.resources/tests/com/intellij/compose/ide/plugin/resources/ComposeResourcesPsiChangesTest.kt b/plugins/compose/intellij.compose.ide.plugin.resources/tests/com/intellij/compose/ide/plugin/resources/ComposeResourcesPsiChangesTest.kt index 272314d15504..fc7edf8b5da2 100644 --- a/plugins/compose/intellij.compose.ide.plugin.resources/tests/com/intellij/compose/ide/plugin/resources/ComposeResourcesPsiChangesTest.kt +++ b/plugins/compose/intellij.compose.ide.plugin.resources/tests/com/intellij/compose/ide/plugin/resources/ComposeResourcesPsiChangesTest.kt @@ -7,6 +7,7 @@ import com.intellij.compose.ide.plugin.resources.psi.ComposeResourcesPsiChangesL import com.intellij.openapi.application.runReadAction import com.intellij.openapi.components.service import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.PsiDirectory import com.intellij.psi.impl.PsiManagerEx import com.intellij.psi.impl.file.impl.FileManager import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -51,23 +52,50 @@ class ComposeResourcesPsiChangesTest : ComposeResourcesTestCase() { assertEquals(10, values.size) } + private data class MovingContext( + val root: PsiDirectory, + val composeResourcesDir: VirtualFile, + val composeResourcesDrawableDir: VirtualFile, + ) + @TargetVersions(TARGET_GRADLE_VERSION) @Test @TestMetadata("ComposeResources") - fun `test moving resource files from outside composeResources inner directories`() = doTest { files, fileManager, values -> - val composeResourcesDrawableDir = files.find { it.name.endsWith("compose-multiplatform.xml") }!!.parent - val composeResourcesDir = composeResourcesDrawableDir.parent - val root = runReadAction { fileManager.findDirectory(composeResourcesDir.parent)!! } - val file = runWriteAction { root.virtualFile.createChildData(root, "root.png") } + fun `test moving resource files from outside composeResources inner directories`() = doMovingTest { movingContext, values -> + val file = runWriteAction { movingContext.root.virtualFile.createChildData(movingContext.root, "root.png") } assertEquals(0, values.size) // outside composeResources dirs, no change - runWriteAction { file.move(file, composeResourcesDrawableDir) } + runWriteAction { file.move(file, movingContext.composeResourcesDrawableDir) } assertEquals(1, values.size) - runWriteAction { file.move(file, composeResourcesDir.parent) } - assertEquals(1, values.size) // outside composeResources dirs, no change + runWriteAction { file.move(file, movingContext.composeResourcesDir.parent) } + assertEquals(2, values.size) } + @TargetVersions(TARGET_GRADLE_VERSION) + @Test + @TestMetadata("ComposeResources") + fun `test moving resource files from composeResources to outer directory`() = doMovingTest { movingContext, values -> + val file = runWriteAction { movingContext.composeResourcesDrawableDir.createChildData(movingContext.composeResourcesDrawableDir, "test.png") } + assertEquals(0, values.size) + + runWriteAction { file.move(file, movingContext.root.virtualFile) } + assertEquals(1, values.size) + + runWriteAction { file.move(file, movingContext.composeResourcesDrawableDir) } + assertEquals(2, values.size) + } + + private fun doMovingTest(body: TestScope.(MovingContext, List) -> Unit) = + doTest { files, fileManager, values -> + val composeResourcesDrawableDir = files.find { it.name.endsWith("compose-multiplatform.xml") }!!.parent + val composeResourcesDir = composeResourcesDrawableDir.parent + val root = runReadAction { fileManager.findDirectory(composeResourcesDir.parent)!! } + + val context = MovingContext(root, composeResourcesDir, composeResourcesDrawableDir) + body(context, values) + } + private fun doTest(body: suspend TestScope.(List, FileManager, List) -> Unit) = runTest { val files = importProjectFromTestData() val psiManager = PsiManagerEx.getInstanceEx(myProject)