gdharness 1.1.53 → 1.1.55
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 +173 -44
- package/build/index.js +175 -42
- package/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -9131,7 +9131,7 @@ var init_gdscript_source = __esm(() => {
|
|
|
9131
9131
|
|
|
9132
9132
|
// src/class-cache.ts
|
|
9133
9133
|
import { existsSync as existsSync9, readdirSync as readdirSync3, readFileSync as readFileSync7, statSync as statSync4 } from "node:fs";
|
|
9134
|
-
import { join as join9 } from "node:path";
|
|
9134
|
+
import { join as join9, posix } from "node:path";
|
|
9135
9135
|
function isToolScript(source) {
|
|
9136
9136
|
return TOOL.test(codeOf(source));
|
|
9137
9137
|
}
|
|
@@ -9237,6 +9237,27 @@ function missingMemberIn(message) {
|
|
|
9237
9237
|
const enumName = inBase[3];
|
|
9238
9238
|
return enumName === undefined ? { kind: "member", member, type } : { kind: "enum member", member, type, enum: enumName };
|
|
9239
9239
|
}
|
|
9240
|
+
function externalMemberIn(message, line, classes) {
|
|
9241
|
+
const member = /Could not resolve external class member "([A-Za-z_]\w*)"/.exec(message)?.[1];
|
|
9242
|
+
if (member === undefined || line === undefined) {
|
|
9243
|
+
return null;
|
|
9244
|
+
}
|
|
9245
|
+
for (const [, type] of line.matchAll(new RegExp(String.raw`\b([A-Za-z_]\w*)\s*\.\s*${member}\b`, "g"))) {
|
|
9246
|
+
if (type !== undefined && classes.has(type)) {
|
|
9247
|
+
return { kind: "member", member, type };
|
|
9248
|
+
}
|
|
9249
|
+
}
|
|
9250
|
+
return null;
|
|
9251
|
+
}
|
|
9252
|
+
function baseScriptOf(source, from, classes) {
|
|
9253
|
+
const found = /^(?:@\w+(?:\([^)]*\))?\s+)*(?:class_name\s+\w+\s+)?extends\s+(?:"([^"]+)"|'([^']+)'|([A-Za-z_]\w*))/m.exec(source);
|
|
9254
|
+
const quoted = found?.[1] ?? found?.[2];
|
|
9255
|
+
if (quoted !== undefined) {
|
|
9256
|
+
return quoted.startsWith("res://") ? quoted : posix.join(posix.dirname(from), quoted).replace(/^res:\/(?!\/)/, "res://");
|
|
9257
|
+
}
|
|
9258
|
+
const named = found?.[3];
|
|
9259
|
+
return named === undefined ? undefined : classes.get(named);
|
|
9260
|
+
}
|
|
9240
9261
|
function declaresMember(source, missing) {
|
|
9241
9262
|
const name = missing.member.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
9242
9263
|
const top = String.raw`^(?:@\w+(?:\([^)]*\))?\s+)*`;
|
|
@@ -9296,24 +9317,39 @@ function classesNamedIn(source, known) {
|
|
|
9296
9317
|
}
|
|
9297
9318
|
return [...named].sort();
|
|
9298
9319
|
}
|
|
9299
|
-
function contradictedDiagnostics(messages, classes, sourceOf) {
|
|
9320
|
+
function contradictedDiagnostics(messages, classes, sourceOf, alsoDenied = []) {
|
|
9300
9321
|
const keyOf = (missing) => [missing.type, missing.enum ?? "", missing.member].join(".");
|
|
9301
|
-
const denials =
|
|
9322
|
+
const denials = [
|
|
9323
|
+
...messages.map((message) => missingMemberIn(message)).filter((missing) => missing !== null),
|
|
9324
|
+
...alsoDenied
|
|
9325
|
+
];
|
|
9302
9326
|
const calledOnTheClass = new Set(denials.filter((missing) => missing.kind === "static method").map((missing) => keyOf(missing)));
|
|
9303
9327
|
const read = new Map;
|
|
9328
|
+
const sourceAt = (path) => {
|
|
9329
|
+
if (!read.has(path)) {
|
|
9330
|
+
read.set(path, sourceOf(path));
|
|
9331
|
+
}
|
|
9332
|
+
return read.get(path) ?? null;
|
|
9333
|
+
};
|
|
9304
9334
|
const found = new Map;
|
|
9305
9335
|
for (const missing of denials) {
|
|
9306
|
-
const declaredIn = classes.get(missing.type);
|
|
9307
9336
|
const key = keyOf(missing);
|
|
9308
|
-
if (
|
|
9337
|
+
if (found.has(key) || missing.kind === "method" && calledOnTheClass.has(key)) {
|
|
9309
9338
|
continue;
|
|
9310
9339
|
}
|
|
9311
|
-
|
|
9312
|
-
|
|
9313
|
-
|
|
9314
|
-
|
|
9315
|
-
|
|
9316
|
-
|
|
9340
|
+
const seen = new Set;
|
|
9341
|
+
let at = classes.get(missing.type);
|
|
9342
|
+
while (at !== undefined && !seen.has(at)) {
|
|
9343
|
+
seen.add(at);
|
|
9344
|
+
const source = sourceAt(at);
|
|
9345
|
+
if (source === null) {
|
|
9346
|
+
break;
|
|
9347
|
+
}
|
|
9348
|
+
if (declaresMember(source, missing)) {
|
|
9349
|
+
found.set(key, { ...missing, declaredIn: at });
|
|
9350
|
+
break;
|
|
9351
|
+
}
|
|
9352
|
+
at = baseScriptOf(source, at, classes);
|
|
9317
9353
|
}
|
|
9318
9354
|
}
|
|
9319
9355
|
return [...found.values()];
|
|
@@ -9326,7 +9362,7 @@ function staleAnalysisNote(contradicted, dependsOn) {
|
|
|
9326
9362
|
const onInstances = contradicted.filter((one) => one.kind === "method" || one.kind === "property");
|
|
9327
9363
|
const onTheClass = contradicted.filter((one) => one.kind === "member" || one.kind === "static method" || one.kind === "enum member");
|
|
9328
9364
|
return [
|
|
9329
|
-
`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
|
|
9365
|
+
`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 script under its " + "declaredIn, the class itself or a project class it extends, so those diagnostics are wrong " + "however the code is written.",
|
|
9330
9366
|
instanceRemedy(onInstances, dependsOn, onTheClass.length > 0),
|
|
9331
9367
|
classRemedy(onTheClass)
|
|
9332
9368
|
].filter((part) => part !== "").join(" ");
|
|
@@ -9343,7 +9379,7 @@ function instanceRemedy(entries, dependsOn, besideOthers) {
|
|
|
9343
9379
|
}
|
|
9344
9380
|
const declaring = declaringScripts(entries);
|
|
9345
9381
|
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`;
|
|
9346
|
-
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
|
|
9382
|
+
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 what that added under methodsAdded and constantsAdded. 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.";
|
|
9347
9383
|
}
|
|
9348
9384
|
function classRemedy(entries) {
|
|
9349
9385
|
if (entries.length === 0) {
|
|
@@ -9367,6 +9403,24 @@ function notReloadedNote(broughtIn, notReloaded) {
|
|
|
9367
9403
|
}
|
|
9368
9404
|
return `The scan brought in ${broughtIn.join(", ")}, and ${all.join(", ")} ${all.length === 1 ? "names one of them and" : "name them and"} could not be reloaded, so the editor still holds what it compiled while it could not see the class and reports that to the language server. ${remedies.join(" ")}`;
|
|
9369
9405
|
}
|
|
9406
|
+
function reloadDifference(reloaded) {
|
|
9407
|
+
const between = (before, after) => {
|
|
9408
|
+
if (before === undefined || after === undefined) {
|
|
9409
|
+
return null;
|
|
9410
|
+
}
|
|
9411
|
+
const was = new Set(before);
|
|
9412
|
+
const is = new Set(after);
|
|
9413
|
+
return { added: after.filter((name) => !was.has(name)), removed: before.filter((name) => !is.has(name)) };
|
|
9414
|
+
};
|
|
9415
|
+
const methods = between(reloaded.heldBefore, reloaded.methods);
|
|
9416
|
+
const constants = between(reloaded.heldConstantsBefore, reloaded.constants);
|
|
9417
|
+
return {
|
|
9418
|
+
...reloaded.methods === undefined ? {} : { methodsHeld: reloaded.methods.length },
|
|
9419
|
+
...methods === null ? {} : { methodsAdded: methods.added, methodsRemoved: methods.removed },
|
|
9420
|
+
...reloaded.constants === undefined ? {} : { constantsHeld: reloaded.constants.length },
|
|
9421
|
+
...constants === null ? {} : { constantsAdded: constants.added, constantsRemoved: constants.removed }
|
|
9422
|
+
};
|
|
9423
|
+
}
|
|
9370
9424
|
function failedReloadNote(failed) {
|
|
9371
9425
|
const missing = (before, after) => before.filter((name) => !after.includes(name));
|
|
9372
9426
|
const lost = [
|
|
@@ -9382,7 +9436,7 @@ function failedReloadNote(failed) {
|
|
|
9382
9436
|
} else if (!failed.restored) {
|
|
9383
9437
|
copy = `${unusable} The editor compiled it again from the text it was built from, and that failed too${failed.restoredAs === "" ? "" : ` (${failed.restoredAs})`}, so it stays that way until the script compiles: reload it then, or restart the editor with editor_launch restart.`;
|
|
9384
9438
|
} else if (unchanged) {
|
|
9385
|
-
copy = "The copy the editor holds was compiled again from the text it was built from, which puts it back as it was, with the members
|
|
9439
|
+
copy = "The copy the editor holds was compiled again from the text it was built from, which puts it back as it was, with the members it held before, which listMembers lists.";
|
|
9386
9440
|
} else {
|
|
9387
9441
|
copy = `The copy the editor holds was compiled again from the text it was built from and 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.`;
|
|
9388
9442
|
}
|
|
@@ -17855,7 +17909,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17855
17909
|
},
|
|
17856
17910
|
{
|
|
17857
17911
|
name: "script_diagnostics",
|
|
17858
|
-
description: "Errors and warnings for a script, or for several, from the editor's language server, and whether each is clean. Needs the editor running. Each diagnostic carries its severity (error or warning), its message, and where it is as the editor shows it: line and column counted from 1, and endLine and endColumn just past its end. 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.",
|
|
17912
|
+
description: "Errors and warnings for a script, or for several, from the editor's language server, and whether each is clean. Needs the editor running. Each diagnostic carries its severity (error or warning), its message, and where it is as the editor shows it: line and column counted from 1, and endLine and endColumn just past its end. 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, or a project class it extends does, a method, property, constant, static function or enum member, comes back under contradictedByTheFile with the script declaring it, and so does one a diagnostic names alone as an external class member, read against the class it is looked up on in the line it is on; 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.",
|
|
17859
17913
|
parameters: {
|
|
17860
17914
|
projectPath: PROJECT_PATH,
|
|
17861
17915
|
scriptPath: {
|
|
@@ -18080,7 +18134,11 @@ var init_tool_definitions = __esm(() => {
|
|
|
18080
18134
|
},
|
|
18081
18135
|
reloadScript: {
|
|
18082
18136
|
type: "string",
|
|
18083
|
-
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.
|
|
18137
|
+
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. What the reload changed comes back under methodsAdded, methodsRemoved, constantsAdded and constantsRemoved, with each enum value as Kind.SHORT among the constants, since an enum member is held as a constant and never shows among the methods; methodsHeld and constantsHeld count what the copy holds afterwards, because a reload that compiled nothing and answered OK is the failure worth catching. listMembers adds the whole lists. 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. A @tool script is not reloaded either, since it runs inside the editor and reloading one from inside a call can take the editor down: reloadProblem says so, and editor_launch restart recompiles it. A path spelled in another case than the file is reloaded under the spelling on disk, which is the one the editor holds its copy under, and that spelling comes back under reloadedAs."
|
|
18138
|
+
},
|
|
18139
|
+
listMembers: {
|
|
18140
|
+
type: "boolean",
|
|
18141
|
+
description: "With reloadScript: also answer the whole member lists, heldBeforeReload and reloadedMethods for the methods, heldConstantsBeforeReload and reloadedConstants for the constants. Default false: a script holding hundreds of constants answers hundreds of lines twice to say what one reload added."
|
|
18084
18142
|
}
|
|
18085
18143
|
},
|
|
18086
18144
|
requires: ["projectPath"]
|
|
@@ -38457,7 +38515,7 @@ var init_godot_bridge = __esm(() => {
|
|
|
38457
38515
|
case "godot_ready":
|
|
38458
38516
|
if (this.connectionInfo) {
|
|
38459
38517
|
if (this.belongsElsewhere(message.project_path)) {
|
|
38460
|
-
this.
|
|
38518
|
+
this.elsewhereUnlessOneShot(message.project_path, message.editor_pid, message.opened_by_a_server === true);
|
|
38461
38519
|
return;
|
|
38462
38520
|
}
|
|
38463
38521
|
this.connectionInfo.projectPath = message.project_path;
|
|
@@ -38568,6 +38626,34 @@ var init_godot_bridge = __esm(() => {
|
|
|
38568
38626
|
const fits = closeReason(said, "start") === said;
|
|
38569
38627
|
stranger?.close(OTHER_PROJECT_CLOSE_CODE, fits ? said : closeReason(`this server serves ${this.ownProject ?? ""}`, "end"));
|
|
38570
38628
|
}
|
|
38629
|
+
elsewhereUnlessOneShot(theirProject, pid, openedByAServer) {
|
|
38630
|
+
const known = pid === undefined ? undefined : this.oneShots.get(pid);
|
|
38631
|
+
if (pid !== undefined && known !== undefined) {
|
|
38632
|
+
this.turnAway(pid, known);
|
|
38633
|
+
return;
|
|
38634
|
+
}
|
|
38635
|
+
const info = this.connectionInfo;
|
|
38636
|
+
const socket = this.socket;
|
|
38637
|
+
const reader = this.oneShotReader;
|
|
38638
|
+
if (pid === undefined || openedByAServer || info === null || reader === null) {
|
|
38639
|
+
this.sendElsewhere(theirProject);
|
|
38640
|
+
return;
|
|
38641
|
+
}
|
|
38642
|
+
info.identifying = true;
|
|
38643
|
+
reader(pid).catch(() => {
|
|
38644
|
+
return;
|
|
38645
|
+
}).then((option) => {
|
|
38646
|
+
if (this.socket !== socket || this.connectionInfo !== info) {
|
|
38647
|
+
return;
|
|
38648
|
+
}
|
|
38649
|
+
if (typeof option === "string") {
|
|
38650
|
+
this.oneShots.set(pid, option);
|
|
38651
|
+
this.turnAway(pid, option);
|
|
38652
|
+
return;
|
|
38653
|
+
}
|
|
38654
|
+
this.sendElsewhere(theirProject);
|
|
38655
|
+
});
|
|
38656
|
+
}
|
|
38571
38657
|
identify(pid) {
|
|
38572
38658
|
const known = this.oneShots.get(pid);
|
|
38573
38659
|
if (known !== undefined) {
|
|
@@ -39139,6 +39225,7 @@ import { existsSync as existsSync16, realpathSync as realpathSync4 } from "node:
|
|
|
39139
39225
|
import { readFile, realpath } from "node:fs/promises";
|
|
39140
39226
|
import { createConnection as createConnection3 } from "node:net";
|
|
39141
39227
|
import { dirname as dirname14, join as join20, resolve as resolve5 } from "node:path";
|
|
39228
|
+
import { setTimeout as delay3 } from "node:timers/promises";
|
|
39142
39229
|
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
|
|
39143
39230
|
function initializeTimeoutMs() {
|
|
39144
39231
|
const override = Number.parseInt(envValue("GDHARNESS_LSP_INITIALIZE_TIMEOUT_MS") ?? "", 10);
|
|
@@ -39177,17 +39264,38 @@ class GodotLSPClient {
|
|
|
39177
39264
|
documentVersions = new Map;
|
|
39178
39265
|
documentTurns = new Map;
|
|
39179
39266
|
timeouts;
|
|
39267
|
+
servedHere;
|
|
39180
39268
|
constructor(port = portFromEnv("GDHARNESS_LSP_PORT", DEFAULT_LSP_PORT), host = "127.0.0.1", timeouts = {
|
|
39181
39269
|
requestMs: REQUEST_TIMEOUT_MS2,
|
|
39182
39270
|
referencesMs: REFERENCES_TIMEOUT_MS,
|
|
39183
|
-
initializeMs: initializeTimeoutMs()
|
|
39184
|
-
|
|
39271
|
+
initializeMs: initializeTimeoutMs(),
|
|
39272
|
+
listenMs: LANGUAGE_SERVER_START_MS
|
|
39273
|
+
}, servedHere = () => false) {
|
|
39185
39274
|
this.port = port;
|
|
39186
39275
|
this.host = host;
|
|
39187
39276
|
this.timeouts = timeouts;
|
|
39277
|
+
this.servedHere = servedHere;
|
|
39188
39278
|
this.pendingRequests = new Map;
|
|
39189
39279
|
}
|
|
39190
39280
|
async connect() {
|
|
39281
|
+
const until = Date.now() + this.timeouts.listenMs;
|
|
39282
|
+
for (;; ) {
|
|
39283
|
+
try {
|
|
39284
|
+
await this.connectOnce();
|
|
39285
|
+
return;
|
|
39286
|
+
} catch (error) {
|
|
39287
|
+
const refused = error instanceof Error && error.message.includes("ECONNREFUSED");
|
|
39288
|
+
if (!refused || !this.servedHere()) {
|
|
39289
|
+
throw error;
|
|
39290
|
+
}
|
|
39291
|
+
if (Date.now() >= until) {
|
|
39292
|
+
throw new LanguageServerNotListening(this.port, this.timeouts.listenMs);
|
|
39293
|
+
}
|
|
39294
|
+
await delay3(LANGUAGE_SERVER_RETRY_MS);
|
|
39295
|
+
}
|
|
39296
|
+
}
|
|
39297
|
+
}
|
|
39298
|
+
async connectOnce() {
|
|
39191
39299
|
if (this.connected && this.socket) {
|
|
39192
39300
|
return;
|
|
39193
39301
|
}
|
|
@@ -39820,11 +39928,12 @@ async function handleLSPTool(client, toolName, args) {
|
|
|
39820
39928
|
...error instanceof AnotherProjectsServer ? { serves: error.serves } : {},
|
|
39821
39929
|
...error instanceof ArgumentRefusal ? { refusedArguments: true } : {},
|
|
39822
39930
|
...error instanceof NoDiagnosticsPublished ? { aboutThisFile: true } : {},
|
|
39823
|
-
...error instanceof LSPTimeout && error.method === "initialize" ? { stillReading: true } : {}
|
|
39931
|
+
...error instanceof LSPTimeout && error.method === "initialize" ? { stillReading: true } : {},
|
|
39932
|
+
...error instanceof LanguageServerNotListening ? { stillReading: true } : {}
|
|
39824
39933
|
});
|
|
39825
39934
|
}
|
|
39826
39935
|
}
|
|
39827
|
-
var DEFAULT_LSP_PORT = 6005, DIAGNOSTICS_TIMEOUT_MS = 5000, REQUEST_TIMEOUT_MS2 = 1e4, REFERENCES_TIMEOUT_MS = 180000, LSPTimeout, ArgumentRefusal, NoDiagnosticsPublished, AnotherProjectsServer, SEVERITIES, SYMBOL_KINDS, COMPLETION_KINDS;
|
|
39936
|
+
var DEFAULT_LSP_PORT = 6005, DIAGNOSTICS_TIMEOUT_MS = 5000, REQUEST_TIMEOUT_MS2 = 1e4, REFERENCES_TIMEOUT_MS = 180000, LSPTimeout, LANGUAGE_SERVER_START_MS = 30000, LANGUAGE_SERVER_RETRY_MS = 250, LanguageServerNotListening, ArgumentRefusal, NoDiagnosticsPublished, AnotherProjectsServer, SEVERITIES, SYMBOL_KINDS, COMPLETION_KINDS;
|
|
39828
39937
|
var init_lsp_client = __esm(() => {
|
|
39829
39938
|
init_answer_json();
|
|
39830
39939
|
init_errors();
|
|
@@ -39840,6 +39949,11 @@ var init_lsp_client = __esm(() => {
|
|
|
39840
39949
|
this.seconds = timeoutMs / 1000;
|
|
39841
39950
|
}
|
|
39842
39951
|
};
|
|
39952
|
+
LanguageServerNotListening = class LanguageServerNotListening extends Error {
|
|
39953
|
+
constructor(port, waitedMs) {
|
|
39954
|
+
super(`The editor on the bridge says its language server is on port ${port}, and nothing accepted a connection there in ${String(waitedMs / 1000)}s. An editor starts it a few seconds after it reaches the bridge, so straight after editor_launch this is the editor still starting, and calling again answers. One that goes on refusing could not take the port; editor_output shows what the editor printed.`);
|
|
39955
|
+
}
|
|
39956
|
+
};
|
|
39843
39957
|
ArgumentRefusal = class ArgumentRefusal extends Refusal {
|
|
39844
39958
|
};
|
|
39845
39959
|
NoDiagnosticsPublished = class NoDiagnosticsPublished extends Error {
|
|
@@ -40249,7 +40363,7 @@ var init_project_scan = __esm(() => {
|
|
|
40249
40363
|
|
|
40250
40364
|
// src/rename.ts
|
|
40251
40365
|
import { existsSync as existsSync18, mkdirSync as mkdirSync11, readFileSync as readFileSync19, renameSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync13 } from "node:fs";
|
|
40252
|
-
import { dirname as dirname15, posix } from "node:path";
|
|
40366
|
+
import { dirname as dirname15, posix as posix2 } from "node:path";
|
|
40253
40367
|
|
|
40254
40368
|
class Script {
|
|
40255
40369
|
path;
|
|
@@ -40362,13 +40476,13 @@ class ClassGraph {
|
|
|
40362
40476
|
}
|
|
40363
40477
|
function resolvedPath(written, from) {
|
|
40364
40478
|
if (written.startsWith("res://")) {
|
|
40365
|
-
return `res://${
|
|
40479
|
+
return `res://${posix2.normalize(written.slice("res://".length))}`;
|
|
40366
40480
|
}
|
|
40367
|
-
const directory =
|
|
40368
|
-
return `res://${
|
|
40481
|
+
const directory = posix2.dirname(from.slice("res://".length));
|
|
40482
|
+
return `res://${posix2.normalize(posix2.join(directory, written))}`;
|
|
40369
40483
|
}
|
|
40370
40484
|
function relativeTo(target, from) {
|
|
40371
|
-
return
|
|
40485
|
+
return posix2.relative(posix2.dirname(from.slice("res://".length)), target.slice("res://".length));
|
|
40372
40486
|
}
|
|
40373
40487
|
function scriptsOf(texts) {
|
|
40374
40488
|
const scripts = new Map;
|
|
@@ -42030,7 +42144,7 @@ import {
|
|
|
42030
42144
|
statSync as statSync9
|
|
42031
42145
|
} from "node:fs";
|
|
42032
42146
|
import { basename as basename7, dirname as dirname19, extname as extname2, isAbsolute as isAbsolute3, join as join26, normalize as normalize2, relative as relative5 } from "node:path";
|
|
42033
|
-
import { setTimeout as
|
|
42147
|
+
import { setTimeout as delay4 } from "node:timers/promises";
|
|
42034
42148
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
42035
42149
|
import { promisify as promisify5 } from "node:util";
|
|
42036
42150
|
function editorRestartTimeoutMs() {
|
|
@@ -42163,7 +42277,7 @@ async function untilGone(pids, withinMs = STOP_WAIT_MS) {
|
|
|
42163
42277
|
const gone = () => pids.every((pid) => !alive(pid));
|
|
42164
42278
|
const deadline = Date.now() + withinMs;
|
|
42165
42279
|
while (!gone() && Date.now() < deadline) {
|
|
42166
|
-
await
|
|
42280
|
+
await delay4(50);
|
|
42167
42281
|
}
|
|
42168
42282
|
return gone();
|
|
42169
42283
|
}
|
|
@@ -42962,7 +43076,7 @@ class GodotServer {
|
|
|
42962
43076
|
taken = true;
|
|
42963
43077
|
this.logDebug(`Took port ${wanted} over from pid ${announced.pid}`);
|
|
42964
43078
|
} catch {
|
|
42965
|
-
await
|
|
43079
|
+
await delay4(HANDOVER_POLL_MS);
|
|
42966
43080
|
}
|
|
42967
43081
|
}
|
|
42968
43082
|
this.notHandedOverBy = taken ? null : announced.pid;
|
|
@@ -44733,14 +44847,14 @@ class GodotServer {
|
|
|
44733
44847
|
}
|
|
44734
44848
|
whereTheFileDisagrees(diagnostics, args) {
|
|
44735
44849
|
const messages = diagnostics.map((entry) => asParams(entry)["message"]).filter((message) => typeof message === "string");
|
|
44736
|
-
const worthChecking = messages.some((message) => missingMemberIn(message) !== null || unknownTypeIn(message) !== null);
|
|
44850
|
+
const worthChecking = messages.some((message) => missingMemberIn(message) !== null || unknownTypeIn(message) !== null || message.includes(EXTERNAL_MEMBER));
|
|
44737
44851
|
const project = worthChecking ? this.project(args) : null;
|
|
44738
44852
|
if (project === null || !project.ok) {
|
|
44739
44853
|
return {};
|
|
44740
44854
|
}
|
|
44741
44855
|
const projectPath = project.value.path;
|
|
44742
44856
|
const cached = cachedClasses(projectPath);
|
|
44743
|
-
const
|
|
44857
|
+
const sourceOf = (resourcePath) => {
|
|
44744
44858
|
const contained = resolveWithinProject(projectPath, resourcePath);
|
|
44745
44859
|
if (!contained.ok || !existsSync20(contained.absolutePath)) {
|
|
44746
44860
|
return null;
|
|
@@ -44750,7 +44864,16 @@ class GodotServer {
|
|
|
44750
44864
|
} catch {
|
|
44751
44865
|
return null;
|
|
44752
44866
|
}
|
|
44753
|
-
}
|
|
44867
|
+
};
|
|
44868
|
+
const external = diagnostics.filter((entry) => String(asParams(entry)["message"]).includes(EXTERNAL_MEMBER));
|
|
44869
|
+
const diagnosed = external.length === 0 ? null : sourceOf(readString(args, "scriptPath") ?? "");
|
|
44870
|
+
const lines = diagnosed?.split(`
|
|
44871
|
+
`) ?? [];
|
|
44872
|
+
const alsoDenied = cached === null ? [] : external.map((entry) => {
|
|
44873
|
+
const { message, line } = asParams(entry);
|
|
44874
|
+
return externalMemberIn(String(message), typeof line === "number" ? lines[line - 1] : undefined, cached);
|
|
44875
|
+
}).filter((missing) => missing !== null);
|
|
44876
|
+
const contradicted = cached === null ? [] : contradictedDiagnostics(messages, cached, sourceOf, alsoDenied);
|
|
44754
44877
|
const unloaded = messages.some((message) => unknownTypeIn(message) !== null) ? unloadedTypes(messages, declaredClasses(projectPath), cached) : [];
|
|
44755
44878
|
const notes = [];
|
|
44756
44879
|
if (contradicted.length > 0) {
|
|
@@ -44820,7 +44943,10 @@ class GodotServer {
|
|
|
44820
44943
|
await this.lspClient.disconnect();
|
|
44821
44944
|
this.lspClient = null;
|
|
44822
44945
|
}
|
|
44823
|
-
this.lspClient ??= new GodotLSPClient(port)
|
|
44946
|
+
this.lspClient ??= new GodotLSPClient(port, undefined, undefined, () => {
|
|
44947
|
+
const status = this.godotBridge.getStatus();
|
|
44948
|
+
return status.connected && status.lspPort === port;
|
|
44949
|
+
});
|
|
44824
44950
|
return this.lspClient;
|
|
44825
44951
|
}
|
|
44826
44952
|
async handleDAP(toolName, args) {
|
|
@@ -45000,7 +45126,7 @@ class GodotServer {
|
|
|
45000
45126
|
try {
|
|
45001
45127
|
const askedAt = Date.now();
|
|
45002
45128
|
const asked = this.godotBridge.invokeTool("playing_status", {});
|
|
45003
|
-
const answer = await Promise.race([asked,
|
|
45129
|
+
const answer = await Promise.race([asked, delay4(PLAYING_STATUS_MS).then(() => null)]);
|
|
45004
45130
|
if (answer === null) {
|
|
45005
45131
|
this.logDebug(`playing_status did not answer within ${PLAYING_STATUS_MS}ms`);
|
|
45006
45132
|
asked.then(() => {
|
|
@@ -45025,7 +45151,7 @@ class GodotServer {
|
|
|
45025
45151
|
try {
|
|
45026
45152
|
const answer = await Promise.race([
|
|
45027
45153
|
this.godotBridge.invokeTool("scan_status", {}),
|
|
45028
|
-
|
|
45154
|
+
delay4(PLAYING_STATUS_MS).then(() => null)
|
|
45029
45155
|
]);
|
|
45030
45156
|
if (answer === null) {
|
|
45031
45157
|
return null;
|
|
@@ -45237,7 +45363,7 @@ class GodotServer {
|
|
|
45237
45363
|
if (Date.now() >= deadline) {
|
|
45238
45364
|
return false;
|
|
45239
45365
|
}
|
|
45240
|
-
await
|
|
45366
|
+
await delay4(250);
|
|
45241
45367
|
}
|
|
45242
45368
|
}
|
|
45243
45369
|
async handleLaunchEditor(args) {
|
|
@@ -45747,7 +45873,7 @@ class GodotServer {
|
|
|
45747
45873
|
if (Date.now() >= deadline) {
|
|
45748
45874
|
return false;
|
|
45749
45875
|
}
|
|
45750
|
-
await
|
|
45876
|
+
await delay4(100);
|
|
45751
45877
|
}
|
|
45752
45878
|
}
|
|
45753
45879
|
async endActiveGame(reason, going) {
|
|
@@ -46339,7 +46465,7 @@ class GodotServer {
|
|
|
46339
46465
|
while (cancelled?.aborted !== true && await this.runStillGoing(run) && Date.now() < until) {
|
|
46340
46466
|
this.drainEditorOutput(run);
|
|
46341
46467
|
this.drainTranscript(run);
|
|
46342
|
-
await
|
|
46468
|
+
await delay4(RUN_POLL_MS);
|
|
46343
46469
|
}
|
|
46344
46470
|
return await this.handleGetDebugOutput(args, await this.runStillGoing(run) ? budgetMs : undefined);
|
|
46345
46471
|
}
|
|
@@ -46376,7 +46502,7 @@ class GodotServer {
|
|
|
46376
46502
|
if (Date.now() >= until) {
|
|
46377
46503
|
return false;
|
|
46378
46504
|
}
|
|
46379
|
-
await
|
|
46505
|
+
await delay4(50);
|
|
46380
46506
|
}
|
|
46381
46507
|
}
|
|
46382
46508
|
async handleEditorConsole(args) {
|
|
@@ -46886,10 +47012,13 @@ class GodotServer {
|
|
|
46886
47012
|
waitedMs: Date.now() - started,
|
|
46887
47013
|
waitedForEditorScanMs: waitedForEditorMs > 0 ? waitedForEditorMs : undefined,
|
|
46888
47014
|
reloadedAs: reloaded.as,
|
|
46889
|
-
|
|
46890
|
-
|
|
46891
|
-
|
|
46892
|
-
|
|
47015
|
+
...reloadDifference(reloaded),
|
|
47016
|
+
...args["listMembers"] === true ? {
|
|
47017
|
+
heldBeforeReload: reloaded.heldBefore,
|
|
47018
|
+
reloadedMethods: reloaded.methods,
|
|
47019
|
+
heldConstantsBeforeReload: reloaded.heldConstantsBefore,
|
|
47020
|
+
reloadedConstants: reloaded.constants
|
|
47021
|
+
} : {},
|
|
46893
47022
|
reloadProblem: reloaded.problem,
|
|
46894
47023
|
unseenByEditor: unseen.length > 0 ? unseen : undefined,
|
|
46895
47024
|
broughtIn: broughtIn.length > 0 ? broughtIn : undefined,
|
|
@@ -47565,7 +47694,7 @@ async function runGodotServer() {
|
|
|
47565
47694
|
const server = new GodotServer;
|
|
47566
47695
|
await server.run();
|
|
47567
47696
|
}
|
|
47568
|
-
var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, run4, __dirname2, GREETING_WAIT_MS = 1e4, HOLD_PING_MS = 2000, SLOWEST_FRAME_RATE = 20, PLAY_STARTS_WITHIN_MS = 30000, STOP_WAIT_MS = 1e4, KEEPER_ANSWER_MS = 15000, EXIT_REPORTED_WITHIN_MS = 2000, CONSOLE_WRAPPER, LONGEST_BOOT_NOTED_MS, MOST_GROUPS = 20, VALUE_NAMES, BRIDGE_RETRY_MS = 2000, SUCCESSOR_CHECK_MS = 1e4, CONSOLE_WATCH_MS = 250, BOUNDARY_WAIT_MS = 2000, HANDOVER_MS, HANDOVER_POLL_MS = 250, CACHE_WRITE_MS = 2000, SCAN_WAIT_MS = 30000, STREAMS_GRACE_MS = 2000, PLAYING_STATUS_MS = 1000, SCAN_START_MS = 2000, SCAN_SETTLED_MS = 500, REIMPORT_TIMEOUT_MS = 300000, AUDIO_BUS_OPS, STALLED_AFTER_MS = 30000, TEST_HEARTBEAT_MS = 20000, DEPENDED_FAILED = "Failed to compile depended scripts", WAIT_FOR_RUN_MS = 600000, RUN_POLL_MS = 250, PATH_SOLUTIONS, PROJECT_FILE_ARGUMENTS, DEBUG_STATE_CALLS, PROJECT_INFO_SECTIONS2, RUNNER_DIRECTORY = "addons/gdUnit4/";
|
|
47697
|
+
var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, run4, __dirname2, GREETING_WAIT_MS = 1e4, HOLD_PING_MS = 2000, SLOWEST_FRAME_RATE = 20, PLAY_STARTS_WITHIN_MS = 30000, STOP_WAIT_MS = 1e4, KEEPER_ANSWER_MS = 15000, EXIT_REPORTED_WITHIN_MS = 2000, CONSOLE_WRAPPER, LONGEST_BOOT_NOTED_MS, MOST_GROUPS = 20, VALUE_NAMES, BRIDGE_RETRY_MS = 2000, SUCCESSOR_CHECK_MS = 1e4, CONSOLE_WATCH_MS = 250, BOUNDARY_WAIT_MS = 2000, HANDOVER_MS, HANDOVER_POLL_MS = 250, CACHE_WRITE_MS = 2000, SCAN_WAIT_MS = 30000, STREAMS_GRACE_MS = 2000, PLAYING_STATUS_MS = 1000, SCAN_START_MS = 2000, SCAN_SETTLED_MS = 500, REIMPORT_TIMEOUT_MS = 300000, AUDIO_BUS_OPS, STALLED_AFTER_MS = 30000, TEST_HEARTBEAT_MS = 20000, DEPENDED_FAILED = "Failed to compile depended scripts", WAIT_FOR_RUN_MS = 600000, RUN_POLL_MS = 250, PATH_SOLUTIONS, PROJECT_FILE_ARGUMENTS, DEBUG_STATE_CALLS, PROJECT_INFO_SECTIONS2, RUNNER_DIRECTORY = "addons/gdUnit4/", EXTERNAL_MEMBER = 'Could not resolve external class member "';
|
|
47569
47698
|
var init_server3 = __esm(() => {
|
|
47570
47699
|
init_mcp();
|
|
47571
47700
|
init_stdio2();
|
package/build/index.js
CHANGED
|
@@ -10261,7 +10261,7 @@ import {
|
|
|
10261
10261
|
statSync as statSync8
|
|
10262
10262
|
} from "node:fs";
|
|
10263
10263
|
import { basename as basename5, dirname as dirname15, extname as extname2, isAbsolute as isAbsolute3, join as join22, normalize as normalize2, relative as relative4 } from "node:path";
|
|
10264
|
-
import { setTimeout as
|
|
10264
|
+
import { setTimeout as delay4 } from "node:timers/promises";
|
|
10265
10265
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
10266
10266
|
import { promisify as promisify5 } from "node:util";
|
|
10267
10267
|
|
|
@@ -26191,7 +26191,7 @@ function callProgress() {
|
|
|
26191
26191
|
|
|
26192
26192
|
// src/class-cache.ts
|
|
26193
26193
|
import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync7, statSync as statSync2 } from "node:fs";
|
|
26194
|
-
import { join as join6 } from "node:path";
|
|
26194
|
+
import { join as join6, posix } from "node:path";
|
|
26195
26195
|
|
|
26196
26196
|
// src/gdscript-source.ts
|
|
26197
26197
|
var IDENTIFIER_START = /[\p{L}_]/u;
|
|
@@ -26692,6 +26692,27 @@ function missingMemberIn(message) {
|
|
|
26692
26692
|
const enumName = inBase[3];
|
|
26693
26693
|
return enumName === undefined ? { kind: "member", member, type } : { kind: "enum member", member, type, enum: enumName };
|
|
26694
26694
|
}
|
|
26695
|
+
function externalMemberIn(message, line, classes) {
|
|
26696
|
+
const member = /Could not resolve external class member "([A-Za-z_]\w*)"/.exec(message)?.[1];
|
|
26697
|
+
if (member === undefined || line === undefined) {
|
|
26698
|
+
return null;
|
|
26699
|
+
}
|
|
26700
|
+
for (const [, type] of line.matchAll(new RegExp(String.raw`\b([A-Za-z_]\w*)\s*\.\s*${member}\b`, "g"))) {
|
|
26701
|
+
if (type !== undefined && classes.has(type)) {
|
|
26702
|
+
return { kind: "member", member, type };
|
|
26703
|
+
}
|
|
26704
|
+
}
|
|
26705
|
+
return null;
|
|
26706
|
+
}
|
|
26707
|
+
function baseScriptOf(source, from, classes) {
|
|
26708
|
+
const found = /^(?:@\w+(?:\([^)]*\))?\s+)*(?:class_name\s+\w+\s+)?extends\s+(?:"([^"]+)"|'([^']+)'|([A-Za-z_]\w*))/m.exec(source);
|
|
26709
|
+
const quoted = found?.[1] ?? found?.[2];
|
|
26710
|
+
if (quoted !== undefined) {
|
|
26711
|
+
return quoted.startsWith("res://") ? quoted : posix.join(posix.dirname(from), quoted).replace(/^res:\/(?!\/)/, "res://");
|
|
26712
|
+
}
|
|
26713
|
+
const named = found?.[3];
|
|
26714
|
+
return named === undefined ? undefined : classes.get(named);
|
|
26715
|
+
}
|
|
26695
26716
|
function declaresMember(source, missing) {
|
|
26696
26717
|
const name = missing.member.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
26697
26718
|
const top = String.raw`^(?:@\w+(?:\([^)]*\))?\s+)*`;
|
|
@@ -26751,24 +26772,39 @@ function classesNamedIn(source, known) {
|
|
|
26751
26772
|
}
|
|
26752
26773
|
return [...named].sort();
|
|
26753
26774
|
}
|
|
26754
|
-
function contradictedDiagnostics(messages, classes, sourceOf) {
|
|
26775
|
+
function contradictedDiagnostics(messages, classes, sourceOf, alsoDenied = []) {
|
|
26755
26776
|
const keyOf = (missing) => [missing.type, missing.enum ?? "", missing.member].join(".");
|
|
26756
|
-
const denials =
|
|
26777
|
+
const denials = [
|
|
26778
|
+
...messages.map((message) => missingMemberIn(message)).filter((missing) => missing !== null),
|
|
26779
|
+
...alsoDenied
|
|
26780
|
+
];
|
|
26757
26781
|
const calledOnTheClass = new Set(denials.filter((missing) => missing.kind === "static method").map((missing) => keyOf(missing)));
|
|
26758
26782
|
const read = new Map;
|
|
26783
|
+
const sourceAt = (path) => {
|
|
26784
|
+
if (!read.has(path)) {
|
|
26785
|
+
read.set(path, sourceOf(path));
|
|
26786
|
+
}
|
|
26787
|
+
return read.get(path) ?? null;
|
|
26788
|
+
};
|
|
26759
26789
|
const found = new Map;
|
|
26760
26790
|
for (const missing of denials) {
|
|
26761
|
-
const declaredIn = classes.get(missing.type);
|
|
26762
26791
|
const key = keyOf(missing);
|
|
26763
|
-
if (
|
|
26792
|
+
if (found.has(key) || missing.kind === "method" && calledOnTheClass.has(key)) {
|
|
26764
26793
|
continue;
|
|
26765
26794
|
}
|
|
26766
|
-
|
|
26767
|
-
|
|
26768
|
-
|
|
26769
|
-
|
|
26770
|
-
|
|
26771
|
-
|
|
26795
|
+
const seen = new Set;
|
|
26796
|
+
let at = classes.get(missing.type);
|
|
26797
|
+
while (at !== undefined && !seen.has(at)) {
|
|
26798
|
+
seen.add(at);
|
|
26799
|
+
const source = sourceAt(at);
|
|
26800
|
+
if (source === null) {
|
|
26801
|
+
break;
|
|
26802
|
+
}
|
|
26803
|
+
if (declaresMember(source, missing)) {
|
|
26804
|
+
found.set(key, { ...missing, declaredIn: at });
|
|
26805
|
+
break;
|
|
26806
|
+
}
|
|
26807
|
+
at = baseScriptOf(source, at, classes);
|
|
26772
26808
|
}
|
|
26773
26809
|
}
|
|
26774
26810
|
return [...found.values()];
|
|
@@ -26781,7 +26817,7 @@ function staleAnalysisNote(contradicted, dependsOn) {
|
|
|
26781
26817
|
const onInstances = contradicted.filter((one) => one.kind === "method" || one.kind === "property");
|
|
26782
26818
|
const onTheClass = contradicted.filter((one) => one.kind === "member" || one.kind === "static method" || one.kind === "enum member");
|
|
26783
26819
|
return [
|
|
26784
|
-
`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
|
|
26820
|
+
`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 script under its " + "declaredIn, the class itself or a project class it extends, so those diagnostics are wrong " + "however the code is written.",
|
|
26785
26821
|
instanceRemedy(onInstances, dependsOn, onTheClass.length > 0),
|
|
26786
26822
|
classRemedy(onTheClass)
|
|
26787
26823
|
].filter((part) => part !== "").join(" ");
|
|
@@ -26798,7 +26834,7 @@ function instanceRemedy(entries, dependsOn, besideOthers) {
|
|
|
26798
26834
|
}
|
|
26799
26835
|
const declaring = declaringScripts(entries);
|
|
26800
26836
|
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`;
|
|
26801
|
-
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
|
|
26837
|
+
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 what that added under methodsAdded and constantsAdded. 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.";
|
|
26802
26838
|
}
|
|
26803
26839
|
function classRemedy(entries) {
|
|
26804
26840
|
if (entries.length === 0) {
|
|
@@ -26822,6 +26858,24 @@ function notReloadedNote(broughtIn, notReloaded) {
|
|
|
26822
26858
|
}
|
|
26823
26859
|
return `The scan brought in ${broughtIn.join(", ")}, and ${all.join(", ")} ${all.length === 1 ? "names one of them and" : "name them and"} could not be reloaded, so the editor still holds what it compiled while it could not see the class and reports that to the language server. ${remedies.join(" ")}`;
|
|
26824
26860
|
}
|
|
26861
|
+
function reloadDifference(reloaded) {
|
|
26862
|
+
const between = (before, after) => {
|
|
26863
|
+
if (before === undefined || after === undefined) {
|
|
26864
|
+
return null;
|
|
26865
|
+
}
|
|
26866
|
+
const was = new Set(before);
|
|
26867
|
+
const is = new Set(after);
|
|
26868
|
+
return { added: after.filter((name) => !was.has(name)), removed: before.filter((name) => !is.has(name)) };
|
|
26869
|
+
};
|
|
26870
|
+
const methods = between(reloaded.heldBefore, reloaded.methods);
|
|
26871
|
+
const constants = between(reloaded.heldConstantsBefore, reloaded.constants);
|
|
26872
|
+
return {
|
|
26873
|
+
...reloaded.methods === undefined ? {} : { methodsHeld: reloaded.methods.length },
|
|
26874
|
+
...methods === null ? {} : { methodsAdded: methods.added, methodsRemoved: methods.removed },
|
|
26875
|
+
...reloaded.constants === undefined ? {} : { constantsHeld: reloaded.constants.length },
|
|
26876
|
+
...constants === null ? {} : { constantsAdded: constants.added, constantsRemoved: constants.removed }
|
|
26877
|
+
};
|
|
26878
|
+
}
|
|
26825
26879
|
function failedReloadNote(failed) {
|
|
26826
26880
|
const missing = (before, after) => before.filter((name) => !after.includes(name));
|
|
26827
26881
|
const lost = [
|
|
@@ -26837,7 +26891,7 @@ function failedReloadNote(failed) {
|
|
|
26837
26891
|
} else if (!failed.restored) {
|
|
26838
26892
|
copy = `${unusable} The editor compiled it again from the text it was built from, and that failed too${failed.restoredAs === "" ? "" : ` (${failed.restoredAs})`}, so it stays that way until the script compiles: reload it then, or restart the editor with editor_launch restart.`;
|
|
26839
26893
|
} else if (unchanged) {
|
|
26840
|
-
copy = "The copy the editor holds was compiled again from the text it was built from, which puts it back as it was, with the members
|
|
26894
|
+
copy = "The copy the editor holds was compiled again from the text it was built from, which puts it back as it was, with the members it held before, which listMembers lists.";
|
|
26841
26895
|
} else {
|
|
26842
26896
|
copy = `The copy the editor holds was compiled again from the text it was built from and 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.`;
|
|
26843
26897
|
}
|
|
@@ -28516,7 +28570,7 @@ class GodotBridge extends EventEmitter {
|
|
|
28516
28570
|
case "godot_ready":
|
|
28517
28571
|
if (this.connectionInfo) {
|
|
28518
28572
|
if (this.belongsElsewhere(message.project_path)) {
|
|
28519
|
-
this.
|
|
28573
|
+
this.elsewhereUnlessOneShot(message.project_path, message.editor_pid, message.opened_by_a_server === true);
|
|
28520
28574
|
return;
|
|
28521
28575
|
}
|
|
28522
28576
|
this.connectionInfo.projectPath = message.project_path;
|
|
@@ -28627,6 +28681,34 @@ class GodotBridge extends EventEmitter {
|
|
|
28627
28681
|
const fits = closeReason(said, "start") === said;
|
|
28628
28682
|
stranger?.close(OTHER_PROJECT_CLOSE_CODE, fits ? said : closeReason(`this server serves ${this.ownProject ?? ""}`, "end"));
|
|
28629
28683
|
}
|
|
28684
|
+
elsewhereUnlessOneShot(theirProject, pid, openedByAServer) {
|
|
28685
|
+
const known = pid === undefined ? undefined : this.oneShots.get(pid);
|
|
28686
|
+
if (pid !== undefined && known !== undefined) {
|
|
28687
|
+
this.turnAway(pid, known);
|
|
28688
|
+
return;
|
|
28689
|
+
}
|
|
28690
|
+
const info = this.connectionInfo;
|
|
28691
|
+
const socket = this.socket;
|
|
28692
|
+
const reader = this.oneShotReader;
|
|
28693
|
+
if (pid === undefined || openedByAServer || info === null || reader === null) {
|
|
28694
|
+
this.sendElsewhere(theirProject);
|
|
28695
|
+
return;
|
|
28696
|
+
}
|
|
28697
|
+
info.identifying = true;
|
|
28698
|
+
reader(pid).catch(() => {
|
|
28699
|
+
return;
|
|
28700
|
+
}).then((option) => {
|
|
28701
|
+
if (this.socket !== socket || this.connectionInfo !== info) {
|
|
28702
|
+
return;
|
|
28703
|
+
}
|
|
28704
|
+
if (typeof option === "string") {
|
|
28705
|
+
this.oneShots.set(pid, option);
|
|
28706
|
+
this.turnAway(pid, option);
|
|
28707
|
+
return;
|
|
28708
|
+
}
|
|
28709
|
+
this.sendElsewhere(theirProject);
|
|
28710
|
+
});
|
|
28711
|
+
}
|
|
28630
28712
|
identify(pid) {
|
|
28631
28713
|
const known = this.oneShots.get(pid);
|
|
28632
28714
|
if (known !== undefined) {
|
|
@@ -29728,6 +29810,7 @@ import { existsSync as existsSync12, realpathSync as realpathSync3 } from "node:
|
|
|
29728
29810
|
import { readFile, realpath } from "node:fs/promises";
|
|
29729
29811
|
import { createConnection as createConnection3 } from "node:net";
|
|
29730
29812
|
import { dirname as dirname9, join as join15, resolve as resolve3 } from "node:path";
|
|
29813
|
+
import { setTimeout as delay3 } from "node:timers/promises";
|
|
29731
29814
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
29732
29815
|
var DEFAULT_LSP_PORT = 6005;
|
|
29733
29816
|
var DIAGNOSTICS_TIMEOUT_MS = 5000;
|
|
@@ -29747,6 +29830,14 @@ class LSPTimeout extends Error {
|
|
|
29747
29830
|
this.seconds = timeoutMs / 1000;
|
|
29748
29831
|
}
|
|
29749
29832
|
}
|
|
29833
|
+
var LANGUAGE_SERVER_START_MS = 30000;
|
|
29834
|
+
var LANGUAGE_SERVER_RETRY_MS = 250;
|
|
29835
|
+
|
|
29836
|
+
class LanguageServerNotListening extends Error {
|
|
29837
|
+
constructor(port, waitedMs) {
|
|
29838
|
+
super(`The editor on the bridge says its language server is on port ${port}, and nothing accepted a connection there in ${String(waitedMs / 1000)}s. An editor starts it a few seconds after it reaches the bridge, so straight after editor_launch this is the editor still starting, and calling again answers. One that goes on refusing could not take the port; editor_output shows what the editor printed.`);
|
|
29839
|
+
}
|
|
29840
|
+
}
|
|
29750
29841
|
|
|
29751
29842
|
class ArgumentRefusal extends Refusal {
|
|
29752
29843
|
}
|
|
@@ -29794,17 +29885,38 @@ class GodotLSPClient {
|
|
|
29794
29885
|
documentVersions = new Map;
|
|
29795
29886
|
documentTurns = new Map;
|
|
29796
29887
|
timeouts;
|
|
29888
|
+
servedHere;
|
|
29797
29889
|
constructor(port = portFromEnv("GDHARNESS_LSP_PORT", DEFAULT_LSP_PORT), host = "127.0.0.1", timeouts = {
|
|
29798
29890
|
requestMs: REQUEST_TIMEOUT_MS2,
|
|
29799
29891
|
referencesMs: REFERENCES_TIMEOUT_MS,
|
|
29800
|
-
initializeMs: initializeTimeoutMs()
|
|
29801
|
-
|
|
29892
|
+
initializeMs: initializeTimeoutMs(),
|
|
29893
|
+
listenMs: LANGUAGE_SERVER_START_MS
|
|
29894
|
+
}, servedHere = () => false) {
|
|
29802
29895
|
this.port = port;
|
|
29803
29896
|
this.host = host;
|
|
29804
29897
|
this.timeouts = timeouts;
|
|
29898
|
+
this.servedHere = servedHere;
|
|
29805
29899
|
this.pendingRequests = new Map;
|
|
29806
29900
|
}
|
|
29807
29901
|
async connect() {
|
|
29902
|
+
const until = Date.now() + this.timeouts.listenMs;
|
|
29903
|
+
for (;; ) {
|
|
29904
|
+
try {
|
|
29905
|
+
await this.connectOnce();
|
|
29906
|
+
return;
|
|
29907
|
+
} catch (error) {
|
|
29908
|
+
const refused = error instanceof Error && error.message.includes("ECONNREFUSED");
|
|
29909
|
+
if (!refused || !this.servedHere()) {
|
|
29910
|
+
throw error;
|
|
29911
|
+
}
|
|
29912
|
+
if (Date.now() >= until) {
|
|
29913
|
+
throw new LanguageServerNotListening(this.port, this.timeouts.listenMs);
|
|
29914
|
+
}
|
|
29915
|
+
await delay3(LANGUAGE_SERVER_RETRY_MS);
|
|
29916
|
+
}
|
|
29917
|
+
}
|
|
29918
|
+
}
|
|
29919
|
+
async connectOnce() {
|
|
29808
29920
|
if (this.connected && this.socket) {
|
|
29809
29921
|
return;
|
|
29810
29922
|
}
|
|
@@ -30493,7 +30605,8 @@ async function handleLSPTool(client, toolName, args) {
|
|
|
30493
30605
|
...error instanceof AnotherProjectsServer ? { serves: error.serves } : {},
|
|
30494
30606
|
...error instanceof ArgumentRefusal ? { refusedArguments: true } : {},
|
|
30495
30607
|
...error instanceof NoDiagnosticsPublished ? { aboutThisFile: true } : {},
|
|
30496
|
-
...error instanceof LSPTimeout && error.method === "initialize" ? { stillReading: true } : {}
|
|
30608
|
+
...error instanceof LSPTimeout && error.method === "initialize" ? { stillReading: true } : {},
|
|
30609
|
+
...error instanceof LanguageServerNotListening ? { stillReading: true } : {}
|
|
30497
30610
|
});
|
|
30498
30611
|
}
|
|
30499
30612
|
}
|
|
@@ -30829,7 +30942,7 @@ function searchProject(projectPath, options) {
|
|
|
30829
30942
|
|
|
30830
30943
|
// src/rename.ts
|
|
30831
30944
|
import { existsSync as existsSync14, mkdirSync as mkdirSync8, readFileSync as readFileSync15, renameSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync10 } from "node:fs";
|
|
30832
|
-
import { dirname as dirname10, posix } from "node:path";
|
|
30945
|
+
import { dirname as dirname10, posix as posix2 } from "node:path";
|
|
30833
30946
|
class Script {
|
|
30834
30947
|
path;
|
|
30835
30948
|
text;
|
|
@@ -30941,13 +31054,13 @@ class ClassGraph {
|
|
|
30941
31054
|
}
|
|
30942
31055
|
function resolvedPath(written, from) {
|
|
30943
31056
|
if (written.startsWith("res://")) {
|
|
30944
|
-
return `res://${
|
|
31057
|
+
return `res://${posix2.normalize(written.slice("res://".length))}`;
|
|
30945
31058
|
}
|
|
30946
|
-
const directory =
|
|
30947
|
-
return `res://${
|
|
31059
|
+
const directory = posix2.dirname(from.slice("res://".length));
|
|
31060
|
+
return `res://${posix2.normalize(posix2.join(directory, written))}`;
|
|
30948
31061
|
}
|
|
30949
31062
|
function relativeTo(target, from) {
|
|
30950
|
-
return
|
|
31063
|
+
return posix2.relative(posix2.dirname(from.slice("res://".length)), target.slice("res://".length));
|
|
30951
31064
|
}
|
|
30952
31065
|
function scriptsOf(texts) {
|
|
30953
31066
|
const scripts = new Map;
|
|
@@ -33678,7 +33791,7 @@ var TOOL_SPECS = [
|
|
|
33678
33791
|
},
|
|
33679
33792
|
{
|
|
33680
33793
|
name: "script_diagnostics",
|
|
33681
|
-
description: "Errors and warnings for a script, or for several, from the editor's language server, and whether each is clean. Needs the editor running. Each diagnostic carries its severity (error or warning), its message, and where it is as the editor shows it: line and column counted from 1, and endLine and endColumn just past its end. 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.",
|
|
33794
|
+
description: "Errors and warnings for a script, or for several, from the editor's language server, and whether each is clean. Needs the editor running. Each diagnostic carries its severity (error or warning), its message, and where it is as the editor shows it: line and column counted from 1, and endLine and endColumn just past its end. 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, or a project class it extends does, a method, property, constant, static function or enum member, comes back under contradictedByTheFile with the script declaring it, and so does one a diagnostic names alone as an external class member, read against the class it is looked up on in the line it is on; 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.",
|
|
33682
33795
|
parameters: {
|
|
33683
33796
|
projectPath: PROJECT_PATH,
|
|
33684
33797
|
scriptPath: {
|
|
@@ -33903,7 +34016,11 @@ var TOOL_SPECS = [
|
|
|
33903
34016
|
},
|
|
33904
34017
|
reloadScript: {
|
|
33905
34018
|
type: "string",
|
|
33906
|
-
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.
|
|
34019
|
+
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. What the reload changed comes back under methodsAdded, methodsRemoved, constantsAdded and constantsRemoved, with each enum value as Kind.SHORT among the constants, since an enum member is held as a constant and never shows among the methods; methodsHeld and constantsHeld count what the copy holds afterwards, because a reload that compiled nothing and answered OK is the failure worth catching. listMembers adds the whole lists. 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. A @tool script is not reloaded either, since it runs inside the editor and reloading one from inside a call can take the editor down: reloadProblem says so, and editor_launch restart recompiles it. A path spelled in another case than the file is reloaded under the spelling on disk, which is the one the editor holds its copy under, and that spelling comes back under reloadedAs."
|
|
34020
|
+
},
|
|
34021
|
+
listMembers: {
|
|
34022
|
+
type: "boolean",
|
|
34023
|
+
description: "With reloadScript: also answer the whole member lists, heldBeforeReload and reloadedMethods for the methods, heldConstantsBeforeReload and reloadedConstants for the constants. Default false: a script holding hundreds of constants answers hundreds of lines twice to say what one reload added."
|
|
33907
34024
|
}
|
|
33908
34025
|
},
|
|
33909
34026
|
requires: ["projectPath"]
|
|
@@ -34589,7 +34706,7 @@ async function untilGone(pids, withinMs = STOP_WAIT_MS) {
|
|
|
34589
34706
|
const gone = () => pids.every((pid) => !alive(pid));
|
|
34590
34707
|
const deadline = Date.now() + withinMs;
|
|
34591
34708
|
while (!gone() && Date.now() < deadline) {
|
|
34592
|
-
await
|
|
34709
|
+
await delay4(50);
|
|
34593
34710
|
}
|
|
34594
34711
|
return gone();
|
|
34595
34712
|
}
|
|
@@ -35271,6 +35388,7 @@ function scriptsToDiagnose(projectPath, given) {
|
|
|
35271
35388
|
}
|
|
35272
35389
|
return { ok: true, value: unique };
|
|
35273
35390
|
}
|
|
35391
|
+
var EXTERNAL_MEMBER = 'Could not resolve external class member "';
|
|
35274
35392
|
function isDirectory(path) {
|
|
35275
35393
|
try {
|
|
35276
35394
|
return statSync8(path).isDirectory();
|
|
@@ -35450,7 +35568,7 @@ class GodotServer {
|
|
|
35450
35568
|
taken = true;
|
|
35451
35569
|
this.logDebug(`Took port ${wanted} over from pid ${announced.pid}`);
|
|
35452
35570
|
} catch {
|
|
35453
|
-
await
|
|
35571
|
+
await delay4(HANDOVER_POLL_MS);
|
|
35454
35572
|
}
|
|
35455
35573
|
}
|
|
35456
35574
|
this.notHandedOverBy = taken ? null : announced.pid;
|
|
@@ -37221,14 +37339,14 @@ class GodotServer {
|
|
|
37221
37339
|
}
|
|
37222
37340
|
whereTheFileDisagrees(diagnostics, args) {
|
|
37223
37341
|
const messages = diagnostics.map((entry) => asParams(entry)["message"]).filter((message) => typeof message === "string");
|
|
37224
|
-
const worthChecking = messages.some((message) => missingMemberIn(message) !== null || unknownTypeIn(message) !== null);
|
|
37342
|
+
const worthChecking = messages.some((message) => missingMemberIn(message) !== null || unknownTypeIn(message) !== null || message.includes(EXTERNAL_MEMBER));
|
|
37225
37343
|
const project = worthChecking ? this.project(args) : null;
|
|
37226
37344
|
if (project === null || !project.ok) {
|
|
37227
37345
|
return {};
|
|
37228
37346
|
}
|
|
37229
37347
|
const projectPath = project.value.path;
|
|
37230
37348
|
const cached = cachedClasses(projectPath);
|
|
37231
|
-
const
|
|
37349
|
+
const sourceOf = (resourcePath) => {
|
|
37232
37350
|
const contained = resolveWithinProject(projectPath, resourcePath);
|
|
37233
37351
|
if (!contained.ok || !existsSync17(contained.absolutePath)) {
|
|
37234
37352
|
return null;
|
|
@@ -37238,7 +37356,16 @@ class GodotServer {
|
|
|
37238
37356
|
} catch {
|
|
37239
37357
|
return null;
|
|
37240
37358
|
}
|
|
37241
|
-
}
|
|
37359
|
+
};
|
|
37360
|
+
const external = diagnostics.filter((entry) => String(asParams(entry)["message"]).includes(EXTERNAL_MEMBER));
|
|
37361
|
+
const diagnosed = external.length === 0 ? null : sourceOf(readString(args, "scriptPath") ?? "");
|
|
37362
|
+
const lines = diagnosed?.split(`
|
|
37363
|
+
`) ?? [];
|
|
37364
|
+
const alsoDenied = cached === null ? [] : external.map((entry) => {
|
|
37365
|
+
const { message, line } = asParams(entry);
|
|
37366
|
+
return externalMemberIn(String(message), typeof line === "number" ? lines[line - 1] : undefined, cached);
|
|
37367
|
+
}).filter((missing) => missing !== null);
|
|
37368
|
+
const contradicted = cached === null ? [] : contradictedDiagnostics(messages, cached, sourceOf, alsoDenied);
|
|
37242
37369
|
const unloaded = messages.some((message) => unknownTypeIn(message) !== null) ? unloadedTypes(messages, declaredClasses(projectPath), cached) : [];
|
|
37243
37370
|
const notes = [];
|
|
37244
37371
|
if (contradicted.length > 0) {
|
|
@@ -37308,7 +37435,10 @@ class GodotServer {
|
|
|
37308
37435
|
await this.lspClient.disconnect();
|
|
37309
37436
|
this.lspClient = null;
|
|
37310
37437
|
}
|
|
37311
|
-
this.lspClient ??= new GodotLSPClient(port)
|
|
37438
|
+
this.lspClient ??= new GodotLSPClient(port, undefined, undefined, () => {
|
|
37439
|
+
const status = this.godotBridge.getStatus();
|
|
37440
|
+
return status.connected && status.lspPort === port;
|
|
37441
|
+
});
|
|
37312
37442
|
return this.lspClient;
|
|
37313
37443
|
}
|
|
37314
37444
|
async handleDAP(toolName, args) {
|
|
@@ -37488,7 +37618,7 @@ class GodotServer {
|
|
|
37488
37618
|
try {
|
|
37489
37619
|
const askedAt = Date.now();
|
|
37490
37620
|
const asked = this.godotBridge.invokeTool("playing_status", {});
|
|
37491
|
-
const answer = await Promise.race([asked,
|
|
37621
|
+
const answer = await Promise.race([asked, delay4(PLAYING_STATUS_MS).then(() => null)]);
|
|
37492
37622
|
if (answer === null) {
|
|
37493
37623
|
this.logDebug(`playing_status did not answer within ${PLAYING_STATUS_MS}ms`);
|
|
37494
37624
|
asked.then(() => {
|
|
@@ -37513,7 +37643,7 @@ class GodotServer {
|
|
|
37513
37643
|
try {
|
|
37514
37644
|
const answer = await Promise.race([
|
|
37515
37645
|
this.godotBridge.invokeTool("scan_status", {}),
|
|
37516
|
-
|
|
37646
|
+
delay4(PLAYING_STATUS_MS).then(() => null)
|
|
37517
37647
|
]);
|
|
37518
37648
|
if (answer === null) {
|
|
37519
37649
|
return null;
|
|
@@ -37725,7 +37855,7 @@ class GodotServer {
|
|
|
37725
37855
|
if (Date.now() >= deadline) {
|
|
37726
37856
|
return false;
|
|
37727
37857
|
}
|
|
37728
|
-
await
|
|
37858
|
+
await delay4(250);
|
|
37729
37859
|
}
|
|
37730
37860
|
}
|
|
37731
37861
|
async handleLaunchEditor(args) {
|
|
@@ -38235,7 +38365,7 @@ class GodotServer {
|
|
|
38235
38365
|
if (Date.now() >= deadline) {
|
|
38236
38366
|
return false;
|
|
38237
38367
|
}
|
|
38238
|
-
await
|
|
38368
|
+
await delay4(100);
|
|
38239
38369
|
}
|
|
38240
38370
|
}
|
|
38241
38371
|
async endActiveGame(reason, going) {
|
|
@@ -38827,7 +38957,7 @@ class GodotServer {
|
|
|
38827
38957
|
while (cancelled?.aborted !== true && await this.runStillGoing(run) && Date.now() < until) {
|
|
38828
38958
|
this.drainEditorOutput(run);
|
|
38829
38959
|
this.drainTranscript(run);
|
|
38830
|
-
await
|
|
38960
|
+
await delay4(RUN_POLL_MS);
|
|
38831
38961
|
}
|
|
38832
38962
|
return await this.handleGetDebugOutput(args, await this.runStillGoing(run) ? budgetMs : undefined);
|
|
38833
38963
|
}
|
|
@@ -38864,7 +38994,7 @@ class GodotServer {
|
|
|
38864
38994
|
if (Date.now() >= until) {
|
|
38865
38995
|
return false;
|
|
38866
38996
|
}
|
|
38867
|
-
await
|
|
38997
|
+
await delay4(50);
|
|
38868
38998
|
}
|
|
38869
38999
|
}
|
|
38870
39000
|
async handleEditorConsole(args) {
|
|
@@ -39374,10 +39504,13 @@ class GodotServer {
|
|
|
39374
39504
|
waitedMs: Date.now() - started,
|
|
39375
39505
|
waitedForEditorScanMs: waitedForEditorMs > 0 ? waitedForEditorMs : undefined,
|
|
39376
39506
|
reloadedAs: reloaded.as,
|
|
39377
|
-
|
|
39378
|
-
|
|
39379
|
-
|
|
39380
|
-
|
|
39507
|
+
...reloadDifference(reloaded),
|
|
39508
|
+
...args["listMembers"] === true ? {
|
|
39509
|
+
heldBeforeReload: reloaded.heldBefore,
|
|
39510
|
+
reloadedMethods: reloaded.methods,
|
|
39511
|
+
heldConstantsBeforeReload: reloaded.heldConstantsBefore,
|
|
39512
|
+
reloadedConstants: reloaded.constants
|
|
39513
|
+
} : {},
|
|
39381
39514
|
reloadProblem: reloaded.problem,
|
|
39382
39515
|
unseenByEditor: unseen.length > 0 ? unseen : undefined,
|
|
39383
39516
|
broughtIn: broughtIn.length > 0 ? broughtIn : undefined,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdharness",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.55",
|
|
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",
|