refactor(CodePreview): smoothing code highlighting on streaming (#7842)

This commit is contained in:
one 2025-07-08 17:25:14 +08:00 committed by GitHub
parent fba6c1642d
commit de75992e7b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -9,6 +9,7 @@ import { debounce } from 'lodash'
import { ChevronsDownUp, ChevronsUpDown, Text as UnWrapIcon, WrapText as WrapIcon } from 'lucide-react'
import React, { memo, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { ThemedToken } from 'shiki/core'
import styled from 'styled-components'
interface CodePreviewProps {
@ -150,7 +151,8 @@ const CodePreview = ({ children, language, setTools }: CodePreviewProps) => {
{
'--gutter-width': `${gutterDigits}ch`,
fontSize: `${fontSize - 1}px`,
maxHeight: shouldCollapse ? MAX_COLLAPSE_HEIGHT : undefined
maxHeight: shouldCollapse ? MAX_COLLAPSE_HEIGHT : undefined,
overflowY: shouldCollapse ? 'auto' : 'hidden'
} as React.CSSProperties
}>
<div
@ -195,9 +197,49 @@ const CodePreview = ({ children, language, setTools }: CodePreviewProps) => {
CodePreview.displayName = 'CodePreview'
/**
* tokens
*/
function completeLineTokens(themedTokens: ThemedToken[], rawLine: string): ThemedToken[] {
// 如果出现空行,补一个空格保证行高
if (rawLine.length === 0) {
return [
{
content: ' ',
offset: 0,
color: 'inherit',
bgColor: 'inherit',
htmlStyle: {
opacity: '0.35'
}
}
]
}
const themedContent = themedTokens.map((token) => token.content).join('')
const extraContent = rawLine.slice(themedContent.length)
// 已有内容已经全部高亮,直接返回
if (!extraContent) return themedTokens
// 补全剩余内容
return [
...themedTokens,
{
content: extraContent,
offset: themedContent.length,
color: 'inherit',
bgColor: 'inherit',
htmlStyle: {
opacity: '0.35'
}
}
]
}
interface VirtualizedRowData {
rawLine: string
tokenLine?: any[]
tokenLine?: ThemedToken[]
showLineNumbers: boolean
}
@ -210,17 +252,11 @@ const VirtualizedRow = memo(
<div className="line">
{showLineNumbers && <span className="line-number">{index + 1}</span>}
<span className="line-content">
{tokenLine ? (
// 渲染高亮后的内容
tokenLine.map((token, tokenIndex) => (
<span key={tokenIndex} style={getReactStyleFromToken(token)}>
{token.content}
</span>
))
) : (
// 渲染原始内容
<span className="line-content-raw">{rawLine || ' '}</span>
)}
{completeLineTokens(tokenLine ?? [], rawLine).map((token, tokenIndex) => (
<span key={tokenIndex} style={getReactStyleFromToken(token)}>
{token.content}
</span>
))}
</span>
</div>
)
@ -234,7 +270,7 @@ const ScrollContainer = styled.div<{
$lineHeight?: number
}>`
display: block;
overflow: auto;
overflow-x: auto;
position: relative;
border-radius: inherit;
padding: 0.5em 1em;
@ -264,10 +300,6 @@ const ScrollContainer = styled.div<{
overflow-wrap: ${(props) => (props.$wrap ? 'break-word' : 'normal')};
}
}
.line-content-raw {
opacity: 0.35;
}
}
`