private FinessedDifferenceCollection(IDifferenceCollection <string> original, IList <Match> newMatches) { LeftSequence = original.LeftSequence; RightSequence = original.RightSequence; Differences = GenerateDifferences(LeftSequence.Count, RightSequence.Count, newMatches); }
private void ConstructChanges() { IDifferenceCollection <SnapshotSpan> diffs = differ.GetDifferences(); List <TextChange> changes = new List <TextChange>(); int pos = this.textPosition; // each difference generates a text change foreach (Difference diff in diffs) { pos += GetMatchSize(differ.DeletedSpans, diff.Before); TextChange change = TextChange.Create(pos, BufferFactoryService.StringRebuilderFromSnapshotSpans(differ.DeletedSpans, diff.Left), BufferFactoryService.StringRebuilderFromSnapshotSpans(differ.InsertedSpans, diff.Right), this.currentSnapshot); changes.Add(change); pos += change.OldLength; } this.normalizedChanges = NormalizedTextChangeCollection.Create(changes); }
public IDifferenceCollection <SnapshotSpan> GetDifferences() { if (this.differences == null) { DecomposeSpans(); var deletedSpans = new List <SnapshotSpan>(); var insertedSpans = new List <SnapshotSpan>(); for (int s = 0; s < deletedSurrogates.Length; ++s) { deletedSpans.AddRange(deletedSurrogates[s]); } for (int s = 0; s < insertedSurrogates.Length; ++s) { insertedSpans.AddRange(insertedSurrogates[s]); } differences = this.diffService.DifferenceSequences(deletedSpans, insertedSpans); } return(differences); }
/// <summary> /// Create a new hierarchical difference collection. /// </summary> /// <param name="differenceCollection">The underlying difference collection for this level /// of the hierarchy.</param> /// <param name="differenceService">The difference service to use for doing the next level of /// differencing</param> /// <param name="options">The options to use for the next level of differencing. /// If <see cref="StringDifferenceOptions.DifferenceType" /> is <c>0</c>, then /// no further differencing will take place.</param> public HierarchicalDifferenceCollection(IDifferenceCollection <string> differenceCollection, ITokenizedStringListInternal left, ITokenizedStringListInternal right, ITextDifferencingService differenceService, StringDifferenceOptions options) { if (differenceCollection == null) { throw new ArgumentNullException(nameof(differenceCollection)); } if (left == null) { throw new ArgumentNullException(nameof(left)); } if (right == null) { throw new ArgumentNullException(nameof(right)); } if (!object.ReferenceEquals(left, differenceCollection.LeftSequence)) { throw new ArgumentException("left must equal differenceCollection.LeftSequence"); } if (!object.ReferenceEquals(right, differenceCollection.RightSequence)) { throw new ArgumentException("right must equal differenceCollection.RightSequence"); } this.left = left; this.right = right; this.differenceCollection = differenceCollection; this.differenceService = differenceService; this.options = options; containedDifferences = new ConcurrentDictionary <int, IHierarchicalDifferenceCollection>(); }
/// <summary> /// Given a set of line difference, finesse the results to produce something likely closer to what the user /// actually changed. This means: /// 1) Transpose diffs forward and backward: /// a)If the user has inserted a new method, say, the diff tends to be off a line or two, e.g. the first few /// lines of the new doc comment may show up as matching the first few lines of the next method's doc comment, or /// vice versa. This slides the diff around until it appears to match up with probable "blocks" in the source file. /// /// b)If the user has ignore (trim) whitespace on, the computed, greedy "match" may be worse than an equally /// correct match with the diff transposed backward. For example, if you have something like: /// /// 1| { /// 2| { /// 3| foo () /// 4| { /// 5| } /// 6| bar () /// 7| { /// 8| } /// 9| } /// 0| } /// /// ...and you delete the two methods, the ignore (trim) whitespace diff may be greedy about matching the /// close braces. Instead of showing lines 3-8 as being deleted, it may show lines 3-4, 6-7, and 8-9 being /// deleted, matching with the close brackets on lines 5 and 7. Even worse, since matches are chosen from /// the right (after) file, the "matches" on lines 5 and 7 will be at the indent level of 9 and 10, shown /// in the middle of the lines 3-8 block that is all indented at the same level. /// </summary> public static IDifferenceCollection <string> FinesseLineDifferences(IDifferenceCollection <string> lineDifferences, IList <string> leftLines, IList <string> rightLines) { if (lineDifferences.Differences.Count == 0) { return(lineDifferences); } // Assume there will be approximately the same number of matches after we're done. List <Match> matches = new List <Match>(lineDifferences.Differences.Count + 1); int lastDiffOffset = 0; for (int i = 0; i < lineDifferences.Differences.Count; i++) { var diff = lineDifferences.Differences[i]; Match before = diff.Before; if (lastDiffOffset != 0 && before != null) { before = new Match(MoveStartPoint(before.Left, lastDiffOffset), MoveStartPoint(before.Right, lastDiffOffset)); } Match after = diff.After; int thisDiffOffset = 0; // #1a) Try to transpose the diff forward while (after != null && after.Length > 0) { // See how far we can transpose this diff forward (towards the end of the file) int offset = TransposeDiffForward(after, diff, lineDifferences.LeftSequence, lineDifferences.RightSequence, leftLines, rightLines); // If we didn't move it at all, we're done. if (offset == 0) { break; } thisDiffOffset += offset; // Offset our after match by the amount the diff chunk moved forwards after = MoveStartPoint(after, offset); // If the diff chunk just ate the entire match sequence, then combine this // diff with the next diff, and use the next diff's After as our new after match. if (after.Length == 0) { if (i < lineDifferences.Differences.Count - 1) { i++; after = lineDifferences.Differences[i].After ?? new Match(new Span(leftLines.Count, 0), new Span(rightLines.Count, 0)); // The offset (for the next round) resets, since we're moving on to the next diff here. thisDiffOffset = 0; } else { after = new Match(new Span(leftLines.Count, 0), new Span(rightLines.Count, 0)); } } // If we didn't have a "before" before, we have one now. if (before == null) { before = new Match(new Span(0, 0), new Span(0, 0)); } // Grow the before match before = MoveEndPoint(before, offset); // Update the current diff to include the lines it ate from the after match (and // possibly the next diff chunk as well, if we detected that the entire match // was eaten above). diff = new Difference(Span.FromBounds(before.Left.End, after.Left.Start), Span.FromBounds(before.Right.End, after.Right.Start), MaybeMatch(before), MaybeMatch(after)); } // #1b) Try to transpose the diff backward. while (before != null && before.Length > 0) { // See how far we can transpose this diff backwards (towards the start of the file) int offset = TransposeDiffBackward(before, diff, lineDifferences.LeftSequence, lineDifferences.RightSequence, leftLines, rightLines); // If we didn't move it at all, we're done. if (offset == 0) { break; } thisDiffOffset += offset; // Offset our before match by the amount the diff chunk moved backwards before = MoveEndPoint(before, offset); // If the diff chunk just ate the entire match sequence, then skip our before Match // back to the previous Match. if (before.Length == 0) { if (matches.Count > 0) { before = matches[matches.Count - 1]; matches.RemoveAt(matches.Count - 1); } else { before = new Match(new Span(0, 0), new Span(0, 0)); } } // If we didn't have an "after" before, we have one now. if (after == null) { after = new Match(new Span(leftLines.Count, 0), new Span(rightLines.Count, 0)); } // Grow the after match after = MoveStartPoint(after, offset); // Update the current diff to include the lines it ate from the before match (and // possibly the previous diff chunk as well, if we detected that the entire match // was eaten above). diff = new Difference(Span.FromBounds(before.Left.End, after.Left.Start), Span.FromBounds(before.Right.End, after.Right.Start), MaybeMatch(before), MaybeMatch(after)); } if (before != null && before.Length > 0) { matches.Add(before); } if (i == lineDifferences.Differences.Count - 1 && after != null && after.Length > 0) { matches.Add(after); } lastDiffOffset = thisDiffOffset; } return(new FinessedDifferenceCollection(lineDifferences, matches)); }
/// <summary> /// Initializes a new instance of <see cref="ProjectionSpanDifference"/>. /// </summary> /// <param name="differenceCollection">The collection of snapshot spans that include the differences.</param> /// <param name="insertedSpans">A read-only collection of the inserted snapshot spans.</param> /// <param name="deletedSpans">A read-only collection of the deleted snapshot spans.</param> public ProjectionSpanDifference(IDifferenceCollection <SnapshotSpan> differenceCollection, ReadOnlyCollection <SnapshotSpan> insertedSpans, ReadOnlyCollection <SnapshotSpan> deletedSpans) { DifferenceCollection = differenceCollection; InsertedSpans = insertedSpans; DeletedSpans = deletedSpans; }