{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "code-block",
  "title": "Code Block",
  "description": "Readable code without a highlighting library: a small tokenizer for ts, json and bash, line numbers, marked lines, wrap, copy, file tabs and diffs.",
  "dependencies": [
    "lucide-react",
    "motion"
  ],
  "registryDependencies": [
    "utils",
    "https://miseui.junsu4621.workers.dev/r/motion.json",
    "https://miseui.junsu4621.workers.dev/r/clipboard.json"
  ],
  "files": [
    {
      "path": "registry/miseui/ui/code-block.tsx",
      "content": "import type * as React from \"react\"\nimport { useId, useMemo, useState } from \"react\"\nimport { CodeActions, FileTabs } from \"@/registry/miseui/ui/code-block-bar\"\nimport { diffResult, parseDiff } from \"@/registry/miseui/ui/code-block-diff\"\nimport { CodeLines, DiffLines } from \"@/registry/miseui/ui/code-block-lines\"\nimport { tokenize, type CodeLanguage, type CodeToken } from \"@/registry/miseui/ui/code-block-tokens\"\nimport { cn } from \"@/registry/miseui/lib/utils\"\n\ntype CodeBlockVariant = \"plain\" | \"window\" | \"tabs\" | \"diff\"\ntype CodeBlockEffect = \"line-reveal\"\ntype TokenLines = readonly (readonly CodeToken[])[]\n\ninterface CodeBlockFile {\n  name: string\n  code: string\n  language?: CodeLanguage\n  /** Your own highlighter's output, one array per line. Skips the built-in tokenizer. */\n  tokens?: TokenLines\n}\n\ntype CodeBlockProps = Omit<React.ComponentProps<\"div\">, \"title\"> & {\n  /** The source. In `diff` this is a unified diff. Ignored when `files` is set. */\n  code?: string\n  language?: CodeLanguage\n  /** Your own highlighter's output, one array per line. Skips the built-in tokenizer. */\n  tokens?: TokenLines\n  /** `plain` code alone, `window` a title bar, `tabs` several files, `diff` added and removed lines. */\n  variant?: CodeBlockVariant\n  /** `line-reveal` brings lines in one after another on mount. Off unless you ask: code should be readable at once. */\n  effect?: CodeBlockEffect\n  /** File name in the title bar of `window`. */\n  title?: React.ReactNode\n  /** The files of `tabs`. */\n  files?: readonly CodeBlockFile[]\n  lineNumbers?: boolean\n  /** Lines to mark, counted from 1. */\n  highlightLines?: readonly number[]\n  wrap?: boolean\n  defaultWrap?: boolean\n  onWrapChange?: (wrap: boolean) => void\n  /** Name of the code region for assistive tech. */\n  label?: string\n}\n\nconst ROOT_CLASS: Record<CodeBlockVariant, string> = {\n  plain: \"group/code\",\n  window: \"shadow-xs\",\n  tabs: \"\",\n  diff: \"group/code\",\n}\n/** Stays out of the way of the first line until the block is in use; on touch there is no hover, so it just stays. */\nconst FLOATING_ACTIONS_CLASS =\n  \"absolute top-1 right-1 z-10 flex rounded-md bg-card opacity-0 transition-opacity duration-(--duration-fast) ease-(--ease-mise) group-hover/code:opacity-100 group-focus-within/code:opacity-100 pointer-coarse:opacity-100 motion-reduce:transition-none\"\nconst EMPTY_LINES: readonly number[] = []\n\nfunction useCodeSource(source: string, language: CodeLanguage, tokens: TokenLines | undefined, isDiff: boolean) {\n  const diff = useMemo(() => (isDiff ? parseDiff(source) : null), [isDiff, source])\n  const lines = useMemo<TokenLines>(() => {\n    if (tokens) return tokens\n    if (diff) return diff.map((line) => tokenize(line.text, line.kind === \"hunk\" ? \"text\" : language)[0])\n    return tokenize(source.replace(/\\n$/, \"\"), language)\n  }, [tokens, diff, source, language])\n  return { diff, lines, copyValue: diff ? diffResult(diff) : source }\n}\n\nfunction useWrap(controlled: boolean | undefined, initial: boolean, onChange?: (wrap: boolean) => void) {\n  const [state, setState] = useState(initial)\n  const wrap = controlled ?? state\n  const toggle = () => {\n    setState(!wrap)\n    onChange?.(!wrap)\n  }\n  return { wrap, toggle }\n}\n\ninterface CodePanelProps {\n  tabs: boolean\n  baseId: string\n  activeFile: number\n  label: string\n  wrap: boolean\n  children: React.ReactNode\n}\n\n/** Scrolls inside itself, and takes a tab stop so a keyboard can scroll it too. */\nfunction CodePanel({ tabs, baseId, activeFile, label, wrap, children }: CodePanelProps) {\n  return (\n    <div\n      role={tabs ? \"tabpanel\" : \"region\"}\n      id={tabs ? `${baseId}-panel` : undefined}\n      aria-labelledby={tabs ? `${baseId}-tab-${activeFile}` : undefined}\n      aria-label={tabs ? undefined : label}\n      tabIndex={0}\n      className=\"max-h-96 overflow-auto outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:ring-inset\"\n    >\n      <pre className={cn(\"py-3 font-mono text-[13px] leading-5\", wrap ? \"w-full\" : \"w-max min-w-full\")}>\n        <code className=\"block\">{children}</code>\n      </pre>\n    </div>\n  )\n}\n\nfunction CodeBlock({\n  code = \"\", language = \"text\", tokens, variant = \"plain\", effect, title, files, lineNumbers = false, highlightLines = EMPTY_LINES,\n  wrap: wrapProp, defaultWrap = false, onWrapChange, label = \"Code\", className, ...props\n}: CodeBlockProps) {\n  const [activeFile, setActiveFile] = useState(0)\n  const baseId = useId()\n  const { wrap, toggle } = useWrap(wrapProp, defaultWrap, onWrapChange)\n  const tabs = variant === \"tabs\"\n  const file = tabs ? files?.[activeFile] : undefined\n  const { diff, lines, copyValue } = useCodeSource(file?.code ?? code, file?.language ?? language, file?.tokens ?? tokens, variant === \"diff\")\n  const actions = <CodeActions value={copyValue} wrap={wrap} onWrap={toggle} />\n  const shared = { lines, lineNumbers, wrap, reveal: effect === \"line-reveal\" }\n  return (\n    <div\n      data-slot=\"code-block\"\n      data-variant={variant}\n      data-effect={effect}\n      className={cn(\"relative grid min-w-0 grid-cols-[minmax(0,1fr)] overflow-hidden rounded-lg border bg-card text-card-foreground\", ROOT_CLASS[variant], className)}\n      {...props}\n    >\n      {variant === \"window\" ? (\n        <div data-slot=\"code-block-bar\" className=\"flex min-w-0 items-center justify-between gap-2 border-b bg-muted py-1 pr-1 pl-3\">\n          <span className=\"min-w-0 truncate font-mono text-[13px] text-muted-foreground\">{title}</span>\n          {actions}\n        </div>\n      ) : null}\n      {tabs ? (\n        <div data-slot=\"code-block-bar\" className=\"flex min-w-0 items-center gap-2 border-b pr-1\">\n          <FileTabs names={(files ?? []).map(({ name }) => name)} active={activeFile} baseId={baseId} onActive={setActiveFile} />\n          {actions}\n        </div>\n      ) : null}\n      {variant === \"plain\" || variant === \"diff\" ? <div className={FLOATING_ACTIONS_CLASS}>{actions}</div> : null}\n      <CodePanel {...{ tabs, baseId, activeFile, label, wrap }}>\n        {diff ? <DiffLines {...shared} diff={diff} /> : <CodeLines {...shared} highlightLines={highlightLines} />}\n      </CodePanel>\n    </div>\n  )\n}\n\nexport {\n  CodeBlock,\n  tokenize,\n  type CodeBlockProps,\n  type CodeBlockFile,\n  type CodeBlockVariant,\n  type CodeBlockEffect,\n  type CodeLanguage,\n  type CodeToken,\n}\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/miseui/ui/code-block-bar.tsx",
      "content": "import type * as React from \"react\"\nimport { useRef } from \"react\"\nimport { WrapText } from \"lucide-react\"\nimport { Clipboard } from \"@/registry/miseui/ui/clipboard\"\nimport { cn } from \"@/registry/miseui/lib/utils\"\n\nconst ICON_BUTTON_CLASS = [\n  \"inline-flex size-8 shrink-0 items-center justify-center rounded-md text-muted-foreground outline-none pointer-coarse:size-10\",\n  \"transition-colors duration-(--duration-fast) ease-(--ease-mise) hover:bg-foreground/5 hover:text-foreground\",\n  \"focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-pressed:bg-foreground/5 aria-pressed:text-foreground\",\n]\n\nfunction CodeActions({ value, wrap, onWrap }: { value: string; wrap: boolean; onWrap: () => void }) {\n  return (\n    <div className=\"flex shrink-0 items-center\">\n      <button type=\"button\" aria-label=\"Wrap lines\" aria-pressed={wrap} className={cn(ICON_BUTTON_CLASS)} onClick={onWrap}>\n        <WrapText aria-hidden=\"true\" className=\"size-4\" />\n      </button>\n      <Clipboard value={value} label=\"Copy code\" />\n    </div>\n  )\n}\n\ninterface FileTabsProps {\n  names: readonly string[]\n  active: number\n  baseId: string\n  onActive: (index: number) => void\n}\n\nfunction FileTabs({ names, active, baseId, onActive }: FileTabsProps) {\n  const refs = useRef<Array<HTMLButtonElement | null>>([])\n  const onKeyDown = (event: React.KeyboardEvent) => {\n    const last = names.length - 1\n    const targets: Record<string, number> = {\n      ArrowRight: active === last ? 0 : active + 1,\n      ArrowLeft: active === 0 ? last : active - 1,\n      Home: 0,\n      End: last,\n    }\n    const next = targets[event.key]\n    if (next === undefined) return\n    event.preventDefault()\n    onActive(next)\n    refs.current[next]?.focus()\n  }\n  return (\n    <div role=\"tablist\" aria-label=\"Files\" className=\"flex min-w-0 flex-1 overflow-x-auto\" onKeyDown={onKeyDown}>\n      {names.map((name, index) => (\n        <button\n          key={name}\n          ref={(node) => {\n            refs.current[index] = node\n          }}\n          type=\"button\"\n          role=\"tab\"\n          id={`${baseId}-tab-${index}`}\n          aria-selected={index === active}\n          aria-controls={`${baseId}-panel`}\n          tabIndex={index === active ? 0 : -1}\n          onClick={() => onActive(index)}\n          className={cn(\n            \"relative h-10 shrink-0 px-3 font-mono text-[13px] whitespace-nowrap outline-none\",\n            \"focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:ring-inset\",\n            \"transition-colors duration-(--duration-fast) ease-(--ease-mise)\",\n            index === active\n              ? \"text-foreground after:absolute after:inset-x-3 after:bottom-0 after:h-0.5 after:bg-foreground\"\n              : \"text-muted-foreground hover:text-foreground\",\n          )}\n        >\n          {name}\n        </button>\n      ))}\n    </div>\n  )\n}\n\nexport { CodeActions, FileTabs }\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/miseui/ui/code-block-diff.ts",
      "content": "type DiffLineKind = \"added\" | \"removed\" | \"context\" | \"hunk\"\n\ninterface DiffLine {\n  kind: DiffLineKind\n  /** The line without its marker. */\n  text: string\n  oldNumber?: number\n  newNumber?: number\n}\n\nconst HUNK_HEADER = /^@@ -(\\d+)(?:,\\d+)? \\+(\\d+)(?:,\\d+)? @@/\nconst FILE_HEADER = /^(?:---|\\+\\+\\+) /\n\n/** Reads a unified diff. Numbers restart from each @@ header and start at 1 when there is none. */\nfunction parseDiff(source: string): DiffLine[] {\n  const lines: DiffLine[] = []\n  let oldNumber = 1\n  let newNumber = 1\n  for (const raw of source.replace(/\\n$/, \"\").split(\"\\n\")) {\n    const hunk = HUNK_HEADER.exec(raw)\n    if (hunk) {\n      oldNumber = Number(hunk[1])\n      newNumber = Number(hunk[2])\n      lines.push({ kind: \"hunk\", text: raw })\n    } else if (FILE_HEADER.test(raw)) {\n      lines.push({ kind: \"hunk\", text: raw })\n    } else if (raw.startsWith(\"+\")) {\n      lines.push({ kind: \"added\", text: raw.slice(1), newNumber })\n      newNumber += 1\n    } else if (raw.startsWith(\"-\")) {\n      lines.push({ kind: \"removed\", text: raw.slice(1), oldNumber })\n      oldNumber += 1\n    } else {\n      lines.push({ kind: \"context\", text: raw.startsWith(\" \") ? raw.slice(1) : raw, oldNumber, newNumber })\n      oldNumber += 1\n      newNumber += 1\n    }\n  }\n  return lines\n}\n\n/** What the file looks like after the change: the thing people mean to copy from a diff. */\nfunction diffResult(lines: readonly DiffLine[]) {\n  return lines\n    .filter((line) => line.kind === \"added\" || line.kind === \"context\")\n    .map((line) => line.text)\n    .join(\"\\n\")\n}\n\nexport { diffResult, parseDiff, type DiffLine, type DiffLineKind }\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/miseui/ui/code-block-lines.tsx",
      "content": "import type * as React from \"react\"\nimport { motion, useReducedMotion } from \"motion/react\"\nimport { SPRING_DEFAULT } from \"@/registry/miseui/lib/motion\"\nimport type { DiffLine, DiffLineKind } from \"@/registry/miseui/ui/code-block-diff\"\nimport type { CodeToken, CodeTokenType } from \"@/registry/miseui/ui/code-block-tokens\"\nimport { cn } from \"@/registry/miseui/lib/utils\"\n\n/**\n * One accent and tone instead of a rainbow, because the accent is the only hue a theme guarantees. Mixing it into\n * the foreground keeps keywords readable on the surface in both modes, whichever accent the palette picked.\n */\nconst ACCENT_INK = \"text-[color-mix(in_oklab,var(--mise)_55%,var(--foreground))]\"\nconst TOKEN_CLASS: Record<CodeTokenType, string> = {\n  plain: \"\",\n  keyword: ACCENT_INK,\n  string: \"text-foreground/70\",\n  number: ACCENT_INK,\n  comment: \"text-muted-foreground italic\",\n  function: \"font-medium\",\n  type: \"font-medium\",\n  property: \"text-foreground/80\",\n  punctuation: \"text-muted-foreground\",\n  variable: ACCENT_INK,\n}\n\n/** Fixed hues: added and removed are status, and the accent may itself be green or red. */\nconst DIFF_LINE_CLASS: Record<DiffLineKind, string> = {\n  added: \"bg-emerald-500/10 dark:bg-emerald-400/10\",\n  removed: \"bg-red-500/10 dark:bg-red-400/10\",\n  context: \"\",\n  hunk: \"bg-muted text-muted-foreground\",\n}\nconst DIFF_SIGN_CLASS: Record<DiffLineKind, string> = {\n  added: \"text-emerald-700 dark:text-emerald-400\",\n  removed: \"text-red-700 dark:text-red-400\",\n  context: \"text-transparent\",\n  hunk: \"text-transparent\",\n}\nconst DIFF_SIGN: Record<DiffLineKind, string> = { added: \"+\", removed: \"-\", context: \" \", hunk: \" \" }\nconst DIFF_SPOKEN: Partial<Record<DiffLineKind, string>> = { added: \"Added: \", removed: \"Removed: \" }\n\nconst LINE_CLASS = \"flex min-w-full px-3\"\nconst GUTTER_CLASS = \"shrink-0 pr-3 text-right text-muted-foreground tabular-nums select-none\"\n/** Beyond this many lines the stagger would make people wait for code they could already read. */\nconst REVEAL_LINE_LIMIT = 24\nconst REVEAL_STEP_S = 0.025\n\nfunction Tokens({ tokens }: { tokens: readonly CodeToken[] }) {\n  if (tokens.length === 0) return \"​\"\n  return tokens.map((token, index) =>\n    token.type === \"plain\" ? (\n      <span key={index}>{token.text}</span>\n    ) : (\n      <span key={index} data-token={token.type} className={TOKEN_CLASS[token.type]}>\n        {token.text}\n      </span>\n    ),\n  )\n}\n\ninterface LineShellProps {\n  index: number\n  reveal: boolean\n  className?: string\n  children: React.ReactNode\n  highlighted?: boolean\n  kind?: DiffLineKind\n}\n\nfunction LineShell({ index, reveal, className, children, highlighted, kind }: LineShellProps) {\n  const reduceMotion = useReducedMotion() ?? false\n  const shared = {\n    \"data-slot\": \"code-block-line\",\n    \"data-highlighted\": highlighted ? \"true\" : undefined,\n    \"data-kind\": kind,\n    className: cn(LINE_CLASS, highlighted && \"bg-mise-soft\", className),\n  }\n  if (!reveal || reduceMotion || index >= REVEAL_LINE_LIMIT) return <div {...shared}>{children}</div>\n  return (\n    <motion.div\n      {...shared}\n      initial={{ opacity: 0, y: 4 }}\n      animate={{ opacity: 1, y: 0 }}\n      transition={{ ...SPRING_DEFAULT, delay: index * REVEAL_STEP_S }}\n    >\n      {children}\n    </motion.div>\n  )\n}\n\ninterface CodeLinesProps {\n  lines: readonly (readonly CodeToken[])[]\n  lineNumbers: boolean\n  highlightLines: readonly number[]\n  wrap: boolean\n  reveal: boolean\n}\n\nfunction CodeLines({ lines, lineNumbers, highlightLines, wrap, reveal }: CodeLinesProps) {\n  const gutterWidth = `${String(lines.length).length}ch`\n  return lines.map((tokens, index) => (\n    <LineShell key={index} index={index} reveal={reveal} highlighted={highlightLines.includes(index + 1)}>\n      {lineNumbers ? (\n        <span aria-hidden=\"true\" className={GUTTER_CLASS} style={{ boxSizing: \"content-box\", width: gutterWidth }}>\n          {index + 1}\n        </span>\n      ) : null}\n      <span className={wrap ? \"min-w-0 wrap-anywhere whitespace-pre-wrap\" : \"whitespace-pre\"}>\n        <Tokens tokens={tokens} />\n      </span>\n    </LineShell>\n  ))\n}\n\ninterface DiffLinesProps {\n  diff: readonly DiffLine[]\n  lines: readonly (readonly CodeToken[])[]\n  lineNumbers: boolean\n  wrap: boolean\n  reveal: boolean\n}\n\nfunction DiffLines({ diff, lines, lineNumbers, wrap, reveal }: DiffLinesProps) {\n  const highest = diff.reduce((top, line) => Math.max(top, line.oldNumber ?? 0, line.newNumber ?? 0), 0)\n  const gutter = { boxSizing: \"content-box\", width: `${String(highest).length}ch` } as const\n  return diff.map((line, index) => (\n    <LineShell key={index} index={index} reveal={reveal} kind={line.kind} className={DIFF_LINE_CLASS[line.kind]}>\n      {lineNumbers ? (\n        <>\n          <span aria-hidden=\"true\" className={GUTTER_CLASS} style={gutter}>\n            {line.oldNumber}\n          </span>\n          <span aria-hidden=\"true\" className={GUTTER_CLASS} style={gutter}>\n            {line.newNumber}\n          </span>\n        </>\n      ) : null}\n      <span aria-hidden=\"true\" className={cn(\"w-[2ch] shrink-0 select-none\", DIFF_SIGN_CLASS[line.kind])}>\n        {DIFF_SIGN[line.kind]}\n      </span>\n      <span className={wrap ? \"min-w-0 wrap-anywhere whitespace-pre-wrap\" : \"whitespace-pre\"}>\n        {DIFF_SPOKEN[line.kind] ? <span className=\"sr-only\">{DIFF_SPOKEN[line.kind]}</span> : null}\n        <Tokens tokens={lines[index] ?? []} />\n      </span>\n    </LineShell>\n  ))\n}\n\nexport { CodeLines, DiffLines }\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/miseui/ui/code-block-tokens.ts",
      "content": "type CodeLanguage = \"ts\" | \"json\" | \"bash\" | \"text\"\r\n\r\ntype CodeTokenType =\r\n  | \"plain\"\r\n  | \"keyword\"\r\n  | \"string\"\r\n  | \"number\"\r\n  | \"comment\"\r\n  | \"function\"\r\n  | \"type\"\r\n  | \"property\"\r\n  | \"punctuation\"\r\n  | \"variable\"\r\n\r\ninterface CodeToken {\r\n  type: CodeTokenType\r\n  text: string\r\n}\r\n\r\ninterface Rule {\r\n  type: CodeTokenType | ((match: string, rest: string, before: string) => CodeTokenType)\r\n  pattern: RegExp\r\n}\r\n\r\nconst TS_KEYWORDS = new Set(\r\n  \"abstract as async await break case catch class const continue debugger default delete do else enum export extends false finally for from function get if implements import in instanceof interface let new null of private protected public readonly return satisfies set static super switch this throw true try type typeof undefined var void while yield\".split(\" \"),\r\n)\r\n\r\nconst BASH_KEYWORDS = new Set(\"case do done elif else esac fi for function if in then until while\".split(\" \"))\r\n\r\n/** Every pattern is sticky and free of nested quantifiers, so a hostile paste cannot make the scanner backtrack for long. */\r\nconst TS_RULES: readonly Rule[] = [\r\n  { type: \"comment\", pattern: /\\/\\/[^\\n]*/y },\r\n  { type: \"comment\", pattern: /\\/\\*[\\s\\S]*?(?:\\*\\/|$)/y },\r\n  { type: \"string\", pattern: /\"(?:[^\"\\\\\\n]|\\\\.)*\"?/y },\r\n  { type: \"string\", pattern: /'(?:[^'\\\\\\n]|\\\\.)*'?/y },\r\n  { type: \"string\", pattern: /`(?:[^`\\\\]|\\\\[\\s\\S])*`?/y },\r\n  { type: \"number\", pattern: /\\b(?:0[xX][\\da-fA-F_]+|\\d[\\d_]*(?:\\.\\d+)?(?:[eE][+-]?\\d+)?n?)\\b/y },\r\n  { type: classifyTsWord, pattern: /[A-Za-z_$][\\w$]*/y },\r\n  { type: \"punctuation\", pattern: /[{}()[\\];:,.<>=+\\-*/%&|^!?~@]+/y },\r\n]\r\n\r\nconst JSON_RULES: readonly Rule[] = [\r\n  { type: (_match, rest) => (/^\\s*:/.test(rest) ? \"property\" : \"string\"), pattern: /\"(?:[^\"\\\\\\n]|\\\\.)*\"?/y },\r\n  { type: \"number\", pattern: /-?\\d+(?:\\.\\d+)?(?:[eE][+-]?\\d+)?/y },\r\n  { type: \"keyword\", pattern: /\\b(?:true|false|null)\\b/y },\r\n  { type: \"punctuation\", pattern: /[{}[\\]:,]/y },\r\n]\r\n\r\nconst BASH_RULES: readonly Rule[] = [\r\n  { type: \"comment\", pattern: /(?<![^\\s;|&])#[^\\n]*/y },\r\n  { type: \"string\", pattern: /\"(?:[^\"\\\\]|\\\\[\\s\\S])*\"?/y },\r\n  { type: \"string\", pattern: /'[^']*'?/y },\r\n  { type: \"variable\", pattern: /\\$(?:\\{[^}\\n]*\\}?|[A-Za-z_]\\w*|[\\d@#?*!$-])/y },\r\n  { type: \"property\", pattern: /(?<![^\\s])--?[A-Za-z][\\w-]*/y },\r\n  { type: \"number\", pattern: /(?<![^\\s=])\\d+(?:\\.\\d+)?(?![^\\s;|&)])/y },\r\n  { type: classifyBashWord, pattern: /[A-Za-z_./~][\\w./~@+:-]*/y },\r\n  { type: \"punctuation\", pattern: /[|&;()<>=]+/y },\r\n]\r\n\r\nconst RULES: Record<CodeLanguage, readonly Rule[]> = { ts: TS_RULES, json: JSON_RULES, bash: BASH_RULES, text: [] }\r\n\r\nfunction classifyTsWord(match: string, rest: string): CodeTokenType {\r\n  if (TS_KEYWORDS.has(match)) return \"keyword\"\r\n  if (/^\\s*\\(/.test(rest) || /^<[A-Za-z][\\w, ]*>\\(/.test(rest)) return \"function\"\r\n  if (/^[A-Z]/.test(match)) return \"type\"\r\n  if (/^\\??:(?!:)/.test(rest)) return \"property\"\r\n  return \"plain\"\r\n}\r\n\r\n/** A word is the command when nothing but a separator or an assignment comes before it on the line. */\r\nfunction classifyBashWord(match: string, rest: string, before: string): CodeTokenType {\r\n  if (BASH_KEYWORDS.has(match)) return \"keyword\"\r\n  if (rest.startsWith(\"=\")) return \"variable\"\r\n  const line = before.slice(before.lastIndexOf(\"\\n\") + 1)\r\n  const segment = line.split(/[|;&(]/).at(-1) ?? \"\"\r\n  const commandPosition = /^\\s*(?:[A-Za-z_]\\w*=\\S*\\s+)*(?:(?:then|do|else|sudo)\\s+)?$/.test(segment)\r\n  return commandPosition ? \"function\" : \"plain\"\r\n}\r\n\r\nfunction resolveType(rule: Rule, match: string, code: string, end: number, start: number): CodeTokenType {\r\n  if (typeof rule.type === \"string\") return rule.type\r\n  return rule.type(match, code.slice(end, end + 64), code.slice(Math.max(0, start - 256), start))\r\n}\r\n\r\nfunction matchAt(code: string, index: number, rules: readonly Rule[]) {\r\n  for (const rule of rules) {\r\n    rule.pattern.lastIndex = index\r\n    const found = rule.pattern.exec(code)\r\n    if (found && found[0].length > 0) return { rule, text: found[0] }\r\n  }\r\n  return null\r\n}\r\n\r\n/** Flat list, unrecognised characters collected into plain runs. Joining every text gives back the input exactly. */\r\nfunction scan(code: string, rules: readonly Rule[]): CodeToken[] {\r\n  const tokens: CodeToken[] = []\r\n  let index = 0\r\n  let plainFrom = 0\r\n  const flushPlain = (upTo: number) => {\r\n    if (upTo > plainFrom) tokens.push({ type: \"plain\", text: code.slice(plainFrom, upTo) })\r\n  }\r\n  while (index < code.length) {\r\n    const hit = matchAt(code, index, rules)\r\n    if (!hit) {\r\n      index += 1\r\n      continue\r\n    }\r\n    const end = index + hit.text.length\r\n    flushPlain(index)\r\n    tokens.push({ type: resolveType(hit.rule, hit.text, code, end, index), text: hit.text })\r\n    index = end\r\n    plainFrom = end\r\n  }\r\n  flushPlain(code.length)\r\n  return tokens\r\n}\r\n\r\n/**\r\n * One array per line. A token that spans lines, like a block comment, is cut at each newline and keeps its type.\r\n * Fills local arrays on purpose: copying them per token took 9 s on a 20,000 word line.\r\n */\r\nfunction toLines(tokens: readonly CodeToken[]): CodeToken[][] {\r\n  const lines: CodeToken[][] = [[]]\r\n  for (const token of tokens) {\r\n    token.text.split(\"\\n\").forEach((text, index) => {\r\n      if (index > 0) lines.push([])\r\n      if (text) lines[lines.length - 1].push({ type: token.type, text })\r\n    })\r\n  }\r\n  return lines\r\n}\r\n\r\n/** Good enough to read, not a parser: regex literals and JSX come out plain, and nothing is ever lost or reordered. */\r\nfunction tokenize(code: string, language: CodeLanguage = \"text\"): CodeToken[][] {\r\n  return toLines(scan(code, RULES[language]))\r\n}\r\n\r\nexport { tokenize, type CodeLanguage, type CodeToken, type CodeTokenType }\r\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "display"
  ],
  "type": "registry:ui"
}