import * as React from 'react' import { memo, useEffect, useMemo, useRef, useState } from 'react' import { useSettings } from '../hooks/useSettings.js' import { Ansi, Box, type DOMElement, measureElement, NoSelect, Text, useTheme } from '@anthropic/ink' import { isFullscreenEnvEnabled } from '../utils/fullscreen.js' import sliceAnsi from '../utils/sliceAnsi.js' import { countCharInString } from '../utils/stringUtils.js' import { HighlightedCodeFallback } from './HighlightedCode/Fallback.js' import { expectColorFile } from './StructuredDiff/colorDiff.js' type Props = { code: string filePath: string width?: number dim?: boolean } const DEFAULT_WIDTH = 80 export const HighlightedCode = memo(function HighlightedCode({ code, filePath, width, dim = false, }: Props): React.ReactElement { const ref = useRef(null) const [measuredWidth, setMeasuredWidth] = useState(width || DEFAULT_WIDTH) const [theme] = useTheme() const settings = useSettings() const syntaxHighlightingDisabled = settings.syntaxHighlightingDisabled ?? false const colorFile = useMemo(() => { if (syntaxHighlightingDisabled) { return null } const ColorFile = expectColorFile() if (!ColorFile) { return null } return new ColorFile(code, filePath) }, [code, filePath, syntaxHighlightingDisabled]) useEffect(() => { if (!width && ref.current) { const { width: elementWidth } = measureElement(ref.current) if (elementWidth > 0) { setMeasuredWidth(elementWidth - 2) } } }, [width]) const lines = useMemo(() => { if (colorFile === null) { return null } return colorFile.render(theme, measuredWidth, dim) }, [colorFile, theme, measuredWidth, dim]) // Gutter width matches ColorFile's layout in lib.rs: space + right-aligned // line number (max_digits = lineCount.toString().length) + space. No marker // column like the diff path. Wrap in so fullscreen selection // yields clean code without line numbers. Only split in fullscreen mode // (~4× DOM nodes + sliceAnsi cost); non-fullscreen uses terminal-native // selection where noSelect is meaningless. const gutterWidth = useMemo(() => { if (!isFullscreenEnvEnabled()) return 0 const lineCount = countCharInString(code, '\n') + 1 return lineCount.toString().length + 2 }, [code]) return ( {lines ? ( {lines.map((line, i) => gutterWidth > 0 ? ( ) : ( {line} ), )} ) : ( )} ) }) function CodeLine({ line, gutterWidth, }: { line: string gutterWidth: number }): React.ReactNode { const gutter = sliceAnsi(line, 0, gutterWidth) const content = sliceAnsi(line, gutterWidth) return ( {gutter} {content} ) }