diff --git a/packages/tui/src/utils.ts b/packages/tui/src/utils.ts index 602cb8dd3b2..ceadfc69ddf 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 a1183f750e5..618be2347d3 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);