phantasmal-world/src/ui/quest_editor/ScriptEditorComponent.tsx

190 lines
5.6 KiB
TypeScript
Raw Normal View History

import { editor, languages } from "monaco-editor";
import React, { Component, createRef, ReactNode } from "react";
import { AutoSizer } from "react-virtualized";
import { OPCODES } from "../../data_formats/parsing/quest/bin";
import { quest_editor_store } from "../../stores/QuestEditorStore";
import "./ScriptEditorComponent.less";
2019-07-22 22:03:58 +08:00
import { disassemble } from "../../scripting/disassembly";
import { IReactionDisposer, autorun } from "mobx";
const ASM_SYNTAX: languages.IMonarchLanguage = {
defaultToken: "invalid",
tokenizer: {
root: [
2019-07-23 01:28:12 +08:00
// Registers.
[/r\d+/, "predefined"],
// Identifiers.
2019-07-23 01:28:12 +08:00
[/[a-z][a-z0-9_=<>!]*/, "identifier"],
// Labels.
2019-07-23 01:28:12 +08:00
[/\d+:/, "tag"],
// Whitespace.
[/[ \t\r\n]+/, "white"],
2019-07-22 22:03:58 +08:00
// [/\/\*/, "comment", "@comment"],
// [/\/\/.*$/, "comment"],
// Numbers.
[/-?\d*\.\d+([eE][-+]?\d+)?/, "number.float"],
2019-07-23 01:28:12 +08:00
// [/-?0[xX][0-9a-fA-F]+/, "number.hex"],
[/-?\d+/, "number"],
// Delimiters.
[/,/, "delimiter"],
// Strings.
[/"([^"\\]|\\.)*$/, "string.invalid"], // Unterminated string.
[/"/, { token: "string.quote", bracket: "@open", next: "@string" }],
],
2019-07-22 22:03:58 +08:00
// comment: [
// [/[^/*]+/, "comment"],
// [/\/\*/, "comment", "@push"], // Nested comment.
// [/\*\//, "comment", "@pop"],
// [/[/*]/, "comment"],
// ],
string: [
[/[^\\"]+/, "string"],
[/\\(?:[n\\"])/, "string.escape"],
[/\\./, "string.escape.invalid"],
[/"/, { token: "string.quote", bracket: "@close", next: "@pop" }],
],
},
};
const INSTRUCTION_SUGGESTIONS = OPCODES.filter(opcode => opcode != null).map(opcode => {
return ({
label: opcode.mnemonic,
kind: languages.CompletionItemKind.Function,
insertText: opcode.mnemonic,
} as any) as languages.CompletionItem;
});
languages.register({ id: "psoasm" });
languages.setMonarchTokensProvider("psoasm", ASM_SYNTAX);
languages.registerCompletionItemProvider("psoasm", {
provideCompletionItems: (model, position) => {
const value = model.getValueInRange({
startLineNumber: position.lineNumber,
endLineNumber: position.lineNumber,
startColumn: 1,
endColumn: position.column + 1,
});
const suggest = /^\s*([a-z][\w=<>!]*)?$/.test(value);
return {
suggestions: suggest ? INSTRUCTION_SUGGESTIONS : [],
incomplete: false,
};
},
});
languages.setLanguageConfiguration("psoasm", {
indentationRules: {
increaseIndentPattern: /\d+:/,
decreaseIndentPattern: /\d+/,
},
autoClosingPairs: [{ open: '"', close: '"' }],
surroundingPairs: [{ open: '"', close: '"' }],
});
editor.defineTheme("phantasmal-world", {
base: "vs-dark",
inherit: true,
2019-07-23 01:28:12 +08:00
rules: [
{ token: "", foreground: "e0e0e0", background: "151c21" },
{ token: "tag", foreground: "99bbff" },
{ token: "predefined", foreground: "bbffbb" },
{ token: "number", foreground: "ffffaa" },
{ token: "string", foreground: "88ffff" },
{ token: "string.escape", foreground: "8888ff" },
],
colors: {
"editor.background": "#151c21",
"editor.lineHighlightBackground": "#1a2228",
},
});
export class ScriptEditorComponent extends Component<{ className?: string }> {
render(): ReactNode {
let className = "qe-ScriptEditorComponent";
if (this.props.className) className += " " + this.props.className;
return (
<section className={className}>
<AutoSizer>
{({ width, height }) => <MonacoComponent width={width} height={height} />}
</AutoSizer>
</section>
);
}
}
type MonacoProps = {
width: number;
height: number;
};
class MonacoComponent extends Component<MonacoProps> {
private div_ref = createRef<HTMLDivElement>();
private editor?: editor.IStandaloneCodeEditor;
private disposer?: IReactionDisposer;
render(): ReactNode {
return <div ref={this.div_ref} />;
}
componentDidMount(): void {
if (this.div_ref.current) {
this.editor = editor.create(this.div_ref.current, {
theme: "phantasmal-world",
scrollBeyondLastLine: false,
autoIndent: true,
});
this.disposer = autorun(() => {
const quest = quest_editor_store.current_quest;
2019-07-22 22:03:58 +08:00
const model =
quest &&
2019-07-23 01:28:12 +08:00
editor.createModel(
disassemble(quest.instructions, quest.labels, true),
"psoasm"
);
if (model && this.editor) {
// model.onDidChangeContent(e => {
// });
this.editor.setModel(model);
}
});
}
}
componentWillUnmount(): void {
if (this.editor) {
const model = this.editor.getModel();
if (model) model.dispose();
this.editor.dispose();
}
if (this.disposer) this.disposer();
}
shouldComponentUpdate(): boolean {
return false;
}
UNSAFE_componentWillReceiveProps(props: MonacoProps): void {
if (
(this.props.width !== props.width || this.props.height !== props.height) &&
this.editor
) {
this.editor.layout(props);
}
}
}