Something went wrong. Try again.
source dump of claude code forked from oppi.li/claude-code
Something went wrong. Try again.
12 kB · 381 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382import chalk from 'chalk'import { marked, type Token, type Tokens } from 'marked'import stripAnsi from 'strip-ansi'import { color } from '../components/design-system/color.js'import { BLOCKQUOTE_BAR } from '../constants/figures.js'import { stringWidth } from '../ink/stringWidth.js'import { supportsHyperlinks } from '../ink/supports-hyperlinks.js'import type { CliHighlight } from './cliHighlight.js'import { logForDebugging } from './debug.js'import { createHyperlink } from './hyperlink.js'import { stripPromptXMLTags } from './messages.js'import type { ThemeName } from './theme.js'
// Use \n unconditionally — os.EOL is \r\n on Windows, and the extra \r// breaks the character-to-segment mapping in applyStylesToWrappedText,// causing styled text to shift right.const EOL = '\n'
let markedConfigured = false
export function configureMarked(): void { if (markedConfigured) return markedConfigured = true
// Disable strikethrough parsing - the model often uses ~ for "approximate" // (e.g., ~100) and rarely intends actual strikethrough formatting marked.use({ tokenizer: { del() { return undefined }, }, })}
export function applyMarkdown( content: string, theme: ThemeName, highlight: CliHighlight | null = null,): string { configureMarked() return marked .lexer(stripPromptXMLTags(content)) .map(_ => formatToken(_, theme, 0, null, null, highlight)) .join('') .trim()}
export function formatToken( token: Token, theme: ThemeName, listDepth = 0, orderedListNumber: number | null = null, parent: Token | null = null, highlight: CliHighlight | null = null,): string { switch (token.type) { case 'blockquote': { const inner = (token.tokens ?? []) .map(_ => formatToken(_, theme, 0, null, null, highlight)) .join('') // Prefix each line with a dim vertical bar. Keep text italic but at // normal brightness — chalk.dim is nearly invisible on dark themes. const bar = chalk.dim(BLOCKQUOTE_BAR) return inner .split(EOL) .map(line => stripAnsi(line).trim() ? `${bar} ${chalk.italic(line)}` : line, ) .join(EOL) } case 'code': { if (!highlight) { return token.text + EOL } let language = 'plaintext' if (token.lang) { if (highlight.supportsLanguage(token.lang)) { language = token.lang } else { logForDebugging( `Language not supported while highlighting code, falling back to plaintext: ${token.lang}`, ) } } return highlight.highlight(token.text, { language }) + EOL } case 'codespan': { // inline code return color('permission', theme)(token.text) } case 'em': return chalk.italic( (token.tokens ?? []) .map(_ => formatToken(_, theme, 0, null, parent, highlight)) .join(''), ) case 'strong': return chalk.bold( (token.tokens ?? []) .map(_ => formatToken(_, theme, 0, null, parent, highlight)) .join(''), ) case 'heading': switch (token.depth) { case 1: // h1 return ( chalk.bold.italic.underline( (token.tokens ?? []) .map(_ => formatToken(_, theme, 0, null, null, highlight)) .join(''), ) + EOL + EOL ) case 2: // h2 return ( chalk.bold( (token.tokens ?? []) .map(_ => formatToken(_, theme, 0, null, null, highlight)) .join(''), ) + EOL + EOL ) default: // h3+ return ( chalk.bold( (token.tokens ?? []) .map(_ => formatToken(_, theme, 0, null, null, highlight)) .join(''), ) + EOL + EOL ) } case 'hr': return '---' case 'image': return token.href case 'link': { // Prevent mailto links from being displayed as clickable links if (token.href.startsWith('mailto:')) { // Extract email from mailto: link and display as plain text const email = token.href.replace(/^mailto:/, '') return email } // Extract display text from the link's child tokens const linkText = (token.tokens ?? []) .map(_ => formatToken(_, theme, 0, null, token, highlight)) .join('') const plainLinkText = stripAnsi(linkText) // If the link has meaningful display text (different from the URL), // show it as a clickable hyperlink. In terminals that support OSC 8, // users see the text and can hover/click to see the URL. if (plainLinkText && plainLinkText !== token.href) { return createHyperlink(token.href, linkText) } // When the display text matches the URL (or is empty), just show the URL return createHyperlink(token.href) } case 'list': { return token.items .map((_: Token, index: number) => formatToken( _, theme, listDepth, token.ordered ? token.start + index : null, token, highlight, ), ) .join('') } case 'list_item': return (token.tokens ?? []) .map( _ => `${' '.repeat(listDepth)}${formatToken(_, theme, listDepth + 1, orderedListNumber, token, highlight)}`, ) .join('') case 'paragraph': return ( (token.tokens ?? []) .map(_ => formatToken(_, theme, 0, null, null, highlight)) .join('') + EOL ) case 'space': return EOL case 'br': return EOL case 'text': if (parent?.type === 'link') { // Already inside a markdown link — the link handler will wrap this // in an OSC 8 hyperlink. Linkifying here would nest a second OSC 8 // sequence, and terminals honor the innermost one, overriding the // link's actual href. return token.text } if (parent?.type === 'list_item') { return `${orderedListNumber === null ? '-' : getListNumber(listDepth, orderedListNumber) + '.'} ${token.tokens ? token.tokens.map(_ => formatToken(_, theme, listDepth, orderedListNumber, token, highlight)).join('') : linkifyIssueReferences(token.text)}${EOL}` } return linkifyIssueReferences(token.text) case 'table': { const tableToken = token as Tokens.Table
// Helper function to get the text content that will be displayed (after stripAnsi) function getDisplayText(tokens: Token[] | undefined): string { return stripAnsi( tokens ?.map(_ => formatToken(_, theme, 0, null, null, highlight)) .join('') ?? '', ) }
// Determine column widths based on displayed content (without formatting) const columnWidths = tableToken.header.map((header, index) => { let maxWidth = stringWidth(getDisplayText(header.tokens)) for (const row of tableToken.rows) { const cellLength = stringWidth(getDisplayText(row[index]?.tokens)) maxWidth = Math.max(maxWidth, cellLength) } return Math.max(maxWidth, 3) // Minimum width of 3 })
// Format header row let tableOutput = '| ' tableToken.header.forEach((header, index) => { const content = header.tokens ?.map(_ => formatToken(_, theme, 0, null, null, highlight)) .join('') ?? '' const displayText = getDisplayText(header.tokens) const width = columnWidths[index]! const align = tableToken.align?.[index] tableOutput += padAligned(content, stringWidth(displayText), width, align) + ' | ' }) tableOutput = tableOutput.trimEnd() + EOL
// Add separator row tableOutput += '|' columnWidths.forEach(width => { // Always use dashes, don't show alignment colons in the output const separator = '-'.repeat(width + 2) // +2 for spaces on each side tableOutput += separator + '|' }) tableOutput += EOL
// Format data rows tableToken.rows.forEach(row => { tableOutput += '| ' row.forEach((cell, index) => { const content = cell.tokens ?.map(_ => formatToken(_, theme, 0, null, null, highlight)) .join('') ?? '' const displayText = getDisplayText(cell.tokens) const width = columnWidths[index]! const align = tableToken.align?.[index] tableOutput += padAligned(content, stringWidth(displayText), width, align) + ' | ' }) tableOutput = tableOutput.trimEnd() + EOL })
return tableOutput + EOL } case 'escape': // Markdown escape: \) → ), \\ → \, etc. return token.text case 'def': case 'del': case 'html': // These token types are not rendered return '' } return ''}
// Matches owner/repo#NNN style GitHub issue/PR references. The qualified form// is unambiguous — bare #NNN was removed because it guessed the current repo// and was wrong whenever the assistant discussed a different one.// Owner segment disallows dots (GitHub usernames are alphanumerics + hyphens// only) so hostnames like docs.github.io/guide#42 don't false-positive. Repo// segment allows dots (e.g. cc.kurs.web). Lookbehind is avoided — it defeats// YARR JIT in JSC.const ISSUE_REF_PATTERN = /(^|[^\w./-])([A-Za-z0-9][\w-]*\/[A-Za-z0-9][\w.-]*)#(\d+)\b/g
/** * Replaces owner/repo#123 references with clickable hyperlinks to GitHub. */function linkifyIssueReferences(text: string): string { if (!supportsHyperlinks()) { return text } return text.replace( ISSUE_REF_PATTERN, (_match, prefix, repo, num) => prefix + createHyperlink( `https://github.com/${repo}/issues/${num}`, `${repo}#${num}`, ), )}
function numberToLetter(n: number): string { let result = '' while (n > 0) { n-- result = String.fromCharCode(97 + (n % 26)) + result n = Math.floor(n / 26) } return result}
const ROMAN_VALUES: ReadonlyArray<[number, string]> = [ [1000, 'm'], [900, 'cm'], [500, 'd'], [400, 'cd'], [100, 'c'], [90, 'xc'], [50, 'l'], [40, 'xl'], [10, 'x'], [9, 'ix'], [5, 'v'], [4, 'iv'], [1, 'i'],]
function numberToRoman(n: number): string { let result = '' for (const [value, numeral] of ROMAN_VALUES) { while (n >= value) { result += numeral n -= value } } return result}
function getListNumber(listDepth: number, orderedListNumber: number): string { switch (listDepth) { case 0: case 1: return orderedListNumber.toString() case 2: return numberToLetter(orderedListNumber) case 3: return numberToRoman(orderedListNumber) default: return orderedListNumber.toString() }}
/** * Pad `content` to `targetWidth` according to alignment. `displayWidth` is the * visible width of `content` (caller computes this, e.g. via stringWidth on * stripAnsi'd text, so ANSI codes in `content` don't affect padding). */export function padAligned( content: string, displayWidth: number, targetWidth: number, align: 'left' | 'center' | 'right' | null | undefined,): string { const padding = Math.max(0, targetWidth - displayWidth) if (align === 'center') { const leftPad = Math.floor(padding / 2) return ' '.repeat(leftPad) + content + ' '.repeat(padding - leftPad) } if (align === 'right') { return ' '.repeat(padding) + content } return content + ' '.repeat(padding)}