using System.Windows; using AxCopilot.SDK; using AxCopilot.Services; using AxCopilot.Themes; namespace AxCopilot.Handlers; /// /// 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 → 결과를 클립보드에 복사. /// public class BaseConvertHandler : IActionHandler { public string? Prefix => "base"; public PluginMetadata Metadata => new( "BaseConvert", "진수 변환기 — 2 · 8 · 10 · 16진수 · ASCII", "1.0", "AX"); public Task> GetItemsAsync(string query, CancellationToken ct) { var q = query.Trim(); var items = new List(); 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>(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>(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>(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>(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 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 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"; } }