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