private void ComputeSourceEdits()

in src/Editor/Text/Impl/TextModel/Projection/ProjectionBuffer.cs [1709:1845]


        private void ComputeSourceEdits(FrugalList<TextChange> changes)
        {
            foreach (TextChange change in changes)
            {
                if (change.OldLength > 0 && change.NewLength == 0)
                {
                    // the change is a deletion
                    IList<SnapshotSpan> sourceDeletionSpans = this.currentProjectionSnapshot.MapToSourceSnapshots(new Span(change.NewPosition, change.OldLength));
                    foreach (SnapshotSpan sourceDeletionSpan in sourceDeletionSpans)
                    {
                        DeleteFromSource(sourceDeletionSpan);
                    }
                }

                else if (change.OldLength > 0 && change.NewLength > 0)
                {
                    // the change is a replacement
                    ReadOnlyCollection<SnapshotSpan> allSourceReplacementSpans =
                        this.currentProjectionSnapshot.MapReplacementSpanToSourceSnapshots
                            (new Span(change.OldPosition, change.OldLength), (this.bufferOptions & ProjectionBufferOptions.WritableLiteralSpans) == 0 ? this.literalBuffer : null);

                    //Filter out replacement spans that are read-only (since we couldn't edit them in any case).
                    FrugalList<SnapshotSpan> sourceReplacementSpans = new FrugalList<SnapshotSpan>();
                    foreach (var s in allSourceReplacementSpans)
                    {
                        if (!s.Snapshot.TextBuffer.IsReadOnly(s.Span, true))
                            sourceReplacementSpans.Add(s);
                    }

                    Debug.Assert(sourceReplacementSpans.Count > 0);  // if replacement is on read-only buffers, the read only check will have already caught it

                    if (sourceReplacementSpans.Count == 1)
                    {
                        ReplaceInSource(sourceReplacementSpans[0], change.NewText, 0 + change.MasterChangeOffset);
                    }
                    else
                    {
                        // the replacement hits the boundary of source spans
                        int[] insertionSizes = new int[sourceReplacementSpans.Count];

                        if (this.resolver != null)
                        {
                            SnapshotSpan projectionReplacementSpan = new SnapshotSpan(this.currentProjectionSnapshot, change.OldPosition, change.OldLength);
                            this.resolver.FillInReplacementSizes(projectionReplacementSpan, new ReadOnlyCollection<SnapshotSpan>(sourceReplacementSpans), change.NewText, insertionSizes);
                            if (BufferGroup.Tracing)
                            {
                                Debug.WriteLine(string.Format(System.Globalization.CultureInfo.CurrentCulture,
                                                              "## Seam Replacement @:{0}", projectionReplacementSpan));
                                for (int s = 0; s < sourceReplacementSpans.Count; ++s)
                                {
                                    Debug.WriteLine(string.Format(System.Globalization.CultureInfo.CurrentCulture,
                                                                  "##    {0,4}: {1}", insertionSizes[s], sourceReplacementSpans[s]));
                                }
                                Debug.WriteLine(string.Format(System.Globalization.CultureInfo.CurrentCulture,
                                                              "## Replacement Text:'{0}'", TextUtilities.Escape(change.NewText)));
                            }
                        }
                        insertionSizes[insertionSizes.Length - 1] = int.MaxValue;

                        int pos = 0;
                        for (int i = 0; i < insertionSizes.Length; ++i)
                        {
                            // contend with any old garbage that the client passed back.
                            int insertionSize = Math.Min(insertionSizes[i], change.NewLength - pos);
                            if (insertionSize > 0)
                            {
                                ReplaceInSource(sourceReplacementSpans[i], TextChange.ChangeNewSubstring(change, pos, insertionSize), pos + change.MasterChangeOffset);
                                pos += insertionSize;
                            }
                            else if (sourceReplacementSpans[i].Length > 0)
                            {
                                DeleteFromSource(sourceReplacementSpans[i]);
                            }
                        }
                    }
                }
                else
                {
                    Debug.Assert(change.OldLength == 0 && change.NewLength > 0);
                    // the change is an insertion
                    ReadOnlyCollection<SnapshotPoint> allSourceInsertionPoints =
                        this.currentProjectionSnapshot.MapInsertionPointToSourceSnapshots
                            (change.NewPosition, (this.bufferOptions & ProjectionBufferOptions.WritableLiteralSpans) == 0 ? this.literalBuffer : null);

                    Debug.Assert(allSourceInsertionPoints.Count > 0);  // if insertion point is between two literal spans, the read only check will have already caught it

                    //Filter out replacement spans that are read-only (since we couldn't edit them in any case).
                    FrugalList<SnapshotPoint> sourceInsertionPoints = new FrugalList<SnapshotPoint>();
                    foreach (var p in allSourceInsertionPoints)
                    {
                        if (!p.Snapshot.TextBuffer.IsReadOnly(p.Position, true))
                            sourceInsertionPoints.Add(p);
                    }

                    Debug.Assert(sourceInsertionPoints.Count > 0);  // if insertion point is between only read-only buffers, the read only check will have already caught it

                    if (sourceInsertionPoints.Count == 1)
                    {
                        // the insertion point is unambiguous
                        InsertInSource(sourceInsertionPoints[0], change.NewText, 0 + change.MasterChangeOffset);
                    }
                    else
                    {
                        // the insertion is at the boundary of source spans
                        int[] insertionSizes = new int[sourceInsertionPoints.Count];

                        if (this.resolver != null)
                        {
                            this.resolver.FillInInsertionSizes(new SnapshotPoint(this.currentProjectionSnapshot, change.NewPosition),
                                                               new ReadOnlyCollection<SnapshotPoint>(sourceInsertionPoints), change.NewText, insertionSizes);
                        }

                        // if resolver was not provided, we just use zeros for the insertion sizes, which will push the entire insertion 
                        // into the last slot.
                        insertionSizes[insertionSizes.Length - 1] = int.MaxValue;

                        int pos = 0;
                        for (int i = 0; i < insertionSizes.Length; ++i)
                        {
                            // contend with any old garbage that the client passed back.
                            int size = Math.Min(insertionSizes[i], change.NewLength - pos);
                            if (size > 0)
                            {
                                InsertInSource(sourceInsertionPoints[i], change._newText.GetText(new Span(pos, size)), pos + change.MasterChangeOffset);
                                pos += size;
                                if (pos == change.NewLength)
                                {
                                    break;  // inserted text is used up, whether we've visited all of the insertionSizes or not
                                }
                            }
                        }
                    }
                }
            }
            // defer interpretation of events that will be raised by source buffers as we make these edits
            this.editApplicationInProgress = true;
        }