import { useRef, useState } from 'react'; import CodeMirror, { EditorView } from '@uiw/react-codemirror'; import { json } from '@codemirror/lang-json'; import { xml } from '@codemirror/lang-xml'; import { javascript } from '@codemirror/lang-javascript'; import { python } from '@codemirror/lang-python'; import { cpp } from '@codemirror/lang-cpp'; import { css } from '@codemirror/lang-css'; import { rust } from '@codemirror/lang-rust'; import { php } from '@codemirror/lang-php'; import { sql } from '@codemirror/lang-sql'; import { oneDark } from '@codemirror/theme-one-dark'; import { Eye, Pencil, Save } from 'lucide-react'; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism'; export type CodeMode = 'text' | 'json' | 'xml' | 'code'; interface CodeEditorProps { text: string; mode: CodeMode; /** Prism language id for syntax highlighting in view mode (used when mode='code'). */ syntaxHighlightLang?: string; readOnly?: boolean; onSave?: (text: string) => Promise; } const cmTheme = EditorView.theme({ '&': { height: '100%', background: '#0a0a0a' }, '.cm-scroller': { overflow: 'auto', fontFamily: 'ui-monospace,monospace', fontSize: '12px', lineHeight: '1.5' }, '.cm-content': { padding: '12px 0' }, '.cm-focused': { outline: 'none' }, }); const langExt: Record, any> = { json: json(), xml: xml(), text: [], }; function cmLangExt(prismLang: string | undefined): any { switch (prismLang) { case 'javascript': return javascript(); case 'typescript': return javascript({ typescript: true }); case 'jsx': return javascript({ jsx: true }); case 'tsx': return javascript({ jsx: true, typescript: true }); case 'python': return python(); case 'c': case 'cpp': return cpp(); case 'css': case 'scss': return css(); case 'rust': return rust(); case 'php': return php(); case 'sql': return sql(); default: return []; } } const syntaxLang: Record = { text: 'text', json: 'json', xml: 'xml', code: 'text', }; function prettify(text: string, mode: CodeMode): string { if (mode === 'json') { try { return JSON.stringify(JSON.parse(text), null, 2); } catch { /* fall through */ } } return text; } export default function CodeEditor({ text, mode, syntaxHighlightLang, readOnly = false, onSave }: CodeEditorProps) { const [editMode, setEditMode] = useState(false); const [editInitText, setEditInitText] = useState(''); const [saving, setSaving] = useState(false); const editorViewRef = useRef(null); const displayText = prettify(text, mode); const extensions = [mode === 'code' ? cmLangExt(syntaxHighlightLang) : langExt[mode], cmTheme].flat(); const handleSave = async () => { if (!editorViewRef.current || !onSave) return; setSaving(true); try { await onSave(editorViewRef.current.state.doc.toString()); } finally { setSaving(false); } }; if (!editMode) { return (
{!readOnly && (
)}
{displayText}
); } return (
{onSave && ( )} Ctrl+Z/Y undo · Ctrl+F search
{ editorViewRef.current = view; }} />
); }