public ElisionMapNode IncorporateChange()

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