From 2de765823ef68322824d43fce464a032365f2c7c Mon Sep 17 00:00:00 2001 From: Dmitry Batrak Date: Wed, 4 Jun 2014 11:44:29 +0400 Subject: [PATCH] folding model implementation cleanup --- .../intellij/openapi/editor/FoldingModel.java | 23 +- .../openapi/editor/impl/FoldRegionsTree.java | 96 +++--- .../openapi/editor/FoldingStressTest.java | 92 ++++++ .../intellij/openapi/editor/FoldingTest.java | 281 ++++++++---------- 4 files changed, 285 insertions(+), 207 deletions(-) create mode 100644 platform/platform-tests/testSrc/com/intellij/openapi/editor/FoldingStressTest.java diff --git a/platform/editor-ui-api/src/com/intellij/openapi/editor/FoldingModel.java b/platform/editor-ui-api/src/com/intellij/openapi/editor/FoldingModel.java index dca85b604208..0720134b2e08 100644 --- a/platform/editor-ui-api/src/com/intellij/openapi/editor/FoldingModel.java +++ b/platform/editor-ui-api/src/com/intellij/openapi/editor/FoldingModel.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * Copyright 2000-2014 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. @@ -33,11 +33,19 @@ public interface FoldingModel { * @param startOffset the start offset of the region to fold. * @param endOffset the end offset of the region to fold. * @param placeholderText the text to display instead of the region contents when the region is folded. - * @return the fold region, or null if folding is currently disabled. + * @return the fold region, or null if folding is currently disabled or corresponding region cannot be added (e.g. if it + * intersects with another existing region) */ @Nullable FoldRegion addFoldRegion(int startOffset, int endOffset, @NotNull String placeholderText); + /** + * Tries to add given region to the folding model. This method must be called + * from the Runnable passed to {@link #runBatchFoldingOperation(Runnable)}. + * + * @return true, if region was added successfully, false if the region cannot be added, e.g. if it + * intersects with another existing region + */ boolean addFoldRegion(@NotNull FoldRegion region); /** @@ -57,13 +65,22 @@ public interface FoldingModel { FoldRegion[] getAllFoldRegions(); /** - * Checks if the specified offset in the document belongs to a folded region. + * Checks if the specified offset in the document belongs to a folded region. The region must contain given offset or be located right + * after given offset, i.e. the following condition must hold: foldStartOffset <= offset < foldEndOffset. * * @param offset the offset to check. * @return true if the offset belongs to a folded region, false otherwise. + * + * @see #getCollapsedRegionAtOffset(int) */ boolean isOffsetCollapsed(int offset); + /** + * Returns collapsed folded region at a given offset or null if there's no such region. Returned region will satisfy the + * following condition: region.getStartOffset() <= offset < region.getEndOffset() + * + * @see #isOffsetCollapsed(int) + */ @Nullable FoldRegion getCollapsedRegionAtOffset(int offset); diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/FoldRegionsTree.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/FoldRegionsTree.java index e9987064169f..6c959e3e2ad8 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/FoldRegionsTree.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/FoldRegionsTree.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * Copyright 2000-2014 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. @@ -40,7 +40,11 @@ abstract class FoldRegionsTree { private int[] myCachedStartOffsets; private int[] myCachedFoldedLines; int myCachedLastIndex = -1; - private ArrayList myRegions = ContainerUtil.newArrayList(); //sorted in tree left-to-right topdown traversal order + + //sorted using RangeMarker.BY_START_OFFSET comparator + //i.e., first by start offset, then, if start offsets are equal, by end offset + private ArrayList myRegions = ContainerUtil.newArrayList(); + private static final Comparator BY_END_OFFSET = new Comparator() { @Override public int compare(FoldRegion r1, FoldRegion r2) { @@ -89,13 +93,15 @@ abstract class FoldRegionsTree { } allValid.add(region); - + + if (!region.isExpanded()) { + removeRegionsWithSameStartOffset(visible, region); + removeRegionsWithSameStartOffset(topLevels, region); + } + if (currentCollapsed == null || !contains(currentCollapsed, region)) { visible.add(region); - } - - if (!region.isExpanded()) { - if (currentCollapsed == null || currentCollapsed.getEndOffset() < region.getStartOffset()) { + if (!region.isExpanded()) { currentCollapsed = region; topLevels.add(region); } @@ -115,6 +121,17 @@ abstract class FoldRegionsTree { updateCachedOffsets(); } + private static void removeRegionsWithSameStartOffset(List regions, FoldRegion region) { + for (int i = regions.size() - 1; i >= 0 ; i--) { + if (regions.get(i).getStartOffset() == region.getStartOffset()) { + regions.remove(i); + } + else { + break; + } + } + } + @NotNull private static FoldRegion[] toFoldArray(@NotNull List topLevels) { return topLevels.isEmpty() ? FoldRegion.EMPTY_ARRAY : topLevels.toArray(new FoldRegion[topLevels.size()]); @@ -164,31 +181,25 @@ abstract class FoldRegionsTree { boolean addRegion(FoldRegion range) { // During batchProcessing elements are inserted in ascending order, // binary search find acceptable insertion place first time - final boolean canUseCachedValue; - if (myCachedLastIndex >= myRegions.size()) { - // todo this happens after removeRegion()... myCachedListIndex must die! - canUseCachedValue = false; + boolean canUseCachedValue = false; + if (isBatchFoldingProcessing() && myCachedLastIndex >= 0 && myCachedLastIndex < myRegions.size()) { + FoldRegion lastRegion = myRegions.get(myCachedLastIndex); + if (RangeMarker.BY_START_OFFSET.compare(lastRegion, range) < 0) { + canUseCachedValue = myCachedLastIndex == (myRegions.size() - 1) + || RangeMarker.BY_START_OFFSET.compare(range, myRegions.get(myCachedLastIndex + 1)) <= 0; + } } - else { - canUseCachedValue = - myCachedLastIndex != -1 && isBatchFoldingProcessing() && myRegions.get(myCachedLastIndex).getStartOffset() <= range.getStartOffset(); - } - int fastIndex = canUseCachedValue ? myCachedLastIndex + 1 : Collections.binarySearch(myRegions, range, RangeMarker.BY_START_OFFSET); - if (fastIndex < 0) fastIndex = -fastIndex - 1; - - // There is a possible case that given range is the first at the current batch iteration. It's also possible that it - // range with the same bounds is already registered (e.g. particular range is registered during 'build initial fold regions' phase - // and given range has the same offsets but different 'expanded' status. - // We explicitly check for such situation, remove existing region and add the given one instead. - if (fastIndex < myRegions.size()) { - FoldRegion foldRegion = myRegions.get(fastIndex); + int index = canUseCachedValue ? myCachedLastIndex + 1 : Collections.binarySearch(myRegions, range, RangeMarker.BY_START_OFFSET); + if (index < 0) index = -index - 1; + + if (index < myRegions.size()) { + FoldRegion foldRegion = myRegions.get(index); if (TextRange.areSegmentsEqual(foldRegion, range)) { - removeRegion(foldRegion); - return addRegion(range); - } + return false; + } } - for (int i = fastIndex - 1; i >=0; --i) { + for (int i = index - 1; i >=0; --i) { final FoldRegion region = myRegions.get(i); if (region.getEndOffset() < range.getStartOffset()) break; if (region.isValid() && intersects(region, range)) { @@ -196,28 +207,15 @@ abstract class FoldRegionsTree { } } - for (int i = fastIndex; i < myRegions.size(); i++) { + for (int i = index; i < myRegions.size(); i++) { final FoldRegion region = myRegions.get(i); - - if (range.getStartOffset() < region.getStartOffset() || - range.getStartOffset() == region.getStartOffset() && range.getEndOffset() > region.getEndOffset()) { - for (int j = i + 1; j < myRegions.size(); j++) { - final FoldRegion next = myRegions.get(j); - if (next.getEndOffset() >= range.getEndOffset() && next.isValid()) { - if (next.getStartOffset() < range.getStartOffset()) { - return false; - } - else { - break; - } - } - } - - myRegions.add(myCachedLastIndex = i, range); - return true; + if (region.getStartOffset() > range.getEndOffset()) break; + if (region.isValid() && intersects(region, range)) { + return false; } } - myRegions.add(myCachedLastIndex = myRegions.size(),range); + + myRegions.add(myCachedLastIndex = index,range); return true; } @@ -274,7 +272,7 @@ abstract class FoldRegionsTree { } private static boolean contains(FoldRegion outer, FoldRegion inner) { - return outer.getStartOffset() < inner.getStartOffset() && outer.getEndOffset() > inner.getStartOffset(); + return outer.getStartOffset() <= inner.getStartOffset() && outer.getEndOffset() >= inner.getEndOffset(); } private static boolean intersects(FoldRegion r1, FoldRegion r2) { @@ -282,7 +280,7 @@ abstract class FoldRegionsTree { final int s2 = r2.getStartOffset(); final int e1 = r1.getEndOffset(); final int e2 = r2.getEndOffset(); - return s1 == s2 && e1 == e2 || s1 < s2 && s2 < e1 && e1 < e2 || s2 < s1 && s1 < e2 && e2 < e1; + return s1 < s2 && s2 < e1 && e1 < e2 || s2 < s1 && s1 < e2 && e2 < e1; } static boolean contains(FoldRegion region, int offset) { diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/editor/FoldingStressTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/editor/FoldingStressTest.java new file mode 100644 index 000000000000..3eff9286d246 --- /dev/null +++ b/platform/platform-tests/testSrc/com/intellij/openapi/editor/FoldingStressTest.java @@ -0,0 +1,92 @@ +/* + * Copyright 2000-2014 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.editor; + +import com.intellij.openapi.editor.impl.DocumentImpl; +import com.intellij.testFramework.LightPlatformTestCase; +import com.intellij.testFramework.PlatformTestCase; + +public class FoldingStressTest extends LightPlatformTestCase { + + static { + PlatformTestCase.autodetectPlatformPrefix(); + } + + public void testStressFoldingFromZeroOffset() throws Exception { + for (int len = 2; len < 25; len++) { + stress(len); + } + } + + public void testStress8() throws Exception { + DocumentImpl doc = new DocumentImpl("0123456789\n123456789\n23456789"); + Editor editor = EditorFactory.getInstance().createEditor(doc); + try { + final FoldingModel model = editor.getFoldingModel(); + model.runBatchFoldingOperation(new Runnable() { + @Override + public void run() { + addAndCollapseFoldRegion(model, 0, 8, "/*...*/"); + addAndCollapseFoldRegion(model, 10, 12, "/*...*/"); + } + }); + + assertEquals(10, editor.logicalPositionToOffset(new LogicalPosition(0, 10))); + + for (int line = 0; line <= 3; line++) { + for (int column = 0; column <= 100; column++) { + LogicalPosition log = new LogicalPosition(line, column); + editor.logicalToVisualPosition(log); + } + } + } + finally { + EditorFactory.getInstance().releaseEditor(editor); + } + } + + private static void stress(final int len) { + DocumentImpl doc = new DocumentImpl("0123456789\n123456789\n23456789"); + Editor editor = EditorFactory.getInstance().createEditor(doc); + try { + final FoldingModel model = editor.getFoldingModel(); + model.runBatchFoldingOperation(new Runnable() { + @Override + public void run() { + addAndCollapseFoldRegion(model, 0, len, "/*...*/"); + addAndCollapseFoldRegion(model, len + 2, len + 4, "/*...*/"); + } + }); + + for (int line = 0; line <= 3; line++) { + for (int column = 0; column <= 100; column++) { + LogicalPosition log = new LogicalPosition(line, column); + editor.logicalToVisualPosition(log); + } + } + } + finally { + EditorFactory.getInstance().releaseEditor(editor); + } + } + + private static void addAndCollapseFoldRegion(FoldingModel model, int startOffset, int endOffset, String placeHolder) { + FoldRegion foldRegion = model.addFoldRegion(startOffset, endOffset, placeHolder); + assertNotNull(foldRegion); + foldRegion.setExpanded(false); + } + +} diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/editor/FoldingTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/editor/FoldingTest.java index d9fb2c5cb29c..5864112f9faa 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/editor/FoldingTest.java +++ b/platform/platform-tests/testSrc/com/intellij/openapi/editor/FoldingTest.java @@ -1,182 +1,153 @@ +/* + * Copyright 2000-2014 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.intellij.openapi.editor; import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.editor.ex.FoldingModelEx; -import com.intellij.openapi.editor.impl.DocumentImpl; -import com.intellij.openapi.util.Ref; -import com.intellij.testFramework.LightPlatformTestCase; +import com.intellij.openapi.editor.impl.AbstractEditorTest; import com.intellij.testFramework.PlatformTestCase; -import org.jetbrains.annotations.NonNls; +import com.intellij.testFramework.TestFileType; + +import static org.junit.Assert.assertArrayEquals; /** * @author max */ -public class FoldingTest extends LightPlatformTestCase { - +public class FoldingTest extends AbstractEditorTest { static { PlatformTestCase.autodetectPlatformPrefix(); } - public void testStressFoldingFromZeroOffset() throws Exception { - for (int len = 2; len < 25; len++) { - stress(len); - } - } + private FoldingModelEx myModel; - public void testStress8() throws Exception { - DocumentImpl doc = new DocumentImpl("0123456789\n123456789\n23456789"); - Editor editor = EditorFactory.getInstance().createEditor(doc); - try { - final FoldingModel model = editor.getFoldingModel(); - model.runBatchFoldingOperation(new Runnable() { - @Override - public void run() { - model.addFoldRegion(0, 8, "/*...*/").setExpanded(false); - model.addFoldRegion(10, 12, "/*...*/").setExpanded(false); - } - }); - - assertEquals(10, editor.logicalPositionToOffset(new LogicalPosition(0, 10))); - - for (int line = 0; line <= 3; line++) { - for (int column = 0; column <= 100; column++) { - LogicalPosition log = new LogicalPosition(line, column); - editor.logicalToVisualPosition(log); - } - } - } - finally { - EditorFactory.getInstance().releaseEditor(editor); - } - } - - private static void stress(final int len) { - DocumentImpl doc = new DocumentImpl("0123456789\n123456789\n23456789"); - Editor editor = EditorFactory.getInstance().createEditor(doc); - try { - final FoldingModel model = editor.getFoldingModel(); - model.runBatchFoldingOperation(new Runnable() { - @Override - public void run() { - model.addFoldRegion(0, len, "/*...*/").setExpanded(false); - model.addFoldRegion(len + 2, len + 4, "/*...*/").setExpanded(false); - } - }); - - for (int line = 0; line <= 3; line++) { - for (int column = 0; column <= 100; column++) { - LogicalPosition log = new LogicalPosition(line, column); - editor.logicalToVisualPosition(log); - } - } - } - finally { - EditorFactory.getInstance().releaseEditor(editor); - } + @Override + public void setUp() throws Exception { + super.setUp(); + init("I don't know what you mean by `glory,'\" Alice said" + + "Humpty Dumpty smiled contemptuously. \"Of course you don't -- till I tell you. I meant `there's a nice knock-down argument for you!'" + + "But glory doesn't mean `a nice knock-down argument,'\" Alice objected." + + "When I use a word,\" Humpty Dumpty said, in a rather scornful tone, \"it means just what I choose it to mean -- neither more nor less." + + "The question is,\" said Alice, \"whether you can make words mean so many different things." + + "The question is,\" said Humpty Dumpty, \"which is to be master -- that's all.", + TestFileType.TEXT); + myModel = (FoldingModelEx)myEditor.getFoldingModel(); } public void testCleanupInvalidRegions() { - final DocumentImpl doc = new DocumentImpl("foo1\nfoo2\nfoo3\nfoo4"); - Editor editor = EditorFactory.getInstance().createEditor(doc); - final FoldingModel model = editor.getFoldingModel(); - try { - model.runBatchFoldingOperation(new Runnable() { - @Override - public void run() { - model.addFoldRegion(0, 4, "/*...*/"); - model.addFoldRegion(5, 9, "/*...*/"); - } - }); - assertSize(2, model.getAllFoldRegions()); - WriteCommandAction.runWriteCommandAction(getProject(), new Runnable() { - @Override - public void run() { - doc.deleteString(0, 5); - } - }); - assertSize(1, model.getAllFoldRegions()); - } - finally { - EditorFactory.getInstance().releaseEditor(editor); - } + myModel.runBatchFoldingOperation(new Runnable() { + @Override + public void run() { + myModel.addFoldRegion(0, 4, "/*...*/"); + myModel.addFoldRegion(5, 9, "/*...*/"); + } + }); + assertSize(2, myModel.getAllFoldRegions()); + WriteCommandAction.runWriteCommandAction(getProject(), new Runnable() { + @Override + public void run() { + myEditor.getDocument().deleteString(0, 5); + } + }); + assertSize(1, myModel.getAllFoldRegions()); } public void testIntersects () throws Exception { - @NonNls DocumentImpl doc = new DocumentImpl("I don't know what you mean by `glory,'\" Alice said" + - "Humpty Dumpty smiled contemptuously. \"Of course you don't -- till I tell you. I meant `there's a nice knock-down argument for you!'" + - "But glory doesn't mean `a nice knock-down argument,'\" Alice objected." + - "When I use a word,\" Humpty Dumpty said, in a rather scornful tone, \"it means just what I choose it to mean -- neither more nor less." + - "The question is,\" said Alice, \"whether you can make words mean so many different things." + - "The question is,\" said Humpty Dumpty, \"which is to be master -- that's all." ); - Editor editor = EditorFactory.getInstance().createEditor(doc); - try { - final FoldingModel model = editor.getFoldingModel(); - - model.runBatchFoldingOperation(new Runnable() { - @Override - public void run() { - FoldRegion region = model.addFoldRegion(5, 10, "."); - assertNotNull(region); - region = model.addFoldRegion(7, 11, "."); - assertNull(region); - region = model.addFoldRegion(20, 30, "."); - assertNotNull(region); - region = model.addFoldRegion(9, 12, "."); - assertNull(region); - region = model.addFoldRegion(7, 10, "."); - assertNotNull(region); - region = model.addFoldRegion(7, 10, "."); - assertNull(region); - region = model.addFoldRegion(5, 30, "."); - assertNotNull(region); - } - }); - } - finally { - EditorFactory.getInstance().releaseEditor(editor); - } + myModel.runBatchFoldingOperation(new Runnable() { + @Override + public void run() { + FoldRegion region = myModel.addFoldRegion(5, 10, "."); + assertNotNull(region); + region = myModel.addFoldRegion(7, 11, "."); + assertNull(region); + region = myModel.addFoldRegion(20, 30, "."); + assertNotNull(region); + region = myModel.addFoldRegion(9, 12, "."); + assertNull(region); + region = myModel.addFoldRegion(7, 10, "."); + assertNotNull(region); + region = myModel.addFoldRegion(7, 10, "."); + assertNull(region); + region = myModel.addFoldRegion(5, 30, "."); + assertNotNull(region); + } + }); } - public void testDuplicateRegions() { - StringBuilder text = new StringBuilder(); - for (int i = 0; i < 450; i++) { - text.append('a'); - } - Editor editor = EditorFactory.getInstance().createEditor(new DocumentImpl(text)); + public void testAddEmptyRegion() { + FoldRegion region = null; try { - final Ref expandedStatus = new Ref(); - final int startOffset = 6; - final int endOffset = 16; - final FoldingModelEx model = (FoldingModelEx)editor.getFoldingModel(); - model.runBatchFoldingOperation(new Runnable() { - @Override - public void run() { - model.addFoldRegion(2, 20, ".."); - model.addFoldRegion(4, 18, ".."); - FoldRegion oldRegion = model.addFoldRegion(startOffset, endOffset, ".."); - assertNotNull(oldRegion); - expandedStatus.set(!oldRegion.isExpanded()); - } - }); - assertEquals(3, model.getAllFoldRegions().length); - - final Ref newRegion = new Ref(); - model.runBatchFoldingOperation(new Runnable() { - @Override - public void run() { - newRegion.set(model.createFoldRegion(startOffset, endOffset, "..", null, false)); - assertNotNull(newRegion.get()); - newRegion.get().setExpanded(expandedStatus.get()); - boolean additionFlag = model.addFoldRegion(newRegion.get()); - assertTrue(additionFlag); - } - }); - FoldRegion fetched = model.fetchOutermost(startOffset); - assertSame(newRegion.get(), fetched); - assertEquals(3, model.getAllFoldRegions().length); + region = myModel.addFoldRegion(5, 5, "..."); } - finally { - EditorFactory.getInstance().releaseEditor(editor); + catch (AssertionError ignored) { } + assertNull(region); + } + + public void testCollapsedRegionQueries() { + addCollapsedFoldRegion(5, 7, "..."); + FoldRegion[] regions = myModel.getAllFoldRegions(); + assertEquals(1, regions.length); + FoldRegion region = regions[0]; + assertNotNull(region); + + assertFalse(myModel.isOffsetCollapsed(4)); + assertTrue(myModel.isOffsetCollapsed(5)); + assertTrue(myModel.isOffsetCollapsed(6)); + assertFalse(myModel.isOffsetCollapsed(7)); + assertFalse(myModel.isOffsetCollapsed(8)); + + assertNull(myModel.getCollapsedRegionAtOffset(4)); + assertSame(region, myModel.getCollapsedRegionAtOffset(5)); + assertSame(region, myModel.getCollapsedRegionAtOffset(6)); + assertNull(myModel.getCollapsedRegionAtOffset(7)); + assertNull(myModel.getCollapsedRegionAtOffset(8)); + } + + public void testAdjacentRegions() { + addCollapsedFoldRegion(5, 7, "AA"); + addCollapsedFoldRegion(7, 10, "BB"); + FoldRegion[] regions = myModel.getAllFoldRegions(); + assertEquals(2, regions.length); + FoldRegion region1 = regions[0]; + assertNotNull(region1); + FoldRegion region2 = regions[1]; + assertNotNull(region2); + + assertFalse(myModel.isOffsetCollapsed(4)); + assertTrue(myModel.isOffsetCollapsed(5)); + assertTrue(myModel.isOffsetCollapsed(6)); + assertTrue(myModel.isOffsetCollapsed(7)); + assertTrue(myModel.isOffsetCollapsed(8)); + assertFalse(myModel.isOffsetCollapsed(10)); + assertFalse(myModel.isOffsetCollapsed(11)); + + assertNull(myModel.getCollapsedRegionAtOffset(4)); + assertSame(region1, myModel.getCollapsedRegionAtOffset(5)); + assertSame(region1, myModel.getCollapsedRegionAtOffset(6)); + assertSame(region2, myModel.getCollapsedRegionAtOffset(7)); + assertSame(region2, myModel.getCollapsedRegionAtOffset(8)); + assertNull(myModel.getCollapsedRegionAtOffset(10)); + assertNull(myModel.getCollapsedRegionAtOffset(11)); + } + + public void testTopLevel() { + FoldRegion region = addCollapsedFoldRegion(5, 15, "..."); + addCollapsedFoldRegion(10, 12, "???"); + + FoldRegion[] topLevelRegions = myModel.fetchTopLevel(); + assertArrayEquals(new FoldRegion[]{region}, topLevelRegions); } }