From d8d02f0191ece5f0fbe44a36aa2b3b02c7ea81ca Mon Sep 17 00:00:00 2001 From: Max Medvedev Date: Fri, 14 Nov 2025 11:28:47 +0100 Subject: [PATCH] IJPL-339 [psi] send property-change events on write access update GitOrigin-RevId: c0f70a5a5cf747492a3c64816ae9b3d791ef8bf8 --- .../psi/impl/file/impl/PsiVFSListener.kt | 38 +++++++++++-------- .../impl/file/impl/MultiversePsiEventTest.kt | 36 +++++++++++++++++- 2 files changed, 57 insertions(+), 17 deletions(-) diff --git a/platform/lang-impl/src/com/intellij/psi/impl/file/impl/PsiVFSListener.kt b/platform/lang-impl/src/com/intellij/psi/impl/file/impl/PsiVFSListener.kt index 98e06e8d0833..344acc0cdd8c 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/file/impl/PsiVFSListener.kt +++ b/platform/lang-impl/src/com/intellij/psi/impl/file/impl/PsiVFSListener.kt @@ -247,15 +247,19 @@ private class PsiVFSListener(private val project: Project) { } } else if (propertyName == VirtualFile.PROP_WRITABLE) { - // todo IJPL-339 implement proper event for multiple files - val psiFile = fileManager.getCachedPsiFileInner(vFile, anyContext()) ?: return@externalChangeAction - val treeEvent = PsiTreeChangeEventImpl(manager) - treeEvent.parent = parentDir - treeEvent.element = psiFile - treeEvent.propertyName = PsiTreeChangeEvent.PROP_WRITABLE - treeEvent.oldValue = event.oldValue - treeEvent.newValue = event.newValue - manager.beforePropertyChange(treeEvent) + val psiFiles = fileManager.getCachedPsiFilesInner(vFile).ifEmpty { + return@externalChangeAction + } + + for (psiFile in psiFiles) { + val treeEvent = PsiTreeChangeEventImpl(manager) + treeEvent.parent = parentDir + treeEvent.element = psiFile + treeEvent.propertyName = PsiTreeChangeEvent.PROP_WRITABLE + treeEvent.oldValue = event.oldValue + treeEvent.newValue = event.newValue + manager.beforePropertyChange(treeEvent) + } } }) } @@ -408,13 +412,15 @@ private class PsiVFSListener(private val project: Project) { return@externalChangeAction } - val treeEvent = PsiTreeChangeEventImpl(manager) - treeEvent.parent = parentDir - treeEvent.element = oldPsiFiles.first() // todo IJPL-339 update me - treeEvent.propertyName = PsiTreeChangeEvent.PROP_WRITABLE - treeEvent.oldValue = event.oldValue - treeEvent.newValue = event.newValue - manager.propertyChanged(treeEvent) + for (oldPsiFile in oldPsiFiles) { + val treeEvent = PsiTreeChangeEventImpl(manager) + treeEvent.parent = parentDir + treeEvent.element = oldPsiFile + treeEvent.propertyName = PsiTreeChangeEvent.PROP_WRITABLE + treeEvent.oldValue = event.oldValue + treeEvent.newValue = event.newValue + manager.propertyChanged(treeEvent) + } } VirtualFile.PROP_ENCODING -> { if (oldPsiFiles.isEmpty()) { diff --git a/platform/lang-impl/testSources/com/intellij/psi/impl/file/impl/MultiversePsiEventTest.kt b/platform/lang-impl/testSources/com/intellij/psi/impl/file/impl/MultiversePsiEventTest.kt index b125b661606d..a8b0dc659736 100644 --- a/platform/lang-impl/testSources/com/intellij/psi/impl/file/impl/MultiversePsiEventTest.kt +++ b/platform/lang-impl/testSources/com/intellij/psi/impl/file/impl/MultiversePsiEventTest.kt @@ -151,10 +151,44 @@ internal class MultiversePsiEventTest { expectedEventNumber = 1 ) + @Test + fun `test we receive 2 writeable event on changing writable status of a file with 2 psi files`() = doChangeTest( + listenerFactory = { counter -> + object : PsiTreeChangeAdapter() { + override fun propertyChanged(event: PsiTreeChangeEvent) { + if (event.propertyName == PsiTreeChangeEvent.PROP_WRITABLE) { + counter.incrementAndGet() + } + } + } + }, + updateBlock = { file -> + file.isWritable = false + }, + expectedEventNumber = 2 + ) + + @Test + fun `test we receive 2 before-writeable event on changing writable status of a file with 2 psi files`() = doChangeTest( + listenerFactory = { counter -> + object : PsiTreeChangeAdapter() { + override fun beforePropertyChange(event: PsiTreeChangeEvent) { + if (event.propertyName == PsiTreeChangeEvent.PROP_WRITABLE) { + counter.incrementAndGet() + } + } + } + }, + updateBlock = { file -> + file.isWritable = false + }, + expectedEventNumber = 2 + ) + private fun doChangeTest( listenerFactory: (AtomicInteger) -> PsiTreeChangeListener, updateBlock: (file: VirtualFile) -> Unit, - @Suppress("SameParameterValue") expectedEventNumber: Int + @Suppress("SameParameterValue") expectedEventNumber: Int, ) = runTest { val counter = AtomicInteger(0) val listener = listenerFactory(counter)