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
@@ -4,7 +4,6 @@ const GdhRead = preload("reading.gd")
4
4
  const GdhSerialisation = preload("serialisation.gd")
5
5
  const GdhFileWalk = preload("file_walk.gd")
6
6
  const GdhLog = preload("logger.gd")
7
-
8
7
  ## What the engine imports with no tool outside it, for finding what has not been imported yet;
9
8
  ## everything imported already is found by its sidecar whatever its extension.
10
9
  const IMPORTABLE_EXTENSIONS: Array[String] = [
@@ -47,15 +46,15 @@ const BLENDER_SETTING: String = "filesystem/import/blender/blender_path"
47
46
  const BLENDER_ENABLED: String = "filesystem/import/blender/enabled"
48
47
  ## The format version each importer writes as `importer_version` where it is above 0, which the
49
48
  ## editor reimports anything older than. Read off sidecars 4.7.2 writes: scene and animation_library
50
- ## write 1, and the texture importers write none. A case imports one fresh and compares, so an engine
51
- ## that raises one fails it rather than leaving this behind.
52
- const IMPORTER_VERSIONS: Dictionary[String, int] = {"scene": 1, "animation_library": 1}
49
+ ## write 1, and the texture importers write none. A case imports one fresh and compares, so an
50
+ ## engine that raises one fails it rather than leaving this behind.
51
+ const IMPORTER_VERSIONS: Dictionary[String, int] = { "scene": 1, "animation_library": 1 }
53
52
  ## The VRAM compression formats a project can ask for, each behind its
54
53
  ## `rendering/textures/vram_compression/import_<format>` setting.
55
54
  const VRAM_FORMATS: Array[String] = ["s3tc_bptc", "etc2_astc"]
56
- ## Options keyed by OpenType tags, by the importer that holds them. The engine keys them by the tag as
57
- ## an int, which a caller can also name, as "liga" for ligatures.
58
- const OPENTYPE_OPTIONS: Dictionary[String, Array] = {"font_data_dynamic": ["opentype_features"]}
55
+ ## Options keyed by OpenType tags, by the importer that holds them. The engine keys them by the tag
56
+ ## as an int, which a caller can also name, as "liga" for ligatures.
57
+ const OPENTYPE_OPTIONS: Dictionary[String, Array] = { "font_data_dynamic": ["opentype_features"] }
59
58
  const EDITOR_DIRECTORY: String = "res://.godot/editor"
60
59
  const EDITOR_CACHE_PREFIX: String = "filesystem_cache"
61
60
 
@@ -83,7 +82,12 @@ func get_import_status(params: Dictionary) -> Dictionary:
83
82
 
84
83
  var resources: Array[Dictionary] = []
85
84
  var summary: Dictionary = {
86
- "total": 0, "needs_reimport": 0, "failed": 0, "up_to_date": 0, "missing_source": 0, "not_imported": 0
85
+ "total": 0,
86
+ "needs_reimport": 0,
87
+ "failed": 0,
88
+ "up_to_date": 0,
89
+ "missing_source": 0,
90
+ "not_imported": 0,
87
91
  }
88
92
 
89
93
  if not resource_path.is_empty():
@@ -91,9 +95,13 @@ func get_import_status(params: Dictionary) -> Dictionary:
91
95
  if not full_path.begins_with("res://"):
92
96
  full_path = "res://" + full_path
93
97
 
94
- # missing_source is a sidecar whose source has gone; with neither, the path names nothing the
95
- # engine ever imported, and answering it as a lost source sent a caller to restore a file.
96
- if not FileAccess.file_exists(full_path) and not FileAccess.file_exists(full_path + ".import"):
98
+ # missing_source is a sidecar whose source has gone; with neither, the path names nothing
99
+ # the engine ever imported, and answering it as a lost source sent a caller to restore a
100
+ # file.
101
+ if (
102
+ not FileAccess.file_exists(full_path)
103
+ and not FileAccess.file_exists(full_path + ".import")
104
+ ):
97
105
  return _log.failure(
98
106
  _files.with_nearest_files(
99
107
  "Nothing is at %s, and no import sidecar names it" % full_path, full_path
@@ -103,8 +111,8 @@ func get_import_status(params: Dictionary) -> Dictionary:
103
111
  resources.append(status)
104
112
  _tally(summary, status)
105
113
  else:
106
- # Sources by extension, for what has never been imported, and sidecars, for what was imported
107
- # from a type the list does not name and for a sidecar whose source is gone.
114
+ # Sources by extension, for what has never been imported, and sidecars, for what was
115
+ # imported from a type the list does not name and for a sidecar whose source is gone.
108
116
  var found: Dictionary[String, bool] = {}
109
117
  for res_path: String in _files.find_files_with_extensions("res://", IMPORTABLE_EXTENSIONS):
110
118
  found[res_path] = true
@@ -121,14 +129,14 @@ func get_import_status(params: Dictionary) -> Dictionary:
121
129
 
122
130
  _tally(summary, status)
123
131
 
124
- var answer: Dictionary = {"resources": resources, "summary": summary}
132
+ var answer: Dictionary = { "resources": resources, "summary": summary }
125
133
  # A total of none reads as a walk that found nothing when the project is full of scripts and
126
134
  # scenes, which the engine loads as they are and never imports.
127
135
  if resource_path.is_empty() and summary["total"] == 0:
128
136
  answer["note"] = (
129
- "Nothing in this project is imported: the engine imports images, audio, fonts, models and"
130
- + " the like, found by extension or by a .import sidecar beside them, and loads scripts and"
131
- + " scenes as they are."
137
+ "Nothing in this project is imported: the engine imports images, audio, fonts, models "
138
+ + "and the like, found by extension or by a .import sidecar beside them, and loads "
139
+ + "scripts and scenes as they are."
132
140
  )
133
141
  return answer
134
142
 
@@ -153,7 +161,11 @@ func get_import_options(params: Dictionary) -> Dictionary:
153
161
  return _log.failure("Failed to parse import file: " + str(err))
154
162
 
155
163
  var result: Dictionary = {
156
- "resource_path": resource_path, "import_file": import_file_path, "remap": {}, "deps": {}, "params": {}
164
+ "resource_path": resource_path,
165
+ "import_file": import_file_path,
166
+ "remap": {},
167
+ "deps": {},
168
+ "params": {},
157
169
  }
158
170
 
159
171
  # Through the serialiser, as every other read answers: an engine type written straight into
@@ -221,7 +233,10 @@ func set_import_options(params: Dictionary) -> Dictionary:
221
233
  var held_type: String = type_string(typeof(held))
222
234
  var article: String = "an" if held_type.to_lower()[0] in "aeiou" else "a"
223
235
  refused.append(
224
- "%s holds %s %s, and %s is not one" % [name, article, held_type, JSON.stringify(options[key])]
236
+ (
237
+ "%s holds %s %s, and %s is not one"
238
+ % [name, article, held_type, JSON.stringify(options[key])]
239
+ )
225
240
  )
226
241
  continue
227
242
  fitted_values[name] = value
@@ -252,7 +267,7 @@ func set_import_options(params: Dictionary) -> Dictionary:
252
267
  return _log.failure("Failed to save import file: " + str(err))
253
268
 
254
269
  # The reimport that applies them is the server's, through the editor or the engine's import.
255
- var answer: Dictionary = {"resource_path": resource_path, "updated_options": updated_keys}
270
+ var answer: Dictionary = { "resource_path": resource_path, "updated_options": updated_keys }
256
271
  if not changes.is_empty():
257
272
  answer["changed"] = changes
258
273
  if not unchanged_keys.is_empty():
@@ -276,16 +291,18 @@ static func _as_option(given: Variant, held: Variant) -> Variant:
276
291
  ## JSON names every key as a string and every number as a float, so a font's opentype_features was
277
292
  ## written as {"1818847073": 0.0}. The importer hands that to FontFile.opentype_feature_overrides,
278
293
  ## which keys features by their tag as an int, so the call reported success and the font kept its
279
- ## ligatures. A key is an int where the importer keys the option by OpenType tags, or where every key
280
- ## the option holds already is one; a value is an int where every value held is, or for a tag.
294
+ ## ligatures. A key is an int where the importer keys the option by OpenType tags, or where every
295
+ ## key the option holds already is one; a value is an int where every value held is, or for a tag.
281
296
  ## Answers the dictionary under value, or why it cannot be written under refused.
282
- static func _keyed_as_option(name: String, given: Dictionary, held: Variant, importer: String) -> Dictionary:
297
+ static func _keyed_as_option(
298
+ name: String, given: Dictionary, held: Variant, importer: String
299
+ ) -> Dictionary:
283
300
  var tag_options: Array = OPENTYPE_OPTIONS.get(importer, [])
284
301
  var tagged: bool = name in tag_options
285
302
  var numbered_keys: bool = tagged or _all_ints(held, true)
286
303
  var whole_values: bool = tagged or _all_ints(held, false)
287
304
  if not numbered_keys and not whole_values:
288
- return {"value": given}
305
+ return { "value": given }
289
306
  var keyed: Dictionary = {}
290
307
  for key: Variant in given:
291
308
  var as_key: Variant = key
@@ -293,12 +310,14 @@ static func _keyed_as_option(name: String, given: Dictionary, held: Variant, imp
293
310
  as_key = _int_key(key, tagged)
294
311
  if as_key == null:
295
312
  var wanted: String = (
296
- 'OpenType tags, by name such as "liga" or as the number TextServer.name_to_tag gives'
313
+ 'OpenType tags, by name such as "liga" or as the number TextServer.name_to_tag '
314
+ + "gives"
297
315
  if tagged
298
316
  else "ints"
299
317
  )
300
318
  return {
301
- "refused": "%s is keyed by %s, and %s is not one" % [name, wanted, JSON.stringify(key)]
319
+ "refused":
320
+ "%s is keyed by %s, and %s is not one" % [name, wanted, JSON.stringify(key)],
302
321
  }
303
322
  var value: Variant = given[key]
304
323
  if whole_values and value is float:
@@ -306,7 +325,7 @@ static func _keyed_as_option(name: String, given: Dictionary, held: Variant, imp
306
325
  if number == floorf(number):
307
326
  value = int(number)
308
327
  keyed[as_key] = value
309
- return {"value": keyed}
328
+ return { "value": keyed }
310
329
 
311
330
 
312
331
  ## [param key] as an int key, or null when it is not one: a whole number, written as a number or as
@@ -367,7 +386,7 @@ func _import_status_of(resource_path: String, import_file_path: String) -> Dicti
367
386
  "path": resource_path,
368
387
  "status": "missing_source",
369
388
  "import_file_exists": FileAccess.file_exists(import_file_path),
370
- "source_exists": false
389
+ "source_exists": false,
371
390
  }
372
391
 
373
392
  var import_file_exists: bool = FileAccess.file_exists(import_file_path)
@@ -381,17 +400,21 @@ func _import_status_of(resource_path: String, import_file_path: String) -> Dicti
381
400
  "status": "needs_reimport" if never.is_empty() else "not_imported",
382
401
  "reason": "it has not been imported" if never.is_empty() else never,
383
402
  "import_file_exists": false,
384
- "source_exists": true
403
+ "source_exists": true,
385
404
  }
386
405
 
387
406
  var status: Dictionary = {
388
- "path": resource_path, "status": "up_to_date", "import_file_exists": true, "source_exists": true
407
+ "path": resource_path,
408
+ "status": "up_to_date",
409
+ "import_file_exists": true,
410
+ "source_exists": true,
389
411
  }
390
412
 
391
- # The checks are the ones EditorFileSystem::_test_for_reimport makes, in its order, so a resource
392
- # reads as needing an import when the editor's next scan would import it. The importer's own two
393
- # cannot be asked here, where no importer is loaded, so they are made from what was measured of
394
- # the built-in ones; an importer a plugin adds is judged by its files alone.
413
+ # The checks are the ones EditorFileSystem::_test_for_reimport makes, in its order, so a
414
+ # resource reads as needing an import when the editor's next scan would import it. The
415
+ # importer's own two cannot be asked here, where no importer is loaded, so they are made from
416
+ # what was measured of the built-in ones; an importer a plugin adds is judged by its files
417
+ # alone.
395
418
  var sidecar: ConfigFile = ConfigFile.new()
396
419
  var stale: Dictionary = (
397
420
  _stale("its import file cannot be read")
@@ -408,7 +431,9 @@ func _import_status_of(resource_path: String, import_file_path: String) -> Dicti
408
431
  # Why [param resource_path] has no import file, with what to do about it where anything can be done.
409
432
  func _no_sidecar(resource_path: String) -> String:
410
433
  if not FileAccess.file_exists(resource_path):
411
- return GdhFileWalk.new().with_nearest_files(resource_path + " is not on disk", resource_path)
434
+ return GdhFileWalk.new().with_nearest_files(
435
+ resource_path + " is not on disk", resource_path
436
+ )
412
437
  var never: String = _never_imported(resource_path)
413
438
  if not never.is_empty():
414
439
  return resource_path + " has no import options: " + never
@@ -428,10 +453,14 @@ static func never_imported_line(paths: Array[String]) -> String:
428
453
  if paths.size() == 1:
429
454
  return (
430
455
  named
431
- + " has never been imported, so nothing can load it until project_import reimport imports it"
456
+ + " has never been imported, so nothing can load it until project_import reimport "
457
+ + "imports it"
432
458
  )
433
459
  return (
434
- "%d files have never been imported, so nothing can load them until project_import reimport imports them: %s"
460
+ (
461
+ "%d files have never been imported, so nothing can load them until project_import "
462
+ + "reimport imports them: %s"
463
+ )
435
464
  % [paths.size(), named]
436
465
  )
437
466
 
@@ -450,14 +479,18 @@ func _never_imported(resource_path: String) -> String:
450
479
  return ""
451
480
  if extension in BLENDER_EXTENSIONS:
452
481
  if not ProjectSettings.get_setting(BLENDER_ENABLED, true):
453
- return "the project sets " + BLENDER_ENABLED + " to false, so the engine leaves them unimported"
482
+ return (
483
+ "the project sets "
484
+ + BLENDER_ENABLED
485
+ + " to false, so the engine leaves them unimported"
486
+ )
454
487
  return (
455
488
  "the engine imports ."
456
489
  + extension
457
490
  + " files through Blender, which it finds through the editor setting "
458
491
  + BLENDER_SETTING
459
- + ". An editor with it set imports them on its scan; an import pass with it unset stops at"
460
- + " the first one and imports nothing, and setting "
492
+ + ". An editor with it set imports them on its scan; an import pass with it unset "
493
+ + "stops at the first one and imports nothing, and setting "
461
494
  + BLENDER_ENABLED
462
495
  + " to false has the engine leave them unimported"
463
496
  )
@@ -467,17 +500,20 @@ func _never_imported(resource_path: String) -> String:
467
500
  return (
468
501
  "no importer built into the engine takes ."
469
502
  + extension
470
- + " files; one added by a plugin or an extension is known only to the editor, whose scan imports them"
503
+ + " files; one added by a plugin or an extension is known only to the editor, whose scan "
504
+ + "imports them"
471
505
  )
472
506
 
473
507
 
474
508
  static func _stale(reason: String) -> Dictionary:
475
- return {"status": "needs_reimport", "reason": reason}
509
+ return { "status": "needs_reimport", "reason": reason }
476
510
 
477
511
 
478
512
  ## What the editor reads off the sidecar before looking at the import: an answer for a stale or
479
513
  ## failed resource, empty otherwise.
480
- func _sidecar_staleness(resource_path: String, import_file_path: String, sidecar: ConfigFile) -> Dictionary:
514
+ func _sidecar_staleness(
515
+ resource_path: String, import_file_path: String, sidecar: ConfigFile
516
+ ) -> Dictionary:
481
517
  # The editor compares the sidecar with the one it last imported from before reading anything in
482
518
  # it, so an option set without a reimport is imported on its next scan, and not before.
483
519
  var imported_from: String = _imported_sidecar_md5s().get(resource_path, "")
@@ -490,7 +526,8 @@ func _sidecar_staleness(resource_path: String, import_file_path: String, sidecar
490
526
  if valid is bool and not valid:
491
527
  return {
492
528
  "status": "failed",
493
- "reason": "its last import failed, and the editor does not try a failed import again on its own",
529
+ "reason":
530
+ "its last import failed, and the editor does not try a failed import again on its own",
494
531
  }
495
532
  return {}
496
533
 
@@ -509,14 +546,21 @@ static func _import_staleness(resource_path: String, sidecar: ConfigFile) -> Dic
509
546
  if current > version:
510
547
  return _stale(
511
548
  (
512
- "it was imported at version %d of the %s importer, and this engine imports at version %d"
549
+ (
550
+ "it was imported at version %d of the %s importer, and this engine imports at "
551
+ + "version %d"
552
+ )
513
553
  % [version, importer, current]
514
554
  )
515
555
  )
516
556
  var lacking: Array[String] = _vram_formats_lacking(sidecar.get_value("remap", "metadata", {}))
517
557
  if not lacking.is_empty():
518
558
  return _stale(
519
- "the project now asks for " + ", ".join(lacking) + " textures, and this import did not write them"
559
+ (
560
+ "the project now asks for "
561
+ + ", ".join(lacking)
562
+ + " textures, and this import did not write them"
563
+ )
520
564
  )
521
565
 
522
566
  if not sidecar.has_section_key("remap", "uid"):
@@ -540,8 +584,8 @@ static func _import_staleness(resource_path: String, sidecar: ConfigFile) -> Dic
540
584
  return _stale("its import file was written for " + source_file)
541
585
 
542
586
  # By content, as the editor judges it: the hash of the source the import recorded, against the
543
- # file. By time, an installer that rewrote 31 images byte for byte made every one read as changed,
544
- # and the editor, which compares the hash, rightly imported none of them.
587
+ # file. By time, an installer that rewrote 31 images byte for byte made every one read as
588
+ # changed, and the editor, which compares the hash, rightly imported none of them.
545
589
  var recorded: Dictionary = _recorded_md5s(resource_path)
546
590
  var recorded_source: String = recorded.get("source_md5", "")
547
591
  if recorded_source.is_empty():
@@ -562,11 +606,11 @@ static func _import_staleness(resource_path: String, sidecar: ConfigFile) -> Dic
562
606
  # file times: a texture a 3D scene uses is reimported compressed after the scene, so its outputs
563
607
  # are always the newer, and 33 correct scenes read as stale when judged that way.
564
608
  #
565
- # Followed through the resources the scene depends on, since a post-import script that swaps each
566
- # material for a saved one leaves the scene depending on the material and only the material on
567
- # the image: 211 correct scenes read as stale when only the scene's own list was read. And not
568
- # held against a scene an import script shaped at all, since the script may replace a material
569
- # with one using other images entirely, which is the scene it was meant to build.
609
+ # Followed through the resources the scene depends on, since a post-import script that swaps
610
+ # each material for a saved one leaves the scene depending on the material and only the material
611
+ # on the image: 211 correct scenes read as stale when only the scene's own list was read. And
612
+ # not held against a scene an import script shaped at all, since the script may replace a
613
+ # material with one using other images entirely, which is the scene it was meant to build.
570
614
  var without: Array[String] = []
571
615
  var images: Array[String] = _images_of(resource_path)
572
616
  if not images.is_empty() and not _shaped_by_a_script(sidecar):
@@ -576,15 +620,17 @@ static func _import_staleness(resource_path: String, sidecar: ConfigFile) -> Dic
576
620
  without.append(image)
577
621
  if without.is_empty():
578
622
  return {}
579
- var built: Dictionary = _stale("it was imported without images it uses, which were not imported yet")
623
+ var built: Dictionary = _stale(
624
+ "it was imported without images it uses, which were not imported yet"
625
+ )
580
626
  built["imported_without"] = without
581
627
  return built
582
628
 
583
629
 
584
630
  ## The VRAM formats the project asks textures to be imported in that an import recorded in
585
631
  ## [param metadata] did not write, as the texture importers judge whether their settings still hold.
586
- ## Measured on 4.7.2 for the texture and texture array importers: turning a format on reimports every
587
- ## VRAM texture without it, and turning one off reimports nothing.
632
+ ## Measured on 4.7.2 for the texture and texture array importers: turning a format on reimports
633
+ ## every VRAM texture without it, and turning one off reimports nothing.
588
634
  static func _vram_formats_lacking(metadata: Variant) -> Array[String]:
589
635
  var lacking: Array[String] = []
590
636
  if not metadata is Dictionary:
@@ -596,7 +642,9 @@ static func _vram_formats_lacking(metadata: Variant) -> Array[String]:
596
642
  var formats: Array = written if written is Array else []
597
643
  for format: String in VRAM_FORMATS:
598
644
  var asked: bool = GdhRead.as_bool(
599
- ProjectSettings.get_setting("rendering/textures/vram_compression/import_" + format, false)
645
+ ProjectSettings.get_setting(
646
+ "rendering/textures/vram_compression/import_" + format, false
647
+ )
600
648
  )
601
649
  if asked and format not in formats:
602
650
  lacking.append(format)
@@ -684,7 +732,10 @@ static func _editor_cache_path() -> String:
684
732
  if not DirAccess.dir_exists_absolute(EDITOR_DIRECTORY):
685
733
  return ""
686
734
  for name: String in DirAccess.get_files_at(EDITOR_DIRECTORY):
687
- if name.begins_with(EDITOR_CACHE_PREFIX) and name.trim_prefix(EDITOR_CACHE_PREFIX).is_valid_int():
735
+ if (
736
+ name.begins_with(EDITOR_CACHE_PREFIX)
737
+ and name.trim_prefix(EDITOR_CACHE_PREFIX).is_valid_int()
738
+ ):
688
739
  newest = maxi(newest, name.trim_prefix(EDITOR_CACHE_PREFIX).to_int())
689
740
  return "" if newest < 0 else EDITOR_DIRECTORY.path_join(EDITOR_CACHE_PREFIX + str(newest))
690
741
 
@@ -738,7 +789,7 @@ static func _images_of(scene_path: String) -> Array[String]:
738
789
  "glb":
739
790
  # A 12-byte header, then the first chunk: its length, its type, and for "JSON" the text.
740
791
  var bytes: PackedByteArray = FileAccess.get_file_as_bytes(scene_path)
741
- if bytes.size() >= 20 and bytes.decode_u32(16) == 0x4E4F534A:
792
+ if bytes.size() >= 20 and bytes.decode_u32(16) == 0x4e4f534a:
742
793
  var length: int = bytes.decode_u32(12)
743
794
  text = bytes.slice(20, mini(20 + length, bytes.size())).get_string_from_utf8()
744
795
  if text.is_empty():
@@ -14,7 +14,8 @@ const READS_AN_ACTION: String = (
14
14
  const ADDS_AN_ACTION: String = "\\badd_action\\("
15
15
  ## An argument that is a name written in the call and nothing else, a String or a StringName.
16
16
  const NAME_ARGUMENT: String = "^&?(?:\"([^\"\\\\]*)\"|'([^'\\\\]*)')$"
17
- ## The device an event matches every device with, InputMap's ALL_DEVICES, which GDScript is not given.
17
+ ## The device an event matches every device with, InputMap's ALL_DEVICES, which GDScript is not
18
+ ## given.
18
19
  const ALL_DEVICES: int = -1
19
20
  const MOUSE_NAMES: String = (
20
21
  "1 left, 2 right, 3 middle, 4 and 5 the wheel up and down, 6 and 7 the wheel left and right,"
@@ -23,7 +24,6 @@ const MOUSE_NAMES: String = (
23
24
  const AXIS_NAMES: String = (
24
25
  "0 and 1 the left stick across and down, 2 and 3 the right stick," + " 4 and 5 the triggers"
25
26
  )
26
-
27
27
  # The key names a caller may write, and what the engine calls them.
28
28
  const KEY_NAMES: Dictionary = {
29
29
  "A": KEY_A,
@@ -155,7 +155,7 @@ func undefined_in_scripts() -> Array[Dictionary]:
155
155
  for name: String in names:
156
156
  if ProjectSettings.has_setting("input/" + name) or added.has(name):
157
157
  continue
158
- undefined.append({"action": name, "read_at": read[name]})
158
+ undefined.append({ "action": name, "read_at": read[name] })
159
159
  return undefined
160
160
 
161
161
 
@@ -198,7 +198,8 @@ static func _names_given(code: String, from: int) -> Array[String]:
198
198
  return names
199
199
 
200
200
 
201
- ## Whether [param at] in [param code] is inside a string literal, where a call is text rather than one.
201
+ ## Whether [param at] in [param code] is inside a string literal, where a call is text rather than
202
+ ## one.
202
203
  static func _in_a_string(code: String, at: int) -> bool:
203
204
  var quote: String = ""
204
205
  for index: int in range(at):
@@ -223,7 +224,9 @@ func add_input_action(params: Dictionary) -> Dictionary:
223
224
  var action_name: String = str(params.get("action_name", ""))
224
225
  var events: Array = params.get("events", [])
225
226
  var setting: String = "input/" + action_name
226
- var held: Variant = ProjectSettings.get_setting(setting) if ProjectSettings.has_setting(setting) else null
227
+ var held: Variant = (
228
+ ProjectSettings.get_setting(setting) if ProjectSettings.has_setting(setting) else null
229
+ )
227
230
  var old: Dictionary = held if held is Dictionary else {}
228
231
  var old_deadzone: float = GdhRead.as_float(old.get("deadzone", 0.5), 0.5)
229
232
  # A rebind that names no deadzone keeps the one the action has, which a caller adding a key did
@@ -242,8 +245,9 @@ func add_input_action(params: Dictionary) -> Dictionary:
242
245
  . failure(
243
246
  (
244
247
  (
245
- "%s is not an action name the engine's Input Map takes: a name cannot hold / : = \\ "
246
- + "or a double quote, or a control character. jump, move_left or ui_menu are names it takes."
248
+ "%s is not an action name the engine's Input Map takes: a name cannot "
249
+ + "hold / : = \\ or a double quote, or a control character. jump, "
250
+ + "move_left or ui_menu are names it takes."
247
251
  )
248
252
  % JSON.stringify(action_name)
249
253
  )
@@ -280,6 +284,7 @@ func add_input_action(params: Dictionary) -> Dictionary:
280
284
  "deadzone": deadzone,
281
285
  "events": events_config,
282
286
  "was_new": not ProjectSettings.has_setting(setting),
287
+ "saved": true,
283
288
  }
284
289
  if not answer["was_new"]:
285
290
  var old_events: Array[Dictionary] = []
@@ -306,7 +311,7 @@ func add_input_action(params: Dictionary) -> Dictionary:
306
311
  ## [param event] in the form add_input_action answers its events with, and as the engine names it
307
312
  ## under text, which also covers a kind of event that form has no fields for.
308
313
  static func _shaped(event: InputEvent) -> Dictionary:
309
- var shaped: Dictionary = {"class_name": event.get_class(), "text": event.as_text()}
314
+ var shaped: Dictionary = { "class_name": event.get_class(), "text": event.as_text() }
310
315
  if event.device != ALL_DEVICES:
311
316
  shaped["device"] = event.device
312
317
  if event is InputEventKey:
@@ -364,7 +369,7 @@ func list_input_actions(_params: Dictionary) -> Dictionary:
364
369
  }
365
370
  )
366
371
  )
367
- return {"actions": actions, "count": actions.size()}
372
+ return { "actions": actions, "count": actions.size() }
368
373
 
369
374
 
370
375
  # Remove an input action the project defines, which add_input_action had no way back from.
@@ -382,30 +387,37 @@ func remove_input_action(params: Dictionary) -> Dictionary:
382
387
  # At the engine's binding the file holds no line for it, and the refusal for a rebound one
383
388
  # spoke of a binding the project had never changed.
384
389
  if ProjectSettings.get_setting(setting) == engine_binding:
385
- return _log.failure(
386
- (
390
+ return (
391
+ _log
392
+ . failure(
387
393
  (
388
- "%s is one of the engine's own actions, and project.godot does not rebind it, so "
389
- + "there is nothing to remove: the engine's binding holds. Rebind it with "
390
- + "add_input_action."
394
+ (
395
+ "%s is one of the engine's own actions, and project.godot does not "
396
+ + "rebind it, so there is nothing to remove: the engine's binding "
397
+ + "holds. Rebind it with add_input_action."
398
+ )
399
+ % action_name
391
400
  )
392
- % action_name
393
401
  )
394
402
  )
395
- return _log.failure(
396
- (
403
+ return (
404
+ _log
405
+ . failure(
397
406
  (
398
- "%s is one of the engine's own actions, so it cannot be removed. project_settings "
399
- + "unset %s puts the engine's binding back, and add_input_action rebinds it."
407
+ (
408
+ "%s is one of the engine's own actions, so it cannot be removed. "
409
+ + "project_settings unset %s puts the engine's binding back, and "
410
+ + "add_input_action rebinds it."
411
+ )
412
+ % [action_name, setting]
400
413
  )
401
- % [action_name, setting]
402
414
  )
403
415
  )
404
416
  ProjectSettings.set_setting(setting, null)
405
417
  var err: Error = GdhProjectFile.save([setting])
406
418
  if err != OK:
407
419
  return _log.failure("Failed to save project.godot: " + error_string(err))
408
- return {"action_name": action_name, "removed": true}
420
+ return { "action_name": action_name, "removed": true }
409
421
 
410
422
 
411
423
  # The value project.godot stores for one input action.
@@ -458,13 +470,13 @@ func build_input_action(deadzone: float, events: Array) -> Dictionary:
458
470
  var bound: InputEvent = built.back()
459
471
  bound.device = ALL_DEVICES
460
472
 
461
- return {"deadzone": deadzone, "events": built}
473
+ return { "deadzone": deadzone, "events": built }
462
474
 
463
475
 
464
476
  # The keycode for a key name as the tool spells it, or KEY_NONE for a name the table lacks.
465
- ## The refusal for [param key_name], which names no key: the names nearest it, then every name taken,
466
- ## since a name the caller has to guess at is the same guess again.
467
- ## The event [param fields] describe, as [method build_input_action] takes it, or why it is no event.
477
+ ## The refusal for [param key_name], which names no key: the names nearest it, then every name
478
+ ## taken, since a name the caller has to guess at is the same guess again. The event [param fields]
479
+ ## describe, as [method build_input_action] takes it, or why it is no event.
468
480
  func _event_config(fields: Dictionary) -> Variant:
469
481
  var event_type: String = str(fields.get("type", ""))
470
482
  match event_type:
@@ -475,7 +487,7 @@ func _event_config(fields: Dictionary) -> Variant:
475
487
  var key_value: int = _keycode_value(keycode)
476
488
  if key_value == KEY_NONE:
477
489
  return _unknown_key(keycode)
478
- var event_config: Dictionary = {"class_name": "InputEventKey"}
490
+ var event_config: Dictionary = { "class_name": "InputEventKey" }
479
491
  var physical: bool = GdhRead.as_bool(fields.get("physical", false))
480
492
  event_config["physical_keycode" if physical else "keycode"] = key_value
481
493
  for modifier: String in ["ctrl", "alt", "shift"]:
@@ -488,46 +500,55 @@ func _event_config(fields: Dictionary) -> Variant:
488
500
  )
489
501
  if mouse is String:
490
502
  return "A mouse_button event " + str(mouse)
491
- return {"class_name": "InputEventMouseButton", "button_index": mouse}
503
+ return { "class_name": "InputEventMouseButton", "button_index": mouse }
492
504
  "joypad_button":
493
- var pad: Variant = _whole_in(fields, "button", 0, JOY_BUTTON_MAX - 1, "0 is A, 1 B, 2 X, 3 Y")
505
+ var pad: Variant = _whole_in(
506
+ fields, "button", 0, JOY_BUTTON_MAX - 1, "0 is A, 1 B, 2 X, 3 Y"
507
+ )
494
508
  if pad is String:
495
509
  return "A joypad_button event " + str(pad)
496
- return {"class_name": "InputEventJoypadButton", "button_index": pad}
510
+ return { "class_name": "InputEventJoypadButton", "button_index": pad }
497
511
  "joypad_axis":
498
512
  var axis: Variant = _whole_in(fields, "axis", 0, JOY_AXIS_MAX - 1, AXIS_NAMES)
499
513
  if axis is String:
500
514
  return "A joypad_axis event " + str(axis)
501
- # The direction is half the binding: left on a stick and right on it are two actions, and the
502
- # editor binds only a whole push one way, so a guessed direction or part of one is a binding
503
- # nobody asked for.
515
+ # The direction is half the binding: left on a stick and right on it are two actions,
516
+ # and the editor binds only a whole push one way, so a guessed direction or part of one
517
+ # is a binding nobody asked for.
504
518
  var direction: Variant = fields.get("axisValue")
505
519
  var numeric: bool = direction is int or direction is float
506
520
  if not numeric or absf(GdhRead.as_float(direction)) != 1.0:
507
521
  return (
508
- "A joypad_axis event needs axisValue, -1 or 1, the way the axis is pushed: %s was given."
522
+ (
523
+ "A joypad_axis event needs axisValue, -1 or 1, the way the axis is pushed: "
524
+ + "%s was given."
525
+ )
509
526
  % _shown(direction)
510
527
  )
511
528
  return {
512
529
  "class_name": "InputEventJoypadMotion",
513
530
  "axis": axis,
514
- "axis_value": GdhRead.as_float(direction)
531
+ "axis_value": GdhRead.as_float(direction),
515
532
  }
516
533
  var known: String = "key, mouse_button, joypad_button, joypad_axis"
517
534
  return "Unknown event type: " + event_type + ". One of " + known + "."
518
535
 
519
536
 
520
537
  ## The whole number [param fields] holds under [param key], from [param least] to [param most], or
521
- ## the rest of a refusal saying what it takes and what [param named] each number is. A field left out
522
- ## was given a default, a left click for a mouse event and axis 0 for a stick, and written as bound.
523
- static func _whole_in(fields: Dictionary, key: String, least: int, most: int, named: String) -> Variant:
538
+ ## the rest of a refusal saying what it takes and what [param named] each number is. A field left
539
+ ## out was given a default, a left click for a mouse event and axis 0 for a stick, and written as
540
+ ## bound.
541
+ static func _whole_in(
542
+ fields: Dictionary, key: String, least: int, most: int, named: String
543
+ ) -> Variant:
524
544
  var held: Variant = fields.get(key)
525
545
  if held is int or held is float:
526
546
  var number: float = GdhRead.as_float(held)
527
547
  if number == floorf(number) and number >= least and number <= most:
528
548
  return int(number)
529
549
  return (
530
- "needs %s, a whole number from %d to %d (%s): %s was given." % [key, least, most, named, _shown(held)]
550
+ "needs %s, a whole number from %d to %d (%s): %s was given."
551
+ % [key, least, most, named, _shown(held)]
531
552
  )
532
553
 
533
554
 
@@ -555,7 +576,10 @@ static func _unknown_key(key_name: String) -> String:
555
576
  said += " The nearest key name is %s." % nearest[0]
556
577
  elif not nearest.is_empty():
557
578
  said += " The nearest key names are %s." % ", ".join(nearest)
558
- return said + " A key is named by its letter, its digit, F1 to F12, or one of: %s." % ", ".join(named)
579
+ return (
580
+ said
581
+ + " A key is named by its letter, its digit, F1 to F12, or one of: %s." % ", ".join(named)
582
+ )
559
583
 
560
584
 
561
585
  func _keycode_value(key_name: String) -> int:
@@ -41,7 +41,9 @@ static func compiled(expression: String) -> RegEx:
41
41
  # `class_name X extends Y` is one line, so a reader that takes the rest of the line as the name gets
42
42
  # `Blade extends Node2D` for the class and never sees the base at all.
43
43
  static func declared_class(line: String) -> RegExMatch:
44
- return compiled("^class_name\\s+([A-Za-z_][A-Za-z0-9_]*)(?:\\s+extends\\s+(\\S+))?").search(line)
44
+ return compiled("^class_name\\s+([A-Za-z_][A-Za-z0-9_]*)(?:\\s+extends\\s+(\\S+))?").search(
45
+ line
46
+ )
45
47
 
46
48
 
47
49
  # The annotations at the front of a line, in order, written as they are without the leading `@`.