|
@@ -1,84 +1,111 @@
|
|
|
|
+import * as fs from "fs";
|
|
import {
|
|
import {
|
|
- Breakpoint, InitializedEvent, LoggingDebugSession, Source, Thread
|
|
|
|
-} from "vscode-debugadapter";
|
|
|
|
-import { DebugProtocol } from "vscode-debugprotocol";
|
|
|
|
-import { get_configuration } from "../utils";
|
|
|
|
-import { GodotDebugData, GodotVariable } from "./debug_runtime";
|
|
|
|
-import { Mediator } from "./mediator";
|
|
|
|
-import { SceneTreeProvider } from "./scene_tree/scene_tree_provider";
|
|
|
|
|
|
+ LoggingDebugSession,
|
|
|
|
+ InitializedEvent,
|
|
|
|
+ Thread,
|
|
|
|
+ Source,
|
|
|
|
+ Breakpoint,
|
|
|
|
+ StoppedEvent,
|
|
|
|
+ TerminatedEvent,
|
|
|
|
+} from "@vscode/debugadapter";
|
|
|
|
+import { DebugProtocol } from "@vscode/debugprotocol";
|
|
|
|
+import { debug } from "vscode";
|
|
|
|
+import { Subject } from "await-notify";
|
|
|
|
+import { GodotDebugData, GodotVariable, GodotStackVars } from "../debug_runtime";
|
|
|
|
+import { LaunchRequestArguments, AttachRequestArguments } from "../debugger";
|
|
|
|
+import { SceneTreeProvider } from "../scene_tree_provider";
|
|
|
|
+import { ObjectId } from "./variables/variants";
|
|
|
|
+import { parse_variable, is_variable_built_in_type } from "./helpers";
|
|
import { ServerController } from "./server_controller";
|
|
import { ServerController } from "./server_controller";
|
|
-import { ObjectId, RawObject } from "./variables/variants";
|
|
|
|
-const { Subject } = require("await-notify");
|
|
|
|
-import fs = require("fs");
|
|
|
|
-
|
|
|
|
-interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArguments {
|
|
|
|
- address: string;
|
|
|
|
- launch_game_instance: boolean;
|
|
|
|
- launch_scene: boolean;
|
|
|
|
- port: number;
|
|
|
|
- project: string;
|
|
|
|
- scene_file: string;
|
|
|
|
- additional_options: string;
|
|
|
|
-}
|
|
|
|
|
|
+import { createLogger } from "../../logger";
|
|
|
|
+
|
|
|
|
+const log = createLogger("debugger.session", { output: "Godot Debugger" });
|
|
|
|
|
|
export class GodotDebugSession extends LoggingDebugSession {
|
|
export class GodotDebugSession extends LoggingDebugSession {
|
|
private all_scopes: GodotVariable[];
|
|
private all_scopes: GodotVariable[];
|
|
- private controller?: ServerController;
|
|
|
|
- private debug_data = new GodotDebugData();
|
|
|
|
|
|
+ public controller = new ServerController(this);
|
|
|
|
+ public debug_data = new GodotDebugData(this);
|
|
|
|
+ public sceneTree: SceneTreeProvider;
|
|
private exception = false;
|
|
private exception = false;
|
|
- private got_scope = new Subject();
|
|
|
|
|
|
+ private got_scope: Subject = new Subject();
|
|
private ongoing_inspections: bigint[] = [];
|
|
private ongoing_inspections: bigint[] = [];
|
|
private previous_inspections: bigint[] = [];
|
|
private previous_inspections: bigint[] = [];
|
|
- private configuration_done = new Subject();
|
|
|
|
|
|
+ private configuration_done: Subject = new Subject();
|
|
|
|
+ private mode: "launch" | "attach" | "" = "";
|
|
|
|
+ public inspect_callbacks: Map<
|
|
|
|
+ bigint,
|
|
|
|
+ (class_name: string, variable: GodotVariable) => void
|
|
|
|
+ > = new Map();
|
|
|
|
|
|
public constructor() {
|
|
public constructor() {
|
|
super();
|
|
super();
|
|
|
|
|
|
this.setDebuggerLinesStartAt1(false);
|
|
this.setDebuggerLinesStartAt1(false);
|
|
this.setDebuggerColumnsStartAt1(false);
|
|
this.setDebuggerColumnsStartAt1(false);
|
|
|
|
+ }
|
|
|
|
|
|
- Mediator.set_session(this);
|
|
|
|
- this.controller = new ServerController();
|
|
|
|
- Mediator.set_controller(this.controller);
|
|
|
|
- Mediator.set_debug_data(this.debug_data);
|
|
|
|
|
|
+ public dispose() {
|
|
|
|
+ this.controller.stop();
|
|
}
|
|
}
|
|
|
|
|
|
- public dispose() {}
|
|
|
|
|
|
+ protected initializeRequest(
|
|
|
|
+ response: DebugProtocol.InitializeResponse,
|
|
|
|
+ args: DebugProtocol.InitializeRequestArguments
|
|
|
|
+ ) {
|
|
|
|
+ response.body = response.body || {};
|
|
|
|
|
|
- public set_exception(exception: boolean) {
|
|
|
|
- this.exception = true;
|
|
|
|
|
|
+ response.body.supportsConfigurationDoneRequest = true;
|
|
|
|
+ response.body.supportsTerminateRequest = true;
|
|
|
|
+ response.body.supportsEvaluateForHovers = false;
|
|
|
|
+ response.body.supportsStepBack = false;
|
|
|
|
+ response.body.supportsGotoTargetsRequest = false;
|
|
|
|
+ response.body.supportsCancelRequest = false;
|
|
|
|
+ response.body.supportsCompletionsRequest = false;
|
|
|
|
+ response.body.supportsFunctionBreakpoints = false;
|
|
|
|
+ response.body.supportsDataBreakpoints = false;
|
|
|
|
+ response.body.supportsBreakpointLocationsRequest = false;
|
|
|
|
+ response.body.supportsConditionalBreakpoints = false;
|
|
|
|
+ response.body.supportsHitConditionalBreakpoints = false;
|
|
|
|
+ response.body.supportsLogPoints = false;
|
|
|
|
+ response.body.supportsModulesRequest = false;
|
|
|
|
+ response.body.supportsReadMemoryRequest = false;
|
|
|
|
+ response.body.supportsRestartFrame = false;
|
|
|
|
+ response.body.supportsRestartRequest = false;
|
|
|
|
+ response.body.supportsSetExpression = false;
|
|
|
|
+ response.body.supportsStepInTargetsRequest = false;
|
|
|
|
+ response.body.supportsTerminateThreadsRequest = false;
|
|
|
|
+
|
|
|
|
+ this.sendResponse(response);
|
|
|
|
+ this.sendEvent(new InitializedEvent());
|
|
}
|
|
}
|
|
|
|
|
|
- public set_inspection(id: bigint, replacement: GodotVariable) {
|
|
|
|
- let variables = this.all_scopes.filter(
|
|
|
|
- (va) => va && va.value instanceof ObjectId && va.value.id === id
|
|
|
|
- );
|
|
|
|
|
|
+ protected async launchRequest(
|
|
|
|
+ response: DebugProtocol.LaunchResponse,
|
|
|
|
+ args: LaunchRequestArguments
|
|
|
|
+ ) {
|
|
|
|
+ await this.configuration_done.wait(1000);
|
|
|
|
|
|
- variables.forEach((va) => {
|
|
|
|
- let index = this.all_scopes.findIndex((va_id) => va_id === va);
|
|
|
|
- let old = this.all_scopes.splice(index, 1);
|
|
|
|
- replacement.name = old[0].name;
|
|
|
|
- replacement.scope_path = old[0].scope_path;
|
|
|
|
- this.append_variable(replacement, index);
|
|
|
|
- });
|
|
|
|
|
|
+ this.mode = "launch";
|
|
|
|
|
|
- this.ongoing_inspections.splice(
|
|
|
|
- this.ongoing_inspections.findIndex((va_id) => va_id === id),
|
|
|
|
- 1
|
|
|
|
- );
|
|
|
|
|
|
+ this.debug_data.projectPath = args.project;
|
|
|
|
+ this.exception = false;
|
|
|
|
+ await this.controller.launch(args);
|
|
|
|
|
|
- this.previous_inspections.push(id);
|
|
|
|
|
|
+ this.sendResponse(response);
|
|
|
|
+ }
|
|
|
|
|
|
- this.add_to_inspections();
|
|
|
|
|
|
+ protected async attachRequest(
|
|
|
|
+ response: DebugProtocol.AttachResponse,
|
|
|
|
+ args: AttachRequestArguments
|
|
|
|
+ ) {
|
|
|
|
+ await this.configuration_done.wait(1000);
|
|
|
|
|
|
- if (this.ongoing_inspections.length === 0) {
|
|
|
|
- this.previous_inspections = [];
|
|
|
|
- this.got_scope.notify();
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ this.mode = "attach";
|
|
|
|
+
|
|
|
|
+ this.exception = false;
|
|
|
|
+ await this.controller.attach(args);
|
|
|
|
|
|
- public set_scene_tree(scene_tree_provider: SceneTreeProvider) {
|
|
|
|
- this.debug_data.scene_tree = scene_tree_provider;
|
|
|
|
|
|
+ this.sendResponse(response);
|
|
}
|
|
}
|
|
|
|
|
|
public configurationDoneRequest(
|
|
public configurationDoneRequest(
|
|
@@ -89,80 +116,35 @@ export class GodotDebugSession extends LoggingDebugSession {
|
|
this.sendResponse(response);
|
|
this.sendResponse(response);
|
|
}
|
|
}
|
|
|
|
|
|
- public set_scopes(
|
|
|
|
- locals: GodotVariable[],
|
|
|
|
- members: GodotVariable[],
|
|
|
|
- globals: GodotVariable[]
|
|
|
|
- ) {
|
|
|
|
- this.all_scopes = [
|
|
|
|
- undefined,
|
|
|
|
- { name: "local", value: undefined, sub_values: locals, scope_path: "@" },
|
|
|
|
- {
|
|
|
|
- name: "member",
|
|
|
|
- value: undefined,
|
|
|
|
- sub_values: members,
|
|
|
|
- scope_path: "@",
|
|
|
|
- },
|
|
|
|
- {
|
|
|
|
- name: "global",
|
|
|
|
- value: undefined,
|
|
|
|
- sub_values: globals,
|
|
|
|
- scope_path: "@",
|
|
|
|
- },
|
|
|
|
- ];
|
|
|
|
-
|
|
|
|
- locals.forEach((va) => {
|
|
|
|
- va.scope_path = `@.local`;
|
|
|
|
- this.append_variable(va);
|
|
|
|
- });
|
|
|
|
-
|
|
|
|
- members.forEach((va) => {
|
|
|
|
- va.scope_path = `@.member`;
|
|
|
|
- this.append_variable(va);
|
|
|
|
- });
|
|
|
|
-
|
|
|
|
- globals.forEach((va) => {
|
|
|
|
- va.scope_path = `@.global`;
|
|
|
|
- this.append_variable(va);
|
|
|
|
- });
|
|
|
|
-
|
|
|
|
- this.add_to_inspections();
|
|
|
|
-
|
|
|
|
- if (this.ongoing_inspections.length === 0) {
|
|
|
|
- this.previous_inspections = [];
|
|
|
|
- this.got_scope.notify();
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
protected continueRequest(
|
|
protected continueRequest(
|
|
response: DebugProtocol.ContinueResponse,
|
|
response: DebugProtocol.ContinueResponse,
|
|
args: DebugProtocol.ContinueArguments
|
|
args: DebugProtocol.ContinueArguments
|
|
) {
|
|
) {
|
|
if (!this.exception) {
|
|
if (!this.exception) {
|
|
response.body = { allThreadsContinued: true };
|
|
response.body = { allThreadsContinued: true };
|
|
- Mediator.notify("continue");
|
|
|
|
|
|
+ this.controller.continue();
|
|
this.sendResponse(response);
|
|
this.sendResponse(response);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- protected evaluateRequest(
|
|
|
|
|
|
+ protected async evaluateRequest(
|
|
response: DebugProtocol.EvaluateResponse,
|
|
response: DebugProtocol.EvaluateResponse,
|
|
args: DebugProtocol.EvaluateArguments
|
|
args: DebugProtocol.EvaluateArguments
|
|
) {
|
|
) {
|
|
|
|
+ await debug.activeDebugSession.customRequest("scopes", { frameId: 0 });
|
|
|
|
+
|
|
if (this.all_scopes) {
|
|
if (this.all_scopes) {
|
|
- let expression = args.expression;
|
|
|
|
- let matches = expression.match(/^[_a-zA-Z0-9]+?$/);
|
|
|
|
- if (matches) {
|
|
|
|
- let result_idx = this.all_scopes.findIndex(
|
|
|
|
- (va) => va && va.name === expression
|
|
|
|
- );
|
|
|
|
- if (result_idx !== -1) {
|
|
|
|
- let result = this.all_scopes[result_idx];
|
|
|
|
- response.body = {
|
|
|
|
- result: this.parse_variable(result).value,
|
|
|
|
- variablesReference: result_idx,
|
|
|
|
- };
|
|
|
|
- }
|
|
|
|
|
|
+ var variable = this.get_variable(args.expression, null, null, null);
|
|
|
|
+
|
|
|
|
+ if (variable.error == null) {
|
|
|
|
+ var parsed_variable = parse_variable(variable.variable);
|
|
|
|
+ response.body = {
|
|
|
|
+ result: parsed_variable.value,
|
|
|
|
+ variablesReference: !is_variable_built_in_type(variable.variable) ? variable.index : 0
|
|
|
|
+ };
|
|
|
|
+ } else {
|
|
|
|
+ response.success = false;
|
|
|
|
+ response.message = variable.error;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -176,76 +158,12 @@ export class GodotDebugSession extends LoggingDebugSession {
|
|
this.sendResponse(response);
|
|
this.sendResponse(response);
|
|
}
|
|
}
|
|
|
|
|
|
- protected initializeRequest(
|
|
|
|
- response: DebugProtocol.InitializeResponse,
|
|
|
|
- args: DebugProtocol.InitializeRequestArguments
|
|
|
|
- ) {
|
|
|
|
- response.body = response.body || {};
|
|
|
|
-
|
|
|
|
- response.body.supportsConfigurationDoneRequest = true;
|
|
|
|
- response.body.supportsTerminateRequest = true;
|
|
|
|
-
|
|
|
|
- response.body.supportsEvaluateForHovers = false;
|
|
|
|
-
|
|
|
|
- response.body.supportsStepBack = false;
|
|
|
|
- response.body.supportsGotoTargetsRequest = false;
|
|
|
|
-
|
|
|
|
- response.body.supportsCancelRequest = false;
|
|
|
|
-
|
|
|
|
- response.body.supportsCompletionsRequest = false;
|
|
|
|
-
|
|
|
|
- response.body.supportsFunctionBreakpoints = false;
|
|
|
|
- response.body.supportsDataBreakpoints = false;
|
|
|
|
- response.body.supportsBreakpointLocationsRequest = false;
|
|
|
|
- response.body.supportsConditionalBreakpoints = false;
|
|
|
|
- response.body.supportsHitConditionalBreakpoints = false;
|
|
|
|
-
|
|
|
|
- response.body.supportsLogPoints = false;
|
|
|
|
-
|
|
|
|
- response.body.supportsModulesRequest = false;
|
|
|
|
-
|
|
|
|
- response.body.supportsReadMemoryRequest = false;
|
|
|
|
-
|
|
|
|
- response.body.supportsRestartFrame = false;
|
|
|
|
- response.body.supportsRestartRequest = false;
|
|
|
|
-
|
|
|
|
- response.body.supportsSetExpression = false;
|
|
|
|
-
|
|
|
|
- response.body.supportsStepInTargetsRequest = false;
|
|
|
|
-
|
|
|
|
- response.body.supportsTerminateThreadsRequest = false;
|
|
|
|
-
|
|
|
|
- this.sendResponse(response);
|
|
|
|
- this.sendEvent(new InitializedEvent());
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- protected async launchRequest(
|
|
|
|
- response: DebugProtocol.LaunchResponse,
|
|
|
|
- args: LaunchRequestArguments
|
|
|
|
- ) {
|
|
|
|
- await this.configuration_done.wait(1000);
|
|
|
|
- this.debug_data.project_path = args.project;
|
|
|
|
- this.exception = false;
|
|
|
|
- Mediator.notify("start", [
|
|
|
|
- args.project,
|
|
|
|
- args.address,
|
|
|
|
- args.port,
|
|
|
|
- args.launch_game_instance,
|
|
|
|
- args.launch_scene,
|
|
|
|
- args.scene_file,
|
|
|
|
- args.additional_options,
|
|
|
|
- get_configuration("sceneFileConfig", "") || args.scene_file,
|
|
|
|
- ]);
|
|
|
|
-
|
|
|
|
- this.sendResponse(response);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
protected nextRequest(
|
|
protected nextRequest(
|
|
response: DebugProtocol.NextResponse,
|
|
response: DebugProtocol.NextResponse,
|
|
args: DebugProtocol.NextArguments
|
|
args: DebugProtocol.NextArguments
|
|
) {
|
|
) {
|
|
if (!this.exception) {
|
|
if (!this.exception) {
|
|
- Mediator.notify("next");
|
|
|
|
|
|
+ this.controller.next();
|
|
this.sendResponse(response);
|
|
this.sendResponse(response);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -255,7 +173,7 @@ export class GodotDebugSession extends LoggingDebugSession {
|
|
args: DebugProtocol.PauseArguments
|
|
args: DebugProtocol.PauseArguments
|
|
) {
|
|
) {
|
|
if (!this.exception) {
|
|
if (!this.exception) {
|
|
- Mediator.notify("break");
|
|
|
|
|
|
+ this.controller.break();
|
|
this.sendResponse(response);
|
|
this.sendResponse(response);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -264,10 +182,7 @@ export class GodotDebugSession extends LoggingDebugSession {
|
|
response: DebugProtocol.ScopesResponse,
|
|
response: DebugProtocol.ScopesResponse,
|
|
args: DebugProtocol.ScopesArguments
|
|
args: DebugProtocol.ScopesArguments
|
|
) {
|
|
) {
|
|
- while (this.ongoing_inspections.length > 0) {
|
|
|
|
- await this.got_scope.wait(100);
|
|
|
|
- }
|
|
|
|
- Mediator.notify("get_scopes", [args.frameId]);
|
|
|
|
|
|
+ this.controller.request_stack_frame_vars(args.frameId);
|
|
await this.got_scope.wait(2000);
|
|
await this.got_scope.wait(2000);
|
|
|
|
|
|
response.body = {
|
|
response.body = {
|
|
@@ -284,12 +199,12 @@ export class GodotDebugSession extends LoggingDebugSession {
|
|
response: DebugProtocol.SetBreakpointsResponse,
|
|
response: DebugProtocol.SetBreakpointsResponse,
|
|
args: DebugProtocol.SetBreakpointsArguments
|
|
args: DebugProtocol.SetBreakpointsArguments
|
|
) {
|
|
) {
|
|
- let path = (args.source.path as string).replace(/\\/g, "/");
|
|
|
|
- let client_lines = args.lines || [];
|
|
|
|
|
|
+ const path = (args.source.path as string).replace(/\\/g, "/");
|
|
|
|
+ const client_lines = args.lines || [];
|
|
|
|
|
|
if (fs.existsSync(path)) {
|
|
if (fs.existsSync(path)) {
|
|
let bps = this.debug_data.get_breakpoints(path);
|
|
let bps = this.debug_data.get_breakpoints(path);
|
|
- let bp_lines = bps.map((bp) => bp.line);
|
|
|
|
|
|
+ const bp_lines = bps.map((bp) => bp.line);
|
|
|
|
|
|
bps.forEach((bp) => {
|
|
bps.forEach((bp) => {
|
|
if (client_lines.indexOf(bp.line) === -1) {
|
|
if (client_lines.indexOf(bp.line) === -1) {
|
|
@@ -298,7 +213,7 @@ export class GodotDebugSession extends LoggingDebugSession {
|
|
});
|
|
});
|
|
client_lines.forEach((l) => {
|
|
client_lines.forEach((l) => {
|
|
if (bp_lines.indexOf(l) === -1) {
|
|
if (bp_lines.indexOf(l) === -1) {
|
|
- let bp = args.breakpoints.find((bp_at_line) => (bp_at_line.line == l));
|
|
|
|
|
|
+ const bp = args.breakpoints.find((bp_at_line) => (bp_at_line.line == l));
|
|
if (!bp.condition) {
|
|
if (!bp.condition) {
|
|
this.debug_data.set_breakpoint(path, l);
|
|
this.debug_data.set_breakpoint(path, l);
|
|
}
|
|
}
|
|
@@ -339,7 +254,7 @@ export class GodotDebugSession extends LoggingDebugSession {
|
|
column: 1,
|
|
column: 1,
|
|
source: new Source(
|
|
source: new Source(
|
|
sf.file,
|
|
sf.file,
|
|
- `${this.debug_data.project_path}/${sf.file.replace("res://", "")}`
|
|
|
|
|
|
+ `${this.debug_data.projectPath}/${sf.file.replace("res://", "")}`
|
|
),
|
|
),
|
|
};
|
|
};
|
|
}),
|
|
}),
|
|
@@ -353,7 +268,7 @@ export class GodotDebugSession extends LoggingDebugSession {
|
|
args: DebugProtocol.StepInArguments
|
|
args: DebugProtocol.StepInArguments
|
|
) {
|
|
) {
|
|
if (!this.exception) {
|
|
if (!this.exception) {
|
|
- Mediator.notify("step");
|
|
|
|
|
|
+ this.controller.step();
|
|
this.sendResponse(response);
|
|
this.sendResponse(response);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -363,7 +278,7 @@ export class GodotDebugSession extends LoggingDebugSession {
|
|
args: DebugProtocol.StepOutArguments
|
|
args: DebugProtocol.StepOutArguments
|
|
) {
|
|
) {
|
|
if (!this.exception) {
|
|
if (!this.exception) {
|
|
- Mediator.notify("step_out");
|
|
|
|
|
|
+ this.controller.step_out();
|
|
this.sendResponse(response);
|
|
this.sendResponse(response);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -372,7 +287,10 @@ export class GodotDebugSession extends LoggingDebugSession {
|
|
response: DebugProtocol.TerminateResponse,
|
|
response: DebugProtocol.TerminateResponse,
|
|
args: DebugProtocol.TerminateArguments
|
|
args: DebugProtocol.TerminateArguments
|
|
) {
|
|
) {
|
|
- Mediator.notify("stop");
|
|
|
|
|
|
+ if (this.mode === "launch") {
|
|
|
|
+ this.controller.stop();
|
|
|
|
+ this.sendEvent(new TerminatedEvent());
|
|
|
|
+ }
|
|
this.sendResponse(response);
|
|
this.sendResponse(response);
|
|
}
|
|
}
|
|
|
|
|
|
@@ -385,25 +303,32 @@ export class GodotDebugSession extends LoggingDebugSession {
|
|
response: DebugProtocol.VariablesResponse,
|
|
response: DebugProtocol.VariablesResponse,
|
|
args: DebugProtocol.VariablesArguments
|
|
args: DebugProtocol.VariablesArguments
|
|
) {
|
|
) {
|
|
- let reference = this.all_scopes[args.variablesReference];
|
|
|
|
|
|
+ if (!this.all_scopes) {
|
|
|
|
+ response.body = {
|
|
|
|
+ variables: []
|
|
|
|
+ };
|
|
|
|
+ this.sendResponse(response);
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const reference = this.all_scopes[args.variablesReference];
|
|
let variables: DebugProtocol.Variable[];
|
|
let variables: DebugProtocol.Variable[];
|
|
|
|
|
|
if (!reference.sub_values) {
|
|
if (!reference.sub_values) {
|
|
variables = [];
|
|
variables = [];
|
|
} else {
|
|
} else {
|
|
variables = reference.sub_values.map((va) => {
|
|
variables = reference.sub_values.map((va) => {
|
|
- let sva = this.all_scopes.find(
|
|
|
|
|
|
+ const sva = this.all_scopes.find(
|
|
(sva) =>
|
|
(sva) =>
|
|
sva && sva.scope_path === va.scope_path && sva.name === va.name
|
|
sva && sva.scope_path === va.scope_path && sva.name === va.name
|
|
);
|
|
);
|
|
if (sva) {
|
|
if (sva) {
|
|
- return this.parse_variable(
|
|
|
|
|
|
+ return parse_variable(
|
|
sva,
|
|
sva,
|
|
this.all_scopes.findIndex(
|
|
this.all_scopes.findIndex(
|
|
(va_idx) =>
|
|
(va_idx) =>
|
|
va_idx &&
|
|
va_idx &&
|
|
- va_idx.scope_path ===
|
|
|
|
- `${reference.scope_path}.${reference.name}` &&
|
|
|
|
|
|
+ va_idx.scope_path === `${reference.scope_path}.${reference.name}` &&
|
|
va_idx.name === va.name
|
|
va_idx.name === va.name
|
|
)
|
|
)
|
|
);
|
|
);
|
|
@@ -418,83 +343,208 @@ export class GodotDebugSession extends LoggingDebugSession {
|
|
this.sendResponse(response);
|
|
this.sendResponse(response);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ public set_exception(exception: boolean) {
|
|
|
|
+ this.exception = true;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public set_scopes(stackVars: GodotStackVars) {
|
|
|
|
+ this.all_scopes = [
|
|
|
|
+ undefined,
|
|
|
|
+ {
|
|
|
|
+ name: "local",
|
|
|
|
+ value: undefined,
|
|
|
|
+ sub_values: stackVars.locals,
|
|
|
|
+ scope_path: "@"
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ name: "member",
|
|
|
|
+ value: undefined,
|
|
|
|
+ sub_values: stackVars.members,
|
|
|
|
+ scope_path: "@",
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ name: "global",
|
|
|
|
+ value: undefined,
|
|
|
|
+ sub_values: stackVars.globals,
|
|
|
|
+ scope_path: "@",
|
|
|
|
+ },
|
|
|
|
+ ];
|
|
|
|
+
|
|
|
|
+ stackVars.locals.forEach((va) => {
|
|
|
|
+ va.scope_path = "@.local";
|
|
|
|
+ this.append_variable(va);
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ stackVars.members.forEach((va) => {
|
|
|
|
+ va.scope_path = "@.member";
|
|
|
|
+ this.append_variable(va);
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ stackVars.globals.forEach((va) => {
|
|
|
|
+ va.scope_path = "@.global";
|
|
|
|
+ this.append_variable(va);
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ this.add_to_inspections();
|
|
|
|
+
|
|
|
|
+ if (this.ongoing_inspections.length === 0) {
|
|
|
|
+ this.previous_inspections = [];
|
|
|
|
+ this.got_scope.notify();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public set_inspection(id: bigint, replacement: GodotVariable) {
|
|
|
|
+ const variables = this.all_scopes.filter(
|
|
|
|
+ (va) => va && va.value instanceof ObjectId && va.value.id === id
|
|
|
|
+ );
|
|
|
|
+
|
|
|
|
+ variables.forEach((va) => {
|
|
|
|
+ const index = this.all_scopes.findIndex((va_id) => va_id === va);
|
|
|
|
+ const old = this.all_scopes.splice(index, 1);
|
|
|
|
+ replacement.name = old[0].name;
|
|
|
|
+ replacement.scope_path = old[0].scope_path;
|
|
|
|
+ this.append_variable(replacement, index);
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ this.ongoing_inspections.splice(
|
|
|
|
+ this.ongoing_inspections.findIndex((va_id) => va_id === id),
|
|
|
|
+ 1
|
|
|
|
+ );
|
|
|
|
+
|
|
|
|
+ this.previous_inspections.push(id);
|
|
|
|
+
|
|
|
|
+ // this.add_to_inspections();
|
|
|
|
+
|
|
|
|
+ if (this.ongoing_inspections.length === 0) {
|
|
|
|
+ this.previous_inspections = [];
|
|
|
|
+ this.got_scope.notify();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
private add_to_inspections() {
|
|
private add_to_inspections() {
|
|
this.all_scopes.forEach((va) => {
|
|
this.all_scopes.forEach((va) => {
|
|
if (va && va.value instanceof ObjectId) {
|
|
if (va && va.value instanceof ObjectId) {
|
|
if (
|
|
if (
|
|
- !this.ongoing_inspections.find((va_id) => va_id === va.value.id) &&
|
|
|
|
- !this.previous_inspections.find((va_id) => va_id === va.value.id)
|
|
|
|
|
|
+ !this.ongoing_inspections.includes(va.value.id) &&
|
|
|
|
+ !this.previous_inspections.includes(va.value.id)
|
|
) {
|
|
) {
|
|
- Mediator.notify("inspect_object", [va.value.id]);
|
|
|
|
|
|
+ this.controller.request_inspect_object(va.value.id);
|
|
this.ongoing_inspections.push(va.value.id);
|
|
this.ongoing_inspections.push(va.value.id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ protected get_variable(expression: string, root: GodotVariable = null, index: number = 0, object_id: number = null): { variable: GodotVariable, index: number, object_id: number, error: string } {
|
|
|
|
+ var result: { variable: GodotVariable, index: number, object_id: number, error: string } = { variable: null, index: null, object_id: null, error: null };
|
|
|
|
+
|
|
|
|
+ if (!root) {
|
|
|
|
+ if (!expression.includes("self")) {
|
|
|
|
+ expression = "self." + expression;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ root = this.all_scopes.find(x => x && x.name == "self");
|
|
|
|
+ object_id = this.all_scopes.find(x => x && x.name == "id" && x.scope_path == "@.member.self").value;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ var items = expression.split(".");
|
|
|
|
+ var propertyName = items[index + 1];
|
|
|
|
+ var path = items.slice(0, index + 1).join(".")
|
|
|
|
+ .split("self.").join("")
|
|
|
|
+ .split("self").join("")
|
|
|
|
+ .split("[").join(".")
|
|
|
|
+ .split("]").join("");
|
|
|
|
+
|
|
|
|
+ if (items.length == 1 && items[0] == "self") {
|
|
|
|
+ propertyName = "self";
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Detect index/key
|
|
|
|
+ var key = (propertyName.match(/(?<=\[).*(?=\])/) || [null])[0];
|
|
|
|
+ if (key) {
|
|
|
|
+ key = key.replace(/['"]+/g, "");
|
|
|
|
+ propertyName = propertyName.split(/(?<=\[).*(?=\])/).join("").split("\[\]").join("");
|
|
|
|
+ if (path) path += ".";
|
|
|
|
+ path += propertyName;
|
|
|
|
+ propertyName = key;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ function sanitizeName(name: string) {
|
|
|
|
+ return name.split("Members/").join("").split("Locals/").join("");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ function sanitizeScopePath(scope_path: string) {
|
|
|
|
+ return scope_path.split("@.member.self.").join("")
|
|
|
|
+ .split("@.member.self").join("")
|
|
|
|
+ .split("@.member.").join("")
|
|
|
|
+ .split("@.member").join("")
|
|
|
|
+ .split("@.local.").join("")
|
|
|
|
+ .split("@.local").join("")
|
|
|
|
+ .split("Locals/").join("")
|
|
|
|
+ .split("Members/").join("")
|
|
|
|
+ .split("@").join("");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ var sanitized_all_scopes = this.all_scopes.filter(x => x).map(function (x) {
|
|
|
|
+ return {
|
|
|
|
+ sanitized: {
|
|
|
|
+ name: sanitizeName(x.name),
|
|
|
|
+ scope_path: sanitizeScopePath(x.scope_path)
|
|
|
|
+ },
|
|
|
|
+ real: x
|
|
|
|
+ };
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ result.variable = sanitized_all_scopes
|
|
|
|
+ .find(x => x.sanitized.name == propertyName && x.sanitized.scope_path == path)
|
|
|
|
+ ?.real;
|
|
|
|
+ if (!result.variable) {
|
|
|
|
+ result.error = `Could not find: ${propertyName}`;
|
|
|
|
+ return result;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (root.value.entries) {
|
|
|
|
+ if (result.variable.name == "self") {
|
|
|
|
+ result.object_id = this.all_scopes
|
|
|
|
+ .find(x => x && x.name == "id" && x.scope_path == "@.member.self").value;
|
|
|
|
+ } else if (key) {
|
|
|
|
+ var collection = path.split(".")[path.split(".").length - 1];
|
|
|
|
+ var collection_items = Array.from(root.value.entries())
|
|
|
|
+ .find(x => x && x[0].split("Members/").join("").split("Locals/").join("") == collection)[1];
|
|
|
|
+ result.object_id = collection_items.get
|
|
|
|
+ ? collection_items.get(key)?.id
|
|
|
|
+ : collection_items[key]?.id;
|
|
|
|
+ } else {
|
|
|
|
+ result.object_id = Array.from(root.value.entries())
|
|
|
|
+ .find(x => x && x[0].split("Members/").join("").split("Locals/").join("") == propertyName)[1].id;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (!result.object_id) {
|
|
|
|
+ result.object_id = object_id;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ result.index = this.all_scopes.findIndex(x => x && x.name == result.variable.name && x.scope_path == result.variable.scope_path);
|
|
|
|
+
|
|
|
|
+ if (items.length > 2 && index < items.length - 2) {
|
|
|
|
+ result = this.get_variable(items.join("."), result.variable, index + 1, result.object_id);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return result;
|
|
|
|
+ }
|
|
|
|
+
|
|
private append_variable(variable: GodotVariable, index?: number) {
|
|
private append_variable(variable: GodotVariable, index?: number) {
|
|
if (index) {
|
|
if (index) {
|
|
this.all_scopes.splice(index, 0, variable);
|
|
this.all_scopes.splice(index, 0, variable);
|
|
} else {
|
|
} else {
|
|
this.all_scopes.push(variable);
|
|
this.all_scopes.push(variable);
|
|
}
|
|
}
|
|
- let base_path = `${variable.scope_path}.${variable.name}`;
|
|
|
|
|
|
+ const base_path = `${variable.scope_path}.${variable.name}`;
|
|
if (variable.sub_values) {
|
|
if (variable.sub_values) {
|
|
variable.sub_values.forEach((va, i) => {
|
|
variable.sub_values.forEach((va, i) => {
|
|
- va.scope_path = `${base_path}`;
|
|
|
|
|
|
+ va.scope_path = base_path;
|
|
this.append_variable(va, index ? index + i + 1 : undefined);
|
|
this.append_variable(va, index ? index + i + 1 : undefined);
|
|
});
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
-
|
|
|
|
- private parse_variable(va: GodotVariable, i?: number) {
|
|
|
|
- let value = va.value;
|
|
|
|
- let rendered_value = "";
|
|
|
|
- let reference = 0;
|
|
|
|
- let array_size = 0;
|
|
|
|
- let array_type = undefined;
|
|
|
|
-
|
|
|
|
- if (typeof value === "number") {
|
|
|
|
- if (Number.isInteger(value)) {
|
|
|
|
- rendered_value = `${value}`;
|
|
|
|
- } else {
|
|
|
|
- rendered_value = `${parseFloat(value.toFixed(5))}`;
|
|
|
|
- }
|
|
|
|
- } else if (
|
|
|
|
- typeof value === "bigint" ||
|
|
|
|
- typeof value === "boolean" ||
|
|
|
|
- typeof value === "string"
|
|
|
|
- ) {
|
|
|
|
- rendered_value = `${value}`;
|
|
|
|
- } else if (typeof value === "undefined") {
|
|
|
|
- rendered_value = "null";
|
|
|
|
- } else {
|
|
|
|
- if (Array.isArray(value)) {
|
|
|
|
- rendered_value = `Array[${value.length}]`;
|
|
|
|
- array_size = value.length;
|
|
|
|
- array_type = "indexed";
|
|
|
|
- reference = i ? i : 0;
|
|
|
|
- } else if (value instanceof Map) {
|
|
|
|
- if (value instanceof RawObject) {
|
|
|
|
- rendered_value = `${value.class_name}`;
|
|
|
|
- } else {
|
|
|
|
- rendered_value = `Dictionary[${value.size}]`;
|
|
|
|
- }
|
|
|
|
- array_size = value.size;
|
|
|
|
- array_type = "named";
|
|
|
|
- reference = i ? i : 0;
|
|
|
|
- } else {
|
|
|
|
- rendered_value = `${value.type_name()}${value.stringify_value()}`;
|
|
|
|
- reference = i ? i : 0;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- return {
|
|
|
|
- name: va.name,
|
|
|
|
- value: rendered_value,
|
|
|
|
- variablesReference: reference,
|
|
|
|
- array_size: array_size > 0 ? array_size : undefined,
|
|
|
|
- filter: array_type,
|
|
|
|
- };
|
|
|
|
- }
|
|
|
|
}
|
|
}
|