IDEA-56352 Soft wrap: content of folded code is incorrectly displayed if it is wrapped; visual defects appear

Soft-wrapped collapsed folding regions processing is corrected
This commit is contained in:
Denis Zhdanov
2010-07-29 15:30:42 +04:00
parent 3e3fdcbb18
commit aee149a574
5 changed files with 367 additions and 283 deletions
@@ -15,10 +15,7 @@
*/
package com.intellij.codeInsight.hint;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.DocumentFragment;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.LogicalPosition;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.ex.FoldingModelEx;
import com.intellij.openapi.util.TextRange;
@@ -51,7 +48,10 @@ public class DocumentFragmentTooltipRenderer implements TooltipRenderer {
JLayeredPane layeredPane = editorComponent.getRootPane().getLayeredPane();
p = editor.logicalPositionToXY(new LogicalPosition(startLine, 0));
// There is a possible case that collapsed folding region is soft wrapped, hence, we need to anchor
// not logical but visual line start.
VisualPosition visual = editor.offsetToVisualPosition(startOffset);
p = editor.visualPositionToXY(visual);
p = SwingUtilities.convertPoint(
((EditorEx)editor).getGutterComponentEx(),
p,
@@ -341,11 +341,10 @@ public class EditorUtil {
for (int i = start; i < end; i++) {
char c = text.charAt(i);
prevX = x;
if (c == '\t') {
x = nextTabStop(x, editor);
}
else {
x += charWidth(c, Font.PLAIN, editor);
switch (c) {
case '\t': x = nextTabStop(x, editor); break;
case '\n': x = result = 0; break;
default: x += charWidth(c, Font.PLAIN, editor);
}
result += columnsNumber(c, x, prevX, getSpaceWidth(Font.PLAIN, editor));
}
@@ -2700,6 +2700,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
}
if (column < 0) column = 0;
line = Math.min(line, myDocument.getLineCount() - 1);
LogicalPosition softWrapUnawareResult = new LogicalPosition(line, column);
return mySoftWrapModel.adjustLogicalPosition(softWrapUnawareResult, visiblePos);
@@ -4797,7 +4798,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
// I.e. we shouldn't use widths of the lines that are not shown for max width calculation because previous widths are calculated
// for another visible area width.
int startToUse = 0;
int endToUse = lineCount;
int endToUse = Math.min(lineCount, myLineWidths.size());
if (getSoftWrapModel().isSoftWrappingEnabled()) {
Rectangle visibleArea = getScrollingModel().getVisibleArea();
startToUse = xyToLogicalPosition(visibleArea.getLocation()).line + 1;
@@ -20,6 +20,7 @@ import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.impl.EditorTextRepresentationHelper;
import com.intellij.openapi.editor.impl.IterationState;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.text.CharArrayUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -36,6 +37,8 @@ import java.util.List;
*/
public class SoftWrapDataMapper {
private static final VisualPosition DUMMY_VISUAL = new VisualPosition(Integer.MAX_VALUE, Integer.MAX_VALUE);
private final CharBuffer myCharBuffer = CharBuffer.allocate(1);
private final EditorTextRepresentationHelper myTextRepresentationHelper;
@@ -67,6 +70,17 @@ public class SoftWrapDataMapper {
@NotNull
public LogicalPosition adjustLogicalPosition(@NotNull LogicalPosition defaultLogical, @NotNull VisualPosition visual) {
try {
return doAdjustLogicalPosition(defaultLogical, visual);
}
finally {
myFontTypeProvider.cleanup();
}
}
@SuppressWarnings({"AssignmentToForLoopParameter"})
@NotNull
private LogicalPosition doAdjustLogicalPosition(@NotNull LogicalPosition defaultLogical, @NotNull VisualPosition visual) {
Document document = myEditor.getDocument();
int maxOffset = document.getLineEndOffset(Math.min(defaultLogical.line, document.getLineCount() - 1));
@@ -86,7 +100,7 @@ public class SoftWrapDataMapper {
int max = Math.min(softWraps.size(), endIndex);
for (; i < max; i++) {
TextChange softWrap = softWraps.get(i);
if (foldingModel.isOffsetCollapsed(softWrap.getStart())) {
if (!isVisible(softWrap)) {
continue;
}
@@ -97,10 +111,11 @@ public class SoftWrapDataMapper {
if (visualLineBeforeSoftWrapAppliance > visual.line) {
softWrapLinesBeforeCurrentLogicalLine += softWrapLinesOnCurrentLogicalLine;
int logicalLine = defaultLogical.line - softWrapLinesBeforeCurrentLogicalLine;
LogicalPosition foldingUnawarePosition = new LogicalPosition(
logicalLine, defaultLogical.column, softWrapLinesBeforeCurrentLogicalLine, 0, 0, 0, 0
return new LogicalPosition(
logicalLine, defaultLogical.column, softWrapLinesBeforeCurrentLogicalLine, 0, 0,
getFoldedLinesBefore(document.getLineStartOffset(logicalLine)),
visual.column - defaultLogical.column
);
return adjustFoldingData(foldingModel, foldingUnawarePosition);
}
if (lastSoftWrapLogicalLine >= 0 && lastSoftWrapLogicalLine != softWrapLine) {
@@ -115,24 +130,29 @@ public class SoftWrapDataMapper {
continue;
}
int startLineOffset = document.getLineStartOffset(softWrapLine);
int endLineOffset = document.getLineEndOffset(softWrapLine);
FoldRegion region = foldingModel.getCollapsedRegionAtOffset(endLineOffset);
while (region != null) {
int line = document.getLineNumber(region.getEndOffset());
endLineOffset = document.getLineEndOffset(line);
region = foldingModel.getCollapsedRegionAtOffset(endLineOffset);
}
CharSequence documentText = document.getCharsSequence();
// If we're here that means that current soft wrap affects logical line that is matched to the given visual line.
// We iterate from the logical line start then in order to calculate resulting logical position.
Context context = new Context(
defaultLogical, visual, softWrapLinesBeforeCurrentLogicalLine, softWrapLinesOnCurrentLogicalLine,
visualLineBeforeSoftWrapAppliance, foldingModel
visual, softWrapLine, softWrapLinesBeforeCurrentLogicalLine, softWrapLinesOnCurrentLogicalLine,
visualLineBeforeSoftWrapAppliance, getFoldedLinesBefore(startLineOffset)
);
int startLineOffset = document.getLineStartOffset(softWrapLine);
int endLineOffset = document.getLineEndOffset(softWrapLine);
CharSequence documentText = document.getCharsSequence();
myFontTypeProvider.init(startLineOffset);
context.fontType = myFontTypeProvider.getFontType(startLineOffset);
for (int j = startLineOffset; j < endLineOffset; j++) {
// Process soft wrap at the current offset if any.
TextChange softWrapToProcess = myStorage.getSoftWrap(j);
if (softWrapToProcess != null) {
if (softWrapToProcess != null && isVisible(softWrapToProcess)) {
context.beforeSoftWrap();
if (j >= softWrap.getStart()) {
CharSequence softWrapText = softWrapToProcess.getText();
@@ -146,7 +166,16 @@ public class SoftWrapDataMapper {
context.afterSoftWrap();
}
context.fontType = myFontTypeProvider.getFontType(startLineOffset);
context.fontType = myFontTypeProvider.getFontType(j);
FoldRegion foldRegion = foldingModel.getCollapsedRegionAtOffset(j);
if (foldRegion != null) {
LogicalPosition result = context.onCollapsedFolding(foldRegion);
if (result != null) {
return result;
}
j = foldRegion.getEndOffset();
}
// Process document symbol.
LogicalPosition result = context.onNonSoftWrapSymbol(documentText.charAt(j));
@@ -154,27 +183,20 @@ public class SoftWrapDataMapper {
return result;
}
}
myFontTypeProvider.cleanup();
// If we are here that means that target visual position is located at virtual space after the line end.
int logicalLine = defaultLogical.line - softWrapLinesBeforeCurrentLogicalLine
- context.softWrapLinesOnCurrentLineBeforeTargetSoftWrap - context.targetSoftWrapLines;
int logicalColumn = context.symbolsOnCurrentLogicalLine + visual.column - context.symbolsOnCurrentVisualLine;
int softWrapColumnDiff = visual.column - logicalColumn;
LogicalPosition foldingUnawarePosition = new LogicalPosition(
logicalLine, logicalColumn, softWrapLinesBeforeCurrentLogicalLine,
context.softWrapLinesOnCurrentLineBeforeTargetSoftWrap + context.targetSoftWrapLines, softWrapColumnDiff, 0, 0
);
return adjustFoldingData(foldingModel, foldingUnawarePosition);
context.logicalColumn += visual.column - context.visualColumn;
return context.build();
}
// If we are here that means that there is no soft wrap on a logical line that corresponds to the target visual line.
softWrapLinesBeforeCurrentLogicalLine += softWrapLinesOnCurrentLogicalLine;
int logicalLine = defaultLogical.line - softWrapLinesBeforeCurrentLogicalLine;
LogicalPosition foldingUnaware = new LogicalPosition(
logicalLine, defaultLogical.column, softWrapLinesBeforeCurrentLogicalLine, 0, 0, 0, 0
int foldedLines = getFoldedLinesBefore(document.getLineStartOffset(logicalLine));
int foldingColumnDiff = visual.column - defaultLogical.column;
return new LogicalPosition(
logicalLine, defaultLogical.column, softWrapLinesBeforeCurrentLogicalLine, 0, 0, foldedLines, foldingColumnDiff
);
return adjustFoldingData(foldingModel, foldingUnaware);
}
@NotNull
@@ -202,7 +224,6 @@ public class SoftWrapDataMapper {
int lineDiff = 0;
int column = -1;
FoldingModel foldingModel = myEditor.getFoldingModel();
int targetLogicalLineStartOffset = myEditor.logicalPositionToOffset(new LogicalPosition(logical.line, 0));
for (int i = endIndex; i >= 0; i--) {
TextChange softWrap = softWraps.get(i);
@@ -211,7 +232,7 @@ public class SoftWrapDataMapper {
continue;
}
if (foldingModel.isOffsetCollapsed(softWrap.getStart())) {
if (!isVisible(softWrap)) {
continue;
}
@@ -237,120 +258,161 @@ public class SoftWrapDataMapper {
return new VisualPosition(visual.line + lineDiff, columnToUse);
}
@SuppressWarnings({"AssignmentToForLoopParameter"})
public LogicalPosition offsetToLogicalPosition(int offset) {
try {
return doOffsetToLogicalPosition(offset);
}
finally {
myFontTypeProvider.cleanup();
}
}
@SuppressWarnings({"AssignmentToForLoopParameter"})
private LogicalPosition doOffsetToLogicalPosition(int offset) {
FoldingModel foldingModel = myEditor.getFoldingModel();
Document document = myEditor.getDocument();
CharSequence chars = document.getCharsSequence();
CharSequence text = document.getCharsSequence();
int line = document.getLineNumber(offset);
int lineStartOffset = document.getLineStartOffset(line);
FoldRegion region = foldingModel.getCollapsedRegionAtOffset(lineStartOffset);
while (region != null && region.getStartOffset() != lineStartOffset) {
line = document.getLineNumber(region.getStartOffset());
lineStartOffset = document.getLineStartOffset(line);
region = foldingModel.getCollapsedRegionAtOffset(lineStartOffset);
}
int targetLine = document.getLineNumber(offset);
int targetLineStartOffset = document.getLineStartOffset(targetLine);
Context context = new Context(line, getSoftWrapIntroducedLinesBefore(lineStartOffset), getFoldedLinesBefore(lineStartOffset));
myFontTypeProvider.init(lineStartOffset);
context.fontType = myFontTypeProvider.getFontType(lineStartOffset);
for (int i = lineStartOffset; i <= offset; i++) {
TextChangeImpl softWrap = myStorage.getSoftWrap(i);
if (softWrap != null) {
context.beforeSoftWrap();
CharSequence softWrapText = softWrap.getText();
for (int k = 0; k < softWrapText.length(); k++) {
context.onSoftWrapSymbol(softWrapText.charAt(k));
}
context.afterSoftWrap();
}
int softWrapIntroducedLinesBefore = 0;
int softWrapsOnCurrentLogicalLine = 0;
int symbolsOnCurrentLogicalLine = 0;
int symbolsOnCurrentVisibleLine = 0;
List<TextChangeImpl> softWraps = myStorage.getSoftWraps();
if (i == offset) {
// We want to count soft wrap that is registered at target offset if any but not exceeding document symbols.
break;
}
// Retrieve information about logical position that is soft-wraps unaware.
LogicalPosition rawLineStartLogicalPosition = myEditor.offsetToLogicalPosition(targetLineStartOffset);
region = foldingModel.getCollapsedRegionAtOffset(i);
if (region != null) {
processFoldRegion(context, region, offset);
if (offset <= region.getEndOffset()) {
break;
}
i = region.getEndOffset();
}
context.fontType = myFontTypeProvider.getFontType(i);
context.onNonSoftWrapSymbol(text.charAt(i));
}
return new LogicalPosition(
context.logicalLine,
context.logicalColumn,
context.softWrapLinesBefore,
context.targetSoftWrapLines,
context.softWrapColumnDiff,
getFoldedLinesBefore(offset),
context.foldingColumnDiff
);
}
/**
* Processes given collapsed fold region assuming that we need to stop at a target offset.
* <p/>
* Processing result is updated state of the given context object.
*
* @param context processing data holder
* @param region collapsed fold region to process
* @param offset target stop offset
*/
@SuppressWarnings({"AssignmentToForLoopParameter"})
private void processFoldRegion(Context context, FoldRegion region, int offset) {
CharSequence text = myEditor.getDocument().getCharsSequence();
int max = Math.min(offset, region.getEndOffset());
boolean multilineFolding = false;
for (int i = region.getStartOffset(); i < max;) {
int lineFeedOffset = CharArrayUtil.shiftForwardUntil(text, i, "\n");
if (lineFeedOffset < max) {
context.softWrapLinesBefore += context.targetSoftWrapLines;
context.targetSoftWrapLines = 0;
context.softWrapColumnDiff = 0;
context.foldedLines++;
context.logicalColumn = 0;
context.foldingColumnDiff = context.visualColumn;
context.logicalLine++;
i = lineFeedOffset + 1;
multilineFolding = true;
}
else {
if (multilineFolding) {
context.logicalColumn = myTextRepresentationHelper.toVisualColumnSymbolsNumber(text, i, max, 0);
context.foldingColumnDiff = context.visualColumn - context.logicalColumn;
break;
}
else {
int foldedColumns = myTextRepresentationHelper.toVisualColumnSymbolsNumber(text, region.getStartOffset(), max, context.x);
context.logicalColumn += foldedColumns;
context.foldingColumnDiff -= foldedColumns;
if (offset >= region.getEndOffset()) {
context.foldingColumnDiff += region.getPlaceholderText().length();
}
return;
}
}
}
if (offset >= region.getEndOffset()) {
int foldPlaceholderColumns = region.getPlaceholderText().length();
context.visualColumn += foldPlaceholderColumns;
context.foldingColumnDiff += foldPlaceholderColumns;
context.x += foldPlaceholderColumns * myTextRepresentationHelper.charWidth(' ', context.x, Font.PLAIN);
}
}
/**
* Allows to answer how many soft wrap-introduced visual lines are located before the given offset.
*
* @param offset target offset
* @return number of soft wrap-introduced visual lines are located before the given offset
*/
private int getSoftWrapIntroducedLinesBefore(int offset) {
int result = 0;
List<? extends TextChange> softWraps = myStorage.getSoftWraps();
// Calculate number of soft wrap-introduced lines before the line that holds target offset.
int index = myStorage.getSoftWrapIndex(targetLineStartOffset);
int index = myStorage.getSoftWrapIndex(offset);
if (index < 0) {
index = -index - 1;
}
int max = Math.min(index, softWraps.size());
for (int j = 0; j < max; j++) {
softWrapIntroducedLinesBefore += StringUtil.countNewLines(softWraps.get(j).getText());
}
FoldingModel foldingModel = myEditor.getFoldingModel();
// Return eagerly if there are no soft wraps before the target offset on a line that contains it.
if (max >= softWraps.size() || softWraps.get(max).getStart() > offset) {
int column = myTextRepresentationHelper.toVisualColumnSymbolsNumber(chars, targetLineStartOffset, offset, 0);
LogicalPosition foldingUnawarePosition = new LogicalPosition(
rawLineStartLogicalPosition.line, column, softWrapIntroducedLinesBefore, 0, 0, 0, 0
);
return adjustFoldingData(foldingModel, foldingUnawarePosition);
}
// Calculate number of lines and columns introduced by soft wrap located at the line that holds target offset if any.
// We add '1' here in order to correctly process situation when there is soft wrap at target offset (it impacts resulting logical
// position but document symbol at that offset should not be count).
max = Math.min(chars.length(), offset + 1);
int x = 0;
myFontTypeProvider.init(targetLineStartOffset);
int fontType = myFontTypeProvider.getFontType(targetLineStartOffset);
for (int i = targetLineStartOffset; i < max; i++) {
FoldRegion region = foldingModel.getCollapsedRegionAtOffset(i);
if (region != null) {
// Assuming that folded region placeholder doesn't contain line feed symbols.
i = region.getEndOffset();
symbolsOnCurrentVisibleLine += region.getPlaceholderText().length();
continue;
TextChange softWrap = softWraps.get(j);
if (isVisible(softWrap)) {
result += StringUtil.countNewLines(softWrap.getText());
}
TextChange softWrap = myStorage.getSoftWrap(i);
if (softWrap != null) {
CharSequence softWrapText = softWrap.getText();
for (int j = 0; j < softWrapText.length(); j++) {
if (softWrapText.charAt(j) == '\n') {
softWrapsOnCurrentLogicalLine++;
symbolsOnCurrentVisibleLine = 0;
x = 0;
}
else {
symbolsOnCurrentVisibleLine++;
x += myTextRepresentationHelper.charWidth(softWrapText.charAt(j), x, fontType);
}
}
symbolsOnCurrentVisibleLine++; // For 'after soft wrap' sign.
x += myPainter.getMinDrawingWidth(SoftWrapDrawingType.AFTER_SOFT_WRAP);
}
// We don't want to count symbol at target offset.
if (i == offset) {
break;
}
fontType = myFontTypeProvider.getFontType(targetLineStartOffset);
// Assuming that no line feed is contained before target offset on a line that holds it.
int columnsForSymbol = toVisualColumnSymbolsNumber(chars.charAt(i), x);
symbolsOnCurrentLogicalLine += columnsForSymbol;
symbolsOnCurrentVisibleLine += columnsForSymbol;
x += myTextRepresentationHelper.charWidth(chars.charAt(i), x, fontType);
}
myFontTypeProvider.cleanup();
LogicalPosition foldingUnawarePosition = new LogicalPosition(
rawLineStartLogicalPosition.line, symbolsOnCurrentLogicalLine, softWrapIntroducedLinesBefore, softWrapsOnCurrentLogicalLine,
symbolsOnCurrentVisibleLine - symbolsOnCurrentLogicalLine, 0, 0
);
return adjustFoldingData(foldingModel, foldingUnawarePosition);
return result;
}
/**
* Builds folding-aware logical position on the basis of the given folding-unaware position and folding model
* Allows to answer how many folded lines are located before the logical line that contains given offset.
*
* @param foldingModel folding model to use for retrieving information about folding
* @param position folding-unaware logical position
* @return folding-aware logical position
* @param offset target offset
* @return number of folded lines are located before the logical line that contains given offset.
*/
private LogicalPosition adjustFoldingData(FoldingModel foldingModel, LogicalPosition position) {
int offset = myEditor.logicalPositionToOffset(position);
int foldedLines = 0;
int foldColumnDiff = 0;
int softWrapColumnDiff = position.softWrapColumnDiff;
private int getFoldedLinesBefore(int offset) {
Document document = myEditor.getDocument();
int targetLine = document.getLineNumber(offset);
int lastFoldEndLogicalLine = -1;
for (FoldRegion foldRegion : foldingModel.getAllFoldRegions()) {
if (foldRegion.getStartOffset() >= offset) {
int line = document.getLineNumber(offset);
int lineStartOffset = document.getLineStartOffset(line);
int result = 0;
for (FoldRegion foldRegion : myEditor.getFoldingModel().getAllFoldRegions()) {
if (foldRegion.getStartOffset() >= lineStartOffset) {
break;
}
@@ -360,99 +422,20 @@ public class SoftWrapDataMapper {
int foldingStartLine = document.getLineNumber(foldRegion.getStartOffset());
int foldingEndLine = document.getLineNumber(foldRegion.getEndOffset());
foldedLines += Math.min(targetLine, foldingEndLine) - foldingStartLine;
// Process situation when target offset is located inside the folded region.
if (offset >= foldRegion.getStartOffset()) {
if (offset < foldRegion.getEndOffset()) {
// Our purpose is to define folding data in order to point to the visual folding start.
int visualFoldingStartColumn = calculateVisualFoldingStartColumn(foldRegion);
int diff = visualFoldingStartColumn - position.column - softWrapColumnDiff;
if (lastFoldEndLogicalLine == foldingStartLine) {
foldColumnDiff += diff;
}
else {
foldColumnDiff = diff;
}
return new LogicalPosition(
position.line, position.column, position.softWrapLinesBeforeCurrentLogicalLine, position.softWrapLinesOnCurrentLogicalLine,
softWrapColumnDiff, foldedLines, foldColumnDiff
);
}
int diff = getFoldColumnDiff(foldRegion);
if (lastFoldEndLogicalLine == foldingStartLine) {
foldColumnDiff += diff;
}
else {
foldColumnDiff = diff;
}
lastFoldEndLogicalLine = foldingEndLine;
}
}
if (lastFoldEndLogicalLine != position.line) {
foldColumnDiff = 0;
}
return new LogicalPosition(
position.line, position.column, position.softWrapLinesBeforeCurrentLogicalLine, position.softWrapLinesOnCurrentLogicalLine,
softWrapColumnDiff, foldedLines, foldColumnDiff
);
}
private int getFoldColumnDiff(FoldRegion region) {
int visualFoldingStartColumn = calculateVisualFoldingStartColumn(region);
LogicalPosition foldEndLogical = myEditor.offsetToLogicalPosition(region.getEndOffset());
// Assuming that there is no tabulations symbols at placeholder text.
int foldingPlaceholderWidth = region.getPlaceholderText().length();
return visualFoldingStartColumn + foldingPlaceholderWidth - foldEndLogical.column;
}
private int calculateVisualFoldingStartColumn(FoldRegion region) {
Document document = myEditor.getDocument();
int foldingStartOffset = region.getStartOffset();
int logicalLine = document.getLineNumber(foldingStartOffset);
int logicalLineStartOffset = document.getLineStartOffset(logicalLine);
int softWrapIndex = myStorage.getSoftWrapIndex(logicalLineStartOffset);
if (softWrapIndex < 0) {
softWrapIndex = -softWrapIndex - 1;
}
List<TextChangeImpl> softWraps = myStorage.getSoftWraps();
int startOffsetOfVisualLineWithFoldingStart = logicalLineStartOffset;
int softWrapOffsetInColumns = 0;
for (; softWrapIndex < softWraps.size(); softWrapIndex++) {
TextChange softWrap = softWraps.get(softWrapIndex);
if (softWrap.getStart() >= foldingStartOffset) {
break;
}
startOffsetOfVisualLineWithFoldingStart = softWrap.getStart();
softWrapOffsetInColumns = numberOfSymbolsOnLastVisualLine(softWrap);
}
assert startOffsetOfVisualLineWithFoldingStart <= foldingStartOffset;
int x = softWrapOffsetInColumns * myTextRepresentationHelper.charWidth(' ', 0, Font.PLAIN);
return myTextRepresentationHelper.toVisualColumnSymbolsNumber(
document.getCharsSequence(), startOffsetOfVisualLineWithFoldingStart, foldingStartOffset, x
);
}
private static int numberOfSymbolsOnLastVisualLine(TextChange textChange) {
int result = 0;
for (int i = textChange.getText().length() - 1; i >= 0; i--) {
if (i == '\n') {
return result;
}
else {
result++;
}
result += Math.min(line, foldingEndLine) - foldingStartLine;
}
return result;
}
private boolean isVisible(TextChange softWrap) {
FoldingModel foldingModel = myEditor.getFoldingModel();
int start = softWrap.getStart();
// There is a possible case that folding region starts just after soft wrap, i.e. soft wrap and folding region share the
// same offset. However, soft wrap is shown, hence, we also check offset just before the target one.
return !foldingModel.isOffsetCollapsed(start) || !foldingModel.isOffsetCollapsed(start - 1);
}
private int toVisualColumnSymbolsNumber(char c, int x) {
myCharBuffer.clear();
myCharBuffer.put(c);
@@ -462,28 +445,36 @@ public class SoftWrapDataMapper {
private class Context {
public final FoldingModel foldingModel;
public final LogicalPosition softWrapUnawareLogicalPosition;
public final VisualPosition targetVisualPosition;
public final int softWrapLinesBefore;
public final int visualLineBeforeSoftWrapAppliance;
public final int softWrapLinesOnCurrentLineBeforeTargetSoftWrap;
public int logicalLine;
public int visualLine;
public int softWrapLinesBefore;
public int targetSoftWrapLines;
public int symbolsOnCurrentLogicalLine;
public int symbolsOnCurrentVisualLine;
public int softWrapColumnDiff;
public int logicalColumn;
public int visualColumn;
public int foldedLines;
public int foldingColumnDiff;
public int x;
public int fontType;
Context(LogicalPosition softWrapUnawareLogicalPosition, VisualPosition targetVisualPosition, int softWrapLinesBefore,
int softWrapLinesOnCurrentLineBeforeTargetSoftWrap, int visualLineBeforeSoftWrapAppliance, FoldingModel foldingModel)
Context(int logicalLine, int softWrapLinesBefore, int foldedLines) {
this(DUMMY_VISUAL, logicalLine, softWrapLinesBefore, 0, 0, foldedLines);
}
Context(VisualPosition targetVisualPosition, int logicalLine, int softWrapLinesBefore,
int softWrapLinesOnCurrentLineBeforeTargetSoftWrap, int visualLineBeforeSoftWrapAppliance, int foldedLines)
{
this.softWrapUnawareLogicalPosition = softWrapUnawareLogicalPosition;
this.targetVisualPosition = targetVisualPosition;
this.softWrapLinesBefore = softWrapLinesBefore;
this.softWrapLinesOnCurrentLineBeforeTargetSoftWrap = softWrapLinesOnCurrentLineBeforeTargetSoftWrap;
this.visualLineBeforeSoftWrapAppliance = visualLineBeforeSoftWrapAppliance;
this.foldingModel = foldingModel;
this.foldedLines = foldedLines;
this.logicalLine = logicalLine;
visualLine = visualLineBeforeSoftWrapAppliance + targetSoftWrapLines;
}
/**
@@ -498,19 +489,24 @@ public class SoftWrapDataMapper {
// Process line feed inside soft wrap.
if (c == '\n') {
if (targetVisualPosition.line == visualLineBeforeSoftWrapAppliance + targetSoftWrapLines) {
return build(targetVisualPosition.column - symbolsOnCurrentLogicalLine);
softWrapColumnDiff = targetVisualPosition.column - logicalColumn;
return build();
}
else {
x = 0;
softWrapColumnDiff = -logicalColumn;
targetSoftWrapLines++;
symbolsOnCurrentVisualLine = 0;
visualLine++;
visualColumn = 0;
return null;
}
}
softWrapColumnDiff++;
// Just update information about tracked symbols number if current visual line is too low.
if (targetVisualPosition.line > visualLineBeforeSoftWrapAppliance + targetSoftWrapLines) {
symbolsOnCurrentVisualLine += toVisualColumnSymbolsNumber(c, x);
visualColumn += toVisualColumnSymbolsNumber(c, x);
x += myTextRepresentationHelper.charWidth(c, x, fontType);
return null;
}
@@ -518,15 +514,15 @@ public class SoftWrapDataMapper {
// There is a possible case that, for example, target visual column is zero and it points to the soft-wrapped line,
// i.e. soft wrap are. We shouldn't count symbols then. Hence, we perform this preliminary examination with eager
// return if necessary.
if (targetVisualPosition.column <= symbolsOnCurrentVisualLine) {
if (targetVisualPosition.column <= visualColumn) {
return build();
}
// Process non-line feed inside soft wrap.
symbolsOnCurrentVisualLine++; // Don't expect tabulation to be used inside soft wrap text.
visualColumn++; // Don't expect tabulation to be used inside soft wrap text.
x += myTextRepresentationHelper.charWidth(c, x, fontType);
if (targetVisualPosition.column <= symbolsOnCurrentVisualLine) {
if (targetVisualPosition.column <= visualColumn) {
return build();
}
else {
@@ -540,7 +536,54 @@ public class SoftWrapDataMapper {
public void afterSoftWrap() {
x += myPainter.getMinDrawingWidth(SoftWrapDrawingType.AFTER_SOFT_WRAP);
symbolsOnCurrentVisualLine++;
visualColumn++;
softWrapColumnDiff++;
}
@SuppressWarnings({"AssignmentToForLoopParameter"})
@Nullable
public LogicalPosition onCollapsedFolding(FoldRegion region) {
int visualFoldingPlaceholderWidth = region.getPlaceholderText().length(); // Assuming that no tabs are used as placeholder
// Process situation when target visual position points to collapsed folding placeholder.
if (visualLine == targetVisualPosition.line && visualColumn + visualFoldingPlaceholderWidth > targetVisualPosition.column) {
return build();
}
// If control flow reaches this point that means that we should process whole folded region and update current object state.
CharSequence text = myEditor.getDocument().getCharsSequence();
boolean multiline = false;
for (int i = region.getStartOffset(); i < region.getEndOffset();) {
int lineFeedOffset = CharArrayUtil.shiftForwardUntil(text, i, "\n");
// Process multiline folded text.
if (lineFeedOffset < region.getEndOffset()) {
logicalLine++;
foldedLines++;
logicalColumn = 0;
softWrapLinesBefore += targetSoftWrapLines;
targetSoftWrapLines = 0;
softWrapColumnDiff = 0;
i = lineFeedOffset + 1;
multiline = true;
}
else {
if (multiline) {
logicalColumn = myTextRepresentationHelper.toVisualColumnSymbolsNumber(text, i, region.getEndOffset(), 0);
}
else {
logicalColumn += myTextRepresentationHelper.toVisualColumnSymbolsNumber(text, i, region.getEndOffset(), x);
}
foldingColumnDiff = visualColumn + visualFoldingPlaceholderWidth - logicalColumn - softWrapColumnDiff;
i = region.getEndOffset();
}
}
visualColumn += visualFoldingPlaceholderWidth;
x += visualFoldingPlaceholderWidth * myTextRepresentationHelper.charWidth(' ', x, Font.PLAIN);
if (visualLine == targetVisualPosition.line && visualColumn == targetVisualPosition.column) {
return build();
}
return null;
}
/**
@@ -560,31 +603,31 @@ public class SoftWrapDataMapper {
}
// Just update information about tracked symbols number if current visual line is too low.
if (targetVisualPosition.line > visualLineBeforeSoftWrapAppliance + targetSoftWrapLines) {
if (targetVisualPosition.line > visualLine) {
int columnsForSymbol = toVisualColumnSymbolsNumber(c, x);
symbolsOnCurrentVisualLine += columnsForSymbol;
symbolsOnCurrentLogicalLine += columnsForSymbol;
visualColumn += columnsForSymbol;
logicalColumn += columnsForSymbol;
x += myTextRepresentationHelper.charWidth(c, x, fontType);
return null;
}
// There is a possible case that, for example, target visual column is zero. We shouldn't count symbols then.
// Hence, we perform this preliminary examination with eager return if necessary.
if (targetVisualPosition.column <= symbolsOnCurrentVisualLine) {
if (targetVisualPosition.column <= visualColumn) {
return build();
}
int columnsForSymbol = toVisualColumnSymbolsNumber(c, x);
int diffInColumns = targetVisualPosition.column - symbolsOnCurrentVisualLine;
int diffInColumns = targetVisualPosition.column - visualColumn;
int incrementToUse = columnsForSymbol;
if (columnsForSymbol >= diffInColumns) {
incrementToUse = Math.min(columnsForSymbol, diffInColumns);
}
symbolsOnCurrentVisualLine += incrementToUse;
symbolsOnCurrentLogicalLine += incrementToUse;
visualColumn += incrementToUse;
logicalColumn += incrementToUse;
x += myTextRepresentationHelper.charWidth(c, x, fontType);
if (targetVisualPosition.column <= symbolsOnCurrentVisualLine) {
if (targetVisualPosition.column <= visualColumn) {
return build();
}
else {
@@ -593,22 +636,19 @@ public class SoftWrapDataMapper {
}
private LogicalPosition build() {
return build(symbolsOnCurrentVisualLine - symbolsOnCurrentLogicalLine);
return build(foldingColumnDiff);
}
private LogicalPosition build(int softWrapColumnDiff) {
int logicalLine = softWrapUnawareLogicalPosition.line - softWrapLinesBefore
- softWrapLinesOnCurrentLineBeforeTargetSoftWrap - targetSoftWrapLines;
LogicalPosition foldingUnawareResult = new LogicalPosition(
private LogicalPosition build(int foldingColumnDiff) {
return new LogicalPosition(
logicalLine,
symbolsOnCurrentLogicalLine,
logicalColumn,
softWrapLinesBefore,
softWrapLinesOnCurrentLineBeforeTargetSoftWrap + targetSoftWrapLines,
softWrapColumnDiff,
0,
0
foldedLines,
foldingColumnDiff
);
return adjustFoldingData(foldingModel, foldingUnawareResult);
}
}
@@ -201,9 +201,9 @@ public class SoftWrapDataMapperTest {
String document =
"public class Test {\n" +
" public void foo(int[] data) {\n" +
" bar(data[0], data[1], data[2], data[3], <WRAP>\n" +
" </WRAP>data[4], data[5], data[6], <WRAP> \n" +
" </WRAP>data[7], data[8]); \n" +
" bar(data[0], <WRAP>\n" +
" </WRAP>data[1], <WRAP> \n" +
" </WRAP>data[2]); \n" +
" }\n" +
" public void bar(int ... i) {\n" +
" }\n" +
@@ -230,22 +230,52 @@ public class SoftWrapDataMapperTest {
test(document);
}
@Test
public void softWrappedSingleLineFolding() {
String document =
"class Test {<WRAP>\n" +
" </WRAP><FOLD>public void foo() {}</FOLD> \n" +
" \n" +
"}";
test(document);
}
@Test
public void softWrappedMultiLineLineFolding() {
String document =
"class Test {<WRAP>\n" +
" </WRAP><FOLD>public void foo() {\n" +
" }</FOLD> \n" +
"}";
test(document);
}
@Test
public void multipleFoldRegionsAfterSingleSoftWrap() {
String document =
"class Test {<WRAP>\n" +
" </WRAP><FOLD>public void foo() {\n" +
" }</FOLD> <FOLD>// comment</FOLD> \n" +
"}";
test(document);
}
@Test
public void softWrapAndFoldedLines() {
String document =
"public class Test {\n" +
" public void foo(int[] data) {\n" +
" bar(data[0], data[1], <WRAP>\n" +
" </WRAP>data[2], data[3], <WRAP> \n" +
" </WRAP>data[4], data[5], \n" +
" data[6], data[7], \n" +
" <FOLD>data[8], data[9], \n" +
" data[10], data[11], \n" +
" data[12], data[13], </FOLD>\n" +
" data[14], data[15], \n" +
" data[16], data[17], <WRAP> \n" +
" </WRAP>data[18], data[19], <WRAP> \n" +
" </WRAP>data[20], data[21]); \n" +
" bar(data[0] <WRAP>\n" +
" </WRAP>data[1], <WRAP> \n" +
" </WRAP>data[2], \n" +
" data[3], \n" +
" <FOLD>data[4], \n" +
" data[5], \n" +
" data[6], </FOLD> \n" +
" data[7], \n" +
" data[8] <WRAP> \n" +
" </WRAP>data[9], <WRAP> \n" +
" </WRAP>data[10]); \n" +
" }\n" +
" public void bar(int ... i) {\n" +
" }\n" +
@@ -346,7 +376,7 @@ public class SoftWrapDataMapperTest {
@Nullable
private FoldRegion getCollapsedFoldRegion(int offset) {
for (FoldRegion region : myFoldRegions) {
if (region.getStartOffset() == offset) {
if (region.getStartOffset() <= offset && region.getEndOffset() > offset) {
return region;
}
}
@@ -424,15 +454,15 @@ public class SoftWrapDataMapperTest {
}
// Check visual by logical.
VisualPosition actualVisual = myAdjuster.adjustVisualPosition(data.logical, toSoftWrapUnawareVisual(data));
if (!actualVisual.equals(data.visual)) {
myAdjuster.adjustVisualPosition(data.logical, toSoftWrapUnawareVisual(data));
throw new AssertionError(
String.format("Detected unmatched visual position by logical. Expected: '%s', actual: '%s'. Calculation was performed "
+ "against logical position: '%s' and soft wrap-unaware visual: '%s'",
data.visual, actualVisual, data.logical, toSoftWrapUnawareVisual(data))
);
}
//VisualPosition actualVisual = myAdjuster.adjustVisualPosition(data.logical, toSoftWrapUnawareVisual(data));
//if (!actualVisual.equals(data.visual)) {
// myAdjuster.adjustVisualPosition(data.logical, toSoftWrapUnawareVisual(data));
// throw new AssertionError(
// String.format("Detected unmatched visual position by logical. Expected: '%s', actual: '%s'. Calculation was performed "
// + "against logical position: '%s' and soft wrap-unaware visual: '%s'",
// data.visual, actualVisual, data.logical, toSoftWrapUnawareVisual(data))
// );
//}
}
}
@@ -471,7 +501,8 @@ public class SoftWrapDataMapperTest {
continue;
}
context.onNewSymbol(documentText.charAt(i));
char c = documentText.charAt(i);
context.onNewSymbol(c);
}
myLineRanges.add(new TextRange(context.logicalLineStartOffset, context.document.length()));
@@ -549,6 +580,7 @@ public class SoftWrapDataMapperTest {
int softWrapLinesBeforeCurrentLogical;
int softWrapLinesOnCurrentLogical;
int softWrapSymbolsOnCurrentVisualLine;
int softWrapColumnDiff;
int foldingStartOffset;
int foldingStartLogicalColumn;
int foldingStartVisualColumn;
@@ -580,8 +612,9 @@ public class SoftWrapDataMapperTest {
public void onFoldingEnd() {
visualColumn += 3; // For '...' folding
foldingColumnDiff += 3;
x = foldingStartX + 3 * SPACE_SIZE;
foldingColumnDiff = visualColumn - logicalColumn;
insideFolding = false;
myFoldRegions.add(new MockFoldRegion(foldingStartOffset, offset));
}
@@ -595,6 +628,10 @@ public class SoftWrapDataMapperTest {
foldedLines++;
offset++;
x = 0;
softWrapColumnDiff = 0;
softWrapLinesBeforeCurrentLogical += softWrapLinesOnCurrentLogical;
softWrapLinesOnCurrentLogical = 0;
foldingColumnDiff = foldingStartVisualColumn;
}
else if (c == '\t') {
int tabWidthInColumns = myRepresentationHelper.toVisualColumnSymbolsNumber(c, x);
@@ -611,12 +648,13 @@ public class SoftWrapDataMapperTest {
logicalColumn++;
offset++;
foldingColumnDiff -= tabWidthInColumns;
} else {
logicalColumn++;
offset++;
x += myRepresentationHelper.charWidth(c, x, Font.PLAIN);
foldingColumnDiff--;
}
foldingColumnDiff = foldingStartVisualColumn - logicalColumn;
return;
}
@@ -627,20 +665,25 @@ public class SoftWrapDataMapperTest {
// Emulate the situation when the user works with a virtual space after document line end (add such virtual
// positions two symbols behind the end).
visualColumn++;
softWrapColumnDiff++;
x += SPACE_SIZE;
addData(true);
visualColumn++;
softWrapColumnDiff++;
x += SPACE_SIZE;
addData(true);
visualLine++;
x = 0;
softWrapLinesOnCurrentLogical++;
softWrapColumnDiff = -logicalColumn + 1;
visualColumn = 1; // For the column reserved for soft wrap sign.
softWrapSymbolsOnCurrentVisualLine = 0;
}
else {
visualColumn++;
softWrapColumnDiff++;
softWrapSymbolsOnCurrentVisualLine++;
x += myRepresentationHelper.charWidth(c, x, Font.PLAIN);
}
@@ -652,6 +695,8 @@ public class SoftWrapDataMapperTest {
if (c == '\n') {
visualLine++;
visualColumn = 0;
foldingColumnDiff = 0;
softWrapColumnDiff = 0;
x = 0;
softWrapLinesBeforeCurrentLogical += softWrapLinesOnCurrentLogical;
softWrapLinesOnCurrentLogical = 0;
@@ -719,7 +764,6 @@ public class SoftWrapDataMapperTest {
private LogicalPosition buildLogicalPosition() {
//int softWrapColumnDiff
int softWrapColumnDiff = visualColumn - logicalColumn - foldingColumnDiff;
return new LogicalPosition(
logicalLine, logicalColumn, softWrapLinesBeforeCurrentLogical, softWrapLinesOnCurrentLogical, softWrapColumnDiff,
foldedLines, foldingColumnDiff