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. Introduced pure mock unit testing framework for soft wraps offsets & positions transformations; 2. Added unit tests for soft wrap processing; 3. Soft wrap processing is refactored in order to improve maintainability (as it's possible to check if the change brokes processing now); 4. Corrected left and right arrows processing in case of soft wraps (move to the next/previous visual soft-wrapped line);
This commit is contained in:
@@ -153,6 +153,11 @@ public class EditorWindow implements EditorEx, UserDataHolderEx {
|
||||
return myDelegate.isViewer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUnderRepainting() {
|
||||
return myDelegate.isUnderRepainting();
|
||||
}
|
||||
|
||||
public boolean isRendererMode() {
|
||||
return myDelegate.isRendererMode();
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/testSrc" isTestSource="true" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
|
||||
@@ -31,6 +31,7 @@ import java.awt.datatransfer.DataFlavor;
|
||||
import java.awt.datatransfer.Transferable;
|
||||
import java.awt.datatransfer.UnsupportedFlavorException;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
public class EditorModificationUtil {
|
||||
private EditorModificationUtil() {}
|
||||
@@ -221,16 +222,45 @@ public class EditorModificationUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates columns difference between current editor caret position and end of the logical line fragment displayed
|
||||
* on a current visual line.
|
||||
*
|
||||
* @param editor target editor
|
||||
* @return column difference between current editor caret position and end of the logical line fragment displayed
|
||||
* on a current visual line
|
||||
*/
|
||||
public static int calcAfterLineEnd(Editor editor) {
|
||||
Document document = editor.getDocument();
|
||||
LogicalPosition logicalPosition = editor.getCaretModel().getLogicalPosition();
|
||||
int columnNumber = logicalPosition.column;
|
||||
CaretModel caretModel = editor.getCaretModel();
|
||||
LogicalPosition logicalPosition = caretModel.getLogicalPosition();
|
||||
int lineNumber = logicalPosition.line;
|
||||
int columnNumber = logicalPosition.column;
|
||||
if (lineNumber >= document.getLineCount()) {
|
||||
return columnNumber;
|
||||
}
|
||||
int lineEndOffset = document.getLineEndOffset(lineNumber);
|
||||
int lineEndColumnNumber = editor.offsetToLogicalPosition(lineEndOffset).column;
|
||||
|
||||
int caretOffset = caretModel.getOffset();
|
||||
int anchorLineEndOffset = document.getLineEndOffset(lineNumber);
|
||||
List<TextChange> softWraps = editor.getSoftWrapModel().getSoftWrapsForLine(logicalPosition.line);
|
||||
for (TextChange softWrap : softWraps) {
|
||||
if (softWrap.getStart() > caretOffset) {
|
||||
anchorLineEndOffset = softWrap.getStart();
|
||||
break;
|
||||
}
|
||||
|
||||
// Same offset corresponds to all soft wrap-introduced symbols, however, current method should behave differently in
|
||||
// situations when the caret is located just before the soft wrap and at the next visual line.
|
||||
if (softWrap.getStart() == caretOffset) {
|
||||
boolean visuallyBeforeSoftWrap = caretModel.getVisualPosition().line < editor.offsetToVisualPosition(caretOffset).line;
|
||||
if (visuallyBeforeSoftWrap) {
|
||||
anchorLineEndOffset = softWrap.getStart();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int lineEndColumnNumber = editor.offsetToLogicalPosition(anchorLineEndOffset).column;
|
||||
return columnNumber - lineEndColumnNumber;
|
||||
}
|
||||
|
||||
|
||||
@@ -44,27 +44,25 @@ public class LogicalPosition implements Comparable<LogicalPosition> {
|
||||
|
||||
/**
|
||||
* Identifies if current logical position may be correctly mapped to visual position. E.g. we can define properties like
|
||||
* {@link #softWrapLines}, {@link #softWrapColumnDiff} etc during {@code 'visual position' -> 'logical position'} conversion
|
||||
* {@link #softWrapLinesBeforeCurrentLogicalLine}, {@link #softWrapColumnDiff} etc during {@code 'visual position' -> 'logical position'} conversion
|
||||
* in order to be able to easy match it back to visual position.
|
||||
*/
|
||||
public final boolean visualPositionAware;
|
||||
|
||||
/**
|
||||
* Number of virtual soft wrap-introduced lines before the visual position that corresponds to the current logical position.
|
||||
* Number of virtual soft wrap-introduced lines before the current logical line.
|
||||
*
|
||||
* @see #visualPositionAware
|
||||
*/
|
||||
public final int softWrapLines;
|
||||
public final int softWrapLinesBeforeCurrentLogicalLine;
|
||||
|
||||
/**
|
||||
* Number of virtual soft lines introduced by the current soft wrap before the visual position that corresponds
|
||||
* Number of virtual soft wrap introduced lines on a current logical line before the visual position that corresponds
|
||||
* to the current logical position.
|
||||
* <p/>
|
||||
* This value is assumed to be not greater than {@link #softWrapLines} all the time.
|
||||
*
|
||||
* @see #visualPositionAware
|
||||
*/
|
||||
public final int linesFromActiveSoftWrap;
|
||||
public final int softWrapLinesOnCurrentLogicalLine;
|
||||
|
||||
/**
|
||||
* Number to add to the {@link #column logical column} in order to get soft wrap-introduced visual column offset.
|
||||
@@ -91,21 +89,22 @@ public class LogicalPosition implements Comparable<LogicalPosition> {
|
||||
this(line, column, 0, 0, 0, 0, 0, false);
|
||||
}
|
||||
|
||||
public LogicalPosition(int line, int column, int softWrapLines, int linesFromActiveSoftWrap, int softWrapColumnDiff,
|
||||
int foldedLines, int foldingColumnDiff)
|
||||
public LogicalPosition(int line, int column, int softWrapLinesBeforeCurrentLogicalLine, int softWrapLinesOnCurrentLogicalLine,
|
||||
int softWrapColumnDiff, int foldedLines, int foldingColumnDiff)
|
||||
{
|
||||
this(line, column, softWrapLines, linesFromActiveSoftWrap, softWrapColumnDiff, foldedLines, foldingColumnDiff, true);
|
||||
this(
|
||||
line, column, softWrapLinesBeforeCurrentLogicalLine, softWrapLinesOnCurrentLogicalLine, softWrapColumnDiff, foldedLines,
|
||||
foldingColumnDiff, true
|
||||
);
|
||||
}
|
||||
|
||||
private LogicalPosition(int line, int column, int softWrapLines, int linesFromActiveSoftWrap, int softWrapColumnDiff, int foldedLines,
|
||||
int foldingColumnDiff, boolean visualPositionAware)
|
||||
private LogicalPosition(int line, int column, int softWrapLinesBeforeCurrentLogicalLine, int softWrapLinesOnCurrentLogicalLine,
|
||||
int softWrapColumnDiff, int foldedLines, int foldingColumnDiff, boolean visualPositionAware)
|
||||
{
|
||||
assert linesFromActiveSoftWrap <= softWrapLines;
|
||||
|
||||
this.line = line;
|
||||
this.column = column;
|
||||
this.softWrapLines = softWrapLines;
|
||||
this.linesFromActiveSoftWrap = linesFromActiveSoftWrap;
|
||||
this.softWrapLinesBeforeCurrentLogicalLine = softWrapLinesBeforeCurrentLogicalLine;
|
||||
this.softWrapLinesOnCurrentLogicalLine = softWrapLinesOnCurrentLogicalLine;
|
||||
this.softWrapColumnDiff = softWrapColumnDiff;
|
||||
this.foldedLines = foldedLines;
|
||||
this.foldingColumnDiff = foldingColumnDiff;
|
||||
@@ -130,7 +129,10 @@ public class LogicalPosition implements Comparable<LogicalPosition> {
|
||||
* @return visual position based on a state of the current logical position
|
||||
*/
|
||||
public VisualPosition toVisualPosition() {
|
||||
return new VisualPosition(line + softWrapLines - foldedLines, column + softWrapColumnDiff + foldingColumnDiff);
|
||||
return new VisualPosition(
|
||||
line + softWrapLinesBeforeCurrentLogicalLine + softWrapLinesOnCurrentLogicalLine - foldedLines,
|
||||
column + softWrapColumnDiff + foldingColumnDiff
|
||||
);
|
||||
}
|
||||
|
||||
public boolean equals(Object o) {
|
||||
@@ -147,14 +149,15 @@ public class LogicalPosition implements Comparable<LogicalPosition> {
|
||||
@NonNls
|
||||
public String toString() {
|
||||
return "LogicalPosition: line=" + line + " column=" + column + "; visual position aware=" + visualPositionAware
|
||||
+ " soft wrap: lines=" + softWrapLines + " (active=" + linesFromActiveSoftWrap + ") columns diff=" + softWrapColumnDiff
|
||||
+ "; folding: lines = " + foldedLines + " columns diff=" + foldingColumnDiff;
|
||||
+ " soft wrap: lines=" + (softWrapLinesBeforeCurrentLogicalLine + softWrapLinesOnCurrentLogicalLine) + " (before=" +
|
||||
softWrapLinesBeforeCurrentLogicalLine + "; current=" + softWrapLinesOnCurrentLogicalLine
|
||||
+ ") columns diff=" + softWrapColumnDiff + "; folding: lines = " + foldedLines + " columns diff=" + foldingColumnDiff;
|
||||
}
|
||||
|
||||
public int compareTo(LogicalPosition position) {
|
||||
if (line != position.line) return line - position.line;
|
||||
if (column != position.column) return column - position.column;
|
||||
if (softWrapLines != position.softWrapLines) return softWrapLines - position.softWrapLines;
|
||||
if (softWrapLinesBeforeCurrentLogicalLine != position.softWrapLinesBeforeCurrentLogicalLine) return softWrapLinesBeforeCurrentLogicalLine - position.softWrapLinesBeforeCurrentLogicalLine;
|
||||
return softWrapColumnDiff - position.softWrapColumnDiff;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,4 +105,13 @@ public interface SoftWrapModel {
|
||||
* @param position position where the document is to be modified
|
||||
*/
|
||||
void beforeDocumentChange(@NotNull VisualPosition position);
|
||||
|
||||
/**
|
||||
* Allows to answer if given visual position points to soft wrap-introduced virtual space.
|
||||
*
|
||||
* @param position target visual position to check
|
||||
* @return <code>true</code> if given visual position points to soft wrap-introduced virtual space;
|
||||
* <code>false</code> otherwise
|
||||
*/
|
||||
boolean isInsideSoftWrap(@NotNull VisualPosition position);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.intellij.openapi.editor;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* @author Denis Zhdanov
|
||||
* @since 06/29/2010
|
||||
*/
|
||||
public class TextChangeTest {
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void negativeStartIndex() {
|
||||
new TextChange("", -1);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void negativeEndIndex() {
|
||||
new TextChange("", 0, -1);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void inconsistentIndices() {
|
||||
new TextChange("", 2, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void propertiesExposing() {
|
||||
int start = 1;
|
||||
int end = 10;
|
||||
String text = "test";
|
||||
TextChange textChange = new TextChange(text, start, end);
|
||||
assertEquals(text, textChange.getText());
|
||||
assertArrayEquals(text.toCharArray(), textChange.getChars());
|
||||
assertEquals(start, textChange.getStart());
|
||||
assertEquals(end, textChange.getEnd());
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void advanceToNegativeOffset() {
|
||||
new TextChange("", 1, 2).advance(-2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void advance() {
|
||||
TextChange base = new TextChange("xyz", 3, 5);
|
||||
|
||||
int[] offsets = {5, 0, -3};
|
||||
for (int offset : offsets) {
|
||||
assertEquals(new TextChange(base.getText(), base.getStart() + offset, base.getEnd() + offset), base.advance(offset));
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-5
@@ -462,14 +462,12 @@ public class EditorActionUtil {
|
||||
newOffset = i;
|
||||
}
|
||||
|
||||
// Check if there are white space symbols between the current caret position and visual line end.
|
||||
if (newOffset == offset) {
|
||||
// Move to the calculated end of visual line if caret is located on a last non-white space symbols on a line and there are
|
||||
// remaining white space symbols.
|
||||
if (newOffset == offset || newOffset == caretModel.getOffset()) {
|
||||
caretModel.moveToVisualPosition(visualEndOfLineWithCaret);
|
||||
}
|
||||
else {
|
||||
if (newOffset == caretModel.getOffset()) {
|
||||
newOffset = offset;
|
||||
}
|
||||
caretModel.moveToOffset(newOffset);
|
||||
}
|
||||
|
||||
|
||||
@@ -99,6 +99,8 @@ public interface EditorEx extends Editor {
|
||||
|
||||
void setRendererMode(boolean isRendererMode);
|
||||
|
||||
boolean isUnderRepainting();
|
||||
|
||||
void setFile(VirtualFile vFile);
|
||||
|
||||
DataContext getDataContext();
|
||||
|
||||
@@ -101,7 +101,8 @@ public class EditorUtil {
|
||||
return resVisEnd.column;
|
||||
}
|
||||
}
|
||||
int result = editor.offsetToVisualPosition(softWrap.getStart() - 1).column;
|
||||
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
|
||||
result += calcColumnNumber(editor, softWrap.getText(), softWrapStartOffset, softWrapEndOffset);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -199,10 +199,20 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener {
|
||||
}
|
||||
|
||||
VisualPosition pos = new VisualPosition(newLineNumber, newColumnNumber);
|
||||
moveToVisualPosition(pos);
|
||||
if (columnShift != 0 && lineShift == 0 && myEditor.getSoftWrapModel().isInsideSoftWrap(pos)) {
|
||||
LogicalPosition logical = myEditor.visualToLogicalPosition(pos);
|
||||
int offsetToUse = myEditor.logicalPositionToOffset(logical);
|
||||
if (columnShift < 0) {
|
||||
offsetToUse--;
|
||||
|
||||
if (!editorSettings.isVirtualSpace() && columnShift == 0) {
|
||||
myEditor.setLastColumnNumber(lastColumnNumber);
|
||||
}
|
||||
moveToOffset(offsetToUse);
|
||||
}
|
||||
else {
|
||||
moveToVisualPosition(pos);
|
||||
if (!editorSettings.isVirtualSpace() && columnShift == 0) {
|
||||
myEditor.setLastColumnNumber(lastColumnNumber);
|
||||
}
|
||||
}
|
||||
|
||||
if (withSelection) {
|
||||
@@ -227,8 +237,8 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener {
|
||||
validateCallContext();
|
||||
int column = pos.column;
|
||||
int line = pos.line;
|
||||
int softWrapLines = pos.softWrapLines;
|
||||
int linesFromCurrentSoftWrap = pos.linesFromActiveSoftWrap;
|
||||
int softWrapLinesBefore = pos.softWrapLinesBeforeCurrentLogicalLine;
|
||||
int softWrapLinesCurrent = pos.softWrapLinesOnCurrentLogicalLine;
|
||||
int softWrapColumns = pos.softWrapColumnDiff;
|
||||
|
||||
Document doc = myEditor.getDocument();
|
||||
@@ -239,8 +249,8 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener {
|
||||
}
|
||||
if (line < 0) {
|
||||
line = 0;
|
||||
softWrapLines = 0;
|
||||
linesFromCurrentSoftWrap = 0;
|
||||
softWrapLinesBefore = 0;
|
||||
softWrapLinesCurrent = 0;
|
||||
}
|
||||
|
||||
int lineCount = doc.getLineCount();
|
||||
@@ -249,8 +259,8 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener {
|
||||
}
|
||||
else if (line > lineCount - 1) {
|
||||
line = lineCount - 1;
|
||||
softWrapLines = 0;
|
||||
linesFromCurrentSoftWrap = 0;
|
||||
softWrapLinesBefore = 0;
|
||||
softWrapLinesCurrent = 0;
|
||||
}
|
||||
|
||||
EditorSettings editorSettings = myEditor.getSettings();
|
||||
@@ -272,7 +282,7 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener {
|
||||
LogicalPosition oldCaretPosition = myLogicalCaret;
|
||||
|
||||
setCurrentLogicalCaret(new LogicalPosition(
|
||||
line, column, softWrapLines, linesFromCurrentSoftWrap, softWrapColumns, pos.foldedLines, pos.foldingColumnDiff
|
||||
line, column, softWrapLinesBefore, softWrapLinesCurrent, softWrapColumns, pos.foldedLines, pos.foldingColumnDiff
|
||||
));
|
||||
|
||||
final int offset = myEditor.logicalPositionToOffset(myLogicalCaret);
|
||||
@@ -307,7 +317,7 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener {
|
||||
requestRepaint(oldInfo);
|
||||
|
||||
if (oldCaretPosition.column + oldCaretPosition.softWrapColumnDiff != myLogicalCaret.column + myLogicalCaret.softWrapColumnDiff
|
||||
|| oldCaretPosition.line + oldCaretPosition.softWrapLines != myLogicalCaret.line + myLogicalCaret.softWrapLines)
|
||||
|| oldCaretPosition.line + oldCaretPosition.softWrapLinesBeforeCurrentLogicalLine != myLogicalCaret.line + myLogicalCaret.softWrapLinesBeforeCurrentLogicalLine)
|
||||
{
|
||||
CaretEvent event = new CaretEvent(myEditor, oldCaretPosition, myLogicalCaret);
|
||||
for (CaretListener listener : myCaretListeners) {
|
||||
|
||||
@@ -844,7 +844,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
if (softWrap != null) {
|
||||
// There is a possible case that soft wrap contains more than one line feed inside and we need to start counting not
|
||||
// from its first line.
|
||||
int softWrapLinesToSkip = activeSoftWrapProcessed ? 0 : logicalPosition.linesFromActiveSoftWrap;
|
||||
int softWrapLinesToSkip = activeSoftWrapProcessed ? 0 : logicalPosition.softWrapLinesOnCurrentLogicalLine;
|
||||
CharSequence softWrapText = softWrap.getText();
|
||||
for (int i = 0; i < softWrapText.length(); i++) {
|
||||
c = softWrapText.charAt(i);
|
||||
@@ -1832,7 +1832,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
if (softWrap != null) {
|
||||
// There is a possible case that soft wrap contains more than one line feed inside and we need to start drawing not
|
||||
// from its first line.
|
||||
int softWrapLinesToSkip = activeSoftWrapProcessed ? 0 : logicalPosition.linesFromActiveSoftWrap;
|
||||
int softWrapLinesToSkip = activeSoftWrapProcessed ? 0 : logicalPosition.softWrapLinesOnCurrentLogicalLine;
|
||||
char[] softWrapChars = softWrap.getChars();
|
||||
|
||||
// We don't draw every soft wrap symbol one-by-one but whole visual line. Current variable holds index that points
|
||||
@@ -2379,7 +2379,11 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
|
||||
public Dimension getContentSize() {
|
||||
Dimension size = mySizeContainer.getContentSize();
|
||||
return new Dimension(size.width, size.height + mySettings.getAdditionalLinesCount() * getLineHeight());
|
||||
//TODO den move soft wrap-related logic to editor size container
|
||||
return new Dimension(
|
||||
size.width,
|
||||
size.height + (mySettings.getAdditionalLinesCount() + mySoftWrapModel.getSoftWrapsIntroducedLinesNumber()) * getLineHeight()
|
||||
);
|
||||
}
|
||||
|
||||
public JScrollPane getScrollPane() {
|
||||
@@ -2567,8 +2571,8 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
else if (visiblePos.column > softWrapAwareVisFoldEnd.column) {
|
||||
int columnToUse = softWrapAwareLogFoldEnd.column + visiblePos.column - softWrapAwareVisFoldEnd.column;
|
||||
return new LogicalPosition(
|
||||
softWrapAwareLogFoldEnd.line, columnToUse, softWrapAwareLogFoldEnd.softWrapLines,
|
||||
softWrapAwareLogFoldEnd.linesFromActiveSoftWrap, visiblePos.column - columnToUse - softWrapAwareLogFoldEnd.foldingColumnDiff,
|
||||
softWrapAwareLogFoldEnd.line, columnToUse, softWrapAwareLogFoldEnd.softWrapLinesBeforeCurrentLogicalLine,
|
||||
softWrapAwareLogFoldEnd.softWrapLinesOnCurrentLogicalLine, visiblePos.column - columnToUse - softWrapAwareLogFoldEnd.foldingColumnDiff,
|
||||
softWrapAwareLogFoldEnd.foldedLines, softWrapAwareLogFoldEnd.foldingColumnDiff
|
||||
);
|
||||
}
|
||||
@@ -2642,15 +2646,15 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
|
||||
int columnNumber = pos.column;
|
||||
int lineNumber = pos.line;
|
||||
int softWrapLines = pos.softWrapLines;
|
||||
int linesFromCurrentSoftWrap = pos.linesFromActiveSoftWrap;
|
||||
int softWrapLinesBeforeTargetLogicalLine = pos.softWrapLinesBeforeCurrentLogicalLine;
|
||||
int softWrapLinesOnTargetLogicalLine = pos.softWrapLinesOnCurrentLogicalLine;
|
||||
int softWrapColumns = pos.softWrapColumnDiff;
|
||||
|
||||
if (lineNumber < 0) {
|
||||
lineNumber = 0;
|
||||
columnNumber = 0;
|
||||
softWrapLines = 0;
|
||||
linesFromCurrentSoftWrap = 0;
|
||||
softWrapLinesBeforeTargetLogicalLine = 0;
|
||||
softWrapLinesOnTargetLogicalLine = 0;
|
||||
softWrapColumns = 0;
|
||||
}
|
||||
|
||||
@@ -2686,7 +2690,8 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
}
|
||||
}
|
||||
LogicalPosition pos1 = new LogicalPosition(
|
||||
lineNumber, columnNumber, softWrapLines, linesFromCurrentSoftWrap, softWrapColumns, pos.foldedLines, pos.foldingColumnDiff
|
||||
lineNumber, columnNumber, softWrapLinesBeforeTargetLogicalLine, softWrapLinesOnTargetLogicalLine, softWrapColumns,
|
||||
pos.foldedLines, pos.foldingColumnDiff
|
||||
);
|
||||
getCaretModel().moveToLogicalPosition(pos1);
|
||||
}
|
||||
|
||||
+171
-154
@@ -20,8 +20,10 @@ import com.intellij.openapi.editor.event.DocumentEvent;
|
||||
import com.intellij.openapi.editor.event.DocumentListener;
|
||||
import com.intellij.openapi.editor.event.VisibleAreaEvent;
|
||||
import com.intellij.openapi.editor.event.VisibleAreaListener;
|
||||
import com.intellij.openapi.editor.ex.EditorEx;
|
||||
import com.intellij.openapi.editor.ex.SoftWrapModelEx;
|
||||
import com.intellij.openapi.editor.ex.util.EditorUtil;
|
||||
import com.intellij.openapi.editor.impl.softwrap.SoftWrapsStorage;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.util.text.CharArrayUtil;
|
||||
import gnu.trove.TIntHashSet;
|
||||
@@ -33,7 +35,6 @@ import java.awt.*;
|
||||
import java.nio.CharBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -60,16 +61,12 @@ import java.util.List;
|
||||
*/
|
||||
public class SoftWrapModelImpl implements SoftWrapModelEx {
|
||||
|
||||
private static final Comparator<TextChange> SOFT_WRAPS_BY_OFFSET_COMPARATOR = new Comparator<TextChange>() {
|
||||
public int compare(TextChange c1, TextChange c2) {
|
||||
return c1.getStart() - c2.getEnd();
|
||||
}
|
||||
};
|
||||
|
||||
private final CharBuffer myCharBuffer = CharBuffer.allocate(1);
|
||||
|
||||
private final SoftWrapsStorage myStorage;
|
||||
|
||||
/**
|
||||
* Holds lines where soft wraps should be removed.
|
||||
* Holds logical lines where soft wraps should be removed.
|
||||
* <p/>
|
||||
* The general idea is to do the following:
|
||||
* <ul>
|
||||
@@ -79,11 +76,7 @@ public class SoftWrapModelImpl implements SoftWrapModelEx {
|
||||
*/
|
||||
private final TIntHashSet myDirtyLines = new TIntHashSet();
|
||||
|
||||
/** Holds registered soft wraps sorted by offsets in ascending order. */
|
||||
private final List<TextChange> myWraps = new ArrayList<TextChange>();
|
||||
private final List<TextChange> myWrapsView = Collections.unmodifiableList(myWraps);
|
||||
|
||||
private final EditorImpl myEditor;
|
||||
private final EditorEx myEditor;
|
||||
private int myRightEdgeLocation = -1;
|
||||
private boolean myInitialized;
|
||||
|
||||
@@ -99,8 +92,13 @@ public class SoftWrapModelImpl implements SoftWrapModelEx {
|
||||
/** Holds number of 'active' calls, i.e. number of methods calls of the current object within the current call stack. */
|
||||
private int myActive;
|
||||
|
||||
public SoftWrapModelImpl(@NotNull EditorImpl editor) {
|
||||
public SoftWrapModelImpl(@NotNull EditorEx editor) {
|
||||
this(editor, new SoftWrapsStorage());
|
||||
}
|
||||
|
||||
public SoftWrapModelImpl(@NotNull EditorEx editor, @NotNull SoftWrapsStorage storage) {
|
||||
myEditor = editor;
|
||||
myStorage = storage;
|
||||
}
|
||||
|
||||
public boolean isSoftWrappingEnabled() {
|
||||
@@ -112,8 +110,7 @@ public class SoftWrapModelImpl implements SoftWrapModelEx {
|
||||
if (myActive <= 0) {
|
||||
dropDataIfNecessary();
|
||||
}
|
||||
int i = getSoftWrapIndex(offset);
|
||||
return i >= 0 ? myWraps.get(i) : null;
|
||||
return myStorage.getSoftWrap(offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -123,14 +120,15 @@ public class SoftWrapModelImpl implements SoftWrapModelEx {
|
||||
int start = document.getLineStartOffset(documentLine);
|
||||
int end = document.getLineEndOffset(documentLine);
|
||||
|
||||
int i = getSoftWrapIndex(start);
|
||||
int i = myStorage.getSoftWrapIndex(start);
|
||||
if (i < 0) {
|
||||
i = -i - 1;
|
||||
}
|
||||
|
||||
List<TextChange> softWraps = myStorage.getSoftWraps();
|
||||
List<TextChange> result = null;
|
||||
for (; i < myWraps.size(); i++) {
|
||||
TextChange softWrap = myWraps.get(i);
|
||||
for (; i < softWraps.size(); i++) {
|
||||
TextChange softWrap = softWraps.get(i);
|
||||
if (softWrap.getStart() >= end) {
|
||||
break;
|
||||
}
|
||||
@@ -147,48 +145,12 @@ public class SoftWrapModelImpl implements SoftWrapModelEx {
|
||||
*/
|
||||
public int getSoftWrapsIntroducedLinesNumber() {
|
||||
int result = 0;
|
||||
for (TextChange myWrap : myWraps) {
|
||||
for (TextChange myWrap : myStorage.getSoftWraps()) {
|
||||
result += StringUtil.countNewLines(myWrap.getText());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to find index of the target soft wrap stored at {@link #myWraps} collection. <code>'Target'</code> soft wrap is the one
|
||||
* that starts at the given offset.
|
||||
*
|
||||
* @param offset target offset
|
||||
* @return index that conforms to {@link Collections#binarySearch(List, Object)} contract, i.e. non-negative returned
|
||||
* index points to soft wrap that starts at the given offset; <code>'-(negative value) - 1'</code> points
|
||||
* to position at {@link #myWraps} collection where soft wrap for the given index should be inserted
|
||||
*/
|
||||
private int getSoftWrapIndex(int offset) {
|
||||
TextChange searchKey = new TextChange("", offset);
|
||||
return Collections.binarySearch(myWraps, searchKey, SOFT_WRAPS_BY_OFFSET_COMPARATOR);
|
||||
}
|
||||
|
||||
private boolean hasSoftWrapAt(int offset) {
|
||||
return getSoftWrapIndex(offset) >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts given soft wrap to {@link #myWraps} collection at the given index.
|
||||
*
|
||||
* @param softWrap soft wrap to store
|
||||
* @return previous soft wrap object stored for the same offset if any; <code>null</code> otherwise
|
||||
*/
|
||||
@Nullable
|
||||
private TextChange storeSoftWrap(TextChange softWrap) {
|
||||
int i = Collections.binarySearch(myWraps, softWrap, SOFT_WRAPS_BY_OFFSET_COMPARATOR);
|
||||
if (i >= 0) {
|
||||
return myWraps.set(i, softWrap);
|
||||
}
|
||||
|
||||
i = -i - 1;
|
||||
myWraps.add(i, softWrap);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to answer if symbols of the given char array located at <code>[start; end)</code> interval should be soft wrapped,
|
||||
* i.e. represented on a next line.
|
||||
@@ -206,7 +168,7 @@ public class SoftWrapModelImpl implements SoftWrapModelEx {
|
||||
initIfNecessary();
|
||||
dropDataIfNecessary();
|
||||
|
||||
if (hasSoftWrapAt(start)) {
|
||||
if (myStorage.hasSoftWrapAt(start)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -241,17 +203,21 @@ public class SoftWrapModelImpl implements SoftWrapModelEx {
|
||||
dropDataIfNecessary();
|
||||
//TODO den implement indent calculation on formatting options
|
||||
result = containsOnlyWhiteSpaces(chars, start, end) ? new TextChange("\n", start) : new TextChange("\n ", start);
|
||||
storeSoftWrap(result);
|
||||
myStorage.storeSoftWrap(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<TextChange> getRegisteredSoftWraps() {
|
||||
return myWrapsView;
|
||||
return myStorage.getSoftWraps();
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops information about registered soft wraps if they are marked as <code>'dirty'</code>.
|
||||
* //TODO den add doc about editor repainting
|
||||
* <p/>
|
||||
* The general idea of current {@link SoftWrapModel} implementation is that new soft wraps are created as necessary during target
|
||||
* document painting. Hence, we drop registered soft wraps during this method processing only
|
||||
* {@link EditorImpl#isUnderRepainting() during editor repainting} (there is a possible case that this method is called during
|
||||
* other processing like gutter repainting).
|
||||
*
|
||||
* @see #myDataIsDirty
|
||||
*/
|
||||
@@ -259,38 +225,27 @@ public class SoftWrapModelImpl implements SoftWrapModelEx {
|
||||
if (!myEditor.isUnderRepainting()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (myDataIsDirty) {
|
||||
myDataIsDirty = false;
|
||||
myDirtyLines.clear();
|
||||
myStorage.removeAll();
|
||||
return;
|
||||
}
|
||||
|
||||
Document document = myEditor.getDocument();
|
||||
for (TIntIterator it = myDirtyLines.iterator(); it.hasNext();) {
|
||||
int line = it.next();
|
||||
int start = document.getLineStartOffset(line);
|
||||
int end = document.getLineEndOffset(line);
|
||||
|
||||
int startIndex = getSoftWrapIndex(start);
|
||||
if (startIndex < 0) {
|
||||
startIndex = -startIndex - 1;
|
||||
}
|
||||
int endIndex = startIndex;
|
||||
for (; endIndex < myWraps.size(); endIndex++) {
|
||||
TextChange softWrap = myWraps.get(endIndex);
|
||||
if (softWrap.getStart() >= end) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
myWraps.subList(startIndex, endIndex).clear();
|
||||
myStorage.removeInRange(start, end);
|
||||
}
|
||||
myDirtyLines.clear();
|
||||
|
||||
if (!myDataIsDirty) {
|
||||
return;
|
||||
|
||||
}
|
||||
myDataIsDirty = false;
|
||||
myWraps.clear();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public LogicalPosition adjustLogicalPosition(@NotNull LogicalPosition defaultLogical, @NotNull VisualPosition visual) {
|
||||
if (myActive > 0 || !isSoftWrappingEnabled() || myWraps.isEmpty() || myEditor.getDocument().getTextLength() <= 0) {
|
||||
if (myActive > 0 || !isSoftWrappingEnabled() || myStorage.isEmpty() || myEditor.getDocument().getTextLength() <= 0) {
|
||||
return defaultLogical;
|
||||
}
|
||||
|
||||
@@ -310,54 +265,69 @@ public class SoftWrapModelImpl implements SoftWrapModelEx {
|
||||
}
|
||||
|
||||
private LogicalPosition doAdjustLogicalPosition(LogicalPosition defaultLogical, VisualPosition visual) {
|
||||
DocumentImpl document = (DocumentImpl)myEditor.getDocument();
|
||||
Document document = myEditor.getDocument();
|
||||
int maxOffset = document.getLineEndOffset(Math.min(defaultLogical.line, document.getLineCount() - 1));
|
||||
|
||||
// This index points to registered soft wrap that is guaranteed to be located after the target visual line.
|
||||
int endIndex = getSoftWrapIndex(maxOffset + 1);
|
||||
int endIndex = myStorage.getSoftWrapIndex(maxOffset + 1);
|
||||
if (endIndex < 0) {
|
||||
endIndex = -endIndex - 1;
|
||||
}
|
||||
|
||||
int softWrapIntroducedLineFeeds = 0;
|
||||
int softWrapLinesBeforeCurrentLogicalLine = 0;
|
||||
int softWrapLinesOnCurrentLogicalLine = 0;
|
||||
int lastSoftWrapLogicalLine = -1;
|
||||
|
||||
FoldingModel foldingModel = myEditor.getFoldingModel();
|
||||
int i = 0;
|
||||
int max = Math.min(myWraps.size(), endIndex);
|
||||
List<TextChange> softWraps = myStorage.getSoftWraps();
|
||||
int max = Math.min(softWraps.size(), endIndex);
|
||||
for (; i < max; i++) {
|
||||
TextChange softWrap = myWraps.get(i);
|
||||
TextChange softWrap = softWraps.get(i);
|
||||
if (foldingModel.isOffsetCollapsed(softWrap.getStart())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int currentSoftWrapLineFeeds = StringUtil.countNewLines(softWrap.getText());
|
||||
int softWrapLine = document.getLineNumber(softWrap.getStart());
|
||||
int visualLineBeforeSoftWrapAppliance
|
||||
= myEditor.logicalToVisualPosition(new LogicalPosition(softWrapLine, 0)).line + softWrapIntroducedLineFeeds;
|
||||
int visualLineBeforeSoftWrapAppliance = myEditor.logicalToVisualPosition(new LogicalPosition(softWrapLine, 0)).line
|
||||
+ softWrapLinesBeforeCurrentLogicalLine + softWrapLinesOnCurrentLogicalLine;
|
||||
if (visualLineBeforeSoftWrapAppliance > visual.line) {
|
||||
int logicalLine = defaultLogical.line - softWrapIntroducedLineFeeds;
|
||||
LogicalPosition foldingUnawarePosition
|
||||
= new LogicalPosition(logicalLine, defaultLogical.column, softWrapIntroducedLineFeeds, 0, 0, 0, 0);
|
||||
softWrapLinesBeforeCurrentLogicalLine += softWrapLinesOnCurrentLogicalLine;
|
||||
int logicalLine = defaultLogical.line - softWrapLinesBeforeCurrentLogicalLine;
|
||||
LogicalPosition foldingUnawarePosition = new LogicalPosition(
|
||||
logicalLine, defaultLogical.column, softWrapLinesBeforeCurrentLogicalLine, 0, 0, 0, 0
|
||||
);
|
||||
return adjustFoldingData(foldingModel, foldingUnawarePosition);
|
||||
}
|
||||
|
||||
if (lastSoftWrapLogicalLine >= 0 && lastSoftWrapLogicalLine != softWrapLine) {
|
||||
softWrapLinesBeforeCurrentLogicalLine += softWrapLinesOnCurrentLogicalLine;
|
||||
softWrapLinesOnCurrentLogicalLine = 0;
|
||||
}
|
||||
lastSoftWrapLogicalLine = softWrapLine;
|
||||
|
||||
int visualLineAfterSoftWrapAppliance = visualLineBeforeSoftWrapAppliance + currentSoftWrapLineFeeds;
|
||||
if (visualLineAfterSoftWrapAppliance < visual.line) {
|
||||
softWrapIntroducedLineFeeds += currentSoftWrapLineFeeds;
|
||||
softWrapLinesOnCurrentLogicalLine += currentSoftWrapLineFeeds;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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, softWrapIntroducedLineFeeds, visualLineBeforeSoftWrapAppliance, foldingModel);
|
||||
Context context = new Context(
|
||||
defaultLogical, visual, softWrapLinesBeforeCurrentLogicalLine, softWrapLinesOnCurrentLogicalLine,
|
||||
visualLineBeforeSoftWrapAppliance, foldingModel
|
||||
);
|
||||
int startLineOffset = document.getLineStartOffset(softWrapLine);
|
||||
int endLineOffset = document.getLineEndOffset(softWrapLine);
|
||||
CharSequence documentText = document.getCharsNoThreadCheck();
|
||||
CharSequence documentText = document.getCharsSequence();
|
||||
for (int j = startLineOffset; j < endLineOffset; j++) {
|
||||
|
||||
// Process soft wrap at the current offset if any.
|
||||
if (j == softWrap.getStart()) {
|
||||
CharSequence softWrapText = softWrap.getText();
|
||||
TextChange softWrapToProcess = myStorage.getSoftWrap(j);
|
||||
if (softWrapToProcess != null && j >= softWrap.getStart()) {
|
||||
CharSequence softWrapText = softWrapToProcess.getText();
|
||||
for (int k = 0; k < softWrapText.length(); k++) {
|
||||
LogicalPosition result = context.onSoftWrapSymbol(softWrapText.charAt(k));
|
||||
if (result != null) {
|
||||
@@ -374,19 +344,23 @@ public class SoftWrapModelImpl implements SoftWrapModelEx {
|
||||
}
|
||||
|
||||
// If we are here that means that target visual position is located at virtual space after the line end.
|
||||
int logicalLine = defaultLogical.line - softWrapIntroducedLineFeeds - context.lineFeedsFromCurrentSoftWrap;
|
||||
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, softWrapIntroducedLineFeeds + context.lineFeedsFromCurrentSoftWrap,
|
||||
context.lineFeedsFromCurrentSoftWrap, softWrapColumnDiff, 0, 0
|
||||
logicalLine, logicalColumn, softWrapLinesBeforeCurrentLogicalLine,
|
||||
context.softWrapLinesOnCurrentLineBeforeTargetSoftWrap + context.targetSoftWrapLines, softWrapColumnDiff, 0, 0
|
||||
);
|
||||
return adjustFoldingData(foldingModel, foldingUnawarePosition);
|
||||
}
|
||||
|
||||
// If we are here that means that there is no soft wrap on a logical line that corresponds to the target visual line.
|
||||
int logicalLine = defaultLogical.line - softWrapIntroducedLineFeeds;
|
||||
LogicalPosition foldingUnaware = new LogicalPosition(logicalLine, defaultLogical.column, softWrapIntroducedLineFeeds, 0, 0, 0, 0);
|
||||
softWrapLinesBeforeCurrentLogicalLine += softWrapLinesOnCurrentLogicalLine;
|
||||
int logicalLine = defaultLogical.line - softWrapLinesBeforeCurrentLogicalLine;
|
||||
LogicalPosition foldingUnaware = new LogicalPosition(
|
||||
logicalLine, defaultLogical.column, softWrapLinesBeforeCurrentLogicalLine, 0, 0, 0, 0
|
||||
);
|
||||
return adjustFoldingData(foldingModel, foldingUnaware);
|
||||
}
|
||||
|
||||
@@ -402,8 +376,9 @@ public class SoftWrapModelImpl implements SoftWrapModelEx {
|
||||
int foldedLines = 0;
|
||||
int foldColumnDiff = 0;
|
||||
int softWrapColumnDiff = position.softWrapColumnDiff;
|
||||
DocumentImpl document = (DocumentImpl)myEditor.getDocument();
|
||||
CharSequence text = document.getCharsNoThreadCheck();
|
||||
Document document = myEditor.getDocument();
|
||||
CharSequence text = document.getCharsSequence();
|
||||
int targetLine = document.getLineNumber(offset);
|
||||
for (FoldRegion foldRegion : foldingModel.getAllFoldRegions()) {
|
||||
if (foldRegion.getStartOffset() >= offset) {
|
||||
break;
|
||||
@@ -415,7 +390,7 @@ public class SoftWrapModelImpl implements SoftWrapModelEx {
|
||||
|
||||
int foldingStartLine = document.getLineNumber(foldRegion.getStartOffset());
|
||||
int foldingEndLine = document.getLineNumber(foldRegion.getEndOffset());
|
||||
foldedLines += foldingEndLine - foldingStartLine;
|
||||
foldedLines += Math.min(targetLine, foldingEndLine) - foldingStartLine;
|
||||
|
||||
// Process situation when target offset is located inside the folded region.
|
||||
if (offset >= foldRegion.getStartOffset() && offset < foldRegion.getEndOffset()) {
|
||||
@@ -447,8 +422,9 @@ public class SoftWrapModelImpl implements SoftWrapModelEx {
|
||||
else {
|
||||
int endOffsetOfLineWithFoldingEnd = document.getLineEndOffset(foldingEndLine);
|
||||
int columnsBetweenFoldingEndAndOffset = toVisualColumnSymbolsNumber(text, foldRegion.getEndOffset(), endOffsetOfLineWithFoldingEnd);
|
||||
if (position.column > endOffsetOfLineWithFoldingEnd) {
|
||||
columnsBetweenFoldingEndAndOffset += position.column - endOffsetOfLineWithFoldingEnd;
|
||||
LogicalPosition foldingEndLineEndPosition = myEditor.offsetToLogicalPosition(endOffsetOfLineWithFoldingEnd);
|
||||
if (position.column > foldingEndLineEndPosition.column) {
|
||||
columnsBetweenFoldingEndAndOffset += position.column - foldingEndLineEndPosition.column;
|
||||
}
|
||||
int visualFoldingStartColumn = calculateVisualFoldingStartColumn(foldRegion);
|
||||
int foldingPlaceholderWidth = toVisualColumnSymbolsNumber(foldRegion.getPlaceholderText());
|
||||
@@ -459,25 +435,26 @@ public class SoftWrapModelImpl implements SoftWrapModelEx {
|
||||
}
|
||||
|
||||
return new LogicalPosition(
|
||||
position.line, position.column, position.softWrapLines, position.linesFromActiveSoftWrap,
|
||||
position.line, position.column, position.softWrapLinesBeforeCurrentLogicalLine, position.softWrapLinesOnCurrentLogicalLine,
|
||||
softWrapColumnDiff, foldedLines, foldColumnDiff
|
||||
);
|
||||
}
|
||||
|
||||
private int calculateVisualFoldingStartColumn(FoldRegion region) {
|
||||
DocumentImpl document = (DocumentImpl)myEditor.getDocument();
|
||||
Document document = myEditor.getDocument();
|
||||
int foldingStartOffset = region.getStartOffset();
|
||||
int logicalLine = document.getLineNumber(foldingStartOffset);
|
||||
int logicalLineStartOffset = document.getLineStartOffset(logicalLine);
|
||||
|
||||
int softWrapIndex = getSoftWrapIndex(logicalLineStartOffset);
|
||||
int softWrapIndex = myStorage.getSoftWrapIndex(logicalLineStartOffset);
|
||||
if (softWrapIndex < 0) {
|
||||
softWrapIndex = -softWrapIndex - 1;
|
||||
}
|
||||
|
||||
List<TextChange> softWraps = myStorage.getSoftWraps();
|
||||
int startOffsetOfVisualLineWithFoldingStart = logicalLineStartOffset;
|
||||
for (; softWrapIndex < myWraps.size(); softWrapIndex++) {
|
||||
TextChange softWrap = myWraps.get(softWrapIndex);
|
||||
for (; softWrapIndex < softWraps.size(); softWrapIndex++) {
|
||||
TextChange softWrap = softWraps.get(softWrapIndex);
|
||||
if (softWrap.getStart() >= foldingStartOffset) {
|
||||
break;
|
||||
}
|
||||
@@ -486,7 +463,7 @@ public class SoftWrapModelImpl implements SoftWrapModelEx {
|
||||
}
|
||||
|
||||
assert startOffsetOfVisualLineWithFoldingStart <= foldingStartOffset;
|
||||
return toVisualColumnSymbolsNumber(document.getCharsNoThreadCheck(), startOffsetOfVisualLineWithFoldingStart, foldingStartOffset);
|
||||
return toVisualColumnSymbolsNumber(document.getCharsSequence(), startOffsetOfVisualLineWithFoldingStart, foldingStartOffset);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -505,39 +482,40 @@ public class SoftWrapModelImpl implements SoftWrapModelEx {
|
||||
|
||||
@SuppressWarnings({"AssignmentToForLoopParameter"})
|
||||
private LogicalPosition offsetToLogicalPosition(int offset) {
|
||||
DocumentImpl document = (DocumentImpl)myEditor.getDocument();
|
||||
CharSequence chars = document.getCharsNoThreadCheck();
|
||||
Document document = myEditor.getDocument();
|
||||
CharSequence chars = document.getCharsSequence();
|
||||
|
||||
int targetLine = document.getLineNumber(offset);
|
||||
int targetLineStartOffset = document.getLineStartOffset(targetLine);
|
||||
|
||||
updateVisibleAreaIfNecessary();
|
||||
|
||||
int softWrapIntroducedLines = 0;
|
||||
int linesFromCurrentSoftWrap = 0;
|
||||
int softWrapIntroducedLinesBefore = 0;
|
||||
int softWrapsOnCurrentLogicalLine = 0;
|
||||
int symbolsOnCurrentLogicalLine = 0;
|
||||
int symbolsOnCurrentVisibleLine = 0;
|
||||
List<TextChange> softWraps = myStorage.getSoftWraps();
|
||||
|
||||
// Retrieve information about logical position that is soft-wraps unaware.
|
||||
LogicalPosition rawLineStartLogicalPosition = myEditor.offsetToLogicalPosition(targetLineStartOffset, false);
|
||||
LogicalPosition rawLineStartLogicalPosition = myEditor.offsetToLogicalPosition(targetLineStartOffset);
|
||||
|
||||
// Calculate number of soft wrap-introduced lines before the line that holds target offset.
|
||||
int index = getSoftWrapIndex(targetLineStartOffset);
|
||||
int index = myStorage.getSoftWrapIndex(targetLineStartOffset);
|
||||
if (index < 0) {
|
||||
index = -index - 1;
|
||||
}
|
||||
int max = Math.min(index, myWraps.size());
|
||||
int max = Math.min(index, softWraps.size());
|
||||
for (int j = 0; j < max; j++) {
|
||||
softWrapIntroducedLines += StringUtil.countNewLines(myWraps.get(j).getText());
|
||||
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 >= myWraps.size() || myWraps.get(max).getStart() > offset) {
|
||||
if (max >= softWraps.size() || softWraps.get(max).getStart() > offset) {
|
||||
int column = toVisualColumnSymbolsNumber(chars, targetLineStartOffset, offset);
|
||||
LogicalPosition foldingUnawarePosition = new LogicalPosition(
|
||||
rawLineStartLogicalPosition.line, column, softWrapIntroducedLines, 0, 0, 0, 0
|
||||
rawLineStartLogicalPosition.line, column, softWrapIntroducedLinesBefore, 0, 0, 0, 0
|
||||
);
|
||||
return adjustFoldingData(foldingModel, foldingUnawarePosition);
|
||||
}
|
||||
@@ -563,8 +541,7 @@ public class SoftWrapModelImpl implements SoftWrapModelEx {
|
||||
CharSequence softWrapText = softWrap.getText();
|
||||
for (int j = 0; j < softWrapText.length(); j++) {
|
||||
if (softWrapText.charAt(j) == '\n') {
|
||||
softWrapIntroducedLines++;
|
||||
linesFromCurrentSoftWrap++;
|
||||
softWrapsOnCurrentLogicalLine++;
|
||||
symbolsOnCurrentVisibleLine = 0;
|
||||
}
|
||||
else {
|
||||
@@ -584,7 +561,7 @@ public class SoftWrapModelImpl implements SoftWrapModelEx {
|
||||
}
|
||||
|
||||
LogicalPosition foldingUnawarePosition = new LogicalPosition(
|
||||
rawLineStartLogicalPosition.line, symbolsOnCurrentLogicalLine, softWrapIntroducedLines, linesFromCurrentSoftWrap,
|
||||
rawLineStartLogicalPosition.line, symbolsOnCurrentLogicalLine, softWrapIntroducedLinesBefore, softWrapsOnCurrentLogicalLine,
|
||||
symbolsOnCurrentVisibleLine - symbolsOnCurrentLogicalLine, 0, 0
|
||||
);
|
||||
return adjustFoldingData(foldingModel, foldingUnawarePosition);
|
||||
@@ -592,7 +569,7 @@ public class SoftWrapModelImpl implements SoftWrapModelEx {
|
||||
|
||||
@NotNull
|
||||
public VisualPosition adjustVisualPosition(@NotNull LogicalPosition logical, @NotNull VisualPosition defaultVisual) {
|
||||
if (myActive > 0 || !isSoftWrappingEnabled() || myWraps.isEmpty()) {
|
||||
if (myActive > 0 || !isSoftWrappingEnabled() || myStorage.isEmpty()) {
|
||||
return defaultVisual;
|
||||
}
|
||||
|
||||
@@ -613,16 +590,18 @@ public class SoftWrapModelImpl implements SoftWrapModelEx {
|
||||
|
||||
@NotNull
|
||||
private VisualPosition doAdjustVisualPosition(LogicalPosition logical, VisualPosition visual) {
|
||||
List<TextChange> softWraps = myStorage.getSoftWraps();
|
||||
|
||||
// Check if there are registered soft wraps before the target logical position.
|
||||
int maxOffset = myEditor.logicalPositionToOffset(logical);
|
||||
int endIndex = getSoftWrapIndex(maxOffset);
|
||||
int endIndex = myStorage.getSoftWrapIndex(maxOffset);
|
||||
if (endIndex < 0) {
|
||||
endIndex = -endIndex - 2; // We subtract '2' instead of '1' here in order to point to offset of the first soft wrap the
|
||||
// is located before the given logical position.
|
||||
}
|
||||
|
||||
// Return eagerly if no soft wraps are registered before the target offset.
|
||||
if (endIndex < 0 || endIndex >= myWraps.size()) {
|
||||
if (endIndex < 0 || endIndex >= softWraps.size()) {
|
||||
return visual;
|
||||
}
|
||||
|
||||
@@ -632,7 +611,7 @@ public class SoftWrapModelImpl implements SoftWrapModelEx {
|
||||
FoldingModel foldingModel = myEditor.getFoldingModel();
|
||||
int targetLogicalLineStartOffset = myEditor.logicalPositionToOffset(new LogicalPosition(logical.line, 0));
|
||||
for (int i = endIndex; i >= 0; i--) {
|
||||
TextChange softWrap = myWraps.get(i);
|
||||
TextChange softWrap = softWraps.get(i);
|
||||
if (softWrap == null) {
|
||||
assert false;
|
||||
continue;
|
||||
@@ -664,15 +643,41 @@ public class SoftWrapModelImpl implements SoftWrapModelEx {
|
||||
return new VisualPosition(visual.line + lineDiff, columnToUse);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInsideSoftWrap(@NotNull VisualPosition visual) {
|
||||
LogicalPosition logical = myEditor.visualToLogicalPosition(visual);
|
||||
int offset = myEditor.logicalPositionToOffset(logical);
|
||||
if (offset <= 0) {
|
||||
// Never expect to be here, just a defensive programming.
|
||||
return false;
|
||||
}
|
||||
|
||||
TextChange softWrap = getSoftWrap(offset);
|
||||
if (softWrap == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// We consider visual positions that point after the last symbol before soft wrap and the first symbol after soft wrap to not
|
||||
// belong to soft wrap-introduced virtual space.
|
||||
VisualPosition visualAfterSoftWrap = myEditor.offsetToVisualPosition(offset);
|
||||
if (visualAfterSoftWrap.equals(visual)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
VisualPosition visualBeforeSoftWrap = myEditor.offsetToVisualPosition(offset - 1);
|
||||
return visual.line > visualBeforeSoftWrap.line || visual.column > visualBeforeSoftWrap.column + 1;
|
||||
}
|
||||
|
||||
public void beforeDocumentChange(@NotNull VisualPosition visualPosition) {
|
||||
List<TextChange> softWraps = myStorage.getSoftWraps();
|
||||
LogicalPosition logicalPosition = myEditor.visualToLogicalPosition(visualPosition);
|
||||
int offset = myEditor.logicalPositionToOffset(logicalPosition);
|
||||
int i = getSoftWrapIndex(offset);
|
||||
if (i < 0 || i >= myWraps.size()) {
|
||||
int i = myStorage.getSoftWrapIndex(offset);
|
||||
if (i < 0 || i >= softWraps.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
TextChange softWrap = myWraps.get(i);
|
||||
TextChange softWrap = softWraps.get(i);
|
||||
|
||||
VisualPosition visualCaretPosition = myEditor.getCaretModel().getVisualPosition();
|
||||
|
||||
@@ -684,7 +689,7 @@ public class SoftWrapModelImpl implements SoftWrapModelEx {
|
||||
|
||||
// Restore caret position.
|
||||
myEditor.getCaretModel().moveToVisualPosition(visualCaretPosition);
|
||||
myWraps.remove(i);
|
||||
myStorage.removeByIndex(i);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -693,14 +698,15 @@ public class SoftWrapModelImpl implements SoftWrapModelEx {
|
||||
* @param change change introduced to the document
|
||||
*/
|
||||
private void updateRegisteredSoftWraps(TextChange change) {
|
||||
int softWrapIndex = getSoftWrapIndex(change.getStart());
|
||||
List<TextChange> softWraps = myStorage.getSoftWraps();
|
||||
int softWrapIndex = myStorage.getSoftWrapIndex(change.getStart());
|
||||
if (softWrapIndex < 0) {
|
||||
softWrapIndex = -softWrapIndex - 1;
|
||||
}
|
||||
|
||||
Document document = myEditor.getDocument();
|
||||
|
||||
if (softWrapIndex >= myWraps.size() || document.getTextLength() <= 0) {
|
||||
if (softWrapIndex >= softWraps.size() || document.getTextLength() <= 0) {
|
||||
return;
|
||||
}
|
||||
int firstChangedLine = document.getLineNumber(change.getStart());
|
||||
@@ -710,13 +716,14 @@ public class SoftWrapModelImpl implements SoftWrapModelEx {
|
||||
}
|
||||
|
||||
// Collect soft wraps which offsets should be modified.
|
||||
List<TextChange> modified = myWraps.subList(softWrapIndex, myWraps.size());
|
||||
List<TextChange> toModify = new ArrayList<TextChange>(modified);
|
||||
modified.clear();
|
||||
List<TextChange> toModify = new ArrayList<TextChange>(softWraps.subList(softWrapIndex, softWraps.size()));
|
||||
|
||||
// Remove soft wraps that should be modified.
|
||||
myStorage.removeInRange(softWrapIndex, softWraps.size());
|
||||
|
||||
// Add modified soft wraps.
|
||||
for (TextChange softWrap : toModify) {
|
||||
myWraps.add(softWrap.advance(change.getDiff()));
|
||||
softWraps.add(softWrap.advance(change.getDiff()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -802,21 +809,24 @@ public class SoftWrapModelImpl implements SoftWrapModelEx {
|
||||
|
||||
private class Context {
|
||||
|
||||
public final FoldingModel foldingModel;
|
||||
public final FoldingModel foldingModel;
|
||||
public final LogicalPosition softWrapUnawareLogicalPosition;
|
||||
public final VisualPosition targetVisualPosition;
|
||||
public final int softWrapIntroducedLines;
|
||||
public final int visualLineBeforeSoftWrapAppliance;
|
||||
public int lineFeedsFromCurrentSoftWrap;
|
||||
public final VisualPosition targetVisualPosition;
|
||||
public final int softWrapLinesBefore;
|
||||
public final int visualLineBeforeSoftWrapAppliance;
|
||||
public final int softWrapLinesOnCurrentLineBeforeTargetSoftWrap;
|
||||
|
||||
public int targetSoftWrapLines;
|
||||
public int symbolsOnCurrentLogicalLine;
|
||||
public int symbolsOnCurrentVisualLine;
|
||||
|
||||
Context(LogicalPosition softWrapUnawareLogicalPosition, VisualPosition targetVisualPosition, int softWrapIntroducedLines,
|
||||
int visualLineBeforeSoftWrapAppliance, FoldingModel foldingModel)
|
||||
Context(LogicalPosition softWrapUnawareLogicalPosition, VisualPosition targetVisualPosition, int softWrapLinesBefore,
|
||||
int softWrapLinesOnCurrentLineBeforeTargetSoftWrap, int visualLineBeforeSoftWrapAppliance, FoldingModel foldingModel)
|
||||
{
|
||||
this.softWrapUnawareLogicalPosition = softWrapUnawareLogicalPosition;
|
||||
this.targetVisualPosition = targetVisualPosition;
|
||||
this.softWrapIntroducedLines = softWrapIntroducedLines;
|
||||
this.softWrapLinesBefore = softWrapLinesBefore;
|
||||
this.softWrapLinesOnCurrentLineBeforeTargetSoftWrap = softWrapLinesOnCurrentLineBeforeTargetSoftWrap;
|
||||
this.visualLineBeforeSoftWrapAppliance = visualLineBeforeSoftWrapAppliance;
|
||||
this.foldingModel = foldingModel;
|
||||
}
|
||||
@@ -832,19 +842,20 @@ public class SoftWrapModelImpl implements SoftWrapModelEx {
|
||||
public LogicalPosition onSoftWrapSymbol(char c) {
|
||||
// Process line feed inside soft wrap.
|
||||
if (c == '\n') {
|
||||
if (targetVisualPosition.line == visualLineBeforeSoftWrapAppliance + lineFeedsFromCurrentSoftWrap) {
|
||||
if (targetVisualPosition.line == visualLineBeforeSoftWrapAppliance + targetSoftWrapLines) {
|
||||
return build(targetVisualPosition.column - symbolsOnCurrentLogicalLine);
|
||||
}
|
||||
else {
|
||||
lineFeedsFromCurrentSoftWrap++;
|
||||
targetSoftWrapLines++;
|
||||
symbolsOnCurrentVisualLine = 0;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Just update information about tracked symbols number if current visual line is too low.
|
||||
if (targetVisualPosition.line > visualLineBeforeSoftWrapAppliance + lineFeedsFromCurrentSoftWrap) {
|
||||
if (targetVisualPosition.line > visualLineBeforeSoftWrapAppliance + targetSoftWrapLines) {
|
||||
symbolsOnCurrentVisualLine += toVisualColumnSymbolsNumber(c);
|
||||
return null;
|
||||
}
|
||||
|
||||
// There is a possible case that, for example, target visual column is zero and it points to the soft-wrapped line,
|
||||
@@ -880,7 +891,7 @@ public class SoftWrapModelImpl implements SoftWrapModelEx {
|
||||
}
|
||||
|
||||
// Just update information about tracked symbols number if current visual line is too low.
|
||||
if (targetVisualPosition.line > visualLineBeforeSoftWrapAppliance + lineFeedsFromCurrentSoftWrap) {
|
||||
if (targetVisualPosition.line > visualLineBeforeSoftWrapAppliance + targetSoftWrapLines) {
|
||||
symbolsOnCurrentVisualLine += toVisualColumnSymbolsNumber(c);
|
||||
symbolsOnCurrentLogicalLine++;
|
||||
return null;
|
||||
@@ -909,10 +920,16 @@ public class SoftWrapModelImpl implements SoftWrapModelEx {
|
||||
}
|
||||
|
||||
private LogicalPosition build(int softWrapColumnDiff) {
|
||||
int logicalLine = softWrapUnawareLogicalPosition.line - softWrapIntroducedLines - lineFeedsFromCurrentSoftWrap;
|
||||
int logicalLine = softWrapUnawareLogicalPosition.line - softWrapLinesBefore
|
||||
- softWrapLinesOnCurrentLineBeforeTargetSoftWrap - targetSoftWrapLines;
|
||||
LogicalPosition foldingUnawareResult = new LogicalPosition(
|
||||
logicalLine, symbolsOnCurrentLogicalLine, softWrapIntroducedLines + lineFeedsFromCurrentSoftWrap, lineFeedsFromCurrentSoftWrap,
|
||||
softWrapColumnDiff, 0, 0
|
||||
logicalLine,
|
||||
symbolsOnCurrentLogicalLine,
|
||||
softWrapLinesBefore,
|
||||
softWrapLinesOnCurrentLineBeforeTargetSoftWrap + targetSoftWrapLines,
|
||||
softWrapColumnDiff,
|
||||
0,
|
||||
0
|
||||
);
|
||||
return adjustFoldingData(foldingModel, foldingUnawareResult);
|
||||
}
|
||||
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright 2000-2010 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.softwrap;
|
||||
|
||||
import com.intellij.openapi.editor.TextChange;
|
||||
import gnu.trove.TIntHashSet;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Holds registered soft wraps and provides monitoring and management facilities for them.
|
||||
* <p/>
|
||||
* Not thread-safe.
|
||||
*
|
||||
* @author Denis Zhdanov
|
||||
* @since Jun 29, 2010 3:04:20 PM
|
||||
*/
|
||||
public class SoftWrapsStorage {
|
||||
|
||||
private static final Comparator<TextChange> SOFT_WRAPS_BY_OFFSET_COMPARATOR = new Comparator<TextChange>() {
|
||||
public int compare(TextChange c1, TextChange c2) {
|
||||
return c1.getStart() - c2.getEnd();
|
||||
}
|
||||
};
|
||||
|
||||
private final List<TextChange> myWraps = new ArrayList<TextChange>();
|
||||
private final List<TextChange> myWrapsView = Collections.unmodifiableList(myWraps);
|
||||
|
||||
/**
|
||||
* @return <code>true</code> if there is at least one soft wrap registered at the current storage; <code>false</code> otherwise
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return myWraps.isEmpty();
|
||||
}
|
||||
|
||||
public boolean hasSoftWrapAt(int offset) {
|
||||
return getSoftWrapIndex(offset) >= 0;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public TextChange getSoftWrap(int offset) {
|
||||
int i = getSoftWrapIndex(offset);
|
||||
return i >= 0 ? myWraps.get(i) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return view for registered soft wraps sorted by offset in ascending order if any; empty collection otherwise
|
||||
*/
|
||||
@NotNull
|
||||
public List<TextChange> getSoftWraps() {
|
||||
return myWrapsView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to find index of the target soft wrap stored at {@link #myWraps} collection. <code>'Target'</code> soft wrap is the one
|
||||
* that starts at the given offset.
|
||||
*
|
||||
* @param offset target offset
|
||||
* @return index that conforms to {@link Collections#binarySearch(List, Object)} contract, i.e. non-negative returned
|
||||
* index points to soft wrap that starts at the given offset; <code>'-(negative value) - 1'</code> points
|
||||
* to position at {@link #myWraps} collection where soft wrap for the given index should be inserted
|
||||
*/
|
||||
public int getSoftWrapIndex(int offset) {
|
||||
TextChange searchKey = new TextChange("", offset);
|
||||
return Collections.binarySearch(myWraps, searchKey, SOFT_WRAPS_BY_OFFSET_COMPARATOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts given soft wrap to {@link #myWraps} collection at the given index.
|
||||
*
|
||||
* @param softWrap soft wrap to store
|
||||
* @return previous soft wrap object stored for the same offset if any; <code>null</code> otherwise
|
||||
*/
|
||||
@Nullable
|
||||
public TextChange storeSoftWrap(TextChange softWrap) {
|
||||
int i = Collections.binarySearch(myWraps, softWrap, SOFT_WRAPS_BY_OFFSET_COMPARATOR);
|
||||
if (i >= 0) {
|
||||
return myWraps.set(i, softWrap);
|
||||
}
|
||||
|
||||
i = -i - 1;
|
||||
myWraps.add(i, softWrap);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asks current storage to remove soft wrap registered for the current index if any (soft wraps are stored at collection
|
||||
* ordered by soft wrap start offsets).
|
||||
*
|
||||
* @param index target soft wrap index
|
||||
* @return removed soft wrap if the one was found for the given index; <code>null</code> otherwise
|
||||
*/
|
||||
@Nullable
|
||||
public TextChange removeByIndex(int index) {
|
||||
if (index < 0 || index >= myWraps.size()) {
|
||||
return null;
|
||||
}
|
||||
return myWraps.remove(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all soft wraps with offsets at [start; end] range registered at the current storage if any.
|
||||
*
|
||||
* @param start start range offset (inclusive)
|
||||
* @param end end range offset (exclusive)
|
||||
*/
|
||||
public void removeInRange(int start, int end) {
|
||||
int startIndex = getSoftWrapIndex(start);
|
||||
if (startIndex < 0) {
|
||||
startIndex = -startIndex - 1;
|
||||
}
|
||||
int endIndex = startIndex;
|
||||
for (; endIndex < myWraps.size(); endIndex++) {
|
||||
TextChange softWrap = myWraps.get(endIndex);
|
||||
if (softWrap.getStart() >= end) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
myWraps.subList(startIndex, endIndex).clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all soft wraps registered at the current storage.
|
||||
*/
|
||||
public void removeAll() {
|
||||
myWraps.clear();
|
||||
}
|
||||
}
|
||||
+5
@@ -50,4 +50,9 @@ public class TextComponentSoftWrapModel implements SoftWrapModel {
|
||||
@Override
|
||||
public void beforeDocumentChange(@NotNull VisualPosition position) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInsideSoftWrap(@NotNull VisualPosition position) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
+620
@@ -0,0 +1,620 @@
|
||||
/*
|
||||
* Copyright 2000-2010 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.mock.MockFoldRegion;
|
||||
import com.intellij.openapi.editor.*;
|
||||
import com.intellij.openapi.editor.ex.EditorEx;
|
||||
import com.intellij.openapi.editor.impl.softwrap.SoftWrapsStorage;
|
||||
import com.intellij.openapi.project.Project;
|
||||
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.lib.legacy.ClassImposteriser;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Denis Zhdanov
|
||||
* @since 06/29/2010
|
||||
*/
|
||||
public class SoftWrapModelImplTest {
|
||||
|
||||
private static final Comparator<DataEntry> LOGICAL_POSITIONS_COMPARATOR = new Comparator<DataEntry>() {
|
||||
@Override
|
||||
public int compare(DataEntry o1, DataEntry o2) {
|
||||
LogicalPosition logical1 = o1.logical;
|
||||
LogicalPosition logical2 = o2.logical;
|
||||
if (logical1.line != logical2.line) {
|
||||
return logical1.line - logical2.line;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
return o1.foldedSpace ? 1 : -1;
|
||||
}
|
||||
return logical1.column - logical2.column;
|
||||
}
|
||||
};
|
||||
|
||||
private static final Comparator<DataEntry> OFFSETS_COMPARATOR = new Comparator<DataEntry>() {
|
||||
@Override
|
||||
public int compare(DataEntry o1, DataEntry o2) {
|
||||
// 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) {
|
||||
return o1.virtualSpace ? 1 : -1;
|
||||
}
|
||||
return o1.offset - o2.offset;
|
||||
}
|
||||
};
|
||||
|
||||
private static final String SOFT_WRAP_START_MARKER = "<WRAP>";
|
||||
private static final String SOFT_WRAP_END_MARKER = "</WRAP>";
|
||||
private static final String FOLDING_START_MARKER = "<FOLD>";
|
||||
private static final String FOLDING_END_MARKER = "</FOLD>";
|
||||
private static final int TAB_SIZE = 4;
|
||||
|
||||
/** Holds expected mappings between visual and logical positions and offset. */
|
||||
private final List<DataEntry> myExpectedData = new ArrayList<DataEntry>();
|
||||
private final List<TextRange> myLineRanges = new ArrayList<TextRange>();
|
||||
/** Holds document offsets that are considered to be folded. */
|
||||
private final TIntHashSet myFoldedOffsets = new TIntHashSet();
|
||||
private final List<FoldRegion> myFoldRegions = new ArrayList<FoldRegion>();
|
||||
|
||||
private SoftWrapModelImpl myModel;
|
||||
private Mockery myMockery;
|
||||
private ScrollingModel myScrollingModel;
|
||||
private EditorEx myEditor;
|
||||
private Document myDocument;
|
||||
private SoftWrapsStorage myStorage;
|
||||
private FoldingModel myFoldingModel;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
myMockery = new JUnit4Mockery() {{
|
||||
setImposteriser(ClassImposteriser.INSTANCE);
|
||||
}};
|
||||
myStorage = new SoftWrapsStorage();
|
||||
myEditor = myMockery.mock(EditorEx.class);
|
||||
myDocument = myMockery.mock(Document.class);
|
||||
final EditorSettings settings = myMockery.mock(EditorSettings.class);
|
||||
myScrollingModel = myMockery.mock(ScrollingModel.class);
|
||||
myFoldingModel = myMockery.mock(FoldingModel.class);
|
||||
final Project project = myMockery.mock(Project.class);
|
||||
|
||||
myMockery.checking(new Expectations() {{
|
||||
// Document
|
||||
allowing(myEditor).getDocument();will(returnValue(myDocument));
|
||||
allowing(myDocument).getLineCount(); will(new CustomAction("getLineCount()") {
|
||||
@Override
|
||||
public Object invoke(Invocation invocation) throws Throwable {
|
||||
return myLineRanges.size();
|
||||
}
|
||||
});
|
||||
allowing(myDocument).getLineNumber(with(any(int.class))); will(new CustomAction("getLineNumber()") {
|
||||
@Override
|
||||
public Object invoke(Invocation invocation) throws Throwable {
|
||||
return getLineNumber((Integer)invocation.getParameter(0));
|
||||
}
|
||||
});
|
||||
allowing(myDocument).getLineStartOffset(with(any(int.class))); will(new CustomAction("getLineStart()") {
|
||||
@Override
|
||||
public Object invoke(Invocation invocation) throws Throwable {
|
||||
return getLineStartOffset((Integer)invocation.getParameter(0));
|
||||
}
|
||||
});
|
||||
allowing(myDocument).getLineEndOffset(with(any(int.class))); will(new CustomAction("getLineEnd()") {
|
||||
@Override
|
||||
public Object invoke(Invocation invocation) throws Throwable {
|
||||
return getLineEndOffset((Integer)invocation.getParameter(0));
|
||||
}
|
||||
});
|
||||
|
||||
// Settings.
|
||||
allowing(myEditor).getSettings();will(returnValue(settings));
|
||||
allowing(settings).isUseSoftWraps();will(returnValue(true));
|
||||
allowing(settings).getTabSize(project);will(returnValue(TAB_SIZE));
|
||||
allowing(myEditor).getProject();will(returnValue(project));
|
||||
|
||||
// Scrolling
|
||||
ignoring(myScrollingModel);
|
||||
allowing(myEditor).getScrollingModel();will(returnValue(myScrollingModel));
|
||||
|
||||
// Folding.
|
||||
allowing(myEditor).getFoldingModel();will(returnValue(myFoldingModel));
|
||||
allowing(myFoldingModel).isOffsetCollapsed(with(any(int.class))); will(new CustomAction("isOffsetCollapsed()") {
|
||||
@Override
|
||||
public Object invoke(Invocation invocation) throws Throwable {
|
||||
return myFoldedOffsets.contains((Integer)invocation.getParameter(0));
|
||||
}
|
||||
});
|
||||
allowing(myFoldingModel).getCollapsedRegionAtOffset(with(any(int.class))); will(new CustomAction("getCollapsedRegionAtOffset()") {
|
||||
@Nullable
|
||||
@Override
|
||||
public Object invoke(Invocation invocation) throws Throwable {
|
||||
return getCollapsedFoldRegion((Integer)invocation.getParameter(0));
|
||||
}
|
||||
});
|
||||
allowing(myFoldingModel).getAllFoldRegions(); will(new CustomAction("getAllFoldRegions()") {
|
||||
@Override
|
||||
public Object invoke(Invocation invocation) throws Throwable {
|
||||
return myFoldRegions.toArray(new FoldRegion[myFoldRegions.size()]);
|
||||
}
|
||||
});
|
||||
|
||||
// Soft wrap-unaware conversions.
|
||||
allowing(myEditor).logicalToVisualPosition(with(any(LogicalPosition.class))); will(new CustomAction("logical2visual()") {
|
||||
@Override
|
||||
public Object invoke(Invocation invocation) throws Throwable {
|
||||
return logicalToVisual((LogicalPosition)invocation.getParameter(0));
|
||||
}
|
||||
});
|
||||
allowing(myEditor).logicalPositionToOffset(with(any(LogicalPosition.class))); will(new CustomAction("logical2offset()") {
|
||||
@Override
|
||||
public Object invoke(Invocation invocation) throws Throwable {
|
||||
return logicalToOffset((LogicalPosition)invocation.getParameter(0));
|
||||
}
|
||||
});
|
||||
allowing(myEditor).offsetToLogicalPosition(with(any(int.class))); will(new CustomAction("offset2logical()") {
|
||||
@Override
|
||||
public Object invoke(Invocation invocation) throws Throwable {
|
||||
return offsetToLogical((Integer)invocation.getParameter(0));
|
||||
}
|
||||
});
|
||||
}});
|
||||
|
||||
myModel = new SoftWrapModelImpl(myEditor, myStorage);
|
||||
}
|
||||
|
||||
@After
|
||||
public void checkExpectations() {
|
||||
myMockery.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void softWrapHasSymbolBeforeFirstLineFeed() {
|
||||
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" +
|
||||
" }\n" +
|
||||
" public void bar(int ... i) {\n" +
|
||||
" }\n" +
|
||||
"}";
|
||||
test(document);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipleSoftWrappedLogicalLines() {
|
||||
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" +
|
||||
" data[8], data[9], <WRAP>\n" +
|
||||
" </WRAP>data[10], data[11], <WRAP> \n" +
|
||||
" </WRAP>data[12], data[13]); \n" +
|
||||
" }\n" +
|
||||
" public void bar(int ... i) {\n" +
|
||||
" }\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" +
|
||||
" }\n" +
|
||||
" public void bar(int ... i) {\n" +
|
||||
" }\n" +
|
||||
"}";
|
||||
test(document);
|
||||
}
|
||||
|
||||
private int getLineNumber(int offset) {
|
||||
int line = 0;
|
||||
for (TextRange range : myLineRanges) {
|
||||
if (offset >= range.getStartOffset() && offset <= range.getEndOffset()) {
|
||||
return line;
|
||||
}
|
||||
line++;
|
||||
}
|
||||
throw new AssertionError(String.format("Can't find test document line for offset %d. Registered lines: %s", offset, myLineRanges));
|
||||
}
|
||||
|
||||
private int getLineStartOffset(int line) {
|
||||
checkLine(line);
|
||||
return myLineRanges.get(line).getStartOffset();
|
||||
}
|
||||
|
||||
private int getLineEndOffset(int line) {
|
||||
checkLine(line);
|
||||
return myLineRanges.get(line).getEndOffset();
|
||||
}
|
||||
|
||||
private void checkLine(int line) {
|
||||
if (line < 0 || line >= myLineRanges.size()) {
|
||||
throw new AssertionError(String.format("Can't retrieve target data for the given line (%d). Reason - it's not within allowed "
|
||||
+ "bounds ([0; %d])", line, myLineRanges.size() - 1));
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private FoldRegion getCollapsedFoldRegion(int offset) {
|
||||
for (FoldRegion region : myFoldRegions) {
|
||||
if (region.getStartOffset() == offset) {
|
||||
return region;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private VisualPosition logicalToVisual(LogicalPosition position) {
|
||||
DataEntry dataEntry = myExpectedData.get(findIndex(new DataEntry(null, position, 0, false), LOGICAL_POSITIONS_COMPARATOR));
|
||||
return toSoftWrapUnawareVisual(dataEntry);
|
||||
}
|
||||
|
||||
private int logicalToOffset(LogicalPosition position) {
|
||||
DataEntry dataEntry = myExpectedData.get(findIndex(new DataEntry(null, position, 0, false), LOGICAL_POSITIONS_COMPARATOR));
|
||||
return dataEntry.offset;
|
||||
}
|
||||
|
||||
private LogicalPosition offsetToLogical(int offset) {
|
||||
DataEntry dataEntry = myExpectedData.get(findIndex(new DataEntry(null, null, offset, false), OFFSETS_COMPARATOR));
|
||||
return toSoftWrapUnawareLogicalByOffset(dataEntry);
|
||||
}
|
||||
|
||||
private int findIndex(DataEntry key, Comparator<DataEntry> comparator) {
|
||||
int i = Collections.binarySearch(myExpectedData, key, comparator);
|
||||
if (i < 0 || i >= myExpectedData.size()) {
|
||||
throw new AssertionError(String.format("Can't find pre-configured data entry for the given key (%s). "
|
||||
+ "Available data: %s", key, myExpectedData));
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
private static VisualPosition toSoftWrapUnawareVisual(DataEntry dataEntry) {
|
||||
LogicalPosition logical = dataEntry.logical;
|
||||
return new VisualPosition(logical.line - logical.foldedLines, logical.column + logical.foldingColumnDiff);
|
||||
}
|
||||
|
||||
private static LogicalPosition toSoftWrapUnawareLogicalByVisual(DataEntry dataEntry) {
|
||||
LogicalPosition logical = dataEntry.logical;
|
||||
return new LogicalPosition(
|
||||
logical.line + logical.softWrapLinesBeforeCurrentLogicalLine + logical.softWrapLinesOnCurrentLogicalLine,
|
||||
logical.column
|
||||
);
|
||||
}
|
||||
|
||||
private static LogicalPosition toSoftWrapUnawareLogicalByOffset(DataEntry dataEntry) {
|
||||
LogicalPosition logical = dataEntry.logical;
|
||||
return new LogicalPosition(logical.line, logical.column);
|
||||
}
|
||||
|
||||
private void test(String documentText) {
|
||||
init(documentText);
|
||||
for (DataEntry data : myExpectedData) {
|
||||
|
||||
// Check logical by visual.
|
||||
LogicalPosition actualLogicalByVisual = myModel.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)) {
|
||||
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'",
|
||||
data.visual, data.logical, actualLogicalByVisual, toSoftWrapUnawareLogicalByVisual(data))
|
||||
);
|
||||
}
|
||||
|
||||
// Check logical by offset.
|
||||
LogicalPosition actualLogicalByOffset = myModel.adjustLogicalPosition(toSoftWrapUnawareLogicalByOffset(data), 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)) {
|
||||
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'",
|
||||
data.logical, actualLogicalByOffset, data.offset, toSoftWrapUnawareLogicalByOffset(data))
|
||||
);
|
||||
}
|
||||
|
||||
// Check visual by logical.
|
||||
VisualPosition actualVisual = myModel.adjustVisualPosition(data.logical, toSoftWrapUnawareVisual(data));
|
||||
if (!actualVisual.equals(data.visual)) {
|
||||
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))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean equals(LogicalPosition expected, LogicalPosition actual) {
|
||||
return expected.equals(actual) && expected.softWrapLinesBeforeCurrentLogicalLine == actual.softWrapLinesBeforeCurrentLogicalLine
|
||||
&& expected.softWrapLinesOnCurrentLogicalLine == actual.softWrapLinesOnCurrentLogicalLine
|
||||
&& expected.softWrapColumnDiff == actual.softWrapColumnDiff && expected.foldedLines == actual.foldedLines
|
||||
&& expected.foldingColumnDiff == actual.foldingColumnDiff;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"AssignmentToForLoopParameter"})
|
||||
private void init(String documentText) {
|
||||
final Context context = new Context();
|
||||
for (int i = 0; i < documentText.length(); i++) {
|
||||
if (isSoftWrapStart(documentText, i)) {
|
||||
context.onSoftWrapStart();
|
||||
i += SOFT_WRAP_START_MARKER.length() - 1; // Subtract 1 because 'i' is incremented by 1 on every iteration
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isSoftWrapEnd(documentText, i)) {
|
||||
context.onSoftWrapEnd();
|
||||
i += SOFT_WRAP_END_MARKER.length() - 1; // Subtract 1 because 'i' is incremented by 1 on every iteration
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isFoldingStart(documentText, i)) {
|
||||
context.onFoldingStart();
|
||||
i += FOLDING_START_MARKER.length() - 1; // Subtract 1 because 'i' is incremented by 1 on every iteration
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isFoldingEnd(documentText, i)) {
|
||||
context.onFoldingEnd();
|
||||
i += FOLDING_END_MARKER.length() - 1; // Subtract 1 because 'i' is incremented by 1 on every iteration
|
||||
continue;
|
||||
}
|
||||
|
||||
context.onNewSymbol(documentText.charAt(i));
|
||||
}
|
||||
|
||||
myLineRanges.add(new TextRange(context.logicalLineStartOffset, context.document.length()));
|
||||
|
||||
myMockery.checking(new Expectations() {{
|
||||
allowing(myDocument).getCharsSequence(); will(returnValue(context.document));
|
||||
allowing(myDocument).getTextLength(); will(returnValue(context.document.length()));
|
||||
}});
|
||||
}
|
||||
|
||||
private static boolean isSoftWrapStart(String document, int index) {
|
||||
return matches(document, index, SOFT_WRAP_START_MARKER);
|
||||
}
|
||||
|
||||
private static boolean isSoftWrapEnd(String document, int index) {
|
||||
return matches(document, index, SOFT_WRAP_END_MARKER);
|
||||
}
|
||||
|
||||
private static boolean isFoldingStart(String document, int index) {
|
||||
return matches(document, index, FOLDING_START_MARKER);
|
||||
}
|
||||
|
||||
private static boolean isFoldingEnd(String document, int index) {
|
||||
return matches(document, index, FOLDING_END_MARKER);
|
||||
}
|
||||
|
||||
private static boolean matches(String document, int index, String pattern) {
|
||||
if (index + pattern.length() > document.length()) {
|
||||
return false;
|
||||
}
|
||||
return pattern.equals(document.substring(index, index + pattern.length()));
|
||||
}
|
||||
|
||||
private static class DataEntry {
|
||||
public final VisualPosition visual;
|
||||
public final LogicalPosition logical;
|
||||
public final int offset;
|
||||
public final boolean foldedSpace;
|
||||
public final boolean virtualSpace;
|
||||
|
||||
DataEntry(VisualPosition visual, LogicalPosition logical, int offset, boolean foldedSpace) {
|
||||
this(visual, logical, offset, foldedSpace, false);
|
||||
}
|
||||
|
||||
DataEntry(VisualPosition visual, LogicalPosition logical, int offset, boolean foldedSpace, boolean virtualSpace) {
|
||||
this.visual = visual;
|
||||
this.logical = logical;
|
||||
this.offset = offset;
|
||||
this.foldedSpace = foldedSpace;
|
||||
this.virtualSpace = virtualSpace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "offset: " + offset + ", logical: " + logical + ", visual: " + visual + ", folded: " + foldedSpace
|
||||
+ ", virtual: " + virtualSpace;
|
||||
}
|
||||
}
|
||||
|
||||
private class Context {
|
||||
private final StringBuilder mySoftWrapBuffer = new StringBuilder();
|
||||
final StringBuilder document = new StringBuilder();
|
||||
|
||||
boolean virtualSpace;
|
||||
boolean insideFolding;
|
||||
int logicalLineStartOffset;
|
||||
int logicalLine;
|
||||
int logicalColumn;
|
||||
int visualLine;
|
||||
int visualColumn;
|
||||
int softWrapStartOffset;
|
||||
int softWrapLinesBeforeCurrentLogical;
|
||||
int softWrapLinesOnCurrentLogical;
|
||||
int softWrapSymbolsOnCurrentVisualLine;
|
||||
int foldingStartOffset;
|
||||
int foldingStartLogicalColumn;
|
||||
int foldingStartVisualColumn;
|
||||
int foldingColumnDiff;
|
||||
int foldedLines;
|
||||
int offset;
|
||||
|
||||
public void onSoftWrapStart() {
|
||||
softWrapStartOffset = offset;
|
||||
virtualSpace = true;
|
||||
}
|
||||
|
||||
public void onSoftWrapEnd() {
|
||||
myStorage.storeSoftWrap(new TextChange(mySoftWrapBuffer.toString(), softWrapStartOffset));
|
||||
mySoftWrapBuffer.setLength(0);
|
||||
virtualSpace = false;
|
||||
}
|
||||
|
||||
public void onFoldingStart() {
|
||||
foldingStartOffset = offset;
|
||||
foldingStartLogicalColumn = logicalColumn;
|
||||
foldingStartVisualColumn = visualColumn;
|
||||
insideFolding = true;
|
||||
}
|
||||
|
||||
public void onFoldingEnd() {
|
||||
visualColumn += 3; // For '...' folding
|
||||
foldingColumnDiff = visualColumn - logicalColumn;
|
||||
insideFolding = false;
|
||||
myFoldRegions.add(new MockFoldRegion(foldingStartOffset, offset));
|
||||
}
|
||||
|
||||
public void onNewSymbol(char c) {
|
||||
addData();
|
||||
if (insideFolding) {
|
||||
myFoldedOffsets.add(offset);
|
||||
onNonSoftWrapSymbol(c);
|
||||
if (c == '\n') {
|
||||
foldedLines++;
|
||||
}
|
||||
foldingColumnDiff = foldingStartVisualColumn - logicalColumn;
|
||||
return;
|
||||
}
|
||||
|
||||
// Symbol inside soft wrap.
|
||||
if (virtualSpace) {
|
||||
mySoftWrapBuffer.append(c);
|
||||
if (c == '\n') {
|
||||
// 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++;
|
||||
addData(true);
|
||||
visualColumn++;
|
||||
addData(true);
|
||||
|
||||
visualLine++;
|
||||
softWrapLinesOnCurrentLogical++;
|
||||
visualColumn = 0;
|
||||
softWrapSymbolsOnCurrentVisualLine = 0;
|
||||
}
|
||||
else {
|
||||
visualColumn++;
|
||||
softWrapSymbolsOnCurrentVisualLine++;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Symbol outside soft wrap and folding.
|
||||
onNonSoftWrapSymbol(c);
|
||||
if (c == '\n') {
|
||||
visualLine++;
|
||||
visualColumn = 0;
|
||||
softWrapLinesBeforeCurrentLogical += softWrapLinesOnCurrentLogical;
|
||||
softWrapLinesOnCurrentLogical = 0;
|
||||
softWrapSymbolsOnCurrentVisualLine = 0;
|
||||
foldingColumnDiff = 0;
|
||||
}
|
||||
else {
|
||||
visualColumn++;
|
||||
}
|
||||
}
|
||||
|
||||
private void onNonSoftWrapSymbol(char c) {
|
||||
document.append(c);
|
||||
if (c == '\n') {
|
||||
myLineRanges.add(new TextRange(logicalLineStartOffset, offset));
|
||||
|
||||
// Emulate the situation when the user works with a virtual space after document line end (add such virtual
|
||||
// positions two symbols behind the end).
|
||||
if (!insideFolding) {
|
||||
visualColumn++;
|
||||
logicalColumn++;
|
||||
addData(true);
|
||||
visualColumn++;
|
||||
logicalColumn++;
|
||||
addData(true);
|
||||
}
|
||||
|
||||
logicalLineStartOffset = offset + 1;
|
||||
logicalLine++;
|
||||
logicalColumn = 0;
|
||||
}
|
||||
else {
|
||||
logicalColumn++;
|
||||
}
|
||||
offset++;
|
||||
}
|
||||
|
||||
private void addData() {
|
||||
addData(virtualSpace);
|
||||
}
|
||||
|
||||
private void addData(boolean virtualSpace) {
|
||||
myExpectedData.add(new DataEntry(
|
||||
buildVisualPosition(), buildLogicalPosition(), offset, insideFolding && offset != foldingStartOffset, virtualSpace
|
||||
));
|
||||
}
|
||||
|
||||
private LogicalPosition buildLogicalPosition() {
|
||||
//int softWrapColumnDiff
|
||||
int softWrapColumnDiff = visualColumn - logicalColumn - foldingColumnDiff;
|
||||
return new LogicalPosition(
|
||||
logicalLine, logicalColumn, softWrapLinesBeforeCurrentLogical, softWrapLinesOnCurrentLogical, softWrapColumnDiff,
|
||||
foldedLines, foldingColumnDiff
|
||||
);
|
||||
}
|
||||
|
||||
private VisualPosition buildVisualPosition() {
|
||||
return new VisualPosition(visualLine, visualColumn);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright 2000-2010 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.mock;
|
||||
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.editor.FoldRegion;
|
||||
import com.intellij.openapi.editor.FoldingGroup;
|
||||
import com.intellij.openapi.util.UserDataHolderBase;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* @author Denis Zhdanov
|
||||
* @since Jun 30, 2010 1:55:53 PM
|
||||
*/
|
||||
public class MockFoldRegion extends UserDataHolderBase implements FoldRegion {
|
||||
|
||||
private final int myStartOffset;
|
||||
private final int myEndOffset;
|
||||
|
||||
public MockFoldRegion(int startOffset, int endOffset) {
|
||||
myStartOffset = startOffset;
|
||||
myEndOffset = endOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExpanded() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setExpanded(boolean expanded) {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getPlaceholderText() {
|
||||
return "...";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Editor getEditor() {
|
||||
throw new UnsupportedOperationException("FoldRegion.getEditor() is not implemented yet at " + getClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
public FoldingGroup getGroup() {
|
||||
throw new UnsupportedOperationException("FoldRegion.getGroup() is not implemented yet at " + getClass());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Document getDocument() {
|
||||
throw new UnsupportedOperationException("FoldRegion.getDocument() is not implemented yet at " + getClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStartOffset() {
|
||||
return myStartOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEndOffset() {
|
||||
return myEndOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGreedyToLeft(boolean greedy) {
|
||||
throw new UnsupportedOperationException("FoldRegion.setGreedyToLeft() is not implemented yet at " + getClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGreedyToRight(boolean greedy) {
|
||||
throw new UnsupportedOperationException("FoldRegion.setGreedyToRight() is not implemented yet at " + getClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGreedyToRight() {
|
||||
throw new UnsupportedOperationException("FoldRegion.isGreedyToRight() is not implemented yet at " + getClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGreedyToLeft() {
|
||||
throw new UnsupportedOperationException("FoldRegion.isGreedyToLeft() is not implemented yet at " + getClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return myStartOffset + "-" + myEndOffset;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user