diff --git a/src/renderer/src/components/CodeBlockView/CodePreview.tsx b/src/renderer/src/components/CodeBlockView/CodePreview.tsx index 3a95a68cd8..3df9491e13 100644 --- a/src/renderer/src/components/CodeBlockView/CodePreview.tsx +++ b/src/renderer/src/components/CodeBlockView/CodePreview.tsx @@ -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 }>
{ 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(
{showLineNumbers && {index + 1}} - {tokenLine ? ( - // 渲染高亮后的内容 - tokenLine.map((token, tokenIndex) => ( - - {token.content} - - )) - ) : ( - // 渲染原始内容 - {rawLine || ' '} - )} + {completeLineTokens(tokenLine ?? [], rawLine).map((token, tokenIndex) => ( + + {token.content} + + ))}
) @@ -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; - } } `