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.
- package/build/cli.js +131 -47
- package/build/godot/addons/auto_reload/auto_reload.gd +18 -19
- package/build/godot/addons/gdharness_editor/animation_reads.gd +84 -35
- 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 +114 -39
- package/build/godot/addons/gdharness_editor/file_walk.gd +12 -5
- package/build/godot/addons/gdharness_editor/headless_check.gd +94 -41
- 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 +37 -12
- package/build/godot/addons/gdharness_editor/same_values.gd +4 -4
- package/build/godot/addons/gdharness_editor/scene_file.gd +136 -79
- 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 +41 -28
- package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +84 -50
- package/build/godot/addons/gdharness_editor/tools/animation_tracks.gd +70 -40
- package/build/godot/addons/gdharness_editor/tools/class_tools.gd +34 -30
- 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 +181 -107
- package/build/godot/addons/gdharness_editor/tools/scene_groups.gd +16 -9
- package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +75 -43
- 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 +66 -0
- package/build/godot/addons/gdharness_editor/tools/scene_sharing.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +171 -62
- 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 +37 -12
- package/build/godot/addons/gdharness_runtime/runtime_accessibility.gd +84 -63
- package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +153 -148
- 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 +38 -20
- 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 +211 -0
- package/build/godot/addons/gdharness_runtime/runtime_touch.gd.uid +1 -0
- 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 +84 -35
- 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 +139 -93
- package/build/godot/operations/editor_tools.gd +9 -10
- package/build/godot/operations/export_presets.gd +100 -54
- package/build/godot/operations/file_reads.gd +114 -39
- package/build/godot/operations/file_walk.gd +12 -5
- package/build/godot/operations/gdscript_analysis.gd +105 -66
- package/build/godot/operations/gdscript_authoring.gd +138 -39
- 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 +63 -39
- package/build/godot/operations/patterns.gd +3 -1
- package/build/godot/operations/plugins.gd +36 -16
- package/build/godot/operations/project_config.gd +144 -27
- 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 +37 -12
- 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/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
|
|
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
|
|
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,
|
|
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
|
|
95
|
-
# engine ever imported, and answering it as a lost source sent a caller to restore a
|
|
96
|
-
|
|
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
|
|
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
|
|
130
|
-
+ " the like, found by extension or by a .import sidecar beside them, and loads
|
|
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,
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
|
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":
|
|
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,
|
|
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
|
|
392
|
-
# reads as needing an import when the editor's next scan would import it. The
|
|
393
|
-
# cannot be asked here, where no importer is loaded, so they are made from
|
|
394
|
-
# the built-in ones; an importer a plugin adds is judged by its files
|
|
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(
|
|
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
|
|
456
|
+
+ " has never been imported, so nothing can load it until project_import reimport "
|
|
457
|
+
+ "imports it"
|
|
432
458
|
)
|
|
433
459
|
return (
|
|
434
|
-
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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":
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
566
|
-
# material for a saved one leaves the scene depending on the material and only the material
|
|
567
|
-
# the image: 211 correct scenes read as stale when only the scene's own list was read. And
|
|
568
|
-
# held against a scene an import script shaped at all, since the script may replace a
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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) ==
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
246
|
-
+ "or a double quote, or a control character. jump,
|
|
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
|
|
386
|
-
|
|
390
|
+
return (
|
|
391
|
+
_log
|
|
392
|
+
. failure(
|
|
387
393
|
(
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
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
|
|
396
|
-
|
|
403
|
+
return (
|
|
404
|
+
_log
|
|
405
|
+
. failure(
|
|
397
406
|
(
|
|
398
|
-
|
|
399
|
-
|
|
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
|
|
466
|
-
## since a name the caller has to guess at is the same guess again.
|
|
467
|
-
##
|
|
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(
|
|
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,
|
|
502
|
-
# editor binds only a whole push one way, so a guessed direction or part of one
|
|
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
|
-
|
|
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
|
|
522
|
-
## was given a default, a left click for a mouse event and axis 0 for a stick, and written as
|
|
523
|
-
|
|
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."
|
|
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
|
|
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(
|
|
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 `@`.
|