From 70bcea72507525465b9f83604e2ddc9e97ec386d Mon Sep 17 00:00:00 2001 From: Dmitry Batrak Date: Tue, 10 Nov 2015 11:53:40 +0300 Subject: [PATCH] speed up editor opening - avoid unneeded/repetitive calculations --- .../impl/view/EditorCoordinateMapper.java | 10 +- .../editor/impl/view/EditorPainter.java | 8 +- .../editor/impl/view/EditorSizeManager.java | 44 +++++- .../openapi/editor/impl/view/EditorView.java | 43 ++---- .../openapi/editor/impl/view/LineLayout.java | 60 ++++---- .../impl/view/LogicalPositionCache.java | 6 + .../editor/impl/view/TextLayoutCache.java | 9 +- .../view/VisualLineFragmentsIterator.java | 95 +++++++++---- .../editor/impl/view/VisualLinesIterator.java | 134 ++++++++++++++++++ 9 files changed, 298 insertions(+), 111 deletions(-) create mode 100644 platform/platform-impl/src/com/intellij/openapi/editor/impl/view/VisualLinesIterator.java diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/EditorCoordinateMapper.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/EditorCoordinateMapper.java index 560d29d25acc..8aa58807f701 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/EditorCoordinateMapper.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/EditorCoordinateMapper.java @@ -73,7 +73,7 @@ class EditorCoordinateMapper { int visualLine = offsetToVisualLine(offset, beforeSoftWrap); int maxVisualColumn = 0; int maxLogicalColumn = 0; - for (VisualLineFragmentsIterator.Fragment fragment : VisualLineFragmentsIterator.create(myView, offset, beforeSoftWrap, null)) { + for (VisualLineFragmentsIterator.Fragment fragment : VisualLineFragmentsIterator.create(myView, offset, beforeSoftWrap)) { if (!pos.leansForward && offset == fragment.getVisualLineStartOffset()) { return new VisualPosition(visualLine, fragment.getStartVisualColumn()); } @@ -125,7 +125,7 @@ class EditorCoordinateMapper { int maxVisualColumn = 0; int maxLogicalColumn = 0; int maxOffset = offset; - for (VisualLineFragmentsIterator.Fragment fragment : VisualLineFragmentsIterator.create(myView, offset, false, null)) { + for (VisualLineFragmentsIterator.Fragment fragment : VisualLineFragmentsIterator.create(myView, offset, false)) { int minColumn = fragment.getStartVisualColumn(); int maxColumn = fragment.getEndVisualColumn(); if (column < minColumn || column == minColumn && !pos.leansRight) { @@ -240,7 +240,7 @@ class EditorCoordinateMapper { if (visualLine < myView.getEditor().getVisibleLineCount()) { int visualLineStartOffset = visualLineToOffset(visualLine); int maxOffset = 0; - for (VisualLineFragmentsIterator.Fragment fragment : VisualLineFragmentsIterator.create(myView, visualLineStartOffset, false, null)) { + for (VisualLineFragmentsIterator.Fragment fragment : VisualLineFragmentsIterator.create(myView, visualLineStartOffset, false)) { if (p.x <= fragment.getStartX()) { int markerWidth = myView.getEditor().getSoftWrapModel().getMinDrawingWidthInPixels(SoftWrapDrawingType.AFTER_SOFT_WRAP); float indent = fragment.getStartX() - markerWidth; @@ -286,7 +286,7 @@ class EditorCoordinateMapper { if (visualLine < myView.getEditor().getVisibleLineCount()) { int visualLineStartOffset = visualLineToOffset(visualLine); int maxOffset = 0; - for (VisualLineFragmentsIterator.Fragment fragment : VisualLineFragmentsIterator.create(myView, visualLineStartOffset, false, null)) { + for (VisualLineFragmentsIterator.Fragment fragment : VisualLineFragmentsIterator.create(myView, visualLineStartOffset, false)) { if (column < fragment.getStartVisualColumn()) { break; } @@ -317,7 +317,7 @@ class EditorCoordinateMapper { float x = getStartX(logicalLine); if (myDocument.getTextLength() > 0) { boolean firstFragment = true; - for (VisualLineFragmentsIterator.Fragment fragment : VisualLineFragmentsIterator.create(myView, offset, beforeSoftWrap, null)) { + for (VisualLineFragmentsIterator.Fragment fragment : VisualLineFragmentsIterator.create(myView, offset, beforeSoftWrap)) { if (firstFragment && offset == visualLineStartOffset && !leanTowardsLargerOffsets) { x = fragment.getStartX(); break; diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/EditorPainter.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/EditorPainter.java index 4b9cc4397d55..234d55275457 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/EditorPainter.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/EditorPainter.java @@ -589,7 +589,7 @@ class EditorPainter implements TextDrawingCallback { TFloatArrayList trailingRanges = adjustedLogicalRangeToVisualRanges( myView.visualPositionToOffset(new VisualPosition(endPosition.line, 0)), endOffset); if (!leadingRanges.isEmpty() && !trailingRanges.isEmpty()) { - int maxWidth = Math.max(myView.getMaxWidthInLineRange(startPosition.line, endPosition.line - 1, null) - 1, + int maxWidth = Math.max(myView.getMaxWidthInLineRange(startPosition.line, endPosition.line - 1) - 1, (int)trailingRanges.get(trailingRanges.size() - 1)); boolean containsInnerLines = endPosition.line > startPosition.line + 1; int leadingTopY = myView.visualLineToY(startPosition.line); @@ -695,7 +695,7 @@ class EditorPainter implements TextDrawingCallback { result.add(0); } else { - for (VisualLineFragmentsIterator.Fragment fragment : VisualLineFragmentsIterator.create(myView, startOffset, false, null)) { + for (VisualLineFragmentsIterator.Fragment fragment : VisualLineFragmentsIterator.create(myView, startOffset, false)) { int minOffset = fragment.getMinOffset(); int maxOffset = fragment.getMaxOffset(); if (startOffset == endOffset) { @@ -777,7 +777,7 @@ class EditorPainter implements TextDrawingCallback { int targetVisualColumn = caret.getVisualPosition().column; for (VisualLineFragmentsIterator.Fragment fragment : VisualLineFragmentsIterator.create(myView, caret.getVisualLineStart(), - false, null)) { + false)) { int startVisualColumn = fragment.getStartVisualColumn(); int endVisualColumn = fragment.getEndVisualColumn(); if (startVisualColumn < targetVisualColumn && endVisualColumn > targetVisualColumn || @@ -815,7 +815,7 @@ class EditorPainter implements TextDrawingCallback { int prevEndOffset = -1; boolean firstFragment = true; int maxColumn = 0; - for (VisualLineFragmentsIterator.Fragment fragment : VisualLineFragmentsIterator.create(myView, offset, false, null)) { + for (VisualLineFragmentsIterator.Fragment fragment : VisualLineFragmentsIterator.create(myView, offset, false)) { int fragmentStartOffset = fragment.getStartOffset(); int start = fragmentStartOffset; int end = fragment.getEndOffset(); diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/EditorSizeManager.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/EditorSizeManager.java index e41b3efc46a3..e757a717042e 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/EditorSizeManager.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/EditorSizeManager.java @@ -26,6 +26,7 @@ import com.intellij.openapi.editor.ex.FoldingListener; import com.intellij.openapi.editor.ex.PrioritizedDocumentListener; import com.intellij.openapi.editor.impl.EditorDocumentPriorities; import com.intellij.openapi.editor.impl.EditorImpl; +import com.intellij.openapi.editor.impl.softwrap.SoftWrapDrawingType; import com.intellij.openapi.editor.impl.softwrap.mapping.IncrementalCacheUpdateEvent; import com.intellij.openapi.editor.impl.softwrap.mapping.SoftWrapAwareDocumentParsingListenerAdapter; import com.intellij.openapi.project.Project; @@ -34,6 +35,7 @@ import com.intellij.openapi.util.TextRange; import com.intellij.openapi.vfs.VirtualFile; import gnu.trove.TIntArrayList; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.awt.*; import java.util.ArrayList; @@ -195,26 +197,56 @@ class EditorSizeManager implements PrioritizedDocumentListener, Disposable, Fold } private int calculatePreferredWidth() { - int lineCount = myLineWidths.size(); + assert myLineWidths.size() == myEditor.getVisibleLineCount(); + VisualLinesIterator iterator = new VisualLinesIterator(myView, 0); int maxWidth = 0; - for (int i = 0; i < lineCount; i++) { - int width = myLineWidths.get(i); + while (!iterator.atEnd()) { + int visualLine = iterator.getVisualLine(); + int width = myLineWidths.get(visualLine); if (width == UNKNOWN_WIDTH) { final Ref approximateValue = new Ref(Boolean.FALSE); - width = myView.getMaxWidthInLineRange(i, i, new Runnable() { + width = getVisualLineWidth(iterator, new Runnable() { @Override public void run() { approximateValue.set(Boolean.TRUE); } }); if (approximateValue.get()) width = -width; - myLineWidths.set(i, width); + myLineWidths.set(visualLine, width); } maxWidth = Math.max(maxWidth, Math.abs(width)); + iterator.advance(); } return maxWidth; } - + + int getVisualLineWidth(VisualLinesIterator visualLinesIterator, @Nullable Runnable quickEvaluationListener) { + assert !visualLinesIterator.atEnd(); + int visualLine = visualLinesIterator.getVisualLine(); + FoldRegion[] topLevelRegions = myEditor.getFoldingModel().fetchTopLevel(); + if (quickEvaluationListener != null && + (topLevelRegions == null || topLevelRegions.length == 0) && myEditor.getSoftWrapModel().getRegisteredSoftWraps().isEmpty() && + !myView.getTextLayoutCache().hasCachedLayoutFor(visualLine)) { + // fast path - speeds up editor opening + quickEvaluationListener.run(); + return myView.getLogicalPositionCache().offsetToLogicalColumn(visualLine, + myDocument.getLineEndOffset(visualLine) - + myDocument.getLineStartOffset(visualLine)) * + myView.getMaxCharWidth(); + } + float x = 0; + int maxOffset = visualLinesIterator.getVisualLineStartOffset(); + for (VisualLineFragmentsIterator.Fragment fragment : VisualLineFragmentsIterator.create(myView, visualLinesIterator, + quickEvaluationListener)) { + x = fragment.getEndX(); + maxOffset = Math.max(maxOffset, fragment.getMaxOffset()); + } + if (myEditor.getSoftWrapModel().getSoftWrap(maxOffset) != null) { + x += myEditor.getSoftWrapModel().getMinDrawingWidthInPixels(SoftWrapDrawingType.BEFORE_SOFT_WRAP_LINE_FEED); + } + return (int)x; + } + void reset() { assert !myDocument.isInBulkUpdate(); doInvalidateRange(0, myDocument.getTextLength()); diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/EditorView.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/EditorView.java index f81cb218fac1..868e149e280d 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/EditorView.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/EditorView.java @@ -28,13 +28,11 @@ import com.intellij.openapi.editor.ex.util.EditorUtil; import com.intellij.openapi.editor.impl.EditorImpl; import com.intellij.openapi.editor.impl.FontInfo; import com.intellij.openapi.editor.impl.TextDrawingCallback; -import com.intellij.openapi.editor.impl.softwrap.SoftWrapDrawingType; import com.intellij.openapi.editor.markup.TextAttributes; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Key; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.awt.*; import java.awt.font.FontRenderContext; @@ -173,6 +171,12 @@ public class EditorView implements TextDrawingCallback, Disposable { return myMapper.offsetToVisualLine(offset, beforeSoftWrap); } + public int visualLineToOffset(int visualLine) { + assertIsDispatchThread(); + myEditor.getSoftWrapModel().prepareToMapping(); + return myMapper.visualLineToOffset(visualLine); + } + public int visualLineStartOffset(int visualLine) { assertIsDispatchThread(); myEditor.getSoftWrapModel().prepareToMapping(); @@ -240,52 +244,31 @@ public class EditorView implements TextDrawingCallback, Disposable { public Dimension getPreferredSize() { assertIsDispatchThread(); + myEditor.getSoftWrapModel().prepareToMapping(); return mySizeManager.getPreferredSize(); } public int getMaxWidthInRange(int startOffset, int endOffset) { assertIsDispatchThread(); - return getMaxWidthInLineRange(offsetToVisualLine(startOffset, false), offsetToVisualLine(endOffset, true), null); + return getMaxWidthInLineRange(offsetToVisualLine(startOffset, false), offsetToVisualLine(endOffset, true)); } /** * If quickEvaluationListener is provided, quick approximate size evaluation becomes enabled, listener will be invoked * if approximation will in fact be used during width calculation. */ - int getMaxWidthInLineRange(int startVisualLine, int endVisualLine, @Nullable Runnable quickEvaluationListener) { + int getMaxWidthInLineRange(int startVisualLine, int endVisualLine) { myEditor.getSoftWrapModel().prepareToMapping(); int maxWidth = 0; - endVisualLine = Math.min(endVisualLine, myEditor.getVisibleLineCount() - 1); - for (int i = startVisualLine; i <= endVisualLine; i++) { - int width = getVisualLineWidth(i, quickEvaluationListener); + VisualLinesIterator iterator = new VisualLinesIterator(this, startVisualLine); + while (!iterator.atEnd() && iterator.getVisualLine() <= endVisualLine) { + int width = mySizeManager.getVisualLineWidth(iterator, null); maxWidth = Math.max(maxWidth, width); + iterator.advance(); } return maxWidth; } - private int getVisualLineWidth(int visualLine, @Nullable Runnable quickEvaluationListener) { - FoldRegion[] topLevelRegions = myEditor.getFoldingModel().fetchTopLevel(); - if (quickEvaluationListener != null && - (topLevelRegions == null || topLevelRegions.length == 0) && myEditor.getSoftWrapModel().getRegisteredSoftWraps().isEmpty() && - !myTextLayoutCache.hasCachedLayoutFor(visualLine)) { - // fast path - speeds up editor opening - quickEvaluationListener.run(); - return myMapper.offsetToLogicalPosition(myDocument.getLineEndOffset(visualLine)).column * getMaxCharWidth(); - } - int startOffset = myMapper.visualLineToOffset(visualLine); - float x = 0; - int maxOffset = 0; - for (VisualLineFragmentsIterator.Fragment fragment : VisualLineFragmentsIterator.create(this, startOffset, false, - quickEvaluationListener)) { - x = fragment.getEndX(); - maxOffset = Math.max(maxOffset, fragment.getMaxOffset()); - } - if (myEditor.getSoftWrapModel().getSoftWrap(maxOffset) != null) { - x += myEditor.getSoftWrapModel().getMinDrawingWidthInPixels(SoftWrapDrawingType.BEFORE_SOFT_WRAP_LINE_FEED); - } - return (int)x; - } - public void reinitSettings() { assertIsDispatchThread(); myPlainSpaceWidth = -1; diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/LineLayout.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/LineLayout.java index 119d4c9c49c6..64d46966c83b 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/LineLayout.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/LineLayout.java @@ -15,6 +15,7 @@ */ package com.intellij.openapi.editor.impl.view; +import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.bidi.BidiRegionsSeparator; import com.intellij.openapi.editor.bidi.LanguageBidiRegionsSeparator; import com.intellij.openapi.editor.colors.FontPreferences; @@ -46,8 +47,8 @@ class LineLayout { /** * Creates a layout for a fragment of text from editor. */ - LineLayout(@NotNull EditorView view, int startOffset, int endOffset, boolean skipBidiLayout) { - this(createFragments(view, startOffset, endOffset, skipBidiLayout), false); + LineLayout(@NotNull EditorView view, int line, boolean skipBidiLayout) { + this(createFragments(view, line, skipBidiLayout), false); } /** @@ -56,7 +57,7 @@ class LineLayout { LineLayout(@NotNull EditorView view, @NotNull CharSequence text, @JdkConstants.FontStyle int fontStyle) { this(createFragments(view, text, fontStyle), true); } - + private LineLayout(@NotNull List runs, boolean calculateWidth) { myBidiRunsInLogicalOrder = runs.toArray(new BidiRun[runs.size()]); myBidiRunsInVisualOrder = myBidiRunsInLogicalOrder.clone(); @@ -65,7 +66,7 @@ class LineLayout { myWidth = calculateWidth ? calculateWidth() : -1; } - + // runs are supposed to be in logical order initially private static void reorderRunsVisually(BidiRun[] bidiRuns) { if (bidiRuns.length > 1) { @@ -82,11 +83,14 @@ class LineLayout { return Bidi.requiresBidi(chars, 0, chars.length); } - private static List createFragments(@NotNull EditorView view, int lineStartOffset, int lineEndOffset, boolean skipBidiLayout) { + private static List createFragments(@NotNull EditorView view, int line, boolean skipBidiLayout) { + EditorImpl editor = view.getEditor(); + Document document = editor.getDocument(); + int lineStartOffset = document.getLineStartOffset(line); + int lineEndOffset = document.getLineEndOffset(line); if (lineEndOffset <= lineStartOffset) return Collections.emptyList(); if (skipBidiLayout) return Collections.singletonList(new BidiRun(lineEndOffset - lineStartOffset)); - EditorImpl editor = view.getEditor(); - CharSequence text = editor.getDocument().getImmutableCharSequence().subSequence(lineStartOffset, lineEndOffset); + CharSequence text = document.getImmutableCharSequence().subSequence(lineStartOffset, lineEndOffset); char[] chars = CharArrayUtil.fromSequence(text); return createRuns(editor, chars, lineStartOffset); } @@ -240,8 +244,8 @@ class LineLayout { * If quickEvaluationListener is provided, quick approximate iteration becomes enabled, listener will be invoked * if approximation will in fact be used during width calculation. */ - Iterable getFragmentsInVisualOrder(@NotNull final EditorView view, - final int lineStartOffset, + Iterator getFragmentsInVisualOrder(@NotNull final EditorView view, + final int line, final float startX, final int startVisualColumn, final int startOffset, @@ -257,18 +261,13 @@ class LineLayout { for (BidiRun run : myBidiRunsInLogicalOrder) { if (run.endOffset <= startOffset) continue; if (run.startOffset >= endOffset) break; - runList.add(run.subRun(view, lineStartOffset, startOffset, endOffset, quickEvaluationListener)); + runList.add(run.subRun(view, line, startOffset, endOffset, quickEvaluationListener)); } runs = runList.toArray(new BidiRun[runList.size()]); reorderRunsVisually(runs); } - final int startLogicalColumn = view.offsetToLogicalPosition(lineStartOffset + startOffset).column; - return new Iterable() { - @Override - public Iterator iterator() { - return new VisualOrderIterator(view, lineStartOffset, startX, startVisualColumn, startLogicalColumn, startOffset, runs); - } - }; + final int startLogicalColumn = view.getLogicalPositionCache().offsetToLogicalColumn(line, startOffset); + return new VisualOrderIterator(view, line, startX, startVisualColumn, startLogicalColumn, startOffset, runs); } boolean isLtr() { @@ -348,7 +347,7 @@ class LineLayout { return chunks; } - private BidiRun subRun(@NotNull EditorView view, int lineStartOffset, int targetStartOffset, int targetEndOffset, + private BidiRun subRun(@NotNull EditorView view, int line, int targetStartOffset, int targetEndOffset, @Nullable Runnable quickEvaluationListener) { assert targetStartOffset < endOffset; assert targetEndOffset > startOffset; @@ -359,7 +358,7 @@ class LineLayout { for (Chunk chunk : getChunks()) { if (chunk.endOffset <= start) continue; if (chunk.startOffset >= end) break; - subChunks.add(chunk.subChunk(view, this, lineStartOffset, start, end, quickEvaluationListener)); + subChunks.add(chunk.subChunk(view, this, line, start, end, quickEvaluationListener)); } subRun.chunks = subChunks.toArray(new Chunk[subChunks.size()]); return subRun; @@ -376,11 +375,12 @@ class LineLayout { this.endOffset = endOffset; } - private void ensureLayout(@NotNull EditorView view, BidiRun run, int lineStartOffset) { + private void ensureLayout(@NotNull EditorView view, BidiRun run, int line) { if (isReal()) { view.getTextLayoutCache().onChunkAccess(this); } if (!fragments.isEmpty()) return; + int lineStartOffset = view.getEditor().getDocument().getLineStartOffset(line); int start = lineStartOffset + startOffset; int end = lineStartOffset + endOffset; IterationState it = new IterationState(view.getEditor(), start, end, false, false, false, false); @@ -395,7 +395,7 @@ class LineLayout { assert !fragments.isEmpty(); } - private Chunk subChunk(EditorView view, BidiRun run, int lineStartOffset, int targetStartOffset, int targetEndOffset, + private Chunk subChunk(EditorView view, BidiRun run, int line, int targetStartOffset, int targetEndOffset, @Nullable Runnable quickEvaluationListener) { assert targetStartOffset < endOffset; assert targetEndOffset > startOffset; @@ -403,12 +403,12 @@ class LineLayout { int end = Math.min(endOffset, targetEndOffset); if (quickEvaluationListener != null && fragments.isEmpty()) { quickEvaluationListener.run(); - return new ApproximationChunk(view, lineStartOffset, start, end); + return new ApproximationChunk(view, line, start, end); } if (start == startOffset && end == this.endOffset) { return this; } - ensureLayout(view, run, lineStartOffset); + ensureLayout(view, run, line); Chunk chunk = new Chunk(start, end); int offset = startOffset; for (LineFragment fragment : fragments) { @@ -432,10 +432,10 @@ class LineLayout { } private static class ApproximationChunk extends Chunk { - private ApproximationChunk(@NotNull EditorView view, int lineStartOffset, int start, int end) { + private ApproximationChunk(@NotNull EditorView view, int line, int start, int end) { super(start, end); - int startColumn = view.offsetToLogicalPosition(lineStartOffset + start).column; - int endColumn = view.offsetToLogicalPosition(lineStartOffset + end).column; + int startColumn = view.getLogicalPositionCache().offsetToLogicalColumn(line, start); + int endColumn = view.getLogicalPositionCache().offsetToLogicalColumn(line, end); fragments.add(new ApproximationFragment(end - start, endColumn - startColumn, view.getMaxCharWidth())); } @@ -447,7 +447,7 @@ class LineLayout { private static class VisualOrderIterator implements Iterator { private final EditorView myView; - private final int myLineStartOffset; + private final int myLine; private final BidiRun[] myRuns; private int myRunIndex = 0; private int myChunkIndex = 0; @@ -455,10 +455,10 @@ class LineLayout { private int myOffsetInsideRun = 0; private VisualFragment myFragment = new VisualFragment(); - private VisualOrderIterator(EditorView view, int lineStartOffset, + private VisualOrderIterator(EditorView view, int line, float startX, int startVisualColumn, int startLogicalColumn, int startOffset, BidiRun[] runsInVisualOrder) { myView = view; - myLineStartOffset = lineStartOffset; + myLine = line; myRuns = runsInVisualOrder; myFragment.startX = startX; myFragment.startVisualColumn = startVisualColumn; @@ -474,7 +474,7 @@ class LineLayout { if (myChunkIndex >= chunks.length) return false; Chunk chunk = chunks[run.isRtl() ? chunks.length - 1 - myChunkIndex : myChunkIndex]; if (myView != null) { - chunk.ensureLayout(myView, run, myLineStartOffset); + chunk.ensureLayout(myView, run, myLine); } return myFragmentIndex < chunk.fragments.size(); } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/LogicalPositionCache.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/LogicalPositionCache.java index b86b722d8a09..6bc92a6b3686 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/LogicalPositionCache.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/LogicalPositionCache.java @@ -83,6 +83,12 @@ class LogicalPositionCache implements PrioritizedDocumentListener, Disposable { LineData lineData = getLineInfo(line); return new LogicalPosition(line, lineData.offsetToLogicalColumn(myDocument, line, myTabSize, offset)); } + + synchronized int offsetToLogicalColumn(int line, int intraLineOffset) { + if (line < 0 || line >= myDocument.getLineCount()) return 0; + LineData lineData = getLineInfo(line); + return lineData.offsetToLogicalColumn(myDocument, line, myTabSize, myDocument.getLineStartOffset(line) + intraLineOffset); + } synchronized int logicalPositionToOffset(@NotNull LogicalPosition pos) { int line = pos.line; diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/TextLayoutCache.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/TextLayoutCache.java index e8dfc21e2069..c316cad2a537 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/TextLayoutCache.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/TextLayoutCache.java @@ -127,7 +127,7 @@ class TextLayoutCache implements PrioritizedDocumentListener, Disposable { checkDisposed(); LineLayout result = myLines.get(line); if (result == null || result == myBidiNotRequiredMarker) { - result = createLineLayout(line, result == myBidiNotRequiredMarker); + result = new LineLayout(myView, line, result == myBidiNotRequiredMarker); myLines.set(line, result); } return result; @@ -138,13 +138,6 @@ class TextLayoutCache implements PrioritizedDocumentListener, Disposable { return layout != null && layout != myBidiNotRequiredMarker; } - @NotNull - private LineLayout createLineLayout(int line, boolean skipBidiLayout) { - int lineStart = myDocument.getLineStartOffset(line); - int lineEnd = myDocument.getLineEndOffset(line); - return new LineLayout(myView, lineStart, lineEnd, skipBidiLayout); - } - private int getChunkCacheSizeLimit() { return myView.getEditor().getContentComponent().isShowing() ? MAX_CHUNKS_IN_ACTIVE_EDITOR : MAX_CHUNKS_IN_INACTIVE_EDITOR; } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/VisualLineFragmentsIterator.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/VisualLineFragmentsIterator.java index 0e2344a1be4e..fa521cb2d625 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/VisualLineFragmentsIterator.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/VisualLineFragmentsIterator.java @@ -22,6 +22,7 @@ import com.intellij.openapi.editor.ex.FoldingModelEx; import com.intellij.openapi.editor.ex.util.EditorUtil; import com.intellij.openapi.editor.impl.EditorImpl; import com.intellij.openapi.editor.impl.SoftWrapModelImpl; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.awt.*; @@ -35,26 +36,35 @@ import java.util.NoSuchElementException; */ class VisualLineFragmentsIterator implements Iterator { - /** - * If quickEvaluationListener is provided, quick approximate iteration mode becomes enabled, listener will be invoked - * if approximation will in fact be used during width calculation. - */ - static Iterable create(final EditorView view, final int offset, final boolean beforeSoftWrap, - @Nullable final Runnable quickEvaluationListener) { + static Iterable create(final EditorView view, final int offset, final boolean beforeSoftWrap) { return new Iterable() { @Override public Iterator iterator() { - return new VisualLineFragmentsIterator(view, offset, beforeSoftWrap, quickEvaluationListener); + return new VisualLineFragmentsIterator(view, offset, beforeSoftWrap, null); } }; } - private final EditorView myView; - private final Document myDocument; - private final FoldRegion[] myRegions; - private final Fragment myFragment = new Fragment(); - private final int myVisualLineStartOffset; - private final Runnable myQuickEvaluationListener; + /** + * If quickEvaluationListener is provided, quick approximate iteration mode becomes enabled, listener will be invoked + * if approximation will in fact be used during width calculation. + */ + static Iterable create(final EditorView view, @NotNull final VisualLinesIterator visualLinesIterator, + @Nullable final Runnable quickEvaluationListener) { + return new Iterable() { + @Override + public Iterator iterator() { + return new VisualLineFragmentsIterator(view, visualLinesIterator, quickEvaluationListener); + } + }; + } + + private EditorView myView; + private Document myDocument; + private FoldRegion[] myRegions; + private Fragment myFragment = new Fragment(); + private int myVisualLineStartOffset; + private Runnable myQuickEvaluationListener; private int mySegmentStartOffset; private int mySegmentEndOffset; @@ -69,15 +79,9 @@ class VisualLineFragmentsIterator implements Iterator softWraps = softWrapModel.getRegisteredSoftWraps(); int currentOrPrevWrapIndex = softWrapModel.getSoftWrapIndex(offset); @@ -89,16 +93,51 @@ class VisualLineFragmentsIterator implements Iterator= softWraps.size() ? null : softWraps.get(currentOrPrevWrapIndex); - SoftWrap followingWrap = (currentOrPrevWrapIndex + 1) >= softWraps.size() ? null : softWraps.get(currentOrPrevWrapIndex + 1); - if (currentOrPrevWrap != null && currentOrPrevWrap.getStart() > visualLineStartOffset) { visualLineStartOffset = currentOrPrevWrap.getStart(); } - myVisualLineStartOffset = mySegmentStartOffset = visualLineStartOffset; + int nextFoldingIndex = editor.getFoldingModel().getLastCollapsedRegionBefore(visualLineStartOffset) + 1; + + init(view, + visualLineStartOffset, + editor.getDocument().getLineNumber(visualLineStartOffset), + currentOrPrevWrapIndex, + nextFoldingIndex, + quickEvaluationListener); + } - myCurrentFoldRegionIndex = foldingModel.getLastCollapsedRegionBefore(mySegmentStartOffset) + 1; - myCurrentEndLogicalLine = myDocument.getLineNumber(mySegmentStartOffset); + public VisualLineFragmentsIterator(@NotNull EditorView view, @NotNull VisualLinesIterator visualLinesIterator, + @Nullable Runnable quickEvaluationListener) { + assert !visualLinesIterator.atEnd(); + init(view, + visualLinesIterator.getVisualLineStartOffset(), + visualLinesIterator.getStartLogicalLine(), + visualLinesIterator.getStartOrPrevWrapIndex(), + visualLinesIterator.getStartFoldingIndex(), + quickEvaluationListener); + } + + private void init(EditorView view, int startOffset, int startLogicalLine, int currentOrPrevWrapIndex, int nextFoldingIndex, + @Nullable Runnable quickEvaluationListener) { + myQuickEvaluationListener = quickEvaluationListener; + myView = view; + EditorImpl editor = view.getEditor(); + myDocument = editor.getDocument(); + FoldingModelEx foldingModel = editor.getFoldingModel(); + FoldRegion[] regions = foldingModel.fetchTopLevel(); + myRegions = regions == null ? FoldRegion.EMPTY_ARRAY : regions; + SoftWrapModelImpl softWrapModel = editor.getSoftWrapModel(); + List softWraps = softWrapModel.getRegisteredSoftWraps(); + SoftWrap currentOrPrevWrap = currentOrPrevWrapIndex < 0 || currentOrPrevWrapIndex >= softWraps.size() ? null : + softWraps.get(currentOrPrevWrapIndex); + SoftWrap followingWrap = (currentOrPrevWrapIndex + 1) < 0 || (currentOrPrevWrapIndex + 1) >= softWraps.size() ? null : + softWraps.get(currentOrPrevWrapIndex + 1); + + myVisualLineStartOffset = mySegmentStartOffset = startOffset; + + myCurrentFoldRegionIndex = nextFoldingIndex; + myCurrentEndLogicalLine = startLogicalLine; if (mySegmentStartOffset == 0) { myCurrentX = myView.getPrefixTextWidthInPixels(); } @@ -117,9 +156,9 @@ class VisualLineFragmentsIterator implements Iterator mySoftWraps; + + private int myVisualLine; + private int myOffset; + private int myLogicalLine = 1; + private int myFoldRegion; + private int mySoftWrap; + + VisualLinesIterator(@NotNull EditorView view, int startVisualLine) { + EditorImpl editor = view.getEditor(); + SoftWrapModelImpl softWrapModel = editor.getSoftWrapModel(); + myDocument = editor.getDocument(); + FoldRegion[] regions = editor.getFoldingModel().fetchTopLevel(); + myFoldRegions = regions == null ? FoldRegion.EMPTY_ARRAY : regions; + mySoftWraps = softWrapModel.getRegisteredSoftWraps(); + + if (startVisualLine < 0 || startVisualLine >= editor.getVisibleLineCount()) { + myOffset = -1; + } + else if (startVisualLine > 0) { + myVisualLine = startVisualLine; + myOffset = startVisualLine >= 0 && startVisualLine < editor.getVisibleLineCount() ? view.visualLineToOffset(startVisualLine) : -1; + myLogicalLine = myDocument.getLineNumber(myOffset) + 1; + mySoftWrap = softWrapModel.getSoftWrapIndex(myOffset) + 1; + if (mySoftWrap <= 0) { + mySoftWrap = -mySoftWrap; + } + myFoldRegion = editor.getFoldingModel().getLastCollapsedRegionBefore(myOffset) + 1; + } + } + + boolean atEnd() { + return myOffset == -1; + } + + void advance() { + checkEnd(); + int nextWrapOffset = getNextSoftWrapOffset(); + int nextLineStart = getNextNotFoldedLineStartOffset(); + myOffset = Math.min(nextWrapOffset, nextLineStart); + if (myOffset == Integer.MAX_VALUE) { + myOffset = -1; + } + else if (myOffset == nextWrapOffset) { + mySoftWrap++; + } + else { + myLogicalLine++; + } + myVisualLine++; + } + + private int getNextSoftWrapOffset() { + return mySoftWrap < mySoftWraps.size() ? mySoftWraps.get(mySoftWrap).getStart() : Integer.MAX_VALUE; + } + + private int getNextNotFoldedLineStartOffset() { + while (myLogicalLine < myDocument.getLineCount()) { + int lineStartOffset = myDocument.getLineStartOffset(myLogicalLine); + if (!isCollapsed(lineStartOffset)) return lineStartOffset; + myLogicalLine++; + } + return Integer.MAX_VALUE; + } + + private boolean isCollapsed(int offset) { + while (myFoldRegion < myFoldRegions.length) { + FoldRegion foldRegion = myFoldRegions[myFoldRegion]; + if (offset <= foldRegion.getStartOffset()) return false; + if (offset <= foldRegion.getEndOffset()) return true; + myFoldRegion++; + } + return false; + } + + int getVisualLine() { + checkEnd(); + return myVisualLine; + } + + int getVisualLineStartOffset() { + checkEnd(); + return myOffset; + } + + int getStartLogicalLine() { + checkEnd(); + return myLogicalLine - 1; + } + + int getStartOrPrevWrapIndex() { + checkEnd(); + return mySoftWrap - 1; + } + + int getStartFoldingIndex() { + checkEnd(); + return myFoldRegion; + } + + private void checkEnd() { + if (atEnd()) throw new IllegalStateException("Iteration finished"); + } + +}