mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-86907 Fold/unfold for the last node is incorrect
This commit is contained in:
+34
@@ -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;
|
||||
}
|
||||
}
|
||||
+69
-185
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+113
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
+125
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user