gdharness 1.2.15 → 1.2.16
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 +346 -76
- package/build/godot/addons/gdharness_editor/property_values.gd +28 -0
- package/build/godot/addons/gdharness_editor/scene_file.gd +27 -8
- package/build/godot/addons/gdharness_editor/serialisation.gd +20 -0
- package/build/godot/addons/gdharness_editor/tool_executor.gd +4 -1
- package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +172 -0
- package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +60 -47
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +46 -4
- package/build/godot/addons/gdharness_runtime/runtime_capture.gd +13 -3
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +1 -17
- package/build/godot/addons/gdharness_runtime/runtime_targets.gd +29 -3
- package/build/godot/addons/gdharness_runtime/serialisation.gd +20 -0
- package/build/godot/operations/audio_buses.gd +35 -0
- package/build/godot/operations/classdb_queries.gd +31 -2
- package/build/godot/operations/file_walk.gd +9 -2
- package/build/godot/operations/gdscript_authoring.gd +4 -5
- package/build/godot/operations/godot_operations.gd +4 -0
- package/build/godot/operations/input_actions.gd +27 -0
- package/build/godot/operations/project_config.gd +8 -1
- package/build/godot/operations/project_diagnostics.gd +9 -5
- package/build/godot/operations/serialisation.gd +20 -0
- package/build/index.js +333 -63
- package/package.json +1 -1
package/build/index.js
CHANGED
|
@@ -28352,10 +28352,58 @@ function editorConsole(projectPath, editorPid, openedByAServer) {
|
|
|
28352
28352
|
}
|
|
28353
28353
|
const log = new GameLog;
|
|
28354
28354
|
const { text, of } = theFirstOf(path);
|
|
28355
|
-
log.append("transcript", text);
|
|
28355
|
+
log.append("transcript", foldProgress(text));
|
|
28356
28356
|
log.finish();
|
|
28357
28357
|
return { log, path, ...of === undefined ? {} : { readBytes: text.length, of } };
|
|
28358
28358
|
}
|
|
28359
|
+
var PROGRESS_STEP = /^\[\s*(\d+)% \] (\S+) \| (.*)$/;
|
|
28360
|
+
var PROGRESS_DONE = /^\[ DONE \] (\S+)\s*$/;
|
|
28361
|
+
function foldProgress(text) {
|
|
28362
|
+
const out = [];
|
|
28363
|
+
const open = new Map;
|
|
28364
|
+
for (const raw of text.split(`
|
|
28365
|
+
`)) {
|
|
28366
|
+
const line = raw.replace(/\r$/, "").replace(COLOUR_CODE, "");
|
|
28367
|
+
const step = PROGRESS_STEP.exec(line);
|
|
28368
|
+
if (step !== null) {
|
|
28369
|
+
const [, percent = "", name = "", said = ""] = step;
|
|
28370
|
+
const started = /^Started (.*)$/.exec(said);
|
|
28371
|
+
if (started !== null) {
|
|
28372
|
+
const task = { name, label: started[1] ?? "", percent, done: false };
|
|
28373
|
+
open.set(name, task);
|
|
28374
|
+
out.push(task);
|
|
28375
|
+
continue;
|
|
28376
|
+
}
|
|
28377
|
+
const task = open.get(name);
|
|
28378
|
+
if (task !== undefined) {
|
|
28379
|
+
task.percent = percent;
|
|
28380
|
+
if (said.trim() !== "") {
|
|
28381
|
+
task.last = said.trim();
|
|
28382
|
+
}
|
|
28383
|
+
continue;
|
|
28384
|
+
}
|
|
28385
|
+
}
|
|
28386
|
+
const finished = PROGRESS_DONE.exec(line);
|
|
28387
|
+
const task = finished === null ? undefined : open.get(finished[1] ?? "");
|
|
28388
|
+
if (task !== undefined) {
|
|
28389
|
+
task.done = true;
|
|
28390
|
+
open.delete(task.name);
|
|
28391
|
+
continue;
|
|
28392
|
+
}
|
|
28393
|
+
out.push(raw);
|
|
28394
|
+
}
|
|
28395
|
+
return out.map((entry) => {
|
|
28396
|
+
if (typeof entry === "string") {
|
|
28397
|
+
return entry;
|
|
28398
|
+
}
|
|
28399
|
+
if (entry.done) {
|
|
28400
|
+
return `[ DONE ] ${entry.name} | ${entry.label}`;
|
|
28401
|
+
}
|
|
28402
|
+
const last = entry.last === undefined ? "" : `, and the last step it named was "${entry.last}"`;
|
|
28403
|
+
return `[ ${entry.percent}% ] ${entry.name} | ${entry.label}, which has not finished${last}`;
|
|
28404
|
+
}).join(`
|
|
28405
|
+
`);
|
|
28406
|
+
}
|
|
28359
28407
|
var MOST_CONSOLE_BYTES = 8 * 1024 * 1024;
|
|
28360
28408
|
function theFirstOf(path) {
|
|
28361
28409
|
const whole = statSync3(path).size;
|
|
@@ -28615,6 +28663,12 @@ function theEditorHasComeBack(status, startedAt) {
|
|
|
28615
28663
|
function hasSaidWhoItIs(status) {
|
|
28616
28664
|
return status.connected && status.addonVersion !== undefined;
|
|
28617
28665
|
}
|
|
28666
|
+
function withoutTrailingSeparator(path) {
|
|
28667
|
+
if (path.length <= 1 || !/[\\/]$/.test(path) || /^[A-Za-z]:[\\/]$/.test(path)) {
|
|
28668
|
+
return path;
|
|
28669
|
+
}
|
|
28670
|
+
return path.slice(0, -1);
|
|
28671
|
+
}
|
|
28618
28672
|
function anEditorIsStillComing(listeningSince, launchedEditorIsAlive, now = Date.now()) {
|
|
28619
28673
|
return mayYetConnect(listeningSince, now) || launchedEditorIsAlive;
|
|
28620
28674
|
}
|
|
@@ -28915,7 +28969,7 @@ class GodotBridge extends EventEmitter {
|
|
|
28915
28969
|
this.elsewhereUnlessOneShot(message.project_path, message.editor_pid, message.opened_by_a_server === true);
|
|
28916
28970
|
return;
|
|
28917
28971
|
}
|
|
28918
|
-
this.connectionInfo.projectPath = message.project_path;
|
|
28972
|
+
this.connectionInfo.projectPath = withoutTrailingSeparator(message.project_path);
|
|
28919
28973
|
this.connectionInfo.addonVersion = message.addon_version ?? "";
|
|
28920
28974
|
this.connectionInfo.addonDigest = message.addon_digest;
|
|
28921
28975
|
this.connectionInfo.editorPid = message.editor_pid;
|
|
@@ -29664,9 +29718,11 @@ var HEADLESS_OPERATIONS = dictionary({
|
|
|
29664
29718
|
remove_autoload: "remove_autoload",
|
|
29665
29719
|
set_main_scene: "set_main_scene",
|
|
29666
29720
|
add_input_action: "add_input_action",
|
|
29721
|
+
remove_input_action: "remove_input_action",
|
|
29667
29722
|
enable_plugin: "enable_plugin",
|
|
29668
29723
|
disable_plugin: "disable_plugin",
|
|
29669
29724
|
add_audio_bus: "create_audio_bus",
|
|
29725
|
+
remove_audio_bus: "remove_audio_bus",
|
|
29670
29726
|
set_audio_bus_effect: "set_audio_bus_effect",
|
|
29671
29727
|
set_audio_bus_volume: "set_audio_bus_volume"
|
|
29672
29728
|
}),
|
|
@@ -30883,6 +30939,29 @@ function shownSymbol(entry) {
|
|
|
30883
30939
|
...children.length > 0 ? { children: children.map(shownSymbol) } : {}
|
|
30884
30940
|
};
|
|
30885
30941
|
}
|
|
30942
|
+
var MOST_COMPLETIONS = 100;
|
|
30943
|
+
function typedBefore(content, line, character) {
|
|
30944
|
+
const text = content.split(/\r?\n/)[line] ?? "";
|
|
30945
|
+
return /[\p{L}\p{N}_]*$/u.exec(text.slice(0, character))?.[0] ?? "";
|
|
30946
|
+
}
|
|
30947
|
+
function narrowedCompletions(completions, typed) {
|
|
30948
|
+
const wanted = typed.toLowerCase();
|
|
30949
|
+
const matching = wanted === "" ? completions : completions.filter((entry) => {
|
|
30950
|
+
const record = asRecord(entry);
|
|
30951
|
+
const name = typeof record["filterText"] === "string" ? record["filterText"] : record["label"];
|
|
30952
|
+
return typeof name === "string" && name.toLowerCase().startsWith(wanted);
|
|
30953
|
+
});
|
|
30954
|
+
const shown = matching.slice(0, MOST_COMPLETIONS);
|
|
30955
|
+
return {
|
|
30956
|
+
...typed === "" ? {} : { typed },
|
|
30957
|
+
completions: shown.map(shownCompletion),
|
|
30958
|
+
total: matching.length,
|
|
30959
|
+
...matching.length > shown.length ? {
|
|
30960
|
+
omitted: matching.length - shown.length,
|
|
30961
|
+
note: `${matching.length - shown.length} more are offered here than one answer carries; ask at a position after more of the name to narrow them.`
|
|
30962
|
+
} : {}
|
|
30963
|
+
};
|
|
30964
|
+
}
|
|
30886
30965
|
function shownCompletion(entry) {
|
|
30887
30966
|
const { data: _request, kind, ...rest } = asRecord(entry);
|
|
30888
30967
|
return { ...rest, kind: named(kind, COMPLETION_KINDS) };
|
|
@@ -30953,7 +31032,7 @@ async function handleLSPTool(client, toolName, args) {
|
|
|
30953
31032
|
case "lsp_get_completions": {
|
|
30954
31033
|
const { line, character } = protocolPosition(parsedArgs);
|
|
30955
31034
|
const completions = await client.getCompletions(scriptPath, content, line, character);
|
|
30956
|
-
return asToolResponse(
|
|
31035
|
+
return asToolResponse(narrowedCompletions(completions, typedBefore(content, line, character)));
|
|
30957
31036
|
}
|
|
30958
31037
|
case "lsp_get_hover": {
|
|
30959
31038
|
const { line, character } = protocolPosition(parsedArgs);
|
|
@@ -31123,9 +31202,16 @@ var SKIPPED = new Set(["node_modules"]);
|
|
|
31123
31202
|
function skipped(name) {
|
|
31124
31203
|
return name.startsWith(".") || SKIPPED.has(name);
|
|
31125
31204
|
}
|
|
31205
|
+
var VERSION_MARKER = ".gdharness-version";
|
|
31126
31206
|
function steppedOver(directory) {
|
|
31207
|
+
return gdignored(directory) || installedByUs(directory);
|
|
31208
|
+
}
|
|
31209
|
+
function gdignored(directory) {
|
|
31127
31210
|
return existsSync13(join16(directory, ".gdignore"));
|
|
31128
31211
|
}
|
|
31212
|
+
function installedByUs(directory) {
|
|
31213
|
+
return existsSync13(join16(directory, VERSION_MARKER));
|
|
31214
|
+
}
|
|
31129
31215
|
var SCENE_EXTENSIONS = new Set(["tscn", "scn"]);
|
|
31130
31216
|
var ASSET_EXTENSIONS = new Set([
|
|
31131
31217
|
"png",
|
|
@@ -31281,7 +31367,10 @@ function projectTexts(projectPath) {
|
|
|
31281
31367
|
function ignoredTexts(projectPath) {
|
|
31282
31368
|
const found = [];
|
|
31283
31369
|
const visit = (directory, prefix, ignored) => {
|
|
31284
|
-
|
|
31370
|
+
if (installedByUs(directory)) {
|
|
31371
|
+
return;
|
|
31372
|
+
}
|
|
31373
|
+
const under = ignored || gdignored(directory);
|
|
31285
31374
|
for (const entry of readdirSync5(directory, { withFileTypes: true })) {
|
|
31286
31375
|
if (skipped(entry.name)) {
|
|
31287
31376
|
continue;
|
|
@@ -33939,10 +34028,14 @@ var TOOL_SPECS = [
|
|
|
33939
34028
|
{
|
|
33940
34029
|
name: "project_settings",
|
|
33941
34030
|
title: "Project settings",
|
|
33942
|
-
description: "Reads or writes project.godot: settings, autoloads, the main scene, input actions, plugins and audio buses. A write with an editor open on the project is handed to the editor, which takes the settings the write changed or names from the file (named under editorAdopted), including a setting the file already held, since the editor can hold another value after the file was put back, or reloads the bus layout, so it answers the new values and does not save the old ones back over them. editorNote is an editor that could not take the write, one on an addon from before this, which still holds the old values until editor_launch restart. An enabled plugin's code still loads on the editor's next start. set answers old_value and new_value, with was_new true when project.godot did not set the setting before, and was_default true when what it held was the engine's own default, which old_value then is.",
|
|
34031
|
+
description: "Reads or writes project.godot: settings, autoloads, the main scene, input actions, plugins and audio buses. A write with an editor open on the project is handed to the editor, which takes the settings the write changed or names from the file (named under editorAdopted), including a setting the file already held, since the editor can hold another value after the file was put back, or reloads the bus layout, so it answers the new values and does not save the old ones back over them. editorNote is an editor that could not take the write, one on an addon from before this, which still holds the old values until editor_launch restart. An enabled plugin's code still loads on the editor's next start. set answers old_value and new_value, with was_new true when project.godot did not set the setting before, and was_default true when what it held was the engine's own default, which old_value then is. get answers exists, true for any setting the engine knows, and at_default, true when the value is the engine's own default and so has no line in project.godot.",
|
|
33943
34032
|
parameters: {
|
|
33944
34033
|
projectPath: PROJECT_PATH2,
|
|
33945
|
-
setting: {
|
|
34034
|
+
setting: {
|
|
34035
|
+
type: "string",
|
|
34036
|
+
ops: ["get", "set"],
|
|
34037
|
+
description: 'Setting path, such as "display/window/size/viewport_width".'
|
|
34038
|
+
},
|
|
33946
34039
|
from: {
|
|
33947
34040
|
type: "string",
|
|
33948
34041
|
enum: ["disk", "editor"],
|
|
@@ -33956,40 +34049,66 @@ var TOOL_SPECS = [
|
|
|
33956
34049
|
},
|
|
33957
34050
|
value: {
|
|
33958
34051
|
blank: true,
|
|
34052
|
+
ops: ["set"],
|
|
33959
34053
|
description: 'The value to write. Engine types may be tagged, {"_type": "Vector2", "x": 1, "y": 2}, and infinity as {"_type": "float", "value": "inf"}, the form a read answers with. "" writes an empty string.'
|
|
33960
34054
|
},
|
|
33961
|
-
name: { type: "string", description: "Autoload name." },
|
|
33962
|
-
path: {
|
|
34055
|
+
name: { type: "string", ops: ["add_autoload", "remove_autoload"], description: "Autoload name." },
|
|
34056
|
+
path: {
|
|
34057
|
+
type: "string",
|
|
34058
|
+
ops: ["add_autoload"],
|
|
34059
|
+
description: "Autoload script or scene inside the project."
|
|
34060
|
+
},
|
|
33963
34061
|
enabled: {
|
|
33964
34062
|
type: "boolean",
|
|
33965
|
-
|
|
34063
|
+
ops: ["add_autoload", "set_audio_bus_effect"],
|
|
34064
|
+
description: "add_autoload: Godot 4 loads every autoload in the list, so false is refused. Use global: false to keep the name out of the global scope, or remove_autoload to stop it loading. set_audio_bus_effect: whether the effect is on. Default true."
|
|
33966
34065
|
},
|
|
33967
34066
|
global: {
|
|
33968
34067
|
type: "boolean",
|
|
34068
|
+
ops: ["add_autoload"],
|
|
33969
34069
|
description: `Autoloads: make the name a global variable, the * in project.godot and the editor's "Global Variable" column. Default true. The autoload loads either way.`
|
|
33970
34070
|
},
|
|
33971
|
-
scenePath: SCENE_PATH,
|
|
33972
|
-
actionName: {
|
|
33973
|
-
|
|
34071
|
+
scenePath: { ...SCENE_PATH, ops: ["set_main_scene"] },
|
|
34072
|
+
actionName: {
|
|
34073
|
+
type: "string",
|
|
34074
|
+
ops: ["add_input_action", "remove_input_action"],
|
|
34075
|
+
description: 'Input action name, such as "jump".'
|
|
34076
|
+
},
|
|
34077
|
+
events: { ...INPUT_EVENTS, ops: ["add_input_action"] },
|
|
33974
34078
|
deadzone: {
|
|
33975
34079
|
type: "number",
|
|
34080
|
+
ops: ["add_input_action"],
|
|
33976
34081
|
minimum: 0,
|
|
33977
34082
|
maximum: 1,
|
|
33978
34083
|
description: "Input actions: analogue deadzone, 0 to 1. Default 0.5."
|
|
33979
34084
|
},
|
|
33980
|
-
pluginName: {
|
|
33981
|
-
|
|
34085
|
+
pluginName: {
|
|
34086
|
+
type: "string",
|
|
34087
|
+
ops: ["enable_plugin", "disable_plugin"],
|
|
34088
|
+
description: "Folder name under addons/."
|
|
34089
|
+
},
|
|
34090
|
+
busName: { type: "string", ops: ["add_audio_bus", "remove_audio_bus"], description: "Audio bus name." },
|
|
33982
34091
|
parentBusIndex: {
|
|
33983
34092
|
type: "number",
|
|
34093
|
+
ops: ["add_audio_bus"],
|
|
33984
34094
|
description: "Audio buses: the bus to send to. Default 0, Master. The new bus goes right after it, so every later bus moves one along, and the answer lists them all."
|
|
33985
34095
|
},
|
|
33986
|
-
busIndex: {
|
|
34096
|
+
busIndex: {
|
|
34097
|
+
type: "number",
|
|
34098
|
+
ops: ["set_audio_bus_effect", "set_audio_bus_volume"],
|
|
34099
|
+
description: "Audio bus index."
|
|
34100
|
+
},
|
|
33987
34101
|
effectIndex: {
|
|
33988
34102
|
type: "number",
|
|
34103
|
+
ops: ["set_audio_bus_effect"],
|
|
33989
34104
|
description: "Slot on the bus for the effect: the effect there is replaced, and one past the last adds one. Any other slot is refused."
|
|
33990
34105
|
},
|
|
33991
|
-
effectType: {
|
|
33992
|
-
|
|
34106
|
+
effectType: {
|
|
34107
|
+
type: "string",
|
|
34108
|
+
ops: ["set_audio_bus_effect"],
|
|
34109
|
+
description: 'Effect class, such as "AudioEffectReverb".'
|
|
34110
|
+
},
|
|
34111
|
+
volumeDb: { type: "number", ops: ["set_audio_bus_volume"], description: "Bus volume in decibels." }
|
|
33993
34112
|
},
|
|
33994
34113
|
requires: ["projectPath"],
|
|
33995
34114
|
operations: {
|
|
@@ -34011,9 +34130,19 @@ var TOOL_SPECS = [
|
|
|
34011
34130
|
summary: "register an input action and its events",
|
|
34012
34131
|
requires: ["actionName", "events"]
|
|
34013
34132
|
},
|
|
34133
|
+
remove_input_action: {
|
|
34134
|
+
effect: "destructive",
|
|
34135
|
+
summary: "remove an input action the project defines",
|
|
34136
|
+
requires: ["actionName"]
|
|
34137
|
+
},
|
|
34014
34138
|
enable_plugin: { effect: "destructive", summary: "enable an addon", requires: ["pluginName"] },
|
|
34015
34139
|
disable_plugin: { effect: "destructive", summary: "disable an addon", requires: ["pluginName"] },
|
|
34016
34140
|
add_audio_bus: { effect: "additive", summary: "add an audio bus", requires: ["busName"] },
|
|
34141
|
+
remove_audio_bus: {
|
|
34142
|
+
effect: "destructive",
|
|
34143
|
+
summary: "remove an audio bus, sending what sent to it on to where it sent",
|
|
34144
|
+
requires: ["busName"]
|
|
34145
|
+
},
|
|
34017
34146
|
set_audio_bus_effect: {
|
|
34018
34147
|
effect: "destructive",
|
|
34019
34148
|
summary: "put an effect in a slot on a bus",
|
|
@@ -34030,7 +34159,7 @@ var TOOL_SPECS = [
|
|
|
34030
34159
|
name: "project_search",
|
|
34031
34160
|
title: "Search the project",
|
|
34032
34161
|
effect: "read",
|
|
34033
|
-
description: "Searches text or a regular expression across project files and returns file paths with line numbers. What the engine steps over is not searched: directories spelled with a dot, node_modules, and any directory holding a .gdignore. A vendored engine or an export directory is therefore absent from the results rather than matching the code you are looking for in a copy nobody runs.",
|
|
34162
|
+
description: "Searches text or a regular expression across project files and returns file paths with line numbers. What the engine steps over is not searched: directories spelled with a dot, node_modules, and any directory holding a .gdignore. A vendored engine or an export directory is therefore absent from the results rather than matching the code you are looking for in a copy nobody runs. The addons gdharness installed are not searched either, since nobody on the project wrote them.",
|
|
34034
34163
|
parameters: {
|
|
34035
34164
|
projectPath: PROJECT_PATH2,
|
|
34036
34165
|
query: { type: "string", description: "The text or pattern to find." },
|
|
@@ -34192,8 +34321,18 @@ var TOOL_SPECS = [
|
|
|
34192
34321
|
parameters: {
|
|
34193
34322
|
projectPath: PROJECT_PATH2,
|
|
34194
34323
|
scenePath: SCENE_PATH,
|
|
34195
|
-
rootNodeType: {
|
|
34196
|
-
|
|
34324
|
+
rootNodeType: {
|
|
34325
|
+
type: "string",
|
|
34326
|
+
ops: ["create"],
|
|
34327
|
+
description: "create: the root node class. Default Node."
|
|
34328
|
+
},
|
|
34329
|
+
rootNodeName: {
|
|
34330
|
+
type: "string",
|
|
34331
|
+
ops: ["create"],
|
|
34332
|
+
description: "create: the root node name. Default the class name, as the editor names a new root."
|
|
34333
|
+
},
|
|
34334
|
+
script: { type: "string", ops: ["create"], description: "create: a script to attach to the root." },
|
|
34335
|
+
newPath: { type: "string", ops: ["save_as"], description: "save_as: where the copy goes." }
|
|
34197
34336
|
},
|
|
34198
34337
|
requires: ["projectPath", "scenePath"],
|
|
34199
34338
|
operations: {
|
|
@@ -34273,7 +34412,7 @@ var TOOL_SPECS = [
|
|
|
34273
34412
|
{
|
|
34274
34413
|
name: "scene_signal",
|
|
34275
34414
|
title: "Scene signals",
|
|
34276
|
-
description: "Signal connections in a scene file. Needs the editor connected.",
|
|
34415
|
+
description: "Signal connections in a scene file. Each connection answers with its flags as the number and as flagNames; persist is on every connection, since a scene saves no other kind. Needs the editor connected.",
|
|
34277
34416
|
parameters: {
|
|
34278
34417
|
projectPath: PROJECT_PATH2,
|
|
34279
34418
|
scenePath: SCENE_PATH,
|
|
@@ -34302,43 +34441,78 @@ var TOOL_SPECS = [
|
|
|
34302
34441
|
{
|
|
34303
34442
|
name: "scene_animation",
|
|
34304
34443
|
title: "Animations",
|
|
34305
|
-
description: "Animations in an AnimationPlayer and states in an AnimationTree state machine. Needs the editor connected.",
|
|
34444
|
+
description: "Animations in an AnimationPlayer and states in an AnimationTree state machine. read answers with every track of an animation in the shape add_track takes, so a track read off one animation can be added to another. Needs the editor connected.",
|
|
34306
34445
|
parameters: {
|
|
34307
34446
|
projectPath: PROJECT_PATH2,
|
|
34308
34447
|
scenePath: SCENE_PATH,
|
|
34309
|
-
playerNodePath: {
|
|
34448
|
+
playerNodePath: {
|
|
34449
|
+
type: "string",
|
|
34450
|
+
ops: ["read", "create", "add_track"],
|
|
34451
|
+
description: "The AnimationPlayer node."
|
|
34452
|
+
},
|
|
34310
34453
|
animationName: {
|
|
34311
34454
|
type: "string",
|
|
34312
|
-
|
|
34455
|
+
ops: ["read", "create", "add_track", "add_state"],
|
|
34456
|
+
description: "The animation in the player. read: the one to answer with whole; without it, read lists them. add_state: the one that state plays."
|
|
34313
34457
|
},
|
|
34314
|
-
length: { type: "number", description: "create: seconds. Default 1." },
|
|
34458
|
+
length: { type: "number", ops: ["create"], description: "create: seconds. Default 1." },
|
|
34315
34459
|
loopMode: {
|
|
34316
34460
|
type: "string",
|
|
34461
|
+
ops: ["create"],
|
|
34317
34462
|
enum: ["none", "linear", "pingpong"],
|
|
34318
34463
|
description: "create: default none."
|
|
34319
34464
|
},
|
|
34320
|
-
step: {
|
|
34321
|
-
|
|
34322
|
-
|
|
34323
|
-
|
|
34465
|
+
step: {
|
|
34466
|
+
type: "number",
|
|
34467
|
+
ops: ["create"],
|
|
34468
|
+
description: "create: keyframe snap in seconds. Default 0.1."
|
|
34469
|
+
},
|
|
34470
|
+
track: { ...ANIMATION_TRACK, ops: ["add_track"] },
|
|
34471
|
+
animTreePath: {
|
|
34472
|
+
type: "string",
|
|
34473
|
+
ops: ["read_states", "add_state", "connect_states"],
|
|
34474
|
+
description: "The AnimationTree node."
|
|
34475
|
+
},
|
|
34476
|
+
stateName: { type: "string", ops: ["add_state"], description: "add_state: the state to add." },
|
|
34324
34477
|
stateMachinePath: {
|
|
34325
34478
|
type: "string",
|
|
34326
|
-
|
|
34479
|
+
ops: ["read_states", "add_state", "connect_states"],
|
|
34480
|
+
description: "A nested state machine, by the names of the states that lead to it. Default the root."
|
|
34481
|
+
},
|
|
34482
|
+
fromState: {
|
|
34483
|
+
type: "string",
|
|
34484
|
+
ops: ["connect_states"],
|
|
34485
|
+
description: "connect_states: the state the transition leaves."
|
|
34486
|
+
},
|
|
34487
|
+
toState: {
|
|
34488
|
+
type: "string",
|
|
34489
|
+
ops: ["connect_states"],
|
|
34490
|
+
description: "connect_states: the state it arrives at."
|
|
34327
34491
|
},
|
|
34328
|
-
fromState: { type: "string", description: "connect_states: the state the transition leaves." },
|
|
34329
|
-
toState: { type: "string", description: "connect_states: the state it arrives at." },
|
|
34330
34492
|
transitionType: {
|
|
34331
34493
|
type: "string",
|
|
34494
|
+
ops: ["connect_states"],
|
|
34332
34495
|
enum: ["immediate", "sync", "at_end"],
|
|
34333
34496
|
description: "connect_states: default immediate."
|
|
34334
34497
|
},
|
|
34335
34498
|
advanceCondition: {
|
|
34336
34499
|
type: "string",
|
|
34500
|
+
ops: ["connect_states"],
|
|
34337
34501
|
description: "connect_states: the condition parameter that advances."
|
|
34338
34502
|
}
|
|
34339
34503
|
},
|
|
34340
34504
|
requires: ["projectPath", "scenePath"],
|
|
34341
34505
|
operations: {
|
|
34506
|
+
read: {
|
|
34507
|
+
effect: "read",
|
|
34508
|
+
summary: "the animations of an AnimationPlayer, or one with its tracks",
|
|
34509
|
+
requires: ["playerNodePath"]
|
|
34510
|
+
},
|
|
34511
|
+
read_states: {
|
|
34512
|
+
effect: "read",
|
|
34513
|
+
summary: "the states and transitions of an AnimationTree state machine",
|
|
34514
|
+
requires: ["animTreePath"]
|
|
34515
|
+
},
|
|
34342
34516
|
create: {
|
|
34343
34517
|
effect: "additive",
|
|
34344
34518
|
summary: "a new animation in an AnimationPlayer",
|
|
@@ -34442,7 +34616,11 @@ var TOOL_SPECS = [
|
|
|
34442
34616
|
summary: "document symbols from the language server, each with its kind, the line and column of its name, and the lastLine of its declaration",
|
|
34443
34617
|
requires: []
|
|
34444
34618
|
},
|
|
34445
|
-
completion: {
|
|
34619
|
+
completion: {
|
|
34620
|
+
effect: "read",
|
|
34621
|
+
summary: "completions at a position, narrowed to the part of a name written before it, at most 100",
|
|
34622
|
+
requires: ["line", "character"]
|
|
34623
|
+
},
|
|
34446
34624
|
hover: { effect: "read", summary: "hover text at a position", requires: ["line", "character"] }
|
|
34447
34625
|
},
|
|
34448
34626
|
defaultOperation: "structure"
|
|
@@ -34465,34 +34643,59 @@ var TOOL_SPECS = [
|
|
|
34465
34643
|
{
|
|
34466
34644
|
name: "resource_edit",
|
|
34467
34645
|
title: "Edit resources",
|
|
34468
|
-
description:
|
|
34646
|
+
description: 'Resource files: read one, create any ClassDB resource as .tres, change one, write a shader, build a TileSet, or set a Theme colour or font size. A material is a create with resourceType StandardMaterial3D, ShaderMaterial or CanvasItemMaterial. read answers with the properties the file sets, in the form modify takes, and a Theme lists its items there as "Button/colors/font_color". Needs the editor connected.',
|
|
34469
34647
|
parameters: {
|
|
34470
34648
|
projectPath: PROJECT_PATH2,
|
|
34471
34649
|
resourcePath: RESOURCE_PATH,
|
|
34472
34650
|
resourceType: {
|
|
34473
34651
|
type: "string",
|
|
34652
|
+
ops: ["create"],
|
|
34474
34653
|
description: 'create: the resource class, such as "PhysicsMaterial" or "StandardMaterial3D".'
|
|
34475
34654
|
},
|
|
34476
|
-
properties: PROPERTIES,
|
|
34477
|
-
script: {
|
|
34655
|
+
properties: { ...PROPERTIES, ops: ["create", "modify"] },
|
|
34656
|
+
script: {
|
|
34657
|
+
type: "string",
|
|
34658
|
+
ops: ["create"],
|
|
34659
|
+
description: "create: a script to attach, for custom resources."
|
|
34660
|
+
},
|
|
34661
|
+
includeDefaults: {
|
|
34662
|
+
type: "boolean",
|
|
34663
|
+
ops: ["read"],
|
|
34664
|
+
description: "read: include properties still at their default. Default false."
|
|
34665
|
+
},
|
|
34478
34666
|
shaderType: {
|
|
34479
34667
|
type: "string",
|
|
34668
|
+
ops: ["create_shader"],
|
|
34480
34669
|
enum: ["canvas_item", "spatial", "particles", "sky", "fog"],
|
|
34481
34670
|
description: "create_shader."
|
|
34482
34671
|
},
|
|
34483
34672
|
code: {
|
|
34484
34673
|
type: "string",
|
|
34674
|
+
ops: ["create_shader"],
|
|
34485
34675
|
description: "create_shader: the shader source. Default: a minimal shader of that type."
|
|
34486
34676
|
},
|
|
34487
|
-
sources: TILESET_SOURCES,
|
|
34488
|
-
controlType: {
|
|
34489
|
-
|
|
34490
|
-
|
|
34491
|
-
|
|
34492
|
-
|
|
34677
|
+
sources: { ...TILESET_SOURCES, ops: ["create_tileset"] },
|
|
34678
|
+
controlType: {
|
|
34679
|
+
type: "string",
|
|
34680
|
+
ops: ["set_theme_color", "set_theme_font_size"],
|
|
34681
|
+
description: 'Theme ops: the Control class, such as "Button".'
|
|
34682
|
+
},
|
|
34683
|
+
colorName: {
|
|
34684
|
+
type: "string",
|
|
34685
|
+
ops: ["set_theme_color"],
|
|
34686
|
+
description: 'set_theme_color: such as "font_color".'
|
|
34687
|
+
},
|
|
34688
|
+
color: { ...COLOUR2, ops: ["set_theme_color"] },
|
|
34689
|
+
fontSizeName: {
|
|
34690
|
+
type: "string",
|
|
34691
|
+
ops: ["set_theme_font_size"],
|
|
34692
|
+
description: 'set_theme_font_size: such as "font_size".'
|
|
34693
|
+
},
|
|
34694
|
+
size: { type: "number", ops: ["set_theme_font_size"], description: "set_theme_font_size: pixels." }
|
|
34493
34695
|
},
|
|
34494
34696
|
requires: ["projectPath", "resourcePath"],
|
|
34495
34697
|
operations: {
|
|
34698
|
+
read: { effect: "read", summary: "the properties a resource file sets", requires: [] },
|
|
34496
34699
|
create: { effect: "destructive", summary: "a new resource of any class", requires: ["resourceType"] },
|
|
34497
34700
|
modify: {
|
|
34498
34701
|
effect: "destructive",
|
|
@@ -34519,8 +34722,8 @@ var TOOL_SPECS = [
|
|
|
34519
34722
|
},
|
|
34520
34723
|
{
|
|
34521
34724
|
name: "editor_launch",
|
|
34522
|
-
title: "Open or
|
|
34523
|
-
description: "Opens the Godot editor on a project, or restarts the one already connected. open refuses while an editor of the project is already running and has not reached this server, naming its pid, rather than putting a second on the project. The editor is an ordinary visible one on the desktop in use, and on Windows it is shown without being activated, so opening or reopening it does not take the keyboard from whatever the user is typing in. hidden: true opens it headless instead and, on Windows, on a desktop of its own, where the games it plays go too; a hidden editor serves the bridge, the language server and the debug adapter as a visible one does. A game a visible editor plays opens as a window on the desktop in use. An editor goes on serving the addon it read at startup, so restart is what puts a gdharness upgrade into effect, and nothing else here restarts one; it saves open scenes on the way out and answers with the version that came back. An editor a server opened is started again by the server, visible unless hidden: true is passed again, once the old one has gone: it waits 90 seconds for that, or GDHARNESS_EDITOR_RESTART_TIMEOUT_MS, and an old editor still running after it is not replaced, since the new one would find its ports held. One opened by hand restarts itself, which only one with a window can do, because the engine hands back none of the arguments it was started with. editor_status says which editor is connected and whether it is holding an old addon. Opening a project is a save and so is closing one, which is the part that is not obvious from outside: the editor imports the project on open and writes project.godot back, and Godot writes only what differs from its own defaults, so a key named deliberately at its default value is dropped either way. A restart says which under settingsDropped in its own answer because it waits for the editor to return. An open cannot, since it answers as soon as the process exists and the save happens during the import minutes later, so editor_status carries that reading once the editor has connected. Nothing else will say a key went until something depends on one. A restart of an editor this server opened is a quit and then a launch, and this server can be ended between them, usually by the reconnect the restart was being taken for: the editor is gone and nothing replaces it. That is written down before the quit, so the next server says so under restartInterrupted in editor_status rather than waiting for an editor that is not coming, and open finishes it, saying so under finishesRestart. An open is refused when the editor it would start could never reach this server: a project other than the one this server serves, whose editor dials that project's own server; a project without the gdharness editor addon installed and enabled; and a project whose editor a restart is already bringing back.",
|
|
34725
|
+
title: "Open, restart or close the editor",
|
|
34726
|
+
description: "Opens the Godot editor on a project, or restarts or closes the one already connected. close saves open scenes, quits the editor and waits for its process to go, which is the only way to end a hidden one short of killing it; settingsDropped in its answer says what that save took out of project.godot. open refuses while an editor of the project is already running and has not reached this server, naming its pid, rather than putting a second on the project. The editor is an ordinary visible one on the desktop in use, and on Windows it is shown without being activated, so opening or reopening it does not take the keyboard from whatever the user is typing in. hidden: true opens it headless instead and, on Windows, on a desktop of its own, where the games it plays go too; a hidden editor serves the bridge, the language server and the debug adapter as a visible one does. A game a visible editor plays opens as a window on the desktop in use. An editor goes on serving the addon it read at startup, so restart is what puts a gdharness upgrade into effect, and nothing else here restarts one; it saves open scenes on the way out and answers with the version that came back. An editor a server opened is started again by the server, visible unless hidden: true is passed again, once the old one has gone: it waits 90 seconds for that, or GDHARNESS_EDITOR_RESTART_TIMEOUT_MS, and an old editor still running after it is not replaced, since the new one would find its ports held. One opened by hand restarts itself, which only one with a window can do, because the engine hands back none of the arguments it was started with. editor_status says which editor is connected and whether it is holding an old addon. Opening a project is a save and so is closing one, which is the part that is not obvious from outside: the editor imports the project on open and writes project.godot back, and Godot writes only what differs from its own defaults, so a key named deliberately at its default value is dropped either way. A restart says which under settingsDropped in its own answer because it waits for the editor to return. An open cannot, since it answers as soon as the process exists and the save happens during the import minutes later, so editor_status carries that reading once the editor has connected. Nothing else will say a key went until something depends on one. A restart of an editor this server opened is a quit and then a launch, and this server can be ended between them, usually by the reconnect the restart was being taken for: the editor is gone and nothing replaces it. That is written down before the quit, so the next server says so under restartInterrupted in editor_status rather than waiting for an editor that is not coming, and open finishes it, saying so under finishesRestart. An open is refused when the editor it would start could never reach this server: a project other than the one this server serves, whose editor dials that project's own server; a project without the gdharness editor addon installed and enabled; and a project whose editor a restart is already bringing back.",
|
|
34524
34727
|
parameters: {
|
|
34525
34728
|
projectPath: { ...PROJECT_PATH2, ops: ["open"] },
|
|
34526
34729
|
hidden: {
|
|
@@ -34536,6 +34739,11 @@ var TOOL_SPECS = [
|
|
|
34536
34739
|
effect: "destructive",
|
|
34537
34740
|
summary: "restart the connected editor and wait for it",
|
|
34538
34741
|
requires: []
|
|
34742
|
+
},
|
|
34743
|
+
close: {
|
|
34744
|
+
effect: "destructive",
|
|
34745
|
+
summary: "save and quit the connected editor, and wait for it to go",
|
|
34746
|
+
requires: []
|
|
34539
34747
|
}
|
|
34540
34748
|
},
|
|
34541
34749
|
defaultOperation: "open"
|
|
@@ -34661,7 +34869,7 @@ var TOOL_SPECS = [
|
|
|
34661
34869
|
{
|
|
34662
34870
|
name: "editor_output",
|
|
34663
34871
|
title: "Game and editor output",
|
|
34664
|
-
description: "What the project started by editor_run has printed, as entries with a severity: the errors and warnings the engine reported, each with where it happened, and everything else as info. For a run the editor plays the reports come from the game itself, through the runtime addon, since the editor's debugger relays what a game prints and not what it reports; a played game without the runtime addon answers here with its prints alone, with errorsUnread true and no clean, since what it raised reaches nothing here. Answers with the counts and the verdict as well as the entries. heldAt is three answers: null for a run this server knows is not held, running or over, an object for one it knows is held at a breakpoint, whether the adapter said so or the runtime did by accepting a connection and never answering, and absent with heldUnknown beside it for a server that connected to the debugger after the stop and has no runtime to ask, since the adapter reports a stop only to the sessions connected when it happened. A run that has quit still answers here, with running false and its exit code, until the next one starts; a run ended by a signal has no exit code, and exitSignal names the signal instead. A run this server started also answers with transcript, the file both its streams are written to: uncapped, written while the run is going, and the thing to read or tail for a long run rather than the engine's own log, which every engine start rotates away. Read the file for output and this answer for state: endedBy says whether gdharness ended the run, and a transcript that has stopped growing is a run between prints rather than a run that is over. running is asked of the operating system whenever there is a process to ask about, which is every run this server started and every editor-played run whose game announced its runtime. For an editor-played run that never announced, there is no process id here and running is the editor's answer, which can lag: one measured elsewhere reported a game as playing for fifteen seconds after its process had been ended from outside. editor_status names that game under runtimes, which does not depend on the editor answering. op: \"editor\" answers about the editor itself instead, which is a different log and the only way anything here can read what an editor printed: its console reaches no plugin, so the addon cannot scrape it, and an editor gdharness opened is asked for a log file as it starts. That covers the whole session from the editor's first line, which is where the output worth reading usually is, since the class cache, the plugins and the language server all say what they have to say before any harness has connected. The answer carries the entries in the order they were printed and, beside them under repeated, each message shape several lines share: how many, what varied in each slot of it and how many different values stood there, the first occurrence, and the ordinary lines above the group, found by walking back past the group rather than by counting lines. An editor somebody opened by hand has no console here and is refused saying so rather than answered empty, because an editor that printed nothing and an editor nobody captured are not the same thing. startedAt is when the run began and elapsedMs how long it has been going; for a run that is over, endedAt is when it ended and elapsedMs how long it ran, and both are left out when nobody saw the end, as for a game closed in the editor or a run picked up from a note written before ends were recorded.",
|
|
34872
|
+
description: "What the project started by editor_run has printed, as entries with a severity: the errors and warnings the engine reported, each with where it happened, and everything else as info. For a run the editor plays the reports come from the game itself, through the runtime addon, since the editor's debugger relays what a game prints and not what it reports; a played game without the runtime addon answers here with its prints alone, with errorsUnread true and no clean, since what it raised reaches nothing here. Answers with the counts and the verdict as well as the entries: clean is true when no error was reported, and warnings, counted beside it, do not make it false. heldAt is three answers: null for a run this server knows is not held, running or over, an object for one it knows is held at a breakpoint, whether the adapter said so or the runtime did by accepting a connection and never answering, and absent with heldUnknown beside it for a server that connected to the debugger after the stop and has no runtime to ask, since the adapter reports a stop only to the sessions connected when it happened. A run that has quit still answers here, with running false and its exit code, until the next one starts; a run ended by a signal has no exit code, and exitSignal names the signal instead. A run this server started also answers with transcript, the file both its streams are written to: uncapped, written while the run is going, and the thing to read or tail for a long run rather than the engine's own log, which every engine start rotates away. Read the file for output and this answer for state: endedBy says whether gdharness ended the run, and a transcript that has stopped growing is a run between prints rather than a run that is over. running is asked of the operating system whenever there is a process to ask about, which is every run this server started and every editor-played run whose game announced its runtime. For an editor-played run that never announced, there is no process id here and running is the editor's answer, which can lag: one measured elsewhere reported a game as playing for fifteen seconds after its process had been ended from outside. editor_status names that game under runtimes, which does not depend on the editor answering. op: \"editor\" answers about the editor itself instead, which is a different log and the only way anything here can read what an editor printed: its console reaches no plugin, so the addon cannot scrape it, and an editor gdharness opened is asked for a log file as it starts. That covers the whole session from the editor's first line, which is where the output worth reading usually is, since the class cache, the plugins and the language server all say what they have to say before any harness has connected. The answer carries the entries in the order they were printed and, beside them under repeated, each message shape several lines share: how many, what varied in each slot of it and how many different values stood there, the first occurrence, and the ordinary lines above the group, found by walking back past the group rather than by counting lines. An editor somebody opened by hand has no console here and is refused saying so rather than answered empty, because an editor that printed nothing and an editor nobody captured are not the same thing. startedAt is when the run began and elapsedMs how long it has been going; for a run that is over, endedAt is when it ended and elapsedMs how long it ran, and both are left out when nobody saw the end, as for a game closed in the editor or a run picked up from a note written before ends were recorded.",
|
|
34665
34873
|
parameters: {
|
|
34666
34874
|
severity: {
|
|
34667
34875
|
type: "string",
|
|
@@ -34702,7 +34910,7 @@ var TOOL_SPECS = [
|
|
|
34702
34910
|
},
|
|
34703
34911
|
editor: {
|
|
34704
34912
|
effect: "read",
|
|
34705
|
-
summary: "What the connected editor itself has printed, from its first line, with repeated messages grouped and the ordinary lines above each group carried with it",
|
|
34913
|
+
summary: "What the connected editor itself has printed, from its first line, with repeated messages grouped and the ordinary lines above each group carried with it. The engine's progress bars are one line per task, saying how far an unfinished one got",
|
|
34706
34914
|
requires: []
|
|
34707
34915
|
}
|
|
34708
34916
|
},
|
|
@@ -35355,7 +35563,6 @@ var RUNTIME_AUTOLOAD = {
|
|
|
35355
35563
|
name: "GdharnessRuntime",
|
|
35356
35564
|
path: "addons/gdharness_runtime/runtime_autoload.gd"
|
|
35357
35565
|
};
|
|
35358
|
-
var VERSION_MARKER = ".gdharness-version";
|
|
35359
35566
|
var DIGEST_MARKER = ".gdharness-digest";
|
|
35360
35567
|
var RUNTIME_ADDON = "gdharness_runtime";
|
|
35361
35568
|
function editorAddonDigest(from = shippedAddonsDirectory()) {
|
|
@@ -36105,6 +36312,7 @@ var SCAN_SETTLED_MS = 500;
|
|
|
36105
36312
|
var REIMPORT_TIMEOUT_MS = 300000;
|
|
36106
36313
|
var AUDIO_BUS_OPS = new Set([
|
|
36107
36314
|
"add_audio_bus",
|
|
36315
|
+
"remove_audio_bus",
|
|
36108
36316
|
"set_audio_bus_effect",
|
|
36109
36317
|
"set_audio_bus_volume"
|
|
36110
36318
|
]);
|
|
@@ -36126,6 +36334,7 @@ function settingsNamedBy(op, args) {
|
|
|
36126
36334
|
case "set_main_scene":
|
|
36127
36335
|
return ["application/run/main_scene"];
|
|
36128
36336
|
case "add_input_action":
|
|
36337
|
+
case "remove_input_action":
|
|
36129
36338
|
return text("actionName") === "" ? [] : [`input/${text("actionName")}`];
|
|
36130
36339
|
case "enable_plugin":
|
|
36131
36340
|
case "disable_plugin":
|
|
@@ -36724,6 +36933,7 @@ class GodotServer {
|
|
|
36724
36933
|
launchedEditor = null;
|
|
36725
36934
|
launchedFrom = null;
|
|
36726
36935
|
launchedDropped = null;
|
|
36936
|
+
closedHere = null;
|
|
36727
36937
|
ownProject;
|
|
36728
36938
|
announcedAt = null;
|
|
36729
36939
|
handingOverFrom = null;
|
|
@@ -36733,6 +36943,7 @@ class GodotServer {
|
|
|
36733
36943
|
this.godotBridge = getDefaultBridge(this.ownProject);
|
|
36734
36944
|
this.godotBridge.identifyEditorsWith(oneShotOptionOf);
|
|
36735
36945
|
this.godotBridge.on("godot_connected", () => {
|
|
36946
|
+
this.closedHere = null;
|
|
36736
36947
|
this.settleTheLaunchReading();
|
|
36737
36948
|
});
|
|
36738
36949
|
this.setupShutdownHandlers();
|
|
@@ -37326,6 +37537,10 @@ class GodotServer {
|
|
|
37326
37537
|
}
|
|
37327
37538
|
case "scene_animation":
|
|
37328
37539
|
switch (op) {
|
|
37540
|
+
case "read":
|
|
37541
|
+
return await bridge("read_animations");
|
|
37542
|
+
case "read_states":
|
|
37543
|
+
return await bridge("read_animation_states");
|
|
37329
37544
|
case "create":
|
|
37330
37545
|
return await bridge("create_animation");
|
|
37331
37546
|
case "add_track":
|
|
@@ -37350,6 +37565,8 @@ class GodotServer {
|
|
|
37350
37565
|
return await this.handleScriptDiagnostics(args);
|
|
37351
37566
|
case "resource_edit":
|
|
37352
37567
|
switch (op) {
|
|
37568
|
+
case "read":
|
|
37569
|
+
return await bridge("read_resource");
|
|
37353
37570
|
case "create":
|
|
37354
37571
|
return await bridge("create_resource");
|
|
37355
37572
|
case "modify":
|
|
@@ -37364,6 +37581,9 @@ class GodotServer {
|
|
|
37364
37581
|
return await bridge("set_theme_font_size", { themePath: args["resourcePath"] });
|
|
37365
37582
|
}
|
|
37366
37583
|
case "editor_launch":
|
|
37584
|
+
if (op === "close") {
|
|
37585
|
+
return await this.handleCloseEditor();
|
|
37586
|
+
}
|
|
37367
37587
|
return op === "restart" ? await this.handleRestartEditor(args) : await this.handleLaunchEditor(args);
|
|
37368
37588
|
case "editor_run":
|
|
37369
37589
|
if (op === "stop") {
|
|
@@ -38261,21 +38481,31 @@ class GodotServer {
|
|
|
38261
38481
|
}
|
|
38262
38482
|
const missing = paths.value.filter((path) => !existsSync17(join22(project.value.path, path.slice("res://".length))));
|
|
38263
38483
|
const asked = paths.value.filter((path) => !missing.includes(path));
|
|
38484
|
+
const runner = "addons/gdUnit4/bin/GdUnitCmdTool.gd";
|
|
38485
|
+
const noRunner = !existsSync17(join22(project.value.path, runner));
|
|
38264
38486
|
if (asked.length === 0) {
|
|
38265
38487
|
const verdict = `nothing at ${missing.join(" and ")}`;
|
|
38488
|
+
const andNoRunner = noRunner ? `, and gdUnit4 is not installed in this project: no ${runner}` : "";
|
|
38266
38489
|
return {
|
|
38267
38490
|
content: [
|
|
38268
38491
|
{
|
|
38269
38492
|
type: "text",
|
|
38270
|
-
text: `No tests ran: ${verdict}.${elsewhereIn(project.value.path, missing[0] ?? "")}`
|
|
38493
|
+
text: `No tests ran: ${verdict}${andNoRunner}.${elsewhereIn(project.value.path, missing[0] ?? "")}`
|
|
38271
38494
|
},
|
|
38272
|
-
{
|
|
38495
|
+
{
|
|
38496
|
+
type: "text",
|
|
38497
|
+
text: answerJson({
|
|
38498
|
+
passed: false,
|
|
38499
|
+
verdict,
|
|
38500
|
+
tests: 0,
|
|
38501
|
+
...noRunner ? { gdUnit4Installed: false } : {}
|
|
38502
|
+
})
|
|
38503
|
+
}
|
|
38273
38504
|
],
|
|
38274
38505
|
isError: true
|
|
38275
38506
|
};
|
|
38276
38507
|
}
|
|
38277
|
-
|
|
38278
|
-
if (!existsSync17(join22(project.value.path, runner))) {
|
|
38508
|
+
if (noRunner) {
|
|
38279
38509
|
return this.createErrorResponse(`gdUnit4 is not installed in this project: no ${runner}.`, [
|
|
38280
38510
|
"Install gdUnit4 under addons/gdUnit4, from https://github.com/godot-gdunit-labs/gdUnit4"
|
|
38281
38511
|
]);
|
|
@@ -38858,8 +39088,12 @@ class GodotServer {
|
|
|
38858
39088
|
addonIsStale: greeted ? stale : undefined,
|
|
38859
39089
|
greeting: status.connected && !greeted ? "The editor has connected and has not yet said who it is: its project, pid, addon version and ports arrive with its greeting a moment after the socket opens, later while it imports. Ask again for them." : undefined,
|
|
38860
39090
|
bridgeAvailable: this.bridgeStartupError === null && status.port !== null,
|
|
38861
|
-
mayYetConnect: status.connected ? undefined : arriving.length > 0 ? true : unfinished !== null ? unfinished.underway : anEditorIsStillComing(status.listeningSince, launchedIsUp),
|
|
39091
|
+
mayYetConnect: status.connected ? undefined : arriving.length > 0 ? true : unfinished !== null ? unfinished.underway : this.closedHere !== null ? false : anEditorIsStillComing(status.listeningSince, launchedIsUp),
|
|
38862
39092
|
editorsNotYetConnected: arriving.length > 0 ? arriving : undefined,
|
|
39093
|
+
closedHere: status.connected || arriving.length > 0 || this.closedHere === null ? undefined : {
|
|
39094
|
+
...this.closedHere,
|
|
39095
|
+
note: "This server closed the editor on request, so nothing is coming until editor_launch open starts one."
|
|
39096
|
+
},
|
|
38863
39097
|
restartInterrupted: unfinished === null || unfinished.underway || arriving.length > 0 ? undefined : {
|
|
38864
39098
|
quitEditorPid: unfinished.note.editorPid,
|
|
38865
39099
|
quitAt: unfinished.note.quitAt,
|
|
@@ -39104,6 +39338,42 @@ class GodotServer {
|
|
|
39104
39338
|
tookMs: Date.now() - began
|
|
39105
39339
|
});
|
|
39106
39340
|
}
|
|
39341
|
+
async handleCloseEditor() {
|
|
39342
|
+
const before = this.godotBridge.getStatus();
|
|
39343
|
+
if (!before.connected) {
|
|
39344
|
+
return this.createErrorResponse("No editor is connected, so there is nothing to close.", [
|
|
39345
|
+
"editor_status says whether the bridge is up and what has reached it"
|
|
39346
|
+
]);
|
|
39347
|
+
}
|
|
39348
|
+
if (!hasSaidWhoItIs(before) || before.editorPid === undefined) {
|
|
39349
|
+
return this.createErrorResponse(`The editor has connected and has not said who it is within ${greetingWaitMs() / 1000}s, so there is no process to wait on once it is asked to go.`, ["editor_status says when it has, under greeting while it has not", "Then ask for the close again"]);
|
|
39350
|
+
}
|
|
39351
|
+
const pid = before.editorPid;
|
|
39352
|
+
const settingsBefore = this.settingKeysOf(before.projectPath);
|
|
39353
|
+
const asked = await this.handleViaBridge("quit_editor", {});
|
|
39354
|
+
if (asked.isError === true) {
|
|
39355
|
+
return asked;
|
|
39356
|
+
}
|
|
39357
|
+
const began = Date.now();
|
|
39358
|
+
const gone = await this.waitForBridge(() => !alive(pid), began + editorRestartTimeoutMs());
|
|
39359
|
+
if (!gone) {
|
|
39360
|
+
return this.createErrorResponse(`The editor saved and was asked to go, and pid ${pid} is still running after ${editorRestartTimeoutMs() / 1000}s.`, [
|
|
39361
|
+
"It may be asking what to do about unsaved changes, or a script may be holding up its exit",
|
|
39362
|
+
"editor_status says whether it is still connected"
|
|
39363
|
+
]);
|
|
39364
|
+
}
|
|
39365
|
+
this.closedHere = { editorPid: pid, at: new Date().toISOString() };
|
|
39366
|
+
if (this.launchedEditor?.pid === pid) {
|
|
39367
|
+
this.launchedEditor = null;
|
|
39368
|
+
}
|
|
39369
|
+
return this.jsonTextResponse({
|
|
39370
|
+
closed: true,
|
|
39371
|
+
editorPid: pid,
|
|
39372
|
+
projectPath: before.projectPath,
|
|
39373
|
+
...this.whatTheEditorDropped(settingsBefore, this.settingKeysOf(before.projectPath)),
|
|
39374
|
+
tookMs: Date.now() - began
|
|
39375
|
+
});
|
|
39376
|
+
}
|
|
39107
39377
|
settleTheLaunchReading() {
|
|
39108
39378
|
const watched = this.launchedFrom;
|
|
39109
39379
|
const status = this.godotBridge.getStatus();
|
|
@@ -39275,6 +39545,7 @@ class GodotServer {
|
|
|
39275
39545
|
pid: launched.pid,
|
|
39276
39546
|
startedAt: (await startTimesOf([launched.pid])).get(launched.pid) ?? null
|
|
39277
39547
|
};
|
|
39548
|
+
this.closedHere = null;
|
|
39278
39549
|
return { pid: launched.pid, error: null };
|
|
39279
39550
|
}
|
|
39280
39551
|
async portsForAnEditor() {
|
|
@@ -39520,7 +39791,7 @@ class GodotServer {
|
|
|
39520
39791
|
frameFunctions: profileFrameFunctions(projectPath)
|
|
39521
39792
|
});
|
|
39522
39793
|
if (armed.isError === true) {
|
|
39523
|
-
return
|
|
39794
|
+
return armed;
|
|
39524
39795
|
}
|
|
39525
39796
|
}
|
|
39526
39797
|
const answer = await this.handleViaBridge("play_scene", scene === null ? {} : { scenePath: `res://${scene}` });
|
|
@@ -40370,7 +40641,7 @@ class GodotServer {
|
|
|
40370
40641
|
async profileOfThePlayedGame(going, projectPath, fromProject, limit) {
|
|
40371
40642
|
const read = await this.handleViaBridge("profile_read", {});
|
|
40372
40643
|
if (read.isError === true) {
|
|
40373
|
-
return
|
|
40644
|
+
return read;
|
|
40374
40645
|
}
|
|
40375
40646
|
const held = asParams(JSON.parse(read.content[0]?.text ?? "{}"));
|
|
40376
40647
|
if (readBoolean(held, "profiled") !== true && readBoolean(held, "armed") !== true) {
|
|
@@ -40383,7 +40654,7 @@ class GodotServer {
|
|
|
40383
40654
|
frameFunctions: profileFrameFunctions(projectPath)
|
|
40384
40655
|
});
|
|
40385
40656
|
if (started.isError === true) {
|
|
40386
|
-
return
|
|
40657
|
+
return started;
|
|
40387
40658
|
}
|
|
40388
40659
|
if (readBoolean(asParams(JSON.parse(started.content[0]?.text ?? "{}")), "started") !== true) {
|
|
40389
40660
|
return this.createErrorResponse("The editor says it is playing and has no debugger session for the game, so there is nothing to switch the profiler on in yet.", ["Ask again in a moment, once the game is up"]);
|
|
@@ -40439,13 +40710,6 @@ class GodotServer {
|
|
|
40439
40710
|
}
|
|
40440
40711
|
return false;
|
|
40441
40712
|
}
|
|
40442
|
-
profilerRefusal(refused) {
|
|
40443
|
-
const said = refused.content[0]?.text ?? "";
|
|
40444
|
-
return said.includes("Unknown tool: profile_") ? this.createErrorResponse("The editor's gdharness addon is older than the profiler, so it cannot profile the game it plays.", [
|
|
40445
|
-
"editor_launch restart loads the addon this server installed",
|
|
40446
|
-
"editor_status says which addon the editor holds"
|
|
40447
|
-
]) : refused;
|
|
40448
|
-
}
|
|
40449
40713
|
async handleWaitForRun(args) {
|
|
40450
40714
|
await this.pickUpWhatTheEditorIsPlaying();
|
|
40451
40715
|
const run = await this.currentRun();
|
|
@@ -41409,7 +41673,13 @@ class GodotServer {
|
|
|
41409
41673
|
try {
|
|
41410
41674
|
return this.jsonTextResponse(markIfStale(await this.godotBridge.invokeTool(toolName, args), this.godotBridge.getStatus().addonVersion, SERVER_VERSION, this.godotBridge.getStatus().addonDigest, shippedEditorDigest(), this.editorCodeOnDisk()));
|
|
41411
41675
|
} catch (error) {
|
|
41412
|
-
|
|
41676
|
+
const message = errorMessage(error);
|
|
41677
|
+
if (message.includes(`Unknown tool: ${toolName}`)) {
|
|
41678
|
+
const status = this.godotBridge.getStatus();
|
|
41679
|
+
const remedy = addonMismatch(status.addonVersion, SERVER_VERSION, status.addonDigest, shippedEditorDigest(), this.editorCodeOnDisk());
|
|
41680
|
+
return this.createErrorResponse(`The editor's gdharness addon is older than this ${SERVER_VERSION} server and has no ${toolName}, so it cannot answer this call. ${remedy ?? "Restart it with editor_launch restart to load the addon the project holds."}`, ["editor_status says which addon the editor holds"]);
|
|
41681
|
+
}
|
|
41682
|
+
return this.createErrorResponse(`The editor answered ${toolName} with an error: ${message}`);
|
|
41413
41683
|
}
|
|
41414
41684
|
}
|
|
41415
41685
|
runtimeTimeoutMs() {
|