Download desktop/src/components/chat/CodeViewer.tsx from chenbhao/codev: direct link, hf CLI and curl.
- Browser
- Download file 12.3 kB
-
https://huggingface.co/chenbhao/codev/resolve/e40073cc959cd4729bf4009662bca374c5c4e266/desktop/src/components/chat/CodeViewer.tsx
- Command line
-
hf download hf://chenbhao/codev@e40073cc959cd4729bf4009662bca374c5c4e266/desktop/src/components/chat/CodeViewer.tsx
-
curl -L -o CodeViewer.tsx https://huggingface.co/chenbhao/codev/resolve/e40073cc959cd4729bf4009662bca374c5c4e266/desktop/src/components/chat/CodeViewer.tsx
12.3 kB
| import { useEffect, useRef, useState, type ComponentType, type CSSProperties } from 'react' | |
| import { Highlight, type PrismTheme } from 'prism-react-renderer' | |
| import { CopyButton } from '../shared/CopyButton' | |
| type Props = { | |
| code: string | |
| language?: string | |
| maxLines?: number | |
| showLineNumbers?: boolean | |
| } | |
| const warmPrismTheme: PrismTheme = { | |
| plain: { | |
| color: 'var(--color-code-fg)', | |
| backgroundColor: 'transparent', | |
| }, | |
| styles: [ | |
| { types: ['comment', 'prolog', 'doctype', 'cdata'], style: { color: 'var(--color-code-comment)', fontStyle: 'italic' as const } }, | |
| { types: ['string', 'attr-value', 'template-string'], style: { color: 'var(--color-code-string)' } }, | |
| { types: ['keyword', 'selector', 'important', 'atrule'], style: { color: 'var(--color-code-keyword)' } }, | |
| { types: ['function'], style: { color: 'var(--color-code-function)' } }, | |
| { types: ['tag'], style: { color: 'var(--color-code-keyword)' } }, | |
| { types: ['number', 'boolean'], style: { color: 'var(--color-code-number)' } }, | |
| { types: ['operator'], style: { color: 'var(--color-code-fg)' } }, | |
| { types: ['punctuation'], style: { color: 'var(--color-code-punctuation)' } }, | |
| { types: ['variable', 'parameter'], style: { color: 'var(--color-code-fg)' } }, | |
| { types: ['property', 'attr-name'], style: { color: 'var(--color-code-property)' } }, | |
| { types: ['builtin', 'class-name', 'constant', 'symbol'], style: { color: 'var(--color-code-type)' } }, | |
| { types: ['regex'], style: { color: 'var(--color-primary-container)' } }, | |
| { types: ['inserted'], style: { color: 'var(--color-code-inserted)' } }, | |
| { types: ['deleted'], style: { color: 'var(--color-code-deleted)' } }, | |
| ], | |
| } | |
| const warmShikiTheme = { | |
| name: 'warm-code', | |
| type: 'dark' as const, | |
| fg: 'var(--color-code-fg)', | |
| bg: 'transparent', | |
| tokenColors: [ | |
| { scope: ['comment', 'punctuation.definition.comment'], settings: { foreground: 'var(--color-code-comment)', fontStyle: 'italic' } }, | |
| { scope: ['string', 'string.quoted', 'string.template', 'string.other.link'], settings: { foreground: 'var(--color-code-string)' } }, | |
| { scope: ['string.regexp'], settings: { foreground: 'var(--color-primary-container)' } }, | |
| { scope: ['keyword', 'keyword.control', 'storage', 'storage.type', 'storage.modifier'], settings: { foreground: 'var(--color-code-keyword)' } }, | |
| { scope: ['keyword.operator'], settings: { foreground: 'var(--color-code-keyword)' } }, | |
| { scope: ['entity.name.function', 'support.function'], settings: { foreground: 'var(--color-code-function)' } }, | |
| { scope: ['entity.name.type', 'support.type', 'support.class', 'entity.name.class', 'entity.other.inherited-class'], settings: { foreground: 'var(--color-code-type)' } }, | |
| { scope: ['entity.name.type.parameter'], settings: { foreground: 'var(--color-code-number)' } }, | |
| { scope: ['variable', 'variable.other', 'variable.other.readwrite'], settings: { foreground: 'var(--color-code-fg)' } }, | |
| { scope: ['variable.parameter'], settings: { foreground: 'var(--color-code-parameter)' } }, | |
| { scope: ['variable.other.property', 'support.type.property-name', 'meta.object-literal.key'], settings: { foreground: 'var(--color-code-property)' } }, | |
| { scope: ['variable.other.constant', 'variable.other.enummember'], settings: { foreground: 'var(--color-code-type)' } }, | |
| { scope: ['constant.numeric', 'constant.language'], settings: { foreground: 'var(--color-code-number)' } }, | |
| { scope: ['punctuation', 'meta.brace', 'meta.bracket'], settings: { foreground: 'var(--color-code-punctuation)' } }, | |
| { scope: ['entity.name.tag', 'punctuation.definition.tag'], settings: { foreground: 'var(--color-code-keyword)' } }, | |
| { scope: ['entity.other.attribute-name'], settings: { foreground: 'var(--color-code-property)' } }, | |
| { scope: ['meta.decorator', 'punctuation.decorator'], settings: { foreground: 'var(--color-code-type)' } }, | |
| { scope: ['markup.inserted', 'punctuation.definition.inserted'], settings: { foreground: 'var(--color-code-inserted)' } }, | |
| { scope: ['markup.deleted', 'punctuation.definition.deleted'], settings: { foreground: 'var(--color-code-deleted)' } }, | |
| { scope: ['markup.heading', 'entity.name.section'], settings: { foreground: 'var(--color-code-function)', fontStyle: 'bold' } }, | |
| { scope: ['markup.bold'], settings: { fontStyle: 'bold' } }, | |
| { scope: ['markup.italic'], settings: { fontStyle: 'italic' } }, | |
| ], | |
| } | |
| const CODE_AREA_PADDING = '0.5rem 12px' | |
| const CODE_LINE_HEIGHT = 1.3 | |
| type ShikiHighlighterProps = { | |
| language: string | |
| theme: typeof warmShikiTheme | |
| engine: unknown | |
| showLineNumbers: boolean | |
| showLanguage: boolean | |
| addDefaultStyles: boolean | |
| style: CSSProperties | |
| children: string | |
| } | |
| type ReactShikiModule = { | |
| ShikiHighlighter: ComponentType<any> | |
| createJavaScriptRegexEngine: (options: { forgiving: boolean }) => unknown | |
| } | |
| type ShikiRuntime = { | |
| Highlighter: ComponentType<ShikiHighlighterProps> | |
| engine: unknown | |
| } | |
| let shikiRuntimePromise: Promise<ShikiRuntime | null> | null = null | |
| function canUseShikiRuntime(): boolean { | |
| if (import.meta.env.MODE === 'test') return false | |
| if (typeof window === 'undefined') return false | |
| try { | |
| new RegExp('(?<name>a)') | |
| new RegExp('(?<=a)b') | |
| } catch { | |
| return false | |
| } | |
| const ua = window.navigator.userAgent | |
| const chromiumLike = /\b(Chrome|Chromium|CriOS|Edg|OPR|Firefox)\b/.test(ua) | |
| const safariVersion = /\bVersion\/(\d+)(?:\.\d+)?\b.*\bSafari\//.exec(ua) | |
| if (!chromiumLike && safariVersion && Number(safariVersion[1]) <= 15) { | |
| return false | |
| } | |
| return true | |
| } | |
| function loadShikiRuntime(): Promise<ShikiRuntime | null> { | |
| if (!canUseShikiRuntime()) return Promise.resolve(null) | |
| shikiRuntimePromise ??= import('react-shiki') | |
| .then((mod) => { | |
| const shiki = mod as unknown as ReactShikiModule | |
| return { | |
| Highlighter: shiki.ShikiHighlighter as ComponentType<ShikiHighlighterProps>, | |
| engine: shiki.createJavaScriptRegexEngine({ forgiving: true }), | |
| } | |
| }) | |
| .catch(() => null) | |
| return shikiRuntimePromise | |
| } | |
| function PrismCodeContent({ code, language, showLineNumbers }: { code: string; language?: string; showLineNumbers: boolean }) { | |
| return ( | |
| <Highlight | |
| theme={warmPrismTheme} | |
| code={code} | |
| language={language || 'text'} | |
| > | |
| {({ tokens, getLineProps, getTokenProps }) => ( | |
| <pre | |
| data-code-viewer-content="" | |
| data-highlight-engine="prism" | |
| style={{ | |
| margin: 0, | |
| padding: CODE_AREA_PADDING, | |
| fontFamily: 'var(--font-mono)', | |
| fontSize: '12px', | |
| lineHeight: String(CODE_LINE_HEIGHT), | |
| whiteSpace: 'pre', | |
| wordBreak: 'normal', | |
| color: 'var(--color-code-fg)', | |
| }} | |
| > | |
| {tokens.map((line, index) => ( | |
| <span | |
| key={index} | |
| {...getLineProps({ line })} | |
| data-line-number={showLineNumbers ? index + 1 : undefined} | |
| > | |
| {showLineNumbers && ( | |
| <span className="mr-3 inline-block min-w-[2.5ch] select-none text-right text-[var(--color-text-tertiary)]"> | |
| {index + 1} | |
| </span> | |
| )} | |
| {line.map((token, key) => ( | |
| <span key={key} {...getTokenProps({ token })} /> | |
| ))} | |
| </span> | |
| ))} | |
| </pre> | |
| )} | |
| </Highlight> | |
| ) | |
| } | |
| function CodeArea({ code, language, showLineNumbers }: { code: string; language?: string; showLineNumbers: boolean }) { | |
| const containerRef = useRef<HTMLDivElement>(null) | |
| const [runtime, setRuntime] = useState<ShikiRuntime | null>(null) | |
| const [loaded, setLoaded] = useState(false) | |
| useEffect(() => { | |
| let cancelled = false | |
| setLoaded(false) | |
| loadShikiRuntime().then((nextRuntime) => { | |
| if (!cancelled) setRuntime(nextRuntime) | |
| }) | |
| return () => { | |
| cancelled = true | |
| } | |
| }, []) | |
| useEffect(() => { | |
| setLoaded(false) | |
| }, [code, language]) | |
| useEffect(() => { | |
| if (!runtime) return | |
| const el = containerRef.current | |
| if (!el) return | |
| const check = () => { | |
| const shikiContainer = el.querySelector('[data-testid="shiki-container"]') | |
| if (shikiContainer?.querySelector('code')) { | |
| setLoaded(true) | |
| } | |
| } | |
| check() | |
| const observer = new MutationObserver(check) | |
| observer.observe(el, { childList: true, subtree: true }) | |
| return () => observer.disconnect() | |
| }, [runtime, code, language]) | |
| const ShikiHighlighter = runtime?.Highlighter | |
| return ( | |
| <div | |
| ref={containerRef} | |
| data-has-line-numbers={showLineNumbers ? 'true' : 'false'} | |
| className="code-viewer-area relative max-h-[420px] overflow-auto bg-[var(--color-code-bg)]" | |
| > | |
| {(!ShikiHighlighter || !loaded) && ( | |
| <PrismCodeContent | |
| code={code} | |
| language={language} | |
| showLineNumbers={showLineNumbers} | |
| /> | |
| )} | |
| {ShikiHighlighter && ( | |
| <div | |
| data-code-viewer-content="" | |
| data-highlight-engine="shiki" | |
| style={ | |
| loaded | |
| ? { padding: CODE_AREA_PADDING } | |
| : { | |
| position: 'absolute', | |
| inset: 0, | |
| opacity: 0, | |
| pointerEvents: 'none', | |
| padding: CODE_AREA_PADDING, | |
| } | |
| } | |
| > | |
| <ShikiHighlighter | |
| language={language || 'text'} | |
| theme={warmShikiTheme} | |
| engine={runtime.engine} | |
| showLineNumbers={showLineNumbers} | |
| showLanguage={false} | |
| addDefaultStyles={false} | |
| style={{ | |
| margin: 0, | |
| fontFamily: 'var(--font-mono)', | |
| fontSize: '12px', | |
| lineHeight: String(CODE_LINE_HEIGHT), | |
| whiteSpace: 'pre', | |
| }} | |
| > | |
| {code} | |
| </ShikiHighlighter> | |
| </div> | |
| )} | |
| </div> | |
| ) | |
| } | |
| export function CodeViewer({ code, language, maxLines = 20, showLineNumbers = false }: Props) { | |
| const [expanded, setExpanded] = useState(false) | |
| const allLines = code.split('\n') | |
| const isTruncated = !expanded && allLines.length > maxLines | |
| const visibleCode = isTruncated ? allLines.slice(0, maxLines).join('\n') : code | |
| const effectiveShowLineNumbers = showLineNumbers && !!language && language !== 'text' | |
| const languageLabel = language || 'code' | |
| const lineCountLabel = `${allLines.length} ${allLines.length === 1 ? 'line' : 'lines'}` | |
| const showExpandToggle = allLines.length > maxLines | |
| return ( | |
| <div className="overflow-hidden rounded-[var(--radius-lg)] border border-[var(--color-outline-variant)]/50 bg-[var(--color-surface-container-low)]"> | |
| {/* Header */} | |
| <div className="flex items-center justify-between border-b border-[var(--color-outline-variant)]/40 bg-[var(--color-surface-container)] px-3 py-1.5 text-[11px] text-[var(--color-text-tertiary)]"> | |
| <div className="flex items-center gap-3"> | |
| <span className="font-semibold uppercase tracking-[0.14em]">{languageLabel}</span> | |
| <span>{lineCountLabel}</span> | |
| </div> | |
| <CopyButton | |
| text={code} | |
| className="rounded-md border border-[var(--color-outline-variant)]/40 bg-[var(--color-surface-container-lowest)] px-2 py-1 text-[11px] text-[var(--color-text-tertiary)] transition-colors hover:bg-[var(--color-surface-container-high)] hover:text-[var(--color-text-primary)]" | |
| /> | |
| </div> | |
| {/* Code area */} | |
| <CodeArea | |
| code={visibleCode} | |
| language={language} | |
| showLineNumbers={effectiveShowLineNumbers} | |
| /> | |
| {/* Expand/collapse toggle */} | |
| {showExpandToggle && ( | |
| <button | |
| onClick={() => setExpanded((value) => !value)} | |
| className="w-full border-t border-[var(--color-outline-variant)]/40 bg-[var(--color-surface-container)] py-1.5 text-[10px] font-semibold uppercase tracking-[0.14em] text-[var(--color-text-tertiary)] transition-colors hover:bg-[var(--color-surface-container-high)] hover:text-[var(--color-text-primary)]" | |
| > | |
| {expanded ? 'Collapse' : `Show ${allLines.length - maxLines} more lines`} | |
| </button> | |
| )} | |
| </div> | |
| ) | |
| } | |