let textbox; let textconsole; let text = "# welcome to gride!\n# type your code here and click run!\n"; let filebutton; let fileinput; let firstRun = true; // Function to save cursor position function saveCursorPosition() { const selection = window.getSelection(); if (selection.rangeCount === 0) return null; const range = selection.getRangeAt(0); const preCaretRange = range.cloneRange(); preCaretRange.selectNodeContents(textbox); preCaretRange.setEnd(range.startContainer, range.startOffset); return preCaretRange.toString().length; } // Function to restore cursor position function restoreCursorPosition(savedPos) { if (savedPos === null || !textbox) return; const textNodes = []; const walker = document.createTreeWalker( textbox, NodeFilter.SHOW_TEXT, null, false, ); let node; while ((node = walker.nextNode())) { textNodes.push(node); } let currentPos = 0; for (const textNode of textNodes) { const nodeLength = textNode.textContent.length; if (currentPos + nodeLength >= savedPos) { const range = document.createRange(); const selection = window.getSelection(); try { range.setStart(textNode, Math.min(savedPos - currentPos, nodeLength)); range.collapse(true); selection.removeAllRanges(); selection.addRange(range); } catch (e) { // If positioning fails, just focus the element textbox.focus(); } return; } currentPos += nodeLength; } // If we couldn't find the exact position, focus at the end const range = document.createRange(); const selection = window.getSelection(); try { range.selectNodeContents(textbox); range.collapse(false); selection.removeAllRanges(); selection.addRange(range); } catch (e) { textbox.focus(); } } function renderHighlightedText() { if (!textbox) return; // Save cursor position before updating const cursorPos = saveCursorPosition(); let renderedText = ""; const lines = text.split("\n"); for (let i = 0; i < lines.length; i++) { const line = lines[i]; if (line.trim() === "") { renderedText += "
 
"; // Preserve empty lines } else { const commentIndex = line.indexOf("#"); if (commentIndex !== -1) { const beforeComment = line.substring(0, commentIndex); const comment = line.substring(commentIndex); renderedText += `
${highlightTokens(beforeComment)}${comment}
`; } else { renderedText += `
${highlightTokens(line)}
`; } } } textbox.innerHTML = renderedText; // Restore cursor position after a brief delay to let the DOM update setTimeout(() => { restoreCursorPosition(cursorPos); }, 0); } function getTextFromEditor() { let newText = ""; for (let i = 0; i < textbox.childNodes.length; i++) { const div = textbox.childNodes[i]; if (div.nodeType === Node.ELEMENT_NODE && div.tagName === 'DIV') { let line = ""; for (let j = 0; j < div.childNodes.length; j++) { line += div.childNodes[j].textContent; } newText += line + (i < textbox.childNodes.length - 1 ? "\n" : ""); } else if (div.nodeType === Node.TEXT_NODE) { newText += div.textContent; } } return newText; } // Wait for the window to load before doing anything window.addEventListener("load", function () { // Get all the elements textbox = document.getElementById("editor"); textconsole = document.getElementById("console"); filebutton = document.getElementById("buttonTrigger"); fileinput = document.getElementById("fileInput"); // Check if elements exist if (!textbox || !textconsole || !filebutton || !fileinput) { console.error("Some required elements not found!"); return; } textbox.innerText = text; // Initial render renderHighlightedText(); // Set up watchers for everything filebutton.addEventListener("click", () => { fileinput.click(); }); fileinput.addEventListener("change", () => { const file = fileinput.files[0]; if (file) { const reader = new FileReader(); reader.onload = function (e) { text = e.target.result; textbox.innerText = text; renderHighlightedText(); }; reader.readAsText(file); } else { console.log("No file selected"); } }); // When we press a key, start doing stuff textbox.addEventListener("input", () => { text = getTextFromEditor(); renderHighlightedText(); }); // Handle tab key to insert 4 spaces textbox.addEventListener("keydown", (e) => { if (e.key === "Tab") { e.preventDefault(); // Insert 4 spaces at cursor position const selection = window.getSelection(); const range = selection.getRangeAt(0); // Create a text node with 4 spaces const textNode = document.createTextNode(" "); range.insertNode(textNode); // Move cursor to after the inserted spaces range.setStartAfter(textNode); range.collapse(true); selection.removeAllRanges(); selection.addRange(range); // Update our text variable and re-render text = getTextFromEditor(); renderHighlightedText(); } }); // Handle paste events textbox.addEventListener("paste", (e) => { e.preventDefault(); const paste = (e.clipboardData || window.clipboardData).getData("text"); document.execCommand("insertText", false, paste); text = getTextFromEditor(); renderHighlightedText(); }); }); const keywords = [ "if", "jump", "end", "input", "stdin", "print", "stdout", "println", "stdlnout", "set", "gettype", "exists", "setlist", "setlistat", "getlistat", "getlistsize", "listappend", "getstrsize", "getstrcharat", "add", "subtract", "multiply", "divide", "equal", "inequal", "not", "greater", "lesser", "stoi", "stod", "tostring", "fun", "return", "endfun", "pusharg", "call", "struct", "endstruct", "init", "use", "extern", "catch", ]; const colours = { keyword: "#42fff2", comment: "#383838", number: "#42ff62", string: "#42ff62", valref: "#9544c7", dirref: "#f587ff", lineref: "#9bf542", label: "#fff67d", function: "#ffa640", typeref: "#ff40ac", }; function highlightTokens(line) { const tokens = line.split(/(\s+|"[^"]*"|'[^']*')/).filter(Boolean); let instring = false; return tokens.map(token => { if (token.startsWith('"') || token.startsWith("'")) { instring = !instring; return `${token}`; } if (instring) { return `${token}`; } if (keywords.includes(token)) { return `${token}`; } if (!isNaN(token) && token.trim() !== "") { return `${token}`; } if (token.startsWith('&')) { return `${token}`; } if (token.startsWith('$')) { return `${token}`; } if (token.startsWith('-')) { return `${token}`; } if (token.startsWith('!')) { return `${token}`; } if (token.startsWith('%')) { return `${token}`; } if (token.startsWith('@')) { return `${token}`; } return token; }).join(''); } // Function to run code on the server async function runCode() { if (!textconsole) { console.error("Console element not found!"); return; } // Make sure we have the latest text // The `text` variable is now the single source of truth. if (firstRun) { textconsole.innerHTML = ""; firstRun = false; } console.log("Running code:", text); try { const result = await fetch("/runProgram", { method: "POST", headers: { "Content-Type": "text/plain", }, body: text, }); if (!result.ok) { throw new Error(`HTTP error! status: ${result.status}`); } const data = await result.json(); const output = data.stdout.split('\n').join('
'); const timestamp = new Date().toLocaleTimeString(); textconsole.innerHTML += `
[${timestamp}]
${output}
`; textconsole.scrollTop = textconsole.scrollHeight; // Scroll to bottom } catch (error) { console.error("Error running code:", error); textconsole.innerHTML = "
Error: " + error.message + "
"; } }