Initial commit to new repository

This commit is contained in:
2026-04-03 18:23:52 +09:00
commit deffb33cf9
5248 changed files with 267762 additions and 0 deletions

View File

@@ -0,0 +1,210 @@
using System.Text;
using System.Text.RegularExpressions;
using System.Windows;
using AxCopilot.SDK;
using AxCopilot.Services;
using AxCopilot.Themes;
namespace AxCopilot.Handlers;
/// <summary>
/// 텍스트 일괄 처리 핸들러. "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 → 결과를 클립보드에 복사.
/// </summary>
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<IEnumerable<LauncherItem>> 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<LauncherItem>();
items.Insert(0, new LauncherItem(
"텍스트 일괄 처리",
"클립보드 텍스트의 각 줄에 변환 적용 · 명령 입력",
null, null, Symbol: Symbols.Info));
return Task.FromResult<IEnumerable<LauncherItem>>(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<IEnumerable<LauncherItem>>(
[
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<IEnumerable<LauncherItem>>(
[
new LauncherItem($"처리 오류: {ex.Message}", "입력 데이터를 확인하세요",
null, null, Symbol: Symbols.Error)
]);
}
if (result == null)
{
return Task.FromResult<IEnumerable<LauncherItem>>(
[
new LauncherItem($"알 수 없는 명령: {cmd}", "batch 만 입력하면 전체 명령 목록",
null, null, Symbol: Symbols.Warning)
]);
}
var preview = result.Length > 120 ? result[..117] + "…" : result;
preview = preview.Replace("\n", "↵ ");
return Task.FromResult<IEnumerable<LauncherItem>>(
[
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;
}
}