Add UTF-8 detection with ASCII fallback for terminal compatibility

- 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
This commit is contained in:
Samuel Bouchet 2026-03-14 20:46:33 +01:00
parent af13380a26
commit 240989e0ff
3 changed files with 96 additions and 14 deletions

View file

@ -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 ?? []));

View file

@ -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
/// <summary>
/// Returns rarity star prefix for Rare and above items.
/// </summary>
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
{

View file

@ -0,0 +1,75 @@
using System.Text;
namespace OpenTheBox.Rendering;
/// <summary>
/// Detects whether the terminal supports UTF-8 output and provides
/// glyph accessors that return Unicode or ASCII equivalents accordingly.
/// Call <see cref="Initialize"/> once at startup.
/// </summary>
public static class UnicodeSupport
{
/// <summary>True when the terminal accepts UTF-8 characters beyond CP437.</summary>
public static bool IsUtf8 { get; private set; }
// ── Glyphs ──────────────────────────────────────────────────────────
/// <summary>Arrow: → or -></summary>
public static string Arrow => IsUtf8 ? "→" : "->";
/// <summary>Star for rarity prefix: ★ or *</summary>
public static string Star => IsUtf8 ? "★" : "*";
/// <summary>
/// Attempts to set UTF-8 output encoding and detects whether the
/// terminal actually supports it (Windows Terminal, PowerShell, etc.).
/// </summary>
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;
}
}