From f35a9231392db4032ffaa7e481cb0555e9e1a637 Mon Sep 17 00:00:00 2001 From: Dmitry Batrak Date: Wed, 25 Nov 2015 15:29:36 +0300 Subject: [PATCH] optimize editor painting - do not invoke costly visualPositionTofOffset conversions --- .../editor/impl/view/EditorPainter.java | 25 ++- .../editor/impl/view/VisualLinesIterator.java | 169 +++++++++++------- 2 files changed, 125 insertions(+), 69 deletions(-) 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 3c820d26e4f3..781637c18048 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 @@ -135,14 +135,17 @@ class EditorPainter implements TextDrawingCallback { private void paintBackground(Graphics2D g, Rectangle clip, int startVisualLine, int endVisualLine) { int lineCount = myEditor.getVisibleLineCount(); final Map> virtualSelectionMap = createVirtualSelectionMap(startVisualLine, endVisualLine); - for (int visualLine = startVisualLine; visualLine <= endVisualLine; visualLine++) { + VisualLinesIterator visLinesIterator = new VisualLinesIterator(myView, startVisualLine); + while (!visLinesIterator.atEnd()) { + int visualLine = visLinesIterator.getVisualLine(); + if (visualLine > endVisualLine) break; int y = myView.visualLineToY(visualLine); LineLayout prefixLayout = myView.getPrefixLayout(); if (visualLine == 0 && prefixLayout != null) { paintBackground(g, myView.getPrefixAttributes(), 0, y, prefixLayout.getWidth()); } if (visualLine >= lineCount) break; - paintLineFragments(g, clip, visualLine, y, new LineFragmentPainter() { + paintLineFragments(g, clip, visLinesIterator, y, new LineFragmentPainter() { @Override public void paintBeforeLineStart(Graphics2D g, TextAttributes attributes, int columnEnd, float xEnd, int y) { paintBackground(g, attributes, 0, y, xEnd); @@ -168,6 +171,7 @@ class EditorPainter implements TextDrawingCallback { } } }); + visLinesIterator.advance(); } } @@ -320,7 +324,10 @@ class EditorPainter implements TextDrawingCallback { final CharSequence text = myDocument.getImmutableCharSequence(); final EditorImpl.LineWhitespacePaintingStrategy whitespacePaintingStrategy = myEditor.new LineWhitespacePaintingStrategy(); int lineCount = myEditor.getVisibleLineCount(); - for (int visualLine = startVisualLine; visualLine <= endVisualLine; visualLine++) { + VisualLinesIterator visLinesIterator = new VisualLinesIterator(myView, startVisualLine); + while (!visLinesIterator.atEnd()) { + int visualLine = visLinesIterator.getVisualLine(); + if (visualLine > endVisualLine) break; int y = myView.visualLineToY(visualLine) + myView.getAscent(); LineLayout prefixLayout = myView.getPrefixLayout(); if (visualLine == 0 && prefixLayout != null) { @@ -332,7 +339,7 @@ class EditorPainter implements TextDrawingCallback { final int[] currentLogicalLine = new int[] {-1}; - paintLineFragments(g, clip, visualLine, y, new LineFragmentPainter() { + paintLineFragments(g, clip, visLinesIterator, y, new LineFragmentPainter() { @Override public void paintBeforeLineStart(Graphics2D g, TextAttributes attributes, int columnEnd, float xEnd, int y) { SoftWrapModelImpl softWrapModel = myEditor.getSoftWrapModel(); @@ -374,6 +381,7 @@ class EditorPainter implements TextDrawingCallback { } } }); + visLinesIterator.advance(); } } @@ -807,15 +815,16 @@ class EditorPainter implements TextDrawingCallback { } } - private void paintLineFragments(Graphics2D g, Rectangle clip, int visualLine, int y, LineFragmentPainter painter) { + private void paintLineFragments(Graphics2D g, Rectangle clip, VisualLinesIterator visLineIterator, int y, LineFragmentPainter painter) { + int visualLine = visLineIterator.getVisualLine(); float x = visualLine == 0 ? myView.getPrefixTextWidthInPixels() : 0; - int offset = myView.visualPositionToOffset(new VisualPosition(visualLine, 0)); - int visualLineEndOffset = myView.visualPositionToOffset(new VisualPosition(visualLine, Integer.MAX_VALUE, true)); + int offset = visLineIterator.getVisualLineStartOffset(); + int visualLineEndOffset = visLineIterator.getVisualLineEndOffset(); IterationState it = null; int prevEndOffset = -1; boolean firstFragment = true; int maxColumn = 0; - for (VisualLineFragmentsIterator.Fragment fragment : VisualLineFragmentsIterator.create(myView, offset, false)) { + for (VisualLineFragmentsIterator.Fragment fragment : VisualLineFragmentsIterator.create(myView, visLineIterator, null)) { int fragmentStartOffset = fragment.getStartOffset(); int start = fragmentStartOffset; int end = fragment.getEndOffset(); diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/VisualLinesIterator.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/VisualLinesIterator.java index 690f5aa0b6b6..f7e71dc176bb 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/VisualLinesIterator.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/VisualLinesIterator.java @@ -25,108 +25,155 @@ import org.jetbrains.annotations.NotNull; import java.util.List; class VisualLinesIterator { + private final EditorView myView; + private final EditorImpl myEditor; private final Document myDocument; private final FoldRegion[] myFoldRegions; private final List mySoftWraps; - private int myVisualLine; - private int myOffset; - private int myLogicalLine = 1; - private int myFoldRegion; - private int mySoftWrap; + @NotNull + private Location myLocation; + private Location myNextLocation; VisualLinesIterator(@NotNull EditorView view, int startVisualLine) { - EditorImpl editor = view.getEditor(); - SoftWrapModelImpl softWrapModel = editor.getSoftWrapModel(); - myDocument = editor.getDocument(); - FoldRegion[] regions = editor.getFoldingModel().fetchTopLevel(); + myView = view; + myEditor = view.getEditor(); + SoftWrapModelImpl softWrapModel = myEditor.getSoftWrapModel(); + myDocument = myEditor.getDocument(); + FoldRegion[] regions = myEditor.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; - } + + myLocation = new Location(startVisualLine); } boolean atEnd() { - return myOffset == -1; + return myLocation.atEnd(); } void advance() { checkEnd(); - int nextWrapOffset = getNextSoftWrapOffset(); - myOffset = getNextVisualLineStartOffset(nextWrapOffset); - if (myOffset == Integer.MAX_VALUE) { - myOffset = -1; + if (myNextLocation == null) { + myLocation.advance(); } - else if (myOffset == nextWrapOffset) { - mySoftWrap++; + else { + myLocation = myNextLocation; + myNextLocation = null; } - myVisualLine++; - while (myFoldRegion < myFoldRegions.length && myFoldRegions[myFoldRegion].getStartOffset() < myOffset) myFoldRegion++; - } - - private int getNextSoftWrapOffset() { - return mySoftWrap < mySoftWraps.size() ? mySoftWraps.get(mySoftWrap).getStart() : Integer.MAX_VALUE; - } - - private int getNextVisualLineStartOffset(int nextWrapOffset) { - while (myLogicalLine < myDocument.getLineCount()) { - int lineStartOffset = myDocument.getLineStartOffset(myLogicalLine); - if (lineStartOffset > nextWrapOffset) return nextWrapOffset; - myLogicalLine++; - if (!isCollapsed(lineStartOffset)) return lineStartOffset; - } - return nextWrapOffset; - } - - 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; + return myLocation.visualLine; } int getVisualLineStartOffset() { checkEnd(); - return myOffset; + return myLocation.offset; + } + + int getVisualLineEndOffset() { + checkEnd(); + if (myNextLocation == null) { + myNextLocation = myLocation.clone(); + myNextLocation.advance(); + } + return myNextLocation.atEnd() ? myDocument.getTextLength() : + myNextLocation.softWrap == myLocation.softWrap ? myDocument.getLineEndOffset(myNextLocation.logicalLine - 2) : + myNextLocation.offset; } int getStartLogicalLine() { checkEnd(); - return myLogicalLine - 1; + return myLocation.logicalLine - 1; } int getStartOrPrevWrapIndex() { checkEnd(); - return mySoftWrap - 1; + return myLocation.softWrap - 1; } int getStartFoldingIndex() { checkEnd(); - return myFoldRegion; + return myLocation.foldRegion; } private void checkEnd() { if (atEnd()) throw new IllegalStateException("Iteration finished"); } + private final class Location implements Cloneable { + private int visualLine; // current visual line + private int offset; // start offset of the current visual line + private int logicalLine = 1; // 1 + start logical line of the current visual line + private int foldRegion; // index of the first folding region on current or following visual lines + private int softWrap; // index of the first soft wrap after the start of current visual line + + private Location(int startVisualLine) { + if (startVisualLine < 0 || startVisualLine >= myEditor.getVisibleLineCount()) { + offset = -1; + } + else if (startVisualLine > 0) { + visualLine = startVisualLine; + offset = myView.visualLineToOffset(startVisualLine); + logicalLine = myDocument.getLineNumber(offset) + 1; + softWrap = myEditor.getSoftWrapModel().getSoftWrapIndex(offset) + 1; + if (softWrap <= 0) { + softWrap = -softWrap; + } + foldRegion = myEditor.getFoldingModel().getLastCollapsedRegionBefore(offset) + 1; + } + } + + private void advance() { + int nextWrapOffset = getNextSoftWrapOffset(); + offset = getNextVisualLineStartOffset(nextWrapOffset); + if (offset == Integer.MAX_VALUE) { + offset = -1; + } + else if (offset == nextWrapOffset) { + softWrap++; + } + visualLine++; + while (foldRegion < myFoldRegions.length && myFoldRegions[foldRegion].getStartOffset() < offset) foldRegion++; + } + + private int getNextSoftWrapOffset() { + return softWrap < mySoftWraps.size() ? mySoftWraps.get(softWrap).getStart() : Integer.MAX_VALUE; + } + + private int getNextVisualLineStartOffset(int nextWrapOffset) { + while (logicalLine < myDocument.getLineCount()) { + int lineStartOffset = myDocument.getLineStartOffset(logicalLine); + if (lineStartOffset > nextWrapOffset) return nextWrapOffset; + logicalLine++; + if (!isCollapsed(lineStartOffset)) return lineStartOffset; + } + return nextWrapOffset; + } + + private boolean isCollapsed(int offset) { + while (foldRegion < myFoldRegions.length) { + FoldRegion region = myFoldRegions[foldRegion]; + if (offset <= region.getStartOffset()) return false; + if (offset <= region.getEndOffset()) return true; + foldRegion++; + } + return false; + } + + private boolean atEnd() { + return offset == -1; + } + + @Override + protected Location clone() { + try { + return (Location)super.clone(); + } + catch (CloneNotSupportedException e) { + throw new RuntimeException(e); + } + } + } }