PageRenderTime 47ms CodeModel.GetById 12ms app.highlight 27ms RepoModel.GetById 1ms app.codeStats 0ms

/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextSegmentCollection.cs

http://github.com/icsharpcode/ILSpy
C# | 989 lines | 709 code | 91 blank | 189 comment | 263 complexity | f03f55d19241bdeca63af37e5d4e6e3f MD5 | raw file
  1// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
  2// 
  3// Permission is hereby granted, free of charge, to any person obtaining a copy of this
  4// software and associated documentation files (the "Software"), to deal in the Software
  5// without restriction, including without limitation the rights to use, copy, modify, merge,
  6// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
  7// to whom the Software is furnished to do so, subject to the following conditions:
  8// 
  9// The above copyright notice and this permission notice shall be included in all copies or
 10// substantial portions of the Software.
 11// 
 12// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
 13// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
 14// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
 15// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 16// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 17// DEALINGS IN THE SOFTWARE.
 18
 19using System;
 20using System.Collections.Generic;
 21using System.Collections.ObjectModel;
 22using System.Diagnostics;
 23using System.Linq;
 24using System.Text;
 25using System.Windows;
 26using ICSharpCode.AvalonEdit.Utils;
 27#if NREFACTORY
 28using ICSharpCode.NRefactory.Editor;
 29#endif
 30
 31namespace ICSharpCode.AvalonEdit.Document
 32{
 33	/// <summary>
 34	/// Interface to allow TextSegments to access the TextSegmentCollection - we cannot use a direct reference
 35	/// because TextSegmentCollection is generic.
 36	/// </summary>
 37	interface ISegmentTree
 38	{
 39		void Add(TextSegment s);
 40		void Remove(TextSegment s);
 41		void UpdateAugmentedData(TextSegment s);
 42	}
 43	
 44	/// <summary>
 45	/// <para>
 46	/// A collection of text segments that supports efficient lookup of segments
 47	/// intersecting with another segment.
 48	/// </para>
 49	/// </summary>
 50	/// <remarks><inheritdoc cref="TextSegment"/></remarks>
 51	/// <see cref="TextSegment"/>
 52	public sealed class TextSegmentCollection<T> : ICollection<T>, ISegmentTree, IWeakEventListener where T : TextSegment
 53	{
 54		// Implementation: this is basically a mixture of an augmented interval tree
 55		// and the TextAnchorTree.
 56		
 57		// WARNING: you need to understand interval trees (the version with the augmented 'high'/'max' field)
 58		// and how the TextAnchorTree works before you have any chance of understanding this code.
 59		
 60		// This means that every node holds two "segments":
 61		// one like the segments in the text anchor tree to support efficient offset changes
 62		// and another that is the interval as seen by the user
 63		
 64		// So basically, the tree contains a list of contiguous node segments of the first kind,
 65		// with interval segments starting at the end of every node segment.
 66		
 67		// Performance:
 68		// Add is O(lg n)
 69		// Remove is O(lg n)
 70		// DocumentChanged is O(m * lg n), with m the number of segments that intersect with the changed document section
 71		// FindFirstSegmentWithStartAfter is O(m + lg n) with m being the number of segments at the same offset as the result segment
 72		// FindIntersectingSegments is O(m + lg n) with m being the number of intersecting segments.
 73		
 74		int count;
 75		TextSegment root;
 76		bool isConnectedToDocument;
 77		
 78		#region Constructor
 79		/// <summary>
 80		/// Creates a new TextSegmentCollection that needs manual calls to <see cref="UpdateOffsets(DocumentChangeEventArgs)"/>.
 81		/// </summary>
 82		public TextSegmentCollection()
 83		{
 84		}
 85		
 86		/// <summary>
 87		/// Creates a new TextSegmentCollection that updates the offsets automatically.
 88		/// </summary>
 89		/// <param name="textDocument">The document to which the text segments
 90		/// that will be added to the tree belong. When the document changes, the
 91		/// position of the text segments will be updated accordingly.</param>
 92		public TextSegmentCollection(TextDocument textDocument)
 93		{
 94			if (textDocument == null)
 95				throw new ArgumentNullException("textDocument");
 96			
 97			textDocument.VerifyAccess();
 98			isConnectedToDocument = true;
 99			TextDocumentWeakEventManager.Changed.AddListener(textDocument, this);
100		}
101		#endregion
102		
103		#region OnDocumentChanged / UpdateOffsets
104		bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
105		{
106			if (managerType == typeof(TextDocumentWeakEventManager.Changed)) {
107				OnDocumentChanged((DocumentChangeEventArgs)e);
108				return true;
109			}
110			return false;
111		}
112		
113		/// <summary>
114		/// Updates the start and end offsets of all segments stored in this collection.
115		/// </summary>
116		/// <param name="e">DocumentChangeEventArgs instance describing the change to the document.</param>
117		public void UpdateOffsets(DocumentChangeEventArgs e)
118		{
119			if (e == null)
120				throw new ArgumentNullException("e");
121			if (isConnectedToDocument)
122				throw new InvalidOperationException("This TextSegmentCollection will automatically update offsets; do not call UpdateOffsets manually!");
123			OnDocumentChanged(e);
124			CheckProperties();
125		}
126		
127		void OnDocumentChanged(DocumentChangeEventArgs e)
128		{
129			OffsetChangeMap map = e.OffsetChangeMapOrNull;
130			if (map != null) {
131				foreach (OffsetChangeMapEntry entry in map) {
132					UpdateOffsetsInternal(entry);
133				}
134			} else {
135				UpdateOffsetsInternal(e.CreateSingleChangeMapEntry());
136			}
137		}
138		
139		/// <summary>
140		/// Updates the start and end offsets of all segments stored in this collection.
141		/// </summary>
142		/// <param name="change">OffsetChangeMapEntry instance describing the change to the document.</param>
143		public void UpdateOffsets(OffsetChangeMapEntry change)
144		{
145			if (isConnectedToDocument)
146				throw new InvalidOperationException("This TextSegmentCollection will automatically update offsets; do not call UpdateOffsets manually!");
147			UpdateOffsetsInternal(change);
148			CheckProperties();
149		}
150		#endregion
151		
152		#region UpdateOffsets (implementation)
153		void UpdateOffsetsInternal(OffsetChangeMapEntry change)
154		{
155			// Special case pure insertions, because they don't always cause a text segment to increase in size when the replaced region
156			// is inside a segment (when offset is at start or end of a text semgent).
157			if (change.RemovalLength == 0) {
158				InsertText(change.Offset, change.InsertionLength);
159			} else {
160				ReplaceText(change);
161			}
162		}
163		
164		void InsertText(int offset, int length)
165		{
166			if (length == 0)
167				return;
168			
169			// enlarge segments that contain offset (excluding those that have offset as endpoint)
170			foreach (TextSegment segment in FindSegmentsContaining(offset)) {
171				if (segment.StartOffset < offset && offset < segment.EndOffset) {
172					segment.Length += length;
173				}
174			}
175			
176			// move start offsets of all segments >= offset
177			TextSegment node = FindFirstSegmentWithStartAfter(offset);
178			if (node != null) {
179				node.nodeLength += length;
180				UpdateAugmentedData(node);
181			}
182		}
183		
184		void ReplaceText(OffsetChangeMapEntry change)
185		{
186			Debug.Assert(change.RemovalLength > 0);
187			int offset = change.Offset;
188			foreach (TextSegment segment in FindOverlappingSegments(offset, change.RemovalLength)) {
189				if (segment.StartOffset <= offset) {
190					if (segment.EndOffset >= offset + change.RemovalLength) {
191						// Replacement inside segment: adjust segment length
192						segment.Length += change.InsertionLength - change.RemovalLength;
193					} else {
194						// Replacement starting inside segment and ending after segment end: set segment end to removal position
195						//segment.EndOffset = offset;
196						segment.Length = offset - segment.StartOffset;
197					}
198				} else {
199					// Replacement starting in front of text segment and running into segment.
200					// Keep segment.EndOffset constant and move segment.StartOffset to the end of the replacement
201					int remainingLength = segment.EndOffset - (offset + change.RemovalLength);
202					RemoveSegment(segment);
203					segment.StartOffset = offset + change.RemovalLength;
204					segment.Length = Math.Max(0, remainingLength);
205					AddSegment(segment);
206				}
207			}
208			// move start offsets of all segments > offset
209			TextSegment node = FindFirstSegmentWithStartAfter(offset + 1);
210			if (node != null) {
211				Debug.Assert(node.nodeLength >= change.RemovalLength);
212				node.nodeLength += change.InsertionLength - change.RemovalLength;
213				UpdateAugmentedData(node);
214			}
215		}
216		#endregion
217		
218		#region Add
219		/// <summary>
220		/// Adds the specified segment to the tree. This will cause the segment to update when the
221		/// document changes.
222		/// </summary>
223		public void Add(T item)
224		{
225			if (item == null)
226				throw new ArgumentNullException("item");
227			if (item.ownerTree != null)
228				throw new ArgumentException("The segment is already added to a SegmentCollection.");
229			AddSegment(item);
230		}
231		
232		void ISegmentTree.Add(TextSegment s)
233		{
234			AddSegment(s);
235		}
236		
237		void AddSegment(TextSegment node)
238		{
239			int insertionOffset = node.StartOffset;
240			node.distanceToMaxEnd = node.segmentLength;
241			if (root == null) {
242				root = node;
243				node.totalNodeLength = node.nodeLength;
244			} else if (insertionOffset >= root.totalNodeLength) {
245				// append segment at end of tree
246				node.nodeLength = node.totalNodeLength = insertionOffset - root.totalNodeLength;
247				InsertAsRight(root.RightMost, node);
248			} else {
249				// insert in middle of tree
250				TextSegment n = FindNode(ref insertionOffset);
251				Debug.Assert(insertionOffset < n.nodeLength);
252				// split node segment 'n' at offset
253				node.totalNodeLength = node.nodeLength = insertionOffset;
254				n.nodeLength -= insertionOffset;
255				InsertBefore(n, node);
256			}
257			node.ownerTree = this;
258			count++;
259			CheckProperties();
260		}
261		
262		void InsertBefore(TextSegment node, TextSegment newNode)
263		{
264			if (node.left == null) {
265				InsertAsLeft(node, newNode);
266			} else {
267				InsertAsRight(node.left.RightMost, newNode);
268			}
269		}
270		#endregion
271		
272		#region GetNextSegment / GetPreviousSegment
273		/// <summary>
274		/// Gets the next segment after the specified segment.
275		/// Segments are sorted by their start offset.
276		/// Returns null if segment is the last segment.
277		/// </summary>
278		public T GetNextSegment(T segment)
279		{
280			if (!Contains(segment))
281				throw new ArgumentException("segment is not inside the segment tree");
282			return (T)segment.Successor;
283		}
284		
285		/// <summary>
286		/// Gets the previous segment before the specified segment.
287		/// Segments are sorted by their start offset.
288		/// Returns null if segment is the first segment.
289		/// </summary>
290		public T GetPreviousSegment(T segment)
291		{
292			if (!Contains(segment))
293				throw new ArgumentException("segment is not inside the segment tree");
294			return (T)segment.Predecessor;
295		}
296		#endregion
297		
298		#region FirstSegment/LastSegment
299		/// <summary>
300		/// Returns the first segment in the collection or null, if the collection is empty.
301		/// </summary>
302		public T FirstSegment {
303			get {
304				return root == null ? null : (T)root.LeftMost;
305			}
306		}
307		
308		/// <summary>
309		/// Returns the last segment in the collection or null, if the collection is empty.
310		/// </summary>
311		public T LastSegment {
312			get {
313				return root == null ? null : (T)root.RightMost;
314			}
315		}
316		#endregion
317		
318		#region FindFirstSegmentWithStartAfter
319		/// <summary>
320		/// Gets the first segment with a start offset greater or equal to <paramref name="startOffset"/>.
321		/// Returns null if no such segment is found.
322		/// </summary>
323		public T FindFirstSegmentWithStartAfter(int startOffset)
324		{
325			if (root == null)
326				return null;
327			if (startOffset <= 0)
328				return (T)root.LeftMost;
329			TextSegment s = FindNode(ref startOffset);
330			// startOffset means that the previous segment is starting at the offset we were looking for
331			while (startOffset == 0) {
332				TextSegment p = (s == null) ? root.RightMost : s.Predecessor;
333				// There must always be a predecessor: if we were looking for the first node, we would have already
334				// returned it as root.LeftMost above.
335				Debug.Assert(p != null);
336				startOffset += p.nodeLength;
337				s = p;
338			}
339			return (T)s;
340		}
341		
342		/// <summary>
343		/// Finds the node at the specified offset.
344		/// After the method has run, offset is relative to the beginning of the returned node.
345		/// </summary>
346		TextSegment FindNode(ref int offset)
347		{
348			TextSegment n = root;
349			while (true) {
350				if (n.left != null) {
351					if (offset < n.left.totalNodeLength) {
352						n = n.left; // descend into left subtree
353						continue;
354					} else {
355						offset -= n.left.totalNodeLength; // skip left subtree
356					}
357				}
358				if (offset < n.nodeLength) {
359					return n; // found correct node
360				} else {
361					offset -= n.nodeLength; // skip this node
362				}
363				if (n.right != null) {
364					n = n.right; // descend into right subtree
365				} else {
366					// didn't find any node containing the offset
367					return null;
368				}
369			}
370		}
371		#endregion
372		
373		#region FindOverlappingSegments
374		/// <summary>
375		/// Finds all segments that contain the given offset.
376		/// (StartOffset &lt;= offset &lt;= EndOffset)
377		/// Segments are returned in the order given by GetNextSegment/GetPreviousSegment.
378		/// </summary>
379		/// <returns>Returns a new collection containing the results of the query.
380		/// This means it is safe to modify the TextSegmentCollection while iterating through the result collection.</returns>
381		public ReadOnlyCollection<T> FindSegmentsContaining(int offset)
382		{
383			return FindOverlappingSegments(offset, 0);
384		}
385		
386		/// <summary>
387		/// Finds all segments that overlap with the given segment (including touching segments).
388		/// </summary>
389		/// <returns>Returns a new collection containing the results of the query.
390		/// This means it is safe to modify the TextSegmentCollection while iterating through the result collection.</returns>
391		public ReadOnlyCollection<T> FindOverlappingSegments(ISegment segment)
392		{
393			if (segment == null)
394				throw new ArgumentNullException("segment");
395			return FindOverlappingSegments(segment.Offset, segment.Length);
396		}
397		
398		/// <summary>
399		/// Finds all segments that overlap with the given segment (including touching segments).
400		/// Segments are returned in the order given by GetNextSegment/GetPreviousSegment.
401		/// </summary>
402		/// <returns>Returns a new collection containing the results of the query.
403		/// This means it is safe to modify the TextSegmentCollection while iterating through the result collection.</returns>
404		public ReadOnlyCollection<T> FindOverlappingSegments(int offset, int length)
405		{
406			ThrowUtil.CheckNotNegative(length, "length");
407			List<T> results = new List<T>();
408			if (root != null) {
409				FindOverlappingSegments(results, root, offset, offset + length);
410			}
411			return results.AsReadOnly();
412		}
413		
414		void FindOverlappingSegments(List<T> results, TextSegment node, int low, int high)
415		{
416			// low and high are relative to node.LeftMost startpos (not node.LeftMost.Offset)
417			if (high < 0) {
418				// node is irrelevant for search because all intervals in node are after high
419				return;
420			}
421			
422			// find values relative to node.Offset
423			int nodeLow = low - node.nodeLength;
424			int nodeHigh = high - node.nodeLength;
425			if (node.left != null) {
426				nodeLow -= node.left.totalNodeLength;
427				nodeHigh -= node.left.totalNodeLength;
428			}
429			
430			if (node.distanceToMaxEnd < nodeLow) {
431				// node is irrelevant for search because all intervals in node are before low
432				return;
433			}
434			
435			if (node.left != null)
436				FindOverlappingSegments(results, node.left, low, high);
437			
438			if (nodeHigh < 0) {
439				// node and everything in node.right is before low
440				return;
441			}
442			
443			if (nodeLow <= node.segmentLength) {
444				results.Add((T)node);
445			}
446			
447			if (node.right != null)
448				FindOverlappingSegments(results, node.right, nodeLow, nodeHigh);
449		}
450		#endregion
451		
452		#region UpdateAugmentedData
453		void UpdateAugmentedData(TextSegment node)
454		{
455			int totalLength = node.nodeLength;
456			int distanceToMaxEnd = node.segmentLength;
457			if (node.left != null) {
458				totalLength += node.left.totalNodeLength;
459				
460				int leftDTME = node.left.distanceToMaxEnd;
461				// dtme is relative, so convert it to the coordinates of node:
462				if (node.left.right != null)
463					leftDTME -= node.left.right.totalNodeLength;
464				leftDTME -= node.nodeLength;
465				if (leftDTME > distanceToMaxEnd)
466					distanceToMaxEnd = leftDTME;
467			}
468			if (node.right != null) {
469				totalLength += node.right.totalNodeLength;
470				
471				int rightDTME = node.right.distanceToMaxEnd;
472				// dtme is relative, so convert it to the coordinates of node:
473				rightDTME += node.right.nodeLength;
474				if (node.right.left != null)
475					rightDTME += node.right.left.totalNodeLength;
476				if (rightDTME > distanceToMaxEnd)
477					distanceToMaxEnd = rightDTME;
478			}
479			if (node.totalNodeLength != totalLength
480			    || node.distanceToMaxEnd != distanceToMaxEnd)
481			{
482				node.totalNodeLength = totalLength;
483				node.distanceToMaxEnd = distanceToMaxEnd;
484				if (node.parent != null)
485					UpdateAugmentedData(node.parent);
486			}
487		}
488		
489		void ISegmentTree.UpdateAugmentedData(TextSegment node)
490		{
491			UpdateAugmentedData(node);
492		}
493		#endregion
494		
495		#region Remove
496		/// <summary>
497		/// Removes the specified segment from the tree. This will cause the segment to not update
498		/// anymore when the document changes.
499		/// </summary>
500		public bool Remove(T item)
501		{
502			if (!Contains(item))
503				return false;
504			RemoveSegment(item);
505			return true;
506		}
507		
508		void ISegmentTree.Remove(TextSegment s)
509		{
510			RemoveSegment(s);
511		}
512		
513		void RemoveSegment(TextSegment s)
514		{
515			int oldOffset = s.StartOffset;
516			TextSegment successor = s.Successor;
517			if (successor != null)
518				successor.nodeLength += s.nodeLength;
519			RemoveNode(s);
520			if (successor != null)
521				UpdateAugmentedData(successor);
522			Disconnect(s, oldOffset);
523			CheckProperties();
524		}
525		
526		void Disconnect(TextSegment s, int offset)
527		{
528			s.left = s.right = s.parent = null;
529			s.ownerTree = null;
530			s.nodeLength = offset;
531			count--;
532		}
533		
534		/// <summary>
535		/// Removes all segments from the tree.
536		/// </summary>
537		public void Clear()
538		{
539			T[] segments = this.ToArray();
540			root = null;
541			int offset = 0;
542			foreach (TextSegment s in segments) {
543				offset += s.nodeLength;
544				Disconnect(s, offset);
545			}
546			CheckProperties();
547		}
548		#endregion
549		
550		#region CheckProperties
551		[Conditional("DATACONSISTENCYTEST")]
552		internal void CheckProperties()
553		{
554			#if DEBUG
555			if (root != null) {
556				CheckProperties(root);
557				
558				// check red-black property:
559				int blackCount = -1;
560				CheckNodeProperties(root, null, RED, 0, ref blackCount);
561			}
562			
563			int expectedCount = 0;
564			// we cannot trust LINQ not to call ICollection.Count, so we need this loop
565			// to count the elements in the tree
566			using (IEnumerator<T> en = GetEnumerator()) {
567				while (en.MoveNext()) expectedCount++;
568			}
569			Debug.Assert(count == expectedCount);
570			#endif
571		}
572		
573		#if DEBUG
574		void CheckProperties(TextSegment node)
575		{
576			int totalLength = node.nodeLength;
577			int distanceToMaxEnd = node.segmentLength;
578			if (node.left != null) {
579				CheckProperties(node.left);
580				totalLength += node.left.totalNodeLength;
581				distanceToMaxEnd = Math.Max(distanceToMaxEnd,
582				                            node.left.distanceToMaxEnd + node.left.StartOffset - node.StartOffset);
583			}
584			if (node.right != null) {
585				CheckProperties(node.right);
586				totalLength += node.right.totalNodeLength;
587				distanceToMaxEnd = Math.Max(distanceToMaxEnd,
588				                            node.right.distanceToMaxEnd + node.right.StartOffset - node.StartOffset);
589			}
590			Debug.Assert(node.totalNodeLength == totalLength);
591			Debug.Assert(node.distanceToMaxEnd == distanceToMaxEnd);
592		}
593		
594		/*
595		1. A node is either red or black.
596		2. The root is black.
597		3. All leaves are black. (The leaves are the NIL children.)
598		4. Both children of every red node are black. (So every red node must have a black parent.)
599		5. Every simple path from a node to a descendant leaf contains the same number of black nodes. (Not counting the leaf node.)
600		 */
601		void CheckNodeProperties(TextSegment node, TextSegment parentNode, bool parentColor, int blackCount, ref int expectedBlackCount)
602		{
603			if (node == null) return;
604			
605			Debug.Assert(node.parent == parentNode);
606			
607			if (parentColor == RED) {
608				Debug.Assert(node.color == BLACK);
609			}
610			if (node.color == BLACK) {
611				blackCount++;
612			}
613			if (node.left == null && node.right == null) {
614				// node is a leaf node:
615				if (expectedBlackCount == -1)
616					expectedBlackCount = blackCount;
617				else
618					Debug.Assert(expectedBlackCount == blackCount);
619			}
620			CheckNodeProperties(node.left, node, node.color, blackCount, ref expectedBlackCount);
621			CheckNodeProperties(node.right, node, node.color, blackCount, ref expectedBlackCount);
622		}
623		
624		static void AppendTreeToString(TextSegment node, StringBuilder b, int indent)
625		{
626			if (node.color == RED)
627				b.Append("RED   ");
628			else
629				b.Append("BLACK ");
630			b.AppendLine(node.ToString() + node.ToDebugString());
631			indent += 2;
632			if (node.left != null) {
633				b.Append(' ', indent);
634				b.Append("L: ");
635				AppendTreeToString(node.left, b, indent);
636			}
637			if (node.right != null) {
638				b.Append(' ', indent);
639				b.Append("R: ");
640				AppendTreeToString(node.right, b, indent);
641			}
642		}
643		#endif
644		
645		internal string GetTreeAsString()
646		{
647			#if DEBUG
648			StringBuilder b = new StringBuilder();
649			if (root != null)
650				AppendTreeToString(root, b, 0);
651			return b.ToString();
652			#else
653			return "Not available in release build.";
654			#endif
655		}
656		#endregion
657		
658		#region Red/Black Tree
659		internal const bool RED = true;
660		internal const bool BLACK = false;
661		
662		void InsertAsLeft(TextSegment parentNode, TextSegment newNode)
663		{
664			Debug.Assert(parentNode.left == null);
665			parentNode.left = newNode;
666			newNode.parent = parentNode;
667			newNode.color = RED;
668			UpdateAugmentedData(parentNode);
669			FixTreeOnInsert(newNode);
670		}
671		
672		void InsertAsRight(TextSegment parentNode, TextSegment newNode)
673		{
674			Debug.Assert(parentNode.right == null);
675			parentNode.right = newNode;
676			newNode.parent = parentNode;
677			newNode.color = RED;
678			UpdateAugmentedData(parentNode);
679			FixTreeOnInsert(newNode);
680		}
681		
682		void FixTreeOnInsert(TextSegment node)
683		{
684			Debug.Assert(node != null);
685			Debug.Assert(node.color == RED);
686			Debug.Assert(node.left == null || node.left.color == BLACK);
687			Debug.Assert(node.right == null || node.right.color == BLACK);
688			
689			TextSegment parentNode = node.parent;
690			if (parentNode == null) {
691				// we inserted in the root -> the node must be black
692				// since this is a root node, making the node black increments the number of black nodes
693				// on all paths by one, so it is still the same for all paths.
694				node.color = BLACK;
695				return;
696			}
697			if (parentNode.color == BLACK) {
698				// if the parent node where we inserted was black, our red node is placed correctly.
699				// since we inserted a red node, the number of black nodes on each path is unchanged
700				// -> the tree is still balanced
701				return;
702			}
703			// parentNode is red, so there is a conflict here!
704			
705			// because the root is black, parentNode is not the root -> there is a grandparent node
706			TextSegment grandparentNode = parentNode.parent;
707			TextSegment uncleNode = Sibling(parentNode);
708			if (uncleNode != null && uncleNode.color == RED) {
709				parentNode.color = BLACK;
710				uncleNode.color = BLACK;
711				grandparentNode.color = RED;
712				FixTreeOnInsert(grandparentNode);
713				return;
714			}
715			// now we know: parent is red but uncle is black
716			// First rotation:
717			if (node == parentNode.right && parentNode == grandparentNode.left) {
718				RotateLeft(parentNode);
719				node = node.left;
720			} else if (node == parentNode.left && parentNode == grandparentNode.right) {
721				RotateRight(parentNode);
722				node = node.right;
723			}
724			// because node might have changed, reassign variables:
725			parentNode = node.parent;
726			grandparentNode = parentNode.parent;
727			
728			// Now recolor a bit:
729			parentNode.color = BLACK;
730			grandparentNode.color = RED;
731			// Second rotation:
732			if (node == parentNode.left && parentNode == grandparentNode.left) {
733				RotateRight(grandparentNode);
734			} else {
735				// because of the first rotation, this is guaranteed:
736				Debug.Assert(node == parentNode.right && parentNode == grandparentNode.right);
737				RotateLeft(grandparentNode);
738			}
739		}
740		
741		void RemoveNode(TextSegment removedNode)
742		{
743			if (removedNode.left != null && removedNode.right != null) {
744				// replace removedNode with it's in-order successor
745				
746				TextSegment leftMost = removedNode.right.LeftMost;
747				RemoveNode(leftMost); // remove leftMost from its current location
748				
749				// and overwrite the removedNode with it
750				ReplaceNode(removedNode, leftMost);
751				leftMost.left = removedNode.left;
752				if (leftMost.left != null) leftMost.left.parent = leftMost;
753				leftMost.right = removedNode.right;
754				if (leftMost.right != null) leftMost.right.parent = leftMost;
755				leftMost.color = removedNode.color;
756				
757				UpdateAugmentedData(leftMost);
758				if (leftMost.parent != null) UpdateAugmentedData(leftMost.parent);
759				return;
760			}
761			
762			// now either removedNode.left or removedNode.right is null
763			// get the remaining child
764			TextSegment parentNode = removedNode.parent;
765			TextSegment childNode = removedNode.left ?? removedNode.right;
766			ReplaceNode(removedNode, childNode);
767			if (parentNode != null) UpdateAugmentedData(parentNode);
768			if (removedNode.color == BLACK) {
769				if (childNode != null && childNode.color == RED) {
770					childNode.color = BLACK;
771				} else {
772					FixTreeOnDelete(childNode, parentNode);
773				}
774			}
775		}
776		
777		void FixTreeOnDelete(TextSegment node, TextSegment parentNode)
778		{
779			Debug.Assert(node == null || node.parent == parentNode);
780			if (parentNode == null)
781				return;
782			
783			// warning: node may be null
784			TextSegment sibling = Sibling(node, parentNode);
785			if (sibling.color == RED) {
786				parentNode.color = RED;
787				sibling.color = BLACK;
788				if (node == parentNode.left) {
789					RotateLeft(parentNode);
790				} else {
791					RotateRight(parentNode);
792				}
793				
794				sibling = Sibling(node, parentNode); // update value of sibling after rotation
795			}
796			
797			if (parentNode.color == BLACK
798			    && sibling.color == BLACK
799			    && GetColor(sibling.left) == BLACK
800			    && GetColor(sibling.right) == BLACK)
801			{
802				sibling.color = RED;
803				FixTreeOnDelete(parentNode, parentNode.parent);
804				return;
805			}
806			
807			if (parentNode.color == RED
808			    && sibling.color == BLACK
809			    && GetColor(sibling.left) == BLACK
810			    && GetColor(sibling.right) == BLACK)
811			{
812				sibling.color = RED;
813				parentNode.color = BLACK;
814				return;
815			}
816			
817			if (node == parentNode.left &&
818			    sibling.color == BLACK &&
819			    GetColor(sibling.left) == RED &&
820			    GetColor(sibling.right) == BLACK)
821			{
822				sibling.color = RED;
823				sibling.left.color = BLACK;
824				RotateRight(sibling);
825			}
826			else if (node == parentNode.right &&
827			         sibling.color == BLACK &&
828			         GetColor(sibling.right) == RED &&
829			         GetColor(sibling.left) == BLACK)
830			{
831				sibling.color = RED;
832				sibling.right.color = BLACK;
833				RotateLeft(sibling);
834			}
835			sibling = Sibling(node, parentNode); // update value of sibling after rotation
836			
837			sibling.color = parentNode.color;
838			parentNode.color = BLACK;
839			if (node == parentNode.left) {
840				if (sibling.right != null) {
841					Debug.Assert(sibling.right.color == RED);
842					sibling.right.color = BLACK;
843				}
844				RotateLeft(parentNode);
845			} else {
846				if (sibling.left != null) {
847					Debug.Assert(sibling.left.color == RED);
848					sibling.left.color = BLACK;
849				}
850				RotateRight(parentNode);
851			}
852		}
853		
854		void ReplaceNode(TextSegment replacedNode, TextSegment newNode)
855		{
856			if (replacedNode.parent == null) {
857				Debug.Assert(replacedNode == root);
858				root = newNode;
859			} else {
860				if (replacedNode.parent.left == replacedNode)
861					replacedNode.parent.left = newNode;
862				else
863					replacedNode.parent.right = newNode;
864			}
865			if (newNode != null) {
866				newNode.parent = replacedNode.parent;
867			}
868			replacedNode.parent = null;
869		}
870		
871		void RotateLeft(TextSegment p)
872		{
873			// let q be p's right child
874			TextSegment q = p.right;
875			Debug.Assert(q != null);
876			Debug.Assert(q.parent == p);
877			// set q to be the new root
878			ReplaceNode(p, q);
879			
880			// set p's right child to be q's left child
881			p.right = q.left;
882			if (p.right != null) p.right.parent = p;
883			// set q's left child to be p
884			q.left = p;
885			p.parent = q;
886			UpdateAugmentedData(p);
887			UpdateAugmentedData(q);
888		}
889		
890		void RotateRight(TextSegment p)
891		{
892			// let q be p's left child
893			TextSegment q = p.left;
894			Debug.Assert(q != null);
895			Debug.Assert(q.parent == p);
896			// set q to be the new root
897			ReplaceNode(p, q);
898			
899			// set p's left child to be q's right child
900			p.left = q.right;
901			if (p.left != null) p.left.parent = p;
902			// set q's right child to be p
903			q.right = p;
904			p.parent = q;
905			UpdateAugmentedData(p);
906			UpdateAugmentedData(q);
907		}
908		
909		static TextSegment Sibling(TextSegment node)
910		{
911			if (node == node.parent.left)
912				return node.parent.right;
913			else
914				return node.parent.left;
915		}
916		
917		static TextSegment Sibling(TextSegment node, TextSegment parentNode)
918		{
919			Debug.Assert(node == null || node.parent == parentNode);
920			if (node == parentNode.left)
921				return parentNode.right;
922			else
923				return parentNode.left;
924		}
925		
926		static bool GetColor(TextSegment node)
927		{
928			return node != null ? node.color : BLACK;
929		}
930		#endregion
931		
932		#region ICollection<T> implementation
933		/// <summary>
934		/// Gets the number of segments in the tree.
935		/// </summary>
936		public int Count {
937			get { return count; }
938		}
939		
940		bool ICollection<T>.IsReadOnly {
941			get { return false; }
942		}
943		
944		/// <summary>
945		/// Gets whether this tree contains the specified item.
946		/// </summary>
947		public bool Contains(T item)
948		{
949			return item != null && item.ownerTree == this;
950		}
951		
952		/// <summary>
953		/// Copies all segments in this SegmentTree to the specified array.
954		/// </summary>
955		public void CopyTo(T[] array, int arrayIndex)
956		{
957			if (array == null)
958				throw new ArgumentNullException("array");
959			if (array.Length < this.Count)
960				throw new ArgumentException("The array is too small", "array");
961			if (arrayIndex < 0 || arrayIndex + count > array.Length)
962				throw new ArgumentOutOfRangeException("arrayIndex", arrayIndex, "Value must be between 0 and " + (array.Length - count));
963			foreach (T s in this) {
964				array[arrayIndex++] = s;
965			}
966		}
967		
968		/// <summary>
969		/// Gets an enumerator to enumerate the segments.
970		/// </summary>
971		public IEnumerator<T> GetEnumerator()
972		{
973			if (root != null) {
974				TextSegment current = root.LeftMost;
975				while (current != null) {
976					yield return (T)current;
977					// TODO: check if collection was modified during enumeration
978					current = current.Successor;
979				}
980			}
981		}
982		
983		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
984		{
985			return this.GetEnumerator();
986		}
987		#endregion
988	}
989}