summaryrefslogtreecommitdiff
path: root/src/engine/entities/LambdaFactory.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/engine/entities/LambdaFactory.ts')
-rw-r--r--src/engine/entities/LambdaFactory.ts157
1 files changed, 126 insertions, 31 deletions
diff --git a/src/engine/entities/LambdaFactory.ts b/src/engine/entities/LambdaFactory.ts
index 0721f80..d8fc7a2 100644
--- a/src/engine/entities/LambdaFactory.ts
+++ b/src/engine/entities/LambdaFactory.ts
@@ -19,12 +19,42 @@ import {
} from "../components";
import { Coord2D, Direction } from "../interfaces";
import { openModal, closeModal } from "../utils";
+import { EditorState, StateField, StateEffect, Range } from "@codemirror/state";
+import { Decoration, EditorView, keymap } from "@codemirror/view";
+import { defaultKeymap } from "@codemirror/commands";
+import rainbowBrackets from "rainbowbrackets";
+import { basicSetup } from "codemirror";
+import { parse } from "../../interpreter";
+
+interface CodeEditorState {
+ view: EditorView;
+ editorElement: HTMLElement;
+}
+
+const highlightEffect = StateEffect.define<Range<Decoration>[]>();
+const highlightExtension = StateField.define({
+ create() {
+ return Decoration.none;
+ },
+ update(value, transaction) {
+ value = value.map(transaction.changes);
+
+ for (let effect of transaction.effects) {
+ if (effect.is(highlightEffect))
+ value = value.update({ add: effect.value, sort: true });
+ }
+
+ return value;
+ },
+ provide: (f) => EditorView.decorations.from(f),
+});
export class LambdaFactory extends Entity {
private static spriteSpec: SpriteSpec = SPRITE_SPECS.get(
Sprites.LAMBDA_FACTORY,
) as SpriteSpec;
+ private codeEditorState: CodeEditorState | null;
private code: string;
private spawns: number;
@@ -33,6 +63,7 @@ export class LambdaFactory extends Entity {
this.code = code;
this.spawns = spawns;
+ this.codeEditorState = null;
this.addComponent(
new BoundingBox(
@@ -82,36 +113,86 @@ export class LambdaFactory extends Entity {
this.removeComponent(ComponentNames.Interactable);
}
- private onHighlight(direction: Direction) {
- if (direction === Direction.LEFT || direction === Direction.RIGHT) {
- const interaction = () => {
- const spawner = this.getComponent<LambdaSpawn>(
- ComponentNames.LambdaSpawn,
- );
- spawner.spawn(direction);
+ private spawnNewLambda(direction: Direction) {
+ const spawner = this.getComponent<LambdaSpawn>(ComponentNames.LambdaSpawn);
+ spawner.spawn(direction);
- const text = this.getComponent<Text>(ComponentNames.Text);
- text.text = spawner.spawnsLeft.toString();
- this.addComponent(text);
- };
+ const text = this.getComponent<Text>(ComponentNames.Text);
+ text.text = spawner.spawnsLeft.toString();
+ this.addComponent(text);
+ }
- this.addComponent(new Interactable(interaction));
+ private onHighlight(direction: Direction) {
+ if (direction === Direction.LEFT || direction === Direction.RIGHT) {
+ this.addComponent(new Interactable(() => this.spawnNewLambda(direction)));
return;
}
let modalOpen = false;
+ let editorView: EditorView | null = null;
+
+ const syntaxErrorDecoration = Decoration.mark({
+ class: "syntax-error",
+ });
+
const close = () => {
+ if (editorView) {
+ const text = editorView.state.doc.toString();
+
+ // remove all text from the editor
+ editorView.dispatch({
+ changes: {
+ from: 0,
+ to: text.length,
+ insert: "",
+ },
+ });
+ // add the new text to the editor
+ editorView.dispatch({
+ changes: {
+ from: 0,
+ to: 0,
+ insert: text,
+ },
+ });
+
+ try {
+ parse(text);
+ } catch (e: any) {
+ if (!e.location) {
+ return;
+ }
+ const {
+ location: {
+ start: { offset: start },
+ end: { offset: end },
+ },
+ } = e;
+
+ editorView.dispatch({
+ effects: highlightEffect.of([
+ syntaxErrorDecoration.range(
+ start === end ? start - 1 : start,
+ end,
+ ),
+ ]),
+ });
+
+ document.getElementById("syntax-error")!.innerText = e.message;
+
+ return;
+ }
+
+ const spawner = this.getComponent<LambdaSpawn>(
+ ComponentNames.LambdaSpawn,
+ );
+ spawner.code = text;
+ this.addComponent(spawner);
+ }
+
modalOpen = false;
closeModal();
- const spawner = this.getComponent<LambdaSpawn>(
- ComponentNames.LambdaSpawn,
- );
- spawner.code = (
- document.getElementById("code") as HTMLTextAreaElement
- ).value;
- this.addComponent(spawner);
-
document.getElementById(Miscellaneous.CANVAS_ID)!.focus();
return;
};
@@ -121,22 +202,36 @@ export class LambdaFactory extends Entity {
close();
return;
}
-
modalOpen = true;
- openModal(this.codeEditor(this.code));
+
+ openModal(
+ "<div class='code'><div id='code'></div><br><p id='syntax-error' class='error'></p><button id='close-modal'>Save</button></div>",
+ );
+ const spawner = this.getComponent<LambdaSpawn>(
+ ComponentNames.LambdaSpawn,
+ );
+
+ const startState = EditorState.create({
+ doc: spawner.code,
+ extensions: [
+ basicSetup,
+ keymap.of(defaultKeymap),
+ rainbowBrackets(),
+ highlightExtension,
+ ],
+ });
+
+ const codeBox = document.getElementById("code")!;
+ editorView = new EditorView({
+ state: startState,
+ parent: codeBox,
+ });
+
+ editorView.focus();
document.getElementById("close-modal")!.addEventListener("click", close);
};
this.addComponent(new Interactable(interaction));
}
-
- private codeEditor(code: string) {
- return `
- <div>
- <textarea id="code" rows="10" cols="50">${code}</textarea>
- <button id="close-modal">Close</button>
- </div>
- `;
- }
}