aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/TextSegmentCollection.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/TextSegmentCollection.cs')
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/TextSegmentCollection.cs971
1 files changed, 971 insertions, 0 deletions
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/TextSegmentCollection.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/TextSegmentCollection.cs
new file mode 100644
index 000000000..549dbb77d
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/TextSegmentCollection.cs
@@ -0,0 +1,971 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using Tango.Scripting.Editors.Utils;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Windows;
+
+namespace Tango.Scripting.Editors.Document
+{
+ /// <summary>
+ /// Interface to allow TextSegments to access the TextSegmentCollection - we cannot use a direct reference
+ /// because TextSegmentCollection is generic.
+ /// </summary>
+ interface ISegmentTree
+ {
+ void Add(TextSegment s);
+ void Remove(TextSegment s);
+ void UpdateAugmentedData(TextSegment s);
+ }
+
+ /// <summary>
+ /// <para>
+ /// A collection of text segments that supports efficient lookup of segments
+ /// intersecting with another segment.
+ /// </para>
+ /// </summary>
+ /// <remarks><inheritdoc cref="TextSegment"/></remarks>
+ /// <see cref="TextSegment"/>
+ public sealed class TextSegmentCollection<T> : ICollection<T>, ISegmentTree, IWeakEventListener where T : TextSegment
+ {
+ // Implementation: this is basically a mixture of an augmented interval tree
+ // and the TextAnchorTree.
+
+ // WARNING: you need to understand interval trees (the version with the augmented 'high'/'max' field)
+ // and how the TextAnchorTree works before you have any chance of understanding this code.
+
+ // This means that every node holds two "segments":
+ // one like the segments in the text anchor tree to support efficient offset changes
+ // and another that is the interval as seen by the user
+
+ // So basically, the tree contains a list of contiguous node segments of the first kind,
+ // with interval segments starting at the end of every node segment.
+
+ // Performance:
+ // Add is O(lg n)
+ // Remove is O(lg n)
+ // DocumentChanged is O(m * lg n), with m the number of segments that intersect with the changed document section
+ // FindFirstSegmentWithStartAfter is O(m + lg n) with m being the number of segments at the same offset as the result segment
+ // FindIntersectingSegments is O(m + lg n) with m being the number of intersecting segments.
+
+ int count;
+ TextSegment root;
+ bool isConnectedToDocument;
+
+ #region Constructor
+ /// <summary>
+ /// Creates a new TextSegmentCollection that needs manual calls to <see cref="UpdateOffsets(DocumentChangeEventArgs)"/>.
+ /// </summary>
+ public TextSegmentCollection()
+ {
+ }
+
+ /// <summary>
+ /// Creates a new TextSegmentCollection that updates the offsets automatically.
+ /// </summary>
+ /// <param name="textDocument">The document to which the text segments
+ /// that will be added to the tree belong. When the document changes, the
+ /// position of the text segments will be updated accordingly.</param>
+ public TextSegmentCollection(TextDocument textDocument)
+ {
+ if (textDocument == null)
+ throw new ArgumentNullException("textDocument");
+
+ textDocument.VerifyAccess();
+ isConnectedToDocument = true;
+ TextDocumentWeakEventManager.Changed.AddListener(textDocument, this);
+ }
+ #endregion
+
+ #region OnDocumentChanged / UpdateOffsets
+ bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
+ {
+ if (managerType == typeof(TextDocumentWeakEventManager.Changed)) {
+ OnDocumentChanged((DocumentChangeEventArgs)e);
+ return true;
+ }
+ return false;
+ }
+
+ /// <summary>
+ /// Updates the start and end offsets of all segments stored in this collection.
+ /// </summary>
+ /// <param name="e">DocumentChangeEventArgs instance describing the change to the document.</param>
+ public void UpdateOffsets(DocumentChangeEventArgs e)
+ {
+ if (e == null)
+ throw new ArgumentNullException("e");
+ if (isConnectedToDocument)
+ throw new InvalidOperationException("This TextSegmentCollection will automatically update offsets; do not call UpdateOffsets manually!");
+ OnDocumentChanged(e);
+ CheckProperties();
+ }
+
+ void OnDocumentChanged(DocumentChangeEventArgs e)
+ {
+ OffsetChangeMap map = e.OffsetChangeMapOrNull;
+ if (map != null) {
+ foreach (OffsetChangeMapEntry entry in map) {
+ UpdateOffsetsInternal(entry);
+ }
+ } else {
+ UpdateOffsetsInternal(e.CreateSingleChangeMapEntry());
+ }
+ }
+
+ /// <summary>
+ /// Updates the start and end offsets of all segments stored in this collection.
+ /// </summary>
+ /// <param name="change">OffsetChangeMapEntry instance describing the change to the document.</param>
+ public void UpdateOffsets(OffsetChangeMapEntry change)
+ {
+ if (isConnectedToDocument)
+ throw new InvalidOperationException("This TextSegmentCollection will automatically update offsets; do not call UpdateOffsets manually!");
+ UpdateOffsetsInternal(change);
+ CheckProperties();
+ }
+ #endregion
+
+ #region UpdateOffsets (implementation)
+ void UpdateOffsetsInternal(OffsetChangeMapEntry change)
+ {
+ // Special case pure insertions, because they don't always cause a text segment to increase in size when the replaced region
+ // is inside a segment (when offset is at start or end of a text semgent).
+ if (change.RemovalLength == 0) {
+ InsertText(change.Offset, change.InsertionLength);
+ } else {
+ ReplaceText(change);
+ }
+ }
+
+ void InsertText(int offset, int length)
+ {
+ if (length == 0)
+ return;
+
+ // enlarge segments that contain offset (excluding those that have offset as endpoint)
+ foreach (TextSegment segment in FindSegmentsContaining(offset)) {
+ if (segment.StartOffset < offset && offset < segment.EndOffset) {
+ segment.Length += length;
+ }
+ }
+
+ // move start offsets of all segments >= offset
+ TextSegment node = FindFirstSegmentWithStartAfter(offset);
+ if (node != null) {
+ node.nodeLength += length;
+ UpdateAugmentedData(node);
+ }
+ }
+
+ void ReplaceText(OffsetChangeMapEntry change)
+ {
+ Debug.Assert(change.RemovalLength > 0);
+ int offset = change.Offset;
+ foreach (TextSegment segment in FindOverlappingSegments(offset, change.RemovalLength)) {
+ if (segment.StartOffset <= offset) {
+ if (segment.EndOffset >= offset + change.RemovalLength) {
+ // Replacement inside segment: adjust segment length
+ segment.Length += change.InsertionLength - change.RemovalLength;
+ } else {
+ // Replacement starting inside segment and ending after segment end: set segment end to removal position
+ //segment.EndOffset = offset;
+ segment.Length = offset - segment.StartOffset;
+ }
+ } else {
+ // Replacement starting in front of text segment and running into segment.
+ // Keep segment.EndOffset constant and move segment.StartOffset to the end of the replacement
+ int remainingLength = segment.EndOffset - (offset + change.RemovalLength);
+ RemoveSegment(segment);
+ segment.StartOffset = offset + change.RemovalLength;
+ segment.Length = Math.Max(0, remainingLength);
+ AddSegment(segment);
+ }
+ }
+ // move start offsets of all segments > offset
+ TextSegment node = FindFirstSegmentWithStartAfter(offset + 1);
+ if (node != null) {
+ Debug.Assert(node.nodeLength >= change.RemovalLength);
+ node.nodeLength += change.InsertionLength - change.RemovalLength;
+ UpdateAugmentedData(node);
+ }
+ }
+ #endregion
+
+ #region Add
+ /// <summary>
+ /// Adds the specified segment to the tree. This will cause the segment to update when the
+ /// document changes.
+ /// </summary>
+ public void Add(T item)
+ {
+ if (item == null)
+ throw new ArgumentNullException("item");
+ if (item.ownerTree != null)
+ throw new ArgumentException("The segment is already added to a SegmentCollection.");
+ AddSegment(item);
+ }
+
+ void ISegmentTree.Add(TextSegment s)
+ {
+ AddSegment(s);
+ }
+
+ void AddSegment(TextSegment node)
+ {
+ int insertionOffset = node.StartOffset;
+ node.distanceToMaxEnd = node.segmentLength;
+ if (root == null) {
+ root = node;
+ node.totalNodeLength = node.nodeLength;
+ } else if (insertionOffset >= root.totalNodeLength) {
+ // append segment at end of tree
+ node.nodeLength = node.totalNodeLength = insertionOffset - root.totalNodeLength;
+ InsertAsRight(root.RightMost, node);
+ } else {
+ // insert in middle of tree
+ TextSegment n = FindNode(ref insertionOffset);
+ Debug.Assert(insertionOffset < n.nodeLength);
+ // split node segment 'n' at offset
+ node.totalNodeLength = node.nodeLength = insertionOffset;
+ n.nodeLength -= insertionOffset;
+ InsertBefore(n, node);
+ }
+ node.ownerTree = this;
+ count++;
+ CheckProperties();
+ }
+
+ void InsertBefore(TextSegment node, TextSegment newNode)
+ {
+ if (node.left == null) {
+ InsertAsLeft(node, newNode);
+ } else {
+ InsertAsRight(node.left.RightMost, newNode);
+ }
+ }
+ #endregion
+
+ #region GetNextSegment / GetPreviousSegment
+ /// <summary>
+ /// Gets the next segment after the specified segment.
+ /// Segments are sorted by their start offset.
+ /// Returns null if segment is the last segment.
+ /// </summary>
+ public T GetNextSegment(T segment)
+ {
+ if (!Contains(segment))
+ throw new ArgumentException("segment is not inside the segment tree");
+ return (T)segment.Successor;
+ }
+
+ /// <summary>
+ /// Gets the previous segment before the specified segment.
+ /// Segments are sorted by their start offset.
+ /// Returns null if segment is the first segment.
+ /// </summary>
+ public T GetPreviousSegment(T segment)
+ {
+ if (!Contains(segment))
+ throw new ArgumentException("segment is not inside the segment tree");
+ return (T)segment.Predecessor;
+ }
+ #endregion
+
+ #region FirstSegment/LastSegment
+ /// <summary>
+ /// Returns the first segment in the collection or null, if the collection is empty.
+ /// </summary>
+ public T FirstSegment {
+ get {
+ return root == null ? null : (T)root.LeftMost;
+ }
+ }
+
+ /// <summary>
+ /// Returns the last segment in the collection or null, if the collection is empty.
+ /// </summary>
+ public T LastSegment {
+ get {
+ return root == null ? null : (T)root.RightMost;
+ }
+ }
+ #endregion
+
+ #region FindFirstSegmentWithStartAfter
+ /// <summary>
+ /// Gets the first segment with a start offset greater or equal to <paramref name="startOffset"/>.
+ /// Returns null if no such segment is found.
+ /// </summary>
+ public T FindFirstSegmentWithStartAfter(int startOffset)
+ {
+ if (root == null)
+ return null;
+ if (startOffset <= 0)
+ return (T)root.LeftMost;
+ TextSegment s = FindNode(ref startOffset);
+ // startOffset means that the previous segment is starting at the offset we were looking for
+ while (startOffset == 0) {
+ TextSegment p = (s == null) ? root.RightMost : s.Predecessor;
+ // There must always be a predecessor: if we were looking for the first node, we would have already
+ // returned it as root.LeftMost above.
+ Debug.Assert(p != null);
+ startOffset += p.nodeLength;
+ s = p;
+ }
+ return (T)s;
+ }
+
+ /// <summary>
+ /// Finds the node at the specified offset.
+ /// After the method has run, offset is relative to the beginning of the returned node.
+ /// </summary>
+ TextSegment FindNode(ref int offset)
+ {
+ TextSegment n = root;
+ while (true) {
+ if (n.left != null) {
+ if (offset < n.left.totalNodeLength) {
+ n = n.left; // descend into left subtree
+ continue;
+ } else {
+ offset -= n.left.totalNodeLength; // skip left subtree
+ }
+ }
+ if (offset < n.nodeLength) {
+ return n; // found correct node
+ } else {
+ offset -= n.nodeLength; // skip this node
+ }
+ if (n.right != null) {
+ n = n.right; // descend into right subtree
+ } else {
+ // didn't find any node containing the offset
+ return null;
+ }
+ }
+ }
+ #endregion
+
+ #region FindOverlappingSegments
+ /// <summary>
+ /// Finds all segments that contain the given offset.
+ /// (StartOffset &lt;= offset &lt;= EndOffset)
+ /// Segments are returned in the order given by GetNextSegment/GetPreviousSegment.
+ /// </summary>
+ /// <returns>Returns a new collection containing the results of the query.
+ /// This means it is safe to modify the TextSegmentCollection while iterating through the result collection.</returns>
+ public ReadOnlyCollection<T> FindSegmentsContaining(int offset)
+ {
+ return FindOverlappingSegments(offset, 0);
+ }
+
+ /// <summary>
+ /// Finds all segments that overlap with the given segment (including touching segments).
+ /// </summary>
+ /// <returns>Returns a new collection containing the results of the query.
+ /// This means it is safe to modify the TextSegmentCollection while iterating through the result collection.</returns>
+ public ReadOnlyCollection<T> FindOverlappingSegments(ISegment segment)
+ {
+ if (segment == null)
+ throw new ArgumentNullException("segment");
+ return FindOverlappingSegments(segment.Offset, segment.Length);
+ }
+
+ /// <summary>
+ /// Finds all segments that overlap with the given segment (including touching segments).
+ /// Segments are returned in the order given by GetNextSegment/GetPreviousSegment.
+ /// </summary>
+ /// <returns>Returns a new collection containing the results of the query.
+ /// This means it is safe to modify the TextSegmentCollection while iterating through the result collection.</returns>
+ public ReadOnlyCollection<T> FindOverlappingSegments(int offset, int length)
+ {
+ ThrowUtil.CheckNotNegative(length, "length");
+ List<T> results = new List<T>();
+ if (root != null) {
+ FindOverlappingSegments(results, root, offset, offset + length);
+ }
+ return results.AsReadOnly();
+ }
+
+ void FindOverlappingSegments(List<T> results, TextSegment node, int low, int high)
+ {
+ // low and high are relative to node.LeftMost startpos (not node.LeftMost.Offset)
+ if (high < 0) {
+ // node is irrelevant for search because all intervals in node are after high
+ return;
+ }
+
+ // find values relative to node.Offset
+ int nodeLow = low - node.nodeLength;
+ int nodeHigh = high - node.nodeLength;
+ if (node.left != null) {
+ nodeLow -= node.left.totalNodeLength;
+ nodeHigh -= node.left.totalNodeLength;
+ }
+
+ if (node.distanceToMaxEnd < nodeLow) {
+ // node is irrelevant for search because all intervals in node are before low
+ return;
+ }
+
+ if (node.left != null)
+ FindOverlappingSegments(results, node.left, low, high);
+
+ if (nodeHigh < 0) {
+ // node and everything in node.right is before low
+ return;
+ }
+
+ if (nodeLow <= node.segmentLength) {
+ results.Add((T)node);
+ }
+
+ if (node.right != null)
+ FindOverlappingSegments(results, node.right, nodeLow, nodeHigh);
+ }
+ #endregion
+
+ #region UpdateAugmentedData
+ void UpdateAugmentedData(TextSegment node)
+ {
+ int totalLength = node.nodeLength;
+ int distanceToMaxEnd = node.segmentLength;
+ if (node.left != null) {
+ totalLength += node.left.totalNodeLength;
+
+ int leftDTME = node.left.distanceToMaxEnd;
+ // dtme is relative, so convert it to the coordinates of node:
+ if (node.left.right != null)
+ leftDTME -= node.left.right.totalNodeLength;
+ leftDTME -= node.nodeLength;
+ if (leftDTME > distanceToMaxEnd)
+ distanceToMaxEnd = leftDTME;
+ }
+ if (node.right != null) {
+ totalLength += node.right.totalNodeLength;
+
+ int rightDTME = node.right.distanceToMaxEnd;
+ // dtme is relative, so convert it to the coordinates of node:
+ rightDTME += node.right.nodeLength;
+ if (node.right.left != null)
+ rightDTME += node.right.left.totalNodeLength;
+ if (rightDTME > distanceToMaxEnd)
+ distanceToMaxEnd = rightDTME;
+ }
+ if (node.totalNodeLength != totalLength
+ || node.distanceToMaxEnd != distanceToMaxEnd)
+ {
+ node.totalNodeLength = totalLength;
+ node.distanceToMaxEnd = distanceToMaxEnd;
+ if (node.parent != null)
+ UpdateAugmentedData(node.parent);
+ }
+ }
+
+ void ISegmentTree.UpdateAugmentedData(TextSegment node)
+ {
+ UpdateAugmentedData(node);
+ }
+ #endregion
+
+ #region Remove
+ /// <summary>
+ /// Removes the specified segment from the tree. This will cause the segment to not update
+ /// anymore when the document changes.
+ /// </summary>
+ public bool Remove(T item)
+ {
+ if (!Contains(item))
+ return false;
+ RemoveSegment(item);
+ return true;
+ }
+
+ void ISegmentTree.Remove(TextSegment s)
+ {
+ RemoveSegment(s);
+ }
+
+ void RemoveSegment(TextSegment s)
+ {
+ int oldOffset = s.StartOffset;
+ TextSegment successor = s.Successor;
+ if (successor != null)
+ successor.nodeLength += s.nodeLength;
+ RemoveNode(s);
+ if (successor != null)
+ UpdateAugmentedData(successor);
+ Disconnect(s, oldOffset);
+ CheckProperties();
+ }
+
+ void Disconnect(TextSegment s, int offset)
+ {
+ s.left = s.right = s.parent = null;
+ s.ownerTree = null;
+ s.nodeLength = offset;
+ count--;
+ }
+
+ /// <summary>
+ /// Removes all segments from the tree.
+ /// </summary>
+ public void Clear()
+ {
+ T[] segments = this.ToArray();
+ root = null;
+ int offset = 0;
+ foreach (TextSegment s in segments) {
+ offset += s.nodeLength;
+ Disconnect(s, offset);
+ }
+ CheckProperties();
+ }
+ #endregion
+
+ #region CheckProperties
+ [Conditional("DATACONSISTENCYTEST")]
+ internal void CheckProperties()
+ {
+ #if DEBUG
+ if (root != null) {
+ CheckProperties(root);
+
+ // check red-black property:
+ int blackCount = -1;
+ CheckNodeProperties(root, null, RED, 0, ref blackCount);
+ }
+
+ int expectedCount = 0;
+ // we cannot trust LINQ not to call ICollection.Count, so we need this loop
+ // to count the elements in the tree
+ using (IEnumerator<T> en = GetEnumerator()) {
+ while (en.MoveNext()) expectedCount++;
+ }
+ Debug.Assert(count == expectedCount);
+ #endif
+ }
+
+ #if DEBUG
+ void CheckProperties(TextSegment node)
+ {
+ int totalLength = node.nodeLength;
+ int distanceToMaxEnd = node.segmentLength;
+ if (node.left != null) {
+ CheckProperties(node.left);
+ totalLength += node.left.totalNodeLength;
+ distanceToMaxEnd = Math.Max(distanceToMaxEnd,
+ node.left.distanceToMaxEnd + node.left.StartOffset - node.StartOffset);
+ }
+ if (node.right != null) {
+ CheckProperties(node.right);
+ totalLength += node.right.totalNodeLength;
+ distanceToMaxEnd = Math.Max(distanceToMaxEnd,
+ node.right.distanceToMaxEnd + node.right.StartOffset - node.StartOffset);
+ }
+ Debug.Assert(node.totalNodeLength == totalLength);
+ Debug.Assert(node.distanceToMaxEnd == distanceToMaxEnd);
+ }
+
+ /*
+ 1. A node is either red or black.
+ 2. The root is black.
+ 3. All leaves are black. (The leaves are the NIL children.)
+ 4. Both children of every red node are black. (So every red node must have a black parent.)
+ 5. Every simple path from a node to a descendant leaf contains the same number of black nodes. (Not counting the leaf node.)
+ */
+ void CheckNodeProperties(TextSegment node, TextSegment parentNode, bool parentColor, int blackCount, ref int expectedBlackCount)
+ {
+ if (node == null) return;
+
+ Debug.Assert(node.parent == parentNode);
+
+ if (parentColor == RED) {
+ Debug.Assert(node.color == BLACK);
+ }
+ if (node.color == BLACK) {
+ blackCount++;
+ }
+ if (node.left == null && node.right == null) {
+ // node is a leaf node:
+ if (expectedBlackCount == -1)
+ expectedBlackCount = blackCount;
+ else
+ Debug.Assert(expectedBlackCount == blackCount);
+ }
+ CheckNodeProperties(node.left, node, node.color, blackCount, ref expectedBlackCount);
+ CheckNodeProperties(node.right, node, node.color, blackCount, ref expectedBlackCount);
+ }
+
+ static void AppendTreeToString(TextSegment node, StringBuilder b, int indent)
+ {
+ if (node.color == RED)
+ b.Append("RED ");
+ else
+ b.Append("BLACK ");
+ b.AppendLine(node.ToString() + node.ToDebugString());
+ indent += 2;
+ if (node.left != null) {
+ b.Append(' ', indent);
+ b.Append("L: ");
+ AppendTreeToString(node.left, b, indent);
+ }
+ if (node.right != null) {
+ b.Append(' ', indent);
+ b.Append("R: ");
+ AppendTreeToString(node.right, b, indent);
+ }
+ }
+ #endif
+
+ internal string GetTreeAsString()
+ {
+ #if DEBUG
+ StringBuilder b = new StringBuilder();
+ if (root != null)
+ AppendTreeToString(root, b, 0);
+ return b.ToString();
+ #else
+ return "Not available in release build.";
+ #endif
+ }
+ #endregion
+
+ #region Red/Black Tree
+ internal const bool RED = true;
+ internal const bool BLACK = false;
+
+ void InsertAsLeft(TextSegment parentNode, TextSegment newNode)
+ {
+ Debug.Assert(parentNode.left == null);
+ parentNode.left = newNode;
+ newNode.parent = parentNode;
+ newNode.color = RED;
+ UpdateAugmentedData(parentNode);
+ FixTreeOnInsert(newNode);
+ }
+
+ void InsertAsRight(TextSegment parentNode, TextSegment newNode)
+ {
+ Debug.Assert(parentNode.right == null);
+ parentNode.right = newNode;
+ newNode.parent = parentNode;
+ newNode.color = RED;
+ UpdateAugmentedData(parentNode);
+ FixTreeOnInsert(newNode);
+ }
+
+ void FixTreeOnInsert(TextSegment node)
+ {
+ Debug.Assert(node != null);
+ Debug.Assert(node.color == RED);
+ Debug.Assert(node.left == null || node.left.color == BLACK);
+ Debug.Assert(node.right == null || node.right.color == BLACK);
+
+ TextSegment parentNode = node.parent;
+ if (parentNode == null) {
+ // we inserted in the root -> the node must be black
+ // since this is a root node, making the node black increments the number of black nodes
+ // on all paths by one, so it is still the same for all paths.
+ node.color = BLACK;
+ return;
+ }
+ if (parentNode.color == BLACK) {
+ // if the parent node where we inserted was black, our red node is placed correctly.
+ // since we inserted a red node, the number of black nodes on each path is unchanged
+ // -> the tree is still balanced
+ return;
+ }
+ // parentNode is red, so there is a conflict here!
+
+ // because the root is black, parentNode is not the root -> there is a grandparent node
+ TextSegment grandparentNode = parentNode.parent;
+ TextSegment uncleNode = Sibling(parentNode);
+ if (uncleNode != null && uncleNode.color == RED) {
+ parentNode.color = BLACK;
+ uncleNode.color = BLACK;
+ grandparentNode.color = RED;
+ FixTreeOnInsert(grandparentNode);
+ return;
+ }
+ // now we know: parent is red but uncle is black
+ // First rotation:
+ if (node == parentNode.right && parentNode == grandparentNode.left) {
+ RotateLeft(parentNode);
+ node = node.left;
+ } else if (node == parentNode.left && parentNode == grandparentNode.right) {
+ RotateRight(parentNode);
+ node = node.right;
+ }
+ // because node might have changed, reassign variables:
+ parentNode = node.parent;
+ grandparentNode = parentNode.parent;
+
+ // Now recolor a bit:
+ parentNode.color = BLACK;
+ grandparentNode.color = RED;
+ // Second rotation:
+ if (node == parentNode.left && parentNode == grandparentNode.left) {
+ RotateRight(grandparentNode);
+ } else {
+ // because of the first rotation, this is guaranteed:
+ Debug.Assert(node == parentNode.right && parentNode == grandparentNode.right);
+ RotateLeft(grandparentNode);
+ }
+ }
+
+ void RemoveNode(TextSegment removedNode)
+ {
+ if (removedNode.left != null && removedNode.right != null) {
+ // replace removedNode with it's in-order successor
+
+ TextSegment leftMost = removedNode.right.LeftMost;
+ RemoveNode(leftMost); // remove leftMost from its current location
+
+ // and overwrite the removedNode with it
+ ReplaceNode(removedNode, leftMost);
+ leftMost.left = removedNode.left;
+ if (leftMost.left != null) leftMost.left.parent = leftMost;
+ leftMost.right = removedNode.right;
+ if (leftMost.right != null) leftMost.right.parent = leftMost;
+ leftMost.color = removedNode.color;
+
+ UpdateAugmentedData(leftMost);
+ if (leftMost.parent != null) UpdateAugmentedData(leftMost.parent);
+ return;
+ }
+
+ // now either removedNode.left or removedNode.right is null
+ // get the remaining child
+ TextSegment parentNode = removedNode.parent;
+ TextSegment childNode = removedNode.left ?? removedNode.right;
+ ReplaceNode(removedNode, childNode);
+ if (parentNode != null) UpdateAugmentedData(parentNode);
+ if (removedNode.color == BLACK) {
+ if (childNode != null && childNode.color == RED) {
+ childNode.color = BLACK;
+ } else {
+ FixTreeOnDelete(childNode, parentNode);
+ }
+ }
+ }
+
+ void FixTreeOnDelete(TextSegment node, TextSegment parentNode)
+ {
+ Debug.Assert(node == null || node.parent == parentNode);
+ if (parentNode == null)
+ return;
+
+ // warning: node may be null
+ TextSegment sibling = Sibling(node, parentNode);
+ if (sibling.color == RED) {
+ parentNode.color = RED;
+ sibling.color = BLACK;
+ if (node == parentNode.left) {
+ RotateLeft(parentNode);
+ } else {
+ RotateRight(parentNode);
+ }
+
+ sibling = Sibling(node, parentNode); // update value of sibling after rotation
+ }
+
+ if (parentNode.color == BLACK
+ && sibling.color == BLACK
+ && GetColor(sibling.left) == BLACK
+ && GetColor(sibling.right) == BLACK)
+ {
+ sibling.color = RED;
+ FixTreeOnDelete(parentNode, parentNode.parent);
+ return;
+ }
+
+ if (parentNode.color == RED
+ && sibling.color == BLACK
+ && GetColor(sibling.left) == BLACK
+ && GetColor(sibling.right) == BLACK)
+ {
+ sibling.color = RED;
+ parentNode.color = BLACK;
+ return;
+ }
+
+ if (node == parentNode.left &&
+ sibling.color == BLACK &&
+ GetColor(sibling.left) == RED &&
+ GetColor(sibling.right) == BLACK)
+ {
+ sibling.color = RED;
+ sibling.left.color = BLACK;
+ RotateRight(sibling);
+ }
+ else if (node == parentNode.right &&
+ sibling.color == BLACK &&
+ GetColor(sibling.right) == RED &&
+ GetColor(sibling.left) == BLACK)
+ {
+ sibling.color = RED;
+ sibling.right.color = BLACK;
+ RotateLeft(sibling);
+ }
+ sibling = Sibling(node, parentNode); // update value of sibling after rotation
+
+ sibling.color = parentNode.color;
+ parentNode.color = BLACK;
+ if (node == parentNode.left) {
+ if (sibling.right != null) {
+ Debug.Assert(sibling.right.color == RED);
+ sibling.right.color = BLACK;
+ }
+ RotateLeft(parentNode);
+ } else {
+ if (sibling.left != null) {
+ Debug.Assert(sibling.left.color == RED);
+ sibling.left.color = BLACK;
+ }
+ RotateRight(parentNode);
+ }
+ }
+
+ void ReplaceNode(TextSegment replacedNode, TextSegment newNode)
+ {
+ if (replacedNode.parent == null) {
+ Debug.Assert(replacedNode == root);
+ root = newNode;
+ } else {
+ if (replacedNode.parent.left == replacedNode)
+ replacedNode.parent.left = newNode;
+ else
+ replacedNode.parent.right = newNode;
+ }
+ if (newNode != null) {
+ newNode.parent = replacedNode.parent;
+ }
+ replacedNode.parent = null;
+ }
+
+ void RotateLeft(TextSegment p)
+ {
+ // let q be p's right child
+ TextSegment q = p.right;
+ Debug.Assert(q != null);
+ Debug.Assert(q.parent == p);
+ // set q to be the new root
+ ReplaceNode(p, q);
+
+ // set p's right child to be q's left child
+ p.right = q.left;
+ if (p.right != null) p.right.parent = p;
+ // set q's left child to be p
+ q.left = p;
+ p.parent = q;
+ UpdateAugmentedData(p);
+ UpdateAugmentedData(q);
+ }
+
+ void RotateRight(TextSegment p)
+ {
+ // let q be p's left child
+ TextSegment q = p.left;
+ Debug.Assert(q != null);
+ Debug.Assert(q.parent == p);
+ // set q to be the new root
+ ReplaceNode(p, q);
+
+ // set p's left child to be q's right child
+ p.left = q.right;
+ if (p.left != null) p.left.parent = p;
+ // set q's right child to be p
+ q.right = p;
+ p.parent = q;
+ UpdateAugmentedData(p);
+ UpdateAugmentedData(q);
+ }
+
+ static TextSegment Sibling(TextSegment node)
+ {
+ if (node == node.parent.left)
+ return node.parent.right;
+ else
+ return node.parent.left;
+ }
+
+ static TextSegment Sibling(TextSegment node, TextSegment parentNode)
+ {
+ Debug.Assert(node == null || node.parent == parentNode);
+ if (node == parentNode.left)
+ return parentNode.right;
+ else
+ return parentNode.left;
+ }
+
+ static bool GetColor(TextSegment node)
+ {
+ return node != null ? node.color : BLACK;
+ }
+ #endregion
+
+ #region ICollection<T> implementation
+ /// <summary>
+ /// Gets the number of segments in the tree.
+ /// </summary>
+ public int Count {
+ get { return count; }
+ }
+
+ bool ICollection<T>.IsReadOnly {
+ get { return false; }
+ }
+
+ /// <summary>
+ /// Gets whether this tree contains the specified item.
+ /// </summary>
+ public bool Contains(T item)
+ {
+ return item != null && item.ownerTree == this;
+ }
+
+ /// <summary>
+ /// Copies all segments in this SegmentTree to the specified array.
+ /// </summary>
+ public void CopyTo(T[] array, int arrayIndex)
+ {
+ if (array == null)
+ throw new ArgumentNullException("array");
+ if (array.Length < this.Count)
+ throw new ArgumentException("The array is too small", "array");
+ if (arrayIndex < 0 || arrayIndex + count > array.Length)
+ throw new ArgumentOutOfRangeException("arrayIndex", arrayIndex, "Value must be between 0 and " + (array.Length - count));
+ foreach (T s in this) {
+ array[arrayIndex++] = s;
+ }
+ }
+
+ /// <summary>
+ /// Gets an enumerator to enumerate the segments.
+ /// </summary>
+ public IEnumerator<T> GetEnumerator()
+ {
+ if (root != null) {
+ TextSegment current = root.LeftMost;
+ while (current != null) {
+ yield return (T)current;
+ // TODO: check if collection was modified during enumeration
+ current = current.Successor;
+ }
+ }
+ }
+
+ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
+ {
+ return this.GetEnumerator();
+ }
+ #endregion
+ }
+}