mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-53596 Soft wrap for editors
1. Tab processing with soft wraps is corrected; 2. Soft wraps offsets unit test engine is expanded in order to check tabs processing; 3. Tab-specific soft wraps mapping tests are added; 4. Minor refactorings of soft wrap processing code;
This commit is contained in:
@@ -140,7 +140,7 @@ public class LogicalPosition implements Comparable<LogicalPosition> {
|
||||
@NonNls
|
||||
public String toString() {
|
||||
return "LogicalPosition: line=" + line + " column=" + column + "; visual position aware=" + visualPositionAware
|
||||
+ " soft wrap: lines=" + (softWrapLinesBeforeCurrentLogicalLine + softWrapLinesOnCurrentLogicalLine) + " (before=" +
|
||||
+ "; soft wrap: lines=" + (softWrapLinesBeforeCurrentLogicalLine + softWrapLinesOnCurrentLogicalLine) + " (before=" +
|
||||
softWrapLinesBeforeCurrentLogicalLine + "; current=" + softWrapLinesOnCurrentLogicalLine
|
||||
+ ") columns diff=" + softWrapColumnDiff + "; folding: lines = " + foldedLines + " columns diff=" + foldingColumnDiff;
|
||||
}
|
||||
|
||||
+6
-4
@@ -15,6 +15,9 @@
|
||||
*/
|
||||
package com.intellij.openapi.editor.ex;
|
||||
|
||||
import com.intellij.openapi.editor.TextChange;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Defines a contract for the callbacks for soft wraps management notifications (addition/removal).
|
||||
*
|
||||
@@ -24,10 +27,9 @@ package com.intellij.openapi.editor.ex;
|
||||
public interface SoftWrapChangeListener {
|
||||
|
||||
/**
|
||||
* This method is assumed to be called every time number of soft wraps registered for the target logical line(s)
|
||||
* is changed (soft wraps are added or remove).
|
||||
* This method is assumed to be called every new soft wrap is registered.
|
||||
*
|
||||
* @param changedLogicalLine logical line for which number of registered soft wraps is changed
|
||||
* @param softWrap newly registered soft wrap
|
||||
*/
|
||||
void softWrapsStateChanged(int changedLogicalLine);
|
||||
void softWrapAdded(@NotNull TextChange softWrap);
|
||||
}
|
||||
|
||||
@@ -63,13 +63,20 @@ public class EditorUtil {
|
||||
List<? extends TextChange> softWraps = editor.getSoftWrapModel().getSoftWrapsForLine(resultLogLine);
|
||||
for (int i = 0; i < softWraps.size(); i++) {
|
||||
TextChange softWrap = softWraps.get(i);
|
||||
CharSequence text = document.getCharsSequence();
|
||||
if (visualLinesToSkip <= 0) {
|
||||
int result = editor.offsetToVisualPosition(softWrap.getStart() - 1).column;
|
||||
// We need to add width of the next symbol because current result column points to the last symbol before the soft wrap.
|
||||
return result + textWidthInColumns(editor, text, softWrap.getStart() - 1, softWrap.getStart(), result);
|
||||
}
|
||||
|
||||
int softWrapLineFeeds = StringUtil.countNewLines(softWrap.getText());
|
||||
if (softWrapLineFeeds < visualLinesToSkip) {
|
||||
visualLinesToSkip -= softWrapLineFeeds;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Target visual column is the one just before line feed introduced by the next line feed.
|
||||
// Target visual column is located on the last visual line of the current soft wrap.
|
||||
if (softWrapLineFeeds == visualLinesToSkip) {
|
||||
if (i >= softWraps.size() - 1) {
|
||||
return resVisEnd.column;
|
||||
@@ -78,11 +85,11 @@ public class EditorUtil {
|
||||
TextChange nextSoftWrap = softWraps.get(i + 1);
|
||||
int result = editor.offsetToVisualPosition(nextSoftWrap.getStart() - 1).column;
|
||||
|
||||
// We need to add '1' because current column points to the last symbol before the next soft wrap;
|
||||
result++;
|
||||
// We need to add symbol width because current column points to the last symbol before the next soft wrap;
|
||||
result += textWidthInColumns(editor, text, nextSoftWrap.getStart() - 1, nextSoftWrap.getStart(), result);
|
||||
|
||||
int lineFeedIndex = StringUtil.indexOf(nextSoftWrap.getText(), '\n');
|
||||
result += calcColumnNumber(editor, nextSoftWrap.getText(), 0, lineFeedIndex);
|
||||
result += textWidthInColumns(editor, nextSoftWrap.getText(), 0, lineFeedIndex, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -103,7 +110,8 @@ public class EditorUtil {
|
||||
}
|
||||
}
|
||||
int result = editor.offsetToVisualPosition(softWrap.getStart() - 1).column; // Column of the symbol just before the soft wrap
|
||||
result++; // Because we calculated column of the symbol just before the soft wrap
|
||||
// Target visual column is located on the last visual line of the current soft wrap.
|
||||
result += textWidthInColumns(editor, text, softWrap.getStart() - 1, softWrap.getStart(), result);
|
||||
result += calcColumnNumber(editor, softWrap.getText(), softWrapStartOffset, softWrapEndOffset);
|
||||
return result;
|
||||
}
|
||||
@@ -298,6 +306,36 @@ public class EditorUtil {
|
||||
return (nTabs + 1) * tabSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to answer how many columns are used to represent tabulation symbols that is started at the given visual column
|
||||
* at the given editor.
|
||||
*
|
||||
* @param visualColumn visual column where target tabulation symbol starts
|
||||
* @param editor target editor where tabulation symbol is to be represented
|
||||
* @return number of visual columns required to represent tabulation symbols that starts at the given column
|
||||
*/
|
||||
public static int tabWidthInColumns(@NotNull Editor editor, int visualColumn) {
|
||||
if (!editor.getSettings().isWhitespacesShown()) {
|
||||
return 1;
|
||||
}
|
||||
int tabSize = getTabSize(editor);
|
||||
int tabsNumber = visualColumn / tabSize;
|
||||
return (tabsNumber + 1) * tabSize - visualColumn;
|
||||
}
|
||||
|
||||
public static int textWidthInColumns(@NotNull Editor editor, CharSequence text, int start, int end, int columnOffset) {
|
||||
int result = 0;
|
||||
for (int i = start; i < end; i++) {
|
||||
if (text.charAt(i) == '\t') {
|
||||
result += tabWidthInColumns(editor, columnOffset + result);
|
||||
}
|
||||
else {
|
||||
result++;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to answer what width in pixels is required to draw fragment of the given char array from <code>[start; end)</code> interval
|
||||
* at the given editor.
|
||||
@@ -312,9 +350,12 @@ public class EditorUtil {
|
||||
* @param start offset within the given char array that points to target text start (inclusive)
|
||||
* @param end offset within the given char array that points to target text end (exclusive)
|
||||
* @param fontType font type to use for target text representation
|
||||
* @param x <code>'x'</code> coordinate that should be used as a starting point for target text representation.
|
||||
* It's necessity is implied by the fact that IDEA editor may represent tabulation symbols in any range
|
||||
* from <code>[1; tab size]</code> (check {@link #nextTabStop(int, Editor)} for more details)
|
||||
* @return width in pixels required for target text representation
|
||||
*/
|
||||
public static int textWidth(@NotNull Editor editor, char[] text, int start, int end, int fontType) {
|
||||
public static int textWidth(@NotNull Editor editor, char[] text, int start, int end, int fontType, int x) {
|
||||
int result = 0;
|
||||
for (int i = start; i < end; i++) {
|
||||
char c = text[i];
|
||||
@@ -325,7 +366,7 @@ public class EditorUtil {
|
||||
}
|
||||
|
||||
if (editor.getSettings().isWhitespacesShown()) {
|
||||
result += getTabSize(editor) * getSpaceWidth(fontType, editor);
|
||||
result += nextTabStop(x + result, editor) - result - x;
|
||||
}
|
||||
else {
|
||||
result += getSpaceWidth(fontType, editor);
|
||||
|
||||
@@ -329,9 +329,10 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
|
||||
mySoftWrapModel.addSoftWrapChangeListener(new SoftWrapChangeListener() {
|
||||
@Override
|
||||
public void softWrapsStateChanged(int changedLogicalLine) {
|
||||
public void softWrapAdded(@NotNull TextChange softWrap) {
|
||||
mySoftWrapsChanged = true;
|
||||
mySizeContainer.update(changedLogicalLine, changedLogicalLine, changedLogicalLine);
|
||||
int softWrapLine = myDocument.getLineNumber(softWrap.getStart());
|
||||
mySizeContainer.update(softWrapLine, softWrapLine, softWrapLine);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1061,7 +1062,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
if (i >= 0) {
|
||||
start = i + 1;
|
||||
}
|
||||
return new Point(EditorUtil.textWidth(this, softWrapChars, start, column + 1, Font.PLAIN), y);
|
||||
return new Point(EditorUtil.textWidth(this, softWrapChars, start, column + 1, Font.PLAIN, 0), y);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -1712,7 +1713,8 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
myGutterComponent.repaint(0, clip.y, myGutterComponent.getWidth(), myGutterComponent.getHeight() - clip.y);
|
||||
|
||||
// Ask caret model to update visual caret position.
|
||||
getCaretModel().moveToOffset(getCaretModel().getOffset());
|
||||
//TODO den implement
|
||||
//getCaretModel().moveToOffset(getCaretModel().getOffset());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4627,9 +4629,9 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
private int myMaxWidth = -1;
|
||||
|
||||
public synchronized void reset() {
|
||||
int visLinesCount = getDocument().getLineCount();
|
||||
myLineWidths = new TIntArrayList(visLinesCount + 300);
|
||||
int[] values = new int[visLinesCount];
|
||||
int lineCount = getDocument().getLineCount();
|
||||
myLineWidths = new TIntArrayList(lineCount + 300);
|
||||
int[] values = new int[lineCount];
|
||||
Arrays.fill(values, -1);
|
||||
myLineWidths.add(values);
|
||||
myIsDirty = true;
|
||||
|
||||
@@ -43,7 +43,7 @@ import java.util.List;
|
||||
*/
|
||||
public class SoftWrapModelImpl implements SoftWrapModelEx {
|
||||
|
||||
private final SoftWrapDataMapper myDataAdjuster;
|
||||
private final SoftWrapDataMapper myDataAdjuster;
|
||||
private final SoftWrapsStorage myStorage;
|
||||
private final SoftWrapPainter myPainter;
|
||||
private final SoftWrapApplianceManager myApplianceManager;
|
||||
@@ -54,7 +54,7 @@ public class SoftWrapModelImpl implements SoftWrapModelEx {
|
||||
private int myActive;
|
||||
|
||||
public SoftWrapModelImpl(@NotNull EditorEx editor) {
|
||||
this(editor, new SoftWrapsStorage(editor.getDocument()), new CompositeSoftWrapPainter(editor));
|
||||
this(editor, new SoftWrapsStorage(), new CompositeSoftWrapPainter(editor));
|
||||
}
|
||||
|
||||
public SoftWrapModelImpl(@NotNull final EditorEx editor, @NotNull SoftWrapsStorage storage, @NotNull SoftWrapPainter painter) {
|
||||
@@ -278,7 +278,7 @@ public class SoftWrapModelImpl implements SoftWrapModelEx {
|
||||
}
|
||||
|
||||
if (start < end) {
|
||||
result += EditorUtil.textWidth(myEditor, chars, start, end, Font.PLAIN);
|
||||
result += EditorUtil.textWidth(myEditor, chars, start, end, Font.PLAIN, 0);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
+1
-1
@@ -214,7 +214,7 @@ public class DefaultSoftWrapApplianceManager implements SoftWrapApplianceManager
|
||||
int x = myPainter.getMinDrawingWidth(SoftWrapDrawingType.BEFORE_SOFT_WRAP_LINE_FEED);
|
||||
int prevSoftWrapOffset = start;
|
||||
for (int i = start; i < end; i++) {
|
||||
int symbolWidth = EditorUtil.textWidth(myEditor, text, i, i + 1, fontType);
|
||||
int symbolWidth = EditorUtil.textWidth(myEditor, text, i, i + 1, fontType, x);
|
||||
if (x + symbolWidth >= myVisibleAreaWidth) {
|
||||
int offset = calculateSoftWrapOffset(text, i - 1, prevSoftWrapOffset, end);
|
||||
if (offset >= end || offset <= prevSoftWrapOffset) {
|
||||
|
||||
+92
-57
@@ -236,7 +236,7 @@ public class SoftWrapDataMapper {
|
||||
|
||||
// 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 = toVisualColumnSymbolsNumber(chars, targetLineStartOffset, offset);
|
||||
int column = toVisualColumnSymbolsNumber(chars, targetLineStartOffset, offset, 0);
|
||||
LogicalPosition foldingUnawarePosition = new LogicalPosition(
|
||||
rawLineStartLogicalPosition.line, column, softWrapIntroducedLinesBefore, 0, 0, 0, 0
|
||||
);
|
||||
@@ -280,8 +280,9 @@ public class SoftWrapDataMapper {
|
||||
}
|
||||
|
||||
// Assuming that no line feed is contained before target offset on a line that holds it.
|
||||
symbolsOnCurrentLogicalLine++;
|
||||
symbolsOnCurrentVisibleLine++;
|
||||
int columnsForSymbol = toVisualColumnSymbolsNumber(chars.charAt(i), symbolsOnCurrentVisibleLine);
|
||||
symbolsOnCurrentLogicalLine += columnsForSymbol;
|
||||
symbolsOnCurrentVisibleLine += columnsForSymbol;
|
||||
}
|
||||
|
||||
LogicalPosition foldingUnawarePosition = new LogicalPosition(
|
||||
@@ -304,8 +305,8 @@ public class SoftWrapDataMapper {
|
||||
int foldColumnDiff = 0;
|
||||
int softWrapColumnDiff = position.softWrapColumnDiff;
|
||||
Document document = myEditor.getDocument();
|
||||
CharSequence text = document.getCharsSequence();
|
||||
int targetLine = document.getLineNumber(offset);
|
||||
int lastFoldEndLogicalLine = -1;
|
||||
for (FoldRegion foldRegion : foldingModel.getAllFoldRegions()) {
|
||||
if (foldRegion.getStartOffset() >= offset) {
|
||||
break;
|
||||
@@ -320,53 +321,52 @@ public class SoftWrapDataMapper {
|
||||
foldedLines += Math.min(targetLine, foldingEndLine) - foldingStartLine;
|
||||
|
||||
// Process situation when target offset is located inside the folded region.
|
||||
if (offset >= foldRegion.getStartOffset() && offset < foldRegion.getEndOffset()) {
|
||||
// Our purpose is to define folding data in order to point to the visual folding start.
|
||||
int visualFoldingStartColumn = calculateVisualFoldingStartColumn(foldRegion);
|
||||
foldColumnDiff = visualFoldingStartColumn - position.column - softWrapColumnDiff;
|
||||
break;
|
||||
}
|
||||
|
||||
if (foldingEndLine != position.line) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// We know here that offset is at the same line where folding ends and is located after it. Hence, we process that as follows:
|
||||
// 1. Check if the folding is single-line;
|
||||
// 2.1. Process as follows if the folding is single-line:
|
||||
// 3.1. Calculate column difference introduced by the folding;
|
||||
// 2.2. Process as follows if the folding is multi-line:
|
||||
// 3.2. Calculate visual column of folding start;
|
||||
// 4.2. Calculate number of columns between target offset and folding end;
|
||||
// 5.1. Calculate folding placeholder width in columns;
|
||||
// 6.1. Calculate resulting offset visual column;
|
||||
// 7.1. Calculate resulting folding column diff;
|
||||
|
||||
if (foldingStartLine == foldingEndLine) {
|
||||
foldColumnDiff = toVisualColumnSymbolsNumber(foldRegion.getPlaceholderText())
|
||||
- toVisualColumnSymbolsNumber(text, foldRegion.getStartOffset(), foldRegion.getEndOffset());
|
||||
}
|
||||
else {
|
||||
int endOffsetOfLineWithFoldingEnd = document.getLineEndOffset(foldingEndLine);
|
||||
int columnsBetweenFoldingEndAndOffset = toVisualColumnSymbolsNumber(text, foldRegion.getEndOffset(), endOffsetOfLineWithFoldingEnd);
|
||||
LogicalPosition foldingEndLineEndPosition = myEditor.offsetToLogicalPosition(endOffsetOfLineWithFoldingEnd);
|
||||
if (position.column > foldingEndLineEndPosition.column) {
|
||||
columnsBetweenFoldingEndAndOffset += position.column - foldingEndLineEndPosition.column;
|
||||
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 visualFoldingStartColumn = calculateVisualFoldingStartColumn(foldRegion);
|
||||
int foldingPlaceholderWidth = toVisualColumnSymbolsNumber(foldRegion.getPlaceholderText());
|
||||
int visual = columnsBetweenFoldingEndAndOffset + visualFoldingStartColumn + foldingPlaceholderWidth;
|
||||
foldColumnDiff = visual - position.column;
|
||||
break;
|
||||
|
||||
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();
|
||||
@@ -380,6 +380,7 @@ public class SoftWrapDataMapper {
|
||||
|
||||
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) {
|
||||
@@ -387,25 +388,46 @@ public class SoftWrapDataMapper {
|
||||
}
|
||||
|
||||
startOffsetOfVisualLineWithFoldingStart = softWrap.getStart();
|
||||
softWrapOffsetInColumns = numberOfSymbolsOnLastVisualLine(softWrap);
|
||||
}
|
||||
|
||||
assert startOffsetOfVisualLineWithFoldingStart <= foldingStartOffset;
|
||||
return toVisualColumnSymbolsNumber(document.getCharsSequence(), startOffsetOfVisualLineWithFoldingStart, foldingStartOffset);
|
||||
return EditorUtil.textWidthInColumns(
|
||||
myEditor, document.getCharsSequence(), startOffsetOfVisualLineWithFoldingStart, foldingStartOffset, softWrapOffsetInColumns
|
||||
);
|
||||
}
|
||||
|
||||
private int toVisualColumnSymbolsNumber(char c) {
|
||||
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++;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private int toVisualColumnSymbolsNumber(char c, int offsetInColumns) {
|
||||
myCharBuffer.clear();
|
||||
myCharBuffer.put(c);
|
||||
myCharBuffer.flip();
|
||||
return toVisualColumnSymbolsNumber(myCharBuffer, 0, 1);
|
||||
return toVisualColumnSymbolsNumber(myCharBuffer, 0, 1, offsetInColumns);
|
||||
}
|
||||
|
||||
private int toVisualColumnSymbolsNumber(CharSequence text) {
|
||||
return toVisualColumnSymbolsNumber(text, 0, text.length());
|
||||
}
|
||||
|
||||
private int toVisualColumnSymbolsNumber(CharSequence text, int start, int end) {
|
||||
return EditorUtil.calcColumnNumber(myEditor, text, start, end);
|
||||
private int toVisualColumnSymbolsNumber(CharSequence text, int start, int end, int offsetInColumns) {
|
||||
int result = 0;
|
||||
for (int i = start; i < end; i++) {
|
||||
if (text.charAt(i) == '\t') {
|
||||
result += EditorUtil.tabWidthInColumns(myEditor, offsetInColumns + result);
|
||||
}
|
||||
else {
|
||||
result++;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private class Context {
|
||||
@@ -420,6 +442,12 @@ public class SoftWrapDataMapper {
|
||||
public int targetSoftWrapLines;
|
||||
public int symbolsOnCurrentLogicalLine;
|
||||
public int symbolsOnCurrentVisualLine;
|
||||
/**
|
||||
* There is a possible case that single tabulation symbols is shown in more than one visual column (IntelliJ editor is configured
|
||||
* to show white spaces and tabulations and tab size is more than one). We keep track in number of such excessive visual
|
||||
* columns used to show tabulations on a current visual line.
|
||||
*/
|
||||
public int excessiveTabColumns;
|
||||
|
||||
Context(LogicalPosition softWrapUnawareLogicalPosition, VisualPosition targetVisualPosition, int softWrapLinesBefore,
|
||||
int softWrapLinesOnCurrentLineBeforeTargetSoftWrap, int visualLineBeforeSoftWrapAppliance, FoldingModel foldingModel)
|
||||
@@ -449,13 +477,14 @@ public class SoftWrapDataMapper {
|
||||
else {
|
||||
targetSoftWrapLines++;
|
||||
symbolsOnCurrentVisualLine = 0;
|
||||
excessiveTabColumns = 0;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Just update information about tracked symbols number if current visual line is too low.
|
||||
if (targetVisualPosition.line > visualLineBeforeSoftWrapAppliance + targetSoftWrapLines) {
|
||||
symbolsOnCurrentVisualLine += toVisualColumnSymbolsNumber(c);
|
||||
symbolsOnCurrentVisualLine += toVisualColumnSymbolsNumber(c, symbolsOnCurrentVisualLine);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -467,7 +496,7 @@ public class SoftWrapDataMapper {
|
||||
}
|
||||
|
||||
// Process non-line feed inside soft wrap.
|
||||
symbolsOnCurrentVisualLine += toVisualColumnSymbolsNumber(c);
|
||||
symbolsOnCurrentVisualLine++; // Don't expect tabulation to be used inside soft wrap text.
|
||||
if (targetVisualPosition.column <= symbolsOnCurrentVisualLine) {
|
||||
return build();
|
||||
}
|
||||
@@ -493,8 +522,9 @@ public class SoftWrapDataMapper {
|
||||
|
||||
// Just update information about tracked symbols number if current visual line is too low.
|
||||
if (targetVisualPosition.line > visualLineBeforeSoftWrapAppliance + targetSoftWrapLines) {
|
||||
symbolsOnCurrentVisualLine += toVisualColumnSymbolsNumber(c);
|
||||
symbolsOnCurrentLogicalLine++;
|
||||
int columnsForSymbol = toVisualColumnSymbolsNumber(c, symbolsOnCurrentVisualLine);
|
||||
symbolsOnCurrentVisualLine += columnsForSymbol;
|
||||
symbolsOnCurrentLogicalLine += columnsForSymbol;
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -504,9 +534,14 @@ public class SoftWrapDataMapper {
|
||||
return build();
|
||||
}
|
||||
|
||||
symbolsOnCurrentVisualLine += toVisualColumnSymbolsNumber(c);
|
||||
symbolsOnCurrentLogicalLine++;
|
||||
|
||||
int columnsForSymbol = toVisualColumnSymbolsNumber(c, symbolsOnCurrentVisualLine);
|
||||
int diffInColumns = targetVisualPosition.column - symbolsOnCurrentVisualLine;
|
||||
int incrementToUse = columnsForSymbol;
|
||||
if (columnsForSymbol >= diffInColumns) {
|
||||
incrementToUse = Math.min(columnsForSymbol, diffInColumns);
|
||||
}
|
||||
symbolsOnCurrentVisualLine += incrementToUse;
|
||||
symbolsOnCurrentLogicalLine += incrementToUse;
|
||||
|
||||
if (targetVisualPosition.column <= symbolsOnCurrentVisualLine) {
|
||||
return build();
|
||||
|
||||
+1
-9
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
package com.intellij.openapi.editor.impl.softwrap;
|
||||
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.editor.ex.SoftWrapChangeListener;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -40,12 +39,6 @@ public class SoftWrapsStorage {
|
||||
private final List<TextChangeImpl> myWrapsView = Collections.unmodifiableList(myWraps);
|
||||
private final Set<SoftWrapChangeListener> myListeners = new CopyOnWriteArraySet<SoftWrapChangeListener>();
|
||||
|
||||
private final Document myDocument;
|
||||
|
||||
public SoftWrapsStorage(Document document) {
|
||||
myDocument = document;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return <code>true</code> if there is at least one soft wrap registered at the current storage; <code>false</code> otherwise
|
||||
*/
|
||||
@@ -114,9 +107,8 @@ public class SoftWrapsStorage {
|
||||
|
||||
i = -i - 1;
|
||||
myWraps.add(i, softWrap);
|
||||
int changedLogicalLine = myDocument.getLineNumber(softWrap.getStart());
|
||||
for (SoftWrapChangeListener listener : myListeners) {
|
||||
listener.softWrapsStateChanged(changedLogicalLine);
|
||||
listener.softWrapAdded(softWrap);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
+144
-26
@@ -1,6 +1,5 @@
|
||||
package com.intellij.openapi.editor.impl.softwrap;
|
||||
|
||||
import com.intellij.idea.Bombed;
|
||||
import com.intellij.mock.MockFoldRegion;
|
||||
import com.intellij.openapi.editor.*;
|
||||
import com.intellij.openapi.editor.ex.EditorEx;
|
||||
@@ -9,16 +8,19 @@ import com.intellij.openapi.util.TextRange;
|
||||
import gnu.trove.TIntHashSet;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.api.Invocation;
|
||||
import org.jmock.lib.action.CustomAction;
|
||||
import org.junit.Test;
|
||||
import org.junit.Before;
|
||||
import org.junit.After;
|
||||
import org.jmock.integration.junit4.JUnit4Mockery;
|
||||
import org.jmock.Mockery;
|
||||
import org.jmock.api.Invocation;
|
||||
import org.jmock.integration.junit4.JUnit4Mockery;
|
||||
import org.jmock.lib.action.CustomAction;
|
||||
import org.jmock.lib.legacy.ClassImposteriser;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Denis Zhdanov
|
||||
@@ -38,7 +40,7 @@ public class SoftWrapDataMapperTest {
|
||||
// There is a possible case that multiple logical positions match to the same visual position (e.g. logical
|
||||
// positions for folded text match to the same visual position). We want to match to the logical position of
|
||||
// folding region start if we search by logical position from folded text.
|
||||
if (o1.foldedSpace & o2.foldedSpace && logical1.column + logical1.foldingColumnDiff == logical2.foldingColumnDiff) {
|
||||
if (o1.foldedSpace && o2.foldedSpace && logical1.column + logical1.foldingColumnDiff == logical2.foldingColumnDiff) {
|
||||
return o1.foldedSpace ? 1 : -1;
|
||||
}
|
||||
return logical1.column - logical2.column;
|
||||
@@ -48,13 +50,19 @@ public class SoftWrapDataMapperTest {
|
||||
private static final Comparator<DataEntry> OFFSETS_COMPARATOR = new Comparator<DataEntry>() {
|
||||
@Override
|
||||
public int compare(DataEntry o1, DataEntry o2) {
|
||||
if (o1.offset != o2.offset) {
|
||||
return o1.offset - o2.offset;
|
||||
}
|
||||
// There are numerous situations when multiple visual positions share the same offset (e.g. all soft wrap-introduced virtual
|
||||
// spaces share offset with the first document symbol after soft wrap or all virtual spaces after line end share the same offset
|
||||
// as the last line symbol). We want to ignore such positions during lookup by offset.
|
||||
if (o1.offset == o2.offset && o1.virtualSpace ^ o2.virtualSpace) {
|
||||
if (o1.virtualSpace ^ o2.virtualSpace) {
|
||||
return o1.virtualSpace ? 1 : -1;
|
||||
}
|
||||
return o1.offset - o2.offset;
|
||||
if (o1.insideTab ^ o2.insideTab) {
|
||||
return o1.insideTab ? 1 : -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -86,7 +94,7 @@ public class SoftWrapDataMapperTest {
|
||||
|
||||
myEditor = myMockery.mock(EditorEx.class);
|
||||
myDocument = myMockery.mock(Document.class);
|
||||
myStorage = new SoftWrapsStorage(myDocument);
|
||||
myStorage = new SoftWrapsStorage();
|
||||
myFoldingModel = myMockery.mock(FoldingModel.class);
|
||||
final EditorSettings settings = myMockery.mock(EditorSettings.class);
|
||||
final Project project = myMockery.mock(Project.class);
|
||||
@@ -123,6 +131,7 @@ public class SoftWrapDataMapperTest {
|
||||
allowing(myEditor).getSettings();will(returnValue(settings));
|
||||
allowing(settings).isUseSoftWraps();will(returnValue(true));
|
||||
allowing(settings).getTabSize(project);will(returnValue(TAB_SIZE));
|
||||
allowing(settings).isWhitespacesShown();will(returnValue(true));
|
||||
allowing(myEditor).getProject();will(returnValue(project));
|
||||
|
||||
// Folding.
|
||||
@@ -176,7 +185,6 @@ public class SoftWrapDataMapperTest {
|
||||
myMockery.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Bombed(day = 12, month = Calendar.JULY)
|
||||
@Test
|
||||
public void softWrapHasSymbolBeforeFirstLineFeed() {
|
||||
String document =
|
||||
@@ -192,7 +200,6 @@ public class SoftWrapDataMapperTest {
|
||||
test(document);
|
||||
}
|
||||
|
||||
@Bombed(day = 12, month = Calendar.JULY)
|
||||
@Test
|
||||
public void multipleSoftWrappedLogicalLines() {
|
||||
String document =
|
||||
@@ -212,7 +219,6 @@ public class SoftWrapDataMapperTest {
|
||||
test(document);
|
||||
}
|
||||
|
||||
@Bombed(day = 12, month = Calendar.JULY)
|
||||
@Test
|
||||
public void softWrapAndFoldedLines() {
|
||||
String document =
|
||||
@@ -236,6 +242,70 @@ public class SoftWrapDataMapperTest {
|
||||
test(document);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tabSymbolsBeforeSoftWrap() {
|
||||
String document =
|
||||
"class Test\t\t{<WRAP>\n" +
|
||||
" </WRAP> \n}";
|
||||
test(document);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tabSymbolsAfterSoftWrap() {
|
||||
String document =
|
||||
"class Test {<WRAP>\n" +
|
||||
" </WRAP> \t\t\n" +
|
||||
"}";
|
||||
test(document);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipleTabsAndSoftWraps() {
|
||||
String document =
|
||||
"public class \tTest {\n" +
|
||||
" public void foo(int[] data) {\n" +
|
||||
" bar(data[0], data[1],\t\t <WRAP>\n" +
|
||||
" </WRAP>data[2], data[3], <WRAP>\t \t\n" +
|
||||
" </WRAP>data[4], data[5],\t \t \n" +
|
||||
" data[6], data[7],\t \t \n" +
|
||||
" data[8], data[9],\t \t <WRAP>\n" +
|
||||
" </WRAP>data[10], data[11], <WRAP>\t \t \n" +
|
||||
" </WRAP>data[12],\t \t data[13]); \n" +
|
||||
" }\n" +
|
||||
" public void bar(int ... i) {\n" +
|
||||
" }\n" +
|
||||
"}";
|
||||
test(document);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tabBeforeFolding() {
|
||||
String document =
|
||||
"class Test\t \t <FOLD>{\n" +
|
||||
" </FOLD> \t\t\n" +
|
||||
"}";
|
||||
test(document);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipleTabsAndFolding() {
|
||||
String document =
|
||||
"public class \tTest {\n" +
|
||||
" public void foo(int[] data) {\n" +
|
||||
" bar(data[0], data[1],\t\t <FOLD>\n" +
|
||||
" \t \t </FOLD>data[2], data[3], <FOLD>\t \t\n" +
|
||||
" \t \t </FOLD>data[4], data[5],\t \t \n" +
|
||||
" data[6], data[7],\t \t \n" +
|
||||
" data[8], data[9],\t \t <FOLD>\n" +
|
||||
"\t \t </FOLD>data[10], data[11], <FOLD>\t \t \n" +
|
||||
" \t \t </FOLD>data[12],\t \t data[13]); \n" +
|
||||
" }\n" +
|
||||
" public void bar(int ... i) {\n" +
|
||||
" }\n" +
|
||||
"}";
|
||||
test(document);
|
||||
}
|
||||
|
||||
private int getLineNumber(int offset) {
|
||||
int line = 0;
|
||||
for (TextRange range : myLineRanges) {
|
||||
@@ -324,7 +394,7 @@ public class SoftWrapDataMapperTest {
|
||||
LogicalPosition actualLogicalByVisual = myAdjuster.adjustLogicalPosition(toSoftWrapUnawareLogicalByVisual(data), data.visual);
|
||||
// We don't want to perform the check for logical positions that correspond to the folded space because all of them relate to
|
||||
// the same logical position of the folding start.
|
||||
if (!data.foldedSpace && !equals(data.logical, actualLogicalByVisual)) {
|
||||
if (!data.foldedSpace && !data.insideTab && !equals(data.logical, actualLogicalByVisual)) {
|
||||
throw new AssertionError(
|
||||
String.format("Detected unmatched logical position by visual (%s). Expected: '%s', actual: '%s'. Calculation was performed "
|
||||
+ "against soft wrap-unaware logical: '%s'",
|
||||
@@ -336,7 +406,7 @@ public class SoftWrapDataMapperTest {
|
||||
LogicalPosition actualLogicalByOffset = myAdjuster.offsetToLogicalPosition(data.offset);
|
||||
// We don't to perform the check for the data that points to soft wrap location here. The reason is that it shares offset
|
||||
// with the first document symbol after soft wrap, hence, examination always fails.
|
||||
if (!data.virtualSpace && !equals(data.logical, actualLogicalByOffset)) {
|
||||
if (!data.virtualSpace && !data.insideTab && !equals(data.logical, actualLogicalByOffset)) {
|
||||
throw new AssertionError(
|
||||
String.format("Detected unmatched logical position by offset. Expected: '%s', actual: '%s'. Calculation was performed "
|
||||
+ "against offset: '%d' and soft wrap-unaware logical: '%s'",
|
||||
@@ -432,17 +502,19 @@ public class SoftWrapDataMapperTest {
|
||||
public final int offset;
|
||||
public final boolean foldedSpace;
|
||||
public final boolean virtualSpace;
|
||||
public final boolean insideTab;
|
||||
|
||||
DataEntry(VisualPosition visual, LogicalPosition logical, int offset, boolean foldedSpace) {
|
||||
this(visual, logical, offset, foldedSpace, false);
|
||||
this(visual, logical, offset, foldedSpace, false, false);
|
||||
}
|
||||
|
||||
DataEntry(VisualPosition visual, LogicalPosition logical, int offset, boolean foldedSpace, boolean virtualSpace) {
|
||||
DataEntry(VisualPosition visual, LogicalPosition logical, int offset, boolean foldedSpace, boolean virtualSpace, boolean insideTab) {
|
||||
this.visual = visual;
|
||||
this.logical = logical;
|
||||
this.offset = offset;
|
||||
this.foldedSpace = foldedSpace;
|
||||
this.virtualSpace = virtualSpace;
|
||||
this.insideTab = insideTab;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -458,6 +530,7 @@ public class SoftWrapDataMapperTest {
|
||||
|
||||
boolean insideSoftWrap;
|
||||
boolean insideFolding;
|
||||
boolean insideTab;
|
||||
int logicalLineStartOffset;
|
||||
int logicalLine;
|
||||
int logicalColumn;
|
||||
@@ -473,6 +546,7 @@ public class SoftWrapDataMapperTest {
|
||||
int foldingColumnDiff;
|
||||
int foldedLines;
|
||||
int offset;
|
||||
int tabAnchorColumn;
|
||||
|
||||
public void onSoftWrapStart() {
|
||||
softWrapStartOffset = offset;
|
||||
@@ -482,7 +556,6 @@ public class SoftWrapDataMapperTest {
|
||||
public void onSoftWrapEnd() {
|
||||
myStorage.storeOrReplace(new TextChangeImpl(mySoftWrapBuffer.toString(), softWrapStartOffset));
|
||||
mySoftWrapBuffer.setLength(0);
|
||||
visualColumn++; // For the column reserved for soft wrap sign.
|
||||
insideSoftWrap = false;
|
||||
}
|
||||
|
||||
@@ -507,6 +580,29 @@ public class SoftWrapDataMapperTest {
|
||||
onNonSoftWrapSymbol(c);
|
||||
if (c == '\n') {
|
||||
foldedLines++;
|
||||
offset++;
|
||||
tabAnchorColumn = 0;
|
||||
}
|
||||
else if (c == '\t') {
|
||||
int tabsNumber = tabAnchorColumn / TAB_SIZE;
|
||||
int tabWidthInColumns = ((tabsNumber + 1) * TAB_SIZE) - tabAnchorColumn;
|
||||
|
||||
// There is a possible case that single tabulation symbols is shown in more than one visual column at IntelliJ editor.
|
||||
// We store data entry only for the first tab column without 'inside tab' flag then.
|
||||
insideTab = true;
|
||||
for (int i =tabWidthInColumns - 1; i > 0; i--) {
|
||||
logicalColumn++;
|
||||
addData(false);
|
||||
}
|
||||
insideTab = false;
|
||||
|
||||
logicalColumn++;
|
||||
offset++;
|
||||
tabAnchorColumn += tabWidthInColumns;
|
||||
} else {
|
||||
logicalColumn++;
|
||||
offset++;
|
||||
tabAnchorColumn++;
|
||||
}
|
||||
foldingColumnDiff = foldingStartVisualColumn - logicalColumn;
|
||||
return;
|
||||
@@ -525,12 +621,14 @@ public class SoftWrapDataMapperTest {
|
||||
|
||||
visualLine++;
|
||||
softWrapLinesOnCurrentLogical++;
|
||||
visualColumn = 0;
|
||||
visualColumn = 1; // For the column reserved for soft wrap sign.
|
||||
tabAnchorColumn = 1;
|
||||
softWrapSymbolsOnCurrentVisualLine = 0;
|
||||
}
|
||||
else {
|
||||
visualColumn++;
|
||||
softWrapSymbolsOnCurrentVisualLine++;
|
||||
tabAnchorColumn++;
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -540,13 +638,37 @@ public class SoftWrapDataMapperTest {
|
||||
if (c == '\n') {
|
||||
visualLine++;
|
||||
visualColumn = 0;
|
||||
tabAnchorColumn = 0;
|
||||
softWrapLinesBeforeCurrentLogical += softWrapLinesOnCurrentLogical;
|
||||
softWrapLinesOnCurrentLogical = 0;
|
||||
softWrapSymbolsOnCurrentVisualLine = 0;
|
||||
foldingColumnDiff = 0;
|
||||
offset++;
|
||||
}
|
||||
else if (c == '\t') {
|
||||
int tabsNumber = tabAnchorColumn / TAB_SIZE;
|
||||
int tabWidthInColumns = ((tabsNumber + 1) * TAB_SIZE) - tabAnchorColumn;
|
||||
|
||||
// There is a possible case that single tabulation symbols is shown in more than one visual column at IntelliJ editor.
|
||||
// We store data entry only for the first tab column without 'inside tab' flag then.
|
||||
insideTab = true;
|
||||
for (int i = tabWidthInColumns - 1; i > 0; i--) {
|
||||
visualColumn++;
|
||||
logicalColumn++;
|
||||
addData(false);
|
||||
}
|
||||
insideTab = false;
|
||||
|
||||
visualColumn++;
|
||||
logicalColumn++;
|
||||
offset++;
|
||||
tabAnchorColumn += tabWidthInColumns;
|
||||
}
|
||||
else {
|
||||
visualColumn++;
|
||||
logicalColumn++;
|
||||
offset++;
|
||||
tabAnchorColumn++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -570,10 +692,6 @@ public class SoftWrapDataMapperTest {
|
||||
logicalLine++;
|
||||
logicalColumn = 0;
|
||||
}
|
||||
else {
|
||||
logicalColumn++;
|
||||
}
|
||||
offset++;
|
||||
}
|
||||
|
||||
private void addData() {
|
||||
@@ -582,7 +700,7 @@ public class SoftWrapDataMapperTest {
|
||||
|
||||
private void addData(boolean virtualSpace) {
|
||||
myExpectedData.add(new DataEntry(
|
||||
buildVisualPosition(), buildLogicalPosition(), offset, insideFolding && offset != foldingStartOffset, virtualSpace
|
||||
buildVisualPosition(), buildLogicalPosition(), offset, insideFolding && offset != foldingStartOffset, virtualSpace, insideTab
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user