AX Commander 비교본 런처 기능 대량 이식
변경 목적: Agent Compare 아래 비교본의 개발 문서와 런처 소스를 기준으로 현재 AX Commander에 빠져 있던 신규 런처 기능을 동일한 흐름으로 옮겨, 비교본 수준의 기능 폭을 현재 제품에 반영했습니다. 핵심 수정사항: 비교본의 신규 런처 핸들러 다수를 src/AxCopilot/Handlers로 이식하고 App.xaml.cs 등록 흐름에 연결했습니다. 빠른 링크, 파일 태그, 알림 센터, 포모도로, 파일 브라우저, 핫키 관리, OCR, 세션/스케줄/매크로, Git/정규식/네트워크/압축/해시/UUID/JWT/QR 등 AX Commander 기능을 추가했습니다. 핵심 수정사항: 신규 기능이 실제 동작하도록 AppSettings 확장, SchedulerService/FileTagService/NotificationCenterService/IconCacheService/UrlTemplateEngine/PomodoroService 추가, 배치 이름변경/세션/스케줄/매크로 편집 창 추가, NotificationService와 Symbols 보강, QR/OCR용 csproj 의존성과 Windows 타겟 프레임워크를 반영했습니다. 문서 반영: README.md와 docs/DEVELOPMENT.md에 비교본 기반 런처 기능 이식 이력과 검증 결과를 업데이트했습니다. 검증 결과: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\ 실행 기준 경고 0개, 오류 0개를 확인했습니다.
This commit is contained in:
235
src/AxCopilot/Handlers/BaseConvertHandler.cs
Normal file
235
src/AxCopilot/Handlers/BaseConvertHandler.cs
Normal file
@@ -0,0 +1,235 @@
|
||||
using System.Windows;
|
||||
using AxCopilot.SDK;
|
||||
using AxCopilot.Services;
|
||||
using AxCopilot.Themes;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// L9-4: 진수 변환기 핸들러. "base" 프리픽스로 사용합니다.
|
||||
///
|
||||
/// 예: base 255 → 10진수 → 2/8/16진수 동시 변환
|
||||
/// base 0xFF → 16진수 → 10/2/8진수
|
||||
/// base 0b11111111 → 2진수 → 10/8/16진수
|
||||
/// base 0o377 → 8진수 → 10/2/16진수
|
||||
/// base 255 to hex → 10→16진수 결과만
|
||||
/// base ascii 65 → ASCII 코드 변환 (65 = 'A')
|
||||
/// base ascii A → 문자 → ASCII 코드
|
||||
/// Enter → 결과를 클립보드에 복사.
|
||||
/// </summary>
|
||||
public class BaseConvertHandler : IActionHandler
|
||||
{
|
||||
public string? Prefix => "base";
|
||||
|
||||
public PluginMetadata Metadata => new(
|
||||
"BaseConvert",
|
||||
"진수 변환기 — 2 · 8 · 10 · 16진수 · ASCII",
|
||||
"1.0",
|
||||
"AX");
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
var q = query.Trim();
|
||||
var items = new List<LauncherItem>();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(q))
|
||||
{
|
||||
items.Add(new LauncherItem(
|
||||
"진수 변환기",
|
||||
"예: base 255 / base 0xFF / base 0b1010 / base ascii 65",
|
||||
null, null, Symbol: "\uE8C4"));
|
||||
items.Add(new LauncherItem("base 255", "10진수 → 2/8/16진수", null, null, Symbol: "\uE8C4"));
|
||||
items.Add(new LauncherItem("base 0xFF", "16진수 → 10/2/8진수", null, null, Symbol: "\uE8C4"));
|
||||
items.Add(new LauncherItem("base 0b1111", "2진수 → 10/8/16진수", null, null, Symbol: "\uE8C4"));
|
||||
items.Add(new LauncherItem("base ascii 65", "ASCII 코드 변환", null, null, Symbol: "\uE8C4"));
|
||||
return Task.FromResult<IEnumerable<LauncherItem>>(items);
|
||||
}
|
||||
|
||||
var parts = q.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
// ASCII 모드
|
||||
if (parts[0].Equals("ascii", StringComparison.OrdinalIgnoreCase) && parts.Length >= 2)
|
||||
{
|
||||
items.AddRange(BuildAsciiItems(string.Join(" ", parts.Skip(1))));
|
||||
return Task.FromResult<IEnumerable<LauncherItem>>(items);
|
||||
}
|
||||
|
||||
// "to" 지정 변환 모드: base 255 to hex
|
||||
if (parts.Length >= 3 && parts[1].Equals("to", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (TryParseNumber(parts[0], out var val))
|
||||
{
|
||||
var targetBase = parts[2].ToLowerInvariant();
|
||||
var result = ConvertToBase(val, targetBase);
|
||||
if (result != null)
|
||||
{
|
||||
items.Add(new LauncherItem(
|
||||
result,
|
||||
$"{parts[0]} → {targetBase}",
|
||||
null,
|
||||
("copy", result),
|
||||
Symbol: "\uE8C4"));
|
||||
}
|
||||
else
|
||||
{
|
||||
items.Add(new LauncherItem("알 수 없는 진수",
|
||||
"bin/oct/dec/hex 중 하나를 지정하세요", null, null, Symbol: "\uE783"));
|
||||
}
|
||||
}
|
||||
return Task.FromResult<IEnumerable<LauncherItem>>(items);
|
||||
}
|
||||
|
||||
// 전체 변환 모드
|
||||
if (TryParseNumber(parts[0], out var number))
|
||||
{
|
||||
items.AddRange(BuildConversionItems(number, parts[0]));
|
||||
}
|
||||
else
|
||||
{
|
||||
// ASCII 문자열로 시도
|
||||
if (q.Length <= 8 && q.All(c => c >= 32 && c < 128))
|
||||
{
|
||||
items.AddRange(BuildAsciiItems(q));
|
||||
}
|
||||
else
|
||||
{
|
||||
items.Add(new LauncherItem("파싱 실패", $"'{q}'을(를) 숫자로 해석할 수 없습니다", null, null, Symbol: "\uE783"));
|
||||
}
|
||||
}
|
||||
|
||||
return Task.FromResult<IEnumerable<LauncherItem>>(items);
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
if (item.Data is ("copy", string text))
|
||||
{
|
||||
try
|
||||
{
|
||||
System.Windows.Application.Current.Dispatcher.Invoke(
|
||||
() => Clipboard.SetText(text));
|
||||
NotificationService.Notify("BaseConvert", "클립보드에 복사했습니다.");
|
||||
}
|
||||
catch { /* 비핵심 */ }
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
// ── 헬퍼 ────────────────────────────────────────────────────────────────
|
||||
|
||||
private static IEnumerable<LauncherItem> BuildConversionItems(long val, string inputStr)
|
||||
{
|
||||
var inputBase = DetectBase(inputStr);
|
||||
|
||||
yield return new LauncherItem(
|
||||
$"{val}",
|
||||
$"10진수 (입력: {inputStr})",
|
||||
null,
|
||||
("copy", val.ToString()),
|
||||
Symbol: "\uE8C4");
|
||||
|
||||
var hex = $"0x{val:X}";
|
||||
yield return new LauncherItem(hex, "16진수 (HEX)", null, ("copy", hex), Symbol: "\uE8C4");
|
||||
|
||||
var bin = val >= 0 ? $"0b{Convert.ToString(val, 2)}" : $"-0b{Convert.ToString(-val, 2)}";
|
||||
yield return new LauncherItem(bin, "2진수 (BIN)", null, ("copy", bin), Symbol: "\uE8C4");
|
||||
|
||||
var oct = val >= 0 ? $"0o{Convert.ToString(val, 8)}" : $"-0o{Convert.ToString(-val, 8)}";
|
||||
yield return new LauncherItem(oct, "8진수 (OCT)", null, ("copy", oct), Symbol: "\uE8C4");
|
||||
|
||||
// 2진수 그룹핑 표시 (4비트 단위)
|
||||
if (val >= 0 && val <= 0xFFFFFFFF)
|
||||
{
|
||||
var binRaw = Convert.ToString(val, 2).PadLeft((Convert.ToString(val, 2).Length + 3) / 4 * 4, '0');
|
||||
var binGrouped = string.Join(" ", Enumerable.Range(0, binRaw.Length / 4)
|
||||
.Select(i => binRaw.Substring(i * 4, 4)));
|
||||
yield return new LauncherItem(binGrouped, "2진수 (4비트 그룹)", null, ("copy", binGrouped), Symbol: "\uE8C4");
|
||||
}
|
||||
|
||||
// ASCII 문자 (0~127 범위)
|
||||
if (val is >= 32 and <= 126)
|
||||
{
|
||||
var ch = (char)val;
|
||||
yield return new LauncherItem($"'{ch}'", $"ASCII 문자 (코드 {val})", null, ("copy", ch.ToString()), Symbol: "\uE8C4");
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<LauncherItem> BuildAsciiItems(string input)
|
||||
{
|
||||
// 숫자 → 문자
|
||||
if (long.TryParse(input, out var code) && code >= 0 && code <= 127)
|
||||
{
|
||||
var ch = (char)code;
|
||||
yield return new LauncherItem(
|
||||
$"'{ch}'",
|
||||
$"ASCII {code} = '{ch}'",
|
||||
null,
|
||||
("copy", ch.ToString()),
|
||||
Symbol: "\uE8C4");
|
||||
|
||||
yield return new LauncherItem($"HEX: 0x{code:X2}", "16진수", null, ("copy", $"0x{code:X2}"), Symbol: "\uE8C4");
|
||||
yield return new LauncherItem($"BIN: {Convert.ToString(code, 2).PadLeft(8, '0')}", "2진수", null, ("copy", Convert.ToString(code, 2).PadLeft(8, '0')), Symbol: "\uE8C4");
|
||||
yield break;
|
||||
}
|
||||
|
||||
// 문자/문자열 → 코드
|
||||
foreach (var c in input.Where(c => c < 128))
|
||||
{
|
||||
var codeVal = (int)c;
|
||||
yield return new LauncherItem(
|
||||
$"'{c}' = {codeVal}",
|
||||
$"HEX: 0x{codeVal:X2} BIN: {Convert.ToString(codeVal, 2).PadLeft(8, '0')}",
|
||||
null,
|
||||
("copy", codeVal.ToString()),
|
||||
Symbol: "\uE8C4");
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryParseNumber(string s, out long result)
|
||||
{
|
||||
result = 0;
|
||||
if (string.IsNullOrEmpty(s)) return false;
|
||||
|
||||
// 0x prefix → 16진수
|
||||
if (s.StartsWith("0x", StringComparison.OrdinalIgnoreCase) || s.StartsWith("0X", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return long.TryParse(s[2..], System.Globalization.NumberStyles.HexNumber, null, out result);
|
||||
}
|
||||
// 0b prefix → 2진수
|
||||
if (s.StartsWith("0b", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
try { result = Convert.ToInt64(s[2..], 2); return true; }
|
||||
catch { return false; }
|
||||
}
|
||||
// 0o prefix → 8진수
|
||||
if (s.StartsWith("0o", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
try { result = Convert.ToInt64(s[2..], 8); return true; }
|
||||
catch { return false; }
|
||||
}
|
||||
// 순수 16진수 (0-9, A-F)
|
||||
if (s.Length >= 2 && s.All(c => "0123456789ABCDEFabcdef".Contains(c)) && !s.All(char.IsDigit))
|
||||
{
|
||||
return long.TryParse(s, System.Globalization.NumberStyles.HexNumber, null, out result);
|
||||
}
|
||||
// 10진수
|
||||
return long.TryParse(s, out result);
|
||||
}
|
||||
|
||||
private static string? ConvertToBase(long val, string targetBase) => targetBase switch
|
||||
{
|
||||
"hex" or "16" or "h" => $"0x{val:X}",
|
||||
"bin" or "2" or "b" => val >= 0 ? $"0b{Convert.ToString(val, 2)}" : $"-0b{Convert.ToString(-val, 2)}",
|
||||
"oct" or "8" or "o" => val >= 0 ? $"0o{Convert.ToString(val, 8)}" : $"-0o{Convert.ToString(-val, 8)}",
|
||||
"dec" or "10" or "d" => val.ToString(),
|
||||
_ => null,
|
||||
};
|
||||
|
||||
private static string DetectBase(string s)
|
||||
{
|
||||
if (s.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) return "HEX";
|
||||
if (s.StartsWith("0b", StringComparison.OrdinalIgnoreCase)) return "BIN";
|
||||
if (s.StartsWith("0o", StringComparison.OrdinalIgnoreCase)) return "OCT";
|
||||
return "DEC";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user