/// <summary> /// Construct an elision map given the size of the source buffer and the set of exposed spans. /// </summary> public ElisionMap(ITextSnapshot sourceSnapshot, NormalizedSpanCollection exposedSpans) { this.spanCount = exposedSpans.Count; if (exposedSpans.Count == 0) { // everything is hidden: one leaf node this.root = new ElisionMapNode(0, sourceSnapshot.Length, 0, sourceSnapshot.LineCount - 1, true); } else { // build a nicely balanced tree // calculate all the line numbers we'll need in advance int[] lineNumbers = new int[exposedSpans.Count * 2 + 1]; for (int es = 0; es < exposedSpans.Count; ++es) { lineNumbers[es * 2] = sourceSnapshot.GetLineNumberFromPosition(exposedSpans[es].Start); lineNumbers[es * 2 + 1] = sourceSnapshot.GetLineNumberFromPosition(exposedSpans[es].End); } lineNumbers[exposedSpans.Count * 2] = sourceSnapshot.LineCount - 1; this.root = Build(new SnapshotSpan(sourceSnapshot, 0, sourceSnapshot.Length), exposedSpans, lineNumbers, new Span(0, exposedSpans.Count)); } if (BufferGroup.Tracing) { root.Dump(0); } }
public ElisionMap IncorporateChanges(INormalizedTextChangeCollection sourceChanges, FrugalList <TextChange> projectedChanges, ITextSnapshot beforeSourceSnapshot, ITextSnapshot sourceSnapshot, ITextSnapshot beforeElisionSnapshot) { ElisionMapNode newRoot = this.root; int accumulatedProjectedDelta = 0; foreach (ITextChange sourceChange in sourceChanges) { int accumulatedDelete = 0; int incrementalAccumulatedProjectedDelta = 0; ChangeString newText; TextChange concreteSourceChange = sourceChange as TextChange; if (concreteSourceChange != null) { newText = concreteSourceChange._newText; } else { // handle mocks in tests newText = new LiteralChangeString(sourceChange.NewText); } newRoot = newRoot.IncorporateChange(beforeSourceSnapshot: beforeSourceSnapshot, afterSourceSnapshot: sourceSnapshot, beforeElisionSnapshot: beforeElisionSnapshot, sourceInsertionPosition: sourceChange.NewLength > 0 ? sourceChange.NewPosition : (int?)null, newText: newText, sourceDeletionSpan: new Span(sourceChange.NewPosition, sourceChange.OldLength), absoluteSourceOldPosition: sourceChange.OldPosition, absoluteSourceNewPosition: sourceChange.NewPosition, projectedPrefixSize: 0, projectedChanges: projectedChanges, incomingAccumulatedDelta: accumulatedProjectedDelta, outgoingAccumulatedDelta: ref incrementalAccumulatedProjectedDelta, accumulatedDelete: ref accumulatedDelete); accumulatedProjectedDelta += incrementalAccumulatedProjectedDelta; } if (newRoot.TotalSourceSize != sourceSnapshot.Length) { Debug.Fail(String.Format(System.Globalization.CultureInfo.InvariantCulture, "Change incorporation length inconsistency. Elision:{0} Source:{1}", newRoot.TotalSourceSize, sourceSnapshot.Length)); throw new InvalidOperationException(Strings.InvalidLengthCalculation); } if (newRoot.TotalSourceLineBreakCount + 1 != sourceSnapshot.LineCount) { Debug.Fail(String.Format(System.Globalization.CultureInfo.InvariantCulture, "Change incorporation line count inconsistency. Elision:{0} Source:{1}", newRoot.TotalSourceLineBreakCount + 1, sourceSnapshot.LineCount)); throw new InvalidOperationException(Strings.InvalidLineCountCalculation); } return(new ElisionMap(newRoot, this.spanCount)); }
/// <summary> /// Recursively build span tree. /// </summary> /// <param name="sourceSpan">SnapshotSpan over the source segment covered by this subtree, including both exposed and hidden text.</param> /// <param name="exposedSpans">Set of exposed spans for the entire buffer.</param> /// <param name="lineNumbers">Precomputed line numbers at all seams.</param> /// <param name="slice">The slice of exposed spans in this subtree.</param> /// <returns></returns> private ElisionMapNode Build(SnapshotSpan sourceSpan, NormalizedSpanCollection exposedSpans, int[] lineNumbers, Span slice) { int mid = slice.Start + (slice.Length / 2); Span midExposedSpan = exposedSpans[mid]; Span leftSlice = Span.FromBounds(slice.Start, mid); ElisionMapNode left; Span leftSpan; if (leftSlice.Length > 0) { leftSpan = Span.FromBounds(sourceSpan.Start, midExposedSpan.Start); left = Build(new SnapshotSpan(sourceSpan.Snapshot, leftSpan), exposedSpans, lineNumbers, leftSlice); Debug.Assert(left.TotalSourceSize == leftSpan.Length); } else if (slice.Start == 0 && midExposedSpan.Start != 0) { Debug.Assert(sourceSpan.Start == 0); leftSpan = Span.FromBounds(0, midExposedSpan.Start); // the beginning of the buffer is elided. Do the special case of the first // node in the tree having an exposed size of zero. // TODO: figure this out in advance so we don't screw up the balance of the tree left = new ElisionMapNode(0, leftSpan.Length, 0, TextUtilities.ScanForLineCount(sourceSpan.Snapshot.GetText(leftSpan)), true); } else { leftSpan = new Span(midExposedSpan.Start, 0); left = null; } Span rightSlice = Span.FromBounds(mid + 1, slice.End); ElisionMapNode right; Span rightSpan; if (rightSlice.Length > 0) { rightSpan = Span.FromBounds(exposedSpans[mid + 1].Start, sourceSpan.End); right = Build(new SnapshotSpan(sourceSpan.Snapshot, rightSpan), exposedSpans, lineNumbers, rightSlice); Debug.Assert(right.TotalSourceSize == rightSpan.Length); } else { rightSpan = new Span(sourceSpan.End, 0); right = null; } Span midHiddenSpan = Span.FromBounds(midExposedSpan.End, rightSpan.Start); ITextSnapshot sourceSnapshot = sourceSpan.Snapshot; int startLineNumber = lineNumbers[2 * mid]; int endExposedLineNumber = lineNumbers[2 * mid + 1]; int endSourceLineNumber = lineNumbers[2 * mid + 2]; int exposedLineBreakCount = endExposedLineNumber - startLineNumber; int hiddenLineBreakCount = endSourceLineNumber - endExposedLineNumber; return(new ElisionMapNode(midExposedSpan.Length, sourceSpan.Length - (leftSpan.Length + rightSpan.Length), exposedLineBreakCount, exposedLineBreakCount + hiddenLineBreakCount, left, right, false)); }
private ElisionMap(ElisionMapNode root, int spanCount) { this.root = root; this.spanCount = spanCount; }