Compare commits
3
Commits
a02116a206
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b88246f111 | ||
|
|
3c082cca0c | ||
|
|
66dead0145 |
@@ -1,3 +1,4 @@
|
|||||||
# Godot 4+ specific ignores
|
# Godot 4+ specific ignores
|
||||||
.godot/
|
.godot/
|
||||||
/android/
|
/android/
|
||||||
|
/output
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2025-present GDQuest
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
@@ -1,240 +0,0 @@
|
|||||||
@tool
|
|
||||||
extends Node
|
|
||||||
|
|
||||||
enum HttpRequestState {
|
|
||||||
IDLE,
|
|
||||||
FETCHING_RELEASE_INFO,
|
|
||||||
DOWNLOADING_BINARY,
|
|
||||||
}
|
|
||||||
|
|
||||||
const URL_GITHUB_API_LATEST_RELEASE = "https://api.github.com/repos/gdquest/GDScript-formatter/releases/latest"
|
|
||||||
signal installation_completed(binary_path: String)
|
|
||||||
signal installation_failed(error_message: String)
|
|
||||||
|
|
||||||
var http_request_state := HttpRequestState.IDLE
|
|
||||||
var http_request: HTTPRequest = HTTPRequest.new()
|
|
||||||
var formatter_cache_dir: String
|
|
||||||
|
|
||||||
|
|
||||||
func _init(cache_dir: String) -> void:
|
|
||||||
formatter_cache_dir = cache_dir
|
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
|
||||||
add_child(http_request)
|
|
||||||
http_request.request_completed.connect(_on_request_completed)
|
|
||||||
|
|
||||||
|
|
||||||
func install_or_update_formatter() -> void:
|
|
||||||
print("Starting GDScript formatter installation...")
|
|
||||||
http_request_state = HttpRequestState.FETCHING_RELEASE_INFO
|
|
||||||
http_request.request(URL_GITHUB_API_LATEST_RELEASE)
|
|
||||||
|
|
||||||
|
|
||||||
func _on_request_completed(
|
|
||||||
_http_result: int,
|
|
||||||
response_code: int,
|
|
||||||
_http_headers: PackedStringArray,
|
|
||||||
body: PackedByteArray,
|
|
||||||
) -> void:
|
|
||||||
if response_code != 200:
|
|
||||||
var error_message := "HTTP request failed. Response code: " + str(response_code)
|
|
||||||
push_error(error_message)
|
|
||||||
http_request_state = HttpRequestState.IDLE
|
|
||||||
installation_failed.emit(error_message)
|
|
||||||
return
|
|
||||||
|
|
||||||
match http_request_state:
|
|
||||||
HttpRequestState.FETCHING_RELEASE_INFO:
|
|
||||||
_process_response_latest_release(body)
|
|
||||||
HttpRequestState.DOWNLOADING_BINARY:
|
|
||||||
_process_response_download_file(body)
|
|
||||||
_:
|
|
||||||
var error_message := "Unexpected HTTP request state: " + str(http_request_state)
|
|
||||||
push_error(error_message)
|
|
||||||
http_request_state = HttpRequestState.IDLE
|
|
||||||
installation_failed.emit(error_message)
|
|
||||||
|
|
||||||
|
|
||||||
func _process_response_latest_release(body: PackedByteArray) -> void:
|
|
||||||
var json = JSON.parse_string(body.get_string_from_utf8())
|
|
||||||
if not json or not json.has("assets"):
|
|
||||||
var error_message := "Failed to parse release information from GitHub API"
|
|
||||||
push_error(error_message)
|
|
||||||
http_request_state = HttpRequestState.IDLE
|
|
||||||
installation_failed.emit(error_message)
|
|
||||||
return
|
|
||||||
|
|
||||||
print("GDScript Formatter release information loaded successfully")
|
|
||||||
|
|
||||||
var assets = json["assets"]
|
|
||||||
var tag = json["tag_name"]
|
|
||||||
var download_url := _find_matching_asset(assets, tag)
|
|
||||||
|
|
||||||
if download_url.is_empty():
|
|
||||||
var error_message := "No matching binary found for current platform"
|
|
||||||
push_error(error_message)
|
|
||||||
http_request_state = HttpRequestState.IDLE
|
|
||||||
installation_failed.emit(error_message)
|
|
||||||
return
|
|
||||||
|
|
||||||
print("Found compatible release, starting download...\n", "Download URL: ", download_url)
|
|
||||||
|
|
||||||
http_request_state = HttpRequestState.DOWNLOADING_BINARY
|
|
||||||
http_request.request(download_url)
|
|
||||||
|
|
||||||
|
|
||||||
func _process_response_download_file(body: PackedByteArray) -> void:
|
|
||||||
http_request_state = HttpRequestState.IDLE
|
|
||||||
|
|
||||||
print("Download completed successfully (", body.size(), " bytes)")
|
|
||||||
|
|
||||||
var asset_info := _get_platform_info()
|
|
||||||
if asset_info.is_empty():
|
|
||||||
var error_message := "Failed to determine platform information"
|
|
||||||
push_error(error_message)
|
|
||||||
installation_failed.emit(error_message)
|
|
||||||
return
|
|
||||||
|
|
||||||
print("Extracting and installing binary...")
|
|
||||||
var binary_path := _download_and_install_binary(body, asset_info)
|
|
||||||
if binary_path.is_empty():
|
|
||||||
var error_message := "Installation failed"
|
|
||||||
push_error(error_message)
|
|
||||||
installation_failed.emit(error_message)
|
|
||||||
return
|
|
||||||
|
|
||||||
print(
|
|
||||||
"\n".join(["GDScript formatter installed successfully!", "Binary location: " + binary_path]),
|
|
||||||
)
|
|
||||||
installation_completed.emit(binary_path)
|
|
||||||
|
|
||||||
|
|
||||||
func _get_platform_info() -> Dictionary:
|
|
||||||
var os_name := OS.get_name().to_lower()
|
|
||||||
var processor_name := OS.get_processor_name().to_lower()
|
|
||||||
var architecture := "x86_64"
|
|
||||||
|
|
||||||
if (
|
|
||||||
processor_name.contains("aarch64") or processor_name.contains("arm64")
|
|
||||||
or processor_name.contains("apple")
|
|
||||||
):
|
|
||||||
architecture = "aarch64"
|
|
||||||
elif processor_name.contains("x86_64") or processor_name.contains("amd64"):
|
|
||||||
architecture = "x86_64"
|
|
||||||
|
|
||||||
var binary_name := "gdscript-formatter"
|
|
||||||
if os_name.contains("windows"):
|
|
||||||
binary_name = "gdscript-formatter.exe"
|
|
||||||
|
|
||||||
var platform_info := { "architecture": architecture, "binary_name": binary_name }
|
|
||||||
|
|
||||||
if os_name.contains("windows"):
|
|
||||||
platform_info["os"] = "windows"
|
|
||||||
elif os_name.contains("linux"):
|
|
||||||
platform_info["os"] = "linux"
|
|
||||||
elif os_name.contains("macos") or os_name.contains("osx"):
|
|
||||||
platform_info["os"] = "macos"
|
|
||||||
|
|
||||||
return platform_info
|
|
||||||
|
|
||||||
|
|
||||||
func _find_matching_asset(assets: Array, tag: String) -> String:
|
|
||||||
var platform_info := _get_platform_info()
|
|
||||||
if platform_info.is_empty():
|
|
||||||
return ""
|
|
||||||
|
|
||||||
var expected_pattern := "gdscript-formatter-%s-%s-%s" % [
|
|
||||||
tag,
|
|
||||||
platform_info["os"],
|
|
||||||
platform_info["architecture"],
|
|
||||||
]
|
|
||||||
if platform_info["os"] == "windows":
|
|
||||||
expected_pattern += ".exe"
|
|
||||||
expected_pattern += ".zip"
|
|
||||||
|
|
||||||
for asset in assets:
|
|
||||||
var asset_name: String = asset["name"]
|
|
||||||
if asset_name == expected_pattern:
|
|
||||||
return asset["browser_download_url"]
|
|
||||||
|
|
||||||
return ""
|
|
||||||
|
|
||||||
|
|
||||||
func _download_and_install_binary(zip_data: PackedByteArray, platform_info: Dictionary) -> String:
|
|
||||||
var binary_name: String = platform_info["binary_name"]
|
|
||||||
# We get the contents of the zip file as a byte array. we need to write
|
|
||||||
# it to a file to be able to read it. We use a temporary file for that.
|
|
||||||
var temp_archive_path := formatter_cache_dir.path_join("temp_formatter_archive.zip")
|
|
||||||
|
|
||||||
print("Installing to cache directory: ", formatter_cache_dir)
|
|
||||||
|
|
||||||
# Create the gdquest cache directory if it doesn't exist
|
|
||||||
if not DirAccess.dir_exists_absolute(formatter_cache_dir):
|
|
||||||
var dir_result := DirAccess.make_dir_recursive_absolute(formatter_cache_dir)
|
|
||||||
if dir_result != OK:
|
|
||||||
push_error("Failed to create cache directory: ", formatter_cache_dir)
|
|
||||||
return ""
|
|
||||||
|
|
||||||
var temp_file := FileAccess.open(temp_archive_path, FileAccess.WRITE)
|
|
||||||
if not temp_file:
|
|
||||||
push_error("Failed to create temporary archive file")
|
|
||||||
return ""
|
|
||||||
|
|
||||||
temp_file.store_buffer(zip_data)
|
|
||||||
temp_file.close()
|
|
||||||
|
|
||||||
var zip_reader := ZIPReader.new()
|
|
||||||
var err := zip_reader.open(temp_archive_path)
|
|
||||||
if err != OK:
|
|
||||||
push_error("Failed to open ZIP archive from temporary file")
|
|
||||||
zip_reader.close()
|
|
||||||
DirAccess.remove_absolute(temp_archive_path)
|
|
||||||
return ""
|
|
||||||
|
|
||||||
var files := zip_reader.get_files()
|
|
||||||
var binary_data: PackedByteArray
|
|
||||||
var found_binary := false
|
|
||||||
var actual_binary_name := ""
|
|
||||||
|
|
||||||
for file_path in files:
|
|
||||||
if not file_path.ends_with("/"):
|
|
||||||
binary_data = zip_reader.read_file(file_path)
|
|
||||||
actual_binary_name = file_path.get_file()
|
|
||||||
found_binary = true
|
|
||||||
break
|
|
||||||
|
|
||||||
zip_reader.close()
|
|
||||||
|
|
||||||
DirAccess.remove_absolute(temp_archive_path)
|
|
||||||
if not found_binary:
|
|
||||||
push_error("No executable found in ZIP archive")
|
|
||||||
return ""
|
|
||||||
|
|
||||||
var binary_path := formatter_cache_dir.path_join(binary_name)
|
|
||||||
|
|
||||||
var file := FileAccess.open(binary_path, FileAccess.WRITE)
|
|
||||||
if not file:
|
|
||||||
push_error("Failed to create binary file at: ", binary_path)
|
|
||||||
return ""
|
|
||||||
|
|
||||||
file.store_buffer(binary_data)
|
|
||||||
file.close()
|
|
||||||
|
|
||||||
if not OS.get_name().to_lower().contains("windows"):
|
|
||||||
# This should be equivalent to setting the binary to permissions 755
|
|
||||||
const UNIX_EXECUTABLE_PERMISSIONS = (
|
|
||||||
FileAccess.UNIX_READ_OWNER | FileAccess.UNIX_WRITE_OWNER
|
|
||||||
| FileAccess.UNIX_EXECUTE_OWNER | FileAccess.UNIX_READ_GROUP
|
|
||||||
| FileAccess.UNIX_EXECUTE_GROUP | FileAccess.UNIX_READ_OTHER | FileAccess.UNIX_EXECUTE_OTHER
|
|
||||||
)
|
|
||||||
var permissions_error := FileAccess.set_unix_permissions(
|
|
||||||
binary_path,
|
|
||||||
UNIX_EXECUTABLE_PERMISSIONS,
|
|
||||||
)
|
|
||||||
if permissions_error != OK:
|
|
||||||
push_error("Failed to make formatter executable: ", binary_path)
|
|
||||||
DirAccess.remove_absolute(binary_path)
|
|
||||||
return ""
|
|
||||||
|
|
||||||
return binary_path
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
uid://ccblhowfwltqi
|
|
||||||
@@ -1,166 +0,0 @@
|
|||||||
## This module handles adding a menu to the script editor with formatter commands.
|
|
||||||
## It safely locates the script editor's menu bar and adds our custom menu.
|
|
||||||
@tool
|
|
||||||
extends Node
|
|
||||||
|
|
||||||
signal menu_item_selected(command: String)
|
|
||||||
|
|
||||||
const MENU_TEXT = "Format"
|
|
||||||
const MENU_ITEMS = {
|
|
||||||
"format_script": "Format Current Script",
|
|
||||||
"lint_script": "Lint Current Script",
|
|
||||||
"reorder_code": "Reorder Code",
|
|
||||||
"install_update": "Install or Update Formatter",
|
|
||||||
"uninstall": "Uninstall Formatter",
|
|
||||||
"report_issue": "Report Issue",
|
|
||||||
"help": "Help",
|
|
||||||
}
|
|
||||||
|
|
||||||
var menu_button: MenuButton = null
|
|
||||||
var popup_menu: PopupMenu = null
|
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
|
||||||
# At the start we insert the menu in the script editor
|
|
||||||
var script_editor := EditorInterface.get_script_editor()
|
|
||||||
var last_menu_button := _find_last_menu_button(script_editor)
|
|
||||||
if not is_instance_valid(last_menu_button):
|
|
||||||
push_warning(
|
|
||||||
"GDScript Formatter: Could not find valid menu button in script editor. Menu will not be available. Use the command palette instead."
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
menu_button = MenuButton.new()
|
|
||||||
menu_button.text = MENU_TEXT
|
|
||||||
menu_button.switch_on_hover = true
|
|
||||||
menu_button.flat = false
|
|
||||||
menu_button.theme_type_variation = &"FlatMenuButton"
|
|
||||||
|
|
||||||
popup_menu = menu_button.get_popup()
|
|
||||||
_populate_menu()
|
|
||||||
|
|
||||||
popup_menu.id_pressed.connect(_on_menu_item_pressed)
|
|
||||||
last_menu_button.add_sibling(menu_button)
|
|
||||||
|
|
||||||
|
|
||||||
## Cleans up the menu from the script editor. Call this when disabling the
|
|
||||||
## plugin (in _exit_tree()).
|
|
||||||
func remove_formatter_menu() -> void:
|
|
||||||
if is_instance_valid(menu_button):
|
|
||||||
if is_instance_valid(popup_menu):
|
|
||||||
popup_menu.id_pressed.disconnect(_on_menu_item_pressed)
|
|
||||||
menu_button.queue_free()
|
|
||||||
menu_button = null
|
|
||||||
popup_menu = null
|
|
||||||
|
|
||||||
|
|
||||||
func update_menu(show_uninstall: bool) -> void:
|
|
||||||
if not is_instance_valid(popup_menu):
|
|
||||||
return
|
|
||||||
popup_menu.clear()
|
|
||||||
_populate_menu(show_uninstall)
|
|
||||||
|
|
||||||
|
|
||||||
## Searches for and returns the last menu node in the script editor top menu bar,
|
|
||||||
## or null if not found.
|
|
||||||
func _find_last_menu_button(script_editor: Control) -> MenuButton:
|
|
||||||
# The first child of the script editor should be a VBoxContainer (main container for the script editor main screen)
|
|
||||||
# Then the first child of that container should be an HBoxContainer (the menu bar)
|
|
||||||
# This is based on the current structure of Godot's script editor as of Godot 4.5
|
|
||||||
# Note: We add multiple checks in there with null returns mainly in case something changes in a future
|
|
||||||
if script_editor.get_child_count() == 0:
|
|
||||||
return null
|
|
||||||
|
|
||||||
var main_container := script_editor.get_child(0)
|
|
||||||
if not is_instance_valid(main_container) or not main_container is VBoxContainer:
|
|
||||||
return null
|
|
||||||
|
|
||||||
if main_container.get_child_count() == 0:
|
|
||||||
return null
|
|
||||||
|
|
||||||
var menu_bar := main_container.get_child(0)
|
|
||||||
if not is_instance_valid(menu_bar) or not menu_bar is HBoxContainer:
|
|
||||||
return null
|
|
||||||
|
|
||||||
# We reached the menu bar. So now we loop through all the children look for
|
|
||||||
# the last menu button, which would be the last menu in the menu list.
|
|
||||||
# Note: Here the goal is to insert the menu after the debug menu, but we
|
|
||||||
# don't check for the button text because "debug" would only work in English
|
|
||||||
# this should work in any language.
|
|
||||||
var last_menu_button: MenuButton = null
|
|
||||||
for child in menu_bar.get_children():
|
|
||||||
if child is MenuButton:
|
|
||||||
last_menu_button = child as MenuButton
|
|
||||||
|
|
||||||
return last_menu_button
|
|
||||||
|
|
||||||
|
|
||||||
func _populate_menu(show_uninstall: bool = true) -> void:
|
|
||||||
if not is_instance_valid(popup_menu):
|
|
||||||
return
|
|
||||||
|
|
||||||
var current_item_index := 0
|
|
||||||
|
|
||||||
popup_menu.add_item(MENU_ITEMS["format_script"], current_item_index)
|
|
||||||
popup_menu.set_item_metadata(current_item_index, "format_script")
|
|
||||||
popup_menu.set_item_tooltip(
|
|
||||||
current_item_index,
|
|
||||||
"Run the GDScript Formatter over the current script",
|
|
||||||
)
|
|
||||||
|
|
||||||
current_item_index += 1
|
|
||||||
popup_menu.add_item(MENU_ITEMS["lint_script"], current_item_index)
|
|
||||||
popup_menu.set_item_metadata(current_item_index, "lint_script")
|
|
||||||
popup_menu.set_item_tooltip(current_item_index, "Check the current script for linting issues")
|
|
||||||
|
|
||||||
current_item_index += 1
|
|
||||||
popup_menu.add_item(MENU_ITEMS["reorder_code"], current_item_index)
|
|
||||||
popup_menu.set_item_metadata(current_item_index, "reorder_code")
|
|
||||||
popup_menu.set_item_tooltip(
|
|
||||||
current_item_index,
|
|
||||||
"Reorder the code elements in the current script according to the GDScript Style Guide",
|
|
||||||
)
|
|
||||||
|
|
||||||
popup_menu.add_separator()
|
|
||||||
|
|
||||||
# NOTE: When we add separators, it bumps the internal index of menu items.
|
|
||||||
# That's why we have to increase it even on separators. Otherwise the
|
|
||||||
# tooltip will lose sync.
|
|
||||||
current_item_index += 2
|
|
||||||
popup_menu.add_item(MENU_ITEMS["install_update"], current_item_index)
|
|
||||||
popup_menu.set_item_metadata(current_item_index, "install_update")
|
|
||||||
popup_menu.set_item_tooltip(
|
|
||||||
current_item_index,
|
|
||||||
"Download the latest version of the GDScript Formatter",
|
|
||||||
)
|
|
||||||
|
|
||||||
if show_uninstall:
|
|
||||||
current_item_index += 1
|
|
||||||
popup_menu.add_item(MENU_ITEMS["uninstall"], current_item_index)
|
|
||||||
popup_menu.set_item_metadata(current_item_index, "uninstall")
|
|
||||||
popup_menu.set_item_tooltip(
|
|
||||||
current_item_index,
|
|
||||||
"Remove the GDScript Formatter installed through this add-on from your computer",
|
|
||||||
)
|
|
||||||
|
|
||||||
popup_menu.add_separator()
|
|
||||||
|
|
||||||
# Bumped index by 1 extra step because of the previous separator.
|
|
||||||
current_item_index += 2
|
|
||||||
popup_menu.add_item(MENU_ITEMS["report_issue"], current_item_index)
|
|
||||||
popup_menu.set_item_metadata(current_item_index, "report_issue")
|
|
||||||
popup_menu.set_item_tooltip(current_item_index, "Tell us about problems or bugs you found")
|
|
||||||
|
|
||||||
current_item_index += 1
|
|
||||||
popup_menu.add_item(MENU_ITEMS["help"], current_item_index)
|
|
||||||
popup_menu.set_item_metadata(current_item_index, "help")
|
|
||||||
popup_menu.set_item_tooltip(current_item_index, "Learn how to use the GDScript Formatter")
|
|
||||||
|
|
||||||
|
|
||||||
func _on_menu_item_pressed(id: int) -> void:
|
|
||||||
if not is_instance_valid(popup_menu):
|
|
||||||
return
|
|
||||||
|
|
||||||
var command: String = popup_menu.get_item_metadata(id)
|
|
||||||
if not command.is_empty():
|
|
||||||
menu_item_selected.emit(command)
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
uid://bqwfn5bsuds1m
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
[plugin]
|
|
||||||
|
|
||||||
name="GDQuest GDScript Formatter"
|
|
||||||
description="A plugin to format GDScript code in the Godot editor using the GDScript Formatter from GDQuest."
|
|
||||||
author="GDQuest"
|
|
||||||
version="0.25.0"
|
|
||||||
script="plugin.gd"
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1 +0,0 @@
|
|||||||
uid://dqgr353xopp3y
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2025-present GDQuest
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
[configuration]
|
|
||||||
entry_symbol = "gdext_rust_init"
|
|
||||||
compatibility_minimum = 4.7
|
|
||||||
|
|
||||||
[libraries]
|
|
||||||
linux.debug.x86_64 = "res://addons/GDQuest_GDScript_formatter_standalone/bin/libgdscript_formatter_gdextension.so"
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
uid://dwlskvf853una
|
|
||||||
@@ -1,145 +0,0 @@
|
|||||||
## This module handles adding a menu to the script editor with formatter commands.
|
|
||||||
## It safely locates the script editor's menu bar and adds our custom menu.
|
|
||||||
@tool
|
|
||||||
extends Node
|
|
||||||
|
|
||||||
signal menu_item_selected(command: String)
|
|
||||||
|
|
||||||
const MENU_TEXT = "Format"
|
|
||||||
const MENU_ITEMS = {
|
|
||||||
"format_script": "Format Current Script",
|
|
||||||
"lint_script": "Lint Current Script",
|
|
||||||
"reorder_code": "Reorder Code",
|
|
||||||
"update": "Update Formatter",
|
|
||||||
"report_issue": "Report Issue",
|
|
||||||
"help": "Help",
|
|
||||||
}
|
|
||||||
|
|
||||||
var menu_button: MenuButton = null
|
|
||||||
var popup_menu: PopupMenu = null
|
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
|
||||||
# At the start we insert the menu in the script editor
|
|
||||||
var script_editor := EditorInterface.get_script_editor()
|
|
||||||
var last_menu_button := _find_last_menu_button(script_editor)
|
|
||||||
if not is_instance_valid(last_menu_button):
|
|
||||||
push_warning("GDScript Formatter: Could not find valid menu button in script editor. Menu will not be available. Use the command palette instead.")
|
|
||||||
return
|
|
||||||
|
|
||||||
menu_button = MenuButton.new()
|
|
||||||
menu_button.text = MENU_TEXT
|
|
||||||
menu_button.switch_on_hover = true
|
|
||||||
menu_button.flat = false
|
|
||||||
menu_button.theme_type_variation = &"FlatMenuButton"
|
|
||||||
|
|
||||||
popup_menu = menu_button.get_popup()
|
|
||||||
_populate_menu()
|
|
||||||
|
|
||||||
popup_menu.id_pressed.connect(_on_menu_item_pressed)
|
|
||||||
last_menu_button.add_sibling(menu_button)
|
|
||||||
|
|
||||||
|
|
||||||
## Cleans up the menu from the script editor. Call this when disabling the
|
|
||||||
## plugin (in _exit_tree()).
|
|
||||||
func remove_formatter_menu() -> void:
|
|
||||||
if is_instance_valid(menu_button):
|
|
||||||
if is_instance_valid(popup_menu):
|
|
||||||
popup_menu.id_pressed.disconnect(_on_menu_item_pressed)
|
|
||||||
menu_button.queue_free()
|
|
||||||
menu_button = null
|
|
||||||
popup_menu = null
|
|
||||||
|
|
||||||
|
|
||||||
func update_menu() -> void:
|
|
||||||
if not is_instance_valid(popup_menu):
|
|
||||||
return
|
|
||||||
popup_menu.clear()
|
|
||||||
_populate_menu()
|
|
||||||
|
|
||||||
|
|
||||||
## Searches for and returns the last menu node in the script editor top menu bar,
|
|
||||||
## or null if not found.
|
|
||||||
func _find_last_menu_button(script_editor: Control) -> MenuButton:
|
|
||||||
# The first child of the script editor should be a VBoxContainer (main container for the script editor main screen)
|
|
||||||
# Then the first child of that container should be an HBoxContainer (the menu bar)
|
|
||||||
# This is based on the current structure of Godot's script editor as of Godot 4.5
|
|
||||||
# Note: We add multiple checks in there with null returns mainly in case something changes in a future
|
|
||||||
if script_editor.get_child_count() == 0:
|
|
||||||
return null
|
|
||||||
|
|
||||||
var main_container := script_editor.get_child(0)
|
|
||||||
if not is_instance_valid(main_container) or not main_container is VBoxContainer:
|
|
||||||
return null
|
|
||||||
|
|
||||||
if main_container.get_child_count() == 0:
|
|
||||||
return null
|
|
||||||
|
|
||||||
var menu_bar := main_container.get_child(0)
|
|
||||||
if not is_instance_valid(menu_bar) or not menu_bar is HBoxContainer:
|
|
||||||
return null
|
|
||||||
|
|
||||||
# We reached the menu bar. So now we loop through all the children look for
|
|
||||||
# the last menu button, which would be the last menu in the menu list.
|
|
||||||
# Note: Here the goal is to insert the menu after the debug menu, but we
|
|
||||||
# don't check for the button text because "debug" would only work in English
|
|
||||||
# this should work in any language.
|
|
||||||
var last_menu_button: MenuButton = null
|
|
||||||
for child in menu_bar.get_children():
|
|
||||||
if child is MenuButton:
|
|
||||||
last_menu_button = child as MenuButton
|
|
||||||
|
|
||||||
return last_menu_button
|
|
||||||
|
|
||||||
|
|
||||||
func _populate_menu() -> void:
|
|
||||||
if not is_instance_valid(popup_menu):
|
|
||||||
return
|
|
||||||
|
|
||||||
var current_item_index := 0
|
|
||||||
|
|
||||||
popup_menu.add_item(MENU_ITEMS["format_script"], current_item_index)
|
|
||||||
popup_menu.set_item_metadata(current_item_index, "format_script")
|
|
||||||
popup_menu.set_item_tooltip(current_item_index, "Run the GDScript Formatter over the current script")
|
|
||||||
|
|
||||||
current_item_index += 1
|
|
||||||
popup_menu.add_item(MENU_ITEMS["lint_script"], current_item_index)
|
|
||||||
popup_menu.set_item_metadata(current_item_index, "lint_script")
|
|
||||||
popup_menu.set_item_tooltip(current_item_index, "Check the current script for linting issues")
|
|
||||||
|
|
||||||
current_item_index += 1
|
|
||||||
popup_menu.add_item(MENU_ITEMS["reorder_code"], current_item_index)
|
|
||||||
popup_menu.set_item_metadata(current_item_index, "reorder_code")
|
|
||||||
popup_menu.set_item_tooltip(current_item_index, "Reorder the code elements in the current script according to the GDScript Style Guide")
|
|
||||||
|
|
||||||
popup_menu.add_separator()
|
|
||||||
|
|
||||||
# NOTE: When we add separators, it bumps the internal index of menu items.
|
|
||||||
# That's why we have to increase it even on separators. Otherwise the
|
|
||||||
# tooltip will lose sync.
|
|
||||||
current_item_index += 2
|
|
||||||
popup_menu.add_item(MENU_ITEMS["update"], current_item_index)
|
|
||||||
popup_menu.set_item_metadata(current_item_index, "update")
|
|
||||||
popup_menu.set_item_tooltip(current_item_index, "Download the latest version of the GDScript Formatter")
|
|
||||||
|
|
||||||
popup_menu.add_separator()
|
|
||||||
|
|
||||||
# Bumped index by 1 extra step because of the previous separator.
|
|
||||||
current_item_index += 2
|
|
||||||
popup_menu.add_item(MENU_ITEMS["report_issue"], current_item_index)
|
|
||||||
popup_menu.set_item_metadata(current_item_index, "report_issue")
|
|
||||||
popup_menu.set_item_tooltip(current_item_index, "Tell us about problems or bugs you found")
|
|
||||||
|
|
||||||
current_item_index += 1
|
|
||||||
popup_menu.add_item(MENU_ITEMS["help"], current_item_index)
|
|
||||||
popup_menu.set_item_metadata(current_item_index, "help")
|
|
||||||
popup_menu.set_item_tooltip(current_item_index, "Learn how to use the GDScript Formatter")
|
|
||||||
|
|
||||||
|
|
||||||
func _on_menu_item_pressed(id: int) -> void:
|
|
||||||
if not is_instance_valid(popup_menu):
|
|
||||||
return
|
|
||||||
|
|
||||||
var command: String = popup_menu.get_item_metadata(id)
|
|
||||||
if not command.is_empty():
|
|
||||||
menu_item_selected.emit(command)
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
uid://bqwpllr5ylrmb
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
[plugin]
|
|
||||||
|
|
||||||
name="GDQuest GDScript Formatter (Standalone)"
|
|
||||||
description="A plugin to format GDScript code in the Godot editor using the GDScript Formatter from GDQuest."
|
|
||||||
author="GDQuest"
|
|
||||||
version="0.22.2"
|
|
||||||
script="plugin.gd"
|
|
||||||
@@ -1,641 +0,0 @@
|
|||||||
## This plug-in adds support for automatically formatting GDScript files on save
|
|
||||||
## and via a command in the Godot Editor, using the GDQuest GDScript Formatter.
|
|
||||||
##
|
|
||||||
## It also provides an option to install or update the formatter binary from the GitHub releases.
|
|
||||||
##
|
|
||||||
## See our website for more information on the formatter and how to use it:
|
|
||||||
## https://www.gdquest.com/library/gdscript_formatter/
|
|
||||||
@tool
|
|
||||||
extends EditorPlugin
|
|
||||||
|
|
||||||
const FormatterMenu = preload("menu.gd")
|
|
||||||
|
|
||||||
const EDITOR_SETTINGS_CATEGORY = "gdquest_gdscript_formatter/"
|
|
||||||
const SETTING_FORMAT_ON_SAVE = "format_on_save"
|
|
||||||
const SETTING_SHORTCUT = "shortcut"
|
|
||||||
const SETTING_USE_SPACES = "use_spaces"
|
|
||||||
const SETTING_INDENT_SIZE = "indent_size"
|
|
||||||
const SETTING_REORDER_CODE = "reorder_code"
|
|
||||||
const SETTING_SAFE_MODE = "safe_mode"
|
|
||||||
const SETTING_LINT_ON_SAVE = "lint_on_save"
|
|
||||||
const SETTING_LINT_LINE_LENGTH = "lint_line_length"
|
|
||||||
const SETTING_LINT_IGNORED_RULES = "lint_ignored_rules"
|
|
||||||
# Directories to ignore when Format on Save is enabled
|
|
||||||
const SETTING_IGNORED_DIRECTORIES = "format_on_save_ignored_directories"
|
|
||||||
|
|
||||||
const COMMAND_PALETTE_CATEGORY = "gdquest gdscript formatter/"
|
|
||||||
const COMMAND_PALETTE_FORMAT_SCRIPT = "Format GDScript"
|
|
||||||
const COMMAND_PALETTE_LINT_SCRIPT = "Lint GDScript"
|
|
||||||
const COMMAND_PALETTE_UPDATE = "Update Formatter"
|
|
||||||
const COMMAND_PALETTE_REPORT_ISSUE = "Report Issue"
|
|
||||||
|
|
||||||
var DEFAULT_SETTINGS = {
|
|
||||||
SETTING_FORMAT_ON_SAVE: false,
|
|
||||||
SETTING_USE_SPACES: false,
|
|
||||||
SETTING_INDENT_SIZE: 4,
|
|
||||||
SETTING_REORDER_CODE: false,
|
|
||||||
SETTING_SAFE_MODE: true,
|
|
||||||
SETTING_LINT_ON_SAVE: false,
|
|
||||||
SETTING_LINT_LINE_LENGTH: 100,
|
|
||||||
SETTING_LINT_IGNORED_RULES: "",
|
|
||||||
SETTING_IGNORED_DIRECTORIES: PackedStringArray(["addons/"]),
|
|
||||||
}
|
|
||||||
|
|
||||||
## Which gutter lint icons are shown in.
|
|
||||||
## By default, gutter 0 is for breakpoints and 1 is for things like overrides.
|
|
||||||
const GUTTER_LINT_ICON_INDEX = 2
|
|
||||||
const GUTTER_LINT_ICONS_NAME = "gdscript_formatter_lint_icons"
|
|
||||||
|
|
||||||
var connection_list: Array[Resource] = []
|
|
||||||
var menu: FormatterMenu = null
|
|
||||||
# Used to auto detect changes to the project's .editorconfig file.
|
|
||||||
var _editorconfig_last_modified_time := -1
|
|
||||||
# Editorconfig allows setting rules per path glob. We track globs for the format
|
|
||||||
# on save rule here so users can enable it selectively for specific folders.
|
|
||||||
var _editorconfig_format_on_save_rules: Array[Dictionary] = []
|
|
||||||
|
|
||||||
|
|
||||||
func _init() -> void:
|
|
||||||
for setting: String in DEFAULT_SETTINGS.keys():
|
|
||||||
if not has_editor_setting(setting):
|
|
||||||
set_editor_setting(setting, DEFAULT_SETTINGS[setting])
|
|
||||||
|
|
||||||
if not has_editor_setting(SETTING_SHORTCUT):
|
|
||||||
var default_shortcut := InputEventKey.new()
|
|
||||||
default_shortcut.echo = false
|
|
||||||
default_shortcut.pressed = true
|
|
||||||
default_shortcut.keycode = KEY_I
|
|
||||||
default_shortcut.ctrl_pressed = true
|
|
||||||
default_shortcut.shift_pressed = false
|
|
||||||
default_shortcut.alt_pressed = true
|
|
||||||
|
|
||||||
var shortcut := Shortcut.new()
|
|
||||||
shortcut.events.push_back(default_shortcut)
|
|
||||||
|
|
||||||
set_editor_setting(SETTING_SHORTCUT, shortcut)
|
|
||||||
|
|
||||||
|
|
||||||
func _enter_tree() -> void:
|
|
||||||
add_format_command()
|
|
||||||
add_lint_command()
|
|
||||||
add_update_command()
|
|
||||||
add_report_issue_command()
|
|
||||||
|
|
||||||
menu = FormatterMenu.new()
|
|
||||||
add_child(menu)
|
|
||||||
menu.menu_item_selected.connect(_on_menu_item_selected)
|
|
||||||
menu.update_menu()
|
|
||||||
|
|
||||||
update_shortcut()
|
|
||||||
resource_saved.connect(_on_resource_saved)
|
|
||||||
|
|
||||||
|
|
||||||
func _exit_tree() -> void:
|
|
||||||
resource_saved.disconnect(_on_resource_saved)
|
|
||||||
|
|
||||||
remove_format_command()
|
|
||||||
remove_lint_command()
|
|
||||||
remove_update_command()
|
|
||||||
remove_report_issue_command()
|
|
||||||
|
|
||||||
if is_instance_valid(menu):
|
|
||||||
menu.menu_item_selected.disconnect(_on_menu_item_selected)
|
|
||||||
menu.remove_formatter_menu()
|
|
||||||
menu.queue_free()
|
|
||||||
menu = null
|
|
||||||
|
|
||||||
|
|
||||||
func _shortcut_input(event: InputEvent) -> void:
|
|
||||||
var shortcut := get_editor_setting(SETTING_SHORTCUT) as Shortcut
|
|
||||||
if not is_instance_valid(shortcut):
|
|
||||||
return
|
|
||||||
if not shortcut.matches_event(event) or not event.is_pressed() or event.is_echo():
|
|
||||||
return
|
|
||||||
if format_current_script():
|
|
||||||
get_tree().root.set_input_as_handled()
|
|
||||||
|
|
||||||
|
|
||||||
func format_current_script() -> bool:
|
|
||||||
if not EditorInterface.get_script_editor().is_visible_in_tree():
|
|
||||||
return false
|
|
||||||
var current_script := EditorInterface.get_script_editor().get_current_script()
|
|
||||||
if not is_instance_valid(current_script) or not current_script is GDScript:
|
|
||||||
return false
|
|
||||||
var code_edit: CodeEdit = EditorInterface.get_script_editor().get_current_editor().get_base_editor()
|
|
||||||
|
|
||||||
var formatted_code := format_code(current_script, false, code_edit.text)
|
|
||||||
if formatted_code.is_empty():
|
|
||||||
return false
|
|
||||||
|
|
||||||
reload_code_edit(code_edit, formatted_code)
|
|
||||||
return true
|
|
||||||
|
|
||||||
|
|
||||||
func lint_current_script() -> bool:
|
|
||||||
if not EditorInterface.get_script_editor().is_visible_in_tree():
|
|
||||||
return false
|
|
||||||
|
|
||||||
var current_script := EditorInterface.get_script_editor().get_current_script()
|
|
||||||
if not is_instance_valid(current_script) or not current_script is GDScript:
|
|
||||||
return false
|
|
||||||
|
|
||||||
var code_edit: CodeEdit = EditorInterface.get_script_editor().get_current_editor().get_base_editor()
|
|
||||||
|
|
||||||
var lint_issues := lint_code(current_script)
|
|
||||||
if lint_issues.is_empty():
|
|
||||||
print("No linting issues found.")
|
|
||||||
clear_lint_highlights(code_edit)
|
|
||||||
return true
|
|
||||||
|
|
||||||
apply_lint_highlights(code_edit, lint_issues)
|
|
||||||
print_lint_summary(lint_issues, current_script.resource_path)
|
|
||||||
|
|
||||||
return true
|
|
||||||
|
|
||||||
|
|
||||||
func update_shortcut() -> void:
|
|
||||||
for obj: Resource in connection_list:
|
|
||||||
obj.changed.disconnect(update_shortcut)
|
|
||||||
|
|
||||||
connection_list.clear()
|
|
||||||
|
|
||||||
var shortcut := get_editor_setting(SETTING_SHORTCUT) as Shortcut
|
|
||||||
if is_instance_valid(shortcut):
|
|
||||||
for event: InputEvent in shortcut.events:
|
|
||||||
if is_instance_valid(event):
|
|
||||||
event.changed.connect(update_shortcut)
|
|
||||||
connection_list.push_back(event)
|
|
||||||
|
|
||||||
remove_format_command()
|
|
||||||
add_format_command()
|
|
||||||
|
|
||||||
|
|
||||||
func _on_resource_saved(saved_resource: Resource) -> void:
|
|
||||||
if saved_resource is not GDScript:
|
|
||||||
return
|
|
||||||
|
|
||||||
var script := saved_resource as GDScript
|
|
||||||
var do_format_on_save := get_editor_setting(SETTING_FORMAT_ON_SAVE) as bool
|
|
||||||
var editorconfig_format_on_save = get_editorconfig_format_on_save(script.resource_path)
|
|
||||||
if editorconfig_format_on_save != null:
|
|
||||||
do_format_on_save = editorconfig_format_on_save as bool
|
|
||||||
var lint_on_save := get_editor_setting(SETTING_LINT_ON_SAVE) as bool
|
|
||||||
|
|
||||||
if not do_format_on_save and not lint_on_save:
|
|
||||||
return
|
|
||||||
|
|
||||||
var ignored_directories = get_editor_setting(SETTING_IGNORED_DIRECTORIES)
|
|
||||||
var path = script.resource_path.trim_prefix("res://")
|
|
||||||
|
|
||||||
var script_path_parts := path.split("/")
|
|
||||||
|
|
||||||
for directory: String in ignored_directories:
|
|
||||||
var normalized_dir := directory.trim_prefix("res://")
|
|
||||||
var directory_parts := normalized_dir.split("/")
|
|
||||||
|
|
||||||
var matches := true
|
|
||||||
for i in range(directory_parts.size()):
|
|
||||||
if directory_parts[i] != script_path_parts[i]:
|
|
||||||
matches = false
|
|
||||||
break
|
|
||||||
|
|
||||||
if matches:
|
|
||||||
return
|
|
||||||
|
|
||||||
if not is_instance_valid(script):
|
|
||||||
return
|
|
||||||
|
|
||||||
if do_format_on_save:
|
|
||||||
var formatted_code := format_code(script, false)
|
|
||||||
if formatted_code.is_empty():
|
|
||||||
return
|
|
||||||
|
|
||||||
script.source_code = formatted_code
|
|
||||||
ResourceSaver.save(script)
|
|
||||||
# The argument (keep_state parameter) tells Godot to try to preserve the
|
|
||||||
# state of the script instance, like static variables. Without this,
|
|
||||||
# attempting to reload tool scripts will fail with an error because they
|
|
||||||
# are already instantiated in the editor and instantiated scripts are
|
|
||||||
# not allowed to force reload without unloading first.
|
|
||||||
script.reload(true)
|
|
||||||
|
|
||||||
var script_editor := EditorInterface.get_script_editor()
|
|
||||||
var open_script_editors := script_editor.get_open_script_editors()
|
|
||||||
var open_scripts := script_editor.get_open_scripts()
|
|
||||||
|
|
||||||
if not open_scripts.has(script):
|
|
||||||
return
|
|
||||||
|
|
||||||
if script_editor.get_current_script() == script:
|
|
||||||
reload_code_edit(script_editor.get_current_editor().get_base_editor(), formatted_code, true)
|
|
||||||
elif open_scripts.size() == open_script_editors.size():
|
|
||||||
for i: int in range(open_scripts.size()):
|
|
||||||
if open_scripts[i] == script:
|
|
||||||
reload_code_edit(open_script_editors[i].get_base_editor(), formatted_code, true)
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
push_error("GDScript Formatter error: Unknown situation, can't reload code editor in Editor. Please report this issue.")
|
|
||||||
|
|
||||||
if lint_on_save:
|
|
||||||
var code_edit: CodeEdit = EditorInterface.get_script_editor().get_current_editor().get_base_editor()
|
|
||||||
var lint_issues := lint_code(script)
|
|
||||||
if lint_issues.is_empty():
|
|
||||||
clear_lint_highlights(code_edit)
|
|
||||||
else:
|
|
||||||
apply_lint_highlights(code_edit, lint_issues)
|
|
||||||
print_lint_summary(lint_issues, script.resource_path)
|
|
||||||
|
|
||||||
|
|
||||||
func add_format_command() -> void:
|
|
||||||
var shortcut := get_editor_setting(SETTING_SHORTCUT) as Shortcut
|
|
||||||
EditorInterface.get_command_palette().add_command(
|
|
||||||
COMMAND_PALETTE_FORMAT_SCRIPT,
|
|
||||||
COMMAND_PALETTE_CATEGORY + COMMAND_PALETTE_FORMAT_SCRIPT,
|
|
||||||
format_current_script,
|
|
||||||
shortcut.get_as_text() if is_instance_valid(shortcut) else "None",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
func remove_format_command() -> void:
|
|
||||||
EditorInterface.get_command_palette().remove_command(COMMAND_PALETTE_CATEGORY + COMMAND_PALETTE_FORMAT_SCRIPT)
|
|
||||||
|
|
||||||
|
|
||||||
func add_lint_command() -> void:
|
|
||||||
EditorInterface.get_command_palette().add_command(
|
|
||||||
COMMAND_PALETTE_LINT_SCRIPT,
|
|
||||||
COMMAND_PALETTE_CATEGORY + COMMAND_PALETTE_LINT_SCRIPT,
|
|
||||||
lint_current_script,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
func remove_lint_command() -> void:
|
|
||||||
EditorInterface.get_command_palette().remove_command(
|
|
||||||
COMMAND_PALETTE_CATEGORY + COMMAND_PALETTE_LINT_SCRIPT,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
func add_update_command() -> void:
|
|
||||||
EditorInterface.get_command_palette().add_command(
|
|
||||||
COMMAND_PALETTE_UPDATE,
|
|
||||||
COMMAND_PALETTE_CATEGORY + COMMAND_PALETTE_UPDATE,
|
|
||||||
# TODO: Implement me
|
|
||||||
func() -> void: push_error("Update command not implemented!"),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
func remove_update_command() -> void:
|
|
||||||
EditorInterface.get_command_palette().remove_command(COMMAND_PALETTE_CATEGORY + COMMAND_PALETTE_UPDATE)
|
|
||||||
|
|
||||||
|
|
||||||
func add_report_issue_command() -> void:
|
|
||||||
EditorInterface.get_command_palette().add_command(
|
|
||||||
COMMAND_PALETTE_REPORT_ISSUE,
|
|
||||||
COMMAND_PALETTE_CATEGORY + COMMAND_PALETTE_REPORT_ISSUE,
|
|
||||||
report_issue,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
func remove_report_issue_command() -> void:
|
|
||||||
EditorInterface.get_command_palette().remove_command(
|
|
||||||
COMMAND_PALETTE_CATEGORY + COMMAND_PALETTE_REPORT_ISSUE)
|
|
||||||
|
|
||||||
|
|
||||||
func reorder_code() -> bool:
|
|
||||||
if not EditorInterface.get_script_editor().is_visible_in_tree():
|
|
||||||
return false
|
|
||||||
var current_script := EditorInterface.get_script_editor().get_current_script()
|
|
||||||
if not is_instance_valid(current_script) or not current_script is GDScript:
|
|
||||||
return false
|
|
||||||
var code_edit: CodeEdit = EditorInterface.get_script_editor().get_current_editor().get_base_editor()
|
|
||||||
|
|
||||||
var formatted_code := format_code(current_script, true, code_edit.text)
|
|
||||||
if formatted_code.is_empty():
|
|
||||||
return false
|
|
||||||
|
|
||||||
reload_code_edit(code_edit, formatted_code)
|
|
||||||
return true
|
|
||||||
|
|
||||||
|
|
||||||
func report_issue() -> void:
|
|
||||||
OS.shell_open("https://github.com/GDQuest/GDScript-formatter/issues")
|
|
||||||
|
|
||||||
|
|
||||||
func show_help() -> void:
|
|
||||||
OS.shell_open("https://www.gdquest.com/library/gdscript_formatter/")
|
|
||||||
|
|
||||||
|
|
||||||
func _on_menu_item_selected(command: String) -> void:
|
|
||||||
match command:
|
|
||||||
"format_script":
|
|
||||||
format_current_script()
|
|
||||||
"lint_script":
|
|
||||||
lint_current_script()
|
|
||||||
"reorder_code":
|
|
||||||
reorder_code()
|
|
||||||
"update":
|
|
||||||
# TODO: Implement me
|
|
||||||
push_error("update not implemented!")
|
|
||||||
"report_issue":
|
|
||||||
report_issue()
|
|
||||||
"help":
|
|
||||||
show_help()
|
|
||||||
_:
|
|
||||||
push_warning("Unsupported command sent from the menu: " + command)
|
|
||||||
|
|
||||||
|
|
||||||
## Reloads the code editor with new text while preserving editor state.
|
|
||||||
## This includes cursor position, scroll position, breakpoints, bookmarks, and folds.
|
|
||||||
func reload_code_edit(
|
|
||||||
code_edit: CodeEdit,
|
|
||||||
new_text: String,
|
|
||||||
tag_saved := false,
|
|
||||||
) -> void:
|
|
||||||
var editor_state := CodeEditState.new(code_edit)
|
|
||||||
code_edit.text = new_text
|
|
||||||
if tag_saved:
|
|
||||||
code_edit.tag_saved_version()
|
|
||||||
editor_state.restore_to_editor(code_edit)
|
|
||||||
code_edit.update_minimum_size()
|
|
||||||
code_edit.text_changed.emit()
|
|
||||||
|
|
||||||
|
|
||||||
func get_editor_setting(setting_name: String) -> Variant:
|
|
||||||
var editor_settings := EditorInterface.get_editor_settings()
|
|
||||||
var full_setting_key := EDITOR_SETTINGS_CATEGORY + setting_name
|
|
||||||
if editor_settings.has_setting(full_setting_key):
|
|
||||||
return editor_settings.get_setting(full_setting_key)
|
|
||||||
return DEFAULT_SETTINGS[setting_name]
|
|
||||||
|
|
||||||
|
|
||||||
func set_editor_setting(setting_name: String, value: Variant) -> void:
|
|
||||||
var editor_settings := EditorInterface.get_editor_settings()
|
|
||||||
var full_setting_key := EDITOR_SETTINGS_CATEGORY + setting_name
|
|
||||||
editor_settings.set_setting(full_setting_key, value)
|
|
||||||
|
|
||||||
|
|
||||||
func has_editor_setting(setting_name: String) -> bool:
|
|
||||||
var editor_settings := EditorInterface.get_editor_settings()
|
|
||||||
var full_setting_key := EDITOR_SETTINGS_CATEGORY + setting_name
|
|
||||||
return editor_settings.has_setting(full_setting_key)
|
|
||||||
|
|
||||||
|
|
||||||
## Returns true if this script should be formatted automatically on save, based
|
|
||||||
## on the project's .editorconfig file. Returns false if the config says not to
|
|
||||||
## format on save. Returns null if no rule matches (then it's the user editor
|
|
||||||
## settings that take over).
|
|
||||||
func get_editorconfig_format_on_save(script_path: String) -> Variant:
|
|
||||||
var editorconfig_path := ProjectSettings.globalize_path("res://.editorconfig")
|
|
||||||
var modified_time := FileAccess.get_modified_time(editorconfig_path)
|
|
||||||
if modified_time != _editorconfig_last_modified_time:
|
|
||||||
_editorconfig_last_modified_time = modified_time
|
|
||||||
_editorconfig_format_on_save_rules.clear()
|
|
||||||
load_editorconfig_format_on_save_rules(editorconfig_path)
|
|
||||||
|
|
||||||
var relative_script_path := script_path.trim_prefix("res://")
|
|
||||||
var format_on_save = null
|
|
||||||
for rule: Dictionary in _editorconfig_format_on_save_rules:
|
|
||||||
var pattern := rule["pattern"] as String
|
|
||||||
if pattern.is_empty() or editorconfig_section_matches(pattern, relative_script_path):
|
|
||||||
format_on_save = rule["format_on_save"]
|
|
||||||
|
|
||||||
return format_on_save
|
|
||||||
|
|
||||||
|
|
||||||
## Loads the project editorconfig file and parses format on save rules.
|
|
||||||
func load_editorconfig_format_on_save_rules(editorconfig_path: String) -> void:
|
|
||||||
var editorconfig_file := FileAccess.open(editorconfig_path, FileAccess.READ)
|
|
||||||
if editorconfig_file == null:
|
|
||||||
return
|
|
||||||
|
|
||||||
var pattern := ""
|
|
||||||
|
|
||||||
while not editorconfig_file.eof_reached():
|
|
||||||
var line := editorconfig_file.get_line().strip_edges()
|
|
||||||
if line.is_empty() or line.begins_with("#") or line.begins_with(";"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
if line.begins_with("[") and line.ends_with("]"):
|
|
||||||
pattern = line.trim_prefix("[").trim_suffix("]")
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not line.contains("="):
|
|
||||||
continue
|
|
||||||
|
|
||||||
var key_and_value := line.split("=", true, 1)
|
|
||||||
if key_and_value[0].strip_edges().to_lower() != "gdscript_formatter_format_on_save":
|
|
||||||
continue
|
|
||||||
|
|
||||||
match key_and_value[1].strip_edges().to_lower():
|
|
||||||
"true":
|
|
||||||
_editorconfig_format_on_save_rules.append({"pattern": pattern, "format_on_save": true})
|
|
||||||
"false":
|
|
||||||
_editorconfig_format_on_save_rules.append({"pattern": pattern, "format_on_save": false})
|
|
||||||
|
|
||||||
editorconfig_file.close()
|
|
||||||
|
|
||||||
|
|
||||||
## Returns true if an EditorConfig section applies to a saved script.
|
|
||||||
## pattern: The EditorConfig pattern to match against.
|
|
||||||
## relative_script_path: The path of the script relative to the project root.
|
|
||||||
func editorconfig_section_matches(pattern: String, relative_script_path: String) -> bool:
|
|
||||||
if pattern.is_empty():
|
|
||||||
return false
|
|
||||||
|
|
||||||
var normalized_pattern := pattern.trim_prefix("/")
|
|
||||||
var matching_path := relative_script_path
|
|
||||||
# If there's no / in the pattern it means this pattern targets a filename.
|
|
||||||
# It's not a folder/path glob pattern.
|
|
||||||
if not normalized_pattern.contains("/"):
|
|
||||||
matching_path = relative_script_path.get_file()
|
|
||||||
|
|
||||||
return matching_path.match(normalized_pattern)
|
|
||||||
|
|
||||||
|
|
||||||
## Formats GDScript code using the GDScript Formatter and returns it as a string.
|
|
||||||
## When source_content is null, reads the code from the GDScript resource directly.
|
|
||||||
## Otherwise, formats source_content without reading from the file.
|
|
||||||
##
|
|
||||||
## Pass a string through source_content when the user is editing the script in
|
|
||||||
## the editor and requests formatting without having saved their changes (in
|
|
||||||
## that case, the code they're editing only exists in the script editor's open
|
|
||||||
## tab).
|
|
||||||
func format_code(script: GDScript, force_reorder := false, source_content: Variant = null) -> String:
|
|
||||||
var script_path := script.resource_path
|
|
||||||
if source_content == null and script_path.is_empty():
|
|
||||||
push_error("GDScript Formatter Error: Can't format an unsaved script.")
|
|
||||||
return ""
|
|
||||||
|
|
||||||
if source_content == null:
|
|
||||||
source_content = script.source_code
|
|
||||||
|
|
||||||
var printer_config := {}
|
|
||||||
var formatter_config := { printer = printer_config }
|
|
||||||
if get_editor_setting(SETTING_USE_SPACES):
|
|
||||||
printer_config[SETTING_USE_SPACES] = true
|
|
||||||
printer_config[SETTING_INDENT_SIZE] = get_editor_setting(SETTING_INDENT_SIZE)
|
|
||||||
if force_reorder or get_editor_setting(SETTING_REORDER_CODE):
|
|
||||||
formatter_config[SETTING_REORDER_CODE] = true
|
|
||||||
if get_editor_setting(SETTING_SAFE_MODE):
|
|
||||||
formatter_config[SETTING_SAFE_MODE] = true
|
|
||||||
|
|
||||||
return GDScriptFormatter.format_gdscript(source_content, formatter_config)
|
|
||||||
|
|
||||||
|
|
||||||
## Lints a GDScript file using the GDScript Formatter's linter,
|
|
||||||
## and returns an array of lint issues.
|
|
||||||
func lint_code(script: GDScript) -> Array:
|
|
||||||
if script.resource_path.is_empty():
|
|
||||||
push_error("GDScript Formatter Error: Can't format an unsaved script.")
|
|
||||||
return []
|
|
||||||
|
|
||||||
var linter_config := {
|
|
||||||
max_line_length = get_editor_setting(SETTING_LINT_LINE_LENGTH) as int,
|
|
||||||
disabled_rules = ",".split(
|
|
||||||
get_editor_setting(SETTING_LINT_IGNORED_RULES) as String),
|
|
||||||
}
|
|
||||||
return GDScriptFormatter.lint_gdscript(script.source_code, linter_config)
|
|
||||||
|
|
||||||
|
|
||||||
## Applies lint highlighting to the code editor
|
|
||||||
func apply_lint_highlights(code_edit: CodeEdit, issues: Array) -> void:
|
|
||||||
clear_lint_highlights(code_edit)
|
|
||||||
|
|
||||||
# Add and set up gutter for lint icons if not already present.
|
|
||||||
# We check by name to avoid conflicts with gutters added by other addons.
|
|
||||||
# Once added, the gutter is never removed so the layout doesn't shift on clear.
|
|
||||||
var has_lint_gutter := false
|
|
||||||
for i: int in code_edit.get_gutter_count():
|
|
||||||
if code_edit.get_gutter_name(i) == GUTTER_LINT_ICONS_NAME:
|
|
||||||
has_lint_gutter = true
|
|
||||||
break
|
|
||||||
if not has_lint_gutter:
|
|
||||||
code_edit.add_gutter(GUTTER_LINT_ICON_INDEX)
|
|
||||||
code_edit.set_gutter_name(GUTTER_LINT_ICON_INDEX, GUTTER_LINT_ICONS_NAME)
|
|
||||||
code_edit.set_gutter_type(GUTTER_LINT_ICON_INDEX, CodeEdit.GutterType.GUTTER_TYPE_ICON)
|
|
||||||
const EDITOR_ICON_DEFAULT_WIDTH = 16.0
|
|
||||||
code_edit.set_gutter_width(GUTTER_LINT_ICON_INDEX, EDITOR_ICON_DEFAULT_WIDTH * EditorInterface.get_editor_scale())
|
|
||||||
|
|
||||||
for issue in issues:
|
|
||||||
var line_number: int = issue.line - 1 # Convert to zero-indexed
|
|
||||||
var severity: String = issue.severity
|
|
||||||
|
|
||||||
var color := Color(1, 0, 0, 0.1) if severity == "error" else Color(1, 1, 0, 0.1)
|
|
||||||
code_edit.set_line_background_color(line_number, color)
|
|
||||||
var icon_name := "StatusError" if severity == "error" else "StatusWarning"
|
|
||||||
var icon := EditorInterface.get_editor_theme().get_icon(icon_name, "EditorIcons")
|
|
||||||
code_edit.set_line_gutter_icon(line_number, GUTTER_LINT_ICON_INDEX, icon)
|
|
||||||
|
|
||||||
|
|
||||||
## Prints a detailed summary of lint issues to the output
|
|
||||||
func print_lint_summary(issues: Array, script_path: String) -> void:
|
|
||||||
print_rich("\n[b]=== Linting Results for %s ===[/b]\n" % script_path)
|
|
||||||
print_rich("[b]Found [i]%s[/i] issue(s)\n[/b]" % issues.size())
|
|
||||||
|
|
||||||
for issue in issues:
|
|
||||||
var line_display = str(issue.line)
|
|
||||||
var severity_label = issue.severity.to_upper()
|
|
||||||
print_rich(
|
|
||||||
"[color=%s]%s[/color] on line [color=cyan]%s[/color] ([i]%s[/i])" % [
|
|
||||||
"red" if severity_label == "ERROR" else "yellow",
|
|
||||||
severity_label,
|
|
||||||
line_display,
|
|
||||||
issue.rule,
|
|
||||||
],
|
|
||||||
)
|
|
||||||
print_rich("[i]%s[/i]\n" % [issue.message])
|
|
||||||
|
|
||||||
print_rich("[b]=== End Linting Results ===[/b]\n")
|
|
||||||
|
|
||||||
|
|
||||||
## Clears all lint highlighting from the code editor.
|
|
||||||
## The lint gutter is intentionally kept so the layout does not shift.
|
|
||||||
func clear_lint_highlights(code_edit: CodeEdit) -> void:
|
|
||||||
var lint_gutter_index := -1
|
|
||||||
for i: int in code_edit.get_gutter_count():
|
|
||||||
if code_edit.get_gutter_name(i) == GUTTER_LINT_ICONS_NAME:
|
|
||||||
lint_gutter_index = i
|
|
||||||
break
|
|
||||||
|
|
||||||
for line in range(code_edit.get_line_count()):
|
|
||||||
code_edit.set_line_background_color(line, Color(0, 0, 0, 0))
|
|
||||||
if lint_gutter_index != -1:
|
|
||||||
code_edit.set_line_gutter_icon(line, lint_gutter_index, null)
|
|
||||||
|
|
||||||
|
|
||||||
## Data structure to hold code editor state information
|
|
||||||
class CodeEditState:
|
|
||||||
var caret_line: int
|
|
||||||
var caret_column: int
|
|
||||||
var horizontal_scroll: int
|
|
||||||
var vertical_scroll: int
|
|
||||||
var breakpoints: Dictionary[int, String] = { }
|
|
||||||
var bookmarks: Dictionary[int, String] = { }
|
|
||||||
var folds: Dictionary[int, String] = { }
|
|
||||||
var code_edit: CodeEdit
|
|
||||||
|
|
||||||
|
|
||||||
func _init(code_edit: CodeEdit) -> void:
|
|
||||||
self.code_edit = code_edit
|
|
||||||
caret_line = code_edit.get_caret_line()
|
|
||||||
caret_column = code_edit.get_caret_column()
|
|
||||||
horizontal_scroll = code_edit.scroll_horizontal
|
|
||||||
vertical_scroll = code_edit.scroll_vertical
|
|
||||||
|
|
||||||
for line in code_edit.get_breakpointed_lines():
|
|
||||||
breakpoints[line] = code_edit.get_line(line)
|
|
||||||
for line in code_edit.get_bookmarked_lines():
|
|
||||||
bookmarks[line] = code_edit.get_line(line)
|
|
||||||
for line in code_edit.get_folded_lines():
|
|
||||||
folds[line] = code_edit.get_line(line)
|
|
||||||
|
|
||||||
|
|
||||||
func restore_to_editor(code_edit: CodeEdit) -> void:
|
|
||||||
var new_line_count := code_edit.get_line_count()
|
|
||||||
|
|
||||||
_restore_line_features(breakpoints, code_edit.set_line_as_breakpoint, new_line_count)
|
|
||||||
_restore_line_features(bookmarks, code_edit.set_line_as_bookmarked, new_line_count)
|
|
||||||
_restore_line_features(folds, func(line: int, _is_folded: bool) -> void: code_edit.fold_line(line), new_line_count)
|
|
||||||
|
|
||||||
code_edit.set_caret_line(caret_line)
|
|
||||||
code_edit.set_caret_column(caret_column)
|
|
||||||
|
|
||||||
code_edit.scroll_horizontal = horizontal_scroll
|
|
||||||
code_edit.scroll_vertical = vertical_scroll
|
|
||||||
|
|
||||||
|
|
||||||
## Restores line-based features (breakpoints, bookmarks, folds) by finding the best matching lines
|
|
||||||
## in the new text based on similarity to the original line text.
|
|
||||||
##
|
|
||||||
## Big thanks to https://github.com/Daylily-Zeleen/GDScript-Formatter for this approach.
|
|
||||||
func _restore_line_features(
|
|
||||||
stored_features: Dictionary,
|
|
||||||
set_line_func: Callable,
|
|
||||||
new_line_count: int,
|
|
||||||
) -> void:
|
|
||||||
var stored_lines := PackedInt64Array(stored_features.keys())
|
|
||||||
for line_index in range(stored_lines.size()):
|
|
||||||
var original_line := stored_lines[line_index] as int
|
|
||||||
var original_text := stored_features[original_line] as String
|
|
||||||
|
|
||||||
# After formatting lines can move, so we need to find the best match for the original line
|
|
||||||
# to restore the breakpoints, bookmarks, and folds.
|
|
||||||
# We first check the same line, then we expand our search outwards until we find a match.
|
|
||||||
# We use a similarity threshold of 0.9 to account for minor changes in the line text.
|
|
||||||
# This should be sufficient for most cases, but might need adjustment for edge cases.
|
|
||||||
# If no match is found, we skip restoring this feature
|
|
||||||
if original_line < new_line_count and code_edit.get_line(original_line).similarity(original_text) > 0.9:
|
|
||||||
set_line_func.call(original_line, true)
|
|
||||||
continue
|
|
||||||
|
|
||||||
var line_above := original_line - 1
|
|
||||||
var line_below := original_line + 1
|
|
||||||
while line_above >= 0 or line_below < new_line_count:
|
|
||||||
if line_below < new_line_count and code_edit.get_line(line_below).similarity(original_text) > 0.9:
|
|
||||||
set_line_func.call(line_below, true)
|
|
||||||
break
|
|
||||||
if line_above >= 0 and code_edit.get_line(line_above).similarity(original_text) > 0.9:
|
|
||||||
set_line_func.call(line_above, true)
|
|
||||||
break
|
|
||||||
|
|
||||||
line_above -= 1
|
|
||||||
line_below += 1
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
uid://ikxq6eiwl3ey
|
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
[runnable_presets]
|
||||||
|
|
||||||
|
"Windows Desktop"="Windows Desktop"
|
||||||
|
|
||||||
|
[preset.0]
|
||||||
|
|
||||||
|
name="Windows Desktop"
|
||||||
|
platform="Windows Desktop"
|
||||||
|
dedicated_server=false
|
||||||
|
custom_features=""
|
||||||
|
export_filter="all_resources"
|
||||||
|
include_filter=""
|
||||||
|
exclude_filter=""
|
||||||
|
export_path="output/agame.exe"
|
||||||
|
patches=PackedStringArray()
|
||||||
|
patch_delta_encoding=false
|
||||||
|
patch_delta_compression_level_zstd=19
|
||||||
|
patch_delta_min_reduction=0.1
|
||||||
|
patch_delta_include_filters="*"
|
||||||
|
patch_delta_exclude_filters=""
|
||||||
|
encryption_include_filters=""
|
||||||
|
encryption_exclude_filters=""
|
||||||
|
seed=0
|
||||||
|
encrypt_pck=false
|
||||||
|
encrypt_directory=false
|
||||||
|
script_export_mode=2
|
||||||
|
|
||||||
|
[preset.0.options]
|
||||||
|
|
||||||
|
custom_template/debug=""
|
||||||
|
custom_template/release=""
|
||||||
|
debug/export_console_wrapper=0
|
||||||
|
binary_format/embed_pck=true
|
||||||
|
texture_format/s3tc_bptc=true
|
||||||
|
texture_format/etc2_astc=false
|
||||||
|
shader_baker/enabled=false
|
||||||
|
binary_format/architecture="x86_64"
|
||||||
|
codesign/enable=false
|
||||||
|
codesign/timestamp=true
|
||||||
|
codesign/timestamp_server_url=""
|
||||||
|
codesign/digest_algorithm=1
|
||||||
|
codesign/description=""
|
||||||
|
codesign/custom_options=PackedStringArray()
|
||||||
|
application/modify_resources=true
|
||||||
|
application/icon=""
|
||||||
|
application/console_wrapper_icon=""
|
||||||
|
application/icon_interpolation=4
|
||||||
|
application/file_version=""
|
||||||
|
application/product_version=""
|
||||||
|
application/company_name=""
|
||||||
|
application/product_name=""
|
||||||
|
application/file_description=""
|
||||||
|
application/copyright=""
|
||||||
|
application/trademarks=""
|
||||||
|
application/export_angle=0
|
||||||
|
application/export_d3d12=0
|
||||||
|
application/d3d12_agility_sdk_multiarch=true
|
||||||
|
ssh_remote_deploy/enabled=false
|
||||||
|
ssh_remote_deploy/host="user@host_ip"
|
||||||
|
ssh_remote_deploy/port="22"
|
||||||
|
ssh_remote_deploy/extra_args_ssh=""
|
||||||
|
ssh_remote_deploy/extra_args_scp=""
|
||||||
|
ssh_remote_deploy/run_script="Expand-Archive -LiteralPath '{temp_dir}\\{archive_name}' -DestinationPath '{temp_dir}'
|
||||||
|
$action = New-ScheduledTaskAction -Execute '{temp_dir}\\{exe_name}' -Argument '{cmd_args}'
|
||||||
|
$trigger = New-ScheduledTaskTrigger -Once -At 00:00
|
||||||
|
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
|
||||||
|
$task = New-ScheduledTask -Action $action -Trigger $trigger -Settings $settings
|
||||||
|
Register-ScheduledTask godot_remote_debug -InputObject $task -Force:$true
|
||||||
|
Start-ScheduledTask -TaskName godot_remote_debug
|
||||||
|
while (Get-ScheduledTask -TaskName godot_remote_debug | ? State -eq running) { Start-Sleep -Milliseconds 100 }
|
||||||
|
Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue"
|
||||||
|
ssh_remote_deploy/cleanup_script="Stop-ScheduledTask -TaskName godot_remote_debug -ErrorAction:SilentlyContinue
|
||||||
|
Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue
|
||||||
|
Remove-Item -Recurse -Force '{temp_dir}'"
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://qsdtdrh44ejn"
|
||||||
|
path="res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://icon.png"
|
||||||
|
dest_files=["res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
+5
-1
@@ -12,13 +12,17 @@ config_version=5
|
|||||||
|
|
||||||
config/name="agame"
|
config/name="agame"
|
||||||
config/features=PackedStringArray("4.7", "Forward Plus")
|
config/features=PackedStringArray("4.7", "Forward Plus")
|
||||||
config/icon="res://icon.svg"
|
config/icon="uid://qsdtdrh44ejn"
|
||||||
|
|
||||||
[display]
|
[display]
|
||||||
|
|
||||||
window/stretch/mode="canvas_items"
|
window/stretch/mode="canvas_items"
|
||||||
window/stretch/aspect="expand"
|
window/stretch/aspect="expand"
|
||||||
|
|
||||||
|
[editor_plugins]
|
||||||
|
|
||||||
|
enabled=PackedStringArray("res://addons/script-ide/plugin.cfg")
|
||||||
|
|
||||||
[physics]
|
[physics]
|
||||||
|
|
||||||
3d/physics_engine="Jolt Physics"
|
3d/physics_engine="Jolt Physics"
|
||||||
|
|||||||
Reference in New Issue
Block a user