optimize editor painting - do not invoke costly visualPositionTofOffset conversions

This commit is contained in:
Dmitry Batrak
2015-11-25 15:30:39 +03:00
parent 64b924a99a
commit f35a923139
2 changed files with 125 additions and 69 deletions
@@ -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<Integer, Couple<Integer>> 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();
@@ -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<? extends SoftWrap> 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);
}
}
}
}