This commit is contained in:
2025-09-27 15:09:26 +10:00
parent c85abbaad1
commit 56e2837c71
3 changed files with 341 additions and 146 deletions

View File

@@ -1,39 +1,61 @@
p { /* Remove the p styling since we're using div now */
line-height: 0.3; div {
line-height: 1.2;
margin: 0;
padding: 0;
} }
html, body { html,
width: 100%; body {
height: 100%; width: 100%;
padding: 0; height: 100%;
margin: 0; padding: 0;
margin: 0;
} }
button { button, .button {
font-family: monospace; font-family: monospace;
background-color: #48156e; background-color: #48156e;
color: #e6e8ff; color: #e6e8ff;
border: 0px; border: 0px;
border-radius: 2px; border-radius: 2px;
padding: 8px 12px;
cursor: pointer;
}
button:hover, .button:hover {
background-color: #5a1a82;
} }
.left { .left {
height: 100%; height: 100%;
width: 70%; width: 70%;
padding: 10px; padding: 10px;
background-color: #0d0a12; background-color: #0d0a12;
color: #e6e8ff; color: #e6e8ff;
position: absolute; position: absolute;
overflow: scroll; overflow: scroll;
} }
.right { .right {
height: 100%; height: 100%;
width: 30%; width: 30%;
padding: 10px; padding: 10px;
background-color: #191324; background-color: #191324;
color: #e6e8ff; color: #e6e8ff;
position: absolute; position: absolute;
right: 0; 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;
}

View File

@@ -12,7 +12,7 @@
<div class="right"> <div class="right">
<button onclick="runCode()">Run</button> <button onclick="runCode()">Run</button>
<input type="file" id="fileInput" hidden accept=".grnd"> <input type="file" id="fileInput" hidden accept=".grnd">
<button for="fileInput" id="buttonTrigger">Choose File</button> <label for="fileInput" id="buttonTrigger" class="button">Choose File</label>
<br> <br>
<code id="console">Output will be shown here, click the run button to run your code!</code> <code id="console">Output will be shown here, click the run button to run your code!</code>
</div> </div>

View File

@@ -1,166 +1,339 @@
let textbox; let textbox;
let textconsole; 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 filebutton;
let fileinput; let fileinput;
let firstRun = true;
function renderHighlightedText() { // Function to save cursor position
let renderedText = ""; function saveCursorPosition() {
const lines = text.split("\n"); const selection = window.getSelection();
for (const line of lines) { if (selection.rangeCount === 0) return null;
iscomment = false;
renderedText += "<p>"; const range = selection.getRangeAt(0);
const tokens = line.split(" "); const preCaretRange = range.cloneRange();
for (const token of tokens) { preCaretRange.selectNodeContents(textbox);
renderedText += highlightToken(token) + " "; preCaretRange.setEnd(range.startContainer, range.startOffset);
}
renderedText += "</p>"; return preCaretRange.toString().length;
instring = false;
}
textbox.innerHTML = renderedText;
} }
// 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 += "<div>&nbsp;</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>`;
}
}
}
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 // Wait for the window to load before doing anything
window.addEventListener("load", function() { window.addEventListener("load", function () {
// Get all the elements // Get all the elements
textbox = document.getElementById("editor"); textbox = document.getElementById("editor");
textconsole = document.getElementById("console"); 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; textbox.innerText = text;
filebutton = document.getElementById('buttonTrigger');
fileinput = document.getElementById('fileInput'); // Initial render
renderHighlightedText();
// Set up watchers for everything // Set up watchers for everything
filebutton.addEventListener('click', () => { filebutton.addEventListener("click", () => {
fileinput.click(); fileinput.click();
}) });
fileinput.addEventListener('change', () => { fileinput.addEventListener("change", () => {
const file = fileinput.files[0]; const file = fileinput.files[0];
if (file) { if (file) {
const reader = new FileReader(); const reader = new FileReader();
reader.onload = function(e) { reader.onload = function (e) {
text = e.target.result; text = e.target.result;
textbox.innerText = text; textbox.innerText = text;
renderHighlightedText(); renderHighlightedText();
}; };
reader.readAsText(file); reader.readAsText(file);
} else { } 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 = { const colours = {
keyword: "#42fff2", keyword: "#42fff2",
comment: "#383838", comment: "#383838",
number: "#42ffb7", number: "#42ff62",
string: "#42ff62", string: "#42ff62",
valref: "#9544c7", valref: "#9544c7",
dirref: "#f587ff", dirref: "#f587ff",
lineref: "#9bf542",
label: "#fff67d", label: "#fff67d",
function: "#ffa640", function: "#ffa640",
typeref: "#ff40ac" typeref: "#ff40ac",
} };
let instring = false; function highlightTokens(line) {
let iscomment = false; const tokens = line.split(/(\s+|"[^"]*"|'[^']*')/).filter(Boolean);
let instring = false;
// Function to handle highlighting tokens that have been typed return tokens.map(token => {
function highlightToken(token) { if (token.startsWith('"') || token.startsWith("'")) {
instring = !instring;
if (instring) { return `<span style="color: ${colours.string}">${token}</span>`;
if (token[token.length - 1] == '"' || token[token.length - 1] == '"') {
instring = false;
} }
return `<span style="color: ${colours.string}">${token}</span>` if (instring) {
} return `<span style="color: ${colours.string}">${token}</span>`;
// Comments
if (iscomment) {
return `<span style="color: ${colours.comment}"> ${token}</span>"`
}
if (token[0] == '#') {
iscomment = true;
return `<span style="color: ${colours.comment}> ${token}</span>"`
}
// Keywords
if (keywords.includes(token)) {
return `<span style="color: ${colours.keyword}">${token}</span>`;
}
// Direct references
if (token[0] == '&') {
return `<span style="color: ${colours.dirref}">${token}</span>`;
}
// Value references
if (token[0] == '$') {
return `<span style="color: ${colours.valref}">${token}</span>`;
}
// Strings and characters
if (token[0] == '"' || token[0] == "'") {
if (!(token[token.length - 1] == '"' || token[token.length - 1] == '"')) {
instring = true;
} }
return `<span style="color: ${colours.string}">${token}</span>`; if (keywords.includes(token)) {
} return `<span style="color: ${colours.keyword}">${token}</span>`;
return `<span>${token}</span>`;
}
// When we press a key, start doing stuff
textbox.addEventListener('input', () => {
text = textbox.innerText;
renderHighlightedText();
});
// Old code
/*
onkeydown = (event) => {
if (event.key == "Enter") {
//text += "</p>\n<p>";
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 += "<p>";
const tokens = line.split(" ");
for (const token of tokens) {
renderedText += highlightToken(token) + " ";
} }
renderedText += "</p>"; if (!isNaN(token) && token.trim() !== "") {
instring = false; return `<span style="color: ${colours.number}">${token}</span>`;
iscomment = false; }
} if (token.startsWith('&')) {
textbox.innerHTML = renderedText; 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('');
} }
*/
// Function to run code on the server // Function to run code on the server
async function runCode() { async function runCode() {
console.log(text.split("<p>").join("").split("</p>").join("")); if (!textconsole) {
const result = await fetch("http://localhost:5000/runProgram", { console.error("Console element not found!");
"method": "POST", return;
//"mode": "no-cors", }
"body": text.split("<p>").join("").split("</p>").join("")
}); // Make sure we have the latest text
const data = await result.json(); // The `text` variable is now the single source of truth.
// Ensure everything's seperated
textconsole.innerHTML = "<p>" + data.stdout.split("\n").join("</p><p>") + "</p>"; 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('</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>";
}
} }