/// <summary> /// Get the text changes between this document and a prior version of the same document. /// The changes, when applied to the text of the old document, will produce the text of the current document. /// </summary> public async Task <IEnumerable <TextChange> > GetTextChangesAsync(Document oldDocument, CancellationToken cancellationToken = default(CancellationToken)) { try { using (Logger.LogBlock(FeatureId.Document, FunctionId.Document_GetTextChanges, this.Name, cancellationToken)) { if (oldDocument == this) { // no changes return(SpecializedCollections.EmptyEnumerable <TextChange>()); } if (this.Id != oldDocument.Id) { throw new ArgumentException(WorkspacesResources.DocumentVersionIsDifferent); } // first try to see if text already knows its changes IList <TextChange> textChanges = null; SourceText text; SourceText oldText; if (this.TryGetText(out text) && oldDocument.TryGetText(out oldText)) { if (text == oldText) { return(SpecializedCollections.EmptyEnumerable <TextChange>()); } var container = text.Container; if (container != null) { textChanges = text.GetTextChanges(oldText).ToList(); // if changes are significant (not the whole document being replaced) then use these changes if (textChanges.Count > 1 || (textChanges.Count == 1 && textChanges[0].Span != new TextSpan(0, oldText.Length))) { return(textChanges); } } } // get changes by diffing the trees SyntaxTree tree = await this.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); SyntaxTree oldTree = await oldDocument.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); return(tree.GetChanges(oldTree)); } } catch (Exception e) if (ExceptionHelpers.CrashUnlessCanceled(e)) { throw ExceptionUtilities.Unreachable; } }
/// <summary> /// Generates Diff results for each change that occurred from the ancestor to the changed /// </summary> /// <param name="ancestor"> /// The abstract syntax tree that the changed version originated from /// </param> /// <param name="changed">The abstract syntax tree that resulted from changing the ancestor</param> /// <returns>Changes between the ancestor and changed</returns> public static IEnumerable<Diff> Compare(SyntaxTree ancestor, SyntaxTree changed) { var offset = 0; //Tracks differences in indexes moving through the changed syntax tree foreach (var change in changed.GetChanges(ancestor)) { //Assumes that this list is sorted by place in file var origLength = change.Span.Length; var offsetChange = (change.NewText.Length - origLength); var chanStart = change.Span.Start + offset; var chanLength = origLength + offsetChange; offset += offsetChange; var d = new Diff { Ancestor = SpanDetails.Create(change.Span, ancestor), Changed = SpanDetails.Create(new TextSpan(chanStart, chanLength), changed) }; yield return d; } }
private static bool TryGetChangesIgnoringWhiteSpace(SyntaxTree oldTree, SyntaxTree newTree, out IEnumerable<string> textchanges) { #region CodeContracts Contract.Requires(oldTree != null); Contract.Requires(newTree != null); Contract.Ensures(!Contract.Result<bool>() || Contract.ValueAtReturn<IEnumerable<string>>(out textchanges) != null); #endregion CodeContracts var workspace = MSBuildWorkspace.Create(); oldTree = Formatter.Format(oldTree.GetRoot(), workspace).SyntaxTree; newTree = Formatter.Format(newTree.GetRoot(), workspace).SyntaxTree; textchanges = null; if (oldTree == newTree) { return false; } var changes = newTree.GetChanges(oldTree); if (!changes.Any()) { return false; } textchanges = changes.Select(x => x.NewText.Trim()); //textchanges = changes.Select(x => x.NewText.Trim()); if (textchanges.All(x => x == "")) { return false; } return true; }