optimisation: visit only overlapping highlighters during paint

This commit is contained in:
Alexey Kudravtsev
2011-05-11 16:26:19 +04:00
parent 8aa18db91e
commit 8b2a300ab0
8 changed files with 171 additions and 151 deletions
@@ -23,7 +23,6 @@ import com.intellij.openapi.editor.impl.event.MarkupModelListener;
import com.intellij.openapi.editor.markup.HighlighterTargetArea;
import com.intellij.openapi.editor.markup.RangeHighlighter;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.editor.impl.Interval;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.UserDataHolderBase;
import com.intellij.openapi.util.ProperTextRange;
@@ -130,18 +129,15 @@ class MarkupModelWindow extends UserDataHolderBase implements MarkupModelEx {
return false;
}
public Iterator<RangeHighlighterEx> iterator() {
// todo
return null;
@NotNull
@Override
public Iterator<RangeHighlighterEx> overlappingIterator(int startOffset, int endOffset) {
// todo convert
return myHostModel.overlappingIterator(startOffset, endOffset);
}
@NotNull
public Iterator<RangeHighlighterEx> iteratorFrom(@NotNull Interval interval) {
// todo convert
return myHostModel.iteratorFrom(interval);
}
public boolean sweep(int start, int end, @NotNull SweepProcessor<RangeHighlighterEx> sweepProcessor) {
// todo convert
return myHostModel.sweep(start, end, sweepProcessor);
}
}
}
@@ -15,7 +15,6 @@
*/
package com.intellij.openapi.editor.ex;
import com.intellij.openapi.editor.impl.Interval;
import com.intellij.openapi.editor.impl.event.MarkupModelListener;
import com.intellij.openapi.editor.markup.HighlighterTargetArea;
import com.intellij.openapi.editor.markup.MarkupModel;
@@ -31,7 +30,7 @@ import java.util.Iterator;
/**
* @author max
*/
public interface MarkupModelEx extends MarkupModel, Iterable<RangeHighlighterEx> {
public interface MarkupModelEx extends MarkupModel {
void dispose();
RangeHighlighter addPersistentLineHighlighter(int lineNumber, int layer, TextAttributes textAttributes);
@@ -43,17 +42,18 @@ public interface MarkupModelEx extends MarkupModel, Iterable<RangeHighlighterEx>
void setRangeHighlighterAttributes(@NotNull RangeHighlighter highlighter, TextAttributes textAttributes);
boolean processHighlightsOverlappingWith(int start, int end, @NotNull Processor<? super RangeHighlighterEx> processor);
@NotNull
Iterator<RangeHighlighterEx> iteratorFrom(@NotNull Interval interval);
Iterator<RangeHighlighterEx> overlappingIterator(int startOffset, int endOffset);
// optimization: creates highlighter and fires only one event: highlighterCreated
RangeHighlighterEx addRangeHighlighterAndChangeAttributes(int startOffset,
int endOffset,
int layer,
TextAttributes textAttributes,
@NotNull HighlighterTargetArea targetArea,
boolean isPersistent,
Consumer<RangeHighlighterEx> changeAttributesAction);
int endOffset,
int layer,
TextAttributes textAttributes,
@NotNull HighlighterTargetArea targetArea,
boolean isPersistent,
Consumer<RangeHighlighterEx> changeAttributesAction);
// runs change attributes action and fires highlighterChanged event if there were changes
void changeAttributesInBatch(@NotNull RangeHighlighterEx highlighter, @NotNull Consumer<RangeHighlighterEx> changeAttributesAction);
@@ -377,18 +377,23 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse
void process(RangeHighlighter highlighter);
}
private void processRangeHighlighters(RangeHighlighterProcessor processor, int startOffset, int endOffset) {
final MarkupModelEx docMarkup = (MarkupModelEx)myEditor.getDocument().getMarkupModel(myEditor.getProject());
Iterator<RangeHighlighterEx> docHighlighters = docMarkup.iterator();
private void processRangeHighlighters(int startOffset, int endOffset, RangeHighlighterProcessor processor) {
Document document = myEditor.getDocument();
final MarkupModelEx docMarkup = (MarkupModelEx)document.getMarkupModel(myEditor.getProject());
// we limit highlighters to process to between line starting at startOffset and line ending at endOffset
int docLength = document.getTextLength();
int patchedStartOffset = startOffset < docLength ? document.getLineStartOffset(document.getLineNumber(startOffset)) : docLength;
int patchedEndOffset = endOffset <= docLength ? document.getLineEndOffset(document.getLineNumber(endOffset)) + 1 : docLength;
Iterator<RangeHighlighterEx> docHighlighters = docMarkup.overlappingIterator(patchedStartOffset, patchedEndOffset);
final MarkupModelEx editorMarkup = (MarkupModelEx)myEditor.getMarkupModel();
Iterator<RangeHighlighterEx> editorHighlighters = editorMarkup.iterator();
Iterator<RangeHighlighterEx> editorHighlighters = editorMarkup.overlappingIterator(startOffset, endOffset);
RangeHighlighterEx lastDocHighlighter = null;
RangeHighlighterEx lastEditorHighlighter = null;
while (true) {
if (lastDocHighlighter == null && docHighlighters != null && docHighlighters.hasNext()) {
if (lastDocHighlighter == null && docHighlighters.hasNext()) {
lastDocHighlighter = docHighlighters.next();
if (!lastDocHighlighter.isValid() || lastDocHighlighter.getAffectedAreaStartOffset() > endOffset) {
lastDocHighlighter = null;
@@ -396,12 +401,11 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse
}
if (lastDocHighlighter.getAffectedAreaEndOffset() < startOffset) {
lastDocHighlighter = null;
//docHighlighters = null;
continue;
}
}
if (lastEditorHighlighter == null && editorHighlighters != null && editorHighlighters.hasNext()) {
if (lastEditorHighlighter == null && editorHighlighters.hasNext()) {
lastEditorHighlighter = editorHighlighters.next();
if (!lastEditorHighlighter.isValid() || lastEditorHighlighter.getAffectedAreaStartOffset() > endOffset) {
lastEditorHighlighter = null;
@@ -409,7 +413,6 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse
}
if (lastEditorHighlighter.getAffectedAreaEndOffset() < startOffset) {
lastEditorHighlighter = null;
//editorHighlighters = null;
continue;
}
}
@@ -431,10 +434,10 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse
if (!lowerHighlighter.isValid()) continue;
int startLineIndex = lowerHighlighter.getDocument().getLineNumber(startOffset);
if (startLineIndex < 0 || startLineIndex >= myEditor.getDocument().getLineCount()) continue;
if (startLineIndex < 0 || startLineIndex >= document.getLineCount()) continue;
int endLineIndex = lowerHighlighter.getDocument().getLineNumber(endOffset);
if (endLineIndex < 0 || endLineIndex >= myEditor.getDocument().getLineCount()) continue;
if (endLineIndex < 0 || endLineIndex >= document.getLineCount()) continue;
if (lowerHighlighter.getEditorFilter().avaliableIn(myEditor)) {
processor.process(lowerHighlighter);
@@ -487,7 +490,7 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse
private void calcIconAreaWidth() {
myLineToGutterRenderers = new TIntObjectHashMap<ArrayList<GutterIconRenderer>>();
processRangeHighlighters(new RangeHighlighterProcessor() {
processRangeHighlighters(0, myEditor.getDocument().getTextLength(), new RangeHighlighterProcessor() {
public void process(RangeHighlighter highlighter) {
GutterIconRenderer renderer = highlighter.getGutterIconRenderer();
if (renderer == null) return;
@@ -505,7 +508,7 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse
renderers.add(renderer);
}
}
}, 0, myEditor.getDocument().getTextLength());
});
myIconsAreaWidth = START_ICON_AREA_WIDTH;
@@ -534,11 +537,11 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse
Object hint = g2.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
try {
processRangeHighlighters(new RangeHighlighterProcessor() {
processRangeHighlighters(firstVisibleOffset, lastVisibleOffset, new RangeHighlighterProcessor() {
public void process(RangeHighlighter highlighter) {
paintLineMarkerRenderer(highlighter, g);
}
}, firstVisibleOffset, lastVisibleOffset);
});
}
finally {
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, hint);
@@ -1199,7 +1202,7 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse
int lastVisibleOffset = myEditor.logicalPositionToOffset(
myEditor.xyToLogicalPosition(new Point(0, clip.y + clip.height + myEditor.getLineHeight())));
processRangeHighlighters(new RangeHighlighterProcessor() {
processRangeHighlighters(firstVisibleOffset, lastVisibleOffset, new RangeHighlighterProcessor() {
public void process(RangeHighlighter highlighter) {
if (gutterRenderer[0] != null) return;
Rectangle rectangle = getLineRendererRectangle(highlighter);
@@ -1218,7 +1221,7 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse
}
}
}
}, firstVisibleOffset, lastVisibleOffset);
});
return gutterRenderer[0];
}
@@ -106,14 +106,12 @@ public class EmptyMarkupModel implements MarkupModelEx {
return false;
}
public Iterator<RangeHighlighterEx> iterator() {
@NotNull
@Override
public Iterator<RangeHighlighterEx> overlappingIterator(int startOffset, int endOffset) {
return ContainerUtil.emptyIterator();
}
@NotNull
public Iterator<RangeHighlighterEx> iteratorFrom(@NotNull Interval interval) {
return iterator();
}
public boolean sweep(int start, int end, @NotNull SweepProcessor<RangeHighlighterEx> sweepProcessor) {
return false;
}
@@ -21,7 +21,7 @@ import org.jetbrains.annotations.NotNull;
/**
* User: cdr
*/
public interface IntervalTree<T extends Interval> extends Iterable<T> {
public interface IntervalTree<T extends Interval> {
boolean process(@NotNull Processor<? super T> processor);
boolean processOverlappingWith(int start, int end, @NotNull Processor<? super T> processor);
boolean processOverlappingWith(int offset, @NotNull Processor<? super T> processor);
@@ -25,10 +25,7 @@ import gnu.trove.TLongHashSet;
import org.jetbrains.annotations.NotNull;
import java.lang.ref.ReferenceQueue;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
@@ -37,16 +34,15 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* User: cdr
*/
public abstract class IntervalTreeImpl<T extends MutableInterval> extends RedBlackTree<T> implements IntervalTree<T> {
public abstract class IntervalTreeImpl<T extends MutableInterval> extends RedBlackTree<T> implements IntervalTree<T>/*, Iterable<T>*/{
private int keySize; // number of all intervals, counting all duplicates, some of them maybe gced
protected final ReadWriteLock l = new ReentrantReadWriteLock();
private IntervalNode minNode; // left most node in the tree
protected abstract EqualStartIntervalComparator<IntervalNode> getComparator();
private final ReferenceQueue<T> myReferenceQueue = new ReferenceQueue<T>();
private int deadReferenceCount;
protected class IntervalNode extends Node<T> implements MutableInterval {
protected class IntervalNode extends RedBlackTree.Node<T> implements MutableInterval {
private volatile int myStart;
private volatile int myEnd;
private volatile boolean isValid = true;
@@ -314,6 +310,121 @@ public abstract class IntervalTreeImpl<T extends MutableInterval> extends RedBla
}
}
@NotNull
Iterator<T> overlappingIterator(final int startOffset, final int endOffset) {
final IntervalNode firstOverlap = findMinOverlappingWith(getRoot(), new TextRangeInterval(startOffset, endOffset), modCount, 0);
if (firstOverlap == null) {
return ContainerUtil.emptyIterator();
}
normalize();
final int modCountBefore = modCount;
return new Iterator<T>() {
IntervalNode currentNode = firstOverlap;
int[] deltaUpToRootExclusive = {0};
private int indexInCurrentList = 0;
T current;
@Override
public boolean hasNext() {
if (current != null) return true;
if (currentNode == null) return false;
if (modCount != modCountBefore) throw new ConcurrentModificationException();
while (indexInCurrentList != currentNode.intervals.size()) {
T t = currentNode.intervals.get(indexInCurrentList++).get();
if (t != null) {
current = t;
return true;
}
}
indexInCurrentList = 0;
while (true) {
currentNode = nextNode(currentNode, startOffset, endOffset, deltaUpToRootExclusive);
if (currentNode == null) {
return false;
}
if (overlaps(currentNode, startOffset, endOffset, deltaUpToRootExclusive[0])) {
assert currentNode.intervalStart() >= firstOverlap.intervalStart();
indexInCurrentList = 0;
while (indexInCurrentList != currentNode.intervals.size()) {
T t = currentNode.intervals.get(indexInCurrentList++).get();
if (t != null) {
current = t;
return true;
}
}
indexInCurrentList = 0;
}
}
}
@Override
public T next() {
if (!hasNext()) throw new NoSuchElementException();
T t = current;
current = null;
return t;
}
@Override
public void remove() {
throw new IncorrectOperationException();
}
};
}
private boolean overlaps(IntervalNode root,
int startOffset, int endOffset,
int deltaUpToRootExclusive) {
if (root == null) return false;
int delta = root.delta + deltaUpToRootExclusive;
int start = root.intervalStart() + delta;
int end = root.intervalEnd() + delta;
return Math.max(start, startOffset) <= Math.min(end, endOffset);
}
private IntervalNode nextNode(@NotNull IntervalNode root,
int startOffset, int endOffset,
@NotNull int[] newDeltaUpToRootExclusive) {
assert root.isValid();
int deltaUpToRootExclusive = newDeltaUpToRootExclusive[0];
int delta = deltaUpToRootExclusive + root.delta;
//int myStartOffset = root.intervalStart() + delta;
//int myEndOffset = root.intervalEnd() + delta;
int myMaxEnd = maxEndOf(root, deltaUpToRootExclusive);
assert startOffset <= myMaxEnd;
// try to go right down
IntervalNode right = root.getRight();
if (right != null) {
int rightMaxEnd = maxEndOf(right, delta);
if (startOffset <= rightMaxEnd) {
int rightDelta = delta + right.delta;
while (right.getLeft() != null && startOffset <= maxEndOf(right.getLeft(), rightDelta)) {
right = right.getLeft();
rightDelta += right.delta;
}
newDeltaUpToRootExclusive[0] = rightDelta - right.delta;
return right;
}
}
// go up
while (true) {
IntervalNode parent = root.getParent();
if (parent == null) return null;
if (parent.intervalStart() + deltaUpToRootExclusive > endOffset) return null; // can't move right
deltaUpToRootExclusive -= parent.delta;
if (parent.getLeft() == root) {
newDeltaUpToRootExclusive[0] = deltaUpToRootExclusive;
return parent;
}
root = parent;
}
}
private boolean processOverlapping(IntervalNode root,
int offset,
Processor<? super T> processor,
@@ -355,7 +466,7 @@ public abstract class IntervalTreeImpl<T extends MutableInterval> extends RedBla
node.setLeft(null);
node.setRight(null);
List<IntervalNode> gced = new ArrayList<IntervalNode>();
List<IntervalNode> gced = new SmartList<IntervalNode>();
if (root == null) {
root = node;
}
@@ -387,7 +498,6 @@ public abstract class IntervalTreeImpl<T extends MutableInterval> extends RedBla
node.delta = -delta;
node.setParent(current);
}
linkNode(node);
correctMaxUp(node);
onInsertNode();
assertUnderWriteLock();
@@ -399,29 +509,6 @@ public abstract class IntervalTreeImpl<T extends MutableInterval> extends RedBla
return node;
}
private void linkNode(@NotNull IntervalNode node) {
IntervalNode previous = previous(node);
if (previous == null) {
node.next = minNode;
minNode = node;
}
else {
node.next = previous.next;
previous.next = node;
}
}
private void unlinkNode(@NotNull IntervalNode node) {
IntervalNode previous = previous(node);
if (previous == null) {
minNode = node.next;
}
else {
previous.next = node.next;
}
node.next = null;
}
// finds previous in the in-order traversal
private IntervalNode previous(@NotNull IntervalNode node) {
IntervalNode left = node.getLeft();
@@ -447,7 +534,6 @@ public abstract class IntervalTreeImpl<T extends MutableInterval> extends RedBla
for (IntervalNode node : collectedAway) {
removeNode(node);
}
checkMax(true);
}
finally {
l.writeLock().unlock();
@@ -493,10 +579,6 @@ public abstract class IntervalTreeImpl<T extends MutableInterval> extends RedBla
assert nodeSize() == nodeCounter.get() : "node size: "+ nodeSize() +"; actual: "+nodeCounter;
assert keySize == keyCounter.get() : "key size: "+ keySize +"; actual: "+keyCounter;
assert keySize >= nodeSize() : keySize + "; "+nodeSize();
IntervalNode left = getRoot();
while (left != null && left.getLeft() != null) left = left.getLeft();
assert minNode == left;
}
return allValid.get();
}
@@ -640,7 +722,6 @@ public abstract class IntervalTreeImpl<T extends MutableInterval> extends RedBla
assertUnderWriteLock();
IntervalNode node = (IntervalNode)n;
pushDeltaFromRoot(node);
unlinkNode(node);
super.deleteNode(n);
@@ -744,6 +825,7 @@ public abstract class IntervalTreeImpl<T extends MutableInterval> extends RedBla
if (r2 != null) r2.setParent(n1);
}
// returns real max endOffset of all intervals below
private int maxEndOf(IntervalNode node, int deltaUpToRootExclusive) {
if (node == null) {
return 0;
@@ -751,6 +833,7 @@ public abstract class IntervalTreeImpl<T extends MutableInterval> extends RedBla
if (node.isValid()) {
return node.maxEnd + node.delta + deltaUpToRootExclusive;
}
// since node is invalid, ignore node.delta
return Math.max(maxEndOf(node.getLeft(), deltaUpToRootExclusive), maxEndOf(node.getRight(), deltaUpToRootExclusive));
}
@@ -831,14 +914,6 @@ public abstract class IntervalTreeImpl<T extends MutableInterval> extends RedBla
}
}
public Iterator<T> iterator() {
IntervalNode firstNode = minNode;
if (firstNode == null) {
return ContainerUtil.emptyIterator();
}
return createIteratorFrom(firstNode);
}
private IntervalNode findMinOverlappingWith(IntervalNode root, Interval interval, int modCountBefore, int deltaUpToRootExclusive) {
if (root == null) {
return null;
@@ -865,59 +940,7 @@ public abstract class IntervalTreeImpl<T extends MutableInterval> extends RedBla
return findMinOverlappingWith(root.getRight(), interval, modCountBefore, delta);
}
@NotNull
Iterator<T> iteratorFrom(@NotNull Interval interval) {
IntervalNode firstOverlap = findMinOverlappingWith(getRoot(), interval, modCount, 0);
if (firstOverlap == null) {
return ContainerUtil.emptyIterator();
}
return createIteratorFrom(firstOverlap);
}
private Iterator<T> createIteratorFrom(@NotNull final IntervalNode firstNode) {
checkMax(true);
normalize();
final int modCountBefore = modCount;
return new Iterator<T>() {
private IntervalNode node = firstNode;
private int indexInCurrentList = 0;
T current;
public boolean hasNext() {
if (current != null) return true;
while (node != null) {
while (indexInCurrentList != node.intervals.size()) {
current = node.intervals.get(indexInCurrentList).get();
if (current != null) return true;
indexInCurrentList++;
}
indexInCurrentList = 0;
node = node.next;
}
return false;
}
public T next() {
assert modCount == modCountBefore : "Must not modify range markers during iterate";
if (!hasNext()) throw new NoSuchElementException();
T t = current;
current = null;
indexInCurrentList++;
return t;
}
public void remove() {
throw new IncorrectOperationException();
}
};
}
protected volatile boolean normalized = true;
public void normalize() {
if (normalized) return;
try {
@@ -982,13 +1005,13 @@ public abstract class IntervalTreeImpl<T extends MutableInterval> extends RedBla
List<IntervalNode> gced = new ArrayList<IntervalNode>();
collectGced(getRoot(), gced);
deleteNodes(gced);
checkMax(true);
}
@Override
public void clear() {
super.clear();
keySize = 0;
minNode = null;
}
private void collectGced(IntervalNode root, List<IntervalNode> gced) {
@@ -120,13 +120,17 @@ public final class IterationState {
private Iterator<RangeHighlighterEx> myIterator;
private void init(MarkupModelEx markupModel, int start) {
myIterator = markupModel.iteratorFrom(new TextRangeInterval(start, myDocument.getTextLength()));
myIterator = markupModel.overlappingIterator(start, myDocument.getTextLength());
int skipped = 0;
while (myIterator.hasNext()) {
myNextHighlighter = myIterator.next();
if (!skipHighlighter(myNextHighlighter)) break;
else myNextHighlighter = null;
RangeHighlighterEx highlighter = myIterator.next();
if (!skipHighlighter(highlighter)) {
myNextHighlighter = highlighter;
break;
}
skipped++;
}
int i = skipped;
}
private void advance() {
@@ -231,13 +231,9 @@ public class MarkupModelImpl extends UserDataHolderBase implements MarkupModelEx
return myHighlighterTree.processOverlappingWith(start, end, processor);
}
public Iterator<RangeHighlighterEx> iterator() {
return myHighlighterTree.iterator();
}
@NotNull
public Iterator<RangeHighlighterEx> iteratorFrom(@NotNull Interval interval) {
return myHighlighterTree.iteratorFrom(interval);
public Iterator<RangeHighlighterEx> overlappingIterator(int startOffset, int endOffset) {
return myHighlighterTree.overlappingIterator(startOffset, endOffset);
}
public boolean sweep(int start, int end, @NotNull SweepProcessor<RangeHighlighterEx> sweepProcessor) {