fix: disable hljs.highlightAuto for unknown languages, hide line numbers for plain text

- highlightAuto was misdetecting plain text (file trees, command output) as
  code and applying wrong syntax colors (red keywords, green tags, etc.)
- Now only highlights when language is explicitly specified and known to hljs
- Code blocks without a specified language render as clean plain text
- Line numbers hidden for plain text blocks (only shown for actual code)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
程序员阿江(Relakkes) 2026-04-06 21:55:00 +08:00
parent be9ae99793
commit 3b83c9bd34
3 changed files with 60 additions and 28 deletions

View File

@ -16,18 +16,23 @@ export function CodeViewer({ code, language, maxLines = 20, showLineNumbers = tr
const isTruncated = !expanded && lines.length > maxLines
const visibleCode = isTruncated ? lines.slice(0, maxLines).join('\n') : code
// Only highlight when language is explicitly known.
// Do NOT use highlightAuto — it misdetects plain text as code.
const hasLanguage = !!language && !!hljs.getLanguage(language)
const effectiveShowLineNumbers = showLineNumbers && hasLanguage
const highlightedLines = useMemo(() => {
return visibleCode.split('\n').map((line) => {
try {
if (language && hljs.getLanguage(language)) {
return hljs.highlight(line || ' ', { language, ignoreIllegals: true }).value
if (hasLanguage) {
return hljs.highlight(line || ' ', { language: language!, ignoreIllegals: true }).value
}
return hljs.highlightAuto(line || ' ').value
return escapeHtml(line || ' ')
} catch {
return escapeHtml(line)
}
})
}, [language, visibleCode])
}, [hasLanguage, language, visibleCode])
const startLine = 1
@ -55,22 +60,30 @@ export function CodeViewer({ code, language, maxLines = 20, showLineNumbers = tr
<div className="max-h-[420px] overflow-auto">
<div className="min-w-full font-[var(--font-mono)] text-[12px] leading-[1.3]">
{visibleLines.map((line, index) => (
<div
key={`${startLine + index}-${line}`}
className="grid grid-cols-[3rem,minmax(0,1fr)] gap-0 hover:bg-[#f6f8fa]/50"
>
{showLineNumbers ? (
effectiveShowLineNumbers ? (
<div
key={`${startLine + index}-${line}`}
className="grid grid-cols-[3rem,minmax(0,1fr)] gap-0 hover:bg-[#f6f8fa]/50"
>
<span className="select-none border-r border-[#eaeef2] bg-[#fafbfc] px-2 py-px text-right text-[11px] text-[#8b949e]">
{startLine + index}
</span>
) : (
<span className="w-0" />
)}
<span
className="overflow-hidden bg-white px-3 py-px whitespace-pre-wrap break-words text-[#24292f]"
dangerouslySetInnerHTML={{ __html: highlightedLines[index] ?? escapeHtml(line) }}
/>
</div>
<span
className="overflow-hidden bg-white px-3 py-px whitespace-pre-wrap break-words text-[#24292f]"
dangerouslySetInnerHTML={{ __html: highlightedLines[index] ?? escapeHtml(line) }}
/>
</div>
) : (
<div
key={`${startLine + index}-${line}`}
className="hover:bg-[#f6f8fa]/50"
>
<span
className="block bg-white px-3 py-px whitespace-pre-wrap break-words text-[#24292f]"
dangerouslySetInnerHTML={{ __html: highlightedLines[index] ?? escapeHtml(line) }}
/>
</div>
)
))}
</div>
</div>

View File

@ -12,14 +12,22 @@ export function highlightCodeLines(code: string, language?: string): string[] {
return code.split('\n').map((line) => highlightCodeLine(line, language))
}
/** Returns true if the language is known to hljs and should be highlighted */
export function isHighlightable(language?: string): boolean {
return !!language && !!hljs.getLanguage(language)
}
function highlightCodeLine(line: string, language?: string): string {
const safeLine = line.length > 0 ? line : ' '
try {
// Only highlight when language is explicitly specified and known.
// Do NOT use highlightAuto — it misdetects plain text (file trees,
// command output, etc.) as code and applies wrong colors.
if (language && hljs.getLanguage(language)) {
return hljs.highlight(safeLine, { language }).value
return hljs.highlight(safeLine, { language, ignoreIllegals: true }).value
}
return hljs.highlightAuto(safeLine).value
return escapeHtml(safeLine)
} catch {
return escapeHtml(safeLine)
}

View File

@ -1,6 +1,6 @@
import { useMemo } from 'react'
import { marked, type Tokens } from 'marked'
import { escapeHtml, highlightCodeLines } from '../chat/highlightCode'
import { escapeHtml, highlightCodeLines, isHighlightable } from '../chat/highlightCode'
type Props = {
content: string
@ -11,16 +11,27 @@ const renderer = new marked.Renderer()
renderer.code = function ({ text, lang }: Tokens.Code) {
const languageLabel = escapeHtml(lang || 'code')
const lines = text.split('\n')
const hasLanguage = isHighlightable(lang)
const highlightedLines = highlightCodeLines(text, lang)
const body = highlightedLines
.map((line, index) => `
<div class="grid grid-cols-[3rem,minmax(0,1fr)] gap-0 hover:bg-[#f6f8fa]/50">
<span class="select-none border-r border-[#eaeef2] bg-[#fafbfc] px-2 py-px text-right text-[11px] text-[#8b949e]">${index + 1}</span>
<span class="overflow-hidden bg-white px-3 py-px whitespace-pre-wrap break-words text-[#24292f]">${line || '&nbsp;'}</span>
</div>
`)
.join('')
// Show line numbers only when language is known (actual code).
// Plain text, file trees, command output etc. look better without them.
const body = hasLanguage
? highlightedLines
.map((line, index) => `
<div class="grid grid-cols-[3rem,minmax(0,1fr)] gap-0 hover:bg-[#f6f8fa]/50">
<span class="select-none border-r border-[#eaeef2] bg-[#fafbfc] px-2 py-px text-right text-[11px] text-[#8b949e]">${index + 1}</span>
<span class="overflow-hidden bg-white px-3 py-px whitespace-pre-wrap break-words text-[#24292f]">${line || '&nbsp;'}</span>
</div>
`)
.join('')
: highlightedLines
.map((line) => `
<div class="hover:bg-[#f6f8fa]/50">
<span class="block bg-white px-3 py-px whitespace-pre-wrap break-words text-[#24292f]">${line || '&nbsp;'}</span>
</div>
`)
.join('')
return `
<div class="my-4 overflow-hidden rounded-lg border border-[#d0d7de] bg-[#f6f8fa] text-[#24292f]">