commit 8058d5e9281f1adb34c3b34c661d2347f4047026
parent ff407690ce63ff2422b3f914d45499bc5ef32057
Author: Amit Dutta <amitdutta4255@gmail.com>
Date: Wed, 14 Jan 2026 10:36:47 +0530
[2026-01-14]
Diffstat:
| M | backend.py | | | 231 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------- |
| M | public/c.html | | | 479 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------- |
| M | public/cpp.html | | | 446 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------- |
| M | public/docs.html | | | 431 | ++++++++++++++++++++++++++++++++++++++++++++++++------------------------------- |
| M | public/index.html | | | 116 | ++++++++++++++++++++++++++++++++++++++++++------------------------------------- |
| M | public/python.html | | | 401 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------ |
6 files changed, 1455 insertions(+), 649 deletions(-)
diff --git a/backend.py b/backend.py
@@ -1,5 +1,5 @@
import asyncio
-import aiohttp # Added missing import
+import aiohttp
from aiohttp import web
import json
import subprocess
@@ -8,10 +8,12 @@ import sys
import threading
import re
import importlib.util
-import platform
+import platform
+import uuid
+import shutil
# ==================================================================================
-# CLOUD COMPILER SERVER (AIOHTTP) - RENDER COMPATIBLE
+# CLOUD COMPILER SERVER (AIOHTTP) - RENDER COMPATIBLE - SECURE VERSION
# ==================================================================================
# Get API Key from Environment
@@ -35,7 +37,7 @@ def install_package(package_name, ws=None, loop=None):
except Exception as e:
error_msg = f"Failed to install {package_name}: {e}"
print(error_msg)
- if ws and loop:
+ if ws and loop:
asyncio.run_coroutine_threadsafe(
ws.send_json({'type': 'stdout', 'data': f"\n[Error] {error_msg}\n"}),
loop
@@ -61,6 +63,13 @@ async def handle_client(request):
await ws.prepare(request)
print(f"Client connected: {request.remote}")
+
+ # Create unique session directory for this client
+ session_id = str(uuid.uuid4())
+ session_dir = os.path.join("temp_sessions", session_id)
+ os.makedirs(session_dir, exist_ok=True)
+ print(f"Created session directory: {session_dir}")
+
process = None
# Helper to read output stream in a separate thread
@@ -79,7 +88,7 @@ async def handle_client(request):
ws.send_json({'type': 'status', 'msg': 'Program finished'}),
loop
)
- except Exception:
+ except Exception:
pass
try:
@@ -97,12 +106,19 @@ async def handle_client(request):
loop = asyncio.get_running_loop()
await loop.run_in_executor(None, check_and_install_packages, code, ws, loop)
- filename = "temp_script.py"
+ filename = os.path.join(session_dir, "temp_script.py")
with open(filename, "w", encoding="utf-8") as f:
f.write(code)
- env = os.environ.copy()
- env["PYTHONIOENCODING"] = "utf-8"
+ # 🔒 SECURITY FIX: Sanitized environment (no API keys)
+ safe_env = {
+ "PYTHONIOENCODING": "utf-8",
+ "PATH": os.environ.get("PATH", ""),
+ "PYTHONPATH": os.environ.get("PYTHONPATH", ""),
+ "HOME": os.environ.get("HOME", ""),
+ "USER": os.environ.get("USER", ""),
+ "LANG": os.environ.get("LANG", "en_US.UTF-8"),
+ }
process = subprocess.Popen(
[sys.executable, "-u", filename],
@@ -112,30 +128,39 @@ async def handle_client(request):
text=True,
bufsize=0,
encoding='utf-8',
- env=env
+ env=safe_env # ✅ Use sanitized environment
)
await ws.send_json({'type': 'status', 'msg': 'Running Python...'})
# --- C HANDLING ---
elif language == 'c':
- filename = "temp_code.c"
- executable = "./a.out" if platform.system() != "Windows" else "a.exe"
+ filename = os.path.join(session_dir, "temp_code.c")
+ executable = os.path.join(session_dir, "a.out") if platform.system() != "Windows" else os.path.join(session_dir, "a.exe")
with open(filename, "w", encoding="utf-8") as f:
f.write(code)
- await ws.send_json({'type': 'status', 'msg': 'Compiling C...'})
+ await ws.send_json({'type': 'status', 'msg': 'Compiling C...'})
+
+ # 🔒 SECURITY: Sanitized environment
+ safe_env = {
+ "PATH": os.environ.get("PATH", ""),
+ "HOME": os.environ.get("HOME", ""),
+ "USER": os.environ.get("USER", ""),
+ "LANG": os.environ.get("LANG", "en_US.UTF-8"),
+ "TMPDIR": os.environ.get("TMPDIR", "/tmp"),
+ }
compile_process = subprocess.run(
- ["gcc", filename, "-o", executable],
+ ["gcc", filename, "-o", executable, "-lm", "-pthread"],
capture_output=True,
- text=True
+ text=True,
+ env=safe_env
)
if compile_process.returncode != 0:
await ws.send_json({'type': 'stdout', 'data': f"Compilation Error:\n{compile_process.stderr}"})
- # Send explicit error status so frontend knows to show AI button
- await ws.send_json({'type': 'status', 'msg': 'Compilation Failed'})
+ await ws.send_json({'type': 'status', 'msg': 'Compilation Failed'})
continue
await ws.send_json({'type': 'status', 'msg': 'Running C Binary...'})
@@ -147,23 +172,35 @@ async def handle_client(request):
stderr=subprocess.STDOUT,
text=True,
bufsize=0,
- encoding='utf-8'
+ encoding='utf-8',
+ env=safe_env # ✅ Sanitized environment
)
# --- C++ HANDLING ---
elif language == 'cpp':
- filename = "temp_code.cpp"
- executable = "./a.out" if platform.system() != "Windows" else "a.exe"
+ # FIXED: Removed the space in "temp_code.cpp"
+ filename = os.path.join(session_dir, "temp_code.cpp")
+ executable = os.path.join(session_dir, "a.out") if platform.system() != "Windows" else os.path.join(session_dir, "a.exe")
with open(filename, "w", encoding="utf-8") as f:
f.write(code)
await ws.send_json({'type': 'status', 'msg': 'Compiling C++...'})
+ # 🔒 SECURITY: Sanitized environment
+ safe_env = {
+ "PATH": os.environ.get("PATH", ""),
+ "HOME": os.environ.get("HOME", ""),
+ "USER": os.environ.get("USER", ""),
+ "LANG": os.environ.get("LANG", "en_US.UTF-8"),
+ "TMPDIR": os.environ.get("TMPDIR", "/tmp"),
+ }
+
compile_process = subprocess.run(
- ["g++", filename, "-o", executable],
+ ["g++", filename, "-o", executable, "-lm", "-pthread"],
capture_output=True,
- text=True
+ text=True,
+ env=safe_env
)
if compile_process.returncode != 0:
@@ -180,7 +217,8 @@ async def handle_client(request):
stderr=subprocess.STDOUT,
text=True,
bufsize=0,
- encoding='utf-8'
+ encoding='utf-8',
+ env=safe_env # ✅ Sanitized environment
)
if process:
@@ -198,75 +236,145 @@ async def handle_client(request):
except Exception:
pass
- # --- NEW: AI FIX HANDLER ---
+ # --- AI FIX HANDLER ---
elif data.get('type') == 'ai_fix':
code = data.get('code')
error_log = data.get('error')
- language = data.get('language', 'c') # Default to C, but respect frontend input
+ language = data.get('language', 'c')
- if not GEMINI_API_KEY:
- await ws.send_json({'type': 'ai_error', 'msg': 'Server Error: GEMINI_API_KEY not configured.'})
+ if not GEMINI_API_KEY:
+ await ws.send_json({'type': 'ai_error', 'msg': 'Server Error: GEMINI_API_KEY not configured.'})
continue
- # Construct Prompt
- json_format = '{\n "explanation": "Brief explanation of the bug (max 2 sentences)",\n "fixed_code": "The full corrected code"\n}'
-
- prompt = (
- f"You are an expert {language.upper()} programming debugger.\n"
- f"CODE:\n{code}\n"
- f"ERROR OUTPUT:\n{error_log}\n"
- "TASK:\n"
- "1. Analyze the error.\n"
- "2. Provide a concise explanation.\n"
- "3. Provide the COMPLETE corrected code.\n"
- "RESPONSE FORMAT:\n"
- "Return ONLY a valid JSON object with no markdown formatting or backticks:\n"
- f"{json_format}"
- )
+ # Construct Prompt - Ask for JSON in plain text
+ prompt = f"""You are an expert {language.upper()} debugger. Analyze this code and error, then respond with ONLY a valid JSON object (no markdown, no backticks).
+
+CODE:
+{code}
+
+ERROR:
+{error_log}
+
+Respond in this exact JSON format:
+{{
+ "explanation": "Brief 1-2 sentence explanation of the bug",
+ "fixed_code": "Complete corrected code here"
+}}"""
try:
+ # ✅ FIXED: Use correct stable model endpoint
api_url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-09-2025:generateContent?key={GEMINI_API_KEY}"
+ print(f"[AI] Calling Gemini API...")
+
async with aiohttp.ClientSession() as session:
- async with session.post(api_url, json={
- "contents": [{ "parts": [{ "text": prompt }] }],
- "generationConfig": { "responseMimeType": "application/json" }
- }) as resp:
+ async with session.post(
+ api_url,
+ headers={"Content-Type": "application/json"},
+ json={
+ "contents": [{"parts": [{"text": prompt}]}],
+ "generationConfig": {
+ "temperature": 0.1,
+ "maxOutputTokens": 2048
+ }
+ }
+ ) as resp:
+ response_text = await resp.text()
+
+ print(f"[AI] Status: {resp.status}")
+ print(f"[AI] Response preview: {response_text[: 200]}")
+
if resp.status != 200:
- error_text = await resp.text()
- await ws.send_json({'type': 'ai_error', 'msg': f"AI API Error: {error_text}"})
- else:
- result = await resp.json()
- # Extract text from Gemini response structure
- content_text = result.get('candidates', [{}])[0].get('content', {}).get('parts', [{}])[0].get('text', '{}')
-
- # Send back to client
await ws.send_json({
- 'type': 'ai_response',
- 'data': json.loads(content_text)
+ 'type': 'ai_error',
+ 'msg': f"AI API Error (Status {resp.status})"
})
+ else:
+ try:
+ result = json.loads(response_text)
+
+ # Extract AI response
+ ai_text = result['candidates'][0]['content']['parts'][0]['text']
+
+ # Clean markdown if present
+ ai_text = ai_text.strip()
+ if ai_text.startswith('```json'):
+ ai_text = ai_text.split('```json')[1].split('```')[0].strip()
+ elif ai_text.startswith('```'):
+ ai_text = ai_text.split('```')[1].split('```')[0].strip()
+
+ # Parse JSON
+ parsed_response = json.loads(ai_text)
+
+ # Validate structure
+ if 'explanation' in parsed_response and 'fixed_code' in parsed_response:
+ print(f"[AI] Success! Sending response to client")
+ await ws.send_json({
+ 'type': 'ai_response',
+ 'data': parsed_response
+ })
+ else:
+ print(f"[AI] Invalid structure: {parsed_response}")
+ await ws.send_json({
+ 'type': 'ai_error',
+ 'msg': 'Invalid AI response format'
+ })
+
+ except (KeyError, IndexError) as e:
+ print(f"[AI] API structure error: {e}")
+ await ws.send_json({
+ 'type': 'ai_error',
+ 'msg': 'Unexpected API response'
+ })
+ except json.JSONDecodeError as e:
+ print(f"[AI] JSON parse error: {e}")
+ print(f"[AI] Attempted to parse: {ai_text[: 300]}")
+ await ws.send_json({
+ 'type': 'ai_error',
+ 'msg': 'AI returned invalid JSON'
+ })
- except Exception as e:
- print(f"AI Error: {e}")
- await ws.send_json({'type': 'ai_error', 'msg': f"Server Processing Error: {str(e)}"})
+ except aiohttp.ClientError as e:
+ print(f"[AI] Network error: {e}")
+ await ws.send_json({
+ 'type': 'ai_error',
+ 'msg': 'Network error'
+ })
+ except Exception as e:
+ print(f"[AI] Unexpected error: {e}")
+ import traceback
+ traceback.print_exc()
+ await ws.send_json({
+ 'type': 'ai_error',
+ 'msg': f'Server error: {type(e).__name__}'
+ })
elif msg.type == web.WSMsgType.ERROR:
print(f'ws connection closed with exception {ws.exception()}')
finally:
- if process:
+ if process:
try:
process.kill()
except:
pass
+ # Clean up session directory
+ try:
+ shutil.rmtree(session_dir, ignore_errors=True)
+ print(f"Cleaned up session directory: {session_dir}")
+ except:
+ pass
print("Client disconnected")
return ws
async def main():
+ # Create temp_sessions directory if it doesn't exist
+ os.makedirs("temp_sessions", exist_ok=True)
+ print("temp_sessions directory ready")
+
port = int(os.environ.get("PORT", 8765))
app = web.Application()
- # Route root path to the unified handler
app.add_routes([web.get('/', handle_client)])
runner = web.AppRunner(app)
@@ -279,4 +387,4 @@ async def main():
await asyncio.Event().wait()
if __name__ == "__main__":
- asyncio.run(main())-
\ No newline at end of file
+ asyncio.run(main())
diff --git a/public/c.html b/public/c.html
@@ -15,6 +15,15 @@
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
+ <!-- CodeMirror 5 (Professional Editor) -->
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.css">
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/theme/dracula.min.css">
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.js"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/clike/clike.min.js"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/addon/edit/closebrackets.min.js"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/addon/edit/matchbrackets.min.js"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/addon/selection/active-line.min.js"></script>
+
<!-- Xterm.js -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.css" />
<script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.js"></script>
@@ -59,43 +68,74 @@
}
@keyframes scanline { 0% { top: -10vh; } 100% { top: 110vh; } }
- /* Custom Editor Styles */
- .editor-container {
- position: relative;
- width: 100%;
- height: 100%;
- overflow: hidden;
+ /* CodeMirror Customization */
+ .CodeMirror {
+ height: 100% !important;
font-family: 'JetBrains Mono', monospace;
font-size: 13px;
- line-height: 1.5;
+ background-color: #0c0f16 !important; /* Slightly lighter than pure black */
+ color: #f8f8f2;
+ }
+ .CodeMirror-gutters {
+ background-color: #0c0f16 !important;
+ border-right: 1px solid rgba(255,255,255,0.05) !important;
+ }
+ .CodeMirror-linenumber {
+ color: #555 !important;
+ }
+ .CodeMirror-cursor {
+ border-left: 2px solid var(--accent) !important;
+ }
+ .CodeMirror-selected {
+ background: rgba(190, 242, 100, 0.2) !important;
+ }
+ .cm-s-dracula .CodeMirror-activeline-background {
+ background: rgba(255, 255, 255, 0.03) !important;
}
- /* The actual textarea for typing */
- #code-input {
- position: absolute;
- left: 0;
- top: 0;
- width: 100%;
- height: 100%;
- padding: 1rem;
- border: none;
- outline: none;
- background: transparent;
- color: #e2e8f0; /* Visible Text */
- caret-color: #fff; /* Cursor visible */
- z-index: 1;
- resize: none;
- white-space: pre;
- overflow: auto;
- }
-
- .xterm-viewport { overflow-y: auto !important; }
-
- ::selection { background: var(--accent); color: black; }
+ /* Scrollbar styling */
+ .CodeMirror-scroll::-webkit-scrollbar,
+ .xterm-viewport::-webkit-scrollbar,
+ .custom-scrollbar::-webkit-scrollbar {
+ width: 6px; height: 6px;
+ }
+ .CodeMirror-scroll::-webkit-scrollbar-track,
+ .xterm-viewport::-webkit-scrollbar-track,
+ .custom-scrollbar::-webkit-scrollbar-track {
+ background: #050505;
+ }
+ .CodeMirror-scroll::-webkit-scrollbar-thumb,
+ .xterm-viewport::-webkit-scrollbar-thumb,
+ .custom-scrollbar::-webkit-scrollbar-thumb {
+ background: #333; border-radius: 3px;
+ }
- .custom-scrollbar::-webkit-scrollbar { width: 6px; height: 6px; }
- .custom-scrollbar::-webkit-scrollbar-track { background: #050505; }
- .custom-scrollbar::-webkit-scrollbar-thumb { background: #333; border-radius: 3px; }
+ /* Mobile Helper Bar */
+ #mobile-helper-bar {
+ display: none; /* Shown via JS on mobile */
+ background: #151515;
+ border-top: 1px solid rgba(255,255,255,0.1);
+ padding: 8px;
+ overflow-x: auto;
+ white-space: nowrap;
+ -webkit-overflow-scrolling: touch;
+ }
+ @media (max-width: 768px) {
+ #mobile-helper-bar { display: flex; gap: 8px; }
+ }
+ .helper-key {
+ background: rgba(255,255,255,0.1);
+ color: var(--accent);
+ border: 1px solid rgba(255,255,255,0.1);
+ border-radius: 4px;
+ padding: 6px 12px;
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 12px;
+ font-weight: bold;
+ cursor: pointer;
+ user-select: none;
+ }
+ .helper-key:active { background: var(--accent); color: black; }
/* Toast Notification */
.toast {
@@ -182,7 +222,7 @@
to { transform: translateY(0); opacity: 1; }
}
- /* AI Modal */
+ /* Modals (AI & Clear) */
.modal-overlay {
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.8); backdrop-filter: blur(5px);
@@ -194,7 +234,6 @@
background: #0f0f0f;
border: 1px solid var(--accent);
width: 90%; max-width: 600px;
- max-height: 80vh;
border-radius: 12px;
display: flex; flex-direction: column;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.5), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
@@ -206,6 +245,31 @@
0% { transform: scale(0.9); opacity: 0; }
100% { transform: scale(1); opacity: 1; }
}
+
+ /* Editor Toolbar Buttons */
+ .editor-btn {
+ background: rgba(255,255,255,0.05);
+ border: 1px solid rgba(255,255,255,0.1);
+ color: #737373;
+ width: 32px;
+ height: 32px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 4px;
+ transition: all 0.2s;
+ cursor: pointer;
+ }
+ .editor-btn:hover {
+ background: rgba(255,255,255,0.1);
+ color: #fff;
+ border-color: rgba(255,255,255,0.3);
+ }
+ .editor-btn.danger:hover {
+ background: rgba(239, 68, 68, 0.1);
+ color: #ef4444;
+ border-color: #ef4444;
+ }
</style>
</head>
<body class="antialiased selection:bg-lime-300 selection:text-black">
@@ -220,9 +284,29 @@
<span>Error detected. <span id="ask-ai-link" class="ai-link">Ask AI</span> to fix it.</span>
</div>
+ <!-- Clear Confirmation Modal -->
+ <div id="clear-modal" class="modal-overlay">
+ <div class="modal-content max-w-[320px] p-0 border-red-500/50">
+ <div class="bg-red-500/10 px-6 py-4 border-b border-red-500/20">
+ <h3 class="font-heading font-bold text-lg text-red-500 flex items-center gap-2">
+ <i class="fas fa-exclamation-circle"></i> CLEAR CODE
+ </h3>
+ </div>
+ <div class="p-6 text-center">
+ <p class="text-neutral-300 text-sm font-sans mb-6">Are you sure you want to clear the editor? This action will remove all current code.</p>
+ <div class="flex justify-center gap-3">
+ <button id="cancel-clear-btn" class="px-4 py-2 rounded text-xs font-mono font-bold uppercase text-neutral-400 hover:text-white hover:bg-white/10 transition-all border border-transparent">Cancel</button>
+ <button id="confirm-clear-btn" class="bg-red-500/10 hover:bg-red-500 hover:text-black text-red-500 border border-red-500/20 px-4 py-2 rounded text-xs font-mono font-bold uppercase transition-all flex items-center gap-2">
+ <i class="fas fa-trash-alt"></i> Confirm
+ </button>
+ </div>
+ </div>
+ </div>
+ </div>
+
<!-- AI Fix Modal -->
<div id="ai-modal" class="modal-overlay">
- <div class="modal-content">
+ <div class="modal-content max-h-[80vh]">
<!-- Modal Header -->
<div class="bg-white/5 px-6 py-4 border-b border-white/10 flex justify-between items-center">
<h3 class="font-heading font-bold text-lg text-lime-300 flex items-center gap-2">
@@ -270,15 +354,29 @@
<!-- Header (Responsive) -->
<header class="h-auto md:h-16 flex flex-col md:flex-row items-center justify-between px-4 py-3 md:px-6 shrink-0 border-b border-white/10 z-20 bg-neutral-900/50 backdrop-blur-md gap-3 md:gap-4">
- <!-- Top Row: Title & Status -->
- <div class="flex items-center justify-between w-full md:w-auto">
+ <!-- Top Row: Title & Editor Tools -->
+ <div class="flex items-center justify-between w-full md:w-auto gap-6">
<h1 class="text-lg md:text-xl font-heading font-bold text-white tracking-tight flex items-center">
<span class="text-lime-300 mr-2">//</span>C.COMPILER
<span id="conn-dot" class="pulse-dot disconnected ml-3" title="Disconnected"></span>
</h1>
+ <!-- Editor Tools -->
+ <div class="flex items-center gap-2">
+ <button id="undo-btn" class="editor-btn" title="Undo (Ctrl+Z)">
+ <i class="fas fa-undo text-xs"></i>
+ </button>
+ <button id="redo-btn" class="editor-btn" title="Redo (Ctrl+Y)">
+ <i class="fas fa-redo text-xs"></i>
+ </button>
+ <div class="w-px h-4 bg-white/10 mx-1"></div>
+ <button id="clear-code-btn" class="editor-btn danger" title="Clear Code">
+ <i class="fas fa-times text-xs"></i>
+ </button>
+ </div>
+
<!-- Mobile Only: Exit Link -->
- <a href="./index.html" class="md:hidden font-mono text-[10px] text-neutral-500 hover:text-white uppercase tracking-wider transition-colors">[ EXIT ]</a>
+ <a href="./index.html" class="md:hidden font-mono text-[10px] text-neutral-500 hover:text-white uppercase tracking-wider transition-colors ml-auto">[ EXIT ]</a>
</div>
<!-- Middle: Connection Text -->
@@ -290,6 +388,10 @@
<div class="flex items-center gap-2 w-full md:w-auto justify-end">
<a href="./index.html" class="hidden md:inline font-mono text-[10px] text-neutral-500 hover:text-white uppercase tracking-wider transition-colors mr-2">[ EXIT ]</a>
+ <button id="download-btn" class="bg-white/5 hover:bg-neutral-800 text-white border border-white/10 px-3 py-2 rounded font-mono text-[10px] font-bold uppercase tracking-wider flex items-center justify-center transition-all" title="Download .c">
+ <i class="fas fa-download"></i>
+ </button>
+
<button id="reset-btn" class="bg-white/5 hover:bg-neutral-800 text-white border border-white/10 px-3 py-2 rounded font-mono text-[10px] font-bold uppercase tracking-wider flex items-center justify-center transition-all" title="Reset Terminal">
<i class="fas fa-rotate-left"></i>
</button>
@@ -297,10 +399,6 @@
<button id="share-btn" class="bg-white/5 hover:bg-neutral-800 text-white border border-white/10 px-3 py-2 rounded font-mono text-[10px] font-bold uppercase tracking-wider flex items-center justify-center transition-all" title="Share Code">
<i class="fas fa-share-alt"></i>
</button>
-
- <button id="copy-btn" class="bg-white/5 hover:bg-neutral-800 text-white border border-white/10 px-3 py-2 rounded font-mono text-[10px] font-bold uppercase tracking-wider flex items-center justify-center transition-all" title="Copy Output">
- <i class="fas fa-copy"></i>
- </button>
<button id="run-btn" disabled class="bg-white/5 hover:bg-lime-300 hover:text-black disabled:opacity-50 disabled:cursor-not-allowed text-white border border-white/10 px-4 py-2 rounded font-mono text-xs font-bold uppercase tracking-wider flex items-center gap-2 transition-all flex-1 md:flex-none justify-center shadow-lg shadow-black/50">
<i class="fas fa-play text-[10px]"></i> <span class="md:hidden lg:inline">EXECUTE</span><span class="hidden md:inline lg:hidden">RUN</span>
@@ -315,37 +413,44 @@
<div class="flex-1 flex flex-col h-[60%] md:h-auto min-h-0 border-b md:border-b-0 md:border-r border-white/10 relative group">
<div class="bg-black/20 px-4 py-2 text-[10px] font-mono text-lime-300 uppercase border-b border-white/10 flex justify-between items-center shrink-0">
<span>SRC // main.c</span>
- <span class="text-neutral-600">GCC 11.2</span>
+ <span class="text-neutral-600">GCC 9.4.0</span>
</div>
- <!-- Custom Editor Area -->
- <div class="editor-container relative bg-[#0c0f16]">
- <textarea id="code-input" spellcheck="false" autocapitalize="off" autocomplete="off" autocorrect="off" class="custom-scrollbar">#include <stdio.h>
+ <!-- CodeMirror Container -->
+ <div id="editor-container" class="relative bg-[#0c0f16] flex-1 overflow-hidden">
+ <textarea id="code-input" class="hidden">#include <stdio.h>
int main() {
- int n, i;
- printf("Enter count: ");
- // Manual flush required for buffered environments
- fflush(stdout);
-
- if (scanf("%d", &n)) {
- printf("Counting to %d:\n", n);
- for(i=1; i<=n; i++) {
- printf("%d ", i);
- }
- printf("\nDone.");
- }
+ printf("Hello C Compiler!\n");
return 0;
}</textarea>
</div>
+ <!-- Mobile Helper Bar -->
+ <div id="mobile-helper-bar">
+ <div class="helper-key" data-char=" ">Tab</div>
+ <div class="helper-key" data-char=";">;</div>
+ <div class="helper-key" data-char="=">=</div>
+ <div class="helper-key" data-char="()">()</div>
+ <div class="helper-key" data-char="[]">[]</div>
+ <div class="helper-key" data-char="{}">{}</div>
+ <div class="helper-key" data-char='""'>""</div>
+ <div class="helper-key" data-char="''">''</div>
+ <div class="helper-key" data-char="#">#</div>
+ <div class="helper-key" data-char="+">+</div>
+ <div class="helper-key" data-char="-">-</div>
+ <div class="helper-key" data-char="printf()">printf</div>
+ </div>
</div>
<!-- Terminal -->
- <div class="flex-1 flex flex-col h-[40%] md:h-auto min-h-0 bg-[#050505] relative">
+ <div class="flex-1 flex flex-col h-[40%] md:h-auto min-h-0 bg-[#050505]">
<div class="bg-black/20 px-4 py-2 text-[10px] font-mono text-lime-300 uppercase border-b border-white/10 flex justify-between items-center shrink-0">
<span>OUT // TERMINAL</span>
- <span class="text-neutral-600">XTERM.JS</span>
+ <div class="flex gap-2">
+ <button id="copy-btn" class="text-neutral-500 hover:text-white transition-colors" title="Copy Output"><i class="fas fa-copy"></i></button>
+ <span class="text-neutral-600">XTERM.JS</span>
+ </div>
</div>
<div class="flex-1 relative">
<div id="terminal-container" class="absolute inset-0 w-full h-full p-2 overflow-hidden"></div>
@@ -374,10 +479,12 @@ int main() {
const db = getFirestore(app);
const auth = getAuth(app);
+ // Attempt initial sign-in
signInAnonymously(auth).catch((error) => {
console.warn("Auto-Auth failed. App will try unauthenticated access.", error.code);
});
+ // --- Helper: Generate ID ---
function generateId(length = 7) {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result = '';
@@ -392,11 +499,19 @@ int main() {
const resetBtn = document.getElementById('reset-btn');
const shareBtn = document.getElementById('share-btn');
const copyBtn = document.getElementById('copy-btn');
+ const downloadBtn = document.getElementById('download-btn');
const connDot = document.getElementById('conn-dot');
const connectionStatus = document.getElementById('connection-status');
- const codeInput = document.getElementById('code-input');
const toast = document.getElementById('toast');
+ // Editor Tool Elements
+ const undoBtn = document.getElementById('undo-btn');
+ const redoBtn = document.getElementById('redo-btn');
+ const clearCodeBtn = document.getElementById('clear-code-btn');
+ const clearModal = document.getElementById('clear-modal');
+ const cancelClearBtn = document.getElementById('cancel-clear-btn');
+ const confirmClearBtn = document.getElementById('confirm-clear-btn');
+
// AI Elements
const aiSuggestion = document.getElementById('ai-suggestion');
const askAiLink = document.getElementById('ask-ai-link');
@@ -415,38 +530,121 @@ int main() {
let currentLine = "";
let startTime = 0;
let lastSuggestedCode = "";
+ let outputBuffer = "";
+
+ // --- CodeMirror Setup ---
+ const editor = CodeMirror.fromTextArea(document.getElementById("code-input"), {
+ mode: "text/x-csrc",
+ theme: "dracula",
+ lineNumbers: true,
+ indentUnit: 4,
+ matchBrackets: true,
+ autoCloseBrackets: true,
+ styleActiveLine: true,
+ inputStyle: "contenteditable",
+ viewportMargin: Infinity,
+ extraKeys: {
+ "Backspace": function(cm) {
+ if (cm.somethingSelected()) {
+ cm.execCommand("delCharBefore");
+ return;
+ }
+ var cursor = cm.getCursor();
+ var line = cm.getLine(cursor.line);
+ // If cursor is not at start, and everything before it is whitespace
+ if (cursor.ch > 0 && /^\s+$/.test(line.slice(0, cursor.ch))) {
+ cm.execCommand("indentLess");
+ } else {
+ cm.execCommand("delCharBefore");
+ }
+ }
+ },
+ });
+
+ // Fix resize issues
+ window.addEventListener('resize', () => editor.refresh());
+
+ // Local Auto-Save
+ const LOCAL_STORAGE_KEY = 'c_compiler_code_autosave';
+ editor.on('change', () => {
+ localStorage.setItem(LOCAL_STORAGE_KEY, editor.getValue());
+ });
+
+ // Load Auto-Save on Start (if not shared)
+ const savedCode = localStorage.getItem(LOCAL_STORAGE_KEY);
+ if (savedCode && !window.location.search.includes('share') && !window.location.search.includes('code')) {
+ editor.setValue(savedCode);
+ }
+
+ // --- Editor Toolbar Logic ---
+ undoBtn.addEventListener('click', () => { editor.undo(); editor.focus(); });
+ redoBtn.addEventListener('click', () => { editor.redo(); editor.focus(); });
- // --- EDITOR LOGIC ---
- codeInput.addEventListener('keydown', function(e) {
- if (e.key === 'Tab') {
- e.preventDefault();
- const start = this.selectionStart;
- const end = this.selectionEnd;
- this.setRangeText(' ', start, end, 'end');
- } else if (e.key === 'Enter') {
- e.preventDefault();
- const start = this.selectionStart;
- const end = this.selectionEnd;
- const value = this.value;
- const lineStart = value.lastIndexOf('\n', start - 1) + 1;
- const currentLineText = value.substring(lineStart, start);
- let match = currentLineText.match(/^(\s*)/);
- let indent = match ? match[1] : "";
- if (currentLineText.trim().endsWith('{')) indent += " ";
- this.setRangeText('\n' + indent, start, end, 'end');
- this.blur(); this.focus();
- } else if (e.key === '}') {
- const start = this.selectionStart;
- const end = this.selectionEnd;
- const value = this.value;
- const lineStart = value.lastIndexOf('\n', start - 1) + 1;
- const currentLineText = value.substring(lineStart, start);
- if (/^\s+$/.test(currentLineText) && currentLineText.length >= 4) {
- const newIndent = currentLineText.substring(0, currentLineText.length - 4);
- this.setSelectionRange(lineStart, start);
- this.setRangeText(newIndent, this.selectionStart, this.selectionEnd, 'end');
+ clearCodeBtn.addEventListener('click', () => {
+ clearModal.classList.add('open');
+ });
+
+ cancelClearBtn.addEventListener('click', () => {
+ clearModal.classList.remove('open');
+ });
+
+ confirmClearBtn.addEventListener('click', () => {
+ editor.setValue('');
+ editor.clearHistory();
+ editor.focus();
+ clearModal.classList.remove('open');
+ showToast("Editor Cleared");
+ });
+
+ clearModal.addEventListener('click', (e) => {
+ if (e.target === clearModal) clearModal.classList.remove('open');
+ });
+
+ // --- Mobile Helper Bar ---
+ document.querySelectorAll('.helper-key').forEach(key => {
+ key.addEventListener('click', (e) => {
+ const char = key.getAttribute('data-char');
+ const doc = editor.getDoc();
+ const cursor = doc.getCursor();
+
+ if (char === '()') {
+ doc.replaceRange('()', cursor);
+ doc.setCursor({line: cursor.line, ch: cursor.ch + 1});
+ } else if (char === '[]') {
+ doc.replaceRange('[]', cursor);
+ doc.setCursor({line: cursor.line, ch: cursor.ch + 1});
+ } else if (char === '{}') {
+ doc.replaceRange('{}', cursor);
+ doc.setCursor({line: cursor.line, ch: cursor.ch + 1});
+ } else if (char === '""') {
+ doc.replaceRange('""', cursor);
+ doc.setCursor({line: cursor.line, ch: cursor.ch + 1});
+ } else if (char === "''") {
+ doc.replaceRange("''", cursor);
+ doc.setCursor({line: cursor.line, ch: cursor.ch + 1});
+ } else if (char === "printf()") {
+ doc.replaceRange('printf("")', cursor);
+ doc.setCursor({line: cursor.line, ch: cursor.ch + 8});
+ } else {
+ doc.replaceRange(char, cursor);
}
- }
+ editor.focus();
+ });
+ });
+
+ // --- Download Button ---
+ downloadBtn.addEventListener('click', () => {
+ const code = editor.getValue();
+ const blob = new Blob([code], { type: 'text/x-csrc' });
+ const url = window.URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = 'main.c';
+ document.body.appendChild(a);
+ a.click();
+ window.URL.revokeObjectURL(url);
+ document.body.removeChild(a);
+ showToast("Downloaded main.c");
});
window.addEventListener('load', async () => {
@@ -455,6 +653,7 @@ int main() {
const sharedCodeOld = params.get('code');
if (shareId) {
+ // Fetch from Firebase
try {
toast.textContent = "Loading shared code...";
toast.classList.add('show');
@@ -464,27 +663,34 @@ int main() {
if (docSnap.exists()) {
const data = docSnap.data();
- codeInput.value = data.code;
+ editor.setValue(data.code);
+
+ // Restore Output if it exists
if (data.output && term) {
term.write('\r\n\x1b[38;2;115;115;115m> PREVIOUS OUTPUT:\x1b[0m\r\n');
term.write(data.output.replace(/\n/g, '\r\n'));
term.write('\r\n\x1b[38;2;115;115;115m> ----------------\x1b[0m\r\n');
}
+
toast.textContent = "Code loaded successfully";
} else {
toast.textContent = "Shared code not found";
}
setTimeout(() => toast.classList.remove('show'), 3000);
} catch (e) {
- console.error("Fetch error:", e);
- toast.textContent = "Error loading code";
- setTimeout(() => toast.classList.remove('show'), 3000);
+ console.error("Fetch error:", e);
+ if (e.code === 'permission-denied') {
+ toast.textContent = "Error: Database Access Denied";
+ } else {
+ toast.textContent = "Error loading code";
+ }
+ setTimeout(() => toast.classList.remove('show'), 3000);
}
} else if (sharedCodeOld) {
- try { codeInput.value = atob(sharedCodeOld); }
+ // Fallback for Base64
+ try { editor.setValue(atob(sharedCodeOld)); }
catch(e) {}
}
-
setTimeout(connect, 500);
});
@@ -522,12 +728,12 @@ int main() {
let lineText = line.translateToString(true);
// Filter system messages
if(lineText.trim().startsWith("> [") ||
- lineText.trim().startsWith("> ./a.out") ||
lineText.includes("SYSTEM READY") ||
lineText.includes("ATTEMPTING") ||
lineText.includes("CONNECTION") ||
lineText.includes("PREVIOUS OUTPUT") ||
- lineText.includes("> ----------------")
+ lineText.includes("> ----------------") ||
+ lineText.includes("> ./a.out")
) {
continue;
}
@@ -537,6 +743,18 @@ int main() {
return text.trim();
}
+ // Custom Key Handler for Ctrl+C Copy
+ term.attachCustomKeyEventHandler((arg) => {
+ if (arg.ctrlKey && arg.code === "KeyC" && arg.type === "keydown") {
+ const selection = term.getSelection();
+ if (selection) {
+ fallbackCopyTextToClipboard(selection);
+ return false;
+ }
+ }
+ return true;
+ });
+
term.write('\x1b[38;2;115;115;115m> SYSTEM READY.\r\n> ATTEMPTING CONNECTION...\x1b[0m\r\n');
// --- WebSocket Logic ---
@@ -571,9 +789,10 @@ int main() {
const msg = JSON.parse(event.data);
if (msg.type === 'stdout') {
let text = msg.data;
-
+ outputBuffer += text;
+
// Trigger AI Overlay on Error
- if (text.toLowerCase().includes("error:") || text.toLowerCase().includes("warning:")) {
+ if (outputBuffer.toLowerCase().includes("error") || outputBuffer.toLowerCase().includes("warning")) {
aiSuggestion.classList.add('visible');
}
@@ -585,8 +804,6 @@ int main() {
if (msg.msg.includes("Program finished")) {
const duration = (Date.now() - startTime) / 1000;
term.write(`\r\n\x1b[38;2;115;115;115m[Finished in ${duration.toFixed(3)}s]\x1b[0m\r\n`);
- } else if (msg.msg === "Compilation Failed") {
- aiSuggestion.classList.add('visible');
}
} else if (msg.type === 'ai_response') {
handleAiResponse(msg.data);
@@ -599,15 +816,17 @@ int main() {
// --- Run Logic ---
runBtn.addEventListener('click', () => {
if (!socket || socket.readyState !== WebSocket.OPEN) return;
+ if (document.activeElement) document.activeElement.blur();
- document.activeElement.blur();
term.reset();
currentLine = "";
- aiSuggestion.classList.remove('visible'); // Hide previous suggestions
+ outputBuffer = "";
+ aiSuggestion.classList.remove('visible');
- let code = codeInput.value;
+ let code = editor.getValue();
code = code.replace(/\u00A0/g, " ").replace(/[\u2018\u2019]/g, "'").replace(/[\u201C\u201D]/g, '"');
+ // Auto-Flush Injection: Fixes buffered output issues for students
function injectAutoFlush(source) {
const mainRegex = /(int|void)\s+main\s*\([^)]*\)\s*\{/;
const match = source.match(mainRegex);
@@ -620,7 +839,7 @@ int main() {
code = injectAutoFlush(code);
startTime = Date.now();
- term.write('\x1b[32m> ./a.out\x1b[0m\r\n');
+ term.write('\x1b[32m> gcc main.c -o main && ./main\x1b[0m\r\n');
socket.send(JSON.stringify({
type: 'run',
@@ -632,20 +851,18 @@ int main() {
});
// --- AI Feature Logic ---
-
- // 1. Open Modal and Request AI Fix
+
+ // --- AI Feature Logic ---
askAiLink.addEventListener('click', () => {
const errorContext = getTerminalContent();
- const currentCode = codeInput.value;
+ const currentCode = editor.getValue();
- // UI Update
aiModal.classList.add('open');
aiLoading.classList.remove('hidden');
aiResult.classList.add('hidden');
aiErrorMsg.classList.add('hidden');
applyFixBtn.disabled = true;
- // Send Request
socket.send(JSON.stringify({
type: 'ai_fix',
code: currentCode,
@@ -654,36 +871,30 @@ int main() {
}));
});
- // 2. Handle AI Response
function handleAiResponse(data) {
aiLoading.classList.add('hidden');
aiResult.classList.remove('hidden');
-
aiExplanation.textContent = data.explanation;
aiCodePreview.textContent = data.fixed_code;
lastSuggestedCode = data.fixed_code;
-
applyFixBtn.disabled = false;
}
- // 3. Handle AI Error
function showAiError(msg) {
aiLoading.classList.add('hidden');
aiErrorMsg.textContent = msg;
aiErrorMsg.classList.remove('hidden');
}
- // 4. Apply Fix
applyFixBtn.addEventListener('click', () => {
if (lastSuggestedCode) {
- codeInput.value = lastSuggestedCode;
+ editor.setValue(lastSuggestedCode);
closeAiModal();
showToast("Fix Applied Successfully!");
aiSuggestion.classList.remove('visible');
}
});
- // 5. Modal Controls
function closeAiModal() {
aiModal.classList.remove('open');
}
@@ -693,8 +904,16 @@ int main() {
if (e.target === aiModal) closeAiModal();
});
+ // --- Reset Logic ---
+ resetBtn.addEventListener('click', () => {
+ term.reset();
+ currentLine = "";
+ outputBuffer = "";
+ aiSuggestion.classList.remove('visible');
+ term.write(`\x1b[38;2;190;242;100m> CONNECTION ESTABLISHED.\x1b[0m\r\n> ./a.out\r\n`);
+ });
- // --- Share Functionality ---
+ // --- Share Functionality with Firebase ---
shareBtn.addEventListener('click', async () => {
const originalIcon = shareBtn.innerHTML;
shareBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
@@ -705,12 +924,12 @@ int main() {
try {
await signInAnonymously(auth);
} catch (authError) {
- console.warn("Anonymous auth failed (proceeding to unauthenticated write attempt):", authError.code);
+ console.warn("Anonymous auth failed:", authError.code);
}
}
- const code = codeInput.value;
- const output = getTerminalContent();
+ const code = editor.getValue();
+ const output = getTerminalContent();
const shareId = generateId();
await setDoc(doc(db, "shares", shareId), {
@@ -740,7 +959,11 @@ int main() {
}
} catch (error) {
console.error("Share failed:", error);
- showToast("Share Failed");
+ if (error.code === 'permission-denied') {
+ showToast("Share Failed: Access Denied");
+ } else {
+ showToast("Share Failed: " + error.message);
+ }
} finally {
shareBtn.innerHTML = originalIcon;
shareBtn.disabled = false;
@@ -778,14 +1001,6 @@ int main() {
document.body.removeChild(textArea);
}
- // --- Reset Logic ---
- resetBtn.addEventListener('click', () => {
- term.reset();
- currentLine = "";
- aiSuggestion.classList.remove('visible');
- term.write(`\x1b[38;2;190;242;100m> CONNECTION ESTABLISHED.\x1b[0m\r\n> ./a.out\r\n`);
- });
-
// Terminal Input
term.onData((data) => {
if (!socket || socket.readyState !== WebSocket.OPEN) return;
diff --git a/public/cpp.html b/public/cpp.html
@@ -15,6 +15,15 @@
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
+ <!-- CodeMirror 5 (Professional Editor) -->
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.css">
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/theme/dracula.min.css">
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.js"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/clike/clike.min.js"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/addon/edit/closebrackets.min.js"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/addon/edit/matchbrackets.min.js"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/addon/selection/active-line.min.js"></script>
+
<!-- Xterm.js -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.css" />
<script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.js"></script>
@@ -59,43 +68,74 @@
}
@keyframes scanline { 0% { top: -10vh; } 100% { top: 110vh; } }
- /* Custom Editor Styles */
- .editor-container {
- position: relative;
- width: 100%;
- height: 100%;
- overflow: hidden;
+ /* CodeMirror Customization */
+ .CodeMirror {
+ height: 100% !important;
font-family: 'JetBrains Mono', monospace;
font-size: 13px;
- line-height: 1.5;
+ background-color: #0c0f16 !important; /* Slightly lighter than pure black */
+ color: #f8f8f2;
+ }
+ .CodeMirror-gutters {
+ background-color: #0c0f16 !important;
+ border-right: 1px solid rgba(255,255,255,0.05) !important;
+ }
+ .CodeMirror-linenumber {
+ color: #555 !important;
+ }
+ .CodeMirror-cursor {
+ border-left: 2px solid var(--accent) !important;
+ }
+ .CodeMirror-selected {
+ background: rgba(190, 242, 100, 0.2) !important;
+ }
+ .cm-s-dracula .CodeMirror-activeline-background {
+ background: rgba(255, 255, 255, 0.03) !important;
}
- /* The actual textarea for typing */
- #code-input {
- position: absolute;
- left: 0;
- top: 0;
- width: 100%;
- height: 100%;
- padding: 1rem;
- border: none;
- outline: none;
- background: transparent;
- color: #e2e8f0; /* Visible Text */
- caret-color: #fff; /* Cursor visible */
- z-index: 1;
- resize: none;
- white-space: pre;
- overflow: auto;
- }
-
- .xterm-viewport { overflow-y: auto !important; }
-
- ::selection { background: var(--accent); color: black; }
+ /* Scrollbar styling */
+ .CodeMirror-scroll::-webkit-scrollbar,
+ .xterm-viewport::-webkit-scrollbar,
+ .custom-scrollbar::-webkit-scrollbar {
+ width: 6px; height: 6px;
+ }
+ .CodeMirror-scroll::-webkit-scrollbar-track,
+ .xterm-viewport::-webkit-scrollbar-track,
+ .custom-scrollbar::-webkit-scrollbar-track {
+ background: #050505;
+ }
+ .CodeMirror-scroll::-webkit-scrollbar-thumb,
+ .xterm-viewport::-webkit-scrollbar-thumb,
+ .custom-scrollbar::-webkit-scrollbar-thumb {
+ background: #333; border-radius: 3px;
+ }
- .custom-scrollbar::-webkit-scrollbar { width: 6px; height: 6px; }
- .custom-scrollbar::-webkit-scrollbar-track { background: #050505; }
- .custom-scrollbar::-webkit-scrollbar-thumb { background: #333; border-radius: 3px; }
+ /* Mobile Helper Bar */
+ #mobile-helper-bar {
+ display: none; /* Shown via JS on mobile */
+ background: #151515;
+ border-top: 1px solid rgba(255,255,255,0.1);
+ padding: 8px;
+ overflow-x: auto;
+ white-space: nowrap;
+ -webkit-overflow-scrolling: touch;
+ }
+ @media (max-width: 768px) {
+ #mobile-helper-bar { display: flex; gap: 8px; }
+ }
+ .helper-key {
+ background: rgba(255,255,255,0.1);
+ color: var(--accent);
+ border: 1px solid rgba(255,255,255,0.1);
+ border-radius: 4px;
+ padding: 6px 12px;
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 12px;
+ font-weight: bold;
+ cursor: pointer;
+ user-select: none;
+ }
+ .helper-key:active { background: var(--accent); color: black; }
/* Toast Notification */
.toast {
@@ -182,7 +222,7 @@
to { transform: translateY(0); opacity: 1; }
}
- /* AI Modal */
+ /* Modals (AI & Clear) */
.modal-overlay {
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.8); backdrop-filter: blur(5px);
@@ -194,7 +234,6 @@
background: #0f0f0f;
border: 1px solid var(--accent);
width: 90%; max-width: 600px;
- max-height: 80vh;
border-radius: 12px;
display: flex; flex-direction: column;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.5), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
@@ -206,6 +245,31 @@
0% { transform: scale(0.9); opacity: 0; }
100% { transform: scale(1); opacity: 1; }
}
+
+ /* Editor Toolbar Buttons */
+ .editor-btn {
+ background: rgba(255,255,255,0.05);
+ border: 1px solid rgba(255,255,255,0.1);
+ color: #737373;
+ width: 32px;
+ height: 32px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 4px;
+ transition: all 0.2s;
+ cursor: pointer;
+ }
+ .editor-btn:hover {
+ background: rgba(255,255,255,0.1);
+ color: #fff;
+ border-color: rgba(255,255,255,0.3);
+ }
+ .editor-btn.danger:hover {
+ background: rgba(239, 68, 68, 0.1);
+ color: #ef4444;
+ border-color: #ef4444;
+ }
</style>
</head>
<body class="antialiased selection:bg-lime-300 selection:text-black">
@@ -220,9 +284,29 @@
<span>Error detected. <span id="ask-ai-link" class="ai-link">Ask AI</span> to fix it.</span>
</div>
+ <!-- Clear Confirmation Modal -->
+ <div id="clear-modal" class="modal-overlay">
+ <div class="modal-content max-w-[320px] p-0 border-red-500/50">
+ <div class="bg-red-500/10 px-6 py-4 border-b border-red-500/20">
+ <h3 class="font-heading font-bold text-lg text-red-500 flex items-center gap-2">
+ <i class="fas fa-exclamation-circle"></i> CLEAR CODE
+ </h3>
+ </div>
+ <div class="p-6 text-center">
+ <p class="text-neutral-300 text-sm font-sans mb-6">Are you sure you want to clear the editor? This action will remove all current code.</p>
+ <div class="flex justify-center gap-3">
+ <button id="cancel-clear-btn" class="px-4 py-2 rounded text-xs font-mono font-bold uppercase text-neutral-400 hover:text-white hover:bg-white/10 transition-all border border-transparent">Cancel</button>
+ <button id="confirm-clear-btn" class="bg-red-500/10 hover:bg-red-500 hover:text-black text-red-500 border border-red-500/20 px-4 py-2 rounded text-xs font-mono font-bold uppercase transition-all flex items-center gap-2">
+ <i class="fas fa-trash-alt"></i> Confirm
+ </button>
+ </div>
+ </div>
+ </div>
+ </div>
+
<!-- AI Fix Modal -->
<div id="ai-modal" class="modal-overlay">
- <div class="modal-content">
+ <div class="modal-content max-h-[80vh]">
<!-- Modal Header -->
<div class="bg-white/5 px-6 py-4 border-b border-white/10 flex justify-between items-center">
<h3 class="font-heading font-bold text-lg text-lime-300 flex items-center gap-2">
@@ -270,15 +354,29 @@
<!-- Header (Responsive) -->
<header class="h-auto md:h-16 flex flex-col md:flex-row items-center justify-between px-4 py-3 md:px-6 shrink-0 border-b border-white/10 z-20 bg-neutral-900/50 backdrop-blur-md gap-3 md:gap-4">
- <!-- Top Row: Title & Status -->
- <div class="flex items-center justify-between w-full md:w-auto">
+ <!-- Top Row: Title & Editor Tools -->
+ <div class="flex items-center justify-between w-full md:w-auto gap-6">
<h1 class="text-lg md:text-xl font-heading font-bold text-white tracking-tight flex items-center">
- <span class="text-lime-300 mr-2">//</span>C++.COMPILER
+ <span class="text-lime-300 mr-2">//</span>CPP.COMPILER
<span id="conn-dot" class="pulse-dot disconnected ml-3" title="Disconnected"></span>
</h1>
+ <!-- Editor Tools -->
+ <div class="flex items-center gap-2">
+ <button id="undo-btn" class="editor-btn" title="Undo (Ctrl+Z)">
+ <i class="fas fa-undo text-xs"></i>
+ </button>
+ <button id="redo-btn" class="editor-btn" title="Redo (Ctrl+Y)">
+ <i class="fas fa-redo text-xs"></i>
+ </button>
+ <div class="w-px h-4 bg-white/10 mx-1"></div>
+ <button id="clear-code-btn" class="editor-btn danger" title="Clear Code">
+ <i class="fas fa-times text-xs"></i>
+ </button>
+ </div>
+
<!-- Mobile Only: Exit Link -->
- <a href="./index.html" class="md:hidden font-mono text-[10px] text-neutral-500 hover:text-white uppercase tracking-wider transition-colors">[ EXIT ]</a>
+ <a href="./index.html" class="md:hidden font-mono text-[10px] text-neutral-500 hover:text-white uppercase tracking-wider transition-colors ml-auto">[ EXIT ]</a>
</div>
<!-- Middle: Connection Text -->
@@ -290,6 +388,10 @@
<div class="flex items-center gap-2 w-full md:w-auto justify-end">
<a href="./index.html" class="hidden md:inline font-mono text-[10px] text-neutral-500 hover:text-white uppercase tracking-wider transition-colors mr-2">[ EXIT ]</a>
+ <button id="download-btn" class="bg-white/5 hover:bg-neutral-800 text-white border border-white/10 px-3 py-2 rounded font-mono text-[10px] font-bold uppercase tracking-wider flex items-center justify-center transition-all" title="Download .cpp">
+ <i class="fas fa-download"></i>
+ </button>
+
<button id="reset-btn" class="bg-white/5 hover:bg-neutral-800 text-white border border-white/10 px-3 py-2 rounded font-mono text-[10px] font-bold uppercase tracking-wider flex items-center justify-center transition-all" title="Reset Terminal">
<i class="fas fa-rotate-left"></i>
</button>
@@ -297,10 +399,6 @@
<button id="share-btn" class="bg-white/5 hover:bg-neutral-800 text-white border border-white/10 px-3 py-2 rounded font-mono text-[10px] font-bold uppercase tracking-wider flex items-center justify-center transition-all" title="Share Code">
<i class="fas fa-share-alt"></i>
</button>
-
- <button id="copy-btn" class="bg-white/5 hover:bg-neutral-800 text-white border border-white/10 px-3 py-2 rounded font-mono text-[10px] font-bold uppercase tracking-wider flex items-center justify-center transition-all" title="Copy Output">
- <i class="fas fa-copy"></i>
- </button>
<button id="run-btn" disabled class="bg-white/5 hover:bg-lime-300 hover:text-black disabled:opacity-50 disabled:cursor-not-allowed text-white border border-white/10 px-4 py-2 rounded font-mono text-xs font-bold uppercase tracking-wider flex items-center gap-2 transition-all flex-1 md:flex-none justify-center shadow-lg shadow-black/50">
<i class="fas fa-play text-[10px]"></i> <span class="md:hidden lg:inline">EXECUTE</span><span class="hidden md:inline lg:hidden">RUN</span>
@@ -315,35 +413,44 @@
<div class="flex-1 flex flex-col h-[60%] md:h-auto min-h-0 border-b md:border-b-0 md:border-r border-white/10 relative group">
<div class="bg-black/20 px-4 py-2 text-[10px] font-mono text-lime-300 uppercase border-b border-white/10 flex justify-between items-center shrink-0">
<span>SRC // main.cpp</span>
- <span class="text-neutral-600">G++ 11.2</span>
+ <span class="text-neutral-600">G++ 9.4.0</span>
</div>
- <!-- Custom Editor Area -->
- <div class="editor-container relative bg-[#0c0f16]">
- <textarea id="code-input" spellcheck="false" autocapitalize="off" autocomplete="off" autocorrect="off" class="custom-scrollbar">#include <iostream>
-#include <string>
+ <!-- CodeMirror Container -->
+ <div id="editor-container" class="relative bg-[#0c0f16] flex-1 overflow-hidden">
+ <textarea id="code-input" class="hidden">#include <iostream>
int main() {
- std::string name;
-
- std::cout << "Enter your name: ";
- // Force flush ensures prompt appears immediately in buffered environments
- std::cout.flush();
-
- std::cin >> name;
- std::cout << "Hello, " << name << "!" << std::endl;
-
+ std::cout << "Hello C++ Compiler!" << std::endl;
return 0;
}</textarea>
</div>
+ <!-- Mobile Helper Bar -->
+ <div id="mobile-helper-bar">
+ <div class="helper-key" data-char=" ">Tab</div>
+ <div class="helper-key" data-char=";">;</div>
+ <div class="helper-key" data-char="=">=</div>
+ <div class="helper-key" data-char="()">()</div>
+ <div class="helper-key" data-char="[]">[]</div>
+ <div class="helper-key" data-char="{}">{}</div>
+ <div class="helper-key" data-char='""'>""</div>
+ <div class="helper-key" data-char="''">''</div>
+ <div class="helper-key" data-char="#">#</div>
+ <div class="helper-key" data-char="<<"><<</div>
+ <div class="helper-key" data-char=">>">>></div>
+ <div class="helper-key" data-char="cout">cout</div>
+ </div>
</div>
<!-- Terminal -->
- <div class="flex-1 flex flex-col h-[40%] md:h-auto min-h-0 bg-[#050505] relative">
+ <div class="flex-1 flex flex-col h-[40%] md:h-auto min-h-0 bg-[#050505]">
<div class="bg-black/20 px-4 py-2 text-[10px] font-mono text-lime-300 uppercase border-b border-white/10 flex justify-between items-center shrink-0">
<span>OUT // TERMINAL</span>
- <span class="text-neutral-600">XTERM.JS</span>
+ <div class="flex gap-2">
+ <button id="copy-btn" class="text-neutral-500 hover:text-white transition-colors" title="Copy Output"><i class="fas fa-copy"></i></button>
+ <span class="text-neutral-600">XTERM.JS</span>
+ </div>
</div>
<div class="flex-1 relative">
<div id="terminal-container" class="absolute inset-0 w-full h-full p-2 overflow-hidden"></div>
@@ -372,9 +479,9 @@ int main() {
const db = getFirestore(app);
const auth = getAuth(app);
- // Attempt initial sign-in, but don't crash if it fails (restricted op)
+ // Attempt initial sign-in
signInAnonymously(auth).catch((error) => {
- console.warn("Auto-Auth failed (likely disabled in console). App will try unauthenticated access.", error.code);
+ console.warn("Auto-Auth failed. App will try unauthenticated access.", error.code);
});
// --- Helper: Generate ID ---
@@ -392,11 +499,19 @@ int main() {
const resetBtn = document.getElementById('reset-btn');
const shareBtn = document.getElementById('share-btn');
const copyBtn = document.getElementById('copy-btn');
+ const downloadBtn = document.getElementById('download-btn');
const connDot = document.getElementById('conn-dot');
const connectionStatus = document.getElementById('connection-status');
- const codeInput = document.getElementById('code-input');
const toast = document.getElementById('toast');
+ // Editor Tool Elements
+ const undoBtn = document.getElementById('undo-btn');
+ const redoBtn = document.getElementById('redo-btn');
+ const clearCodeBtn = document.getElementById('clear-code-btn');
+ const clearModal = document.getElementById('clear-modal');
+ const cancelClearBtn = document.getElementById('cancel-clear-btn');
+ const confirmClearBtn = document.getElementById('confirm-clear-btn');
+
// AI Elements
const aiSuggestion = document.getElementById('ai-suggestion');
const askAiLink = document.getElementById('ask-ai-link');
@@ -415,38 +530,121 @@ int main() {
let currentLine = "";
let startTime = 0;
let lastSuggestedCode = "";
+ let outputBuffer = "";
+
+ // --- CodeMirror Setup ---
+ const editor = CodeMirror.fromTextArea(document.getElementById("code-input"), {
+ mode: "text/x-c++src",
+ theme: "dracula",
+ lineNumbers: true,
+ indentUnit: 4,
+ matchBrackets: true,
+ autoCloseBrackets: true,
+ styleActiveLine: true,
+ inputStyle: "contenteditable",
+ viewportMargin: Infinity,
+ extraKeys: {
+ "Backspace": function(cm) {
+ if (cm.somethingSelected()) {
+ cm.execCommand("delCharBefore");
+ return;
+ }
+ var cursor = cm.getCursor();
+ var line = cm.getLine(cursor.line);
+ // If cursor is not at start, and everything before it is whitespace
+ if (cursor.ch > 0 && /^\s+$/.test(line.slice(0, cursor.ch))) {
+ cm.execCommand("indentLess");
+ } else {
+ cm.execCommand("delCharBefore");
+ }
+ }
+ },
+ });
+
+ // Fix resize issues
+ window.addEventListener('resize', () => editor.refresh());
+
+ // Local Auto-Save
+ const LOCAL_STORAGE_KEY = 'cpp_compiler_code_autosave';
+ editor.on('change', () => {
+ localStorage.setItem(LOCAL_STORAGE_KEY, editor.getValue());
+ });
+
+ // Load Auto-Save on Start (if not shared)
+ const savedCode = localStorage.getItem(LOCAL_STORAGE_KEY);
+ if (savedCode && !window.location.search.includes('share') && !window.location.search.includes('code')) {
+ editor.setValue(savedCode);
+ }
+
+ // --- Editor Toolbar Logic ---
+ undoBtn.addEventListener('click', () => { editor.undo(); editor.focus(); });
+ redoBtn.addEventListener('click', () => { editor.redo(); editor.focus(); });
+
+ clearCodeBtn.addEventListener('click', () => {
+ clearModal.classList.add('open');
+ });
+
+ cancelClearBtn.addEventListener('click', () => {
+ clearModal.classList.remove('open');
+ });
+
+ confirmClearBtn.addEventListener('click', () => {
+ editor.setValue('');
+ editor.clearHistory();
+ editor.focus();
+ clearModal.classList.remove('open');
+ showToast("Editor Cleared");
+ });
- // --- EDITOR LOGIC (Indentation) ---
- codeInput.addEventListener('keydown', function(e) {
- if (e.key === 'Tab') {
- e.preventDefault();
- const start = this.selectionStart;
- const end = this.selectionEnd;
- this.setRangeText(' ', start, end, 'end');
- } else if (e.key === 'Enter') {
- e.preventDefault();
- const start = this.selectionStart;
- const end = this.selectionEnd;
- const value = this.value;
- const lineStart = value.lastIndexOf('\n', start - 1) + 1;
- const currentLineText = value.substring(lineStart, start);
- let match = currentLineText.match(/^(\s*)/);
- let indent = match ? match[1] : "";
- if (currentLineText.trim().endsWith('{')) indent += " ";
- this.setRangeText('\n' + indent, start, end, 'end');
- this.blur(); this.focus();
- } else if (e.key === '}') {
- const start = this.selectionStart;
- const end = this.selectionEnd;
- const value = this.value;
- const lineStart = value.lastIndexOf('\n', start - 1) + 1;
- const currentLineText = value.substring(lineStart, start);
- if (/^\s+$/.test(currentLineText) && currentLineText.length >= 4) {
- const newIndent = currentLineText.substring(0, currentLineText.length - 4);
- this.setSelectionRange(lineStart, start);
- this.setRangeText(newIndent, this.selectionStart, this.selectionEnd, 'end');
+ clearModal.addEventListener('click', (e) => {
+ if (e.target === clearModal) clearModal.classList.remove('open');
+ });
+
+ // --- Mobile Helper Bar ---
+ document.querySelectorAll('.helper-key').forEach(key => {
+ key.addEventListener('click', (e) => {
+ const char = key.getAttribute('data-char');
+ const doc = editor.getDoc();
+ const cursor = doc.getCursor();
+
+ if (char === '()') {
+ doc.replaceRange('()', cursor);
+ doc.setCursor({line: cursor.line, ch: cursor.ch + 1});
+ } else if (char === '[]') {
+ doc.replaceRange('[]', cursor);
+ doc.setCursor({line: cursor.line, ch: cursor.ch + 1});
+ } else if (char === '{}') {
+ doc.replaceRange('{}', cursor);
+ doc.setCursor({line: cursor.line, ch: cursor.ch + 1});
+ } else if (char === '""') {
+ doc.replaceRange('""', cursor);
+ doc.setCursor({line: cursor.line, ch: cursor.ch + 1});
+ } else if (char === "''") {
+ doc.replaceRange("''", cursor);
+ doc.setCursor({line: cursor.line, ch: cursor.ch + 1});
+ } else if (char === "cout") {
+ doc.replaceRange('cout << ', cursor);
+ doc.setCursor({line: cursor.line, ch: cursor.ch + 8});
+ } else {
+ doc.replaceRange(char, cursor);
}
- }
+ editor.focus();
+ });
+ });
+
+ // --- Download Button ---
+ downloadBtn.addEventListener('click', () => {
+ const code = editor.getValue();
+ const blob = new Blob([code], { type: 'text/x-c++src' });
+ const url = window.URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = 'main.cpp';
+ document.body.appendChild(a);
+ a.click();
+ window.URL.revokeObjectURL(url);
+ document.body.removeChild(a);
+ showToast("Downloaded main.cpp");
});
window.addEventListener('load', async () => {
@@ -465,7 +663,7 @@ int main() {
if (docSnap.exists()) {
const data = docSnap.data();
- codeInput.value = data.code;
+ editor.setValue(data.code);
// Restore Output if it exists
if (data.output && term) {
@@ -473,7 +671,7 @@ int main() {
term.write(data.output.replace(/\n/g, '\r\n'));
term.write('\r\n\x1b[38;2;115;115;115m> ----------------\x1b[0m\r\n');
}
-
+
toast.textContent = "Code loaded successfully";
} else {
toast.textContent = "Shared code not found";
@@ -481,7 +679,6 @@ int main() {
setTimeout(() => toast.classList.remove('show'), 3000);
} catch (e) {
console.error("Fetch error:", e);
- // More descriptive error for user
if (e.code === 'permission-denied') {
toast.textContent = "Error: Database Access Denied";
} else {
@@ -491,10 +688,9 @@ int main() {
}
} else if (sharedCodeOld) {
// Fallback for Base64
- try { codeInput.value = atob(sharedCodeOld); }
+ try { editor.setValue(atob(sharedCodeOld)); }
catch(e) {}
}
-
setTimeout(connect, 500);
});
@@ -532,12 +728,12 @@ int main() {
let lineText = line.translateToString(true);
// Filter system messages
if(lineText.trim().startsWith("> [") ||
- lineText.trim().startsWith("> ./a.out") ||
lineText.includes("SYSTEM READY") ||
lineText.includes("ATTEMPTING") ||
lineText.includes("CONNECTION") ||
lineText.includes("PREVIOUS OUTPUT") ||
- lineText.includes("> ----------------")
+ lineText.includes("> ----------------") ||
+ lineText.includes("> ./a.out")
) {
continue;
}
@@ -593,9 +789,10 @@ int main() {
const msg = JSON.parse(event.data);
if (msg.type === 'stdout') {
let text = msg.data;
-
+ outputBuffer += text;
+
// Trigger AI Overlay on Error
- if (text.toLowerCase().includes("error:") || text.toLowerCase().includes("warning:")) {
+ if (outputBuffer.toLowerCase().includes("error") || outputBuffer.toLowerCase().includes("warning")) {
aiSuggestion.classList.add('visible');
}
@@ -607,8 +804,6 @@ int main() {
if (msg.msg.includes("Program finished")) {
const duration = (Date.now() - startTime) / 1000;
term.write(`\r\n\x1b[38;2;115;115;115m[Finished in ${duration.toFixed(3)}s]\x1b[0m\r\n`);
- } else if (msg.msg === "Compilation Failed") {
- aiSuggestion.classList.add('visible');
}
} else if (msg.type === 'ai_response') {
handleAiResponse(msg.data);
@@ -621,29 +816,18 @@ int main() {
// --- Run Logic ---
runBtn.addEventListener('click', () => {
if (!socket || socket.readyState !== WebSocket.OPEN) return;
+ if (document.activeElement) document.activeElement.blur();
- document.activeElement.blur();
term.reset();
currentLine = "";
- aiSuggestion.classList.remove('visible'); // Hide previous suggestions
+ outputBuffer = "";
+ aiSuggestion.classList.remove('visible');
- let code = codeInput.value;
+ let code = editor.getValue();
code = code.replace(/\u00A0/g, " ").replace(/[\u2018\u2019]/g, "'").replace(/[\u201C\u201D]/g, '"');
- // Auto-inject std::cout.setf(std::ios::unitbuf); for C++
- function injectAutoFlush(source) {
- const mainRegex = /(int|void)\s+main\s*\([^)]*\)\s*\{/;
- const match = source.match(mainRegex);
- if (match) {
- const insertionPoint = match.index + match[0].length;
- return source.slice(0, insertionPoint) + "\n std::cout.setf(std::ios::unitbuf); // Auto-injected" + source.slice(insertionPoint);
- }
- return source;
- }
- code = injectAutoFlush(code);
-
startTime = Date.now();
- term.write('\x1b[32m> ./a.out\x1b[0m\r\n');
+ term.write('\x1b[32m> g++ main.cpp -o main && ./main\x1b[0m\r\n');
socket.send(JSON.stringify({
type: 'run',
@@ -655,20 +839,16 @@ int main() {
});
// --- AI Feature Logic ---
-
- // 1. Open Modal and Request AI Fix
askAiLink.addEventListener('click', () => {
const errorContext = getTerminalContent();
- const currentCode = codeInput.value;
+ const currentCode = editor.getValue();
- // UI Update
aiModal.classList.add('open');
aiLoading.classList.remove('hidden');
aiResult.classList.add('hidden');
aiErrorMsg.classList.add('hidden');
applyFixBtn.disabled = true;
- // Send Request
socket.send(JSON.stringify({
type: 'ai_fix',
code: currentCode,
@@ -677,36 +857,30 @@ int main() {
}));
});
- // 2. Handle AI Response
function handleAiResponse(data) {
aiLoading.classList.add('hidden');
aiResult.classList.remove('hidden');
-
aiExplanation.textContent = data.explanation;
aiCodePreview.textContent = data.fixed_code;
lastSuggestedCode = data.fixed_code;
-
applyFixBtn.disabled = false;
}
- // 3. Handle AI Error
function showAiError(msg) {
aiLoading.classList.add('hidden');
aiErrorMsg.textContent = msg;
aiErrorMsg.classList.remove('hidden');
}
- // 4. Apply Fix
applyFixBtn.addEventListener('click', () => {
if (lastSuggestedCode) {
- codeInput.value = lastSuggestedCode;
+ editor.setValue(lastSuggestedCode);
closeAiModal();
showToast("Fix Applied Successfully!");
aiSuggestion.classList.remove('visible');
}
});
- // 5. Modal Controls
function closeAiModal() {
aiModal.classList.remove('open');
}
@@ -720,32 +894,30 @@ int main() {
resetBtn.addEventListener('click', () => {
term.reset();
currentLine = "";
+ outputBuffer = "";
aiSuggestion.classList.remove('visible');
term.write(`\x1b[38;2;190;242;100m> CONNECTION ESTABLISHED.\x1b[0m\r\n> ./a.out\r\n`);
});
// --- Share Functionality with Firebase ---
shareBtn.addEventListener('click', async () => {
- // Original Share icon animation
const originalIcon = shareBtn.innerHTML;
shareBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
shareBtn.disabled = true;
try {
- // Robust Auth Check
if (!auth.currentUser) {
try {
await signInAnonymously(auth);
} catch (authError) {
- console.warn("Anonymous auth failed (proceeding to unauthenticated write attempt):", authError.code);
+ console.warn("Anonymous auth failed:", authError.code);
}
}
- const code = codeInput.value;
+ const code = editor.getValue();
const output = getTerminalContent();
const shareId = generateId();
- // Save to Firestore
await setDoc(doc(db, "shares", shareId), {
code: code,
language: 'cpp',
@@ -753,13 +925,11 @@ int main() {
createdAt: new Date().toISOString()
});
- // Generate URL
const url = new URL(window.location.href);
url.searchParams.delete('code');
url.searchParams.set('share', shareId);
const shareUrl = url.toString();
- // Share or Copy
if (navigator.share) {
try {
await navigator.share({
diff --git a/public/docs.html b/public/docs.html
@@ -4,7 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documentation // Cloud Code Compiler</title>
- <link rel="icon" type="image/x-icon" href="https://compiler.amit.is-a.dev/logo.png">
+ <link rel="icon" type="image/x-icon" href="https://compiler.aranag.site/logo.png">
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
@@ -45,7 +45,10 @@
.doc-section {
margin-bottom: 4rem;
scroll-margin-top: 6rem;
+ border-bottom: 1px solid rgba(255,255,255,0.05);
+ padding-bottom: 3rem;
}
+ .doc-section:last-child { border-bottom: none; }
.code-block {
background: #0f1115;
@@ -54,6 +57,7 @@
padding: 1rem;
margin: 1.5rem 0;
font-size: 0.9rem;
+ overflow-x: auto;
}
.nav-link {
@@ -94,26 +98,34 @@
</div>
<nav class="space-y-1">
- <p class="px-4 text-xs font-mono text-neutral-500 uppercase tracking-widest mb-2">Contents</p>
- <a href="#architecture" class="nav-link font-mono text-sm">Architecture</a>
- <a href="#communication" class="nav-link font-mono text-sm">WebSocket Protocol</a>
- <a href="#python-engine" class="nav-link font-mono text-sm">Python Engine</a>
- <a href="#cpp-engine" class="nav-link font-mono text-sm">C / C++ Engine</a>
- <a href="#ai-debug" class="nav-link font-mono text-sm text-lime-300">New: AI Auto-Debug</a>
- <a href="#sharing" class="nav-link font-mono text-sm">Sharing & Persistence</a>
- <a href="#security" class="nav-link font-mono text-sm">Security & Sandbox</a>
- <a href="#privacy" class="nav-link font-mono text-sm">Privacy & Data</a>
- <a href="#disclaimer" class="nav-link font-mono text-sm">Legal & Disclaimer</a>
+ <p class="px-4 text-xs font-mono text-neutral-500 uppercase tracking-widest mb-2">Core System</p>
+ <a href="#architecture" class="nav-link font-mono text-sm">01. Architecture</a>
+ <a href="#editor" class="nav-link font-mono text-sm text-lime-300">02. Editor & Tools (NEW)</a>
+ <a href="#communication" class="nav-link font-mono text-sm">03. WebSocket I/O</a>
+
+ <p class="px-4 text-xs font-mono text-neutral-500 uppercase tracking-widest mb-2 mt-6">Engines</p>
+ <a href="#engines" class="nav-link font-mono text-sm">04. Execution Engines</a>
+
+ <p class="px-4 text-xs font-mono text-neutral-500 uppercase tracking-widest mb-2 mt-6">Intelligence</p>
+ <a href="#ai-debug" class="nav-link font-mono text-sm">05. AI Auto-Debug</a>
+
+ <p class="px-4 text-xs font-mono text-neutral-500 uppercase tracking-widest mb-2 mt-6">Data</p>
+ <a href="#persistence" class="nav-link font-mono text-sm">06. Sharing & Persistence</a>
+ <a href="#lifecycle" class="nav-link font-mono text-sm">07. Data Lifecycle</a>
+ <a href="#security" class="nav-link font-mono text-sm">08. Security</a>
+
+ <p class="px-4 text-xs font-mono text-neutral-500 uppercase tracking-widest mb-2 mt-6">Community</p>
+ <a href="#contribution" class="nav-link font-mono text-sm">09. Contribution</a>
+ <a href="#disclaimer" class="nav-link font-mono text-sm text-red-400">10. Disclaimer</a>
</nav>
<div class="mt-12 p-4 rounded-xl bg-white/5 border border-white/10">
- <p class="text-xs text-neutral-400 mb-2">Powered by</p>
+ <p class="text-xs text-neutral-400 mb-2">Tech Stack</p>
<div class="flex flex-wrap gap-2">
- <span class="text-xs px-2 py-1 rounded bg-black border border-white/10 text-lime-300 font-mono">aiohttp</span>
+ <span class="text-xs px-2 py-1 rounded bg-black border border-white/10 text-lime-300 font-mono">CodeMirror</span>
+ <span class="text-xs px-2 py-1 rounded bg-black border border-white/10 text-lime-300 font-mono">Gemini 2.5</span>
<span class="text-xs px-2 py-1 rounded bg-black border border-white/10 text-lime-300 font-mono">Firebase</span>
- <span class="text-xs px-2 py-1 rounded bg-black border border-white/10 text-lime-300 font-mono">Gemini AI</span>
- <span class="text-xs px-2 py-1 rounded bg-black border border-white/10 text-lime-300 font-mono">Render</span>
- <span class="text-xs px-2 py-1 rounded bg-black border border-white/10 text-lime-300 font-mono">WebSocket</span>
+ <span class="text-xs px-2 py-1 rounded bg-black border border-white/10 text-lime-300 font-mono">aiohttp</span>
</div>
</div>
</aside>
@@ -133,237 +145,313 @@
<div class="mb-16 border-b border-white/10 pb-12">
<h1 class="hidden lg:block text-5xl font-heading font-bold text-white mb-6">System Documentation</h1>
<p class="text-xl text-neutral-400 leading-relaxed max-w-3xl">
- A technical deep-dive into the Cloud Code Compiler. Learn how we achieve low-latency remote execution, handle real-time I/O streams, and manage dynamic dependency resolution.
+ A comprehensive technical overview of the Cloud Code Compiler v2.0. This document covers the full stack, from the CodeMirror-enhanced frontend to the AI-powered debugging backend and real-time execution engines.
</p>
</div>
- <!-- Architecture -->
+ <!-- 01 Architecture -->
<section id="architecture" class="doc-section">
<h2 class="text-2xl font-heading font-bold text-lime-300 mb-6 flex items-center">
- <span class="mr-3">01.</span> Architecture
+ <span class="mr-3 text-neutral-600">01.</span> Architecture
</h2>
<p class="text-neutral-300 mb-4">
- The compiler uses a client-server model optimized for persistent connections. Unlike traditional stateless REST APIs which require polling for output, we utilize <strong>WebSockets</strong> to maintain a full-duplex communication channel between the browser's xterm.js terminal and the backend process.
+ The platform operates as a distributed system designed for low-latency feedback. It replaces traditional HTTP polling with persistent WebSocket connections, enabling interactive coding (REPL-like behavior) over the web.
</p>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 my-8">
<div class="p-6 rounded-xl bg-white/5 border border-white/10">
- <h3 class="text-white font-bold mb-2"><i class="fas fa-desktop text-lime-300 mr-2"></i> Frontend</h3>
- <p class="text-sm text-neutral-400">Xterm.js renders a TTY-like interface. User keystrokes are captured and sent as binary/text frames.</p>
+ <h3 class="text-white font-bold mb-2"><i class="fas fa-laptop-code text-lime-300 mr-2"></i> Frontend (Client)</h3>
+ <ul class="text-sm text-neutral-400 space-y-2">
+ <li>• <strong>Editor:</strong> CodeMirror 5 (Dracula Theme)</li>
+ <li>• <strong>Terminal:</strong> Xterm.js with FitAddon</li>
+ <li>• <strong>State:</strong> Browser LocalStorage</li>
+ <li>• <strong>Intelligence:</strong> Client-side Error Parsing</li>
+ </ul>
</div>
<div class="p-6 rounded-xl bg-white/5 border border-white/10">
- <h3 class="text-white font-bold mb-2"><i class="fas fa-server text-lime-300 mr-2"></i> Backend</h3>
- <p class="text-sm text-neutral-400">Python <code>aiohttp</code> server running on Linux. Spawns <code>subprocess</code> threads for isolation.</p>
+ <h3 class="text-white font-bold mb-2"><i class="fas fa-server text-lime-300 mr-2"></i> Backend (Server)</h3>
+ <ul class="text-sm text-neutral-400 space-y-2">
+ <li>• <strong>Server:</strong> Python <code>aiohttp</code> (AsyncIO)</li>
+ <li>• <strong>Runtime:</strong> <code>subprocess</code> pipelines</li>
+ <li>• <strong>AI Engine:</strong> Google Gemini 2.5 Flash</li>
+ <li>• <strong>Database:</strong> Firebase Firestore (NoSQL)</li>
+ </ul>
</div>
</div>
</section>
- <!-- WebSocket Protocol -->
- <section id="communication" class="doc-section">
+ <!-- 02 Editor -->
+ <section id="editor" class="doc-section">
<h2 class="text-2xl font-heading font-bold text-lime-300 mb-6 flex items-center">
- <span class="mr-3">02.</span> WebSocket Protocol
+ <span class="mr-3 text-neutral-600">02.</span> Professional Editor Experience
</h2>
<p class="text-neutral-300 mb-4">
- Communication occurs via JSON payloads. The server handles two main event types: <strong>execution</strong> and <strong>input streaming</strong>.
+ Replaced the standard textarea with <strong>CodeMirror</strong> to provide an IDE-grade environment directly in the browser.
</p>
- <h3 class="text-lg font-bold text-white mt-6 mb-2">Client -> Server</h3>
- <div class="code-block">
- <pre><code class="language-json">// 1. Initiate Execution
-{
- "type": "run",
- "language": "python", // or "c", "cpp"
- "code": "print('Hello World')"
-}
-
-// 2. Send User Input (stdin)
-{
- "type": "input",
- "data": "user_input_string\n"
-}</code></pre>
- </div>
-
- <h3 class="text-lg font-bold text-white mt-6 mb-2">Server -> Client</h3>
- <div class="code-block">
- <pre><code class="language-json">// 1. Standard Output/Error Stream (sent character-by-character or chunked)
-{
- "type": "stdout",
- "data": "H"
-}
-
-// 2. Status Updates
-{
- "type": "status",
- "msg": "Compiling..."
-}</code></pre>
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
+ <div class="p-4 border border-lime-300/20 bg-lime-300/5 rounded-lg">
+ <h4 class="text-lime-300 font-bold mb-2">Visual & Editing</h4>
+ <ul class="text-xs text-neutral-300 space-y-1">
+ <li>• <strong>Syntax Highlighting:</strong> Dracula theme for easy reading.</li>
+ <li>• <strong>Line Numbers:</strong> Essential for debugging.</li>
+ <li>• <strong>Bracket Matching:</strong> Visual cues for matching parentheses.</li>
+ <li>• <strong>Auto-Indent:</strong> Smart indentation for Python and C++.</li>
+ </ul>
+ </div>
+ <div class="p-4 border border-white/10 bg-white/5 rounded-lg">
+ <h4 class="text-white font-bold mb-2">Developer Tools</h4>
+ <ul class="text-xs text-neutral-300 space-y-1">
+ <li>• <strong>Undo/Redo:</strong> <code>Ctrl+Z</code> / <code>Ctrl+Y</code> support with history stack.</li>
+ <li>• <strong>Mobile Helper Bar:</strong> Sticky keys for <code>{ } [ ] ;</code> on touch devices.</li>
+ <li>• <strong>Export:</strong> Download code as <code>.py</code>, <code>.c</code>, or <code>.cpp</code> files.</li>
+ <li>• <strong>Clear Code:</strong> Quick reset with confirmation modal.</li>
+ </ul>
+ </div>
</div>
</section>
- <!-- Python Engine -->
- <section id="python-engine" class="doc-section">
+ <!-- 03 Communication -->
+ <section id="communication" class="doc-section">
<h2 class="text-2xl font-heading font-bold text-lime-300 mb-6 flex items-center">
- <span class="mr-3">03.</span> Python Engine & Auto-Pip
+ <span class="mr-3 text-neutral-600">03.</span> WebSocket I/O Protocol
</h2>
<p class="text-neutral-300 mb-4">
- The Python runner is equipped with a dynamic dependency resolver. Before execution, the backend statically analyzes the code imports using RegEx.
+ Real-time interaction handles input/output streams efficiently. The server streams <code>stdout</code> characters immediately, preventing "hanging" on long-running processes.
</p>
-
- <div class="p-6 rounded-xl border border-lime-300/20 bg-lime-300/5 mb-6">
- <h4 class="font-bold text-white mb-2">Feature: Dynamic Installation</h4>
- <p class="text-sm text-neutral-300">
- If a user imports a non-standard library (e.g., <code>import numpy</code>), the server automatically runs <code>pip install numpy</code> before executing the script. This allows for rich script execution without pre-configuration.
- </p>
- </div>
<div class="code-block">
- <pre><code class="language-python"># Backend Logic Snippet
-def check_and_install_packages(code):
- imports = re.findall(r'^\s*import\s+(\w+)', code, re.MULTILINE)
- for pkg in imports:
- if pkg not in standard_libs:
- subprocess.check_call([sys.executable, "-m", "pip", "install", pkg])</code></pre>
+ <pre><code class="language-json">// 1. Run Command (Client -> Server)
+{ "type": "run", "language": "python", "code": "print('Hello')" }
+
+// 2. Output Stream (Server -> Client)
+// Streamed character-by-character or line-by-line
+{ "type": "stdout", "data": "H" }
+{ "type": "stdout", "data": "e" } ...
+
+// 3. User Input (Client -> Server)
+// Sent when user types in terminal
+{ "type": "input", "data": "user_input\n" }</code></pre>
</div>
</section>
- <!-- C / C++ Engine -->
- <section id="cpp-engine" class="doc-section">
+ <!-- 04 Engines -->
+ <section id="engines" class="doc-section">
<h2 class="text-2xl font-heading font-bold text-lime-300 mb-6 flex items-center">
- <span class="mr-3">04.</span> C / C++ Compilation
+ <span class="mr-3 text-neutral-600">04.</span> Execution Engines
</h2>
- <p class="text-neutral-300 mb-4">
- For compiled languages, the server performs a two-step process. Source code is written to a temporary file, compiled via GCC/G++, and then executed.
- </p>
- <ul class="list-disc list-inside text-neutral-400 space-y-2 mb-6 ml-4">
- <li><strong>C:</strong> <code>gcc temp.c -o a.out</code></li>
- <li><strong>C++:</strong> <code>g++ temp.cpp -o a.out</code></li>
- </ul>
- <p class="text-neutral-300">
- Standard Input buffer flushing is handled automatically to ensure prompts (like <code>cin >> var</code>) appear immediately in the terminal before blocking for input.
+ <p class="text-neutral-300 mb-6">
+ Each language runs in a specialized, isolated subprocess on the backend.
</p>
+
+ <div class="space-y-6">
+ <div class="p-6 bg-white/5 rounded-xl border border-white/10">
+ <div class="flex items-center gap-3 mb-4">
+ <i class="fab fa-python text-2xl text-blue-400"></i>
+ <h3 class="text-xl font-bold text-white">Python 3 Engine</h3>
+ </div>
+ <p class="text-sm text-neutral-400 mb-4">
+ The Python environment runs on the latest stable <strong>Python 3</strong> release.
+ </p>
+ <ul class="list-disc list-inside text-sm text-neutral-300 space-y-1">
+ <li><strong>Execution:</strong> Code is written to a temporary file and executed via `python3 -u` (unbuffered) to ensure real-time output.</li>
+ <li><strong>Pip Packages:</strong> The environment comes pre-configured with standard libraries. <code>pip</code> is available in the backend environment, allowing for pre-installed packages to be imported (e.g., math, time, random).</li>
+ <li><strong>Indentation:</strong> The editor converts Tabs to 4 Spaces automatically to prevent <code>IndentationError</code>.</li>
+ </ul>
+ </div>
+
+ <div class="p-6 bg-white/5 rounded-xl border border-white/10">
+ <div class="flex items-center gap-3 mb-4">
+ <i class="fas fa-microchip text-2xl text-blue-600"></i>
+ <h3 class="text-xl font-bold text-white">C / C++ Compiler</h3>
+ </div>
+ <p class="text-sm text-neutral-400 mb-4">
+ Powered by the <strong>GNU Compiler Collection (GCC)</strong>.
+ </p>
+ <ul class="list-disc list-inside text-sm text-neutral-300 space-y-1">
+ <li><strong>C Language:</strong> Compiles via `gcc source.c -o app`. Uses standard C11/C17 standards.</li>
+ <li><strong>C++ Language:</strong> Compiles via `g++ source.cpp -o app`. Supports modern C++ standards.</li>
+ <li><strong>Binary Execution:</strong> After compilation, the binary is immediately executed (`./app`), and output is piped to the WebSocket.</li>
+ <li><strong>Error Handling:</strong> Compilation errors (stderr) are captured and displayed in red in the terminal.</li>
+ </ul>
+ </div>
+ </div>
</section>
-
- <!-- AI Auto-Debug -->
+
+ <!-- 05 AI Auto-Debug -->
<section id="ai-debug" class="doc-section">
<h2 class="text-2xl font-heading font-bold text-lime-300 mb-6 flex items-center">
- <span class="mr-3">05.</span> AI Auto-Debugger (New)
+ <span class="mr-3 text-neutral-600">05.</span> AI Auto-Debug System
</h2>
<p class="text-neutral-300 mb-4">
- The latest version of the compiler includes an intelligent debugging assistant powered by <strong>Google Gemini 2.5 Flash</strong>. This feature automatically detects runtime errors and compilation failures in real-time.
+ The compiler monitors the output stream for common error patterns (e.g., <code>Traceback</code>, <code>Segmentation fault</code>, <code>error:</code>).
</p>
-
- <h3 class="text-lg font-bold text-white mt-6 mb-2">How it works</h3>
- <div class="grid grid-cols-1 md:grid-cols-2 gap-6 my-4">
- <div class="p-6 rounded-xl bg-white/5 border border-white/10">
- <h4 class="text-white font-bold mb-2">1. Error Detection</h4>
- <p class="text-sm text-neutral-400 mb-2">
- The frontend monitors the terminal output stream for keywords like <code>error:</code>, <code>warning:</code>, or <code>Traceback</code>. When detected, a "Ask AI" prompt appears overlaying the terminal.
+ <div class="bg-black/30 p-4 rounded-lg border-l-2 border-lime-300">
+ <h4 class="text-sm font-bold text-white mb-2">How it works:</h4>
+ <ol class="list-decimal list-inside text-xs text-neutral-400 space-y-2">
+ <li><strong>Capture:</strong> The client buffers the last 4KB of terminal output.</li>
+ <li><strong>Trigger:</strong> If an error keyword is found, the "Ask AI" button appears.</li>
+ <li><strong>Processing:</strong> Code + Error Log is sent to <strong>Gemini 2.5 Flash</strong>.</li>
+ <li><strong>Response:</strong> The AI generates a JSON object containing a plain-English explanation and the corrected code block.</li>
+ <li><strong>Patch:</strong> The user can review the fix in a modal and apply it to the editor instantly.</li>
+ </ol>
+ </div>
+ </section>
+
+ <!-- 06 Persistence -->
+ <section id="persistence" class="doc-section">
+ <h2 class="text-2xl font-heading font-bold text-lime-300 mb-6 flex items-center">
+ <span class="mr-3 text-neutral-600">06.</span> Sharing & Persistence
+ </h2>
+ <p class="text-neutral-300 mb-6">
+ Data storage is handled in two distinct layers to balance privacy and collaboration.
+ </p>
+
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-6">
+ <div class="border border-white/10 rounded-xl p-5">
+ <div class="flex items-center gap-2 mb-3 text-lime-300">
+ <i class="fas fa-hdd"></i>
+ <h3 class="font-bold">Local Persistence</h3>
+ </div>
+ <p class="text-xs text-neutral-400 leading-relaxed">
+ Every keystroke is saved to the browser's <code>localStorage</code>. This data never leaves your device until you execute or share it.
</p>
</div>
- <div class="p-6 rounded-xl bg-white/5 border border-white/10">
- <h4 class="text-white font-bold mb-2">2. Contextual Analysis</h4>
- <p class="text-sm text-neutral-400 mb-2">
- When the user requests help, the system packages:
+
+ <div class="border border-white/10 rounded-xl p-5">
+ <div class="flex items-center gap-2 mb-3 text-lime-300">
+ <i class="fas fa-share-alt"></i>
+ <h3 class="font-bold">Cloud Sharing</h3>
+ </div>
+ <p class="text-xs text-neutral-400 leading-relaxed">
+ Uploads a snapshot to <strong>Firebase</strong> generating a short 7-char ID (e.g., <code>?share=AbC123X</code>).
+ </p>
+ </div>
+
+ <div class="border border-white/10 rounded-xl p-5">
+ <div class="flex items-center gap-2 mb-3 text-lime-300">
+ <i class="fas fa-link"></i>
+ <h3 class="font-bold">Base64 / LZ Fallback</h3>
+ </div>
+ <p class="text-xs text-neutral-400 leading-relaxed">
+ Code can be encoded directly into the URL (e.g., <code>?code=...</code>).
+ <br><span class="text-lime-300/80 mt-2 block">Pro Tip: Use <strong>LZ-String</strong> compression to reduce URL length by ~60% vs standard Base64.</span>
</p>
- <ul class="list-disc list-inside text-xs text-neutral-500 space-y-1">
- <li>The full source code.</li>
- <li>The exact error message from the terminal.</li>
- <li>The language context.</li>
- </ul>
</div>
- </div>
-
- <div class="p-4 border-l-4 border-lime-300 bg-white/5 mt-4">
- <p class="text-sm text-neutral-300">
- <strong>Privacy Note:</strong> Your code is sent to the AI API <em>only</em> when you explicitly click the "Ask AI" link. The data is processed ephemerally and returned as a JSON object containing an explanation and the corrected code block.
- </p>
</div>
</section>
-
- <!-- Sharing & Persistence -->
- <section id="sharing" class="doc-section">
+
+ <!-- 07 Lifecycle -->
+ <section id="lifecycle" class="doc-section">
<h2 class="text-2xl font-heading font-bold text-lime-300 mb-6 flex items-center">
- <span class="mr-3">06.</span> Sharing & Persistence
+ <span class="mr-3 text-neutral-600">07.</span> Data Lifecycle
</h2>
- <p class="text-neutral-300 mb-4">
- The sharing functionality leverages <strong>Firebase Firestore</strong> to provide persistent access to code and execution results. This ensures that sharing a link not only loads the correct source code but also restores the exact terminal state seen at the moment of sharing.
- </p>
-
- <div class="code-block">
- <pre><code class="language-javascript">// Data Structure in Firestore
-{
- "code": "#include <stdio.h>\n...",
- "language": "c",
- "output": "Enter count: 5\nCounting to 5:\n1 2 3 4 5 \nDone.",
- "createdAt": "2023-10-27T10:00:00.000Z"
-}</code></pre>
+ <div class="overflow-x-auto">
+ <table class="w-full text-left text-sm text-neutral-400">
+ <thead class="bg-white/5 text-white font-mono uppercase text-xs">
+ <tr>
+ <th class="p-3">Stage</th>
+ <th class="p-3">Storage Location</th>
+ <th class="p-3">Duration</th>
+ <th class="p-3">Accessibility</th>
+ </tr>
+ </thead>
+ <tbody class="divide-y divide-white/5">
+ <tr>
+ <td class="p-3 text-lime-300">Drafting</td>
+ <td class="p-3">Browser LocalStorage</td>
+ <td class="p-3">Persistent (User Control)</td>
+ <td class="p-3">Private (Device Only)</td>
+ </tr>
+ <tr>
+ <td class="p-3 text-lime-300">Execution</td>
+ <td class="p-3">Server RAM / Temp File</td>
+ <td class="p-3">Ephemeral (Seconds)</td>
+ <td class="p-3">System (Deleted after run)</td>
+ </tr>
+ <tr>
+ <td class="p-3 text-lime-300">Sharing</td>
+ <td class="p-3">Firebase Firestore</td>
+ <td class="p-3">Permanent</td>
+ <td class="p-3">Public (Link Holders)</td>
+ </tr>
+ </tbody>
+ </table>
</div>
</section>
- <!-- Security -->
+ <!-- 08 Security -->
<section id="security" class="doc-section">
<h2 class="text-2xl font-heading font-bold text-lime-300 mb-6 flex items-center">
- <span class="mr-3">07.</span> Security & Limitations
+ <span class="mr-3 text-neutral-600">08.</span> Security Implementation
</h2>
<p class="text-neutral-300 mb-4">
- The current implementation runs code directly on the host instance. While effective for a portfolio demonstration or trusted environment, this setup is <strong>not sandboxed</strong>.
+ Security is enforced at the environment level to prevent abuse.
</p>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
- <div class="p-4 border border-red-500/30 bg-red-500/5 rounded-lg">
- <h4 class="text-red-400 font-bold mb-2">Current Limitations</h4>
- <ul class="text-xs text-neutral-400 space-y-1">
- <li>• No containerization (Docker)</li>
- <li>• No CPU/RAM usage quotas</li>
- <li>• File system access is unrestricted within user scope</li>
- </ul>
+ <div class="p-4 border border-white/10 bg-white/5 rounded-lg">
+ <h4 class="text-white font-bold mb-2">Sanitized Environment</h4>
+ <p class="text-xs text-neutral-400">
+ Subprocesses run with a stripped environment (`safe_env`). API keys, system paths, and sensitive variables are removed from the execution context.
+ </p>
</div>
- <div class="p-4 border border-lime-300/30 bg-lime-300/5 rounded-lg">
- <h4 class="text-lime-300 font-bold mb-2">Future Roadmap</h4>
- <ul class="text-xs text-neutral-400 space-y-1">
- <li>• Docker container per session</li>
- <li>• Timeouts for infinite loops</li>
- <li>• Network access restrictions</li>
- </ul>
+ <div class="p-4 border border-white/10 bg-white/5 rounded-lg">
+ <h4 class="text-white font-bold mb-2">Ephemeral Filesystem</h4>
+ <p class="text-xs text-neutral-400">
+ Scripts are written to temporary files (e.g., `temp_script.py`) which are unlinked/deleted immediately after execution or upon connection closure.
+ </p>
</div>
</div>
</section>
- <!-- Privacy & Data Handling -->
- <section id="privacy" class="doc-section">
+ <!-- 09 Developer / Contribution -->
+ <section id="contribution" class="doc-section">
<h2 class="text-2xl font-heading font-bold text-lime-300 mb-6 flex items-center">
- <span class="mr-3">08.</span> Privacy & Data Handling
+ <span class="mr-3 text-neutral-600">09.</span> Developers & Contribution
</h2>
- <p class="text-neutral-300 mb-4">
- We believe in complete transparency regarding how your data is handled. As a demonstration platform, we prioritize privacy and ephemeral execution.
+ <p class="text-neutral-300 mb-6">
+ We welcome contributions from the community. Whether you've found a bug, want to suggest a feature, or add support for a new language, here is how you can get involved.
</p>
- <div class="grid grid-cols-1 md:grid-cols-3 gap-6 my-8">
- <div class="p-6 rounded-xl bg-white/5 border border-white/10">
- <h3 class="text-white font-bold mb-2"><i class="fas fa-trash-alt text-lime-300 mr-2"></i> Ephemeral Execution</h3>
- <p class="text-sm text-neutral-400">
- By default, your code is processed in volatile memory and <strong>immediately deleted</strong> after execution. Code is stored in our database <strong>only</strong> when you explicitly click "Share".
- </p>
- </div>
- <div class="p-6 rounded-xl bg-white/5 border border-white/10">
- <h3 class="text-white font-bold mb-2"><i class="fas fa-user-secret text-lime-300 mr-2"></i> Anonymous</h3>
- <p class="text-sm text-neutral-400">
- No user accounts, cookies, or personal identifiers are required to use the compiler. Usage is completely anonymous.
+
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
+ <!-- Open Source Card -->
+ <div class="p-6 bg-white/5 rounded-xl border border-white/10 hover:border-lime-300/50 transition-colors group">
+ <i class="fab fa-github text-3xl text-white group-hover:text-lime-300 mb-4 transition-colors"></i>
+ <h3 class="text-xl font-bold text-white mb-2">Open Source</h3>
+ <p class="text-sm text-neutral-400 mb-6">
+ The core infrastructure is open-source. Clone the repo, create a branch, and submit a Pull Request.
</p>
+ <div class="flex gap-3">
+ <a href="https://github.com/notamitgamer/cloud-compiler/issues" target="_blank" class="px-3 py-1.5 rounded bg-white/10 text-xs font-mono hover:bg-white/20 transition-colors border border-white/10">
+ <i class="fas fa-bug mr-1"></i> Report Bug
+ </a>
+ <a href="https://github.com/notamitgamer/cloud-compiler" target="_blank" class="px-3 py-1.5 rounded bg-lime-300/10 text-lime-300 text-xs font-mono hover:bg-lime-300/20 transition-colors border border-lime-300/20">
+ <i class="fas fa-code-branch mr-1"></i> Fork Repo
+ </a>
+ </div>
</div>
- <div class="p-6 rounded-xl bg-white/5 border border-white/10">
- <h3 class="text-white font-bold mb-2"><i class="fab fa-github text-lime-300 mr-2"></i> Open Source</h3>
- <p class="text-sm text-neutral-400">
- The entire backend infrastructure is open source. You can audit the code yourself on <a href="https://github.com/notamitgamer/cloud-compiler" target="_blank" class="text-lime-300 hover:underline">GitHub</a>.
+
+ <!-- Contact Card -->
+ <div class="p-6 bg-white/5 rounded-xl border border-white/10">
+ <i class="fas fa-envelope-open-text text-3xl text-white mb-4"></i>
+ <h3 class="text-xl font-bold text-white mb-2">Direct Contact</h3>
+ <p class="text-sm text-neutral-400 mb-6">
+ For security vulnerabilities, private feedback, or collaboration inquiries, please email directly.
</p>
+ <a href="mailto:amitdutta4255@gmail.com" class="text-lime-300 font-mono text-sm hover:underline flex items-center gap-2">
+ <i class="fas fa-at"></i> amitdutta4255@gmail.com
+ </a>
</div>
</div>
</section>
- <!-- Legal & Disclaimer -->
+ <!-- 10 Disclaimer -->
<section id="disclaimer" class="doc-section">
<h2 class="text-2xl font-heading font-bold text-lime-300 mb-6 flex items-center">
- <span class="mr-3">09.</span> Legal & Disclaimer
+ <span class="mr-3 text-neutral-600">10.</span> Legal & Disclaimer
</h2>
<div class="p-6 rounded-xl border border-red-500/20 bg-red-500/5">
<p class="text-neutral-300 mb-4">
- This compiler infrastructure has been extensively refactored by <strong>Gemini 3 Pro</strong>. For bug reports or inquiries, please contact us via:
+ This compiler infrastructure utilizes Google's Generative AI services and Firebase for data storage.
</p>
<ul class="text-lime-300 font-mono text-sm mb-6 space-y-1">
<li><i class="fas fa-envelope mr-2"></i> <a href="mailto:amitdutta4255@gmail.com" class="hover:underline">amitdutta4255@gmail.com</a></li>
@@ -371,8 +459,11 @@ def check_and_install_packages(code):
<li><i class="fab fa-github mr-2"></i> <a href="https://github.com/notamitgamer" target="_blank" class="hover:underline">@notamitgamer</a></li>
</ul>
<div class="text-neutral-400 text-sm border-t border-white/10 pt-4 leading-relaxed">
- <p class="mb-2"><strong class="text-red-400">Disclaimer of Liability:</strong> Amit Dutta assumes no responsibility or liability for any consequences, errors, or data loss arising from the use of this compiler. This project is provided for demonstration purposes only.</p>
- <p class="italic opacity-80">"I explicitly disclaim any affiliation with or responsibility for the ongoing operation of this project. I reserve the absolute right to modify, suspend, or permanently delete this compiler and its associated services at any time, for any reason, without prior notice."</p>
+ <p class="mb-4"><strong class="text-red-400 uppercase">Disclaimer of Liability:</strong> This tool is provided "as is". Amit Dutta assumes no responsibility or liability for any consequences, errors, or data loss arising from the use of this compiler. This project is provided for demonstration purposes only.</p>
+
+ <p class="italic font-mono text-xs opacity-80 bg-black/20 p-3 border border-red-500/20 rounded">
+ "I explicitly disclaim any affiliation with or responsibility for the ongoing operation of this project. I reserve the absolute right to modify, suspend, or permanently delete this compiler and its associated services at any time, for any reason, without prior notice."
+ </p>
</div>
</div>
</section>
diff --git a/public/index.html b/public/index.html
@@ -95,18 +95,17 @@
background: rgba(190, 242, 100, 0.1);
}
- /* Buttons */
- .btn-primary {
- background-color: var(--accent);
- color: #000;
- font-family: 'Space Grotesk', sans-serif;
- font-weight: 700;
- transition: all 0.3s ease;
+ /* Feature Card (Smaller) */
+ .feature-card {
+ background: rgba(255, 255, 255, 0.02);
+ border: 1px solid var(--border-color);
+ border-radius: 1rem;
+ padding: 1.5rem;
+ transition: all 0.3s;
}
- .btn-primary:hover {
- background-color: #d9f99d; /* Lime 200 */
- transform: translateY(-2px);
- box-shadow: 0 4px 12px rgba(190, 242, 100, 0.3);
+ .feature-card:hover {
+ background: rgba(255, 255, 255, 0.04);
+ border-color: rgba(190, 242, 100, 0.3);
}
/* Animations */
@@ -139,18 +138,13 @@
::selection { background: var(--accent); color: black; }
- .typewriter h1 {
- border-right: none;
- }
+ .typewriter h1 { border-right: none; }
- /* Fade in animation for "Create." */
.fade-in-create {
animation: fadeIn 1s ease-in forwards;
opacity: 0;
}
- @keyframes fadeIn {
- to { opacity: 1; }
- }
+ @keyframes fadeIn { to { opacity: 1; } }
</style>
</head>
<body class="antialiased selection:bg-lime-300 selection:text-black">
@@ -171,16 +165,16 @@
<header class="text-center mb-16" data-aos="fade-up">
<div class="inline-block mb-4 px-4 py-1 rounded-full border border-white/10 bg-white/5 backdrop-blur-sm">
- <span class="text-xs font-mono text-lime-300 tracking-wider">/// REAL-TIME CLOUD COMPILER</span>
+ <span class="text-xs font-mono text-lime-300 tracking-wider">/// V2.0 SYSTEM ONLINE</span>
</div>
<div class="typewriter max-w-4xl mx-auto">
<h1 class="text-4xl sm:text-6xl md:text-7xl font-heading font-bold text-white mb-6 tracking-tight leading-tight">
<span id="type-text"></span><br />
- <span id="create-text" class="text-lime-300 fade-in-create hidden">Create.</span>
+ <span id="create-text" class="text-lime-300 fade-in-create hidden">Execute.</span>
</h1>
</div>
<p class="sub-text-fade opacity-0 text-base sm:text-lg md:text-xl text-gray-400 max-w-3xl mx-auto leading-relaxed mt-4 font-light">
- Experience powerful, server-side code execution with low-latency WebSocket connections. No simulations, just pure performance.
+ Professional-grade remote execution. Now with AI debugging, local auto-save, and CodeMirror editor integration.
</p>
</header>
@@ -195,7 +189,7 @@
</div>
<h2 class="text-2xl font-heading font-bold text-white mb-2 group-hover:text-lime-300 transition-colors">Standard C</h2>
<p class="text-sm text-neutral-500 mb-6 leading-relaxed">
- Compile standard C programs on a remote server using GCC. Supports pointers and memory logic.
+ Compile standard C programs on a remote Linux server using GCC. Supports pointers and low-level memory logic.
</p>
<div class="flex items-center text-xs font-mono text-lime-300 uppercase tracking-widest">
Launch <i class="fas fa-arrow-right ml-2 group-hover:translate-x-1 transition-transform"></i>
@@ -209,7 +203,7 @@
</div>
<h2 class="text-2xl font-heading font-bold text-white mb-2 group-hover:text-lime-300 transition-colors">Modern C++</h2>
<p class="text-sm text-neutral-500 mb-6 leading-relaxed">
- Execute C++ code via G++. Features standard input/output streaming and STL support.
+ Execute C++ code via G++. Features standard input/output streaming, STL support, and modern syntax.
</p>
<div class="flex items-center text-xs font-mono text-lime-300 uppercase tracking-widest">
Launch <i class="fas fa-arrow-right ml-2 group-hover:translate-x-1 transition-transform"></i>
@@ -223,7 +217,7 @@
</div>
<h2 class="text-2xl font-heading font-bold text-white mb-2 group-hover:text-lime-300 transition-colors">Python 3</h2>
<p class="text-sm text-neutral-500 mb-6 leading-relaxed">
- Full Python 3 environment with automatic package installation (pip) and interactive shell.
+ Full Python 3 environment with pip support. Includes enhanced indentation and real-time execution.
</p>
<div class="flex items-center text-xs font-mono text-lime-300 uppercase tracking-widest">
Launch <i class="fas fa-arrow-right ml-2 group-hover:translate-x-1 transition-transform"></i>
@@ -231,10 +225,6 @@
</a>
</div>
-
- <div class="text-center mt-8">
- <p class="text-[10px] font-mono text-neutral-600 uppercase tracking-widest">More languages loading...</p>
- </div>
</main>
<a href="#features" class="absolute bottom-10 animate-bounce text-neutral-600 hover:text-white transition-colors">
@@ -246,37 +236,54 @@
<section id="features" class="py-32 px-6 border-t border-white/5 bg-black/20 z-10">
<div class="max-w-6xl mx-auto">
<div class="text-center mb-20" data-aos="fade-up">
- <h2 class="text-3xl sm:text-4xl font-heading font-bold text-white mb-4">Engineered for <span class="text-lime-300">Performance</span></h2>
- <p class="text-neutral-400 max-w-xl mx-auto">A robust platform designed for real development tasks.</p>
+ <h2 class="text-3xl sm:text-4xl font-heading font-bold text-white mb-4">Features & <span class="text-lime-300">Capabilities</span></h2>
+ <p class="text-neutral-400 max-w-xl mx-auto">Upgraded with developer-focused tools for a complete coding experience.</p>
</div>
- <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
- <!-- Feature 1 -->
- <div class="compiler-card p-8" data-aos="fade-up" data-aos-delay="100">
- <i class="fas fa-server text-2xl text-lime-300 mb-6"></i>
- <h3 class="text-xl font-bold text-white mb-3">Real Backend</h3>
- <p class="text-sm text-neutral-500 leading-relaxed">Code is executed on a live Linux server instance, ensuring accurate runtime behavior.</p>
- </div>
- <!-- Feature 2 -->
- <div class="compiler-card p-8" data-aos="fade-up" data-aos-delay="200">
- <i class="fas fa-bolt text-2xl text-lime-300 mb-6"></i>
- <h3 class="text-xl font-bold text-white mb-3">Low Latency</h3>
- <p class="text-sm text-neutral-500 leading-relaxed">WebSockets provide a persistent connection for instant input/output streaming.</p>
- </div>
- <!-- Feature 3 -->
- <div class="compiler-card p-8" data-aos="fade-up" data-aos-delay="300">
- <i class="fas fa-box-open text-2xl text-lime-300 mb-6"></i>
- <h3 class="text-xl font-bold text-white mb-3">Auto-Pip Install</h3>
- <p class="text-sm text-neutral-500 leading-relaxed">The server detects missing Python libraries and installs them on the fly.</p>
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
+ <!-- Feature: Pro Editor -->
+ <div class="feature-card" data-aos="fade-up" data-aos-delay="100">
+ <i class="fas fa-code text-lime-300 mb-4 text-xl"></i>
+ <h3 class="text-lg font-bold text-white mb-2">Pro Code Editor</h3>
+ <p class="text-sm text-neutral-500 leading-relaxed">Powered by CodeMirror with Dracula theme, syntax highlighting, bracket matching, and line numbers.</p>
</div>
- <!-- Feature 4 (AI - NEW) -->
- <div class="compiler-card p-8 border-lime-300/30" data-aos="fade-up" data-aos-delay="400">
- <div class="flex items-center justify-between mb-6">
- <i class="fas fa-robot text-2xl text-lime-300"></i>
+
+ <!-- Feature: AI Debug -->
+ <div class="feature-card border-lime-300/30" data-aos="fade-up" data-aos-delay="200">
+ <div class="flex justify-between items-start">
+ <i class="fas fa-robot text-lime-300 mb-4 text-xl"></i>
<span class="text-[10px] bg-lime-300 text-black px-2 py-0.5 rounded font-bold uppercase">New</span>
</div>
- <h3 class="text-xl font-bold text-white mb-3">AI Auto-Debug</h3>
- <p class="text-sm text-neutral-500 leading-relaxed">Intelligent error detection and real-time code fixes powered by Gemini AI.</p>
+ <h3 class="text-lg font-bold text-white mb-2">AI Auto-Debug</h3>
+ <p class="text-sm text-neutral-500 leading-relaxed">Detects runtime errors and offers one-click code fixes powered by Gemini 2.5 Flash.</p>
+ </div>
+
+ <!-- Feature: Persistence -->
+ <div class="feature-card" data-aos="fade-up" data-aos-delay="300">
+ <i class="fas fa-save text-lime-300 mb-4 text-xl"></i>
+ <h3 class="text-lg font-bold text-white mb-2">Auto-Save & Share</h3>
+ <p class="text-sm text-neutral-500 leading-relaxed">Never lose work. Code is saved locally automatically and can be stored in the cloud via share links.</p>
+ </div>
+
+ <!-- Feature: Mobile -->
+ <div class="feature-card" data-aos="fade-up" data-aos-delay="400">
+ <i class="fas fa-mobile-alt text-lime-300 mb-4 text-xl"></i>
+ <h3 class="text-lg font-bold text-white mb-2">Mobile Optimized</h3>
+ <p class="text-sm text-neutral-500 leading-relaxed">Includes a custom helper bar for quick access to symbols like <code>{ } [ ] ;</code> on touch devices.</p>
+ </div>
+
+ <!-- Feature: Backend -->
+ <div class="feature-card" data-aos="fade-up" data-aos-delay="500">
+ <i class="fas fa-server text-lime-300 mb-4 text-xl"></i>
+ <h3 class="text-lg font-bold text-white mb-2">Real Backend</h3>
+ <p class="text-sm text-neutral-500 leading-relaxed">Code runs on a live Linux instance via WebSockets for authentic I/O streaming performance.</p>
+ </div>
+
+ <!-- Feature: Export -->
+ <div class="feature-card" data-aos="fade-up" data-aos-delay="600">
+ <i class="fas fa-download text-lime-300 mb-4 text-xl"></i>
+ <h3 class="text-lg font-bold text-white mb-2">One-Click Export</h3>
+ <p class="text-sm text-neutral-500 leading-relaxed">Download your source code directly as a <code>.py</code>, <code>.c</code>, or <code>.cpp</code> file.</p>
</div>
</div>
</div>
@@ -286,7 +293,6 @@
<footer class="py-12 border-t border-white/5 z-10 bg-black/40">
<div class="max-w-6xl mx-auto px-6 text-center">
- <!-- Documentation Link Added Here -->
<div class="mb-8">
<a href="./docs.html" class="inline-flex items-center gap-2 text-sm font-mono text-neutral-500 hover:text-lime-300 transition-colors uppercase tracking-widest border border-white/10 px-4 py-2 rounded-full bg-white/5 hover:bg-white/10">
<i class="fas fa-book"></i> Read Documentation
diff --git a/public/python.html b/public/python.html
@@ -15,6 +15,15 @@
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
+ <!-- CodeMirror 5 (Professional Editor) -->
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.css">
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/theme/dracula.min.css">
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.js"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/python/python.min.js"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/addon/edit/closebrackets.min.js"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/addon/edit/matchbrackets.min.js"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/addon/selection/active-line.min.js"></script>
+
<!-- Xterm.js -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.css" />
<script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.js"></script>
@@ -59,43 +68,74 @@
}
@keyframes scanline { 0% { top: -10vh; } 100% { top: 110vh; } }
- /* Custom Editor Styles */
- .editor-container {
- position: relative;
- width: 100%;
- height: 100%;
- overflow: hidden;
+ /* CodeMirror Customization */
+ .CodeMirror {
+ height: 100% !important;
font-family: 'JetBrains Mono', monospace;
font-size: 13px;
- line-height: 1.5;
+ background-color: #0c0f16 !important; /* Slightly lighter than pure black */
+ color: #f8f8f2;
+ }
+ .CodeMirror-gutters {
+ background-color: #0c0f16 !important;
+ border-right: 1px solid rgba(255,255,255,0.05) !important;
+ }
+ .CodeMirror-linenumber {
+ color: #555 !important;
+ }
+ .CodeMirror-cursor {
+ border-left: 2px solid var(--accent) !important;
+ }
+ .CodeMirror-selected {
+ background: rgba(190, 242, 100, 0.2) !important;
+ }
+ .cm-s-dracula .CodeMirror-activeline-background {
+ background: rgba(255, 255, 255, 0.03) !important;
}
- /* The actual textarea for typing */
- #code-input {
- position: absolute;
- left: 0;
- top: 0;
- width: 100%;
- height: 100%;
- padding: 1rem;
- border: none;
- outline: none;
- background: transparent;
- color: #e2e8f0; /* Visible Text */
- caret-color: #fff; /* Cursor visible */
- z-index: 1;
- resize: none;
- white-space: pre;
- overflow: auto;
- }
-
- .xterm-viewport { overflow-y: auto !important; }
-
- ::selection { background: var(--accent); color: black; }
+ /* Scrollbar styling */
+ .CodeMirror-scroll::-webkit-scrollbar,
+ .xterm-viewport::-webkit-scrollbar,
+ .custom-scrollbar::-webkit-scrollbar {
+ width: 6px; height: 6px;
+ }
+ .CodeMirror-scroll::-webkit-scrollbar-track,
+ .xterm-viewport::-webkit-scrollbar-track,
+ .custom-scrollbar::-webkit-scrollbar-track {
+ background: #050505;
+ }
+ .CodeMirror-scroll::-webkit-scrollbar-thumb,
+ .xterm-viewport::-webkit-scrollbar-thumb,
+ .custom-scrollbar::-webkit-scrollbar-thumb {
+ background: #333; border-radius: 3px;
+ }
- .custom-scrollbar::-webkit-scrollbar { width: 6px; height: 6px; }
- .custom-scrollbar::-webkit-scrollbar-track { background: #050505; }
- .custom-scrollbar::-webkit-scrollbar-thumb { background: #333; border-radius: 3px; }
+ /* Mobile Helper Bar */
+ #mobile-helper-bar {
+ display: none; /* Shown via JS on mobile */
+ background: #151515;
+ border-top: 1px solid rgba(255,255,255,0.1);
+ padding: 8px;
+ overflow-x: auto;
+ white-space: nowrap;
+ -webkit-overflow-scrolling: touch;
+ }
+ @media (max-width: 768px) {
+ #mobile-helper-bar { display: flex; gap: 8px; }
+ }
+ .helper-key {
+ background: rgba(255,255,255,0.1);
+ color: var(--accent);
+ border: 1px solid rgba(255,255,255,0.1);
+ border-radius: 4px;
+ padding: 6px 12px;
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 12px;
+ font-weight: bold;
+ cursor: pointer;
+ user-select: none;
+ }
+ .helper-key:active { background: var(--accent); color: black; }
/* Toast Notification */
.toast {
@@ -182,7 +222,7 @@
to { transform: translateY(0); opacity: 1; }
}
- /* AI Modal */
+ /* Modals (AI & Clear) */
.modal-overlay {
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.8); backdrop-filter: blur(5px);
@@ -194,7 +234,6 @@
background: #0f0f0f;
border: 1px solid var(--accent);
width: 90%; max-width: 600px;
- max-height: 80vh;
border-radius: 12px;
display: flex; flex-direction: column;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.5), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
@@ -206,6 +245,31 @@
0% { transform: scale(0.9); opacity: 0; }
100% { transform: scale(1); opacity: 1; }
}
+
+ /* Editor Toolbar Buttons */
+ .editor-btn {
+ background: rgba(255,255,255,0.05);
+ border: 1px solid rgba(255,255,255,0.1);
+ color: #737373;
+ width: 32px;
+ height: 32px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 4px;
+ transition: all 0.2s;
+ cursor: pointer;
+ }
+ .editor-btn:hover {
+ background: rgba(255,255,255,0.1);
+ color: #fff;
+ border-color: rgba(255,255,255,0.3);
+ }
+ .editor-btn.danger:hover {
+ background: rgba(239, 68, 68, 0.1);
+ color: #ef4444;
+ border-color: #ef4444;
+ }
</style>
</head>
<body class="antialiased selection:bg-lime-300 selection:text-black">
@@ -220,9 +284,29 @@
<span>Error detected. <span id="ask-ai-link" class="ai-link">Ask AI</span> to fix it.</span>
</div>
+ <!-- Clear Confirmation Modal -->
+ <div id="clear-modal" class="modal-overlay">
+ <div class="modal-content max-w-[320px] p-0 border-red-500/50">
+ <div class="bg-red-500/10 px-6 py-4 border-b border-red-500/20">
+ <h3 class="font-heading font-bold text-lg text-red-500 flex items-center gap-2">
+ <i class="fas fa-exclamation-circle"></i> CLEAR CODE
+ </h3>
+ </div>
+ <div class="p-6 text-center">
+ <p class="text-neutral-300 text-sm font-sans mb-6">Are you sure you want to clear the editor? This action will remove all current code.</p>
+ <div class="flex justify-center gap-3">
+ <button id="cancel-clear-btn" class="px-4 py-2 rounded text-xs font-mono font-bold uppercase text-neutral-400 hover:text-white hover:bg-white/10 transition-all border border-transparent">Cancel</button>
+ <button id="confirm-clear-btn" class="bg-red-500/10 hover:bg-red-500 hover:text-black text-red-500 border border-red-500/20 px-4 py-2 rounded text-xs font-mono font-bold uppercase transition-all flex items-center gap-2">
+ <i class="fas fa-trash-alt"></i> Confirm
+ </button>
+ </div>
+ </div>
+ </div>
+ </div>
+
<!-- AI Fix Modal -->
<div id="ai-modal" class="modal-overlay">
- <div class="modal-content">
+ <div class="modal-content max-h-[80vh]">
<!-- Modal Header -->
<div class="bg-white/5 px-6 py-4 border-b border-white/10 flex justify-between items-center">
<h3 class="font-heading font-bold text-lg text-lime-300 flex items-center gap-2">
@@ -270,15 +354,29 @@
<!-- Header (Responsive) -->
<header class="h-auto md:h-16 flex flex-col md:flex-row items-center justify-between px-4 py-3 md:px-6 shrink-0 border-b border-white/10 z-20 bg-neutral-900/50 backdrop-blur-md gap-3 md:gap-4">
- <!-- Top Row: Title & Status -->
- <div class="flex items-center justify-between w-full md:w-auto">
+ <!-- Top Row: Title & Editor Tools -->
+ <div class="flex items-center justify-between w-full md:w-auto gap-6">
<h1 class="text-lg md:text-xl font-heading font-bold text-white tracking-tight flex items-center">
<span class="text-lime-300 mr-2">//</span>PY.COMPILER
<span id="conn-dot" class="pulse-dot disconnected ml-3" title="Disconnected"></span>
</h1>
+
+ <!-- Editor Tools (Undo/Redo/Clear) -->
+ <div class="flex items-center gap-2">
+ <button id="undo-btn" class="editor-btn" title="Undo (Ctrl+Z)">
+ <i class="fas fa-undo text-xs"></i>
+ </button>
+ <button id="redo-btn" class="editor-btn" title="Redo (Ctrl+Y)">
+ <i class="fas fa-redo text-xs"></i>
+ </button>
+ <div class="w-px h-4 bg-white/10 mx-1"></div>
+ <button id="clear-code-btn" class="editor-btn danger" title="Clear Code">
+ <i class="fas fa-times text-xs"></i>
+ </button>
+ </div>
<!-- Mobile Only: Exit Link -->
- <a href="./index.html" class="md:hidden font-mono text-[10px] text-neutral-500 hover:text-white uppercase tracking-wider transition-colors">[ EXIT ]</a>
+ <a href="./index.html" class="md:hidden font-mono text-[10px] text-neutral-500 hover:text-white uppercase tracking-wider transition-colors ml-auto">[ EXIT ]</a>
</div>
<!-- Middle: Connection Text -->
@@ -290,6 +388,10 @@
<div class="flex items-center gap-2 w-full md:w-auto justify-end">
<a href="./index.html" class="hidden md:inline font-mono text-[10px] text-neutral-500 hover:text-white uppercase tracking-wider transition-colors mr-2">[ EXIT ]</a>
+ <button id="download-btn" class="bg-white/5 hover:bg-neutral-800 text-white border border-white/10 px-3 py-2 rounded font-mono text-[10px] font-bold uppercase tracking-wider flex items-center justify-center transition-all" title="Download .py">
+ <i class="fas fa-download"></i>
+ </button>
+
<button id="reset-btn" class="bg-white/5 hover:bg-neutral-800 text-white border border-white/10 px-3 py-2 rounded font-mono text-[10px] font-bold uppercase tracking-wider flex items-center justify-center transition-all" title="Reset Terminal">
<i class="fas fa-rotate-left"></i>
</button>
@@ -297,10 +399,6 @@
<button id="share-btn" class="bg-white/5 hover:bg-neutral-800 text-white border border-white/10 px-3 py-2 rounded font-mono text-[10px] font-bold uppercase tracking-wider flex items-center justify-center transition-all" title="Share Code">
<i class="fas fa-share-alt"></i>
</button>
-
- <button id="copy-btn" class="bg-white/5 hover:bg-neutral-800 text-white border border-white/10 px-3 py-2 rounded font-mono text-[10px] font-bold uppercase tracking-wider flex items-center justify-center transition-all" title="Copy Output">
- <i class="fas fa-copy"></i>
- </button>
<button id="run-btn" disabled class="bg-white/5 hover:bg-lime-300 hover:text-black disabled:opacity-50 disabled:cursor-not-allowed text-white border border-white/10 px-4 py-2 rounded font-mono text-xs font-bold uppercase tracking-wider flex items-center gap-2 transition-all flex-1 md:flex-none justify-center shadow-lg shadow-black/50">
<i class="fas fa-play text-[10px]"></i> <span class="md:hidden lg:inline">EXECUTE</span><span class="hidden md:inline lg:hidden">RUN</span>
@@ -318,23 +416,41 @@
<span class="text-neutral-600">PYTHON 3</span>
</div>
- <!-- Custom Editor Area -->
- <div class="editor-container relative bg-[#0c0f16]">
- <textarea id="code-input" spellcheck="false" autocapitalize="off" autocomplete="off" autocorrect="off" class="custom-scrollbar">import time
+ <!-- CodeMirror Container -->
+ <div id="editor-container" class="relative bg-[#0c0f16] flex-1 overflow-hidden">
+ <textarea id="code-input" class="hidden">import time
# Simple Clock Preview
print("Current Time:")
print(time.strftime("%H:%M:%S"))
print("Done.")</textarea>
</div>
-
+
+ <!-- Mobile Helper Bar -->
+ <div id="mobile-helper-bar">
+ <div class="helper-key" data-char=" ">Tab</div>
+ <div class="helper-key" data-char=":">:</div>
+ <div class="helper-key" data-char="=">=</div>
+ <div class="helper-key" data-char="()">()</div>
+ <div class="helper-key" data-char="[]">[]</div>
+ <div class="helper-key" data-char="{}">{}</div>
+ <div class="helper-key" data-char='""'>""</div>
+ <div class="helper-key" data-char="''">''</div>
+ <div class="helper-key" data-char="#">#</div>
+ <div class="helper-key" data-char="+">+</div>
+ <div class="helper-key" data-char="-">-</div>
+ <div class="helper-key" data-char="print()">print</div>
+ </div>
</div>
<!-- Terminal -->
<div class="flex-1 flex flex-col h-[40%] md:h-auto min-h-0 bg-[#050505]">
<div class="bg-black/20 px-4 py-2 text-[10px] font-mono text-lime-300 uppercase border-b border-white/10 flex justify-between items-center shrink-0">
<span>OUT // TERMINAL</span>
- <span class="text-neutral-600">XTERM.JS</span>
+ <div class="flex gap-2">
+ <button id="copy-btn" class="text-neutral-500 hover:text-white transition-colors" title="Copy Output"><i class="fas fa-copy"></i></button>
+ <span class="text-neutral-600">XTERM.JS</span>
+ </div>
</div>
<div class="flex-1 relative">
<div id="terminal-container" class="absolute inset-0 w-full h-full p-2 overflow-hidden"></div>
@@ -383,11 +499,19 @@ print("Done.")</textarea>
const resetBtn = document.getElementById('reset-btn');
const shareBtn = document.getElementById('share-btn');
const copyBtn = document.getElementById('copy-btn');
+ const downloadBtn = document.getElementById('download-btn');
const connDot = document.getElementById('conn-dot');
const connectionStatus = document.getElementById('connection-status');
- const codeInput = document.getElementById('code-input');
const toast = document.getElementById('toast');
+ // Editor Tool Elements
+ const undoBtn = document.getElementById('undo-btn');
+ const redoBtn = document.getElementById('redo-btn');
+ const clearCodeBtn = document.getElementById('clear-code-btn');
+ const clearModal = document.getElementById('clear-modal');
+ const cancelClearBtn = document.getElementById('cancel-clear-btn');
+ const confirmClearBtn = document.getElementById('confirm-clear-btn');
+
// AI Elements
const aiSuggestion = document.getElementById('ai-suggestion');
const askAiLink = document.getElementById('ask-ai-link');
@@ -406,30 +530,126 @@ print("Done.")</textarea>
let currentLine = "";
let startTime = 0;
let lastSuggestedCode = "";
+ let outputBuffer = "";
+
+ // --- CodeMirror Setup ---
+ const editor = CodeMirror.fromTextArea(document.getElementById("code-input"), {
+ mode: "python",
+ theme: "dracula",
+ lineNumbers: true,
+ indentUnit: 4,
+ matchBrackets: true,
+ autoCloseBrackets: true,
+ styleActiveLine: true,
+ inputStyle: "contenteditable", // Better for mobile
+ viewportMargin: Infinity,
+ extraKeys: {
+ "Backspace": function(cm) {
+ if (cm.somethingSelected()) {
+ cm.execCommand("delCharBefore");
+ return;
+ }
+ var cursor = cm.getCursor();
+ var line = cm.getLine(cursor.line);
+ // If cursor is not at start, and everything before it is whitespace
+ if (cursor.ch > 0 && /^\s+$/.test(line.slice(0, cursor.ch))) {
+ cm.execCommand("indentLess");
+ } else {
+ cm.execCommand("delCharBefore");
+ }
+ }
+ },
+ });
- // --- EDITOR LOGIC (Indentation for Python) ---
- codeInput.addEventListener('keydown', function(e) {
- if (e.key === 'Tab') {
- e.preventDefault();
- const start = this.selectionStart;
- const end = this.selectionEnd;
- this.setRangeText(' ', start, end, 'end');
- } else if (e.key === 'Enter') {
- e.preventDefault();
- const start = this.selectionStart;
- const end = this.selectionEnd;
- const value = this.value;
- const lineStart = value.lastIndexOf('\n', start - 1) + 1;
- const currentLineText = value.substring(lineStart, start);
- let match = currentLineText.match(/^(\s*)/);
- let indent = match ? match[1] : "";
-
- // Python specific: indent if line ends with colon
- if (currentLineText.trim().endsWith(':')) indent += " ";
+ // Fix resize issues
+ window.addEventListener('resize', () => editor.refresh());
+
+ // Local Auto-Save
+ const LOCAL_STORAGE_KEY = 'py_compiler_code_autosave';
+ editor.on('change', () => {
+ localStorage.setItem(LOCAL_STORAGE_KEY, editor.getValue());
+ });
+
+ // Load Auto-Save on Start (if not shared)
+ const savedCode = localStorage.getItem(LOCAL_STORAGE_KEY);
+ if (savedCode && !window.location.search.includes('share') && !window.location.search.includes('code')) {
+ editor.setValue(savedCode);
+ }
+
+ // --- Editor Toolbar Logic ---
+
+ // Undo/Redo
+ undoBtn.addEventListener('click', () => { editor.undo(); editor.focus(); });
+ redoBtn.addEventListener('click', () => { editor.redo(); editor.focus(); });
+
+ // Clear Code with Confirmation
+ clearCodeBtn.addEventListener('click', () => {
+ clearModal.classList.add('open');
+ });
+
+ cancelClearBtn.addEventListener('click', () => {
+ clearModal.classList.remove('open');
+ });
+
+ confirmClearBtn.addEventListener('click', () => {
+ editor.setValue('');
+ editor.clearHistory(); // Optional: Clear history if you want a true "fresh start"
+ editor.focus();
+ clearModal.classList.remove('open');
+ showToast("Editor Cleared");
+ });
+
+ // Close modal on outside click
+ clearModal.addEventListener('click', (e) => {
+ if (e.target === clearModal) clearModal.classList.remove('open');
+ });
+
+
+ // --- Mobile Helper Bar ---
+ document.querySelectorAll('.helper-key').forEach(key => {
+ key.addEventListener('click', (e) => {
+ const char = key.getAttribute('data-char');
+ const doc = editor.getDoc();
+ const cursor = doc.getCursor();
- this.setRangeText('\n' + indent, start, end, 'end');
- this.blur(); this.focus();
- }
+ if (char === '()') {
+ doc.replaceRange('()', cursor);
+ doc.setCursor({line: cursor.line, ch: cursor.ch + 1});
+ } else if (char === '[]') {
+ doc.replaceRange('[]', cursor);
+ doc.setCursor({line: cursor.line, ch: cursor.ch + 1});
+ } else if (char === '{}') {
+ doc.replaceRange('{}', cursor);
+ doc.setCursor({line: cursor.line, ch: cursor.ch + 1});
+ } else if (char === '""') {
+ doc.replaceRange('""', cursor);
+ doc.setCursor({line: cursor.line, ch: cursor.ch + 1});
+ } else if (char === "''") {
+ doc.replaceRange("''", cursor);
+ doc.setCursor({line: cursor.line, ch: cursor.ch + 1});
+ } else if (char === "print()") {
+ doc.replaceRange('print()', cursor);
+ doc.setCursor({line: cursor.line, ch: cursor.ch + 6});
+ } else {
+ doc.replaceRange(char, cursor);
+ }
+ editor.focus();
+ });
+ });
+
+ // --- Download Button ---
+ downloadBtn.addEventListener('click', () => {
+ const code = editor.getValue();
+ const blob = new Blob([code], { type: 'text/x-python' });
+ const url = window.URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = 'main.py';
+ document.body.appendChild(a);
+ a.click();
+ window.URL.revokeObjectURL(url);
+ document.body.removeChild(a);
+ showToast("Downloaded main.py");
});
window.addEventListener('load', async () => {
@@ -448,7 +668,7 @@ print("Done.")</textarea>
if (docSnap.exists()) {
const data = docSnap.data();
- codeInput.value = data.code;
+ editor.setValue(data.code);
// Restore Output if it exists
if (data.output && term) {
@@ -464,7 +684,6 @@ print("Done.")</textarea>
setTimeout(() => toast.classList.remove('show'), 3000);
} catch (e) {
console.error("Fetch error:", e);
- // More descriptive error for user
if (e.code === 'permission-denied') {
toast.textContent = "Error: Database Access Denied";
} else {
@@ -474,7 +693,7 @@ print("Done.")</textarea>
}
} else if (sharedCodeOld) {
// Fallback for Base64
- try { codeInput.value = atob(sharedCodeOld); }
+ try { editor.setValue(atob(sharedCodeOld)); }
catch(e) {}
}
setTimeout(connect, 500);
@@ -575,10 +794,10 @@ print("Done.")</textarea>
const msg = JSON.parse(event.data);
if (msg.type === 'stdout') {
let text = msg.data;
-
+ outputBuffer += text;
+
// Trigger AI Overlay on Error
- // Python errors: "Traceback (most recent call last):", "SyntaxError:", "NameError:", etc.
- if (text.toLowerCase().includes("error") || text.toLowerCase().includes("traceback")) {
+ if (outputBuffer.toLowerCase().includes("error") || outputBuffer.toLowerCase().includes("traceback")) {
aiSuggestion.classList.add('visible');
}
@@ -603,13 +822,17 @@ print("Done.")</textarea>
runBtn.addEventListener('click', () => {
if (!socket || socket.readyState !== WebSocket.OPEN) return;
- document.activeElement.blur();
+ // Force focus out to hide mobile keyboards
+ if (document.activeElement) document.activeElement.blur();
+
term.reset();
currentLine = "";
- aiSuggestion.classList.remove('visible'); // Hide previous suggestions
+ outputBuffer = "";
+ aiSuggestion.classList.remove('visible');
+
+ // Use Editor Value instead of Textarea
+ let code = editor.getValue();
- let code = codeInput.value;
- // Basic sanitization
code = code.replace(/\u00A0/g, " ").replace(/[\u2018\u2019]/g, "'").replace(/[\u201C\u201D]/g, '"');
startTime = Date.now();
@@ -629,16 +852,14 @@ print("Done.")</textarea>
// 1. Open Modal and Request AI Fix
askAiLink.addEventListener('click', () => {
const errorContext = getTerminalContent();
- const currentCode = codeInput.value;
+ const currentCode = editor.getValue();
- // UI Update
aiModal.classList.add('open');
aiLoading.classList.remove('hidden');
aiResult.classList.add('hidden');
aiErrorMsg.classList.add('hidden');
applyFixBtn.disabled = true;
- // Send Request
socket.send(JSON.stringify({
type: 'ai_fix',
code: currentCode,
@@ -669,7 +890,7 @@ print("Done.")</textarea>
// 4. Apply Fix
applyFixBtn.addEventListener('click', () => {
if (lastSuggestedCode) {
- codeInput.value = lastSuggestedCode;
+ editor.setValue(lastSuggestedCode);
closeAiModal();
showToast("Fix Applied Successfully!");
aiSuggestion.classList.remove('visible');
@@ -690,32 +911,30 @@ print("Done.")</textarea>
resetBtn.addEventListener('click', () => {
term.reset();
currentLine = "";
+ outputBuffer = "";
aiSuggestion.classList.remove('visible');
term.write(`\x1b[38;2;190;242;100m> CONNECTION ESTABLISHED.\x1b[0m\r\n> python3 main.py\r\n`);
});
// --- Share Functionality with Firebase ---
shareBtn.addEventListener('click', async () => {
- // Original Share icon animation
const originalIcon = shareBtn.innerHTML;
shareBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
shareBtn.disabled = true;
try {
- // Robust Auth Check
if (!auth.currentUser) {
try {
await signInAnonymously(auth);
} catch (authError) {
- console.warn("Anonymous auth failed (proceeding to unauthenticated write attempt):", authError.code);
+ console.warn("Anonymous auth failed:", authError.code);
}
}
- const code = codeInput.value;
+ const code = editor.getValue();
const output = getTerminalContent();
const shareId = generateId();
- // Save to Firestore
await setDoc(doc(db, "shares", shareId), {
code: code,
language: 'python',
@@ -723,13 +942,11 @@ print("Done.")</textarea>
createdAt: new Date().toISOString()
});
- // Generate URL
const url = new URL(window.location.href);
- url.searchParams.delete('code'); // Remove legacy param
+ url.searchParams.delete('code');
url.searchParams.set('share', shareId);
const shareUrl = url.toString();
- // Share or Copy
if (navigator.share) {
try {
await navigator.share({