diff --git a/platform/core-impl/src/com/intellij/openapi/editor/impl/RangeMarkerTree.java b/platform/core-impl/src/com/intellij/openapi/editor/impl/RangeMarkerTree.java index fdd81e5038dc..f0b624ac5a67 100644 --- a/platform/core-impl/src/com/intellij/openapi/editor/impl/RangeMarkerTree.java +++ b/platform/core-impl/src/com/intellij/openapi/editor/impl/RangeMarkerTree.java @@ -168,7 +168,8 @@ class RangeMarkerTree extends IntervalTreeImpl imple incModCount(); List> affected = new SmartList<>(); - collectAffectedMarkersAndShiftSubtrees(getRoot(), e, affected); + int oldDocumentLength = e.getDocument().getTextLength() + e.getOldLength() - e.getNewLength(); + collectAffectedMarkersAndShiftSubtrees(getRoot(), e, oldDocumentLength, affected); checkMax(false); if (!affected.isEmpty()) { @@ -198,6 +199,9 @@ class RangeMarkerTree extends IntervalTreeImpl imple Getter key = keys.get(i); marker = (RangeMarkerImpl)key.get(); if (marker != null) { + if (marker.isValid() && (marker.getStartOffset() < 0 || marker.getEndOffset() > oldDocumentLength)) { + marker.invalidate(e); + } if (!marker.isValid()) { // marker can become invalid on its own, e.g. FoldRegion node.removeIntervalInternal(i); @@ -220,8 +224,9 @@ class RangeMarkerTree extends IntervalTreeImpl imple } checkMax(true); - IntervalNode root = getRoot(); - assert root == null || root.maxEnd + root.delta <= e.getDocument().getTextLength(); + // can be false when create lazy range marker from virtual file with invalid (e.g. too large) offset (with no ability to verify the offset at creation time) + //IntervalNode root = getRoot(); + //assert root == null || root.maxEnd + root.delta <= e.getDocument().getTextLength(); } finally { l.writeLock().unlock(); @@ -239,8 +244,8 @@ class RangeMarkerTree extends IntervalTreeImpl imple // returns true if all deltas involved are still 0 boolean collectAffectedMarkersAndShiftSubtrees(@Nullable IntervalNode root, - @NotNull DocumentEvent e, - @NotNull List> affected) { + @NotNull DocumentEvent e, int oldDocumentLength, + @NotNull List> affected) { if (root == null) return true; boolean norm = pushDelta(root); @@ -250,11 +255,11 @@ class RangeMarkerTree extends IntervalTreeImpl imple int offset = e.getOffset(); int affectedEndOffset = offset + e.getOldLength(); boolean hasAliveKeys = root.hasAliveKey(false); - if (!hasAliveKeys) { - // marker was garbage collected + if (!hasAliveKeys || root.intervalEnd() > oldDocumentLength) { + // marker was garbage collected or its offsets become invalid (e.g. after loading document for range marker created from virtual file with invalid offsets) affected.add(root); } - if (offset > maxEnd) { + if (offset > maxEnd && maxEnd <= oldDocumentLength) { // no need to bother } else if (affectedEndOffset < root.intervalStart()) { @@ -268,18 +273,21 @@ class RangeMarkerTree extends IntervalTreeImpl imple norm &= newL == 0; } norm &= pushDelta(root); - norm &= collectAffectedMarkersAndShiftSubtrees(left, e, affected); + norm &= collectAffectedMarkersAndShiftSubtrees(left, e, oldDocumentLength, affected); + if (maxEnd > oldDocumentLength) { + collectAffectedMarkersAndShiftSubtrees(root.getRight(), e, oldDocumentLength, affected); + } correctMax(root, 0); } else { if (offset <= root.intervalEnd()) { // unlucky enough so that change affects the interval - if (hasAliveKeys) affected.add(root); // otherwise we've already added it + if (affected.isEmpty() || affected.get(affected.size()-1) != root) affected.add(root); // otherwise we've already added it root.setValid(false); //make invisible } - norm &= collectAffectedMarkersAndShiftSubtrees(root.getLeft(), e, affected); - norm &= collectAffectedMarkersAndShiftSubtrees(root.getRight(), e, affected); + norm &= collectAffectedMarkersAndShiftSubtrees(root.getLeft(), e, oldDocumentLength, affected); + norm &= collectAffectedMarkersAndShiftSubtrees(root.getRight(), e, oldDocumentLength, affected); correctMax(root,0); } return norm; diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/FoldingModelImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/FoldingModelImpl.java index 5df5daf8e296..85acd86ef357 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/FoldingModelImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/FoldingModelImpl.java @@ -754,12 +754,12 @@ public class FoldingModelImpl extends InlayModel.SimpleAdapter @Override boolean collectAffectedMarkersAndShiftSubtrees(@Nullable IntervalNode root, @NotNull DocumentEvent e, - @NotNull List> affected) { - if (inCollectCall) return super.collectAffectedMarkersAndShiftSubtrees(root, e, affected); + int oldDocumentLength, @NotNull List> affected) { + if (inCollectCall) return super.collectAffectedMarkersAndShiftSubtrees(root, e, oldDocumentLength, affected); inCollectCall = true; boolean result; try { - result = super.collectAffectedMarkersAndShiftSubtrees(root, e, affected); + result = super.collectAffectedMarkersAndShiftSubtrees(root, e, oldDocumentLength, affected); } finally { inCollectCall = false; diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/RangeMarkerTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/RangeMarkerTest.java index 93d25565312e..ca156814357e 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/RangeMarkerTest.java +++ b/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/RangeMarkerTest.java @@ -1112,6 +1112,22 @@ public class RangeMarkerTest extends LightPlatformTestCase { marker.dispose(); } + public void testLazyRangeMarkersWithInvalidOffset() { + psiFile = createFile("x.txt", ""); + + LazyRangeMarkerFactoryImpl factory = (LazyRangeMarkerFactoryImpl)LazyRangeMarkerFactory.getInstance(getProject()); + VirtualFile virtualFile = psiFile.getVirtualFile(); + Document document = FileDocumentManager.getInstance().getDocument(virtualFile); + + assertEquals("", document.getText()); + + RangeMarker marker = factory.createRangeMarker(virtualFile, 1 /* invalid offset */); + + document.replaceString(0, 0, "\n\t\n"); // used to throw AssertionError from RangeMarkerTree.updateMarkersOnChange + assertEquals("\n\t\n", document.getText()); + assertFalse(marker.isValid()); + } + public void testNonGreedyMarkersGrowOnAppendingReplace() { Document doc = new DocumentImpl("foo"); RangeMarker marker = doc.createRangeMarker(0, 3);