gdharness 1.1.15 → 1.1.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/cli.js
CHANGED
|
@@ -8571,20 +8571,57 @@ function withAddonClassesCounted(rebuilt, listed) {
|
|
|
8571
8571
|
return counted;
|
|
8572
8572
|
}
|
|
8573
8573
|
function missingMemberIn(message) {
|
|
8574
|
-
const
|
|
8575
|
-
if (
|
|
8574
|
+
const inferred = /The (method|property) "([^"(]+)(?:\(\))?" is not present on the inferred type "([^"]+)"/.exec(message);
|
|
8575
|
+
if (inferred !== null) {
|
|
8576
|
+
return {
|
|
8577
|
+
kind: inferred[1] === "property" ? "property" : "method",
|
|
8578
|
+
member: inferred[2] ?? "",
|
|
8579
|
+
type: inferred[3] ?? ""
|
|
8580
|
+
};
|
|
8581
|
+
}
|
|
8582
|
+
const called = /Static function "([A-Za-z_]\w*)\(\)" not found in base "([A-Za-z_]\w*)"/.exec(message);
|
|
8583
|
+
if (called !== null) {
|
|
8584
|
+
return { kind: "static method", member: called[1] ?? "", type: called[2] ?? "" };
|
|
8585
|
+
}
|
|
8586
|
+
const inBase = /Cannot find member "([A-Za-z_]\w*)" in base "([A-Za-z_]\w*)((?:\.[A-Za-z_]\w*)*)"/.exec(message);
|
|
8587
|
+
if (inBase === null) {
|
|
8576
8588
|
return null;
|
|
8577
8589
|
}
|
|
8578
|
-
|
|
8579
|
-
|
|
8580
|
-
|
|
8581
|
-
|
|
8582
|
-
};
|
|
8590
|
+
const member = inBase[1] ?? "";
|
|
8591
|
+
const type = inBase[2] ?? "";
|
|
8592
|
+
const within = (inBase[3] ?? "").split(".").filter((part) => part !== "");
|
|
8593
|
+
const enumName = within.at(-1);
|
|
8594
|
+
return enumName === undefined ? { kind: "member", member, type } : { kind: "enum member", member, type, enum: enumName };
|
|
8583
8595
|
}
|
|
8584
8596
|
function declaresMember(source, missing) {
|
|
8585
8597
|
const name = missing.member.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
8586
|
-
|
|
8587
|
-
|
|
8598
|
+
if (missing.kind === "method") {
|
|
8599
|
+
return new RegExp(String.raw`^\s*(?:static\s+)?func\s+${name}\s*\(`, "m").test(source);
|
|
8600
|
+
}
|
|
8601
|
+
if (missing.kind === "static method") {
|
|
8602
|
+
return new RegExp(String.raw`^\s*static\s+func\s+${name}\s*\(`, "m").test(source);
|
|
8603
|
+
}
|
|
8604
|
+
if (missing.kind === "enum member") {
|
|
8605
|
+
return enumValues(source, missing.enum ?? "").includes(missing.member);
|
|
8606
|
+
}
|
|
8607
|
+
const kinds = missing.kind === "property" ? "var|const|enum|class|signal|func" : "var|const|enum|class|signal";
|
|
8608
|
+
return new RegExp(String.raw`^\s*(?:static\s+)?(?:@\w+(?:\([^)]*\))?\s+)*(?:${kinds})\s+${name}\b`, "m").test(source) || enumValues(source, null).includes(missing.member);
|
|
8609
|
+
}
|
|
8610
|
+
function enumValues(source, named) {
|
|
8611
|
+
const opening = named === null ? /^\s*enum\s*\{([^}]*)\}/gm : new RegExp(String.raw`^\s*enum\s+${named.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\s*\{([^}]*)\}`, "gm");
|
|
8612
|
+
const values = [];
|
|
8613
|
+
for (const [, body] of source.matchAll(opening)) {
|
|
8614
|
+
const uncommented = (body ?? "").split(`
|
|
8615
|
+
`).map((line) => line.replace(/#.*$/, "")).join(`
|
|
8616
|
+
`);
|
|
8617
|
+
for (const part of uncommented.split(",")) {
|
|
8618
|
+
const value = /^\s*([A-Za-z_]\w*)/.exec(part)?.[1];
|
|
8619
|
+
if (value !== undefined) {
|
|
8620
|
+
values.push(value);
|
|
8621
|
+
}
|
|
8622
|
+
}
|
|
8623
|
+
}
|
|
8624
|
+
return values;
|
|
8588
8625
|
}
|
|
8589
8626
|
function unknownTypeIn(message) {
|
|
8590
8627
|
const found = /(?:Could not find (?:base class|type)|Cannot find class|Identifier) "?([A-Za-z_][A-Za-z0-9_]*)"?(?: not declared| in the current scope|\b)/.exec(message);
|
|
@@ -8615,7 +8652,7 @@ function classesNamedIn(source, known) {
|
|
|
8615
8652
|
}
|
|
8616
8653
|
function contradictedDiagnostics(messages, classes, sourceOf) {
|
|
8617
8654
|
const read = new Map;
|
|
8618
|
-
const found =
|
|
8655
|
+
const found = new Map;
|
|
8619
8656
|
for (const message of messages) {
|
|
8620
8657
|
const missing = missingMemberIn(message);
|
|
8621
8658
|
const declaredIn = missing === null ? undefined : classes.get(missing.type);
|
|
@@ -8626,20 +8663,60 @@ function contradictedDiagnostics(messages, classes, sourceOf) {
|
|
|
8626
8663
|
read.set(declaredIn, sourceOf(declaredIn));
|
|
8627
8664
|
}
|
|
8628
8665
|
const source = read.get(declaredIn) ?? null;
|
|
8629
|
-
|
|
8630
|
-
|
|
8666
|
+
const key = [missing.type, missing.enum ?? "", missing.member].join(".");
|
|
8667
|
+
const already = found.get(key);
|
|
8668
|
+
if (source !== null && declaresMember(source, missing) && already?.kind !== "static method") {
|
|
8669
|
+
found.set(key, { ...missing, declaredIn });
|
|
8631
8670
|
}
|
|
8632
8671
|
}
|
|
8633
|
-
return found;
|
|
8672
|
+
return [...found.values()];
|
|
8634
8673
|
}
|
|
8635
8674
|
function staleAnalysisNote(contradicted, dependsOn) {
|
|
8636
8675
|
if (contradicted.length === 0) {
|
|
8637
8676
|
return "";
|
|
8638
8677
|
}
|
|
8639
8678
|
const types = new Set(contradicted.map((one) => one.type));
|
|
8640
|
-
const
|
|
8679
|
+
const onInstances = contradicted.filter((one) => one.kind === "method" || one.kind === "property");
|
|
8680
|
+
const onTheClass = contradicted.filter((one) => one.kind === "member" || one.kind === "static method" || one.kind === "enum member");
|
|
8681
|
+
return [
|
|
8682
|
+
`The editor is reporting against an older copy of ${types.size === 1 ? "a type" : "some types"} ` + "named under contradictedByTheFile. Each member listed is declared in the file the class cache " + "points at, so those diagnostics are wrong however the code is written.",
|
|
8683
|
+
instanceRemedy(onInstances, dependsOn, onTheClass.length > 0),
|
|
8684
|
+
classRemedy(onTheClass)
|
|
8685
|
+
].filter((part) => part !== "").join(" ");
|
|
8686
|
+
}
|
|
8687
|
+
function written(one) {
|
|
8688
|
+
return [one.type, ...one.enum === undefined ? [] : [one.enum], one.member].join(".");
|
|
8689
|
+
}
|
|
8690
|
+
function declaringScripts(entries) {
|
|
8691
|
+
return [...new Set(entries.map((one) => one.declaredIn))].sort();
|
|
8692
|
+
}
|
|
8693
|
+
function instanceRemedy(entries, dependsOn, besideOthers) {
|
|
8694
|
+
if (entries.length === 0) {
|
|
8695
|
+
return "";
|
|
8696
|
+
}
|
|
8697
|
+
const declaring = declaringScripts(entries);
|
|
8641
8698
|
const lever = dependsOn.length === 0 ? ", and the named types depend on no other global class, so that lever is not available here. " + "That is the common case rather than the awkward one: an edit near a type that depends on " + "something refreshes it before the fault is noticed, which is why a type with no " + "dependencies is where this turns up" : `, so changing ${[...dependsOn].sort().join(" or ")} and then rescanning is worth a try`;
|
|
8642
|
-
return
|
|
8699
|
+
return `${besideOthers ? `For ${entries.map(written).join(", ")}, run` : "Run"} editor_rescan first: ` + "it costs about half a second, and it has cleared this in every reproduction measured since the " + "scan timing was fixed: both in one project, the scan returning in 243ms and 275ms, and one in " + "another where the same call also rebuilt the copy. " + "The fault behind it is that a script the editor has loaded keeps the copy it built, and nothing " + `rebuilds that copy when the file changes; ${declaring.length === 1 ? `editor_rescan with reloadScript set to ${declaring[0]} recompiles that copy` : `reloadScript takes one script, so a call each for ${declaring.join(" and ")} recompiles those copies`} from the file into the same ` + "object and answers with the members it has afterwards under reloadedMethods. Read that and the " + "next diagnostics as two readings, because they are of two things: measured in one window on " + "one editor, the analyser resolved a call to a newly added method while the built copy of that " + "same script did not have it. So the reload is worth doing and is not what clears these; the " + "scan is. The other lever measured is that a held type is refreshed when something it " + `depends on changes rather than when it changes itself${lever}. editor_launch restart has always ` + "worked and costs a window. project_import refresh_classes does not, and answers added: [] while " + "this is happening.";
|
|
8700
|
+
}
|
|
8701
|
+
function classRemedy(entries) {
|
|
8702
|
+
if (entries.length === 0) {
|
|
8703
|
+
return "";
|
|
8704
|
+
}
|
|
8705
|
+
const declaring = declaringScripts(entries);
|
|
8706
|
+
const reload = declaring.length === 1 ? `editor_rescan with reloadScript set to ${declaring[0]}` : `editor_rescan with reloadScript, a call each for ${declaring.join(" and ")} since it takes one script,`;
|
|
8707
|
+
return `For ${entries.map(written).join(", ")}, run ${reload} first. In the one project that has ` + "reproduced a stale enum member, twice, the reload cleared it at once both times, and the one " + "plain editor_rescan tried left the diagnostic standing.";
|
|
8708
|
+
}
|
|
8709
|
+
function failedReloadNote(failed) {
|
|
8710
|
+
const missing = (before, after) => before.filter((name) => !after.includes(name));
|
|
8711
|
+
const lost = [
|
|
8712
|
+
...missing(failed.heldBefore, failed.heldAfter),
|
|
8713
|
+
...missing(failed.constantsBefore, failed.constantsAfter)
|
|
8714
|
+
];
|
|
8715
|
+
const unchanged = lost.length === 0 && missing(failed.heldAfter, failed.heldBefore).length === 0 && missing(failed.constantsAfter, failed.constantsBefore).length === 0;
|
|
8716
|
+
const answered = failed.codeName === "" ? `error ${failed.code}` : `error ${failed.code}, ${failed.codeName}`;
|
|
8717
|
+
const copy = unchanged ? "The copy the editor holds is as it was, with the members under heldBeforeReload and heldConstantsBeforeReload." : `The copy the editor holds changed even so${lost.length === 0 ? "" : `, and has lost ${lost.join(", ")}`}: anything diagnosed against it now is answered from what is left, and reloading it once it compiles rebuilds it.`;
|
|
8718
|
+
const order = failed.names.length === 0 ? "" : ` If they deny a member of ${failed.names.join(" or ")} that its file declares, the editor's copy of that class is behind: reload that class first with reloadScript and then this script, the order that cleared it in the one project that has met this.`;
|
|
8719
|
+
return `${failed.script} did not compile, so nothing was reloaded: Godot answered ${answered}. ${copy} ` + `script_diagnostics on it gives the errors.${order}`;
|
|
8643
8720
|
}
|
|
8644
8721
|
function uncachedClassNote(types) {
|
|
8645
8722
|
if (types.length === 0) {
|
|
@@ -17055,7 +17132,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17055
17132
|
},
|
|
17056
17133
|
{
|
|
17057
17134
|
name: "script_diagnostics",
|
|
17058
|
-
description: "Errors and warnings for a script from the editor's language server, and whether the script is clean. Needs the editor running. Diagnostics are checked against the project's own files, and the two that the files disprove are named rather than passed on: a member the class cache's file declares comes back under contradictedByTheFile, and a class this project declares that the diagnostic could not resolve comes back under typesTheEditorHasNotLoaded, with inTheClassCache saying whether a launched game would resolve it. staleAnalysis then says what to do, and for both states it is editor_rescan first: it asks the editor already running, starts nothing, and has cleared each in every reproduction measured.
|
|
17135
|
+
description: "Errors and warnings for a script from the editor's language server, and whether the script is clean. Needs the editor running. Diagnostics are checked against the project's own files, and the two that the files disprove are named rather than passed on: a member the class cache's file declares, a method, property, constant, static function or enum member, comes back under contradictedByTheFile, and a class this project declares that the diagnostic could not resolve comes back under typesTheEditorHasNotLoaded, with inTheClassCache saying whether a launched game would resolve it. staleAnalysis then says what to do, and for both states it is editor_rescan first: it asks the editor already running, starts nothing, and has cleared each in every reproduction measured. Two exceptions are named when they apply. For an enum member, constant or static function of a global class the reload comes first: in the one project that has reproduced a stale enum member, editor_rescan with reloadScript on the declaring script cleared it and a plain rescan did not. And an editor whose last scan wrote the class cache shorter than the file holds a list behind the files and writes it on every scan, so on that editor the rescan loads nothing and staleAnalysis says editor_launch restart instead. What follows differs: for a stale analysed type, editor_rescan with reloadScript recompiles the held copy and editor_launch restart is the last resort that costs a window; for a class missing from the cache, project_import refresh_classes rewrites the cache from disk at the cost of a short headless engine, which matters beside a running bench.",
|
|
17059
17136
|
parameters: {
|
|
17060
17137
|
projectPath: PROJECT_PATH,
|
|
17061
17138
|
scriptPath: SCRIPT_PATH
|
|
@@ -17276,7 +17353,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17276
17353
|
},
|
|
17277
17354
|
reloadScript: {
|
|
17278
17355
|
type: "string",
|
|
17279
|
-
description: "A script to recompile in the editor after the scan, for the half a scan does not reach: a script the editor has loaded keeps the copy it built, that copy is refreshed when one of its dependencies changes rather than when it changes itself, and a scan leaves it alone. Measured here, a built copy comes back without a method added to it while script_diagnostics on the same script reads clean in the same window, so the copy the editor built and whatever the analyser resolves types against are not known to be one thing: reload for the copy, and read the diagnostics as a separate answer rather than as this one confirmed. This recompiles into the same object, so the holders that kept the stale copy alive see the new one. The methods it has afterwards come back under reloadedMethods, because a reload that compiled nothing and answered OK is the failure worth catching."
|
|
17356
|
+
description: "A script to recompile in the editor after the scan, for the half a scan does not reach: a script the editor has loaded keeps the copy it built, that copy is refreshed when one of its dependencies changes rather than when it changes itself, and a scan leaves it alone. Measured here, a built copy comes back without a method added to it while script_diagnostics on the same script reads clean in the same window, so the copy the editor built and whatever the analyser resolves types against are not known to be one thing: reload for the copy, and read the diagnostics as a separate answer rather than as this one confirmed. This recompiles into the same object, so the holders that kept the stale copy alive see the new one. The methods it has afterwards come back under reloadedMethods, because a reload that compiled nothing and answered OK is the failure worth catching, and what it held before under heldBeforeReload. Its constants come back the same way, under heldConstantsBeforeReload and reloadedConstants, with each enum value as Kind.SHORT: an enum member is held as a constant and never shows among the methods. A script that does not compile is not reloaded: reloadProblem gives Godot's error, says whether the copy the editor holds survived, and names the classes the file uses, whose held copies being behind is what failed one before."
|
|
17280
17357
|
}
|
|
17281
17358
|
},
|
|
17282
17359
|
requires: ["projectPath"]
|
|
@@ -17537,7 +17614,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17537
17614
|
pressed: {
|
|
17538
17615
|
type: "boolean",
|
|
17539
17616
|
ops: ["action", "key", "mouse_click"],
|
|
17540
|
-
description: "action, key: leave it out and the press is a whole one, down and up a frame apart. true holds it down, false lets go of one being held.
|
|
17617
|
+
description: "action, key, mouse_click: leave it out and the press is a whole one, down and up a frame apart. true holds it down, false lets go of one being held. A wheel button held down is still a whole step, since a wheel is never held."
|
|
17541
17618
|
},
|
|
17542
17619
|
strength: {
|
|
17543
17620
|
type: "number",
|
|
@@ -17621,7 +17698,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17621
17698
|
requires: ["text"]
|
|
17622
17699
|
},
|
|
17623
17700
|
mouse_click: {
|
|
17624
|
-
summary: "
|
|
17701
|
+
summary: "click a mouse button at a position, pressed and released a frame apart, each event carrying the buttons held once it has happened; the answer says whole. A drag is a press held with pressed true, motions, and a release with pressed false. A wheel button is always a whole step, press and release together as a mouse sends one: a lone wheel press holds the viewport on the control that took it, so every click after it lands there",
|
|
17625
17702
|
requires: ["x", "y"]
|
|
17626
17703
|
},
|
|
17627
17704
|
mouse_motion: {
|
|
@@ -41232,6 +41309,11 @@ class GodotServer {
|
|
|
41232
41309
|
shortListEditorPid: this.godotBridge.getStatus().editorPid ?? null
|
|
41233
41310
|
});
|
|
41234
41311
|
}
|
|
41312
|
+
classesAScriptNames(projectPath, scriptPath) {
|
|
41313
|
+
const source = this.sourceOfClass(projectPath, scriptPath);
|
|
41314
|
+
const known = cachedClasses(projectPath) ?? declaredClasses(projectPath);
|
|
41315
|
+
return source === null ? [] : classesNamedIn(source, known).filter((name) => known.get(name) !== scriptPath);
|
|
41316
|
+
}
|
|
41235
41317
|
sourceOfClass(projectPath, resourcePath) {
|
|
41236
41318
|
const contained = resolveWithinProject(projectPath, resourcePath);
|
|
41237
41319
|
if (!contained.ok || !existsSync15(contained.absolutePath)) {
|
|
@@ -42958,8 +43040,34 @@ class GodotServer {
|
|
|
42958
43040
|
try {
|
|
42959
43041
|
const answer = asParams(await this.godotBridge.invokeTool("reload_script", { ...args, scriptPath: reloading }));
|
|
42960
43042
|
const methods = readArray(answer, "methods");
|
|
42961
|
-
const
|
|
42962
|
-
|
|
43043
|
+
const listed = (field) => {
|
|
43044
|
+
const found = readArray(answer, field);
|
|
43045
|
+
return found ? { [field]: found.map(String) } : {};
|
|
43046
|
+
};
|
|
43047
|
+
const failed = answer["failed"];
|
|
43048
|
+
if (typeof failed === "number" && failed !== 0) {
|
|
43049
|
+
reloaded = {
|
|
43050
|
+
...listed("heldBefore"),
|
|
43051
|
+
...listed("heldConstantsBefore"),
|
|
43052
|
+
problem: failedReloadNote({
|
|
43053
|
+
script: reloading,
|
|
43054
|
+
code: failed,
|
|
43055
|
+
codeName: readString(answer, "failedAs") ?? "",
|
|
43056
|
+
heldBefore: listed("heldBefore")["heldBefore"] ?? [],
|
|
43057
|
+
heldAfter: methods?.map(String) ?? [],
|
|
43058
|
+
constantsBefore: listed("heldConstantsBefore")["heldConstantsBefore"] ?? [],
|
|
43059
|
+
constantsAfter: listed("constants")["constants"] ?? [],
|
|
43060
|
+
names: projectPath === "" ? [] : this.classesAScriptNames(projectPath, reloading)
|
|
43061
|
+
})
|
|
43062
|
+
};
|
|
43063
|
+
} else {
|
|
43064
|
+
reloaded = methods ? {
|
|
43065
|
+
methods: methods.map(String),
|
|
43066
|
+
...listed("heldBefore"),
|
|
43067
|
+
...listed("constants"),
|
|
43068
|
+
...listed("heldConstantsBefore")
|
|
43069
|
+
} : { problem: `the editor would not say what ${reloading} has after reloading it` };
|
|
43070
|
+
}
|
|
42963
43071
|
} catch (error) {
|
|
42964
43072
|
reloaded = { problem: `${reloading} could not be reloaded: ${errorMessage(error)}` };
|
|
42965
43073
|
}
|
|
@@ -42994,6 +43102,8 @@ class GodotServer {
|
|
|
42994
43102
|
waitedForEditorScanMs: waitedForEditorMs > 0 ? waitedForEditorMs : undefined,
|
|
42995
43103
|
heldBeforeReload: reloaded.heldBefore,
|
|
42996
43104
|
reloadedMethods: reloaded.methods,
|
|
43105
|
+
heldConstantsBeforeReload: reloaded.heldConstantsBefore,
|
|
43106
|
+
reloadedConstants: reloaded.constants,
|
|
42997
43107
|
reloadProblem: reloaded.problem,
|
|
42998
43108
|
unseenByEditor: unseen.length > 0 ? unseen : undefined,
|
|
42999
43109
|
broughtIn: broughtIn.length > 0 ? broughtIn : undefined,
|
|
@@ -43020,7 +43130,13 @@ class GodotServer {
|
|
|
43020
43130
|
}
|
|
43021
43131
|
try {
|
|
43022
43132
|
const answer = asParams(await this.godotBridge.invokeTool("reload_script", { scriptPath }));
|
|
43023
|
-
|
|
43133
|
+
const failed = answer["failed"];
|
|
43134
|
+
if (typeof failed === "number" && failed !== 0) {
|
|
43135
|
+
notReloaded.push({
|
|
43136
|
+
scriptPath,
|
|
43137
|
+
problem: `it did not compile: Godot answered error ${failed}${readString(answer, "failedAs") ? `, ${readString(answer, "failedAs")}` : ""}`
|
|
43138
|
+
});
|
|
43139
|
+
} else if (readArray(answer, "methods") === undefined) {
|
|
43024
43140
|
notReloaded.push({
|
|
43025
43141
|
scriptPath,
|
|
43026
43142
|
problem: "the editor would not say what it has after reloading it"
|
|
@@ -64,6 +64,7 @@ func reload_script(args: Dictionary) -> Dictionary:
|
|
|
64
64
|
# than the one that mends it. Without it a caller cannot tell a copy that was already current
|
|
65
65
|
# from one this call repaired, and those are different facts about their editor.
|
|
66
66
|
var held: Array[String] = _method_names(script)
|
|
67
|
+
var held_constants: Array[String] = _constant_names(script)
|
|
67
68
|
|
|
68
69
|
# The source is read off disk and put back before reloading, which is the whole of the fix and
|
|
69
70
|
# is not obvious. `reload` recompiles from the object's own `source_code`, not from the file, so
|
|
@@ -73,14 +74,27 @@ func reload_script(args: Dictionary) -> Dictionary:
|
|
|
73
74
|
var reader: FileAccess = FileAccess.open(path, FileAccess.READ)
|
|
74
75
|
if reader == null:
|
|
75
76
|
return {"ok": false, "error": "Could not read " + path + ": " + str(FileAccess.get_open_error())}
|
|
77
|
+
var previous: String = script.source_code
|
|
76
78
|
script.source_code = reader.get_as_text()
|
|
77
79
|
reader.close()
|
|
78
80
|
|
|
81
|
+
# A reload that fails is still answered as a call that ran, because the readings are what a
|
|
82
|
+
# caller needs then: a failure travels back as its message alone, and a project met one and
|
|
83
|
+
# could not tell whether the copy had been damaged by it or before it. The old text goes back
|
|
84
|
+
# into the object, so what it holds and the source it says it was built from still agree.
|
|
79
85
|
var failed: Error = script.reload(true)
|
|
80
86
|
if failed != OK:
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
87
|
+
script.source_code = previous
|
|
88
|
+
return {
|
|
89
|
+
"ok": true,
|
|
90
|
+
"script": path,
|
|
91
|
+
"failed": failed,
|
|
92
|
+
"failedAs": error_string(failed) if failed != OK else "",
|
|
93
|
+
"heldBefore": held,
|
|
94
|
+
"methods": _method_names(script),
|
|
95
|
+
"heldConstantsBefore": held_constants,
|
|
96
|
+
"constants": _constant_names(script),
|
|
97
|
+
}
|
|
84
98
|
|
|
85
99
|
|
|
86
100
|
func _method_names(script: GDScript) -> Array[String]:
|
|
@@ -91,6 +105,24 @@ func _method_names(script: GDScript) -> Array[String]:
|
|
|
91
105
|
return names
|
|
92
106
|
|
|
93
107
|
|
|
108
|
+
## The script's own constants, and the keys of each one that is a Dictionary as `Kind.SHORT`, which
|
|
109
|
+
## is how the copy holds a named enum: a stale enum member is denied as a member of the enum rather
|
|
110
|
+
## than of the class. A constant Dictionary that is not an enum is listed the same way, since the
|
|
111
|
+
## map does not tell the two apart.
|
|
112
|
+
func _constant_names(script: GDScript) -> Array[String]:
|
|
113
|
+
var names: Array[String] = []
|
|
114
|
+
var constants: Dictionary = script.get_script_constant_map()
|
|
115
|
+
for named: Variant in constants:
|
|
116
|
+
var value: Variant = constants[named]
|
|
117
|
+
names.append(str(named))
|
|
118
|
+
if value is Dictionary:
|
|
119
|
+
var values: Dictionary = value
|
|
120
|
+
for member: Variant in values:
|
|
121
|
+
names.append("%s.%s" % [named, member])
|
|
122
|
+
names.sort()
|
|
123
|
+
return names
|
|
124
|
+
|
|
125
|
+
|
|
94
126
|
func global_classes(_args: Dictionary) -> Dictionary:
|
|
95
127
|
if not _editor_plugin:
|
|
96
128
|
return {"ok": false, "error": "Editor plugin unavailable"}
|
|
@@ -353,6 +353,14 @@ func _read_point(params: Dictionary, x_key: String, y_key: String, pair_key: Str
|
|
|
353
353
|
return "%s must be Vector2 or [x, y]" % pair_key
|
|
354
354
|
|
|
355
355
|
|
|
356
|
+
## Clicks a mouse button at a position, and lets go of it again unless asked to hold it.
|
|
357
|
+
##
|
|
358
|
+
## The whole click by default, for the reason [method inject_action] is the whole press, and
|
|
359
|
+
## because much of a UI acts on the release: a rich label opens its link then, so a click on one
|
|
360
|
+
## answered input_injected and opened nothing, while the same op on the wheel was already whole.
|
|
361
|
+
##
|
|
362
|
+
## `pressed` is how a caller asks for one half, the way a drag is made: true holds the button down,
|
|
363
|
+
## false lets go of one being held.
|
|
356
364
|
func inject_mouse_click(params: Dictionary) -> Dictionary:
|
|
357
365
|
var point: Variant = _read_point(params, "x", "y", "position")
|
|
358
366
|
if point is String:
|
|
@@ -361,32 +369,37 @@ func inject_mouse_click(params: Dictionary) -> Dictionary:
|
|
|
361
369
|
var button: int = _resolve_mouse_button(params.get("button", MOUSE_BUTTON_LEFT))
|
|
362
370
|
if button < 0:
|
|
363
371
|
return _no_such_button(params.get("button"))
|
|
364
|
-
var
|
|
372
|
+
var held: bool = Read.as_bool(params.get("pressed", true), true)
|
|
365
373
|
var double: bool = Read.as_bool(params.get("doubleClick", false))
|
|
366
|
-
|
|
367
|
-
# A button pressed somewhere puts the pointer there: the motion after a held press measures
|
|
368
|
-
# its drag from the press, which is where a real pointer would be.
|
|
369
|
-
var _moved: Vector2 = _arrive(position)
|
|
370
|
-
Input.parse_input_event(_button(position, button, pressed, double))
|
|
371
374
|
# A wheel step is a press and a release together, as a mouse sends one: there is no holding a
|
|
372
375
|
# wheel. A lone wheel press left the viewport's mouse focus on the control that took it, with
|
|
373
376
|
# the wheel's bit in the focus mask, and every click after it landed on that control rather
|
|
374
377
|
# than under the pointer, landing true and doing nothing, until a release was sent by hand.
|
|
375
|
-
var notch: bool =
|
|
378
|
+
var notch: bool = held and _mask_of(button) == 0
|
|
379
|
+
var whole: bool = notch or not params.has("pressed")
|
|
380
|
+
|
|
381
|
+
# A button pressed somewhere puts the pointer there: the motion after a held press measures
|
|
382
|
+
# its drag from the press, which is where a real pointer would be.
|
|
383
|
+
var _moved: Vector2 = _arrive(position)
|
|
384
|
+
Input.parse_input_event(_button(position, button, whole or held, double))
|
|
376
385
|
if notch:
|
|
377
386
|
Input.parse_input_event(_button(position, button, false, false))
|
|
387
|
+
elif whole:
|
|
388
|
+
# A frame between the halves, as inject_action has, so a control that acts on the press and
|
|
389
|
+
# one that acts on the release each get a frame to do it in.
|
|
390
|
+
await _host.get_tree().process_frame
|
|
391
|
+
Input.parse_input_event(_button(position, button, false, false))
|
|
392
|
+
await _host.get_tree().process_frame
|
|
378
393
|
|
|
379
|
-
|
|
394
|
+
return {
|
|
380
395
|
"type": "input_injected",
|
|
381
396
|
"input_type": "mouse_click",
|
|
382
397
|
"position": [position.x, position.y],
|
|
383
398
|
"button": button,
|
|
384
|
-
"pressed":
|
|
399
|
+
"pressed": held and not whole,
|
|
400
|
+
"whole": whole,
|
|
385
401
|
"double": double
|
|
386
402
|
}
|
|
387
|
-
if notch:
|
|
388
|
-
answer["released"] = true
|
|
389
|
-
return answer
|
|
390
403
|
|
|
391
404
|
|
|
392
405
|
## Moves the pointer to a position, with the movement the event carries taken from where the
|
package/build/index.js
CHANGED
|
@@ -26141,20 +26141,57 @@ function withAddonClassesCounted(rebuilt, listed) {
|
|
|
26141
26141
|
return counted;
|
|
26142
26142
|
}
|
|
26143
26143
|
function missingMemberIn(message) {
|
|
26144
|
-
const
|
|
26145
|
-
if (
|
|
26144
|
+
const inferred = /The (method|property) "([^"(]+)(?:\(\))?" is not present on the inferred type "([^"]+)"/.exec(message);
|
|
26145
|
+
if (inferred !== null) {
|
|
26146
|
+
return {
|
|
26147
|
+
kind: inferred[1] === "property" ? "property" : "method",
|
|
26148
|
+
member: inferred[2] ?? "",
|
|
26149
|
+
type: inferred[3] ?? ""
|
|
26150
|
+
};
|
|
26151
|
+
}
|
|
26152
|
+
const called = /Static function "([A-Za-z_]\w*)\(\)" not found in base "([A-Za-z_]\w*)"/.exec(message);
|
|
26153
|
+
if (called !== null) {
|
|
26154
|
+
return { kind: "static method", member: called[1] ?? "", type: called[2] ?? "" };
|
|
26155
|
+
}
|
|
26156
|
+
const inBase = /Cannot find member "([A-Za-z_]\w*)" in base "([A-Za-z_]\w*)((?:\.[A-Za-z_]\w*)*)"/.exec(message);
|
|
26157
|
+
if (inBase === null) {
|
|
26146
26158
|
return null;
|
|
26147
26159
|
}
|
|
26148
|
-
|
|
26149
|
-
|
|
26150
|
-
|
|
26151
|
-
|
|
26152
|
-
};
|
|
26160
|
+
const member = inBase[1] ?? "";
|
|
26161
|
+
const type = inBase[2] ?? "";
|
|
26162
|
+
const within = (inBase[3] ?? "").split(".").filter((part) => part !== "");
|
|
26163
|
+
const enumName = within.at(-1);
|
|
26164
|
+
return enumName === undefined ? { kind: "member", member, type } : { kind: "enum member", member, type, enum: enumName };
|
|
26153
26165
|
}
|
|
26154
26166
|
function declaresMember(source, missing) {
|
|
26155
26167
|
const name = missing.member.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
26156
|
-
|
|
26157
|
-
|
|
26168
|
+
if (missing.kind === "method") {
|
|
26169
|
+
return new RegExp(String.raw`^\s*(?:static\s+)?func\s+${name}\s*\(`, "m").test(source);
|
|
26170
|
+
}
|
|
26171
|
+
if (missing.kind === "static method") {
|
|
26172
|
+
return new RegExp(String.raw`^\s*static\s+func\s+${name}\s*\(`, "m").test(source);
|
|
26173
|
+
}
|
|
26174
|
+
if (missing.kind === "enum member") {
|
|
26175
|
+
return enumValues(source, missing.enum ?? "").includes(missing.member);
|
|
26176
|
+
}
|
|
26177
|
+
const kinds = missing.kind === "property" ? "var|const|enum|class|signal|func" : "var|const|enum|class|signal";
|
|
26178
|
+
return new RegExp(String.raw`^\s*(?:static\s+)?(?:@\w+(?:\([^)]*\))?\s+)*(?:${kinds})\s+${name}\b`, "m").test(source) || enumValues(source, null).includes(missing.member);
|
|
26179
|
+
}
|
|
26180
|
+
function enumValues(source, named) {
|
|
26181
|
+
const opening = named === null ? /^\s*enum\s*\{([^}]*)\}/gm : new RegExp(String.raw`^\s*enum\s+${named.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\s*\{([^}]*)\}`, "gm");
|
|
26182
|
+
const values = [];
|
|
26183
|
+
for (const [, body] of source.matchAll(opening)) {
|
|
26184
|
+
const uncommented = (body ?? "").split(`
|
|
26185
|
+
`).map((line) => line.replace(/#.*$/, "")).join(`
|
|
26186
|
+
`);
|
|
26187
|
+
for (const part of uncommented.split(",")) {
|
|
26188
|
+
const value = /^\s*([A-Za-z_]\w*)/.exec(part)?.[1];
|
|
26189
|
+
if (value !== undefined) {
|
|
26190
|
+
values.push(value);
|
|
26191
|
+
}
|
|
26192
|
+
}
|
|
26193
|
+
}
|
|
26194
|
+
return values;
|
|
26158
26195
|
}
|
|
26159
26196
|
function unknownTypeIn(message) {
|
|
26160
26197
|
const found = /(?:Could not find (?:base class|type)|Cannot find class|Identifier) "?([A-Za-z_][A-Za-z0-9_]*)"?(?: not declared| in the current scope|\b)/.exec(message);
|
|
@@ -26185,7 +26222,7 @@ function classesNamedIn(source, known) {
|
|
|
26185
26222
|
}
|
|
26186
26223
|
function contradictedDiagnostics(messages, classes, sourceOf) {
|
|
26187
26224
|
const read = new Map;
|
|
26188
|
-
const found =
|
|
26225
|
+
const found = new Map;
|
|
26189
26226
|
for (const message of messages) {
|
|
26190
26227
|
const missing = missingMemberIn(message);
|
|
26191
26228
|
const declaredIn = missing === null ? undefined : classes.get(missing.type);
|
|
@@ -26196,20 +26233,60 @@ function contradictedDiagnostics(messages, classes, sourceOf) {
|
|
|
26196
26233
|
read.set(declaredIn, sourceOf(declaredIn));
|
|
26197
26234
|
}
|
|
26198
26235
|
const source = read.get(declaredIn) ?? null;
|
|
26199
|
-
|
|
26200
|
-
|
|
26236
|
+
const key = [missing.type, missing.enum ?? "", missing.member].join(".");
|
|
26237
|
+
const already = found.get(key);
|
|
26238
|
+
if (source !== null && declaresMember(source, missing) && already?.kind !== "static method") {
|
|
26239
|
+
found.set(key, { ...missing, declaredIn });
|
|
26201
26240
|
}
|
|
26202
26241
|
}
|
|
26203
|
-
return found;
|
|
26242
|
+
return [...found.values()];
|
|
26204
26243
|
}
|
|
26205
26244
|
function staleAnalysisNote(contradicted, dependsOn) {
|
|
26206
26245
|
if (contradicted.length === 0) {
|
|
26207
26246
|
return "";
|
|
26208
26247
|
}
|
|
26209
26248
|
const types = new Set(contradicted.map((one) => one.type));
|
|
26210
|
-
const
|
|
26249
|
+
const onInstances = contradicted.filter((one) => one.kind === "method" || one.kind === "property");
|
|
26250
|
+
const onTheClass = contradicted.filter((one) => one.kind === "member" || one.kind === "static method" || one.kind === "enum member");
|
|
26251
|
+
return [
|
|
26252
|
+
`The editor is reporting against an older copy of ${types.size === 1 ? "a type" : "some types"} ` + "named under contradictedByTheFile. Each member listed is declared in the file the class cache " + "points at, so those diagnostics are wrong however the code is written.",
|
|
26253
|
+
instanceRemedy(onInstances, dependsOn, onTheClass.length > 0),
|
|
26254
|
+
classRemedy(onTheClass)
|
|
26255
|
+
].filter((part) => part !== "").join(" ");
|
|
26256
|
+
}
|
|
26257
|
+
function written(one) {
|
|
26258
|
+
return [one.type, ...one.enum === undefined ? [] : [one.enum], one.member].join(".");
|
|
26259
|
+
}
|
|
26260
|
+
function declaringScripts(entries) {
|
|
26261
|
+
return [...new Set(entries.map((one) => one.declaredIn))].sort();
|
|
26262
|
+
}
|
|
26263
|
+
function instanceRemedy(entries, dependsOn, besideOthers) {
|
|
26264
|
+
if (entries.length === 0) {
|
|
26265
|
+
return "";
|
|
26266
|
+
}
|
|
26267
|
+
const declaring = declaringScripts(entries);
|
|
26211
26268
|
const lever = dependsOn.length === 0 ? ", and the named types depend on no other global class, so that lever is not available here. " + "That is the common case rather than the awkward one: an edit near a type that depends on " + "something refreshes it before the fault is noticed, which is why a type with no " + "dependencies is where this turns up" : `, so changing ${[...dependsOn].sort().join(" or ")} and then rescanning is worth a try`;
|
|
26212
|
-
return
|
|
26269
|
+
return `${besideOthers ? `For ${entries.map(written).join(", ")}, run` : "Run"} editor_rescan first: ` + "it costs about half a second, and it has cleared this in every reproduction measured since the " + "scan timing was fixed: both in one project, the scan returning in 243ms and 275ms, and one in " + "another where the same call also rebuilt the copy. " + "The fault behind it is that a script the editor has loaded keeps the copy it built, and nothing " + `rebuilds that copy when the file changes; ${declaring.length === 1 ? `editor_rescan with reloadScript set to ${declaring[0]} recompiles that copy` : `reloadScript takes one script, so a call each for ${declaring.join(" and ")} recompiles those copies`} from the file into the same ` + "object and answers with the members it has afterwards under reloadedMethods. Read that and the " + "next diagnostics as two readings, because they are of two things: measured in one window on " + "one editor, the analyser resolved a call to a newly added method while the built copy of that " + "same script did not have it. So the reload is worth doing and is not what clears these; the " + "scan is. The other lever measured is that a held type is refreshed when something it " + `depends on changes rather than when it changes itself${lever}. editor_launch restart has always ` + "worked and costs a window. project_import refresh_classes does not, and answers added: [] while " + "this is happening.";
|
|
26270
|
+
}
|
|
26271
|
+
function classRemedy(entries) {
|
|
26272
|
+
if (entries.length === 0) {
|
|
26273
|
+
return "";
|
|
26274
|
+
}
|
|
26275
|
+
const declaring = declaringScripts(entries);
|
|
26276
|
+
const reload = declaring.length === 1 ? `editor_rescan with reloadScript set to ${declaring[0]}` : `editor_rescan with reloadScript, a call each for ${declaring.join(" and ")} since it takes one script,`;
|
|
26277
|
+
return `For ${entries.map(written).join(", ")}, run ${reload} first. In the one project that has ` + "reproduced a stale enum member, twice, the reload cleared it at once both times, and the one " + "plain editor_rescan tried left the diagnostic standing.";
|
|
26278
|
+
}
|
|
26279
|
+
function failedReloadNote(failed) {
|
|
26280
|
+
const missing = (before, after) => before.filter((name) => !after.includes(name));
|
|
26281
|
+
const lost = [
|
|
26282
|
+
...missing(failed.heldBefore, failed.heldAfter),
|
|
26283
|
+
...missing(failed.constantsBefore, failed.constantsAfter)
|
|
26284
|
+
];
|
|
26285
|
+
const unchanged = lost.length === 0 && missing(failed.heldAfter, failed.heldBefore).length === 0 && missing(failed.constantsAfter, failed.constantsBefore).length === 0;
|
|
26286
|
+
const answered = failed.codeName === "" ? `error ${failed.code}` : `error ${failed.code}, ${failed.codeName}`;
|
|
26287
|
+
const copy = unchanged ? "The copy the editor holds is as it was, with the members under heldBeforeReload and heldConstantsBeforeReload." : `The copy the editor holds changed even so${lost.length === 0 ? "" : `, and has lost ${lost.join(", ")}`}: anything diagnosed against it now is answered from what is left, and reloading it once it compiles rebuilds it.`;
|
|
26288
|
+
const order = failed.names.length === 0 ? "" : ` If they deny a member of ${failed.names.join(" or ")} that its file declares, the editor's copy of that class is behind: reload that class first with reloadScript and then this script, the order that cleared it in the one project that has met this.`;
|
|
26289
|
+
return `${failed.script} did not compile, so nothing was reloaded: Godot answered ${answered}. ${copy} ` + `script_diagnostics on it gives the errors.${order}`;
|
|
26213
26290
|
}
|
|
26214
26291
|
function uncachedClassNote(types) {
|
|
26215
26292
|
if (types.length === 0) {
|
|
@@ -30712,7 +30789,7 @@ var TOOL_SPECS = [
|
|
|
30712
30789
|
},
|
|
30713
30790
|
{
|
|
30714
30791
|
name: "script_diagnostics",
|
|
30715
|
-
description: "Errors and warnings for a script from the editor's language server, and whether the script is clean. Needs the editor running. Diagnostics are checked against the project's own files, and the two that the files disprove are named rather than passed on: a member the class cache's file declares comes back under contradictedByTheFile, and a class this project declares that the diagnostic could not resolve comes back under typesTheEditorHasNotLoaded, with inTheClassCache saying whether a launched game would resolve it. staleAnalysis then says what to do, and for both states it is editor_rescan first: it asks the editor already running, starts nothing, and has cleared each in every reproduction measured.
|
|
30792
|
+
description: "Errors and warnings for a script from the editor's language server, and whether the script is clean. Needs the editor running. Diagnostics are checked against the project's own files, and the two that the files disprove are named rather than passed on: a member the class cache's file declares, a method, property, constant, static function or enum member, comes back under contradictedByTheFile, and a class this project declares that the diagnostic could not resolve comes back under typesTheEditorHasNotLoaded, with inTheClassCache saying whether a launched game would resolve it. staleAnalysis then says what to do, and for both states it is editor_rescan first: it asks the editor already running, starts nothing, and has cleared each in every reproduction measured. Two exceptions are named when they apply. For an enum member, constant or static function of a global class the reload comes first: in the one project that has reproduced a stale enum member, editor_rescan with reloadScript on the declaring script cleared it and a plain rescan did not. And an editor whose last scan wrote the class cache shorter than the file holds a list behind the files and writes it on every scan, so on that editor the rescan loads nothing and staleAnalysis says editor_launch restart instead. What follows differs: for a stale analysed type, editor_rescan with reloadScript recompiles the held copy and editor_launch restart is the last resort that costs a window; for a class missing from the cache, project_import refresh_classes rewrites the cache from disk at the cost of a short headless engine, which matters beside a running bench.",
|
|
30716
30793
|
parameters: {
|
|
30717
30794
|
projectPath: PROJECT_PATH,
|
|
30718
30795
|
scriptPath: SCRIPT_PATH
|
|
@@ -30933,7 +31010,7 @@ var TOOL_SPECS = [
|
|
|
30933
31010
|
},
|
|
30934
31011
|
reloadScript: {
|
|
30935
31012
|
type: "string",
|
|
30936
|
-
description: "A script to recompile in the editor after the scan, for the half a scan does not reach: a script the editor has loaded keeps the copy it built, that copy is refreshed when one of its dependencies changes rather than when it changes itself, and a scan leaves it alone. Measured here, a built copy comes back without a method added to it while script_diagnostics on the same script reads clean in the same window, so the copy the editor built and whatever the analyser resolves types against are not known to be one thing: reload for the copy, and read the diagnostics as a separate answer rather than as this one confirmed. This recompiles into the same object, so the holders that kept the stale copy alive see the new one. The methods it has afterwards come back under reloadedMethods, because a reload that compiled nothing and answered OK is the failure worth catching."
|
|
31013
|
+
description: "A script to recompile in the editor after the scan, for the half a scan does not reach: a script the editor has loaded keeps the copy it built, that copy is refreshed when one of its dependencies changes rather than when it changes itself, and a scan leaves it alone. Measured here, a built copy comes back without a method added to it while script_diagnostics on the same script reads clean in the same window, so the copy the editor built and whatever the analyser resolves types against are not known to be one thing: reload for the copy, and read the diagnostics as a separate answer rather than as this one confirmed. This recompiles into the same object, so the holders that kept the stale copy alive see the new one. The methods it has afterwards come back under reloadedMethods, because a reload that compiled nothing and answered OK is the failure worth catching, and what it held before under heldBeforeReload. Its constants come back the same way, under heldConstantsBeforeReload and reloadedConstants, with each enum value as Kind.SHORT: an enum member is held as a constant and never shows among the methods. A script that does not compile is not reloaded: reloadProblem gives Godot's error, says whether the copy the editor holds survived, and names the classes the file uses, whose held copies being behind is what failed one before."
|
|
30937
31014
|
}
|
|
30938
31015
|
},
|
|
30939
31016
|
requires: ["projectPath"]
|
|
@@ -31194,7 +31271,7 @@ var TOOL_SPECS = [
|
|
|
31194
31271
|
pressed: {
|
|
31195
31272
|
type: "boolean",
|
|
31196
31273
|
ops: ["action", "key", "mouse_click"],
|
|
31197
|
-
description: "action, key: leave it out and the press is a whole one, down and up a frame apart. true holds it down, false lets go of one being held.
|
|
31274
|
+
description: "action, key, mouse_click: leave it out and the press is a whole one, down and up a frame apart. true holds it down, false lets go of one being held. A wheel button held down is still a whole step, since a wheel is never held."
|
|
31198
31275
|
},
|
|
31199
31276
|
strength: {
|
|
31200
31277
|
type: "number",
|
|
@@ -31278,7 +31355,7 @@ var TOOL_SPECS = [
|
|
|
31278
31355
|
requires: ["text"]
|
|
31279
31356
|
},
|
|
31280
31357
|
mouse_click: {
|
|
31281
|
-
summary: "
|
|
31358
|
+
summary: "click a mouse button at a position, pressed and released a frame apart, each event carrying the buttons held once it has happened; the answer says whole. A drag is a press held with pressed true, motions, and a release with pressed false. A wheel button is always a whole step, press and release together as a mouse sends one: a lone wheel press holds the viewport on the control that took it, so every click after it lands there",
|
|
31282
31359
|
requires: ["x", "y"]
|
|
31283
31360
|
},
|
|
31284
31361
|
mouse_motion: {
|
|
@@ -33692,6 +33769,11 @@ class GodotServer {
|
|
|
33692
33769
|
shortListEditorPid: this.godotBridge.getStatus().editorPid ?? null
|
|
33693
33770
|
});
|
|
33694
33771
|
}
|
|
33772
|
+
classesAScriptNames(projectPath, scriptPath) {
|
|
33773
|
+
const source = this.sourceOfClass(projectPath, scriptPath);
|
|
33774
|
+
const known = cachedClasses(projectPath) ?? declaredClasses(projectPath);
|
|
33775
|
+
return source === null ? [] : classesNamedIn(source, known).filter((name) => known.get(name) !== scriptPath);
|
|
33776
|
+
}
|
|
33695
33777
|
sourceOfClass(projectPath, resourcePath) {
|
|
33696
33778
|
const contained = resolveWithinProject(projectPath, resourcePath);
|
|
33697
33779
|
if (!contained.ok || !existsSync12(contained.absolutePath)) {
|
|
@@ -35418,8 +35500,34 @@ class GodotServer {
|
|
|
35418
35500
|
try {
|
|
35419
35501
|
const answer = asParams(await this.godotBridge.invokeTool("reload_script", { ...args, scriptPath: reloading }));
|
|
35420
35502
|
const methods = readArray(answer, "methods");
|
|
35421
|
-
const
|
|
35422
|
-
|
|
35503
|
+
const listed = (field) => {
|
|
35504
|
+
const found = readArray(answer, field);
|
|
35505
|
+
return found ? { [field]: found.map(String) } : {};
|
|
35506
|
+
};
|
|
35507
|
+
const failed = answer["failed"];
|
|
35508
|
+
if (typeof failed === "number" && failed !== 0) {
|
|
35509
|
+
reloaded = {
|
|
35510
|
+
...listed("heldBefore"),
|
|
35511
|
+
...listed("heldConstantsBefore"),
|
|
35512
|
+
problem: failedReloadNote({
|
|
35513
|
+
script: reloading,
|
|
35514
|
+
code: failed,
|
|
35515
|
+
codeName: readString(answer, "failedAs") ?? "",
|
|
35516
|
+
heldBefore: listed("heldBefore")["heldBefore"] ?? [],
|
|
35517
|
+
heldAfter: methods?.map(String) ?? [],
|
|
35518
|
+
constantsBefore: listed("heldConstantsBefore")["heldConstantsBefore"] ?? [],
|
|
35519
|
+
constantsAfter: listed("constants")["constants"] ?? [],
|
|
35520
|
+
names: projectPath === "" ? [] : this.classesAScriptNames(projectPath, reloading)
|
|
35521
|
+
})
|
|
35522
|
+
};
|
|
35523
|
+
} else {
|
|
35524
|
+
reloaded = methods ? {
|
|
35525
|
+
methods: methods.map(String),
|
|
35526
|
+
...listed("heldBefore"),
|
|
35527
|
+
...listed("constants"),
|
|
35528
|
+
...listed("heldConstantsBefore")
|
|
35529
|
+
} : { problem: `the editor would not say what ${reloading} has after reloading it` };
|
|
35530
|
+
}
|
|
35423
35531
|
} catch (error) {
|
|
35424
35532
|
reloaded = { problem: `${reloading} could not be reloaded: ${errorMessage(error)}` };
|
|
35425
35533
|
}
|
|
@@ -35454,6 +35562,8 @@ class GodotServer {
|
|
|
35454
35562
|
waitedForEditorScanMs: waitedForEditorMs > 0 ? waitedForEditorMs : undefined,
|
|
35455
35563
|
heldBeforeReload: reloaded.heldBefore,
|
|
35456
35564
|
reloadedMethods: reloaded.methods,
|
|
35565
|
+
heldConstantsBeforeReload: reloaded.heldConstantsBefore,
|
|
35566
|
+
reloadedConstants: reloaded.constants,
|
|
35457
35567
|
reloadProblem: reloaded.problem,
|
|
35458
35568
|
unseenByEditor: unseen.length > 0 ? unseen : undefined,
|
|
35459
35569
|
broughtIn: broughtIn.length > 0 ? broughtIn : undefined,
|
|
@@ -35480,7 +35590,13 @@ class GodotServer {
|
|
|
35480
35590
|
}
|
|
35481
35591
|
try {
|
|
35482
35592
|
const answer = asParams(await this.godotBridge.invokeTool("reload_script", { scriptPath }));
|
|
35483
|
-
|
|
35593
|
+
const failed = answer["failed"];
|
|
35594
|
+
if (typeof failed === "number" && failed !== 0) {
|
|
35595
|
+
notReloaded.push({
|
|
35596
|
+
scriptPath,
|
|
35597
|
+
problem: `it did not compile: Godot answered error ${failed}${readString(answer, "failedAs") ? `, ${readString(answer, "failedAs")}` : ""}`
|
|
35598
|
+
});
|
|
35599
|
+
} else if (readArray(answer, "methods") === undefined) {
|
|
35484
35600
|
notReloaded.push({
|
|
35485
35601
|
scriptPath,
|
|
35486
35602
|
problem: "the editor would not say what it has after reloading it"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdharness",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.17",
|
|
4
4
|
"mcpName": "io.github.Aureliolo/gdharness",
|
|
5
5
|
"description": "A harness for driving a Godot 4 project from an agent: editor addons, a runtime bridge into the running game, an MCP server in front of them, and a CLI that installs and diagnoses the Godot side.",
|
|
6
6
|
"type": "module",
|