/* Copyright 2026 Marimo. All rights reserved. */ import { markdown } from "@codemirror/lang-markdown"; import { sql } from "@codemirror/lang-sql"; import { defaultHighlightStyle, syntaxHighlighting, } from "@codemirror/language"; import CodeMirror, { EditorView, type ReactCodeMirrorProps, } from "@uiw/react-codemirror"; import { CopyIcon, EyeIcon, EyeOffIcon, PlusIcon } from "lucide-react"; import { memo, useMemo, useState } from "react"; import { useAddCodeToNewCell } from "@/components/editor/cell/useAddCell"; import { Button } from "@/components/ui/button"; import { Tooltip } from "@/components/ui/tooltip"; import { toast } from "@/components/ui/use-toast"; import type { LanguageAdapterType } from "@/core/codemirror/language/types"; import { customPythonLanguageSupport } from "@/core/codemirror/language/languages/python"; import { darkTheme } from "@/core/codemirror/theme/dark"; import { lightTheme } from "@/core/codemirror/theme/light"; import { useTheme } from "@/theme/useTheme"; import { cn } from "@/utils/cn"; import { copyToClipboard } from "@/utils/copy"; import { Events } from "@/utils/events"; const pythonExtensions = [ customPythonLanguageSupport(), EditorView.lineWrapping, ]; const sqlExtensions = [sql(), EditorView.lineWrapping]; const markdownExtensions = [markdown(), EditorView.lineWrapping]; function readonlyCodeExtensions(language: LanguageAdapterType) { switch (language) { case "sql": return sqlExtensions; case "markdown": return markdownExtensions; default: return pythonExtensions; } } /** * A readonly code component that can be used to display code in a readonly state. * * @param props.className - The class name to apply to the component. * @param props.code - The code to display. * @param props.initiallyHideCode - Whether to initially hide the code. * @param props.showHideCode - Whether to show the hide code button. * @param props.insertNewCell - Whether to add a insert new cell button; when clicked will add a new cell next to the current cell or at the end of the file * @param props.language - The language of the code. Default is "python". */ export const ReadonlyCode = memo( ( props: { className?: string; code: string; initiallyHideCode?: boolean; showHideCode?: boolean; showCopyCode?: boolean; insertNewCell?: boolean; language?: LanguageAdapterType; } & ReactCodeMirrorProps, ) => { const { theme } = useTheme(); const { code, className, initiallyHideCode, showHideCode = true, showCopyCode = true, insertNewCell, language = "python", ...rest } = props; const [hideCode, setHideCode] = useState(!!initiallyHideCode); const extensions = useMemo( () => [ theme === "dark" ? darkTheme : lightTheme, syntaxHighlighting(defaultHighlightStyle, { fallback: true }), ...readonlyCodeExtensions(language), ], [theme, language], ); return (
{showCopyCode && } {insertNewCell && } {showHideCode && (
); }, ); ReadonlyCode.displayName = "ReadonlyCode"; const CopyButton = (props: { text: string }) => { const copy = Events.stopPropagation(async () => { await copyToClipboard(props.text); toast({ title: "Copied to clipboard" }); }); return ( ); }; const ToggleCodeButton = (props: { hidden: boolean; onClick: () => void }) => { return ( ); }; const InsertNewCell = (props: { code: string }) => { const addCodeToNewCell = useAddCodeToNewCell(); const handleClick = () => { addCodeToNewCell(props.code); }; return ( ); };