namespace AxCopilot.Services; /// /// 라인 기반 텍스트 diff 서비스. 두 텍스트의 변경 사항을 비교합니다. /// public static class DiffService { public enum DiffType { Equal, Added, Removed } public record DiffLine(DiffType Type, int? OldLineNo, int? NewLineNo, string Content); /// 두 텍스트를 라인 단위로 비교하여 diff 결과를 반환합니다. public static List ComputeDiff(string oldText, string newText) { var oldLines = (oldText ?? "").Split('\n'); var newLines = (newText ?? "").Split('\n'); var result = new List(); // LCS (Longest Common Subsequence) 기반 간단 diff var lcs = ComputeLcs(oldLines, newLines); int oi = 0, ni = 0, li = 0; while (oi < oldLines.Length || ni < newLines.Length) { if (li < lcs.Count && oi < oldLines.Length && ni < newLines.Length && oldLines[oi].TrimEnd('\r') == lcs[li] && newLines[ni].TrimEnd('\r') == lcs[li]) { result.Add(new DiffLine(DiffType.Equal, oi + 1, ni + 1, oldLines[oi].TrimEnd('\r'))); oi++; ni++; li++; } else if (li < lcs.Count && oi < oldLines.Length && oldLines[oi].TrimEnd('\r') != lcs[li]) { result.Add(new DiffLine(DiffType.Removed, oi + 1, null, oldLines[oi].TrimEnd('\r'))); oi++; } else if (ni < newLines.Length && (li >= lcs.Count || newLines[ni].TrimEnd('\r') != lcs[li])) { result.Add(new DiffLine(DiffType.Added, null, ni + 1, newLines[ni].TrimEnd('\r'))); ni++; } else { // 나머지 처리 if (oi < oldLines.Length) { result.Add(new DiffLine(DiffType.Removed, oi + 1, null, oldLines[oi].TrimEnd('\r'))); oi++; } if (ni < newLines.Length) { result.Add(new DiffLine(DiffType.Added, null, ni + 1, newLines[ni].TrimEnd('\r'))); ni++; } } } return result; } private static List ComputeLcs(string[] a, string[] b) { int m = a.Length, n = b.Length; var dp = new int[m + 1, n + 1]; for (int i = 1; i <= m; i++) for (int j = 1; j <= n; j++) dp[i, j] = a[i - 1].TrimEnd('\r') == b[j - 1].TrimEnd('\r') ? dp[i - 1, j - 1] + 1 : Math.Max(dp[i - 1, j], dp[i, j - 1]); // 역추적 var lcs = new List(); int x = m, y = n; while (x > 0 && y > 0) { if (a[x - 1].TrimEnd('\r') == b[y - 1].TrimEnd('\r')) { lcs.Add(a[x - 1].TrimEnd('\r')); x--; y--; } else if (dp[x - 1, y] > dp[x, y - 1]) x--; else y--; } lcs.Reverse(); return lcs; } /// diff 결과를 통계 요약 문자열로 반환합니다. public static string GetSummary(List diff) { int added = diff.Count(d => d.Type == DiffType.Added); int removed = diff.Count(d => d.Type == DiffType.Removed); return $"+{added} -{removed} 라인 변경"; } }