diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/DaemonRespondToChangesTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/DaemonRespondToChangesTest.java index dfbbfc87deba..60f56a4a96c5 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/DaemonRespondToChangesTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/DaemonRespondToChangesTest.java @@ -20,6 +20,7 @@ import com.intellij.codeInsight.completion.CompletionContributor; import com.intellij.codeInsight.daemon.*; import com.intellij.codeInsight.daemon.impl.quickfix.DeleteCatchFix; import com.intellij.codeInsight.daemon.quickFix.LightQuickFixTestCase; +import com.intellij.codeInsight.folding.CodeFoldingManager; import com.intellij.codeInsight.generation.actions.CommentByLineCommentAction; import com.intellij.codeInsight.hint.EditorHintListener; import com.intellij.codeInsight.intention.AbstractIntentionAction; @@ -2078,5 +2079,54 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase { assertEmpty(visibleHints); } + + public void testCodeFoldingPassRestartsOnRegionUnfolding() throws Exception { + DaemonCodeAnalyzerSettings settings = DaemonCodeAnalyzerSettings.getInstance(); + int savedDelay = settings.AUTOREPARSE_DELAY; + settings.AUTOREPARSE_DELAY = 0; + try { + configureByText(StdFileTypes.JAVA, "class Foo {\n" + + " void m() {\n" + + "\n" + + " }\n" + + "}"); + CodeFoldingManager.getInstance(getProject()).buildInitialFoldings(myEditor); + EditorTestUtil.executeAction(myEditor, IdeActions.ACTION_COLLAPSE_ALL_REGIONS); + waitForDaemon(); + checkFoldingState("[FoldRegion +(25:33), placeholder='{...}']"); + + new WriteCommandAction(myProject){ + @Override + protected void run(@NotNull Result result) throws Throwable { + myEditor.getDocument().insertString(0, "/*"); + } + }.execute(); + waitForDaemon(); + checkFoldingState("[FoldRegion -(0:37), placeholder='/.../', FoldRegion +(27:35), placeholder='{...}']"); + + EditorTestUtil.executeAction(myEditor, IdeActions.ACTION_EXPAND_ALL_REGIONS); + waitForDaemon(); + checkFoldingState("[FoldRegion -(0:37), placeholder='/.../']"); + } + finally { + settings.AUTOREPARSE_DELAY = savedDelay; + } + } + + private void checkFoldingState(String expected) { + assertEquals(expected, Arrays.toString(myEditor.getFoldingModel().getAllFoldRegions())); + } + + private void waitForDaemon() { + long deadline = System.currentTimeMillis() + 10000; + while (!myDaemonCodeAnalyzer.isRunning()) { + if (System.currentTimeMillis() > deadline) fail("Too long waiting for daemon to start"); + UIUtil.dispatchAllInvocationEvents(); + } + while (myDaemonCodeAnalyzer.isRunning()) { + if (System.currentTimeMillis() > deadline) fail("Too long waiting for daemon to finish"); + UIUtil.dispatchAllInvocationEvents(); + } + } } diff --git a/java/java-tests/testSrc/com/intellij/openapi/editor/impl/FoldingProcessingOnDocumentModificationTest.java b/java/java-tests/testSrc/com/intellij/openapi/editor/impl/FoldingProcessingOnDocumentModificationTest.java index ad12ef131055..ff8aae2a0192 100644 --- a/java/java-tests/testSrc/com/intellij/openapi/editor/impl/FoldingProcessingOnDocumentModificationTest.java +++ b/java/java-tests/testSrc/com/intellij/openapi/editor/impl/FoldingProcessingOnDocumentModificationTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * 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. @@ -19,9 +19,11 @@ import com.intellij.codeInsight.folding.CodeFoldingManager; import com.intellij.openapi.actionSystem.IdeActions; import com.intellij.openapi.editor.CaretModel; import com.intellij.openapi.editor.FoldRegion; +import com.intellij.psi.PsiDocumentManager; import com.intellij.testFramework.TestFileType; import java.io.IOException; +import java.util.Arrays; /** * @author Denis Zhdanov @@ -70,6 +72,62 @@ public class FoldingProcessingOnDocumentModificationTest extends AbstractEditorT assertEquals(1, myEditor.getFoldingModel().getAllFoldRegions().length); } + public void testSurvivingBrokenPsi() throws Exception { + openJavaEditor("class Foo {\n" + + " void m() {\n" + + "\n" + + " }\n" + + "}"); + executeAction(IdeActions.ACTION_COLLAPSE_ALL_REGIONS); + checkFoldingState("[FoldRegion +(25:33), placeholder='{...}']"); + + myEditor.getDocument().insertString(0, "/*"); + checkFoldingState("[FoldRegion -(0:37), placeholder='/.../', FoldRegion +(27:35), placeholder='{...}']"); + + myEditor.getDocument().deleteString(0, 2); + checkFoldingState("[FoldRegion +(25:33), placeholder='{...}']"); + } + + public void testInvalidRegionIsRemovedOnExpanding() throws Exception { + openJavaEditor("class Foo {\n" + + " void m() {\n" + + "\n" + + " }\n" + + "}"); + executeAction(IdeActions.ACTION_COLLAPSE_ALL_REGIONS); + checkFoldingState("[FoldRegion +(25:33), placeholder='{...}']"); + + myEditor.getDocument().insertString(0, "/*"); + checkFoldingState("[FoldRegion -(0:37), placeholder='/.../', FoldRegion +(27:35), placeholder='{...}']"); + + executeAction(IdeActions.ACTION_EXPAND_ALL_REGIONS); + checkFoldingState("[FoldRegion -(0:37), placeholder='/.../']"); + } + + public void testEditingNearRegionExpandsIt() throws Exception { + openJavaEditor("class Foo {\n" + + " void m() {\n" + + "\n" + + " }\n" + + "}"); + executeAction(IdeActions.ACTION_COLLAPSE_ALL_REGIONS); + executeAction(IdeActions.ACTION_EDITOR_DELETE); + checkFoldingState("[]"); + } + + private void openJavaEditor(String text) throws Exception { + init(text, TestFileType.JAVA); + buildInitialFoldRegions(); + runFoldingPass(true); + runFoldingPass(); + } + + private static void checkFoldingState(String expectedState) { + PsiDocumentManager.getInstance(ourProject).commitDocument(myEditor.getDocument()); + runFoldingPass(); + assertEquals(expectedState, Arrays.toString(myEditor.getFoldingModel().getAllFoldRegions())); + } + private static void buildInitialFoldRegions() { CodeFoldingManager.getInstance(getProject()).buildInitialFoldings(myEditor); } @@ -78,6 +136,10 @@ public class FoldingProcessingOnDocumentModificationTest extends AbstractEditorT CodeFoldingManager.getInstance(getProject()).updateFoldRegions(myEditor); } + private static void runFoldingPass() { + runFoldingPass(false); + } + private static void runFoldingPass(boolean firstTime) { Runnable runnable = CodeFoldingManager.getInstance(getProject()).updateFoldRegionsAsync(myEditor, firstTime); assertNotNull(runnable); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/folding/impl/FoldingUpdate.java b/platform/lang-impl/src/com/intellij/codeInsight/folding/impl/FoldingUpdate.java index 43e742168970..127e65944b63 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/folding/impl/FoldingUpdate.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/folding/impl/FoldingUpdate.java @@ -127,6 +127,7 @@ public class FoldingUpdate { }; Set dependencies = new HashSet(); dependencies.add(document); + dependencies.add(editor.getFoldingModel()); for (FoldingDescriptor descriptor : elementsToFoldMap.values()) { dependencies.addAll(descriptor.getDependencies()); } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/folding/impl/UpdateFoldRegionsOperation.java b/platform/lang-impl/src/com/intellij/codeInsight/folding/impl/UpdateFoldRegionsOperation.java index 0dea15c5a013..7f4c2ad556ff 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/folding/impl/UpdateFoldRegionsOperation.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/folding/impl/UpdateFoldRegionsOperation.java @@ -37,8 +37,7 @@ import java.util.Collection; import java.util.List; import java.util.Map; -import static com.intellij.util.containers.ContainerUtil.newArrayList; -import static com.intellij.util.containers.ContainerUtil.newTroveMap; +import static com.intellij.util.containers.ContainerUtil.*; /** * @author cdr @@ -172,6 +171,8 @@ class UpdateFoldRegionsOperation implements Runnable { List toRemove = newArrayList(); InjectedLanguageManager injectedManager = InjectedLanguageManager.getInstance(myProject); for (FoldRegion region : foldingModel.getAllFoldRegions()) { + if (!region.isExpanded() && !isRegionInCaretLine(region)) continue; + PsiElement element = info.getPsiElement(region); if (element != null) { PsiFile containingFile = element.getContainingFile(); @@ -225,4 +226,10 @@ class UpdateFoldRegionsOperation implements Runnable { } } + private boolean isRegionInCaretLine(FoldRegion region) { + int regionStartLine = myEditor.getDocument().getLineNumber(region.getStartOffset()); + int regionEndLine = myEditor.getDocument().getLineNumber(region.getEndOffset()); + int caretLine = myEditor.getCaretModel().getLogicalPosition().line; + return caretLine >= regionStartLine && caretLine <= regionEndLine; + } } 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 c15e7f8374d5..efe4b2311172 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 @@ -35,6 +35,7 @@ import com.intellij.openapi.editor.ex.*; import com.intellij.openapi.editor.markup.TextAttributes; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.ModificationTracker; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.MultiMap; import org.jetbrains.annotations.NotNull; @@ -43,8 +44,9 @@ import org.jetbrains.annotations.Nullable; import java.awt.*; import java.util.Arrays; import java.util.List; +import java.util.concurrent.atomic.AtomicLong; -public class FoldingModelImpl implements FoldingModelEx, PrioritizedInternalDocumentListener, Dumpable { +public class FoldingModelImpl implements FoldingModelEx, PrioritizedInternalDocumentListener, Dumpable, ModificationTracker { private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorFoldingModelImpl"); private static final Key SAVED_CARET_POSITION = Key.create("saved.position.before.folding"); @@ -63,6 +65,7 @@ public class FoldingModelImpl implements FoldingModelEx, PrioritizedInternalDocu private int mySavedCaretShift; private final MultiMap myGroups = new MultiMap(); private boolean myDocumentChangeProcessed = true; + private final AtomicLong myExpansionCounter = new AtomicLong(); public FoldingModelImpl(EditorImpl editor) { myEditor = editor; @@ -334,6 +337,7 @@ public class FoldingModelImpl implements FoldingModelEx, PrioritizedInternalDocu } myFoldRegionsProcessed = true; + myExpansionCounter.incrementAndGet(); ((FoldRegionImpl) region).setExpandedInternal(true); notifyListenersOnFoldRegionStateChange(region); } @@ -584,4 +588,9 @@ public class FoldingModelImpl implements FoldingModelEx, PrioritizedInternalDocu public String toString() { return dumpState(); } + + @Override + public long getModificationCount() { + return myExpansionCounter.get(); + } }