private void InterpretSourceBufferChange()

in src/Editor/Text/Impl/TextModel/Projection/ProjectionBuffer.cs [1374:1496]


        private void InterpretSourceBufferChange(ITextBuffer changedBuffer,
                                                 ITextChange change,
                                                 List<TextChange> projectedChanges,
                                                 HashSet<SnapshotPoint> urPoints,
                                                 SortedDictionary<int, SpanAdjustment> spanPreAdjustments,
                                                 SortedDictionary<int, SpanAdjustment> spanPostAdjustments, 
                                                 int accumulatedDelta)
        {
            ProjectionSnapshot priorSnapshot = this.currentProjectionSnapshot;
            int sourceChangePosition = change.NewPosition;
            Span deletionSpan = new Span(sourceChangePosition, change.OldLength);
            int insertionCount = change.NewLength;
            int cumulativeLength = 0;

            int spanPosition = 0;

            ITextSnapshot afterSourceSnapshot = changedBuffer.CurrentSnapshot;   
            // todo: consider whether need to use a more precise snapshot. I don't think so, but give it more thought.
            // this is used only for mapping to urPoints.

            // This algorithm does a linear search of source spans in forward order.

            foreach (ITrackingSpan sourceSpan in this.sourceSpans)
            {
                SnapshotSpan priorRawSpan = priorSnapshot.GetSourceSpan(spanPosition);
                // Note: if we switch back to not generating a new snapshot of the projection buffer on every source
                // buffer change, then here we have to be careful to map the priorRawSpan to the current snapshot of the source buffer,
                // since it might be coming from an old snapshot (see e.g. Edit00 unit test)

                if (sourceSpan.TextBuffer == changedBuffer)
                {
                    SpanTrackingMode mode = sourceSpan.TrackingMode;
                    // is there an easy way to handle custom spans here?
                    Span? deletedHere = deletionSpan.Overlap(priorRawSpan);
                    // n.b.: Null span does not overlap with anything
                    if (deletedHere.HasValue && deletedHere.Value.Length > 0)
                    {
                        // part or all of the source span was deleted by the change

                        // compute the position at which the change takes place in the projection buffer 
                        // with respect to its current snapshot
                        int projectedPosition = cumulativeLength + deletedHere.Value.Start - priorRawSpan.Start;

                        Debug.Assert(projectedPosition >= 0 && projectedPosition <= priorSnapshot.Length);

                        StringRebuilder deletedText = TextChange.ChangeOldSubText(change, Math.Max(priorRawSpan.Start.Position - deletionSpan.Start, 0), deletedHere.Value.Length);
                        StringRebuilder insertedText = StringRebuilder.Empty;

                        SnapshotSpan adjustedPriorRawSpan = new SnapshotSpan(priorRawSpan.Snapshot, priorRawSpan.Start, priorRawSpan.Length - deletedText.Length);

                        if (sourceSpan.TrackingMode != SpanTrackingMode.EdgeInclusive && sourceSpan.TrackingMode != SpanTrackingMode.Custom && this.editInProgress)
                        {
                            // the tricky cases. If the deletion touches the edge of the source span, we first explicitly
                            // shrink the span to effect the deletion. If the change is later undone, the source span
                            // will be grown explicitly to encompass the restored text (otherwise we would lose it since
                            // the source span is EdgeExclusive and won't grow on its own).
                            if ((sourceSpan.TrackingMode != SpanTrackingMode.EdgeNegative) && (deletedHere.Value.Start == priorRawSpan.Start))
                            {
                                // A prefix of the source span (or the whole thing) is to be shrunk to effect the deletion.
                                SpanAdjustment adjust = GetAdjustment(spanPreAdjustments, spanPosition);
                                // Create the text change that will be induced by the span adjustment
                                Debug.Assert(adjust.LeadingChange == null); // there can only be one leading change for a particular span
                                adjust.LeadingChange = TextChange.Create(projectedPosition, deletedText, string.Empty, this.currentProjectionSnapshot);
                                Debug.Assert(adjust.LeadingChange.OldEnd <= priorSnapshot.Length);
                                deletedText = StringRebuilder.Empty;
                            }
                            else if ((sourceSpan.TrackingMode != SpanTrackingMode.EdgePositive) && (deletedHere.Value.End == priorRawSpan.End))
                            {
                                // A suffix of the source span is to be shrunk to effect the deletion.
                                SpanAdjustment adjust = GetAdjustment(spanPreAdjustments, spanPosition);
                                // Create the text change that will be induced by the span adjustment
                                Debug.Assert(adjust.TrailingChange == null);
                                adjust.TrailingChange = TextChange.Create(projectedPosition, deletedText, string.Empty, this.currentProjectionSnapshot);
                                Debug.Assert(adjust.TrailingChange.OldEnd <= priorSnapshot.Length);
                                deletedText = StringRebuilder.Empty;
                            }
                        }

                        if (change.NewLength > 0)                             // change includes an insertion
                        {
                            insertedText = InsertionLiesInSpan(afterSourceSnapshot, projectedPosition, spanPosition, adjustedPriorRawSpan, deletionSpan, 
                                                               sourceChangePosition, mode, change, urPoints, spanPostAdjustments, accumulatedDelta);
                            if (insertedText.Length > 0)
                            {
                                // replacement string is inserted here. There can be more than one insertion
                                // per change if custom tracking spans are involved.
                                insertionCount = change.NewLength - insertedText.Length;
                            }
                        }

                        if (deletedText.Length > 0 || insertedText.Length > 0)
                        {
                            TextChange interpretedChange = TextChange.Create(projectedPosition, deletedText, insertedText, this.currentProjectionSnapshot);
                            Debug.Assert(interpretedChange.OldEnd <= priorSnapshot.Length);
                            projectedChanges.Add(interpretedChange);
                        }
                    }
                    else if (insertionCount > 0)
                    {
                        int projectedPosition = cumulativeLength + Math.Max(sourceChangePosition - priorRawSpan.Start, 0);
                        // if the insertion is part of a replacement and the source span in question is edge inclusive, a sourceChangePosition to the
                        // left of the current source span may actually end up being interesting, in which case it would be at the beginning of the span.
                        // If those conditions don't obtain, InsertionLiesInSpan will return false and nobody will be the wiser.
                        int hack = spanPostAdjustments == null ? 0 : spanPostAdjustments.Count;
                        StringRebuilder insertedText = InsertionLiesInSpan(afterSourceSnapshot, projectedPosition, spanPosition, priorRawSpan, deletionSpan,
                                                                  sourceChangePosition, mode, change, urPoints, spanPostAdjustments, accumulatedDelta);
                        if (insertedText.Length > 0)
                        {
                            // a pure insertion into the source span

                            TextChange interpretedChange = TextChange.Create(projectedPosition, string.Empty, insertedText, this.currentProjectionSnapshot);
                            projectedChanges.Add(interpretedChange);
                        }
                        if (spanPostAdjustments != null && spanPostAdjustments.Count != hack)   // ur points should have eliminated the need for the hack
                        {
                            insertionCount = 0;
                        }
                    }
                }
                cumulativeLength += priorRawSpan.Length;
                spanPosition++;
            }
        }