diff options
Diffstat (limited to 'src/engine/entities/LambdaFactory.ts')
-rw-r--r-- | src/engine/entities/LambdaFactory.ts | 157 |
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> - `; - } } |