175 lines
5.8 KiB
JavaScript
175 lines
5.8 KiB
JavaScript
// Kyler Olsen
|
|
// November 2023
|
|
|
|
class PythonEditor{
|
|
constructor(ele) {
|
|
this.element = ele;
|
|
|
|
this.element.addEventListener("input", this.textInput.bind(this));
|
|
this.element.addEventListener("keydown", this.handleKeyDown.bind(this));
|
|
|
|
this.view_code = document.createElement("code");
|
|
this.edit_code = document.createElement("code");
|
|
this.view_pre = document.createElement("pre");
|
|
this.edit_pre = document.createElement("pre");
|
|
|
|
this.edit_code.contentEditable = true;
|
|
this.edit_code.spellcheck = false;
|
|
this.view_pre.classList.add("view-code");
|
|
this.edit_pre.classList.add("edit-code");
|
|
|
|
this.view_pre.appendChild(this.view_code);
|
|
this.edit_pre.appendChild(this.edit_code);
|
|
this.element.appendChild(this.view_pre);
|
|
this.element.appendChild(this.edit_pre);
|
|
}
|
|
|
|
textInput(e) {
|
|
let text = this.edit_code.innerText;
|
|
this.view_code.innerHTML = "";
|
|
syntax_highlight_html(text).forEach(ele => this.view_code.appendChild(ele));
|
|
}
|
|
|
|
handleKeyDown(e) {
|
|
if (e.key === "Tab") {
|
|
e.preventDefault();
|
|
insertTab(e.target);
|
|
}
|
|
}
|
|
}
|
|
|
|
function insertTab() {
|
|
const selection = window.getSelection();
|
|
|
|
if (selection.rangeCount > 0) {
|
|
const range = selection.getRangeAt(0);
|
|
const startOffset = range.startOffset;
|
|
const lineStart = range.startContainer.textContent.lastIndexOf('\n', startOffset - 1) + 1;
|
|
const spacesToAdd = 4 - ((startOffset - lineStart) % 4);
|
|
const tabSpaces = ' '.repeat(spacesToAdd);
|
|
|
|
const tabNode = document.createTextNode(tabSpaces);
|
|
range.deleteContents();
|
|
range.insertNode(tabNode);
|
|
range.setStartAfter(tabNode);
|
|
range.setEndAfter(tabNode);
|
|
selection.removeAllRanges();
|
|
selection.addRange(range);
|
|
}
|
|
}
|
|
|
|
function syntax_highlight(text) {
|
|
const whitespace = " \n\t";
|
|
const digit_start = "0123456789.";
|
|
const digit_second = "bBoO";
|
|
const digit_continue = "0123456789._";
|
|
const string_start = `'"`;
|
|
const string_alt_start = [
|
|
`r'`, `r"`, `rf`, `rb`, `rF`, `rB`,
|
|
`R'`, `R"`, `Rf`, `Rb`, `RF`, `RB`,
|
|
`f'`, `f"`, `fr`, `fR`,
|
|
`F'`, `F"`, `Fr`, `FR`,
|
|
`b'`, `b"`, `br`, `bR`,
|
|
`B'`, `B"`, `Br`, `BR`,
|
|
];
|
|
|
|
const tokens = [];
|
|
var char = text.substr(0, 1); text = text.substr(1);
|
|
|
|
while (text.length) {
|
|
const token = {'token_type': '', 'value':''};
|
|
|
|
// Whitespace
|
|
if (whitespace.includes(char)) {
|
|
token.token_type = "whitespace";
|
|
while (text.length && whitespace.includes(char)) {
|
|
token.value += char;
|
|
char = text.substr(0, 1); text = text.substr(1);
|
|
}
|
|
}
|
|
|
|
// Comments
|
|
else if (char == '#') {
|
|
token.token_type = "comment";
|
|
while (text.length && char != "\n") {
|
|
token.value += char;
|
|
char = text.substr(0, 1); text = text.substr(1);
|
|
}
|
|
}
|
|
|
|
// String literals
|
|
else if (string_start.includes(char) || (string_alt_start.includes(char + text.substr(0, 1)))) {
|
|
var depth = 1;
|
|
var start;
|
|
if (string_start.includes(char)) {
|
|
token.token_type = "string";
|
|
start = char;
|
|
} else {
|
|
token.value += char;
|
|
char = text.substr(0, 1); text = text.substr(1);
|
|
if (!string_start.includes(char)) {
|
|
token.value += char;
|
|
char = text.substr(0, 1); text = text.substr(1);
|
|
}
|
|
if (token.value.includes('r') || token.value.includes('R'))
|
|
token.token_type += "raw-";
|
|
if (token.value.includes('f') || token.value.includes('F'))
|
|
token.token_type += "f-";
|
|
if (token.value.includes('b') || token.value.includes('B'))
|
|
token.token_type += "byte-";
|
|
token.token_type += "string";
|
|
start = char;
|
|
}
|
|
if (text.substr(0, 1) == start && text.substr(1, 1) == start) depth = 3;
|
|
token.value += char;
|
|
char = text.substr(0, 1); text = text.substr(1);
|
|
|
|
while (text.length && depth && (depth > 1 || char != '\n')) {
|
|
token.value += char;
|
|
if (char == start && depth == 1) depth--;
|
|
else if (char == start && text.substr(0, 1) == start && depth == 2) depth--;
|
|
else if (char == start && text.substr(0, 1) == start && text.substr(1, 1) == start && depth == 3) depth--;
|
|
char = text.substr(0, 1); text = text.substr(1);
|
|
}
|
|
}
|
|
|
|
else {
|
|
token.token_type = "token";
|
|
token.value = char;
|
|
char = text.substr(0, 1); text = text.substr(1);
|
|
}
|
|
|
|
tokens.push(token);
|
|
}
|
|
|
|
return tokens;
|
|
}
|
|
|
|
function syntax_highlight_html(text) {
|
|
const tokens = syntax_highlight(text);
|
|
const elements = [];
|
|
var last = "whitespace";
|
|
|
|
tokens.forEach(token => {
|
|
const element = document.createElement("span");
|
|
if (token.token_type == "whitespace")
|
|
element.classList.add("whitespace");
|
|
else if (token.token_type == "comment")
|
|
element.classList.add("comment");
|
|
else if (token.token_type.substr(-6) == "string") {
|
|
element.classList.add("literal");
|
|
element.classList.add("string");
|
|
} else
|
|
element.classList.add("token");
|
|
element.innerText = token.value;
|
|
elements.push(element);
|
|
last = token.token_type;
|
|
})
|
|
|
|
return elements;
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
document.querySelectorAll(".code").forEach(ele => new PythonEditor(ele))
|
|
});
|