Code Coverage Statistics for Source File
c:\Tools\SD3\src\Libraries\ICSharpCode.TextEditor\Project\Src\Document\LineManager\LineSegmentTree.cs
|
Sequence Point Coverage
N/A
0 of 0
|
Branch Coverage
N/A
0 of 0
|
Lines
477
|
Highlight:
Uncovered Code
Covered Code
| L | V | Source |
|---|---|---|
1 |
// <file> |
|
2 |
// <copyright see="prj:///doc/copyright.txt"/> |
|
3 |
// <license see="prj:///doc/license.txt"/> |
|
4 |
// <owner name="Daniel Grunwald" email="daniel@danielgrunwald.de"/> |
|
5 |
// <version>$Revision: 2683 $</version> |
|
6 |
// </file> |
|
7 |
||
8 |
using System; |
|
9 |
using System.Collections.Generic; |
|
10 |
using System.Diagnostics; |
|
11 |
using ICSharpCode.TextEditor.Util; |
|
12 |
||
13 |
namespace ICSharpCode.TextEditor.Document |
|
14 |
{ |
|
15 |
/// <summary> |
|
16 |
/// Data structure for efficient management of the line segments (most operations are O(lg n)). |
|
17 |
/// This implements an augmented red-black tree where each node has fields for the number of |
|
18 |
/// nodes in its subtree (like an order statistics tree) for access by index(=line number). |
|
19 |
/// Additionally, each node knows the total length of all segments in its subtree. |
|
20 |
/// This means we can find nodes by offset in O(lg n) time. Since the offset itself is not stored in |
|
21 |
/// the line segment but computed from the lengths stored in the tree, we adjusting the offsets when |
|
22 |
/// text is inserted in one line means we just have to increment the totalLength of the affected line and |
|
23 |
/// its parent nodes - an O(lg n) operation. |
|
24 |
/// However this means getting the line number or offset from a LineSegment is not a constant time |
|
25 |
/// operation, but takes O(lg n). |
|
26 |
/// |
|
27 |
/// NOTE: The tree is never empty, Clear() causes it to contain an empty segment. |
|
28 |
/// </summary> |
|
29 |
sealed class LineSegmentTree : IList<LineSegment> |
|
30 |
{ |
|
31 |
internal struct RBNode |
|
32 |
{ |
|
33 |
internal LineSegment lineSegment; |
|
34 |
internal int count; |
|
35 |
internal int totalLength; |
|
36 |
||
37 |
public RBNode(LineSegment lineSegment) |
|
38 |
{ |
|
39 |
this.lineSegment = lineSegment; |
|
40 |
this.count = 1; |
|
41 |
this.totalLength = lineSegment.TotalLength; |
|
42 |
} |
|
43 |
||
44 |
public override string ToString() |
|
45 |
{ |
|
46 |
return "[RBNode count=" + count + " totalLength="+totalLength |
|
47 |
+ " lineSegment.LineNumber=" + lineSegment.LineNumber |
|
48 |
+ " lineSegment.Offset=" + lineSegment.Offset |
|
49 |
+ " lineSegment.TotalLength=" + lineSegment.TotalLength |
|
50 |
+ " lineSegment.DelimiterLength=" + lineSegment.DelimiterLength + "]"; |
|
51 |
} |
|
52 |
} |
|
53 |
||
54 |
struct MyHost : IRedBlackTreeHost<RBNode> |
|
55 |
{ |
|
56 |
public int Compare(RBNode x, RBNode y) |
|
57 |
{ |
|
58 |
throw new NotImplementedException(); |
|
59 |
} |
|
60 |
||
61 |
public bool Equals(RBNode a, RBNode b) |
|
62 |
{ |
|
63 |
throw new NotImplementedException(); |
|
64 |
} |
|
65 |
||
66 |
public void UpdateAfterChildrenChange(RedBlackTreeNode<RBNode> node) |
|
67 |
{ |
|
68 |
int count = 1; |
|
69 |
int totalLength = node.val.lineSegment.TotalLength; |
|
70 |
if (node.left != null) { |
|
71 |
count += node.left.val.count; |
|
72 |
totalLength += node.left.val.totalLength; |
|
73 |
} |
|
74 |
if (node.right != null) { |
|
75 |
count += node.right.val.count; |
|
76 |
totalLength += node.right.val.totalLength; |
|
77 |
} |
|
78 |
if (count != node.val.count || totalLength != node.val.totalLength) { |
|
79 |
node.val.count = count; |
|
80 |
node.val.totalLength = totalLength; |
|
81 |
if (node.parent != null) UpdateAfterChildrenChange(node.parent); |
|
82 |
} |
|
83 |
} |
|
84 |
||
85 |
public void UpdateAfterRotateLeft(RedBlackTreeNode<RBNode> node) |
|
86 |
{ |
|
87 |
UpdateAfterChildrenChange(node); |
|
88 |
UpdateAfterChildrenChange(node.parent); |
|
89 |
} |
|
90 |
||
91 |
public void UpdateAfterRotateRight(RedBlackTreeNode<RBNode> node) |
|
92 |
{ |
|
93 |
UpdateAfterChildrenChange(node); |
|
94 |
UpdateAfterChildrenChange(node.parent); |
|
95 |
} |
|
96 |
} |
|
97 |
||
98 |
readonly AugmentableRedBlackTree<RBNode, MyHost> tree = new AugmentableRedBlackTree<RBNode, MyHost>(new MyHost()); |
|
99 |
||
100 |
RedBlackTreeNode<RBNode> GetNode(int index) |
|
101 |
{ |
|
102 |
if (index < 0 || index >= tree.Count) |
|
103 |
throw new ArgumentOutOfRangeException("index", index, "index should be between 0 and " + (tree.Count-1)); |
|
104 |
RedBlackTreeNode<RBNode> node = tree.root; |
|
105 |
while (true) { |
|
106 |
if (node.left != null && index < node.left.val.count) { |
|
107 |
node = node.left; |
|
108 |
} else { |
|
109 |
if (node.left != null) { |
|
110 |
index -= node.left.val.count; |
|
111 |
} |
|
112 |
if (index == 0) |
|
113 |
return node; |
|
114 |
index--; |
|
115 |
node = node.right; |
|
116 |
} |
|
117 |
} |
|
118 |
} |
|
119 |
||
120 |
static int GetIndexFromNode(RedBlackTreeNode<RBNode> node) |
|
121 |
{ |
|
122 |
int index = (node.left != null) ? node.left.val.count : 0; |
|
123 |
while (node.parent != null) { |
|
124 |
if (node == node.parent.right) { |
|
125 |
if (node.parent.left != null) |
|
126 |
index += node.parent.left.val.count; |
|
127 |
index++; |
|
128 |
} |
|
129 |
node = node.parent; |
|
130 |
} |
|
131 |
return index; |
|
132 |
} |
|
133 |
||
134 |
RedBlackTreeNode<RBNode> GetNodeByOffset(int offset) |
|
135 |
{ |
|
136 |
if (offset < 0 || offset > this.TotalLength) |
|
137 |
throw new ArgumentOutOfRangeException("offset", offset, "offset should be between 0 and " + this.TotalLength); |
|
138 |
if (offset == this.TotalLength) { |
|
139 |
if (tree.root == null) |
|
140 |
throw new InvalidOperationException("Cannot call GetNodeByOffset while tree is empty."); |
|
141 |
return tree.root.RightMost; |
|
142 |
} |
|
143 |
RedBlackTreeNode<RBNode> node = tree.root; |
|
144 |
while (true) { |
|
145 |
if (node.left != null && offset < node.left.val.totalLength) { |
|
146 |
node = node.left; |
|
147 |
} else { |
|
148 |
if (node.left != null) { |
|
149 |
offset -= node.left.val.totalLength; |
|
150 |
} |
|
151 |
offset -= node.val.lineSegment.TotalLength; |
|
152 |
if (offset < 0) |
|
153 |
return node; |
|
154 |
node = node.right; |
|
155 |
} |
|
156 |
} |
|
157 |
} |
|
158 |
||
159 |
static int GetOffsetFromNode(RedBlackTreeNode<RBNode> node) |
|
160 |
{ |
|
161 |
int offset = (node.left != null) ? node.left.val.totalLength : 0; |
|
162 |
while (node.parent != null) { |
|
163 |
if (node == node.parent.right) { |
|
164 |
if (node.parent.left != null) |
|
165 |
offset += node.parent.left.val.totalLength; |
|
166 |
offset += node.parent.val.lineSegment.TotalLength; |
|
167 |
} |
|
168 |
node = node.parent; |
|
169 |
} |
|
170 |
return offset; |
|
171 |
} |
|
172 |
||
173 |
public LineSegment GetByOffset(int offset) |
|
174 |
{ |
|
175 |
return GetNodeByOffset(offset).val.lineSegment; |
|
176 |
} |
|
177 |
||
178 |
/// <summary> |
|
179 |
/// Gets the total length of all line segments. Runs in O(1). |
|
180 |
/// </summary> |
|
181 |
public int TotalLength { |
|
182 |
get { |
|
183 |
if (tree.root == null) |
|
184 |
return 0; |
|
185 |
else |
|
186 |
return tree.root.val.totalLength; |
|
187 |
} |
|
188 |
} |
|
189 |
||
190 |
/// <summary> |
|
191 |
/// Updates the length of a line segment. Runs in O(lg n). |
|
192 |
/// </summary> |
|
193 |
public void SetSegmentLength(LineSegment segment, int newTotalLength) |
|
194 |
{ |
|
195 |
if (segment == null) |
|
196 |
throw new ArgumentNullException("segment"); |
|
197 |
RedBlackTreeNode<RBNode> node = segment.treeEntry.it.node; |
|
198 |
segment.TotalLength = newTotalLength; |
|
199 |
default(MyHost).UpdateAfterChildrenChange(node); |
|
200 |
#if DEBUG |
|
201 |
CheckProperties(); |
|
202 |
#endif |
|
203 |
} |
|
204 |
||
205 |
public void RemoveSegment(LineSegment segment) |
|
206 |
{ |
|
207 |
tree.RemoveAt(segment.treeEntry.it); |
|
208 |
#if DEBUG |
|
209 |
CheckProperties(); |
|
210 |
#endif |
|
211 |
} |
|
212 |
||
213 |
public LineSegment InsertSegmentAfter(LineSegment segment, int length) |
|
214 |
{ |
|
215 |
LineSegment newSegment = new LineSegment(); |
|
216 |
newSegment.TotalLength = length; |
|
217 |
newSegment.DelimiterLength = segment.DelimiterLength; |
|
218 |
||
219 |
newSegment.treeEntry = InsertAfter(segment.treeEntry.it.node, newSegment); |
|
220 |
return newSegment; |
|
221 |
} |
|
222 |
||
223 |
Enumerator InsertAfter(RedBlackTreeNode<RBNode> node, LineSegment newSegment) |
|
224 |
{ |
|
225 |
RedBlackTreeNode<RBNode> newNode = new RedBlackTreeNode<RBNode>(new RBNode(newSegment)); |
|
226 |
if (node.right == null) { |
|
227 |
tree.InsertAsRight(node, newNode); |
|
228 |
} else { |
|
229 |
tree.InsertAsLeft(node.right.LeftMost, newNode); |
|
230 |
} |
|
231 |
#if DEBUG |
|
232 |
CheckProperties(); |
|
233 |
#endif |
|
234 |
return new Enumerator(new RedBlackTreeIterator<RBNode>(newNode)); |
|
235 |
} |
|
236 |
||
237 |
/// <summary> |
|
238 |
/// Gets the number of items in the collections. Runs in O(1). |
|
239 |
/// </summary> |
|
240 |
public int Count { |
|
241 |
get { return tree.Count; } |
|
242 |
} |
|
243 |
||
244 |
/// <summary> |
|
245 |
/// Gets or sets an item by index. Runs in O(lg n). |
|
246 |
/// </summary> |
|
247 |
public LineSegment this[int index] { |
|
248 |
get { |
|
249 |
return GetNode(index).val.lineSegment; |
|
250 |
} |
|
251 |
set { |
|
252 |
throw new NotSupportedException(); |
|
253 |
} |
|
254 |
} |
|
255 |
||
256 |
bool ICollection<LineSegment>.IsReadOnly { |
|
257 |
get { return true; } |
|
258 |
} |
|
259 |
||
260 |
/// <summary> |
|
261 |
/// Gets the index of an item. Runs in O(lg n). |
|
262 |
/// </summary> |
|
263 |
public int IndexOf(LineSegment item) |
|
264 |
{ |
|
265 |
int index = item.LineNumber; |
|
266 |
if (index < 0 || index >= this.Count) |
|
267 |
return -1; |
|
268 |
if (item != this[index]) |
|
269 |
return -1; |
|
270 |
return index; |
|
271 |
} |
|
272 |
||
273 |
void IList<LineSegment>.RemoveAt(int index) |
|
274 |
{ |
|
275 |
throw new NotSupportedException(); |
|
276 |
} |
|
277 |
||
278 |
#if DEBUG |
|
279 |
[Conditional("DATACONSISTENCYTEST")] |
|
280 |
void CheckProperties() |
|
281 |
{ |
|
282 |
if (tree.root == null) { |
|
283 |
Debug.Assert(this.Count == 0); |
|
284 |
} else { |
|
285 |
Debug.Assert(tree.root.val.count == this.Count); |
|
286 |
CheckProperties(tree.root); |
|
287 |
} |
|
288 |
} |
|
289 |
||
290 |
void CheckProperties(RedBlackTreeNode<RBNode> node) |
|
291 |
{ |
|
292 |
int count = 1; |
|
293 |
int totalLength = node.val.lineSegment.TotalLength; |
|
294 |
if (node.left != null) { |
|
295 |
CheckProperties(node.left); |
|
296 |
count += node.left.val.count; |
|
297 |
totalLength += node.left.val.totalLength; |
|
298 |
} |
|
299 |
if (node.right != null) { |
|
300 |
CheckProperties(node.right); |
|
301 |
count += node.right.val.count; |
|
302 |
totalLength += node.right.val.totalLength; |
|
303 |
} |
|
304 |
Debug.Assert(node.val.count == count); |
|
305 |
Debug.Assert(node.val.totalLength == totalLength); |
|
306 |
} |
|
307 |
||
308 |
public string GetTreeAsString() |
|
309 |
{ |
|
310 |
return tree.GetTreeAsString(); |
|
311 |
} |
|
312 |
#endif |
|
313 |
||
314 |
public LineSegmentTree() |
|
315 |
{ |
|
316 |
Clear(); |
|
317 |
} |
|
318 |
||
319 |
/// <summary> |
|
320 |
/// Clears the list. Runs in O(1). |
|
321 |
/// </summary> |
|
322 |
public void Clear() |
|
323 |
{ |
|
324 |
tree.Clear(); |
|
325 |
LineSegment emptySegment = new LineSegment(); |
|
326 |
emptySegment.TotalLength = 0; |
|
327 |
emptySegment.DelimiterLength = 0; |
|
328 |
tree.Add(new RBNode(emptySegment)); |
|
329 |
emptySegment.treeEntry = GetEnumeratorForIndex(0); |
|
330 |
#if DEBUG |
|
331 |
CheckProperties(); |
|
332 |
#endif |
|
333 |
} |
|
334 |
||
335 |
/// <summary> |
|
336 |
/// Tests whether an item is in the list. Runs in O(n). |
|
337 |
/// </summary> |
|
338 |
public bool Contains(LineSegment item) |
|
339 |
{ |
|
340 |
return IndexOf(item) >= 0; |
|
341 |
} |
|
342 |
||
343 |
/// <summary> |
|
344 |
/// Copies all elements from the list to the array. |
|
345 |
/// </summary> |
|
346 |
public void CopyTo(LineSegment[] array, int arrayIndex) |
|
347 |
{ |
|
348 |
if (array == null) throw new ArgumentNullException("array"); |
|
349 |
foreach (LineSegment val in this) |
|
350 |
array[arrayIndex++] = val; |
|
351 |
} |
|
352 |
||
353 |
IEnumerator<LineSegment> IEnumerable<LineSegment>.GetEnumerator() |
|
354 |
{ |
|
355 |
return this.GetEnumerator(); |
|
356 |
} |
|
357 |
||
358 |
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() |
|
359 |
{ |
|
360 |
return this.GetEnumerator(); |
|
361 |
} |
|
362 |
||
363 |
public Enumerator GetEnumerator() |
|
364 |
{ |
|
365 |
return new Enumerator(tree.GetEnumerator()); |
|
366 |
} |
|
367 |
||
368 |
public Enumerator GetEnumeratorForIndex(int index) |
|
369 |
{ |
|
370 |
return new Enumerator(new RedBlackTreeIterator<RBNode>(GetNode(index))); |
|
371 |
} |
|
372 |
||
373 |
public Enumerator GetEnumeratorForOffset(int offset) |
|
374 |
{ |
|
375 |
return new Enumerator(new RedBlackTreeIterator<RBNode>(GetNodeByOffset(offset))); |
|
376 |
} |
|
377 |
||
378 |
public struct Enumerator : IEnumerator<LineSegment> |
|
379 |
{ |
|
380 |
/// <summary> |
|
381 |
/// An invalid enumerator value. Calling MoveNext on the invalid enumerator |
|
382 |
/// will always return false, accessing Current will throw an exception. |
|
383 |
/// </summary> |
|
384 |
public static readonly Enumerator Invalid = default(Enumerator); |
|
385 |
||
386 |
internal RedBlackTreeIterator<RBNode> it; |
|
387 |
||
388 |
internal Enumerator(RedBlackTreeIterator<RBNode> it) |
|
389 |
{ |
|
390 |
this.it = it; |
|
391 |
} |
|
392 |
||
393 |
/// <summary> |
|
394 |
/// Gets the current value. Runs in O(1). |
|
395 |
/// </summary> |
|
396 |
public LineSegment Current { |
|
397 |
get { |
|
398 |
return it.Current.lineSegment; |
|
399 |
} |
|
400 |
} |
|
401 |
||
402 |
public bool IsValid { |
|
403 |
get { |
|
404 |
return it.IsValid; |
|
405 |
} |
|
406 |
} |
|
407 |
||
408 |
/// <summary> |
|
409 |
/// Gets the index of the current value. Runs in O(lg n). |
|
410 |
/// </summary> |
|
411 |
public int CurrentIndex { |
|
412 |
get { |
|
413 |
if (it.node == null) |
|
414 |
throw new InvalidOperationException(); |
|
415 |
return GetIndexFromNode(it.node); |
|
416 |
} |
|
417 |
} |
|
418 |
||
419 |
/// <summary> |
|
420 |
/// Gets the offset of the current value. Runs in O(lg n). |
|
421 |
/// </summary> |
|
422 |
public int CurrentOffset { |
|
423 |
get { |
|
424 |
if (it.node == null) |
|
425 |
throw new InvalidOperationException(); |
|
426 |
return GetOffsetFromNode(it.node); |
|
427 |
} |
|
428 |
} |
|
429 |
||
430 |
object System.Collections.IEnumerator.Current { |
|
431 |
get { |
|
432 |
return it.Current.lineSegment; |
|
433 |
} |
|
434 |
} |
|
435 |
||
436 |
public void Dispose() |
|
437 |
{ |
|
438 |
} |
|
439 |
||
440 |
/// <summary> |
|
441 |
/// Moves to the next index. Runs in O(lg n), but for k calls, the combined time is only O(k+lg n). |
|
442 |
/// </summary> |
|
443 |
public bool MoveNext() |
|
444 |
{ |
|
445 |
return it.MoveNext(); |
|
446 |
} |
|
447 |
||
448 |
/// <summary> |
|
449 |
/// Moves to the previous index. Runs in O(lg n), but for k calls, the combined time is only O(k+lg n). |
|
450 |
/// </summary> |
|
451 |
public bool MoveBack() |
|
452 |
{ |
|
453 |
return it.MoveBack(); |
|
454 |
} |
|
455 |
||
456 |
void System.Collections.IEnumerator.Reset() |
|
457 |
{ |
|
458 |
throw new NotSupportedException(); |
|
459 |
} |
|
460 |
} |
|
461 |
||
462 |
void IList<LineSegment>.Insert(int index, LineSegment item) |
|
463 |
{ |
|
464 |
throw new NotSupportedException(); |
|
465 |
} |
|
466 |
||
467 |
void ICollection<LineSegment>.Add(LineSegment item) |
|
468 |
{ |
|
469 |
throw new NotSupportedException(); |
|
470 |
} |
|
471 |
||
472 |
bool ICollection<LineSegment>.Remove(LineSegment item) |
|
473 |
{ |
|
474 |
throw new NotSupportedException(); |
|
475 |
} |
|
476 |
} |
|
477 |
} |