function repeat(ch: string, n: number): string { if (n <= 0) { return"";
} return ch.repeat(n);
}
function padCell(text: string, width: number, align: Align): string { const w = visibleWidth(text); if (w >= width) { return text;
} const pad = width - w; if (align === "right") { return `${repeat(" ", pad)}${text}`;
} if (align === "center") { const left = Math.floor(pad / 2); const right = pad - left; return `${repeat(" ", left)}${text}${repeat(" ", right)}`;
} return `${text}${repeat(" ", pad)}`;
}
function wrapLine(text: string, width: number): string[] { if (width <= 0) { return [text];
}
// ANSI-aware wrapping: never split inside ANSI SGR/OSC-8 sequences. // We don't attempt to re-open styling per line; terminals keep SGR state // across newlines, so as long as we don't corrupt escape sequences we're safe. const ESC = "\u001b";
type Token = { kind: "ansi" | "char"; value: string }; const tokens: Token[] = []; for (let i = 0; i < text.length; ) { if (text[i] === ESC) { // SGR: ESC [ ... m if (text[i + 1] === "[") {
let j = i + 2; while (j < text.length) { const ch = text[j]; if (ch === "m") { break;
} if (ch && ch >= "0" && ch <= "9") {
j += 1; continue;
} if (ch === ";") {
j += 1; continue;
} break;
} if (text[j] === "m") {
tokens.push({ kind: "ansi", value: text.slice(i, j + 1) });
i = j + 1; continue;
}
}
// OSC-8 link open/close: ESC ] 8 ; ; ... ST (ST = ESC \) if (text[i + 1] === "]" && text.slice(i + 2, i + 5) === "8;;") { const st = text.indexOf(`${ESC}\\`, i + 5); if (st >= 0) {
tokens.push({ kind: "ansi", value: text.slice(i, st + 2) });
i = st + 2; continue;
}
}
}
let nextEsc = text.indexOf(ESC, i); if (nextEsc < 0) {
nextEsc = text.length;
} if (nextEsc === i) { // Consume unsupported escape bytes as plain characters so wrapping // cannot stall on unknown ANSI/control sequences.
tokens.push({ kind: "char", value: ESC });
i += ESC.length; continue;
} const plainChunk = text.slice(i, nextEsc); for (const grapheme of splitGraphemes(plainChunk)) {
tokens.push({ kind: "char", value: grapheme });
}
i = nextEsc;
}
flushAt(buf.length); if (!lines.length) { return [""];
} if (!prefixAnsi && !suffixAnsi) { return lines;
} return lines.map((line) => { if (!line) { return line;
} return `${prefixAnsi}${line}${suffixAnsi}`;
});
}
function normalizeWidth(n: number | undefined): number | undefined { if (n == null) { return undefined;
} if (!Number.isFinite(n) || n <= 0) { return undefined;
} return Math.floor(n);
}
export function getTerminalTableWidth(minWidth = 60, fallbackWidth = 120): number { return Math.max(minWidth, process.stdout.columns ?? fallbackWidth);
}
if (maxWidth && total > maxWidth) {
let over = total - maxWidth;
const flexOrder = columns
.map((_c, i) => ({ i, w: widths[i] ?? 0 }))
.filter(({ i }) => Boolean(columns[i]?.flex))
.toSorted((a, b) => b.w - a.w)
.map((x) => x.i);
const nonFlexOrder = columns
.map((_c, i) => ({ i, w: widths[i] ?? 0 }))
.filter(({ i }) => !columns[i]?.flex)
.toSorted((a, b) => b.w - a.w)
.map((x) => x.i);
const shrink = (order: number[], minWidths: number[]) => { while (over > 0) {
let progressed = false; for (const i of order) { if ((widths[i] ?? 0) <= (minWidths[i] ?? 0)) { continue;
}
widths[i] = (widths[i] ?? 0) - 1;
over -= 1;
progressed = true; if (over <= 0) { break;
}
} if (!progressed) { break;
}
}
};
// Prefer shrinking flex columns; only shrink non-flex if necessary. // If required to fit, allow flex columns to shrink below user minWidth // down to their absolute minimum (header + padding).
shrink(flexOrder, preferredMinWidths);
shrink(flexOrder, absoluteMinWidths);
shrink(nonFlexOrder, preferredMinWidths);
shrink(nonFlexOrder, absoluteMinWidths);
}
// If we have room and any flex columns, expand them to fill the available width. // This keeps tables from looking "clipped" and reduces wrapping in wide terminals. if (maxWidth) { const sepCount = columns.length + 1; const currentTotal = widths.reduce((a, b) => a + b, 0) + sepCount;
let extra = maxWidth - currentTotal; if (extra > 0) { const flexCols = columns
.map((c, i) => ({ c, i }))
.filter(({ c }) => Boolean(c.flex))
.map(({ i }) => i); if (flexCols.length > 0) { const caps = columns.map((c) => typeof c.maxWidth === "number" && c.maxWidth > 0
? Math.floor(c.maxWidth)
: Number.POSITIVE_INFINITY,
); while (extra > 0) {
let progressed = false; for (const i of flexCols) { if ((widths[i] ?? 0) >= (caps[i] ?? Number.POSITIVE_INFINITY)) { continue;
}
widths[i] = (widths[i] ?? 0) + 1;
extra -= 1;
progressed = true; if (extra <= 0) { break;
}
} if (!progressed) { break;
}
}
}
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.