gdharness 1.3.41 → 1.3.43

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.
Files changed (107) hide show
  1. package/build/cli.js +131 -47
  2. package/build/godot/addons/auto_reload/auto_reload.gd +18 -19
  3. package/build/godot/addons/gdharness_editor/animation_reads.gd +84 -35
  4. package/build/godot/addons/gdharness_editor/bridge_client.gd +53 -58
  5. package/build/godot/addons/gdharness_editor/declared_values.gd +68 -32
  6. package/build/godot/addons/gdharness_editor/file_reads.gd +114 -39
  7. package/build/godot/addons/gdharness_editor/file_walk.gd +12 -5
  8. package/build/godot/addons/gdharness_editor/headless_check.gd +94 -41
  9. package/build/godot/addons/gdharness_editor/plugin.gd +2 -3
  10. package/build/godot/addons/gdharness_editor/property_paths.gd +22 -12
  11. package/build/godot/addons/gdharness_editor/property_values.gd +143 -78
  12. package/build/godot/addons/gdharness_editor/reading.gd +37 -12
  13. package/build/godot/addons/gdharness_editor/same_values.gd +4 -4
  14. package/build/godot/addons/gdharness_editor/scene_file.gd +136 -79
  15. package/build/godot/addons/gdharness_editor/scene_uids.gd +12 -6
  16. package/build/godot/addons/gdharness_editor/serialisation.gd +75 -51
  17. package/build/godot/addons/gdharness_editor/setting_reads.gd +7 -6
  18. package/build/godot/addons/gdharness_editor/tool_executor.gd +3 -6
  19. package/build/godot/addons/gdharness_editor/tools/animation_states.gd +41 -28
  20. package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +84 -50
  21. package/build/godot/addons/gdharness_editor/tools/animation_tracks.gd +70 -40
  22. package/build/godot/addons/gdharness_editor/tools/class_tools.gd +34 -30
  23. package/build/godot/addons/gdharness_editor/tools/filesystem_tools.gd +24 -22
  24. package/build/godot/addons/gdharness_editor/tools/navigation_bake.gd +49 -29
  25. package/build/godot/addons/gdharness_editor/tools/play_tools.gd +26 -21
  26. package/build/godot/addons/gdharness_editor/tools/profile_tools.gd +40 -40
  27. package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +181 -107
  28. package/build/godot/addons/gdharness_editor/tools/scene_groups.gd +16 -9
  29. package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +75 -43
  30. package/build/godot/addons/gdharness_editor/tools/scene_paths.gd +35 -20
  31. package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +28 -13
  32. package/build/godot/addons/gdharness_editor/tools/scene_sharing.gd +66 -0
  33. package/build/godot/addons/gdharness_editor/tools/scene_sharing.gd.uid +1 -0
  34. package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +171 -62
  35. package/build/godot/addons/gdharness_editor/tools/script_paths.gd +20 -8
  36. package/build/godot/addons/gdharness_editor/tools/settings_tools.gd +11 -8
  37. package/build/godot/addons/gdharness_editor/tools/signal_tools.gd +47 -34
  38. package/build/godot/addons/gdharness_editor/tools/tileset_tools.gd +77 -50
  39. package/build/godot/addons/gdharness_editor/value_shapes.gd +5 -6
  40. package/build/godot/addons/gdharness_runtime/call_errors.gd +1 -2
  41. package/build/godot/addons/gdharness_runtime/declared_values.gd +68 -32
  42. package/build/godot/addons/gdharness_runtime/error_report.gd +1 -2
  43. package/build/godot/addons/gdharness_runtime/reading.gd +37 -12
  44. package/build/godot/addons/gdharness_runtime/runtime_accessibility.gd +84 -63
  45. package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +153 -148
  46. package/build/godot/addons/gdharness_runtime/runtime_capture.gd +64 -42
  47. package/build/godot/addons/gdharness_runtime/runtime_changes.gd +184 -122
  48. package/build/godot/addons/gdharness_runtime/runtime_frames.gd +2 -2
  49. package/build/godot/addons/gdharness_runtime/runtime_hover.gd +24 -20
  50. package/build/godot/addons/gdharness_runtime/runtime_input.gd +134 -92
  51. package/build/godot/addons/gdharness_runtime/runtime_items.gd +29 -13
  52. package/build/godot/addons/gdharness_runtime/runtime_menus.gd +0 -1
  53. package/build/godot/addons/gdharness_runtime/runtime_mouse.gd +38 -20
  54. package/build/godot/addons/gdharness_runtime/runtime_pad.gd +17 -13
  55. package/build/godot/addons/gdharness_runtime/runtime_paths.gd +84 -46
  56. package/build/godot/addons/gdharness_runtime/runtime_queries.gd +117 -79
  57. package/build/godot/addons/gdharness_runtime/runtime_says.gd +14 -17
  58. package/build/godot/addons/gdharness_runtime/runtime_screen.gd +23 -16
  59. package/build/godot/addons/gdharness_runtime/runtime_targets.gd +117 -66
  60. package/build/godot/addons/gdharness_runtime/runtime_touch.gd +211 -0
  61. package/build/godot/addons/gdharness_runtime/runtime_touch.gd.uid +1 -0
  62. package/build/godot/addons/gdharness_runtime/runtime_typing.gd +44 -27
  63. package/build/godot/addons/gdharness_runtime/runtime_values.gd +108 -73
  64. package/build/godot/addons/gdharness_runtime/runtime_waits.gd +111 -84
  65. package/build/godot/addons/gdharness_runtime/runtime_words.gd +10 -7
  66. package/build/godot/addons/gdharness_runtime/serialisation.gd +75 -51
  67. package/build/godot/addons/gdharness_runtime/value_shapes.gd +5 -6
  68. package/build/godot/addons/gdharness_tests/bench.gd +4 -5
  69. package/build/godot/addons/gdharness_tests/event.gd +10 -5
  70. package/build/godot/addons/gdharness_tests/share.gd +38 -18
  71. package/build/godot/addons/gdharness_tests/suites.gd +3 -3
  72. package/build/godot/addons/gdharness_tests/wait.gd +3 -2
  73. package/build/godot/loader/loader.gd +9 -9
  74. package/build/godot/operations/animation_reads.gd +84 -35
  75. package/build/godot/operations/audio_buses.gd +48 -19
  76. package/build/godot/operations/class_cache.gd +16 -11
  77. package/build/godot/operations/classdb_queries.gd +89 -44
  78. package/build/godot/operations/declared_values.gd +68 -32
  79. package/build/godot/operations/dependencies.gd +139 -93
  80. package/build/godot/operations/editor_tools.gd +9 -10
  81. package/build/godot/operations/export_presets.gd +100 -54
  82. package/build/godot/operations/file_reads.gd +114 -39
  83. package/build/godot/operations/file_walk.gd +12 -5
  84. package/build/godot/operations/gdscript_analysis.gd +105 -66
  85. package/build/godot/operations/gdscript_authoring.gd +138 -39
  86. package/build/godot/operations/godot_operations.gd +17 -11
  87. package/build/godot/operations/import_pipeline.gd +110 -59
  88. package/build/godot/operations/input_actions.gd +63 -39
  89. package/build/godot/operations/patterns.gd +3 -1
  90. package/build/godot/operations/plugins.gd +36 -16
  91. package/build/godot/operations/project_config.gd +144 -27
  92. package/build/godot/operations/project_diagnostics.gd +24 -11
  93. package/build/godot/operations/project_file.gd +16 -16
  94. package/build/godot/operations/project_validation.gd +71 -44
  95. package/build/godot/operations/property_paths.gd +22 -12
  96. package/build/godot/operations/property_values.gd +143 -78
  97. package/build/godot/operations/reading.gd +37 -12
  98. package/build/godot/operations/rename_checks.gd +2 -2
  99. package/build/godot/operations/resource_files.gd +11 -9
  100. package/build/godot/operations/same_values.gd +4 -4
  101. package/build/godot/operations/scene_connections.gd +27 -13
  102. package/build/godot/operations/script_check.gd +52 -49
  103. package/build/godot/operations/serialisation.gd +75 -51
  104. package/build/godot/operations/setting_reads.gd +7 -6
  105. package/build/godot/operations/value_shapes.gd +5 -6
  106. package/build/godot/tests/listed.gd +321 -268
  107. package/package.json +1 -1
@@ -1,6 +1,5 @@
1
1
  @tool
2
2
  extends Node
3
-
4
3
  ## Resource files, shaders and themes, written through the open editor.
5
4
 
6
5
  const GdhFileReads = preload("../file_reads.gd")
@@ -12,7 +11,6 @@ const GdhSame = preload("../same_values.gd")
12
11
  const GdhSceneFile = preload("../scene_file.gd")
13
12
  const GdhSceneUids = preload("../scene_uids.gd")
14
13
  const GdhSerialisation = preload("../serialisation.gd")
15
-
16
14
  # The minimal shader of a type: its declaration and the one function the engine calls for it.
17
15
  const SHADER_EMPTY: String = """shader_type %s;
18
16
 
@@ -20,7 +18,11 @@ void %s() {
20
18
  }
21
19
  """
22
20
  const SHADER_ENTRY: Dictionary[String, String] = {
23
- "canvas_item": "fragment", "spatial": "fragment", "particles": "process", "sky": "sky", "fog": "fog"
21
+ "canvas_item": "fragment",
22
+ "spatial": "fragment",
23
+ "particles": "process",
24
+ "sky": "sky",
25
+ "fog": "fog",
24
26
  }
25
27
 
26
28
  var _editor_plugin: EditorPlugin = null
@@ -59,7 +61,10 @@ func _taken(path: String, op: String, remedy: String = "Change it with op=modify
59
61
  if not FileAccess.file_exists(path):
60
62
  return ""
61
63
  return (
62
- "%s already exists, and %s would replace everything in it. %s, or create the new one under another path."
64
+ (
65
+ "%s already exists, and %s would replace everything in it. %s, or create the new one "
66
+ + "under another path."
67
+ )
63
68
  % [path, op, remedy]
64
69
  )
65
70
 
@@ -84,7 +89,9 @@ func _parse_properties_dict(raw: Variant) -> Dictionary:
84
89
  func _load_theme(theme_path: String) -> Theme:
85
90
  if not ResourceLoader.exists(theme_path, "Theme"):
86
91
  return null
87
- var loaded: Resource = ResourceLoader.load(theme_path, "Theme", ResourceLoader.CACHE_MODE_IGNORE)
92
+ var loaded: Resource = ResourceLoader.load(
93
+ theme_path, "Theme", ResourceLoader.CACHE_MODE_IGNORE
94
+ )
88
95
  if loaded is Theme:
89
96
  return loaded
90
97
  return null
@@ -97,7 +104,9 @@ func _load_theme(theme_path: String) -> Theme:
97
104
  ## Read back because a property can be set and still not be saved: one without storage, a
98
105
  ## script variable that is not exported. Both were answered as changed.
99
106
  func _save_checked(resource: Resource, path: String, names: Array, previous: Variant) -> String:
100
- var carried: Dictionary = GdhHeadlessCheck.embedded_in(GdhHeadlessCheck.stored(resource).values(), {})
107
+ var carried: Dictionary = GdhHeadlessCheck.embedded_in(
108
+ GdhHeadlessCheck.stored(resource).values(), {}
109
+ )
101
110
  var saving: Error = GdhSceneUids.save_keeping_uid(resource, path)
102
111
  if saving != OK:
103
112
  return "Failed to save %s: %s" % [path, error_string(saving)]
@@ -117,8 +126,8 @@ func _save_checked(resource: Resource, path: String, names: Array, previous: Var
117
126
  if not GdhSame.same(read, _properties.read_at(resource, property)):
118
127
  lost = (
119
128
  (
120
- "%s.%s would load from the file as %s: it is not a property the file keeps, so "
121
- + "nothing was changed."
129
+ "%s.%s would load from the file as %s: it is not a property the file "
130
+ + "keeps, so nothing was changed."
122
131
  )
123
132
  % [path, property, _properties.shown(read)]
124
133
  )
@@ -145,8 +154,9 @@ func _unkept_headless(
145
154
  return (
146
155
  (
147
156
  (
148
- "%s is a binary resource with a script that is not a tool, and what that script "
149
- + "changed when a headless engine loaded it cannot be told apart from the file here. "
157
+ "%s is a binary resource with a script that is not a tool, and what that "
158
+ + "script changed when a headless engine loaded it cannot be told apart "
159
+ + "from the file here. "
150
160
  )
151
161
  % path
152
162
  )
@@ -157,22 +167,23 @@ func _unkept_headless(
157
167
  if previous is PackedByteArray:
158
168
  var bytes: PackedByteArray = previous
159
169
  before = bytes.get_string_from_utf8()
160
- var asked: Dictionary = {"script": true}
170
+ var asked: Dictionary = { "script": true }
161
171
  var reached: Dictionary = {}
162
172
  for key: Variant in names:
163
173
  var property: String = str(key)
164
174
  asked[property] = true
165
175
  asked[property.get_slice(":", 0)] = true
166
176
  GdhHeadlessCheck.spare(reached, GdhHeadlessCheck.reached(resource, property))
177
+ var just_saved: GdhHeadlessCheck.GdhSaved = GdhHeadlessCheck.GdhSaved.new(
178
+ path, before, func() -> Error: return GdhSceneUids.save_keeping_uid(resource, path)
179
+ )
167
180
  return GdhHeadlessCheck.settle_saved(
168
- path,
169
- before,
181
+ just_saved,
170
182
  GdhHeadlessCheck.stored(resource).values(),
171
183
  resource if scripted else null,
172
184
  asked,
173
185
  reached,
174
- carried,
175
- func() -> Error: return GdhSceneUids.save_keeping_uid(resource, path)
186
+ carried
176
187
  )
177
188
 
178
189
 
@@ -180,17 +191,20 @@ func create_resource(args: Dictionary) -> Dictionary:
180
191
  var res_path: String = _ensure_res_path(str(args.get("resourcePath", "")))
181
192
  var resource_type: String = str(args.get("resourceType", "Resource"))
182
193
  if res_path == "res://":
183
- return {"ok": false, "error": "resourcePath is required"}
194
+ return { "ok": false, "error": "resourcePath is required" }
184
195
  var taken: String = _taken(res_path, "resource_edit op=create")
185
196
  if not taken.is_empty():
186
- return {"ok": false, "error": taken}
197
+ return { "ok": false, "error": taken }
187
198
 
188
199
  var script_path: String = str(args.get("script", ""))
189
200
  var made: Dictionary = GdhSceneFile.made_as(
190
- resource_type, "" if script_path == "" else _ensure_res_path(script_path), "Resource", "resourceType"
201
+ resource_type,
202
+ "" if script_path == "" else _ensure_res_path(script_path),
203
+ "Resource",
204
+ "resourceType"
191
205
  )
192
206
  if made.has("error"):
193
- return {"ok": false, "error": made["error"]}
207
+ return { "ok": false, "error": made["error"] }
194
208
  resource_type = str(made["type"])
195
209
  script_path = str(made["script"])
196
210
  if not ClassDB.class_exists(resource_type):
@@ -200,10 +214,15 @@ func create_resource(args: Dictionary) -> Dictionary:
200
214
  (
201
215
  "%s is neither an engine class nor a class_name a script in this project declares."
202
216
  % resource_type
203
- )
217
+ ),
218
+ }
219
+ if (
220
+ not ClassDB.is_parent_class(resource_type, "Resource")
221
+ or not ClassDB.can_instantiate(resource_type)
222
+ ):
223
+ return {
224
+ "ok": false, "error": "%s is not a resource class that can be created." % resource_type,
204
225
  }
205
- if not ClassDB.is_parent_class(resource_type, "Resource") or not ClassDB.can_instantiate(resource_type):
206
- return {"ok": false, "error": "%s is not a resource class that can be created." % resource_type}
207
226
  var resource: Resource = ClassDB.instantiate(resource_type)
208
227
 
209
228
  # A script that did not load was skipped, and a plain resource was saved in place of the custom
@@ -212,30 +231,37 @@ func create_resource(args: Dictionary) -> Dictionary:
212
231
  var script_file: String = _ensure_res_path(script_path)
213
232
  var script: Script = load(script_file) as Script
214
233
  if script == null:
215
- return {"ok": false, "error": "No script loads from " + script_file}
234
+ return { "ok": false, "error": "No script loads from " + script_file }
216
235
  var uncompiled: String = GdhSceneFile.uncompiled(script, script_file)
217
236
  if not uncompiled.is_empty():
218
- return {"ok": false, "error": uncompiled}
237
+ return { "ok": false, "error": uncompiled }
219
238
  var base: String = str(script.get_instance_base_type())
220
239
  if not ClassDB.is_parent_class(resource_type, base):
221
240
  return {
222
241
  "ok": false,
223
- "error": "%s extends %s, so it cannot be on a %s." % [script_file, base, resource_type]
242
+ "error":
243
+ "%s extends %s, so it cannot be on a %s." % [script_file, base, resource_type],
224
244
  }
225
245
  resource.set_script(script)
226
246
 
227
247
  var properties: Dictionary = _parse_properties_dict(args.get("properties", {}))
228
248
  var refused: String = _properties.write_all(resource, properties)
229
249
  if not refused.is_empty():
230
- return {"ok": false, "error": GdhHeadlessCheck.setter_ran(resource, _properties.unheld, refused)}
250
+ return {
251
+ "ok": false, "error": GdhHeadlessCheck.setter_ran(
252
+ resource,
253
+ _properties.unheld,
254
+ refused,
255
+ ),
256
+ }
231
257
 
232
258
  var folder: String = GdhSceneFile.make_folder_for(res_path)
233
259
  if not folder.is_empty():
234
- return {"ok": false, "error": folder}
235
- # As a read names it, the engine class under resourceType and the script's class beside it, since
236
- # a create given resourceType Resource and a script answered nothing of the class it had made, and
237
- # one given a class_name answered it where a read of the same file answers Resource.
238
- var answer: Dictionary = {"ok": true, "resourcePath": res_path, "resourceType": resource_type}
260
+ return { "ok": false, "error": folder }
261
+ # As a read names it, the engine class under resourceType and the script's class beside it,
262
+ # since a create given resourceType Resource and a script answered nothing of the class it had
263
+ # made, and one given a class_name answered it where a read of the same file answers Resource.
264
+ var answer: Dictionary = { "ok": true, "resourcePath": res_path, "resourceType": resource_type }
239
265
  var scripted: Variant = resource.get_script()
240
266
  if scripted is Script:
241
267
  var attached: Script = scripted
@@ -245,7 +271,8 @@ func create_resource(args: Dictionary) -> Dictionary:
245
271
  _properties.note_write(answer)
246
272
  var failed: String = _save_checked(resource, res_path, properties.keys(), null)
247
273
  if not failed.is_empty():
248
- return {"ok": false, "error": failed}
274
+ return { "ok": false, "error": failed }
275
+ _note_put_back(answer, resource, res_path)
249
276
  if not properties.is_empty():
250
277
  answer["properties"] = _held(resource, properties.keys())
251
278
  return answer
@@ -254,9 +281,9 @@ func create_resource(args: Dictionary) -> Dictionary:
254
281
  func modify_resource(args: Dictionary) -> Dictionary:
255
282
  var res_path: String = _ensure_res_path(str(args.get("resourcePath", "")))
256
283
  if res_path == "res://":
257
- return {"ok": false, "error": "resourcePath is required"}
284
+ return { "ok": false, "error": "resourcePath is required" }
258
285
  if not FileAccess.file_exists(res_path):
259
- return {"ok": false, "error": "No resource at %s; op=create makes one there." % res_path}
286
+ return { "ok": false, "error": "No resource at %s; op=create makes one there." % res_path }
260
287
  # The editor's copy of an imported file is rebuilt from the source on every import, and a save
261
288
  # to it fails; edited through the cache, the editor kept the changed copy after the refusal.
262
289
  if FileAccess.file_exists(res_path + ".import"):
@@ -265,17 +292,17 @@ func modify_resource(args: Dictionary) -> Dictionary:
265
292
  "error":
266
293
  (
267
294
  (
268
- "%s is imported, so what it holds comes from its source file and its import options. "
269
- + "Change those with project_import op=set_options."
295
+ "%s is imported, so what it holds comes from its source file and its import "
296
+ + "options. Change those with project_import op=set_options."
270
297
  )
271
298
  % res_path
272
- )
299
+ ),
273
300
  }
274
301
 
275
302
  # Past the cache, so a refusal leaves the copy open scenes hold as it was.
276
303
  var resource: Resource = ResourceLoader.load(res_path, "", ResourceLoader.CACHE_MODE_IGNORE)
277
304
  if resource == null:
278
- return {"ok": false, "error": "%s did not load as a resource." % res_path}
305
+ return { "ok": false, "error": "%s did not load as a resource." % res_path }
279
306
  # A scene's own properties are its packed bundle, so a property named for a scene is one of its
280
307
  # nodes', and "PackedScene has no property" left the caller looking for the tool that sets it.
281
308
  if resource is PackedScene:
@@ -283,9 +310,12 @@ func modify_resource(args: Dictionary) -> Dictionary:
283
310
  "ok": false,
284
311
  "error":
285
312
  (
286
- "%s is a scene: scene_node set changes one node's properties, the root's at nodePath \".\"."
313
+ (
314
+ "%s is a scene: scene_node set changes one node's properties, the root's at "
315
+ + "nodePath \".\"."
316
+ )
287
317
  % res_path
288
- )
318
+ ),
289
319
  }
290
320
 
291
321
  var properties: Dictionary = _parse_properties_dict(args.get("properties", ""))
@@ -295,17 +325,31 @@ func modify_resource(args: Dictionary) -> Dictionary:
295
325
  else:
296
326
  refused = GdhHeadlessCheck.setter_ran(resource, _properties.unheld, refused)
297
327
  if not refused.is_empty():
298
- return {"ok": false, "error": refused}
299
- var answer: Dictionary = {"ok": true, "resourcePath": res_path}
328
+ return { "ok": false, "error": refused }
329
+ var answer: Dictionary = { "ok": true, "resourcePath": res_path }
300
330
  _properties.note_write(answer)
301
331
  var previous: PackedByteArray = FileAccess.get_file_as_bytes(res_path)
302
332
  var failed: String = _save_checked(resource, res_path, properties.keys(), previous)
303
333
  if not failed.is_empty():
304
- return {"ok": false, "error": failed}
334
+ return { "ok": false, "error": failed }
335
+ _note_put_back(answer, resource, res_path)
305
336
  answer["properties"] = _held(resource, properties.keys())
306
337
  return answer
307
338
 
308
339
 
340
+ ## Moves to setByScript each property alsoChanged in [param answer] gave a value [param resource] no
341
+ ## longer holds once saved at [param path]: the save put it back as its script's doing.
342
+ func _note_put_back(answer: Dictionary, resource: Resource, path: String) -> void:
343
+ var noted: Dictionary = answer.get("alsoChanged", {})
344
+ var restored: Array = []
345
+ for key: Variant in noted:
346
+ var now: Variant = _values.serialize_value(_properties.read_at(resource, str(key)))
347
+ var entry: Dictionary = noted[key]
348
+ if JSON.stringify(now) != JSON.stringify(entry.get("now")):
349
+ restored.append(key)
350
+ GdhHeadlessCheck.move_script_set(answer, restored, path)
351
+
352
+
309
353
  ## Why [param resource], saved at [param path], cannot hold what [param names] were given, or "": a
310
354
  ## resource cannot hold the file it is saved as. Godot saves the reference as null with only a
311
355
  ## warning in its log, and the read back after the save reported the property as one the file does
@@ -313,12 +357,14 @@ func modify_resource(args: Dictionary) -> Dictionary:
313
357
  func _holding_itself(resource: Resource, path: String, names: Array) -> String:
314
358
  for key: Variant in names:
315
359
  var property: String = str(key)
316
- for held: Resource in GdhHeadlessCheck.resources_in(_properties.read_at(resource, property)):
360
+ for held: Resource in GdhHeadlessCheck.resources_in(
361
+ _properties.read_at(resource, property)
362
+ ):
317
363
  if held == resource or held.resource_path == path:
318
364
  return (
319
365
  (
320
- "%s.%s would hold %s itself, and a resource cannot hold the file it is saved as: "
321
- + "Godot saves the reference as null. Nothing was written."
366
+ "%s.%s would hold %s itself, and a resource cannot hold the file it is "
367
+ + "saved as: Godot saves the reference as null. Nothing was written."
322
368
  )
323
369
  % [GdhPropertyPaths.class_called(resource), property, path]
324
370
  )
@@ -339,7 +385,7 @@ func _held(resource: Resource, asked: Array) -> Dictionary:
339
385
  func read_resource(args: Dictionary) -> Dictionary:
340
386
  var res_path: String = _ensure_res_path(str(args.get("resourcePath", "")))
341
387
  if res_path == "res://":
342
- return {"ok": false, "error": "resourcePath is required"}
388
+ return { "ok": false, "error": "resourcePath is required" }
343
389
  # The server takes a resource kept inside a file apart, so the boundary judges the file alone.
344
390
  var inner: String = str(args.get("subResource", ""))
345
391
  if not inner.is_empty():
@@ -351,10 +397,10 @@ func create_shader(args: Dictionary) -> Dictionary:
351
397
  var shader_path: String = _ensure_res_path(str(args.get("resourcePath", "")))
352
398
  var shader_type: String = str(args.get("shaderType", "canvas_item"))
353
399
  if shader_path == "res://":
354
- return {"ok": false, "error": "resourcePath is required"}
400
+ return { "ok": false, "error": "resourcePath is required" }
355
401
  var taken: String = _taken(shader_path, "resource_edit op=create_shader", "Edit its text")
356
402
  if not taken.is_empty():
357
- return {"ok": false, "error": taken}
403
+ return { "ok": false, "error": taken }
358
404
 
359
405
  var code: String = str(args.get("code", ""))
360
406
  if code == "":
@@ -362,21 +408,24 @@ func create_shader(args: Dictionary) -> Dictionary:
362
408
 
363
409
  var folder: String = GdhSceneFile.make_folder_for(shader_path)
364
410
  if not folder.is_empty():
365
- return {"ok": false, "error": folder}
411
+ return { "ok": false, "error": folder }
366
412
  var file: FileAccess = FileAccess.open(shader_path, FileAccess.WRITE)
367
413
  if file == null:
368
414
  return {
369
415
  "ok": false,
370
416
  "error":
371
- "Could not open %s for writing: %s" % [shader_path, error_string(FileAccess.get_open_error())]
417
+ (
418
+ "Could not open %s for writing: %s"
419
+ % [shader_path, error_string(FileAccess.get_open_error())]
420
+ ),
372
421
  }
373
422
  var written: bool = file.store_string(code)
374
423
  file.close()
375
424
  if not written:
376
- return {"ok": false, "error": "Failed to write the shader file " + shader_path}
425
+ return { "ok": false, "error": "Failed to write the shader file " + shader_path }
377
426
 
378
427
  _written(shader_path)
379
- var answer: Dictionary = {"ok": true, "shaderPath": shader_path, "shaderType": shader_type}
428
+ var answer: Dictionary = { "ok": true, "shaderPath": shader_path, "shaderType": shader_type }
380
429
  var errors: Array[Dictionary] = _shader_errors(shader_path)
381
430
  answer["compiles"] = errors.is_empty()
382
431
  if not errors.is_empty():
@@ -390,42 +439,21 @@ func create_shader(args: Dictionary) -> Dictionary:
390
439
  func _shader_errors(path: String) -> Array[Dictionary]:
391
440
  var caught: GdhShaderErrors = GdhShaderErrors.new()
392
441
  OS.add_logger(caught)
393
- var shader: Shader = ResourceLoader.load(path, "Shader", ResourceLoader.CACHE_MODE_IGNORE) as Shader
442
+ var shader: Shader = (
443
+ ResourceLoader.load(path, "Shader", ResourceLoader.CACHE_MODE_IGNORE) as Shader
444
+ )
394
445
  if shader != null:
395
446
  var _uniforms: Array = shader.get_shader_uniform_list()
396
447
  OS.remove_logger(caught)
397
448
  return caught.raised
398
449
 
399
450
 
400
- ## Every shader error the engine raises while it is attached, in order.
401
- class GdhShaderErrors:
402
- extends Logger
403
-
404
- var raised: Array[Dictionary] = []
405
-
406
- func _log_message(_message: String, _error: bool) -> void:
407
- pass
408
-
409
- # The engine's own signature for a Logger, eight parameters and not this project's to shape.
410
- # gdlint:ignore = function-arguments-number
411
- func _log_error(
412
- _function: String,
413
- _file: String,
414
- line: int,
415
- code: String,
416
- rationale: String,
417
- _editor_notify: bool,
418
- error_type: int,
419
- _script_backtraces: Array[ScriptBacktrace],
420
- ) -> void:
421
- if error_type == Logger.ERROR_TYPE_SHADER:
422
- raised.append({"line": line, "message": code if rationale.is_empty() else rationale})
423
-
424
-
425
451
  ## Saves [param theme] over [param theme_path] with [param items] changed, answering "" or why not.
426
452
  func _save_theme(theme: Theme, theme_path: String, items: Array) -> String:
427
453
  var previous: PackedByteArray = FileAccess.get_file_as_bytes(theme_path)
428
- var carried: Dictionary = GdhHeadlessCheck.embedded_in(GdhHeadlessCheck.stored(theme).values(), {})
454
+ var carried: Dictionary = GdhHeadlessCheck.embedded_in(
455
+ GdhHeadlessCheck.stored(theme).values(), {}
456
+ )
429
457
  var saving: Error = GdhSceneUids.save_keeping_uid(theme, theme_path)
430
458
  if saving != OK:
431
459
  return "Failed to save %s: %s" % [theme_path, error_string(saving)]
@@ -439,32 +467,37 @@ func _save_theme(theme: Theme, theme_path: String, items: Array) -> String:
439
467
  func set_theme_color(args: Dictionary) -> Dictionary:
440
468
  var theme_path: String = _ensure_res_path(str(args.get("resourcePath", "")))
441
469
  if theme_path == "res://":
442
- return {"ok": false, "error": "resourcePath is required"}
470
+ return { "ok": false, "error": "resourcePath is required" }
443
471
 
444
472
  var color_name: String = str(args.get("colorName", ""))
445
473
  var control_type: String = str(args.get("controlType", ""))
446
474
  if color_name.is_empty() or control_type.is_empty():
447
- return {"ok": false, "error": "colorName and controlType are required"}
475
+ return { "ok": false, "error": "colorName and controlType are required" }
448
476
 
449
477
  var theme: Theme = _load_theme(theme_path)
450
478
  if theme == null:
451
- return {"ok": false, "error": "No Theme at %s. Create one there first." % theme_path}
479
+ return { "ok": false, "error": "No Theme at %s. Create one there first." % theme_path }
452
480
 
453
481
  # Read as a property write reads a colour, html code included, so a value scene_node takes for a
454
482
  # Button's modulate is taken for its theme colour too; "#ff8000" was refused here alone.
455
483
  var parsed: Array = _properties.colour_from(args.get("color", {}), "color")
456
484
  if not parsed[0]:
457
- return {"ok": false, "error": parsed[1]}
485
+ return { "ok": false, "error": parsed[1] }
458
486
  var color: Color = parsed[1]
459
487
  theme.set_color(color_name, control_type, color)
460
488
 
461
- var unsaved: String = _save_theme(theme, theme_path, ["%s/colors/%s" % [control_type, color_name]])
489
+ var unsaved: String = _save_theme(
490
+ theme, theme_path, ["%s/colors/%s" % [control_type, color_name]]
491
+ )
462
492
  if not unsaved.is_empty():
463
- return {"ok": false, "error": unsaved}
493
+ return { "ok": false, "error": unsaved }
464
494
 
465
495
  var saved: Theme = _load_theme(theme_path)
466
496
  if saved == null or not saved.has_color(color_name, control_type):
467
- return {"ok": false, "error": "The theme saved without %s/%s" % [control_type, color_name]}
497
+ return {
498
+ "ok": false,
499
+ "error": "The theme saved without %s/%s" % [control_type, color_name],
500
+ }
468
501
 
469
502
  var stored: Color = saved.get_color(color_name, control_type)
470
503
  return _noting_variation(
@@ -473,7 +506,7 @@ func set_theme_color(args: Dictionary) -> Dictionary:
473
506
  "resourcePath": theme_path,
474
507
  "controlType": control_type,
475
508
  "colorName": color_name,
476
- "color": {"r": stored.r, "g": stored.g, "b": stored.b, "a": stored.a}
509
+ "color": { "r": stored.r, "g": stored.g, "b": stored.b, "a": stored.a },
477
510
  }
478
511
  )
479
512
 
@@ -481,33 +514,36 @@ func set_theme_color(args: Dictionary) -> Dictionary:
481
514
  func set_theme_font_size(args: Dictionary) -> Dictionary:
482
515
  var theme_path: String = _ensure_res_path(str(args.get("resourcePath", "")))
483
516
  if theme_path == "res://":
484
- return {"ok": false, "error": "resourcePath is required"}
517
+ return { "ok": false, "error": "resourcePath is required" }
485
518
 
486
519
  var font_size_name: String = str(args.get("fontSizeName", ""))
487
520
  var control_type: String = str(args.get("controlType", ""))
488
521
  if font_size_name.is_empty() or control_type.is_empty():
489
- return {"ok": false, "error": "fontSizeName and controlType are required"}
522
+ return { "ok": false, "error": "fontSizeName and controlType are required" }
490
523
 
491
524
  var theme: Theme = _load_theme(theme_path)
492
525
  if theme == null:
493
- return {"ok": false, "error": "No Theme at %s. Create one there first." % theme_path}
526
+ return { "ok": false, "error": "No Theme at %s. Create one there first." % theme_path }
494
527
 
495
528
  # Godot reads a size of 0 or less as unset and answers the theme's default, so -3 would be read
496
529
  # back as 16.
497
530
  var pixels: float = GdhRead.as_float(args.get("size"), 0.0)
498
531
  if not is_finite(pixels) or pixels != floorf(pixels):
499
- return {"ok": false, "error": "size is a whole number of pixels, and %s is not one." % str(pixels)}
532
+ return {
533
+ "ok": false,
534
+ "error": "size is a whole number of pixels, and %s is not one." % str(pixels),
535
+ }
500
536
  if pixels < 1:
501
537
  return {
502
538
  "ok": false,
503
539
  "error":
504
540
  (
505
541
  (
506
- "size must be 1 or more, not %d: the theme would hold it, and Godot reads a size of 0 or "
507
- + "less as unset and draws the default size instead."
542
+ "size must be 1 or more, not %d: the theme would hold it, and Godot reads a "
543
+ + "size of 0 or less as unset and draws the default size instead."
508
544
  )
509
545
  % int(pixels)
510
- )
546
+ ),
511
547
  }
512
548
  theme.set_font_size(font_size_name, control_type, int(pixels))
513
549
 
@@ -515,11 +551,13 @@ func set_theme_font_size(args: Dictionary) -> Dictionary:
515
551
  theme, theme_path, ["%s/font_sizes/%s" % [control_type, font_size_name]]
516
552
  )
517
553
  if not unsaved.is_empty():
518
- return {"ok": false, "error": unsaved}
554
+ return { "ok": false, "error": unsaved }
519
555
 
520
556
  var saved: Theme = _load_theme(theme_path)
521
557
  if saved == null or not saved.has_font_size(font_size_name, control_type):
522
- return {"ok": false, "error": "The theme saved without %s/%s" % [control_type, font_size_name]}
558
+ return {
559
+ "ok": false, "error": "The theme saved without %s/%s" % [control_type, font_size_name],
560
+ }
523
561
 
524
562
  return _noting_variation(
525
563
  {
@@ -527,7 +565,7 @@ func set_theme_font_size(args: Dictionary) -> Dictionary:
527
565
  "resourcePath": theme_path,
528
566
  "controlType": control_type,
529
567
  "fontSizeName": font_size_name,
530
- "size": saved.get_font_size(font_size_name, control_type)
568
+ "size": saved.get_font_size(font_size_name, control_type),
531
569
  }
532
570
  )
533
571
 
@@ -539,11 +577,11 @@ func remove_theme_item(args: Dictionary) -> Dictionary:
539
577
  var control_type: String = str(args.get("controlType", ""))
540
578
  var theme: Theme = _load_theme(theme_path)
541
579
  if theme == null:
542
- return {"ok": false, "error": "No Theme at %s." % theme_path}
580
+ return { "ok": false, "error": "No Theme at %s." % theme_path }
543
581
  if not theme.get_type_list().has(control_type):
544
582
  var types: PackedStringArray = theme.get_type_list()
545
583
  var said: String = "%s holds no type %s" % [theme_path, control_type]
546
- return {"ok": false, "error": GdhRead.offered(said, control_type, Array(types))}
584
+ return { "ok": false, "error": GdhRead.offered(said, control_type, Array(types)) }
547
585
  var kinds: Array[String] = ["colors", "constants", "fonts", "font_sizes", "icons", "styles"]
548
586
  var item: String = str(args.get("item", ""))
549
587
  var removed: Array[String] = []
@@ -566,13 +604,21 @@ func remove_theme_item(args: Dictionary) -> Dictionary:
566
604
  "%s holds no items, only the type it varies, %s; "
567
605
  + "without item, remove_theme_item takes it out."
568
606
  )
569
- return {"ok": false, "error": only % [control_type, base]}
607
+ return { "ok": false, "error": only % [control_type, base] }
570
608
  elif removed.is_empty():
571
- return {"ok": false, "error": GdhRead.offered("%s has no item %s" % [control_type, item], item, held)}
609
+ return {
610
+ "ok": false,
611
+ "error": GdhRead.offered("%s has no item %s" % [control_type, item], item, held),
612
+ }
572
613
  var unsaved: String = _save_theme(theme, theme_path, removed)
573
614
  if not unsaved.is_empty():
574
- return {"ok": false, "error": unsaved}
575
- return {"ok": true, "resourcePath": theme_path, "controlType": control_type, "removed": removed}
615
+ return { "ok": false, "error": unsaved }
616
+ return {
617
+ "ok": true,
618
+ "resourcePath": theme_path,
619
+ "controlType": control_type,
620
+ "removed": removed,
621
+ }
576
622
 
577
623
 
578
624
  ## [param answer] with a note when its controlType names no class a theme styles. Any name is a
@@ -587,8 +633,8 @@ func _noting_variation(answer: Dictionary) -> Dictionary:
587
633
  return answer
588
634
  answer["note"] = (
589
635
  (
590
- "%s is not a Control or Window class, so this is a type variation: it styles only a control "
591
- + "whose theme_type_variation is %s."
636
+ "%s is not a Control or Window class, so this is a type variation: it styles only a "
637
+ + "control whose theme_type_variation is %s."
592
638
  )
593
639
  % [type, type]
594
640
  )
@@ -601,7 +647,35 @@ func _noting_variation(answer: Dictionary) -> Dictionary:
601
647
  var close: PackedStringArray = GdhRead.nearest(type, themed)
602
648
  if not close.is_empty():
603
649
  answer["note"] += (
604
- (" If a class was meant, the nearest %s %s, and remove_theme_item with controlType %s takes this out.")
650
+ (
651
+ " If a class was meant, the nearest %s %s, and remove_theme_item with controlType "
652
+ + "%s takes this out."
653
+ )
605
654
  % ["is" if close.size() == 1 else "are", ", ".join(close), type]
606
655
  )
607
656
  return answer
657
+
658
+
659
+ ## Every shader error the engine raises while it is attached, in order.
660
+ class GdhShaderErrors:
661
+ extends Logger
662
+
663
+ var raised: Array[Dictionary] = []
664
+
665
+ func _log_message(_message: String, _error: bool) -> void:
666
+ pass
667
+
668
+ # The engine's own signature for a Logger, eight parameters and not this project's to shape.
669
+ # gdstyle:ignore=quality/max-parameters
670
+ func _log_error(
671
+ _function: String,
672
+ _file: String,
673
+ line: int,
674
+ code: String,
675
+ rationale: String,
676
+ _editor_notify: bool,
677
+ error_type: int,
678
+ _script_backtraces: Array[ScriptBacktrace],
679
+ ) -> void:
680
+ if error_type == Logger.ERROR_TYPE_SHADER:
681
+ raised.append({ "line": line, "message": code if rationale.is_empty() else rationale })