internal void OnDeleted(DelayedEvents delayedEvents) { node = null; delayedEvents.DelayedRaise(Deleted, this, EventArgs.Empty); }
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(); }
void DoReplace(int offset, int length, string newText, OffsetChangeMap offsetChangeMap) { if (length == 0 && newText.Length == 0) return; // trying to replace a single character in 'Normal' mode? // for single characters, 'CharacterReplace' mode is equivalent, but more performant // (we don't have to touch the anchorTree at all in 'CharacterReplace' mode) if (length == 1 && newText.Length == 1 && offsetChangeMap == null) offsetChangeMap = OffsetChangeMap.Empty; string removedText = rope.ToString(offset, length); DocumentChangeEventArgs args = new DocumentChangeEventArgs(offset, removedText, newText, offsetChangeMap); // fire DocumentChanging event if (Changing != null) Changing(this, args); undoStack.Push(this, args); cachedText = null; // reset cache of complete document text fireTextChanged = true; DelayedEvents delayedEvents = new DelayedEvents(); lock (lockObject) { // create linked list of checkpoints, if required if (currentCheckpoint != null) { currentCheckpoint = currentCheckpoint.Append(args); } // now update the textBuffer and lineTree if (offset == 0 && length == rope.Length) { // optimize replacing the whole document rope.Clear(); rope.InsertText(0, newText); lineManager.Rebuild(); } else { rope.RemoveRange(offset, length); lineManager.Remove(offset, length); #if DEBUG lineTree.CheckProperties(); #endif rope.InsertText(offset, newText); lineManager.Insert(offset, newText); #if DEBUG lineTree.CheckProperties(); #endif } } // update text anchors if (offsetChangeMap == null) { anchorTree.HandleTextChange(args.CreateSingleChangeMapEntry(), delayedEvents); } else { foreach (OffsetChangeMapEntry entry in offsetChangeMap) { anchorTree.HandleTextChange(entry, delayedEvents); } } // raise delayed events after our data structures are consistent again delayedEvents.RaiseEvents(); // fire DocumentChanged event if (Changed != null) Changed(this, args); }