fix(run): draw the lane console through the canvas renderer
Scrolling a console left stray characters glued to the left edge of blank lines. Copying an affected line yields clean text, so the buffer was always correct — only the painting drifted, which no CSS can correct. xterm's default DOM renderer aligns each row with a single `letter-spacing` value. That holds only while every glyph comes from one monospace font, and TerminalView passed no fontFamily, so xterm used its default `courier-new, courier, monospace`: Vietnamese diacritics and CJK fall through to a fallback face whose advance width differs, glyphs shove their neighbours along, and the row's tail spills onto the row below. The canvas renderer draws every cell at a fixed position, so a wide glyph can no longer displace its neighbours. Loaded best-effort — a browser that cannot hand back a 2D context keeps the DOM renderer rather than losing the terminal entirely. Ruled out first, each by replaying the real byte stream into xterm.js and diffing against `tmux capture-pane`: wrapped Vietnamese at the margin, mixed CJK, wheel scrolling through copy-mode, and a live 131->95->131 resize all render byte-identically at matched widths. client 448/448, tsc clean.
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import { Terminal } from "@xterm/xterm";
|
||||
import { FitAddon } from "@xterm/addon-fit";
|
||||
import { CanvasAddon } from "@xterm/addon-canvas";
|
||||
import "@xterm/xterm/css/xterm.css";
|
||||
|
||||
interface TerminalViewProps {
|
||||
@@ -29,6 +30,24 @@ export function TerminalView({ runId, wsBaseUrl }: TerminalViewProps) {
|
||||
const fit = new FitAddon();
|
||||
term.loadAddon(fit);
|
||||
if (containerRef.current) term.open(containerRef.current);
|
||||
|
||||
// Draw each cell at a fixed position instead of letting the browser lay the
|
||||
// row out as text. xterm's default DOM renderer aligns a row with a single
|
||||
// `letter-spacing`, which only holds while every glyph comes from one
|
||||
// monospace font — Vietnamese diacritics and CJK fall through to a fallback
|
||||
// face whose advance width differs, so glyphs shove their neighbours along
|
||||
// and the row's tail spills onto the row below. The buffer is correct
|
||||
// throughout (copying the line yields clean text); only the painting drifts,
|
||||
// and no CSS can correct it. Best-effort: a browser that cannot give a 2D
|
||||
// context keeps the DOM renderer rather than losing the terminal.
|
||||
let canvas: CanvasAddon | null = null;
|
||||
try {
|
||||
canvas = new CanvasAddon();
|
||||
term.loadAddon(canvas);
|
||||
} catch {
|
||||
canvas = null;
|
||||
}
|
||||
|
||||
fit.fit();
|
||||
|
||||
// When the pane's program has NOT enabled wheel reporting, xterm falls back
|
||||
@@ -160,6 +179,7 @@ export function TerminalView({ runId, wsBaseUrl }: TerminalViewProps) {
|
||||
resizeDisposable.dispose();
|
||||
dataDisposable.dispose();
|
||||
ws?.close();
|
||||
canvas?.dispose();
|
||||
term.dispose();
|
||||
};
|
||||
}, [runId, wsBaseUrl]);
|
||||
|
||||
Reference in New Issue
Block a user