/// <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;
            }
        }