import { Circle, Code as Format, Home, HorizontalSplit, PlayArrow, VerticalSplit, } from "@mui/icons-material"; import { LoadingButton } from "@mui/lab"; import { Box, Button, Chip, Stack, ToggleButton, ToggleButtonGroup, Typography, } from "@mui/material"; import ansi from "ansicolor"; import dynamic from "next/dynamic"; import Head from "next/head"; import { useRouter } from "next/router"; import React, { useEffect, useRef, useState } from "react"; import Layouts from "../../components/Layouts"; import langs from "../../static/langs.json"; import { EventEmitter } from "../../utils/EventEmitter"; ansi.rgb = { green: "#00FD61", }; const RijuTerminal = dynamic(() => import("../../components/RijuTerminal"), { ssr: false, }); const RijuEditor = dynamic(() => import("../../components/RijuEditor"), { ssr: false, }); const DEBUG = true; let serviceLogBuffers = {}; let serviceLogLines = {}; const CodeRunner = (props) => { const router = useRouter(); const { langConfig } = props; const editorRef = useRef(null); const [config, setConfig] = useState(langConfig); const [mounted, setMounted] = useState(false); const [isRunning, setRunning] = useState(false); const [isFormatting, setFormatting] = useState(false); const [isLspStarted, setLspStarted] = useState(false); const [isLspRequested, setIsLspRequested] = useState(false); const [splitType, setSplitType] = useState("horizontal"); const [status, setStatus] = useState("connecting"); function sendToTerminal(type, data) { EventEmitter.dispatch("terminal", { type, data }); } function connect() { serviceLogBuffers = {}; serviceLogLines = {}; setStatus("connecting"); const socket = new WebSocket( // (document.location.protocol === "http:" ? "ws://" : "wss://") + "wss://" + "riju.codes" + `/api/v1/ws?lang=${encodeURIComponent(config.id)}` ); socket.addEventListener("open", () => { console.log("Successfully connected to server playground"); setStatus("connected"); }); EventEmitter.subscribe("send", (payload) => { if (DEBUG) { console.log("SEND:", payload); } if (socket) { socket.send(JSON.stringify(payload)); } }); socket.addEventListener("message", async (event) => { let message; try { message = JSON.parse(event.data); } catch (err) { console.error("Malformed message from server:", event.data); return; } if ( DEBUG && message && message.event !== "lspOutput" && message.event !== "serviceLog" ) { console.log("RECEIVE:", message); } if (message && message.event && message.event !== "error") { // retryDelayMs = initialRetryDelayMs; } switch (message && message.event) { case "terminalClear": // term.reset(); sendToTerminal("terminalClear"); return; case "terminalOutput": if (typeof message.output !== "string") { console.error("Unexpected message from server:", message); return; } sendToTerminal("terminalOutput", ansi.white(message.output)); setRunning(false); return; case "formattedCode": setFormatting(false); if ( typeof message.code !== "string" || typeof message.originalCode !== "string" ) { console.error("Unexpected message from server:", message); return; } if (editorRef.current?.getValue() === message.originalCode) { editorRef.current?.setValue(message.code); } return; case "lspStopped": setIsLspRequested(false); setLspStarted(false); EventEmitter.dispatch("lspStopped"); break; case "lspStarted": setLspStarted(true); setIsLspRequested(false); if (typeof message.root !== "string") { console.error("Unexpected message from server:", message); return; } EventEmitter.dispatch("lspStarted", { message, socket }); setTimeout(() => {}, 3000); return; case "lspOutput": // Should be handled by RijuMessageReader return; case "serviceLog": if ( typeof message.service !== "string" || typeof message.output !== "string" ) { console.error("Unexpected message from server:", message); return; } let buffer = serviceLogBuffers[message.service] || ""; let lines = serviceLogLines[message.service] || []; buffer += message.output; while (buffer.includes("\n")) { const idx = buffer.indexOf("\n"); const line = buffer.slice(0, idx); buffer = buffer.slice(idx + 1); lines.push(line); if (DEBUG) { console.log(`${message.service.toUpperCase()} || ${line}`); } } serviceLogBuffers[message.service] = buffer; serviceLogLines[message.service] = lines; return; case "serviceFailed": if ( typeof message.service !== "string" || typeof message.error !== "string" ) { console.error("Unexpected message from server:", message); return; } switch (message.service) { case "formatter": setFormatting(false); // showError({ // message: "Could not prettify code!", // data: serviceLogLines["formatter"].join("\n"), // }); break; case "lsp": setLspStarted(false); setIsLspRequested(false); EventEmitter.dispatch("lspStopped"); break; case "terminal": sendToTerminal( "terminalOutput", ansi.red(`\r\n[${message.error}]`) ); break; } return; case "langConfig": console.log("Lang Config", message); // We could use this message instead of hardcoding the // language config into the HTML page returned from the // server, but for now we just ignore it. return; default: console.error("Unexpected message from server:", message); } }); socket.addEventListener("close", (event) => { if (event.wasClean) { console.log("Connection closed cleanly"); } else { console.error("Connection died"); } EventEmitter.dispatch("lspStopped"); setRunning(false); setLspStarted(false); setIsLspRequested(false); setStatus("idle"); }); return socket; } useEffect(() => { if (!config || !mounted) return; const socket = connect(); return () => socket && socket.close(); }, [config, mounted]); function showValue() { setRunning(true); EventEmitter.dispatch("send", { event: "runCode", code: editorRef.current.getValue(), }); } function sendFormat() { setFormatting(true); serviceLogBuffers["formatter"] = ""; serviceLogLines["formatter"] = []; EventEmitter.dispatch("send", { event: "formatCode", code: editorRef.current.getValue(), }); } function editorDidMount(editor, monaco) { editorRef.current = editor; editor.addAction({ id: "runCode", label: "Run", keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter], contextMenuGroupId: "2_execution", run: () => { showValue(); }, }); setMounted(true); } const handleLspClick = () => { setIsLspRequested(true); if (isLspStarted) { EventEmitter.dispatch("send", { event: "lspStop", }); } else { EventEmitter.dispatch("send", { event: "lspStart", }); } }; const handleChange = () => { if (status != "idle") { return; } else { connect(); } }; const handleLayout = (event, value) => { const e = document.querySelector(".split .gutter"); e.classList.replace(`gutter-${splitType}`, `gutter-${value}`); const es = document.querySelectorAll(".split .panel"); for (const e of es) { e.removeAttribute("style"); e.removeAttribute("style"); } setSplitType(value); }; return ( <>