diff --git a/java/java-tests/testSrc/com/intellij/psi/impl/smartPointers/SmartPsiElementPointersTest.java b/java/java-tests/testSrc/com/intellij/psi/impl/smartPointers/SmartPsiElementPointersTest.java index 1b6f5bd64a41..1f62e804e683 100644 --- a/java/java-tests/testSrc/com/intellij/psi/impl/smartPointers/SmartPsiElementPointersTest.java +++ b/java/java-tests/testSrc/com/intellij/psi/impl/smartPointers/SmartPsiElementPointersTest.java @@ -27,6 +27,7 @@ import com.intellij.openapi.editor.EditorFactory; import com.intellij.openapi.editor.event.DocumentEvent; import com.intellij.openapi.editor.event.DocumentListener; import com.intellij.openapi.editor.event.EditorEventMulticaster; +import com.intellij.openapi.editor.ex.DocumentEx; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileTypes.PlainTextFileType; import com.intellij.openapi.fileTypes.StdFileTypes; @@ -716,6 +717,33 @@ public class SmartPsiElementPointersTest extends CodeInsightTestCase { assertEquals(TextRange.create(1, 2), range2.getRange()); } + public void testMoveText() { + PsiJavaFile file = (PsiJavaFile)configureByText(JavaFileType.INSTANCE, "class C1{}\nclass C2 {}"); + DocumentEx document = (DocumentEx)file.getViewProvider().getDocument(); + + SmartPsiElementPointer pointer1 = + SmartPointerManager.getInstance(myProject).createSmartPsiElementPointer(file.getClasses()[0]); + SmartPsiElementPointer pointer2 = + SmartPointerManager.getInstance(myProject).createSmartPsiElementPointer(file.getClasses()[1]); + assertEquals("C1", pointer1.getElement().getName()); + assertEquals("C2", pointer2.getElement().getName()); + + PlatformTestUtil.tryGcSoftlyReachableObjects(); + assertNull(((SmartPointerEx) pointer1).getCachedElement()); + assertNull(((SmartPointerEx) pointer2).getCachedElement()); + + TextRange range = file.getClasses()[1].getTextRange(); + document.moveText(range.getStartOffset(), range.getEndOffset(), 0); + + System.out.println(pointer1.getRange()); + System.out.println(pointer2.getRange()); + + PsiDocumentManager.getInstance(myProject).commitAllDocuments(); + + assertEquals("C1", pointer1.getElement().getName()); + assertEquals("C2", pointer2.getElement().getName()); + } + public void testNonPhysicalFile() { PsiJavaFile file = (PsiJavaFile)myJavaFacade.findClass("AClass", GlobalSearchScope.allScope(getProject())).getContainingFile().copy(); SmartPsiFileRange pointer = SmartPointerManager.getInstance(myProject).createSmartPsiFileRangePointer(file, TextRange.create(1, 2)); diff --git a/platform/core-impl/src/com/intellij/openapi/editor/impl/ManualRangeMarker.java b/platform/core-impl/src/com/intellij/openapi/editor/impl/ManualRangeMarker.java index 2f079ee60e4f..3f6fea8a1ba1 100644 --- a/platform/core-impl/src/com/intellij/openapi/editor/impl/ManualRangeMarker.java +++ b/platform/core-impl/src/com/intellij/openapi/editor/impl/ManualRangeMarker.java @@ -16,6 +16,7 @@ package com.intellij.openapi.editor.impl; import com.intellij.openapi.editor.event.DocumentEvent; +import com.intellij.openapi.editor.impl.event.RetargetRangeMarkers; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.ProperTextRange; import org.jetbrains.annotations.NotNull; @@ -57,6 +58,14 @@ public class ManualRangeMarker { @Nullable private Pair getUpdatedState(@NotNull DocumentEvent event) { + if (event instanceof RetargetRangeMarkers) { + int start = ((RetargetRangeMarkers)event).getStartOffset(); + if (myRange.getStartOffset() >= start && myRange.getEndOffset() <= ((RetargetRangeMarkers)event).getEndOffset()) { + ProperTextRange range = myRange.shiftRight(((RetargetRangeMarkers)event).getMoveDestinationOffset() - start); + return Pair.create(range, myLinesCols == null ? null : PersistentRangeMarker.storeLinesAndCols(range, event.getDocument())); + } + } + if (myLinesCols != null) { return PersistentRangeMarker .applyChange(event, myRange, myRange.getStartOffset(), myRange.getEndOffset(), myGreedyLeft, myGreedyRight, myLinesCols); diff --git a/platform/core-impl/src/com/intellij/openapi/editor/impl/event/RetargetRangeMarkers.java b/platform/core-impl/src/com/intellij/openapi/editor/impl/event/RetargetRangeMarkers.java new file mode 100644 index 000000000000..898141ff9195 --- /dev/null +++ b/platform/core-impl/src/com/intellij/openapi/editor/impl/event/RetargetRangeMarkers.java @@ -0,0 +1,48 @@ +/* + * Copyright 2000-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.editor.impl.event; + +import com.intellij.openapi.editor.Document; +import org.jetbrains.annotations.NotNull; + +/** + * @author peter + */ +public class RetargetRangeMarkers extends DocumentEventImpl { + private final int myStartOffset; + private final int myEndOffset; + private final int myMoveDestinationOffset; + + public RetargetRangeMarkers(@NotNull Document document, + int startOffset, int endOffset, int moveDestinationOffset) { + super(document, startOffset, "", "", 0, false); + myStartOffset = startOffset; + myEndOffset = endOffset; + myMoveDestinationOffset = moveDestinationOffset; + } + + public int getStartOffset() { + return myStartOffset; + } + + public int getEndOffset() { + return myEndOffset; + } + + public int getMoveDestinationOffset() { + return myMoveDestinationOffset; + } +} diff --git a/platform/core-impl/src/com/intellij/psi/impl/PsiDocumentManagerBase.java b/platform/core-impl/src/com/intellij/psi/impl/PsiDocumentManagerBase.java index 95d21bf2b602..1c66324519d0 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/PsiDocumentManagerBase.java +++ b/platform/core-impl/src/com/intellij/psi/impl/PsiDocumentManagerBase.java @@ -24,11 +24,15 @@ import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.impl.ApplicationInfoImpl; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.event.DocumentAdapter; import com.intellij.openapi.editor.event.DocumentEvent; import com.intellij.openapi.editor.event.DocumentListener; import com.intellij.openapi.editor.ex.DocumentEx; +import com.intellij.openapi.editor.ex.PrioritizedInternalDocumentListener; import com.intellij.openapi.editor.impl.DocumentImpl; +import com.intellij.openapi.editor.impl.EditorDocumentPriorities; import com.intellij.openapi.editor.impl.FrozenDocument; +import com.intellij.openapi.editor.impl.event.RetargetRangeMarkers; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; @@ -667,10 +671,6 @@ public abstract class PsiDocumentManagerBase extends PsiDocumentManager implemen if (myStopTrackingDocuments || myProject.isDisposed()) return; final Document document = event.getDocument(); - if (document instanceof DocumentImpl) { - myUncommittedInfos.get(document).myEvents.add(event); - } - VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document); boolean isRelevant = virtualFile != null && isRelevant(virtualFile); @@ -767,6 +767,7 @@ public abstract class PsiDocumentManagerBase extends PsiDocumentManager implemen UncommittedInfo info = myUncommittedInfos.remove(document); if (info != null) { ((SmartPointerManagerImpl)SmartPointerManager.getInstance(myProject)).updatePointers(document, info.myFrozen, info.myEvents); + Disposer.dispose(info); } return info; } @@ -841,6 +842,9 @@ public abstract class PsiDocumentManagerBase extends PsiDocumentManager implemen @TestOnly public void clearUncommittedDocuments() { + for (UncommittedInfo info : myUncommittedInfos.values()) { + Disposer.dispose(info); + } myUncommittedInfos.clear(); myUncommittedDocuments.clear(); mySynchronizer.cleanupForNextTest(); @@ -863,7 +867,7 @@ public abstract class PsiDocumentManagerBase extends PsiDocumentManager implemen return mySynchronizer; } - private static class UncommittedInfo { + private static class UncommittedInfo extends DocumentAdapter implements PrioritizedInternalDocumentListener, Disposable { final DocumentImpl myOriginal; final FrozenDocument myFrozen; final List myEvents = ContainerUtil.newArrayList(); @@ -871,8 +875,27 @@ public abstract class PsiDocumentManagerBase extends PsiDocumentManager implemen public UncommittedInfo(DocumentImpl original) { myOriginal = original; myFrozen = original.freeze(); + myOriginal.addDocumentListener(this, this); } + @Override + public int getPriority() { + return EditorDocumentPriorities.RANGE_MARKER; + } + + @Override + public void documentChanged(DocumentEvent e) { + myEvents.add(e); + } + + @Override + public void moveTextHappened(int start, int end, int base) { + myEvents.add(new RetargetRangeMarkers(myOriginal, start, end, base)); + } + + @Override + public void dispose() { + } } } diff --git a/platform/core-impl/src/com/intellij/psi/impl/smartPointers/MarkerCache.java b/platform/core-impl/src/com/intellij/psi/impl/smartPointers/MarkerCache.java index ebd895d1782f..35376b3f18c2 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/smartPointers/MarkerCache.java +++ b/platform/core-impl/src/com/intellij/psi/impl/smartPointers/MarkerCache.java @@ -19,6 +19,7 @@ import com.intellij.openapi.editor.event.DocumentEvent; import com.intellij.openapi.editor.impl.FrozenDocument; import com.intellij.openapi.editor.impl.ManualRangeMarker; import com.intellij.openapi.editor.impl.event.DocumentEventImpl; +import com.intellij.openapi.editor.impl.event.RetargetRangeMarkers; import com.intellij.openapi.util.ProperTextRange; import com.intellij.openapi.util.Trinity; import com.intellij.util.NullableFunction; @@ -114,10 +115,17 @@ class MarkerCache { @NotNull List events, Map map) { for (DocumentEvent event : events) { - frozen = frozen.applyEvent(event, 0); - final DocumentEvent corrected = - new DocumentEventImpl(frozen, event.getOffset(), event.getOldFragment(), event.getNewFragment(), event.getOldTimeStamp(), - event.isWholeTextReplaced()); + DocumentEvent corrected; + if ((event instanceof RetargetRangeMarkers)) { + RetargetRangeMarkers retarget = (RetargetRangeMarkers)event; + corrected = new RetargetRangeMarkers(frozen, retarget.getStartOffset(), retarget.getEndOffset(), retarget.getMoveDestinationOffset()); + } + else { + frozen = frozen.applyEvent(event, 0); + corrected = new DocumentEventImpl(frozen, event.getOffset(), event.getOldFragment(), event.getNewFragment(), event.getOldTimeStamp(), + event.isWholeTextReplaced()); + } + for (Map.Entry entry : map.entrySet()) { ManualRangeMarker currentRange = entry.getValue(); if (currentRange != null) {