private static TextAnchorNode Sibling(TextAnchorNode node) { if (node == node.Parent.Left) { return(node.Parent.Right); } return(node.Parent.Left); }
private static TextAnchorNode Sibling(TextAnchorNode node, TextAnchorNode parentNode) { Debug.Assert(node == null || node.Parent == parentNode); if (node == parentNode.Left) { return(parentNode.Right); } return(parentNode.Left); }
private void InsertAsRight(TextAnchorNode parentNode, TextAnchorNode newNode) { Debug.Assert(parentNode.Right == null); parentNode.Right = newNode; newNode.Parent = parentNode; newNode.Color = Red; UpdateAugmentedData(parentNode); FixTreeOnInsert(newNode); }
private TextAnchorNode FindActualBeginNode(TextAnchorNode node) { // now find the actual beginNode while (node != null && node.Length == 0) { node = node.Predecessor; } // no predecessor = beginNode is first node in tree return(node ?? _root.LeftMost); }
private void InsertBefore(TextAnchorNode node, TextAnchorNode newNode) { if (node.Left == null) { InsertAsLeft(node, newNode); } else { InsertAsRight(node.Left.RightMost, newNode); } }
private void RemoveNode(TextAnchorNode removedNode) { if (removedNode.Left != null && removedNode.Right != null) { // replace removedNode with it's in-order successor TextAnchorNode leftMost = removedNode.Right.LeftMost; RemoveNode(leftMost); // remove leftMost from its current location // and overwrite the removedNode with it ReplaceNode(removedNode, leftMost); leftMost.Left = removedNode.Left; if (leftMost.Left != null) { leftMost.Left.Parent = leftMost; } leftMost.Right = removedNode.Right; if (leftMost.Right != null) { leftMost.Right.Parent = leftMost; } leftMost.Color = removedNode.Color; UpdateAugmentedData(leftMost); if (leftMost.Parent != null) { UpdateAugmentedData(leftMost.Parent); } return; } // now either removedNode.left or removedNode.right is null // get the remaining child TextAnchorNode parentNode = removedNode.Parent; TextAnchorNode childNode = removedNode.Left ?? removedNode.Right; ReplaceNode(removedNode, childNode); if (parentNode != null) { UpdateAugmentedData(parentNode); } if (removedNode.Color == Black) { if (childNode != null && childNode.Color == Red) { childNode.Color = Black; } else { FixTreeOnDelete(childNode, parentNode); } } }
// Sorts the nodes in the range [beginNode, endNode) by MovementType // and inserts the length between the BeforeInsertion and the AfterInsertion nodes. private void PerformInsertText(TextAnchorNode beginNode, TextAnchorNode endNode, int length, bool defaultAnchorMovementIsBeforeInsertion) { Debug.Assert(beginNode != null); // endNode may be null at the end of the anchor tree // now we need to sort the nodes in the range [beginNode, endNode); putting those with // MovementType.BeforeInsertion in front of those with MovementType.AfterInsertion List <TextAnchorNode> beforeInsert = new List <TextAnchorNode>(); //List<TextAnchorNode> afterInsert = new List<TextAnchorNode>(); TextAnchorNode temp = beginNode; while (temp != endNode) { TextAnchor anchor = (TextAnchor)temp.Target; if (anchor == null) { // afterInsert.Add(temp); MarkNodeForDelete(temp); } else if (defaultAnchorMovementIsBeforeInsertion ? anchor.MovementType != AnchorMovementType.AfterInsertion : anchor.MovementType == AnchorMovementType.BeforeInsertion) { beforeInsert.Add(temp); // } else { // afterInsert.Add(temp); } temp = temp.Successor; } // now again go through the range and swap the nodes with those in the beforeInsert list temp = beginNode; foreach (TextAnchorNode node in beforeInsert) { SwapAnchors(node, temp); temp = temp.Successor; } // now temp is pointing to the first node that is afterInsert, // or to endNode, if there is no afterInsert node at the offset // So add the length to temp if (temp == null) { // temp might be null if endNode==null and no afterInserts Debug.Assert(endNode == null); } else { temp.Length += length; UpdateAugmentedData(temp); } }
private void CheckProperties(TextAnchorNode node) { int totalLength = node.Length; if (node.Left != null) { CheckProperties(node.Left); totalLength += node.Left.TotalLength; } if (node.Right != null) { CheckProperties(node.Right); totalLength += node.Right.TotalLength; } Debug.Assert(node.TotalLength == totalLength); }
private static void AppendTreeToString(TextAnchorNode node, StringBuilder b, int indent) { b.Append(node.Color == Red ? "RED " : "BLACK "); b.AppendLine(node.ToString()); indent += 2; if (node.Left != null) { b.Append(' ', indent); b.Append("L: "); AppendTreeToString(node.Left, b, indent); } if (node.Right != null) { b.Append(' ', indent); b.Append("R: "); AppendTreeToString(node.Right, b, indent); } }
private void RotateRight(TextAnchorNode p) { // let q be p's left child TextAnchorNode q = p.Left; Debug.Assert(q != null); Debug.Assert(q.Parent == p); // set q to be the new root ReplaceNode(p, q); // set p's left child to be q's right child p.Left = q.Right; if (p.Left != null) { p.Left.Parent = p; } // set q's right child to be p q.Right = p; p.Parent = q; UpdateAugmentedData(p); UpdateAugmentedData(q); }
private void DeleteMarkedNodes() { CheckProperties(); while (_nodesToDelete.Count > 0) { int pos = _nodesToDelete.Count - 1; TextAnchorNode n = _nodesToDelete[pos]; // combine section of n with the following section TextAnchorNode s = n.Successor; if (s != null) { s.Length += n.Length; } RemoveNode(n); if (s != null) { UpdateAugmentedData(s); } _nodesToDelete.RemoveAt(pos); CheckProperties(); } CheckProperties(); }
private void ReplaceNode(TextAnchorNode replacedNode, TextAnchorNode newNode) { if (replacedNode.Parent == null) { Debug.Assert(replacedNode == _root); _root = newNode; } else { if (replacedNode.Parent.Left == replacedNode) { replacedNode.Parent.Left = newNode; } else { replacedNode.Parent.Right = newNode; } } if (newNode != null) { newNode.Parent = replacedNode.Parent; } replacedNode.Parent = null; }
private static bool GetColor(TextAnchorNode node) { return(node != null && node.Color); }
private void FixTreeOnDelete(TextAnchorNode node, TextAnchorNode parentNode) { Debug.Assert(node == null || node.Parent == parentNode); if (parentNode == null) { return; } // warning: node may be null TextAnchorNode sibling = Sibling(node, parentNode); if (sibling.Color == Red) { parentNode.Color = Red; sibling.Color = Black; if (node == parentNode.Left) { RotateLeft(parentNode); } else { RotateRight(parentNode); } sibling = Sibling(node, parentNode); // update value of sibling after rotation } if (parentNode.Color == Black && sibling.Color == Black && GetColor(sibling.Left) == Black && GetColor(sibling.Right) == Black) { sibling.Color = Red; FixTreeOnDelete(parentNode, parentNode.Parent); return; } if (parentNode.Color == Red && sibling.Color == Black && GetColor(sibling.Left) == Black && GetColor(sibling.Right) == Black) { sibling.Color = Red; parentNode.Color = Black; return; } if (node == parentNode.Left && sibling.Color == Black && GetColor(sibling.Left) == Red && GetColor(sibling.Right) == Black) { sibling.Color = Red; sibling.Left.Color = Black; RotateRight(sibling); } else if (node == parentNode.Right && sibling.Color == Black && GetColor(sibling.Right) == Red && GetColor(sibling.Left) == Black) { sibling.Color = Red; sibling.Right.Color = Black; RotateLeft(sibling); } sibling = Sibling(node, parentNode); // update value of sibling after rotation sibling.Color = parentNode.Color; parentNode.Color = Black; if (node == parentNode.Left) { if (sibling.Right != null) { Debug.Assert(sibling.Right.Color == Red); sibling.Right.Color = Black; } RotateLeft(parentNode); } else { if (sibling.Left != null) { Debug.Assert(sibling.Left.Color == Red); sibling.Left.Color = Black; } RotateRight(parentNode); } }
private void FixTreeOnInsert(TextAnchorNode node) { Debug.Assert(node != null); Debug.Assert(node.Color == Red); Debug.Assert(node.Left == null || node.Left.Color == Black); Debug.Assert(node.Right == null || node.Right.Color == Black); TextAnchorNode parentNode = node.Parent; if (parentNode == null) { // we inserted in the root -> the node must be black // since this is a root node, making the node black increments the number of black nodes // on all paths by one, so it is still the same for all paths. node.Color = Black; return; } if (parentNode.Color == Black) { // if the parent node where we inserted was black, our red node is placed correctly. // since we inserted a red node, the number of black nodes on each path is unchanged // -> the tree is still balanced return; } // parentNode is red, so there is a conflict here! // because the root is black, parentNode is not the root -> there is a grandparent node TextAnchorNode grandparentNode = parentNode.Parent; TextAnchorNode uncleNode = Sibling(parentNode); if (uncleNode != null && uncleNode.Color == Red) { parentNode.Color = Black; uncleNode.Color = Black; grandparentNode.Color = Red; FixTreeOnInsert(grandparentNode); return; } // now we know: parent is red but uncle is black // First rotation: if (node == parentNode.Right && parentNode == grandparentNode.Left) { RotateLeft(parentNode); node = node.Left; } else if (node == parentNode.Left && parentNode == grandparentNode.Right) { RotateRight(parentNode); node = node.Right; } // because node might have changed, reassign variables: // ReSharper disable once PossibleNullReferenceException parentNode = node.Parent; grandparentNode = parentNode.Parent; // Now recolor a bit: parentNode.Color = Black; grandparentNode.Color = Red; // Second rotation: if (node == parentNode.Left && parentNode == grandparentNode.Left) { RotateRight(grandparentNode); } else { // because of the first rotation, this is guaranteed: Debug.Assert(node == parentNode.Right && parentNode == grandparentNode.Right); RotateLeft(grandparentNode); } }
public void HandleTextChange(OffsetChangeMapEntry entry, DelayedEvents delayedEvents) { //Log("HandleTextChange(" + entry + ")"); if (entry.RemovalLength == 0) { // This is a pure insertion. // Unlike a replace with removal, a pure insertion can result in nodes at the same location // to split depending on their MovementType. // Thus, we handle this case on a separate code path // (the code below looks like it does something similar, but it can only split // the set of deletion survivors, not all nodes at an offset) InsertText(entry.Offset, entry.InsertionLength, entry.DefaultAnchorMovementIsBeforeInsertion); return; } // When handling a replacing text change, we need to: // - find all anchors in the deleted segment and delete them / move them to the appropriate // surviving side. // - adjust the segment size between the left and right side int offset = entry.Offset; int remainingRemovalLength = entry.RemovalLength; // if the text change is happening after the last anchor, we don't have to do anything if (_root == null || offset >= _root.TotalLength) { return; } TextAnchorNode node = FindNode(ref offset); TextAnchorNode firstDeletionSurvivor = null; // go forward through the tree and delete all nodes in the removal segment while (node != null && offset + remainingRemovalLength > node.Length) { TextAnchor anchor = (TextAnchor)node.Target; if (anchor != null && (anchor.SurviveDeletion || entry.RemovalNeverCausesAnchorDeletion)) { if (firstDeletionSurvivor == null) { firstDeletionSurvivor = node; } // This node should be deleted, but it wants to survive. // We'll just remove the deleted length segment, so the node will be positioned // in front of the removed segment. remainingRemovalLength -= node.Length - offset; node.Length = offset; offset = 0; UpdateAugmentedData(node); node = node.Successor; } else { // delete node TextAnchorNode s = node.Successor; remainingRemovalLength -= node.Length; RemoveNode(node); // we already deleted the node, don't delete it twice _nodesToDelete.Remove(node); anchor?.OnDeleted(delayedEvents); node = s; } } // 'node' now is the first anchor after the deleted segment. // If there are no anchors after the deleted segment, 'node' is null. // firstDeletionSurvivor was set to the first node surviving deletion. // Because all non-surviving nodes up to 'node' were deleted, the node range // [firstDeletionSurvivor, node) now refers to the set of all deletion survivors. // do the remaining job of the removal if (node != null) { node.Length -= remainingRemovalLength; Debug.Assert(node.Length >= 0); } if (entry.InsertionLength > 0) { // we are performing a replacement if (firstDeletionSurvivor != null) { // We got deletion survivors which need to be split into BeforeInsertion // and AfterInsertion groups. // Take care that we don't regroup everything at offset, but only the deletion // survivors - from firstDeletionSurvivor (inclusive) to node (exclusive). // This ensures that nodes immediately before or after the replaced segment // stay where they are (independent from their MovementType) PerformInsertText(firstDeletionSurvivor, node, entry.InsertionLength, entry.DefaultAnchorMovementIsBeforeInsertion); } else if (node != null) { // No deletion survivors: // just perform the insertion node.Length += entry.InsertionLength; } } if (node != null) { UpdateAugmentedData(node); } DeleteMarkedNodes(); }