Fix carriage return handling to emulate line overwrite

This commit is contained in:
Jared Miller 2026-01-30 08:06:36 -05:00
parent 350c352989
commit 21b8caeb67
Signed by: shmup
GPG key ID: 22B5C6D66A38B06C
2 changed files with 62 additions and 16 deletions

View file

@ -285,14 +285,35 @@ test("multiple newlines with styling", () => {
);
});
test("carriage return is stripped", () => {
test("carriage return in \\r\\n is treated as line ending", () => {
const result = ansiToHtml("line1\r\nline2");
expect(result).toBe("line1\nline2");
});
test("standalone carriage return is stripped", () => {
test("standalone carriage return overwrites from line start", () => {
const result = ansiToHtml("text\rmore");
expect(result).toBe("textmore");
expect(result).toBe("more");
});
test("multiple carriage returns overwrite progressively", () => {
const result = ansiToHtml("loading...\rloading...\rdone ");
// Note: trailing spaces are trimmed by trimLineEndPreserveAnsi
expect(result).toBe("done");
});
test("carriage return with newlines preserves lines", () => {
const result = ansiToHtml("line1\nloading...\rdone\nline3");
expect(result).toBe("line1\ndone\nline3");
});
test("carriage return with styling preserves style", () => {
const result = ansiToHtml("\x1b[31mloading...\rdone\x1b[0m");
expect(result).toBe('<span style="color:#f85149">done</span>');
});
test("carriage return at start of line", () => {
const result = ansiToHtml("\rtext");
expect(result).toBe("text");
});
// 9. trimLineEndPreserveAnsi tests

View file

@ -274,20 +274,45 @@ export function ansiToHtml(text: string): string {
continue;
}
// Track newlines in the content
if (text[i] === "\n" || text[i] === "\r") {
if (text[i] === "\n") {
// Close span before newline to prevent background color bleeding
if (inSpan) {
result += "</span>";
}
result += "\n";
// Reopen span after newline if we had styling
if (inSpan) {
result += `<span style="${currentStyle}">`;
}
// Track newlines and carriage returns
if (text[i] === "\n") {
// Close span before newline to prevent background color bleeding
if (inSpan) {
result += "</span>";
}
// Skip carriage return - we only care about line feeds
result += "\n";
// Reopen span after newline if we had styling
if (inSpan) {
result += `<span style="${currentStyle}">`;
}
i++;
continue;
}
// Carriage return: overwrite from start of current line
if (text[i] === "\r") {
// Check if this is \r\n - if so, just skip the \r
if (text[i + 1] === "\n") {
i++;
continue;
}
// Standalone \r: truncate back to last newline (or start)
// Need to handle open spans carefully
const lastNewline = result.lastIndexOf("\n");
if (lastNewline >= 0) {
// Truncate after the last newline
result = result.substring(0, lastNewline + 1);
} else {
// No newline found, truncate everything
result = "";
}
// If we had a span open, we need to reopen it after truncation
if (inSpan) {
result += `<span style="${currentStyle}">`;
}
i++;
continue;
}