2019-07-22 02:44:34 +08:00
|
|
|
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";
|
2019-07-22 02:44:34 +08:00
|
|
|
import { IReactionDisposer, autorun } from "mobx";
|
|
|
|
|
|
|
|
const ASM_SYNTAX: languages.IMonarchLanguage = {
|
|
|
|
defaultToken: "invalid",
|
|
|
|
|
|
|
|
tokenizer: {
|
|
|
|
root: [
|
|
|
|
// Identifiers.
|
2019-07-22 22:03:58 +08:00
|
|
|
[/[a-z][a-z_=<>!]*/, "identifier"],
|
2019-07-22 02:44:34 +08:00
|
|
|
|
|
|
|
// Labels.
|
|
|
|
[/^\d+:/, "tag"],
|
|
|
|
|
|
|
|
// Registers.
|
|
|
|
[/r\d+/, "predefined"],
|
|
|
|
|
|
|
|
// Whitespace.
|
|
|
|
[/[ \t\r\n]+/, "white"],
|
2019-07-22 22:03:58 +08:00
|
|
|
// [/\/\*/, "comment", "@comment"],
|
|
|
|
// [/\/\/.*$/, "comment"],
|
2019-07-22 02:44:34 +08:00
|
|
|
|
|
|
|
// Numbers.
|
|
|
|
[/-?\d*\.\d+([eE][-+]?\d+)?/, "number.float"],
|
|
|
|
[/-?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"],
|
|
|
|
// ],
|
2019-07-22 02:44:34 +08:00
|
|
|
|
|
|
|
string: [
|
|
|
|
[/[^\\"]+/, "string"],
|
|
|
|
[/\\(?:[n\\"])/, "string.escape"],
|
|
|
|
[/\\./, "string.escape.invalid"],
|
|
|
|
[/"/, { token: "string.quote", bracket: "@close", next: "@pop" }],
|
|
|
|
],
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2019-07-22 18:31:20 +08:00
|
|
|
const INSTRUCTION_SUGGESTIONS = OPCODES.filter(opcode => opcode != null).map(opcode => {
|
2019-07-22 02:44:34 +08:00
|
|
|
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,
|
|
|
|
rules: [{ token: "", background: "151c21" }],
|
|
|
|
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 &&
|
|
|
|
editor.createModel(disassemble(quest.instructions, quest.labels, true), "psoasm");
|
2019-07-22 02:44:34 +08:00
|
|
|
|
|
|
|
if (model && this.editor) {
|
2019-07-22 18:31:20 +08:00
|
|
|
// model.onDidChangeContent(e => {
|
|
|
|
// });
|
|
|
|
|
2019-07-22 02:44:34 +08:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|