private static async Task <(ProjectChanges, IEnumerable <TextChange>)> GetChangesForCodeActionAsync(
            Document document,
            CodeAction codeAction,
            ProgressTracker progressTracker,
            IDocumentTextDifferencingService textDiffingService,
            CancellationToken cancellationToken)
        {
            // CodeAction.GetChangedSolutionAsync is only implemented for code actions that can fully compute the new
            // solution without deferred computation or taking a dependency on the main thread. In other cases, the
            // implementation of GetChangedSolutionAsync will throw an exception and the code action application is
            // expected to apply the changes by executing the operations in GetOperationsAsync (which may have other
            // side effects). This code cannot assume the input CodeAction supports GetChangedSolutionAsync, so it first
            // attempts to apply text changes obtained from GetOperationsAsync. Two forms are supported:
            //
            // 1. GetOperationsAsync returns an empty list of operations (i.e. no changes are required)
            // 2. GetOperationsAsync returns a list of operations, where the first change is an ApplyChangesOperation to
            //    change the text in the solution, and any remaining changes are deferred computation changes.
            //
            // If GetOperationsAsync does not adhere to one of these patterns, the code falls back to calling
            // GetChangedSolutionAsync since there is no clear way to apply the changes otherwise.
            var operations = await codeAction.GetOperationsAsync(cancellationToken).ConfigureAwait(false);

            Solution newSolution;

            if (operations.Length == 0)
            {
                newSolution = document.Project.Solution;
            }
            else if (operations.Length == 1 && operations[0] is ApplyChangesOperation applyChangesOperation)
            {
                newSolution = applyChangesOperation.ChangedSolution;
            }
            else
            {
                newSolution = await codeAction.GetRequiredChangedSolutionAsync(
                    progressTracker, cancellationToken : cancellationToken).ConfigureAwait(false);
            }

            var newDocument = newSolution.GetRequiredDocument(document.Id);

            // Use Line differencing to reduce the possibility of changes that overwrite existing code.
            var textChanges = await textDiffingService.GetTextChangesAsync(
                document, newDocument, TextDifferenceTypes.Line, cancellationToken).ConfigureAwait(false);

            var projectChanges = newDocument.Project.GetChanges(document.Project);

            return(projectChanges, textChanges);
        }
        private async Task <(ProjectChanges, IEnumerable <TextChange>)> GetChangesForCodeActionAsync(
            Document document,
            CodeAction codeAction,
            ProgressTracker progressTracker,
            IDocumentTextDifferencingService textDiffingService,
            CancellationToken cancellationToken)
        {
            var newSolution = await codeAction.GetChangedSolutionAsync(
                progressTracker, cancellationToken : cancellationToken).ConfigureAwait(false);

            var newDocument = newSolution.GetDocument(document.Id);

            // Use Line differencing to reduce the possibility of changes that overwrite existing code.
            var textChanges = await textDiffingService.GetTextChangesAsync(
                document, newDocument, TextDifferenceTypes.Line, cancellationToken).ConfigureAwait(false);

            var projectChanges = newDocument.Project.GetChanges(document.Project);

            return(projectChanges, textChanges);
        }
Example #3
0
        private static async Task <ImmutableArray <TextChange> > AddDocumentMergeChangesAsync(
            Document oldDocument,
            Document newDocument,
            List <TextChange> cumulativeChanges,
            List <UnmergedDocumentChanges> unmergedChanges,
            LinkedFileGroupSessionInfo groupSessionInfo,
            IDocumentTextDifferencingService textDiffService,
            CancellationToken cancellationToken)
        {
            var unmergedDocumentChanges   = new List <TextChange>();
            var successfullyMergedChanges = ArrayBuilder <TextChange> .GetInstance();

            var cumulativeChangeIndex = 0;

            var textchanges = await textDiffService.GetTextChangesAsync(oldDocument, newDocument, cancellationToken).ConfigureAwait(false);

            foreach (var change in textchanges)
            {
                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(),
                                        oldDocument.Project.Name,
                                        oldDocument.Id));
            }

            return(successfullyMergedChanges.ToImmutableAndFree());
        }
        private static async Task<IEnumerable<TextChange>> AddDocumentMergeChangesAsync(
            Document oldDocument,
            Document newDocument,
            List<TextChange> cumulativeChanges,
            List<UnmergedDocumentChanges> unmergedChanges,
            LinkedFileGroupSessionInfo groupSessionInfo,
            IDocumentTextDifferencingService textDiffService,
            CancellationToken cancellationToken)
        {
            var unmergedDocumentChanges = new List<TextChange>();
            var successfullyMergedChanges = new List<TextChange>();

            int cumulativeChangeIndex = 0;

            var textchanges = await textDiffService.GetTextChangesAsync(oldDocument, newDocument, cancellationToken).ConfigureAwait(false);
            foreach (var change in textchanges)
            {
                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(),
                    oldDocument.Project.Name,
                    oldDocument.Id));
            }

            return successfullyMergedChanges;
        }
Example #5
0
 public TextChangeMerger(Document document)
 {
     _oldDocument       = document;
     _differenceService =
         document.Project.Solution.Workspace.Services.GetRequiredService <IDocumentTextDifferencingService>();
 }