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 c172d60c64ca..c0accddd92fd 100644 --- a/platform/lang-impl/src/com/intellij/injected/editor/EditorWindow.java +++ b/platform/lang-impl/src/com/intellij/injected/editor/EditorWindow.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2011 JetBrains s.r.o. + * Copyright 2000-2012 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -560,7 +560,7 @@ public class EditorWindow extends UserDataHolderBase implements EditorEx { int end = myDocumentWindow.getLineEndOffset(lineNumber); CharSequence text = myDocumentWindow.getCharsSequence(); - return EditorUtil.calcOffset(this, text, lineStartOffset, end, col, EditorUtil.getTabSize(myDelegate)); + return EditorUtil.calcOffset(this, text, lineStartOffset, end, col, EditorUtil.getTabSize(myDelegate), null); } @Override 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 ad2fe4b161c4..2e7322c59bf1 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 @@ -32,6 +32,7 @@ import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.text.StringUtil; import org.intellij.lang.annotations.JdkConstants; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.awt.*; import java.awt.event.MouseWheelEvent; @@ -205,11 +206,18 @@ public class EditorUtil { * @param end end offset of the logical line that holds target column (exclusive) * @param columnNumber target column number * @param tabSize number of desired visual columns to use for tabulation representation + * @param debugBuffer buffer to hold debug info during the processing (if any) * @return given text offset that identifies the same position that is pointed by the given visual column */ - public static int calcOffset(EditorEx editor, CharSequence text, int start, int end, int columnNumber, int tabSize) { + public static int calcOffset(EditorEx editor, CharSequence text, int start, int end, int columnNumber, int tabSize, + @Nullable StringBuilder debugBuffer) + { assert start >= 0 : "start (" + start + ") must not be negative. end (" + end + ")"; assert end >= start : "start (" + start + ") must not be greater than end (" + end + ")"; + if (debugBuffer != null) { + debugBuffer.append(String.format("Starting calcOffset(). Start=%d, end=%d, column number=%d, tab size=%d%n", + start, end, columnNumber, tabSize)); + } final int maxScanIndex = Math.min(start + columnNumber + 1, end); SoftWrapModel softWrapModel = editor.getSoftWrapModel(); List softWraps = softWrapModel.getSoftWrapsForRange(start, maxScanIndex); @@ -221,7 +229,8 @@ public class EditorUtil { if (currentColumn[0] >= columnNumber) { return startToUse; } - int result = calcSoftWrapUnawareOffset(editor, text, startToUse, softWrap.getEnd(), columnNumber, tabSize, x, currentColumn); + int result + = calcSoftWrapUnawareOffset(editor, text, startToUse, softWrap.getEnd(), columnNumber, tabSize, x, currentColumn, debugBuffer); if (result >= 0) { return result; } @@ -235,13 +244,17 @@ public class EditorUtil { return startToUse; } - int result = calcSoftWrapUnawareOffset(editor, text, startToUse, end, columnNumber, tabSize, x, currentColumn); + int result = calcSoftWrapUnawareOffset(editor, text, startToUse, end, columnNumber, tabSize, x, currentColumn, debugBuffer); 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. + if (debugBuffer != null) { + debugBuffer.append(String.format("Returning %d as no match has been found for the target column (%d) at the target range [%d;%d)", + end, columnNumber, start, end)); + } return end; } @@ -256,12 +269,18 @@ public class EditorUtil { * @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 + * @param debugBuffer buffer to hold debug info during the processing (if any) * @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, - int[] currentColumn) - { + int[] currentColumn, @Nullable StringBuilder debugBuffer) { + if (debugBuffer != null) { + debugBuffer.append(String.format( + "Starting calcSoftWrapUnawareOffset(). Target range: [%d; %d), target column number to map: %d, tab size: %d, " + + "x: %d, current column: %d%n", start, end, columnNumber, tabSize, x, currentColumn[0])); + } + // 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 @@ -270,17 +289,26 @@ public class EditorUtil { boolean hasNonTabs = false; boolean hasTabs = false; for (int i = start; i < end; i++) { - if (text.charAt(i) == '\t') { + char c = text.charAt(i); + if (debugBuffer != null) { + debugBuffer.append(String.format("Found symbol '%c' at the offset %d%n", c, i)); + } + if (c == '\t') { hasTabs = true; if (hasNonTabs) { useOptimization = false; break; } - } else { + } + else { hasNonTabs = true; } } + if (debugBuffer != null) { + debugBuffer.append(String.format("Has tabs: %b, use optimisation: %b%n", hasTabs, useOptimization)); + } + // 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) { @@ -291,6 +319,9 @@ public class EditorUtil { } else { currentColumn[0] += end - start; + if (debugBuffer != null) { + debugBuffer.append(String.format("Incrementing 'current column' by %d (new value is %d)%n", end - start, currentColumn[0])); + } return -1; } } @@ -300,14 +331,26 @@ public class EditorUtil { int shift = 0; int offset = start; int prevX = x; + if (debugBuffer != null) { + debugBuffer.append("Processing a string that contains only tabs\n"); + } for (; offset < end && offset + shift + currentColumn[0] < start + columnNumber; offset++) { - if (text.charAt(offset) == '\t') { + final char c = text.charAt(offset); + if (c == '\t') { int nextX = nextTabStop(prevX, editor, tabSize); - shift += columnsNumber(nextX - prevX, getSpaceWidth(Font.PLAIN, editor)) - 1; + final int columnsShift = columnsNumber(nextX - prevX, getSpaceWidth(Font.PLAIN, editor)) - 1; + if (debugBuffer != null) { + debugBuffer.append(String.format( + "Processing tabulation symbol at the offset %d. Current X: %d, new X: %d, current columns shift: %d, new column shift: %d%n", + offset, prevX, nextX, shift, shift + columnsShift + )); + } + shift += columnsShift; prevX = nextX; } } int diff = start + columnNumber - offset - shift - currentColumn[0]; + if (debugBuffer != null) debugBuffer.append(String.format("Resulting diff: %d%n", diff)); if (diff < 0) { return offset - 1; } @@ -315,7 +358,11 @@ public class EditorUtil { return offset; } else { - currentColumn[0] += offset - start + shift; + final int inc = offset - start + shift; + if (debugBuffer != null) { + debugBuffer.append(String.format("Incrementing 'current column' by %d (new value is %d)%n", inc, currentColumn[0] + inc)); + } + currentColumn[0] += inc; return -1; } } @@ -339,11 +386,25 @@ public class EditorUtil { char c = text.charAt(offset); if (c == '\t') { int prevX = x; - x = nextTabStop(x, editorImpl); - column += columnsNumber(x - prevX, spaceSize); + final int newX = nextTabStop(x, editorImpl); + final int columns = columnsNumber(x - prevX, spaceSize); + if (debugBuffer != null) { + debugBuffer.append(String.format( + "Processing tabulation at the offset %d. Current X: %d, new X: %d, current column: %d, new column: %d%n", + offset, x, newX, column, column + columns + )); + } + x = newX; + column += columns; } else { - x += charWidth(c, fontType, editorImpl); + final int width = charWidth(c, fontType, editorImpl); + if (debugBuffer != null) { + debugBuffer.append(String.format( + "Processing symbol '%c' at the offset %d. Current X: %d, new X: %d%n", c, offset, x, x + width + )); + } + x += width; column++; } } 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 f292d06c58cf..0263f6332cd8 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 @@ -182,7 +182,7 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener, myVisualLineStart = myEditor.logicalPositionToOffset(myEditor.visualToLogicalPosition(new VisualPosition(myVisibleCaret.line, 0))); myVisualLineEnd = myEditor.logicalPositionToOffset(myEditor.visualToLogicalPosition(new VisualPosition(myVisibleCaret.line + 1, 0))); - ((FoldingModelImpl)myEditor.getFoldingModel()).flushCaretPosition(); + myEditor.getFoldingModel().flushCaretPosition(); myEditor.setLastColumnNumber(myVisibleCaret.column); myEditor.updateCaretCursor(); @@ -220,13 +220,15 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener, final DocumentEx document = myEditor.getDocument(); int textEnd = Math.min(document.getTextLength() - 1, Math.max(offset, myOffset) + 1); CharSequence text = document.getCharsSequence().subSequence(textStart, textEnd); + StringBuilder positionToOffsetTrace = new StringBuilder(); + int inverseOffset = myEditor.logicalPositionToOffset(logicalPosition, positionToOffsetTrace); LogMessageEx.error( - LOG, "caret moved to wrong offset", + LOG, "caret moved to wrong offset. Please submit a dedicated ticket and attach current editor's text to it.", String.format( "Requested: offset=%d, logical position='%s' but actual: offset=%d, logical position='%s' (%s). %s%n" - + "interested text [%d;%d): '%s'%n debug trace: %s", + + "interested text [%d;%d): '%s'%n debug trace: %s%nLogical position -> offset ('%s'->'%d') trace: %s", offset, logicalPosition, myOffset, myLogicalCaret, positionByOffsetAfterMove, myEditor.dumpState(), - textStart, textEnd, text, debugBuffer + textStart, textEnd, text, debugBuffer, logicalPosition, inverseOffset, positionToOffsetTrace )); } if (event != null) { @@ -493,7 +495,7 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener, } } - ((FoldingModelImpl)myEditor.getFoldingModel()).flushCaretPosition(); + myEditor.getFoldingModel().flushCaretPosition(); VerticalInfo oldInfo = myCaretInfo; LogicalPosition oldCaretPosition = myLogicalCaret; @@ -522,7 +524,7 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener, Runnable runnable = new Runnable() { @Override public void run() { - FoldRegion[] allCollapsedAt = ((FoldingModelImpl)myEditor.getFoldingModel()).fetchCollapsedAt(offset); + FoldRegion[] allCollapsedAt = myEditor.getFoldingModel().fetchCollapsedAt(offset); for (FoldRegion foldRange : allCollapsedAt) { foldRange.setExpanded(true); } 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 fa6a7ba84451..28e18587875e 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 @@ -3312,6 +3312,11 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi @Override public int logicalPositionToOffset(@NotNull LogicalPosition pos) { + return logicalPositionToOffset(pos, null); + } + + + public int logicalPositionToOffset(@NotNull LogicalPosition pos, @Nullable StringBuilder debugBuffer) { assertReadAccess(); if (myDocument.getLineCount() == 0) return 0; @@ -3328,7 +3333,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi CharSequence text = myDocument.getCharsNoThreadCheck(); - return EditorUtil.calcOffset(this, text, start, end, pos.column, EditorUtil.getTabSize(this)); + return EditorUtil.calcOffset(this, text, start, end, pos.column, EditorUtil.getTabSize(this), debugBuffer); } @Override