From 00b032676f12212efe02319c1a9787a4956c931f Mon Sep 17 00:00:00 2001 From: Xiangzhe <32761048+xz-dev@users.noreply.github.com> Date: Fri, 17 Jul 2026 15:01:10 +0800 Subject: [PATCH] fix(tui): handle CRLF and CR line endings (#6764) --- packages/tui/src/utils.ts | 2 +- packages/tui/test/wrap-ansi.test.ts | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/packages/tui/src/utils.ts b/packages/tui/src/utils.ts index 602cb8dd..ceadfc69 100644 --- a/packages/tui/src/utils.ts +++ b/packages/tui/src/utils.ts @@ -719,7 +719,7 @@ export function wrapTextWithAnsi(text: string, width: number): string[] { // Handle newlines by processing each line separately // Track ANSI state across lines so styles carry over after literal newlines - const inputLines = text.split("\n"); + const inputLines = text.split(/\r\n|\r|\n/); const result: string[] = []; const tracker = new AnsiCodeTracker(); diff --git a/packages/tui/test/wrap-ansi.test.ts b/packages/tui/test/wrap-ansi.test.ts index a1183f75..618be234 100644 --- a/packages/tui/test/wrap-ansi.test.ts +++ b/packages/tui/test/wrap-ansi.test.ts @@ -101,6 +101,26 @@ describe("wrapTextWithAnsi", () => { }); describe("basic wrapping", () => { + it("should handle LF, CRLF, and CR line endings", () => { + assert.deepStrictEqual(wrapTextWithAnsi("first\nsecond\r\nthird\rfourth", 80), [ + "first", + "second", + "third", + "fourth", + ]); + }); + + it("should preserve ANSI state across CRLF and CR line endings", () => { + const red = "\x1b[31m"; + const reset = "\x1b[0m"; + + assert.deepStrictEqual(wrapTextWithAnsi(`${red}first\r\nsecond\rthird${reset}`, 80), [ + `${red}first`, + `${red}second`, + `${red}third${reset}`, + ]); + }); + it("should wrap plain text correctly", () => { const text = "hello world this is a test"; const wrapped = wrapTextWithAnsi(text, 10);