import MonacoEditor, { useMonaco } from "@monaco-editor/react"; import { Circle, Code as Format, Home, PlayArrow } from "@mui/icons-material"; import { LoadingButton } from "@mui/lab"; import { Box, Button, Divider, Stack, 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 { createMessageConnection } from "vscode-jsonrpc"; import langs from "../../static/langs.json"; import { EventEmitter } from "../../utils/EventEmitter"; ansi.rgb = { green: "#00FD61", }; const RijuTerminal = dynamic(() => import("../../components/RijuTerminal"), { ssr: false, }); const DEBUG = true; let clientDisposable = null; let servicesDisposable = null; const serviceLogBuffers = {}; 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 monaco = useMonaco(); function sendToTerminal(type, data) { EventEmitter.dispatch("terminal", { type, data }); } function connect() { 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"); }); 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": 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); } setFormatting(false); return; case "lspStopped": setIsLspRequested(false); setLspStarted(false); if (clientDisposable) { clientDisposable.dispose(); clientDisposable = null; } if (servicesDisposable) { servicesDisposable.dispose(); servicesDisposable = null; } break; case "lspStarted": setLspStarted(true); setIsLspRequested(false); if (typeof message.root !== "string") { console.error("Unexpected message from server:", message); return; } console.log("Started", message.root, config.main); // EventEmitter.dispatch("lspStarted", message); const { createConnection, MonacoLanguageClient, MonacoServices, Services, } = await import("monaco-languageclient"); const services = MonacoServices.create(editorRef.current, { rootUri: `file://${message.root}`, }); servicesDisposable = Services.install(services); const newURI = `file://${message.root}/${config.main}`; const oldModel = editorRef.current.getModel(); console.log("Check 4", oldModel.uri, newURI); if (oldModel.uri.toString() !== newURI) { // This code is likely to be buggy as it will probably // never run and has thus never been tested. editorRef.current.setModel( monaco.editor.createModel( oldModel.getValue(), undefined, monaco.Uri.parse(newURI) ) ); oldModel.dispose(); } const RijuMessageReader = ( await import("../../services/RijuMessageReader") ).default; const RijuMessageWriter = ( await import("../../services/RijuMessageWriter") ).default; const connection = createMessageConnection( new RijuMessageReader(socket), new RijuMessageWriter(socket, config) ); const client = new MonacoLanguageClient({ name: "Riju", clientOptions: { documentSelector: [{ pattern: "**" }], middleware: { workspace: { configuration: (params, token, configuration) => { return Array(configuration(params, token).length).fill( config.lsp.config !== undefined ? config.lsp.config : {} ); }, }, }, initializationOptions: config.lsp.init || {}, }, connectionProvider: { get: (errorHandler, closeHandler) => Promise.resolve( createConnection(connection, errorHandler, closeHandler) ), }, }); clientDisposable = client.start(); 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); // lspButton.classList.add("is-light"); // lspButtonState.innerText = "CRASHED"; break; case "terminal": sendToTerminal( "terminalOutput", ansi.red(`\r\n[${message.error}]`) ); break; } 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"); } if (clientDisposable) { clientDisposable.dispose(); clientDisposable = null; } if (servicesDisposable) { servicesDisposable.dispose(); servicesDisposable = null; } setRunning(false); setLspStarted(false); setIsLspRequested(false); }); return () => socket && socket.close(); } 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(); }, }); // editor.getModel().onDidChangeContent(() => recordActivity()); // window.addEventListener("resize", () => editor.layout()); editor.getModel().setValue(config.template + "\n"); monaco.editor.setModelLanguage( editor.getModel(), config.monacoLang || "plaintext" ); setMounted(true); } const handleLspClick = () => { setIsLspRequested(true); if (isLspStarted) { EventEmitter.dispatch("send", { event: "lspStop", }); } else { EventEmitter.dispatch("send", { event: "lspStart", }); } }; return ( <>