/// <summary> Updates the ranges as a result of a insert text modification. </summary> private void UpdateFromInsert(RangeModification changeEvent) { var findings = GetAffectedNodes(new Range(changeEvent.Index, changeEvent.Index)); bool didAdjustedOffset = false; foreach (var nodeAndRange in findings.GetAffectedFragments()) { var currentFragmentNode = nodeAndRange.Node; var currentRange = nodeAndRange.Range; if (currentRange.ContainsExclusive(changeEvent.Index)) { // if the change is in the middle, extend the range to include the newly inserted text. currentFragmentNode.Length += changeEvent.NumberOfItems; } else if (!didAdjustedOffset && currentRange.StartIndex == changeEvent.Index) { didAdjustedOffset = true; currentFragmentNode.RelativeOffsetSinceLastNode += changeEvent.NumberOfItems; } ProcessForOwner(currentFragmentNode.Descriptor.ExpandBehavior, currentFragmentNode, true); } if (!didAdjustedOffset && findings.RemainingNode != null) { // we need to offset the remaining nodes, so make sure this offset is adjusted findings.RemainingNode.Value.RelativeOffsetSinceLastNode += changeEvent.NumberOfItems; } }
/// <summary> Updates the collection based on the given text-changed event. </summary> /// <param name="changeEvent"> The event for which the collection of entries should be updated. </param> internal void UpdateFromEvent(RangeModification changeEvent) { if (changeEvent.WasAdded) { UpdateFromInsert(changeEvent); } else { UpdateFromDelete(changeEvent); } // todo update dirty things ChangeIndex = ChangeIndex.Next(); }
/// <summary> Updates the ranges as a result of a delete text modification. </summary> private void UpdateFromDelete(RangeModification changeEvent) { // $Simpler?$: NOTE deletion is so much more complicated than insert. It's possible that this // implementation is just overly complicated, so we may want to revisit this at some point. var deletionRange = new Range(changeEvent.Index, changeEvent.Index + changeEvent.NumberOfItems); var findings = GetAffectedNodes(deletionRange); bool didAdjustedOffset = false; int adjustmentsThusFar = 0; foreach (var nodeAndRange in findings.GetAffectedFragments()) { var currentFragmentNode = nodeAndRange.Node; var currentRange = nodeAndRange.Range; // originalRange: the range before all previous edits to preceding ranges // // the algorithm didn't originally use the originalRange, but after finding the edge cases that // the UTs exposed, it was determined that it was easier to reason about. However, it's // possible that a simpler algorithm exists (see $Simpler?$ above) var originalRange = new Range(currentRange.StartIndex + adjustmentsThusFar, currentRange.EndIndex + adjustmentsThusFar); // if the deletion range is before the markup range, we need to both reduce the length and shift it over if (deletionRange.StartIndex <= originalRange.StartIndex) { // reduce the length by the number of characters after our start index int reduceLengthBy = Math.Min(deletionRange.EndIndex - originalRange.StartIndex, originalRange.Length); // $SafeReduction$: because we're using the original range without taking into account the modifications // of ranges before this one, it's possible that we get a negative # to delete, which is not // what we want, so normalize it. reduceLengthBy = Math.Max(reduceLengthBy, 0); currentFragmentNode.Length -= reduceLengthBy; int startIndexOffset = originalRange.StartIndex - deletionRange.StartIndex; // again, we're using the original range, so we need to adjust our value by the offset that everyone // has already offset by startIndexOffset = startIndexOffset - adjustmentsThusFar; currentFragmentNode.RelativeOffsetSinceLastNode -= startIndexOffset; adjustmentsThusFar += startIndexOffset; didAdjustedOffset = true; } else /* deletion range starts inside our range */ { int numberOfCharactersToRemove = Math.Min(originalRange.EndIndex - deletionRange.StartIndex, deletionRange.Length); // see comment ~20 lines up ($SafeReduction$) numberOfCharactersToRemove = Math.Max(numberOfCharactersToRemove, 0); currentFragmentNode.Length -= numberOfCharactersToRemove; } var associatedDescriptor = currentFragmentNode.Descriptor; var behavior = currentRange.Length == 0 ? associatedDescriptor.ShrinkToEmptyBehavior : associatedDescriptor.ShrinkBehavior; ProcessForOwner(behavior, currentFragmentNode, false); } // this would only occur if nothing above adjusted the offset, in which case all of the nodes // are after the deletion. if (!didAdjustedOffset && findings.RemainingNode != null) { // we need to offset the remaining nodes, so make sure this offset is adjusted findings.RemainingNode.Value.RelativeOffsetSinceLastNode -= changeEvent.NumberOfItems; } }