using System.Windows; using AxCopilot.SDK; using AxCopilot.Services; using AxCopilot.Themes; namespace AxCopilot.Handlers; /// /// paste prefix 핸들러: 클립보드 히스토리를 순서대로 붙여넣습니다. /// 예: paste / paste 3 1 5 / paste all /// public class PasteHandler : IActionHandler { private readonly ClipboardHistoryService _history; public string? Prefix => "paste"; public PluginMetadata Metadata => new( "순차 붙여넣기", "클립보드 히스토리를 순서대로 붙여넣기 (Paste Sequentially)", "1.0", "AX"); public PasteHandler(ClipboardHistoryService historyService) { _history = historyService; } public Task> GetItemsAsync(string query, CancellationToken ct) { var q = query.Trim(); var items = new List(); var history = _history.History.Where(e => e.IsText && !string.IsNullOrEmpty(e.Text)).ToList(); if (history.Count == 0) { items.Add(new LauncherItem( "클립보드 히스토리가 비어 있습니다", "텍스트를 복사하면 사용할 수 있습니다", null, null, Symbol: Symbols.ClipPaste)); return Task.FromResult>(items); } if (!string.IsNullOrWhiteSpace(q) && q != "all") { var nums = q.Split(' ', StringSplitOptions.RemoveEmptyEntries); var indices = new List(); foreach (var n in nums) { if (int.TryParse(n, out int idx) && idx >= 1 && idx <= history.Count) indices.Add(idx); } if (indices.Count > 0) { var preview = string.Join(" · ", indices.Select(i => $"#{i}")); var texts = indices.Select(i => history[i - 1].Text ?? "").ToList(); var totalLen = texts.Sum(t => t.Length); items.Add(new LauncherItem( $"순차 붙여넣기: {preview}", $"{indices.Count}개 항목 · 총 {totalLen}자 · Enter로 순서대로 붙여넣기", null, ("seq", texts), Symbol: Symbols.ClipPaste)); for (int i = 0; i < indices.Count; i++) { var entry = history[indices[i] - 1]; items.Add(new LauncherItem( $" {i + 1}. #{indices[i]}: {Truncate(entry.Preview, 60)}", entry.RelativeTime, null, null, Symbol: Symbols.History)); } return Task.FromResult>(items); } } if (q.Equals("all", StringComparison.OrdinalIgnoreCase)) { var texts = history.Take(20).Select(e => e.Text ?? "").ToList(); items.Add(new LauncherItem( $"전체 순차 붙여넣기 ({texts.Count}개)", $"Enter로 최근 {texts.Count}개 항목을 순서대로 붙여넣기", null, ("seq", texts), Symbol: Symbols.ClipPaste)); return Task.FromResult>(items); } items.Add(new LauncherItem( "순차 붙여넣기 번호를 입력하세요", "예: paste 3 1 5 · paste all", null, null, Symbol: Symbols.ClipPaste)); for (int i = 0; i < Math.Min(history.Count, 15); i++) { var entry = history[i]; var pinMark = entry.IsPinned ? "\uD83D\uDCCC " : ""; items.Add(new LauncherItem( $" #{i + 1} {pinMark}{Truncate(entry.Preview, 50)}", $"{entry.RelativeTime} · {entry.CopiedAt:MM/dd HH:mm}", null, ("single", entry.Text ?? ""), Symbol: Symbols.History)); } return Task.FromResult>(items); } public async Task ExecuteAsync(LauncherItem item, CancellationToken ct) { if (item.Data is ("single", string singleText)) { await PasteTexts([singleText], ct); } else if (item.Data is ("seq", List texts)) { await PasteTexts(texts, ct); } } private async Task PasteTexts(List texts, CancellationToken ct) { if (texts.Count == 0) return; try { var prevWindow = WindowTracker.PreviousWindow; if (prevWindow == IntPtr.Zero) return; _history.SuppressNextCapture(); var restored = await ForegroundPasteHelper.RestoreWindowAsync(prevWindow, ct, initialDelayMs: 260); if (!restored) return; foreach (var text in texts) { if (ct.IsCancellationRequested) break; _history.SuppressNextCapture(); Application.Current.Dispatcher.Invoke(() => Clipboard.SetText(text)); await Task.Delay(50, ct); await ForegroundPasteHelper.PasteClipboardAsync(prevWindow, ct, initialDelayMs: 0); await Task.Delay(200, ct); } NotificationService.Notify("paste", $"{texts.Count}개 항목 붙여넣기 완료"); } catch (OperationCanceledException) { } catch (Exception ex) { NotificationService.Notify("paste", $"붙여넣기 실패: {ex.Message}"); } } private static string Truncate(string s, int max) => s.Length <= max ? s : s[..max] + "…"; }