in src/dotnet/APIView/APIViewWeb/Helpers/CodeFileHelpers.cs [430:507]
public static List<ReviewLine> FindDiff(List<ReviewLine> activeLines, List<ReviewLine> diffLines)
{
List<ReviewLine> resultLines = [];
Dictionary<string, int> refCountMap = [];
//Set lines from diff revision as not from active revision
foreach (var line in diffLines)
{
line.IsActiveRevisionLine = false;
}
UpdateMissingRelatedLineId(activeLines);
UpdateMissingRelatedLineId(diffLines);
var intersectLines = diffLines.Intersect(activeLines);
var interleavedLines = activeLines.InterleavedUnion(diffLines);
foreach (var line in interleavedLines)
{
if (line.IsDocumentation || line.Processed || (!line.IsActiveRevisionLine && line.IsSkippedFromDiff()))
continue;
// Current node is not in both revisions. Mark current node as added or removed and then go to next sibling.
// If a node is diff then no need to check it's children as they will be marked as diff as well.
if (!intersectLines.Contains(line))
{
//Recursively mark line as added or removed if line is not skipped from diff
if (!line.IsSkippedFromDiff())
{
MarkTreeNodeAsModified(line, line.IsActiveRevisionLine ? DiffKind.Added : DiffKind.Removed);
}
//Check if diff revision has a line at same level with same Line Id. This is to handle where a API was removed and added back in different order.
// This will also ensure added and modified lines are visible next to each other in the review.
var relatedLine = line.IsActiveRevisionLine ? diffLines.FirstOrDefault(l => !string.IsNullOrEmpty(l.LineId) && l.LineId == line.LineId) :
activeLines.FirstOrDefault(l => !string.IsNullOrEmpty(l.LineId) && l.LineId == line.LineId);
if (relatedLine != null)
{
relatedLine.Processed = true;
if (!relatedLine.IsSkippedFromDiff())
{
MarkTreeNodeAsModified(relatedLine, relatedLine.IsActiveRevisionLine ? DiffKind.Added : DiffKind.Removed);
//Identify the tokens within modified lines and highlight them in the UI
FindModifiedTokens(line, relatedLine);
}
}
if (relatedLine != null)
{
// First add removed line and then added line
resultLines.Add(line.IsActiveRevisionLine ? relatedLine : line);
resultLines.Add(line.IsActiveRevisionLine ? line : relatedLine);
}
else
{
resultLines.Add(line);
}
continue;
}
var activeLine = activeLines.FirstOrDefault(l => l.LineId == line.LineId && l.Processed == false && l.Equals(line));
if (activeLine == null)
activeLine = line;
//current node is present in both trees. Compare child nodes recursively
var diffLine = diffLines.FirstOrDefault(l => l.LineId == line.LineId && l.Processed == false && l.Equals(line));
var diffLineChildren = diffLine != null ? diffLine.Children : new List<ReviewLine>();
var resultantSubTree = FindDiff(activeLine.Children, diffLineChildren);
//Update new resulting subtree as children of current node
activeLine.Children.Clear();
activeLine.Children.AddRange(resultantSubTree);
resultLines.Add(activeLine);
activeLine.Processed = true;
if (diffLine != null)
diffLine.Processed = true;
}
return resultLines;
}