Merge pull request #1 from jtuu/script-editor-settings

Script editor toolbar and inline args mode toggle
This commit is contained in:
Daan Vanden Bosch 2019-09-15 22:37:24 +02:00 committed by GitHub
commit 7fe508716c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 177 additions and 46 deletions

View File

@ -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);

View 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);
}
}

View File

@ -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",

View File

@ -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();
} }

View File

@ -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,

View File

@ -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) {

View File

@ -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,

View File

@ -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();