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 | |
---|
19 | using System; |
---|
20 | using System.Collections.Generic; |
---|
21 | using System.Diagnostics; |
---|
22 | using System.Text; |
---|
23 | using ICSharpCode.AvalonEdit.Utils; |
---|
24 | #if NREFACTORY |
---|
25 | using ICSharpCode.NRefactory.Editor; |
---|
26 | #endif |
---|
27 | |
---|
28 | namespace ICSharpCode.AvalonEdit.Document |
---|
29 | { |
---|
30 | /// <summary> |
---|
31 | /// A tree of TextAnchorNodes. |
---|
32 | /// </summary> |
---|
33 | sealed class TextAnchorTree |
---|
34 | { |
---|
35 | // The text anchor tree has difficult requirements: |
---|
36 | // - it must QUICKLY update the offset in all anchors whenever there is a document change |
---|
37 | // - it must not reference text anchors directly, using weak references instead |
---|
38 | |
---|
39 | // Clearly, we cannot afford updating an Offset property on all anchors (that would be O(N)). |
---|
40 | // So instead, the anchors need to be able to calculate their offset from a data structure |
---|
41 | // that can be efficiently updated. |
---|
42 | |
---|
43 | // This implementation is built using an augmented red-black-tree. |
---|
44 | // There is a 'TextAnchorNode' for each text anchor. |
---|
45 | // Such a node represents a section of text (just the length is stored) with a (weakly referenced) text anchor at the end. |
---|
46 | |
---|
47 | // Basically, you can imagine the list of text anchors as a sorted list of text anchors, where each anchor |
---|
48 | // just stores the distance to the previous anchor. |
---|
49 | // (next node = TextAnchorNode.Successor, distance = TextAnchorNode.length) |
---|
50 | // Distances are never negative, so this representation means anchors are always sorted by offset |
---|
51 | // (the order of anchors at the same offset is undefined) |
---|
52 | |
---|
53 | // Of course, a linked list of anchors would be way too slow (one would need to traverse the whole list |
---|
54 | // every time the offset of an anchor is being looked up). |
---|
55 | // Instead, we use a red-black-tree. We aren't actually using the tree for sorting - it's just a binary tree |
---|
56 | // as storage format for what's conceptually a list, the red-black properties are used to keep the tree balanced. |
---|
57 | // Other balanced binary trees would work, too. |
---|
58 | |
---|
59 | // What makes the tree-form efficient is that is augments the data by a 'totalLength'. Where 'length' |
---|
60 | // represents the distance to the previous node, 'totalLength' is the sum of all 'length' values in the subtree |
---|
61 | // under that node. |
---|
62 | // This allows computing the Offset from an anchor by walking up the list of parent nodes instead of going |
---|
63 | // through all predecessor nodes. So computing the Offset runs in O(log N). |
---|
64 | |
---|
65 | readonly TextDocument document; |
---|
66 | readonly List<TextAnchorNode> nodesToDelete = new List<TextAnchorNode>(); |
---|
67 | TextAnchorNode root; |
---|
68 | |
---|
69 | public TextAnchorTree(TextDocument document) |
---|
70 | { |
---|
71 | this.document = document; |
---|
72 | } |
---|
73 | |
---|
74 | [Conditional("DEBUG")] |
---|
75 | static void Log(string text) |
---|
76 | { |
---|
77 | Debug.WriteLine("TextAnchorTree: " + text); |
---|
78 | } |
---|
79 | |
---|
80 | #region Insert Text |
---|
81 | void InsertText(int offset, int length, bool defaultAnchorMovementIsBeforeInsertion) |
---|
82 | { |
---|
83 | if (length == 0 || root == null || offset > root.totalLength) |
---|
84 | return; |
---|
85 | |
---|
86 | // find the range of nodes that are placed exactly at offset |
---|
87 | // beginNode is inclusive, endNode is exclusive |
---|
88 | if (offset == root.totalLength) { |
---|
89 | PerformInsertText(FindActualBeginNode(root.RightMost), null, length, defaultAnchorMovementIsBeforeInsertion); |
---|
90 | } else { |
---|
91 | TextAnchorNode endNode = FindNode(ref offset); |
---|
92 | Debug.Assert(endNode.length > 0); |
---|
93 | |
---|
94 | if (offset > 0) { |
---|
95 | // there are no nodes exactly at offset |
---|
96 | endNode.length += length; |
---|
97 | UpdateAugmentedData(endNode); |
---|
98 | } else { |
---|
99 | PerformInsertText(FindActualBeginNode(endNode.Predecessor), endNode, length, defaultAnchorMovementIsBeforeInsertion); |
---|
100 | } |
---|
101 | } |
---|
102 | DeleteMarkedNodes(); |
---|
103 | } |
---|
104 | |
---|
105 | TextAnchorNode FindActualBeginNode(TextAnchorNode node) |
---|
106 | { |
---|
107 | // now find the actual beginNode |
---|
108 | while (node != null && node.length == 0) |
---|
109 | node = node.Predecessor; |
---|
110 | if (node == null) { |
---|
111 | // no predecessor = beginNode is first node in tree |
---|
112 | node = root.LeftMost; |
---|
113 | } |
---|
114 | return node; |
---|
115 | } |
---|
116 | |
---|
117 | // Sorts the nodes in the range [beginNode, endNode) by MovementType |
---|
118 | // and inserts the length between the BeforeInsertion and the AfterInsertion nodes. |
---|
119 | void PerformInsertText(TextAnchorNode beginNode, TextAnchorNode endNode, int length, bool defaultAnchorMovementIsBeforeInsertion) |
---|
120 | { |
---|
121 | Debug.Assert(beginNode != null); |
---|
122 | // endNode may be null at the end of the anchor tree |
---|
123 | |
---|
124 | // now we need to sort the nodes in the range [beginNode, endNode); putting those with |
---|
125 | // MovementType.BeforeInsertion in front of those with MovementType.AfterInsertion |
---|
126 | List<TextAnchorNode> beforeInsert = new List<TextAnchorNode>(); |
---|
127 | //List<TextAnchorNode> afterInsert = new List<TextAnchorNode>(); |
---|
128 | TextAnchorNode temp = beginNode; |
---|
129 | while (temp != endNode) { |
---|
130 | TextAnchor anchor = (TextAnchor)temp.Target; |
---|
131 | if (anchor == null) { |
---|
132 | // afterInsert.Add(temp); |
---|
133 | MarkNodeForDelete(temp); |
---|
134 | } else if (defaultAnchorMovementIsBeforeInsertion |
---|
135 | ? anchor.MovementType != AnchorMovementType.AfterInsertion |
---|
136 | : anchor.MovementType == AnchorMovementType.BeforeInsertion) |
---|
137 | { |
---|
138 | beforeInsert.Add(temp); |
---|
139 | // } else { |
---|
140 | // afterInsert.Add(temp); |
---|
141 | } |
---|
142 | temp = temp.Successor; |
---|
143 | } |
---|
144 | // now again go through the range and swap the nodes with those in the beforeInsert list |
---|
145 | temp = beginNode; |
---|
146 | foreach (TextAnchorNode node in beforeInsert) { |
---|
147 | SwapAnchors(node, temp); |
---|
148 | temp = temp.Successor; |
---|
149 | } |
---|
150 | // now temp is pointing to the first node that is afterInsert, |
---|
151 | // or to endNode, if there is no afterInsert node at the offset |
---|
152 | // So add the length to temp |
---|
153 | if (temp == null) { |
---|
154 | // temp might be null if endNode==null and no afterInserts |
---|
155 | Debug.Assert(endNode == null); |
---|
156 | } else { |
---|
157 | temp.length += length; |
---|
158 | UpdateAugmentedData(temp); |
---|
159 | } |
---|
160 | } |
---|
161 | |
---|
162 | /// <summary> |
---|
163 | /// Swaps the anchors stored in the two nodes. |
---|
164 | /// </summary> |
---|
165 | void SwapAnchors(TextAnchorNode n1, TextAnchorNode n2) |
---|
166 | { |
---|
167 | if (n1 != n2) { |
---|
168 | TextAnchor anchor1 = (TextAnchor)n1.Target; |
---|
169 | TextAnchor anchor2 = (TextAnchor)n2.Target; |
---|
170 | if (anchor1 == null && anchor2 == null) { |
---|
171 | // -> no swap required |
---|
172 | return; |
---|
173 | } |
---|
174 | n1.Target = anchor2; |
---|
175 | n2.Target = anchor1; |
---|
176 | if (anchor1 == null) { |
---|
177 | // unmark n1 from deletion, mark n2 for deletion |
---|
178 | nodesToDelete.Remove(n1); |
---|
179 | MarkNodeForDelete(n2); |
---|
180 | anchor2.node = n1; |
---|
181 | } else if (anchor2 == null) { |
---|
182 | // unmark n2 from deletion, mark n1 for deletion |
---|
183 | nodesToDelete.Remove(n2); |
---|
184 | MarkNodeForDelete(n1); |
---|
185 | anchor1.node = n2; |
---|
186 | } else { |
---|
187 | anchor1.node = n2; |
---|
188 | anchor2.node = n1; |
---|
189 | } |
---|
190 | } |
---|
191 | } |
---|
192 | #endregion |
---|
193 | |
---|
194 | #region Remove or Replace text |
---|
195 | public void HandleTextChange(OffsetChangeMapEntry entry, DelayedEvents delayedEvents) |
---|
196 | { |
---|
197 | //Log("HandleTextChange(" + entry + ")"); |
---|
198 | if (entry.RemovalLength == 0) { |
---|
199 | // This is a pure insertion. |
---|
200 | // Unlike a replace with removal, a pure insertion can result in nodes at the same location |
---|
201 | // to split depending on their MovementType. |
---|
202 | // Thus, we handle this case on a separate code path |
---|
203 | // (the code below looks like it does something similar, but it can only split |
---|
204 | // the set of deletion survivors, not all nodes at an offset) |
---|
205 | InsertText(entry.Offset, entry.InsertionLength, entry.DefaultAnchorMovementIsBeforeInsertion); |
---|
206 | return; |
---|
207 | } |
---|
208 | // When handling a replacing text change, we need to: |
---|
209 | // - find all anchors in the deleted segment and delete them / move them to the appropriate |
---|
210 | // surviving side. |
---|
211 | // - adjust the segment size between the left and right side |
---|
212 | |
---|
213 | int offset = entry.Offset; |
---|
214 | int remainingRemovalLength = entry.RemovalLength; |
---|
215 | // if the text change is happening after the last anchor, we don't have to do anything |
---|
216 | if (root == null || offset >= root.totalLength) |
---|
217 | return; |
---|
218 | TextAnchorNode node = FindNode(ref offset); |
---|
219 | TextAnchorNode firstDeletionSurvivor = null; |
---|
220 | // go forward through the tree and delete all nodes in the removal segment |
---|
221 | while (node != null && offset + remainingRemovalLength > node.length) { |
---|
222 | TextAnchor anchor = (TextAnchor)node.Target; |
---|
223 | if (anchor != null && (anchor.SurviveDeletion || entry.RemovalNeverCausesAnchorDeletion)) { |
---|
224 | if (firstDeletionSurvivor == null) |
---|
225 | firstDeletionSurvivor = node; |
---|
226 | // This node should be deleted, but it wants to survive. |
---|
227 | // We'll just remove the deleted length segment, so the node will be positioned |
---|
228 | // in front of the removed segment. |
---|
229 | remainingRemovalLength -= node.length - offset; |
---|
230 | node.length = offset; |
---|
231 | offset = 0; |
---|
232 | UpdateAugmentedData(node); |
---|
233 | node = node.Successor; |
---|
234 | } else { |
---|
235 | // delete node |
---|
236 | TextAnchorNode s = node.Successor; |
---|
237 | remainingRemovalLength -= node.length; |
---|
238 | RemoveNode(node); |
---|
239 | // we already deleted the node, don't delete it twice |
---|
240 | nodesToDelete.Remove(node); |
---|
241 | if (anchor != null) |
---|
242 | anchor.OnDeleted(delayedEvents); |
---|
243 | node = s; |
---|
244 | } |
---|
245 | } |
---|
246 | // 'node' now is the first anchor after the deleted segment. |
---|
247 | // If there are no anchors after the deleted segment, 'node' is null. |
---|
248 | |
---|
249 | // firstDeletionSurvivor was set to the first node surviving deletion. |
---|
250 | // Because all non-surviving nodes up to 'node' were deleted, the node range |
---|
251 | // [firstDeletionSurvivor, node) now refers to the set of all deletion survivors. |
---|
252 | |
---|
253 | // do the remaining job of the removal |
---|
254 | if (node != null) { |
---|
255 | node.length -= remainingRemovalLength; |
---|
256 | Debug.Assert(node.length >= 0); |
---|
257 | } |
---|
258 | if (entry.InsertionLength > 0) { |
---|
259 | // we are performing a replacement |
---|
260 | if (firstDeletionSurvivor != null) { |
---|
261 | // We got deletion survivors which need to be split into BeforeInsertion |
---|
262 | // and AfterInsertion groups. |
---|
263 | // Take care that we don't regroup everything at offset, but only the deletion |
---|
264 | // survivors - from firstDeletionSurvivor (inclusive) to node (exclusive). |
---|
265 | // This ensures that nodes immediately before or after the replaced segment |
---|
266 | // stay where they are (independent from their MovementType) |
---|
267 | PerformInsertText(firstDeletionSurvivor, node, entry.InsertionLength, entry.DefaultAnchorMovementIsBeforeInsertion); |
---|
268 | } else if (node != null) { |
---|
269 | // No deletion survivors: |
---|
270 | // just perform the insertion |
---|
271 | node.length += entry.InsertionLength; |
---|
272 | } |
---|
273 | } |
---|
274 | if (node != null) { |
---|
275 | UpdateAugmentedData(node); |
---|
276 | } |
---|
277 | DeleteMarkedNodes(); |
---|
278 | } |
---|
279 | #endregion |
---|
280 | |
---|
281 | #region Node removal when TextAnchor was GC'ed |
---|
282 | void MarkNodeForDelete(TextAnchorNode node) |
---|
283 | { |
---|
284 | if (!nodesToDelete.Contains(node)) |
---|
285 | nodesToDelete.Add(node); |
---|
286 | } |
---|
287 | |
---|
288 | void DeleteMarkedNodes() |
---|
289 | { |
---|
290 | CheckProperties(); |
---|
291 | while (nodesToDelete.Count > 0) { |
---|
292 | int pos = nodesToDelete.Count - 1; |
---|
293 | TextAnchorNode n = nodesToDelete[pos]; |
---|
294 | // combine section of n with the following section |
---|
295 | TextAnchorNode s = n.Successor; |
---|
296 | if (s != null) { |
---|
297 | s.length += n.length; |
---|
298 | } |
---|
299 | RemoveNode(n); |
---|
300 | if (s != null) { |
---|
301 | UpdateAugmentedData(s); |
---|
302 | } |
---|
303 | nodesToDelete.RemoveAt(pos); |
---|
304 | CheckProperties(); |
---|
305 | } |
---|
306 | CheckProperties(); |
---|
307 | } |
---|
308 | #endregion |
---|
309 | |
---|
310 | #region FindNode |
---|
311 | /// <summary> |
---|
312 | /// Finds the node at the specified offset. |
---|
313 | /// After the method has run, offset is relative to the beginning of the returned node. |
---|
314 | /// </summary> |
---|
315 | TextAnchorNode FindNode(ref int offset) |
---|
316 | { |
---|
317 | TextAnchorNode n = root; |
---|
318 | while (true) { |
---|
319 | if (n.left != null) { |
---|
320 | if (offset < n.left.totalLength) { |
---|
321 | n = n.left; // descend into left subtree |
---|
322 | continue; |
---|
323 | } else { |
---|
324 | offset -= n.left.totalLength; // skip left subtree |
---|
325 | } |
---|
326 | } |
---|
327 | if (!n.IsAlive) |
---|
328 | MarkNodeForDelete(n); |
---|
329 | if (offset < n.length) { |
---|
330 | return n; // found correct node |
---|
331 | } else { |
---|
332 | offset -= n.length; // skip this node |
---|
333 | } |
---|
334 | if (n.right != null) { |
---|
335 | n = n.right; // descend into right subtree |
---|
336 | } else { |
---|
337 | // didn't find any node containing the offset |
---|
338 | return null; |
---|
339 | } |
---|
340 | } |
---|
341 | } |
---|
342 | #endregion |
---|
343 | |
---|
344 | #region UpdateAugmentedData |
---|
345 | void UpdateAugmentedData(TextAnchorNode n) |
---|
346 | { |
---|
347 | if (!n.IsAlive) |
---|
348 | MarkNodeForDelete(n); |
---|
349 | |
---|
350 | int totalLength = n.length; |
---|
351 | if (n.left != null) |
---|
352 | totalLength += n.left.totalLength; |
---|
353 | if (n.right != null) |
---|
354 | totalLength += n.right.totalLength; |
---|
355 | if (n.totalLength != totalLength) { |
---|
356 | n.totalLength = totalLength; |
---|
357 | if (n.parent != null) |
---|
358 | UpdateAugmentedData(n.parent); |
---|
359 | } |
---|
360 | } |
---|
361 | #endregion |
---|
362 | |
---|
363 | #region CreateAnchor |
---|
364 | public TextAnchor CreateAnchor(int offset) |
---|
365 | { |
---|
366 | Log("CreateAnchor(" + offset + ")"); |
---|
367 | TextAnchor anchor = new TextAnchor(document); |
---|
368 | anchor.node = new TextAnchorNode(anchor); |
---|
369 | if (root == null) { |
---|
370 | // creating the first text anchor |
---|
371 | root = anchor.node; |
---|
372 | root.totalLength = root.length = offset; |
---|
373 | } else if (offset >= root.totalLength) { |
---|
374 | // append anchor at end of tree |
---|
375 | anchor.node.totalLength = anchor.node.length = offset - root.totalLength; |
---|
376 | InsertAsRight(root.RightMost, anchor.node); |
---|
377 | } else { |
---|
378 | // insert anchor in middle of tree |
---|
379 | TextAnchorNode n = FindNode(ref offset); |
---|
380 | Debug.Assert(offset < n.length); |
---|
381 | // split segment 'n' at offset |
---|
382 | anchor.node.totalLength = anchor.node.length = offset; |
---|
383 | n.length -= offset; |
---|
384 | InsertBefore(n, anchor.node); |
---|
385 | } |
---|
386 | DeleteMarkedNodes(); |
---|
387 | return anchor; |
---|
388 | } |
---|
389 | |
---|
390 | void InsertBefore(TextAnchorNode node, TextAnchorNode newNode) |
---|
391 | { |
---|
392 | if (node.left == null) { |
---|
393 | InsertAsLeft(node, newNode); |
---|
394 | } else { |
---|
395 | InsertAsRight(node.left.RightMost, newNode); |
---|
396 | } |
---|
397 | } |
---|
398 | #endregion |
---|
399 | |
---|
400 | #region Red/Black Tree |
---|
401 | internal const bool RED = true; |
---|
402 | internal const bool BLACK = false; |
---|
403 | |
---|
404 | void InsertAsLeft(TextAnchorNode parentNode, TextAnchorNode newNode) |
---|
405 | { |
---|
406 | Debug.Assert(parentNode.left == null); |
---|
407 | parentNode.left = newNode; |
---|
408 | newNode.parent = parentNode; |
---|
409 | newNode.color = RED; |
---|
410 | UpdateAugmentedData(parentNode); |
---|
411 | FixTreeOnInsert(newNode); |
---|
412 | } |
---|
413 | |
---|
414 | void InsertAsRight(TextAnchorNode parentNode, TextAnchorNode newNode) |
---|
415 | { |
---|
416 | Debug.Assert(parentNode.right == null); |
---|
417 | parentNode.right = newNode; |
---|
418 | newNode.parent = parentNode; |
---|
419 | newNode.color = RED; |
---|
420 | UpdateAugmentedData(parentNode); |
---|
421 | FixTreeOnInsert(newNode); |
---|
422 | } |
---|
423 | |
---|
424 | void FixTreeOnInsert(TextAnchorNode node) |
---|
425 | { |
---|
426 | Debug.Assert(node != null); |
---|
427 | Debug.Assert(node.color == RED); |
---|
428 | Debug.Assert(node.left == null || node.left.color == BLACK); |
---|
429 | Debug.Assert(node.right == null || node.right.color == BLACK); |
---|
430 | |
---|
431 | TextAnchorNode parentNode = node.parent; |
---|
432 | if (parentNode == null) { |
---|
433 | // we inserted in the root -> the node must be black |
---|
434 | // since this is a root node, making the node black increments the number of black nodes |
---|
435 | // on all paths by one, so it is still the same for all paths. |
---|
436 | node.color = BLACK; |
---|
437 | return; |
---|
438 | } |
---|
439 | if (parentNode.color == BLACK) { |
---|
440 | // if the parent node where we inserted was black, our red node is placed correctly. |
---|
441 | // since we inserted a red node, the number of black nodes on each path is unchanged |
---|
442 | // -> the tree is still balanced |
---|
443 | return; |
---|
444 | } |
---|
445 | // parentNode is red, so there is a conflict here! |
---|
446 | |
---|
447 | // because the root is black, parentNode is not the root -> there is a grandparent node |
---|
448 | TextAnchorNode grandparentNode = parentNode.parent; |
---|
449 | TextAnchorNode uncleNode = Sibling(parentNode); |
---|
450 | if (uncleNode != null && uncleNode.color == RED) { |
---|
451 | parentNode.color = BLACK; |
---|
452 | uncleNode.color = BLACK; |
---|
453 | grandparentNode.color = RED; |
---|
454 | FixTreeOnInsert(grandparentNode); |
---|
455 | return; |
---|
456 | } |
---|
457 | // now we know: parent is red but uncle is black |
---|
458 | // First rotation: |
---|
459 | if (node == parentNode.right && parentNode == grandparentNode.left) { |
---|
460 | RotateLeft(parentNode); |
---|
461 | node = node.left; |
---|
462 | } else if (node == parentNode.left && parentNode == grandparentNode.right) { |
---|
463 | RotateRight(parentNode); |
---|
464 | node = node.right; |
---|
465 | } |
---|
466 | // because node might have changed, reassign variables: |
---|
467 | parentNode = node.parent; |
---|
468 | grandparentNode = parentNode.parent; |
---|
469 | |
---|
470 | // Now recolor a bit: |
---|
471 | parentNode.color = BLACK; |
---|
472 | grandparentNode.color = RED; |
---|
473 | // Second rotation: |
---|
474 | if (node == parentNode.left && parentNode == grandparentNode.left) { |
---|
475 | RotateRight(grandparentNode); |
---|
476 | } else { |
---|
477 | // because of the first rotation, this is guaranteed: |
---|
478 | Debug.Assert(node == parentNode.right && parentNode == grandparentNode.right); |
---|
479 | RotateLeft(grandparentNode); |
---|
480 | } |
---|
481 | } |
---|
482 | |
---|
483 | void RemoveNode(TextAnchorNode removedNode) |
---|
484 | { |
---|
485 | if (removedNode.left != null && removedNode.right != null) { |
---|
486 | // replace removedNode with it's in-order successor |
---|
487 | |
---|
488 | TextAnchorNode leftMost = removedNode.right.LeftMost; |
---|
489 | RemoveNode(leftMost); // remove leftMost from its current location |
---|
490 | |
---|
491 | // and overwrite the removedNode with it |
---|
492 | ReplaceNode(removedNode, leftMost); |
---|
493 | leftMost.left = removedNode.left; |
---|
494 | if (leftMost.left != null) leftMost.left.parent = leftMost; |
---|
495 | leftMost.right = removedNode.right; |
---|
496 | if (leftMost.right != null) leftMost.right.parent = leftMost; |
---|
497 | leftMost.color = removedNode.color; |
---|
498 | |
---|
499 | UpdateAugmentedData(leftMost); |
---|
500 | if (leftMost.parent != null) UpdateAugmentedData(leftMost.parent); |
---|
501 | return; |
---|
502 | } |
---|
503 | |
---|
504 | // now either removedNode.left or removedNode.right is null |
---|
505 | // get the remaining child |
---|
506 | TextAnchorNode parentNode = removedNode.parent; |
---|
507 | TextAnchorNode childNode = removedNode.left ?? removedNode.right; |
---|
508 | ReplaceNode(removedNode, childNode); |
---|
509 | if (parentNode != null) UpdateAugmentedData(parentNode); |
---|
510 | if (removedNode.color == BLACK) { |
---|
511 | if (childNode != null && childNode.color == RED) { |
---|
512 | childNode.color = BLACK; |
---|
513 | } else { |
---|
514 | FixTreeOnDelete(childNode, parentNode); |
---|
515 | } |
---|
516 | } |
---|
517 | } |
---|
518 | |
---|
519 | void FixTreeOnDelete(TextAnchorNode node, TextAnchorNode parentNode) |
---|
520 | { |
---|
521 | Debug.Assert(node == null || node.parent == parentNode); |
---|
522 | if (parentNode == null) |
---|
523 | return; |
---|
524 | |
---|
525 | // warning: node may be null |
---|
526 | TextAnchorNode sibling = Sibling(node, parentNode); |
---|
527 | if (sibling.color == RED) { |
---|
528 | parentNode.color = RED; |
---|
529 | sibling.color = BLACK; |
---|
530 | if (node == parentNode.left) { |
---|
531 | RotateLeft(parentNode); |
---|
532 | } else { |
---|
533 | RotateRight(parentNode); |
---|
534 | } |
---|
535 | |
---|
536 | sibling = Sibling(node, parentNode); // update value of sibling after rotation |
---|
537 | } |
---|
538 | |
---|
539 | if (parentNode.color == BLACK |
---|
540 | && sibling.color == BLACK |
---|
541 | && GetColor(sibling.left) == BLACK |
---|
542 | && GetColor(sibling.right) == BLACK) |
---|
543 | { |
---|
544 | sibling.color = RED; |
---|
545 | FixTreeOnDelete(parentNode, parentNode.parent); |
---|
546 | return; |
---|
547 | } |
---|
548 | |
---|
549 | if (parentNode.color == RED |
---|
550 | && sibling.color == BLACK |
---|
551 | && GetColor(sibling.left) == BLACK |
---|
552 | && GetColor(sibling.right) == BLACK) |
---|
553 | { |
---|
554 | sibling.color = RED; |
---|
555 | parentNode.color = BLACK; |
---|
556 | return; |
---|
557 | } |
---|
558 | |
---|
559 | if (node == parentNode.left && |
---|
560 | sibling.color == BLACK && |
---|
561 | GetColor(sibling.left) == RED && |
---|
562 | GetColor(sibling.right) == BLACK) |
---|
563 | { |
---|
564 | sibling.color = RED; |
---|
565 | sibling.left.color = BLACK; |
---|
566 | RotateRight(sibling); |
---|
567 | } |
---|
568 | else if (node == parentNode.right && |
---|
569 | sibling.color == BLACK && |
---|
570 | GetColor(sibling.right) == RED && |
---|
571 | GetColor(sibling.left) == BLACK) |
---|
572 | { |
---|
573 | sibling.color = RED; |
---|
574 | sibling.right.color = BLACK; |
---|
575 | RotateLeft(sibling); |
---|
576 | } |
---|
577 | sibling = Sibling(node, parentNode); // update value of sibling after rotation |
---|
578 | |
---|
579 | sibling.color = parentNode.color; |
---|
580 | parentNode.color = BLACK; |
---|
581 | if (node == parentNode.left) { |
---|
582 | if (sibling.right != null) { |
---|
583 | Debug.Assert(sibling.right.color == RED); |
---|
584 | sibling.right.color = BLACK; |
---|
585 | } |
---|
586 | RotateLeft(parentNode); |
---|
587 | } else { |
---|
588 | if (sibling.left != null) { |
---|
589 | Debug.Assert(sibling.left.color == RED); |
---|
590 | sibling.left.color = BLACK; |
---|
591 | } |
---|
592 | RotateRight(parentNode); |
---|
593 | } |
---|
594 | } |
---|
595 | |
---|
596 | void ReplaceNode(TextAnchorNode replacedNode, TextAnchorNode newNode) |
---|
597 | { |
---|
598 | if (replacedNode.parent == null) { |
---|
599 | Debug.Assert(replacedNode == root); |
---|
600 | root = newNode; |
---|
601 | } else { |
---|
602 | if (replacedNode.parent.left == replacedNode) |
---|
603 | replacedNode.parent.left = newNode; |
---|
604 | else |
---|
605 | replacedNode.parent.right = newNode; |
---|
606 | } |
---|
607 | if (newNode != null) { |
---|
608 | newNode.parent = replacedNode.parent; |
---|
609 | } |
---|
610 | replacedNode.parent = null; |
---|
611 | } |
---|
612 | |
---|
613 | void RotateLeft(TextAnchorNode p) |
---|
614 | { |
---|
615 | // let q be p's right child |
---|
616 | TextAnchorNode q = p.right; |
---|
617 | Debug.Assert(q != null); |
---|
618 | Debug.Assert(q.parent == p); |
---|
619 | // set q to be the new root |
---|
620 | ReplaceNode(p, q); |
---|
621 | |
---|
622 | // set p's right child to be q's left child |
---|
623 | p.right = q.left; |
---|
624 | if (p.right != null) p.right.parent = p; |
---|
625 | // set q's left child to be p |
---|
626 | q.left = p; |
---|
627 | p.parent = q; |
---|
628 | UpdateAugmentedData(p); |
---|
629 | UpdateAugmentedData(q); |
---|
630 | } |
---|
631 | |
---|
632 | void RotateRight(TextAnchorNode p) |
---|
633 | { |
---|
634 | // let q be p's left child |
---|
635 | TextAnchorNode q = p.left; |
---|
636 | Debug.Assert(q != null); |
---|
637 | Debug.Assert(q.parent == p); |
---|
638 | // set q to be the new root |
---|
639 | ReplaceNode(p, q); |
---|
640 | |
---|
641 | // set p's left child to be q's right child |
---|
642 | p.left = q.right; |
---|
643 | if (p.left != null) p.left.parent = p; |
---|
644 | // set q's right child to be p |
---|
645 | q.right = p; |
---|
646 | p.parent = q; |
---|
647 | UpdateAugmentedData(p); |
---|
648 | UpdateAugmentedData(q); |
---|
649 | } |
---|
650 | |
---|
651 | static TextAnchorNode Sibling(TextAnchorNode node) |
---|
652 | { |
---|
653 | if (node == node.parent.left) |
---|
654 | return node.parent.right; |
---|
655 | else |
---|
656 | return node.parent.left; |
---|
657 | } |
---|
658 | |
---|
659 | static TextAnchorNode Sibling(TextAnchorNode node, TextAnchorNode parentNode) |
---|
660 | { |
---|
661 | Debug.Assert(node == null || node.parent == parentNode); |
---|
662 | if (node == parentNode.left) |
---|
663 | return parentNode.right; |
---|
664 | else |
---|
665 | return parentNode.left; |
---|
666 | } |
---|
667 | |
---|
668 | static bool GetColor(TextAnchorNode node) |
---|
669 | { |
---|
670 | return node != null ? node.color : BLACK; |
---|
671 | } |
---|
672 | #endregion |
---|
673 | |
---|
674 | #region CheckProperties |
---|
675 | [Conditional("DATACONSISTENCYTEST")] |
---|
676 | internal void CheckProperties() |
---|
677 | { |
---|
678 | #if DEBUG |
---|
679 | if (root != null) { |
---|
680 | CheckProperties(root); |
---|
681 | |
---|
682 | // check red-black property: |
---|
683 | int blackCount = -1; |
---|
684 | CheckNodeProperties(root, null, RED, 0, ref blackCount); |
---|
685 | } |
---|
686 | #endif |
---|
687 | } |
---|
688 | |
---|
689 | #if DEBUG |
---|
690 | void CheckProperties(TextAnchorNode node) |
---|
691 | { |
---|
692 | int totalLength = node.length; |
---|
693 | if (node.left != null) { |
---|
694 | CheckProperties(node.left); |
---|
695 | totalLength += node.left.totalLength; |
---|
696 | } |
---|
697 | if (node.right != null) { |
---|
698 | CheckProperties(node.right); |
---|
699 | totalLength += node.right.totalLength; |
---|
700 | } |
---|
701 | Debug.Assert(node.totalLength == totalLength); |
---|
702 | } |
---|
703 | |
---|
704 | /* |
---|
705 | 1. A node is either red or black. |
---|
706 | 2. The root is black. |
---|
707 | 3. All leaves are black. (The leaves are the NIL children.) |
---|
708 | 4. Both children of every red node are black. (So every red node must have a black parent.) |
---|
709 | 5. Every simple path from a node to a descendant leaf contains the same number of black nodes. (Not counting the leaf node.) |
---|
710 | */ |
---|
711 | void CheckNodeProperties(TextAnchorNode node, TextAnchorNode parentNode, bool parentColor, int blackCount, ref int expectedBlackCount) |
---|
712 | { |
---|
713 | if (node == null) return; |
---|
714 | |
---|
715 | Debug.Assert(node.parent == parentNode); |
---|
716 | |
---|
717 | if (parentColor == RED) { |
---|
718 | Debug.Assert(node.color == BLACK); |
---|
719 | } |
---|
720 | if (node.color == BLACK) { |
---|
721 | blackCount++; |
---|
722 | } |
---|
723 | if (node.left == null && node.right == null) { |
---|
724 | // node is a leaf node: |
---|
725 | if (expectedBlackCount == -1) |
---|
726 | expectedBlackCount = blackCount; |
---|
727 | else |
---|
728 | Debug.Assert(expectedBlackCount == blackCount); |
---|
729 | } |
---|
730 | CheckNodeProperties(node.left, node, node.color, blackCount, ref expectedBlackCount); |
---|
731 | CheckNodeProperties(node.right, node, node.color, blackCount, ref expectedBlackCount); |
---|
732 | } |
---|
733 | #endif |
---|
734 | #endregion |
---|
735 | |
---|
736 | #region GetTreeAsString |
---|
737 | #if DEBUG |
---|
738 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] |
---|
739 | public string GetTreeAsString() |
---|
740 | { |
---|
741 | if (root == null) |
---|
742 | return "<empty tree>"; |
---|
743 | StringBuilder b = new StringBuilder(); |
---|
744 | AppendTreeToString(root, b, 0); |
---|
745 | return b.ToString(); |
---|
746 | } |
---|
747 | |
---|
748 | static void AppendTreeToString(TextAnchorNode node, StringBuilder b, int indent) |
---|
749 | { |
---|
750 | if (node.color == RED) |
---|
751 | b.Append("RED "); |
---|
752 | else |
---|
753 | b.Append("BLACK "); |
---|
754 | b.AppendLine(node.ToString()); |
---|
755 | indent += 2; |
---|
756 | if (node.left != null) { |
---|
757 | b.Append(' ', indent); |
---|
758 | b.Append("L: "); |
---|
759 | AppendTreeToString(node.left, b, indent); |
---|
760 | } |
---|
761 | if (node.right != null) { |
---|
762 | b.Append(' ', indent); |
---|
763 | b.Append("R: "); |
---|
764 | AppendTreeToString(node.right, b, indent); |
---|
765 | } |
---|
766 | } |
---|
767 | #endif |
---|
768 | #endregion |
---|
769 | } |
---|
770 | } |
---|