Files
claude-code/src/components/HighlightedCode/Fallback.tsx
claude-code-best 08cd02cd37 fix: highlight 缓存改用 LRUCache 降低内存开销
- Fallback.tsx: 手动 Map LRU 替换为 lru-cache 的 LRUCache
- Markdown.tsx: tokenCache 同样替换为 LRUCache
- color-diff-napi: 新增行级 hljs AST 缓存,避免终端 resize 时重复高亮

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-29 21:59:10 +08:00

92 lines
2.6 KiB
TypeScript

import { extname } from 'path'
import React, { Suspense, use, useMemo } from 'react'
import { Ansi, Text } from '@anthropic/ink'
import { LRUCache } from 'lru-cache'
import { getCliHighlightPromise } from '../../utils/cliHighlight.js'
import { logForDebugging } from '../../utils/debug.js'
import { convertLeadingTabsToSpaces } from '../../utils/file.js'
import { hashPair } from '../../utils/hash.js'
type Props = {
code: string
filePath: string
dim?: boolean
skipColoring?: boolean
}
// Module-level highlight cache — hl.highlight() is the hot cost on virtual-
// scroll remounts. useMemo doesn't survive unmount→remount. Keyed by hash
// of code+language to avoid retaining full source strings (#24180 RSS fix).
const hlCache = new LRUCache<string, string>({ max: 500 })
function cachedHighlight(
hl: NonNullable<Awaited<ReturnType<typeof getCliHighlightPromise>>>,
code: string,
language: string,
): string {
const key = hashPair(language, code)
const hit = hlCache.get(key)
if (hit !== undefined) return hit
const out = hl.highlight(code, { language })
hlCache.set(key, out)
return out
}
export function HighlightedCodeFallback({
code,
filePath,
dim = false,
skipColoring = false,
}: Props): React.ReactElement {
const codeWithSpaces = convertLeadingTabsToSpaces(code)
if (skipColoring) {
return (
<Text dimColor={dim}>
<Ansi>{codeWithSpaces}</Ansi>
</Text>
)
}
const language = extname(filePath).slice(1)
return (
<Text dimColor={dim}>
<Suspense fallback={<Ansi>{codeWithSpaces}</Ansi>}>
<Highlighted codeWithSpaces={codeWithSpaces} language={language} />
</Suspense>
</Text>
)
}
function Highlighted({
codeWithSpaces,
language,
}: {
codeWithSpaces: string
language: string
}): React.ReactElement {
const hl = use(getCliHighlightPromise())
const out = useMemo(() => {
if (!hl) return codeWithSpaces
let highlightLang = 'markdown'
if (language) {
if (hl.supportsLanguage(language)) {
highlightLang = language
} else {
logForDebugging(
`Language not supported while highlighting code, falling back to markdown: ${language}`,
)
}
}
try {
return cachedHighlight(hl, codeWithSpaces, highlightLang)
} catch (e) {
if (e instanceof Error && e.message.includes('Unknown language')) {
logForDebugging(
`Language not supported while highlighting code, falling back to markdown: ${e}`,
)
return cachedHighlight(hl, codeWithSpaces, 'markdown')
}
return codeWithSpaces
}
}, [codeWithSpaces, language, hl])
return <Ansi>{out}</Ansi>
}