import React from 'react'; import { View, ScrollView, Text, Platform, ColorValue, TextStyle, Pressable, TouchableOpacity, } from 'react-native'; import Highlighter, { SyntaxHighlighterProps as HighlighterProps } from 'react-syntax-highlighter'; import * as HLJSSyntaxStyles from 'react-syntax-highlighter/dist/esm/styles/hljs'; type Node = { children?: Node[]; properties?: { className: string[]; }; tagName?: string; type: string; value?: string; }; type StyleSheet = { [key: string]: TextStyle & { background?: string; }; }; type RendererParams = { rows: Node[]; stylesheet: StyleSheet; }; export type SyntaxHighlighterStyleType = { /** * Default is Menlo-Regular (iOS) and Monospace (Android). */ fontFamily?: string; /** * Default is 16. */ fontSize?: number; /** * Override the syntax style background. */ backgroundColor?: ColorValue; /** * Default is 16. */ padding?: number; /** * Text color of the line numbers. */ lineNumbersColor?: ColorValue; /** * Background color of the line numbers. */ lineNumbersBackgroundColor?: ColorValue; /** * Use this property to align the syntax highlighter text with the text input. */ highlighterLineHeight?: number; /** * Use this property to help you align the syntax highlighter text with the text input. * Do not use in production. */ highlighterColor?: ColorValue; }; export const SyntaxHighlighterSyntaxStyles = HLJSSyntaxStyles; export type SyntaxHighlighterProps = HighlighterProps & { /** * Code to display. */ children: string; /** * Syntax highlighting style. * @See https://github.com/react-syntax-highlighter/react-syntax-highlighter/blob/master/AVAILABLE_STYLES_HLJS.MD */ syntaxStyle?: typeof SyntaxHighlighterSyntaxStyles; /** * Extra styling options for the syntax highlighter. */ addedStyle?: SyntaxHighlighterStyleType; /** * Whether to allow scrolling on the syntax highlighter. */ scrollEnabled?: boolean; }; type PropsWithForwardRef = SyntaxHighlighterProps & { forwardedRef: React.Ref; }; const SyntaxHighlighter = (props: PropsWithForwardRef): JSX.Element => { const { syntaxStyle = SyntaxHighlighterSyntaxStyles.atomOneDark, addedStyle, scrollEnabled, showLineNumbers = false, forwardedRef, ...highlighterProps } = props; // Default values const { fontFamily = Platform.OS === 'ios' ? 'Menlo-Regular' : 'monospace', fontSize = 16, backgroundColor = undefined, padding = 16, lineNumbersColor = 'rgba(127, 127, 127, 0.9)', lineNumbersBackgroundColor = undefined, highlighterLineHeight = undefined, highlighterColor = undefined, } = addedStyle || {}; // Only when line numbers are showing const lineNumbersPadding = showLineNumbers ? 1.75 * fontSize : undefined; const lineNumbersFontSize = 0.7 * fontSize; // Prevents the last line from clipping when scrolling highlighterProps.children += '\n\n'; const cleanStyle = (style: TextStyle) => { const clean: TextStyle = { ...style, display: undefined, }; return clean; }; const stylesheet: StyleSheet = Object.fromEntries( Object.entries(syntaxStyle as StyleSheet).map(([className, style]) => [ className, cleanStyle(style), ]) ); const renderLineNumbersBackground = () => ( ); let numberOfChars = 0; let isMarkup = false; const renderNode = (nodes: Node[], key = '0') => { return nodes.reduce((acc, node, index) => { if (node.children) { const textElement = ( stylesheet[c]), { lineHeight: highlighterLineHeight, fontFamily, fontSize, paddingLeft: lineNumbersPadding ?? padding, }, ]} > {renderNode(node.children, `${key}.${index}`)} ); const lineNumberElement = key !== '0' || index >= nodes.length - 2 ? undefined : ( {index + 1} ); acc.push( showLineNumbers && lineNumberElement ? ( {lineNumberElement} {textElement} ) : ( textElement ) ); } if (node.value) { const isMarkup = node.value.includes('⁂'); numberOfChars += node.value.split('⁂').length - 1; // To prevent an empty line after each string node.value = node.value.replace('\n', ''); node.value = node.value.replace(/⁂/g, ''); // To render blank lines at an equal font height node.value = node.value.length ? node.value : ''; if (isMarkup) { acc.push( {node.value} ); } else { if (numberOfChars % 2 !== 0) { acc.push( {node.value} ); } else { acc.push(node.value); } } } return acc; }, []); }; const nativeRenderer = ({ rows, ...rest }: RendererParams) => { return ( {showLineNumbers && renderLineNumbersBackground()} {renderNode(rows)} ); }; return ( ); }; const SyntaxHighlighterWithForwardRef = React.forwardRef( (props, ref) => ); export default SyntaxHighlighterWithForwardRef;