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