mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-31 08:29:07 +08:00
refactor(CodePreview): smoothing code highlighting on streaming (#7842)
This commit is contained in:
parent
fba6c1642d
commit
de75992e7b
@ -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;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user