2025-09-25 22:16:05 +10:00
|
|
|
let textbox;
|
2025-09-25 09:07:44 +10:00
|
|
|
let textconsole;
|
2025-09-27 15:09:26 +10:00
|
|
|
let text = "# welcome to gride!\n# type your code here and click run!\n";
|
2025-09-25 11:34:38 +10:00
|
|
|
let filebutton;
|
|
|
|
|
let fileinput;
|
2025-09-27 15:09:26 +10:00
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-09-25 11:34:38 +10:00
|
|
|
|
|
|
|
|
function renderHighlightedText() {
|
2025-09-27 15:09:26 +10:00
|
|
|
if (!textbox) return;
|
|
|
|
|
|
|
|
|
|
// Save cursor position before updating
|
|
|
|
|
const cursorPos = saveCursorPosition();
|
|
|
|
|
|
2025-09-25 11:34:38 +10:00
|
|
|
let renderedText = "";
|
|
|
|
|
const lines = text.split("\n");
|
2025-09-27 15:09:26 +10:00
|
|
|
|
|
|
|
|
for (let i = 0; i < lines.length; i++) {
|
|
|
|
|
const line = lines[i];
|
|
|
|
|
if (line.trim() === "") {
|
|
|
|
|
renderedText += "<div> </div>"; // Preserve empty lines
|
|
|
|
|
} else {
|
|
|
|
|
const commentIndex = line.indexOf("#");
|
|
|
|
|
if (commentIndex !== -1) {
|
|
|
|
|
const beforeComment = line.substring(0, commentIndex);
|
|
|
|
|
const comment = line.substring(commentIndex);
|
|
|
|
|
renderedText += `<div>${highlightTokens(beforeComment)}<span style="color: ${colours.comment}">${comment}</span></div>`;
|
|
|
|
|
} else {
|
|
|
|
|
renderedText += `<div>${highlightTokens(line)}</div>`;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-09-25 11:34:38 +10:00
|
|
|
}
|
2025-09-27 15:09:26 +10:00
|
|
|
|
2025-09-25 11:34:38 +10:00
|
|
|
textbox.innerHTML = renderedText;
|
2025-09-27 15:09:26 +10:00
|
|
|
|
|
|
|
|
// Restore cursor position after a brief delay to let the DOM update
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
restoreCursorPosition(cursorPos);
|
|
|
|
|
}, 0);
|
2025-09-25 11:34:38 +10:00
|
|
|
}
|
|
|
|
|
|
2025-09-27 15:09:26 +10:00
|
|
|
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;
|
|
|
|
|
}
|
2025-09-25 22:16:05 +10:00
|
|
|
|
|
|
|
|
// Wait for the window to load before doing anything
|
2025-09-27 15:09:26 +10:00
|
|
|
window.addEventListener("load", function () {
|
2025-09-25 11:34:38 +10:00
|
|
|
// Get all the elements
|
2025-09-25 22:16:05 +10:00
|
|
|
textbox = document.getElementById("editor");
|
2025-09-25 09:07:44 +10:00
|
|
|
textconsole = document.getElementById("console");
|
2025-09-27 15:09:26 +10:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-25 11:34:38 +10:00
|
|
|
textbox.innerText = text;
|
2025-09-27 15:09:26 +10:00
|
|
|
|
|
|
|
|
// Initial render
|
|
|
|
|
renderHighlightedText();
|
2025-09-25 11:34:38 +10:00
|
|
|
|
|
|
|
|
// Set up watchers for everything
|
2025-09-27 15:09:26 +10:00
|
|
|
filebutton.addEventListener("click", () => {
|
2025-09-25 11:34:38 +10:00
|
|
|
fileinput.click();
|
2025-09-27 15:09:26 +10:00
|
|
|
});
|
2025-09-25 11:34:38 +10:00
|
|
|
|
2025-09-27 15:09:26 +10:00
|
|
|
fileinput.addEventListener("change", () => {
|
2025-09-25 11:34:38 +10:00
|
|
|
const file = fileinput.files[0];
|
|
|
|
|
if (file) {
|
|
|
|
|
const reader = new FileReader();
|
2025-09-27 15:09:26 +10:00
|
|
|
reader.onload = function (e) {
|
2025-09-25 11:34:38 +10:00
|
|
|
text = e.target.result;
|
|
|
|
|
textbox.innerText = text;
|
|
|
|
|
renderHighlightedText();
|
|
|
|
|
};
|
|
|
|
|
reader.readAsText(file);
|
|
|
|
|
} else {
|
2025-09-27 15:09:26 +10:00
|
|
|
console.log("No file selected");
|
2025-09-25 11:34:38 +10:00
|
|
|
}
|
|
|
|
|
});
|
2025-09-27 15:09:26 +10:00
|
|
|
|
|
|
|
|
// 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();
|
|
|
|
|
});
|
2025-09-25 22:16:05 +10:00
|
|
|
});
|
|
|
|
|
|
2025-09-27 15:09:26 +10:00
|
|
|
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",
|
|
|
|
|
];
|
2025-09-25 11:34:38 +10:00
|
|
|
|
|
|
|
|
const colours = {
|
|
|
|
|
keyword: "#42fff2",
|
|
|
|
|
comment: "#383838",
|
2025-09-27 15:09:26 +10:00
|
|
|
number: "#42ff62",
|
2025-09-25 11:34:38 +10:00
|
|
|
string: "#42ff62",
|
|
|
|
|
valref: "#9544c7",
|
|
|
|
|
dirref: "#f587ff",
|
2025-09-27 15:09:26 +10:00
|
|
|
lineref: "#9bf542",
|
2025-09-25 11:34:38 +10:00
|
|
|
label: "#fff67d",
|
|
|
|
|
function: "#ffa640",
|
2025-09-27 15:09:26 +10:00
|
|
|
typeref: "#ff40ac",
|
|
|
|
|
};
|
2025-09-25 11:34:38 +10:00
|
|
|
|
2025-09-27 15:09:26 +10:00
|
|
|
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 `<span style="color: ${colours.string}">${token}</span>`;
|
2025-09-25 11:34:38 +10:00
|
|
|
}
|
2025-09-27 15:09:26 +10:00
|
|
|
if (instring) {
|
|
|
|
|
return `<span style="color: ${colours.string}">${token}</span>`;
|
|
|
|
|
}
|
|
|
|
|
if (keywords.includes(token)) {
|
|
|
|
|
return `<span style="color: ${colours.keyword}">${token}</span>`;
|
|
|
|
|
}
|
|
|
|
|
if (!isNaN(token) && token.trim() !== "") {
|
|
|
|
|
return `<span style="color: ${colours.number}">${token}</span>`;
|
|
|
|
|
}
|
|
|
|
|
if (token.startsWith('&')) {
|
|
|
|
|
return `<span style="color: ${colours.dirref}">${token}</span>`;
|
|
|
|
|
}
|
|
|
|
|
if (token.startsWith('$')) {
|
|
|
|
|
return `<span style="color: ${colours.valref}">${token}</span>`;
|
|
|
|
|
}
|
|
|
|
|
if (token.startsWith('-')) {
|
|
|
|
|
return `<span style="color: ${colours.typeref}">${token}</span>`;
|
|
|
|
|
}
|
|
|
|
|
if (token.startsWith('!')) {
|
|
|
|
|
return `<span style="color: ${colours.function}">${token}</span>`;
|
|
|
|
|
}
|
|
|
|
|
if (token.startsWith('%')) {
|
|
|
|
|
return `<span style="color: ${colours.lineref}">${token}</span>`;
|
|
|
|
|
}
|
|
|
|
|
if (token.startsWith('@')) {
|
|
|
|
|
return `<span style="color: ${colours.label}">${token}</span>`;
|
|
|
|
|
}
|
|
|
|
|
return token;
|
|
|
|
|
}).join('');
|
|
|
|
|
}
|
2025-09-25 11:34:38 +10:00
|
|
|
|
2025-09-27 15:09:26 +10:00
|
|
|
// Function to run code on the server
|
|
|
|
|
async function runCode() {
|
|
|
|
|
if (!textconsole) {
|
|
|
|
|
console.error("Console element not found!");
|
|
|
|
|
return;
|
2025-09-25 11:34:38 +10:00
|
|
|
}
|
|
|
|
|
|
2025-09-27 15:09:26 +10:00
|
|
|
// Make sure we have the latest text
|
|
|
|
|
// The `text` variable is now the single source of truth.
|
2025-09-25 11:34:38 +10:00
|
|
|
|
2025-09-27 15:09:26 +10:00
|
|
|
if (firstRun) {
|
|
|
|
|
textconsole.innerHTML = "";
|
|
|
|
|
firstRun = false;
|
2025-09-25 11:34:38 +10:00
|
|
|
}
|
|
|
|
|
|
2025-09-27 15:09:26 +10:00
|
|
|
console.log("Running code:", text);
|
2025-09-25 11:34:38 +10:00
|
|
|
|
2025-09-27 15:09:26 +10:00
|
|
|
try {
|
|
|
|
|
const result = await fetch("/runProgram", {
|
|
|
|
|
method: "POST",
|
|
|
|
|
headers: {
|
|
|
|
|
"Content-Type": "text/plain",
|
|
|
|
|
},
|
|
|
|
|
body: text,
|
|
|
|
|
});
|
2025-09-25 11:34:38 +10:00
|
|
|
|
2025-09-27 15:09:26 +10:00
|
|
|
if (!result.ok) {
|
|
|
|
|
throw new Error(`HTTP error! status: ${result.status}`);
|
2025-09-25 11:34:38 +10:00
|
|
|
}
|
|
|
|
|
|
2025-09-27 15:09:26 +10:00
|
|
|
const data = await result.json();
|
|
|
|
|
const output = data.stdout.split('\n').join('</div><div>');
|
|
|
|
|
const timestamp = new Date().toLocaleTimeString();
|
|
|
|
|
textconsole.innerHTML += `<div>[${timestamp}]</div><div>${output}</div>`;
|
|
|
|
|
textconsole.scrollTop = textconsole.scrollHeight; // Scroll to bottom
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("Error running code:", error);
|
|
|
|
|
textconsole.innerHTML =
|
|
|
|
|
"<div style='color: #ff4444'>Error: " + error.message + "</div>";
|
2025-09-25 22:16:05 +10:00
|
|
|
}
|
|
|
|
|
}
|