Skip to content

Fix garbled output when terminal isn't UTF-8 at startup (#204)#208

Merged
Aaronontheweb merged 2 commits into
devfrom
fix/ansi-terminal-stale-console-out
May 18, 2026
Merged

Fix garbled output when terminal isn't UTF-8 at startup (#204)#208
Aaronontheweb merged 2 commits into
devfrom
fix/ansi-terminal-stale-console-out

Conversation

@Aaronontheweb

Copy link
Copy Markdown
Owner

Fixes #204. Supersedes #205.

Problem

Setting Console.OutputEncoding replaces Console.Out with a brand-new TextWriter bound to the new encoding. AnsiTerminal captured Console.Out in its constructor, so on a console that did not start in UTF-8 (e.g. code page 850, the VS debugger case in #204) the cached writer stayed bound to the old encoding. Unicode box-drawing characters written through it were replaced with U+FFFD.

Credit to @logical-intent for diagnosing this precisely and proposing the original fix in #205.

Approach

#205 fixes this by reordering the constructor so Console.Out is captured after the encoding flip. That works for the reported case, but it still caches Console.Out — and Console.OutputEncoding is set again later by WindowsConsole/FallbackConsole.Initialize(), which invalidates the cached writer. The cache is correct only as long as every encoding flip in the process happens to be UTF-8 and is timed just right.

This PR removes the coupling instead of timing around it:

  • AnsiTerminal no longer caches Console.Out. The output writer is resolved on every flush via a private Output property (_explicitOutput ?? Console.Out). It always reflects the current Console.Out, regardless of when — or how often — the encoding changes. The explicit-writer seam for tests/benchmarks is preserved.
  • One owner for encoding setup. UTF-8 configuration is consolidated into ConsoleEnvironment.EnsureUtf8Output(), invoked by the platform console during Initialize(), replacing the three separate Console.OutputEncoding = call sites (AnsiTerminal, WindowsConsole, FallbackConsole).

Performance is unchanged: Console.Out's getter is a single volatile static read in steady state, resolved once per Flush() (once per frame) — negligible against the ToString()/encode/syscall the flush already does.

Tests

Added AnsiTerminalTests with a deterministic regression test: construct an AnsiTerminal, swap Console.Out via Console.SetOut (exactly what setting Console.OutputEncoding does), then assert the flush lands in the new writer. This fails against both the original code and #205's caching approach, and passes only with non-caching. No real terminal or code-page juggling required.

All 1004 tests pass locally.

Notes for reviewer

  • Behavior change: AnsiTerminal no longer sets Console.OutputEncoding itself — the platform console owns it. In practice AnsiTerminal is always used via TerminaApplication, whose RunAsync calls platformConsole.Initialize() before the first render, so this is covered. A standalone new AnsiTerminal() used without a platform console would no longer force UTF-8; flag if that path should be supported.
  • Version/date in RELEASE_NOTES.md (0.8.1, today) is a guess — adjust to match release cadence.
  • CI will be red on the unrelated OpenTelemetry.Api NU1902 advisory in Termina.Demo.Streaming — pre-existing on dev, not introduced here.
  • A follow-up worth filing: move AnsiTerminal's remaining constructor side effect (EnterAlternateScreen) into an explicit Initialize()/Dispose() lifecycle so setup ordering is explicit rather than emergent from the DI graph.

Setting Console.OutputEncoding replaces Console.Out with a new TextWriter.
AnsiTerminal captured Console.Out at construction, so on a console that did
not start in UTF-8 the cached writer stayed bound to the old encoding and
rendered Unicode box-drawing characters as U+FFFD.

- AnsiTerminal no longer caches Console.Out; the output writer is resolved
  on every flush via the Output property, so it always reflects the current
  Console.Out regardless of when the encoding was changed.
- Consolidated UTF-8 encoding setup into ConsoleEnvironment.EnsureUtf8Output,
  invoked by the platform console, replacing three separate call sites.
- Added regression tests covering writer resolution.

Diagnosis and original fix by @logical-intent in #204 / #205.

Closes #204
@Aaronontheweb Aaronontheweb merged commit 1cb2745 into dev May 18, 2026
7 checks passed
@Aaronontheweb Aaronontheweb deleted the fix/ansi-terminal-stale-console-out branch May 18, 2026 15:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Corrupt borders when launching from the VS Debugger

1 participant