diff --git a/platform/core-api/src/com/intellij/pom/event/PomChangeSet.java b/platform/core-api/src/com/intellij/pom/event/PomChangeSet.java index c2247b97c9dc..43a31b07ef7e 100644 --- a/platform/core-api/src/com/intellij/pom/event/PomChangeSet.java +++ b/platform/core-api/src/com/intellij/pom/event/PomChangeSet.java @@ -22,4 +22,6 @@ public interface PomChangeSet { @NotNull PomModelAspect getAspect(); void merge(@NotNull PomChangeSet blocked); + + default void beforeNestedTransaction() {} } \ No newline at end of file diff --git a/platform/core-api/src/com/intellij/pom/event/PomModelEvent.java b/platform/core-api/src/com/intellij/pom/event/PomModelEvent.java index 1a5df6019d81..cd939f5d94db 100644 --- a/platform/core-api/src/com/intellij/pom/event/PomModelEvent.java +++ b/platform/core-api/src/com/intellij/pom/event/PomModelEvent.java @@ -89,4 +89,12 @@ public class PomModelEvent extends EventObject { public PomModel getSource() { return (PomModel)super.getSource(); } + + public void beforeNestedTransaction() { + if (myChangeSets != null) { + for (PomChangeSet changeSet : myChangeSets.values()) { + changeSet.beforeNestedTransaction(); + } + } + } } diff --git a/platform/core-impl/src/com/intellij/pom/core/impl/PomModelImpl.java b/platform/core-impl/src/com/intellij/pom/core/impl/PomModelImpl.java index d12268f26967..c7d97baa2df6 100644 --- a/platform/core-impl/src/com/intellij/pom/core/impl/PomModelImpl.java +++ b/platform/core-impl/src/com/intellij/pom/core/impl/PomModelImpl.java @@ -147,6 +147,12 @@ public class PomModelImpl extends UserDataHolderBase implements PomModel { List throwables = new ArrayList<>(0); final PomModelAspect aspect = transaction.getTransactionAspect(); startTransaction(transaction); + + Pair block = getBlockingTransaction(aspect, transaction); + if (block != null) { + block.getSecond().getAccumulatedEvent().beforeNestedTransaction(); + } + try{ DebugUtil.startPsiModification(null); Stack> blockedAspects = myBlockedAspects.get(); @@ -167,10 +173,8 @@ public class PomModelImpl extends UserDataHolderBase implements PomModel { finally{ blockedAspects.pop(); } - final Pair block = getBlockingTransaction(aspect, transaction); if(block != null){ - final PomModelEvent currentEvent = block.getSecond().getAccumulatedEvent(); - currentEvent.merge(event); + block.getSecond().getAccumulatedEvent().merge(event); return; } diff --git a/platform/core-impl/src/com/intellij/pom/tree/events/impl/TreeChangeEventImpl.java b/platform/core-impl/src/com/intellij/pom/tree/events/impl/TreeChangeEventImpl.java index d11bda895421..2d70f77cfea2 100644 --- a/platform/core-impl/src/com/intellij/pom/tree/events/impl/TreeChangeEventImpl.java +++ b/platform/core-impl/src/com/intellij/pom/tree/events/impl/TreeChangeEventImpl.java @@ -152,6 +152,15 @@ public class TreeChangeEventImpl implements TreeChangeEvent{ } } + @Override + public void beforeNestedTransaction() { + // compute changes and remember them, to prevent lazy computation to happen in another transaction + // when more changes might have occurred but shouldn't count in this transaction + for (TreeChangeImpl change : myChangedElements.values()) { + change.getAffectedChildren(); + } + } + public String toString() { return new ArrayList<>(myChangedElements.values()).toString(); } diff --git a/platform/platform-tests/testSrc/com/intellij/psi/impl/PsiEventConsistencyTest.kt b/platform/platform-tests/testSrc/com/intellij/psi/impl/PsiEventConsistencyTest.kt index e057ba0fab2a..5570284cc8b1 100644 --- a/platform/platform-tests/testSrc/com/intellij/psi/impl/PsiEventConsistencyTest.kt +++ b/platform/platform-tests/testSrc/com/intellij/psi/impl/PsiEventConsistencyTest.kt @@ -26,17 +26,39 @@ class PsiEventConsistencyTest : LightPlatformCodeInsightFixtureTestCase() { WriteCommandAction.runWriteCommandAction(project) { // prepare val root = createEmptyFile().node - root.replaceChild(root.firstChildNode, createComposite(compositeTypes[0], listOf(createLeaf(leafTypes[0], "d")))) + root.replaceChild(root.firstChildNode, composite(compositeTypes[0], leaf(leafTypes[0], "d"))) // actual composite change ChangeUtil.prepareAndRunChangeAction(ChangeUtil.ChangeAction { root.firstChildNode.removeChild(root.firstChildNode.firstChildNode) // remove "d" leaf - root.replaceChild(root.firstChildNode, createComposite(compositeTypes[0], listOf())) // replace now empty composite with another one + root.replaceChild(root.firstChildNode, composite(compositeTypes[0])) // replace now empty composite with another one }, root as FileElement) assertEquals("", root.text) } } + fun `test no excessive change merging between transactions if the second one has also a change higher in the tree`() { + val file = createEmptyFile() + WriteCommandAction.runWriteCommandAction(project) { + //prepare + val root = file.node as FileElement + root.addChild(composite(compositeTypes[0], leaf(leafTypes[0], "a")), root.firstChildNode) + + ChangeUtil.prepareAndRunChangeAction(ChangeUtil.ChangeAction { + // change in root child + root.firstChildNode.replaceChild(root.firstChildNode.firstChildNode, leaf(leafTypes[0], "b")) + ChangeUtil.prepareAndRunChangeAction(ChangeUtil.ChangeAction { + // change in root + root.removeChild(root.lastChildNode) + // change in the same root child as above + root.firstChildNode.addChild(leaf(leafTypes[0], "c"), null) + }, root) + }, root) + assertEquals("bc", root.text) + assertEquals(root.text, file.viewProvider.document!!.text) + } + } + fun testPsiDocSynchronization() { PropertyChecker.forAll(commands).shouldHold { cmd -> runCommand(cmd) @@ -122,10 +144,10 @@ class PsiEventConsistencyTest : LightPlatformCodeInsightFixtureTestCase() { private val compositeTypes = IntStreamEx.range(1, 5).mapToObj { i -> IElementType("Composite" + i, null) }.toList() private val leaves = Generator.zipWith(Generator.sampledFrom(leafTypes), Generator.asciiLetters()) { type, c -> - createLeaf(type, c.toString()) + leaf(type, c.toString()) } - private fun createLeaf(type: IElementType, text: String): TreeElement { + private fun leaf(type: IElementType, text: String): TreeElement { return withDummyHolder(object : LeafPsiElement(type, text) { override fun toString() = text }) @@ -133,11 +155,11 @@ class PsiEventConsistencyTest : LightPlatformCodeInsightFixtureTestCase() { private val composites : Generator = Generator.sampledFrom(compositeTypes).flatMap { type -> Generator.listsOf(IntDistribution.uniform(0, 5), nodes).map { children -> - createComposite(type, children) + composite(type, *children.toTypedArray()) } } - private fun createComposite(type: IElementType, children: List): TreeElement { + private fun composite(type: IElementType, vararg children: TreeElement): TreeElement { val composite = withDummyHolder(object : CompositePsiElement(type) { override fun toString() = getChildren(null).asList().toString() })