gdharness 1.3.42 → 1.3.44
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 +383 -243
- package/build/godot/addons/auto_reload/auto_reload.gd +18 -19
- package/build/godot/addons/gdharness_editor/animation_reads.gd +55 -33
- package/build/godot/addons/gdharness_editor/bridge_client.gd +53 -58
- package/build/godot/addons/gdharness_editor/declared_values.gd +68 -32
- package/build/godot/addons/gdharness_editor/file_reads.gd +83 -42
- package/build/godot/addons/gdharness_editor/file_walk.gd +12 -5
- package/build/godot/addons/gdharness_editor/headless_check.gd +68 -44
- package/build/godot/addons/gdharness_editor/plugin.gd +2 -3
- package/build/godot/addons/gdharness_editor/property_paths.gd +22 -12
- package/build/godot/addons/gdharness_editor/property_values.gd +143 -78
- package/build/godot/addons/gdharness_editor/reading.gd +51 -17
- package/build/godot/addons/gdharness_editor/same_values.gd +4 -4
- package/build/godot/addons/gdharness_editor/scene_file.gd +127 -80
- package/build/godot/addons/gdharness_editor/scene_uids.gd +12 -6
- package/build/godot/addons/gdharness_editor/serialisation.gd +75 -51
- package/build/godot/addons/gdharness_editor/setting_reads.gd +7 -6
- package/build/godot/addons/gdharness_editor/tool_executor.gd +3 -6
- package/build/godot/addons/gdharness_editor/tools/animation_states.gd +37 -27
- package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +79 -49
- package/build/godot/addons/gdharness_editor/tools/animation_tracks.gd +70 -40
- package/build/godot/addons/gdharness_editor/tools/class_tools.gd +65 -42
- package/build/godot/addons/gdharness_editor/tools/filesystem_tools.gd +24 -22
- package/build/godot/addons/gdharness_editor/tools/navigation_bake.gd +49 -29
- package/build/godot/addons/gdharness_editor/tools/play_tools.gd +26 -21
- package/build/godot/addons/gdharness_editor/tools/profile_tools.gd +40 -40
- package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +166 -107
- package/build/godot/addons/gdharness_editor/tools/scene_groups.gd +16 -9
- package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +68 -41
- package/build/godot/addons/gdharness_editor/tools/scene_paths.gd +35 -20
- package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +28 -13
- package/build/godot/addons/gdharness_editor/tools/scene_sharing.gd +12 -5
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +113 -67
- package/build/godot/addons/gdharness_editor/tools/script_paths.gd +20 -8
- package/build/godot/addons/gdharness_editor/tools/settings_tools.gd +11 -8
- package/build/godot/addons/gdharness_editor/tools/signal_tools.gd +47 -34
- package/build/godot/addons/gdharness_editor/tools/tileset_tools.gd +77 -50
- package/build/godot/addons/gdharness_editor/value_shapes.gd +5 -6
- package/build/godot/addons/gdharness_runtime/call_errors.gd +1 -2
- package/build/godot/addons/gdharness_runtime/declared_values.gd +68 -32
- package/build/godot/addons/gdharness_runtime/error_report.gd +1 -2
- package/build/godot/addons/gdharness_runtime/reading.gd +51 -17
- package/build/godot/addons/gdharness_runtime/runtime_accessibility.gd +84 -63
- package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +149 -147
- package/build/godot/addons/gdharness_runtime/runtime_capture.gd +64 -42
- package/build/godot/addons/gdharness_runtime/runtime_changes.gd +184 -122
- package/build/godot/addons/gdharness_runtime/runtime_frames.gd +2 -2
- package/build/godot/addons/gdharness_runtime/runtime_hover.gd +24 -20
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +134 -92
- package/build/godot/addons/gdharness_runtime/runtime_items.gd +29 -13
- package/build/godot/addons/gdharness_runtime/runtime_menus.gd +0 -1
- package/build/godot/addons/gdharness_runtime/runtime_mouse.gd +34 -17
- package/build/godot/addons/gdharness_runtime/runtime_pad.gd +17 -13
- package/build/godot/addons/gdharness_runtime/runtime_paths.gd +84 -46
- package/build/godot/addons/gdharness_runtime/runtime_queries.gd +117 -79
- package/build/godot/addons/gdharness_runtime/runtime_says.gd +14 -17
- package/build/godot/addons/gdharness_runtime/runtime_screen.gd +23 -16
- package/build/godot/addons/gdharness_runtime/runtime_targets.gd +117 -66
- package/build/godot/addons/gdharness_runtime/runtime_touch.gd +15 -15
- package/build/godot/addons/gdharness_runtime/runtime_typing.gd +44 -27
- package/build/godot/addons/gdharness_runtime/runtime_values.gd +108 -73
- package/build/godot/addons/gdharness_runtime/runtime_waits.gd +111 -84
- package/build/godot/addons/gdharness_runtime/runtime_words.gd +10 -7
- package/build/godot/addons/gdharness_runtime/serialisation.gd +75 -51
- package/build/godot/addons/gdharness_runtime/value_shapes.gd +5 -6
- package/build/godot/addons/gdharness_tests/bench.gd +4 -5
- package/build/godot/addons/gdharness_tests/event.gd +10 -5
- package/build/godot/addons/gdharness_tests/share.gd +38 -18
- package/build/godot/addons/gdharness_tests/suites.gd +3 -3
- package/build/godot/addons/gdharness_tests/wait.gd +3 -2
- package/build/godot/loader/loader.gd +9 -9
- package/build/godot/operations/animation_reads.gd +55 -33
- package/build/godot/operations/audio_buses.gd +48 -19
- package/build/godot/operations/class_cache.gd +16 -11
- package/build/godot/operations/classdb_queries.gd +89 -44
- package/build/godot/operations/declared_values.gd +68 -32
- package/build/godot/operations/dependencies.gd +190 -109
- package/build/godot/operations/editor_tools.gd +10 -10
- package/build/godot/operations/export_presets.gd +100 -54
- package/build/godot/operations/file_reads.gd +83 -42
- package/build/godot/operations/file_walk.gd +12 -5
- package/build/godot/operations/gdscript_analysis.gd +86 -65
- package/build/godot/operations/gdscript_authoring.gd +83 -36
- package/build/godot/operations/godot_operations.gd +17 -11
- package/build/godot/operations/import_pipeline.gd +110 -59
- package/build/godot/operations/input_actions.gd +62 -39
- package/build/godot/operations/patterns.gd +3 -1
- package/build/godot/operations/plugins.gd +36 -16
- package/build/godot/operations/project_config.gd +61 -32
- package/build/godot/operations/project_diagnostics.gd +24 -11
- package/build/godot/operations/project_file.gd +16 -16
- package/build/godot/operations/project_validation.gd +71 -44
- package/build/godot/operations/property_paths.gd +22 -12
- package/build/godot/operations/property_values.gd +143 -78
- package/build/godot/operations/reading.gd +51 -17
- package/build/godot/operations/rename_checks.gd +2 -2
- package/build/godot/operations/resource_files.gd +11 -9
- package/build/godot/operations/same_values.gd +4 -4
- package/build/godot/operations/scene_connections.gd +27 -13
- package/build/godot/operations/script_check.gd +52 -49
- package/build/godot/operations/serialisation.gd +75 -51
- package/build/godot/operations/setting_reads.gd +7 -6
- package/build/godot/operations/value_shapes.gd +5 -6
- package/build/godot/tests/listed.gd +321 -268
- package/build/keeper.js +43 -43
- package/package.json +1 -1
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
@tool
|
|
2
2
|
extends "animation_tools.gd"
|
|
3
|
-
|
|
4
|
-
##
|
|
5
|
-
## whose helpers it shares.
|
|
3
|
+
## The tracks and keys of an animation, beside the players and libraries the script it extends
|
|
4
|
+
## edits, whose helpers it shares.
|
|
6
5
|
|
|
7
6
|
|
|
8
7
|
# =============================================================================
|
|
@@ -15,19 +14,19 @@ func add_animation_track(args: Dictionary) -> Dictionary:
|
|
|
15
14
|
var given_track: Variant = args.get("track", {})
|
|
16
15
|
|
|
17
16
|
if scene_path.strip_edges() == "res://":
|
|
18
|
-
return {"ok": false, "error": "Missing scenePath"}
|
|
17
|
+
return { "ok": false, "error": "Missing scenePath" }
|
|
19
18
|
if animation_name.strip_edges().is_empty():
|
|
20
|
-
return {"ok": false, "error": "Missing animationName"}
|
|
19
|
+
return { "ok": false, "error": "Missing animationName" }
|
|
21
20
|
var track: Dictionary = given_track if given_track is Dictionary else {}
|
|
22
21
|
if track.is_empty():
|
|
23
|
-
return {"ok": false, "error": "Missing track"}
|
|
24
|
-
# The schema leaves keyframes out of what track requires, since remove_track finds a track
|
|
25
|
-
# them.
|
|
22
|
+
return { "ok": false, "error": "Missing track" }
|
|
23
|
+
# The schema leaves keyframes out of what track requires, since remove_track finds a track
|
|
24
|
+
# without them.
|
|
26
25
|
if not track.has("keyframes"):
|
|
27
|
-
return {"ok": false, "error": "Missing track.keyframes"}
|
|
26
|
+
return { "ok": false, "error": "Missing track.keyframes" }
|
|
28
27
|
var keyed: Array = _keyframes(track)
|
|
29
28
|
if not keyed[0]:
|
|
30
|
-
return {"ok": false, "error": keyed[1]}
|
|
29
|
+
return { "ok": false, "error": keyed[1] }
|
|
31
30
|
|
|
32
31
|
# Asked of the player, which knows every library: looking only in the default one refused
|
|
33
32
|
# "moves/walk" as missing.
|
|
@@ -48,13 +47,16 @@ func add_animation_track(args: Dictionary) -> Dictionary:
|
|
|
48
47
|
target_path = "."
|
|
49
48
|
var target: Node = _animated(player, target_path)
|
|
50
49
|
if target == null:
|
|
51
|
-
return
|
|
52
|
-
|
|
50
|
+
return (
|
|
51
|
+
scene
|
|
52
|
+
. refuse(
|
|
53
53
|
(
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
(
|
|
55
|
+
"No node at %s from the player's root node, %s, which is what the track "
|
|
56
|
+
+ "path is read against."
|
|
57
|
+
)
|
|
58
|
+
% [target_path, str(player.root_node)]
|
|
56
59
|
)
|
|
57
|
-
% [target_path, str(player.root_node)]
|
|
58
60
|
)
|
|
59
61
|
)
|
|
60
62
|
var keyframes: Array = keyed[1]
|
|
@@ -120,7 +122,12 @@ func add_animation_track(args: Dictionary) -> Dictionary:
|
|
|
120
122
|
## animates: two tracks for one path both play, the later overriding the earlier, and a key meant
|
|
121
123
|
## to extend the motion was split off where reading the animation showed two halves.
|
|
122
124
|
func _add_track(
|
|
123
|
-
anim: Animation,
|
|
125
|
+
anim: Animation,
|
|
126
|
+
track: Dictionary,
|
|
127
|
+
target: Node,
|
|
128
|
+
target_path: String,
|
|
129
|
+
keyframes: Array,
|
|
130
|
+
replacing: bool
|
|
124
131
|
) -> Array:
|
|
125
132
|
var track_type: String = str(track.get("type", ""))
|
|
126
133
|
var keys: Array = []
|
|
@@ -148,7 +155,7 @@ func _add_track(
|
|
|
148
155
|
return _not_added("keyframes[%d].args must be a list." % index)
|
|
149
156
|
var raw: Array = given
|
|
150
157
|
var arguments: Array = _fitted(target, method_name, _parse_method_args(raw))
|
|
151
|
-
keys.append({"method": method_name, "args": arguments})
|
|
158
|
+
keys.append({ "method": method_name, "args": arguments })
|
|
152
159
|
if misfit.is_empty():
|
|
153
160
|
misfit = _key_misfit(target, method_name, arguments, index)
|
|
154
161
|
method_found = target.has_method(method_name)
|
|
@@ -156,7 +163,10 @@ func _add_track(
|
|
|
156
163
|
else:
|
|
157
164
|
return _not_added(
|
|
158
165
|
(
|
|
159
|
-
|
|
166
|
+
(
|
|
167
|
+
"add_track makes property and method tracks, and track.type is %s; "
|
|
168
|
+
+ "remove_track takes any."
|
|
169
|
+
)
|
|
160
170
|
% track_type
|
|
161
171
|
)
|
|
162
172
|
)
|
|
@@ -178,14 +188,16 @@ func _add_track(
|
|
|
178
188
|
return _not_added(
|
|
179
189
|
(
|
|
180
190
|
(
|
|
181
|
-
"Track %d already animates %s and has a key at %s s, which
|
|
182
|
-
+ "replace. Nothing was written; replaceKeys true
|
|
191
|
+
"Track %d already animates %s and has a key at %s s, which "
|
|
192
|
+
+ "keyframes[%d] would replace. Nothing was written; replaceKeys true "
|
|
193
|
+
+ "replaces it."
|
|
183
194
|
)
|
|
184
195
|
% [track_idx, str(path), _seconds(time), index]
|
|
185
196
|
)
|
|
186
197
|
)
|
|
187
|
-
# A method key's value is its call, {"method": ..., "args": [...]}. The time is as
|
|
188
|
-
# the key's own, read back from a scene's 32-bit times, puts 0.1 at
|
|
198
|
+
# A method key's value is its call, {"method": ..., "args": [...]}. The time is as
|
|
199
|
+
# asked: the key's own, read back from a scene's 32-bit times, puts 0.1 at
|
|
200
|
+
# 0.100000001490116.
|
|
189
201
|
(
|
|
190
202
|
replaced
|
|
191
203
|
. append(
|
|
@@ -228,7 +240,9 @@ static func _fitted(target: Node, method: String, arguments: Array) -> Array:
|
|
|
228
240
|
declared = GdhRead.as_int(parameter.get("type", TYPE_NIL))
|
|
229
241
|
if value is float and declared != TYPE_FLOAT:
|
|
230
242
|
var number: float = value
|
|
231
|
-
if is_finite(number)
|
|
243
|
+
if (is_finite(number)
|
|
244
|
+
and number == floorf(number)
|
|
245
|
+
and absf(number) < 9_007_199_254_740_992.0):
|
|
232
246
|
value = int(number)
|
|
233
247
|
fitted.append(value)
|
|
234
248
|
return fitted
|
|
@@ -256,10 +270,16 @@ static func _key_misfit(target: Node, method: String, arguments: Array, index: i
|
|
|
256
270
|
else ("1 argument" if arguments.size() == 1 else "%d arguments" % arguments.size())
|
|
257
271
|
)
|
|
258
272
|
if arguments.size() < needed:
|
|
259
|
-
return
|
|
273
|
+
return (
|
|
274
|
+
"keyframes[%d] calls %s with %s, and it needs %d%s"
|
|
275
|
+
% [index, method, passed, needed, fails]
|
|
276
|
+
)
|
|
260
277
|
if arguments.size() > parameters.size():
|
|
261
278
|
var most: String = "none" if parameters.is_empty() else "at most %d" % parameters.size()
|
|
262
|
-
return
|
|
279
|
+
return (
|
|
280
|
+
"keyframes[%d] calls %s with %s, and it takes %s%s"
|
|
281
|
+
% [index, method, passed, most, fails]
|
|
282
|
+
)
|
|
263
283
|
for at: int in arguments.size():
|
|
264
284
|
var parameter: Dictionary = parameters[at]
|
|
265
285
|
var taken_type: int = GdhRead.as_int(parameter.get("type", TYPE_NIL))
|
|
@@ -276,7 +296,7 @@ static func _key_misfit(target: Node, method: String, arguments: Array, index: i
|
|
|
276
296
|
method,
|
|
277
297
|
str(parameter.get("name", "")),
|
|
278
298
|
type_string(taken_type),
|
|
279
|
-
fails
|
|
299
|
+
fails,
|
|
280
300
|
]
|
|
281
301
|
)
|
|
282
302
|
return ""
|
|
@@ -293,22 +313,22 @@ func remove_animation_track(args: Dictionary) -> Dictionary:
|
|
|
293
313
|
var animation_name: String = str(args.get("animationName", ""))
|
|
294
314
|
var given_track: Variant = args.get("track", {})
|
|
295
315
|
if scene_path.strip_edges() == "res://":
|
|
296
|
-
return {"ok": false, "error": "Missing scenePath"}
|
|
316
|
+
return { "ok": false, "error": "Missing scenePath" }
|
|
297
317
|
if animation_name.strip_edges().is_empty():
|
|
298
|
-
return {"ok": false, "error": "Missing animationName"}
|
|
318
|
+
return { "ok": false, "error": "Missing animationName" }
|
|
299
319
|
var track: Dictionary = given_track if given_track is Dictionary else {}
|
|
300
320
|
if track.is_empty():
|
|
301
|
-
return {"ok": false, "error": "Missing track"}
|
|
321
|
+
return { "ok": false, "error": "Missing track" }
|
|
302
322
|
var track_type: String = str(track.get("type", ""))
|
|
303
323
|
if not track_type in GdhAnimationReads.TRACK_TYPE_NAMES.values():
|
|
304
|
-
return {"ok": false, "error": "Unsupported track.type: " + track_type}
|
|
324
|
+
return { "ok": false, "error": "Unsupported track.type: " + track_type }
|
|
305
325
|
var node_path: String = str(track.get("nodePath", ""))
|
|
306
326
|
var property: String = str(track.get("property", ""))
|
|
307
327
|
var shape: Array = [track_type, node_path, property]
|
|
308
328
|
var method: String = str(track.get("method", "")) if track_type == "method" else ""
|
|
309
329
|
var read_times: Variant = _times_of(args.get("times"))
|
|
310
330
|
if read_times is String:
|
|
311
|
-
return {"ok": false, "error": read_times}
|
|
331
|
+
return { "ok": false, "error": read_times }
|
|
312
332
|
var times: Array = read_times
|
|
313
333
|
|
|
314
334
|
var found: Array = _animation_at(scene_path, player_node_path, animation_name)
|
|
@@ -335,7 +355,7 @@ func remove_animation_track(args: Dictionary) -> Dictionary:
|
|
|
335
355
|
% [
|
|
336
356
|
animation_name,
|
|
337
357
|
track_type,
|
|
338
|
-
node_path if property.is_empty() else "%s:%s" % [node_path, property]
|
|
358
|
+
node_path if property.is_empty() else "%s:%s" % [node_path, property],
|
|
339
359
|
]
|
|
340
360
|
)
|
|
341
361
|
if held.is_empty():
|
|
@@ -353,7 +373,9 @@ func remove_animation_track(args: Dictionary) -> Dictionary:
|
|
|
353
373
|
var timed: Array = _keys_at(anim, calling, times)
|
|
354
374
|
if timed[1] != "":
|
|
355
375
|
var on: String = node_path if property.is_empty() else "%s:%s" % [node_path, property]
|
|
356
|
-
return scene.refuse(
|
|
376
|
+
return scene.refuse(
|
|
377
|
+
"%s's %s track on %s %s" % [animation_name, track_type, on, timed[1]]
|
|
378
|
+
)
|
|
357
379
|
calling = timed[0]
|
|
358
380
|
var keys: int = 0
|
|
359
381
|
var tracks_removed: int = 0
|
|
@@ -383,7 +405,7 @@ func remove_animation_track(args: Dictionary) -> Dictionary:
|
|
|
383
405
|
if loaded.get_track_count() != left or _key_total(loaded) != keys_left:
|
|
384
406
|
return "the track would still be in %s when the scene loads." % animation_name
|
|
385
407
|
return ""
|
|
386
|
-
var removed: Dictionary = {"type": track_type, "nodePath": node_path}
|
|
408
|
+
var removed: Dictionary = { "type": track_type, "nodePath": node_path }
|
|
387
409
|
if not property.is_empty():
|
|
388
410
|
removed["property"] = property
|
|
389
411
|
if not method.is_empty():
|
|
@@ -432,8 +454,8 @@ static func _times_of(given: Variant) -> Variant:
|
|
|
432
454
|
|
|
433
455
|
|
|
434
456
|
## Of the keys [param calling] names, as [method _keys_calling] answers them, the ones at
|
|
435
|
-
## [param times], as [[[track, key]...], ""], or with the second the words refusing a time none is
|
|
436
|
-
## Removing a key took removing its whole track, so moving one key in time rewrote every other.
|
|
457
|
+
## [param times], as [[[track, key]...], ""], or with the second the words refusing a time none is
|
|
458
|
+
## at. Removing a key took removing its whole track, so moving one key in time rewrote every other.
|
|
437
459
|
static func _keys_at(anim: Animation, calling: Array, times: Array) -> Array:
|
|
438
460
|
var at: Array = []
|
|
439
461
|
var held: PackedStringArray = []
|
|
@@ -457,7 +479,10 @@ static func _keys_at(anim: Animation, calling: Array, times: Array) -> Array:
|
|
|
457
479
|
var _named: bool = named.append(str(given))
|
|
458
480
|
return [
|
|
459
481
|
at,
|
|
460
|
-
|
|
482
|
+
(
|
|
483
|
+
"has no key at %s s; its keys are at %s s. Nothing was removed."
|
|
484
|
+
% [", ".join(named), ", ".join(held)]
|
|
485
|
+
),
|
|
461
486
|
]
|
|
462
487
|
|
|
463
488
|
|
|
@@ -483,7 +508,9 @@ static func _key_total(anim: Animation) -> int:
|
|
|
483
508
|
static func _track_shape(anim: Animation, index: int) -> Array:
|
|
484
509
|
var path: NodePath = anim.track_get_path(index)
|
|
485
510
|
return [
|
|
486
|
-
GdhAnimationReads.TRACK_TYPE_NAMES.get(
|
|
511
|
+
GdhAnimationReads.TRACK_TYPE_NAMES.get(
|
|
512
|
+
anim.track_get_type(index), str(anim.track_get_type(index))
|
|
513
|
+
),
|
|
487
514
|
str(path.get_concatenated_names()),
|
|
488
515
|
str(path.get_concatenated_subnames()),
|
|
489
516
|
]
|
|
@@ -508,7 +535,8 @@ func _keyframes(track: Dictionary) -> Array:
|
|
|
508
535
|
var seconds: float = GdhRead.as_float(time)
|
|
509
536
|
if times.has(seconds):
|
|
510
537
|
return [
|
|
511
|
-
false,
|
|
538
|
+
false,
|
|
539
|
+
"keyframes[%d] and [%d] are both at %s seconds." % [times[seconds], index, seconds],
|
|
512
540
|
]
|
|
513
541
|
times[seconds] = index
|
|
514
542
|
return [true, keyframes]
|
|
@@ -530,7 +558,9 @@ func _key_values(target: Node, prop_name: String, keyframes: Array) -> Array:
|
|
|
530
558
|
# A subname such as position:x keys one component, which is a number.
|
|
531
559
|
var component: Variant = _properties.parse(raw, TYPE_FLOAT)
|
|
532
560
|
if not component is float:
|
|
533
|
-
return [
|
|
561
|
+
return [
|
|
562
|
+
false, "keyframes[%d].value keys %s, which is a number." % [index, prop_name],
|
|
563
|
+
]
|
|
534
564
|
values.append(component)
|
|
535
565
|
continue
|
|
536
566
|
var value: Array = _properties.value_for(target, declared, raw)
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
@tool
|
|
2
2
|
extends Node
|
|
3
|
-
|
|
4
3
|
## The global classes the running editor is holding.
|
|
5
4
|
##
|
|
6
5
|
## The editor fixes this list when it starts and adds to it only from a filesystem scan, and
|
|
@@ -51,10 +50,10 @@ func set_editor_plugin(plugin: EditorPlugin) -> void:
|
|
|
51
50
|
func reload_script(args: Dictionary) -> Dictionary:
|
|
52
51
|
var asked: String = str(args.get("scriptPath", ""))
|
|
53
52
|
if asked.is_empty():
|
|
54
|
-
return {"ok": false, "error": "scriptPath is required"}
|
|
55
|
-
var path: String = _spelled_as_on_disk(asked)
|
|
53
|
+
return { "ok": false, "error": "scriptPath is required" }
|
|
54
|
+
var path: String = _spelled_as_on_disk(asked, {})
|
|
56
55
|
if not ResourceLoader.exists(path):
|
|
57
|
-
return {"ok": false, "error": "No script at " + path}
|
|
56
|
+
return { "ok": false, "error": "No script at " + path }
|
|
58
57
|
|
|
59
58
|
# The copy the editor holds, when it holds one, and otherwise a load that reads the file, rather
|
|
60
59
|
# than load(): a load recompiles a script whose base has been reloaded from the text it holds,
|
|
@@ -63,11 +62,13 @@ func reload_script(args: Dictionary) -> Dictionary:
|
|
|
63
62
|
# the reload below compiled it clean.
|
|
64
63
|
var cached: Resource = ResourceLoader.get_cached_ref(path)
|
|
65
64
|
var loaded: Resource = (
|
|
66
|
-
cached
|
|
65
|
+
cached
|
|
66
|
+
if cached != null
|
|
67
|
+
else ResourceLoader.load(path, "", ResourceLoader.CACHE_MODE_IGNORE)
|
|
67
68
|
)
|
|
68
69
|
var script: GDScript = loaded as GDScript
|
|
69
70
|
if script == null:
|
|
70
|
-
return {"ok": false, "error": path + " is not a GDScript"}
|
|
71
|
+
return { "ok": false, "error": path + " is not a GDScript" }
|
|
71
72
|
|
|
72
73
|
# What the copy had before anything touched it, which is the reading that shows the fault rather
|
|
73
74
|
# than the one that mends it. Without it a caller cannot tell a copy that was already current
|
|
@@ -82,7 +83,10 @@ func reload_script(args: Dictionary) -> Dictionary:
|
|
|
82
83
|
# method added since is still absent from the method list.
|
|
83
84
|
var reader: FileAccess = FileAccess.open(path, FileAccess.READ)
|
|
84
85
|
if reader == null:
|
|
85
|
-
return {
|
|
86
|
+
return {
|
|
87
|
+
"ok": false,
|
|
88
|
+
"error": "Could not read " + path + ": " + str(FileAccess.get_open_error()),
|
|
89
|
+
}
|
|
86
90
|
var previous: String = script.source_code
|
|
87
91
|
var text: String = reader.get_as_text()
|
|
88
92
|
reader.close()
|
|
@@ -90,11 +94,11 @@ func reload_script(args: Dictionary) -> Dictionary:
|
|
|
90
94
|
# Godot keeps one parse of each script for other scripts to resolve its class through, and a
|
|
91
95
|
# reload drops that parse only when the text it compiles differs from the text that was parsed.
|
|
92
96
|
# A parse that failed because a class it names was not yet known has the file's own text, so it
|
|
93
|
-
# outlived every reload: the class answered with all its methods while each script naming it
|
|
94
|
-
# "because of a parser error" until the editor restarted, for as long as anything held the
|
|
95
|
-
# such as a file open in an editor's language server. Compiling once with a line more
|
|
96
|
-
# texts differ, which drops that parse and those of the scripts that resolved through
|
|
97
|
-
# file's own text is compiled below.
|
|
97
|
+
# outlived every reload: the class answered with all its methods while each script naming it
|
|
98
|
+
# read "because of a parser error" until the editor restarted, for as long as anything held the
|
|
99
|
+
# parse, such as a file open in an editor's language server. Compiling once with a line more
|
|
100
|
+
# makes the texts differ, which drops that parse and those of the scripts that resolved through
|
|
101
|
+
# it; the file's own text is compiled below.
|
|
98
102
|
script.source_code = text + "\n"
|
|
99
103
|
var _dropped: Error = script.reload(true)
|
|
100
104
|
script.source_code = text
|
|
@@ -103,11 +107,11 @@ func reload_script(args: Dictionary) -> Dictionary:
|
|
|
103
107
|
# caller needs then: a failure travels back as its message alone, and a project met one and
|
|
104
108
|
# could not tell whether the copy had been damaged by it or before it.
|
|
105
109
|
#
|
|
106
|
-
# And the old text is compiled again. A failed reload leaves the object unusable while its
|
|
107
|
-
# lists read as before, measured on 4.7.2: it can make no instance, and an instance it
|
|
108
|
-
# has lost its methods, and the old text put back uncompiled leaves it that way.
|
|
109
|
-
# it was built from puts it back, and whether that worked is said, since the
|
|
110
|
-
# compile against what it names.
|
|
110
|
+
# And the old text is compiled again. A failed reload leaves the object unusable while its
|
|
111
|
+
# member lists read as before, measured on 4.7.2: it can make no instance, and an instance it
|
|
112
|
+
# already made has lost its methods, and the old text put back uncompiled leaves it that way.
|
|
113
|
+
# Compiling the text it was built from puts it back, and whether that worked is said, since the
|
|
114
|
+
# text may no longer compile against what it names.
|
|
111
115
|
var failed: Error = script.reload(true)
|
|
112
116
|
var answer: Dictionary = {
|
|
113
117
|
"ok": true,
|
|
@@ -129,8 +133,8 @@ func reload_script(args: Dictionary) -> Dictionary:
|
|
|
129
133
|
return answer
|
|
130
134
|
|
|
131
135
|
|
|
132
|
-
## Which of [code]scriptPaths[/code] the editor holds a compiled copy of whose text is not the
|
|
133
|
-
## without loading or reloading anything. A script compiled against such a copy fails on the
|
|
136
|
+
## Which of [code]scriptPaths[/code] the editor holds a compiled copy of whose text is not the
|
|
137
|
+
## file's, without loading or reloading anything. A script compiled against such a copy fails on the
|
|
134
138
|
## members the file has gained, and a reload of it fails the same way until the copy is reloaded
|
|
135
139
|
## first; the rescan that met this blamed the new classes the script also named, which were fine.
|
|
136
140
|
##
|
|
@@ -140,15 +144,16 @@ func reload_script(args: Dictionary) -> Dictionary:
|
|
|
140
144
|
func scripts_behind(args: Dictionary) -> Dictionary:
|
|
141
145
|
var given: Variant = args.get("scriptPaths", [])
|
|
142
146
|
if not given is Array:
|
|
143
|
-
return {"ok": false, "error": "scriptPaths must be a list of res:// paths"}
|
|
147
|
+
return { "ok": false, "error": "scriptPaths must be a list of res:// paths" }
|
|
144
148
|
var asked: Array = given
|
|
145
149
|
var unsaved: PackedStringArray = []
|
|
146
150
|
if Engine.is_editor_hint():
|
|
147
151
|
unsaved = EditorInterface.get_script_editor().get_unsaved_files()
|
|
148
152
|
var behind: Array[String] = []
|
|
149
153
|
var edited: Array[String] = []
|
|
154
|
+
var listings: Dictionary = {}
|
|
150
155
|
for entry: Variant in asked:
|
|
151
|
-
var path: String = _spelled_as_on_disk(str(entry))
|
|
156
|
+
var path: String = _spelled_as_on_disk(str(entry), listings)
|
|
152
157
|
var script: GDScript = ResourceLoader.get_cached_ref(path) as GDScript
|
|
153
158
|
if script == null:
|
|
154
159
|
continue
|
|
@@ -163,13 +168,13 @@ func scripts_behind(args: Dictionary) -> Dictionary:
|
|
|
163
168
|
edited.append(path)
|
|
164
169
|
else:
|
|
165
170
|
behind.append(path)
|
|
166
|
-
return {"ok": true, "behind": behind, "unsavedInEditor": edited}
|
|
171
|
+
return { "ok": true, "behind": behind, "unsavedInEditor": edited }
|
|
167
172
|
|
|
168
173
|
|
|
169
|
-
## Has the script editor's tabs reread what changed on disk, as the editor does when its window
|
|
170
|
-
## the focus, which a hidden editor's never does. A tab with unsaved edits is left alone and
|
|
171
|
-
## saving it writes its text over the file, and rereading would ask a question nobody can
|
|
172
|
-
## hidden editor's dialog.
|
|
174
|
+
## Has the script editor's tabs reread what changed on disk, as the editor does when its window
|
|
175
|
+
## takes the focus, which a hidden editor's never does. A tab with unsaved edits is left alone and
|
|
176
|
+
## named: saving it writes its text over the file, and rereading would ask a question nobody can
|
|
177
|
+
## answer in a hidden editor's dialog.
|
|
173
178
|
func _reread_open_tabs(path: String, answer: Dictionary) -> void:
|
|
174
179
|
# The engine fixtures reload from a script run outside any editor, where there are no tabs and
|
|
175
180
|
# EditorInterface has none of its methods.
|
|
@@ -192,28 +197,46 @@ func _reread_open_tabs(path: String, answer: Dictionary) -> void:
|
|
|
192
197
|
## `res://core/rules.gd`, but the editor holds its copy under the spelling on disk, and a load by
|
|
193
198
|
## another spelling misses that copy and compiles a new one: reloading that answers with a fresh
|
|
194
199
|
## compile's members and leaves the copy the editor holds as it was.
|
|
195
|
-
|
|
200
|
+
##
|
|
201
|
+
## [param listings] keeps each directory's entries for the length of one call that spells many
|
|
202
|
+
## paths. Listed again for every path, a check of the 416 classes of one project listed a folder of
|
|
203
|
+
## eight hundred files twice per class, and held an editor's main thread for close to a minute on a
|
|
204
|
+
## CI runner, with every request behind it waiting.
|
|
205
|
+
static func _spelled_as_on_disk(path: String, listings: Dictionary) -> String:
|
|
196
206
|
var local: String = ProjectSettings.localize_path(path)
|
|
197
207
|
if not local.begins_with("res://"):
|
|
198
208
|
return local
|
|
199
209
|
var spelled: String = "res://"
|
|
200
210
|
for step: String in local.trim_prefix("res://").split("/", false):
|
|
201
|
-
|
|
202
|
-
|
|
211
|
+
if not listings.has(spelled):
|
|
212
|
+
listings[spelled] = _entries_by_spelling(spelled)
|
|
213
|
+
var entries: Variant = listings[spelled]
|
|
214
|
+
if entries == null:
|
|
203
215
|
return local
|
|
204
|
-
var
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
found = entry
|
|
208
|
-
break
|
|
209
|
-
if found.is_empty() and entry.nocasecmp_to(step) == 0:
|
|
210
|
-
found = entry
|
|
216
|
+
var exact: Dictionary = entries[0]
|
|
217
|
+
var folded: Dictionary = entries[1]
|
|
218
|
+
var found: String = step if exact.has(step) else str(folded.get(step.to_lower(), ""))
|
|
211
219
|
if found.is_empty():
|
|
212
220
|
return local
|
|
213
221
|
spelled = spelled.path_join(found)
|
|
214
222
|
return spelled
|
|
215
223
|
|
|
216
224
|
|
|
225
|
+
## The entries of [param directory] by their own spelling and by their spelling in lower case, the
|
|
226
|
+
## first of those that fold together winning, or null for a directory that cannot be opened.
|
|
227
|
+
static func _entries_by_spelling(directory: String) -> Variant:
|
|
228
|
+
var listing: DirAccess = DirAccess.open(directory)
|
|
229
|
+
if listing == null:
|
|
230
|
+
return null
|
|
231
|
+
var exact: Dictionary = {}
|
|
232
|
+
var folded: Dictionary = {}
|
|
233
|
+
for entry: String in listing.get_directories() + listing.get_files():
|
|
234
|
+
exact[entry] = true
|
|
235
|
+
if not folded.has(entry.to_lower()):
|
|
236
|
+
folded[entry.to_lower()] = entry
|
|
237
|
+
return [exact, folded]
|
|
238
|
+
|
|
239
|
+
|
|
217
240
|
func _method_names(script: GDScript) -> Array[String]:
|
|
218
241
|
var names: Array[String] = []
|
|
219
242
|
for entry: Dictionary in script.get_script_method_list():
|
|
@@ -223,11 +246,11 @@ func _method_names(script: GDScript) -> Array[String]:
|
|
|
223
246
|
|
|
224
247
|
|
|
225
248
|
## The script's constants, those of the scripts it extends included as its methods include theirs,
|
|
226
|
-
## and the keys of each one that is a Dictionary as `Kind.SHORT`, which is how the copy holds a
|
|
227
|
-
## enum: a stale enum member is denied as a member of the enum rather than of the class. A
|
|
228
|
-
## Dictionary that is not an enum is listed the same way, since the map does not tell the
|
|
229
|
-
## The engine's map holds only the script's own, so a script extending a class_name
|
|
230
|
-
## method it inherited and none of the constants.
|
|
249
|
+
## and the keys of each one that is a Dictionary as `Kind.SHORT`, which is how the copy holds a
|
|
250
|
+
## named enum: a stale enum member is denied as a member of the enum rather than of the class. A
|
|
251
|
+
## constant Dictionary that is not an enum is listed the same way, since the map does not tell the
|
|
252
|
+
## two apart. The engine's map holds only the script's own, so a script extending a class_name
|
|
253
|
+
## listed every method it inherited and none of the constants.
|
|
231
254
|
func _constant_names(script: GDScript) -> Array[String]:
|
|
232
255
|
var names: Array[String] = []
|
|
233
256
|
var holder: Script = script
|
|
@@ -250,7 +273,7 @@ func _constant_names(script: GDScript) -> Array[String]:
|
|
|
250
273
|
|
|
251
274
|
func global_classes(_args: Dictionary) -> Dictionary:
|
|
252
275
|
if not _editor_plugin:
|
|
253
|
-
return {"ok": false, "error": "Editor plugin unavailable"}
|
|
276
|
+
return { "ok": false, "error": "Editor plugin unavailable" }
|
|
254
277
|
|
|
255
278
|
var filesystem: EditorFileSystem = EditorInterface.get_resource_filesystem()
|
|
256
279
|
var names: Array[String] = []
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
@tool
|
|
2
2
|
extends Node
|
|
3
|
-
|
|
4
3
|
## The editor's view of the files on disk: scans, the reimports asked for, where each stands, and
|
|
5
4
|
## files moved under it.
|
|
6
5
|
|
|
@@ -8,20 +7,17 @@ const GdhRead = preload("../reading.gd")
|
|
|
8
7
|
const GdhSceneFile = preload("../scene_file.gd")
|
|
9
8
|
|
|
10
9
|
var _editor_plugin: EditorPlugin = null
|
|
11
|
-
|
|
12
10
|
## How many scans the editor has completed, counted off `sources_changed`, which it emits at the end
|
|
13
11
|
## of every scan once the reimport the scan found is done: the one moment a scan is over. Neither
|
|
14
12
|
## flag it offers says so. A threaded scan stops reporting itself when its thread finishes, and its
|
|
15
13
|
## import runs a frame or more later when the thread is joined; an editor in the background took
|
|
16
14
|
## long enough over that frame that a rescan answered before the import of 211 changed scenes began.
|
|
17
15
|
var _scans_completed: int = 0
|
|
18
|
-
|
|
19
16
|
## When the last scan completed, on the engine's clock, or -1 before the first. The editor writes
|
|
20
17
|
## the class cache a frame after it stops reporting a scan, measured on 4.7.2, so a game started
|
|
21
18
|
## in between reads a file being rewritten; how long ago the scan ended is what tells a caller
|
|
22
19
|
## whether that write can still be coming.
|
|
23
20
|
var _scan_finished_at_msec: int = -1
|
|
24
|
-
|
|
25
21
|
## How many reimports asked for through [method reimport_files] have finished. Counted here rather
|
|
26
22
|
## than off `resources_reimported`, which a scan's own import emits as well.
|
|
27
23
|
var _reimports_completed: int = 0
|
|
@@ -53,7 +49,7 @@ func _scan_completed(_sources_exist: bool) -> void:
|
|
|
53
49
|
## another scan.
|
|
54
50
|
func rescan_filesystem(args: Dictionary) -> Dictionary:
|
|
55
51
|
if not _editor_plugin:
|
|
56
|
-
return {"ok": false, "error": "Editor plugin unavailable"}
|
|
52
|
+
return { "ok": false, "error": "Editor plugin unavailable" }
|
|
57
53
|
|
|
58
54
|
var filesystem: EditorFileSystem = EditorInterface.get_resource_filesystem()
|
|
59
55
|
# Not over a scan or an import the editor is already running: a scan asked for then starts a
|
|
@@ -62,13 +58,13 @@ func rescan_filesystem(args: Dictionary) -> Dictionary:
|
|
|
62
58
|
var busy: bool = filesystem.is_scanning() or filesystem.is_importing()
|
|
63
59
|
var started: bool = false
|
|
64
60
|
var completed_before: int = _scans_completed
|
|
65
|
-
var uids: Dictionary = {"reread": [], "duplicated": []}
|
|
61
|
+
var uids: Dictionary = { "reread": [], "duplicated": [] }
|
|
66
62
|
if not GdhRead.as_bool(args.get("statusOnly", false)) and not busy:
|
|
67
63
|
uids = _reread_changed_uids(filesystem)
|
|
68
|
-
# Files the caller has just written, moved or deleted, read again one by one. The scan finds
|
|
69
|
-
# script that appeared and leaves the global class of one that has gone in the editor's
|
|
70
|
-
# which it then writes back into the class cache at a path that is not there; reading
|
|
71
|
-
# path again is what drops it.
|
|
64
|
+
# Files the caller has just written, moved or deleted, read again one by one. The scan finds
|
|
65
|
+
# a script that appeared and leaves the global class of one that has gone in the editor's
|
|
66
|
+
# list, which it then writes back into the class cache at a path that is not there; reading
|
|
67
|
+
# the gone path again is what drops it.
|
|
72
68
|
var named: Variant = args.get("updateFiles", [])
|
|
73
69
|
if named is Array:
|
|
74
70
|
var paths: Array = named
|
|
@@ -137,14 +133,14 @@ func _reread_changed_uids(filesystem: EditorFileSystem) -> Dictionary:
|
|
|
137
133
|
reread.append(path)
|
|
138
134
|
reread.sort()
|
|
139
135
|
duplicated.sort()
|
|
140
|
-
return {"reread": reread, "duplicated": duplicated}
|
|
136
|
+
return { "reread": reread, "duplicated": duplicated }
|
|
141
137
|
|
|
142
138
|
|
|
143
139
|
## Every `.uid` under [param directory], skipping what the editor skips: hidden directories, any
|
|
144
140
|
## holding a `.gdignore`, and any below the project's own that holds a `project.godot` of its own,
|
|
145
|
-
## which the editor passes over as another project. A nested project's UIDs are never in the
|
|
146
|
-
## table, so without that last one its files would be read again on every rescan, and one
|
|
147
|
-
## copy of an outer file's `.uid` named as a duplicate on disk.
|
|
141
|
+
## which the editor passes over as another project. A nested project's UIDs are never in the
|
|
142
|
+
## editor's table, so without that last one its files would be read again on every rescan, and one
|
|
143
|
+
## holding a copy of an outer file's `.uid` named as a duplicate on disk.
|
|
148
144
|
func _uid_files(directory: String, found: Array[String]) -> void:
|
|
149
145
|
var listing: DirAccess = DirAccess.open(directory)
|
|
150
146
|
if listing == null or FileAccess.file_exists(directory.path_join(".gdignore")):
|
|
@@ -166,12 +162,13 @@ func _uid_files(directory: String, found: Array[String]) -> void:
|
|
|
166
162
|
## Apart from `rescan_filesystem` because asking must not start or count anything.
|
|
167
163
|
func scan_status(_args: Dictionary) -> Dictionary:
|
|
168
164
|
if not _editor_plugin:
|
|
169
|
-
return {"ok": false, "error": "Editor plugin unavailable"}
|
|
165
|
+
return { "ok": false, "error": "Editor plugin unavailable" }
|
|
170
166
|
var filesystem: EditorFileSystem = EditorInterface.get_resource_filesystem()
|
|
171
|
-
# The scenes open in tabs, the one shown and the ones holding unsaved edits: a scene edit on
|
|
172
|
-
# meets what the editor holds of it, and nothing said which scenes those were. A tab of a
|
|
173
|
-
# never saved has no path, and a new project's editor opens with an empty one, so it is
|
|
174
|
-
# of the open; one with edits is kept among the unsaved, as the edits a close would
|
|
167
|
+
# The scenes open in tabs, the one shown and the ones holding unsaved edits: a scene edit on
|
|
168
|
+
# disk meets what the editor holds of it, and nothing said which scenes those were. A tab of a
|
|
169
|
+
# scene never saved has no path, and a new project's editor opens with an empty one, so it is
|
|
170
|
+
# left out of the open; one with edits is kept among the unsaved, as the edits a close would
|
|
171
|
+
# lose.
|
|
175
172
|
var shown: Node = EditorInterface.get_edited_scene_root()
|
|
176
173
|
var open: Array[String] = []
|
|
177
174
|
open.assign(Array(EditorInterface.get_open_scenes()).filter(_has_path))
|
|
@@ -204,14 +201,14 @@ func _has_path(path: String) -> bool:
|
|
|
204
201
|
## while the editor scans or imports, since `reimport_files` refuses to run inside an import.
|
|
205
202
|
func reimport_files(args: Dictionary) -> Dictionary:
|
|
206
203
|
if not _editor_plugin:
|
|
207
|
-
return {"ok": false, "error": "Editor plugin unavailable"}
|
|
204
|
+
return { "ok": false, "error": "Editor plugin unavailable" }
|
|
208
205
|
var paths: Array[String] = []
|
|
209
206
|
var given: Variant = args.get("paths", [])
|
|
210
207
|
if given is Array:
|
|
211
208
|
for path: Variant in given:
|
|
212
209
|
paths.append(str(path))
|
|
213
210
|
if paths.is_empty():
|
|
214
|
-
return {"ok": false, "error": "reimport_files needs paths"}
|
|
211
|
+
return { "ok": false, "error": "reimport_files needs paths" }
|
|
215
212
|
var filesystem: EditorFileSystem = EditorInterface.get_resource_filesystem()
|
|
216
213
|
var busy: bool = filesystem.is_scanning() or filesystem.is_importing()
|
|
217
214
|
if not busy:
|
|
@@ -282,7 +279,12 @@ func files_moved(args: Dictionary) -> Dictionary:
|
|
|
282
279
|
reloaded.append(scene)
|
|
283
280
|
reloaded.sort()
|
|
284
281
|
unsaved.sort()
|
|
285
|
-
return {
|
|
282
|
+
return {
|
|
283
|
+
"ok": true,
|
|
284
|
+
"retargetedTabs": retargeted,
|
|
285
|
+
"reloadedScenes": reloaded,
|
|
286
|
+
"unsavedScenes": unsaved,
|
|
287
|
+
}
|
|
286
288
|
|
|
287
289
|
|
|
288
290
|
func _reimport(paths: PackedStringArray) -> void:
|