IDEA-86907 Fold/unfold for the last node is incorrect

This commit is contained in:
Dmitry Batrak
2013-12-23 15:55:18 +04:00
parent 5c0518e821
commit 8086000a6e
5 changed files with 343 additions and 190 deletions
@@ -0,0 +1,34 @@
/*
* Copyright 2000-2013 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.impl;
import com.intellij.openapi.editor.FoldRegion;
class DisplayedFoldingAnchor {
enum Type {COLLAPSED, EXPANDED_TOP, EXPANDED_BOTTOM}
public final FoldRegion foldRegion;
public final int visualLine;
public final int foldRegionVisualLines;
public final Type type;
DisplayedFoldingAnchor(FoldRegion foldRegion, int visualLine, int foldRegionVisualLines, Type type) {
this.foldRegion = foldRegion;
this.visualLine = visualLine;
this.foldRegionVisualLines = foldRegionVisualLines;
this.type = type;
}
}
@@ -73,10 +73,8 @@ import javax.swing.plaf.ComponentUI;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.AffineTransform;
import java.util.ArrayList;
import java.util.*;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
class EditorGutterComponentImpl extends EditorGutterComponentEx implements MouseListener, MouseMotionListener {
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorGutterComponentImpl");
@@ -86,6 +84,7 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse
private static final TooltipGroup GUTTER_TOOLTIP_GROUP = new TooltipGroup("GUTTER_TOOLTIP_GROUP", 0);
private final EditorImpl myEditor;
private final FoldingAnchorsOverlayStrategy myAnchorsDisplayStrategy;
private int myLineMarkerAreaWidth = START_ICON_AREA_WIDTH + FREE_PAINTERS_AREA_WIDTH;
private int myIconsAreaWidth = START_ICON_AREA_WIDTH;
private int myLineNumberAreaWidth = 0;
@@ -118,6 +117,7 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse
}
});
}
myAnchorsDisplayStrategy = new FoldingAnchorsOverlayStrategy(editor);
}
@SuppressWarnings({"ConstantConditions"})
@@ -237,7 +237,8 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse
try {
int firstVisibleOffset = myEditor.logicalPositionToOffset(myEditor.xyToLogicalPosition(new Point(0, clip.y - myEditor.getLineHeight())));
int lastVisibleOffset = myEditor.logicalPositionToOffset(myEditor.xyToLogicalPosition(new Point(0, clip.y + clip.height + myEditor.getLineHeight())));
paintFoldingBackground(g, clip, firstVisibleOffset, lastVisibleOffset);
paintFoldingBackground(g, clip);
paintFoldingLines((Graphics2D)g, clip);
paintLineMarkers(g, clip, firstVisibleOffset, lastVisibleOffset);
paintFoldingTree(g, clip, firstVisibleOffset, lastVisibleOffset);
paintLineNumbers(g, clip);
@@ -339,7 +340,7 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse
doPaintFoldingTree((Graphics2D)g, clip, firstVisibleOffset, lastVisibleOffset);
}
else {
UIUtil.drawVDottedLine((Graphics2D)g, clip.x + clip.width -1, clip.y, clip.y + clip.height, null, getOutlineColor(false));
UIUtil.drawVDottedLine((Graphics2D)g, clip.x + clip.width - 1, clip.y, clip.y + clip.height, null, getOutlineColor(false));
}
}
@@ -816,51 +817,18 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse
updateSize();
}
private int offsetToVisualLine(int offset) {
offset = Math.min(myEditor.getDocument().getTextLength() - 1, offset);
return myEditor.offsetToVisualLine(offset);
}
private void doPaintFoldingTree(final Graphics2D g, final Rectangle clip, int firstVisibleOffset, int lastVisibleOffset) {
final int anchorX = getFoldingAreaOffset();
final int width = getFoldingAnchorWidth();
doForVisibleFoldRegions(
new NullableFunction<FoldRegion, Void>() {
@Override
public Void fun(FoldRegion foldRegion) {
drawAnchor(foldRegion, width, clip, g, anchorX, false, false);
return null;
}
},
firstVisibleOffset,
lastVisibleOffset
);
if (myActiveFoldRegion != null) {
drawAnchor(myActiveFoldRegion, width, clip, g, anchorX, true, true);
drawAnchor(myActiveFoldRegion, width, clip, g, anchorX, true, false);
Collection<DisplayedFoldingAnchor> anchorsToDisplay =
myAnchorsDisplayStrategy.getAnchorsToDisplay(firstVisibleOffset, lastVisibleOffset, myActiveFoldRegion);
for (DisplayedFoldingAnchor anchor : anchorsToDisplay) {
drawAnchor(width, clip, g, anchorX, anchor.visualLine, anchor.type, anchor.foldRegion == myActiveFoldRegion);
}
}
private void doForVisibleFoldRegions(@NotNull NullableFunction<FoldRegion, Void> action, int firstVisibleOffset, int lastVisibleOffset) {
FoldRegion[] visibleFoldRegions = myEditor.getFoldingModel().fetchVisible();
final Document document = myEditor.getDocument();
for (FoldRegion visibleFoldRegion : visibleFoldRegions) {
if (!visibleFoldRegion.isValid()) continue;
final int startOffset = visibleFoldRegion.getStartOffset();
if (startOffset > lastVisibleOffset) continue;
final int endOffset = getEndOffset(visibleFoldRegion);
if (endOffset < firstVisibleOffset) continue;
if (document.getLineNumber(startOffset) >= document.getLineNumber(endOffset)) {
//TODO den remove this check as soon as editor performance on dimension mapping is improved (IDEA-69317)
continue;
}
action.fun(visibleFoldRegion);
}
}
private void paintFoldingBackground(Graphics g, Rectangle clip, int firstVisibleOffset, int lastVisibleOffset) {
private void paintFoldingBackground(Graphics g, Rectangle clip) {
int lineX = getWhitespaceSeparatorOffset();
paintBackground(g, clip, getFoldingAreaOffset(), getFoldingAreaWidth());
@@ -868,11 +836,9 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse
g.fillRect(lineX, clip.y, getFoldingAreaWidth(), clip.height);
paintCaretRowBackground(g, lineX, getFoldingAnchorWidth());
doPaintFoldingBoxBackground((Graphics2D)g, clip, firstVisibleOffset, lastVisibleOffset);
}
private void doPaintFoldingBoxBackground(final Graphics2D g, final Rectangle clip, int firstVisibleOffset, int lastVisibleOffset) {
private void paintFoldingLines(final Graphics2D g, final Rectangle clip) {
if (!isFoldingOutlineShown()) return;
UIUtil.drawVDottedLine(g, getWhitespaceSeparatorOffset(), clip.y, clip.y + clip.height, null, getOutlineColor(false));
@@ -880,21 +846,20 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse
final int anchorX = getFoldingAreaOffset();
final int width = getFoldingAnchorWidth();
if (myActiveFoldRegion != null) {
drawFoldingLines(myActiveFoldRegion, clip, width, anchorX, g);
}
if (myActiveFoldRegion != null && myActiveFoldRegion.isExpanded() && myActiveFoldRegion.isValid()) {
int foldStart = myEditor.offsetToVisualLine(myActiveFoldRegion.getStartOffset());
int foldEnd = myEditor.offsetToVisualLine(getEndOffset(myActiveFoldRegion));
int startY = myEditor.visibleLineToY(foldStart + 1) - myEditor.getDescent();
int endY = myEditor.visibleLineToY(foldEnd) + myEditor.getLineHeight() -
myEditor.getDescent();
doForVisibleFoldRegions(
new NullableFunction<FoldRegion, Void>() {
@Override
public Void fun(FoldRegion foldRegion) {
drawAnchor(foldRegion, width, clip, g, anchorX, false, true);
return null;
}
},
firstVisibleOffset,
lastVisibleOffset
);
if (startY <= clip.y + clip.height && endY + 1 + myEditor.getDescent() >= clip.y) {
int lineX = anchorX + width / 2;
g.setColor(getOutlineColor(true));
UIUtil.drawLine(g, lineX, startY, lineX, endY);
}
}
}
@Override
@@ -911,52 +876,35 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse
public int getHeadCenterY(FoldRegion foldRange) {
int width = getFoldingAnchorWidth();
int foldStart = offsetToVisualLine(foldRange.getStartOffset());
int foldStart = myEditor.offsetToVisualLine(foldRange.getStartOffset());
return myEditor.visibleLineToY(foldStart) + myEditor.getLineHeight() - myEditor.getDescent() - width / 2;
}
private void drawAnchor(FoldRegion foldRange, int width, Rectangle clip, Graphics2D g,
int anchorX, boolean active, boolean paintBackground) {
if (!foldRange.isValid()) {
return;
}
int startOffset = foldRange.getStartOffset();
private void drawAnchor(int width, Rectangle clip, Graphics2D g, int anchorX, int visualLine,
DisplayedFoldingAnchor.Type type, boolean active) {
final int endOffset = getEndOffset(foldRange);
if (!isFoldingPossible(startOffset, endOffset)) {
return;
}
int foldStart = offsetToVisualLine(startOffset);
int y = myEditor.visibleLineToY(foldStart) + myEditor.getLineHeight() - myEditor.getDescent() -
width;
int height = width + 2;
final FoldingGroup group = foldRange.getGroup();
final boolean drawTop = group == null || myEditor.getFoldingModel().getFirstRegion(group, foldRange) == foldRange;
if (!foldRange.isExpanded()) {
if (y <= clip.y + clip.height && y + height >= clip.y) {
if (drawTop) {
drawSquareWithPlus(g, anchorX, y, width, active, paintBackground);
int y;
switch (type) {
case COLLAPSED:
y = myEditor.visibleLineToY(visualLine) + myEditor.getLineHeight() - myEditor.getDescent() - width;
if (y <= clip.y + clip.height && y + height >= clip.y) {
drawSquareWithPlus(g, anchorX, y, width, active);
}
}
}
else {
int foldEnd = offsetToVisualLine(endOffset);
int endY = myEditor.visibleLineToY(foldEnd) + myEditor.getLineHeight() -
myEditor.getDescent();
if (y <= clip.y + clip.height && y + height >= clip.y) {
if (drawTop) {
drawDirectedBox(g, anchorX, y, width, height, width - 2, active, paintBackground);
break;
case EXPANDED_TOP:
y = myEditor.visibleLineToY(visualLine) + myEditor.getLineHeight() - myEditor.getDescent() - width;
if (y <= clip.y + clip.height && y + height >= clip.y) {
drawDirectedBox(g, anchorX, y, width, height, width - 2, active);
}
}
if (endY - height <= clip.y + clip.height && endY >= clip.y) {
drawDirectedBox(g, anchorX, endY, width, -height, -width + 2, active, paintBackground);
}
break;
case EXPANDED_BOTTOM:
y = myEditor.visibleLineToY(visualLine) + myEditor.getLineHeight() - myEditor.getDescent();
if (y - height <= clip.y + clip.height && y >= clip.y) {
drawDirectedBox(g, anchorX, y, width, -height, -width + 2, active);
}
break;
}
}
@@ -972,7 +920,7 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse
int width,
int height,
int baseHeight,
boolean active, boolean paintBackground) {
boolean active) {
Object antialiasing = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
if (SystemInfo.isMac && SystemInfo.JAVA_VERSION.startsWith("1.4.1") || UIUtil.isRetina()) {
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
@@ -982,19 +930,15 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse
int[] xPoints = {anchorX, anchorX + width, anchorX + width, anchorX + width / 2, anchorX};
int[] yPoints = {y, y, y + baseHeight, y + height, y + baseHeight};
if (paintBackground) {
g.setColor(myEditor.getBackgroundColor());
g.setColor(myEditor.getBackgroundColor());
g.fillPolygon(xPoints, yPoints, 5);
g.fillPolygon(xPoints, yPoints, 5);
}
else {
g.setColor(getOutlineColor(active));
g.drawPolygon(xPoints, yPoints, 5);
g.setColor(getOutlineColor(active));
g.drawPolygon(xPoints, yPoints, 5);
//Minus
int minusHeight = y + baseHeight / 2 + (height - baseHeight) / 4;
UIUtil.drawLine(g, anchorX + 2, minusHeight, anchorX + width - 2, minusHeight);
}
//Minus
int minusHeight = y + baseHeight / 2 + (height - baseHeight) / 4;
UIUtil.drawLine(g, anchorX + 2, minusHeight, anchorX + width - 2, minusHeight);
}
finally {
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, antialiasing);
@@ -1005,9 +949,8 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse
int anchorX,
int y,
int width,
boolean active,
boolean paintBackground) {
drawSquareWithMinus(g, anchorX, y, width, active, paintBackground);
boolean active) {
drawSquareWithMinus(g, anchorX, y, width, active);
UIUtil.drawLine(g, anchorX + width / 2, y + 2, anchorX + width / 2, y + width - 2);
}
@@ -1017,37 +960,16 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse
int anchorX,
int y,
int width,
boolean active,
boolean paintBackground) {
if (paintBackground) {
g.setColor(myEditor.getBackgroundColor());
g.fillRect(anchorX, y, width, width);
}
else {
g.setColor(getOutlineColor(active));
g.drawRect(anchorX, y, width, width);
boolean active) {
g.setColor(myEditor.getBackgroundColor());
g.fillRect(anchorX, y, width, width);
// Draw plus
if (!active) g.setColor(getOutlineColor(true));
UIUtil.drawLine(g, anchorX + 2, y + width / 2, anchorX + width - 2, y + width / 2);
}
}
g.setColor(getOutlineColor(active));
g.drawRect(anchorX, y, width, width);
private void drawFoldingLines(FoldRegion foldRange, Rectangle clip, int width, int anchorX, Graphics2D g) {
if (foldRange.isExpanded() && foldRange.isValid()) {
int foldStart = offsetToVisualLine(foldRange.getStartOffset());
int foldEnd = offsetToVisualLine(getEndOffset(foldRange));
int startY = myEditor.visibleLineToY(foldStart + 1) - myEditor.getDescent();
int endY = myEditor.visibleLineToY(foldEnd) + myEditor.getLineHeight() -
myEditor.getDescent();
if (startY > clip.y + clip.height || endY + 1 + myEditor.getDescent() < clip.y) return;
int lineX = anchorX + width / 2;
g.setColor(getOutlineColor(true));
UIUtil.drawLine(g, lineX, startY, lineX, endY);
}
// Draw plus
if (!active) g.setColor(getOutlineColor(true));
UIUtil.drawLine(g, anchorX + 2, y + width / 2, anchorX + width - 2, y + width / 2);
}
private int getFoldingAnchorWidth() {
@@ -1135,55 +1057,17 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse
int anchorX = getFoldingAreaOffset();
int anchorWidth = getFoldingAnchorWidth();
FoldRegion[] visibleRanges = myEditor.getFoldingModel().fetchVisible();
for (FoldRegion foldRange : visibleRanges) {
if (!foldRange.isValid()) continue;
final FoldingGroup group = foldRange.getGroup();
if (group != null && myEditor.getFoldingModel().getFirstRegion(group, foldRange) != foldRange) {
continue;
}
int neighbourhoodStartOffset = myEditor.logicalPositionToOffset(myEditor.xyToLogicalPosition(new Point(0, y - myEditor.getLineHeight())));
int neighbourhoodEndOffset = myEditor.logicalPositionToOffset(myEditor.xyToLogicalPosition(new Point(0, y + myEditor.getLineHeight())));
int foldStart = offsetToVisualLine(foldRange.getStartOffset());
final int endOffset = getEndOffset(foldRange);
int foldEnd = offsetToVisualLine(endOffset);
if (!isFoldingPossible(foldRange.getStartOffset(), endOffset)) {
continue;
}
if (rectangleByFoldOffset(foldStart, anchorWidth, anchorX).contains(x, y)) return foldRange;
if ((group == null || foldRange.isExpanded()) && rectangleByFoldOffset(foldEnd, anchorWidth, anchorX).contains(x, y)) return foldRange;
Collection<DisplayedFoldingAnchor> displayedAnchors = myAnchorsDisplayStrategy.getAnchorsToDisplay(neighbourhoodStartOffset, neighbourhoodEndOffset, null);
for (DisplayedFoldingAnchor anchor : displayedAnchors) {
if (rectangleByFoldOffset(anchor.visualLine, anchorWidth, anchorX).contains(x, y)) return anchor.foldRegion;
}
return null;
}
/**
* Allows to answer if there may be folding for the given offsets.
* <p/>
* The rule is that we can fold range that occupies multiple logical or visual lines.
*
* @param startOffset start offset of the target region to check
* @param endOffset end offset of the target region to check
* @return
*/
private boolean isFoldingPossible(int startOffset, int endOffset) {
Document document = myEditor.getDocument();
if (startOffset >= document.getTextLength()) {
return false;
}
int endOffsetToUse = Math.min(endOffset, document.getTextLength());
if (endOffsetToUse <= startOffset) {
return false;
}
if (document.getLineNumber(startOffset) != document.getLineNumber(endOffsetToUse)) {
return true;
}
return myEditor.getSettings().isAllowSingleLogicalLineFolding()
&& !myEditor.getSoftWrapModel().getSoftWrapsForRange(startOffset, endOffsetToUse).isEmpty();
}
@SuppressWarnings("SuspiciousNameCombination")
private Rectangle rectangleByFoldOffset(int foldStart, int anchorWidth, int anchorX) {
int anchorY = myEditor.visibleLineToY(foldStart) + myEditor.getLineHeight() -
@@ -1278,11 +1278,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
public int offsetToVisualLine(int offset) {
int textLength = getDocument().getTextLength();
if (offset >= textLength) {
int result = Math.max(0, getVisibleLineCount() - 1); // lines are 0 based
if (textLength > 0 && getDocument().getCharsSequence().charAt(textLength - 1) == '\n') {
result++;
}
return result;
return Math.max(0, getVisibleLineCount() - 1); // lines are 0 based
}
int line = offsetToLogicalLine(offset);
int lineStartOffset = line >= myDocument.getLineCount() ? myDocument.getTextLength() : myDocument.getLineStartOffset(line);
@@ -5487,6 +5483,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
y = myGutterComponent.getHeadCenterY(range);
getScrollingModel().scrollVertically(y - scrollShift);
myGutterComponent.updateSize();
validateMousePointer(e);
return isNavigation;
}
}
@@ -0,0 +1,113 @@
/*
* Copyright 2000-2013 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.impl;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.FoldRegion;
import com.intellij.openapi.editor.FoldingGroup;
import com.intellij.util.containers.hash.HashMap;
import java.util.Collection;
import java.util.Map;
class FoldingAnchorsOverlayStrategy {
private final EditorImpl myEditor;
public FoldingAnchorsOverlayStrategy(EditorImpl editor) {
myEditor = editor;
}
public Collection<DisplayedFoldingAnchor> getAnchorsToDisplay(int firstVisibleOffset, int lastVisibleOffset, FoldRegion activeFoldRegion) {
Map<Integer, DisplayedFoldingAnchor> result = new HashMap<Integer, DisplayedFoldingAnchor>();
FoldRegion[] visibleFoldRegions = myEditor.getFoldingModel().fetchVisible();
for (FoldRegion region : visibleFoldRegions) {
if (!region.isValid()) continue;
final int startOffset = region.getStartOffset();
if (startOffset > lastVisibleOffset) continue;
final int endOffset = getEndOffset(region);
if (endOffset < firstVisibleOffset) continue;
if (!isFoldingPossible(startOffset, endOffset)) continue;
final FoldingGroup group = region.getGroup();
if (group != null && myEditor.getFoldingModel().getFirstRegion(group, region) != region) continue;
//offset = Math.min(myEditor.getDocument().getTextLength() - 1, offset);
int foldStart = myEditor.offsetToVisualLine(startOffset);
if (!region.isExpanded()) {
tryAdding(result, region, foldStart, 0, DisplayedFoldingAnchor.Type.COLLAPSED, activeFoldRegion);
}
else {
//offset = Math.min(myEditor.getDocument().getTextLength() - 1, offset);
int foldEnd = myEditor.offsetToVisualLine(endOffset);
tryAdding(result, region, foldStart, foldEnd - foldStart, DisplayedFoldingAnchor.Type.EXPANDED_TOP, activeFoldRegion);
tryAdding(result, region, foldEnd, foldEnd - foldStart, DisplayedFoldingAnchor.Type.EXPANDED_BOTTOM, activeFoldRegion);
}
}
return result.values();
}
private static void tryAdding(Map<Integer, DisplayedFoldingAnchor> resultsMap,
FoldRegion region,
int visualLine,
int visualHeight,
DisplayedFoldingAnchor.Type type,
FoldRegion activeRegion) {
DisplayedFoldingAnchor prev = resultsMap.get(visualLine);
if (prev != null) {
if (prev.foldRegion == activeRegion) {
return;
}
if (region != activeRegion && prev.foldRegionVisualLines < visualHeight) {
return;
}
}
resultsMap.put(visualLine, new DisplayedFoldingAnchor(region, visualLine, visualHeight, type));
}
private int getEndOffset(FoldRegion foldRange) {
FoldingGroup group = foldRange.getGroup();
return group == null ? foldRange.getEndOffset() : myEditor.getFoldingModel().getEndOffset(group);
}
/**
* Allows to answer if there may be folding for the given offsets.
* <p/>
* The rule is that we can fold range that occupies multiple logical or visual lines.
*
* @param startOffset start offset of the target region to check
* @param endOffset end offset of the target region to check
* @return
*/
private boolean isFoldingPossible(int startOffset, int endOffset) {
Document document = myEditor.getDocument();
if (startOffset >= document.getTextLength()) {
return false;
}
int endOffsetToUse = Math.min(endOffset, document.getTextLength());
if (endOffsetToUse <= startOffset) {
return false;
}
if (document.getLineNumber(startOffset) != document.getLineNumber(endOffsetToUse)) {
return true;
}
return myEditor.getSettings().isAllowSingleLogicalLineFolding()
&& !myEditor.getSoftWrapModel().getSoftWrapsForRange(startOffset, endOffsetToUse).isEmpty();
}
}
@@ -0,0 +1,125 @@
/*
* Copyright 2000-2013 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.impl;
import com.intellij.codeInsight.folding.CodeFoldingManager;
import com.intellij.openapi.editor.FoldRegion;
import com.intellij.openapi.editor.FoldingModel;
import com.intellij.openapi.fileTypes.FileTypes;
import com.intellij.openapi.fileTypes.StdFileTypes;
import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixtureTestCase;
import java.util.*;
import static com.intellij.openapi.editor.impl.DisplayedFoldingAnchor.Type;
public class FoldingAnchorsOverlayStrategyTest extends LightPlatformCodeInsightFixtureTestCase {
public void testExpanded() {
prepareEditor("<body><div>\n" +
"</div><div>\n" +
"some text\n" +
"some text\n" +
"some text\n" +
"</div><div>\n" +
"</div></body>");
verifyAnchors(null,
0, Type.EXPANDED_TOP,
1, Type.EXPANDED_BOTTOM,
5, Type.EXPANDED_TOP,
6, Type.EXPANDED_BOTTOM);
}
public void testCollapsed() {
prepareEditor("<body><div>\n" +
"</div><div>\n" +
"some text\n" +
"some text\n" +
"some text\n" +
"</div><div>\n" +
"</div></body>");
collapseFoldingRegion(2);
verifyAnchors(null,
0, Type.EXPANDED_TOP,
1, Type.COLLAPSED,
2, Type.EXPANDED_BOTTOM);
}
public void testWithActiveRegion() {
prepareEditor("<body><div>\n" +
"</div><div>\n" +
"some text\n" +
"some text\n" +
"some text\n" +
"</div><div>\n" +
"</div></body>");
collapseFoldingRegion(2);
verifyAnchors(myFixture.getEditor().getFoldingModel().getAllFoldRegions()[1],
0, Type.EXPANDED_TOP,
1, Type.EXPANDED_BOTTOM,
2, Type.EXPANDED_BOTTOM);
}
public void testWithEmptyLastLine() {
myFixture.configureByText(FileTypes.PLAIN_TEXT, "some text\n");
final FoldingModel foldingModel = myFixture.getEditor().getFoldingModel();
foldingModel.runBatchFoldingOperation(new Runnable() {
@Override
public void run() {
foldingModel.addFoldRegion(0, 10, "...");
}
});
verifyAnchors(null,
0, Type.EXPANDED_TOP,
1, Type.EXPANDED_BOTTOM);
}
private void prepareEditor(String text) {
myFixture.configureByText(StdFileTypes.XML, text);
CodeFoldingManager.getInstance(myFixture.getProject()).updateFoldRegions(myFixture.getEditor());
}
private void collapseFoldingRegion(int n) {
FoldingModel foldingModel = myFixture.getEditor().getFoldingModel();
final FoldRegion foldRegion = foldingModel.getAllFoldRegions()[n];
foldingModel.runBatchFoldingOperation(new Runnable() {
@Override
public void run() {
foldRegion.setExpanded(false);
}
});
}
private void verifyAnchors(FoldRegion activeFoldRegion, Object... expectedAnchorParameters) {
Collection<DisplayedFoldingAnchor> actualAnchors = new FoldingAnchorsOverlayStrategy((EditorImpl)myFixture.getEditor())
.getAnchorsToDisplay(0, myFixture.getEditor().getDocument().getTextLength(), activeFoldRegion);
List<DisplayedFoldingAnchor> sortedActualAnchors = new ArrayList<DisplayedFoldingAnchor>(actualAnchors);
Collections.sort(sortedActualAnchors, new Comparator<DisplayedFoldingAnchor>() {
@Override
public int compare(DisplayedFoldingAnchor o1, DisplayedFoldingAnchor o2) {
return o1.visualLine - o2.visualLine;
}
});
assertEquals("Wrong number of anchors", expectedAnchorParameters.length / 2, sortedActualAnchors.size());
int i = 0;
for (DisplayedFoldingAnchor anchor : sortedActualAnchors) {
int expectedVisualLine = (Integer) expectedAnchorParameters[i++];
assertEquals("Folding anchor at wrong line found", expectedVisualLine, anchor.visualLine);
Type expectedType = (Type)expectedAnchorParameters[i++];
assertEquals("Folding anchor of wrong type found at line " + expectedVisualLine, expectedType, anchor.type);
}
}
}