diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/SoftWrapModelImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/SoftWrapModelImpl.java index 1ed621d490a9..8592e5ce55dd 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/SoftWrapModelImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/SoftWrapModelImpl.java @@ -64,8 +64,18 @@ public class SoftWrapModelImpl implements SoftWrapModelEx, PrioritizedDocumentLi private final LogicalToVisualTask myLogicalToVisualTask = new LogicalToVisualTask(); private final FoldProcessingEndTask myFoldProcessingEndTask = new FoldProcessingEndTask(); - private final List myDocumentListeners = new ArrayList(); - private final List myFoldListeners = new ArrayList(); + private final List myDocumentListeners = new ArrayList(); + private final List myFoldListeners = new ArrayList(); + + /** + * There is a possible case that particular activity performs batch fold regions operations (addition, removal etc). + * We don't want to process them at the same time we get notifications about that because there is a big chance that + * we see inconsistent state (e.g. there was a problem with {@link FoldingModel#getCollapsedRegionAtOffset(int)} because that + * method uses caching internally and cached data becomes inconsistent if, for example, the top region is removed). + *

+ * So, our strategy is to collect information about changed fold regions and process it only when batch folding processing ends. + */ + private final List myDeferredFoldRegions = new ArrayList(); private final SoftWrapFoldBasedApplianceStrategy myFoldBasedApplianceStrategy; private final CachingSoftWrapDataMapper myDataMapper; @@ -170,6 +180,7 @@ public class SoftWrapModelImpl implements SoftWrapModelEx, PrioritizedDocumentLi myUseSoftWraps = settings.isUseSoftWraps(); if (myUseSoftWraps && (!softWrapsUsedBefore || settings.getAdditionalColumnsCount() > 0)) { myApplianceManager.reset(); + myDeferredFoldRegions.clear(); myAdditionalColumnsCount = settings.getAdditionalColumnsCount(); settings.setAdditionalColumnsCount(0); } @@ -413,28 +424,12 @@ public class SoftWrapModelImpl implements SoftWrapModelEx, PrioritizedDocumentLi if (myDirty) { myApplianceManager.reset(); + myDeferredFoldRegions.clear(); myDirty = false; } myApplianceManager.recalculateIfNecessary(); return true; - // - //Rectangle visibleArea = myEditor.getScrollingModel().getVisibleArea(); - //if (visibleArea.width <= 0) { - // // We don't know visible area width, hence, can't calculate soft wraps positions. - // return false; - //} - // - //myActive++; - //try { - // LogicalPosition logicalPosition = myEditor.xyToLogicalPosition(visibleArea.getLocation()); - // int offset = myEditor.logicalPositionToOffset(logicalPosition); - // myApplianceManager.registerSoftWrapIfNecessary(visibleArea, offset); - // return true; - //} - //finally { - // myActive--; - //} } /** @@ -573,18 +568,7 @@ public class SoftWrapModelImpl implements SoftWrapModelEx, PrioritizedDocumentLi return; } - // There is a possible case that given fold region is contained inside another collapsed fold region. We don't want to process - // such nested region then. - FoldRegion outerRegion = myEditor.getFoldingModel().getCollapsedRegionAtOffset(region.getStartOffset()); - if (outerRegion != null && outerRegion != region && outerRegion.getStartOffset() <= region.getStartOffset() - && outerRegion.getEndOffset() >= region.getEndOffset()) - { - return; - } - - for (FoldingListener listener : myFoldListeners) { - listener.onFoldRegionStateChange(region); - } + myDeferredFoldRegions.add(region); } @Override @@ -608,6 +592,7 @@ public class SoftWrapModelImpl implements SoftWrapModelEx, PrioritizedDocumentLi myDataMapper.release(); myApplianceManager.release(); myStorage.removeAll(); + myDeferredFoldRegions.clear(); } public SoftWrapApplianceManager getApplianceManager() { @@ -742,6 +727,27 @@ public class SoftWrapModelImpl implements SoftWrapModelEx, PrioritizedDocumentLi if (!softWrapAware) { return; } + + try { + for (FoldRegion region : myDeferredFoldRegions) { + // There is a possible case that given fold region is contained inside another collapsed fold region. We don't want to process + // such nested region then. + FoldRegion outerRegion = myEditor.getFoldingModel().getCollapsedRegionAtOffset(region.getStartOffset()); + if (outerRegion != null && outerRegion != region && outerRegion.getStartOffset() <= region.getStartOffset() + && outerRegion.getEndOffset() >= region.getEndOffset()) + { + continue; + } + + for (FoldingListener listener : myFoldListeners) { + listener.onFoldRegionStateChange(region); + } + } + } + finally { + myDeferredFoldRegions.clear(); + } + for (FoldingListener listener : myFoldListeners) { listener.onFoldProcessingEnd(); } diff --git a/platform/platform-impl/testSrc/com/intellij/openapi/editor/impl/softwrap/mapping/SoftWrapApplianceOnDocumentModificationTest.java b/platform/platform-impl/testSrc/com/intellij/openapi/editor/impl/softwrap/mapping/SoftWrapApplianceOnDocumentModificationTest.java index 3fedf1ce7bef..ff61d49ffcdf 100644 --- a/platform/platform-impl/testSrc/com/intellij/openapi/editor/impl/softwrap/mapping/SoftWrapApplianceOnDocumentModificationTest.java +++ b/platform/platform-impl/testSrc/com/intellij/openapi/editor/impl/softwrap/mapping/SoftWrapApplianceOnDocumentModificationTest.java @@ -15,6 +15,7 @@ */ package com.intellij.openapi.editor.impl.softwrap.mapping; +import com.intellij.codeInsight.folding.CodeFoldingManager; import com.intellij.openapi.editor.*; import com.intellij.openapi.editor.ex.SoftWrapModelEx; import com.intellij.openapi.editor.impl.AbstractEditorProcessingOnDocumentModificationTest; @@ -703,6 +704,39 @@ public class SoftWrapApplianceOnDocumentModificationTest extends AbstractEditorP assertEquals(new VisualPosition(visLine, 0), caretModel.getVisualPosition()); } + public void testFoldRegionsUpdate() throws IOException { + String text = + "import java.util.List;\n" + + "import java.util.ArrayList;\n" + + "\n" + + "class Test {\n" + + "}"; + init(300, text); + + final int foldStartOffset = "import".length() + 1; + int foldEndOffset = text.indexOf("class") - 2; + addCollapsedFoldRegion(foldStartOffset, foldEndOffset, "..."); + + // Simulate addition of the new import that modifies existing fold region. + myEditor.getDocument().insertString(foldEndOffset, "\nimport java.util.Date;\n"); + final FoldingModel foldingModel = myEditor.getFoldingModel(); + foldingModel.runBatchFoldingOperation(new Runnable() { + @Override + public void run() { + FoldRegion oldFoldRegion = getFoldRegion(foldStartOffset); + assertNotNull(oldFoldRegion); + foldingModel.removeFoldRegion(oldFoldRegion); + + int newFoldEndOffset = myEditor.getDocument().getText().indexOf("class") - 2; + FoldRegion newFoldRegion = foldingModel.addFoldRegion(foldStartOffset, newFoldEndOffset, "..."); + assertNotNull(newFoldRegion); + newFoldRegion.setExpanded(false); + } + }); + CodeFoldingManager.getInstance(getProject()).updateFoldRegions(myEditor); + assertEquals(new VisualPosition(2, 0), myEditor.logicalToVisualPosition(new LogicalPosition(5, 0))); + } + private void init(final int visibleWidth, String fileText) throws IOException { init(fileText); myEditor.getSettings().setUseSoftWraps(true);