From e8bc7bea447936d8cef6e9a7ed7138379ca89892 Mon Sep 17 00:00:00 2001 From: Jacob Richman Date: Thu, 5 Mar 2026 13:43:13 -0800 Subject: [PATCH] Fix code colorizer ansi escape bug. (#21321) --- .../cli/src/ui/utils/CodeColorizer.test.tsx | 32 +++++++++++++++++++ packages/cli/src/ui/utils/CodeColorizer.tsx | 12 ++++--- ...pe-codes-leak-into-colorized-code.snap.svg | 16 ++++++++++ .../__snapshots__/CodeColorizer.test.tsx.snap | 7 ++++ 4 files changed, 62 insertions(+), 5 deletions(-) create mode 100644 packages/cli/src/ui/utils/__snapshots__/CodeColorizer-colorizeCode-does-not-let-colors-from-ansi-escape-codes-leak-into-colorized-code.snap.svg create mode 100644 packages/cli/src/ui/utils/__snapshots__/CodeColorizer.test.tsx.snap diff --git a/packages/cli/src/ui/utils/CodeColorizer.test.tsx b/packages/cli/src/ui/utils/CodeColorizer.test.tsx index 2f231e1bb3..7fc120b58b 100644 --- a/packages/cli/src/ui/utils/CodeColorizer.test.tsx +++ b/packages/cli/src/ui/utils/CodeColorizer.test.tsx @@ -50,4 +50,36 @@ describe('colorizeCode', () => { expect(lastFrame()).toMatch(/line 1\s*\n\s*\n\s*line 3/); unmount(); }); + + it('does not let colors from ansi escape codes leak into colorized code', async () => { + const code = 'line 1\n\x1b[41mline 2 with red background\x1b[0m\nline 3'; + const settings = new LoadedSettings( + { path: '', settings: {}, originalSettings: {} }, + { path: '', settings: {}, originalSettings: {} }, + { + path: '', + settings: { ui: { useAlternateBuffer: true, showLineNumbers: false } }, + originalSettings: { + ui: { useAlternateBuffer: true, showLineNumbers: false }, + }, + }, + { path: '', settings: {}, originalSettings: {} }, + true, + [], + ); + + const result = colorizeCode({ + code, + language: 'javascript', + maxWidth: 80, + settings, + hideLineNumbers: true, + }); + + const renderResult = renderWithProviders(<>{result}); + await renderResult.waitUntilReady(); + + await expect(renderResult).toMatchSvgSnapshot(); + renderResult.unmount(); + }); }); diff --git a/packages/cli/src/ui/utils/CodeColorizer.tsx b/packages/cli/src/ui/utils/CodeColorizer.tsx index 56e34eefa4..e5ce2562af 100644 --- a/packages/cli/src/ui/utils/CodeColorizer.tsx +++ b/packages/cli/src/ui/utils/CodeColorizer.tsx @@ -14,6 +14,7 @@ import type { ElementContent, RootContent, } from 'hast'; +import stripAnsi from 'strip-ansi'; import { themeManager } from '../themes/theme-manager.js'; import type { Theme } from '../themes/theme.js'; import { @@ -98,16 +99,17 @@ function highlightAndRenderLine( theme: Theme, ): React.ReactNode { try { + const strippedLine = stripAnsi(line); const getHighlightedLine = () => !language || !lowlight.registered(language) - ? lowlight.highlightAuto(line) - : lowlight.highlight(language, line); + ? lowlight.highlightAuto(strippedLine) + : lowlight.highlight(language, strippedLine); const renderedNode = renderHastNode(getHighlightedLine(), theme, undefined); - return renderedNode !== null ? renderedNode : line; + return renderedNode !== null ? renderedNode : strippedLine; } catch (_error) { - return line; + return stripAnsi(line); } } @@ -238,7 +240,7 @@ export function colorizeCode({ {`${index + 1}`} )} - {line} + {stripAnsi(line)} )); diff --git a/packages/cli/src/ui/utils/__snapshots__/CodeColorizer-colorizeCode-does-not-let-colors-from-ansi-escape-codes-leak-into-colorized-code.snap.svg b/packages/cli/src/ui/utils/__snapshots__/CodeColorizer-colorizeCode-does-not-let-colors-from-ansi-escape-codes-leak-into-colorized-code.snap.svg new file mode 100644 index 0000000000..04b774eb58 --- /dev/null +++ b/packages/cli/src/ui/utils/__snapshots__/CodeColorizer-colorizeCode-does-not-let-colors-from-ansi-escape-codes-leak-into-colorized-code.snap.svg @@ -0,0 +1,16 @@ + + + + + line + 1 + line + 2 + with + red background + line + 3 + + \ No newline at end of file diff --git a/packages/cli/src/ui/utils/__snapshots__/CodeColorizer.test.tsx.snap b/packages/cli/src/ui/utils/__snapshots__/CodeColorizer.test.tsx.snap new file mode 100644 index 0000000000..c348c6ef50 --- /dev/null +++ b/packages/cli/src/ui/utils/__snapshots__/CodeColorizer.test.tsx.snap @@ -0,0 +1,7 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`colorizeCode > does not let colors from ansi escape codes leak into colorized code 1`] = ` +"line 1 +line 2 with red background +line 3" +`;