diff --git a/Src/Test/Utilities/Traits.cs b/Src/Test/Utilities/Traits.cs
index dd276fd5c2b37d6bacf186c2cd3ef8fe0b31a3a9..eee530fb28f747ae2d4cfd0057dea2e4e15032e1 100644
--- a/Src/Test/Utilities/Traits.cs
+++ b/Src/Test/Utilities/Traits.cs
@@ -17,6 +17,7 @@ public static class Features
public const string Workspace = "Workspace";
public const string Diagnostics = "Diagnostics";
public const string Formatting = "Formatting";
+ public const string LinkedFileDiffMerging = "LinkedFileDiffMerging";
}
public const string Environment = "Environment";
diff --git a/Src/Workspaces/CSharp/CSharpWorkspace.csproj b/Src/Workspaces/CSharp/CSharpWorkspace.csproj
index 8e50db7645ae54c74d10cbd10ecc6cd58ac4ba89..2fd6a2654d61b479e284f368d4b9a8d48f1f911b 100644
--- a/Src/Workspaces/CSharp/CSharpWorkspace.csproj
+++ b/Src/Workspaces/CSharp/CSharpWorkspace.csproj
@@ -208,6 +208,7 @@
+
diff --git a/Src/Workspaces/CSharp/LinkedFiles/CSharpLinkedFileMergeConflictCommentAdditionService.cs b/Src/Workspaces/CSharp/LinkedFiles/CSharpLinkedFileMergeConflictCommentAdditionService.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c2ecb3e8ad7d78d6f3eb0deff4db6455cd270af9
--- /dev/null
+++ b/Src/Workspaces/CSharp/LinkedFiles/CSharpLinkedFileMergeConflictCommentAdditionService.cs
@@ -0,0 +1,62 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.CodeAnalysis.Host.Mef;
+
+namespace Microsoft.CodeAnalysis.CSharp
+{
+ [ExportLanguageService(typeof(ILinkedFileMergeConflictCommentAdditionService), LanguageNames.CSharp)]
+ internal sealed class CSharpLinkedFileMergeConflictCommentAdditionService : AbstractLinkedFileMergeConflictCommentAdditionService
+ {
+ internal override string GetConflictCommentText(string header, string beforeString, string afterString)
+ {
+ if (beforeString == null && afterString == null)
+ {
+ // Whitespace only
+ return null;
+ }
+ else if (beforeString == null)
+ {
+ // New code
+ return string.Format(@"
+/* {0}
+{1}
+{2}
+*/
+",
+ header,
+ WorkspacesResources.AddedHeader,
+ afterString);
+ }
+ else if (afterString == null)
+ {
+ // Removed code
+ return string.Format(@"
+/* {0}
+{1}
+{2}
+*/
+",
+ header,
+ WorkspacesResources.RemovedHeader,
+ beforeString);
+ }
+ else
+ {
+ // Changed code
+ return string.Format(@"
+/* {0}
+{1}
+{2}
+{3}
+{4}
+*/
+",
+ header,
+ WorkspacesResources.BeforeHeader,
+ beforeString,
+ WorkspacesResources.AfterHeader,
+ afterString);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Src/Workspaces/Core/Log/ITelemetry.cs b/Src/Workspaces/Core/Log/ITelemetry.cs
index c591b61cb0805843d358b0eaf5802f196bd83981..017ae4737ac222a982c9de69f5c09ba62952eb0c 100644
--- a/Src/Workspaces/Core/Log/ITelemetry.cs
+++ b/Src/Workspaces/Core/Log/ITelemetry.cs
@@ -13,5 +13,6 @@ internal interface ITelemetryService : IWorkspaceService
void EndCurrentSession();
void LogRenameSession(RenameSessionInfo renameSession);
void LogEncDebugSession(EncDebuggingSessionInfo session);
+ void LogLinkedFileDiffMergingSession(LinkedFileDiffMergingSessionInfo session);
}
}
diff --git a/Src/Workspaces/Core/Log/Telemetry.Parameters.cs b/Src/Workspaces/Core/Log/Telemetry.Parameters.cs
index 06b2d8f901ee03dbaeca129da19b18ec941b3a89..992acdd5d3e2471a9f81077d3785dfa710589c35 100644
--- a/Src/Workspaces/Core/Log/Telemetry.Parameters.cs
+++ b/Src/Workspaces/Core/Log/Telemetry.Parameters.cs
@@ -91,4 +91,27 @@ internal bool IsEmpty()
return !(HadCompilationErrors || HadRudeEdits || HadValidChanges || HadValidInsignificantChanges);
}
}
+
+ internal class LinkedFileDiffMergingSessionInfo
+ {
+ public readonly List LinkedFileGroups = new List();
+
+ public void LogLinkedFileResult(LinkedFileGroupSessionInfo info)
+ {
+ LinkedFileGroups.Add(info);
+ }
+ }
+
+ internal class LinkedFileGroupSessionInfo
+ {
+ public int LinkedDocuments;
+ public int DocumentsWithChanges;
+ public int IsolatedDiffs;
+ public int IdenticalDiffs;
+ public int OverlappingDistinctDiffs;
+ public int OverlappingDistinctDiffsWithSameSpan;
+ public int OverlappingDistinctDiffsWithSameSpanAndSubstringRelation;
+ public int InsertedMergeConflictComments;
+ public int InsertedMergeConflictCommentsAtAdjustedLocation;
+ }
}
diff --git a/Src/Workspaces/Core/Log/TelemetryWorkspaceServiceFactory.cs b/Src/Workspaces/Core/Log/TelemetryWorkspaceServiceFactory.cs
index a721ffad9f2386007fd89626504adec57b4f59a7..0c7249f0f9192fb325891ef85cdf5d9cce2805a4 100644
--- a/Src/Workspaces/Core/Log/TelemetryWorkspaceServiceFactory.cs
+++ b/Src/Workspaces/Core/Log/TelemetryWorkspaceServiceFactory.cs
@@ -2,6 +2,7 @@
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
+using Microsoft.CodeAnalysis.Internal.Log.Telemetry;
namespace Microsoft.CodeAnalysis.Internal.Log
{
@@ -26,6 +27,10 @@ void ITelemetryService.LogRenameSession(Telemetry.RenameSessionInfo renameSessio
void ITelemetryService.LogEncDebugSession(Telemetry.EncDebuggingSessionInfo session)
{
}
+
+ void ITelemetryService.LogLinkedFileDiffMergingSession(LinkedFileDiffMergingSessionInfo session)
+ {
+ }
}
}
}
diff --git a/Src/Workspaces/Core/Workspace/Solution/AbstractLinkedFileMergeConflictCommentAdditionService.cs b/Src/Workspaces/Core/Workspace/Solution/AbstractLinkedFileMergeConflictCommentAdditionService.cs
new file mode 100644
index 0000000000000000000000000000000000000000..b63da692b9f4393d7f033273d76e35bc7db37d15
--- /dev/null
+++ b/Src/Workspaces/Core/Workspace/Solution/AbstractLinkedFileMergeConflictCommentAdditionService.cs
@@ -0,0 +1,112 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Microsoft.CodeAnalysis
+{
+ internal abstract class AbstractLinkedFileMergeConflictCommentAdditionService : ILinkedFileMergeConflictCommentAdditionService
+ {
+ internal abstract string GetConflictCommentText(string header, string beforeString, string afterString);
+
+ public IEnumerable CreateCommentsForUnmergedChanges(SourceText originalSourceText, IEnumerable unmergedChanges)
+ {
+ var commentChanges = new List();
+
+ foreach (var documentWithChanges in unmergedChanges)
+ {
+ var partitionedChanges = PartitionChangesForDocument(documentWithChanges.UnmergedChanges, originalSourceText);
+ var comments = GetCommentChangesForDocument(partitionedChanges, documentWithChanges.ProjectName, originalSourceText, documentWithChanges.Text);
+
+ commentChanges.AddRange(comments);
+ }
+
+ return commentChanges;
+ }
+
+ private IEnumerable> PartitionChangesForDocument(IEnumerable changes, SourceText originalSourceText)
+ {
+ var partitionedChanges = new List>();
+ var currentPartition = new List();
+
+ currentPartition.Add(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 = new List();
+ }
+
+ currentPartition.Add(change);
+ currentPartitionEndLine = originalSourceText.Lines.GetLineFromPosition(change.Span.End);
+ }
+
+ if (currentPartition.Any())
+ {
+ partitionedChanges.Add(currentPartition);
+ }
+
+ return partitionedChanges;
+ }
+
+ private List GetCommentChangesForDocument(IEnumerable> partitionedChanges, string projectName, SourceText oldDocumentText, SourceText newDocumentText)
+ {
+ var commentChanges = new List();
+
+ 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.UnmergedChangeFromProject, projectName),
+ TrimBlankLines(oldText),
+ TrimBlankLines(newText));
+
+ if (warningText != null)
+ {
+ commentChanges.Add(new TextChange(TextSpan.FromBounds(startLineStartPosition, startLineStartPosition), warningText));
+ }
+ }
+
+ return commentChanges;
+ }
+
+ private string TrimBlankLines(SourceText text)
+ {
+ int startLine, endLine;
+ for (startLine = 0; startLine < text.Lines.Count; startLine++)
+ {
+ if (text.Lines[startLine].ToString().Any(c => !char.IsWhiteSpace(c)))
+ {
+ break;
+ }
+ }
+
+ for (endLine = text.Lines.Count - 1; endLine > startLine; endLine--)
+ {
+ if (text.Lines[endLine].ToString().Any(c => !char.IsWhiteSpace(c)))
+ {
+ break;
+ }
+ }
+
+ return startLine <= endLine
+ ? text.GetSubText(TextSpan.FromBounds(text.Lines[startLine].Start, text.Lines[endLine].End)).ToString()
+ : null;
+ }
+ }
+}
diff --git a/Src/Workspaces/Core/Workspace/Solution/ILinkedFileMergeConflictCommentAdditionService.cs b/Src/Workspaces/Core/Workspace/Solution/ILinkedFileMergeConflictCommentAdditionService.cs
new file mode 100644
index 0000000000000000000000000000000000000000..00d5678aeea6ce4d93b9518f16b0f32eac6fa354
--- /dev/null
+++ b/Src/Workspaces/Core/Workspace/Solution/ILinkedFileMergeConflictCommentAdditionService.cs
@@ -0,0 +1,14 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using Microsoft.CodeAnalysis.Host;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Microsoft.CodeAnalysis
+{
+ internal interface ILinkedFileMergeConflictCommentAdditionService : ILanguageService
+ {
+ IEnumerable CreateCommentsForUnmergedChanges(SourceText originalSourceText, IEnumerable unmergedChanges);
+ }
+}
\ No newline at end of file
diff --git a/Src/Workspaces/Core/Workspace/Solution/Solution.LinkedFileDiffMergingSession.cs b/Src/Workspaces/Core/Workspace/Solution/Solution.LinkedFileDiffMergingSession.cs
new file mode 100644
index 0000000000000000000000000000000000000000..055f5abfa0efe64bc1d751fb0b9fb122747bdf9d
--- /dev/null
+++ b/Src/Workspaces/Core/Workspace/Solution/Solution.LinkedFileDiffMergingSession.cs
@@ -0,0 +1,288 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.Internal.Log;
+using Microsoft.CodeAnalysis.Internal.Log.Telemetry;
+using Microsoft.CodeAnalysis.Shared.Extensions;
+using Microsoft.CodeAnalysis.Text;
+using Roslyn.Utilities;
+
+namespace Microsoft.CodeAnalysis
+{
+ public partial class Solution
+ {
+ private sealed class LinkedFileDiffMergingSession
+ {
+ private Solution oldSolution;
+ private Solution newSolution;
+ private SolutionChanges solutionChanges;
+
+ public LinkedFileDiffMergingSession(Solution oldSolution, Solution newSolution, SolutionChanges solutionChanges)
+ {
+ this.oldSolution = oldSolution;
+ this.newSolution = newSolution;
+ this.solutionChanges = solutionChanges;
+ }
+
+ internal async Task MergeDiffsAsync(CancellationToken cancellationToken)
+ {
+ LinkedFileDiffMergingSessionInfo sessionInfo = new LinkedFileDiffMergingSessionInfo();
+
+ var linkedDocumentGroupsWithChanges = solutionChanges
+ .GetProjectChanges()
+ .SelectMany(p => p.GetChangedDocuments())
+ .GroupBy(d => oldSolution.GetDocument(d).FilePath, StringComparer.InvariantCultureIgnoreCase);
+
+ var updatedSolution = newSolution;
+ foreach (var linkedDocumentGroup in linkedDocumentGroupsWithChanges)
+ {
+ var allLinkedDocuments = newSolution.GetDocumentIdsWithFilePath(newSolution.GetDocumentState(linkedDocumentGroup.First()).FilePath);
+ if (allLinkedDocuments.Length == 1)
+ {
+ continue;
+ }
+
+ SourceText mergedText;
+ if (linkedDocumentGroup.Count() > 1)
+ {
+ mergedText = (await MergeLinkedDocumentGroupAsync(linkedDocumentGroup, sessionInfo, cancellationToken).ConfigureAwait(false)).MergedSourceText;
+ }
+ else
+ {
+ mergedText = await newSolution.GetDocument(linkedDocumentGroup.Single()).GetTextAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ foreach (var documentId in allLinkedDocuments)
+ {
+ updatedSolution = updatedSolution.WithDocumentText(documentId, mergedText);
+ }
+ }
+
+ var telemetryService = newSolution.Workspace.Services.GetService();
+ telemetryService.LogLinkedFileDiffMergingSession(sessionInfo);
+
+ return updatedSolution;
+ }
+
+ private async Task MergeLinkedDocumentGroupAsync(
+ IEnumerable linkedDocumentGroup,
+ LinkedFileDiffMergingSessionInfo sessionInfo,
+ CancellationToken cancellationToken)
+ {
+ var groupSessionInfo = new LinkedFileGroupSessionInfo();
+
+ // Automatically merge non-conflicting diffs while collecting the conflicting diffs
+
+ var appliedChanges = await newSolution.GetDocument(linkedDocumentGroup.First()).GetTextChangesAsync(oldSolution.GetDocument(linkedDocumentGroup.First())).ConfigureAwait(false);
+ var unmergedChanges = new List();
+
+ foreach (var documentId in linkedDocumentGroup.Skip(1))
+ {
+ appliedChanges = await AddDocumentMergeChangesAsync(
+ oldSolution.GetDocument(documentId),
+ newSolution.GetDocument(documentId),
+ appliedChanges.ToList(),
+ unmergedChanges,
+ groupSessionInfo,
+ cancellationToken).ConfigureAwait(false);
+ }
+
+ var originalDocument = oldSolution.GetDocument(linkedDocumentGroup.First());
+ var originalSourceText = await originalDocument.GetTextAsync().ConfigureAwait(false);
+
+ // Add comments in source explaining diffs that could not be merged
+
+ IEnumerable allChanges;
+ if (unmergedChanges.Any())
+ {
+ var mergeConflictCommentAdder = originalDocument.GetLanguageService();
+ var commentChanges = mergeConflictCommentAdder.CreateCommentsForUnmergedChanges(originalSourceText, unmergedChanges);
+
+ allChanges = MergeChangesWithMergeFailComments(appliedChanges, commentChanges, groupSessionInfo);
+ }
+ else
+ {
+ allChanges = appliedChanges;
+ }
+
+ groupSessionInfo.LinkedDocuments = newSolution.GetDocumentIdsWithFilePath(originalDocument.FilePath).Length;
+ groupSessionInfo.DocumentsWithChanges = linkedDocumentGroup.Count();
+ sessionInfo.LogLinkedFileResult(groupSessionInfo);
+
+ return new LinkedFileMergeResult(originalSourceText.WithChanges(allChanges), hasMergeConflicts: unmergedChanges.Any());
+ }
+
+ private static async Task> AddDocumentMergeChangesAsync(
+ Document oldDocument,
+ Document newDocument,
+ List cumulativeChanges,
+ List unmergedChanges,
+ LinkedFileGroupSessionInfo groupSessionInfo,
+ CancellationToken cancellationToken)
+ {
+ var unmergedDocumentChanges = new List();
+ var successfullyMergedChanges = new List();
+
+ 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 IEnumerable MergeChangesWithMergeFailComments(IEnumerable mergedChanges, IEnumerable commentChanges, LinkedFileGroupSessionInfo groupSessionInfo)
+ {
+ var mergedChangesList = NormalizeChanges(mergedChanges).ToList();
+ var commentChangesList = NormalizeChanges(commentChanges).ToList();
+
+ var combinedChanges = new List();
+ var insertedMergeConflictCommentsAtAdjustedLocation = 0;
+
+ var commentChangeIndex = 0;
+ foreach (var mergedChange in mergedChangesList)
+ {
+ while (commentChangeIndex < commentChangesList.Count && commentChangesList[commentChangeIndex].Span.End <= mergedChange.Span.Start)
+ {
+ // Add a comment change that does not conflict with any merge change
+ combinedChanges.Add(commentChangesList[commentChangeIndex]);
+ commentChangeIndex++;
+ }
+
+ if (commentChangeIndex >= commentChangesList.Count || mergedChange.Span.End <= commentChangesList[commentChangeIndex].Span.Start)
+ {
+ // Add a merge change that does not conflict with any comment change
+ combinedChanges.Add(mergedChange);
+ continue;
+ }
+
+ // The current comment insertion location conflicts with a merge diff location. Add the comment before the diff.
+ var conflictingCommentInsertionLocation = new TextSpan(mergedChange.Span.Start, 0);
+ while (commentChangeIndex < commentChangesList.Count && commentChangesList[commentChangeIndex].Span.Start < mergedChange.Span.End)
+ {
+ combinedChanges.Add(new TextChange(conflictingCommentInsertionLocation, commentChangesList[commentChangeIndex].NewText));
+ commentChangeIndex++;
+
+ insertedMergeConflictCommentsAtAdjustedLocation++;
+ }
+
+ combinedChanges.Add(mergedChange);
+ }
+
+ while (commentChangeIndex < commentChangesList.Count)
+ {
+ // Add a comment change that does not conflict with any merge change
+ combinedChanges.Add(commentChangesList[commentChangeIndex]);
+ commentChangeIndex++;
+ }
+
+ groupSessionInfo.InsertedMergeConflictComments = commentChanges.Count();
+ groupSessionInfo.InsertedMergeConflictCommentsAtAdjustedLocation = insertedMergeConflictCommentsAtAdjustedLocation;
+
+ return NormalizeChanges(combinedChanges);
+ }
+
+ private IEnumerable NormalizeChanges(IEnumerable changes)
+ {
+ if (changes.Count() <= 1)
+ {
+ return changes;
+ }
+
+ changes = changes.OrderBy(c => c.Span.Start);
+ var normalizedChanges = new List();
+
+ var currentChange = changes.First();
+ foreach (var nextChange in changes.Skip(1))
+ {
+ if (nextChange.Span.Start == currentChange.Span.End)
+ {
+ currentChange = new TextChange(TextSpan.FromBounds(currentChange.Span.Start, nextChange.Span.End), currentChange.NewText + nextChange.NewText);
+ }
+ else
+ {
+ normalizedChanges.Add(currentChange);
+ currentChange = nextChange;
+ }
+ }
+
+ normalizedChanges.Add(currentChange);
+ return normalizedChanges;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Src/Workspaces/Core/Workspace/Solution/Solution.LinkedFileMergeResult.cs b/Src/Workspaces/Core/Workspace/Solution/Solution.LinkedFileMergeResult.cs
new file mode 100644
index 0000000000000000000000000000000000000000..ef206e260007516dd5429a90f2b83387a131c0dd
--- /dev/null
+++ b/Src/Workspaces/Core/Workspace/Solution/Solution.LinkedFileMergeResult.cs
@@ -0,0 +1,21 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.CodeAnalysis.Text;
+
+namespace Microsoft.CodeAnalysis
+{
+ public partial class Solution
+ {
+ private sealed class LinkedFileMergeResult
+ {
+ public SourceText MergedSourceText { get; internal set; }
+ public bool HasMergeConflicts { get; private set; }
+
+ public LinkedFileMergeResult(SourceText mergedSourceText, bool hasMergeConflicts)
+ {
+ MergedSourceText = mergedSourceText;
+ HasMergeConflicts = hasMergeConflicts;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Src/Workspaces/Core/Workspace/Solution/Solution.cs b/Src/Workspaces/Core/Workspace/Solution/Solution.cs
index 5be2b145822f3487d82b2d20b96ed75393af7dff..f4189a8d8c29a4252ac8a405e2adc8615cee9854 100644
--- a/Src/Workspaces/Core/Workspace/Solution/Solution.cs
+++ b/Src/Workspaces/Core/Workspace/Solution/Solution.cs
@@ -1,7 +1,6 @@
// Copyright (c) Microsoft Open Technologies, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
-using System.Text;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
@@ -11,8 +10,6 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Diagnostics;
-using Microsoft.CodeAnalysis.Host;
-using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Collections.Immutable;
using Roslyn.Utilities;
@@ -1282,6 +1279,12 @@ public Solution WithDocumentText(DocumentId documentId, SourceText text, Preserv
return newSolution;
}
+ internal async Task WithMergedLinkedFileChangesAsync(Solution oldSolution, SolutionChanges? solutionChanges = null, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ var session = new LinkedFileDiffMergingSession(oldSolution, this, solutionChanges ?? this.GetChanges(oldSolution));
+ return await session.MergeDiffsAsync(cancellationToken).ConfigureAwait(false);
+ }
+
private SolutionBranch firstBranch;
private class SolutionBranch
diff --git a/Src/Workspaces/Core/Workspace/Solution/UnmergedDocumentChanges.cs b/Src/Workspaces/Core/Workspace/Solution/UnmergedDocumentChanges.cs
new file mode 100644
index 0000000000000000000000000000000000000000..be5c80067ba6e9c0ac73532c1eab94b245ba2e9b
--- /dev/null
+++ b/Src/Workspaces/Core/Workspace/Solution/UnmergedDocumentChanges.cs
@@ -0,0 +1,21 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Microsoft.CodeAnalysis
+{
+ internal sealed class UnmergedDocumentChanges
+ {
+ public IEnumerable UnmergedChanges { get; private set; }
+ public SourceText Text { get; private set; }
+ public string ProjectName { get; private set; }
+
+ public UnmergedDocumentChanges(IEnumerable unmergedChanges, SourceText text, string projectName)
+ {
+ UnmergedChanges = unmergedChanges;
+ Text = text;
+ ProjectName = projectName;
+ }
+ }
+}
diff --git a/Src/Workspaces/Core/Workspace/Workspace.cs b/Src/Workspaces/Core/Workspace/Workspace.cs
index 827f9133ba8b42bab6b8dd395e9beff26e5f90a9..1977d513429ea6eda4235ba100802a8a6edf1183 100644
--- a/Src/Workspaces/Core/Workspace/Workspace.cs
+++ b/Src/Workspaces/Core/Workspace/Workspace.cs
@@ -774,6 +774,9 @@ public virtual bool TryApplyChanges(Solution newSolution)
throw new NotSupportedException(WorkspacesResources.AddingProjectsNotSupported);
}
+ var solutionWithLinkedFileChangesMerged = newSolution.WithMergedLinkedFileChangesAsync(oldSolution, solutionChanges, CancellationToken.None).Result;
+ solutionChanges = solutionWithLinkedFileChangesMerged.GetChanges(oldSolution);
+
// process all project changes
foreach (var projectChanges in solutionChanges.GetProjectChanges())
{
diff --git a/Src/Workspaces/Core/Workspaces.csproj b/Src/Workspaces/Core/Workspaces.csproj
index 5e7efbc97e407b0fd461a2e861d23e95900ef241..d3a28340148db60d4680638b6e45300bb609a986 100644
--- a/Src/Workspaces/Core/Workspaces.csproj
+++ b/Src/Workspaces/Core/Workspaces.csproj
@@ -1,9 +1,9 @@
-
+
Debug
@@ -853,6 +853,7 @@
+
true
@@ -863,6 +864,7 @@
+
@@ -886,6 +888,8 @@
true
+
+
@@ -894,6 +898,7 @@
+
@@ -948,9 +953,6 @@
-
-
-
@@ -961,6 +963,5 @@
This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
-
\ No newline at end of file
diff --git a/Src/Workspaces/Core/WorkspacesResources.Designer.cs b/Src/Workspaces/Core/WorkspacesResources.Designer.cs
index d395898a617778affcce3f9ae6c23d026807be24..05d670ab87fc39567c7f33390a7e8b6f57a06e85 100644
--- a/Src/Workspaces/Core/WorkspacesResources.Designer.cs
+++ b/Src/Workspaces/Core/WorkspacesResources.Designer.cs
@@ -69,6 +69,15 @@ internal class WorkspacesResources {
}
}
+ ///
+ /// Looks up a localized string similar to Added:.
+ ///
+ internal static string AddedHeader {
+ get {
+ return ResourceManager.GetString("AddedHeader", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Adding projects not supported in ApplyChanges..
///
@@ -78,6 +87,15 @@ internal class WorkspacesResources {
}
}
+ ///
+ /// Looks up a localized string similar to After:.
+ ///
+ internal static string AfterHeader {
+ get {
+ return ResourceManager.GetString("AfterHeader", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to {0} is already present..
///
@@ -105,6 +123,15 @@ internal class WorkspacesResources {
}
}
+ ///
+ /// Looks up a localized string similar to Before:.
+ ///
+ internal static string BeforeHeader {
+ get {
+ return ResourceManager.GetString("BeforeHeader", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Cannot generate code for unsupported operator '{0}'.
///
@@ -609,6 +636,15 @@ internal class WorkspacesResources {
}
}
+ ///
+ /// Looks up a localized string similar to Removed:.
+ ///
+ internal static string RemovedHeader {
+ get {
+ return ResourceManager.GetString("RemovedHeader", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Remove Unnecessary Imports/Usings..
///
@@ -672,6 +708,15 @@ internal class WorkspacesResources {
}
}
+ ///
+ /// Looks up a localized string similar to Unmerged change from project '{0}'.
+ ///
+ internal static string UnmergedChangeFromProject {
+ get {
+ return ResourceManager.GetString("UnmergedChangeFromProject", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Unrecognized language name..
///
diff --git a/Src/Workspaces/Core/WorkspacesResources.resx b/Src/Workspaces/Core/WorkspacesResources.resx
index 25fa421a8c78422644b56564e19a67bf48bc2e0d..d5c5be4b192fd8d786acac4889908628293cae77 100644
--- a/Src/Workspaces/Core/WorkspacesResources.resx
+++ b/Src/Workspaces/Core/WorkspacesResources.resx
@@ -333,4 +333,19 @@
Solution file not found: '{0}'
+
+ Unmerged change from project '{0}'
+
+
+ Added:
+
+
+ After:
+
+
+ Before:
+
+
+ Removed:
+
\ No newline at end of file
diff --git a/Src/Workspaces/CoreTest/LinkedFileDiffMerging/LinkedFileDiffMergingTests.Features.cs b/Src/Workspaces/CoreTest/LinkedFileDiffMerging/LinkedFileDiffMergingTests.Features.cs
new file mode 100644
index 0000000000000000000000000000000000000000..272ab37f35e47dbece175860e8f1800ceb69feb6
--- /dev/null
+++ b/Src/Workspaces/CoreTest/LinkedFileDiffMerging/LinkedFileDiffMergingTests.Features.cs
@@ -0,0 +1,169 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+using Microsoft.CodeAnalysis.Test.Utilities;
+using Xunit;
+
+namespace Microsoft.CodeAnalysis.UnitTests.LinkedFileDiffMerging
+{
+ public partial class LinkedFileDiffMergingTests
+ {
+ [Fact]
+ [Trait(Traits.Feature, Traits.Features.LinkedFileDiffMerging)]
+ public void TestChangeSignature()
+ {
+ TestLinkedFileSet(
+ @"public class Class1
+{
+ void M(int x, string y, int z)
+ {
+ }
+#if LIB1
+ void N()
+ {
+ M(2, ""A"", 1);
+ }
+#elif LIB2
+ void N()
+ {
+ M(4, ""B"", 3);
+ }
+#endif
+}",
+ new List
+ {
+ @"public class Class1
+{
+ void M(int z, int x)
+ {
+ }
+#if LIB1
+ void N()
+ {
+ M(1, 2);
+ }
+#elif LIB2
+ void N()
+ {
+ M(4, ""B"", 3);
+ }
+#endif
+}",
+ @"public class Class1
+{
+ void M(int z, int x)
+ {
+ }
+#if LIB1
+ void N()
+ {
+ M(2, ""A"", 1);
+ }
+#elif LIB2
+ void N()
+ {
+ M(3, 4);
+ }
+#endif
+}"
+ },
+ @"public class Class1
+{
+ void M(int z, int x)
+ {
+ }
+#if LIB1
+ void N()
+ {
+ M(1, 2);
+ }
+#elif LIB2
+ void N()
+ {
+ M(3, 4);
+ }
+#endif
+}",
+ LanguageNames.CSharp);
+ }
+
+ [Fact]
+ [Trait(Traits.Feature, Traits.Features.LinkedFileDiffMerging)]
+ public void TestRename()
+ {
+ TestLinkedFileSet(
+ @"public class Class1
+{
+ void M()
+ {
+ }
+#if LIB1
+ void N()
+ {
+ M();
+ }
+#elif LIB2
+ void N()
+ {
+ M();
+ }
+#endif
+}",
+ new List
+ {
+ @"public class Class1
+{
+ void Method()
+ {
+ }
+#if LIB1
+ void N()
+ {
+ Method();
+ }
+#elif LIB2
+ void N()
+ {
+ M();
+ }
+#endif
+}",
+ @"public class Class1
+{
+ void Method()
+ {
+ }
+#if LIB1
+ void N()
+ {
+ M();
+ }
+#elif LIB2
+ void N()
+ {
+ Method();
+ }
+#endif
+}"
+ },
+ @"public class Class1
+{
+ void Method()
+ {
+ }
+#if LIB1
+ void N()
+ {
+ Method();
+ }
+#elif LIB2
+ void N()
+ {
+ Method();
+ }
+#endif
+}",
+ LanguageNames.CSharp);
+ }
+ }
+}
diff --git a/Src/Workspaces/CoreTest/LinkedFileDiffMerging/LinkedFileDiffMergingTests.TextMerging.cs b/Src/Workspaces/CoreTest/LinkedFileDiffMerging/LinkedFileDiffMergingTests.TextMerging.cs
new file mode 100644
index 0000000000000000000000000000000000000000..d41624147d30d55f6d55216e82c3abedb10cb07f
--- /dev/null
+++ b/Src/Workspaces/CoreTest/LinkedFileDiffMerging/LinkedFileDiffMergingTests.TextMerging.cs
@@ -0,0 +1,279 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+using Microsoft.CodeAnalysis.Test.Utilities;
+using Xunit;
+
+namespace Microsoft.CodeAnalysis.UnitTests.LinkedFileDiffMerging
+{
+ public partial class LinkedFileDiffMergingTests
+ {
+ [Fact]
+ [Trait(Traits.Feature, Traits.Features.LinkedFileDiffMerging)]
+ public void TestIdenticalChanges()
+ {
+ TestLinkedFileSet(
+ "x",
+ new List { "y", "y" },
+ @"y",
+ LanguageNames.CSharp);
+ }
+
+ [Fact]
+ [Trait(Traits.Feature, Traits.Features.LinkedFileDiffMerging)]
+ public void TestChangesInOnlyOneFile()
+ {
+ TestLinkedFileSet(
+ "a b c d e",
+ new List { "a b c d e", "a z c z e" },
+ @"a z c z e",
+ LanguageNames.CSharp);
+ }
+
+ [Fact]
+ [Trait(Traits.Feature, Traits.Features.LinkedFileDiffMerging)]
+ public void TestIsolatedChangesInBothFiles()
+ {
+ TestLinkedFileSet(
+ "a b c d e",
+ new List { "a z c d e", "a b c z e" },
+ @"a z c z e",
+ LanguageNames.CSharp);
+ }
+
+ [Fact]
+ [Trait(Traits.Feature, Traits.Features.LinkedFileDiffMerging)]
+ public void TestIdenticalEditAfterIsolatedChanges()
+ {
+ TestLinkedFileSet(
+ "a b c d e",
+ new List { "a zzz c xx e", "a b c xx e" },
+ @"a zzz c xx e",
+ LanguageNames.CSharp);
+ }
+
+ [Fact]
+ [Trait(Traits.Feature, Traits.Features.LinkedFileDiffMerging)]
+ public void TestOneConflict()
+ {
+ TestLinkedFileSet(
+ "a b c d e",
+ new List { "a b y d e", "a b z d e" },
+ @"
+/* Unmerged change from project 'ProjectName1'
+Before:
+a b c d e
+After:
+a b z d e
+*/
+a b y d e",
+ LanguageNames.CSharp);
+ }
+
+ [Fact]
+ [Trait(Traits.Feature, Traits.Features.LinkedFileDiffMerging)]
+ public void TestTwoConflictsOnSameLine()
+ {
+ TestLinkedFileSet(
+ "a b c d e",
+ new List { "a q1 c z1 e", "a q2 c z2 e" },
+ @"
+/* Unmerged change from project 'ProjectName1'
+Before:
+a b c d e
+After:
+a q2 c z2 e
+*/
+a q1 c z1 e",
+ LanguageNames.CSharp);
+ }
+
+ [Fact]
+ [Trait(Traits.Feature, Traits.Features.LinkedFileDiffMerging)]
+ public void TestTwoConflictsOnAdjacentLines()
+ {
+ TestLinkedFileSet(
+ @"One
+Two
+Three
+Four",
+ new List
+ {
+ @"One
+TwoY
+ThreeY
+Four",
+ @"One
+TwoZ
+ThreeZ
+Four"
+ },
+ @"One
+
+/* Unmerged change from project 'ProjectName1'
+Before:
+Two
+Three
+After:
+TwoZ
+ThreeZ
+*/
+TwoY
+ThreeY
+Four",
+ LanguageNames.CSharp);
+ }
+
+ [Fact]
+ [Trait(Traits.Feature, Traits.Features.LinkedFileDiffMerging)]
+ public void TestTwoConflictsOnSeparatedLines()
+ {
+ TestLinkedFileSet(
+ @"One
+Two
+Three
+Four
+Five",
+ new List
+ {
+ @"One
+TwoY
+Three
+FourY
+Five",
+ @"One
+TwoZ
+Three
+FourZ
+Five"
+ },
+ @"One
+
+/* Unmerged change from project 'ProjectName1'
+Before:
+Two
+After:
+TwoZ
+*/
+TwoY
+Three
+
+/* Unmerged change from project 'ProjectName1'
+Before:
+Four
+After:
+FourZ
+*/
+FourY
+Five",
+ LanguageNames.CSharp);
+ }
+
+ [Fact]
+ [Trait(Traits.Feature, Traits.Features.LinkedFileDiffMerging)]
+ public void TestManyLinkedFilesWithOverlappingChange()
+ {
+ TestLinkedFileSet(
+ @"A",
+ new List
+ {
+ @"A",
+ @"B",
+ @"C",
+ @"",
+ },
+ @"
+/* Unmerged change from project 'ProjectName2'
+Before:
+A
+After:
+C
+*/
+
+/* Unmerged change from project 'ProjectName3'
+Removed:
+A
+*/
+B",
+ LanguageNames.CSharp);
+ }
+
+ [Fact]
+ [Trait(Traits.Feature, Traits.Features.LinkedFileDiffMerging)]
+ public void TestCommentsAddedCodeCSharp()
+ {
+ TestLinkedFileSet(
+ @"",
+ new List
+ {
+ @"A",
+ @"B",
+ },
+ @"
+/* Unmerged change from project 'ProjectName1'
+Added:
+B
+*/
+A",
+ LanguageNames.CSharp);
+ }
+
+ [Fact]
+ [Trait(Traits.Feature, Traits.Features.LinkedFileDiffMerging)]
+ public void TestCommentsAddedCodeVB()
+ {
+ TestLinkedFileSet(
+ @"",
+ new List
+ {
+ @"A",
+ @"B",
+ },
+ @"
+' Unmerged change from project 'ProjectName1'
+' Added:
+' B
+A",
+ LanguageNames.VisualBasic);
+ }
+
+ [Fact]
+ [Trait(Traits.Feature, Traits.Features.LinkedFileDiffMerging)]
+ public void TestCommentsRemovedCodeCSharp()
+ {
+ TestLinkedFileSet(
+ @"A",
+ new List
+ {
+ @"B",
+ @"",
+ },
+ @"
+/* Unmerged change from project 'ProjectName1'
+Removed:
+A
+*/
+B",
+ LanguageNames.CSharp);
+ }
+
+ [Fact]
+ [Trait(Traits.Feature, Traits.Features.LinkedFileDiffMerging)]
+ public void TestCommentsRemovedCodeVB()
+ {
+ TestLinkedFileSet(
+ @"A",
+ new List
+ {
+ @"B",
+ @"",
+ },
+ @"
+' Unmerged change from project 'ProjectName1'
+' Removed:
+' A
+B",
+ LanguageNames.VisualBasic);
+ }
+ }
+}
diff --git a/Src/Workspaces/CoreTest/LinkedFileDiffMerging/LinkedFileDiffMergingTests.cs b/Src/Workspaces/CoreTest/LinkedFileDiffMerging/LinkedFileDiffMergingTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..05307c89651c7fdd491ed01ea86d808136b70c35
--- /dev/null
+++ b/Src/Workspaces/CoreTest/LinkedFileDiffMerging/LinkedFileDiffMergingTests.cs
@@ -0,0 +1,53 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+using Microsoft.CodeAnalysis.Text;
+using Xunit;
+
+namespace Microsoft.CodeAnalysis.UnitTests.LinkedFileDiffMerging
+{
+ public partial class LinkedFileDiffMergingTests
+ {
+ public void TestLinkedFileSet(string startText, List updatedTexts, string expectedMergedText, string languageName)
+ {
+ using (var workspace = new CustomWorkspace())
+ {
+ var solution = new CustomWorkspace().CurrentSolution;
+ var startSourceText = SourceText.From(startText);
+ var documentIds = new List();
+
+ for (int i = 0; i < updatedTexts.Count; i++)
+ {
+ var projectId = ProjectId.CreateNewId();
+ var documentId = DocumentId.CreateNewId(projectId);
+ documentIds.Add(documentId);
+
+ var projectInfo = ProjectInfo.Create(projectId, VersionStamp.Create(), "ProjectName" + i, "AssemblyName" + i, languageName);
+
+ solution = solution
+ .AddProject(projectInfo)
+ .AddDocument(documentId, "DocumentName", startSourceText, filePath: "FilePath");
+ }
+
+ var startingSolution = solution;
+ var updatedSolution = solution;
+
+ for (int i = 0; i < updatedTexts.Count; i++)
+ {
+ var text = updatedTexts[i];
+ if (text != startText)
+ {
+ updatedSolution = updatedSolution
+ .WithDocumentText(documentIds[i], SourceText.From(text));
+ }
+ }
+
+ var mergedSolution = updatedSolution.WithMergedLinkedFileChangesAsync(startingSolution).Result;
+ for (int i = 0; i < updatedTexts.Count; i++)
+ {
+ Assert.Equal(expectedMergedText, mergedSolution.GetDocument(documentIds[i]).GetTextAsync().Result.ToString());
+ }
+ }
+ }
+ }
+}
diff --git a/Src/Workspaces/CoreTest/ServicesTest.csproj b/Src/Workspaces/CoreTest/ServicesTest.csproj
index 90220c3261889d679755ab8ef7343b37ec43f03c..54f97d0c87f7b95a51243b44d720936163a12dfa 100644
--- a/Src/Workspaces/CoreTest/ServicesTest.csproj
+++ b/Src/Workspaces/CoreTest/ServicesTest.csproj
@@ -1,9 +1,9 @@
-
+
true
@@ -83,12 +83,13 @@
..\..\packages\xunit.core.2.0.0-alpha-build2576\lib\net45\xunit2.dll
-
-
-
-
+
+
+
+
+
@@ -266,9 +267,6 @@
-
-
-
@@ -279,6 +277,5 @@
This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
-
\ No newline at end of file
diff --git a/Src/Workspaces/VisualBasic/BasicWorkspace.vbproj b/Src/Workspaces/VisualBasic/BasicWorkspace.vbproj
index 8c477f7d942fb8eee1e60f4661080e330dd2101f..2b22256892e627bb198327aebbb7b05024c40b18 100644
--- a/Src/Workspaces/VisualBasic/BasicWorkspace.vbproj
+++ b/Src/Workspaces/VisualBasic/BasicWorkspace.vbproj
@@ -205,6 +205,7 @@
+
diff --git a/Src/Workspaces/VisualBasic/LinkedFiles/BasicLinkedFileMergeConflictCommentAdditionService.vb b/Src/Workspaces/VisualBasic/LinkedFiles/BasicLinkedFileMergeConflictCommentAdditionService.vb
new file mode 100644
index 0000000000000000000000000000000000000000..0d24fdb750409264a399869d70a66b4c58804277
--- /dev/null
+++ b/Src/Workspaces/VisualBasic/LinkedFiles/BasicLinkedFileMergeConflictCommentAdditionService.vb
@@ -0,0 +1,72 @@
+' Copyright (c) Microsoft Open Technologies, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+Imports System.Text
+Imports System.Text.RegularExpressions
+Imports Microsoft.CodeAnalysis.Host.Mef
+
+Namespace Microsoft.CodeAnalysis.VisualBasic
+
+ Class BasicLinkedFileMergeConflictCommentAdditionService
+ Inherits AbstractLinkedFileMergeConflictCommentAdditionService
+
+ Friend Overrides Function GetConflictCommentText(header As String, beforeString As String, afterString As String) As String
+ If beforeString Is Nothing AndAlso afterString Is Nothing Then
+ Return Nothing
+ ElseIf beforeString Is Nothing Then
+ ' Added code
+ Return String.Format("
+' {0}
+' {1}
+{2}
+",
+ header,
+ WorkspacesResources.AddedHeader,
+ GetCommentedText(afterString))
+ ElseIf afterString Is Nothing Then
+ ' Removed code
+ Return String.Format("
+' {0}
+' {1}
+{2}
+",
+ header,
+ WorkspacesResources.RemovedHeader,
+ GetCommentedText(beforeString))
+ Else
+ Return String.Format("
+' {0}
+' {1}
+{2}
+' {3}
+{4}
+",
+ header,
+ WorkspacesResources.BeforeHeader,
+ GetCommentedText(beforeString),
+ WorkspacesResources.AfterHeader,
+ GetCommentedText(afterString))
+
+ End If
+ End Function
+
+ Private Function GetCommentedText(text As String) As String
+ Dim lines = Regex.Split(text, "\r\n|\r|\n")
+ If Not lines.Any() Then
+ Return text
+ End If
+
+ Dim newlines = Regex.Matches(text, "\r\n|\r|\n")
+ Contract.Assert(newlines.Count = lines.Count - 1)
+
+ Dim builder = New StringBuilder()
+
+ For i = 0 To lines.Count() - 2
+ builder.Append(String.Format("' {0}{1}", lines(i), newlines(i)))
+ Next
+
+ builder.Append(String.Format("' {0}", lines.Last()))
+
+ Return builder.ToString()
+ End Function
+ End Class
+End Namespace
\ No newline at end of file