From ba9e4f9bf4207e19ba2febdb6e1a0e7db8b59d60 Mon Sep 17 00:00:00 2001 From: Alexey Kudravtsev Date: Wed, 9 Mar 2011 12:54:10 +0300 Subject: [PATCH] range marker lazy creation --- .../injected/editor/DocumentWindowImpl.java | 4 - .../openapi/editor/ex/DocumentEx.java | 1 - .../openapi/editor/impl/DocumentImpl.java | 8 +- .../intellij/openapi/editor/impl/Getable.java | 31 ++ .../openapi/editor/impl/IntervalTree.java | 1 - .../openapi/editor/impl/IntervalTreeImpl.java | 373 +++++++++++------- .../openapi/editor/impl/MarkupModelImpl.java | 27 +- .../impl/PersistentRangeHighlighterImpl.java | 3 +- .../editor/impl/RangeHighlighterData.java | 24 +- .../editor/impl/RangeHighlighterImpl.java | 42 +- .../editor/impl/RangeHighlighterTree.java | 36 +- .../openapi/editor/impl/RangeMarkerImpl.java | 59 +-- .../openapi/editor/impl/RangeMarkerTree.java | 151 +++---- .../openapi/editor/impl/RedBlackTree.java | 37 +- .../src/com/intellij/mock/MockDocument.java | 4 - .../util/containers/ContainerUtil.java | 29 ++ 16 files changed, 506 insertions(+), 324 deletions(-) create mode 100644 platform/platform-impl/src/com/intellij/openapi/editor/impl/Getable.java diff --git a/platform/lang-impl/src/com/intellij/injected/editor/DocumentWindowImpl.java b/platform/lang-impl/src/com/intellij/injected/editor/DocumentWindowImpl.java index 41ee9e967491..06e9afc0ba37 100644 --- a/platform/lang-impl/src/com/intellij/injected/editor/DocumentWindowImpl.java +++ b/platform/lang-impl/src/com/intellij/injected/editor/DocumentWindowImpl.java @@ -526,10 +526,6 @@ public class DocumentWindowImpl extends UserDataHolderBase implements Disposable return myDelegate.removeRangeMarker(((RangeMarkerWindow)rangeMarker).getDelegate()); } - public void addRangeMarker(@NotNull RangeMarkerEx rangeMarker, int start, int end) { - myDelegate.addRangeMarker(((RangeMarkerWindow)rangeMarker).getDelegate(), start, end); - } - public boolean isInBulkUpdate() { return false; } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/ex/DocumentEx.java b/platform/platform-impl/src/com/intellij/openapi/editor/ex/DocumentEx.java index 1d7cff61ee85..6757c21e7371 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/ex/DocumentEx.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/ex/DocumentEx.java @@ -50,7 +50,6 @@ public interface DocumentEx extends Document { void clearLineModificationFlags(); boolean removeRangeMarker(@NotNull RangeMarkerEx rangeMarker); - void addRangeMarker(@NotNull RangeMarkerEx rangeMarker, int start, int end); boolean isInBulkUpdate(); diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/DocumentImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/DocumentImpl.java index 734f2f2cb502..838d0b5770f9 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/DocumentImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/DocumentImpl.java @@ -213,15 +213,19 @@ public class DocumentImpl extends UserDataHolderBase implements DocumentEx { return myRangeMarkers.removeInterval(rangeMarker); } - public void addRangeMarker(@NotNull RangeMarkerEx rangeMarker, int start, int end) { + public void addRangeMarker(@NotNull RangeMarkerEx rangeMarker, int start, int end, boolean greedyToLeft, boolean greedyToRight, int layer) { ApplicationManagerEx.getApplicationEx().assertReadAccessToDocumentsAllowed(); - myRangeMarkers.addInterval(rangeMarker, start, end, null); + myRangeMarkers.addInterval(rangeMarker, start, end, greedyToLeft, greedyToRight, layer); } @TestOnly public int getRangeMarkersSize() { return myRangeMarkers.size(); } + @TestOnly + public int getRangeMarkersNodeSize() { + return myRangeMarkers.nodeSize(); + } @NotNull public RangeMarker createGuardedBlock(int startOffset, int endOffset) { diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/Getable.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/Getable.java new file mode 100644 index 000000000000..3ebdfcf3b6b6 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/Getable.java @@ -0,0 +1,31 @@ +/* + * Copyright 2000-2011 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.editor.impl; + +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; + +/** + * User: cdr + */ +interface Getable { + T get(); +} +class WeakReferencedGetable extends WeakReference implements Getable { + public WeakReferencedGetable(T referent, ReferenceQueue q) { + super(referent, q); + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/IntervalTree.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/IntervalTree.java index 120878b3c521..2225a43bd179 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/IntervalTree.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/IntervalTree.java @@ -26,6 +26,5 @@ public interface IntervalTree extends Iterable { boolean processOverlappingWith(int start, int end, @NotNull Processor processor); boolean processOverlappingWith(int offset, @NotNull Processor processor); - Object addInterval(@NotNull T interval, int start, int end, Object data); boolean removeInterval(@NotNull T interval); } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/IntervalTreeImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/IntervalTreeImpl.java index 76a499692d42..c5f7460f6616 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/IntervalTreeImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/IntervalTreeImpl.java @@ -15,17 +15,16 @@ */ package com.intellij.openapi.editor.impl; -import com.intellij.openapi.editor.ex.RangeMarkerEx; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.Trinity; import com.intellij.util.IncorrectOperationException; import com.intellij.util.Processor; +import com.intellij.util.SmartList; import com.intellij.util.containers.ContainerUtil; +import gnu.trove.TLongHashSet; import org.jetbrains.annotations.NotNull; -import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; -import java.lang.ref.WeakReference; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.ReadWriteLock; @@ -35,13 +34,14 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; * User: cdr */ public abstract class IntervalTreeImpl extends RedBlackTree implements IntervalTree { + protected int keySize; // number of all keys protected final ReadWriteLock l = new ReentrantReadWriteLock(); protected abstract EqualStartIntervalComparator getComparator(); private final ReferenceQueue myReferenceQueue = new ReferenceQueue(); private int deadReferenceCount; - public abstract static class IntervalNode extends RedBlackTree.Node implements MutableInterval { + public abstract class IntervalNode extends RedBlackTree.Node implements MutableInterval { protected int maxEnd; // max of all intervalEnd()s among all children. protected int delta; // delta of startOffset. getStartOffset() = myStartOffset + Sum of deltas up to root protected abstract int computeDeltaUpToRoot(); @@ -53,28 +53,98 @@ public abstract class IntervalTreeImpl extends RedBla public IntervalNode getRight() { return (IntervalNode)super.getRight(); } - @Override public IntervalNode getParent() { return (IntervalNode)super.getParent(); } - } - protected class MyNode extends IntervalNode { + protected class MyNode extends IntervalNode { private volatile int myStart; private volatile int myEnd; private volatile boolean isValid = true; - private final Reference interval; + protected final List> intervals; public MyNode(@NotNull T key, int start, int end) { // maxEnd == 0 so to not disrupt existing maxes - interval = new WeakReference(key,myReferenceQueue); + intervals = new SmartList>(createGetable(key)); myStart = start; myEnd = end; } @Override - public T getKey() { - return interval.get(); + public boolean processAliveKeys(@NotNull Processor processor) { + for (Getable interval : intervals) { + T key = interval.get(); + if (key != null && !processor.process(key)) return false; + } + return true; + } + + @NotNull + @Override + public List getAliveKeys() { + List result = null; + for (Getable ref : intervals) { + T interval = ref.get(); + if (interval == null) continue; + if (result == null) { + result = new ArrayList(intervals.size()); + } + result.add(interval); + } + return result == null ? Collections.emptyList() : result; + } + + public boolean hasAliveKey(boolean purgeDead) { + for (int i = intervals.size() - 1; i >= 0; i--) { + Getable interval = intervals.get(i); + if (interval.get() != null) return true; + if (purgeDead) { + assertUnderWriteLock(); + intervals.remove(i); + assert keySize > 0 : keySize; + keySize--; + } + } + return false; + } + + // removes interval and the node, if node become empty + public boolean removeInterval(@NotNull T key) { + checkBelongsToTheTree(key, true); + assertUnderWriteLock(); + for (int i = intervals.size() - 1; i >= 0; i--) { + Getable interval = intervals.get(i); + T t = interval.get(); + if (t == key) { + intervals.remove(i); + assert keySize > 0 : keySize; + keySize--; + if (intervals.isEmpty()) { + removeNode(this); + return true; + } + return false; + } + } + List aliveKeys = getAliveKeys(); + assert false: "not found interval: "+key +"; "+ aliveKeys; + return false; + } + + private void assertUnderWriteLock() { + if (!VERIFY) return; + String s = l.writeLock().toString(); + assert s.contains("Locked by thread") : s; + } + + public void addInterval(@NotNull T interval) { + assertUnderWriteLock(); + intervals.add(createGetable(interval)); + keySize++; + } + + protected Getable createGetable(@NotNull T interval) { + return new WeakReferencedGetable(interval, myReferenceQueue); } @Override @@ -134,38 +204,12 @@ public abstract class IntervalTreeImpl extends RedBla } } - @Override - protected MyNode createNewNode(T key, int start, int end, Object data) { - return new MyNode(key, start, end); - } + @NotNull + protected abstract MyNode createNewNode(@NotNull T key, int start, int end, boolean greedyToLeft, boolean greedyToRight, int layer); + protected abstract MyNode lookupNode(@NotNull T key); - @Override - protected IntervalNode lookupNode(@NotNull T key, Node interval) { - return ((RangeMarkerImpl)key).myNode; - //int delta = 0; - //int delta1 = ((RangeMarkerImpl)key).myNode == null ? 0 : ((RangeMarkerImpl)key).myNode.computeDeltaUpToRoot(); - //List invalid = new ArrayList(); - //IntervalNode root = (IntervalNode)interval; - //while (root != null) { - // delta += root.delta; - // int compResult = compareNodes(((RangeMarkerImpl)key).myNode, delta1, root, delta, invalid); - // if (compResult < 0) { - // root = root.getLeft(); - // } - // else if (compResult > 0) { - // root = root.getRight(); - // } - // else { - // return root; - // } - //} - //deleteNodes(invalid); - //return root; - } - - private int compareNodes(@NotNull IntervalNode i1, int delta1, @NotNull IntervalNode i2, int delta2, List invalid) { - T i2Key = (T)i2.getKey(); - if (i2Key == null) { + private int compareNodes(@NotNull IntervalNode i1, int delta1, @NotNull IntervalNode i2, int delta2, @NotNull List invalid) { + if (!i2.hasAliveKey(false)) { invalid.add(i2); //gced } int start1 = i1.intervalStart() + delta1; @@ -179,8 +223,10 @@ public abstract class IntervalTreeImpl extends RedBla protected interface EqualStartIntervalComparator { int compare(T i1, T i2); + } - protected IntervalTreeImpl.IntervalNode getRoot() { + + protected IntervalNode getRoot() { return (IntervalNode)root; } @@ -200,11 +246,8 @@ public abstract class IntervalTreeImpl extends RedBla if (root == null) return true; if (!process(root.getLeft(), processor, modCountBefore)) return false; - T key = (T)root.getKey(); - if (key != null) { - if (!processor.process(key)) return false; - assert modCount == modCountBefore; - } + if (!root.processAliveKeys(processor)) return false; + assert modCount == modCountBefore; return process(root.getRight(), processor, modCountBefore); } @@ -241,9 +284,8 @@ public abstract class IntervalTreeImpl extends RedBla int myStartOffset = root.intervalStart() + delta; int myEndOffset = root.intervalEnd() + delta; boolean overlaps = Math.max(myStartOffset, start) <= Math.min(myEndOffset, end); - T key = (T)root.getKey(); - if (key != null) { - if (overlaps && !processor.process(key)) return false; + if (overlaps) { + if (!root.processAliveKeys(processor)) return false; assert modCount == modCountBefore; } @@ -285,9 +327,8 @@ public abstract class IntervalTreeImpl extends RedBla int myEndOffset = root.intervalEnd() + delta; boolean overlaps = myStartOffset <= offset && offset < myEndOffset; - T key = (T)root.getKey(); - if (key != null) { - if (overlaps && !processor.process(key)) return false; + if (overlaps) { + if (!root.processAliveKeys(processor)) return false; assert modCount == modCountBefore; } @@ -298,7 +339,7 @@ public abstract class IntervalTreeImpl extends RedBla return processOverlapping(root.getRight(), offset, processor, modCountBefore, delta); } - protected void insert(@NotNull IntervalNode node) { + protected MyNode findOrInsert(@NotNull IntervalNode node) { node.color = Color.RED; node.setParent(null); node.setValid(true); @@ -308,8 +349,6 @@ public abstract class IntervalTreeImpl extends RedBla node.setRight(null); List gced = new ArrayList(); - //T nodeKey = (T)node.getKey(); - //assert nodeKey != null; if (root == null) { root = node; } @@ -320,6 +359,9 @@ public abstract class IntervalTreeImpl extends RedBla while (true) { delta += current.delta; int compResult = compareNodes(node, 0, current, delta, gced); + if (compResult == 0) { + return (MyNode)current; + } if (compResult < 0) { if (current.getLeft() == null) { current.setLeft(node); @@ -327,44 +369,25 @@ public abstract class IntervalTreeImpl extends RedBla } current = current.getLeft(); } - else if (compResult > 0) { + else /*if (compResult > 0)*/ { if (current.getRight() == null) { current.setRight(node); break loop; } current = current.getRight(); } - else { - T i1 = (T)node.getKey(); - T i2 = (T)current.getKey(); - int delta1 = 0; - int delta2 = delta; - assert false : "already inserted: " + - i1 + - ":" + - delta1 + - "; " + - ((RangeMarkerEx)i1).getId() + - " <-> " + - i2 + - ":" + - delta2 + - "; " + - ((RangeMarkerEx)i2).getId() + - " iden=" + - (i1 == i2); - return; - } } node.delta = -delta; node.setParent(current); } - size++; correctMaxUp(node); + onInsertNode(); + keySize += ((MyNode)node).intervals.size(); insertCase1(node); verifyProperties(); deleteNodes(gced); + return (MyNode)node; } private void deleteNodes(List collectedAway) { @@ -382,18 +405,21 @@ public abstract class IntervalTreeImpl extends RedBla } } - @Override - public IntervalNode addInterval(@NotNull T interval, int start, int end, Object data) { + public MyNode addInterval(@NotNull T interval, int start, int end, boolean greedyToLeft, boolean greedyToRight, int layer) { try { l.writeLock().lock(); checkMax(true); processReferenceQueue(); modCount++; - IntervalNode newNode = createNewNode(interval, start, end, data); - insert(newNode); + IntervalNode newNode = createNewNode(interval, start, end, greedyToLeft, greedyToRight, layer); + MyNode insertedNode = findOrInsert(newNode); + if (insertedNode != newNode) { + // merged + insertedNode.addInterval(interval); + } - checkMax(true); // myNode assigned - return newNode; + checkMax(true); + return insertedNode; } finally { l.writeLock().unlock(); @@ -404,10 +430,13 @@ public abstract class IntervalTreeImpl extends RedBla public boolean checkMax(boolean assertInvalid) { if (!VERIFY) return false; Ref allValid = new Ref(true); - AtomicInteger counter = new AtomicInteger(); - checkMax(getRoot(), 0, assertInvalid, allValid, counter); + AtomicInteger keyCounter = new AtomicInteger(); + AtomicInteger nodeCounter = new AtomicInteger(); + TLongHashSet ids = new TLongHashSet(); + checkMax(getRoot(), 0, assertInvalid, allValid, keyCounter, nodeCounter, ids); if (assertInvalid) { - assert size == counter.get() : "size: "+size+"; actual: "+counter; + assert nodeSize() == nodeCounter.get() : "node size: "+ nodeSize() +"; actual: "+nodeCounter; + assert keySize == keyCounter.get() : "key size: "+ keySize +"; actual: "+keyCounter; } return allValid.get(); } @@ -416,15 +445,23 @@ public abstract class IntervalTreeImpl extends RedBla protected Trinity checkMax(IntervalNode root, int deltaUpToRootExclusive, boolean assertInvalid, - Ref allValid, AtomicInteger counter) { + Ref allValid, + AtomicInteger keyCounter, + AtomicInteger nodeCounter, + TLongHashSet ids) { if (root == null) return Trinity.create(Integer.MAX_VALUE,Integer.MIN_VALUE,Integer.MIN_VALUE); - counter.getAndIncrement(); + for (T t : root.getAliveKeys()) { + checkBelongsToTheTree(t, assertInvalid); + assert ids.add(((RangeMarkerImpl)t).getId()) : t; + } + keyCounter.addAndGet(((MyNode)root).intervals.size()); + nodeCounter.incrementAndGet(); int delta = deltaUpToRootExclusive + (root.isValid() ? root.delta : 0); - Trinity l = checkMax(root.getLeft(), delta, assertInvalid, allValid, counter); + Trinity l = checkMax(root.getLeft(), delta, assertInvalid, allValid, keyCounter, nodeCounter, ids); int minLeftStart = l.first; int maxLeftStart = l.second; int maxLeftEnd = l.third; - Trinity r = checkMax(root.getRight(), delta, assertInvalid, allValid, counter); + Trinity r = checkMax(root.getRight(), delta, assertInvalid, allValid, keyCounter, nodeCounter, ids); int maxRightEnd = r.third; int minRightStart = r.first; int maxRightStart = r.second; @@ -434,8 +471,7 @@ public abstract class IntervalTreeImpl extends RedBla return Trinity.create(Math.min(minLeftStart, minRightStart), Math.max(maxLeftStart, maxRightStart), Math.max(maxRightEnd, maxLeftEnd)); } IntervalNode parent = root.getParent(); - T rootKey = (T)root.getKey(); - if (parent != null && assertInvalid && rootKey != null) { + if (parent != null && assertInvalid && root.hasAliveKey(false)) { int c = compareNodes(root, delta, parent, delta - root.delta, new ArrayList()); assert c != 0; assert c < 0 && parent.getLeft() == root || c > 0 && parent.getRight() == root; @@ -465,17 +501,39 @@ public abstract class IntervalTreeImpl extends RedBla return root; } + protected void checkBelongsToTheTree(T interval, boolean assertInvalid) { + if (!VERIFY) return; + MyNode root = lookupNode(interval); + if (root == null) return; + assert !root.intervals.isEmpty(); + + assert root.getTree() == this; + List keys = root.getAliveKeys(); + assert keys.contains(interval) : keys + "; " + interval; + for (T key : keys) { + MyNode node = lookupNode(key); + assert assertInvalid && node == root || !assertInvalid && (node == null || node == root) : node; + assert assertInvalid && node.getTree() == this || !assertInvalid && (node == null || node.getTree() == this) : node; + } + + IntervalNode e = root; + while (e.getParent() != null) e = e.getParent(); + assert e == this.root; // assert the node belongs to our tree + } + @Override public boolean removeInterval(@NotNull T interval) { if (!interval.isValid()) return false; try { l.writeLock().lock(); + checkBelongsToTheTree(interval, true); checkMax(true); processReferenceQueue(); - IntervalNode node = lookupNode(interval, root); + MyNode node = lookupNode(interval); if (node == null) return false; - removeNode(node); + + node.removeInterval(interval); checkMax(true); return true; } @@ -493,33 +551,51 @@ public abstract class IntervalTreeImpl extends RedBla @Override protected void deleteNode(Node n) { - pushDeltaFromRoot((IntervalNode)n); + MyNode node = (MyNode)n; + pushDeltaFromRoot(node); super.deleteNode(n); + + keySize -= node.intervals.size(); + assert keySize >= 0 : keySize; } - protected static void pushDelta(IntervalNode root) { - if (root == null || !root.isValid()) return; + @Override + public int size() { + return keySize; + } + + // returns true if some delta was or became not null + protected boolean pushDelta(IntervalNode root) { + if (root == null || !root.isValid()) return false; int delta = root.delta; if (delta != 0) { root.setIntervalStart(root.intervalStart() + delta); root.setIntervalEnd(root.intervalEnd() + delta); root.maxEnd += delta; root.delta = 0; - incDelta(root.getLeft(), delta); + //noinspection NonShortCircuitBooleanExpression + return + incDelta(root.getLeft(), delta) | + incDelta(root.getRight(), delta); + } + return false; + } + + // returns true if some delta was or became not null + private boolean incDelta(IntervalNode root, int delta) { + if (root == null) return false; + if (root.isValid()) { + int newDelta = root.delta += delta; + return newDelta != 0; + } + else { + //noinspection NonShortCircuitBooleanExpression + return + incDelta(root.getLeft(), delta) | incDelta(root.getRight(), delta); } } - private static void incDelta(IntervalNode root, int delta) { - if (root == null) return; - if (root.isValid()) { - root.delta += delta; - } - else { - incDelta(root.getLeft(), delta); - incDelta(root.getRight(), delta); - } - } @Override protected IntervalNode swapWithMaxPred(Node root, Node maxPred) { checkMax(false); @@ -546,7 +622,6 @@ public abstract class IntervalTreeImpl extends RedBla checkMax(false); return a; } - private void swapNodes(IntervalNode n1, IntervalNode n2) { IntervalNode l1 = n1.getLeft(); IntervalNode r1 = n1.getRight(); @@ -580,7 +655,7 @@ public abstract class IntervalTreeImpl extends RedBla if (r2 != null) r2.setParent(n1); } - protected static int maxEndOf(IntervalNode node, int deltaUpToRootExclusive) { + private int maxEndOf(IntervalNode node, int deltaUpToRootExclusive) { if (node == null) { return 0; } @@ -591,14 +666,14 @@ public abstract class IntervalTreeImpl extends RedBla } // max of n.left's maxend, n.right's maxend and its own interval endOffset - protected static void correctMax(@NotNull IntervalNode node, int deltaUpToRoot) { + protected void correctMax(@NotNull IntervalNode node, int deltaUpToRoot) { if (!node.isValid()) return; int realMax = Math.max(Math.max(maxEndOf(node.getLeft(), deltaUpToRoot), maxEndOf(node.getRight(), deltaUpToRoot)), deltaUpToRoot + node.intervalEnd()); node.maxEnd = realMax - deltaUpToRoot; } - private static void correctMaxUp(IntervalNode node) { + private void correctMaxUp(IntervalNode node) { int delta = node == null ? 0 : node.computeDeltaUpToRoot(); while (node != null) { if (node.isValid()) { @@ -720,37 +795,39 @@ public abstract class IntervalTreeImpl extends RedBla final int modCountBefore = modCount; return new Iterator() { private IntervalNode node = firstNode; - private T element = (T)firstNode.getKey(); + private Iterator iteratorInCurrentList = firstNode.getAliveKeys().iterator(); { // find first non-null key - while (element == null) { + while (!iteratorInCurrentList.hasNext()) { moveNext(); if (node == null) break; } } public boolean hasNext() { - return element != null; + Iterator it = iteratorInCurrentList; + return it != null && it.hasNext(); } public T next() { assert modCount == modCountBefore : "Must not modify range markers during iterate"; - if (node == null || element == null) throw new NoSuchElementException(); - T current = element; + if (node == null || iteratorInCurrentList == null || !iteratorInCurrentList.hasNext()) throw new NoSuchElementException(); + T current = iteratorInCurrentList.next(); moveNext(); return current; } private void moveNext() { + if (iteratorInCurrentList != null && iteratorInCurrentList.hasNext()) return; while (true) { node = getNextNode(); if (node == null) { - element = null; + iteratorInCurrentList = null; break; } - element = (T)node.getKey(); - if (element != null) break; + iteratorInCurrentList = node.getAliveKeys().iterator(); + if (iteratorInCurrentList.hasNext()) break; } } @@ -793,14 +870,38 @@ public abstract class IntervalTreeImpl extends RedBla l.writeLock().unlock(); } } - - private static void normalize(IntervalNode root) { + private void normalize(IntervalNode root) { if (root == null) return; pushDelta(root); normalize(root.getLeft()); normalize(root.getRight()); } + public void changeData(T interval, int start, int end, boolean greedyToLeft, boolean greedyToRight, int layer) { + try { + l.writeLock().lock(); + + MyNode node = lookupNode(interval); + if (node == null) return; + int before = size(); + boolean nodeRemoved = node.removeInterval(interval); + assert nodeRemoved || !node.intervals.isEmpty(); + + MyNode insertedNode = addInterval(interval, start, end, greedyToLeft, greedyToRight, layer); + assert node != insertedNode; + + int after = size(); + // can be gced + assert before >= after : before +";" + after; + checkBelongsToTheTree(interval, true); + checkMax(true); + } + finally { + l.writeLock().unlock(); + } + } + + // called under write lock private void processReferenceQueue() { int dead = 0; @@ -821,35 +922,19 @@ public abstract class IntervalTreeImpl extends RedBla deleteNodes(gced); } - private static void collectGced(IntervalNode root, List gced) { + private void collectGced(IntervalNode root, List gced) { if (root == null) return; - if (root.getKey() == null) gced.add(root); + if (!root.hasAliveKey(true)) gced.add(root); collectGced(root.getLeft(), gced); collectGced(root.getRight(), gced); } - public void changeAttribute(@NotNull IntervalNode node, @NotNull Runnable changeAttributes) { - normalize(); - try { - l.writeLock().lock(); - checkMax(true); - removeNode(node); - changeAttributes.run(); - insert(node); - checkMax(true); - } - finally { - l.writeLock().unlock(); - } - } - private void printSorted() { printSorted(getRoot());} - private static void printSorted(IntervalNode root) { + private void printSorted(IntervalNode root) { if (root == null) return; printSorted(root.getLeft()); System.out.println(root); printSorted(root.getRight()); } - } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/MarkupModelImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/MarkupModelImpl.java index 04812916c5e3..c3efa8a61aed 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/MarkupModelImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/MarkupModelImpl.java @@ -35,9 +35,7 @@ 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.util.UserDataHolderBase; -import com.intellij.util.CommonProcessors; -import com.intellij.util.Consumer; -import com.intellij.util.Processor; +import com.intellij.util.*; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -99,6 +97,7 @@ public class MarkupModelImpl extends UserDataHolderBase implements MarkupModelEx return offset; } + // NB: Can return invalid highlighters @NotNull public RangeHighlighter[] getAllHighlighters() { ApplicationManager.getApplication().assertIsDispatchThread(); @@ -123,16 +122,11 @@ public class MarkupModelImpl extends UserDataHolderBase implements MarkupModelEx ApplicationManager.getApplication().assertIsDispatchThread(); RangeHighlighterEx highlighter = isPersistent ? new PersistentRangeHighlighterImpl(this, startOffset, layer, targetArea, textAttributes) - : new RangeHighlighterImpl(this, startOffset, endOffset, layer, targetArea, textAttributes); + : new RangeHighlighterImpl(this, startOffset, endOffset, layer, targetArea, textAttributes, false, false); myCachedHighlighters = null; if (changeAttributesAction != null) { - if (isPersistent) { - ((PersistentRangeHighlighterImpl)highlighter).changeAttributesInBatch(changeAttributesAction); - } - else { - ((RangeHighlighterImpl)highlighter).changeAttributesInBatch(changeAttributesAction); - } + ((RangeHighlighterImpl)highlighter).changeAttributesNoEvents(changeAttributesAction); } fireAfterAdded(highlighter); return highlighter; @@ -141,17 +135,20 @@ public class MarkupModelImpl extends UserDataHolderBase implements MarkupModelEx @Override public void changeAttributesInBatch(@NotNull RangeHighlighterEx highlighter, @NotNull Consumer changeAttributesAction) { ApplicationManager.getApplication().assertIsDispatchThread(); - boolean changed = highlighter instanceof PersistentRangeHighlighterImpl - ? ((PersistentRangeHighlighterImpl)highlighter).changeAttributesInBatch(changeAttributesAction) - : ((RangeHighlighterImpl)highlighter).changeAttributesInBatch(changeAttributesAction); + boolean changed = ((RangeHighlighterImpl)highlighter).changeAttributesNoEvents(changeAttributesAction); if (changed) { fireAttributesChanged(highlighter); } } - void addRangeHighlighter(RangeHighlighterEx marker, int start, int end, RangeHighlighterData data) { + IntervalTreeImpl.IntervalNode addRangeHighlighter(RangeHighlighterEx marker, + int start, + int end, + boolean greedyToLeft, + boolean greedyToRight, + int layer) { ApplicationManager.getApplication().assertIsDispatchThread(); - myHighlighterTree.addInterval(marker, start, end, data); + return myHighlighterTree.addInterval(marker, start, end, greedyToLeft, greedyToRight, layer); } @NotNull diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/PersistentRangeHighlighterImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/PersistentRangeHighlighterImpl.java index 30b1aedf7f52..55b3bb8c3d9a 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/PersistentRangeHighlighterImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/PersistentRangeHighlighterImpl.java @@ -34,7 +34,8 @@ class PersistentRangeHighlighterImpl extends RangeHighlighterImpl implements Ran int layer, @NotNull HighlighterTargetArea target, TextAttributes textAttributes) { - super(model, model.getDocument().getLineStartOffset(model.getDocument().getLineNumber(offset)), model.getDocument().getLineEndOffset(model.getDocument().getLineNumber(offset)),layer, target, textAttributes); + super(model, model.getDocument().getLineStartOffset(model.getDocument().getLineNumber(offset)), model.getDocument().getLineEndOffset(model.getDocument().getLineNumber(offset)),layer, target, textAttributes, + false, false); setLine(model.getDocument().getLineNumber(offset)); } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/RangeHighlighterData.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/RangeHighlighterData.java index fc498a3e15f4..24d1ebec98ee 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/RangeHighlighterData.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/RangeHighlighterData.java @@ -15,6 +15,7 @@ */ package com.intellij.openapi.editor.impl; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.ex.RangeHighlighterEx; import com.intellij.openapi.editor.markup.*; @@ -30,7 +31,6 @@ import java.awt.*; */ abstract class RangeHighlighterData { private final MarkupModel myModel; - private final int myLayer; private final HighlighterTargetArea myTargetArea; private TextAttributes myTextAttributes; private LineMarkerRenderer myLineMarkerRenderer; @@ -46,12 +46,10 @@ abstract class RangeHighlighterData { int myLine; // for PersistentRangeHighlighterImpl only RangeHighlighterData(@NotNull MarkupModel model, - int layer, @NotNull HighlighterTargetArea target, TextAttributes textAttributes) { myTextAttributes = textAttributes; myTargetArea = target; - myLayer = layer; myModel = model; if (textAttributes != null) { myErrorStripeColor = textAttributes.getErrorStripeColor(); @@ -73,10 +71,6 @@ abstract class RangeHighlighterData { } } - public int getLayer() { - return myLayer; - } - public HighlighterTargetArea getTargetArea() { return myTargetArea; } @@ -127,6 +121,7 @@ abstract class RangeHighlighterData { } public void setErrorStripeTooltip(Object tooltipObject) { + ApplicationManager.getApplication().assertIsDispatchThread(); Object old = myErrorStripeTooltip; myErrorStripeTooltip = tooltipObject; if (!Comparing.equal(old, tooltipObject)) { @@ -139,6 +134,7 @@ abstract class RangeHighlighterData { } public void setThinErrorStripeMark(boolean value) { + ApplicationManager.getApplication().assertIsDispatchThread(); boolean old = myErrorStripeMarkIsThin; myErrorStripeMarkIsThin = value; if (old != value) { @@ -223,16 +219,10 @@ abstract class RangeHighlighterData { return Math.min(textLength, document.getLineEndOffset(document.getLineNumber(endOffset)) + 1); } - public void registerMe(int start, int end) { - ((MarkupModelImpl)myModel).addRangeHighlighter(getRangeHighlighter(), start, end,this); - } - - public void unregisterMe() { - myModel.removeHighlighter(getRangeHighlighter()); - } - // returns true if change was detected boolean changeAttributesInBatch(@NotNull Consumer change) { + assert !inBatchChange; + assert !changed; inBatchChange = true; boolean result; try { @@ -245,4 +235,8 @@ abstract class RangeHighlighterData { } return result; } + + public MarkupModel getMarkupModel() { + return myModel; + } } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/RangeHighlighterImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/RangeHighlighterImpl.java index fe847de29778..9f028ebcbb51 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/RangeHighlighterImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/RangeHighlighterImpl.java @@ -28,43 +28,64 @@ import java.awt.*; * Implementation of the markup element for the editor and document. * @author max */ -class RangeHighlighterImpl extends RangeMarkerImpl implements RangeHighlighterEx { +class RangeHighlighterImpl extends RangeMarkerImpl implements RangeHighlighterEx, Getable { + private final RangeHighlighterData data; + RangeHighlighterImpl(@NotNull MarkupModel model, int start, int end, int layer, @NotNull HighlighterTargetArea target, - TextAttributes textAttributes) { + TextAttributes textAttributes, boolean greedyToLeft, boolean greedyToRight) { super((DocumentEx)model.getDocument(), start, end,false); - RangeHighlighterData data = new RangeHighlighterData(model, layer, target, textAttributes) { + data = new RangeHighlighterData(model, target, textAttributes) { @NotNull @Override public RangeHighlighterEx getRangeHighlighter() { return RangeHighlighterImpl.this; } }; - data.registerMe(start, end); + + registerInTree(start, end, greedyToLeft, greedyToRight, layer); + } + + @Override + public void setGreedyToLeft(boolean greedy) { + if (!isValid() || greedy == isGreedyToLeft()) return; + myNode.getTree().changeData(this, getStartOffset(), getEndOffset(), greedy, isGreedyToRight(), getLayer()); + } + + @Override + public void setGreedyToRight(boolean greedy) { + if (!isValid() || greedy == isGreedyToRight()) return; + myNode.getTree().changeData(this, getStartOffset(), getEndOffset(), isGreedyToLeft(), greedy, getLayer()); } protected RangeHighlighterData getData() { - return ((RangeHighlighterTree.RHNode)myNode).data; + return data; } @Override - protected void registerInDocument(int start, int end) { + protected void registerInTree(int start, int end, boolean greedyToLeft, boolean greedyToRight, int layer) { // we store highlighters in MarkupModel + ((MarkupModelImpl)data.getMarkupModel()).addRangeHighlighter(this, start, end, greedyToLeft, greedyToRight, layer); } @Override - protected boolean unregisterInDocument() { + protected boolean unregisterInTree() { if (myNode == null) return false; // we store highlighters in MarkupModel - getData().unregisterMe(); + getData().getMarkupModel().removeHighlighter(this); myNode = null; return true; } + @Override + public RangeHighlighterImpl get() { + return this; + } + // delegates public TextAttributes getTextAttributes() { return getData().getTextAttributes(); @@ -74,12 +95,13 @@ class RangeHighlighterImpl extends RangeMarkerImpl implements RangeHighlighterEx getData().setTextAttributes(textAttributes); } - boolean changeAttributesInBatch(@NotNull Consumer change) { + boolean changeAttributesNoEvents(@NotNull Consumer change) { return getData().changeAttributesInBatch(change); } public int getLayer() { - return getData().getLayer(); + RangeHighlighterTree.RHNode node = (RangeHighlighterTree.RHNode)myNode; + return node == null ? -1 : node.myLayer; } public HighlighterTargetArea getTargetArea() { diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/RangeHighlighterTree.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/RangeHighlighterTree.java index fcf92a4c720e..f8fa6ff69c4d 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/RangeHighlighterTree.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/RangeHighlighterTree.java @@ -28,8 +28,8 @@ public class RangeHighlighterTree extends RangeMarkerTree { public int compare(IntervalNode i1, IntervalNode i2) { RHNode o1 = (RHNode)i1; RHNode o2 = (RHNode)i2; - if (o1.data.getLayer() != o2.data.getLayer()) { - return o2.data.getLayer() - o1.data.getLayer(); + if (o1.myLayer != o2.myLayer) { + return o2.myLayer - o1.myLayer; } boolean greedyL1 = o1.isGreedyToLeft(); boolean greedyL2 = o2.isGreedyToLeft(); @@ -42,9 +42,7 @@ public class RangeHighlighterTree extends RangeMarkerTree { boolean greedyR2 = o2.isGreedyToRight(); if (greedyR1 != greedyR2) return greedyR1 ? -1 : 1; - // for now we tolerate equal range range markers (till lazy creation impl) - d = (int)(o1.getId() - o2.getId()); - return d; + return 0; } }; @@ -57,19 +55,29 @@ public class RangeHighlighterTree extends RangeMarkerTree { return myComparator; } + @NotNull @Override - protected RHNode createNewNode(RangeHighlighterEx key, int start, int end, Object data) { - RHNode node = new RHNode(key, start, end, (RangeHighlighterData)data); - ((RangeMarkerImpl)key).myNode = node; - return node; + protected RHNode createNewNode(@NotNull RangeHighlighterEx key, int start, int end, boolean greedyToLeft, boolean greedyToRight, int layer) { + return new RHNode(key, start, end, greedyToLeft, greedyToRight,layer); } class RHNode extends RMNode { - final RangeHighlighterData data; - public RHNode(@NotNull final RangeHighlighterEx key, int start, int end, RangeHighlighterData data) { - super(key, start, end); - this.data = data; + final int myLayer; + + public RHNode(@NotNull final RangeHighlighterEx key, + int start, + int end, + boolean greedyToLeft, + boolean greedyToRight, + int layer) { + super(key, start, end, greedyToLeft, greedyToRight); + myLayer = layer; + } + + // range highlighters are strongly referenced + @Override + protected Getable createGetable(@NotNull RangeHighlighterEx interval) { + return (Getable)interval; } } - } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/RangeMarkerImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/RangeMarkerImpl.java index 88e05ac03d34..77d654cfe5d8 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/RangeMarkerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/RangeMarkerImpl.java @@ -23,13 +23,22 @@ import com.intellij.openapi.util.UserDataHolderBase; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; +import java.util.concurrent.atomic.AtomicLong; + public class RangeMarkerImpl extends UserDataHolderBase implements RangeMarkerEx, MutableInterval { private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.RangeMarkerImpl"); protected final DocumentEx myDocument; RangeMarkerTree.RMNode myNode; + private final long myId; + //private static long counter; + private static final AtomicLong counter = new AtomicLong(); + protected RangeMarkerImpl(@NotNull DocumentEx document, int start, int end, boolean register) { + this(document, start, end, register, false, false); + } + private RangeMarkerImpl(@NotNull DocumentEx document, int start, int end, boolean register, boolean greedyToLeft, boolean greedyToRight) { if (start < 0) { throw new IllegalArgumentException("Wrong start: " + start+"; end="+end); } @@ -41,29 +50,38 @@ public class RangeMarkerImpl extends UserDataHolderBase implements RangeMarkerEx } myDocument = document; + myId = counter.getAndIncrement(); if (register) { - registerInDocument(start, end); + registerInTree(start, end, greedyToLeft, greedyToRight, 0); } } - protected void registerInDocument(int start, int end) { - myNode = null; - myDocument.addRangeMarker(this, start, end); - assert myNode != null; + private static Object createData(boolean greedyToLeft, boolean greedyToRight) { + return (greedyToLeft ? 2 : 0) | (greedyToRight ? 1 : 0); } - protected boolean unregisterInDocument() { + + protected void registerInTree(int start, int end, boolean greedyToLeft, boolean greedyToRight, int layer) { + ((DocumentImpl)myDocument).addRangeMarker(this, start, end, greedyToLeft, greedyToRight, layer); + } + + protected boolean unregisterInTree() { + IntervalTreeImpl tree = myNode.getTree(); + tree.checkMax(true); boolean b = myDocument.removeRangeMarker(this); myNode = null; + tree.checkMax(true); return b; } public long getId() { - return myNode.getId(); + return myId; } @Override public void dispose() { - unregisterInDocument(); + if(isValid()) { + unregisterInTree(); + } } public int getStartOffset() { @@ -85,22 +103,13 @@ public class RangeMarkerImpl extends UserDataHolderBase implements RangeMarkerEx public void setGreedyToLeft(final boolean greedy) { if (!isValid() || greedy == isGreedyToLeft()) return; - myNode.getTree().changeAttribute(myNode, new Runnable() { - @Override - public void run() { - myNode.setGreedyToLeft(greedy); - } - }); + + myNode.getTree().changeData(this, getStartOffset(), getEndOffset(), greedy, isGreedyToRight(), 0); } public void setGreedyToRight(final boolean greedy) { if (!isValid() || greedy == isGreedyToRight()) return; - myNode.getTree().changeAttribute(myNode, new Runnable() { - @Override - public void run() { - myNode.setGreedyToRight(greedy); - } - }); + myNode.getTree().changeData(this, getStartOffset(), getEndOffset(), isGreedyToLeft(), greedy, 0); } public boolean isGreedyToLeft() { @@ -210,7 +219,7 @@ public class RangeMarkerImpl extends UserDataHolderBase implements RangeMarkerEx @NonNls public String toString() { return "RangeMarker" + (isGreedyToLeft() ? "[" : "(") + (isValid() ? "valid" : "invalid") + "," + getStartOffset() + "," + getEndOffset() + ( - isGreedyToRight() ? "]" : ")"); + isGreedyToRight() ? "]" : ")") + " " + getId(); } @Override @@ -241,9 +250,7 @@ public class RangeMarkerImpl extends UserDataHolderBase implements RangeMarkerEx if (node == null) { return -1; } - else { - return node.intervalStart(); - } + return node.intervalStart(); } @Override @@ -252,8 +259,6 @@ public class RangeMarkerImpl extends UserDataHolderBase implements RangeMarkerEx if (node == null) { return -1; } - else { - return node.intervalEnd(); - } + return node.intervalEnd(); } } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/RangeMarkerTree.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/RangeMarkerTree.java index 1c3dd66d76c8..65e4ba599d06 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/RangeMarkerTree.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/RangeMarkerTree.java @@ -20,22 +20,18 @@ import com.intellij.openapi.editor.event.DocumentEvent; import com.intellij.openapi.editor.ex.MarkupModelEx; import com.intellij.openapi.editor.ex.PrioritizedDocumentListener; import com.intellij.openapi.editor.ex.RangeMarkerEx; -import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.Segment; -import com.intellij.openapi.util.Trinity; import com.intellij.util.Processor; import org.jetbrains.annotations.NotNull; import java.util.*; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; /** * User: cdr */ public class RangeMarkerTree extends IntervalTreeImpl { private final PrioritizedDocumentListener myListener; - private final Document myDocument; + final Document myDocument; private final EqualStartIntervalComparator myEqualStartIntervalComparator = new EqualStartIntervalComparator() { @Override public int compare(IntervalNode i1, IntervalNode i2) { @@ -54,9 +50,7 @@ public class RangeMarkerTree extends IntervalTreeImpl extends IntervalTreeImpl checkMax(IntervalNode root, - int deltaUpToRootExclusive, - boolean assertInvalid, - Ref allValid, AtomicInteger counter) { - if (root != null) { - RangeMarkerImpl r = (RangeMarkerImpl)root.getKey(); - if (r != null) { - assert /*r.myNode == null || */r.myNode == root; - assert r.myNode.getTree() == this; - } - } - return super.checkMax(root, deltaUpToRootExclusive, assertInvalid, allValid, counter); - } - - @Override - public IntervalNode addInterval(@NotNull T interval, int start, int end, Object data) { + public MyNode addInterval(@NotNull T interval, int start, int end, boolean greedyToLeft, boolean greedyToRight, int layer) { RangeMarkerImpl marker = (RangeMarkerImpl)interval; marker.setValid(true); - return super.addInterval(interval, start, end, data); - } + RMNode node = (RMNode)super.addInterval(interval, start, end, greedyToLeft, greedyToRight, layer); + ((RangeMarkerImpl)interval).myNode = node; + + checkBelongsToTheTree(interval, true); - @Override - protected RMNode createNewNode(T key, int start, int end, Object data) { - RMNode node = new RMNode(key, start, end); - ((RangeMarkerImpl)key).myNode = node; return node; } - //private static long counter; - private static final AtomicLong counter = new AtomicLong(); + @NotNull + @Override + protected RMNode createNewNode(@NotNull T key, int start, int end, boolean greedyToLeft, boolean greedyToRight, int layer) { + return new RMNode(key, start, end, greedyToLeft, greedyToRight); + } + + @Override + protected void checkBelongsToTheTree(T interval, boolean assertInvalid) { + if (!VERIFY) return; + assert ((RangeMarkerImpl)interval).myDocument == myDocument; + + super.checkBelongsToTheTree(interval, assertInvalid); + } + + @Override + protected MyNode lookupNode(@NotNull T key) { + return ((RangeMarkerImpl)key).myNode; + } + public class RMNode extends MyNode { - private boolean isExpandToLeft = false; - private boolean isExpandToRight = false; - private final long myId; + private final boolean isExpandToLeft; + private final boolean isExpandToRight; - public RMNode(@NotNull T key, int start, int end) { + public RMNode(@NotNull T key, int start, int end, boolean greedyToLeft, boolean greedyToRight) { super(key, start, end); - myId = counter.getAndIncrement(); - //myId = counter++; + isExpandToLeft = greedyToLeft; + isExpandToRight = greedyToRight; } - public void setGreedyToLeft(boolean greedy) { - isExpandToLeft = greedy; - } - - public void setGreedyToRight(boolean greedy) { - isExpandToRight = greedy; + @Override + public boolean processAliveKeys(@NotNull Processor processor) { + return super.processAliveKeys(processor); } public boolean isGreedyToLeft() { @@ -145,8 +135,18 @@ public class RangeMarkerTree extends IntervalTreeImpl extends IntervalTreeImpl affected = new ArrayList(); - updateMarkersOnChange(getRoot(), e, affected); + normalized &= !collectAffectedMarkers(getRoot(), e, affected); checkMax(false); if (!affected.isEmpty()) { @@ -172,7 +171,7 @@ public class RangeMarkerTree extends IntervalTreeImpl extends IntervalTreeImpl aliveKeys = node.getAliveKeys(); + if (aliveKeys.isEmpty()) continue; // collected + RangeMarkerImpl marker = (RangeMarkerImpl)aliveKeys.get(0); marker.setValid(true); //marker.myNode = null; marker.documentChanged(e); if (marker.isValid()) { - insert(node); + RMNode insertedNode = (RMNode)findOrInsert(node); + // can change if two range become the one + if (insertedNode != node) { + // merge happened + for (T interval : aliveKeys) { + insertedNode.addInterval(interval); + } + } + assert marker.isValid(); } } - checkMax(true); } + checkMax(true); IntervalNode root = getRoot(); assert root == null || root.maxEnd + root.delta <= myDocument.getTextLength(); @@ -201,19 +209,20 @@ public class RangeMarkerTree extends IntervalTreeImpl affected) { - if (root == null) return; - pushDelta(root); + // returns true if some delta was or became not null + private boolean collectAffectedMarkers(IntervalNode root, + @NotNull DocumentEvent e, + @NotNull List affected) { + if (root == null) return false; + boolean denorm = pushDelta(root); int maxEnd = root.maxEnd; assert root.isValid(); int offset = e.getOffset(); int affectedEndOffset = offset + e.getOldLength(); - Object key = root.getKey(); - if (key == null) { + boolean hasAliveKeys = root.hasAliveKey(false); + if (!hasAliveKeys) { // marker was garbage collected affected.add(root); } @@ -222,28 +231,31 @@ public class RangeMarkerTree extends IntervalTreeImpl sweepProcessor) { normalize(); return sweep(new Generator() { @@ -252,7 +264,6 @@ public class RangeMarkerTree extends IntervalTreeImpl { diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/RedBlackTree.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/RedBlackTree.java index c616d49cbeff..88919cc518b9 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/RedBlackTree.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/RedBlackTree.java @@ -15,8 +15,11 @@ */ package com.intellij.openapi.editor.impl; +import com.intellij.util.Processor; import org.jetbrains.annotations.NotNull; +import java.util.List; + /** * User: cdr @@ -24,7 +27,7 @@ import org.jetbrains.annotations.NotNull; public abstract class RedBlackTree { public static boolean VERIFY = false; private static final int INDENT_STEP = 4; - protected int size; + private int nodeSize; // number of nodes protected int modCount; protected Node root; @@ -33,8 +36,6 @@ public abstract class RedBlackTree { verifyProperties(); } - protected abstract Node lookupNode(@NotNull K key, Node root); - protected void rotateLeft(Node n) { Node r = n.getRight(); replaceNode(n, r); @@ -78,7 +79,9 @@ public abstract class RedBlackTree { //oldn.right = null; } - protected abstract Node createNewNode(K key, int start, int end, Object data); + protected void onInsertNode() { + nodeSize++; + } protected void insertCase1(Node n) { if (n.getParent() == null) { @@ -137,12 +140,6 @@ public abstract class RedBlackTree { assert node1 == null || node1.getParent() == null || node1.getParent().getLeft() == node1 || node1.getParent().getRight() == node1; } - public Node delete(@NotNull K key) { - Node n = lookupNode(key, root); - deleteNode(n); - return n; - } - protected void deleteNode(Node n) { modCount++; if (n == null) return; // Key not found, do nothing @@ -175,7 +172,9 @@ public abstract class RedBlackTree { if (nodeColor(root) == Color.RED) { root.color = Color.BLACK; } - size--; + + assert nodeSize > 0 : nodeSize; + nodeSize--; verifyProperties(); } @@ -286,10 +285,10 @@ public abstract class RedBlackTree { System.err.print(" "); } if (n.color == Color.BLACK) { - System.err.println(n.getKey()); + System.err.println(n); } else { - System.err.println("<" + n.getKey() + ">"); + System.err.println("<" + n + ">"); } if (n.getLeft() != null) { printHelper(n.getLeft(), indent + INDENT_STEP); @@ -347,7 +346,10 @@ public abstract class RedBlackTree { this.parent = parent; } - public abstract K getKey(); + public abstract boolean processAliveKeys(@NotNull Processor processor); + @NotNull + public abstract List getAliveKeys(); + public abstract boolean hasAliveKey(boolean purgeDead); } protected static enum Color { @@ -355,7 +357,10 @@ public abstract class RedBlackTree { } public int size() { - return size; + return nodeSize; + } + public int nodeSize() { + return nodeSize; } public void verifyProperties() { @@ -426,6 +431,6 @@ public abstract class RedBlackTree { public void clear() { modCount++; root = null; - size = 0; + nodeSize = 0; } } diff --git a/platform/testFramework/src/com/intellij/mock/MockDocument.java b/platform/testFramework/src/com/intellij/mock/MockDocument.java index 2156531a7f69..c5a84d8011d2 100644 --- a/platform/testFramework/src/com/intellij/mock/MockDocument.java +++ b/platform/testFramework/src/com/intellij/mock/MockDocument.java @@ -287,10 +287,6 @@ public class MockDocument implements DocumentEx { return false; } - @Override - public void addRangeMarker(@NotNull RangeMarkerEx rangeMarker, int start, int end) { - } - @Override public boolean isInBulkUpdate() { return false; diff --git a/platform/util/src/com/intellij/util/containers/ContainerUtil.java b/platform/util/src/com/intellij/util/containers/ContainerUtil.java index f6f7f4979abe..ecfb00ea0c68 100644 --- a/platform/util/src/com/intellij/util/containers/ContainerUtil.java +++ b/platform/util/src/com/intellij/util/containers/ContainerUtil.java @@ -768,11 +768,29 @@ public class ContainerUtil { } return result; } + @NotNull + public static List map(@NotNull Collection iterable, @NotNull Function mapping) { + List result = new ArrayList(iterable.size()); + for (T t : iterable) { + result.add(mapping.fun(t)); + } + return result; + } @NotNull public static List mapNotNull(@NotNull T[] array, Function mapping) { return mapNotNull(Arrays.asList(array), mapping); } + public static V[] mapNotNull(@NotNull T[] arr, @NotNull Function mapping, @NotNull V[] emptyArray) { + List result = new ArrayList(arr.length); + for (T t : arr) { + V v = mapping.fun(t); + if (v != null) { + result.add(v); + } + } + return result.toArray(emptyArray); + } @NotNull public static List mapNotNull(Iterable iterable, Function mapping) { @@ -785,6 +803,17 @@ public class ContainerUtil { } return result; } + @NotNull + public static List mapNotNull(Collection iterable, Function mapping) { + List result = new ArrayList(iterable.size()); + for (T t : iterable) { + final V o = mapping.fun(t); + if (o != null) { + result.add(o); + } + } + return result; + } @NotNull public static List packNullables(@NotNull T... elements) {