diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/folding/JavaFoldingTest.groovy b/java/java-tests/testSrc/com/intellij/codeInsight/folding/JavaFoldingTest.groovy index f87cd5bc331e..1f31d9d3435a 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/folding/JavaFoldingTest.groovy +++ b/java/java-tests/testSrc/com/intellij/codeInsight/folding/JavaFoldingTest.groovy @@ -426,6 +426,26 @@ public class Test { } assert count == 2 : "Not all custom regions are found"; } + + public void "test custom foldings intersecting with language ones"() { + def text = """\ +class Foo { +//********************************************* +// region Some +//********************************************* + + int t = 1; + +//********************************************* +// endregion +//********************************************* +} +""" + configure text + def foldingModel = myFixture.editor.foldingModel as FoldingModelImpl + assertEquals 1, foldRegionsCount + assertEquals "Some", foldingModel.allFoldRegions[0].placeholderText + } public void "test custom folding collapsed by default"() { def text = """\ diff --git a/platform/core-api/src/com/intellij/lang/folding/CustomFoldingBuilder.java b/platform/core-api/src/com/intellij/lang/folding/CustomFoldingBuilder.java index ab03627d7d6a..17fd5fc8fb98 100644 --- a/platform/core-api/src/com/intellij/lang/folding/CustomFoldingBuilder.java +++ b/platform/core-api/src/com/intellij/lang/folding/CustomFoldingBuilder.java @@ -41,22 +41,50 @@ public abstract class CustomFoldingBuilder extends FoldingBuilderEx implements P @NotNull @Override public final FoldingDescriptor[] buildFoldRegions(@NotNull PsiElement root, @NotNull Document document, boolean quick) { - List descriptors = new ArrayList(); + List customRegions = new ArrayList(); if (CustomFoldingProvider.getAllProviders().length > 0) { myDefaultProvider = null; ASTNode rootNode = root.getNode(); if (rootNode != null) { - addCustomFoldingRegionsRecursively(new FoldingStack(rootNode), rootNode, descriptors, 0); + addCustomFoldingRegionsRecursively(new FoldingStack(rootNode), rootNode, customRegions, 0); } } - buildLanguageFoldRegions(descriptors, root, document, quick); - return descriptors.toArray(new FoldingDescriptor[descriptors.size()]); + List langRegions = new ArrayList(); + buildLanguageFoldRegions(langRegions, root, document, quick); + List regions = addNonOverlappingRegions(customRegions, langRegions); + return regions.toArray(new FoldingDescriptor[regions.size()]); } - @NotNull - @Override - public final FoldingDescriptor[] buildFoldRegions(@NotNull ASTNode node, @NotNull Document document) { - return buildFoldRegions(node.getPsi(), document, false); + private static List addNonOverlappingRegions(List customRegions, + List otherRegions) { + List result = new ArrayList(customRegions.size() + otherRegions.size()); + result.addAll(customRegions); + outer: + for (FoldingDescriptor region : otherRegions) { + for (int i = customRegions.size() - 1; i >= 0; i--) { + FoldingDescriptor custom = customRegions.get(i); + if (custom.getRange().getEndOffset() <= region.getRange().getStartOffset()) { + // custom regions are ordered by their end offset (due to AST traversal order in addCustomFoldingRegionsRecursively method) + break; + } + if (intersect(region, custom)) { + // Custom regions take preference if they overlap with language regions + continue outer; + } + } + result.add(region); + } + return result; + } + + private static boolean intersect(FoldingDescriptor region1, FoldingDescriptor region2) { + int start1 = region1.getRange().getStartOffset(); + int end1 = region1.getRange().getEndOffset(); + int start2 = region2.getRange().getStartOffset(); + int end2 = region2.getRange().getEndOffset(); + return start1 == start2 && end1 == end2 + || start1 < start2 && start2 < end1 && end1 < end2 + || start2 < start1 && start1 < end2 && end2 < end1; } /** @@ -78,6 +106,7 @@ public abstract class CustomFoldingBuilder extends FoldingBuilderEx implements P @NotNull List descriptors, int currDepth) { FoldingStack localFoldingStack = isCustomFoldingRoot(node) ? new FoldingStack(node) : foldingStack; + // an optimization in addNonOverlappingRegions method depends on AST traversal order here for (ASTNode child = node.getFirstChildNode(); child != null; child = child.getTreeNext()) { if (isCustomRegionStart(child)) { localFoldingStack.push(child);