/** * CodeEditor Component - Lightweight Code Editor for Coding Rounds * * Features: * - Multi-language support (Python, JavaScript, Java, C++, etc.) * - Syntax highlighting via simple CSS classes * - Code execution with output display * - Keyboard shortcuts (Ctrl+Enter to run) * - Line numbers * - Auto-indentation */ import React, { useState, useRef, useEffect, useCallback } from 'react'; import { Play, Square, RotateCcw, Copy, Check, ChevronDown, Terminal, Code, Loader2, AlertCircle, CheckCircle2, Maximize2, Minimize2 } from 'lucide-react'; import { getToken } from '../services/api'; import './CodeEditor.css'; // Supported Languages const LANGUAGES = [ { id: 'python', name: 'Python', icon: '🐍', extension: '.py' }, { id: 'javascript', name: 'JavaScript', icon: '🟨', extension: '.js' }, { id: 'typescript', name: 'TypeScript', icon: '🔷', extension: '.ts' }, { id: 'java', name: 'Java', icon: '☕', extension: '.java' }, { id: 'cpp', name: 'C++', icon: '⚡', extension: '.cpp' }, { id: 'c', name: 'C', icon: '🔵', extension: '.c' }, { id: 'go', name: 'Go', icon: '🔹', extension: '.go' }, { id: 'ruby', name: 'Ruby', icon: '💎', extension: '.rb' }, { id: 'php', name: 'PHP', icon: '🐘', extension: '.php' }, ]; // Default code templates const DEFAULT_TEMPLATES = { python: `# Python Solution def solution(): # Write your code here print("Hello, World!") solution()`, javascript: `// JavaScript Solution function solution() { // Write your code here console.log("Hello, World!"); } solution();`, typescript: `// TypeScript Solution function solution(): void { // Write your code here console.log("Hello, World!"); } solution();`, java: `// Java Solution public class Solution { public static void main(String[] args) { // Write your code here System.out.println("Hello, World!"); } }`, cpp: `// C++ Solution #include using namespace std; int main() { // Write your code here cout << "Hello, World!" << endl; return 0; }`, c: `// C Solution #include int main() { // Write your code here printf("Hello, World!\\n"); return 0; }`, go: `// Go Solution package main import "fmt" func main() { // Write your code here fmt.Println("Hello, World!") }`, ruby: `# Ruby Solution def solution # Write your code here puts "Hello, World!" end solution`, php: ``, }; const CodeEditor = ({ initialCode = '', initialLanguage = 'python', onCodeChange, onRun, readOnly = false, className = '', showOutput = true, apiEndpoint = '/api/compiler/execute', }) => { const [code, setCode] = useState(initialCode || DEFAULT_TEMPLATES[initialLanguage] || ''); const [language, setLanguage] = useState(initialLanguage); const [output, setOutput] = useState(''); const [isRunning, setIsRunning] = useState(false); const [error, setError] = useState(null); const [executionTime, setExecutionTime] = useState(null); const [copied, setCopied] = useState(false); const [isExpanded, setIsExpanded] = useState(false); const [showLanguageDropdown, setShowLanguageDropdown] = useState(false); const [stdin, setStdin] = useState(''); const [showStdin, setShowStdin] = useState(false); const textareaRef = useRef(null); const lineNumbersRef = useRef(null); const dropdownRef = useRef(null); // Calculate line numbers const lineCount = code.split('\n').length; const lineNumbers = Array.from({ length: lineCount }, (_, i) => i + 1); // Sync scroll between textarea and line numbers const handleScroll = useCallback((e) => { if (lineNumbersRef.current) { lineNumbersRef.current.scrollTop = e.target.scrollTop; } }, []); // Handle code change const handleCodeChange = (e) => { const newCode = e.target.value; setCode(newCode); onCodeChange?.(newCode, language); }; // Handle language change const handleLanguageChange = (langId) => { setLanguage(langId); // If code is empty or default template, switch to new template if (!code || Object.values(DEFAULT_TEMPLATES).includes(code)) { setCode(DEFAULT_TEMPLATES[langId] || ''); } setShowLanguageDropdown(false); onCodeChange?.(code, langId); }; // Handle Tab key for indentation const handleKeyDown = (e) => { if (e.key === 'Tab') { e.preventDefault(); const start = e.target.selectionStart; const end = e.target.selectionEnd; const newCode = code.substring(0, start) + ' ' + code.substring(end); setCode(newCode); // Move cursor after the inserted spaces setTimeout(() => { e.target.selectionStart = e.target.selectionEnd = start + 4; }, 0); } // Ctrl/Cmd + Enter to run if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { e.preventDefault(); handleRun(); } }; // Run code const handleRun = async () => { if (isRunning || !code.trim()) return; setIsRunning(true); setError(null); setOutput(''); setExecutionTime(null); const startTime = Date.now(); try { const token = getToken(); const response = await fetch(apiEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', ...(token && { Authorization: `Bearer ${token}` }), }, body: JSON.stringify({ code, language, stdin: stdin || undefined, timeout: 10 }), }); const data = await response.json(); const elapsed = Date.now() - startTime; setExecutionTime(elapsed); // Handle HTTP error responses (e.g., 503 when runtime not installed) if (!response.ok) { const errorMsg = data.detail || data.error || `Server error (${response.status})`; setError(errorMsg); onRun?.({ success: false, error: errorMsg, language, executionTime: elapsed }); } else if (data.success) { setOutput(data.output || 'Program executed successfully (no output)'); onRun?.({ success: true, output: data.output, language, executionTime: elapsed }); } else { setError(data.error || data.detail || 'Execution failed'); setOutput(data.output || ''); onRun?.({ success: false, error: data.error, language, executionTime: elapsed }); } } catch (err) { const elapsed = Date.now() - startTime; setExecutionTime(elapsed); setError(`Network error: ${err.message}`); onRun?.({ success: false, error: err.message, language, executionTime: elapsed }); } finally { setIsRunning(false); } }; // Reset code to template const handleReset = () => { setCode(DEFAULT_TEMPLATES[language] || ''); setOutput(''); setError(null); setExecutionTime(null); }; // Copy code const handleCopy = async () => { try { await navigator.clipboard.writeText(code); setCopied(true); setTimeout(() => setCopied(false), 2000); } catch (err) { console.error('Failed to copy:', err); } }; // Close dropdown on outside click useEffect(() => { const handleClickOutside = (e) => { if (dropdownRef.current && !dropdownRef.current.contains(e.target)) { setShowLanguageDropdown(false); } }; document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); }, []); const currentLang = LANGUAGES.find(l => l.id === language) || LANGUAGES[0]; return (
{/* Toolbar */}
{/* Language Selector */}
{showLanguageDropdown && (
{LANGUAGES.map((lang) => ( ))}
)}
{/* Input Toggle */} {/* Copy */} {/* Reset */} {/* Expand */} {/* Run Button */}
{/* Standard Input (Optional) */} {showStdin && (