From 3a2a72786cc48af9594ded45cd3ce048e283ec11 Mon Sep 17 00:00:00 2001 From: Bryan Morgan Date: Sun, 8 Feb 2026 14:49:40 -0500 Subject: [PATCH] fix(cli): support LaTeX-style arrows in markdown renderer --- .../ui/utils/InlineMarkdownRenderer.test.ts | 25 --------- .../ui/utils/InlineMarkdownRenderer.test.tsx | 53 +++++++++++++++++++ .../src/ui/utils/InlineMarkdownRenderer.tsx | 20 ++++++- 3 files changed, 71 insertions(+), 27 deletions(-) delete mode 100644 packages/cli/src/ui/utils/InlineMarkdownRenderer.test.ts create mode 100644 packages/cli/src/ui/utils/InlineMarkdownRenderer.test.tsx diff --git a/packages/cli/src/ui/utils/InlineMarkdownRenderer.test.ts b/packages/cli/src/ui/utils/InlineMarkdownRenderer.test.ts deleted file mode 100644 index 11fb6d56eb..0000000000 --- a/packages/cli/src/ui/utils/InlineMarkdownRenderer.test.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * @license - * Copyright 2025 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -import { getPlainTextLength } from './InlineMarkdownRenderer.js'; -import { describe, it, expect } from 'vitest'; - -describe('getPlainTextLength', () => { - it.each([ - ['**Primary Go', 12], - ['*Primary Go', 11], - ['**Primary Go**', 10], - ['*Primary Go*', 10], - ['**', 2], - ['*', 1], - ['compile-time**', 14], - ])( - 'should measure markdown text length correctly for "%s"', - (input, expected) => { - expect(getPlainTextLength(input)).toBe(expected); - }, - ); -}); diff --git a/packages/cli/src/ui/utils/InlineMarkdownRenderer.test.tsx b/packages/cli/src/ui/utils/InlineMarkdownRenderer.test.tsx new file mode 100644 index 0000000000..2b9e0bf645 --- /dev/null +++ b/packages/cli/src/ui/utils/InlineMarkdownRenderer.test.tsx @@ -0,0 +1,53 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { describe, it, expect } from 'vitest'; +import { RenderInline, getPlainTextLength } from './InlineMarkdownRenderer.js'; +import { renderWithProviders } from '../../test-utils/render.js'; +import { Text } from 'ink'; + +describe('InlineMarkdownRenderer', () => { + describe('getPlainTextLength', () => { + it.each([ + ['**Primary Go', 12], + ['*Primary Go', 11], + ['**Primary Go**', 10], + ['*Primary Go*', 10], + ['**', 2], + ['*', 1], + ['compile-time**', 14], + ['$\\rightarrow$', 1], + ['Sign Out $\\rightarrow$ Sign In', 18], + ])( + 'should measure markdown text length correctly for "%s"', + (input, expected) => { + expect(getPlainTextLength(input)).toBe(expected); + }, + ); + }); + + describe('', () => { + it('renders LaTeX rightarrow correctly', () => { + const text = 'Sign Out $\\rightarrow$ Sign In'; + const { lastFrame } = renderWithProviders( + + + , + ); + expect(lastFrame()).toContain('Sign Out → Sign In'); + }); + + it('renders other LaTeX arrows correctly', () => { + const text = '$\\leftarrow$ $\\uparrow$ $\\downarrow$'; + const { lastFrame } = renderWithProviders( + + + , + ); + expect(lastFrame()).toContain('← ↑ ↓'); + }); + }); +}); diff --git a/packages/cli/src/ui/utils/InlineMarkdownRenderer.tsx b/packages/cli/src/ui/utils/InlineMarkdownRenderer.tsx index 8d4c6a7da6..dcba8bf80e 100644 --- a/packages/cli/src/ui/utils/InlineMarkdownRenderer.tsx +++ b/packages/cli/src/ui/utils/InlineMarkdownRenderer.tsx @@ -36,7 +36,7 @@ const RenderInlineInternal: React.FC = ({ const nodes: React.ReactNode[] = []; let lastIndex = 0; const inlineRegex = - /(\*\*.*?\*\*|\*.*?\*|_.*?_|~~.*?~~|\[.*?\]\(.*?\)|`+.+?`+|.*?<\/u>|https?:\/\/\S+)/g; + /(\*\*.*?\*\*|\*.*?\*|_.*?_|~~.*?~~|\[.*?\]\(.*?\)|`+.+?`+|.*?<\/u>|https?:\/\/\S+|\$\\[a-z]+\$)/g; let match; while ((match = inlineRegex.exec(text)) !== null) { @@ -143,6 +143,21 @@ const RenderInlineInternal: React.FC = ({ {fullMatch} ); + } else if (fullMatch.startsWith('$') && fullMatch.endsWith('$')) { + const latexMap: Record = { + '$\\rightarrow$': '→', + '$\\leftarrow$': '←', + '$\\uparrow$': '↑', + '$\\downarrow$': '↓', + }; + const replacement = latexMap[fullMatch]; + if (replacement) { + renderedNode = ( + + {replacement} + + ); + } } } catch (e) { debugLogger.warn('Error parsing inline markdown part:', fullMatch, e); @@ -184,6 +199,7 @@ export const getPlainTextLength = (text: string): number => { .replace(/~~(.*?)~~/g, '$1') .replace(/`(.*?)`/g, '$1') .replace(/(.*?)<\/u>/g, '$1') - .replace(/.*\[(.*?)\]\(.*\)/g, '$1'); + .replace(/.*\[(.*?)\]\(.*\)/g, '$1') + .replace(/\$\\(rightarrow|leftarrow|uparrow|downarrow)\$/g, '→'); return stringWidth(cleanText); };