using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Threading; using AxCopilot.SDK; using AxCopilot.Services; using Microsoft.Win32; namespace AxCopilot.Handlers; public class DiffHandler : IActionHandler { private record DiffResult(string Text, int Added, int Removed, int Same); private readonly ClipboardHistoryService? _clipHistory; public string? Prefix => "diff"; public PluginMetadata Metadata => new PluginMetadata("Diff", "텍스트/파일 비교 — diff", "1.0", "AX"); public DiffHandler(ClipboardHistoryService? clipHistory = null) { _clipHistory = clipHistory; } public Task> GetItemsAsync(string query, CancellationToken ct) { string text = query.Trim(); if (!string.IsNullOrWhiteSpace(text)) { string[] array = text.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); if (array.Length == 2 && File.Exists(array[0]) && File.Exists(array[1])) { try { string textA = File.ReadAllText(array[0]); string textB = File.ReadAllText(array[1]); DiffResult diffResult = BuildDiff(textA, textB, Path.GetFileName(array[0]), Path.GetFileName(array[1])); return Task.FromResult((IEnumerable)new _003C_003Ez__ReadOnlySingleElementList(new LauncherItem("파일 비교: " + Path.GetFileName(array[0]) + " ↔ " + Path.GetFileName(array[1]), $"{diffResult.Added}줄 추가, {diffResult.Removed}줄 삭제, {diffResult.Same}줄 동일 · Enter로 결과 복사", null, diffResult.Text, null, "\ue8a5"))); } catch (Exception ex) { return Task.FromResult((IEnumerable)new _003C_003Ez__ReadOnlySingleElementList(new LauncherItem("파일 읽기 실패: " + ex.Message, "", null, null, null, "\uea39"))); } } if (array.Length >= 1 && (File.Exists(array[0]) || Directory.Exists(Path.GetDirectoryName(array[0]) ?? ""))) { return Task.FromResult((IEnumerable)new _003C_003Ez__ReadOnlySingleElementList(new LauncherItem("비교할 파일 2개의 경로를 입력하세요", "예: diff C:\\a.txt C:\\b.txt", null, null, null, "\ue946"))); } } IReadOnlyList readOnlyList = _clipHistory?.History; if (readOnlyList == null || readOnlyList.Count < 2) { return Task.FromResult((IEnumerable)new _003C_003Ez__ReadOnlySingleElementList(new LauncherItem("비교할 텍스트가 부족합니다", "클립보드에 2개 이상의 텍스트를 복사하거나, diff [파일A] [파일B]로 파일 비교", null, null, null, "\ue946"))); } List list = readOnlyList.Where((ClipboardEntry e) => e.IsText).Take(2).ToList(); if (list.Count < 2) { return Task.FromResult((IEnumerable)new _003C_003Ez__ReadOnlySingleElementList(new LauncherItem("텍스트 히스토리가 2개 미만입니다", "텍스트를 2번 이상 복사하세요", null, null, null, "\ue946"))); } ClipboardEntry clipboardEntry = list[1]; ClipboardEntry clipboardEntry2 = list[0]; DiffResult diffResult2 = BuildDiff(clipboardEntry.Text, clipboardEntry2.Text, "이전 (" + clipboardEntry.RelativeTime + ")", "최근 (" + clipboardEntry2.RelativeTime + ")"); List list2 = new List { new LauncherItem($"클립보드 비교: +{diffResult2.Added} -{diffResult2.Removed} ={diffResult2.Same}", "이전 복사 ↔ 최근 복사 · Enter로 결과 복사", null, diffResult2.Text, null, "\ue81c") }; IEnumerable enumerable = (from l in diffResult2.Text.Split('\n') where l.StartsWith("+ ") || l.StartsWith("- ") select l).Take(5); foreach (string item in enumerable) { string symbol = (item.StartsWith("+ ") ? "\ue710" : "\ue711"); list2.Add(new LauncherItem(item, "", null, null, null, symbol)); } list2.Add(new LauncherItem("파일 선택하여 비교", "파일 선택 대화 상자에서 2개의 파일을 골라 비교합니다", null, "__FILE_DIALOG__", null, "\ue8a5")); return Task.FromResult((IEnumerable)list2); } public async Task ExecuteAsync(LauncherItem item, CancellationToken ct) { object data = item.Data; if (data is string s && s == "__FILE_DIALOG__") { await Task.Delay(200, ct); Application current = Application.Current; if (current == null) { return; } ((DispatcherObject)current).Dispatcher.Invoke((Action)delegate { OpenFileDialog openFileDialog = new OpenFileDialog { Title = "비교할 첫 번째 파일 선택", Filter = "텍스트 파일|*.txt;*.cs;*.json;*.xml;*.md;*.csv;*.log|모든 파일|*.*" }; if (openFileDialog.ShowDialog() == true) { string fileName = openFileDialog.FileName; openFileDialog.Title = "비교할 두 번째 파일 선택"; if (openFileDialog.ShowDialog() == true) { string fileName2 = openFileDialog.FileName; try { string textA = File.ReadAllText(fileName); string textB = File.ReadAllText(fileName2); DiffResult diffResult = BuildDiff(textA, textB, Path.GetFileName(fileName), Path.GetFileName(fileName2)); Clipboard.SetText(diffResult.Text); NotificationService.Notify("파일 비교 완료", $"+{diffResult.Added} -{diffResult.Removed} ={diffResult.Same} · 결과 클립보드 복사됨"); } catch (Exception ex) { NotificationService.Notify("비교 실패", ex.Message); } } } }); return; } data = item.Data; string text = data as string; if (text == null || string.IsNullOrWhiteSpace(text)) { return; } try { Application current2 = Application.Current; if (current2 != null) { ((DispatcherObject)current2).Dispatcher.Invoke((Action)delegate { Clipboard.SetText(text); }); } } catch { } NotificationService.Notify("비교 결과", "클립보드에 복사되었습니다"); } private static DiffResult BuildDiff(string textA, string textB, string labelA, string labelB) { string[] array = (from l in textA.Split('\n') select l.TrimEnd('\r')).ToArray(); string[] array2 = (from l in textB.Split('\n') select l.TrimEnd('\r')).ToArray(); HashSet hashSet = new HashSet(array); HashSet hashSet2 = new HashSet(array2); StringBuilder stringBuilder = new StringBuilder(); StringBuilder stringBuilder2 = stringBuilder; StringBuilder stringBuilder3 = stringBuilder2; StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(4, 1, stringBuilder2); handler.AppendLiteral("--- "); handler.AppendFormatted(labelA); stringBuilder3.AppendLine(ref handler); stringBuilder2 = stringBuilder; StringBuilder stringBuilder4 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(4, 1, stringBuilder2); handler.AppendLiteral("+++ "); handler.AppendFormatted(labelB); stringBuilder4.AppendLine(ref handler); stringBuilder.AppendLine(); int num = 0; int num2 = 0; int num3 = 0; int num4 = Math.Max(array.Length, array2.Length); for (int num5 = 0; num5 < num4; num5++) { string text = ((num5 < array.Length) ? array[num5] : null); string text2 = ((num5 < array2.Length) ? array2[num5] : null); if (text == text2) { stringBuilder2 = stringBuilder; StringBuilder stringBuilder5 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(2, 1, stringBuilder2); handler.AppendLiteral(" "); handler.AppendFormatted(text); stringBuilder5.AppendLine(ref handler); num3++; continue; } if (text != null && !hashSet2.Contains(text)) { stringBuilder2 = stringBuilder; StringBuilder stringBuilder6 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(2, 1, stringBuilder2); handler.AppendLiteral("- "); handler.AppendFormatted(text); stringBuilder6.AppendLine(ref handler); num2++; } if (text2 != null && !hashSet.Contains(text2)) { stringBuilder2 = stringBuilder; StringBuilder stringBuilder7 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(2, 1, stringBuilder2); handler.AppendLiteral("+ "); handler.AppendFormatted(text2); stringBuilder7.AppendLine(ref handler); num++; } if (text != null && hashSet2.Contains(text) && text != text2) { stringBuilder2 = stringBuilder; StringBuilder stringBuilder8 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(2, 1, stringBuilder2); handler.AppendLiteral(" "); handler.AppendFormatted(text); stringBuilder8.AppendLine(ref handler); num3++; } } return new DiffResult(stringBuilder.ToString(), num, num2, num3); } }