{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "codemirror-editor",
  "title": "CodeMirror Editor",
  "description": "A CodeMirror 6 editor component for notebook cells with syntax highlighting, key bindings, and language support for Python, IPython (with magics and shell commands), Markdown, SQL, HTML, JavaScript, TypeScript, and JSON.",
  "dependencies": [
    "@codemirror/view",
    "@codemirror/state",
    "@codemirror/commands",
    "@codemirror/autocomplete",
    "@codemirror/language",
    "@codemirror/lint",
    "@codemirror/lang-python",
    "@codemirror/lang-markdown",
    "@codemirror/lang-sql",
    "@codemirror/lang-html",
    "@codemirror/lang-javascript",
    "@codemirror/lang-json",
    "@uiw/react-codemirror",
    "@uiw/codemirror-theme-github"
  ],
  "files": [
    {
      "path": "registry/editor/codemirror-editor.tsx",
      "content": "\"use client\";\n\nimport {\n  EditorView,\n  type KeyBinding,\n  keymap,\n  placeholder as placeholderExt,\n} from \"@codemirror/view\";\nimport { type Extension, useCodeMirror } from \"@uiw/react-codemirror\";\nimport {\n  forwardRef,\n  useCallback,\n  useEffect,\n  useImperativeHandle,\n  useMemo,\n  useRef,\n  useState,\n} from \"react\";\n\nimport { cn } from \"@/lib/utils\";\nimport { defaultExtensions } from \"./extensions\";\nimport {\n  getIPythonExtension,\n  getLanguageExtension,\n  type SupportedLanguage,\n} from \"./languages\";\nimport { darkTheme, isDarkMode, lightTheme, type ThemeMode } from \"./themes\";\n\nexport interface CodeMirrorEditorRef {\n  /** Focus the editor */\n  focus: () => void;\n  /** Set cursor position to start or end of document */\n  setCursorPosition: (position: \"start\" | \"end\") => void;\n  /** Get the underlying EditorView instance */\n  getEditor: () => EditorView | null;\n}\n\nexport interface CodeMirrorEditorProps {\n  /** Current editor content */\n  value: string;\n  /** Language for syntax highlighting */\n  language?: SupportedLanguage;\n  /** Callback when content changes */\n  onValueChange?: (value: string) => void;\n  /** Auto-focus on mount */\n  autoFocus?: boolean;\n  /** Callback when editor gains focus */\n  onFocus?: () => void;\n  /** Callback when editor loses focus */\n  onBlur?: () => void;\n  /** Placeholder text when empty */\n  placeholder?: string;\n  /** Additional key bindings */\n  keyMap?: KeyBinding[];\n  /** Additional CSS classes */\n  className?: string;\n  /** Maximum height (CSS value) */\n  maxHeight?: string;\n  /** Enable line wrapping */\n  lineWrapping?: boolean;\n  /** Additional CodeMirror extensions */\n  extensions?: Extension[];\n  /** Replace default extensions entirely */\n  baseExtensions?: Extension[];\n  /** Read-only mode */\n  readOnly?: boolean;\n  /** Theme mode: \"light\", \"dark\", or \"auto\" (default) */\n  theme?: ThemeMode;\n}\n\n/**\n * CodeMirror 6 editor component for notebook cells\n *\n * Provides syntax highlighting, key bindings, and a clean API\n * for integration with notebook cell components.\n *\n * @example\n * ```tsx\n * <CodeMirrorEditor\n *   value={source}\n *   language=\"python\"\n *   onValueChange={setSource}\n *   placeholder=\"Enter code...\"\n * />\n * ```\n */\nexport const CodeMirrorEditor = forwardRef<\n  CodeMirrorEditorRef,\n  CodeMirrorEditorProps\n>(\n  (\n    {\n      value,\n      language = \"python\",\n      onValueChange,\n      autoFocus = false,\n      onFocus,\n      onBlur,\n      placeholder,\n      keyMap,\n      className,\n      maxHeight,\n      lineWrapping = false,\n      extensions: additionalExtensions,\n      baseExtensions = defaultExtensions,\n      readOnly = false,\n      theme = \"system\",\n    },\n    ref,\n  ) => {\n    const editorRef = useRef<HTMLDivElement | null>(null);\n    const editorViewRef = useRef<EditorView | null>(null);\n\n    // Track dark mode state for \"system\" theme\n    const [isDark, setIsDark] = useState(() =>\n      typeof window !== \"undefined\" ? isDarkMode() : false,\n    );\n\n    // Listen for dark mode changes (system preference + document class)\n    useEffect(() => {\n      if (theme !== \"system\") return;\n\n      // Check initial state\n      setIsDark(isDarkMode());\n\n      // Listen for system preference changes\n      const mediaQuery = window.matchMedia(\"(prefers-color-scheme: dark)\");\n      const handleMediaChange = () => setIsDark(isDarkMode());\n      mediaQuery.addEventListener(\"change\", handleMediaChange);\n\n      // Observe document changes (for site-level dark mode toggle)\n      // Watch class, style (for color-scheme), and data attributes\n      const observer = new MutationObserver(() => {\n        setIsDark(isDarkMode());\n      });\n      observer.observe(document.documentElement, {\n        attributes: true,\n        attributeFilter: [\"class\", \"style\", \"data-theme\", \"data-mode\"],\n      });\n\n      return () => {\n        mediaQuery.removeEventListener(\"change\", handleMediaChange);\n        observer.disconnect();\n      };\n    }, [theme]);\n\n    // For IPython, detect cell magics and switch to appropriate language mode\n    const langExtension = useMemo(() => {\n      if (language === \"ipython\") {\n        return getIPythonExtension(value).extension;\n      }\n      return getLanguageExtension(language);\n    }, [language, value]);\n\n    // Determine which theme to use\n    const themeExtension = useMemo(() => {\n      if (theme === \"light\") return lightTheme;\n      if (theme === \"dark\") return darkTheme;\n      // \"system\" - use detected state\n      return isDark ? darkTheme : lightTheme;\n    }, [theme, isDark]);\n\n    const extensions = useMemo(() => {\n      const exts: Extension[] = [\n        ...baseExtensions,\n        langExtension,\n        themeExtension,\n      ];\n\n      if (keyMap && keyMap.length > 0) {\n        exts.unshift(keymap.of(keyMap));\n      }\n\n      if (placeholder) {\n        exts.push(placeholderExt(placeholder));\n      }\n\n      if (lineWrapping) {\n        exts.push(EditorView.lineWrapping);\n      }\n\n      if (readOnly) {\n        exts.push(EditorView.editable.of(false));\n      }\n\n      if (additionalExtensions) {\n        exts.push(...additionalExtensions);\n      }\n\n      return exts;\n    }, [\n      baseExtensions,\n      langExtension,\n      themeExtension,\n      keyMap,\n      placeholder,\n      lineWrapping,\n      readOnly,\n      additionalExtensions,\n    ]);\n\n    const handleChange = useCallback(\n      (val: string) => {\n        onValueChange?.(val);\n      },\n      [onValueChange],\n    );\n\n    const handleFocus = useCallback(() => {\n      onFocus?.();\n    }, [onFocus]);\n\n    const { setContainer, view } = useCodeMirror({\n      container: editorRef.current,\n      basicSetup: false,\n      indentWithTab: false,\n      extensions,\n      maxHeight,\n      value,\n      onChange: handleChange,\n      autoFocus,\n    });\n\n    // Store the editor view reference\n    useEffect(() => {\n      editorViewRef.current = view || null;\n    }, [view]);\n\n    // Expose methods via ref\n    useImperativeHandle(\n      ref,\n      () => ({\n        focus: () => {\n          editorViewRef.current?.focus();\n        },\n        setCursorPosition: (position: \"start\" | \"end\") => {\n          if (editorViewRef.current) {\n            const doc = editorViewRef.current.state.doc;\n            const pos = position === \"start\" ? 0 : doc.length;\n            editorViewRef.current.dispatch({\n              selection: { anchor: pos, head: pos },\n              scrollIntoView: true,\n            });\n          }\n        },\n        getEditor: () => editorViewRef.current,\n      }),\n      [],\n    );\n\n    useEffect(() => {\n      if (editorRef.current) {\n        setContainer(editorRef.current);\n      }\n    }, [setContainer]);\n\n    return (\n      <div\n        ref={editorRef}\n        onBlur={onBlur}\n        onFocus={handleFocus}\n        className={cn(\"text-sm\", className)}\n      />\n    );\n  },\n);\n\nCodeMirrorEditor.displayName = \"CodeMirrorEditor\";\n\nexport default CodeMirrorEditor;\n",
      "type": "registry:component",
      "target": "components/editor/codemirror-editor.tsx"
    },
    {
      "path": "registry/editor/extensions.ts",
      "content": "import {\n  closeBrackets,\n  closeBracketsKeymap,\n  completionKeymap,\n} from \"@codemirror/autocomplete\";\nimport { defaultKeymap, history, historyKeymap } from \"@codemirror/commands\";\nimport {\n  bracketMatching,\n  defaultHighlightStyle,\n  indentOnInput,\n  syntaxHighlighting,\n} from \"@codemirror/language\";\nimport { lintKeymap } from \"@codemirror/lint\";\nimport { EditorState, type Extension } from \"@codemirror/state\";\nimport {\n  crosshairCursor,\n  drawSelection,\n  dropCursor,\n  EditorView,\n  keymap,\n  rectangularSelection,\n} from \"@codemirror/view\";\n\n/**\n * Custom editor styles for notebook contexts\n */\nexport const notebookEditorTheme = EditorView.theme({\n  // Transparent background so editor inherits from container\n  // (overrides theme's background)\n  \"&.cm-editor\": {\n    backgroundColor: \"transparent\",\n  },\n  // Remove focus dotted outline\n  \"&.cm-focused\": {\n    outline: \"none\",\n  },\n  // Reset line padding so code aligns with output areas\n  // (CodeMirror's base theme adds \"padding: 0 2px 0 6px\" to .cm-line)\n  \".cm-line\": {\n    paddingLeft: \"0\",\n  },\n  // Mobile-friendly padding\n  \"@media (max-width: 640px)\": {\n    \".cm-content\": {\n      padding: \"0.75rem 0.5rem\",\n    },\n  },\n  // Slightly thicker cursor for better visibility\n  \".cm-cursor\": {\n    borderLeftWidth: \"2px\",\n  },\n});\n\n/**\n * Core editor setup with all standard features\n * Includes: history, bracket matching, autocomplete, syntax highlighting\n */\nexport const coreSetup: Extension = (() => [\n  history(),\n  drawSelection(),\n  dropCursor(),\n  EditorState.allowMultipleSelections.of(true),\n  indentOnInput(),\n  syntaxHighlighting(defaultHighlightStyle, { fallback: true }),\n  bracketMatching(),\n  closeBrackets(),\n  rectangularSelection(),\n  crosshairCursor(),\n  keymap.of([\n    ...closeBracketsKeymap,\n    ...defaultKeymap,\n    ...historyKeymap,\n    ...completionKeymap,\n    ...lintKeymap,\n  ]),\n  notebookEditorTheme,\n])();\n\n/**\n * Minimal editor setup without autocomplete\n * Useful for AI prompts or simple text input\n */\nexport const minimalSetup: Extension = (() => [\n  history(),\n  drawSelection(),\n  dropCursor(),\n  EditorState.allowMultipleSelections.of(true),\n  indentOnInput(),\n  syntaxHighlighting(defaultHighlightStyle, { fallback: true }),\n  bracketMatching(),\n  closeBrackets(),\n  rectangularSelection(),\n  crosshairCursor(),\n  keymap.of([\n    ...closeBracketsKeymap,\n    ...defaultKeymap,\n    ...historyKeymap,\n    ...lintKeymap,\n  ]),\n  notebookEditorTheme,\n])();\n\n/**\n * Default extensions bundle for notebook cells\n * Uses core setup - add your own theme on top\n */\nexport const defaultExtensions: Extension[] = [coreSetup];\n\n/**\n * Extensions bundle without autocomplete\n */\nexport const minimalExtensions: Extension[] = [minimalSetup];\n",
      "type": "registry:component",
      "target": "components/editor/extensions.ts"
    },
    {
      "path": "registry/editor/languages.ts",
      "content": "import { html } from \"@codemirror/lang-html\";\nimport { javascript } from \"@codemirror/lang-javascript\";\nimport { json } from \"@codemirror/lang-json\";\nimport { markdown } from \"@codemirror/lang-markdown\";\nimport { python } from \"@codemirror/lang-python\";\nimport { sql } from \"@codemirror/lang-sql\";\nimport { indentUnit } from \"@codemirror/language\";\nimport type { Extension } from \"@codemirror/state\";\n\nimport {\n  CELL_MAGIC_LANGUAGES,\n  detectCellMagic,\n  getCellMagicLanguage,\n  ipythonHighlighting,\n  ipythonIndent,\n  ipythonStyles,\n  ipythonStylesDark,\n} from \"./ipython\";\n\n/**\n * Supported languages for the CodeMirror editor\n */\nexport type SupportedLanguage =\n  | \"python\"\n  | \"ipython\"\n  | \"markdown\"\n  | \"sql\"\n  | \"html\"\n  | \"javascript\"\n  | \"typescript\"\n  | \"json\"\n  | \"plain\";\n\n/**\n * Get the CodeMirror language extension for a given language\n */\n// PEP 8 specifies 4-space indentation for Python\nconst pythonIndent = indentUnit.of(\"    \");\n\nexport function getLanguageExtension(language: SupportedLanguage): Extension {\n  switch (language) {\n    case \"python\":\n      return [python(), pythonIndent];\n    case \"ipython\":\n      return [\n        python(),\n        pythonIndent,\n        ipythonIndent,\n        ipythonHighlighting(),\n        ipythonStyles,\n        ipythonStylesDark,\n      ];\n    case \"markdown\":\n      return markdown();\n    case \"sql\":\n      return sql();\n    case \"html\":\n      return html();\n    case \"javascript\":\n      return javascript();\n    case \"typescript\":\n      return javascript({ typescript: true });\n    case \"json\":\n      return json();\n    default:\n      return [];\n  }\n}\n\n/**\n * Get the language extension for IPython content, detecting cell magics.\n *\n * If the content starts with a cell magic (e.g., %%html, %%bash),\n * returns the appropriate language extension for that magic.\n * Otherwise returns the standard IPython extension.\n *\n * @param content - The editor content to analyze\n * @returns Object with language extension and detected cell magic (if any)\n *\n * @example\n * // Cell magic - returns HTML language\n * getIPythonExtension(\"%%html\\n<div>Hello</div>\")\n * // { extension: html(), cellMagic: \"html\", language: \"html\" }\n *\n * // No cell magic - returns IPython\n * getIPythonExtension(\"%time x = sum(range(100))\")\n * // { extension: [python(), ...], cellMagic: null, language: \"ipython\" }\n */\nexport function getIPythonExtension(content: string): {\n  extension: Extension;\n  cellMagic: string | null;\n  language: SupportedLanguage;\n} {\n  const magic = detectCellMagic(content);\n\n  if (magic) {\n    const langId = getCellMagicLanguage(magic);\n    const language = (\n      langId in languageDisplayNames ? langId : \"plain\"\n    ) as SupportedLanguage;\n\n    // For cell magics, use the target language but add IPython decoration\n    // for the first line (the %%magic declaration)\n    const baseExtension = getLanguageExtension(language);\n    return {\n      extension: [\n        baseExtension,\n        ipythonHighlighting(),\n        ipythonStyles,\n        ipythonStylesDark,\n      ],\n      cellMagic: magic,\n      language,\n    };\n  }\n\n  // No cell magic - use standard IPython mode\n  return {\n    extension: getLanguageExtension(\"ipython\"),\n    cellMagic: null,\n    language: \"ipython\",\n  };\n}\n\n// Re-export cell magic utilities for consumers\nexport { CELL_MAGIC_LANGUAGES, detectCellMagic, getCellMagicLanguage };\n\n/**\n * Language display names for UI\n */\nexport const languageDisplayNames: Record<SupportedLanguage, string> = {\n  python: \"Python\",\n  ipython: \"IPython\",\n  markdown: \"Markdown\",\n  sql: \"SQL\",\n  html: \"HTML\",\n  javascript: \"JavaScript\",\n  typescript: \"TypeScript\",\n  json: \"JSON\",\n  plain: \"Plain Text\",\n};\n\n/**\n * File extensions mapped to languages\n */\nexport const fileExtensionToLanguage: Record<string, SupportedLanguage> = {\n  \".py\": \"python\",\n  \".ipy\": \"ipython\",\n  \".md\": \"markdown\",\n  \".markdown\": \"markdown\",\n  \".sql\": \"sql\",\n  \".html\": \"html\",\n  \".htm\": \"html\",\n  \".js\": \"javascript\",\n  \".jsx\": \"javascript\",\n  \".ts\": \"typescript\",\n  \".tsx\": \"typescript\",\n  \".json\": \"json\",\n  \".txt\": \"plain\",\n};\n\n/**\n * Detect language from filename\n */\nexport function detectLanguage(filename: string): SupportedLanguage {\n  const ext = filename.slice(filename.lastIndexOf(\".\")).toLowerCase();\n  return fileExtensionToLanguage[ext] || \"plain\";\n}\n",
      "type": "registry:component",
      "target": "components/editor/languages.ts"
    },
    {
      "path": "registry/editor/themes.ts",
      "content": "import type { Extension } from \"@codemirror/state\";\nimport { githubDark, githubLight } from \"@uiw/codemirror-theme-github\";\n\nimport {\n  documentHasDarkMode,\n  isDarkMode,\n  prefersDarkMode,\n  useDarkMode,\n} from \"@/lib/dark-mode\";\n\n// Re-export theme detection utilities from canonical location\nexport { documentHasDarkMode, isDarkMode, prefersDarkMode, useDarkMode };\n\n/**\n * Theme mode options\n */\nexport type ThemeMode = \"light\" | \"dark\" | \"system\";\n\n/**\n * Light theme - GitHub Light\n */\nexport const lightTheme: Extension = githubLight;\n\n/**\n * Dark theme - GitHub Dark\n */\nexport const darkTheme: Extension = githubDark;\n\n/**\n * Get the appropriate theme extension based on mode\n */\nexport function getTheme(mode: ThemeMode): Extension {\n  if (mode === \"light\") {\n    return lightTheme;\n  }\n  if (mode === \"dark\") {\n    return darkTheme;\n  }\n  // System mode - detect from media query\n  if (typeof window !== \"undefined\") {\n    const prefersDark = window.matchMedia(\n      \"(prefers-color-scheme: dark)\",\n    ).matches;\n    return prefersDark ? darkTheme : lightTheme;\n  }\n  // SSR fallback\n  return lightTheme;\n}\n\n/**\n * Get the current theme based on automatic detection\n * Checks document class, color-scheme, data-theme attribute, and system preference\n */\nexport function getAutoTheme(): Extension {\n  return isDarkMode() ? darkTheme : lightTheme;\n}\n",
      "type": "registry:component",
      "target": "components/editor/themes.ts"
    },
    {
      "path": "registry/editor/ipython.ts",
      "content": "import { indentService } from \"@codemirror/language\";\nimport { RangeSetBuilder } from \"@codemirror/state\";\nimport type { DecorationSet, ViewUpdate } from \"@codemirror/view\";\nimport { Decoration, EditorView, ViewPlugin } from \"@codemirror/view\";\n\n/**\n * IPython syntax highlighting extension for CodeMirror 6\n *\n * Highlights IPython-specific syntax on top of standard Python:\n * - Shell commands: !ls, !pip install\n * - Line magics: %time, %run script.py\n * - Cell magics: %%bash, %%javascript (first line only)\n * - Help operators: object?, object??\n *\n * Note: For cell magics (%%), only the first line is decorated.\n * The rest of the cell should use the appropriate language mode.\n * Use detectCellMagic() and CELL_MAGIC_LANGUAGES to determine\n * the correct language for cell magic content.\n */\n\n// Decoration marks for IPython syntax\nconst shellMark = Decoration.mark({ class: \"cm-ipython-shell\" });\nconst magicMark = Decoration.mark({ class: \"cm-ipython-magic\" });\nconst cellMagicMark = Decoration.mark({ class: \"cm-ipython-cell-magic\" });\nconst helpMark = Decoration.mark({ class: \"cm-ipython-help\" });\n\n// Patterns for IPython syntax (applied to line content)\nconst CELL_MAGIC_PATTERN = /^(%%[a-zA-Z_]\\w*)/;\nconst LINE_MAGIC_PATTERN = /^(%[a-zA-Z_]\\w*)/;\nconst SHELL_PATTERN = /^(!)/;\nconst HELP_PATTERN = /(\\?\\??)$/;\n\n/**\n * Custom indentation service for IPython.\n *\n * Prevents auto-indent after:\n * - Line magics (%time, %matplotlib, etc.)\n * - Shell commands (!pip, !ls, etc.)\n * - Cell magic declarations (%%bash, %%html, etc.)\n *\n * These lines don't follow Python's indentation rules,\n * so we reset to column 0 after them.\n */\nexport const ipythonIndent = indentService.of((context, pos) => {\n  // Get the previous line\n  const line = context.state.doc.lineAt(pos);\n  if (line.number <= 1) return null; // Let Python handle first line\n\n  const prevLine = context.state.doc.line(line.number - 1);\n  const prevText = prevLine.text.trim();\n\n  // After line magic, shell command, or cell magic - don't auto-indent\n  if (\n    LINE_MAGIC_PATTERN.test(prevText) ||\n    SHELL_PATTERN.test(prevText) ||\n    CELL_MAGIC_PATTERN.test(prevText)\n  ) {\n    return 0;\n  }\n\n  // Let Python's indentation handle everything else\n  return null;\n});\n\n/**\n * Mapping of cell magic names to language identifiers.\n * Use with getLanguageExtension() from languages.ts\n */\nexport const CELL_MAGIC_LANGUAGES: Record<string, string> = {\n  // HTML/SVG\n  html: \"html\",\n  HTML: \"html\",\n  svg: \"html\",\n  SVG: \"html\",\n  // JavaScript\n  javascript: \"javascript\",\n  js: \"javascript\",\n  // TypeScript\n  typescript: \"typescript\",\n  ts: \"typescript\",\n  // SQL\n  sql: \"sql\",\n  SQL: \"sql\",\n  // Markdown\n  markdown: \"markdown\",\n  md: \"markdown\",\n  // JSON\n  json: \"json\",\n  // Shell (falls back to plain since we don't have shell lang)\n  bash: \"plain\",\n  sh: \"plain\",\n  // Python (stays as Python)\n  python: \"python\",\n  python3: \"python\",\n  // Others without specific support fall back to plain\n};\n\n/**\n * Detect cell magic from content.\n * Cell magics must be on the first line.\n *\n * @param content - The editor content\n * @returns The magic name (without %%) if found, null otherwise\n *\n * @example\n * detectCellMagic(\"%%html\\n<div>Hello</div>\") // \"html\"\n * detectCellMagic(\"%time x = 1\") // null (line magic, not cell magic)\n * detectCellMagic(\"print('hello')\") // null\n */\nexport function detectCellMagic(content: string): string | null {\n  const firstLine = content.split(\"\\n\")[0].trim();\n  const match = firstLine.match(/^%%([a-zA-Z_]\\w*)/);\n  return match ? match[1] : null;\n}\n\n/**\n * Get the language identifier for a cell magic.\n *\n * @param magic - The cell magic name (without %%)\n * @returns The language identifier to use with getLanguageExtension(),\n *          or \"plain\" for unsupported magics\n *\n * @example\n * getCellMagicLanguage(\"html\") // \"html\"\n * getCellMagicLanguage(\"bash\") // \"plain\" (no shell support)\n * getCellMagicLanguage(\"unknown\") // \"plain\"\n */\nexport function getCellMagicLanguage(magic: string): string {\n  return CELL_MAGIC_LANGUAGES[magic] ?? \"plain\";\n}\n\nclass IPythonHighlighter {\n  decorations: DecorationSet;\n\n  constructor(view: EditorView) {\n    this.decorations = this.buildDecorations(view);\n  }\n\n  update(update: ViewUpdate) {\n    if (update.docChanged || update.viewportChanged) {\n      this.decorations = this.buildDecorations(update.view);\n    }\n  }\n\n  buildDecorations(view: EditorView): DecorationSet {\n    const builder = new RangeSetBuilder<Decoration>();\n    const doc = view.state.doc;\n\n    // Check if we're in cell magic mode (first line is %%)\n    const firstLineText = doc.line(1).text.trim();\n    const isInCellMagicMode = CELL_MAGIC_PATTERN.test(firstLineText);\n\n    for (const { from, to } of view.visibleRanges) {\n      const startLine = doc.lineAt(from).number;\n      const endLine = doc.lineAt(to).number;\n\n      for (let lineNum = startLine; lineNum <= endLine; lineNum++) {\n        const line = doc.line(lineNum);\n        const lineText = line.text;\n        const trimmedText = lineText.trimStart();\n        const leadingSpaces = lineText.length - trimmedText.length;\n        const lineStart = line.from + leadingSpaces;\n\n        // Cell magic on first line - decorate it\n        if (lineNum === 1) {\n          const cellMagicMatch = trimmedText.match(CELL_MAGIC_PATTERN);\n          if (cellMagicMatch) {\n            builder.add(\n              lineStart,\n              lineStart + cellMagicMatch[1].length,\n              cellMagicMark,\n            );\n            continue;\n          }\n        }\n\n        // If we're in cell magic mode, don't apply IPython decorations\n        // to subsequent lines (they belong to the cell magic's language)\n        if (isInCellMagicMode && lineNum > 1) {\n          continue;\n        }\n\n        // Line magic (%magic)\n        const lineMagicMatch = trimmedText.match(LINE_MAGIC_PATTERN);\n        if (lineMagicMatch) {\n          builder.add(\n            lineStart,\n            lineStart + lineMagicMatch[1].length,\n            magicMark,\n          );\n          continue;\n        }\n\n        // Shell command (!command)\n        const shellMatch = trimmedText.match(SHELL_PATTERN);\n        if (shellMatch) {\n          builder.add(lineStart, line.to, shellMark);\n          continue;\n        }\n\n        // Help operator (object? or object??)\n        const helpMatch = lineText.match(HELP_PATTERN);\n        if (helpMatch && helpMatch.index !== undefined) {\n          const helpStart = line.from + helpMatch.index;\n          const helpEnd = helpStart + helpMatch[1].length;\n          builder.add(helpStart, helpEnd, helpMark);\n        }\n      }\n    }\n\n    return builder.finish();\n  }\n}\n\n/**\n * CodeMirror extension that adds IPython syntax highlighting.\n *\n * For cells with cell magics (%%html, %%bash, etc.), only the first\n * line is decorated. Use detectCellMagic() and getLanguageExtension()\n * to set the appropriate language for the cell content.\n */\nexport function ipythonHighlighting() {\n  return ViewPlugin.fromClass(IPythonHighlighter, {\n    decorations: (v) => v.decorations,\n  });\n}\n\n/**\n * Light theme styles for IPython syntax\n */\nexport const ipythonStyles = EditorView.theme({\n  \".cm-ipython-shell\": {\n    color: \"#0550ae\",\n  },\n  \".cm-ipython-magic\": {\n    color: \"#6639ba\",\n  },\n  \".cm-ipython-cell-magic\": {\n    color: \"#6639ba\",\n    fontWeight: \"bold\",\n  },\n  \".cm-ipython-help\": {\n    color: \"#0969da\",\n  },\n});\n\n/**\n * Dark theme styles for IPython syntax\n */\nexport const ipythonStylesDark = EditorView.theme(\n  {\n    \".cm-ipython-shell\": {\n      color: \"#79c0ff\",\n    },\n    \".cm-ipython-magic\": {\n      color: \"#d2a8ff\",\n    },\n    \".cm-ipython-cell-magic\": {\n      color: \"#d2a8ff\",\n      fontWeight: \"bold\",\n    },\n    \".cm-ipython-help\": {\n      color: \"#58a6ff\",\n    },\n  },\n  { dark: true },\n);\n",
      "type": "registry:component",
      "target": "components/editor/ipython.ts"
    },
    {
      "path": "registry/editor/search-highlight.ts",
      "content": "/**\n * CodeMirror extension for highlighting search matches in the editor.\n *\n * Provides visual highlighting for find/search functionality with distinction\n * between regular matches (yellow) and the active/current match (orange).\n */\n\nimport { type Extension, RangeSetBuilder } from \"@codemirror/state\";\nimport {\n  Decoration,\n  type DecorationSet,\n  EditorView,\n  ViewPlugin,\n  type ViewUpdate,\n} from \"@codemirror/view\";\n\nconst searchMatchMark = Decoration.mark({ class: \"cm-global-find-match\" });\nconst activeMatchMark = Decoration.mark({\n  class: \"cm-global-find-match-active\",\n});\n\nconst searchHighlightTheme = EditorView.theme({\n  \".cm-global-find-match\": {\n    background: \"#fbbf24\",\n    color: \"#000\",\n    borderRadius: \"2px\",\n  },\n  \".cm-global-find-match-active\": {\n    background: \"#f97316\",\n    color: \"#000\",\n    borderRadius: \"2px\",\n  },\n});\n\n/**\n * Build decorations for all search matches in a document.\n * @param doc - The CodeMirror document text\n * @param query - Search query (case-insensitive)\n * @param activeOffset - Character offset of the active match (-1 for none)\n */\nfunction buildDecorations(\n  doc: string,\n  query: string,\n  activeOffset: number,\n): DecorationSet {\n  if (!query) return Decoration.none;\n\n  const builder = new RangeSetBuilder<Decoration>();\n  const lowerDoc = doc.toLowerCase();\n  const lowerQuery = query.toLowerCase();\n  let pos = lowerDoc.indexOf(lowerQuery, 0);\n\n  while (pos !== -1) {\n    const isActive = pos === activeOffset;\n    builder.add(\n      pos,\n      pos + query.length,\n      isActive ? activeMatchMark : searchMatchMark,\n    );\n    pos = lowerDoc.indexOf(lowerQuery, pos + query.length);\n  }\n\n  return builder.finish();\n}\n\n/**\n * Create a search highlight ViewPlugin for a given query.\n *\n * Note: Since ViewPlugin instances are static (cannot update config),\n * this returns a new extension each time the query changes. The parent\n * component should replace extensions when the query changes, which\n * is the standard pattern for react-codemirror.\n */\nfunction createSearchHighlightPlugin(query: string, activeOffset: number) {\n  return ViewPlugin.define(\n    (view) => ({\n      decorations: buildDecorations(\n        view.state.doc.toString(),\n        query,\n        activeOffset,\n      ),\n      update(update: ViewUpdate) {\n        if (update.docChanged) {\n          this.decorations = buildDecorations(\n            update.state.doc.toString(),\n            query,\n            activeOffset,\n          );\n        }\n      },\n    }),\n    {\n      decorations: (v) => v.decorations,\n    },\n  );\n}\n\n/**\n * Create a CodeMirror extension that highlights all matches of a search query.\n *\n * @param query - The search string to highlight (case-insensitive). Empty string = no highlights.\n * @param activeOffset - Character offset of the \"active\" match to highlight differently (-1 for none).\n * @returns A CodeMirror Extension array to pass to the editor.\n *\n * @example\n * ```tsx\n * import { searchHighlight } from \"@/registry/editor/search-highlight\";\n *\n * // Highlight all occurrences of \"function\"\n * <CodeMirrorEditor extensions={[searchHighlight(\"function\")]} />\n *\n * // Highlight with active match at character offset 100\n * <CodeMirrorEditor extensions={[searchHighlight(\"function\", 100)]} />\n * ```\n */\nexport function searchHighlight(query: string, activeOffset = -1): Extension[] {\n  if (!query) return [];\n  return [\n    searchHighlightTheme,\n    createSearchHighlightPlugin(query, activeOffset),\n  ];\n}\n",
      "type": "registry:component",
      "target": "components/editor/search-highlight.ts"
    },
    {
      "path": "registry/editor/index.ts",
      "content": "export {\n  CodeMirrorEditor,\n  type CodeMirrorEditorProps,\n  type CodeMirrorEditorRef,\n} from \"./codemirror-editor\";\nexport {\n  coreSetup,\n  defaultExtensions,\n  minimalExtensions,\n  minimalSetup,\n  notebookEditorTheme,\n} from \"./extensions\";\nexport {\n  CELL_MAGIC_LANGUAGES,\n  detectCellMagic,\n  getCellMagicLanguage,\n  ipythonHighlighting,\n  ipythonIndent,\n  ipythonStyles,\n  ipythonStylesDark,\n} from \"./ipython\";\nexport {\n  detectLanguage,\n  fileExtensionToLanguage,\n  getIPythonExtension,\n  getLanguageExtension,\n  languageDisplayNames,\n  type SupportedLanguage,\n} from \"./languages\";\nexport {\n  darkTheme,\n  documentHasDarkMode,\n  getAutoTheme,\n  getTheme,\n  isDarkMode,\n  lightTheme,\n  prefersDarkMode,\n  type ThemeMode,\n} from \"./themes\";\n",
      "type": "registry:component",
      "target": "components/editor/index.ts"
    }
  ],
  "type": "registry:component"
}