diff --git a/ui-tui/src/components/markdown.tsx b/ui-tui/src/components/markdown.tsx index 9a75952f54..5882ab8c7e 100644 --- a/ui-tui/src/components/markdown.tsx +++ b/ui-tui/src/components/markdown.tsx @@ -3,6 +3,10 @@ import type { ReactNode } from 'react' import type { Theme } from '../theme.js' +/** OSC 8 hyperlink — wrap-ansi / Ink keep the link active across soft line wraps. */ +const osc8 = (url: string) => '\x1b]8;;' + url + '\x1b\\' +const OSC8_END = '\x1b]8;;\x1b\\' + function MdInline({ t, text }: { t: Theme; text: string }) { const parts: ReactNode[] = [] const re = /(\[(.+?)\]\((https?:\/\/[^\s)]+)\)|\*\*(.+?)\*\*|`([^`]+)`|\*(.+?)\*|(https?:\/\/[^\s]+))/g @@ -18,8 +22,12 @@ function MdInline({ t, text }: { t: Theme; text: string }) { if (m[2] && m[3]) { parts.push( - - {m[2]} + + {osc8(m[3])} + + {m[2]} + + {OSC8_END} ) } else if (m[4]) { @@ -41,9 +49,14 @@ function MdInline({ t, text }: { t: Theme; text: string }) { ) } else if (m[7]) { + const u = m[7] parts.push( - - {m[7]} + + {osc8(u)} + + {u} + + {OSC8_END} ) } diff --git a/ui-tui/src/lib/text.ts b/ui-tui/src/lib/text.ts index 264d741fde..44d6a31522 100644 --- a/ui-tui/src/lib/text.ts +++ b/ui-tui/src/lib/text.ts @@ -5,7 +5,7 @@ const ANSI_RE = /\x1b\[[0-9;]*m/g export const stripAnsi = (s: string) => s.replace(ANSI_RE, '') -export const hasAnsi = (s: string) => s.includes('\x1b[') +export const hasAnsi = (s: string) => s.includes('\x1b[') || s.includes('\x1b]') const renderEstimateLine = (line: string) => { const trimmed = line.trim()