diff --git a/platform/core-impl/src/com/intellij/psi/impl/source/tree/CompositeElement.java b/platform/core-impl/src/com/intellij/psi/impl/source/tree/CompositeElement.java index 01638a9fa2b6..b5ccb93c1e65 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/source/tree/CompositeElement.java +++ b/platform/core-impl/src/com/intellij/psi/impl/source/tree/CompositeElement.java @@ -673,23 +673,23 @@ public class CompositeElement extends TreeElement { TreeUtil.ensureParsed(getFirstChildNode()); TreeUtil.ensureParsed(anotherParent.getFirstChildNode()); final ASTNode firstChild = anotherParent.getFirstChildNode(); - ChangeUtil.prepareAndRunChangeAction(new ChangeUtil.ChangeAction(){ - @Override - public void makeChange(TreeChangeEvent destinationTreeChange) { - remove((TreeChangeEventImpl)destinationTreeChange, (TreeElement)anotherParent.getFirstChildNode(), null); - } - }, (TreeElement)anotherParent); + ChangeUtil.prepareAndRunChangeAction( + event -> remove((TreeChangeEventImpl)event, (TreeElement)anotherParent.getFirstChildNode(), null), + (TreeElement)anotherParent); if (firstChild != null) { - ChangeUtil.prepareAndRunChangeAction(new ChangeUtil.ChangeAction(){ - @Override - public void makeChange(TreeChangeEvent destinationTreeChange) { - TreeElement first = getFirstChildNode(); - remove((TreeChangeEventImpl)destinationTreeChange, first, null); - add((TreeChangeEventImpl)destinationTreeChange, CompositeElement.this, (TreeElement)firstChild); - if(getTreeParent() != null){ - repairRemovedElement(CompositeElement.this, first); - } + ChangeUtil.prepareAndRunChangeAction(destinationTreeChange -> { + TreeElement first = getFirstChildNode(); + TreeChangeEventImpl event = (TreeChangeEventImpl)destinationTreeChange; + CompositeElement parent = getTreeParent(); + if (parent != null) { + // treat all replacements as one big childrenChanged to simplify resulting PSI/document events + event.addElementaryChange(parent); + } + remove(event, first, null); + add(event, this, (TreeElement)firstChild); + if(parent != null) { + repairRemovedElement(this, first); } }, this); } diff --git a/xml/tests/src/com/intellij/codeInsight/XmlEventsTest.java b/xml/tests/src/com/intellij/codeInsight/XmlEventsTest.java index 9b276996f6ca..c1033e51143c 100644 --- a/xml/tests/src/com/intellij/codeInsight/XmlEventsTest.java +++ b/xml/tests/src/com/intellij/codeInsight/XmlEventsTest.java @@ -15,11 +15,13 @@ */ package com.intellij.codeInsight; +import com.intellij.lang.xml.XMLLanguage; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.Result; import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.RangeMarker; import com.intellij.openapi.util.TextRange; import com.intellij.pom.PomManager; import com.intellij.pom.PomModel; @@ -30,6 +32,7 @@ import com.intellij.pom.event.PomModelListener; import com.intellij.pom.xml.XmlAspect; import com.intellij.psi.*; import com.intellij.psi.impl.source.PsiFileImpl; +import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.xml.*; import com.intellij.testFramework.LightCodeInsightTestCase; import com.intellij.util.DocumentUtil; @@ -256,6 +259,22 @@ public class XmlEventsTest extends LightCodeInsightTestCase { PsiManager.getInstance(getProject()).removePsiTreeChangeListener(listener); } + public void testRangeMarkersShouldSurviveOnSmallTagValueModification() { + String text = "x y z a b c d"; + + PsiFile file = PsiFileFactory.getInstance(getProject()).createFileFromText("a.xml", XMLLanguage.INSTANCE, text); + Document document = file.getViewProvider().getDocument(); + RangeMarker marker = document.createRangeMarker(TextRange.from(text.indexOf("a b"), 3)); + + XmlTag tag = PsiTreeUtil.findElementOfClassAtOffset(file, 0, XmlTag.class, false); + WriteCommandAction.runWriteCommandAction(null, () -> tag.getValue().setText("x a b c d")); + + assertEquals("x a b c d", document.getText()); + assertTrue(marker.isValid()); + assertEquals("a b", document.getText(TextRange.create(marker))); + } + + private static class TestListener extends PsiTreeChangeAdapter { @Override public void childReplaced(@NotNull PsiTreeChangeEvent event) {