fix(terminal): keep wrapped multiline cells aligned (#142305)

This commit is contained in:
Peter Steinberger 2026-09-08 10:20:36 -07:00 • committed by GitHub
parent ecbad71d09
commit f7c9adacdc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 14 additions and 9 deletions

View file

@ -626,18 +626,18 @@ console.log(JSON.stringify({
["colored CRLF", "\r\n", "\x1b[31m", "\x1b[39m"],
["linked LF", "\n", "\x1b]8;;https://example.com/\x07", "\x1b]8;;\x07"],
])(
"preserves blank %s lines at their positions across cells",
"preserves blank %s lines without adding rows after wrapped spacing",
(_label, separator, open, close) => {
const out = renderTable({
border: "ascii",
width: 30,
width: 15,
columns: [
{ key: "A", header: "A", flex: true },
{ key: "B", header: "B", flex: true },
{ key: "A", header: "A", minWidth: 6, maxWidth: 6 },
{ key: "B", header: "B", minWidth: 6, maxWidth: 6 },
],
rows: [
{
A: `${open}${["", "alpha", "", "omega", "", ""].join(separator)}${close}`,
A: `${open}${["", "Read ", "", "Next", "", ""].join(separator)}${close}`,
B: "0\n1\n2\n3\n4",
},
],
@ -653,9 +653,9 @@ console.log(JSON.stringify({
),
).toEqual([
["", "0"],
["alpha", "1"],
["Read", "1"],
["", "2"],
["omega", "3"],
["Next", "3"],
["", "4"],
]);
if (open) {

View file

@ -251,12 +251,13 @@ function wrapLine(text: string, width: number): string[] {
ch === " " || ch === "/" || ch === "-" || ch === "_" || ch === ".";
let skipNextLf = false;
let hasChar = false;
let logicalLineHasOutput = false;
const buf: AnsiToken[] = [];
let bufVisible = 0;
let lastBreakIndex: number | null = null;
// Explicit newlines emit empty buffers too, keeping sibling cell rows aligned.
// A soft wrap can empty the buffer before a newline without creating a blank logical line.
const flushAt = (breakAt: number | null) => {
// Keep the suffix in its buffer: long zero-width runs can exceed the argument
// limit of a spread-based copy even when their visible width is small.
@ -286,7 +287,10 @@ function wrapLine(text: string, width: number): string[] {
const openOsc8 = activeOsc8 ? `${ESC}]8;${activeOsc8.params};${activeOsc8.uri}${BEL}` : "";
const closeSgr = activeSgr.map((state) => state.close).join("");
lines.push(`${content.join("")}${closeOsc8}${closeSgr}`.trimEnd());
if (bufVisible > 0 || !logicalLineHasOutput) {
lines.push(`${content.join("")}${closeOsc8}${closeSgr}`.trimEnd());
logicalLineHasOutput = true;
}
if (breakAt == null || breakAt <= 0) {
buf.length = 0;
if (openOsc8) {
@ -331,6 +335,7 @@ function wrapLine(text: string, width: number): string[] {
if (ch === "\n" || ch === "\r" || ch === "\r\n") {
skipNextLf = ch === "\r";
flushAt(buf.length);
logicalLineHasOutput = false;
return;
}
// Soft-wrap remainders reuse the width measured when each token entered the buffer.