/// <summary> /// Update the workspace so that the document with the Id of <paramref name="newDocument"/> /// has the text of newDocument. If the document is open, then this method will determine a /// minimal set of changes to apply to the document. /// </summary> internal static void ApplyDocumentChanges (this Workspace workspace, Document newDocument, CancellationToken cancellationToken) { var oldSolution = workspace.CurrentSolution; var oldDocument = oldSolution.GetDocument (newDocument.Id); var changes = newDocument.GetTextChangesAsync (oldDocument, cancellationToken).WaitAndGetResult (cancellationToken); var newSolution = oldSolution.UpdateDocument (newDocument.Id, changes, cancellationToken); workspace.TryApplyChanges (newSolution); }
public async Task <ImmutableArray <TextChange> > GetTextChangesAsync( Document oldDocument, Document newDocument, TextDifferenceTypes preferredDifferenceType, CancellationToken cancellationToken ) { var changes = await newDocument .GetTextChangesAsync(oldDocument, cancellationToken) .ConfigureAwait(false); return(changes.ToImmutableArray()); }
private static async Task<List<TextChange>> AddDocumentMergeChangesAsync( Document oldDocument, Document newDocument, List<TextChange> cumulativeChanges, List<UnmergedDocumentChanges> unmergedChanges, LinkedFileGroupSessionInfo groupSessionInfo, CancellationToken cancellationToken) { var unmergedDocumentChanges = new List<TextChange>(); var successfullyMergedChanges = new List<TextChange>(); int cumulativeChangeIndex = 0; foreach (var change in await newDocument.GetTextChangesAsync(oldDocument).ConfigureAwait(false)) { while (cumulativeChangeIndex < cumulativeChanges.Count && cumulativeChanges[cumulativeChangeIndex].Span.End < change.Span.Start) { // Existing change that does not overlap with the current change in consideration successfullyMergedChanges.Add(cumulativeChanges[cumulativeChangeIndex]); cumulativeChangeIndex++; groupSessionInfo.IsolatedDiffs++; } if (cumulativeChangeIndex < cumulativeChanges.Count) { var cumulativeChange = cumulativeChanges[cumulativeChangeIndex]; if (!cumulativeChange.Span.IntersectsWith(change.Span)) { // The current change in consideration does not intersect with any existing change successfullyMergedChanges.Add(change); groupSessionInfo.IsolatedDiffs++; } else { if (change.Span != cumulativeChange.Span || change.NewText != cumulativeChange.NewText) { // The current change in consideration overlaps an existing change but // the changes are not identical. unmergedDocumentChanges.Add(change); groupSessionInfo.OverlappingDistinctDiffs++; if (change.Span == cumulativeChange.Span) { groupSessionInfo.OverlappingDistinctDiffsWithSameSpan++; if (change.NewText.Contains(cumulativeChange.NewText) || cumulativeChange.NewText.Contains(change.NewText)) { groupSessionInfo.OverlappingDistinctDiffsWithSameSpanAndSubstringRelation++; } } } else { // The current change in consideration is identical to an existing change successfullyMergedChanges.Add(change); cumulativeChangeIndex++; groupSessionInfo.IdenticalDiffs++; } } } else { // The current change in consideration does not intersect with any existing change successfullyMergedChanges.Add(change); groupSessionInfo.IsolatedDiffs++; } } while (cumulativeChangeIndex < cumulativeChanges.Count) { // Existing change that does not overlap with the current change in consideration successfullyMergedChanges.Add(cumulativeChanges[cumulativeChangeIndex]); cumulativeChangeIndex++; groupSessionInfo.IsolatedDiffs++; } if (unmergedDocumentChanges.Any()) { unmergedChanges.Add(new UnmergedDocumentChanges( unmergedDocumentChanges.AsEnumerable(), await oldDocument.GetTextAsync(cancellationToken).ConfigureAwait(false), oldDocument.Project.Name)); } return successfullyMergedChanges; }
/// <summary> /// Try to merge the changes between <paramref name="newDocument"/> and <paramref name="oldDocument"/> into <paramref name="cumulativeChanges"/>. /// If there is any conflicting change in <paramref name="newDocument"/> with existing <paramref name="cumulativeChanges"/>, then the original <paramref name="cumulativeChanges"/> are returned. /// Otherwise, the newly merged changes are returned. /// </summary> /// <param name="oldDocument">Base document on which FixAll was invoked.</param> /// <param name="newDocument">New document with a code fix that is being merged.</param> /// <param name="cumulativeChanges">Existing merged changes from other batch fixes into which newDocument changes are being merged.</param> /// <param name="cancellationToken">Cancellation token.</param> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> private static async Task<List<TextChange>> TryAddDocumentMergeChangesAsync( Document oldDocument, Document newDocument, List<TextChange> cumulativeChanges, CancellationToken cancellationToken) { var successfullyMergedChanges = new List<TextChange>(); int cumulativeChangeIndex = 0; foreach (var change in await newDocument.GetTextChangesAsync(oldDocument, cancellationToken).ConfigureAwait(false)) { cancellationToken.ThrowIfCancellationRequested(); while (cumulativeChangeIndex < cumulativeChanges.Count && cumulativeChanges[cumulativeChangeIndex].Span.End < change.Span.Start) { cancellationToken.ThrowIfCancellationRequested(); // Existing change that does not overlap with the current change in consideration successfullyMergedChanges.Add(cumulativeChanges[cumulativeChangeIndex]); cumulativeChangeIndex++; } if (cumulativeChangeIndex < cumulativeChanges.Count) { var cumulativeChange = cumulativeChanges[cumulativeChangeIndex]; if (!cumulativeChange.Span.IntersectsWith(change.Span)) { // The current change in consideration does not intersect with any existing change successfullyMergedChanges.Add(change); } else { if (change.Span != cumulativeChange.Span || change.NewText != cumulativeChange.NewText) { // The current change in consideration overlaps an existing change but // the changes are not identical. // Bail out merge efforts and return the original 'cumulativeChanges'. return cumulativeChanges; } else { // The current change in consideration is identical to an existing change successfullyMergedChanges.Add(change); cumulativeChangeIndex++; } } } else { // The current change in consideration does not intersect with any existing change successfullyMergedChanges.Add(change); } } while (cumulativeChangeIndex < cumulativeChanges.Count) { cancellationToken.ThrowIfCancellationRequested(); // Existing change that does not overlap with the current change in consideration successfullyMergedChanges.Add(cumulativeChanges[cumulativeChangeIndex]); cumulativeChangeIndex++; } return successfullyMergedChanges; }
public async Task<IEnumerable<TextChange>> GetTextChangesAsync(Document oldDocument, Document newDocument, CancellationToken cancellationToken) { return await newDocument.GetTextChangesAsync(oldDocument, cancellationToken).ConfigureAwait(false); }
public async Task <IEnumerable <TextChange> > GetTextChangesAsync(Document oldDocument, Document newDocument, CancellationToken cancellationToken) { return(await newDocument.GetTextChangesAsync(oldDocument, cancellationToken).ConfigureAwait(false)); }
private static async Task <List <TextChange> > AddDocumentMergeChangesAsync( Document oldDocument, Document newDocument, List <TextChange> cumulativeChanges, List <UnmergedDocumentChanges> unmergedChanges, LinkedFileGroupSessionInfo groupSessionInfo, CancellationToken cancellationToken) { var unmergedDocumentChanges = new List <TextChange>(); var successfullyMergedChanges = new List <TextChange>(); int cumulativeChangeIndex = 0; foreach (var change in await newDocument.GetTextChangesAsync(oldDocument).ConfigureAwait(false)) { while (cumulativeChangeIndex < cumulativeChanges.Count && cumulativeChanges[cumulativeChangeIndex].Span.End < change.Span.Start) { // Existing change that does not overlap with the current change in consideration successfullyMergedChanges.Add(cumulativeChanges[cumulativeChangeIndex]); cumulativeChangeIndex++; groupSessionInfo.IsolatedDiffs++; } if (cumulativeChangeIndex < cumulativeChanges.Count) { var cumulativeChange = cumulativeChanges[cumulativeChangeIndex]; if (!cumulativeChange.Span.IntersectsWith(change.Span)) { // The current change in consideration does not intersect with any existing change successfullyMergedChanges.Add(change); groupSessionInfo.IsolatedDiffs++; } else { if (change.Span != cumulativeChange.Span || change.NewText != cumulativeChange.NewText) { // The current change in consideration overlaps an existing change but // the changes are not identical. unmergedDocumentChanges.Add(change); groupSessionInfo.OverlappingDistinctDiffs++; if (change.Span == cumulativeChange.Span) { groupSessionInfo.OverlappingDistinctDiffsWithSameSpan++; if (change.NewText.Contains(cumulativeChange.NewText) || cumulativeChange.NewText.Contains(change.NewText)) { groupSessionInfo.OverlappingDistinctDiffsWithSameSpanAndSubstringRelation++; } } } else { // The current change in consideration is identical to an existing change successfullyMergedChanges.Add(change); cumulativeChangeIndex++; groupSessionInfo.IdenticalDiffs++; } } } else { // The current change in consideration does not intersect with any existing change successfullyMergedChanges.Add(change); groupSessionInfo.IsolatedDiffs++; } } while (cumulativeChangeIndex < cumulativeChanges.Count) { // Existing change that does not overlap with the current change in consideration successfullyMergedChanges.Add(cumulativeChanges[cumulativeChangeIndex]); cumulativeChangeIndex++; groupSessionInfo.IsolatedDiffs++; } if (unmergedDocumentChanges.Any()) { unmergedChanges.Add(new UnmergedDocumentChanges( unmergedDocumentChanges.AsEnumerable(), await oldDocument.GetTextAsync(cancellationToken).ConfigureAwait(false), oldDocument.Project.Name)); } return(successfullyMergedChanges); }
private static async Task <O.ChangedLineMap[]> CreateChangedLineMaps(R.Document oldDocument, R.Document newDocument) { var changes = await newDocument.GetTextChangesAsync(oldDocument).ConfigureAwait(false); var oldText = await oldDocument.GetTextAsync().ConfigureAwait(false); var lineChanges = new List <LineChange>(); foreach (var change in changes) { var startLine = oldText.Lines.IndexOf(change.Span.Start); var oldLineCount = oldText.ToString(change.Span).Count(c => c == '\n') + 1; var endLine = startLine + oldLineCount; var additionalLineCount = change.NewText.Count(c => c == '\n') + 1 - oldLineCount; // 近いものがあるならマージしていく var isMerged = false; for (var i = 0; i < lineChanges.Count; i++) { var lc = lineChanges[i]; // change // lc // みたいな配置になっている場合 var b1 = startLine <= lc.StartLine && startLine + oldLineCount >= lc.StartLine; // lc // change var b2 = lc.StartLine <= startLine && lc.StartLine + lc.OldLineCount >= startLine; isMerged = b1 || b2; if (isMerged) { var newStartLine = Math.Min(startLine, lc.StartLine); lineChanges[i] = new LineChange( newStartLine, Math.Max(endLine, lc.StartLine + lc.OldLineCount) - newStartLine, lc.AdditionalLineCount + additionalLineCount ); break; } } if (!isMerged) { lineChanges.Add(new LineChange(startLine, oldLineCount, additionalLineCount)); } } // StartLine 順に並べて、変更後の行数を反映しながら結果を詰めていく lineChanges.Sort((x, y) => x.StartLine.CompareTo(y.StartLine)); var changedLineMaps = new O.ChangedLineMap[lineChanges.Count]; var additional = 0; for (var i = 0; i < changedLineMaps.Length; i++) { var lc = lineChanges[i]; changedLineMaps[i] = new O.ChangedLineMap( new O.LineRange(lc.StartLine, lc.OldLineCount), new O.LineRange(lc.StartLine + additional, lc.OldLineCount + lc.AdditionalLineCount) ); additional += lc.AdditionalLineCount; } return(changedLineMaps); }