diff --git a/platform/lang-impl/src/com/intellij/injected/editor/EditorWindow.java b/platform/lang-impl/src/com/intellij/injected/editor/EditorWindow.java
index a107bb1d9691..8fc447cb4c66 100644
--- a/platform/lang-impl/src/com/intellij/injected/editor/EditorWindow.java
+++ b/platform/lang-impl/src/com/intellij/injected/editor/EditorWindow.java
@@ -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();
}
diff --git a/platform/platform-api/platform-api.iml b/platform/platform-api/platform-api.iml
index e0035d800df2..64d58129d8ac 100644
--- a/platform/platform-api/platform-api.iml
+++ b/platform/platform-api/platform-api.iml
@@ -4,6 +4,7 @@
+
diff --git a/platform/platform-api/src/com/intellij/openapi/editor/EditorModificationUtil.java b/platform/platform-api/src/com/intellij/openapi/editor/EditorModificationUtil.java
index c90a8ffb1d6c..39e3a57c658c 100644
--- a/platform/platform-api/src/com/intellij/openapi/editor/EditorModificationUtil.java
+++ b/platform/platform-api/src/com/intellij/openapi/editor/EditorModificationUtil.java
@@ -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 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;
}
diff --git a/platform/platform-api/src/com/intellij/openapi/editor/LogicalPosition.java b/platform/platform-api/src/com/intellij/openapi/editor/LogicalPosition.java
index 3b9f0b34a704..7b8e3bf1e9c7 100644
--- a/platform/platform-api/src/com/intellij/openapi/editor/LogicalPosition.java
+++ b/platform/platform-api/src/com/intellij/openapi/editor/LogicalPosition.java
@@ -44,27 +44,25 @@ public class LogicalPosition implements Comparable {
/**
* 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.
- *
- * 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 {
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 {
* @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 {
@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;
}
}
diff --git a/platform/platform-api/src/com/intellij/openapi/editor/SoftWrapModel.java b/platform/platform-api/src/com/intellij/openapi/editor/SoftWrapModel.java
index ae447fe5ee2f..8f77a3086b40 100644
--- a/platform/platform-api/src/com/intellij/openapi/editor/SoftWrapModel.java
+++ b/platform/platform-api/src/com/intellij/openapi/editor/SoftWrapModel.java
@@ -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 true if given visual position points to soft wrap-introduced virtual space;
+ * false otherwise
+ */
+ boolean isInsideSoftWrap(@NotNull VisualPosition position);
}
diff --git a/platform/platform-api/testSrc/com/intellij/openapi/editor/TextChangeTest.java b/platform/platform-api/testSrc/com/intellij/openapi/editor/TextChangeTest.java
new file mode 100644
index 000000000000..0009ff18b083
--- /dev/null
+++ b/platform/platform-api/testSrc/com/intellij/openapi/editor/TextChangeTest.java
@@ -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));
+ }
+ }
+}
diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/actions/EditorActionUtil.java b/platform/platform-impl/src/com/intellij/openapi/editor/actions/EditorActionUtil.java
index 63aa173a6f59..598f36197e5e 100644
--- a/platform/platform-impl/src/com/intellij/openapi/editor/actions/EditorActionUtil.java
+++ b/platform/platform-impl/src/com/intellij/openapi/editor/actions/EditorActionUtil.java
@@ -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);
}
diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/ex/EditorEx.java b/platform/platform-impl/src/com/intellij/openapi/editor/ex/EditorEx.java
index d57e1cfb1dc5..ea689d85a0a3 100644
--- a/platform/platform-impl/src/com/intellij/openapi/editor/ex/EditorEx.java
+++ b/platform/platform-impl/src/com/intellij/openapi/editor/ex/EditorEx.java
@@ -99,6 +99,8 @@ public interface EditorEx extends Editor {
void setRendererMode(boolean isRendererMode);
+ boolean isUnderRepainting();
+
void setFile(VirtualFile vFile);
DataContext getDataContext();
diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/ex/util/EditorUtil.java b/platform/platform-impl/src/com/intellij/openapi/editor/ex/util/EditorUtil.java
index fb514e5382fd..845dfd3ff759 100644
--- a/platform/platform-impl/src/com/intellij/openapi/editor/ex/util/EditorUtil.java
+++ b/platform/platform-impl/src/com/intellij/openapi/editor/ex/util/EditorUtil.java
@@ -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;
}
diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/CaretModelImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/CaretModelImpl.java
index 56a94026f1ac..3310c6c80f17 100644
--- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/CaretModelImpl.java
+++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/CaretModelImpl.java
@@ -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) {
diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java
index 4c09f1fc8f4b..ffe3b12fd5de 100644
--- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java
+++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java
@@ -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);
}
diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/SoftWrapModelImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/SoftWrapModelImpl.java
index e3a14b80b6cc..f7580cb0ca4f 100644
--- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/SoftWrapModelImpl.java
+++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/SoftWrapModelImpl.java
@@ -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 SOFT_WRAPS_BY_OFFSET_COMPARATOR = new Comparator() {
- 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.
*
* The general idea is to do the following:
*
@@ -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 myWraps = new ArrayList();
- private final List 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 softWraps = myStorage.getSoftWraps();
List 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. 'Target' 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; '-(negative value) - 1' 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; null 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 [start; end) 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 getRegisteredSoftWraps() {
- return myWrapsView;
+ return myStorage.getSoftWraps();
}
/**
* Drops information about registered soft wraps if they are marked as 'dirty'.
- * //TODO den add doc about editor repainting
+ *
+ * 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 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 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 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 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 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 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 modified = myWraps.subList(softWrapIndex, myWraps.size());
- List toModify = new ArrayList(modified);
- modified.clear();
+ List toModify = new ArrayList(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);
}
diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/SoftWrapsStorage.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/SoftWrapsStorage.java
new file mode 100644
index 000000000000..6a76ebca2e3c
--- /dev/null
+++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/SoftWrapsStorage.java
@@ -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.
+ *
+ * Not thread-safe.
+ *
+ * @author Denis Zhdanov
+ * @since Jun 29, 2010 3:04:20 PM
+ */
+public class SoftWrapsStorage {
+
+ private static final Comparator SOFT_WRAPS_BY_OFFSET_COMPARATOR = new Comparator() {
+ public int compare(TextChange c1, TextChange c2) {
+ return c1.getStart() - c2.getEnd();
+ }
+ };
+
+ private final List myWraps = new ArrayList();
+ private final List myWrapsView = Collections.unmodifiableList(myWraps);
+
+ /**
+ * @return true if there is at least one soft wrap registered at the current storage; false 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 getSoftWraps() {
+ return myWrapsView;
+ }
+
+ /**
+ * Tries to find index of the target soft wrap stored at {@link #myWraps} collection. 'Target' 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; '-(negative value) - 1' 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; null 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; null 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();
+ }
+}
diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/textarea/TextComponentSoftWrapModel.java b/platform/platform-impl/src/com/intellij/openapi/editor/textarea/TextComponentSoftWrapModel.java
index 682b7a75a17c..802b20aa31cb 100644
--- a/platform/platform-impl/src/com/intellij/openapi/editor/textarea/TextComponentSoftWrapModel.java
+++ b/platform/platform-impl/src/com/intellij/openapi/editor/textarea/TextComponentSoftWrapModel.java
@@ -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;
+ }
}
diff --git a/platform/platform-impl/testSrc/com/intellij/openapi/editor/impl/SoftWrapModelImplTest.java b/platform/platform-impl/testSrc/com/intellij/openapi/editor/impl/SoftWrapModelImplTest.java
new file mode 100644
index 000000000000..d1c97b570825
--- /dev/null
+++ b/platform/platform-impl/testSrc/com/intellij/openapi/editor/impl/SoftWrapModelImplTest.java
@@ -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 LOGICAL_POSITIONS_COMPARATOR = new Comparator() {
+ @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 OFFSETS_COMPARATOR = new Comparator() {
+ @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 = "";
+ private static final String SOFT_WRAP_END_MARKER = "";
+ private static final String FOLDING_START_MARKER = "";
+ private static final String FOLDING_END_MARKER = "";
+ private static final int TAB_SIZE = 4;
+
+ /** Holds expected mappings between visual and logical positions and offset. */
+ private final List myExpectedData = new ArrayList();
+ private final List myLineRanges = new ArrayList();
+ /** Holds document offsets that are considered to be folded. */
+ private final TIntHashSet myFoldedOffsets = new TIntHashSet();
+ private final List myFoldRegions = new ArrayList();
+
+ 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], \n" +
+ " data[4], data[5], data[6], \n" +
+ " 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], \n" +
+ " data[2], data[3], \n" +
+ " data[4], data[5], \n" +
+ " data[6], data[7], \n" +
+ " data[8], data[9], \n" +
+ " data[10], data[11], \n" +
+ " 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], \n" +
+ " data[2], data[3], \n" +
+ " data[4], data[5], \n" +
+ " data[6], data[7], \n" +
+ " data[8], data[9], \n" +
+ " data[10], data[11], \n" +
+ " data[12], data[13], \n" +
+ " data[14], data[15], \n" +
+ " data[16], data[17], \n" +
+ " data[18], data[19], \n" +
+ " 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 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);
+ }
+ }
+}
diff --git a/platform/testFramework/src/com/intellij/mock/MockFoldRegion.java b/platform/testFramework/src/com/intellij/mock/MockFoldRegion.java
new file mode 100644
index 000000000000..44658d04c29b
--- /dev/null
+++ b/platform/testFramework/src/com/intellij/mock/MockFoldRegion.java
@@ -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;
+ }
+}