File: LinkedFileDiffMerging\AbstractLinkedFileMergeConflictCommentAdditionService.cs
Web Access
Project: src\src\Workspaces\Core\Portable\Microsoft.CodeAnalysis.Workspaces.csproj (Microsoft.CodeAnalysis.Workspaces)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
#nullable disable
 
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
 
namespace Microsoft.CodeAnalysis;
 
internal abstract class AbstractLinkedFileMergeConflictCommentAdditionService : IMergeConflictHandler, ILanguageService, ILinkedFileMergeConflictCommentAdditionService
{
    internal abstract string GetConflictCommentText(string header, string beforeString, string afterString);
 
    public ImmutableArray<TextChange> CreateEdits(SourceText originalSourceText, ArrayBuilder<UnmergedDocumentChanges> unmergedChanges)
    {
        using var _ = ArrayBuilder<TextChange>.GetInstance(out var commentChanges);
 
        foreach (var documentWithChanges in unmergedChanges)
        {
            var partitionedChanges = PartitionChangesForDocument(documentWithChanges.UnmergedChanges, originalSourceText);
            var comments = GetCommentChangesForDocument(partitionedChanges, documentWithChanges.ProjectName, originalSourceText);
 
            commentChanges.AddRange(comments);
        }
 
        return commentChanges.ToImmutableAndClear();
    }
 
    private static List<List<TextChange>> PartitionChangesForDocument(IEnumerable<TextChange> changes, SourceText originalSourceText)
    {
        var partitionedChanges = new List<List<TextChange>>();
        var currentPartition = new List<TextChange>
        {
            changes.First()
        };
        var currentPartitionEndLine = originalSourceText.Lines.GetLineFromPosition(changes.First().Span.End);
 
        foreach (var change in changes.Skip(1))
        {
            // If changes are on adjacent lines, consider them part of the same change.
            var changeStartLine = originalSourceText.Lines.GetLineFromPosition(change.Span.Start);
            if (changeStartLine.LineNumber >= currentPartitionEndLine.LineNumber + 2)
            {
                partitionedChanges.Add(currentPartition);
                currentPartition = [];
            }
 
            currentPartition.Add(change);
            currentPartitionEndLine = originalSourceText.Lines.GetLineFromPosition(change.Span.End);
        }
 
        if (currentPartition.Any())
        {
            partitionedChanges.Add(currentPartition);
        }
 
        return partitionedChanges;
    }
 
    private List<TextChange> GetCommentChangesForDocument(IEnumerable<IEnumerable<TextChange>> partitionedChanges, string projectName, SourceText oldDocumentText)
    {
        var commentChanges = new List<TextChange>();
 
        foreach (var changePartition in partitionedChanges)
        {
            var startPosition = changePartition.First().Span.Start;
            var endPosition = changePartition.Last().Span.End;
 
            var startLineStartPosition = oldDocumentText.Lines.GetLineFromPosition(startPosition).Start;
            var endLineEndPosition = oldDocumentText.Lines.GetLineFromPosition(endPosition).End;
 
            var oldText = oldDocumentText.GetSubText(TextSpan.FromBounds(startLineStartPosition, endLineEndPosition));
            var adjustedChanges = changePartition.Select(c => new TextChange(TextSpan.FromBounds(c.Span.Start - startLineStartPosition, c.Span.End - startLineStartPosition), c.NewText));
            var newText = oldText.WithChanges(adjustedChanges);
 
            var warningText = GetConflictCommentText(
                string.Format(WorkspacesResources.Unmerged_change_from_project_0, projectName),
                TrimBlankLines(oldText),
                TrimBlankLines(newText));
 
            if (warningText != null)
            {
                commentChanges.Add(new TextChange(TextSpan.FromBounds(startLineStartPosition, startLineStartPosition), warningText));
            }
        }
 
        return commentChanges;
    }
 
    private static string TrimBlankLines(SourceText text)
    {
        int startLine, endLine;
        for (startLine = 0; startLine < text.Lines.Count; startLine++)
        {
            if (!text.Lines[startLine].IsEmptyOrWhitespace())
            {
                break;
            }
        }
 
        for (endLine = text.Lines.Count - 1; endLine > startLine; endLine--)
        {
            if (!text.Lines[endLine].IsEmptyOrWhitespace())
            {
                break;
            }
        }
 
        return startLine <= endLine
            ? text.GetSubText(TextSpan.FromBounds(text.Lines[startLine].Start, text.Lines[endLine].End)).ToString()
            : null;
    }
}