From 240989e0ff1e324ffe23af98c3c0d8f6401d0da4 Mon Sep 17 00:00:00 2001 From: Samuel Bouchet Date: Sat, 14 Mar 2026 20:46:33 +0100 Subject: [PATCH] Add UTF-8 detection with ASCII fallback for terminal compatibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Detect Windows Terminal (WT_SESSION), PowerShell, and non-Windows terminals to enable UTF-8 output encoding automatically - Use ★ and → when UTF-8 is supported, fall back to * and -> on cmd.exe - Set Console.OutputEncoding = UTF8 at startup for capable terminals --- src/OpenTheBox/Program.cs | 12 ++-- src/OpenTheBox/Rendering/SpectreRenderer.cs | 23 ++++--- src/OpenTheBox/Rendering/UnicodeSupport.cs | 75 +++++++++++++++++++++ 3 files changed, 96 insertions(+), 14 deletions(-) create mode 100644 src/OpenTheBox/Rendering/UnicodeSupport.cs diff --git a/src/OpenTheBox/Program.cs b/src/OpenTheBox/Program.cs index 9b33d1d..dd9921c 100644 --- a/src/OpenTheBox/Program.cs +++ b/src/OpenTheBox/Program.cs @@ -35,6 +35,8 @@ public static class Program public static async Task Main(string[] args) { + UnicodeSupport.Initialize(); + // --snapshot N: directly load snapshot_N save and start playing int snapshotSlot = 0; var snapshotIdx = Array.IndexOf(args, "--snapshot"); @@ -521,8 +523,8 @@ public static class Program case ResourceChangedEvent resEvt: var resName = _loc.Get($"resource.{resEvt.Type.ToString().ToLower()}"); - _renderer.ShowMessage($"{resName}: {resEvt.OldValue} -> {resEvt.NewValue}"); - AddEventLog($"{resName}: {resEvt.OldValue} -> {resEvt.NewValue}"); + _renderer.ShowMessage($"{resName}: {resEvt.OldValue} {UnicodeSupport.Arrow} {resEvt.NewValue}"); + AddEventLog($"{resName}: {resEvt.OldValue} {UnicodeSupport.Arrow} {resEvt.NewValue}"); break; case MessageEvent msgEvt: @@ -758,8 +760,8 @@ public static class Program ? _loc.Get("inventory.item_used_qty", itemName, remaining.ToString()) : _loc.Get("inventory.item_used", itemName); _renderer.ShowMessage(usedMsg); - _renderer.ShowMessage($"{resName}: {resEvt.OldValue} -> {resEvt.NewValue}"); - AddEventLog($"{itemName} -> {resName} {resEvt.OldValue}->{resEvt.NewValue}"); + _renderer.ShowMessage($"{resName}: {resEvt.OldValue} {UnicodeSupport.Arrow} {resEvt.NewValue}"); + AddEventLog($"{itemName} {UnicodeSupport.Arrow} {resName} {resEvt.OldValue}{UnicodeSupport.Arrow}{resEvt.NewValue}"); break; case MessageEvent msgEvt: _renderer.ShowMessage(_loc.Get(msgEvt.MessageKey, msgEvt.Args ?? [])); @@ -784,7 +786,7 @@ public static class Program break; case ResourceChangedEvent resEvt: var cookieResName = _loc.Get($"resource.{resEvt.Type.ToString().ToLower()}"); - _renderer.ShowMessage($"{cookieResName}: {resEvt.OldValue} → {resEvt.NewValue}"); + _renderer.ShowMessage($"{cookieResName}: {resEvt.OldValue} {UnicodeSupport.Arrow} {resEvt.NewValue}"); break; case MessageEvent msgEvt: _renderer.ShowMessage(_loc.Get(msgEvt.MessageKey, msgEvt.Args ?? [])); diff --git a/src/OpenTheBox/Rendering/SpectreRenderer.cs b/src/OpenTheBox/Rendering/SpectreRenderer.cs index 9dba95d..40722d4 100644 --- a/src/OpenTheBox/Rendering/SpectreRenderer.cs +++ b/src/OpenTheBox/Rendering/SpectreRenderer.cs @@ -323,7 +323,8 @@ public sealed class SpectreRenderer : IRenderer { if (_context.HasColors) { - var panel = new Panel($"[bold yellow]* {Markup.Escape(featureName)} *[/]") + var star = UnicodeSupport.Star; + var panel = new Panel($"[bold yellow]{star} {Markup.Escape(featureName)} {star}[/]") .Border(BoxBorder.Double) .BorderStyle(new Style(Color.Yellow)) .Padding(2, 0) @@ -333,7 +334,7 @@ public sealed class SpectreRenderer : IRenderer else { Console.WriteLine("========================================"); - Console.WriteLine($" * {featureName} *"); + Console.WriteLine($" {UnicodeSupport.Star} {featureName} {UnicodeSupport.Star}"); Console.WriteLine("========================================"); } } @@ -429,14 +430,18 @@ public sealed class SpectreRenderer : IRenderer /// /// Returns rarity star prefix for Rare and above items. /// - private static string RarityStars(string rarity) => rarity.ToLowerInvariant() switch + private static string RarityStars(string rarity) { - "rare" => "* ", - "epic" => "** ", - "legendary" => "*** ", - "mythic" => "**** ", - _ => "" - }; + var s = UnicodeSupport.Star; + return rarity.ToLowerInvariant() switch + { + "rare" => $"{s} ", + "epic" => $"{s}{s} ", + "legendary" => $"{s}{s}{s} ", + "mythic" => $"{s}{s}{s}{s} ", + _ => "" + }; + } private static Color RarityColorValue(string rarity) => rarity.ToLowerInvariant() switch { diff --git a/src/OpenTheBox/Rendering/UnicodeSupport.cs b/src/OpenTheBox/Rendering/UnicodeSupport.cs new file mode 100644 index 0000000..c856e81 --- /dev/null +++ b/src/OpenTheBox/Rendering/UnicodeSupport.cs @@ -0,0 +1,75 @@ +using System.Text; + +namespace OpenTheBox.Rendering; + +/// +/// Detects whether the terminal supports UTF-8 output and provides +/// glyph accessors that return Unicode or ASCII equivalents accordingly. +/// Call once at startup. +/// +public static class UnicodeSupport +{ + /// True when the terminal accepts UTF-8 characters beyond CP437. + public static bool IsUtf8 { get; private set; } + + // ── Glyphs ────────────────────────────────────────────────────────── + + /// Arrow: → or -> + public static string Arrow => IsUtf8 ? "→" : "->"; + + /// Star for rarity prefix: ★ or * + public static string Star => IsUtf8 ? "★" : "*"; + + /// + /// Attempts to set UTF-8 output encoding and detects whether the + /// terminal actually supports it (Windows Terminal, PowerShell, etc.). + /// + public static void Initialize() + { + // Windows Terminal always sets WT_SESSION + if (Environment.GetEnvironmentVariable("WT_SESSION") is not null) + { + EnableUtf8(); + return; + } + + // Modern PowerShell (pwsh) or Windows PowerShell with PSModulePath + // typically supports UTF-8 when we set the encoding + if (Environment.GetEnvironmentVariable("PSModulePath") is not null) + { + EnableUtf8(); + return; + } + + // Non-Windows platforms (Linux, macOS) generally support UTF-8 + if (!OperatingSystem.IsWindows()) + { + EnableUtf8(); + return; + } + + // Fallback: try to set UTF-8 and check if it stuck + try + { + Console.OutputEncoding = Encoding.UTF8; + IsUtf8 = Console.OutputEncoding.CodePage == 65001; + } + catch + { + IsUtf8 = false; + } + } + + private static void EnableUtf8() + { + try + { + Console.OutputEncoding = Encoding.UTF8; + } + catch + { + // Ignore if setting encoding fails + } + IsUtf8 = true; + } +}