import { useEffect, useRef, useState } from "react"; import { useLocation } from "react-router-dom"; import AskAI from "../components/AskAI"; import GitHubSearch from "../components/GitHubSearch"; import { useAuth } from "../context/AuthContext"; import { pyRunner } from "../lib/pyRunner"; import { createSnippet } from "../lib/snippets"; /* ===== CDN sources (not available as npm packages in this setup) ===== */ const CM_BASE = "https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13"; const CM_JS = `${CM_BASE}/codemirror.min.js`; const CM_CSS = `${CM_BASE}/codemirror.min.css`; const CM_PYTHON_MODE = `${CM_BASE}/mode/python/python.min.js`; const CM_DRACULA_CSS = `${CM_BASE}/theme/dracula.min.css`; const STARTER_CODE = `import os # 1. Testing Environment Variables print("Environment Variable (MODE):", os.environ.get("MODE")) # 2. Testing Virtual File System Access try: with open('/data/config.txt', 'r') as file: print("File Contents:", file.read()) except Exception as e: print("File error:", e) # 3. Testing Package Auto-installation (e.g., NumPy) try: import numpy as np print("NumPy imported successfully! Array:", np.array([1, 2, 3])) except Exception as e: print("Dependency error:", e) `; interface CodeMirrorEditor { getValue(): string; setValue(code: string): void; toTextArea(): void; on(eventName: string, handler: (instance: CodeMirrorEditor) => void): void; } declare global { interface Window { CodeMirror?: { fromTextArea( el: HTMLTextAreaElement, opts: Record, ): CodeMirrorEditor; }; } } /* ===== Idempotent CDN loaders (safe across remounts / StrictMode) ===== */ function loadScript(src: string): Promise { return new Promise((resolve, reject) => { const existing = document.querySelector( `script[src="${src}"]`, ); if (existing) { if (existing.dataset.loaded) return resolve(); existing.addEventListener("load", () => resolve()); existing.addEventListener("error", () => reject(new Error(`Failed to load ${src}`)), ); return; } const script = document.createElement("script"); script.src = src; script.onload = () => { script.dataset.loaded = "1"; resolve(); }; script.onerror = () => { // Remove the failed tag so a later mount can retry the download // (a lingering failed tag would make waiting promises hang forever). script.remove(); reject(new Error(`Failed to load ${src}`)); }; document.head.appendChild(script); }); } function loadCss(href: string): void { if (document.querySelector(`link[href="${href}"]`)) return; const link = document.createElement("link"); link.rel = "stylesheet"; link.href = href; document.head.appendChild(link); } /* ===== Page ===== */ type Status = "loading" | "ready" | "running" | "failed"; export default function Compiler() { const location = useLocation(); const { enabled: authEnabled, user, signInWithGoogle } = useAuth(); const textareaRef = useRef(null); const editorBoxRef = useRef(null); const editorRef = useRef(null); const [status, setStatus] = useState("loading"); const [output, setOutput] = useState("Loading Python environment..."); const [editorFailed, setEditorFailed] = useState(false); const [needsInput, setNeedsInput] = useState(false); const [stdin, setStdin] = useState(""); const [copied, setCopied] = useState(false); const [saveOpen, setSaveOpen] = useState(false); const [saveTitle, setSaveTitle] = useState(""); const [saveState, setSaveState] = useState<"idle" | "saving" | "saved" | "error">("idle"); // Real-time code analysis: wrong-language banner + sandbox warnings. const [detectedLang, setDetectedLang] = useState("Python 3"); const [unsupportedWarnings, setUnsupportedWarnings] = useState([]); // Code passed from other pages (e.g. My Codes → Open). const initialCode = (location.state as { code?: string } | null)?.code ?? STARTER_CODE; // Simple heuristics to analyze the code const analyzeCode = (code: string) => { // 1. Language Detection if (/\bprint\s+['"]/.test(code) || /\braw_input\s*\(/.test(code)) { setDetectedLang("Python 2"); } else if (/#include\s*<|\bint\s+main\s*\(/.test(code)) { setDetectedLang("C/C++"); } else if (/\bpublic\s+class\s+|\bSystem\.out\.println/.test(code)) { setDetectedLang("Java"); } else { setDetectedLang("Python 3"); } // 2. Sandbox Warnings (subprocess, spawned processes, local drives). // Plain `import os` is fine — the environment mocks env vars and a // virtual filesystem — so only flag the calls that truly can't work. const warnings = []; if (/import\s+subprocess|\bsubprocess\./.test(code)) warnings.push("subprocess module"); if (/os\.system|os\.spawn|os\.fork|os\.exec/.test(code)) warnings.push("OS level commands"); if (/[a-zA-Z]:\\|\/[Uu]sers\//.test(code)) warnings.push("local file paths"); setUnsupportedWarnings(warnings); // stdin box appears whenever the program reads input(). setNeedsInput(code.includes("input(")); }; useEffect(() => { let cancelled = false; let editor: CodeMirrorEditor | null = null; analyzeCode(initialCode); // Editor: CodeMirror core must load before the Python mode. (async () => { try { loadCss(CM_CSS); loadCss(CM_DRACULA_CSS); await loadScript(CM_JS); await loadScript(CM_PYTHON_MODE); if (cancelled || !textareaRef.current || !window.CodeMirror) return; editor = window.CodeMirror.fromTextArea(textareaRef.current, { mode: "python", theme: "dracula", lineNumbers: true, indentUnit: 4, matchBrackets: true, }); editor.on("change", (instance) => { if (!cancelled) analyzeCode(instance.getValue()); }); editorRef.current = editor; } catch { // Non-fatal: the plain textarea still works and runCode falls back // to it, so don't block the Run button over a missing editor. if (!cancelled) setEditorFailed(true); } })(); // Runtime: the Pyodide worker initializes in parallel with the editor. pyRunner.init().then( () => { if (cancelled) return; setStatus("ready"); setOutput("Environment Ready."); }, () => { if (cancelled) return; setStatus("failed"); setOutput( "Failed to load the Python environment. Check your internet connection and refresh.", ); }, ); return () => { cancelled = true; editorRef.current = null; editor?.toTextArea(); }; // initialCode is stable for the lifetime of the mount (navigation remounts). // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const getCode = () => editorRef.current?.getValue() ?? textareaRef.current?.value ?? ""; // Shared with the GitHub search panel and the AI panel: loads fetched // code into the editor. const handleImport = (code: string) => { if (editorRef.current) { editorRef.current.setValue(code); } else if (textareaRef.current) { textareaRef.current.value = code; analyzeCode(code); } // On mobile the panel sits below the compiler — bring the editor into view. editorBoxRef.current?.scrollIntoView({ behavior: "smooth", block: "start" }); }; const handleCopyCode = () => { navigator.clipboard.writeText(getCode()); setCopied(true); setTimeout(() => setCopied(false), 2000); }; const runCode = async () => { if (status !== "ready") return; const code = getCode(); setStatus("running"); setOutput("Running..."); const result = await pyRunner.run(code, stdin); const parts = [result.output, result.error].filter(Boolean); let text = parts.join(result.output ? "\n" : ""); if (result.error?.includes("ModuleNotFoundError")) { text += "\n\nHint: this module couldn't be installed in the browser Python environment. Packages with compiled/OS-level code (pygame, tkinter...) can't run here."; } setOutput(text || "(program finished with no output)"); setStatus("ready"); }; const stopCode = () => { pyRunner.stop(); }; const saveCode = async () => { if (!user) { await signInWithGoogle(); return; } const title = saveTitle.trim() || "Untitled program"; setSaveState("saving"); try { await createSnippet(user.id, title, getCode()); setSaveState("saved"); setSaveOpen(false); setSaveTitle(""); setTimeout(() => setSaveState("idle"), 2500); } catch { setSaveState("error"); } }; return (
{/* User-Provided CSS injected directly */}