From 56e2837c7179ecec87f21bb6ea71312ee027e373 Mon Sep 17 00:00:00 2001 From: Maxwell Jeffress Date: Sat, 27 Sep 2025 15:09:26 +1000 Subject: [PATCH] crap --- client/index.css | 76 ++++++--- client/index.html | 2 +- client/index.js | 409 +++++++++++++++++++++++++++++++++------------- 3 files changed, 341 insertions(+), 146 deletions(-) diff --git a/client/index.css b/client/index.css index 881cb5d..54aca79 100644 --- a/client/index.css +++ b/client/index.css @@ -1,39 +1,61 @@ -p { - line-height: 0.3; +/* Remove the p styling since we're using div now */ +div { + line-height: 1.2; + margin: 0; + padding: 0; } -html, body { - width: 100%; - height: 100%; - padding: 0; - margin: 0; +html, +body { + width: 100%; + height: 100%; + padding: 0; + margin: 0; } -button { - font-family: monospace; - background-color: #48156e; - color: #e6e8ff; - border: 0px; - border-radius: 2px; +button, .button { + font-family: monospace; + background-color: #48156e; + color: #e6e8ff; + border: 0px; + border-radius: 2px; + padding: 8px 12px; + cursor: pointer; +} + +button:hover, .button:hover { + background-color: #5a1a82; } .left { - height: 100%; - width: 70%; - padding: 10px; - background-color: #0d0a12; - color: #e6e8ff; - position: absolute; - overflow: scroll; + height: 100%; + width: 70%; + padding: 10px; + background-color: #0d0a12; + color: #e6e8ff; + position: absolute; + overflow: scroll; } .right { - height: 100%; - width: 30%; - padding: 10px; - background-color: #191324; - color: #e6e8ff; - position: absolute; - right: 0; + height: 100%; + width: 30%; + padding: 10px; + background-color: #191324; + color: #e6e8ff; + position: absolute; + right: 0; } +/* Remove focus outline on the editor */ +#editor { + outline: none; + font-family: monospace; + white-space: pre-wrap; + spellcheck: false; /* Disable spellcheck squiggly lines */ +} + +#console { + font-family: monospace; + white-space: pre-wrap; +} diff --git a/client/index.html b/client/index.html index a2e9aa2..1b90e94 100644 --- a/client/index.html +++ b/client/index.html @@ -12,7 +12,7 @@
- +
Output will be shown here, click the run button to run your code!
diff --git a/client/index.js b/client/index.js index b08d2b5..19bba24 100644 --- a/client/index.js +++ b/client/index.js @@ -1,166 +1,339 @@ let textbox; let textconsole; -let text = '# welcome to gride! start typing to code a program in Ground!\n'; - +let text = "# welcome to gride!\n# type your code here and click run!\n"; let filebutton; let fileinput; +let firstRun = true; -function renderHighlightedText() { - let renderedText = ""; - const lines = text.split("\n"); - for (const line of lines) { - iscomment = false; - renderedText += "

"; - const tokens = line.split(" "); - for (const token of tokens) { - renderedText += highlightToken(token) + " "; - } - renderedText += "

"; - instring = false; - } - textbox.innerHTML = renderedText; +// 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() { - +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; - filebutton = document.getElementById('buttonTrigger'); - fileinput = document.getElementById('fileInput'); + + // Initial render + renderHighlightedText(); // Set up watchers for everything - filebutton.addEventListener('click', () => { + filebutton.addEventListener("click", () => { fileinput.click(); - }) + }); - fileinput.addEventListener('change', () => { + fileinput.addEventListener("change", () => { const file = fileinput.files[0]; if (file) { const reader = new FileReader(); - reader.onload = function(e) { + reader.onload = function (e) { text = e.target.result; textbox.innerText = text; renderHighlightedText(); }; reader.readAsText(file); } else { - console.log("what"); + 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 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: "#42ffb7", + number: "#42ff62", string: "#42ff62", valref: "#9544c7", dirref: "#f587ff", + lineref: "#9bf542", label: "#fff67d", function: "#ffa640", - typeref: "#ff40ac" -} + typeref: "#ff40ac", +}; -let instring = false; -let iscomment = false; - -// Function to handle highlighting tokens that have been typed -function highlightToken(token) { - - if (instring) { - if (token[token.length - 1] == '"' || token[token.length - 1] == '"') { - instring = false; +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}`; } - return `${token}` - } - - // Comments - if (iscomment) { - return ` ${token}"` - } - - if (token[0] == '#') { - iscomment = true; - return `${token}`; - } - - // Direct references - if (token[0] == '&') { - return `${token}`; - } - - // Value references - if (token[0] == '$') { - return `${token}`; - } - - // Strings and characters - if (token[0] == '"' || token[0] == "'") { - if (!(token[token.length - 1] == '"' || token[token.length - 1] == '"')) { - instring = true; + if (instring) { + return `${token}`; } - return `${token}`; - } - - return `${token}`; -} - -// When we press a key, start doing stuff -textbox.addEventListener('input', () => { - text = textbox.innerText; - renderHighlightedText(); -}); - - -// Old code -/* -onkeydown = (event) => { - if (event.key == "Enter") { - //text += "

\n

"; - text += "\n" - } else if (event.key == "Tab") { - text += " "; - } else if (event.key == "Backspace") { - text = text.slice(0, -1) - } else if (!(event.key == "Control" || event.key == "Alt" || event.key == "Meta" || event.key == "Shift" || event.key == "Escape")) { - text += event.key; - } - let renderedText = ""; - const lines = text.split("\n"); - for (const line of lines) { - renderedText += "

"; - const tokens = line.split(" "); - for (const token of tokens) { - renderedText += highlightToken(token) + " "; + if (keywords.includes(token)) { + return `${token}`; } - renderedText += "

"; - instring = false; - iscomment = false; - } - textbox.innerHTML = renderedText; + 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() { - console.log(text.split("

").join("").split("

").join("")); - const result = await fetch("http://localhost:5000/runProgram", { - "method": "POST", - //"mode": "no-cors", - "body": text.split("

").join("").split("

").join("") - }); - const data = await result.json(); - // Ensure everything's seperated - textconsole.innerHTML = "

" + data.stdout.split("\n").join("

") + "

"; + 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 + "
"; + } }