IDEA-56987 Throwable at EditorImpl.logicalToVisualPosition() on opening a database console

Soft wrap-aware mapping rules are corrected
This commit is contained in:
Denis Zhdanov
2010-08-02 18:53:55 +04:00
parent 7bc4276ce9
commit 0686359835
13 changed files with 817 additions and 696 deletions
@@ -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);
}
@@ -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);
}
@@ -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.
* <p/>
* 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
@@ -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 <code>[start; end)</code> 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 <code>'x'</code> coordinate that corresponds to the given <code>'start'</code> offset
* @param currentColumn logical column that corresponds to the given <code>'start'</code> offset
* @return target offset that belongs to the <code>[start; end)</code> range and points to the target logical
* column if any; <code>-1</code> 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 <code>[start; end)</code> interval
* at the given editor.
@@ -390,10 +481,10 @@ public class EditorUtil {
* from <code>[1; tab size]</code> (check {@link #nextTabStop(int, Editor)} for more details)
* @return width in pixels required for target text representation
*/
public static int textWidth(@NotNull Editor editor, char[] text, int start, int end, int fontType, 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());
@@ -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);
}
@@ -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);
}
}
@@ -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);
@@ -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.
* <p/>
@@ -37,16 +39,28 @@ public interface EditorTextRepresentationHelper {
* @param x <code>'x'</code> 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 <code>'x'</code> 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 <code>'x'</code> 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 (<code>[start; end)</code>) starting
* at the given <code>'x'</code> offset from visual line start using given font type.
* <p/>
* <b>Note:</b> target region is allows to contain line feeds, the width is calculated as a difference between <code>'x'</code>
* 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 <code>'x'</code> 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);
}
@@ -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;
@@ -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:
* <pre>
@@ -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) {
@@ -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], <WRAP>\n" +
" </WRAP>data[2], data[3], <WRAP> \n" +
" </WRAP>data[4], data[5], \n" +
" data[6], data[7], \n" +
" data[8], data[9], <WRAP>\n" +
" </WRAP>data[10], data[11], <WRAP> \n" +
" </WRAP>data[12], data[13]); \n" +
" bar(data[0], <WRAP>\n" +
" </WRAP>data[1] <WRAP> \n" +
" </WRAP>data[2] \n" +
" data[3], \n" +
" data[4], <WRAP>\n" +
" </WRAP>data[5] <WRAP> \n" +
" </WRAP>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() {
// }
//}
}
@@ -1236,6 +1236,20 @@ public class StringUtil {
return i;
}
/**
* Allows to answer if target symbol is contained at given char sequence at <code>[start; end)</code> 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 <code>true</code> if given symbol is contained at the target range of the given char sequence;
* <code>false</code> 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());
}