mirror of
https://github.com/DaanVandenBosch/phantasmal-world.git
synced 2025-04-06 08:08:28 +08:00
Merge pull request #1 from jtuu/script-editor-settings
Script editor toolbar and inline args mode toggle
This commit is contained in:
commit
7fe508716c
@ -43,7 +43,11 @@ class GuiStore implements Disposable {
|
|||||||
window.removeEventListener("keydown", this.dispatch_global_keydown);
|
window.removeEventListener("keydown", this.dispatch_global_keydown);
|
||||||
}
|
}
|
||||||
|
|
||||||
on_global_keydown(tool: GuiTool, binding: string, handler: (e: KeyboardEvent) => void): Disposable {
|
on_global_keydown(
|
||||||
|
tool: GuiTool,
|
||||||
|
binding: string,
|
||||||
|
handler: (e: KeyboardEvent) => void,
|
||||||
|
): Disposable {
|
||||||
const key = this.handler_key(tool, binding);
|
const key = this.handler_key(tool, binding);
|
||||||
this.global_keydown_handlers.set(key, handler);
|
this.global_keydown_handlers.set(key, handler);
|
||||||
|
|
||||||
|
33
src/quest_editor/gui/AsmEditorToolBar.ts
Normal file
33
src/quest_editor/gui/AsmEditorToolBar.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { ToolBar } from "../../core/gui/ToolBar";
|
||||||
|
import { CheckBox } from "../../core/gui/CheckBox";
|
||||||
|
import { asm_editor_store } from "../stores/AsmEditorStore";
|
||||||
|
|
||||||
|
export class AsmEditorToolBar extends ToolBar {
|
||||||
|
constructor() {
|
||||||
|
const inline_args_mode_checkbox = new CheckBox(true, {
|
||||||
|
label: "Inline args mode",
|
||||||
|
tooltip: asm_editor_store.has_issues.map(has_issues => {
|
||||||
|
let text =
|
||||||
|
"Transform arg_push* opcodes to be inline with the opcode the arguments are given to.";
|
||||||
|
|
||||||
|
if (has_issues) {
|
||||||
|
text += "\nThis mode cannot be toggled because there are issues in the script.";
|
||||||
|
}
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
super({
|
||||||
|
children: [inline_args_mode_checkbox],
|
||||||
|
});
|
||||||
|
|
||||||
|
this.disposables(
|
||||||
|
asm_editor_store.inline_args_mode.bind_to(inline_args_mode_checkbox.checked),
|
||||||
|
|
||||||
|
inline_args_mode_checkbox.enabled.bind_to(asm_editor_store.has_issues.map(b => !b)),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.finalize_construction(AsmEditorToolBar.prototype);
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@ import { el } from "../../core/gui/dom";
|
|||||||
import { editor, KeyCode, KeyMod } from "monaco-editor";
|
import { editor, KeyCode, KeyMod } from "monaco-editor";
|
||||||
import { asm_editor_store } from "../stores/AsmEditorStore";
|
import { asm_editor_store } from "../stores/AsmEditorStore";
|
||||||
import IStandaloneCodeEditor = editor.IStandaloneCodeEditor;
|
import IStandaloneCodeEditor = editor.IStandaloneCodeEditor;
|
||||||
|
import { AsmEditorToolBar } from "./AsmEditorToolBar";
|
||||||
|
|
||||||
editor.defineTheme("phantasmal-world", {
|
editor.defineTheme("phantasmal-world", {
|
||||||
base: "vs-dark",
|
base: "vs-dark",
|
||||||
@ -26,6 +27,7 @@ editor.defineTheme("phantasmal-world", {
|
|||||||
const DUMMY_MODEL = editor.createModel("", "psoasm");
|
const DUMMY_MODEL = editor.createModel("", "psoasm");
|
||||||
|
|
||||||
export class AsmEditorView extends ResizableWidget {
|
export class AsmEditorView extends ResizableWidget {
|
||||||
|
private readonly tool_bar_view = this.disposable(new AsmEditorToolBar());
|
||||||
readonly element = el.div();
|
readonly element = el.div();
|
||||||
|
|
||||||
private readonly editor: IStandaloneCodeEditor;
|
private readonly editor: IStandaloneCodeEditor;
|
||||||
@ -33,6 +35,8 @@ export class AsmEditorView extends ResizableWidget {
|
|||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
this.element.append(this.tool_bar_view.element);
|
||||||
|
|
||||||
this.editor = this.disposable(
|
this.editor = this.disposable(
|
||||||
editor.create(this.element, {
|
editor.create(this.element, {
|
||||||
theme: "phantasmal-world",
|
theme: "phantasmal-world",
|
||||||
|
@ -7,8 +7,9 @@ import {
|
|||||||
NewAssemblyInput,
|
NewAssemblyInput,
|
||||||
OutputMessageType,
|
OutputMessageType,
|
||||||
SignatureHelpInput,
|
SignatureHelpInput,
|
||||||
|
AssemblySettingsChangeInput,
|
||||||
} from "./assembly_worker_messages";
|
} from "./assembly_worker_messages";
|
||||||
import { AssemblyError, AssemblyWarning } from "./assembly";
|
import { AssemblyError, AssemblyWarning, AssemblySettings } from "./assembly";
|
||||||
import { disassemble } from "./disassembly";
|
import { disassemble } from "./disassembly";
|
||||||
import { QuestModel } from "../model/QuestModel";
|
import { QuestModel } from "../model/QuestModel";
|
||||||
import { Kind, OPCODES } from "./opcodes";
|
import { Kind, OPCODES } from "./opcodes";
|
||||||
@ -74,9 +75,9 @@ export class AssemblyAnalyser implements Disposable {
|
|||||||
this.worker.onmessage = this.process_worker_message;
|
this.worker.onmessage = this.process_worker_message;
|
||||||
}
|
}
|
||||||
|
|
||||||
disassemble(quest: QuestModel): string[] {
|
disassemble(quest: QuestModel, manual_stack?: boolean): string[] {
|
||||||
this.quest = quest;
|
this.quest = quest;
|
||||||
const assembly = disassemble(quest.object_code);
|
const assembly = disassemble(quest.object_code, manual_stack);
|
||||||
const message: NewAssemblyInput = { type: InputMessageType.NewAssembly, assembly };
|
const message: NewAssemblyInput = { type: InputMessageType.NewAssembly, assembly };
|
||||||
this.worker.postMessage(message);
|
this.worker.postMessage(message);
|
||||||
return assembly;
|
return assembly;
|
||||||
@ -130,6 +131,14 @@ export class AssemblyAnalyser implements Disposable {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
update_settings(changed_settings: Partial<AssemblySettings>): void {
|
||||||
|
const message: AssemblySettingsChangeInput = {
|
||||||
|
type: InputMessageType.SettingsChange,
|
||||||
|
settings: changed_settings,
|
||||||
|
};
|
||||||
|
this.worker.postMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
dispose(): void {
|
dispose(): void {
|
||||||
this.worker.terminate();
|
this.worker.terminate();
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,10 @@ export type AssemblyWarning = {
|
|||||||
|
|
||||||
export type AssemblyError = AssemblyWarning;
|
export type AssemblyError = AssemblyWarning;
|
||||||
|
|
||||||
|
export type AssemblySettings = {
|
||||||
|
manual_stack: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export function assemble(
|
export function assemble(
|
||||||
assembly: string[],
|
assembly: string[],
|
||||||
manual_stack: boolean = false,
|
manual_stack: boolean = false,
|
||||||
|
@ -6,8 +6,9 @@ import {
|
|||||||
OutputMessageType,
|
OutputMessageType,
|
||||||
SignatureHelpInput,
|
SignatureHelpInput,
|
||||||
SignatureHelpOutput,
|
SignatureHelpOutput,
|
||||||
|
AssemblySettingsChangeInput,
|
||||||
} from "./assembly_worker_messages";
|
} from "./assembly_worker_messages";
|
||||||
import { assemble } from "./assembly";
|
import { assemble, AssemblySettings } from "./assembly";
|
||||||
import Logger from "js-logger";
|
import Logger from "js-logger";
|
||||||
import { SegmentType } from "./instructions";
|
import { SegmentType } from "./instructions";
|
||||||
import { Opcode, OPCODES_BY_MNEMONIC } from "./opcodes";
|
import { Opcode, OPCODES_BY_MNEMONIC } from "./opcodes";
|
||||||
@ -24,6 +25,10 @@ let lines: string[] = [];
|
|||||||
const messages: AssemblyWorkerInput[] = [];
|
const messages: AssemblyWorkerInput[] = [];
|
||||||
let timeout: any;
|
let timeout: any;
|
||||||
|
|
||||||
|
const assembly_settings: AssemblySettings = {
|
||||||
|
manual_stack: false,
|
||||||
|
};
|
||||||
|
|
||||||
ctx.onmessage = (e: MessageEvent) => {
|
ctx.onmessage = (e: MessageEvent) => {
|
||||||
messages.push(e.data);
|
messages.push(e.data);
|
||||||
|
|
||||||
@ -52,6 +57,9 @@ function process_messages(): void {
|
|||||||
case InputMessageType.SignatureHelp:
|
case InputMessageType.SignatureHelp:
|
||||||
signature_help(message);
|
signature_help(message);
|
||||||
break;
|
break;
|
||||||
|
case InputMessageType.SettingsChange:
|
||||||
|
settings_change(message);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -130,8 +138,17 @@ function signature_help(message: SignatureHelpInput): void {
|
|||||||
ctx.postMessage(response);
|
ctx.postMessage(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply changes to settings.
|
||||||
|
*/
|
||||||
|
function settings_change(message: AssemblySettingsChangeInput): void {
|
||||||
|
if (message.settings.hasOwnProperty("manual_stack")) {
|
||||||
|
assembly_settings.manual_stack = Boolean(message.settings.manual_stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function assemble_and_send(): void {
|
function assemble_and_send(): void {
|
||||||
const assembler_result = assemble(lines);
|
const assembler_result = assemble(lines, assembly_settings.manual_stack);
|
||||||
const map_designations = new Map<number, number>();
|
const map_designations = new Map<number, number>();
|
||||||
|
|
||||||
for (const segment of assembler_result.object_code) {
|
for (const segment of assembler_result.object_code) {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { AssemblyError, AssemblyWarning } from "./assembly";
|
import { AssemblyError, AssemblyWarning, AssemblySettings } from "./assembly";
|
||||||
import { Segment } from "./instructions";
|
import { Segment } from "./instructions";
|
||||||
import { Opcode } from "./opcodes";
|
import { Opcode } from "./opcodes";
|
||||||
|
|
||||||
@ -6,9 +6,14 @@ export enum InputMessageType {
|
|||||||
NewAssembly,
|
NewAssembly,
|
||||||
AssemblyChange,
|
AssemblyChange,
|
||||||
SignatureHelp,
|
SignatureHelp,
|
||||||
|
SettingsChange,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AssemblyWorkerInput = NewAssemblyInput | AssemblyChangeInput | SignatureHelpInput;
|
export type AssemblyWorkerInput =
|
||||||
|
| NewAssemblyInput
|
||||||
|
| AssemblyChangeInput
|
||||||
|
| SignatureHelpInput
|
||||||
|
| AssemblySettingsChangeInput;
|
||||||
|
|
||||||
export type NewAssemblyInput = {
|
export type NewAssemblyInput = {
|
||||||
readonly type: InputMessageType.NewAssembly;
|
readonly type: InputMessageType.NewAssembly;
|
||||||
@ -33,6 +38,11 @@ export type SignatureHelpInput = {
|
|||||||
readonly col: number;
|
readonly col: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type AssemblySettingsChangeInput = {
|
||||||
|
readonly type: InputMessageType.SettingsChange;
|
||||||
|
readonly settings: Partial<AssemblySettings>;
|
||||||
|
};
|
||||||
|
|
||||||
export enum OutputMessageType {
|
export enum OutputMessageType {
|
||||||
NewObjectCode,
|
NewObjectCode,
|
||||||
SignatureHelp,
|
SignatureHelp,
|
||||||
|
@ -69,6 +69,9 @@ export class AsmEditorStore implements Disposable {
|
|||||||
() => this._did_redo.emit({ value: "asm undo" }),
|
() => this._did_redo.emit({ value: "asm undo" }),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
readonly inline_args_mode: WritableProperty<boolean> = property(true);
|
||||||
|
readonly has_issues: WritableProperty<boolean> = property(false);
|
||||||
|
|
||||||
private readonly disposer = new Disposer();
|
private readonly disposer = new Disposer();
|
||||||
private readonly model_disposer = this.disposer.add(new Disposer());
|
private readonly model_disposer = this.disposer.add(new Disposer());
|
||||||
private readonly _model: WritableProperty<ITextModel | undefined> = property(undefined);
|
private readonly _model: WritableProperty<ITextModel | undefined> = property(undefined);
|
||||||
@ -88,6 +91,18 @@ export class AsmEditorStore implements Disposable {
|
|||||||
assembly_analyser.issues.observe(({ value }) => this.update_model_markers(value), {
|
assembly_analyser.issues.observe(({ value }) => this.update_model_markers(value), {
|
||||||
call_now: true,
|
call_now: true,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
this.inline_args_mode.observe(() => {
|
||||||
|
// don't allow changing inline args mode if there are issues
|
||||||
|
if (!this.has_issues.val) {
|
||||||
|
this.change_inline_args_mode();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
assembly_analyser.issues.observe(({ value }) => {
|
||||||
|
this.has_issues.val =
|
||||||
|
Boolean(value.warnings.length) || Boolean(value.errors.length);
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,53 +110,63 @@ export class AsmEditorStore implements Disposable {
|
|||||||
this.disposer.dispose();
|
this.disposer.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup features for a given editor model.
|
||||||
|
* Features include undo/redo history and reassembling on change.
|
||||||
|
*/
|
||||||
|
private setup_editor_model_features(model: editor.ITextModel): void {
|
||||||
|
let initial_version = model.getAlternativeVersionId();
|
||||||
|
let current_version = initial_version;
|
||||||
|
let last_version = initial_version;
|
||||||
|
|
||||||
|
this.model_disposer.add(
|
||||||
|
model.onDidChangeContent(e => {
|
||||||
|
const version = model.getAlternativeVersionId();
|
||||||
|
|
||||||
|
if (version < current_version) {
|
||||||
|
// Undoing.
|
||||||
|
this.undo.can_redo.val = true;
|
||||||
|
|
||||||
|
if (version === initial_version) {
|
||||||
|
this.undo.can_undo.val = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Redoing.
|
||||||
|
if (version <= last_version) {
|
||||||
|
if (version === last_version) {
|
||||||
|
this.undo.can_redo.val = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.undo.can_redo.val = false;
|
||||||
|
|
||||||
|
if (current_version > last_version) {
|
||||||
|
last_version = current_version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.undo.can_undo.val = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
current_version = version;
|
||||||
|
|
||||||
|
assembly_analyser.update_assembly(e.changes);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private quest_changed(quest?: QuestModel): void {
|
private quest_changed(quest?: QuestModel): void {
|
||||||
this.undo.reset();
|
this.undo.reset();
|
||||||
this.model_disposer.dispose_all();
|
this.model_disposer.dispose_all();
|
||||||
|
|
||||||
if (quest) {
|
if (quest) {
|
||||||
const assembly = assembly_analyser.disassemble(quest);
|
const manual_stack = !this.inline_args_mode.val;
|
||||||
|
|
||||||
|
const assembly = assembly_analyser.disassemble(quest, manual_stack);
|
||||||
const model = this.model_disposer.add(
|
const model = this.model_disposer.add(
|
||||||
editor.createModel(assembly.join("\n"), "psoasm"),
|
editor.createModel(assembly.join("\n"), "psoasm"),
|
||||||
);
|
);
|
||||||
|
|
||||||
let initial_version = model.getAlternativeVersionId();
|
this.setup_editor_model_features(model);
|
||||||
let current_version = initial_version;
|
|
||||||
let last_version = initial_version;
|
|
||||||
|
|
||||||
this.model_disposer.add(
|
|
||||||
model.onDidChangeContent(e => {
|
|
||||||
const version = model.getAlternativeVersionId();
|
|
||||||
|
|
||||||
if (version < current_version) {
|
|
||||||
// Undoing.
|
|
||||||
this.undo.can_redo.val = true;
|
|
||||||
|
|
||||||
if (version === initial_version) {
|
|
||||||
this.undo.can_undo.val = false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Redoing.
|
|
||||||
if (version <= last_version) {
|
|
||||||
if (version === last_version) {
|
|
||||||
this.undo.can_redo.val = false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.undo.can_redo.val = false;
|
|
||||||
|
|
||||||
if (current_version > last_version) {
|
|
||||||
last_version = current_version;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.undo.can_undo.val = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
current_version = version;
|
|
||||||
|
|
||||||
assembly_analyser.update_assembly(e.changes);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
this._model.val = model;
|
this._model.val = model;
|
||||||
} else {
|
} else {
|
||||||
@ -188,6 +213,31 @@ export class AsmEditorStore implements Disposable {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private change_inline_args_mode(): void {
|
||||||
|
this.update_assembly_settings();
|
||||||
|
|
||||||
|
const quest = quest_editor_store.current_quest.val;
|
||||||
|
|
||||||
|
if (!quest) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const manual_stack = !this.inline_args_mode.val;
|
||||||
|
|
||||||
|
const assembly = assembly_analyser.disassemble(quest, manual_stack);
|
||||||
|
const model = this.model_disposer.add(editor.createModel(assembly.join("\n"), "psoasm"));
|
||||||
|
|
||||||
|
this.setup_editor_model_features(model);
|
||||||
|
|
||||||
|
this._model.val = model;
|
||||||
|
}
|
||||||
|
|
||||||
|
private update_assembly_settings(): void {
|
||||||
|
assembly_analyser.update_settings({
|
||||||
|
manual_stack: !this.inline_args_mode.val,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const asm_editor_store = new AsmEditorStore();
|
export const asm_editor_store = new AsmEditorStore();
|
||||||
|
Loading…
Reference in New Issue
Block a user