first commit

This commit is contained in:
2026-01-07 11:33:05 +08:00
commit fc54ffd43b
215 changed files with 31856 additions and 0 deletions

View File

@@ -0,0 +1,44 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Daniel Grunwald"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Collections.Generic;
namespace ICSharpCode.TextEditor.Document
{
/// <summary>
/// A list of events that are fired after the line manager has finished working.
/// </summary>
struct DeferredEventList
{
internal List<LineSegment> removedLines;
internal List<TextAnchor> textAnchor;
public void AddRemovedLine(LineSegment line)
{
if (removedLines == null)
removedLines = new List<LineSegment>();
removedLines.Add(line);
}
public void AddDeletedAnchor(TextAnchor anchor)
{
if (textAnchor == null)
textAnchor = new List<TextAnchor>();
textAnchor.Add(anchor);
}
public void RaiseEvents()
{
// removedLines is raised by the LineManager
if (textAnchor != null) {
foreach (TextAnchor a in textAnchor) {
a.RaiseDeleted();
}
}
}
}
}

View File

@@ -0,0 +1,369 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Collections.Generic;
using System.Diagnostics;
namespace ICSharpCode.TextEditor.Document
{
internal sealed class LineManager
{
LineSegmentTree lineCollection = new LineSegmentTree();
IDocument document;
IHighlightingStrategy highlightingStrategy;
public IList<LineSegment> LineSegmentCollection {
get {
return lineCollection;
}
}
public int TotalNumberOfLines {
get {
return lineCollection.Count;
}
}
public IHighlightingStrategy HighlightingStrategy {
get {
return highlightingStrategy;
}
set {
if (highlightingStrategy != value) {
highlightingStrategy = value;
if (highlightingStrategy != null) {
highlightingStrategy.MarkTokens(document);
}
}
}
}
public LineManager(IDocument document, IHighlightingStrategy highlightingStrategy)
{
this.document = document;
this.highlightingStrategy = highlightingStrategy;
}
public int GetLineNumberForOffset(int offset)
{
return GetLineSegmentForOffset(offset).LineNumber;
}
public LineSegment GetLineSegmentForOffset(int offset)
{
return lineCollection.GetByOffset(offset);
}
public LineSegment GetLineSegment(int lineNr)
{
return lineCollection[lineNr];
}
public void Insert(int offset, string text)
{
Replace(offset, 0, text);
}
public void Remove(int offset, int length)
{
Replace(offset, length, string.Empty);
}
public void Replace(int offset, int length, string text)
{
Debug.WriteLine("Replace offset="+offset+" length="+length+" text.Length="+text.Length);
int lineStart = GetLineNumberForOffset(offset);
int oldNumberOfLines = this.TotalNumberOfLines;
DeferredEventList deferredEventList = new DeferredEventList();
RemoveInternal(ref deferredEventList, offset, length);
int numberOfLinesAfterRemoving = this.TotalNumberOfLines;
if (!string.IsNullOrEmpty(text)) {
InsertInternal(offset, text);
}
// #if DEBUG
// Console.WriteLine("New line collection:");
// Console.WriteLine(lineCollection.GetTreeAsString());
// Console.WriteLine("New text:");
// Console.WriteLine("'" + document.TextContent + "'");
// #endif
// Only fire events after RemoveInternal+InsertInternal finished completely:
// Otherwise we would expose inconsistent state to the event handlers.
RunHighlighter(lineStart, 1 + Math.Max(0, this.TotalNumberOfLines - numberOfLinesAfterRemoving));
if (deferredEventList.removedLines != null) {
foreach (LineSegment ls in deferredEventList.removedLines)
OnLineDeleted(new LineEventArgs(document, ls));
}
deferredEventList.RaiseEvents();
if (this.TotalNumberOfLines != oldNumberOfLines) {
OnLineCountChanged(new LineCountChangeEventArgs(document, lineStart, this.TotalNumberOfLines - oldNumberOfLines));
}
}
void RemoveInternal(ref DeferredEventList deferredEventList, int offset, int length)
{
Debug.Assert(length >= 0);
if (length == 0) return;
LineSegmentTree.Enumerator it = lineCollection.GetEnumeratorForOffset(offset);
LineSegment startSegment = it.Current;
int startSegmentOffset = startSegment.Offset;
if (offset + length < startSegmentOffset + startSegment.TotalLength) {
// just removing a part of this line segment
startSegment.RemovedLinePart(ref deferredEventList, offset - startSegmentOffset, length);
SetSegmentLength(startSegment, startSegment.TotalLength - length);
return;
}
// merge startSegment with another line segment because startSegment's delimiter was deleted
// possibly remove lines in between if multiple delimiters were deleted
int charactersRemovedInStartLine = startSegmentOffset + startSegment.TotalLength - offset;
Debug.Assert(charactersRemovedInStartLine > 0);
startSegment.RemovedLinePart(ref deferredEventList, offset - startSegmentOffset, charactersRemovedInStartLine);
LineSegment endSegment = lineCollection.GetByOffset(offset + length);
if (endSegment == startSegment) {
// special case: we are removing a part of the last line up to the
// end of the document
SetSegmentLength(startSegment, startSegment.TotalLength - length);
return;
}
int endSegmentOffset = endSegment.Offset;
int charactersLeftInEndLine = endSegmentOffset + endSegment.TotalLength - (offset + length);
endSegment.RemovedLinePart(ref deferredEventList, 0, endSegment.TotalLength - charactersLeftInEndLine);
startSegment.MergedWith(endSegment, offset - startSegmentOffset);
SetSegmentLength(startSegment, startSegment.TotalLength - charactersRemovedInStartLine + charactersLeftInEndLine);
startSegment.DelimiterLength = endSegment.DelimiterLength;
// remove all segments between startSegment (excl.) and endSegment (incl.)
it.MoveNext();
LineSegment segmentToRemove;
do {
segmentToRemove = it.Current;
it.MoveNext();
lineCollection.RemoveSegment(segmentToRemove);
segmentToRemove.Deleted(ref deferredEventList);
} while (segmentToRemove != endSegment);
}
void InsertInternal(int offset, string text)
{
LineSegment segment = lineCollection.GetByOffset(offset);
DelimiterSegment ds = NextDelimiter(text, 0);
if (ds == null) {
// no newline is being inserted, all text is inserted in a single line
segment.InsertedLinePart(offset - segment.Offset, text.Length);
SetSegmentLength(segment, segment.TotalLength + text.Length);
return;
}
LineSegment firstLine = segment;
firstLine.InsertedLinePart(offset - firstLine.Offset, ds.Offset);
int lastDelimiterEnd = 0;
while (ds != null) {
// split line segment at line delimiter
int lineBreakOffset = offset + ds.Offset + ds.Length;
int segmentOffset = segment.Offset;
int lengthAfterInsertionPos = segmentOffset + segment.TotalLength - (offset + lastDelimiterEnd);
lineCollection.SetSegmentLength(segment, lineBreakOffset - segmentOffset);
LineSegment newSegment = lineCollection.InsertSegmentAfter(segment, lengthAfterInsertionPos);
segment.DelimiterLength = ds.Length;
segment = newSegment;
lastDelimiterEnd = ds.Offset + ds.Length;
ds = NextDelimiter(text, lastDelimiterEnd);
}
firstLine.SplitTo(segment);
// insert rest after last delimiter
if (lastDelimiterEnd != text.Length) {
segment.InsertedLinePart(0, text.Length - lastDelimiterEnd);
SetSegmentLength(segment, segment.TotalLength + text.Length - lastDelimiterEnd);
}
}
void SetSegmentLength(LineSegment segment, int newTotalLength)
{
int delta = newTotalLength - segment.TotalLength;
if (delta != 0) {
lineCollection.SetSegmentLength(segment, newTotalLength);
OnLineLengthChanged(new LineLengthChangeEventArgs(document, segment, delta));
}
}
void RunHighlighter(int firstLine, int lineCount)
{
if (highlightingStrategy != null) {
List<LineSegment> markLines = new List<LineSegment>();
LineSegmentTree.Enumerator it = lineCollection.GetEnumeratorForIndex(firstLine);
for (int i = 0; i < lineCount && it.IsValid; i++) {
markLines.Add(it.Current);
it.MoveNext();
}
highlightingStrategy.MarkTokens(document, markLines);
}
}
public void SetContent(string text)
{
lineCollection.Clear();
if (text != null) {
Replace(0, 0, text);
}
}
public int GetVisibleLine(int logicalLineNumber)
{
if (!document.TextEditorProperties.EnableFolding) {
return logicalLineNumber;
}
int visibleLine = 0;
int foldEnd = 0;
List<FoldMarker> foldings = document.FoldingManager.GetTopLevelFoldedFoldings();
foreach (FoldMarker fm in foldings) {
if (fm.StartLine >= logicalLineNumber) {
break;
}
if (fm.StartLine >= foldEnd) {
visibleLine += fm.StartLine - foldEnd;
if (fm.EndLine > logicalLineNumber) {
return visibleLine;
}
foldEnd = fm.EndLine;
}
}
// Debug.Assert(logicalLineNumber >= foldEnd);
visibleLine += logicalLineNumber - foldEnd;
return visibleLine;
}
public int GetFirstLogicalLine(int visibleLineNumber)
{
if (!document.TextEditorProperties.EnableFolding) {
return visibleLineNumber;
}
int v = 0;
int foldEnd = 0;
List<FoldMarker> foldings = document.FoldingManager.GetTopLevelFoldedFoldings();
foreach (FoldMarker fm in foldings) {
if (fm.StartLine >= foldEnd) {
if (v + fm.StartLine - foldEnd >= visibleLineNumber) {
break;
}
v += fm.StartLine - foldEnd;
foldEnd = fm.EndLine;
}
}
// help GC
foldings.Clear();
foldings = null;
return foldEnd + visibleLineNumber - v;
}
public int GetLastLogicalLine(int visibleLineNumber)
{
if (!document.TextEditorProperties.EnableFolding) {
return visibleLineNumber;
}
return GetFirstLogicalLine(visibleLineNumber + 1) - 1;
}
// TODO : speedup the next/prev visible line search
// HOW? : save the foldings in a sorted list and lookup the
// line numbers in this list
public int GetNextVisibleLineAbove(int lineNumber, int lineCount)
{
int curLineNumber = lineNumber;
if (document.TextEditorProperties.EnableFolding) {
for (int i = 0; i < lineCount && curLineNumber < TotalNumberOfLines; ++i) {
++curLineNumber;
while (curLineNumber < TotalNumberOfLines && (curLineNumber >= lineCollection.Count || !document.FoldingManager.IsLineVisible(curLineNumber))) {
++curLineNumber;
}
}
} else {
curLineNumber += lineCount;
}
return Math.Min(TotalNumberOfLines - 1, curLineNumber);
}
public int GetNextVisibleLineBelow(int lineNumber, int lineCount)
{
int curLineNumber = lineNumber;
if (document.TextEditorProperties.EnableFolding) {
for (int i = 0; i < lineCount; ++i) {
--curLineNumber;
while (curLineNumber >= 0 && !document.FoldingManager.IsLineVisible(curLineNumber)) {
--curLineNumber;
}
}
} else {
curLineNumber -= lineCount;
}
return Math.Max(0, curLineNumber);
}
// use always the same DelimiterSegment object for the NextDelimiter
DelimiterSegment delimiterSegment = new DelimiterSegment();
DelimiterSegment NextDelimiter(string text, int offset)
{
for (int i = offset; i < text.Length; i++) {
switch (text[i]) {
case '\r':
if (i + 1 < text.Length) {
if (text[i + 1] == '\n') {
delimiterSegment.Offset = i;
delimiterSegment.Length = 2;
return delimiterSegment;
}
}
#if DATACONSISTENCYTEST
Debug.Assert(false, "Found lone \\r, data consistency problems?");
#endif
goto case '\n';
case '\n':
delimiterSegment.Offset = i;
delimiterSegment.Length = 1;
return delimiterSegment;
}
}
return null;
}
void OnLineCountChanged(LineCountChangeEventArgs e)
{
if (LineCountChanged != null) {
LineCountChanged(this, e);
}
}
void OnLineLengthChanged(LineLengthChangeEventArgs e)
{
if (LineLengthChanged != null) {
LineLengthChanged(this, e);
}
}
void OnLineDeleted(LineEventArgs e)
{
if (LineDeleted != null) {
LineDeleted(this, e);
}
}
public event EventHandler<LineLengthChangeEventArgs> LineLengthChanged;
public event EventHandler<LineCountChangeEventArgs> LineCountChanged;
public event EventHandler<LineEventArgs> LineDeleted;
sealed class DelimiterSegment
{
internal int Offset;
internal int Length;
}
}
}

View File

@@ -0,0 +1,97 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
namespace ICSharpCode.TextEditor.Document
{
public class LineCountChangeEventArgs : EventArgs
{
IDocument document;
int start;
int moved;
/// <returns>
/// always a valid Document which is related to the Event.
/// </returns>
public IDocument Document {
get {
return document;
}
}
/// <returns>
/// -1 if no offset was specified for this event
/// </returns>
public int LineStart {
get {
return start;
}
}
/// <returns>
/// -1 if no length was specified for this event
/// </returns>
public int LinesMoved {
get {
return moved;
}
}
public LineCountChangeEventArgs(IDocument document, int lineStart, int linesMoved)
{
this.document = document;
this.start = lineStart;
this.moved = linesMoved;
}
}
public class LineEventArgs : EventArgs
{
IDocument document;
LineSegment lineSegment;
public IDocument Document {
get { return document; }
}
public LineSegment LineSegment {
get { return lineSegment; }
}
public LineEventArgs(IDocument document, LineSegment lineSegment)
{
this.document = document;
this.lineSegment = lineSegment;
}
public override string ToString()
{
return string.Format("[LineEventArgs Document={0} LineSegment={1}]", this.document, this.lineSegment);
}
}
public class LineLengthChangeEventArgs : LineEventArgs
{
int lengthDelta;
public int LengthDelta {
get { return lengthDelta; }
}
public LineLengthChangeEventArgs(IDocument document, LineSegment lineSegment, int moved)
: base(document, lineSegment)
{
this.lengthDelta = moved;
}
public override string ToString()
{
return string.Format("[LineLengthEventArgs Document={0} LineSegment={1} LengthDelta={2}]", this.Document, this.LineSegment, this.lengthDelta);
}
}
}

View File

@@ -0,0 +1,259 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Diagnostics;
using System.Text;
namespace ICSharpCode.TextEditor.Document
{
public sealed class LineSegment : ISegment
{
internal LineSegmentTree.Enumerator treeEntry;
int totalLength, delimiterLength;
List<TextWord> words;
SpanStack highlightSpanStack;
public TextWord GetWord(int column)
{
int curColumn = 0;
foreach (TextWord word in words) {
if (column < curColumn + word.Length) {
return word;
}
curColumn += word.Length;
}
return null;
}
public bool IsDeleted {
get { return !treeEntry.IsValid; }
}
public int LineNumber {
get { return treeEntry.CurrentIndex; }
}
public int Offset {
get { return treeEntry.CurrentOffset; }
}
public int Length {
get { return totalLength - delimiterLength; }
}
int ISegment.Offset {
get { return this.Offset; }
set { throw new NotSupportedException(); }
}
int ISegment.Length {
get { return this.Length; }
set { throw new NotSupportedException(); }
}
public int TotalLength {
get { return totalLength; }
internal set { totalLength = value; }
}
public int DelimiterLength {
get { return delimiterLength; }
internal set { delimiterLength = value; }
}
// highlighting information
public List<TextWord> Words {
get {
return words;
}
set {
words = value;
}
}
public HighlightColor GetColorForPosition(int x)
{
if (Words != null) {
int xPos = 0;
foreach (TextWord word in Words) {
if (x < xPos + word.Length) {
return word.SyntaxColor;
}
xPos += word.Length;
}
}
return new HighlightColor(Color.Black, false, false);
}
public SpanStack HighlightSpanStack {
get {
return highlightSpanStack;
}
set {
highlightSpanStack = value;
}
}
/// <summary>
/// Converts a <see cref="LineSegment"/> instance to string (for debug purposes)
/// </summary>
public override string ToString()
{
if (IsDeleted)
return "[LineSegment: (deleted) Length = " + Length + ", TotalLength = " + TotalLength + ", DelimiterLength = " + delimiterLength + "]";
else
return "[LineSegment: LineNumber=" + LineNumber + ", Offset = "+ Offset +", Length = " + Length + ", TotalLength = " + TotalLength + ", DelimiterLength = " + delimiterLength + "]";
}
#region Anchor management
Util.WeakCollection<TextAnchor> anchors;
public TextAnchor CreateAnchor(int column)
{
if (column < 0 || column > Length)
throw new ArgumentOutOfRangeException("column");
TextAnchor anchor = new TextAnchor(this, column);
AddAnchor(anchor);
return anchor;
}
void AddAnchor(TextAnchor anchor)
{
Debug.Assert(anchor.Line == this);
if (anchors == null)
anchors = new Util.WeakCollection<TextAnchor>();
anchors.Add(anchor);
}
/// <summary>
/// Is called when the LineSegment is deleted.
/// </summary>
internal void Deleted(ref DeferredEventList deferredEventList)
{
//Console.WriteLine("Deleted");
treeEntry = LineSegmentTree.Enumerator.Invalid;
if (anchors != null) {
foreach (TextAnchor a in anchors) {
a.Delete(ref deferredEventList);
}
anchors = null;
}
}
/// <summary>
/// Is called when a part of the line is removed.
/// </summary>
internal void RemovedLinePart(ref DeferredEventList deferredEventList, int startColumn, int length)
{
if (length == 0)
return;
Debug.Assert(length > 0);
//Console.WriteLine("RemovedLinePart " + startColumn + ", " + length);
if (anchors != null) {
List<TextAnchor> deletedAnchors = null;
foreach (TextAnchor a in anchors) {
if (a.ColumnNumber > startColumn) {
if (a.ColumnNumber >= startColumn + length) {
a.ColumnNumber -= length;
} else {
if (deletedAnchors == null)
deletedAnchors = new List<TextAnchor>();
a.Delete(ref deferredEventList);
deletedAnchors.Add(a);
}
}
}
if (deletedAnchors != null) {
foreach (TextAnchor a in deletedAnchors) {
anchors.Remove(a);
}
}
}
}
/// <summary>
/// Is called when a part of the line is inserted.
/// </summary>
internal void InsertedLinePart(int startColumn, int length)
{
if (length == 0)
return;
Debug.Assert(length > 0);
//Console.WriteLine("InsertedLinePart " + startColumn + ", " + length);
if (anchors != null) {
foreach (TextAnchor a in anchors) {
if (a.MovementType == AnchorMovementType.BeforeInsertion
? a.ColumnNumber > startColumn
: a.ColumnNumber >= startColumn)
{
a.ColumnNumber += length;
}
}
}
}
/// <summary>
/// Is called after another line's content is appended to this line because the newline in between
/// was deleted.
/// The DefaultLineManager will call Deleted() on the deletedLine after the MergedWith call.
///
/// firstLineLength: the length of the line before the merge.
/// </summary>
internal void MergedWith(LineSegment deletedLine, int firstLineLength)
{
//Console.WriteLine("MergedWith");
if (deletedLine.anchors != null) {
foreach (TextAnchor a in deletedLine.anchors) {
a.Line = this;
AddAnchor(a);
a.ColumnNumber += firstLineLength;
}
deletedLine.anchors = null;
}
}
/// <summary>
/// Is called after a newline was inserted into this line, splitting it into this and followingLine.
/// </summary>
internal void SplitTo(LineSegment followingLine)
{
//Console.WriteLine("SplitTo");
if (anchors != null) {
List<TextAnchor> movedAnchors = null;
foreach (TextAnchor a in anchors) {
if (a.MovementType == AnchorMovementType.BeforeInsertion
? a.ColumnNumber > this.Length
: a.ColumnNumber >= this.Length)
{
a.Line = followingLine;
followingLine.AddAnchor(a);
a.ColumnNumber -= this.Length;
if (movedAnchors == null)
movedAnchors = new List<TextAnchor>();
movedAnchors.Add(a);
}
}
if (movedAnchors != null) {
foreach (TextAnchor a in movedAnchors) {
anchors.Remove(a);
}
}
}
}
#endregion
}
}

View File

@@ -0,0 +1,477 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Daniel Grunwald" email="daniel@danielgrunwald.de"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Collections.Generic;
using System.Diagnostics;
using ICSharpCode.TextEditor.Util;
namespace ICSharpCode.TextEditor.Document
{
/// <summary>
/// Data structure for efficient management of the line segments (most operations are O(lg n)).
/// This implements an augmented red-black tree where each node has fields for the number of
/// nodes in its subtree (like an order statistics tree) for access by index(=line number).
/// Additionally, each node knows the total length of all segments in its subtree.
/// This means we can find nodes by offset in O(lg n) time. Since the offset itself is not stored in
/// the line segment but computed from the lengths stored in the tree, we adjusting the offsets when
/// text is inserted in one line means we just have to increment the totalLength of the affected line and
/// its parent nodes - an O(lg n) operation.
/// However this means getting the line number or offset from a LineSegment is not a constant time
/// operation, but takes O(lg n).
///
/// NOTE: The tree is never empty, Clear() causes it to contain an empty segment.
/// </summary>
sealed class LineSegmentTree : IList<LineSegment>
{
internal struct RBNode
{
internal LineSegment lineSegment;
internal int count;
internal int totalLength;
public RBNode(LineSegment lineSegment)
{
this.lineSegment = lineSegment;
this.count = 1;
this.totalLength = lineSegment.TotalLength;
}
public override string ToString()
{
return "[RBNode count=" + count + " totalLength="+totalLength
+ " lineSegment.LineNumber=" + lineSegment.LineNumber
+ " lineSegment.Offset=" + lineSegment.Offset
+ " lineSegment.TotalLength=" + lineSegment.TotalLength
+ " lineSegment.DelimiterLength=" + lineSegment.DelimiterLength + "]";
}
}
struct MyHost : IRedBlackTreeHost<RBNode>
{
public int Compare(RBNode x, RBNode y)
{
throw new NotImplementedException();
}
public bool Equals(RBNode a, RBNode b)
{
throw new NotImplementedException();
}
public void UpdateAfterChildrenChange(RedBlackTreeNode<RBNode> node)
{
int count = 1;
int totalLength = node.val.lineSegment.TotalLength;
if (node.left != null) {
count += node.left.val.count;
totalLength += node.left.val.totalLength;
}
if (node.right != null) {
count += node.right.val.count;
totalLength += node.right.val.totalLength;
}
if (count != node.val.count || totalLength != node.val.totalLength) {
node.val.count = count;
node.val.totalLength = totalLength;
if (node.parent != null) UpdateAfterChildrenChange(node.parent);
}
}
public void UpdateAfterRotateLeft(RedBlackTreeNode<RBNode> node)
{
UpdateAfterChildrenChange(node);
UpdateAfterChildrenChange(node.parent);
}
public void UpdateAfterRotateRight(RedBlackTreeNode<RBNode> node)
{
UpdateAfterChildrenChange(node);
UpdateAfterChildrenChange(node.parent);
}
}
readonly AugmentableRedBlackTree<RBNode, MyHost> tree = new AugmentableRedBlackTree<RBNode, MyHost>(new MyHost());
RedBlackTreeNode<RBNode> GetNode(int index)
{
if (index < 0 || index >= tree.Count)
throw new ArgumentOutOfRangeException("index", index, "index should be between 0 and " + (tree.Count-1));
RedBlackTreeNode<RBNode> node = tree.root;
while (true) {
if (node.left != null && index < node.left.val.count) {
node = node.left;
} else {
if (node.left != null) {
index -= node.left.val.count;
}
if (index == 0)
return node;
index--;
node = node.right;
}
}
}
static int GetIndexFromNode(RedBlackTreeNode<RBNode> node)
{
int index = (node.left != null) ? node.left.val.count : 0;
while (node.parent != null) {
if (node == node.parent.right) {
if (node.parent.left != null)
index += node.parent.left.val.count;
index++;
}
node = node.parent;
}
return index;
}
RedBlackTreeNode<RBNode> GetNodeByOffset(int offset)
{
if (offset < 0 || offset > this.TotalLength)
throw new ArgumentOutOfRangeException("offset", offset, "offset should be between 0 and " + this.TotalLength);
if (offset == this.TotalLength) {
if (tree.root == null)
throw new InvalidOperationException("Cannot call GetNodeByOffset while tree is empty.");
return tree.root.RightMost;
}
RedBlackTreeNode<RBNode> node = tree.root;
while (true) {
if (node.left != null && offset < node.left.val.totalLength) {
node = node.left;
} else {
if (node.left != null) {
offset -= node.left.val.totalLength;
}
offset -= node.val.lineSegment.TotalLength;
if (offset < 0)
return node;
node = node.right;
}
}
}
static int GetOffsetFromNode(RedBlackTreeNode<RBNode> node)
{
int offset = (node.left != null) ? node.left.val.totalLength : 0;
while (node.parent != null) {
if (node == node.parent.right) {
if (node.parent.left != null)
offset += node.parent.left.val.totalLength;
offset += node.parent.val.lineSegment.TotalLength;
}
node = node.parent;
}
return offset;
}
public LineSegment GetByOffset(int offset)
{
return GetNodeByOffset(offset).val.lineSegment;
}
/// <summary>
/// Gets the total length of all line segments. Runs in O(1).
/// </summary>
public int TotalLength {
get {
if (tree.root == null)
return 0;
else
return tree.root.val.totalLength;
}
}
/// <summary>
/// Updates the length of a line segment. Runs in O(lg n).
/// </summary>
public void SetSegmentLength(LineSegment segment, int newTotalLength)
{
if (segment == null)
throw new ArgumentNullException("segment");
RedBlackTreeNode<RBNode> node = segment.treeEntry.it.node;
segment.TotalLength = newTotalLength;
default(MyHost).UpdateAfterChildrenChange(node);
#if DEBUG
CheckProperties();
#endif
}
public void RemoveSegment(LineSegment segment)
{
tree.RemoveAt(segment.treeEntry.it);
#if DEBUG
CheckProperties();
#endif
}
public LineSegment InsertSegmentAfter(LineSegment segment, int length)
{
LineSegment newSegment = new LineSegment();
newSegment.TotalLength = length;
newSegment.DelimiterLength = segment.DelimiterLength;
newSegment.treeEntry = InsertAfter(segment.treeEntry.it.node, newSegment);
return newSegment;
}
Enumerator InsertAfter(RedBlackTreeNode<RBNode> node, LineSegment newSegment)
{
RedBlackTreeNode<RBNode> newNode = new RedBlackTreeNode<RBNode>(new RBNode(newSegment));
if (node.right == null) {
tree.InsertAsRight(node, newNode);
} else {
tree.InsertAsLeft(node.right.LeftMost, newNode);
}
#if DEBUG
CheckProperties();
#endif
return new Enumerator(new RedBlackTreeIterator<RBNode>(newNode));
}
/// <summary>
/// Gets the number of items in the collections. Runs in O(1).
/// </summary>
public int Count {
get { return tree.Count; }
}
/// <summary>
/// Gets or sets an item by index. Runs in O(lg n).
/// </summary>
public LineSegment this[int index] {
get {
return GetNode(index).val.lineSegment;
}
set {
throw new NotSupportedException();
}
}
bool ICollection<LineSegment>.IsReadOnly {
get { return true; }
}
/// <summary>
/// Gets the index of an item. Runs in O(lg n).
/// </summary>
public int IndexOf(LineSegment item)
{
int index = item.LineNumber;
if (index < 0 || index >= this.Count)
return -1;
if (item != this[index])
return -1;
return index;
}
void IList<LineSegment>.RemoveAt(int index)
{
throw new NotSupportedException();
}
#if DEBUG
[Conditional("DATACONSISTENCYTEST")]
void CheckProperties()
{
if (tree.root == null) {
Debug.Assert(this.Count == 0);
} else {
Debug.Assert(tree.root.val.count == this.Count);
CheckProperties(tree.root);
}
}
void CheckProperties(RedBlackTreeNode<RBNode> node)
{
int count = 1;
int totalLength = node.val.lineSegment.TotalLength;
if (node.left != null) {
CheckProperties(node.left);
count += node.left.val.count;
totalLength += node.left.val.totalLength;
}
if (node.right != null) {
CheckProperties(node.right);
count += node.right.val.count;
totalLength += node.right.val.totalLength;
}
Debug.Assert(node.val.count == count);
Debug.Assert(node.val.totalLength == totalLength);
}
public string GetTreeAsString()
{
return tree.GetTreeAsString();
}
#endif
public LineSegmentTree()
{
Clear();
}
/// <summary>
/// Clears the list. Runs in O(1).
/// </summary>
public void Clear()
{
tree.Clear();
LineSegment emptySegment = new LineSegment();
emptySegment.TotalLength = 0;
emptySegment.DelimiterLength = 0;
tree.Add(new RBNode(emptySegment));
emptySegment.treeEntry = GetEnumeratorForIndex(0);
#if DEBUG
CheckProperties();
#endif
}
/// <summary>
/// Tests whether an item is in the list. Runs in O(n).
/// </summary>
public bool Contains(LineSegment item)
{
return IndexOf(item) >= 0;
}
/// <summary>
/// Copies all elements from the list to the array.
/// </summary>
public void CopyTo(LineSegment[] array, int arrayIndex)
{
if (array == null) throw new ArgumentNullException("array");
foreach (LineSegment val in this)
array[arrayIndex++] = val;
}
IEnumerator<LineSegment> IEnumerable<LineSegment>.GetEnumerator()
{
return this.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
public Enumerator GetEnumerator()
{
return new Enumerator(tree.GetEnumerator());
}
public Enumerator GetEnumeratorForIndex(int index)
{
return new Enumerator(new RedBlackTreeIterator<RBNode>(GetNode(index)));
}
public Enumerator GetEnumeratorForOffset(int offset)
{
return new Enumerator(new RedBlackTreeIterator<RBNode>(GetNodeByOffset(offset)));
}
public struct Enumerator : IEnumerator<LineSegment>
{
/// <summary>
/// An invalid enumerator value. Calling MoveNext on the invalid enumerator
/// will always return false, accessing Current will throw an exception.
/// </summary>
public static readonly Enumerator Invalid = default(Enumerator);
internal RedBlackTreeIterator<RBNode> it;
internal Enumerator(RedBlackTreeIterator<RBNode> it)
{
this.it = it;
}
/// <summary>
/// Gets the current value. Runs in O(1).
/// </summary>
public LineSegment Current {
get {
return it.Current.lineSegment;
}
}
public bool IsValid {
get {
return it.IsValid;
}
}
/// <summary>
/// Gets the index of the current value. Runs in O(lg n).
/// </summary>
public int CurrentIndex {
get {
if (it.node == null)
throw new InvalidOperationException();
return GetIndexFromNode(it.node);
}
}
/// <summary>
/// Gets the offset of the current value. Runs in O(lg n).
/// </summary>
public int CurrentOffset {
get {
if (it.node == null)
throw new InvalidOperationException();
return GetOffsetFromNode(it.node);
}
}
object System.Collections.IEnumerator.Current {
get {
return it.Current.lineSegment;
}
}
public void Dispose()
{
}
/// <summary>
/// Moves to the next index. Runs in O(lg n), but for k calls, the combined time is only O(k+lg n).
/// </summary>
public bool MoveNext()
{
return it.MoveNext();
}
/// <summary>
/// Moves to the previous index. Runs in O(lg n), but for k calls, the combined time is only O(k+lg n).
/// </summary>
public bool MoveBack()
{
return it.MoveBack();
}
void System.Collections.IEnumerator.Reset()
{
throw new NotSupportedException();
}
}
void IList<LineSegment>.Insert(int index, LineSegment item)
{
throw new NotSupportedException();
}
void ICollection<LineSegment>.Add(LineSegment item)
{
throw new NotSupportedException();
}
bool ICollection<LineSegment>.Remove(LineSegment item)
{
throw new NotSupportedException();
}
}
}