syntax highlighting
This commit is contained in:
parent
b9218c8ace
commit
d9b351ad64
|
@ -288,7 +288,7 @@ styles = ["tairu.css"]
|
|||
as I've already said, we represent each direction using a single bit.
|
||||
|
||||
% id = "01HPSY4Y19AW70YX8PPA7AS4DH"
|
||||
- I'm using JavaScript by the way, because it's the native programming language of your web browser. read on to the end of this tangent to see why.
|
||||
- I'm using JavaScript by the way, because it's the native programming language of your web browser. read on to see why.
|
||||
|
||||
% id = "01HPSY4Y19HPNXC54VP6TFFHXN"
|
||||
- now I don't know about you, but I find the usual C-style way of checking whether a bit is set extremely hard to read, so let's take care of that:
|
||||
|
@ -406,9 +406,6 @@ styles = ["tairu.css"]
|
|||
4
|
||||
```
|
||||
|
||||
TODO: The value from the previous output should not leak into this one. how do we do this? do we emit extra `pushMessage` calls inbetween the editors so that they know when to end?
|
||||
maybe use a `classic` context instead of a module? or maybe have a way of sharing data between outputs? (return value?)
|
||||
|
||||
% id = "01HPD4XQPWT9N8X9BD9GKWD78F"
|
||||
- bitwise autotiling is a really cool technique that I've used in plenty of games in the past.
|
||||
|
||||
|
|
|
@ -175,6 +175,7 @@ th-literate-program {
|
|||
--recursive-mono: 1.0;
|
||||
--recursive-casl: 0.0;
|
||||
--recursive-slnt: 0.0;
|
||||
--recursive-wght: 450;
|
||||
}
|
||||
|
||||
b,
|
||||
|
@ -548,3 +549,62 @@ th-literate-program[data-mode="output"] {
|
|||
opacity: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Syntax highlighting */
|
||||
|
||||
:root {
|
||||
/* TODO: Light mode syntax highlighting */
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--syntax-comment: #aca8a4;
|
||||
--syntax-identifier: var(--text-color);
|
||||
--syntax-keyword1: #ffb06a;
|
||||
--syntax-keyword2: #9acfe3;
|
||||
--syntax-operator: #ec9f8d;
|
||||
--syntax-function: #fbd283;
|
||||
--syntax-literal: #e9b9f0;
|
||||
--syntax-string: #b0dd7a;
|
||||
--syntax-punct: #9d9a96;
|
||||
}
|
||||
}
|
||||
|
||||
.th-syntax-highlighting span {
|
||||
&.comment {
|
||||
--recursive-slnt: -16.0;
|
||||
color: var(--syntax-comment);
|
||||
}
|
||||
|
||||
&.identifier {
|
||||
color: var(--syntax-identifier);
|
||||
}
|
||||
|
||||
&.keyword1 {
|
||||
color: var(--syntax-keyword1);
|
||||
}
|
||||
|
||||
&.keyword2 {
|
||||
color: var(--syntax-keyword2);
|
||||
}
|
||||
|
||||
&.operator {
|
||||
color: var(--syntax-operator);
|
||||
}
|
||||
|
||||
&.function {
|
||||
color: var(--syntax-function);
|
||||
}
|
||||
|
||||
&.literal {
|
||||
color: var(--syntax-literal);
|
||||
}
|
||||
|
||||
&.string {
|
||||
color: var(--syntax-string);
|
||||
}
|
||||
|
||||
&.punct {
|
||||
color: var(--syntax-punct);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { CodeJar } from "../vendor/codejar.js";
|
||||
import { compileSyntax, highlight } from "./literate-programming/highlight.js";
|
||||
|
||||
let literatePrograms = new Map();
|
||||
|
||||
|
@ -34,9 +35,81 @@ function getLiterateProgramWorkerCommands(name) {
|
|||
}
|
||||
|
||||
class InputMode {
|
||||
static JAVASCRIPT = compileSyntax({
|
||||
patterns: [
|
||||
{ regex: /\/\/.*/, as: "comment" },
|
||||
{ regex: /\/\*.*?\*\//ms, as: "comment" },
|
||||
{ regex: /[A-Z_][a-zA-Z0-9_]*/, as: "keyword2" },
|
||||
{ regex: /[a-zA-Z_][a-zA-Z0-9_]*(?=\()/, as: "function" },
|
||||
{ regex: /[a-zA-Z_][a-zA-Z0-9_]*/, as: "identifier" },
|
||||
{ regex: /0[bB][01_]+n?/, as: "literal" },
|
||||
{ regex: /0[oO][0-7_]+n?/, as: "literal" },
|
||||
{ regex: /0[xX][0-9a-fA-F_]+n?/, as: "literal" },
|
||||
{ regex: /[0-9_]+n/, as: "literal" },
|
||||
{ regex: /[0-9_]+(\.[0-9_]*([eE][-+]?[0-9_]+)?)?/, as: "literal" },
|
||||
{ regex: /'(\\'|[^'])*'/, as: "string" },
|
||||
{ regex: /"(\\"|[^"])*"/, as: "string" },
|
||||
{ regex: /`(\\`|[^"])*`/, as: "string" },
|
||||
// TODO: RegExp literals?
|
||||
{ regex: /[+=/*^%<>!~|&\.-]+/, as: "operator" },
|
||||
{ regex: /[,;]/, as: "punct" },
|
||||
],
|
||||
keywords: new Map([
|
||||
["as", { into: "keyword1", onlyReplaces: "identifier" }],
|
||||
["async", { into: "keyword1", onlyReplaces: "identifier" }],
|
||||
["await", { into: "keyword1" }],
|
||||
["break", { into: "keyword1" }],
|
||||
["case", { into: "keyword1" }],
|
||||
["catch", { into: "keyword1" }],
|
||||
["class", { into: "keyword1" }],
|
||||
["const", { into: "keyword1" }],
|
||||
["continue", { into: "keyword1" }],
|
||||
["debugger", { into: "keyword1" }],
|
||||
["default", { into: "keyword1" }],
|
||||
["delete", { into: "keyword1" }],
|
||||
["do", { into: "keyword1" }],
|
||||
["else", { into: "keyword1" }],
|
||||
["export", { into: "keyword1" }],
|
||||
["extends", { into: "keyword1" }],
|
||||
["finally", { into: "keyword1" }],
|
||||
["for", { into: "keyword1" }],
|
||||
["from", { into: "keyword1", onlyReplaces: "identifier" }],
|
||||
["function", { into: "keyword1" }],
|
||||
["get", { into: "keyword1", onlyReplaces: "identifier" }],
|
||||
["if", { into: "keyword1" }],
|
||||
["import", { into: "keyword1" }],
|
||||
["in", { into: "keyword1" }],
|
||||
["instanceof", { into: "keyword1" }],
|
||||
["let", { into: "keyword1" }],
|
||||
["new", { into: "keyword1" }],
|
||||
["of", { into: "keyword1", onlyReplaces: "identifier" }],
|
||||
["return", { into: "keyword1" }],
|
||||
["set", { into: "keyword1", onlyReplaces: "identifier" }],
|
||||
["static", { into: "keyword1" }],
|
||||
["switch", { into: "keyword1" }],
|
||||
["throw", { into: "keyword1" }],
|
||||
["try", { into: "keyword1" }],
|
||||
["typeof", { into: "keyword1" }],
|
||||
["var", { into: "keyword1" }],
|
||||
["void", { into: "keyword1" }],
|
||||
["while", { into: "keyword1" }],
|
||||
["with", { into: "keyword1" }],
|
||||
["yield", { into: "keyword1" }],
|
||||
|
||||
["super", { into: "keyword2" }],
|
||||
["this", { into: "keyword2" }],
|
||||
|
||||
["false", { into: "literal" }],
|
||||
["true", { into: "literal" }],
|
||||
["undefined", { into: "literal" }],
|
||||
["null", { into: "literal" }],
|
||||
]),
|
||||
})
|
||||
|
||||
constructor(frame) {
|
||||
this.frame = frame;
|
||||
|
||||
InputMode.highlight(frame);
|
||||
this.codeJar = CodeJar(frame, InputMode.highlight);
|
||||
this.codeJar.onUpdate(() => {
|
||||
for (let handler of frame.program.onChanged) {
|
||||
|
@ -48,7 +121,7 @@ class InputMode {
|
|||
}
|
||||
|
||||
static highlight(frame) {
|
||||
// TODO: Syntax highlighting
|
||||
highlight(frame, InputMode.JAVASCRIPT);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
72
static/js/components/literate-programming/highlight.js
Normal file
72
static/js/components/literate-programming/highlight.js
Normal file
|
@ -0,0 +1,72 @@
|
|||
// This tokenizer is highly inspired by the one found in rxi's lite.
|
||||
// I highly recommend checking it out!
|
||||
// https://github.com/rxi/lite/blob/master/data/core/tokenizer.lua
|
||||
|
||||
export function compileSyntax(def) {
|
||||
for (let pattern of def.patterns) {
|
||||
// Remove g (global) flag as it would interfere with the lexis process. We only want to match
|
||||
// the first token at the cursor.
|
||||
let flags = pattern.regex.flags.replace("g", "");
|
||||
// Add d (indices) and y (sticky) flags so that we can tell where the matches start and end.
|
||||
pattern.regex = new RegExp(pattern.regex, "y" + flags);
|
||||
}
|
||||
return def;
|
||||
}
|
||||
|
||||
function pushToken(tokens, kind, string) {
|
||||
let previousToken = tokens[tokens.length - 1];
|
||||
if (previousToken != null && previousToken.kind == kind) {
|
||||
previousToken.string += string;
|
||||
} else {
|
||||
tokens.push({ kind, string });
|
||||
}
|
||||
}
|
||||
|
||||
function tokenize(text, syntax) {
|
||||
let tokens = [];
|
||||
let i = 0;
|
||||
|
||||
while (i < text.length) {
|
||||
let hadMatch = false;
|
||||
for (let pattern of syntax.patterns) {
|
||||
let match;
|
||||
pattern.regex.lastIndex = i;
|
||||
if ((match = pattern.regex.exec(text)) != null) {
|
||||
pushToken(tokens, pattern.as, match[0]); // TODO
|
||||
i = pattern.regex.lastIndex;
|
||||
hadMatch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Base case: no pattern matched, just add the current character to the output.
|
||||
if (!hadMatch) {
|
||||
pushToken(tokens, "default", text.substring(i, i + 1));
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
for (let token of tokens) {
|
||||
let replacement = syntax.keywords.get(token.string);
|
||||
if (replacement != null) {
|
||||
if (replacement.onlyReplaces == null || token.kind == replacement.onlyReplaces) {
|
||||
token.kind = replacement.into;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
export function highlight(element, syntax) {
|
||||
let tokens = tokenize(element.textContent, syntax);
|
||||
|
||||
element.textContent = "";
|
||||
element.classList.add("th-syntax-highlighting");
|
||||
for (let token of tokens) {
|
||||
let span = document.createElement("span");
|
||||
span.textContent = token.string;
|
||||
span.classList.add(token.kind);
|
||||
element.appendChild(span);
|
||||
}
|
||||
}
|
10
static/js/vendor/codejar.js
vendored
10
static/js/vendor/codejar.js
vendored
|
@ -34,11 +34,12 @@ export function CodeJar(editor, highlight, opt = {}) {
|
|||
isLegacy = true;
|
||||
if (isLegacy)
|
||||
editor.setAttribute('contenteditable', 'true');
|
||||
const debounceHighlight = debounce(() => {
|
||||
// PATCH(liquidex): Remove debouncing here.
|
||||
const debounceHighlight = () => {
|
||||
const pos = save();
|
||||
doHighlight(editor, pos);
|
||||
restore(pos);
|
||||
}, 30);
|
||||
};
|
||||
let recording = false;
|
||||
const shouldRecord = (event) => {
|
||||
return !isUndo(event) && !isRedo(event)
|
||||
|
@ -78,14 +79,15 @@ export function CodeJar(editor, highlight, opt = {}) {
|
|||
}
|
||||
if (isLegacy && !isCopy(event))
|
||||
restore(save());
|
||||
|
||||
// PATCH(liquidex): Do highlighting on keypress for faster feedback.
|
||||
requestAnimationFrame(debounceHighlight);
|
||||
});
|
||||
on('keyup', event => {
|
||||
if (event.defaultPrevented)
|
||||
return;
|
||||
if (event.isComposing)
|
||||
return;
|
||||
if (prev !== toString())
|
||||
debounceHighlight();
|
||||
debounceRecordHistory(event);
|
||||
onUpdate(toString());
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue