fix(ui): consistent frame glyphs on Windows — agree with clack, keep raw path ASCII (#1307)
codegraph's glyphs were ASCII on every Windows console while @clack/prompts drew its Unicode frame around them, so one index block mixed `|` and `│` rails (#398). supportsUnicode() now mirrors the is-unicode-supported detection clack bundles (Windows Terminal, VS Code, ConEmu/Cmder, Alacritty, xterm-256color, JetBrains, CI), so both systems always pick the same glyph family. The shimmer worker's raw fs.writeSync(1) bytes still decode through the console codepage (OEM codepages mojibake UTF-8 even under Windows Terminal — the #168 regression to avoid), so: - the raw path gets its own supportsUnicodeRawWrites() that stays ASCII on win32 unless CODEGRAPH_UNICODE=1, and - the persistent "phase done" lines move from the worker to the parent, written via process.stdout (wide-char console API, codepage-immune) at phase transitions — the main thread is alive there, it's delivering the progress callback. Only transient, self-erasing animation frames remain on the raw path, so ASCII never lands in scrollback. Fixes #398 Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
d6efd437b3
commit
30421953ac
+58
-11
@@ -2,18 +2,30 @@
|
||||
* Glyph selection for CLI output.
|
||||
*
|
||||
* On Windows, console output is interpreted via the active output
|
||||
* codepage. PowerShell 5.1 and cmd.exe default to OEM codepages
|
||||
* (CP437, CP936, ...), so UTF-8 bytes written to the console render
|
||||
* as mojibake (see #168). The shimmer worker is hit hardest because
|
||||
* it uses `fs.writeSync(1, ...)` (raw bytes, no TTY-aware encoding
|
||||
* conversion) to keep animation smooth while the main thread is
|
||||
* blocked in SQLite. To stay readable everywhere, we fall back to
|
||||
* ASCII glyphs whenever the terminal is not known to handle UTF-8.
|
||||
* codepage. PowerShell 5.1 and cmd.exe in legacy conhost default to
|
||||
* OEM codepages (CP437, CP936, ...), so UTF-8 bytes written to the
|
||||
* console render as mojibake (see #168). The shimmer worker is hit
|
||||
* hardest because it uses `fs.writeSync(1, ...)` (raw bytes, no
|
||||
* TTY-aware encoding conversion) to keep animation smooth while the
|
||||
* main thread is blocked in SQLite. To stay readable everywhere, we
|
||||
* fall back to ASCII glyphs whenever the terminal is not known to
|
||||
* handle UTF-8.
|
||||
*
|
||||
* Detection is intentionally simple:
|
||||
* The Windows branch must agree with @clack/prompts (which bundles
|
||||
* `is-unicode-supported`): clack draws the outer `┌ │ └` frame around
|
||||
* init/index/sync output, and if it decides Unicode while we decide
|
||||
* ASCII, one block mixes `│` and `|` rails (#398). The terminals the
|
||||
* list recognizes (Windows Terminal, VS Code, ConEmu/Cmder, Alacritty,
|
||||
* JetBrains, Terminus, CI log viewers) all run with a UTF-8-capable
|
||||
* output path, so the raw-byte shimmer writes render correctly there
|
||||
* too; unrecognized Windows consoles keep the safe ASCII fallback —
|
||||
* and clack falls back to ASCII in those as well, so output stays
|
||||
* consistent in both directions.
|
||||
*
|
||||
* Detection:
|
||||
* - `CODEGRAPH_ASCII=1` -> ASCII (escape hatch for any terminal)
|
||||
* - `CODEGRAPH_UNICODE=1` -> Unicode (opt-in on Windows)
|
||||
* - Windows -> ASCII by default
|
||||
* - `CODEGRAPH_UNICODE=1` -> Unicode (opt-in on any terminal)
|
||||
* - Windows -> mirror is-unicode-supported (see above)
|
||||
* - Linux kernel console (`TERM=linux`) -> ASCII
|
||||
* - Everything else -> Unicode
|
||||
*/
|
||||
@@ -21,7 +33,20 @@
|
||||
export function supportsUnicode(): boolean {
|
||||
if (process.env.CODEGRAPH_ASCII === '1') return false;
|
||||
if (process.env.CODEGRAPH_UNICODE === '1') return true;
|
||||
if (process.platform === 'win32') return false;
|
||||
if (process.platform === 'win32') {
|
||||
const env = process.env;
|
||||
return Boolean(
|
||||
env.CI ||
|
||||
env.WT_SESSION || // Windows Terminal
|
||||
env.TERMINUS_SUBLIME ||
|
||||
env.ConEmuTask === '{cmd::Cmder}' || // ConEmu and cmder
|
||||
env.TERM_PROGRAM === 'Terminus-Sublime' ||
|
||||
env.TERM_PROGRAM === 'vscode' ||
|
||||
env.TERM === 'xterm-256color' ||
|
||||
env.TERM === 'alacritty' ||
|
||||
env.TERMINAL_EMULATOR === 'JetBrains-JediTerm'
|
||||
);
|
||||
}
|
||||
return process.env.TERM !== 'linux';
|
||||
}
|
||||
|
||||
@@ -85,6 +110,28 @@ export function getGlyphs(): Glyphs {
|
||||
return cached;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unicode support for the RAW console write path — `fs.writeSync(1, ...)`,
|
||||
* used only by the shimmer worker's transient animation frames. Raw bytes
|
||||
* bypass Node's TTY-aware conversion and get decoded by the ACTIVE CONSOLE
|
||||
* CODEPAGE on Windows; OEM codepages (CP437, CP936, ...) mojibake UTF-8
|
||||
* there even inside Windows Terminal, whose ConPTY still decodes app output
|
||||
* with the session codepage (#168). So the raw path stays ASCII on every
|
||||
* Windows terminal unless the user opts in via CODEGRAPH_UNICODE=1 —
|
||||
* independent of `supportsUnicode()`, which governs the codepage-immune
|
||||
* main-thread writes (`process.stdout` uses the wide-char console API).
|
||||
*/
|
||||
export function supportsUnicodeRawWrites(): boolean {
|
||||
if (process.env.CODEGRAPH_ASCII === '1') return false;
|
||||
if (process.env.CODEGRAPH_UNICODE === '1') return true;
|
||||
if (process.platform === 'win32') return false;
|
||||
return process.env.TERM !== 'linux';
|
||||
}
|
||||
|
||||
export function getRawWriteGlyphs(): Glyphs {
|
||||
return supportsUnicodeRawWrites() ? UNICODE_GLYPHS : ASCII_GLYPHS;
|
||||
}
|
||||
|
||||
/** Reset the cached glyph set. Test-only; production code should call `getGlyphs()`. */
|
||||
export function _resetGlyphsCache(): void {
|
||||
cached = null;
|
||||
|
||||
Reference in New Issue
Block a user