diff --git a/beta/src/components/MDX/CodeBlock/CodeBlock.tsx b/beta/src/components/MDX/CodeBlock/CodeBlock.tsx index e9ba2452..0b44734a 100644 --- a/beta/src/components/MDX/CodeBlock/CodeBlock.tsx +++ b/beta/src/components/MDX/CodeBlock/CodeBlock.tsx @@ -3,10 +3,9 @@ */ import cn from 'classnames'; -import { - SandpackCodeViewer, - SandpackProvider, -} from '@codesandbox/sandpack-react'; +import {highlightTree} from '@codemirror/highlight'; +import {javascript} from '@codemirror/lang-javascript'; +import {HighlightStyle, tags} from '@codemirror/highlight'; import rangeParser from 'parse-numeric-range'; import {CustomTheme} from '../Sandpack/Themes'; @@ -33,84 +32,275 @@ const CodeBlock = function CodeBlock({ className?: string; noMargin?: boolean; }) { - const getDecoratedLineInfo = () => { - if (!meta) { - return []; + code = code.trimEnd(); + const tree = language.language.parser.parse(code); + let tokenStarts = new Map(); + let tokenEnds = new Map(); + const highlightTheme = getSyntaxHighlight(CustomTheme); + highlightTree(tree, highlightTheme.match, (from, to, className) => { + tokenStarts.set(from, className); + tokenEnds.set(to, className); + }); + + const highlightedLines = new Map(); + const lines = code.split('\n'); + const lineDecorators = getLineDecorators(code, meta); + for (let decorator of lineDecorators) { + highlightedLines.set(decorator.line - 1, decorator.className); + } + + const inlineDecorators = getInlineDecorators(code, meta); + const decoratorStarts = new Map(); + const decoratorEnds = new Map(); + for (let decorator of inlineDecorators) { + // Find where inline highlight starts and ends. + let decoratorStart = 0; + for (let i = 0; i < decorator.line - 1; i++) { + decoratorStart += lines[i].length + 1; + } + decoratorStart += decorator.startColumn; + const decoratorEnd = + decoratorStart + (decorator.endColumn - decorator.startColumn); + if (decoratorStarts.has(decoratorStart)) { + throw Error('Already opened decorator at ' + decoratorStart); + } + decoratorStarts.set(decoratorStart, decorator.className); + if (decoratorEnds.has(decoratorEnd)) { + throw Error('Already closed decorator at ' + decoratorEnd); } + decoratorEnds.set(decoratorEnd, decorator.className); + } - const linesToHighlight = getHighlightLines(meta); - const highlightedLineConfig = linesToHighlight.map((line) => { - return { - className: 'bg-github-highlight dark:bg-opacity-10', - line, - }; - }); - - const inlineHighlightLines = getInlineHighlights(meta, code); - const inlineHighlightConfig = inlineHighlightLines.map( - (line: InlineHiglight) => ({ - ...line, - elementAttributes: {'data-step': `${line.step}`}, - className: cn( - 'code-step bg-opacity-10 dark:bg-opacity-20 relative rounded px-1 py-[1.5px] border-b-[2px] border-opacity-60', - { - 'bg-blue-40 border-blue-40 text-blue-60 dark:text-blue-30 font-bold': - line.step === 1, - 'bg-yellow-40 border-yellow-40 text-yellow-60 dark:text-yellow-30 font-bold': - line.step === 2, - 'bg-purple-40 border-purple-40 text-purple-60 dark:text-purple-30 font-bold': - line.step === 3, - 'bg-green-40 border-green-40 text-green-60 dark:text-green-30 font-bold': - line.step === 4, - } - ), - }) - ); - - return highlightedLineConfig.concat(inlineHighlightConfig); - }; + // Produce output based on tokens and decorators. + // We assume tokens never overlap other tokens, and + // decorators never overlap with other decorators. + // However, tokens and decorators may mutually overlap. + // In that case, decorators always take precedence. + let currentDecorator = null; + let currentToken = null; + let buffer = ''; + let lineIndex = 0; + let lineOutput = []; + let finalOutput = []; + for (let i = 0; i < code.length; i++) { + if (tokenEnds.has(i)) { + if (!currentToken) { + throw Error('Cannot close token at ' + i + ' because it was not open.'); + } + if (!currentDecorator) { + lineOutput.push( + + {buffer} + + ); + buffer = ''; + } + currentToken = null; + } + if (decoratorEnds.has(i)) { + if (!currentDecorator) { + throw Error( + 'Cannot close decorator at ' + i + ' because it was not open.' + ); + } + lineOutput.push( + + {buffer} + + ); + buffer = ''; + currentDecorator = null; + } + if (decoratorStarts.has(i)) { + if (currentDecorator) { + throw Error( + 'Cannot open decorator at ' + i + ' before closing last one.' + ); + } + if (currentToken) { + lineOutput.push( + + {buffer} + + ); + buffer = ''; + } else { + lineOutput.push(buffer); + buffer = ''; + } + currentDecorator = decoratorStarts.get(i); + } + if (tokenStarts.has(i)) { + if (currentToken) { + throw Error('Cannot open token at ' + i + ' before closing last one.'); + } + currentToken = tokenStarts.get(i); + if (!currentDecorator) { + lineOutput.push(buffer); + buffer = ''; + } + } + if (code[i] === '\n') { + lineOutput.push(buffer); + buffer = ''; + finalOutput.push( +
+
+ {finalOutput}
+
+
+