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 719aa3daf73e..aae8a0edaa25 100644 --- a/platform/lang-impl/src/com/intellij/injected/editor/EditorWindow.java +++ b/platform/lang-impl/src/com/intellij/injected/editor/EditorWindow.java @@ -195,7 +195,7 @@ public class EditorWindow implements EditorEx, UserDataHolderEx { } @NotNull - public FoldingModel getFoldingModel() { + public FoldingModelEx getFoldingModel() { return myFoldingModelWindow; } @@ -283,6 +283,11 @@ public class EditorWindow implements EditorEx, UserDataHolderEx { @NotNull public LogicalPosition offsetToLogicalPosition(final int offset) { + return offsetToLogicalPosition(offset, true); + } + + @NotNull + public LogicalPosition offsetToLogicalPosition(final int offset, boolean softWrapAware) { assert isValid(); int lineNumber = myDocumentWindow.getLineNumber(offset); int lineStartOffset = myDocumentWindow.getLineStartOffset(lineNumber); @@ -465,6 +470,11 @@ public class EditorWindow implements EditorEx, UserDataHolderEx { @NotNull public LogicalPosition visualToLogicalPosition(@NotNull final VisualPosition pos) { + return visualToLogicalPosition(pos, true); + } + + @NotNull + public LogicalPosition visualToLogicalPosition(@NotNull final VisualPosition pos, boolean softWrapAware) { assert isValid(); return new LogicalPosition(pos.line, pos.column); } 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 f3ac15c6c37b..7728292bd43e 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 @@ -21,6 +21,9 @@ import com.intellij.ide.DeleteProvider; import com.intellij.ide.PasteProvider; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.FoldingModel; +import com.intellij.openapi.editor.LogicalPosition; +import com.intellij.openapi.editor.VisualPosition; import com.intellij.openapi.editor.colors.EditorColorsScheme; import com.intellij.openapi.editor.highlighter.EditorHighlighter; import com.intellij.openapi.editor.impl.TextDrawingCallback; @@ -124,4 +127,13 @@ public interface EditorEx extends Editor { int calcColumnNumber(int offset, int lineIndex); TextDrawingCallback getTextDrawingCallback(); + + @NotNull + @Override + FoldingModelEx getFoldingModel(); + + @NotNull + LogicalPosition visualToLogicalPosition(@NotNull VisualPosition visiblePos, boolean softWrapAware); + + @NotNull LogicalPosition offsetToLogicalPosition(int offset, boolean softWrapAware); } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/ex/SoftWrapModelEx.java b/platform/platform-impl/src/com/intellij/openapi/editor/ex/SoftWrapModelEx.java index 88d897c9d3e7..3f309e1987d9 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/ex/SoftWrapModelEx.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/ex/SoftWrapModelEx.java @@ -35,26 +35,22 @@ import java.util.List; public interface SoftWrapModelEx extends SoftWrapModel { /** - * Asks current model to adjust logical position for the given visual position if necessary. - *
- * Given logical position is allowed to be non-soft wrap aware, i.e. the one calculated as there are no soft wraps at the moment. + * Asks current model to map given visual position to logical. * - * @param defaultLogical default logical position that corresponds to the given visual position - * @param visual target visual position for which logical position should be adjusted if necessary + * @param visual target visual position for which logical position should be mapped * @return logical position that corresponds to the given visual position */ @NotNull - LogicalPosition adjustLogicalPosition(@NotNull LogicalPosition defaultLogical, @NotNull VisualPosition visual); + LogicalPosition visualToLogicalPosition(@NotNull VisualPosition visual); /** - * Asks current model to adjust logical position for the given document offset. + * Asks current model to map given document offset to logical position. * - * @param defaultLogical default logical position that corresponds to the given document offset * @param offset target editor document offset * @return logical position for the given editor document offset */ @NotNull - LogicalPosition adjustLogicalPosition(LogicalPosition defaultLogical, int offset); + LogicalPosition offsetToLogicalPosition(int offset); /** * Asks current model to adjust visual position that corresponds to the given logical position if necessary. @@ -103,7 +99,15 @@ public interface SoftWrapModelEx extends SoftWrapModel { * @param drawingType target drawing type * @return width in pixels required for the painting of the given type */ - int getMinDrawingWidth(@NotNull SoftWrapDrawingType drawingType); + int getMinDrawingWidthInPixels(@NotNull SoftWrapDrawingType drawingType); + + /** + * Allows to ask for the minimal width in columns required for painting of the given type. + * + * @param drawingType target drawing type + * @return width in columns required for the painting of the given type + */ + int getMinDrawingWidthInColumns(@NotNull SoftWrapDrawingType drawingType); /** * Registers given listener within the current model 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 a12865d77d70..11763b6fb1ce 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 @@ -28,6 +28,7 @@ import org.jetbrains.annotations.NotNull; import java.awt.*; import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; public class EditorUtil { private EditorUtil() { } @@ -178,54 +179,69 @@ public class EditorUtil { */ public static int calcOffset(Editor editor, CharSequence text, int start, int end, int columnNumber, int tabSize) { final int maxScanIndex = Math.min(start + columnNumber + 1, end); - if (editor == null) { - return calcSoftWrapUnawareOffset(text, start, maxScanIndex, columnNumber, tabSize); - } - - EditorEx editorImpl = (EditorEx)editor; - int offset = start; - IterationState state = new IterationState(editorImpl, offset, false); - int fontType = state.getMergedAttributes().getFontType(); - int column = 0; - int x = 0; - int spaceSize = getSpaceWidth(fontType, editorImpl); SoftWrapModel softWrapModel = editor.getSoftWrapModel(); - while (column < columnNumber) { - TextChange softWrap = softWrapModel.getSoftWrap(offset); - if (softWrap != null) { - x = softWrapModel.getSoftWrapIndentWidthInPixels(softWrap); + List extends TextChange> softWraps = softWrapModel.getSoftWrapsForRange(start, maxScanIndex); + int startToUse = start; + int x = 0; + AtomicInteger currentColumn = new AtomicInteger(); + for (TextChange softWrap : softWraps) { + // There is a possible case that target column points inside soft wrap-introduced virtual space. + if (currentColumn.get() >= columnNumber) { + return startToUse; } - if (offset >= state.getEndOffset()) { - state.advance(); - fontType = state.getMergedAttributes().getFontType(); + int result = calcSoftWrapUnawareOffset(editor, text, startToUse, softWrap.getEnd(), columnNumber, tabSize, x, currentColumn); + if (result >= 0) { + return result; } - char c = offset < end ? text.charAt(offset++) : ' '; - int prevX = x; - if (c == '\t') { - x = nextTabStop(x, editorImpl); - } - else { - x += charWidth(c, fontType, editorImpl); - } - column += columnsNumber(c, x, prevX, spaceSize); + startToUse = softWrap.getStart(); + x = softWrapModel.getSoftWrapIndentWidthInPixels(softWrap); } - //if (column == columnNumber && offset < end && text.charAt(offset) == '\t' && (nextTabStop(x, editorImpl) - x) / spaceSize == 0) { - // offset++; - //} - if (column > columnNumber) offset--; - return offset; + // There is a possible case that target column points inside soft wrap-introduced virtual space. + if (currentColumn.get() >= columnNumber) { + return startToUse; + } + + int result = calcSoftWrapUnawareOffset(editor, text, startToUse, end, columnNumber, tabSize, x, currentColumn); + if (result >= 0) { + return result; + } + + // We assume that given column points to the virtual space after the line end if control flow reaches this place, + // hence, just return end of line offset then. + return end; } - private static int calcSoftWrapUnawareOffset(CharSequence text, int start, int end, int columnNumber, int tabSize) { + /** + * Tries to match given logical column to the document offset assuming that it's located at[start; end) region.
+ *
+ * @param editor editor that is used to represent target document
+ * @param text target document text
+ * @param start start offset to check (inclusive)
+ * @param end end offset to check (exclusive)
+ * @param columnNumber target logical column number
+ * @param tabSize user-defined desired number of columns to use for tabulation symbol representation
+ * @param x 'x' coordinate that corresponds to the given 'start' offset
+ * @param currentColumn logical column that corresponds to the given 'start' offset
+ * @return target offset that belongs to the [start; end) range and points to the target logical
+ * column if any; -1 otherwise
+ */
+ private static int calcSoftWrapUnawareOffset(Editor editor, CharSequence text, int start, int end, int columnNumber, int tabSize, int x,
+ AtomicInteger currentColumn)
+ {
+ // The main problem in a calculation is that target text may contain tabulation symbols and every such symbol may take different
+ // number of logical columns to represent. E.g. it takes two columns if tab size is four and current column is two; three columns
+ // if tab size is four and current column is one etc. So, first of all we check if there are tabulation symbols at the target
+ // text fragment.
+ boolean useOptimization = true;
boolean hasNonTabs = false;
boolean hasTabs = false;
-
for (int i = start; i < end; i++) {
if (text.charAt(i) == '\t') {
hasTabs = true;
if (hasNonTabs) {
+ useOptimization = false;
break;
}
} else {
@@ -233,20 +249,77 @@ public class EditorUtil {
}
}
- if (!hasTabs) return Math.min(start + columnNumber, end);
+ // Perform optimized processing if possible. 'Optimized' here means the processing when we exactly know how many logical
+ // columns are occupied by tabulation symbols.
+ if (editor == null || useOptimization) {
+ if (!hasTabs) {
+ int result = start + columnNumber - currentColumn.get();
+ if (result < end) {
+ return result;
+ }
+ else {
+ currentColumn.addAndGet(end - start);
+ return -1;
+ }
+ }
- int shift = 0;
- int offset = start;
- for (; offset < end && offset + shift < start + columnNumber; offset++) {
- if (text.charAt(offset) == '\t') {
- shift += getTabLength(offset + shift - start, tabSize) - 1;
+ int shift = 0;
+ int offset = start;
+ int prevX = x;
+ for (; offset < end && offset + shift + currentColumn.get() < start + columnNumber; offset++) {
+ if (text.charAt(offset) == '\t') {
+ int nextX = nextTabStop(prevX, editor, tabSize);
+ shift += columnsNumber(nextX - prevX, getSpaceWidth(Font.PLAIN, editor));
+ prevX = nextX;
+ }
+ }
+ int diff = start + columnNumber - offset - shift - currentColumn.get();
+ if (diff < 0) {
+ return offset - 1;
+ }
+ else if (diff == 0) {
+ return offset;
+ }
+ else {
+ currentColumn.addAndGet(offset - start + shift);
+ return -1;
}
}
- if (offset + shift > start + columnNumber) {
- offset--;
+
+ // It means that there are tabulation symbols that can't be explicitly mapped to the occupied logical columns number,
+ // hence, we need to perform special calculations to get know that.
+ EditorEx editorImpl = (EditorEx)editor;
+ int offset = start;
+ IterationState state = new IterationState(editorImpl, offset, false);
+ int fontType = state.getMergedAttributes().getFontType();
+ int column = currentColumn.get();
+ int spaceSize = getSpaceWidth(fontType, editorImpl);
+ for (; column <= columnNumber && offset < end; offset++) {
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontType = state.getMergedAttributes().getFontType();
+ }
+
+ char c = text.charAt(offset);
+ if (c == '\t') {
+ int prevX = x;
+ x = nextTabStop(x, editorImpl);
+ column += columnsNumber(x - prevX, spaceSize);
+ }
+ else {
+ x += charWidth(c, fontType, editorImpl);
+ column++;
+ }
}
- return offset;
+ if (column == columnNumber) {
+ return offset;
+ }
+ if (column > columnNumber && text.charAt(offset) == '\t') {
+ return offset - 1;
+ }
+ currentColumn.set(column);
+ return -1;
}
private static int getTabLength(int colNumber, int tabSize) {
@@ -328,7 +401,10 @@ public class EditorUtil {
if (tabSize <= 0) {
tabSize = 1;
}
+ return nextTabStop(x, editor, tabSize);
+ }
+ public static int nextTabStop(int x, Editor editor, int tabSize) {
tabSize *= getSpaceWidth(Font.PLAIN, editor);
int nTabs = x / tabSize;
@@ -371,6 +447,21 @@ public class EditorUtil {
return result;
}
+ /**
+ * Allows to answer how many visual columns are occupied by the given width.
+ *
+ * @param width target width
+ * @param spaceSize width of the single space symbol within the target editor
+ * @return number of visual columns are occupied by the given width
+ */
+ public static int columnsNumber(int width, int spaceSize) {
+ int result = width / spaceSize;
+ if (width % spaceSize > 0) {
+ result++;
+ }
+ return result;
+ }
+
/**
* Allows to answer what width in pixels is required to draw fragment of the given char array from [start; end) interval
* at the given editor.
@@ -390,10 +481,10 @@ public class EditorUtil {
* from [1; tab size] (check {@link #nextTabStop(int, Editor)} for more details)
* @return width in pixels required for target text representation
*/
- public static int textWidth(@NotNull Editor editor, char[] text, int start, int end, int fontType, int x) {
+ public static int textWidth(@NotNull Editor editor, CharSequence text, int start, int end, int fontType, int x) {
int result = 0;
for (int i = start; i < end; i++) {
- char c = text[i];
+ char c = text.charAt(i);
if (c != '\t') {
FontInfo font = fontForChar(c, fontType, editor);
result += font.charWidth(c, editor.getContentComponent());
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 6f4d4290843a..2870ab265133 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
@@ -470,24 +470,27 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener {
private VerticalInfo createVerticalInfo(LogicalPosition position) {
Document document = myEditor.getDocument();
- int line = position.line;
+ int logicalLine = position.line;
// There is a possible case that active logical line is represented on multiple lines due to soft wraps processing.
// We want to highlight those visual lines as 'active' then, so, we calculate 'y' position for the logical line start
// and height in accordance with the number of occupied visual lines.
- int y = myEditor.logicalPositionToXY(myEditor.offsetToLogicalPosition(document.getLineStartOffset(line))).y;
+ LogicalPosition logicalPosition = myEditor.offsetToLogicalPosition(document.getLineStartOffset(logicalLine));
+ VisualPosition visualPosition = myEditor.logicalToVisualPosition(logicalPosition);
+ int y = myEditor.visualPositionToXY(visualPosition).y;
int height = myEditor.getLineHeight();
- if (line < document.getLineCount() - 1) {
- int nextLineY = myEditor.logicalPositionToXY(myEditor.offsetToLogicalPosition(document.getLineStartOffset(line + 1))).y;
- int heightCandidate = nextLineY - y;
-
- // There is a possible case that active line is the one that ends with folding, so, 'y' position
- // of its next logical line is the same as the previous. We explicitly check that in order to use non-standard
- // line height only in case of visible soft-wrapped line.
- if (heightCandidate > height) {
- height = heightCandidate;
+ int visualLine = visualPosition.line + 1;
+ while (true) {
+ LogicalPosition logical = myEditor.visualToLogicalPosition(new VisualPosition(visualLine, 0));
+ if (logical.line == logicalLine) {
+ height += myEditor.getLineHeight();
+ visualLine++;
+ }
+ else {
+ break;
}
}
+
return new VerticalInfo(y, height);
}
diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/DefaultEditorTextRepresentationHelper.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/DefaultEditorTextRepresentationHelper.java
index ca7ef098ee32..a866ba0daa4f 100644
--- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/DefaultEditorTextRepresentationHelper.java
+++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/DefaultEditorTextRepresentationHelper.java
@@ -17,6 +17,9 @@ package com.intellij.openapi.editor.impl;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.ex.util.EditorUtil;
+import org.jetbrains.annotations.NotNull;
+
+import java.awt.*;
/**
* @author Denis Zhdanov
@@ -31,17 +34,17 @@ public class DefaultEditorTextRepresentationHelper implements EditorTextRepresen
}
@Override
- public int toVisualColumnSymbolsNumber(CharSequence text, int start, int end, int x) {
+ public int toVisualColumnSymbolsNumber(@NotNull CharSequence text, int start, int end, int x) {
return EditorUtil.textWidthInColumns(myEditor, text, start, end, x);
}
@Override
- public int charWidth(char c, int x, int fontType) {
- if (c == '\t') {
- return EditorUtil.nextTabStop(x, myEditor) - x;
- }
- else {
- return EditorUtil.charWidth(c, fontType, myEditor);
- }
+ public int toVisualColumnSymbolsNumber(int width) {
+ return EditorUtil.columnsNumber(width, EditorUtil.getSpaceWidth(Font.PLAIN, myEditor));
+ }
+
+ @Override
+ public int textWidth(@NotNull CharSequence text, int start, int end, int x) {
+ return EditorUtil.textWidth(myEditor, text, start, end, Font.PLAIN, x);
}
}
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 122d1f28fcca..5bda19660b94 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
@@ -440,7 +440,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
}
@NotNull
- public FoldingModel getFoldingModel() {
+ public FoldingModelEx getFoldingModel() {
return myFoldingModel;
}
@@ -902,7 +902,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
// Process 'after soft wrap' sign.
prevX = x;
- charWidth = mySoftWrapModel.getMinDrawingWidth(SoftWrapDrawingType.AFTER_SOFT_WRAP);
+ charWidth = mySoftWrapModel.getMinDrawingWidthInPixels(SoftWrapDrawingType.AFTER_SOFT_WRAP);
x += charWidth;
if (x >= px) {
break outer;
@@ -983,16 +983,15 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
return offsetToLogicalPosition(offset, true);
}
+ @NotNull
+ @Override
public LogicalPosition offsetToLogicalPosition(int offset, boolean softWrapAware) {
+ if (softWrapAware) {
+ return mySoftWrapModel.offsetToLogicalPosition(offset);
+ }
int line = calcLogicalLineNumber(offset, false);
int column = calcColumnNumber(offset, line, false);
- LogicalPosition position = new LogicalPosition(line, column);
- if (softWrapAware) {
- return mySoftWrapModel.adjustLogicalPosition(position, offset);
- }
- else {
- return position;
- }
+ return new LogicalPosition(line, column);
}
@NotNull
@@ -1055,7 +1054,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
if (i >= 0) {
start = i + 1;
}
- return new Point(EditorUtil.textWidth(this, softWrapChars, start, column + 1, Font.PLAIN, 0), y);
+ return new Point(EditorUtil.textWidth(this, softWrap.getText(), start, column + 1, Font.PLAIN, 0), y);
}
break;
}
@@ -1319,6 +1318,8 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
}
void paint(Graphics g) {
+ //TODO den remove
+ System.out.printf("EditorImpl.paint(): (%d; %d) - [%d; %d]%n", g.getClipBounds().x, g.getClipBounds().y, g.getClipBounds().width, g.getClipBounds().height);
startOptimizedScrolling();
if (myCursorUpdater != null) {
@@ -2667,10 +2668,16 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
@NotNull
public LogicalPosition visualToLogicalPosition(@NotNull VisualPosition visiblePos) {
- assertReadAccess();
- if (!myFoldingModel.isFoldingEnabled() && !mySoftWrapModel.isSoftWrappingEnabled()) {
- return new LogicalPosition(visiblePos.line, visiblePos.column);
+ return visualToLogicalPosition(visiblePos, true);
+ }
+
+ @NotNull
+ public LogicalPosition visualToLogicalPosition(@NotNull VisualPosition visiblePos, boolean softWrapAware) {
+ if (softWrapAware) {
+ return mySoftWrapModel.visualToLogicalPosition(visiblePos);
}
+ assertReadAccess();
+ if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column);
int line = visiblePos.line;
int column = visiblePos.column;
@@ -2678,35 +2685,23 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
FoldRegion lastCollapsedBefore = getLastCollapsedBeforePosition(visiblePos);
if (lastCollapsedBefore != null) {
- LogicalPosition softWrapAwareLogFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset());
- VisualPosition softWrapAwareVisFoldEnd = logicalToVisualPosition(softWrapAwareLogFoldEnd);
- if (softWrapAwareVisFoldEnd.line == visiblePos.line) {
- if (visiblePos.column == softWrapAwareVisFoldEnd.column) {
- return softWrapAwareLogFoldEnd;
- }
- else if (visiblePos.column > softWrapAwareVisFoldEnd.column) {
- int columnToUse = softWrapAwareLogFoldEnd.column + visiblePos.column - softWrapAwareVisFoldEnd.column;
- return new LogicalPosition(
- softWrapAwareLogFoldEnd.line, columnToUse, softWrapAwareLogFoldEnd.softWrapLinesBeforeCurrentLogicalLine,
- softWrapAwareLogFoldEnd.softWrapLinesOnCurrentLogicalLine, visiblePos.column - columnToUse - softWrapAwareLogFoldEnd.foldingColumnDiff,
- softWrapAwareLogFoldEnd.foldedLines, softWrapAwareLogFoldEnd.foldingColumnDiff
- );
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset(), false);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd, false);
+
+ line = logFoldEnd.line + (visiblePos.line - visFoldEnd.line);
+ if (visFoldEnd.line == visiblePos.line) {
+ if (visiblePos.column >= visFoldEnd.column) {
+ column = logFoldEnd.column + (visiblePos.column - visFoldEnd.column);
}
else {
- return offsetToLogicalPosition(lastCollapsedBefore.getStartOffset());
+ return offsetToLogicalPosition(lastCollapsedBefore.getStartOffset(), false);
}
}
-
- LogicalPosition softWrapUnawareLogFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset(), false);
- VisualPosition softWrapUnawareVisFoldEnd = logicalToVisualPosition(softWrapUnawareLogFoldEnd, false);
- line = softWrapUnawareLogFoldEnd.line + (visiblePos.line - softWrapUnawareVisFoldEnd.line);
}
if (column < 0) column = 0;
- line = Math.min(line, myDocument.getLineCount() - 1);
- LogicalPosition softWrapUnawareResult = new LogicalPosition(line, column);
- return mySoftWrapModel.adjustLogicalPosition(softWrapUnawareResult, visiblePos);
+ return new LogicalPosition(line, column);
}
private int calcLogicalLineNumber(int offset) {
@@ -4844,7 +4839,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
TextChange softWrap = getSoftWrapModel().getSoftWrap(i);
if (softWrap != null) {
column++; // For 'after soft wrap' drawing.
- x = getSoftWrapModel().getMinDrawingWidth(SoftWrapDrawingType.AFTER_SOFT_WRAP);
+ x = getSoftWrapModel().getMinDrawingWidthInPixels(SoftWrapDrawingType.AFTER_SOFT_WRAP);
}
char c = text.charAt(i);
diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorTextRepresentationHelper.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorTextRepresentationHelper.java
index da31f72de73f..6e391667b15e 100644
--- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorTextRepresentationHelper.java
+++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorTextRepresentationHelper.java
@@ -15,6 +15,8 @@
*/
package com.intellij.openapi.editor.impl;
+import org.jetbrains.annotations.NotNull;
+
/**
* Strategy interface for various utility methods used for representing document text at the editor.
*
@@ -37,16 +39,28 @@ public interface EditorTextRepresentationHelper {
* @param x 'x' offset from the visual line start
* @return number of visual columns necessary for the target text sub-sequence representation
*/
- int toVisualColumnSymbolsNumber(CharSequence text, int start, int end, int x);
+ int toVisualColumnSymbolsNumber(@NotNull CharSequence text, int start, int end, int x);
/**
- * Allows to retrieve width (in pixels) necessary to represent given symbol at the given 'x' offset from
- * visual line start using given font type.
+ * Allows to answer how many visual columns is necessary for representing text of the given width.
*
- * @param c target symbol which width should be calculated
- * @param x current 'x' of the visual line start to use for the target symbol representation
- * @param fontType font type to use for representing given symbol
- * @return number of pixels necessary for the given symbol representation
+ * @param width target width
+ * @return number of visual columns necessary for representation of the text with the given width
*/
- int charWidth(char c, int x, int fontType);
+ int toVisualColumnSymbolsNumber(int width);
+
+ /**
+ * Allows to retrieve width (in pixels) necessary to represent given region ([start; end)) starting
+ * at the given 'x' offset from visual line start using given font type.
+ *
+ * Note: target region is allows to contain line feeds, the width is calculated as a difference between 'x'
+ * coordinates of the last and first symbols.
+ *
+ * @param text target text holder
+ * @param start start offset of the target text sub-sequence (inclusive)
+ * @param end end offset of the target text sub-sequence (exclusive)
+ * @param x 'x' offset from the visual line start
+ * @return width in pixels necessary for the target text sub-sequence representation
+ */
+ int textWidth(@NotNull CharSequence text, int start, int end, int x);
}
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 8dc8a3acfb08..3dd07cde9a4b 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
@@ -43,7 +43,7 @@ import java.util.List;
*/
public class SoftWrapModelImpl implements SoftWrapModelEx {
- private final SoftWrapDataMapper myDataAdjuster;
+ private final SoftWrapDataMapper myDataMapper;
private final SoftWrapsStorage myStorage;
private final SoftWrapPainter myPainter;
private final SoftWrapApplianceManager myApplianceManager;
@@ -60,20 +60,20 @@ public class SoftWrapModelImpl implements SoftWrapModelEx {
public SoftWrapModelImpl(@NotNull final EditorEx editor, @NotNull SoftWrapsStorage storage, @NotNull SoftWrapPainter painter) {
this(
editor, storage, painter, new DefaultSoftWrapApplianceManager(storage, editor, painter),
- new SoftWrapDataMapper(editor, storage, painter, new DefaultEditorTextRepresentationHelper(editor)),
+ new SoftWrapDataMapper(editor, storage, new DefaultEditorTextRepresentationHelper(editor)),
new SoftWrapDocumentChangeManager(editor, storage)
);
}
public SoftWrapModelImpl(@NotNull EditorEx editor, @NotNull SoftWrapsStorage storage, @NotNull SoftWrapPainter painter,
- @NotNull SoftWrapApplianceManager applianceManager, @NotNull SoftWrapDataMapper dataAdjuster,
+ @NotNull SoftWrapApplianceManager applianceManager, @NotNull SoftWrapDataMapper dataMapper,
@NotNull SoftWrapDocumentChangeManager documentChangeManager)
{
myEditor = editor;
myStorage = storage;
myPainter = painter;
myApplianceManager = applianceManager;
- myDataAdjuster = dataAdjuster;
+ myDataMapper = dataMapper;
myDocumentChangeManager = documentChangeManager;
}
@@ -184,25 +184,39 @@ public class SoftWrapModelImpl implements SoftWrapModelEx {
}
@Override
- public int getMinDrawingWidth(@NotNull SoftWrapDrawingType drawingType) {
+ public int getMinDrawingWidthInPixels(@NotNull SoftWrapDrawingType drawingType) {
return myPainter.getMinDrawingWidth(drawingType);
}
+ @Override
+ public int getMinDrawingWidthInColumns(@NotNull SoftWrapDrawingType drawingType) {
+ return myPainter.getMinDrawingWidth(drawingType) > 0 ? 1 : 0;
+ }
+
@NotNull
- public LogicalPosition adjustLogicalPosition(@NotNull LogicalPosition defaultLogical, @NotNull VisualPosition visual) {
+ @Override
+ public LogicalPosition visualToLogicalPosition(@NotNull VisualPosition visual) {
if (myActive > 0 || !isSoftWrappingEnabled() || myStorage.isEmpty() || myEditor.getDocument().getTextLength() <= 0) {
- return defaultLogical;
+ return myEditor.visualToLogicalPosition(visual, false);
}
-
- if (defaultLogical.visualPositionAware) {
- return defaultLogical;
- }
-
myActive++;
try {
- return myDataAdjuster.adjustLogicalPosition(defaultLogical, visual);
+ return myDataMapper.visualToLogical(visual);
+ } finally {
+ myActive--;
}
- finally {
+ }
+
+ @NotNull
+ @Override
+ public LogicalPosition offsetToLogicalPosition(int offset) {
+ if (myActive > 0 || !isSoftWrappingEnabled() || myStorage.isEmpty() || myEditor.getDocument().getTextLength() <= 0) {
+ return myEditor.offsetToLogicalPosition(offset, false);
+ }
+ myActive++;
+ try {
+ return myDataMapper.offsetToLogicalPosition(offset);
+ } finally {
myActive--;
}
}
@@ -215,7 +229,7 @@ public class SoftWrapModelImpl implements SoftWrapModelEx {
myActive++;
try {
- return myDataAdjuster.offsetToLogicalPosition(offset);
+ return myDataMapper.offsetToLogicalPosition(offset);
} finally {
myActive--;
}
@@ -229,7 +243,7 @@ public class SoftWrapModelImpl implements SoftWrapModelEx {
myActive++;
try {
- return myDataAdjuster.adjustVisualPosition(logical, defaultVisual);
+ return myDataMapper.adjustVisualPosition(logical, defaultVisual);
}
finally {
myActive--;
@@ -292,7 +306,7 @@ public class SoftWrapModelImpl implements SoftWrapModelEx {
}
if (start < end) {
- result += EditorUtil.textWidth(myEditor, chars, start, end, Font.PLAIN, 0);
+ result += EditorUtil.textWidth(myEditor, softWrap.getText(), start, end, Font.PLAIN, 0);
}
return result;
diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/DefaultSoftWrapApplianceManager.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/DefaultSoftWrapApplianceManager.java
index 876d7bd3ea71..9b7a7fdd8ee2 100644
--- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/DefaultSoftWrapApplianceManager.java
+++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/DefaultSoftWrapApplianceManager.java
@@ -29,6 +29,8 @@ import gnu.trove.TIntArrayList;
import gnu.trove.TIntHashSet;
import org.jetbrains.annotations.NotNull;
+import java.nio.CharBuffer;
+
/**
* Default {@link SoftWrapApplianceManager} implementation that is built with the following design guide lines:
*
@@ -214,7 +216,7 @@ public class DefaultSoftWrapApplianceManager implements SoftWrapApplianceManager
int x = myPainter.getMinDrawingWidth(SoftWrapDrawingType.BEFORE_SOFT_WRAP_LINE_FEED);
int prevSoftWrapOffset = start;
for (int i = start; i < end; i++) {
- int symbolWidth = EditorUtil.textWidth(myEditor, text, i, i + 1, fontType, x);
+ int symbolWidth = EditorUtil.textWidth(myEditor, CharBuffer.wrap(text), i, i + 1, fontType, x);
if (x + symbolWidth >= myVisibleAreaWidth) {
int offset = calculateSoftWrapOffset(text, i - 1, prevSoftWrapOffset, end);
if (offset >= end || offset <= prevSoftWrapOffset) {
diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/SoftWrapDataMapper.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/SoftWrapDataMapper.java
index 99cd59788f5f..cc965b8ff252 100644
--- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/SoftWrapDataMapper.java
+++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/SoftWrapDataMapper.java
@@ -18,14 +18,11 @@ package com.intellij.openapi.editor.impl.softwrap;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.impl.EditorTextRepresentationHelper;
-import com.intellij.openapi.editor.impl.IterationState;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.text.CharArrayUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-import java.awt.*;
-import java.nio.CharBuffer;
import java.util.List;
/**
@@ -37,171 +34,44 @@ import java.util.List;
*/
public class SoftWrapDataMapper {
- private static final VisualPosition DUMMY_VISUAL = new VisualPosition(Integer.MAX_VALUE, Integer.MAX_VALUE);
-
- private final CharBuffer myCharBuffer = CharBuffer.allocate(1);
-
private final EditorTextRepresentationHelper myTextRepresentationHelper;
private final EditorEx myEditor;
private final SoftWrapsStorage myStorage;
- private final SoftWrapPainter myPainter;
- private final FontTypeProvider myFontTypeProvider;
+ //private final FontTypeProvider myFontTypeProvider;
+
+ //public SoftWrapDataMapper(EditorEx editor,
+ // SoftWrapsStorage storage,
+ // EditorTextRepresentationHelper textRepresentationHelper)
+ //{
+ // this(editor, storage, textRepresentationHelper, new IterationStateFontTypeProvider(editor));
+ //}
public SoftWrapDataMapper(EditorEx editor,
SoftWrapsStorage storage,
- SoftWrapPainter painter,
- EditorTextRepresentationHelper textRepresentationHelper)
- {
- this(editor, storage, painter, textRepresentationHelper, new IterationStateFontTypeProvider(editor));
- }
-
- SoftWrapDataMapper(EditorEx editor,
- SoftWrapsStorage storage,
- SoftWrapPainter painter,
- EditorTextRepresentationHelper textRepresentationHelper,
- FontTypeProvider fontTypeProvider)
+ EditorTextRepresentationHelper textRepresentationHelper/*,
+ FontTypeProvider fontTypeProvider*/)
{
myEditor = editor;
myStorage = storage;
- myPainter = painter;
myTextRepresentationHelper = textRepresentationHelper;
- myFontTypeProvider = fontTypeProvider;
+ //myFontTypeProvider = fontTypeProvider;
}
@NotNull
- public LogicalPosition adjustLogicalPosition(@NotNull LogicalPosition defaultLogical, @NotNull VisualPosition visual) {
- try {
- return doAdjustLogicalPosition(defaultLogical, visual);
- }
- finally {
- myFontTypeProvider.cleanup();
- }
+ public LogicalPosition visualToLogical(@NotNull VisualPosition visual) {
+ return toLogical(new VisualPositionBasedStrategy(visual));
}
- @SuppressWarnings({"AssignmentToForLoopParameter"})
@NotNull
- private LogicalPosition doAdjustLogicalPosition(@NotNull LogicalPosition defaultLogical, @NotNull VisualPosition visual) {
- Document document = myEditor.getDocument();
- int maxOffset = document.getLineEndOffset(Math.min(defaultLogical.line, document.getLineCount() - 1));
+ public LogicalPosition offsetToLogicalPosition(int offset) {
+ OffsetBasedStrategy strategy = new OffsetBasedStrategy(myTextRepresentationHelper, myEditor.getDocument(), offset);
+ return toLogical(strategy);
+ }
- // This index points to registered soft wrap that is guaranteed to be located after the target visual line.
- int endIndex = myStorage.getSoftWrapIndex(maxOffset + 1);
- if (endIndex < 0) {
- endIndex = -endIndex - 1;
- }
-
- int softWrapLinesBeforeCurrentLogicalLine = 0;
- int softWrapLinesOnCurrentLogicalLine = 0;
- int lastSoftWrapLogicalLine = -1;
-
- FoldingModel foldingModel = myEditor.getFoldingModel();
- int i = 0;
- List softWraps = myStorage.getSoftWraps();
- int max = Math.min(softWraps.size(), endIndex);
- for (; i < max; i++) {
- TextChange softWrap = softWraps.get(i);
- if (!isVisible(softWrap)) {
- continue;
- }
-
- int currentSoftWrapLineFeeds = StringUtil.countNewLines(softWrap.getText());
- int softWrapLine = document.getLineNumber(softWrap.getStart());
- int visualLineBeforeSoftWrapAppliance = myEditor.logicalToVisualPosition(new LogicalPosition(softWrapLine, 0)).line
- + softWrapLinesBeforeCurrentLogicalLine + softWrapLinesOnCurrentLogicalLine;
- if (visualLineBeforeSoftWrapAppliance > visual.line) {
- softWrapLinesBeforeCurrentLogicalLine += softWrapLinesOnCurrentLogicalLine;
- int logicalLine = defaultLogical.line - softWrapLinesBeforeCurrentLogicalLine;
- return new LogicalPosition(
- logicalLine, defaultLogical.column, softWrapLinesBeforeCurrentLogicalLine, 0, 0,
- getFoldedLinesBefore(document.getLineStartOffset(logicalLine)),
- visual.column - defaultLogical.column
- );
- }
-
- if (lastSoftWrapLogicalLine >= 0 && lastSoftWrapLogicalLine != softWrapLine) {
- softWrapLinesBeforeCurrentLogicalLine += softWrapLinesOnCurrentLogicalLine;
- softWrapLinesOnCurrentLogicalLine = 0;
- }
- lastSoftWrapLogicalLine = softWrapLine;
-
- int visualLineAfterSoftWrapAppliance = visualLineBeforeSoftWrapAppliance + currentSoftWrapLineFeeds;
- if (visualLineAfterSoftWrapAppliance < visual.line) {
- softWrapLinesOnCurrentLogicalLine += currentSoftWrapLineFeeds;
- continue;
- }
-
- int startLineOffset = document.getLineStartOffset(softWrapLine);
- int endLineOffset = document.getLineEndOffset(softWrapLine);
- FoldRegion region = foldingModel.getCollapsedRegionAtOffset(endLineOffset);
- while (region != null) {
- int line = document.getLineNumber(region.getEndOffset());
- endLineOffset = document.getLineEndOffset(line);
- region = foldingModel.getCollapsedRegionAtOffset(endLineOffset);
- }
- CharSequence documentText = document.getCharsSequence();
-
- // If we're here that means that current soft wrap affects logical line that is matched to the given visual line.
- // We iterate from the logical line start then in order to calculate resulting logical position.
- Context context = new Context(
- visual, softWrapLine, softWrapLinesBeforeCurrentLogicalLine, softWrapLinesOnCurrentLogicalLine,
- visualLineBeforeSoftWrapAppliance, getFoldedLinesBefore(startLineOffset)
- );
- myFontTypeProvider.init(startLineOffset);
-
- for (int j = startLineOffset; j < endLineOffset; j++) {
-
- // Process soft wrap at the current offset if any.
- TextChange softWrapToProcess = myStorage.getSoftWrap(j);
- if (softWrapToProcess != null && isVisible(softWrapToProcess)) {
- context.beforeSoftWrap();
- if (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) {
- return result;
- }
- }
- }
- context.afterSoftWrap();
- }
-
- context.fontType = myFontTypeProvider.getFontType(j);
-
- FoldRegion foldRegion = foldingModel.getCollapsedRegionAtOffset(j);
- if (foldRegion != null) {
- LogicalPosition result = context.onCollapsedFolding(foldRegion);
- if (result != null) {
- return result;
- }
- j = foldRegion.getEndOffset();
- }
-
- // Process document symbol.
- LogicalPosition result = context.onNonSoftWrapSymbol(documentText.charAt(j));
- if (result != null) {
- return result;
- }
- }
-
- // If we are here that means that target visual position is located at virtual space after the line end.
- context.logicalColumn += visual.column - context.visualColumn;
- return context.build();
- }
-
- // If we are here that means that there is no soft wrap on a logical line that corresponds to the target visual line.
- softWrapLinesBeforeCurrentLogicalLine += softWrapLinesOnCurrentLogicalLine;
- int logicalLine = defaultLogical.line - softWrapLinesBeforeCurrentLogicalLine;
- // There is a possible case that we can't count on given default logical position - e.g. if given visual position line
- // is more than total document lines count.
- if (logicalLine < 0) {
- logicalLine = Math.min(lastSoftWrapLogicalLine + 1, document.getLineCount() - 1);
- }
- int foldedLines = getFoldedLinesBefore(document.getLineStartOffset(logicalLine));
- int foldingColumnDiff = visual.column - defaultLogical.column;
- return new LogicalPosition(
- logicalLine, defaultLogical.column, softWrapLinesBeforeCurrentLogicalLine, 0, 0, foldedLines, foldingColumnDiff
- );
+ @NotNull
+ private LogicalPosition toLogical(LogicalPositionCalculatorStrategy strategy) {
+ LogicalPositionCalculator calculator = new LogicalPositionCalculator(strategy);
+ return calculator.calculate();
}
@NotNull
@@ -263,175 +133,6 @@ public class SoftWrapDataMapper {
return new VisualPosition(visual.line + lineDiff, columnToUse);
}
- public LogicalPosition offsetToLogicalPosition(int offset) {
- try {
- return doOffsetToLogicalPosition(offset);
- }
- finally {
- myFontTypeProvider.cleanup();
- }
- }
-
- @SuppressWarnings({"AssignmentToForLoopParameter"})
- private LogicalPosition doOffsetToLogicalPosition(int offset) {
- FoldingModel foldingModel = myEditor.getFoldingModel();
- Document document = myEditor.getDocument();
- CharSequence text = document.getCharsSequence();
- int line = document.getLineNumber(offset);
- int lineStartOffset = document.getLineStartOffset(line);
- FoldRegion region = foldingModel.getCollapsedRegionAtOffset(lineStartOffset);
- while (region != null && region.getStartOffset() != lineStartOffset) {
- line = document.getLineNumber(region.getStartOffset());
- lineStartOffset = document.getLineStartOffset(line);
- region = foldingModel.getCollapsedRegionAtOffset(lineStartOffset);
- }
-
- Context context = new Context(line, getSoftWrapIntroducedLinesBefore(lineStartOffset), getFoldedLinesBefore(lineStartOffset));
- myFontTypeProvider.init(lineStartOffset);
- context.fontType = myFontTypeProvider.getFontType(lineStartOffset);
- for (int i = lineStartOffset; i <= offset; i++) {
- TextChangeImpl softWrap = myStorage.getSoftWrap(i);
- if (softWrap != null) {
- context.beforeSoftWrap();
- CharSequence softWrapText = softWrap.getText();
- for (int k = 0; k < softWrapText.length(); k++) {
- context.onSoftWrapSymbol(softWrapText.charAt(k));
- }
- context.afterSoftWrap();
- }
-
- if (i == offset) {
- // We want to count soft wrap that is registered at target offset if any but not exceeding document symbols.
- break;
- }
-
- region = foldingModel.getCollapsedRegionAtOffset(i);
- if (region != null) {
- processFoldRegion(context, region, offset);
- if (offset <= region.getEndOffset()) {
- break;
- }
- i = region.getEndOffset();
- }
- context.fontType = myFontTypeProvider.getFontType(i);
- context.onNonSoftWrapSymbol(text.charAt(i));
- }
- return new LogicalPosition(
- context.logicalLine,
- context.logicalColumn,
- context.softWrapLinesBefore,
- context.targetSoftWrapLines,
- context.softWrapColumnDiff,
- getFoldedLinesBefore(offset),
- context.foldingColumnDiff
- );
- }
-
- /**
- * Processes given collapsed fold region assuming that we need to stop at a target offset.
- *
- * Processing result is updated state of the given context object.
- *
- * @param context processing data holder
- * @param region collapsed fold region to process
- * @param offset target stop offset
- */
- @SuppressWarnings({"AssignmentToForLoopParameter"})
- private void processFoldRegion(Context context, FoldRegion region, int offset) {
- CharSequence text = myEditor.getDocument().getCharsSequence();
- int max = Math.min(offset, region.getEndOffset());
- boolean multilineFolding = false;
- for (int i = region.getStartOffset(); i < max;) {
- int lineFeedOffset = CharArrayUtil.shiftForwardUntil(text, i, "\n");
- if (lineFeedOffset < max) {
- context.softWrapLinesBefore += context.targetSoftWrapLines;
- context.targetSoftWrapLines = 0;
- context.softWrapColumnDiff = 0;
- context.foldedLines++;
- context.logicalColumn = 0;
- context.foldingColumnDiff = context.visualColumn;
- context.logicalLine++;
- i = lineFeedOffset + 1;
- multilineFolding = true;
- }
- else {
- if (multilineFolding) {
- context.logicalColumn = myTextRepresentationHelper.toVisualColumnSymbolsNumber(text, i, max, 0);
- context.foldingColumnDiff = context.visualColumn - context.logicalColumn;
- break;
- }
- else {
- int foldedColumns = myTextRepresentationHelper.toVisualColumnSymbolsNumber(text, region.getStartOffset(), max, context.x);
- context.logicalColumn += foldedColumns;
- context.foldingColumnDiff -= foldedColumns;
- if (offset >= region.getEndOffset()) {
- context.foldingColumnDiff += region.getPlaceholderText().length();
- }
- return;
- }
- }
- }
-
- if (offset >= region.getEndOffset()) {
- int foldPlaceholderColumns = region.getPlaceholderText().length();
- context.visualColumn += foldPlaceholderColumns;
- context.foldingColumnDiff += foldPlaceholderColumns;
- context.x += foldPlaceholderColumns * myTextRepresentationHelper.charWidth(' ', context.x, Font.PLAIN);
- }
- }
-
- /**
- * Allows to answer how many soft wrap-introduced visual lines are located before the given offset.
- *
- * @param offset target offset
- * @return number of soft wrap-introduced visual lines are located before the given offset
- */
- private int getSoftWrapIntroducedLinesBefore(int offset) {
- int result = 0;
- List extends TextChange> softWraps = myStorage.getSoftWraps();
-
- // Calculate number of soft wrap-introduced lines before the line that holds target offset.
- int index = myStorage.getSoftWrapIndex(offset);
- if (index < 0) {
- index = -index - 1;
- }
- int max = Math.min(index, softWraps.size());
- for (int j = 0; j < max; j++) {
- TextChange softWrap = softWraps.get(j);
- if (isVisible(softWrap)) {
- result += StringUtil.countNewLines(softWrap.getText());
- }
- }
- return result;
- }
-
- /**
- * Allows to answer how many folded lines are located before the logical line that contains given offset.
- *
- * @param offset target offset
- * @return number of folded lines are located before the logical line that contains given offset.
- */
- private int getFoldedLinesBefore(int offset) {
- Document document = myEditor.getDocument();
- int line = document.getLineNumber(offset);
- int lineStartOffset = document.getLineStartOffset(line);
- int result = 0;
- for (FoldRegion foldRegion : myEditor.getFoldingModel().getAllFoldRegions()) {
- if (foldRegion.getStartOffset() >= lineStartOffset) {
- break;
- }
-
- if (foldRegion.isExpanded() || !foldRegion.isValid()) {
- continue;
- }
-
- int foldingStartLine = document.getLineNumber(foldRegion.getStartOffset());
- int foldingEndLine = document.getLineNumber(foldRegion.getEndOffset());
- result += Math.min(line, foldingEndLine) - foldingStartLine;
- }
- return result;
- }
-
private boolean isVisible(TextChange softWrap) {
FoldingModel foldingModel = myEditor.getFoldingModel();
int start = softWrap.getStart();
@@ -441,219 +142,345 @@ public class SoftWrapDataMapper {
return !foldingModel.isOffsetCollapsed(start) || !foldingModel.isOffsetCollapsed(start - 1);
}
- private int toVisualColumnSymbolsNumber(char c, int x) {
- myCharBuffer.clear();
- myCharBuffer.put(c);
- myCharBuffer.flip();
- return myTextRepresentationHelper.toVisualColumnSymbolsNumber(myCharBuffer, 0, 1, x);
- }
-
- private class Context {
-
- public final VisualPosition targetVisualPosition;
- public final int visualLineBeforeSoftWrapAppliance;
- public final int softWrapLinesOnCurrentLineBeforeTargetSoftWrap;
+ private static class Context implements Cloneable {
public int logicalLine;
- public int visualLine;
- public int softWrapLinesBefore;
- public int targetSoftWrapLines;
- public int softWrapColumnDiff;
public int logicalColumn;
+ public int visualLine;
public int visualColumn;
+ public int offset;
+ public int softWrapLinesBefore;
+ public int softWrapLinesCurrent;
+ public int softWrapColumnDiff;
public int foldedLines;
public int foldingColumnDiff;
public int x;
- public int fontType;
- Context(int logicalLine, int softWrapLinesBefore, int foldedLines) {
- this(DUMMY_VISUAL, logicalLine, softWrapLinesBefore, 0, 0, foldedLines);
+ @NotNull
+ public LogicalPosition build() {
+ return new LogicalPosition(
+ logicalLine, logicalColumn, softWrapLinesBefore, softWrapLinesCurrent, softWrapColumnDiff, foldedLines, foldingColumnDiff
+ );
}
- Context(VisualPosition targetVisualPosition, int logicalLine, int softWrapLinesBefore,
- int softWrapLinesOnCurrentLineBeforeTargetSoftWrap, int visualLineBeforeSoftWrapAppliance, int foldedLines)
- {
- this.targetVisualPosition = targetVisualPosition;
- this.softWrapLinesBefore = softWrapLinesBefore;
- this.softWrapLinesOnCurrentLineBeforeTargetSoftWrap = softWrapLinesOnCurrentLineBeforeTargetSoftWrap;
- this.visualLineBeforeSoftWrapAppliance = visualLineBeforeSoftWrapAppliance;
- this.foldedLines = foldedLines;
- this.logicalLine = logicalLine;
- visualLine = visualLineBeforeSoftWrapAppliance + targetSoftWrapLines;
+ @Override
+ protected Context clone() {
+ Context result = new Context();
+ result.logicalLine = logicalLine;
+ result.logicalColumn = logicalColumn;
+ result.visualLine = visualLine;
+ result.visualColumn = visualColumn;
+ result.offset = offset;
+ result.softWrapLinesBefore = softWrapLinesBefore;
+ result.softWrapLinesCurrent = softWrapLinesCurrent;
+ result.softWrapColumnDiff = softWrapColumnDiff;
+ result.foldedLines = foldedLines;
+ result.foldingColumnDiff = foldingColumnDiff;
+ result.x = x;
+ return result;
}
- /**
- * Updates current context within the soft wrap symbol.
- *
- * @param c soft wrap symbol to process
- * @return logical position that matches target visual position if given symbol processing makes it possible to calculate it;
- * null otherwise
- */
- @Nullable
- public LogicalPosition onSoftWrapSymbol(char c) {
- // Process line feed inside soft wrap.
- if (c == '\n') {
- if (targetVisualPosition.line == visualLineBeforeSoftWrapAppliance + targetSoftWrapLines) {
- softWrapColumnDiff = targetVisualPosition.column - logicalColumn;
- return build();
+ private void onNewLine() {
+ softWrapLinesBefore += softWrapLinesCurrent;
+ softWrapLinesCurrent = 0;
+ softWrapColumnDiff = 0;
+ foldingColumnDiff = 0;
+ }
+ }
+
+ private class LogicalPositionCalculator {
+
+ public final LogicalPositionCalculatorStrategy strategy;
+
+ public Context context = new Context();
+
+ LogicalPositionCalculator(LogicalPositionCalculatorStrategy strategy) {
+ this.strategy = strategy;
+ }
+
+ @NotNull
+ public LogicalPosition calculate() {
+ FoldingProvider foldRegions = new FoldingProvider();
+ SoftWrapsProvider softWraps = new SoftWrapsProvider();
+
+ FoldRegion foldRegion = foldRegions.get();
+ TextChange softWrap = softWraps.get();
+
+ LogicalPosition result = null;
+ while (true) {
+ if (foldRegion == null && softWrap == null || strategy.exceeds(context)) {
+ return strategy.build(context);
}
- else {
- x = 0;
- softWrapColumnDiff = -logicalColumn;
- targetSoftWrapLines++;
- visualLine++;
- visualColumn = 0;
- return null;
- }
- }
- softWrapColumnDiff++;
-
- // Just update information about tracked symbols number if current visual line is too low.
- if (targetVisualPosition.line > visualLineBeforeSoftWrapAppliance + targetSoftWrapLines) {
- visualColumn += toVisualColumnSymbolsNumber(c, x);
- x += myTextRepresentationHelper.charWidth(c, x, fontType);
- return null;
- }
-
- // There is a possible case that, for example, target visual column is zero and it points to the soft-wrapped line,
- // i.e. soft wrap are. We shouldn't count symbols then. Hence, we perform this preliminary examination with eager
- // return if necessary.
- if (targetVisualPosition.column <= visualColumn) {
- return build();
- }
-
- // Process non-line feed inside soft wrap.
- visualColumn++; // Don't expect tabulation to be used inside soft wrap text.
- x += myTextRepresentationHelper.charWidth(c, x, fontType);
-
- if (targetVisualPosition.column <= visualColumn) {
- return build();
- }
- else {
- return null;
- }
- }
-
- public void beforeSoftWrap() {
- x = 0;
- }
-
- public void afterSoftWrap() {
- x += myPainter.getMinDrawingWidth(SoftWrapDrawingType.AFTER_SOFT_WRAP);
- visualColumn++;
- softWrapColumnDiff++;
- }
-
- @SuppressWarnings({"AssignmentToForLoopParameter"})
- @Nullable
- public LogicalPosition onCollapsedFolding(FoldRegion region) {
- int visualFoldingPlaceholderWidth = region.getPlaceholderText().length(); // Assuming that no tabs are used as placeholder
-
- // Process situation when target visual position points to collapsed folding placeholder.
- if (visualLine == targetVisualPosition.line && visualColumn + visualFoldingPlaceholderWidth > targetVisualPosition.column) {
- return build();
- }
-
- // If control flow reaches this point that means that we should process whole folded region and update current object state.
- CharSequence text = myEditor.getDocument().getCharsSequence();
- boolean multiline = false;
- for (int i = region.getStartOffset(); i < region.getEndOffset();) {
- int lineFeedOffset = CharArrayUtil.shiftForwardUntil(text, i, "\n");
- // Process multiline folded text.
- if (lineFeedOffset < region.getEndOffset()) {
- logicalLine++;
- foldedLines++;
- logicalColumn = 0;
- softWrapLinesBefore += targetSoftWrapLines;
- targetSoftWrapLines = 0;
- softWrapColumnDiff = 0;
- i = lineFeedOffset + 1;
- multiline = true;
- }
- else {
- if (multiline) {
- logicalColumn = myTextRepresentationHelper.toVisualColumnSymbolsNumber(text, i, region.getEndOffset(), 0);
+ if (foldRegion != null && softWrap != null) {
+ if (softWrap.getStart() <= foldRegion.getStartOffset()) {
+ result = process(softWrap);
+ softWrap = softWraps.get();
}
else {
- logicalColumn += myTextRepresentationHelper.toVisualColumnSymbolsNumber(text, i, region.getEndOffset(), x);
+ result = process(foldRegion);
+ foldRegion = foldRegions.get();
}
- foldingColumnDiff = visualColumn + visualFoldingPlaceholderWidth - logicalColumn - softWrapColumnDiff;
- i = region.getEndOffset();
+ }
+ else {
+ if (foldRegion != null) {
+ result = process(foldRegion);
+ foldRegion = foldRegions.get();
+ }
+ if (softWrap != null) {
+ result = process(softWrap);
+ softWrap = softWraps.get();
+ }
+ }
+ if (result != null) {
+ return result;
}
}
-
- visualColumn += visualFoldingPlaceholderWidth;
- x += visualFoldingPlaceholderWidth * myTextRepresentationHelper.charWidth(' ', x, Font.PLAIN);
- if (visualLine == targetVisualPosition.line && visualColumn == targetVisualPosition.column) {
- return build();
- }
- return null;
}
- /**
- * Updates current context within the non-soft wrap symbol.
- *
- * @param c soft wrap symbol to process
- * @return logical position that matches target visual position if given symbol processing makes it possible to calculate it;
- * null otherwise
- */
@Nullable
- public LogicalPosition onNonSoftWrapSymbol(char c) {
- // Don't expect line feed symbol to be delivered to this method in assumption that we process only one logical line here.
- if (c == '\n') {
- x = 0;
- assert false;
+ private LogicalPosition process(@NotNull FoldRegion region) {
+ if (region.getStartOffset() > context.offset) {
+ Context newContext = advanceToOffset(region.getStartOffset());
+ if (strategy.exceeds(newContext)) {
+ return strategy.build(context);
+ }
+ context = newContext;
+ }
+
+ Document document = myEditor.getDocument();
+ CharSequence text = document.getCharsSequence();
+ int foldingStartLine = document.getLineNumber(region.getStartOffset());
+
+ Context afterFolding = context.clone();
+ afterFolding.logicalLine += document.getLineNumber(region.getEndOffset()) - foldingStartLine;
+ int visualColumnInc = region.getPlaceholderText().length(); // Assuming that no tabulations are used at placeholder.
+ afterFolding.visualColumn += visualColumnInc;
+
+ int i = CharArrayUtil.shiftBackwardUntil(text, region.getEndOffset() - 1, "\n");
+ // Process multi-line folding.
+ if (i >= region.getStartOffset()) {
+ int width = myTextRepresentationHelper.textWidth(text, i + 1, region.getEndOffset(), 0);
+ afterFolding.logicalColumn = myTextRepresentationHelper.toVisualColumnSymbolsNumber(width);
+ afterFolding.x = width;
+ afterFolding.softWrapLinesBefore += afterFolding.softWrapLinesCurrent;
+ afterFolding.softWrapLinesCurrent = 0;
+ afterFolding.softWrapColumnDiff = 0;
+ afterFolding.foldedLines += document.getLineNumber(region.getEndOffset()) - foldingStartLine;
+ afterFolding.foldingColumnDiff = afterFolding.visualColumn - afterFolding.logicalColumn;
+ }
+ // Process single-line folding
+ else {
+ int width = myTextRepresentationHelper.textWidth(text, region.getStartOffset(), region.getEndOffset(), context.x);
+ int logicalColumnInc = myTextRepresentationHelper.toVisualColumnSymbolsNumber(width);
+ afterFolding.logicalColumn += logicalColumnInc;
+ afterFolding.x += width;
+ afterFolding.foldingColumnDiff += visualColumnInc - logicalColumnInc;
+ }
+ afterFolding.offset = region.getEndOffset();
+
+ if (!strategy.exceeds(afterFolding)) {
+ context = afterFolding;
return null;
}
- // Just update information about tracked symbols number if current visual line is too low.
- if (targetVisualPosition.line > visualLine) {
- int columnsForSymbol = toVisualColumnSymbolsNumber(c, x);
- visualColumn += columnsForSymbol;
- logicalColumn += columnsForSymbol;
- x += myTextRepresentationHelper.charWidth(c, x, fontType);
- return null;
- }
+ return strategy.build(context, region);
+ }
- // There is a possible case that, for example, target visual column is zero. We shouldn't count symbols then.
- // Hence, we perform this preliminary examination with eager return if necessary.
- if (targetVisualPosition.column <= visualColumn) {
- return build();
+ @Nullable
+ private LogicalPosition process(@NotNull TextChange softWrap) {
+ Context newContext = advanceToOffset(softWrap.getStart());
+ if (strategy.exceeds(newContext)) {
+ return strategy.build(context);
}
+ Document document = myEditor.getDocument();
+ int lastUsedLogicalLine = document.getLineNumber(context.offset);
+ context = newContext;
- int columnsForSymbol = toVisualColumnSymbolsNumber(c, x);
- int diffInColumns = targetVisualPosition.column - visualColumn;
- int incrementToUse = columnsForSymbol;
- if (columnsForSymbol >= diffInColumns) {
- incrementToUse = Math.min(columnsForSymbol, diffInColumns);
- }
- visualColumn += incrementToUse;
- logicalColumn += incrementToUse;
- x += myTextRepresentationHelper.charWidth(c, x, fontType);
-
- if (targetVisualPosition.column <= visualColumn) {
- return build();
+ // Create context that points to the soft wrap end visual position.
+ Context afterSoftWrap = context.clone();
+ int lineFeeds = StringUtil.countNewLines(softWrap.getText());
+ afterSoftWrap.visualLine += lineFeeds;
+ afterSoftWrap.visualColumn = myEditor.getSoftWrapModel().getSoftWrapIndentWidthInColumns(softWrap);
+ afterSoftWrap.x = myEditor.getSoftWrapModel().getSoftWrapIndentWidthInPixels(softWrap);
+ if (lastUsedLogicalLine == context.logicalLine) {
+ afterSoftWrap.softWrapLinesCurrent += lineFeeds;
}
else {
+ afterSoftWrap.softWrapLinesBefore += context.softWrapLinesCurrent;
+ afterSoftWrap.softWrapLinesCurrent = lineFeeds;
+ }
+ afterSoftWrap.softWrapColumnDiff = afterSoftWrap.visualColumn - afterSoftWrap.logicalColumn;
+ afterSoftWrap.foldingColumnDiff = 0;
+
+ if (!strategy.exceeds(afterSoftWrap)) {
+ context = afterSoftWrap;
return null;
}
+ return strategy.build(context, softWrap);
}
- private LogicalPosition build() {
- return build(foldingColumnDiff);
+ private Context advanceToOffset(int newOffset) {
+ Context result = context.clone();
+ if (result.offset == newOffset) {
+ return result;
+ }
+
+ Document document = myEditor.getDocument();
+ CharSequence text = document.getCharsSequence();
+ int lastUsedLogicalLine = document.getLineNumber(context.offset);
+ int currentLogicalLine = document.getLineNumber(newOffset);
+
+ // Update state to the offset that corresponds to the same logical line that was used last time.
+ if (currentLogicalLine == lastUsedLogicalLine) {
+ int width = myTextRepresentationHelper.textWidth(text, result.offset, newOffset, result.x);
+ int columnDiff = myTextRepresentationHelper.toVisualColumnSymbolsNumber(width);
+ result.x += width;
+ result.logicalColumn += columnDiff;
+ result.visualColumn += columnDiff;
+ }
+ // Update state to the start of the folding that doesn't belong to the same logical line that was used last time.
+ else {
+ int lineDiff = currentLogicalLine - lastUsedLogicalLine;
+ result.logicalLine += lineDiff;
+ result.visualLine += lineDiff;
+ int startLineOffset = document.getLineStartOffset(currentLogicalLine);
+ result.x = myTextRepresentationHelper.textWidth(text, startLineOffset, newOffset, result.x);
+ result.visualColumn = myTextRepresentationHelper.toVisualColumnSymbolsNumber(result.x);
+ result.logicalColumn = result.visualColumn;
+ result.onNewLine();
+ }
+ result.offset = newOffset;
+ return result;
+ }
+ }
+
+ private interface LogicalPositionCalculatorStrategy {
+ boolean exceeds(Context context);
+ @NotNull LogicalPosition build(Context context);
+ @NotNull LogicalPosition build(Context context, FoldRegion region);
+ @NotNull LogicalPosition build(Context context, TextChange softWrap);
+ }
+
+ private static class VisualPositionBasedStrategy implements LogicalPositionCalculatorStrategy {
+
+ private final VisualPosition myTargetVisual;
+
+ VisualPositionBasedStrategy(VisualPosition visual) {
+ myTargetVisual = visual;
}
- private LogicalPosition build(int foldingColumnDiff) {
- return new LogicalPosition(
- logicalLine,
- logicalColumn,
- softWrapLinesBefore,
- softWrapLinesOnCurrentLineBeforeTargetSoftWrap + targetSoftWrapLines,
- softWrapColumnDiff,
- foldedLines,
- foldingColumnDiff
- );
+ @Override
+ public boolean exceeds(Context context) {
+ return context.visualLine > myTargetVisual.line
+ || (context.visualLine == myTargetVisual.line && context.visualColumn > myTargetVisual.column);
+ }
+
+ @NotNull
+ @Override
+ public LogicalPosition build(Context context) {
+ if (context.visualLine == myTargetVisual.line) {
+ context.logicalColumn += myTargetVisual.column - context.visualColumn;
+ return context.build();
+ }
+ context.logicalLine += myTargetVisual.line - context.visualLine;
+ context.logicalColumn = myTargetVisual.column;
+ context.onNewLine();
+ return context.build();
+ }
+
+ @NotNull
+ @Override
+ public LogicalPosition build(Context context, FoldRegion region) {
+ // We just point to the logical position of folding region start if visual position points to collapsed fold region placeholder.
+ return context.build();
+ }
+
+ @NotNull
+ @Override
+ public LogicalPosition build(Context context, TextChange softWrap) {
+ if (myTargetVisual.line == context.visualLine) {
+ context.softWrapColumnDiff = myTargetVisual.column - context.logicalColumn - context.foldingColumnDiff;
+ }
+ else {
+ context.foldingColumnDiff = 0;
+ context.softWrapLinesCurrent += myTargetVisual.line - context.visualLine;
+ context.softWrapColumnDiff = myTargetVisual.column - context.logicalColumn;
+ }
+ return context.build();
+ }
+ }
+
+ private static class OffsetBasedStrategy implements LogicalPositionCalculatorStrategy {
+
+ private final EditorTextRepresentationHelper myRepresentationHelper;
+ private final Document myDocument;
+
+ private final int myOffset;
+
+ OffsetBasedStrategy(EditorTextRepresentationHelper representationHelper, Document document, int offset) {
+ myRepresentationHelper = representationHelper;
+ myDocument = document;
+ myOffset = offset;
+ }
+
+ @Override
+ public boolean exceeds(Context context) {
+ return context.offset > myOffset;
+ }
+
+ @NotNull
+ @Override
+ public LogicalPosition build(Context context) {
+ int targetLogicalLine = myDocument.getLineNumber(myOffset);
+ if (targetLogicalLine == context.logicalLine) {
+ context.logicalColumn
+ += myRepresentationHelper.toVisualColumnSymbolsNumber(myDocument.getCharsSequence(), context.offset, myOffset, context.x);
+ return context.build();
+ }
+ context.logicalLine = targetLogicalLine;
+ int i = CharArrayUtil.shiftBackwardUntil(myDocument.getCharsSequence(), myOffset - 1, "\n");
+ if (i >= context.offset) {
+ context.logicalColumn = myRepresentationHelper.toVisualColumnSymbolsNumber(myDocument.getCharsSequence(), i + 1, myOffset, 0);
+ }
+ else {
+ context.logicalColumn
+ = myRepresentationHelper.toVisualColumnSymbolsNumber(myDocument.getCharsSequence(), context.offset, myOffset, context.x);
+ }
+ context.onNewLine();
+ return context.build();
+ }
+
+ @NotNull
+ @Override
+ public LogicalPosition build(Context context, FoldRegion region) {
+ // We want to return logical position that corresponds to the visual start of the given folding region.
+ int startLine = myDocument.getLineNumber(region.getStartOffset());
+ int endLine = myDocument.getLineNumber(myOffset);
+ int lineFeeds = endLine - startLine;
+
+ if (lineFeeds > 0) {
+ context.logicalLine += lineFeeds;
+ context.foldedLines += lineFeeds;
+ context.onNewLine();
+ int i = CharArrayUtil.shiftBackwardUntil(myDocument.getCharsSequence(), myOffset - 1, "\n");
+ context.logicalColumn = myRepresentationHelper.toVisualColumnSymbolsNumber(myDocument.getCharsSequence(), i + 1, myOffset, 0);
+ context.foldingColumnDiff = context.visualColumn - context.logicalColumn;
+ }
+ else {
+ int logicalColumns
+ = myRepresentationHelper.toVisualColumnSymbolsNumber(myDocument.getCharsSequence(), region.getStartOffset(), myOffset, context.x);
+ context.logicalColumn += logicalColumns;
+ context.foldingColumnDiff -= logicalColumns;
+ }
+ return context.build();
+ }
+
+ @NotNull
+ @Override
+ public LogicalPosition build(Context context, TextChange softWrap) {
+ assert false; // Don't expect soft wrap do affect offset-based mapping request.
+ return new LogicalPosition(0, 0);
}
}
@@ -662,41 +489,89 @@ public class SoftWrapDataMapper {
*
* It's primary purpose is to relief unit testing.
*/
- interface FontTypeProvider {
- void init(int start);
- int getFontType(int offset);
- void cleanup();
+ //interface FontTypeProvider {
+ // void init(int start);
+ // int getFontType(int offset);
+ // void cleanup();
+ //}
+
+ //private static class IterationStateFontTypeProvider implements FontTypeProvider {
+ //
+ // private final EditorEx myEditor;
+ //
+ // private IterationState myState;
+ // private int myFontType;
+ //
+ // private IterationStateFontTypeProvider(EditorEx editor) {
+ // myEditor = editor;
+ // }
+ //
+ // @Override
+ // public void init(int start) {
+ // myState = new IterationState(myEditor, start, false);
+ // myFontType = myState.getMergedAttributes().getFontType();
+ // }
+ //
+ // @Override
+ // public int getFontType(int offset) {
+ // if (offset >= myState.getEndOffset()) {
+ // myState.advance();
+ // myFontType = myState.getMergedAttributes().getFontType();
+ // }
+ // return myFontType;
+ // }
+ //
+ // @Override
+ // public void cleanup() {
+ // myState = null;
+ // }
+ //}
+
+ private class SoftWrapsProvider {
+
+ private final List extends TextChange> mySoftWraps;
+ private int myIndex;
+
+ SoftWrapsProvider() {
+ mySoftWraps = myStorage.getSoftWraps();
+ }
+
+ @Nullable
+ public TextChange get() {
+ if (myIndex < 0 || myIndex >= mySoftWraps.size()) {
+ return null;
+ }
+ TextChange result = mySoftWraps.get(myIndex++);
+ if (isVisible(result)) {
+ return result;
+ }
+ else {
+ return get();
+ }
+ }
}
- private static class IterationStateFontTypeProvider implements FontTypeProvider {
+ private class FoldingProvider {
- private final EditorEx myEditor;
+ private final FoldRegion[] myFoldRegions;
+ private int myIndex;
- private IterationState myState;
- private int myFontType;
-
- private IterationStateFontTypeProvider(EditorEx editor) {
- myEditor = editor;
+ FoldingProvider() {
+ myFoldRegions = myEditor.getFoldingModel().fetchTopLevel();
}
- @Override
- public void init(int start) {
- myState = new IterationState(myEditor, start, false);
- myFontType = myState.getMergedAttributes().getFontType();
- }
-
- @Override
- public int getFontType(int offset) {
- if (offset >= myState.getEndOffset()) {
- myState.advance();
- myFontType = myState.getMergedAttributes().getFontType();
+ @Nullable
+ public FoldRegion get() {
+ if (myFoldRegions == null || myIndex < 0 || myIndex >= myFoldRegions.length) {
+ return null;
+ }
+ FoldRegion result = myFoldRegions[myIndex++];
+ if (result.isExpanded()) {
+ return get();
+ }
+ else {
+ return result;
}
- return myFontType;
- }
-
- @Override
- public void cleanup() {
- myState = null;
}
}
}
diff --git a/platform/platform-impl/testSrc/com/intellij/openapi/editor/impl/softwrap/SoftWrapDataMapperTest.java b/platform/platform-impl/testSrc/com/intellij/openapi/editor/impl/softwrap/SoftWrapDataMapperTest.java
index b65c3c88755c..39b69460cdaa 100644
--- a/platform/platform-impl/testSrc/com/intellij/openapi/editor/impl/softwrap/SoftWrapDataMapperTest.java
+++ b/platform/platform-impl/testSrc/com/intellij/openapi/editor/impl/softwrap/SoftWrapDataMapperTest.java
@@ -3,10 +3,13 @@ package com.intellij.openapi.editor.impl.softwrap;
import com.intellij.mock.MockFoldRegion;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.ex.EditorEx;
+import com.intellij.openapi.editor.ex.FoldingModelEx;
+import com.intellij.openapi.editor.ex.SoftWrapModelEx;
import com.intellij.openapi.editor.impl.EditorTextRepresentationHelper;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import gnu.trove.TIntHashSet;
+import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jmock.Expectations;
import org.jmock.Mockery;
@@ -18,7 +21,6 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
-import java.awt.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@@ -88,7 +90,8 @@ public class SoftWrapDataMapperTest {
private EditorEx myEditor;
private Document myDocument;
private SoftWrapsStorage myStorage;
- private FoldingModel myFoldingModel;
+ private SoftWrapModelEx mySoftWrapModel;
+ private FoldingModelEx myFoldingModel;
private MockEditorTextRepresentationHelper myRepresentationHelper;
@Before
@@ -100,7 +103,8 @@ public class SoftWrapDataMapperTest {
myEditor = myMockery.mock(EditorEx.class);
myDocument = myMockery.mock(Document.class);
myStorage = new SoftWrapsStorage();
- myFoldingModel = myMockery.mock(FoldingModel.class);
+ mySoftWrapModel = myMockery.mock(SoftWrapModelEx.class);
+ myFoldingModel = myMockery.mock(FoldingModelEx.class);
final EditorSettings settings = myMockery.mock(EditorSettings.class);
final Project project = myMockery.mock(Project.class);
final SoftWrapPainter painter = myMockery.mock(SoftWrapPainter.class);
@@ -140,6 +144,23 @@ public class SoftWrapDataMapperTest {
allowing(settings).isWhitespacesShown();will(returnValue(true));
allowing(myEditor).getProject();will(returnValue(project));
+ // Soft wraps.
+ allowing(myEditor).getSoftWrapModel(); will(returnValue(mySoftWrapModel));
+ allowing(mySoftWrapModel).getSoftWrapIndentWidthInColumns(with(any(TextChange.class)));
+ will(new CustomAction("getSoftWrapIndentWidthInColumns") {
+ @Override
+ public Object invoke(Invocation invocation) throws Throwable {
+ return getSoftWrapIndentWidthInColumns((TextChange)invocation.getParameter(0));
+ }
+ });
+ allowing(mySoftWrapModel).getSoftWrapIndentWidthInPixels(with(any(TextChange.class)));
+ will(new CustomAction("getSoftWrapIndentWidthInPixels") {
+ @Override
+ public Object invoke(Invocation invocation) throws Throwable {
+ return getSoftWrapIndentWidthInPixels((TextChange)invocation.getParameter(0));
+ }
+ });
+
// Folding.
allowing(myEditor).getFoldingModel();will(returnValue(myFoldingModel));
allowing(myFoldingModel).isOffsetCollapsed(with(any(int.class))); will(new CustomAction("isOffsetCollapsed()") {
@@ -155,7 +176,7 @@ public class SoftWrapDataMapperTest {
return getCollapsedFoldRegion((Integer)invocation.getParameter(0));
}
});
- allowing(myFoldingModel).getAllFoldRegions(); will(new CustomAction("getAllFoldRegions()") {
+ allowing(myFoldingModel).fetchTopLevel(); will(new CustomAction("fetchTopLevel()") {
@Override
public Object invoke(Invocation invocation) throws Throwable {
return myFoldRegions.toArray(new FoldRegion[myFoldRegions.size()]);
@@ -188,7 +209,7 @@ public class SoftWrapDataMapperTest {
myRepresentationHelper = new MockEditorTextRepresentationHelper();
- myAdjuster = new SoftWrapDataMapper(myEditor, myStorage, painter, myRepresentationHelper, new MockFontTypeProvider());
+ myAdjuster = new SoftWrapDataMapper(myEditor, myStorage, myRepresentationHelper/*, new MockFontTypeProvider()*/);
}
@After
@@ -196,6 +217,16 @@ public class SoftWrapDataMapperTest {
myMockery.assertIsSatisfied();
}
+ @Test
+ public void noSoftWrapsAndFolding() {
+ String document =
+ "class Test { \n" +
+ " public void foo() {} \n" +
+ " \n" +
+ "}";
+ test(document);
+ }
+
@Test
public void softWrapHasSymbolBeforeFirstLineFeed() {
String document =
@@ -216,13 +247,13 @@ public class SoftWrapDataMapperTest {
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" +
+ " bar(data[0], \n" +
+ " data[1] \n" +
+ " data[2] \n" +
+ " data[3], \n" +
+ " data[4], \n" +
+ " data[5] \n" +
+ " data[6]); \n" +
" }\n" +
" public void bar(int ... i) {\n" +
" }\n" +
@@ -345,6 +376,34 @@ public class SoftWrapDataMapperTest {
test(document);
}
+ private static int getSoftWrapIndentWidthInColumns(TextChange softWrap) {
+ int result = 0;
+ CharSequence text = softWrap.getText();
+ for (int i = text.length() - 1; i >= 0; i--) {
+ if (text.charAt(i) == '\n') {
+ break;
+ }
+ result++;
+ }
+ if (SOFT_WRAP_DRAWING_WIDTH > 0) {
+ result++;
+ }
+ return result;
+ }
+
+ private int getSoftWrapIndentWidthInPixels(TextChange softWrap) {
+ int result = 0;
+ CharSequence text = softWrap.getText();
+ for (int i = text.length() - 1; i >= 0; i--) {
+ if (text.charAt(i) == '\n') {
+ break;
+ }
+ result += myRepresentationHelper.textWidth(text, i, i + 1, 0);
+ }
+ result += SOFT_WRAP_DRAWING_WIDTH;
+ return result;
+ }
+
private int getLineNumber(int offset) {
int line = 0;
for (TextRange range : myLineRanges) {
@@ -430,10 +489,12 @@ public class SoftWrapDataMapperTest {
for (DataEntry data : myExpectedData) {
// Check logical by visual.
- LogicalPosition actualLogicalByVisual = myAdjuster.adjustLogicalPosition(toSoftWrapUnawareLogicalByVisual(data), data.visual);
+ LogicalPosition actualLogicalByVisual = myAdjuster.visualToLogical(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 && !data.insideTab && !equals(data.logical, actualLogicalByVisual)) {
+ //TODO den remove
+ myAdjuster.visualToLogical(data.visual);
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'",
@@ -446,6 +507,8 @@ public class SoftWrapDataMapperTest {
// 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 && !data.insideTab && !equals(data.logical, actualLogicalByOffset)) {
+ //TODO den remove
+ myAdjuster.offsetToLogicalPosition(data.offset);
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'",
@@ -635,7 +698,7 @@ public class SoftWrapDataMapperTest {
}
else if (c == '\t') {
int tabWidthInColumns = myRepresentationHelper.toVisualColumnSymbolsNumber(c, x);
- x += myRepresentationHelper.charWidth(c, x, Font.PLAIN);
+ x += MockEditorTextRepresentationHelper.charWidth(c, x);
// There is a possible case that single tabulation symbols is shown in more than one visual column at IntelliJ editor.
// We store data entry only for the first tab column without 'inside tab' flag then.
@@ -652,7 +715,7 @@ public class SoftWrapDataMapperTest {
} else {
logicalColumn++;
offset++;
- x += myRepresentationHelper.charWidth(c, x, Font.PLAIN);
+ x += MockEditorTextRepresentationHelper.charWidth(c, x);
foldingColumnDiff--;
}
return;
@@ -685,7 +748,7 @@ public class SoftWrapDataMapperTest {
visualColumn++;
softWrapColumnDiff++;
softWrapSymbolsOnCurrentVisualLine++;
- x += myRepresentationHelper.charWidth(c, x, Font.PLAIN);
+ x += MockEditorTextRepresentationHelper.charWidth(c, x);
}
return;
}
@@ -706,7 +769,7 @@ public class SoftWrapDataMapperTest {
}
else if (c == '\t') {
int tabWidthInColumns = myRepresentationHelper.toVisualColumnSymbolsNumber(c, x);
- x += myRepresentationHelper.charWidth(c, x, Font.PLAIN);
+ x += MockEditorTextRepresentationHelper.charWidth(c, x);
// There is a possible case that single tabulation symbols is shown in more than one visual column at IntelliJ editor.
// We store data entry only for the first tab column without 'inside tab' flag then.
@@ -726,7 +789,7 @@ public class SoftWrapDataMapperTest {
visualColumn++;
logicalColumn++;
offset++;
- x += myRepresentationHelper.charWidth(c, x, Font.PLAIN);
+ x += MockEditorTextRepresentationHelper.charWidth(c, x);
}
}
@@ -782,10 +845,10 @@ public class SoftWrapDataMapperTest {
}
@Override
- public int toVisualColumnSymbolsNumber(CharSequence text, int start, int end, int x) {
+ public int toVisualColumnSymbolsNumber(@NotNull CharSequence text, int start, int end, int x) {
int result = 0;
for (int i = start; i < end; i++) {
- int width = charWidth(text.charAt(i), x, Font.PLAIN);
+ int width = charWidth(text.charAt(i), x);
result += width / SPACE_SIZE;
if (width % SPACE_SIZE > 0) {
result++;
@@ -796,7 +859,28 @@ public class SoftWrapDataMapperTest {
}
@Override
- public int charWidth(char c, int x, int fontType) {
+ public int toVisualColumnSymbolsNumber(int width) {
+ int result = width / SPACE_SIZE;
+ if (width % SPACE_SIZE > 0) {
+ result++;
+ }
+ return result;
+ }
+
+ @Override
+ public int textWidth(@NotNull CharSequence text, int start, int end, int x) {
+ int result = 0;
+ for (int i = start; i < end; i++) {
+ char c = text.charAt(i);
+ switch (c) {
+ case '\n': result = 0; break;
+ default: result += charWidth(c, result);
+ }
+ }
+ return result;
+ }
+
+ public static int charWidth(char c, int x) {
if (c == '\t') {
int tabWidth = SPACE_SIZE * TAB_SIZE;
int tabsNumber = x / tabWidth;
@@ -808,18 +892,18 @@ public class SoftWrapDataMapperTest {
}
}
- private static class MockFontTypeProvider implements SoftWrapDataMapper.FontTypeProvider {
- @Override
- public void init(int start) {
- }
-
- @Override
- public int getFontType(int offset) {
- return Font.PLAIN;
- }
-
- @Override
- public void cleanup() {
- }
- }
+ //private static class MockFontTypeProvider implements SoftWrapDataMapper.FontTypeProvider {
+ // @Override
+ // public void init(int start) {
+ // }
+ //
+ // @Override
+ // public int getFontType(int offset) {
+ // return Font.PLAIN;
+ // }
+ //
+ // @Override
+ // public void cleanup() {
+ // }
+ //}
}
diff --git a/platform/util/src/com/intellij/openapi/util/text/StringUtil.java b/platform/util/src/com/intellij/openapi/util/text/StringUtil.java
index fe4299e6e766..119a720c0803 100644
--- a/platform/util/src/com/intellij/openapi/util/text/StringUtil.java
+++ b/platform/util/src/com/intellij/openapi/util/text/StringUtil.java
@@ -1236,6 +1236,20 @@ public class StringUtil {
return i;
}
+ /**
+ * Allows to answer if target symbol is contained at given char sequence at [start; end) interval.
+ *
+ * @param s target char sequence to check
+ * @param start start offset to use within the given char sequence (inclusive)
+ * @param end end offset to use within the given char sequence (exclusive)
+ * @param c target symbol to check
+ * @return true if given symbol is contained at the target range of the given char sequence;
+ * false otherwise
+ */
+ public static boolean contains(CharSequence s, int start, int end, char c) {
+ return indexOf(s, c, start, end) >= 0;
+ }
+
public static int indexOf(@NotNull CharSequence s, char c) {
return indexOf(s, c, 0, s.length());
}