From 7767ad3b49bb7d0d615322865d088ecd4245f8c2 Mon Sep 17 00:00:00 2001 From: neverland Date: Fri, 27 Dec 2024 07:42:30 +0800 Subject: [PATCH] fix: ansi HTML edge cases --- packages/core/src/server/ansiHTML.ts | 34 +++++++++++--------- packages/core/tests/ansi.test.ts | 47 +++++++++++++++++++++++++++- 2 files changed, 65 insertions(+), 16 deletions(-) diff --git a/packages/core/src/server/ansiHTML.ts b/packages/core/src/server/ansiHTML.ts index d6db8c2181..7cebcbca03 100644 --- a/packages/core/src/server/ansiHTML.ts +++ b/packages/core/src/server/ansiHTML.ts @@ -1,8 +1,8 @@ -const openCodes: Record = { +const styles: Record = { 1: 'font-weight:bold', // bold 2: 'opacity:0.5', // dim 3: 'font-style:italic', // italic - 4: 'text-decoration:underline', // underscore + 4: 'text-decoration:underline;text-underline-offset:3px', // underscore 8: 'display:none', // hidden 9: 'text-decoration:line-through', // delete 30: 'color:#000', // darkgrey @@ -13,9 +13,14 @@ const openCodes: Record = { 35: 'color:#f76ebe', // magenta, hsl(300deg 90% 70%) 36: 'color:#6eecf7', // cyan, hsl(210deg 90% 70%) 37: 'color:#f0f0f0', // lightgrey, hsl(0deg 0% 94%) - 90: 'color:#888', // darkgrey + 90: 'color:#888', // bright black }; +// use the same color for bright colors +for (let i = 91; i <= 97; i++) { + styles[i] = styles[i - 60]; +} + const closeCode = [0, 21, 22, 23, 24, 27, 28, 29, 39, 49]; /** @@ -28,21 +33,20 @@ export function ansiHTML(text: string): string { let ret = text.replace( // biome-ignore lint/suspicious/noControlCharactersInRegex: allowed /\x1B\[([0-9;]+)m/g, - (_match: string, seq: string): string => { - const openStyle = openCodes[seq]; - if (openStyle) { - // If current sequence has been opened, close it. - if (ansiCodes.indexOf(seq) !== -1) { - ansiCodes.pop(); - return ''; + (_match: string, sequences: string): string => { + let style = ''; + for (const seq of sequences.split(';')) { + if (styles[seq]) { + style += `${styles[seq]};`; } - // Open tag. - ansiCodes.push(seq); - return ``; } - if (closeCode.includes(Number(seq))) { - // Pop sequence + if (style) { + ansiCodes.push(sequences); + return ``; + } + + if (closeCode.includes(Number(sequences)) && ansiCodes.length > 0) { ansiCodes.pop(); return ''; } diff --git a/packages/core/tests/ansi.test.ts b/packages/core/tests/ansi.test.ts index 940314ee15..1498fa6e81 100644 --- a/packages/core/tests/ansi.test.ts +++ b/packages/core/tests/ansi.test.ts @@ -57,7 +57,7 @@ describe('ansiHTML', () => { it('should convert ANSI underline codes to HTML', () => { const input = '\x1B[4mHello, World!\x1B[0m'; const expected = - 'Hello, World!'; + 'Hello, World!'; expect(ansiHTML(input)).toEqual(expected); }); @@ -67,4 +67,49 @@ describe('ansiHTML', () => { 'Hello, World!'; expect(ansiHTML(input)).toEqual(expected); }); + + it('should convert multiple styles ', () => { + const input = '\x1B[31;1;4mHello, World!\x1B[0m'; + const expected = + 'Hello, World!'; + expect(ansiHTML(input)).toEqual(expected); + }); + + it('should convert file path with ANSI color codes to HTML', () => { + const input = '[\u001b[36;1;4m/path/to/src/index.js\u001b[0m:4:1]'; + const expected = + '[/path/to/src/index.js:4:1]'; + expect(ansiHTML(input)).toEqual(expected); + }); + + it('should ignore background colors', () => { + const bgRedInput = '\x1B[41mHello, World!\x1B[0m'; + const bgRedExpected = 'Hello, World!'; + expect(ansiHTML(bgRedInput)).toEqual(bgRedExpected); + + const bgBlueInput = '\x1B[44;1mHello, World!\x1B[0m'; + const bgBlueExpected = + 'Hello, World!'; + expect(ansiHTML(bgBlueInput)).toEqual(bgBlueExpected); + }); + + it('should handle nested styles', () => { + const input = '\x1B[31mRed \x1B[1mBold Red \x1B[34mBold Blue\x1B[0m'; + const expected = + 'Red Bold Red Bold Blue'; + expect(ansiHTML(input)).toEqual(expected); + }); + + it('should handle bright colors', () => { + const input = '\x1B[91mBright Red\x1B[0m'; + const expected = 'Bright Red'; + expect(ansiHTML(input)).toEqual(expected); + }); + + it('should handle reset within text', () => { + const input = '\x1B[31mRed\x1B[0m Normal \x1B[34mBlue\x1B[0m'; + const expected = + 'Red Normal Blue'; + expect(ansiHTML(input)).toEqual(expected); + }); });