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 {
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;
}

View File

@@ -12,7 +12,7 @@
<div class="right">
<button onclick="runCode()">Run</button>
<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>
<code id="console">Output will be shown here, click the run button to run your code!</code>
</div>

View File

@@ -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 += "<p>";
const tokens = line.split(" ");
for (const token of tokens) {
renderedText += highlightToken(token) + " ";
}
renderedText += "</p>";
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 += "<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
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 `<span style="color: ${colours.string}">${token}</span>`;
}
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;
if (instring) {
return `<span style="color: ${colours.string}">${token}</span>`;
}
return `<span style="color: ${colours.string}">${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) + " ";
if (keywords.includes(token)) {
return `<span style="color: ${colours.keyword}">${token}</span>`;
}
renderedText += "</p>";
instring = false;
iscomment = false;
}
textbox.innerHTML = renderedText;
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('');
}
*/
// Function to run code on the server
async function runCode() {
console.log(text.split("<p>").join("").split("</p>").join(""));
const result = await fetch("http://localhost:5000/runProgram", {
"method": "POST",
//"mode": "no-cors",
"body": text.split("<p>").join("").split("</p>").join("")
});
const data = await result.json();
// Ensure everything's seperated
textconsole.innerHTML = "<p>" + data.stdout.split("\n").join("</p><p>") + "</p>";
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('</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>";
}
}