import { Box, Text } from 'ink' import type { ReactNode } from 'react' import type { Theme } from '../theme.js' function MdInline({ t, text }: { t: Theme; text: string }) { const parts: ReactNode[] = [] const re = /(\[(.+?)\]\((https?:\/\/[^\s)]+)\)|\*\*(.+?)\*\*|`([^`]+)`|\*(.+?)\*|(https?:\/\/[^\s]+))/g let last = 0 for (const m of text.matchAll(re)) { const i = m.index ?? 0 if (i > last) { parts.push({text.slice(last, i)}) } if (m[2] && m[3]) { parts.push( {m[2]} ) } else if (m[4]) { parts.push( {m[4]} ) } else if (m[5]) { parts.push( {m[5]} ) } else if (m[6]) { parts.push( {m[6]} ) } else if (m[7]) { parts.push( {m[7]} ) } last = i + m[0].length } if (last < text.length) { parts.push({text.slice(last)}) } return {parts.length ? parts : {text}} } export function Md({ compact, t, text }: { compact?: boolean; t: Theme; text: string }) { const lines = text.split('\n') const nodes: ReactNode[] = [] let i = 0 let prevKind: 'blank' | 'code' | 'heading' | 'list' | 'paragraph' | 'quote' | 'table' | null = null const gap = () => { if (nodes.length && prevKind !== 'blank') { nodes.push( ) prevKind = 'blank' } } const start = (kind: Exclude) => { if (prevKind && prevKind !== 'blank' && prevKind !== kind) { gap() } prevKind = kind } while (i < lines.length) { const line = lines[i]! const key = nodes.length if (compact && !line.trim()) { i++ continue } if (!line.trim()) { gap() i++ continue } if (line.startsWith('```')) { start('code') const lang = line.slice(3).trim() const block: string[] = [] for (i++; i < lines.length && !lines[i]!.startsWith('```'); i++) { block.push(lines[i]!) } i++ const isDiff = lang === 'diff' nodes.push( {lang && !isDiff && {'─ ' + lang}} {block.map((l, j) => ( {l} ))} ) continue } const heading = line.match(/^#{1,3}\s+(.*)/) if (heading) { start('heading') nodes.push( {heading[1]} ) i++ continue } const bullet = line.match(/^\s*[-*]\s(.*)/) if (bullet) { start('list') nodes.push( ) i++ continue } const numbered = line.match(/^\s*(\d+)\.\s(.*)/) if (numbered) { start('list') nodes.push( {numbered[1]}. ) i++ continue } if (line.match(/^>\s?/)) { start('quote') const quoteLines: string[] = [] while (i < lines.length && lines[i]!.match(/^>\s?/)) { quoteLines.push(lines[i]!.replace(/^>\s?/, '')) i++ } nodes.push( {quoteLines.map((ql, qi) => ( {' │ '} ))} ) continue } if (line.includes('|') && line.trim().startsWith('|')) { start('table') const tableRows: string[][] = [] while (i < lines.length && lines[i]!.trim().startsWith('|')) { const row = lines[i]!.trim() if (!/^[|\s:-]+$/.test(row)) { tableRows.push( row .split('|') .filter(Boolean) .map(c => c.trim()) ) } i++ } if (tableRows.length) { const widths = tableRows[0]!.map((_, ci) => Math.max(...tableRows.map(r => (r[ci] ?? '').length))) nodes.push( {tableRows.map((row, ri) => ( {row.map((cell, ci) => cell.padEnd(widths[ci] ?? 0)).join(' ')} ))} ) } continue } start('paragraph') nodes.push() i++ } return {nodes} }