diff --git a/platform/lang-api/src/com/intellij/lang/folding/FoldingBuilder.java b/platform/lang-api/src/com/intellij/lang/folding/FoldingBuilder.java index 2e527cb7cbc0..9750f9def470 100644 --- a/platform/lang-api/src/com/intellij/lang/folding/FoldingBuilder.java +++ b/platform/lang-api/src/com/intellij/lang/folding/FoldingBuilder.java @@ -32,6 +32,7 @@ import org.jetbrains.annotations.Nullable; public interface FoldingBuilder { /** * Builds the folding regions for the specified node in the AST tree and its children. + * Note that you can have several folding regions for one AST node, i.e. several FoldingDescriptior with similar AST node. * * @param node the node for which folding is requested. * @param document the document for which folding is built. Can be used to retrieve line 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 45db44294342..e4c6b1ef6b7c 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 @@ -37,6 +37,7 @@ import com.intellij.psi.util.CachedValuesManager; import com.intellij.psi.util.ParameterizedCachedValue; import com.intellij.psi.util.ParameterizedCachedValueProvider; import com.intellij.util.ArrayUtil; +import com.intellij.util.containers.MultiMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -86,7 +87,7 @@ public class FoldingUpdate { final Editor editor, final boolean applyDefaultState) { - final TreeMap elementsToFoldMap = new TreeMap(COMPARE_BY_OFFSET); + final FoldingMap elementsToFoldMap = new FoldingMap(); getFoldingsFor(file instanceof PsiCompiledElement ? (PsiFile)((PsiCompiledElement)file).getMirror() : file, document, elementsToFoldMap, quick); final UpdateFoldRegionsOperation operation = new UpdateFoldRegionsOperation(project, editor, file, elementsToFoldMap, applyDefaultState, false); @@ -120,8 +121,7 @@ public class FoldingUpdate { List injectedDocuments = InjectedLanguageUtil.getCachedInjectedDocuments(file); if (injectedDocuments.isEmpty()) return null; final List injectedEditors = new ArrayList(); - final List injectedFiles = new ArrayList(); - final List> maps = new ArrayList>(); + final List maps = new ArrayList(); for (DocumentWindow injectedDocument : injectedDocuments) { PsiFile injectedFile = PsiDocumentManager.getInstance(project).getPsiFile(injectedDocument); if (injectedFile == null || !injectedFile.isValid() || !injectedDocument.isValid()) continue; @@ -129,8 +129,7 @@ public class FoldingUpdate { if (!(injectedEditor instanceof EditorWindow)) continue; injectedEditors.add((EditorWindow)injectedEditor); - injectedFiles.add(injectedFile); - Map map = new TreeMap(COMPARE_BY_OFFSET); + final FoldingMap map = new FoldingMap(); maps.add(map); getFoldingsFor(injectedFile, injectedDocument, map, false); } @@ -141,8 +140,8 @@ public class FoldingUpdate { EditorWindow injectedEditor = injectedEditors.get(i); PsiFile injectedFile = injectedFiles.get(i); if (!injectedEditor.getDocument().isValid()) continue; - Map map = maps.get(i); - UpdateFoldRegionsOperation op = new UpdateFoldRegionsOperation(project, injectedEditor, injectedFile, map, applyDefaultState, true); + FoldingMap map = maps.get(i); + UpdateFoldRegionsOperation op = new UpdateFoldRegionsOperation(project, injectedEditor, map, applyDefaultState, true); injectedEditor.getFoldingModel().runBatchFoldingOperationDoNotCollapseCaret(op); } @@ -151,7 +150,10 @@ public class FoldingUpdate { }; } - private static void getFoldingsFor(@NotNull PsiFile file, @NotNull Document document, @NotNull Map elementsToFoldMap, boolean quick) { + private static void getFoldingsFor(@NotNull PsiFile file, + @NotNull Document document, + @NotNull FoldingMap elementsToFoldMap, + boolean quick) { final FileViewProvider viewProvider = file.getViewProvider(); for (final Language language : viewProvider.getLanguages()) { final PsiFile psi = viewProvider.getPsi(language); @@ -167,10 +169,17 @@ public class FoldingUpdate { " and called on file " + psi + " is outside document range: " + docRange); } - elementsToFoldMap.put(descriptor.getElement().getPsi(), descriptor); + elementsToFoldMap.putValue(descriptor.getElement().getPsi(), descriptor); } } } } + public static class FoldingMap extends MultiMap{ + @Override + protected Map> createMap() { + return new TreeMap>(COMPARE_BY_OFFSET); + } + } + } 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 ded0bd20d842..3c2003cdbc4e 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 @@ -33,6 +33,7 @@ import com.intellij.psi.SmartPointerManager; import com.intellij.util.containers.HashMap; import org.jetbrains.annotations.NotNull; +import java.util.Collection; import java.util.List; import java.util.Map; @@ -43,20 +44,20 @@ import static com.intellij.util.containers.CollectionFactory.newTroveMap; * @author cdr */ class UpdateFoldRegionsOperation implements Runnable { - + static final Key ALLOW_FOLDING_ON_CARET_LINE_KEY = Key.create("AllowFoldingOnCaretLine.KEY"); - + private final Project myProject; private final Editor myEditor; private final PsiFile myFile; private final boolean myApplyDefaultState; - private final Map myElementsToFoldMap; + private final FoldingUpdate.FoldingMap myElementsToFoldMap; private final boolean myForInjected; UpdateFoldRegionsOperation(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file, - @NotNull Map elementsToFoldMap, + @NotNull FoldingUpdate.FoldingMap elementsToFoldMap, boolean applyDefaultState, boolean forInjected) { myProject = project; @@ -79,7 +80,7 @@ class UpdateFoldRegionsOperation implements Runnable { List newRegions = addNewRegions(info, foldingModel, rangeToExpandStatusMap, shouldExpand, groupExpand); applyExpandStatus(newRegions, shouldExpand, groupExpand); - + // Reset the key. myEditor.putUserData(ALLOW_FOLDING_ON_CARET_LINE_KEY, false); } @@ -102,19 +103,18 @@ class UpdateFoldRegionsOperation implements Runnable { Map groupExpand) { List newRegions = arrayList(); SmartPointerManager smartPointerManager = SmartPointerManager.getInstance(myProject); - - for (final Map.Entry entry : myElementsToFoldMap.entrySet()) { + for (PsiElement element : myElementsToFoldMap.keySet()) { ProgressManager.checkCanceled(); - PsiElement element = entry.getKey(); - final FoldingDescriptor descriptor = entry.getValue(); - FoldingGroup group = descriptor.getGroup(); - TextRange range = descriptor.getRange(); - String placeholder = descriptor.getPlaceholderText(); - FoldRegion region = foldingModel.createFoldRegion(range.getStartOffset(), range.getEndOffset(), - placeholder == null ? "..." : placeholder, - group, - descriptor.isNonExpandable()); - if (region == null) continue; + final Collection descriptors = myElementsToFoldMap.get(element); + for (FoldingDescriptor descriptor : descriptors) { + FoldingGroup group = descriptor.getGroup(); + TextRange range = descriptor.getRange(); + String placeholder = descriptor.getPlaceholderText(); + FoldRegion region = foldingModel.createFoldRegion(range.getStartOffset(), range.getEndOffset(), + placeholder == null ? "..." : placeholder, + group, + descriptor.isNonExpandable()); + if (region == null) continue; PsiElement psi = descriptor.getElement().getPsi(); @@ -126,15 +126,25 @@ class UpdateFoldRegionsOperation implements Runnable { info.addRegion(region, smartPointerManager.createSmartPsiElementPointer(psi, myFile)); newRegions.add(region); - boolean expandStatus = !descriptor.isNonExpandable() && shouldExpandNewRegion(element, range, rangeToExpandStatusMap); - if (group == null) { - shouldExpand.put(region, expandStatus); - } - else { - final Boolean alreadyExpanded = groupExpand.get(group); - groupExpand.put(group, alreadyExpanded == null ? expandStatus : alreadyExpanded.booleanValue() || expandStatus); + if (psi == null || !psi.isValid() || !foldingModel.addFoldRegion(region)) { + region.dispose(); + continue; + } + + info.addRegion(region, smartPointerManager.createSmartPsiElementPointer(psi)); + newRegions.add(region); + + boolean expandStatus = !descriptor.isNonExpandable() && shouldExpandNewRegion(element, range, rangeToExpandStatusMap); + if (group == null) { + shouldExpand.put(region, expandStatus); + } + else { + final Boolean alreadyExpanded = groupExpand.get(group); + groupExpand.put(group, alreadyExpanded == null ? expandStatus : alreadyExpanded.booleanValue() || expandStatus); + } } } + return newRegions; } @@ -174,21 +184,23 @@ class UpdateFoldRegionsOperation implements Runnable { if (isInjected != myForInjected) continue; } if (element != null && myElementsToFoldMap.containsKey(element)) { - final FoldingDescriptor descriptor = myElementsToFoldMap.get(element); - TextRange range = descriptor.getRange(); - if (!region.isValid() || - region.getGroup() != null || - descriptor.getGroup() != null || - region.getStartOffset() != range.getStartOffset() || - region.getEndOffset() != range.getEndOffset() || - !region.getPlaceholderText().equals(descriptor.getPlaceholderText()) || - range.getLength() < 2 - ) { - rangeToExpandStatusMap.put(range, region.isExpanded()); - toRemove.add(region); - } - else { - myElementsToFoldMap.remove(element); + final Collection descriptors = myElementsToFoldMap.get(element); + for (FoldingDescriptor descriptor : descriptors) { + TextRange range = descriptor.getRange(); + if (!region.isValid() || + region.getGroup() != null || + descriptor.getGroup() != null || + region.getStartOffset() != range.getStartOffset() || + region.getEndOffset() != range.getEndOffset() || + !region.getPlaceholderText().equals(descriptor.getPlaceholderText()) || + range.getLength() < 2 + ) { + rangeToExpandStatusMap.put(range, region.isExpanded()); + toRemove.add(region); + } + else { + myElementsToFoldMap.remove(element); + } } } else if (region.isValid() && info.isLightRegion(region)) {