|
// 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.
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis;
internal static class LinkedFileMergeConflictCommentAdditionService
{
public static 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 static ImmutableArray<TextChange> GetCommentChangesForDocument(IEnumerable<IEnumerable<TextChange>> partitionedChanges, string projectName, SourceText oldDocumentText)
{
using var _ = ArrayBuilder<TextChange>.GetInstance(out var commentChanges);
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.TODO_Unmerged_change_from_project_0, projectName),
TrimBlankLines(oldText),
TrimBlankLines(newText));
if (warningText != null)
commentChanges.Add(new TextChange(TextSpan.FromBounds(startLineStartPosition, startLineStartPosition), warningText));
}
return commentChanges.ToImmutableAndClear();
}
private static string? GetConflictCommentText(string header, string? beforeString, string? afterString)
=> (beforeString, afterString) switch
{
(null, null) => null,
(not null, not null) =>
$"""
<<<<<<< {header}, {WorkspacesResources.Before_colon}
{beforeString}
=======
{afterString}
>>>>>>> {WorkspacesResources.After}
""",
(null, not null) =>
$"""
<<<<<<< {header}, {WorkspacesResources.Before_colon}
=======
{afterString}
>>>>>>> {WorkspacesResources.After}
""",
(not null, null) =>
$"""
<<<<<<< {header}, {WorkspacesResources.Before_colon}
{beforeString}
=======
>>>>>>> {WorkspacesResources.After}
""",
};
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;
}
}
|