fix(status): improve diagnostics and output

This commit is contained in:
Peter Steinberger
2026-01-11 02:42:14 +01:00
parent 2e2f05a0e1
commit e824b3514b
18 changed files with 632 additions and 82 deletions

View File

@@ -39,74 +39,130 @@ function padCell(text: string, width: number, align: Align): string {
function wrapLine(text: string, width: number): string[] {
if (width <= 0) return [text];
const words = text.split(/(\s+)/).filter(Boolean);
const lines: string[] = [];
let current = "";
let currentWidth = 0;
const push = (value: string) => lines.push(value.replace(/\s+$/, ""));
const flush = () => {
if (current.trim().length === 0) return;
push(current);
current = "";
currentWidth = 0;
};
// 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";
const breakLong = (word: string) => {
const parts: string[] = [];
let buf = "";
let lastBreakAt = 0;
const isBreakChar = (ch: string) =>
ch === "/" || ch === "-" || ch === "_" || ch === ".";
for (const ch of Array.from(word)) {
const next = buf + ch;
if (visibleWidth(next) > width && buf) {
if (lastBreakAt > 0) {
parts.push(buf.slice(0, lastBreakAt));
buf = `${buf.slice(lastBreakAt)}${ch}`;
lastBreakAt = 0;
for (let i = 0; i < buf.length; i += 1) {
const c = buf[i];
if (c && isBreakChar(c)) lastBreakAt = i + 1;
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;
}
} else {
parts.push(buf);
buf = ch;
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;
}
} else {
buf = next;
if (isBreakChar(ch)) lastBreakAt = buf.length;
}
}
if (buf) parts.push(buf);
return parts;
const cp = text.codePointAt(i);
if (!cp) break;
const ch = String.fromCodePoint(cp);
tokens.push({ kind: "char", value: ch });
i += ch.length;
}
const lines: string[] = [];
const isBreakChar = (ch: string) =>
ch === " " ||
ch === "\t" ||
ch === "\n" ||
ch === "\r" ||
ch === "/" ||
ch === "-" ||
ch === "_" ||
ch === ".";
const isSpaceChar = (ch: string) => ch === " " || ch === "\t";
const buf: Token[] = [];
let bufVisible = 0;
let lastBreakIndex: number | null = null;
const bufToString = (slice?: Token[]) =>
(slice ?? buf).map((t) => t.value).join("");
const bufVisibleWidth = (slice: Token[]) =>
slice.reduce((acc, t) => acc + (t.kind === "char" ? 1 : 0), 0);
const pushLine = (value: string) => {
const cleaned = value.replace(/\s+$/, "");
if (cleaned.trim().length === 0) return;
lines.push(cleaned);
};
for (const token of words) {
const tokenWidth = visibleWidth(token);
const isSpace = /^\s+$/.test(token);
const flushAt = (breakAt: number | null) => {
if (buf.length === 0) return;
if (breakAt == null || breakAt <= 0) {
pushLine(bufToString());
buf.length = 0;
bufVisible = 0;
lastBreakIndex = null;
return;
}
if (tokenWidth > width && !isSpace) {
flush();
for (const part of breakLong(token.replace(/^\s+/, ""))) {
push(part);
}
const left = buf.slice(0, breakAt);
const rest = buf.slice(breakAt);
pushLine(bufToString(left));
while (
rest.length > 0 &&
rest[0]?.kind === "char" &&
isSpaceChar(rest[0].value)
) {
rest.shift();
}
buf.length = 0;
buf.push(...rest);
bufVisible = bufVisibleWidth(buf);
lastBreakIndex = null;
};
for (const token of tokens) {
if (token.kind === "ansi") {
buf.push(token);
continue;
}
if (
currentWidth + tokenWidth > width &&
current.trim().length > 0 &&
!isSpace
) {
flush();
const ch = token.value;
if (bufVisible + 1 > width && bufVisible > 0) {
flushAt(lastBreakIndex);
}
current += token;
currentWidth = visibleWidth(current);
buf.push(token);
bufVisible += 1;
if (isBreakChar(ch)) lastBreakIndex = buf.length;
}
flush();
flushAt(buf.length);
return lines.length ? lines : [""];
}