From 089477eb1e4cd247ad041661d571723415fa044c Mon Sep 17 00:00:00 2001 From: one Date: Tue, 2 Sep 2025 21:52:14 +0800 Subject: [PATCH] refactor(CodeViewer): improve props, aligned to CodeEditor (#9786) * refactor(CodeViewer): improve props, aligned to CodeEditor * refactor: simplify internal variables * refactor: remove default lineNumbers * fix: shiki theme container style * revert: use ReactMarkdown for prompt editing --- .../src/components/CodeBlockView/view.tsx | 7 +- .../src/components/CodeEditor/index.tsx | 14 +-- src/renderer/src/components/CodeViewer.tsx | 89 ++++++++++++++----- .../AssistantPromptSettings.tsx | 12 ++- 4 files changed, 89 insertions(+), 33 deletions(-) diff --git a/src/renderer/src/components/CodeBlockView/view.tsx b/src/renderer/src/components/CodeBlockView/view.tsx index 174327ae68..46f6cbce86 100644 --- a/src/renderer/src/components/CodeBlockView/view.tsx +++ b/src/renderer/src/components/CodeBlockView/view.tsx @@ -257,12 +257,13 @@ export const CodeBlockView: React.FC = memo(({ children, language, onSave ) : ( - {children} - + maxHeight={`${MAX_COLLAPSED_CODE_HEIGHT}px`} + /> ), [children, codeEditor.enabled, handleHeightChange, language, onSave, shouldExpand, shouldWrap] ) diff --git a/src/renderer/src/components/CodeEditor/index.tsx b/src/renderer/src/components/CodeEditor/index.tsx index 7c541cbdb8..4304ec324e 100644 --- a/src/renderer/src/components/CodeEditor/index.tsx +++ b/src/renderer/src/components/CodeEditor/index.tsx @@ -48,8 +48,6 @@ export interface CodeEditorProps { maxHeight?: string /** Minimum editor height. */ minHeight?: string - /** Font size that overrides the app setting. */ - fontSize?: string /** Editor options that extend BasicSetupOptions. */ options?: { /** @@ -70,6 +68,8 @@ export interface CodeEditorProps { } & BasicSetupOptions /** Additional extensions for CodeMirror. */ extensions?: Extension[] + /** Font size that overrides the app setting. */ + fontSize?: number /** Style overrides for the editor, passed directly to CodeMirror's style property. */ style?: React.CSSProperties /** CSS class name appended to the default `code-editor` class. */ @@ -108,9 +108,9 @@ const CodeEditor = ({ height, maxHeight, minHeight, - fontSize, options, extensions, + fontSize: customFontSize, style, className, editable = true, @@ -121,7 +121,7 @@ const CodeEditor = ({ const enableKeymap = useMemo(() => options?.keymap ?? codeEditor.keymap, [options?.keymap, codeEditor.keymap]) // 合并 codeEditor 和 options 的 basicSetup,options 优先 - const customBasicSetup = useMemo(() => { + const basicSetup = useMemo(() => { return { lineNumbers: _lineNumbers, ...(codeEditor as BasicSetupOptions), @@ -129,7 +129,7 @@ const CodeEditor = ({ } }, [codeEditor, _lineNumbers, options]) - const customFontSize = useMemo(() => fontSize ?? `${_fontSize - 1}px`, [fontSize, _fontSize]) + const fontSize = useMemo(() => customFontSize ?? _fontSize - 1, [customFontSize, _fontSize]) const { activeCmTheme } = useCodeStyle() const initialContent = useRef(options?.stream ? (value ?? '').trimEnd() : (value ?? '')) @@ -214,10 +214,10 @@ const CodeEditor = ({ foldKeymap: enableKeymap, completionKeymap: enableKeymap, lintKeymap: enableKeymap, - ...customBasicSetup // override basicSetup + ...basicSetup // override basicSetup }} style={{ - fontSize: customFontSize, + fontSize, marginTop: 0, borderRadius: 'inherit', ...style diff --git a/src/renderer/src/components/CodeViewer.tsx b/src/renderer/src/components/CodeViewer.tsx index 440aed9c7c..e8080d9518 100644 --- a/src/renderer/src/components/CodeViewer.tsx +++ b/src/renderer/src/components/CodeViewer.tsx @@ -1,4 +1,3 @@ -import { MAX_COLLAPSED_CODE_HEIGHT } from '@renderer/config/constant' import { useCodeStyle } from '@renderer/context/CodeStyleProvider' import { useCodeHighlight } from '@renderer/hooks/useCodeHighlight' import { useSettings } from '@renderer/hooks/useSettings' @@ -11,13 +10,49 @@ import { ThemedToken } from 'shiki/core' import styled from 'styled-components' interface CodeViewerProps { + /** Code string value. */ + value: string + /** + * Code language string. + * - Case-insensitive. + * - Supports common names: javascript, json, python, etc. + * - Supports shiki aliases: c#/csharp, objective-c++/obj-c++/objc++, etc. + */ language: string - children: string - expanded?: boolean - wrapped?: boolean + /** Fired when the editor height changes. */ onHeightChange?: (scrollHeight: number) => void - className?: string + /** + * Height of the scroll container. + * Only works when expanded is false. + */ height?: string | number + /** + * Maximum height of the scroll container. + * Only works when expanded is false. + */ + maxHeight?: string | number + /** Viewer options. */ + options?: { + /** + * Whether to show line numbers. + */ + lineNumbers?: boolean + } + /** Font size that overrides the app setting. */ + fontSize?: number + /** CSS class name appended to the default `code-viewer` class. */ + className?: string + /** + * Whether the editor is expanded. + * If true, the height and maxHeight props are ignored. + * @default true + */ + expanded?: boolean + /** + * Whether the code lines are wrapped. + * @default true + */ + wrapped?: boolean } /** @@ -26,19 +61,33 @@ interface CodeViewerProps { * - 使用虚拟滚动和按需高亮,改善页面内有大量长代码块时的响应 * - 并发安全 */ -const CodeViewer = ({ children, language, expanded, wrapped, onHeightChange, className, height }: CodeViewerProps) => { - const { codeShowLineNumbers, fontSize } = useSettings() +const CodeViewer = ({ + value, + language, + height, + maxHeight, + onHeightChange, + options, + fontSize: customFontSize, + className, + expanded = true, + wrapped = true +}: CodeViewerProps) => { + const { codeShowLineNumbers: _lineNumbers, fontSize: _fontSize } = useSettings() const { getShikiPreProperties, isShikiThemeDark } = useCodeStyle() const shikiThemeRef = useRef(null) const scrollerRef = useRef(null) const callerId = useRef(`${Date.now()}-${uuid()}`).current - const rawLines = useMemo(() => (typeof children === 'string' ? children.trimEnd().split('\n') : []), [children]) + const fontSize = useMemo(() => customFontSize ?? _fontSize - 1, [customFontSize, _fontSize]) + const lineNumbers = useMemo(() => options?.lineNumbers ?? _lineNumbers, [options?.lineNumbers, _lineNumbers]) + + const rawLines = useMemo(() => (typeof value === 'string' ? value.trimEnd().split('\n') : []), [value]) // 计算行号数字位数 const gutterDigits = useMemo( - () => (codeShowLineNumbers ? Math.max(rawLines.length.toString().length, 1) : 0), - [codeShowLineNumbers, rawLines.length] + () => (lineNumbers ? Math.max(rawLines.length.toString().length, 1) : 0), + [lineNumbers, rawLines.length] ) // 设置 pre 标签属性 @@ -68,7 +117,7 @@ const CodeViewer = ({ children, language, expanded, wrapped, onHeightChange, cla const getScrollElement = useCallback(() => scrollerRef.current, []) const getItemKey = useCallback((index: number) => `${callerId}-${index}`, [callerId]) // `line-height: 1.6` 为全局样式,但是为了避免测量误差在这里取整 - const estimateSize = useCallback(() => Math.round((fontSize - 1) * 1.6), [fontSize]) + const estimateSize = useCallback(() => Math.round(fontSize * 1.6), [fontSize]) // 创建 virtualizer 实例 const virtualizer = useVirtualizer({ @@ -105,20 +154,19 @@ const CodeViewer = ({ children, language, expanded, wrapped, onHeightChange, cla }, [rawLines.length, onHeightChange]) return ( -
+
@@ -142,7 +190,7 @@ const CodeViewer = ({ children, language, expanded, wrapped, onHeightChange, cla
@@ -226,9 +274,8 @@ VirtualizedRow.displayName = 'VirtualizedRow' const ScrollContainer = styled.div<{ $wrap?: boolean - $expanded?: boolean + $expand?: boolean $lineHeight?: number - $height?: string | number }>` display: block; overflow-x: auto; @@ -244,7 +291,7 @@ const ScrollContainer = styled.div<{ line-height: ${(props) => props.$lineHeight}px; /* contain 优化 wrap 时滚动性能,will-change 优化 unwrap 时滚动性能 */ contain: ${(props) => (props.$wrap ? 'content' : 'none')}; - will-change: ${(props) => (!props.$wrap && !props.$expanded ? 'transform' : 'auto')}; + will-change: ${(props) => (!props.$wrap && !props.$expand ? 'transform' : 'auto')}; .line-number { width: var(--gutter-width, 1.2ch); diff --git a/src/renderer/src/pages/settings/AssistantSettings/AssistantPromptSettings.tsx b/src/renderer/src/pages/settings/AssistantSettings/AssistantPromptSettings.tsx index 9f27db6afb..a2ce2657b9 100644 --- a/src/renderer/src/pages/settings/AssistantSettings/AssistantPromptSettings.tsx +++ b/src/renderer/src/pages/settings/AssistantSettings/AssistantPromptSettings.tsx @@ -2,7 +2,6 @@ import 'emoji-picker-element' import { CloseCircleFilled } from '@ant-design/icons' import CodeEditor from '@renderer/components/CodeEditor' -import CodeViewer from '@renderer/components/CodeViewer' import EmojiPicker from '@renderer/components/EmojiPicker' import { Box, HSpaceBetweenStack, HStack } from '@renderer/components/Layout' import { RichEditorRef } from '@renderer/components/RichEditor/types' @@ -14,6 +13,7 @@ import { Button, Input, Popover } from 'antd' import { Edit, HelpCircle, Save } from 'lucide-react' import { useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' +import ReactMarkdown from 'react-markdown' import styled from 'styled-components' import { SettingDivider } from '..' @@ -122,7 +122,9 @@ const AssistantPromptSettings: React.FC = ({ assistant, updateAssistant } {showPreview ? ( - + + {processedPrompt || prompt} + ) : (