internal TextContentChangedEventArgs ApplyReload(StringRebuilder newContent, EditOptions editOptions, object editTag) { // we construct a normalized change list where the inserted text is a reference string that // points "forward" to the next snapshot and whose deleted text is a reference string that points // "backward" to the prior snapshot. This pins both snapshots in memory but that's better than materializing // giant strings, and when (?) we have paging text storage, memory requirements will be minimal. TextVersion newVersion = new TextVersion(this, this.currentVersion.VersionNumber + 1, this.currentVersion.VersionNumber + 1, newContent.Length); ITextSnapshot oldSnapshot = this.currentSnapshot; TextSnapshot newSnapshot = new TextSnapshot(this, newVersion, newContent); ReferenceChangeString oldText = new ReferenceChangeString(new SnapshotSpan(oldSnapshot, 0, oldSnapshot.Length)); ReferenceChangeString newText = new ReferenceChangeString(new SnapshotSpan(newSnapshot, 0, newSnapshot.Length)); TextChange change = new TextChange(oldPosition: 0, oldText: oldText, newText: newText, currentSnapshot: oldSnapshot); this.currentVersion.AddNextVersion(NormalizedTextChangeCollection.Create(new FrugalList <TextChange>() { change }, editOptions.ComputeMinimalChange ? (StringDifferenceOptions?)editOptions.DifferenceOptions : null, this.textDifferencingService, oldSnapshot, newSnapshot), newVersion); this.builder = newContent; this.currentVersion = newVersion; this.currentSnapshot = newSnapshot; return(new TextContentChangedEventArgs(oldSnapshot, newSnapshot, editOptions, editTag)); }
public static ChangeString CreateChangeString(IList <SnapshotSpan> sourceSpans, Span selected) { if (selected.Length == 0) { return(ChangeString.EmptyChangeString); } else if (selected.Length == 1) { return(ReferenceChangeString.CreateChangeString(sourceSpans[selected.Start])); } else { StringRebuilder builder = SimpleStringRebuilder.Create(String.Empty); for (int i = 0; (i < selected.Length); ++i) { builder = builder.Insert(builder.Length, BufferFactoryService.StringRebuilderFromSnapshotSpan(sourceSpans[selected.Start + i])); } return(ReferenceChangeString.CreateChangeString(builder)); } }
/// <summary> /// Normalize a sequence of changes that were all applied consecutively to the same version of a buffer. Positions of the /// normalized changes are adjusted to account for other changes that occur at lower indexes in the /// buffer, and changes are sorted and merged if possible. /// </summary> /// <param name="changes">The changes to normalize.</param> /// <param name="differenceOptions">The options to use for minimal differencing, if any.</param> /// <param name="before">Text snapshot before the change (can be null).</param> /// <param name="after">Text snapshot after the change (can be null).</param> /// <returns>A (possibly empty) list of changes, sorted by Position, with adjacent and overlapping changes combined /// where possible.</returns> /// <exception cref="ArgumentNullException"><paramref name="changes"/> is null.</exception> private static IList <ITextChange> Normalize(IList <TextChange> changes, StringDifferenceOptions?differenceOptions, ITextDifferencingService textDifferencingService, ITextSnapshot before, ITextSnapshot after) { if (changes.Count == 1 && differenceOptions == null) { // By far the most common path // If we are computing minimal changes, we need to go through the // algorithm anyway, since this change may be split up into many // smaller changes FrugalList <ITextChange> singleResult = new FrugalList <ITextChange>(); singleResult.Add(changes[0]); return(singleResult); } else if (changes.Count == 0) { return(new FrugalList <ITextChange>()); } TextChange[] work = TextUtilities.StableSort(changes, TextChange.Compare); // work is now sorted by increasing Position int accumulatedDelta = 0; int a = 0; int b = 1; while (b < work.Length) { // examine a pair of changes and attempt to combine them TextChange aChange = work[a]; TextChange bChange = work[b]; int gap = bChange.OldPosition - aChange.OldEnd; if (gap > 0) { // independent changes aChange.NewPosition = aChange.OldPosition + accumulatedDelta; accumulatedDelta += aChange.Delta; a = b; b = a + 1; } else { // dependent changes. merge all adjacent dependent changes into a single change in one pass, // to avoid expensive pairwise concatenations. // // Use StringRebuilders (which allow strings to be concatenated without creating copies of the strings) to assemble the // pieces and then convert the rebuilders to a ReferenceChangeString (which wraps a StringRebuilder) at the end. StringRebuilder newRebuilder = aChange._newText.Content; StringRebuilder oldRebuilder = aChange._oldText.Content; int aChangeIncrementalDeletions = 0; do { newRebuilder = newRebuilder.Append(bChange._newText.Content); if (gap == 0) { // abutting deletions oldRebuilder = oldRebuilder.Append(bChange._oldText.Content); aChangeIncrementalDeletions += bChange.OldLength; aChange.LineBreakBoundaryConditions = (aChange.LineBreakBoundaryConditions & LineBreakBoundaryConditions.PrecedingReturn) | (bChange.LineBreakBoundaryConditions & LineBreakBoundaryConditions.SucceedingNewline); } else { // overlapping deletions if (aChange.OldEnd + aChangeIncrementalDeletions < bChange.OldEnd) { int overlap = aChange.OldEnd + aChangeIncrementalDeletions - bChange.OldPosition; oldRebuilder = oldRebuilder.Append(bChange._oldText.Content.Substring(Span.FromBounds(overlap, bChange._oldText.Length))); aChangeIncrementalDeletions += (bChange.OldLength - overlap); aChange.LineBreakBoundaryConditions = (aChange.LineBreakBoundaryConditions & LineBreakBoundaryConditions.PrecedingReturn) | (bChange.LineBreakBoundaryConditions & LineBreakBoundaryConditions.SucceedingNewline); } // else bChange deletion subsumed by aChange deletion } work[b] = null; b++; if (b == work.Length) { break; } bChange = work[b]; gap = bChange.OldPosition - aChange.OldEnd - aChangeIncrementalDeletions; } while (gap <= 0); work[a]._oldText = ReferenceChangeString.CreateChangeString(oldRebuilder); work[a]._newText = ReferenceChangeString.CreateChangeString(newRebuilder); if (b < work.Length) { aChange.NewPosition = aChange.OldPosition + accumulatedDelta; accumulatedDelta += aChange.Delta; a = b; b = a + 1; } } } // a points to the last surviving change work[a].NewPosition = work[a].OldPosition + accumulatedDelta; List <ITextChange> result = new List <ITextChange>(); if (differenceOptions.HasValue) { if (textDifferencingService == null) { throw new ArgumentNullException("stringDifferenceUtility"); } foreach (TextChange change in work) { if (change == null) { continue; } // Make sure this is a replacement if (change.OldLength == 0 || change.NewLength == 0) { result.Add(change); continue; } if (change.OldLength >= TextModelOptions.DiffSizeThreshold || change.NewLength >= TextModelOptions.DiffSizeThreshold) { change.IsOpaque = true; result.Add(change); continue; // too big to even attempt a diff. This is aimed at the reload-a-giant-file scenario // where OOM during diff is a distinct possibility. } // Make sure to turn off IgnoreTrimWhiteSpace, since that doesn't make sense in // the context of a minimal edit StringDifferenceOptions options = new StringDifferenceOptions(differenceOptions.Value); options.IgnoreTrimWhiteSpace = false; IHierarchicalDifferenceCollection diffs; if (before != null && after != null) { // Don't materialize the strings when we know the before and after snapshots. They might be really huge and cause OOM. // We will take this path in the file reload case. diffs = textDifferencingService.DiffSnapshotSpans(new SnapshotSpan(before, change.OldSpan), new SnapshotSpan(after, change.NewSpan), options); } else { // We need to evaluate the old and new text for the differencing service string oldText = change.OldText; string newText = change.NewText; if (oldText == newText) { // This change simply evaporates. This case occurs frequently in Venus and it is much // better to short circuit it here than to fire up the differencing engine. continue; } diffs = textDifferencingService.DiffStrings(oldText, newText, options); } // Keep track of deltas for the "new" position, for sanity check int delta = 0; // Add all the changes from the difference collection result.AddRange(GetChangesFromDifferenceCollection(ref delta, change, change._oldText, change._newText, diffs)); // Sanity check // If delta != 0, then we've constructed asymmetrical insertions and // deletions, which should be impossible Debug.Assert(delta == change.Delta, "Minimal edit delta should be equal to replaced text change's delta."); } } // If we aren't computing minimal changes, then copy over the non-null changes else { foreach (TextChange change in work) { if (change != null) { result.Add(change); } } } return(result); }
public static ChangeString CreateChangeString(SnapshotSpan span) { return(ReferenceChangeString.CreateChangeString(span.Snapshot, span.Span)); }
private ChangeString DeletionChangeString(Span deleteSpan) { return(ReferenceChangeString.CreateChangeString(this.originSnapshot, deleteSpan)); }