From 6da30a435fea38f054fd9450258657a6bde704db Mon Sep 17 00:00:00 2001 From: Alexey Kudravtsev Date: Mon, 26 Aug 2013 17:52:48 +0400 Subject: [PATCH] calculate column/offset: optimisation of the no-tabs case --- .../openapi/editor/ex/util/EditorUtil.java | 125 ++++++++++-------- .../openapi/editor/impl/EditorImpl.java | 21 ++- 2 files changed, 86 insertions(+), 60 deletions(-) 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 c4bb90b90a8a..4c09c612e038 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 @@ -41,9 +41,9 @@ import java.util.Arrays; import java.util.List; public class EditorUtil { - + private static final Logger LOG = Logger.getInstance("#" + EditorUtil.class.getName()); - + private EditorUtil() { } public static int getLastVisualLineColumnNumber(@NotNull Editor editor, final int line) { @@ -136,14 +136,14 @@ public class EditorUtil { result += calcColumnNumber(editor, softWrap.getText(), softWrapStartOffset, softWrapEndOffset); return result; } - + CharSequence editorInfo; if (editor instanceof EditorImpl) { editorInfo = ((EditorImpl)editor).dumpState(); } else { editorInfo = "editor's class: " + editor.getClass() - + ", all soft wraps: " + editor.getSoftWrapModel().getSoftWrapsForRange(0, document.getTextLength()) + + ", all soft wraps: " + editor.getSoftWrapModel().getSoftWrapsForRange(0, document.getTextLength()) + ", fold regions: " + Arrays.toString(editor.getFoldingModel().getAllFoldRegions()); } LogMessageEx.error(LOG, "Can't calculate last visual column", String.format( @@ -151,7 +151,7 @@ public class EditorUtil { + "the target logical line: %s. Editor info: %s", line, resultLogLine, resVisStart, resVisEnd, softWraps, editorInfo )); - + return resVisEnd.column; } @@ -294,36 +294,43 @@ public class EditorUtil { "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 // text fragment. boolean useOptimization = true; - boolean hasNonTabs = false; - boolean hasTabs = false; - int scanEndOffset = Math.min(end, start + columnNumber - currentColumn[0] + 1); - for (int i = start; i < scanEndOffset; i++) { - 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; + boolean hasTabs; + if ((editor instanceof EditorImpl) && !((EditorImpl)editor).hasTabs()) { + hasTabs = false; + useOptimization = true; + } + else { + boolean hasNonTabs = false; + hasTabs = false; + int scanEndOffset = Math.min(end, start + columnNumber - currentColumn[0] + 1); + for (int i = start; i < scanEndOffset; i++) { + 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 { + hasNonTabs = true; } - } - 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 (useOptimization) { @@ -454,16 +461,23 @@ public class EditorUtil { SoftWrap softWrap = editor.getSoftWrapModel().getSoftWrap(start); useOptimization = softWrap == null; } + boolean hasTabs = true; if (useOptimization) { - boolean hasNonTabs = false; - for (int i = start; i < offset; i++) { - if (text.charAt(i) == '\t') { - if (hasNonTabs) { - useOptimization = false; - break; + if ((editor instanceof EditorImpl) && !((EditorImpl)editor).hasTabs()) { + hasTabs = false; + } + else { + boolean hasNonTabs = false; + for (int i = start; i < offset; i++) { + if (text.charAt(i) == '\t') { + if (hasNonTabs) { + useOptimization = false; + break; + } + } + else { + hasNonTabs = true; } - } else { - hasNonTabs = true; } } } @@ -471,27 +485,26 @@ public class EditorUtil { if (editor == null || useOptimization) { int shift = 0; - for (int i = start; i < offset; i++) { - char c = text.charAt(i); - if (c == '\n' || c == '\r') { - String editorInfo = editor instanceof EditorImpl ? ". Editor info: " + ((EditorImpl)editor).dumpState() : ""; - String documentInfo; - if (text instanceof Dumpable) { - documentInfo = ((Dumpable)text).dumpState(); - } - else { - documentInfo = "Text holder class: " + text.getClass(); - } - LogMessageEx.error( - LOG, "detected incorrect offset -> column number calculation", - String.format( - "Symbol: '%c', its index: %d, given start: %d, given offset: %d, given tab size: %d. %s%s", - c, i, start, offset, tabSize, documentInfo, editorInfo - ) - ); + Document document = editor == null ? null : editor.getDocument(); + if (document != null && start < offset-1 && document.getLineNumber(start) != document.getLineNumber(offset-1)) { + String editorInfo = editor instanceof EditorImpl ? ". Editor info: " + ((EditorImpl)editor).dumpState() : ""; + String documentInfo; + if (text instanceof Dumpable) { + documentInfo = ((Dumpable)text).dumpState(); } - if (c == '\t') { - shift += getTabLength(i + shift - start, tabSize) - 1; + else { + documentInfo = "Text holder class: " + text.getClass(); + } + LogMessageEx.error( + LOG, "detected incorrect offset -> column number calculation", + "start: " + start + ", given offset: " + offset+", given tab size: " + tabSize + ". "+documentInfo+ editorInfo); + } + if (hasTabs) { + for (int i = start; i < offset; i++) { + char c = text.charAt(i); + if (c == '\t') { + shift += getTabLength(i + shift - start, tabSize) - 1; + } } } return offset - start + shift; @@ -580,7 +593,7 @@ public class EditorUtil { char c = text.charAt(i); int prevX = x; switch (c) { - case '\t': + case '\t': x = nextTabStop(x, editor); result += columnsNumber(x - prevX, spaceSize); break; @@ -666,10 +679,10 @@ public class EditorUtil { /** * Delegates to the {@link #calcSurroundingRange(Editor, VisualPosition, VisualPosition)} with the * {@link CaretModel#getVisualPosition() caret visual position} as an argument. - * + * * @param editor target editor * @return surrounding logical positions - * @see #calcSurroundingRange(Editor, VisualPosition, VisualPosition) + * @see #calcSurroundingRange(Editor, VisualPosition, VisualPosition) */ public static Pair calcCaretLineRange(@NotNull Editor editor) { return calcSurroundingRange(editor, editor.getCaretModel().getVisualPosition(), editor.getCaretModel().getVisualPosition()); @@ -719,7 +732,7 @@ public class EditorUtil { } line = foldEndLine; } - + LogicalPosition second = editor.visualToLogicalPosition(new VisualPosition(end.line, 0)); for ( @@ -739,7 +752,7 @@ public class EditorUtil { } line = foldEndLine; } - + if (second.line >= document.getLineCount()) { second = editor.offsetToLogicalPosition(document.getTextLength()); } 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 1b4d3e7b778b..fcef9aa8dff7 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 @@ -152,6 +152,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi @NotNull private final EditorComponentImpl myEditorComponent; @NotNull private final EditorGutterComponentImpl myGutterComponent; private final TraceableDisposable myTraceableDisposable = new TraceableDisposable(new Throwable()); + private volatile boolean hasTabs; // optimisation flag: when editor contains no tabs it is dramatically easier to calculate positions static { ComplementaryFontsRegistry.getFontAbleToDisplay(' ', 0, 0, UIManager.getFont("Label.font").getFamily()); // load costly font info @@ -518,6 +519,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi } }); } + updateHasTabsFlag(document.getCharsSequence()); } public static boolean isPresentationMode(Project project) { @@ -1693,6 +1695,16 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition()); int scrollOffset = caretLocation.y - myCaretUpdateVShift; getScrollingModel().scrollVertically(scrollOffset); + updateHasTabsFlag(e.getNewFragment()); + } + + private void updateHasTabsFlag(CharSequence newChars) { + if (!hasTabs) { + hasTabs = StringUtil.contains(newChars, 0, newChars.length(), '\t'); + } + } + public boolean hasTabs() { + return hasTabs; } public boolean isScrollToCaret() { @@ -2474,7 +2486,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi // there then. VisualPosition selectionStartPosition = getSelectionModel().getSelectionStartPosition(); VisualPosition selectionEndPosition = getSelectionModel().getSelectionEndPosition(); - if (selectionStartPosition == null || selectionEndPosition == null || selectionStartPosition.equals(selectionEndPosition)) { + if (selectionStartPosition.equals(selectionEndPosition)) { return; } @@ -2535,7 +2547,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi // there then. VisualPosition selectionStartPosition = getSelectionModel().getSelectionStartPosition(); VisualPosition selectionEndPosition = getSelectionModel().getSelectionEndPosition(); - if (selectionStartPosition == null || selectionEndPosition == null || selectionStartPosition.equals(selectionEndPosition)) { + if (selectionStartPosition.equals(selectionEndPosition)) { return; } @@ -3978,8 +3990,9 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi int x = myGutterComponent.convertX(e.getX()); - if (x >= myGutterComponent.getLineNumberAreaOffset() && - x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth()) { + int lineNumberAreaOffset = EditorGutterComponentImpl.getLineNumberAreaOffset(); + if (x >= lineNumberAreaOffset && + x < lineNumberAreaOffset + myGutterComponent.getLineNumberAreaWidth()) { return EditorMouseEventArea.LINE_NUMBERS_AREA; }