public async Task <IEnumerable <TextChange> > GetTextChangesAsync(Document oldDocument, Document newDocument, CancellationToken cancellationToken) { var oldText = await oldDocument.GetTextAsync(cancellationToken).ConfigureAwait(false); var newText = await newDocument.GetTextAsync(cancellationToken).ConfigureAwait(false); var diffService = _differenceSelectorService.GetTextDifferencingService(oldDocument.Project.LanguageServices.GetService <IContentTypeLanguageService>().GetDefaultContentType()) ?? _differenceSelectorService.DefaultTextDifferencingService; var differenceOptions = new StringDifferenceOptions() { DifferenceType = StringDifferenceTypes.Word }; var oldTextSnapshot = oldText.FindCorrespondingEditorTextSnapshot(); var newTextSnapshot = newText.FindCorrespondingEditorTextSnapshot(); var useSnapshots = oldTextSnapshot != null && newTextSnapshot != null; var diffResult = useSnapshots ? diffService.DiffSnapshotSpans(oldTextSnapshot.GetFullSpan(), newTextSnapshot.GetFullSpan(), differenceOptions) : diffService.DiffStrings(oldText.ToString(), newText.ToString(), differenceOptions); return(diffResult.Differences.Select(d => new TextChange( diffResult.LeftDecomposition.GetSpanInOriginal(d.Left).ToTextSpan(), newText.GetSubText(diffResult.RightDecomposition.GetSpanInOriginal(d.Right).ToTextSpan()).ToString()))); }
public SnapshotLineList(SnapshotSpan snapshotSpan, Func <ITextSnapshotLine, string> getLineTextCallback, StringDifferenceOptions options) { if (getLineTextCallback == null) { throw new ArgumentNullException(nameof(getLineTextCallback)); } if ((options.DifferenceType & StringDifferenceTypes.Line) == 0) { throw new InvalidOperationException("This collection can only be used for line differencing"); } _snapshotSpan = snapshotSpan; _getLineTextCallback = getLineTextCallback; _options = options; // Figure out the first and last line in the span var startLine = snapshotSpan.Start.GetContainingLine(); int start = snapshotSpan.Start.GetContainingLine().LineNumber; //Perf hack to avoid calling GetContainingLine() if the lines are the same. SnapshotPoint endPoint = snapshotSpan.End; int end = ((endPoint.Position < startLine.EndIncludingLineBreak) ? start : endPoint.GetContainingLine().LineNumber) + 1; _lineSpan = Span.FromBounds(start, end); }
public async Task<IEnumerable<TextChange>> GetTextChangesAsync(Document oldDocument, Document newDocument, CancellationToken cancellationToken) { var oldText = await oldDocument.GetTextAsync(cancellationToken).ConfigureAwait(false); var newText = await newDocument.GetTextAsync(cancellationToken).ConfigureAwait(false); var diffService = _differenceSelectorService.GetTextDifferencingService(oldDocument.Project.LanguageServices.GetService<IContentTypeLanguageService>().GetDefaultContentType()) ?? _differenceSelectorService.DefaultTextDifferencingService; var differenceOptions = new StringDifferenceOptions() { DifferenceType = StringDifferenceTypes.Word }; var oldTextSnapshot = oldText.FindCorrespondingEditorTextSnapshot(); var newTextSnapshot = newText.FindCorrespondingEditorTextSnapshot(); var useSnapshots = oldTextSnapshot != null && newTextSnapshot != null; var diffResult = useSnapshots ? diffService.DiffSnapshotSpans(oldTextSnapshot.GetFullSpan(), newTextSnapshot.GetFullSpan(), differenceOptions) : diffService.DiffStrings(oldText.ToString(), newText.ToString(), differenceOptions); return diffResult.Differences.Select(d => new TextChange( diffResult.LeftDecomposition.GetSpanInOriginal(d.Left).ToTextSpan(), newText.GetSubText(diffResult.RightDecomposition.GetSpanInOriginal(d.Right).ToTextSpan()).ToString())); }
public IHierarchicalDifferenceCollection GetDifferences(IContentType contentType, string left, string right) { Initialize(); ITextDifferencingService diffService = this.diffSelectorService.GetTextDifferencingService(contentType); StringDifferenceOptions diffOptions = new StringDifferenceOptions(StringDifferenceTypes.Line, 0, true); IHierarchicalDifferenceCollection differences = diffService.DiffStrings(left, right, diffOptions); return(differences); }
IHierarchicalDifferenceCollection DiffText(ITokenizedStringListInternal left, ITokenizedStringListInternal right, StringDifferenceTypes type, StringDifferenceOptions differenceOptions) { StringDifferenceOptions nextOptions = new StringDifferenceOptions(differenceOptions); nextOptions.DifferenceType &= ~type; var diffCollection = ComputeMatches(type, differenceOptions, left, right); return(new HierarchicalDifferenceCollection(diffCollection, left, right, this, nextOptions)); }
protected DecompositionListMaker(StringDifferenceOptions options) { // Make a copy, so we don't accidentally modify the one we're given options = new StringDifferenceOptions(options); if (options.WordSplitBehavior == WordSplitBehavior.LanguageAppropriate) { options.WordSplitBehavior = WordSplitBehavior.WhiteSpaceAndPunctuation; } Options = options; }
private void CreateTokens(StringDifferenceOptions options, bool ignoreTrimWhiteSpace) { int end = this.OriginalLength; int i = 0; int tokenStart = 0; TokenType previousTokenType = TokenType.WhiteSpace; bool skipPreviousToken = ignoreTrimWhiteSpace; // Assume that the 1st whitespace token is ignoreable if we're trimming whitespace. while (i < end) { bool skipNextToken; TokenType nextTokenType; int breakLength = this.LengthOfLineBreak(i, end); if (breakLength != 0) { nextTokenType = ignoreTrimWhiteSpace ? TokenType.WhiteSpace : TokenType.LineBreak; skipNextToken = ignoreTrimWhiteSpace; } else { nextTokenType = GetTokenType(this.CharacterAt(i), options.WordSplitBehavior); skipNextToken = (nextTokenType == TokenType.WhiteSpace) ? skipPreviousToken : false; breakLength = 1; } if ((nextTokenType != previousTokenType) || (nextTokenType == TokenType.Symbol)) { if ((tokenStart < i) && !skipPreviousToken) { this.Tokens.Add(new Span(tokenStart, i - tokenStart)); } previousTokenType = nextTokenType; tokenStart = i; } skipPreviousToken = skipNextToken; i += breakLength; } if ((end == 0) || // 0-length sequences get a single token !(ignoreTrimWhiteSpace && (previousTokenType == TokenType.WhiteSpace))) // act as if there is an implicit line break at the end of the line. { this.Tokens.Add(new Span(tokenStart, end - tokenStart)); } }
public HashSet <Tuple <int, int> > PerformDiffString(string origContent, string modifiedContent) { HashSet <Tuple <int, int> > diffRanges = new HashSet <Tuple <int, int> >(); StringDifferenceOptions sdo = new StringDifferenceOptions() { DifferenceType = StringDifferenceTypes.Line | StringDifferenceTypes.Word, IgnoreTrimWhiteSpace = false }; var diffs = differenceService.DiffStrings(origContent, modifiedContent, sdo); var diffList = new List <CCDifference>(); foreach (var d in diffs) { diffList.Add(new CCDifference(d.Left, d.Right, d.Before, d.After)); diffRanges.Add(Tuple.Create <int, int>(d.Left.Start, d.Left.End)); } return(diffRanges); }
/// <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> /// Create a set of edit options for computing a minimal difference, /// with the given <see cref="StringDifferenceOptions" />. /// </summary> public EditOptions(StringDifferenceOptions differenceOptions) { this.ComputeMinimalChange = true; this.DifferenceOptions = differenceOptions; }
public IHierarchicalDifferenceCollection DiffStrings(string leftString, string rightString, StringDifferenceOptions differenceOptions) { if (leftString == null) { throw new ArgumentNullException(nameof(leftString)); } if (rightString == null) { throw new ArgumentNullException(nameof(rightString)); } StringDifferenceTypes type; ITokenizedStringListInternal left; ITokenizedStringListInternal right; if (differenceOptions.DifferenceType.HasFlag(StringDifferenceTypes.Line)) { type = StringDifferenceTypes.Line; left = new LineDecompositionList(leftString, differenceOptions.IgnoreTrimWhiteSpace); right = new LineDecompositionList(rightString, differenceOptions.IgnoreTrimWhiteSpace); } else if (differenceOptions.DifferenceType.HasFlag(StringDifferenceTypes.Word)) { type = StringDifferenceTypes.Word; left = new WordDecompositionList(leftString, differenceOptions); right = new WordDecompositionList(rightString, differenceOptions); } else if (differenceOptions.DifferenceType.HasFlag(StringDifferenceTypes.Character)) { type = StringDifferenceTypes.Character; left = new CharacterDecompositionList(leftString); right = new CharacterDecompositionList(rightString); } else { throw new ArgumentOutOfRangeException("differenceOptions"); } return(DiffText(left, right, type, differenceOptions)); }
public IHierarchicalDifferenceCollection DiffSnapshotSpans(SnapshotSpan leftSpan, SnapshotSpan rightSpan, StringDifferenceOptions differenceOptions, Func <ITextSnapshotLine, string> getLineTextCallback) { StringDifferenceTypes type; ITokenizedStringListInternal left; ITokenizedStringListInternal right; if (differenceOptions.DifferenceType.HasFlag(StringDifferenceTypes.Line)) { type = StringDifferenceTypes.Line; left = new SnapshotLineList(leftSpan, getLineTextCallback, differenceOptions); right = new SnapshotLineList(rightSpan, getLineTextCallback, differenceOptions); } else if (differenceOptions.DifferenceType.HasFlag(StringDifferenceTypes.Word)) { type = StringDifferenceTypes.Word; left = new WordDecompositionList(leftSpan, differenceOptions); right = new WordDecompositionList(rightSpan, differenceOptions); } else if (differenceOptions.DifferenceType.HasFlag(StringDifferenceTypes.Character)) { type = StringDifferenceTypes.Character; left = new CharacterDecompositionList(leftSpan); right = new CharacterDecompositionList(rightSpan); } else { throw new ArgumentOutOfRangeException("differenceOptions"); } return(DiffText(left, right, type, differenceOptions)); }
public IHierarchicalDifferenceCollection DiffSnapshotSpans(SnapshotSpan leftSpan, SnapshotSpan rightSpan, StringDifferenceOptions differenceOptions) { return(this.DiffSnapshotSpans(leftSpan, rightSpan, differenceOptions, DefaultGetLineTextCallback)); }
static IDifferenceCollection <string> ComputeMatches(StringDifferenceTypes differenceType, StringDifferenceOptions differenceOptions, IList <string> leftSequence, IList <string> rightSequence, IList <string> originalLeftSequence, IList <string> originalRightSequence) { return(MaximalSubsequenceAlgorithm.DifferenceSequences(leftSequence, rightSequence, originalLeftSequence, originalRightSequence, differenceOptions.ContinueProcessingPredicate)); }
static IDifferenceCollection <string> ComputeMatches(StringDifferenceTypes differenceType, StringDifferenceOptions differenceOptions, IList <string> leftSequence, IList <string> rightSequence) { return(ComputeMatches(differenceType, differenceOptions, leftSequence, rightSequence, leftSequence, rightSequence)); }
public WordDecompositionList(SnapshotSpan original, StringDifferenceOptions options) : base(original) { this.CreateTokens(options, options.IgnoreTrimWhiteSpace); }
public StringDecompositionListMaker(string s, StringDifferenceOptions options) : base(options) { _string = s; }
public IDifferenceBuffer CreateDifferenceBuffer(ITextBuffer leftBaseBuffer, ITextBuffer rightBaseBuffer, StringDifferenceOptions options, bool disableEditing = false, bool wrapLeftBuffer = true, bool wrapRightBuffer = true) => throw new System.NotImplementedException();
public IHierarchicalDifferenceCollection DiffSnapshotSpans(SnapshotSpan left, SnapshotSpan right, StringDifferenceOptions differenceOptions, Func <ITextSnapshotLine, string> getLineTextCallback) { if (left == null) { throw new ArgumentNullException("left"); } if (right == null) { throw new ArgumentNullException("right"); } var leftDecompositions = new SnapshotSpanDecompositionListMaker(left, differenceOptions, getLineTextCallback); var rightDecompositions = new SnapshotSpanDecompositionListMaker(right, differenceOptions, getLineTextCallback); return(DiffText(leftDecompositions, rightDecompositions, differenceOptions)); }
public IHierarchicalDifferenceCollection DiffStrings(string left, string right, StringDifferenceOptions differenceOptions) { if (left == null) { throw new ArgumentNullException("left"); } if (right == null) { throw new ArgumentNullException("right"); } var leftDecompositions = new StringDecompositionListMaker(left, differenceOptions); var rightDecompositions = new StringDecompositionListMaker(right, differenceOptions); return(DiffText(leftDecompositions, rightDecompositions, differenceOptions)); }
IHierarchicalDifferenceCollection DiffText(DecompositionListMaker left, DecompositionListMaker right, StringDifferenceOptions differenceOptions) { if (left == null) { throw new ArgumentNullException("left"); } if (right == null) { throw new ArgumentNullException("right"); } StringDifferenceTypes differenceType = differenceOptions.DifferenceType; if (differenceType == 0) { throw new ArgumentOutOfRangeException("differenceOptions"); } if ((differenceType & StringDifferenceTypes.Line) != 0) { //The DecompositionListMaker creates a new copy of the list so make sure we only ask once var leftUnfilteredLineList = left.UnfilteredLineList; var rightUnfilteredLineList = right.UnfilteredLineList; var diffCollection = ComputeMatches(differenceType, differenceOptions, left.FilteredLineList, right.FilteredLineList, leftUnfilteredLineList, rightUnfilteredLineList); //Try to clean up the differences so things align better with the block structure of typical languages. diffCollection = FinessedDifferenceCollection.FinesseLineDifferences(diffCollection, leftUnfilteredLineList, rightUnfilteredLineList); StringDifferenceOptions nextOptions = new StringDifferenceOptions(differenceOptions); nextOptions.DifferenceType &= ~StringDifferenceTypes.Line; return(new HierarchicalDifferenceCollection(diffCollection, leftUnfilteredLineList, rightUnfilteredLineList, this, nextOptions)); } else if ((differenceType & StringDifferenceTypes.Word) != 0) { var leftWords = left.WordList; var rightWords = right.WordList; var diffCollection = ComputeMatches(StringDifferenceTypes.Word, differenceOptions, leftWords, rightWords); StringDifferenceOptions nextOptions = new StringDifferenceOptions(differenceOptions); nextOptions.DifferenceType &= ~StringDifferenceTypes.Word; return(new HierarchicalDifferenceCollection(diffCollection, leftWords, rightWords, this, nextOptions)); } else if ((differenceType & StringDifferenceTypes.Character) != 0) { var leftChars = left.CharacterList; var rightChars = right.CharacterList; var diffCollection = ComputeMatches(StringDifferenceTypes.Character, differenceOptions, leftChars, rightChars); // This should always be 0. StringDifferenceOptions nextOptions = new StringDifferenceOptions(differenceOptions); nextOptions.DifferenceType &= ~StringDifferenceTypes.Character; Debug.Assert(nextOptions.DifferenceType == 0, "After character differencing, the difference type should be empty (invalid)."); return(new HierarchicalDifferenceCollection(diffCollection, leftChars, rightChars, this, nextOptions)); } else { throw new ArgumentOutOfRangeException("differenceOptions"); } }
/// <summary> /// Create a set of edit options. /// </summary> public EditOptions(bool computeMinimalChange, StringDifferenceOptions differenceOptions) { this.ComputeMinimalChange = computeMinimalChange; this.DifferenceOptions = differenceOptions; }
public SnapshotSpanDecompositionListMaker(SnapshotSpan snapshotSpan, StringDifferenceOptions options, Func <ITextSnapshotLine, string> getLineTextCallback) : base(options) { _snapshotSpan = snapshotSpan; _getLineTextCallback = getLineTextCallback; }
/// <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 WordDecompositionList(string original, StringDifferenceOptions options) : base(original) { this.CreateTokens(options, ignoreTrimWhiteSpace: false); // We never paid attention to trim whitespace for strings when doing line or word diffs. }