void InsertAsRight(TextAnchorNode parentNode, TextAnchorNode newNode) { Debug.Assert(parentNode.right == null); parentNode.right = newNode; newNode.parent = parentNode; newNode.color = RED; UpdateAugmentedData(parentNode); FixTreeOnInsert(newNode); }
static TextAnchorNode Sibling(TextAnchorNode node) { if (node == node.parent.left) { return(node.parent.right); } else { return(node.parent.left); } }
void InsertBefore(TextAnchorNode node, TextAnchorNode newNode) { if (node.left == null) { InsertAsLeft(node, newNode); } else { InsertAsRight(node.left.RightMost, newNode); } }
static TextAnchorNode Sibling(TextAnchorNode node, TextAnchorNode parentNode) { Debug.Assert(node == null || node.parent == parentNode); if (node == parentNode.left) { return(parentNode.right); } else { return(parentNode.left); } }
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); } } }
TextAnchorNode FindActualBeginNode(TextAnchorNode node) { // now find the actual beginNode while (node != null && node.length == 0) { node = node.Predecessor; } if (node == null) { // no predecessor = beginNode is first node in tree node = root.LeftMost; } return(node); }
// Sorts the nodes in the range [beginNode, endNode) by MovementType // and inserts the length between the BeforeInsertion and the AfterInsertion nodes. 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); } }
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); }
/// <summary> /// Finds the node at the specified offset. /// After the method has run, offset is relative to the beginning of the returned node. /// </summary> TextAnchorNode FindNode(ref int offset) { TextAnchorNode n = root; while (true) { if (n.left != null) { if (offset < n.left.totalLength) { n = n.left; // descend into left subtree continue; } else { offset -= n.left.totalLength; // skip left subtree } } if (!n.IsAlive) { MarkNodeForDelete(n); } if (offset < n.length) { return(n); // found correct node } else { offset -= n.length; // skip this node } if (n.right != null) { n = n.right; // descend into right subtree } else { // didn't find any node containing the offset return(null); } } }
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); }
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(); }
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; }
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); } }
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); if (anchor != null) { 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(); }
static bool GetColor(TextAnchorNode node) { return(node != null ? node.color : BLACK); }
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: 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); } }
internal void OnDeleted(DelayedEvents delayedEvents) { node = null; delayedEvents.DelayedRaise(Deleted, this, EventArgs.Empty); }