import { type PropsWithChildren } from 'react' import { type BundledLanguage, codeToTokens, type ThemedToken } from 'shiki' import { createTwoslasher, type ExtraFiles, type NodeHover } from 'twoslash' import { cn } from 'ui' import { AnnotatedSpan, CodeCopyButton } from './CodeBlock.client' import denoTypes from './types/lib.deno.d.ts.include' const extraFiles: ExtraFiles = { 'deno.d.ts': denoTypes } const twoslasher = createTwoslasher({ extraFiles }) const TWOSLASHABLE_LANGS: ReadonlyArray = ['js', 'ts', 'javascript', 'typescript'] export async function CodeBlock({ className, lang: langSetting, lineNumbers = true, contents, children, }: PropsWithChildren<{ className?: string lang?: string lineNumbers?: boolean contents?: string }>) { let code = (contents || extractCode(children)).trim() const lang = tryToBundledLanguage(langSetting) || extractLang(children) let twoslashed = null as null | Map>> if (TWOSLASHABLE_LANGS.includes(lang)) { try { const { code: editedCode, nodes } = twoslasher(code) const hoverNodes: Array = nodes.filter((node) => node.type === 'hover') twoslashed = annotationsByLine(hoverNodes) code = editedCode } catch (_err) { // Silently ignore, if imports aren't defined type compilation fails // Uncomment lines below to debug in dev // console.log('\n==========CODE==========\n') // console.log(code) // console.error(_err.recommendation) } } const { tokens } = await codeToTokens(code, { lang, themes: { light: 'vitesse-light', dark: 'vitesse-dark', }, }) return (
        
          {lineNumbers && (
            
{tokens.map((_, idx) => (
{idx + 1}
))}
)}
{tokens.map((line, idx) => ( ))}
) } function CodeLine({ tokens: rawTokens, twoslash, }: { tokens: Array twoslash?: Map> }) { let offset = 0 const tokens = rawTokens.map((token) => { const newToken = { ...token, offset } offset += token.content.length return newToken }) return ( {tokens.map((token) => twoslash?.has(token.offset) ? ( ) : ( {token.content} ) )} ) } function extractCode(children: React.ReactNode): string { if (typeof children === 'string') return children const child = Array.isArray(children) ? children[0] : children if (!!child && typeof child === 'object' && 'props' in child) { const props = child.props if (!!props && typeof props === 'object' && 'children' in props) { const code = props.children if (typeof code === 'string') return code } } return '' } function extractLang(children: React.ReactNode): BundledLanguage | null { if (typeof children === 'string') return null const child = Array.isArray(children) ? children[0] : children if (!!child && typeof child === 'object' && 'props' in child) { const props = child.props if (!!props && typeof props === 'object' && 'className' in props) { const className = props.className if (typeof className === 'string') { const lang = className.split(' ').find((className) => className.startsWith('language-')) return lang ? tryToBundledLanguage(lang.replace('language-', '')) : null } } } return null } function annotationsByLine(nodes: Array): Map>> { const result = new Map() nodes.forEach((node) => { const line = node.line const char = node.character if (!result.has(line)) { result.set(line, new Map()) } if (!result.get(line).has(char)) { result.get(line).set(char, []) } result.get(line).get(char).push(node) }) return result } function tryToBundledLanguage(lang: string): BundledLanguage | null { if (BUNDLED_LANGUAGES.includes(lang)) { return lang as BundledLanguage } return null } const BUNDLED_LANGUAGES = [ 'abap', 'actionscript-3', 'ada', 'adoc', 'angular-html', 'angular-ts', 'apache', 'apex', 'apl', 'applescript', 'ara', 'asciidoc', 'asm', 'astro', 'awk', 'ballerina', 'bash', 'bat', 'batch', 'be', 'beancount', 'berry', 'bibtex', 'bicep', 'blade', 'c', 'c#', 'c++', 'cadence', 'cdc', 'clarity', 'clj', 'clojure', 'closure-templates', 'cmake', 'cmd', 'cobol', 'codeowners', 'codeql', 'coffee', 'coffeescript', 'common-lisp', 'console', 'cpp', 'cql', 'crystal', 'cs', 'csharp', 'css', 'csv', 'cue', 'cypher', 'd', 'dart', 'dax', 'desktop', 'diff', 'docker', 'dockerfile', 'dream-maker', 'elisp', 'elixir', 'elm', 'emacs-lisp', 'erb', 'erl', 'erlang', 'f', 'f#', 'f03', 'f08', 'f18', 'f77', 'f90', 'f95', 'fennel', 'fish', 'fluent', 'for', 'fortran-fixed-form', 'fortran-free-form', 'fs', 'fsharp', 'fsl', 'ftl', 'gdresource', 'gdscript', 'gdshader', 'genie', 'gherkin', 'git-commit', 'git-rebase', 'gjs', 'gleam', 'glimmer-js', 'glimmer-ts', 'glsl', 'gnuplot', 'go', 'gql', 'graphql', 'groovy', 'gts', 'hack', 'haml', 'handlebars', 'haskell', 'haxe', 'hbs', 'hcl', 'hjson', 'hlsl', 'hs', 'html', 'html-derivative', 'http', 'hxml', 'hy', 'imba', 'ini', 'jade', 'java', 'javascript', 'jinja', 'jison', 'jl', 'js', 'json', 'json5', 'jsonc', 'jsonl', 'jsonnet', 'jssm', 'jsx', 'julia', 'kotlin', 'kql', 'kt', 'kts', 'kusto', 'latex', 'less', 'liquid', 'lisp', 'log', 'logo', 'lua', 'make', 'makefile', 'markdown', 'marko', 'matlab', 'md', 'mdc', 'mdx', 'mediawiki', 'mermaid', 'mojo', 'move', 'nar', 'narrat', 'nextflow', 'nf', 'nginx', 'nim', 'nix', 'nu', 'nushell', 'objc', 'objective-c', 'objective-cpp', 'ocaml', 'pascal', 'perl', 'perl6', 'php', 'plsql', 'po', 'postcss', 'pot', 'potx', 'powerquery', 'powershell', 'prisma', 'prolog', 'properties', 'proto', 'ps', 'ps1', 'pug', 'puppet', 'purescript', 'py', 'python', 'ql', 'qml', 'qmldir', 'qss', 'r', 'racket', 'raku', 'razor', 'rb', 'reg', 'regex', 'regexp', 'rel', 'riscv', 'rs', 'rst', 'ruby', 'rust', 'sas', 'sass', 'scala', 'scheme', 'scss', 'sh', 'shader', 'shaderlab', 'shell', 'shellscript', 'shellsession', 'smalltalk', 'solidity', 'soy', 'sparql', 'spl', 'splunk', 'sql', 'ssh-config', 'stata', 'styl', 'stylus', 'svelte', 'swift', 'system-verilog', 'systemd', 'tasl', 'tcl', 'terraform', 'tex', 'tf', 'tfvars', 'toml', 'ts', 'tsp', 'tsv', 'tsx', 'turtle', 'twig', 'typ', 'typescript', 'typespec', 'typst', 'v', 'vala', 'vb', 'verilog', 'vhdl', 'vim', 'viml', 'vimscript', 'vue', 'vue-html', 'vy', 'vyper', 'wasm', 'wenyan', 'wgsl', 'wiki', 'wikitext', 'wl', 'wolfram', 'xml', 'xsl', 'yaml', 'yml', 'zenscript', 'zig', 'zsh', '文言', ]