mirror of
https://github.com/godotengine/godot-vscode-plugin.git
synced 2025-12-31 13:48:24 +03:00
Implement warnings and errors in Debug Console (#749)
This commit is contained in:
8
.vscode/launch.json
vendored
8
.vscode/launch.json
vendored
@@ -16,6 +16,10 @@
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/out/**/*.js"
|
||||
],
|
||||
"skipFiles": [
|
||||
"**/extensionHostProcess.js",
|
||||
"<node_internals>/**/*.js"
|
||||
],
|
||||
"preLaunchTask": "npm: watch",
|
||||
"env": {
|
||||
"VSCODE_DEBUG_MODE": true
|
||||
@@ -34,6 +38,10 @@
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/out/**/*.js"
|
||||
],
|
||||
"skipFiles": [
|
||||
"**/extensionHostProcess.js",
|
||||
"<node_internals>/**/*.js"
|
||||
],
|
||||
"preLaunchTask": "npm: watch",
|
||||
"env": {
|
||||
"VSCODE_DEBUG_MODE": true
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
"linter": {
|
||||
"rules": {
|
||||
"style": {
|
||||
"noUselessElse": "off"
|
||||
"noUselessElse": "off",
|
||||
"useImportType": "off"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { SceneTreeProvider } from "./scene_tree_provider";
|
||||
import path = require("path");
|
||||
import * as path from "node:path";
|
||||
|
||||
import { createLogger } from "../utils";
|
||||
import { SceneTreeProvider } from "./scene_tree_provider";
|
||||
|
||||
const log = createLogger("debugger.runtime");
|
||||
|
||||
@@ -24,9 +25,9 @@ export class GodotStackVars {
|
||||
public locals: GodotVariable[] = [],
|
||||
public members: GodotVariable[] = [],
|
||||
public globals: GodotVariable[] = [],
|
||||
) { }
|
||||
) {}
|
||||
|
||||
public reset(count: number = 0) {
|
||||
public reset(count = 0) {
|
||||
this.locals = [];
|
||||
this.members = [];
|
||||
this.globals = [];
|
||||
@@ -62,7 +63,7 @@ export class RawObject extends Map<any, any> {
|
||||
}
|
||||
|
||||
export class ObjectId implements GDObject {
|
||||
constructor(public id: bigint) { }
|
||||
constructor(public id: bigint) {}
|
||||
|
||||
public stringify_value(): string {
|
||||
return `<${this.id}>`;
|
||||
@@ -85,7 +86,7 @@ export class GodotDebugData {
|
||||
public last_frames: GodotStackFrame[] = [];
|
||||
public projectPath: string;
|
||||
public scene_tree?: SceneTreeProvider;
|
||||
public stack_count: number = 0;
|
||||
public stack_count = 0;
|
||||
public stack_files: string[] = [];
|
||||
public session;
|
||||
|
||||
@@ -126,19 +127,16 @@ export class GodotDebugData {
|
||||
bps.splice(index, 1);
|
||||
this.breakpoints.set(pathTo, bps);
|
||||
const file = `res://${path.relative(this.projectPath, bp.file)}`;
|
||||
this.session?.controller.remove_breakpoint(
|
||||
file.replace(/\\/g, "/"),
|
||||
bp.line,
|
||||
);
|
||||
this.session?.controller.remove_breakpoint(file.replace(/\\/g, "/"), bp.line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public get_all_breakpoints(): GodotBreakpoint[] {
|
||||
const output: GodotBreakpoint[] = [];
|
||||
Array.from(this.breakpoints.values()).forEach((bp_array) => {
|
||||
for (const bp_array of Array.from(this.breakpoints.values())) {
|
||||
output.push(...bp_array);
|
||||
});
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
@@ -150,14 +148,14 @@ export class GodotDebugData {
|
||||
const breakpoints = this.get_all_breakpoints();
|
||||
let output = "";
|
||||
if (breakpoints.length > 0) {
|
||||
output += " --breakpoints \"";
|
||||
output += ' --breakpoints "';
|
||||
breakpoints.forEach((bp, i) => {
|
||||
output += `${this.get_breakpoint_path(bp.file)}:${bp.line}`;
|
||||
if (i < breakpoints.length - 1) {
|
||||
output += ",";
|
||||
}
|
||||
});
|
||||
output += "\"";
|
||||
output += '"';
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
@@ -309,13 +309,13 @@ export class GodotDebugger implements DebugAdapterDescriptorFactory, DebugConfig
|
||||
break;
|
||||
case "number":
|
||||
if (is_float) {
|
||||
new_parsed_value = parseFloat(value);
|
||||
if (isNaN(new_parsed_value)) {
|
||||
new_parsed_value = Number.parseFloat(value);
|
||||
if (Number.isNaN(new_parsed_value)) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
new_parsed_value = parseInt(value);
|
||||
if (isNaN(new_parsed_value)) {
|
||||
new_parsed_value = Number.parseInt(value);
|
||||
if (Number.isNaN(new_parsed_value)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,33 @@
|
||||
import * as fs from "fs";
|
||||
import {
|
||||
LoggingDebugSession,
|
||||
InitializedEvent,
|
||||
Thread,
|
||||
Source,
|
||||
Breakpoint,
|
||||
StoppedEvent,
|
||||
InitializedEvent,
|
||||
LoggingDebugSession,
|
||||
Source,
|
||||
TerminatedEvent,
|
||||
Thread,
|
||||
} 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 * as fs from "node:fs";
|
||||
import { debug } from "vscode";
|
||||
|
||||
import { createLogger } from "../../utils";
|
||||
import { GodotDebugData, GodotStackVars, GodotVariable } from "../debug_runtime";
|
||||
import { AttachRequestArguments, LaunchRequestArguments } from "../debugger";
|
||||
import { SceneTreeProvider } from "../scene_tree_provider";
|
||||
import { is_variable_built_in_type, parse_variable } from "./helpers";
|
||||
import { ServerController } from "./server_controller";
|
||||
import { ObjectId } from "./variables/variants";
|
||||
|
||||
const log = createLogger("debugger.session", { output: "Godot Debugger" });
|
||||
|
||||
interface Variable {
|
||||
variable: GodotVariable;
|
||||
index: number;
|
||||
object_id: number;
|
||||
error: string;
|
||||
}
|
||||
|
||||
export class GodotDebugSession extends LoggingDebugSession {
|
||||
private all_scopes: GodotVariable[];
|
||||
public controller = new ServerController(this);
|
||||
@@ -32,10 +39,7 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
private previous_inspections: bigint[] = [];
|
||||
private configuration_done: Subject = new Subject();
|
||||
private mode: "launch" | "attach" | "" = "";
|
||||
public inspect_callbacks: Map<
|
||||
bigint,
|
||||
(class_name: string, variable: GodotVariable) => void
|
||||
> = new Map();
|
||||
public inspect_callbacks: Map<bigint, (class_name: string, variable: GodotVariable) => void> = new Map();
|
||||
|
||||
public constructor() {
|
||||
super();
|
||||
@@ -50,7 +54,7 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
|
||||
protected initializeRequest(
|
||||
response: DebugProtocol.InitializeResponse,
|
||||
args: DebugProtocol.InitializeRequestArguments
|
||||
args: DebugProtocol.InitializeRequestArguments,
|
||||
) {
|
||||
response.body = response.body || {};
|
||||
|
||||
@@ -79,10 +83,7 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
this.sendEvent(new InitializedEvent());
|
||||
}
|
||||
|
||||
protected async launchRequest(
|
||||
response: DebugProtocol.LaunchResponse,
|
||||
args: LaunchRequestArguments
|
||||
) {
|
||||
protected async launchRequest(response: DebugProtocol.LaunchResponse, args: LaunchRequestArguments) {
|
||||
await this.configuration_done.wait(1000);
|
||||
|
||||
this.mode = "launch";
|
||||
@@ -94,10 +95,7 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
this.sendResponse(response);
|
||||
}
|
||||
|
||||
protected async attachRequest(
|
||||
response: DebugProtocol.AttachResponse,
|
||||
args: AttachRequestArguments
|
||||
) {
|
||||
protected async attachRequest(response: DebugProtocol.AttachResponse, args: AttachRequestArguments) {
|
||||
await this.configuration_done.wait(1000);
|
||||
|
||||
this.mode = "attach";
|
||||
@@ -110,16 +108,13 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
|
||||
public configurationDoneRequest(
|
||||
response: DebugProtocol.ConfigurationDoneResponse,
|
||||
args: DebugProtocol.ConfigurationDoneArguments
|
||||
args: DebugProtocol.ConfigurationDoneArguments,
|
||||
) {
|
||||
this.configuration_done.notify();
|
||||
this.sendResponse(response);
|
||||
}
|
||||
|
||||
protected continueRequest(
|
||||
response: DebugProtocol.ContinueResponse,
|
||||
args: DebugProtocol.ContinueArguments
|
||||
) {
|
||||
protected continueRequest(response: DebugProtocol.ContinueResponse, args: DebugProtocol.ContinueArguments) {
|
||||
if (!this.exception) {
|
||||
response.body = { allThreadsContinued: true };
|
||||
this.controller.continue();
|
||||
@@ -127,20 +122,17 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
}
|
||||
}
|
||||
|
||||
protected async evaluateRequest(
|
||||
response: DebugProtocol.EvaluateResponse,
|
||||
args: DebugProtocol.EvaluateArguments
|
||||
) {
|
||||
protected async evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments) {
|
||||
await debug.activeDebugSession.customRequest("scopes", { frameId: 0 });
|
||||
|
||||
if (this.all_scopes) {
|
||||
var variable = this.get_variable(args.expression, null, null, null);
|
||||
const variable = this.get_variable(args.expression, null, null, null);
|
||||
|
||||
if (variable.error == null) {
|
||||
var parsed_variable = parse_variable(variable.variable);
|
||||
const parsed_variable = parse_variable(variable.variable);
|
||||
response.body = {
|
||||
result: parsed_variable.value,
|
||||
variablesReference: !is_variable_built_in_type(variable.variable) ? variable.index : 0
|
||||
variablesReference: !is_variable_built_in_type(variable.variable) ? variable.index : 0,
|
||||
};
|
||||
} else {
|
||||
response.success = false;
|
||||
@@ -158,30 +150,21 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
this.sendResponse(response);
|
||||
}
|
||||
|
||||
protected nextRequest(
|
||||
response: DebugProtocol.NextResponse,
|
||||
args: DebugProtocol.NextArguments
|
||||
) {
|
||||
protected nextRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments) {
|
||||
if (!this.exception) {
|
||||
this.controller.next();
|
||||
this.sendResponse(response);
|
||||
}
|
||||
}
|
||||
|
||||
protected pauseRequest(
|
||||
response: DebugProtocol.PauseResponse,
|
||||
args: DebugProtocol.PauseArguments
|
||||
) {
|
||||
protected pauseRequest(response: DebugProtocol.PauseResponse, args: DebugProtocol.PauseArguments) {
|
||||
if (!this.exception) {
|
||||
this.controller.break();
|
||||
this.sendResponse(response);
|
||||
}
|
||||
}
|
||||
|
||||
protected async scopesRequest(
|
||||
response: DebugProtocol.ScopesResponse,
|
||||
args: DebugProtocol.ScopesArguments
|
||||
) {
|
||||
protected async scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments) {
|
||||
this.controller.request_stack_frame_vars(args.frameId);
|
||||
await this.got_scope.wait(2000);
|
||||
|
||||
@@ -197,7 +180,7 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
|
||||
protected setBreakPointsRequest(
|
||||
response: DebugProtocol.SetBreakpointsResponse,
|
||||
args: DebugProtocol.SetBreakpointsArguments
|
||||
args: DebugProtocol.SetBreakpointsArguments,
|
||||
) {
|
||||
const path = (args.source.path as string).replace(/\\/g, "/");
|
||||
const client_lines = args.lines || [];
|
||||
@@ -206,19 +189,19 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
let bps = this.debug_data.get_breakpoints(path);
|
||||
const bp_lines = bps.map((bp) => bp.line);
|
||||
|
||||
bps.forEach((bp) => {
|
||||
for (const bp of bps) {
|
||||
if (client_lines.indexOf(bp.line) === -1) {
|
||||
this.debug_data.remove_breakpoint(path, bp.line);
|
||||
}
|
||||
});
|
||||
client_lines.forEach((l) => {
|
||||
}
|
||||
for (const l of client_lines) {
|
||||
if (bp_lines.indexOf(l) === -1) {
|
||||
const 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) {
|
||||
this.debug_data.set_breakpoint(path, l);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bps = this.debug_data.get_breakpoints(path);
|
||||
// Sort to ensure breakpoints aren't out-of-order, which would confuse VS Code.
|
||||
@@ -226,12 +209,7 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
|
||||
response.body = {
|
||||
breakpoints: bps.map((bp) => {
|
||||
return new Breakpoint(
|
||||
true,
|
||||
bp.line,
|
||||
1,
|
||||
new Source(bp.file.split("/").reverse()[0], bp.file)
|
||||
);
|
||||
return new Breakpoint(true, bp.line, 1, new Source(bp.file.split("/").reverse()[0], bp.file));
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -239,10 +217,7 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
}
|
||||
}
|
||||
|
||||
protected stackTraceRequest(
|
||||
response: DebugProtocol.StackTraceResponse,
|
||||
args: DebugProtocol.StackTraceArguments
|
||||
) {
|
||||
protected stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments) {
|
||||
if (this.debug_data.last_frame) {
|
||||
response.body = {
|
||||
totalFrames: this.debug_data.last_frames.length,
|
||||
@@ -252,10 +227,7 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
name: sf.function,
|
||||
line: sf.line,
|
||||
column: 1,
|
||||
source: new Source(
|
||||
sf.file,
|
||||
`${this.debug_data.projectPath}/${sf.file.replace("res://", "")}`
|
||||
),
|
||||
source: new Source(sf.file, `${this.debug_data.projectPath}/${sf.file.replace("res://", "")}`),
|
||||
};
|
||||
}),
|
||||
};
|
||||
@@ -263,30 +235,21 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
this.sendResponse(response);
|
||||
}
|
||||
|
||||
protected stepInRequest(
|
||||
response: DebugProtocol.StepInResponse,
|
||||
args: DebugProtocol.StepInArguments
|
||||
) {
|
||||
protected stepInRequest(response: DebugProtocol.StepInResponse, args: DebugProtocol.StepInArguments) {
|
||||
if (!this.exception) {
|
||||
this.controller.step();
|
||||
this.sendResponse(response);
|
||||
}
|
||||
}
|
||||
|
||||
protected stepOutRequest(
|
||||
response: DebugProtocol.StepOutResponse,
|
||||
args: DebugProtocol.StepOutArguments
|
||||
) {
|
||||
protected stepOutRequest(response: DebugProtocol.StepOutResponse, args: DebugProtocol.StepOutArguments) {
|
||||
if (!this.exception) {
|
||||
this.controller.step_out();
|
||||
this.sendResponse(response);
|
||||
}
|
||||
}
|
||||
|
||||
protected terminateRequest(
|
||||
response: DebugProtocol.TerminateResponse,
|
||||
args: DebugProtocol.TerminateArguments
|
||||
) {
|
||||
protected terminateRequest(response: DebugProtocol.TerminateResponse, args: DebugProtocol.TerminateArguments) {
|
||||
if (this.mode === "launch") {
|
||||
this.controller.stop();
|
||||
this.sendEvent(new TerminatedEvent());
|
||||
@@ -301,11 +264,11 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
|
||||
protected async variablesRequest(
|
||||
response: DebugProtocol.VariablesResponse,
|
||||
args: DebugProtocol.VariablesArguments
|
||||
args: DebugProtocol.VariablesArguments,
|
||||
) {
|
||||
if (!this.all_scopes) {
|
||||
response.body = {
|
||||
variables: []
|
||||
variables: [],
|
||||
};
|
||||
this.sendResponse(response);
|
||||
return;
|
||||
@@ -319,8 +282,7 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
} else {
|
||||
variables = reference.sub_values.map((va) => {
|
||||
const sva = this.all_scopes.find(
|
||||
(sva) =>
|
||||
sva && sva.scope_path === va.scope_path && sva.name === va.name
|
||||
(sva) => sva && sva.scope_path === va.scope_path && sva.name === va.name,
|
||||
);
|
||||
if (sva) {
|
||||
return parse_variable(
|
||||
@@ -329,8 +291,8 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
(va_idx) =>
|
||||
va_idx &&
|
||||
va_idx.scope_path === `${reference.scope_path}.${reference.name}` &&
|
||||
va_idx.name === va.name
|
||||
)
|
||||
va_idx.name === va.name,
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -354,7 +316,7 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
name: "local",
|
||||
value: undefined,
|
||||
sub_values: stackVars.locals,
|
||||
scope_path: "@"
|
||||
scope_path: "@",
|
||||
},
|
||||
{
|
||||
name: "member",
|
||||
@@ -370,20 +332,20 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
},
|
||||
];
|
||||
|
||||
stackVars.locals.forEach((va) => {
|
||||
for (const va of stackVars.locals) {
|
||||
va.scope_path = "@.local";
|
||||
this.append_variable(va);
|
||||
});
|
||||
}
|
||||
|
||||
stackVars.members.forEach((va) => {
|
||||
for (const va of stackVars.members) {
|
||||
va.scope_path = "@.member";
|
||||
this.append_variable(va);
|
||||
});
|
||||
}
|
||||
|
||||
stackVars.globals.forEach((va) => {
|
||||
for (const va of stackVars.globals) {
|
||||
va.scope_path = "@.global";
|
||||
this.append_variable(va);
|
||||
});
|
||||
}
|
||||
|
||||
this.add_to_inspections();
|
||||
|
||||
@@ -394,21 +356,19 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
}
|
||||
|
||||
public set_inspection(id: bigint, replacement: GodotVariable) {
|
||||
const variables = this.all_scopes.filter(
|
||||
(va) => va && va.value instanceof ObjectId && va.value.id === id
|
||||
);
|
||||
const variables = this.all_scopes.filter((va) => va && va.value instanceof ObjectId && va.value.id === id);
|
||||
|
||||
variables.forEach((va) => {
|
||||
for (const va of variables) {
|
||||
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
|
||||
1,
|
||||
);
|
||||
|
||||
this.previous_inspections.push(id);
|
||||
@@ -422,7 +382,7 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
}
|
||||
|
||||
private add_to_inspections() {
|
||||
this.all_scopes.forEach((va) => {
|
||||
for (const va of this.all_scopes) {
|
||||
if (va && va.value instanceof ObjectId) {
|
||||
if (
|
||||
!this.ongoing_inspections.includes(va.value.id) &&
|
||||
@@ -432,38 +392,58 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
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 };
|
||||
protected get_variable(
|
||||
expression: string,
|
||||
root: GodotVariable = null,
|
||||
index = 0,
|
||||
object_id: number = null,
|
||||
): Variable {
|
||||
let result: Variable = {
|
||||
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;
|
||||
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("");
|
||||
const items = expression.split(".");
|
||||
let propertyName = items[index + 1];
|
||||
let path = items
|
||||
.slice(0, index + 1)
|
||||
.join(".")
|
||||
.split("self.")
|
||||
.join("")
|
||||
.split("self")
|
||||
.join("")
|
||||
.split("[")
|
||||
.join(".")
|
||||
.split("]")
|
||||
.join("");
|
||||
|
||||
if (items.length == 1 && items[0] == "self") {
|
||||
if (items.length === 1 && items[0] === "self") {
|
||||
propertyName = "self";
|
||||
}
|
||||
|
||||
// Detect index/key
|
||||
var key = (propertyName.match(/(?<=\[).*(?=\])/) || [null])[0];
|
||||
let key = (propertyName.match(/(?<=\[).*(?=\])/) || [null])[0];
|
||||
if (key) {
|
||||
key = key.replace(/['"]+/g, "");
|
||||
propertyName = propertyName.split(/(?<=\[).*(?=\])/).join("").split("\[\]").join("");
|
||||
propertyName = propertyName
|
||||
.split(/(?<=\[).*(?=\])/)
|
||||
.join("")
|
||||
.split("[]")
|
||||
.join("");
|
||||
if (path) path += ".";
|
||||
path += propertyName;
|
||||
propertyName = key;
|
||||
@@ -474,49 +454,61 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
}
|
||||
|
||||
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("");
|
||||
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 {
|
||||
const sanitized_all_scopes = this.all_scopes
|
||||
.filter((x) => x)
|
||||
.map((x) => ({
|
||||
sanitized: {
|
||||
name: sanitizeName(x.name),
|
||||
scope_path: sanitizeScopePath(x.scope_path)
|
||||
scope_path: sanitizeScopePath(x.scope_path),
|
||||
},
|
||||
real: x
|
||||
};
|
||||
});
|
||||
real: x,
|
||||
}));
|
||||
|
||||
result.variable = sanitized_all_scopes
|
||||
.find(x => x.sanitized.name == propertyName && x.sanitized.scope_path == path)
|
||||
?.real;
|
||||
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;
|
||||
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;
|
||||
const collection = path.split(".")[path.split(".").length - 1];
|
||||
const 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;
|
||||
const item = Array.from(root.value.entries()).find(
|
||||
(x) => x && x[0].split("Members/").join("").split("Locals/").join("") === propertyName,
|
||||
);
|
||||
result.object_id = item?.[1].id;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -524,7 +516,9 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
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);
|
||||
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);
|
||||
|
||||
@@ -1,17 +1,28 @@
|
||||
import * as fs from "fs";
|
||||
import net = require("net");
|
||||
import { debug, window } from "vscode";
|
||||
import { StoppedEvent, TerminatedEvent } from "@vscode/debugadapter";
|
||||
import { VariantEncoder } from "./variables/variant_encoder";
|
||||
import { VariantDecoder } from "./variables/variant_decoder";
|
||||
import { RawObject } from "./variables/variants";
|
||||
import { GodotStackFrame, GodotStackVars } from "../debug_runtime";
|
||||
import { GodotDebugSession } from "./debug_session";
|
||||
import { parse_next_scene_node, split_buffers, build_sub_values } from "./helpers";
|
||||
import { get_configuration, get_free_port, createLogger, verify_godot_version, get_project_version } from "../../utils";
|
||||
import { DebugProtocol } from "@vscode/debugprotocol";
|
||||
import * as fs from "node:fs";
|
||||
import * as net from "node:net";
|
||||
import { debug, window } from "vscode";
|
||||
|
||||
import {
|
||||
ansi,
|
||||
convert_resource_path_to_uri,
|
||||
createLogger,
|
||||
get_configuration,
|
||||
get_free_port,
|
||||
get_project_version,
|
||||
verify_godot_version,
|
||||
VERIFY_RESULT,
|
||||
} from "../../utils";
|
||||
import { prompt_for_godot_executable } from "../../utils/prompts";
|
||||
import { subProcess, killSubProcesses } from "../../utils/subspawn";
|
||||
import { LaunchRequestArguments, AttachRequestArguments, pinnedScene } from "../debugger";
|
||||
import { killSubProcesses, subProcess } from "../../utils/subspawn";
|
||||
import { GodotStackFrame, GodotStackVars } from "../debug_runtime";
|
||||
import { AttachRequestArguments, LaunchRequestArguments, pinnedScene } from "../debugger";
|
||||
import { GodotDebugSession } from "./debug_session";
|
||||
import { build_sub_values, parse_next_scene_node, split_buffers } from "./helpers";
|
||||
import { VariantDecoder } from "./variables/variant_decoder";
|
||||
import { VariantEncoder } from "./variables/variant_encoder";
|
||||
import { RawObject } from "./variables/variants";
|
||||
|
||||
const log = createLogger("debugger.controller", { output: "Godot Debugger" });
|
||||
const socketLog = createLogger("debugger.socket");
|
||||
@@ -37,9 +48,7 @@ export class ServerController {
|
||||
private didFirstOutput: boolean = false;
|
||||
private connectedVersion = "";
|
||||
|
||||
public constructor(
|
||||
public session: GodotDebugSession
|
||||
) { }
|
||||
public constructor(public session: GodotDebugSession) {}
|
||||
|
||||
public break() {
|
||||
this.send_command("break");
|
||||
@@ -87,12 +96,8 @@ export class ServerController {
|
||||
this.send_command("get_stack_frame_vars", [frame_id]);
|
||||
}
|
||||
|
||||
public set_object_property(objectId: bigint, label: string, newParsedValue: any) {
|
||||
this.send_command("set_object_property", [
|
||||
objectId,
|
||||
label,
|
||||
newParsedValue,
|
||||
]);
|
||||
public set_object_property(objectId: bigint, label: string, newParsedValue) {
|
||||
this.send_command("set_object_property", [objectId, label, newParsedValue]);
|
||||
}
|
||||
|
||||
public set_exception(exception: string) {
|
||||
@@ -103,7 +108,7 @@ export class ServerController {
|
||||
log.info("Starting game process");
|
||||
|
||||
let godotPath: string;
|
||||
let result;
|
||||
let result: VERIFY_RESULT;
|
||||
if (args.editor_path) {
|
||||
log.info("Using 'editor_path' variable from launch.json");
|
||||
|
||||
@@ -168,12 +173,12 @@ export class ServerController {
|
||||
const address = args.address.replace("tcp://", "");
|
||||
command += ` --remote-debug "${address}:${args.port}"`;
|
||||
|
||||
if (args.profiling) { command += " --profiling"; }
|
||||
if (args.debug_collisions) { command += " --debug-collisions"; }
|
||||
if (args.debug_paths) { command += " --debug-paths"; }
|
||||
if (args.frame_delay) { command += ` --frame-delay ${args.frame_delay}`; }
|
||||
if (args.time_scale) { command += ` --time-scale ${args.time_scale}`; }
|
||||
if (args.fixed_fps) { command += ` --fixed-fps ${args.fixed_fps}`; }
|
||||
if (args.profiling) command += " --profiling";
|
||||
if (args.debug_collisions) command += " --debug-collisions";
|
||||
if (args.debug_paths) command += " --debug-paths";
|
||||
if (args.frame_delay) command += ` --frame-delay ${args.frame_delay}`;
|
||||
if (args.time_scale) command += ` --time-scale ${args.time_scale}`;
|
||||
if (args.fixed_fps) command += ` --fixed-fps ${args.fixed_fps}`;
|
||||
|
||||
if (args.scene && args.scene !== "main") {
|
||||
log.info(`Custom scene argument provided: ${args.scene}`);
|
||||
@@ -219,15 +224,15 @@ export class ServerController {
|
||||
command += this.session.debug_data.get_breakpoint_string();
|
||||
|
||||
if (args.additional_options) {
|
||||
command += " " + args.additional_options;
|
||||
command += ` ${args.additional_options}`;
|
||||
}
|
||||
|
||||
log.info(`Launching game process using command: '${command}'`);
|
||||
const debugProcess = subProcess("debug", command, { shell: true, detached: true });
|
||||
|
||||
debugProcess.stdout.on("data", (data) => { });
|
||||
debugProcess.stderr.on("data", (data) => { });
|
||||
debugProcess.on("close", (code) => { });
|
||||
debugProcess.stdout.on("data", (data) => {});
|
||||
debugProcess.stderr.on("data", (data) => {});
|
||||
debugProcess.on("close", (code) => {});
|
||||
}
|
||||
|
||||
private stash: Buffer;
|
||||
@@ -351,7 +356,7 @@ export class ServerController {
|
||||
}
|
||||
}
|
||||
|
||||
private handle_command(command: Command) {
|
||||
private async handle_command(command: Command) {
|
||||
switch (command.command) {
|
||||
case "debug_enter": {
|
||||
const reason: string = command.parameters[1];
|
||||
@@ -379,7 +384,7 @@ export class ServerController {
|
||||
case "message:inspect_object": {
|
||||
let id = BigInt(command.parameters[0]);
|
||||
const className: string = command.parameters[1];
|
||||
const properties: any[] = command.parameters[2];
|
||||
const properties: string[] = command.parameters[2];
|
||||
|
||||
// message:inspect_object returns the id as an unsigned 64 bit integer, but it is decoded as a signed 64 bit integer,
|
||||
// thus we need to convert it to its equivalent unsigned value here.
|
||||
@@ -388,16 +393,13 @@ export class ServerController {
|
||||
}
|
||||
|
||||
const rawObject = new RawObject(className);
|
||||
properties.forEach((prop) => {
|
||||
for (const prop of properties) {
|
||||
rawObject.set(prop[0], prop[5]);
|
||||
});
|
||||
}
|
||||
const inspectedVariable = { name: "", value: rawObject };
|
||||
build_sub_values(inspectedVariable);
|
||||
if (this.session.inspect_callbacks.has(BigInt(id))) {
|
||||
this.session.inspect_callbacks.get(BigInt(id))(
|
||||
inspectedVariable.name,
|
||||
inspectedVariable
|
||||
);
|
||||
this.session.inspect_callbacks.get(BigInt(id))(inspectedVariable.name, inspectedVariable);
|
||||
this.session.inspect_callbacks.delete(BigInt(id));
|
||||
}
|
||||
this.session.set_inspection(id, inspectedVariable);
|
||||
@@ -425,15 +427,100 @@ export class ServerController {
|
||||
this.didFirstOutput = true;
|
||||
// this.request_scene_tree();
|
||||
}
|
||||
|
||||
command.parameters.forEach((line) => {
|
||||
debug.activeDebugConsole.appendLine(line[0]);
|
||||
});
|
||||
const lines = command.parameters;
|
||||
for (const line of lines) {
|
||||
debug.activeDebugConsole.appendLine(ansi.bright.blue + line[0]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "error": {
|
||||
if (!this.didFirstOutput) {
|
||||
this.didFirstOutput = true;
|
||||
}
|
||||
this.handle_error(command);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async handle_error(command: Command) {
|
||||
const params = command.parameters[0];
|
||||
const e = {
|
||||
hr: params[0],
|
||||
min: params[1],
|
||||
sec: params[2],
|
||||
msec: params[3],
|
||||
func: params[4] as string,
|
||||
file: params[5] as string,
|
||||
line: params[6],
|
||||
cond: params[7] as string,
|
||||
msg: params[8] as string,
|
||||
warning: params[9] as boolean,
|
||||
stack: [],
|
||||
};
|
||||
const stackCount = command.parameters[1];
|
||||
for (let i = 0; i < stackCount; i += 3) {
|
||||
const file = command.parameters[i + 2];
|
||||
const func = command.parameters[i + 3];
|
||||
const line = command.parameters[i + 4];
|
||||
const msg = `${file}:${line} @ ${func}()`;
|
||||
const extras = {
|
||||
source: { name: (await convert_resource_path_to_uri(file)).toString() },
|
||||
line: line,
|
||||
};
|
||||
e.stack.push({ msg: msg, extras: extras });
|
||||
}
|
||||
|
||||
const time = `${e.hr}:${e.min}:${e.sec}.${e.msec}`;
|
||||
const location = `${e.file}:${e.line} @ ${e.func}()`;
|
||||
const color = e.warning ? "yellow" : "red";
|
||||
const lang = e.file.startsWith("res://") ? "GDScript" : "C++";
|
||||
|
||||
const extras = {
|
||||
source: { name: (await convert_resource_path_to_uri(e.file)).toString() },
|
||||
line: e.line,
|
||||
group: "startCollapsed",
|
||||
};
|
||||
if (e.msg) {
|
||||
this.stderr(`${ansi[color]}${time} | ${e.func}: ${e.msg}`, extras);
|
||||
this.stderr(`${ansi.dim.white}<${lang} Error> ${ansi.white}${e.cond}`);
|
||||
} else {
|
||||
this.stderr(`${ansi[color]}${time} | ${e.func}: ${e.cond}`, extras);
|
||||
}
|
||||
this.stderr(`${ansi.dim.white}<${lang} Source> ${ansi.white}${location}`);
|
||||
|
||||
if (stackCount !== 0) {
|
||||
this.stderr(`${ansi.dim.white}<Stack Trace>`, { group: "start" });
|
||||
for (const frame of e.stack) {
|
||||
this.stderr(`${ansi.white}${frame.msg}`, frame.extras);
|
||||
}
|
||||
this.stderr("", { group: "end" });
|
||||
}
|
||||
this.stderr("", { group: "end" });
|
||||
}
|
||||
|
||||
stdout(output = "", extra = {}) {
|
||||
this.session.sendEvent({
|
||||
event: "output",
|
||||
body: {
|
||||
category: "stdout",
|
||||
output: output + ansi.reset,
|
||||
...extra,
|
||||
},
|
||||
} as DebugProtocol.OutputEvent);
|
||||
}
|
||||
|
||||
stderr(output = "", extra = {}) {
|
||||
this.session.sendEvent({
|
||||
event: "output",
|
||||
body: {
|
||||
category: "stderr",
|
||||
output: output + ansi.reset,
|
||||
...extra,
|
||||
},
|
||||
} as DebugProtocol.OutputEvent);
|
||||
}
|
||||
|
||||
public abort() {
|
||||
log.info("Aborting debug controller");
|
||||
this.session.sendEvent(new TerminatedEvent());
|
||||
@@ -468,19 +555,14 @@ export class ServerController {
|
||||
const line = stackFrames[0].line;
|
||||
|
||||
if (this.steppingOut) {
|
||||
const breakpoint = this.session.debug_data
|
||||
.get_breakpoints(file)
|
||||
.find((bp) => bp.line === line);
|
||||
const breakpoint = this.session.debug_data.get_breakpoints(file).find((bp) => bp.line === line);
|
||||
if (!breakpoint) {
|
||||
if (this.session.debug_data.stack_count > 1) {
|
||||
continueStepping = this.session.debug_data.stack_count === stackCount;
|
||||
} else {
|
||||
const fileSame =
|
||||
stackFrames[0].file === this.session.debug_data.last_frame.file;
|
||||
const funcSame =
|
||||
stackFrames[0].function === this.session.debug_data.last_frame.function;
|
||||
const lineGreater =
|
||||
stackFrames[0].line >= this.session.debug_data.last_frame.line;
|
||||
const fileSame = stackFrames[0].file === this.session.debug_data.last_frame.file;
|
||||
const funcSame = stackFrames[0].function === this.session.debug_data.last_frame.function;
|
||||
const lineGreater = stackFrames[0].line >= this.session.debug_data.last_frame.line;
|
||||
|
||||
continueStepping = fileSame && funcSame && lineGreater;
|
||||
}
|
||||
@@ -506,9 +588,7 @@ export class ServerController {
|
||||
this.session.sendEvent(new StoppedEvent("breakpoint", 0));
|
||||
} else {
|
||||
this.session.set_exception(true);
|
||||
this.session.sendEvent(
|
||||
new StoppedEvent("exception", 0, this.exception)
|
||||
);
|
||||
this.session.sendEvent(new StoppedEvent("exception", 0, this.exception));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -535,8 +615,8 @@ export class ServerController {
|
||||
const stackVars = new GodotStackVars();
|
||||
|
||||
let localsRemaining = parameters[0];
|
||||
let membersRemaining = parameters[1 + (localsRemaining * 2)];
|
||||
let globalsRemaining = parameters[2 + ((localsRemaining + membersRemaining) * 2)];
|
||||
let membersRemaining = parameters[1 + localsRemaining * 2];
|
||||
let globalsRemaining = parameters[2 + (localsRemaining + membersRemaining) * 2];
|
||||
|
||||
let i = 1;
|
||||
while (localsRemaining--) {
|
||||
@@ -551,7 +631,7 @@ export class ServerController {
|
||||
stackVars.globals.push({ name: parameters[i++], value: parameters[i++] });
|
||||
}
|
||||
|
||||
stackVars.forEach(item => build_sub_values(item));
|
||||
stackVars.forEach((item) => build_sub_values(item));
|
||||
|
||||
this.session.set_scopes(stackVars);
|
||||
}
|
||||
|
||||
@@ -1,26 +1,33 @@
|
||||
import * as fs from "fs";
|
||||
import {
|
||||
LoggingDebugSession,
|
||||
InitializedEvent,
|
||||
Thread,
|
||||
Source,
|
||||
Breakpoint,
|
||||
StoppedEvent,
|
||||
InitializedEvent,
|
||||
LoggingDebugSession,
|
||||
Source,
|
||||
TerminatedEvent,
|
||||
Thread,
|
||||
} 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 * as fs from "node:fs";
|
||||
import { debug } from "vscode";
|
||||
|
||||
import { createLogger } from "../../utils";
|
||||
import { GodotDebugData, GodotStackVars, GodotVariable } from "../debug_runtime";
|
||||
import { AttachRequestArguments, LaunchRequestArguments } from "../debugger";
|
||||
import { SceneTreeProvider } from "../scene_tree_provider";
|
||||
import { is_variable_built_in_type, parse_variable } from "./helpers";
|
||||
import { ServerController } from "./server_controller";
|
||||
import { ObjectId } from "./variables/variants";
|
||||
|
||||
const log = createLogger("debugger.session", { output: "Godot Debugger" });
|
||||
|
||||
interface Variable {
|
||||
variable: GodotVariable;
|
||||
index: number;
|
||||
object_id: number;
|
||||
error: string;
|
||||
}
|
||||
|
||||
export class GodotDebugSession extends LoggingDebugSession {
|
||||
private all_scopes: GodotVariable[];
|
||||
public controller = new ServerController(this);
|
||||
@@ -32,10 +39,7 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
private previous_inspections: bigint[] = [];
|
||||
private configuration_done: Subject = new Subject();
|
||||
private mode: "launch" | "attach" | "" = "";
|
||||
public inspect_callbacks: Map<
|
||||
bigint,
|
||||
(class_name: string, variable: GodotVariable) => void
|
||||
> = new Map();
|
||||
public inspect_callbacks: Map<bigint, (class_name: string, variable: GodotVariable) => void> = new Map();
|
||||
|
||||
public constructor() {
|
||||
super();
|
||||
@@ -50,7 +54,7 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
|
||||
protected initializeRequest(
|
||||
response: DebugProtocol.InitializeResponse,
|
||||
args: DebugProtocol.InitializeRequestArguments
|
||||
args: DebugProtocol.InitializeRequestArguments,
|
||||
) {
|
||||
response.body = response.body || {};
|
||||
|
||||
@@ -79,10 +83,7 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
this.sendEvent(new InitializedEvent());
|
||||
}
|
||||
|
||||
protected async launchRequest(
|
||||
response: DebugProtocol.LaunchResponse,
|
||||
args: LaunchRequestArguments
|
||||
) {
|
||||
protected async launchRequest(response: DebugProtocol.LaunchResponse, args: LaunchRequestArguments) {
|
||||
await this.configuration_done.wait(1000);
|
||||
|
||||
this.mode = "launch";
|
||||
@@ -94,10 +95,7 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
this.sendResponse(response);
|
||||
}
|
||||
|
||||
protected async attachRequest(
|
||||
response: DebugProtocol.AttachResponse,
|
||||
args: AttachRequestArguments
|
||||
) {
|
||||
protected async attachRequest(response: DebugProtocol.AttachResponse, args: AttachRequestArguments) {
|
||||
await this.configuration_done.wait(1000);
|
||||
|
||||
this.mode = "attach";
|
||||
@@ -110,16 +108,13 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
|
||||
public configurationDoneRequest(
|
||||
response: DebugProtocol.ConfigurationDoneResponse,
|
||||
args: DebugProtocol.ConfigurationDoneArguments
|
||||
args: DebugProtocol.ConfigurationDoneArguments,
|
||||
) {
|
||||
this.configuration_done.notify();
|
||||
this.sendResponse(response);
|
||||
}
|
||||
|
||||
protected continueRequest(
|
||||
response: DebugProtocol.ContinueResponse,
|
||||
args: DebugProtocol.ContinueArguments
|
||||
) {
|
||||
protected continueRequest(response: DebugProtocol.ContinueResponse, args: DebugProtocol.ContinueArguments) {
|
||||
if (!this.exception) {
|
||||
response.body = { allThreadsContinued: true };
|
||||
this.controller.continue();
|
||||
@@ -127,20 +122,17 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
}
|
||||
}
|
||||
|
||||
protected async evaluateRequest(
|
||||
response: DebugProtocol.EvaluateResponse,
|
||||
args: DebugProtocol.EvaluateArguments
|
||||
) {
|
||||
protected async evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments) {
|
||||
await debug.activeDebugSession.customRequest("scopes", { frameId: 0 });
|
||||
|
||||
if (this.all_scopes) {
|
||||
var variable = this.get_variable(args.expression, null, null, null);
|
||||
const variable = this.get_variable(args.expression, null, null, null);
|
||||
|
||||
if (variable.error == null) {
|
||||
var parsed_variable = parse_variable(variable.variable);
|
||||
const parsed_variable = parse_variable(variable.variable);
|
||||
response.body = {
|
||||
result: parsed_variable.value,
|
||||
variablesReference: !is_variable_built_in_type(variable.variable) ? variable.index : 0
|
||||
variablesReference: !is_variable_built_in_type(variable.variable) ? variable.index : 0,
|
||||
};
|
||||
} else {
|
||||
response.success = false;
|
||||
@@ -158,30 +150,21 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
this.sendResponse(response);
|
||||
}
|
||||
|
||||
protected nextRequest(
|
||||
response: DebugProtocol.NextResponse,
|
||||
args: DebugProtocol.NextArguments
|
||||
) {
|
||||
protected nextRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments) {
|
||||
if (!this.exception) {
|
||||
this.controller.next();
|
||||
this.sendResponse(response);
|
||||
}
|
||||
}
|
||||
|
||||
protected pauseRequest(
|
||||
response: DebugProtocol.PauseResponse,
|
||||
args: DebugProtocol.PauseArguments
|
||||
) {
|
||||
protected pauseRequest(response: DebugProtocol.PauseResponse, args: DebugProtocol.PauseArguments) {
|
||||
if (!this.exception) {
|
||||
this.controller.break();
|
||||
this.sendResponse(response);
|
||||
}
|
||||
}
|
||||
|
||||
protected async scopesRequest(
|
||||
response: DebugProtocol.ScopesResponse,
|
||||
args: DebugProtocol.ScopesArguments
|
||||
) {
|
||||
protected async scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments) {
|
||||
this.controller.request_stack_frame_vars(args.frameId);
|
||||
await this.got_scope.wait(2000);
|
||||
|
||||
@@ -197,7 +180,7 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
|
||||
protected setBreakPointsRequest(
|
||||
response: DebugProtocol.SetBreakpointsResponse,
|
||||
args: DebugProtocol.SetBreakpointsArguments
|
||||
args: DebugProtocol.SetBreakpointsArguments,
|
||||
) {
|
||||
const path = (args.source.path as string).replace(/\\/g, "/");
|
||||
const client_lines = args.lines || [];
|
||||
@@ -206,19 +189,19 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
let bps = this.debug_data.get_breakpoints(path);
|
||||
const bp_lines = bps.map((bp) => bp.line);
|
||||
|
||||
bps.forEach((bp) => {
|
||||
for (const bp of bps) {
|
||||
if (client_lines.indexOf(bp.line) === -1) {
|
||||
this.debug_data.remove_breakpoint(path, bp.line);
|
||||
}
|
||||
});
|
||||
client_lines.forEach((l) => {
|
||||
}
|
||||
for (const l of client_lines) {
|
||||
if (bp_lines.indexOf(l) === -1) {
|
||||
const 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) {
|
||||
this.debug_data.set_breakpoint(path, l);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bps = this.debug_data.get_breakpoints(path);
|
||||
// Sort to ensure breakpoints aren't out-of-order, which would confuse VS Code.
|
||||
@@ -226,12 +209,7 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
|
||||
response.body = {
|
||||
breakpoints: bps.map((bp) => {
|
||||
return new Breakpoint(
|
||||
true,
|
||||
bp.line,
|
||||
1,
|
||||
new Source(bp.file.split("/").reverse()[0], bp.file)
|
||||
);
|
||||
return new Breakpoint(true, bp.line, 1, new Source(bp.file.split("/").reverse()[0], bp.file));
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -239,10 +217,7 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
}
|
||||
}
|
||||
|
||||
protected stackTraceRequest(
|
||||
response: DebugProtocol.StackTraceResponse,
|
||||
args: DebugProtocol.StackTraceArguments
|
||||
) {
|
||||
protected stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments) {
|
||||
if (this.debug_data.last_frame) {
|
||||
response.body = {
|
||||
totalFrames: this.debug_data.last_frames.length,
|
||||
@@ -252,10 +227,7 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
name: sf.function,
|
||||
line: sf.line,
|
||||
column: 1,
|
||||
source: new Source(
|
||||
sf.file,
|
||||
`${this.debug_data.projectPath}/${sf.file.replace("res://", "")}`
|
||||
),
|
||||
source: new Source(sf.file, `${this.debug_data.projectPath}/${sf.file.replace("res://", "")}`),
|
||||
};
|
||||
}),
|
||||
};
|
||||
@@ -263,30 +235,21 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
this.sendResponse(response);
|
||||
}
|
||||
|
||||
protected stepInRequest(
|
||||
response: DebugProtocol.StepInResponse,
|
||||
args: DebugProtocol.StepInArguments
|
||||
) {
|
||||
protected stepInRequest(response: DebugProtocol.StepInResponse, args: DebugProtocol.StepInArguments) {
|
||||
if (!this.exception) {
|
||||
this.controller.step();
|
||||
this.sendResponse(response);
|
||||
}
|
||||
}
|
||||
|
||||
protected stepOutRequest(
|
||||
response: DebugProtocol.StepOutResponse,
|
||||
args: DebugProtocol.StepOutArguments
|
||||
) {
|
||||
protected stepOutRequest(response: DebugProtocol.StepOutResponse, args: DebugProtocol.StepOutArguments) {
|
||||
if (!this.exception) {
|
||||
this.controller.step_out();
|
||||
this.sendResponse(response);
|
||||
}
|
||||
}
|
||||
|
||||
protected terminateRequest(
|
||||
response: DebugProtocol.TerminateResponse,
|
||||
args: DebugProtocol.TerminateArguments
|
||||
) {
|
||||
protected terminateRequest(response: DebugProtocol.TerminateResponse, args: DebugProtocol.TerminateArguments) {
|
||||
if (this.mode === "launch") {
|
||||
this.controller.stop();
|
||||
this.sendEvent(new TerminatedEvent());
|
||||
@@ -301,11 +264,11 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
|
||||
protected async variablesRequest(
|
||||
response: DebugProtocol.VariablesResponse,
|
||||
args: DebugProtocol.VariablesArguments
|
||||
args: DebugProtocol.VariablesArguments,
|
||||
) {
|
||||
if (!this.all_scopes) {
|
||||
response.body = {
|
||||
variables: []
|
||||
variables: [],
|
||||
};
|
||||
this.sendResponse(response);
|
||||
return;
|
||||
@@ -319,8 +282,7 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
} else {
|
||||
variables = reference.sub_values.map((va) => {
|
||||
const sva = this.all_scopes.find(
|
||||
(sva) =>
|
||||
sva && sva.scope_path === va.scope_path && sva.name === va.name
|
||||
(sva) => sva && sva.scope_path === va.scope_path && sva.name === va.name,
|
||||
);
|
||||
if (sva) {
|
||||
return parse_variable(
|
||||
@@ -329,8 +291,8 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
(va_idx) =>
|
||||
va_idx &&
|
||||
va_idx.scope_path === `${reference.scope_path}.${reference.name}` &&
|
||||
va_idx.name === va.name
|
||||
)
|
||||
va_idx.name === va.name,
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -354,7 +316,7 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
name: "local",
|
||||
value: undefined,
|
||||
sub_values: stackVars.locals,
|
||||
scope_path: "@"
|
||||
scope_path: "@",
|
||||
},
|
||||
{
|
||||
name: "member",
|
||||
@@ -370,20 +332,20 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
},
|
||||
];
|
||||
|
||||
stackVars.locals.forEach((va) => {
|
||||
for (const va of stackVars.locals) {
|
||||
va.scope_path = "@.local";
|
||||
this.append_variable(va);
|
||||
});
|
||||
}
|
||||
|
||||
stackVars.members.forEach((va) => {
|
||||
for (const va of stackVars.members) {
|
||||
va.scope_path = "@.member";
|
||||
this.append_variable(va);
|
||||
});
|
||||
}
|
||||
|
||||
stackVars.globals.forEach((va) => {
|
||||
for (const va of stackVars.globals) {
|
||||
va.scope_path = "@.global";
|
||||
this.append_variable(va);
|
||||
});
|
||||
}
|
||||
|
||||
this.add_to_inspections();
|
||||
|
||||
@@ -394,21 +356,19 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
}
|
||||
|
||||
public set_inspection(id: bigint, replacement: GodotVariable) {
|
||||
const variables = this.all_scopes.filter(
|
||||
(va) => va && va.value instanceof ObjectId && va.value.id === id
|
||||
);
|
||||
const variables = this.all_scopes.filter((va) => va && va.value instanceof ObjectId && va.value.id === id);
|
||||
|
||||
variables.forEach((va) => {
|
||||
for (const va of variables) {
|
||||
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
|
||||
1,
|
||||
);
|
||||
|
||||
this.previous_inspections.push(id);
|
||||
@@ -422,7 +382,7 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
}
|
||||
|
||||
private add_to_inspections() {
|
||||
this.all_scopes.forEach((va) => {
|
||||
for (const va of this.all_scopes) {
|
||||
if (va && va.value instanceof ObjectId) {
|
||||
if (
|
||||
!this.ongoing_inspections.includes(va.value.id) &&
|
||||
@@ -432,38 +392,58 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
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 };
|
||||
protected get_variable(
|
||||
expression: string,
|
||||
root: GodotVariable = null,
|
||||
index = 0,
|
||||
object_id: number = null,
|
||||
): Variable {
|
||||
let result: Variable = {
|
||||
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;
|
||||
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("");
|
||||
const items = expression.split(".");
|
||||
let propertyName = items[index + 1];
|
||||
let path = items
|
||||
.slice(0, index + 1)
|
||||
.join(".")
|
||||
.split("self.")
|
||||
.join("")
|
||||
.split("self")
|
||||
.join("")
|
||||
.split("[")
|
||||
.join(".")
|
||||
.split("]")
|
||||
.join("");
|
||||
|
||||
if (items.length == 1 && items[0] == "self") {
|
||||
if (items.length === 1 && items[0] === "self") {
|
||||
propertyName = "self";
|
||||
}
|
||||
|
||||
// Detect index/key
|
||||
var key = (propertyName.match(/(?<=\[).*(?=\])/) || [null])[0];
|
||||
let key = (propertyName.match(/(?<=\[).*(?=\])/) || [null])[0];
|
||||
if (key) {
|
||||
key = key.replace(/['"]+/g, "");
|
||||
propertyName = propertyName.split(/(?<=\[).*(?=\])/).join("").split("\[\]").join("");
|
||||
propertyName = propertyName
|
||||
.split(/(?<=\[).*(?=\])/)
|
||||
.join("")
|
||||
.split("[]")
|
||||
.join("");
|
||||
if (path) path += ".";
|
||||
path += propertyName;
|
||||
propertyName = key;
|
||||
@@ -474,49 +454,60 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
}
|
||||
|
||||
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("");
|
||||
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 {
|
||||
const sanitized_all_scopes = this.all_scopes
|
||||
.filter((x) => x)
|
||||
.map((x) => ({
|
||||
sanitized: {
|
||||
name: sanitizeName(x.name),
|
||||
scope_path: sanitizeScopePath(x.scope_path)
|
||||
scope_path: sanitizeScopePath(x.scope_path),
|
||||
},
|
||||
real: x
|
||||
};
|
||||
});
|
||||
real: x,
|
||||
}));
|
||||
|
||||
result.variable = sanitized_all_scopes
|
||||
.find(x => x.sanitized.name == propertyName && x.sanitized.scope_path == path)
|
||||
?.real;
|
||||
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;
|
||||
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;
|
||||
const collection = path.split(".")[path.split(".").length - 1];
|
||||
const 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 {
|
||||
const entries = Array.from(root.value.entries());
|
||||
const item = entries.find(x => x && x[0].split("Members/").join("").split("Locals/").join("") == propertyName);
|
||||
const item = Array.from(root.value.entries()).find(
|
||||
(x) => x && x[0].split("Members/").join("").split("Locals/").join("") === propertyName,
|
||||
);
|
||||
result.object_id = item?.[1].id;
|
||||
}
|
||||
}
|
||||
@@ -525,7 +516,9 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
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);
|
||||
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);
|
||||
|
||||
@@ -1,17 +1,28 @@
|
||||
import * as fs from "fs";
|
||||
import net = require("net");
|
||||
import { debug, window } from "vscode";
|
||||
import { StoppedEvent, TerminatedEvent } from "@vscode/debugadapter";
|
||||
import { VariantEncoder } from "./variables/variant_encoder";
|
||||
import { VariantDecoder } from "./variables/variant_decoder";
|
||||
import { RawObject } from "./variables/variants";
|
||||
import { GodotStackFrame, GodotVariable, GodotStackVars } from "../debug_runtime";
|
||||
import { GodotDebugSession } from "./debug_session";
|
||||
import { parse_next_scene_node, split_buffers, build_sub_values } from "./helpers";
|
||||
import { get_configuration, get_free_port, createLogger, verify_godot_version, get_project_version } from "../../utils";
|
||||
import { DebugProtocol } from "@vscode/debugprotocol";
|
||||
import * as fs from "node:fs";
|
||||
import * as net from "node:net";
|
||||
import { debug, window } from "vscode";
|
||||
|
||||
import {
|
||||
ansi,
|
||||
convert_resource_path_to_uri,
|
||||
createLogger,
|
||||
get_configuration,
|
||||
get_free_port,
|
||||
get_project_version,
|
||||
verify_godot_version,
|
||||
VERIFY_RESULT,
|
||||
} from "../../utils";
|
||||
import { prompt_for_godot_executable } from "../../utils/prompts";
|
||||
import { subProcess, killSubProcesses } from "../../utils/subspawn";
|
||||
import { LaunchRequestArguments, AttachRequestArguments, pinnedScene } from "../debugger";
|
||||
import { killSubProcesses, subProcess } from "../../utils/subspawn";
|
||||
import { GodotStackFrame, GodotStackVars, GodotVariable } from "../debug_runtime";
|
||||
import { AttachRequestArguments, LaunchRequestArguments, pinnedScene } from "../debugger";
|
||||
import { GodotDebugSession } from "./debug_session";
|
||||
import { build_sub_values, parse_next_scene_node, split_buffers } from "./helpers";
|
||||
import { VariantDecoder } from "./variables/variant_decoder";
|
||||
import { VariantEncoder } from "./variables/variant_encoder";
|
||||
import { RawObject } from "./variables/variants";
|
||||
|
||||
const log = createLogger("debugger.controller", { output: "Godot Debugger" });
|
||||
const socketLog = createLogger("debugger.socket");
|
||||
@@ -34,13 +45,11 @@ export class ServerController {
|
||||
private server?: net.Server;
|
||||
private socket?: net.Socket;
|
||||
private steppingOut = false;
|
||||
private didFirstOutput: boolean = false;
|
||||
private didFirstOutput = false;
|
||||
private partialStackVars = new GodotStackVars();
|
||||
private connectedVersion = "";
|
||||
|
||||
public constructor(
|
||||
public session: GodotDebugSession
|
||||
) { }
|
||||
public constructor(public session: GodotDebugSession) {}
|
||||
|
||||
public break() {
|
||||
this.send_command("break");
|
||||
@@ -88,12 +97,8 @@ export class ServerController {
|
||||
this.send_command("get_stack_frame_vars", [frame_id]);
|
||||
}
|
||||
|
||||
public set_object_property(objectId: bigint, label: string, newParsedValue: any) {
|
||||
this.send_command("scene:set_object_property", [
|
||||
objectId,
|
||||
label,
|
||||
newParsedValue,
|
||||
]);
|
||||
public set_object_property(objectId: bigint, label: string, newParsedValue) {
|
||||
this.send_command("scene:set_object_property", [objectId, label, newParsedValue]);
|
||||
}
|
||||
|
||||
public set_exception(exception: string) {
|
||||
@@ -104,7 +109,7 @@ export class ServerController {
|
||||
log.info("Starting game process");
|
||||
|
||||
let godotPath: string;
|
||||
let result;
|
||||
let result: VERIFY_RESULT;
|
||||
if (args.editor_path) {
|
||||
log.info("Using 'editor_path' variable from launch.json");
|
||||
|
||||
@@ -169,17 +174,17 @@ export class ServerController {
|
||||
const address = args.address.replace("tcp://", "");
|
||||
command += ` --remote-debug "tcp://${address}:${args.port}"`;
|
||||
|
||||
if (args.profiling) { command += " --profiling"; }
|
||||
if (args.single_threaded_scene) { command += " --single-threaded-scene"; }
|
||||
if (args.debug_collisions) { command += " --debug-collisions"; }
|
||||
if (args.debug_paths) { command += " --debug-paths"; }
|
||||
if (args.debug_navigation) { command += " --debug-navigation"; }
|
||||
if (args.debug_avoidance) { command += " --debug-avoidance"; }
|
||||
if (args.debug_stringnames) { command += " --debug-stringnames"; }
|
||||
if (args.frame_delay) { command += ` --frame-delay ${args.frame_delay}`; }
|
||||
if (args.time_scale) { command += ` --time-scale ${args.time_scale}`; }
|
||||
if (args.disable_vsync) { command += " --disable-vsync"; }
|
||||
if (args.fixed_fps) { command += ` --fixed-fps ${args.fixed_fps}`; }
|
||||
if (args.profiling) command += " --profiling";
|
||||
if (args.single_threaded_scene) command += " --single-threaded-scene";
|
||||
if (args.debug_collisions) command += " --debug-collisions";
|
||||
if (args.debug_paths) command += " --debug-paths";
|
||||
if (args.debug_navigation) command += " --debug-navigation";
|
||||
if (args.debug_avoidance) command += " --debug-avoidance";
|
||||
if (args.debug_stringnames) command += " --debug-stringnames";
|
||||
if (args.frame_delay) command += ` --frame-delay ${args.frame_delay}`;
|
||||
if (args.time_scale) command += ` --time-scale ${args.time_scale}`;
|
||||
if (args.disable_vsync) command += " --disable-vsync";
|
||||
if (args.fixed_fps) command += ` --fixed-fps ${args.fixed_fps}`;
|
||||
|
||||
if (args.scene && args.scene !== "main") {
|
||||
log.info(`Custom scene argument provided: ${args.scene}`);
|
||||
@@ -225,15 +230,15 @@ export class ServerController {
|
||||
command += this.session.debug_data.get_breakpoint_string();
|
||||
|
||||
if (args.additional_options) {
|
||||
command += " " + args.additional_options;
|
||||
command += ` ${args.additional_options}`;
|
||||
}
|
||||
|
||||
log.info(`Launching game process using command: '${command}'`);
|
||||
const debugProcess = subProcess("debug", command, { shell: true, detached: true });
|
||||
|
||||
debugProcess.stdout.on("data", (data) => { });
|
||||
debugProcess.stderr.on("data", (data) => { });
|
||||
debugProcess.on("close", (code) => { });
|
||||
debugProcess.stdout.on("data", (data) => {});
|
||||
debugProcess.stderr.on("data", (data) => {});
|
||||
debugProcess.on("close", (code) => {});
|
||||
}
|
||||
|
||||
private stash: Buffer;
|
||||
@@ -336,7 +341,7 @@ export class ServerController {
|
||||
this.server.listen(args.port, args.address);
|
||||
}
|
||||
|
||||
private parse_message(dataset: any[]) {
|
||||
private parse_message(dataset: []) {
|
||||
const command = new Command();
|
||||
let i = 0;
|
||||
command.command = dataset[i++];
|
||||
@@ -347,7 +352,7 @@ export class ServerController {
|
||||
return command;
|
||||
}
|
||||
|
||||
private handle_command(command: Command) {
|
||||
private async handle_command(command: Command) {
|
||||
switch (command.command) {
|
||||
case "debug_enter": {
|
||||
const reason: string = command.parameters[1];
|
||||
@@ -378,7 +383,7 @@ export class ServerController {
|
||||
case "scene:inspect_object": {
|
||||
let id = BigInt(command.parameters[0]);
|
||||
const className: string = command.parameters[1];
|
||||
const properties: any[] = command.parameters[2];
|
||||
const properties: string[] = command.parameters[2];
|
||||
|
||||
// message:inspect_object returns the id as an unsigned 64 bit integer, but it is decoded as a signed 64 bit integer,
|
||||
// thus we need to convert it to its equivalent unsigned value here.
|
||||
@@ -387,16 +392,13 @@ export class ServerController {
|
||||
}
|
||||
|
||||
const rawObject = new RawObject(className);
|
||||
properties.forEach((prop) => {
|
||||
for (const prop of properties) {
|
||||
rawObject.set(prop[0], prop[5]);
|
||||
});
|
||||
}
|
||||
const inspectedVariable = { name: "", value: rawObject };
|
||||
build_sub_values(inspectedVariable);
|
||||
if (this.session.inspect_callbacks.has(BigInt(id))) {
|
||||
this.session.inspect_callbacks.get(BigInt(id))(
|
||||
inspectedVariable.name,
|
||||
inspectedVariable
|
||||
);
|
||||
this.session.inspect_callbacks.get(BigInt(id))(inspectedVariable.name, inspectedVariable);
|
||||
this.session.inspect_callbacks.delete(BigInt(id));
|
||||
}
|
||||
this.session.set_inspection(id, inspectedVariable);
|
||||
@@ -439,13 +441,102 @@ export class ServerController {
|
||||
}
|
||||
const lines = command.parameters[0];
|
||||
for (const line of lines) {
|
||||
debug.activeDebugConsole.appendLine(line);
|
||||
debug.activeDebugConsole.appendLine(ansi.bright.blue + line);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "error": {
|
||||
if (!this.didFirstOutput) {
|
||||
this.didFirstOutput = true;
|
||||
}
|
||||
this.handle_error(command);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async handle_error(command: Command) {
|
||||
const params = command.parameters;
|
||||
const e = {
|
||||
hr: params[0],
|
||||
min: params[1],
|
||||
sec: params[2],
|
||||
msec: params[3],
|
||||
file: params[4] as string,
|
||||
func: params[5] as string,
|
||||
line: params[6],
|
||||
error: params[7] as string,
|
||||
desc: params[8] as string,
|
||||
warning: params[9] as boolean,
|
||||
stack: [],
|
||||
};
|
||||
const stackCount = params[10] ?? 0;
|
||||
for (let i = 0; i < stackCount; i += 3) {
|
||||
const file = params[11 + i];
|
||||
const func = params[12 + i];
|
||||
const line = params[13 + i];
|
||||
const msg = `${file.slice("res://".length)}:${line} @ ${func}()`;
|
||||
const extras = {
|
||||
source: { name: (await convert_resource_path_to_uri(file)).toString() },
|
||||
line: line,
|
||||
};
|
||||
e.stack.push({ msg: msg, extras: extras });
|
||||
}
|
||||
|
||||
const time = `${e.hr}:${e.min}:${e.sec}.${e.msec}`;
|
||||
let file = e.file;
|
||||
if (file.startsWith("res://")) {
|
||||
file = file.slice("res://".length);
|
||||
}
|
||||
const location = `${file}:${e.line}`;
|
||||
const color = e.warning ? "yellow" : "red";
|
||||
const lang = e.file.startsWith("res://") ? "GDScript" : "C++";
|
||||
|
||||
const extras = {
|
||||
source: { name: (await convert_resource_path_to_uri(e.file)).toString() },
|
||||
line: e.line,
|
||||
group: "startCollapsed",
|
||||
};
|
||||
if (e.desc) {
|
||||
this.stderr(`${ansi[color]}${time} | ${e.desc}`, extras);
|
||||
this.stderr(`${ansi.dim.white}<${lang} Error> ${ansi.white}${e.error}`);
|
||||
} else {
|
||||
this.stderr(`${ansi[color]}${time} | ${e.error}`, extras);
|
||||
}
|
||||
this.stderr(`${ansi.dim.white}<${lang} Source> ${ansi.white}${location}`);
|
||||
|
||||
if (stackCount !== 0) {
|
||||
this.stderr(`${ansi.dim.white}<Stack Trace>`, { group: "start" });
|
||||
for (const frame of e.stack) {
|
||||
this.stderr(`${ansi.white}${frame.msg}`, frame.extras);
|
||||
}
|
||||
this.stderr("", { group: "end" });
|
||||
}
|
||||
this.stderr("", { group: "end" });
|
||||
}
|
||||
|
||||
stdout(output = "", extra = {}) {
|
||||
this.session.sendEvent({
|
||||
event: "output",
|
||||
body: {
|
||||
category: "stdout",
|
||||
output: output + ansi.reset,
|
||||
...extra,
|
||||
},
|
||||
} as DebugProtocol.OutputEvent);
|
||||
}
|
||||
|
||||
stderr(output = "", extra = {}) {
|
||||
this.session.sendEvent({
|
||||
event: "output",
|
||||
body: {
|
||||
category: "stderr",
|
||||
output: output + ansi.reset,
|
||||
...extra,
|
||||
},
|
||||
} as DebugProtocol.OutputEvent);
|
||||
}
|
||||
|
||||
public abort() {
|
||||
log.info("Aborting debug controller");
|
||||
this.session.sendEvent(new TerminatedEvent());
|
||||
@@ -480,19 +571,14 @@ export class ServerController {
|
||||
const line = stackFrames[0].line;
|
||||
|
||||
if (this.steppingOut) {
|
||||
const breakpoint = this.session.debug_data
|
||||
.get_breakpoints(file)
|
||||
.find((bp) => bp.line === line);
|
||||
const breakpoint = this.session.debug_data.get_breakpoints(file).find((bp) => bp.line === line);
|
||||
if (!breakpoint) {
|
||||
if (this.session.debug_data.stack_count > 1) {
|
||||
continueStepping = this.session.debug_data.stack_count === stackCount;
|
||||
} else {
|
||||
const fileSame =
|
||||
stackFrames[0].file === this.session.debug_data.last_frame.file;
|
||||
const funcSame =
|
||||
stackFrames[0].function === this.session.debug_data.last_frame.function;
|
||||
const lineGreater =
|
||||
stackFrames[0].line >= this.session.debug_data.last_frame.line;
|
||||
const fileSame = stackFrames[0].file === this.session.debug_data.last_frame.file;
|
||||
const funcSame = stackFrames[0].function === this.session.debug_data.last_frame.function;
|
||||
const lineGreater = stackFrames[0].line >= this.session.debug_data.last_frame.line;
|
||||
|
||||
continueStepping = fileSame && funcSame && lineGreater;
|
||||
}
|
||||
@@ -518,15 +604,12 @@ export class ServerController {
|
||||
this.session.sendEvent(new StoppedEvent("breakpoint", 0));
|
||||
} else {
|
||||
this.session.set_exception(true);
|
||||
this.session.sendEvent(
|
||||
new StoppedEvent("exception", 0, this.exception)
|
||||
);
|
||||
this.session.sendEvent(new StoppedEvent("exception", 0, this.exception));
|
||||
}
|
||||
}
|
||||
|
||||
private send_command(command: string, parameters?: any[]) {
|
||||
const commandArray: any[] = [command];
|
||||
// log.debug("send_command", this.connectedVersion);
|
||||
if (this.connectedVersion[2] >= "2") {
|
||||
commandArray.push(this.threadId);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as vscode from "vscode";
|
||||
import * as path from "path";
|
||||
import * as fs from "fs";
|
||||
import { AddressInfo, createServer } from "net";
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import * as vscode from "vscode";
|
||||
|
||||
export * from "./logger";
|
||||
export * from "./project_utils";
|
||||
@@ -21,12 +21,12 @@ export async function find_file(file: string): Promise<vscode.Uri | null> {
|
||||
if (results.length === 1) {
|
||||
return results[0];
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function get_free_port(): Promise<number> {
|
||||
return new Promise(res => {
|
||||
return new Promise((res) => {
|
||||
const srv = createServer();
|
||||
srv.listen(0, () => {
|
||||
const port = (srv.address() as AddressInfo).port;
|
||||
@@ -45,7 +45,7 @@ export function make_docs_uri(path: string, fragment?: string) {
|
||||
|
||||
/**
|
||||
* Can be used to convert a conventional node name to a snake_case variable name.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* nodeNameToVar("MyNode") // my_node
|
||||
@@ -54,10 +54,39 @@ export function make_docs_uri(path: string, fragment?: string) {
|
||||
* ```
|
||||
*/
|
||||
export function node_name_to_snake(name: string): string {
|
||||
const snakeCase: string = name.replace(/([a-z])([A-Z0-9])/g, "$1_$2").toLowerCase();
|
||||
|
||||
if (snakeCase.startsWith("_")) {
|
||||
return snakeCase.substring(1);
|
||||
}
|
||||
return snakeCase;
|
||||
const snakeCase: string = name.replace(/([a-z])([A-Z0-9])/g, "$1_$2").toLowerCase();
|
||||
|
||||
if (snakeCase.startsWith("_")) {
|
||||
return snakeCase.substring(1);
|
||||
}
|
||||
return snakeCase;
|
||||
}
|
||||
|
||||
export const ansi = {
|
||||
reset: "\u001b[0;37m",
|
||||
red: "\u001b[0;31m",
|
||||
green: "\u001b[0;32m",
|
||||
yellow: "\u001b[0;33m",
|
||||
blue: "\u001b[0;34m",
|
||||
purple: "\u001b[0;35m",
|
||||
cyan: "\u001b[0;36m",
|
||||
white: "\u001b[0;37m",
|
||||
bright: {
|
||||
red: "\u001b[1;31m",
|
||||
green: "\u001b[1;32m",
|
||||
yellow: "\u001b[1;33m",
|
||||
blue: "\u001b[1;34m",
|
||||
purple: "\u001b[1;35m",
|
||||
cyan: "\u001b[1;36m",
|
||||
white: "\u001b[1;37m",
|
||||
},
|
||||
dim: {
|
||||
red: "\u001b[1;2;31m",
|
||||
green: "\u001b[1;2;32m",
|
||||
yellow: "\u001b[1;2;33m",
|
||||
blue: "\u001b[1;2;34m",
|
||||
purple: "\u001b[1;2;35m",
|
||||
cyan: "\u001b[1;2;36m",
|
||||
white: "\u001b[1;2;37m",
|
||||
},
|
||||
} as const;
|
||||
|
||||
@@ -105,8 +105,8 @@ export async function convert_resource_path_to_uri(resPath: string): Promise<vsc
|
||||
return vscode.Uri.joinPath(vscode.Uri.file(dir), resPath.substring("res://".length));
|
||||
}
|
||||
|
||||
type VERIFY_STATUS = "SUCCESS" | "WRONG_VERSION" | "INVALID_EXE";
|
||||
type VERIFY_RESULT = {
|
||||
export type VERIFY_STATUS = "SUCCESS" | "WRONG_VERSION" | "INVALID_EXE";
|
||||
export type VERIFY_RESULT = {
|
||||
status: VERIFY_STATUS;
|
||||
godotPath: string;
|
||||
version?: string;
|
||||
|
||||
Reference in New Issue
Block a user