using System.Text; using System.Text.RegularExpressions; using System.Windows; using AxCopilot.SDK; using AxCopilot.Services; using AxCopilot.Themes; namespace AxCopilot.Handlers; /// /// 텍스트 일괄 처리 핸들러. "batch" 프리픽스로 사용합니다. /// 클립보드 텍스트의 각 줄에 동시에 변환을 적용합니다. /// 예: batch prefix [텍스트] → 각 줄 앞에 [텍스트] 추가 /// batch suffix [텍스트] → 각 줄 뒤에 [텍스트] 추가 /// batch number → 줄번호 추가 /// batch sort → 줄 정렬 /// batch unique → 중복 줄 제거 /// batch wrap " → 각 줄을 "로 감싸기 /// batch replace A B → A를 B로 치환 /// batch csv → 줄 → CSV 한 줄로 합치기 /// batch split , → CSV 한 줄 → 여러 줄로 분리 /// Enter → 결과를 클립보드에 복사. /// public class BatchTextHandler : IActionHandler { public string? Prefix => "batch"; public PluginMetadata Metadata => new( "BatchText", "텍스트 일괄 처리 — batch", "1.0", "AX"); private static readonly (string Cmd, string Desc)[] Commands = [ ("prefix [텍스트]", "각 줄 앞에 텍스트 추가"), ("suffix [텍스트]", "각 줄 뒤에 텍스트 추가"), ("wrap [문자]", "각 줄을 지정 문자로 감싸기 (예: wrap \")"), ("number", "줄번호 추가 (1. 2. 3. ...)"), ("sort", "줄 오름차순 정렬"), ("sortd", "줄 내림차순 정렬"), ("reverse", "줄 순서 뒤집기"), ("unique", "중복 줄 제거"), ("trim", "각 줄 앞뒤 공백 제거"), ("replace [A] [B]", "A를 B로 전체 치환"), ("csv", "줄들을 쉼표로 합쳐 한 줄로"), ("split [구분자]", "한 줄을 구분자로 분리하여 여러 줄로"), ("indent [N]", "각 줄 앞에 공백 N개 추가"), ("unindent", "각 줄의 선행 공백/탭 제거"), ]; public Task> GetItemsAsync(string query, CancellationToken ct) { var q = query.Trim(); if (string.IsNullOrWhiteSpace(q)) { var items = Commands.Select(c => new LauncherItem( $"batch {c.Cmd}", c.Desc, null, null, Symbol: Symbols.Text)).ToList(); items.Insert(0, new LauncherItem( "텍스트 일괄 처리", "클립보드 텍스트의 각 줄에 변환 적용 · 명령 입력", null, null, Symbol: Symbols.Info)); return Task.FromResult>(items); } // 클립보드 읽기 string? text = null; try { if (Application.Current?.Dispatcher.Invoke(() => Clipboard.ContainsText()) == true) text = Application.Current.Dispatcher.Invoke(() => Clipboard.GetText()); } catch (Exception) { } if (string.IsNullOrEmpty(text)) { return Task.FromResult>( [ new LauncherItem("클립보드에 텍스트가 없습니다", "텍스트를 복사한 후 시도하세요", null, null, Symbol: Symbols.Warning) ]); } var lines = text.Split('\n').Select(l => l.TrimEnd('\r')).ToArray(); var parts = q.Split(' ', 2, StringSplitOptions.TrimEntries); var cmd = parts[0].ToLowerInvariant(); var arg = parts.Length > 1 ? parts[1] : ""; string? result = null; string? desc = null; try { switch (cmd) { case "prefix": result = string.Join("\n", lines.Select(l => arg + l)); desc = $"각 줄 앞에 '{arg}' 추가"; break; case "suffix": result = string.Join("\n", lines.Select(l => l + arg)); desc = $"각 줄 뒤에 '{arg}' 추가"; break; case "wrap": var w = string.IsNullOrEmpty(arg) ? "\"" : arg; result = string.Join("\n", lines.Select(l => w + l + w)); desc = $"각 줄을 '{w}'로 감싸기"; break; case "number": result = string.Join("\n", lines.Select((l, i) => $"{i + 1}. {l}")); desc = "줄번호 추가"; break; case "sort": result = string.Join("\n", lines.Order()); desc = "오름차순 정렬"; break; case "sortd": result = string.Join("\n", lines.OrderDescending()); desc = "내림차순 정렬"; break; case "reverse": result = string.Join("\n", lines.Reverse()); desc = "줄 순서 뒤집기"; break; case "unique": var unique = lines.Distinct().ToArray(); result = string.Join("\n", unique); desc = $"중복 제거: {lines.Length}줄 → {unique.Length}줄"; break; case "trim": result = string.Join("\n", lines.Select(l => l.Trim())); desc = "각 줄 공백 제거"; break; case "replace": var rParts = arg.Split(' ', 2, StringSplitOptions.TrimEntries); if (rParts.Length == 2) { result = text.Replace(rParts[0], rParts[1]); desc = $"'{rParts[0]}' → '{rParts[1]}' 치환"; } break; case "csv": result = string.Join(",", lines.Select(l => l.Trim())); desc = $"{lines.Length}줄 → CSV 한 줄"; break; case "split": var sep = string.IsNullOrEmpty(arg) ? "," : arg; result = string.Join("\n", text.Split(sep)); desc = $"'{sep}' 기준 분리"; break; case "indent": var n = int.TryParse(arg, out var indent) ? indent : 4; var pad = new string(' ', n); result = string.Join("\n", lines.Select(l => pad + l)); desc = $"{n}칸 들여쓰기"; break; case "unindent": result = string.Join("\n", lines.Select(l => l.TrimStart(' ', '\t'))); desc = "선행 공백 제거"; break; } } catch (Exception ex) { return Task.FromResult>( [ new LauncherItem($"처리 오류: {ex.Message}", "입력 데이터를 확인하세요", null, null, Symbol: Symbols.Error) ]); } if (result == null) { return Task.FromResult>( [ new LauncherItem($"알 수 없는 명령: {cmd}", "batch 만 입력하면 전체 명령 목록", null, null, Symbol: Symbols.Warning) ]); } var preview = result.Length > 120 ? result[..117] + "…" : result; preview = preview.Replace("\n", "↵ "); return Task.FromResult>( [ new LauncherItem( $"[{desc}] → Enter로 클립보드 복사", preview, null, result, Symbol: Symbols.Text) ]); } public Task ExecuteAsync(LauncherItem item, CancellationToken ct) { if (item.Data is string text) { try { Application.Current?.Dispatcher.Invoke(() => Clipboard.SetText(text)); } catch (Exception) { } NotificationService.Notify("일괄 처리 완료", "변환 결과가 클립보드에 복사되었습니다"); } return Task.CompletedTask; } }