in src/Editor/Text/Impl/TextModel/Projection/ElisionMapNode.cs [822:1055]
public ElisionMapNode IncorporateChange(ITextSnapshot beforeSourceSnapshot,
ITextSnapshot afterSourceSnapshot,
ITextSnapshot beforeElisionSnapshot,
int? sourceInsertionPosition,
StringRebuilder newText,
Span? sourceDeletionSpan,
int absoluteSourceOldPosition,
int absoluteSourceNewPosition,
int projectedPrefixSize,
FrugalList<TextChange> projectedChanges,
int incomingAccumulatedDelta,
ref int outgoingAccumulatedDelta,
ref int accumulatedDelete)
{
// All positions and spans in this method are relative to the current subtree except those with 'absolute' in the name
// All positions and spans in coordinate space of source buffer have 'source' in the name
// All positions and spans in coordinate space of elision buffer have 'projected' in the name
Debug.Assert(sourceDeletionSpan == null || sourceDeletionSpan.Value.End <= this.totalSourceSize);
Debug.Assert(sourceInsertionPosition == null || sourceInsertionPosition >= 0);
Debug.Assert(sourceInsertionPosition == null || sourceInsertionPosition <= this.totalSourceSize);
ElisionMapNode newLeft = this.left;
ElisionMapNode newRight = this.right;
int newExposedLineBreakCount = this.exposedLineBreakCount;
int newSourceLineBreakCount = this.sourceLineBreakCount;
int newExposedSize = exposedSize;
int newSourceSize = sourceSize;
bool newLeftmostElision = this.leftmostElision;
int leftTotalSourceSize = LeftTotalSourceSize();
Span leftSourceSpan = new Span(0, leftTotalSourceSize);
Span midExposedSourceSpan = new Span(leftTotalSourceSize, this.exposedSize);
Span midHiddenSourceSpan = new Span(midExposedSourceSpan.End, this.sourceSize - this.exposedSize);
Span rightSourceSpan = new Span(midHiddenSourceSpan.End, this.totalSourceSize - leftTotalSourceSize - this.sourceSize);
#region Incorporate left subtree changes
Span? leftSourceDeletionSpan = leftSourceSpan.Overlap(sourceDeletionSpan);
bool insertionOnLeft = sourceInsertionPosition.HasValue && sourceInsertionPosition.Value < leftTotalSourceSize;
int? leftSourceInsertionPosition = null;
if (insertionOnLeft)
{
if (leftSourceDeletionSpan.HasValue && leftSourceDeletionSpan.Value.End == midExposedSourceSpan.Start)
{
// we have a replacement, and the deleted text in the left subtree touches the left edge of the current node.
// The inserted text needs to be exposed in this node, not possibly swallowed into a hidden part of the left node.
// Leave the leftSourceInsertionPosition equal to null so the left subtree doesn't stick it at the end of itself
insertionOnLeft = false;
sourceInsertionPosition = midExposedSourceSpan.Start;
Debug.Assert(!this.leftmostElision); // there is no node to the left of a leftmost elision node
}
else
{
leftSourceInsertionPosition = sourceInsertionPosition;
}
}
if (insertionOnLeft || leftSourceDeletionSpan.HasValue)
{
// insertion (if any) and start of deletion (if any) is in the left subtree
newLeft = this.left.IncorporateChange(beforeSourceSnapshot : beforeSourceSnapshot,
afterSourceSnapshot : afterSourceSnapshot,
beforeElisionSnapshot : beforeElisionSnapshot,
sourceInsertionPosition : leftSourceInsertionPosition,
newText : newText,
sourceDeletionSpan : leftSourceSpan.Overlap(sourceDeletionSpan),
absoluteSourceOldPosition : absoluteSourceOldPosition,
absoluteSourceNewPosition : absoluteSourceNewPosition,
projectedPrefixSize : projectedPrefixSize,
projectedChanges : projectedChanges,
incomingAccumulatedDelta : incomingAccumulatedDelta,
outgoingAccumulatedDelta : ref outgoingAccumulatedDelta,
accumulatedDelete : ref accumulatedDelete);
}
#endregion
#region Incorporate current subtree changes
Span? exposedSourceDeletion = midExposedSourceSpan.Overlap(sourceDeletionSpan);
Span? hiddenSourceDeletion = midHiddenSourceSpan.Overlap(sourceDeletionSpan);
// Insertion and deletion are handled independently and recombined by text change normalization
// Insertion
// In each of the three cases below, if an insertion belongs in this node, set sourceInsertionPosition to
// null, preventing it from also being performed in the right subtree when the current node has size zero.
if (sourceInsertionPosition.HasValue)
{
// special case for leftmostElision node
if (this.leftmostElision)
{
// insertion into the exposed part of this node isn't possible
Debug.Assert(this.left == null);
Debug.Assert(this.exposedSize == 0);
Debug.Assert(leftTotalSourceSize == 0);
if (sourceInsertionPosition.Value <= this.sourceSize)
{
// insertion into hidden prefix of elision buffer
newSourceSize += newText.Length;
int incrementalLineCount;
ComputeIncrementalLineCountForHiddenInsertion(afterSourceSnapshot, absoluteSourceNewPosition, newText, out incrementalLineCount);
newSourceLineBreakCount += incrementalLineCount;
sourceInsertionPosition = null;
}
}
else if ((leftTotalSourceSize <= sourceInsertionPosition.Value) &&
(sourceInsertionPosition.Value <= leftTotalSourceSize + this.exposedSize))
{
// insertion (if any) is in the exposed part of the current node
newExposedSize += newText.Length;
newSourceSize += newText.Length;
int projectedPosition = projectedPrefixSize + sourceInsertionPosition.Value - LeftTotalHiddenSize() - incomingAccumulatedDelta;
// effects on line count are computed based on local information. Interactions with adjacent segments
// must be handled at a higher level (undone).
int deletionLength = 0;
if (exposedSourceDeletion.HasValue)
{
Debug.Assert(exposedSourceDeletion.Value.Start == sourceInsertionPosition.Value);
deletionLength = exposedSourceDeletion.Value.Length;
}
char? predChar = sourceInsertionPosition.Value > midExposedSourceSpan.Start
? afterSourceSnapshot[absoluteSourceNewPosition - 1]
: (char?)null; // insertion is at start of segment
char? succChar = sourceInsertionPosition.Value + deletionLength < midExposedSourceSpan.End
? afterSourceSnapshot[absoluteSourceNewPosition + newText.Length]
: (char?)null; // insertion is at end of segment
LineBreakBoundaryConditions boundaryConditions;
int incrementalLineCount;
ComputeIncrementalLineCountForExposedInsertion(predChar, succChar, newText, out incrementalLineCount, out boundaryConditions);
newExposedLineBreakCount += incrementalLineCount;
newSourceLineBreakCount += incrementalLineCount;
TextChange change = new TextChange(projectedPosition, StringRebuilder.Empty, newText, boundaryConditions);
projectedChanges.Add(change);
outgoingAccumulatedDelta += change.Delta;
sourceInsertionPosition = null;
}
else if ((leftTotalSourceSize + this.exposedSize < sourceInsertionPosition.Value) &&
((sourceInsertionPosition.Value < leftTotalSourceSize + this.sourceSize) || (this.right == null)))
{
// insertion (if any) is in the hidden part of the current node
// Unless...we are also deleting from the point of the insertion through the end of the node, in which case
// the insertion should go at the beginning of the next segment. If there is no right subtree, this case will
// have been handled on the way down by some ancestor node.
if (this.right != null && (hiddenSourceDeletion.HasValue && hiddenSourceDeletion.Value.End == midHiddenSourceSpan.End))
{
sourceInsertionPosition = midHiddenSourceSpan.End;
}
else
{
newSourceSize += newText.Length;
int incrementalLineCount;
ComputeIncrementalLineCountForHiddenInsertion(afterSourceSnapshot, absoluteSourceNewPosition, newText, out incrementalLineCount);
newSourceLineBreakCount += incrementalLineCount;
sourceInsertionPosition = null;
}
}
}
// Deletion of exposed text
if (exposedSourceDeletion.HasValue)
{
newExposedSize -= exposedSourceDeletion.Value.Length;
newSourceSize -= exposedSourceDeletion.Value.Length;
int projectedDeletionPosition = projectedPrefixSize + exposedSourceDeletion.Value.Start - LeftTotalHiddenSize();
int sourceDeletionSegmentPosition = absoluteSourceOldPosition - accumulatedDelete;
StringRebuilder exposedDeletionText =
BufferFactoryService.StringRebuilderFromSnapshotAndSpan(beforeSourceSnapshot, new Span(sourceDeletionSegmentPosition, exposedSourceDeletion.Value.Length));
LineBreakBoundaryConditions boundaryConditions;
int incrementalLineCount;
ComputeIncrementalLineCountForDeletion(beforeElisionSnapshot, new Span(projectedDeletionPosition - incomingAccumulatedDelta, exposedSourceDeletion.Value.Length), exposedDeletionText, out incrementalLineCount, out boundaryConditions);
newExposedLineBreakCount += incrementalLineCount;
newSourceLineBreakCount += incrementalLineCount;
TextChange change = new TextChange(projectedDeletionPosition - incomingAccumulatedDelta, exposedDeletionText, StringRebuilder.Empty, boundaryConditions);
projectedChanges.Add(change);
outgoingAccumulatedDelta += change.Delta;
accumulatedDelete += change.Delta;
}
// Deletion of hidden text
if (hiddenSourceDeletion.HasValue)
{
int sourceDeletionSegmentPosition = absoluteSourceOldPosition - accumulatedDelete;
StringRebuilder hiddenDeletionText =
BufferFactoryService.StringRebuilderFromSnapshotAndSpan(beforeSourceSnapshot, new Span(sourceDeletionSegmentPosition, hiddenSourceDeletion.Value.Length));
LineBreakBoundaryConditions dontCare;
int incrementalLineCount;
ComputeIncrementalLineCountForDeletion(beforeSourceSnapshot, new Span(sourceDeletionSegmentPosition, hiddenSourceDeletion.Value.Length), hiddenDeletionText, out incrementalLineCount, out dontCare);
newSourceLineBreakCount += incrementalLineCount;
newSourceSize -= hiddenSourceDeletion.Value.Length;
accumulatedDelete -= hiddenSourceDeletion.Value.Length;
}
#endregion
#region Incorporate right subtree changes
Span? rightSourceDeletionSpan = rightSourceSpan.Overlap(sourceDeletionSpan);
bool insertionOnRight = (sourceInsertionPosition.HasValue) && (this.right != null) && (leftTotalSourceSize + this.sourceSize <= sourceInsertionPosition.Value);
if (rightSourceDeletionSpan.HasValue || insertionOnRight)
{
// insertion (if any) or part of deletion (if any) is in the right subtree
newRight = this.right.IncorporateChange(beforeSourceSnapshot : beforeSourceSnapshot,
afterSourceSnapshot : afterSourceSnapshot,
beforeElisionSnapshot : beforeElisionSnapshot,
sourceInsertionPosition : insertionOnRight ? sourceInsertionPosition.Value - leftTotalSourceSize - this.sourceSize : (int?)null,
newText : insertionOnRight ? newText : StringRebuilder.Empty,
sourceDeletionSpan : rightSourceDeletionSpan.HasValue
? new Span(rightSourceDeletionSpan.Value.Start - (leftTotalSourceSize + this.sourceSize),
rightSourceDeletionSpan.Value.Length)
: (Span?)null,
absoluteSourceOldPosition : absoluteSourceOldPosition,
absoluteSourceNewPosition : absoluteSourceNewPosition,
projectedPrefixSize : projectedPrefixSize + LeftTotalExposedSize() + this.exposedSize,
projectedChanges : projectedChanges,
incomingAccumulatedDelta : incomingAccumulatedDelta,
outgoingAccumulatedDelta : ref outgoingAccumulatedDelta,
accumulatedDelete : ref accumulatedDelete);
}
#endregion
return new ElisionMapNode(newExposedSize, newSourceSize, newExposedLineBreakCount, newSourceLineBreakCount, newLeft, newRight, newLeftmostElision);
}