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 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 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 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()); }