From 0fed470d77993f214f1456127ac879a11e62c74e Mon Sep 17 00:00:00 2001 From: Eduardo Date: Tue, 6 Feb 2024 01:33:14 +0100 Subject: [PATCH] gdscript formater --- .../gdscript_formatter/format_preference.tres | 29 ++ .../gdscript_formatter/format_shortcut.tres | 10 + .../gdscript_formatter/gdscript_formatter.gd | 286 ++++++++++++++++++ 3 files changed, 325 insertions(+) create mode 100644 addons/gdscript_formatter/format_preference.tres create mode 100644 addons/gdscript_formatter/format_shortcut.tres create mode 100644 addons/gdscript_formatter/gdscript_formatter.gd diff --git a/addons/gdscript_formatter/format_preference.tres b/addons/gdscript_formatter/format_preference.tres new file mode 100644 index 0000000..324d022 --- /dev/null +++ b/addons/gdscript_formatter/format_preference.tres @@ -0,0 +1,29 @@ +[gd_resource type="Resource" load_steps=2 format=3 uid="uid://bkxyatcbwyv53"] + +[sub_resource type="GDScript" id="GDScript_10tgm"] +script/source = "@tool +extends Resource +## How many characters per line to allow. +@export var line_length := 100: + set(v): + line_length = v + emit_changed() + +## If true, will skip safety checks. +@export var fast_but_unsafe := false: + set(v): + fast_but_unsafe = v + emit_changed() + +## If true, will format on save. +@export var format_on_save := false: + set(v): + format_on_save = v + emit_changed() +" + +[resource] +script = SubResource("GDScript_10tgm") +line_length = 100 +fast_but_unsafe = false +format_on_save = false diff --git a/addons/gdscript_formatter/format_shortcut.tres b/addons/gdscript_formatter/format_shortcut.tres new file mode 100644 index 0000000..f0fe107 --- /dev/null +++ b/addons/gdscript_formatter/format_shortcut.tres @@ -0,0 +1,10 @@ +[gd_resource type="Shortcut" load_steps=2 format=3 uid="uid://d3djianudl3ns"] + +[sub_resource type="InputEventKey" id="InputEventKey_7d7c7"] +alt_pressed = true +shift_pressed = true +pressed = true +keycode = 70 + +[resource] +events = [SubResource("InputEventKey_7d7c7")] diff --git a/addons/gdscript_formatter/gdscript_formatter.gd b/addons/gdscript_formatter/gdscript_formatter.gd new file mode 100644 index 0000000..016ab6b --- /dev/null +++ b/addons/gdscript_formatter/gdscript_formatter.gd @@ -0,0 +1,286 @@ +@tool +extends EditorPlugin + +var _preference: Resource +var _shortcut: Shortcut +var _has_format_tool_item: bool = false +var _install_task_id: int = -1 +var _connection_list: Array[Resource] = [] + + +func _init() -> void: + var shortcur_res_file := (get_script() as Resource).resource_path.get_base_dir().path_join( + "format_shortcut.tres" + ) + if FileAccess.file_exists(shortcur_res_file): + _shortcut = load(shortcur_res_file) + if not is_instance_valid(_shortcut): + var default_shortcut := InputEventKey.new() + default_shortcut.echo = false + default_shortcut.pressed = true + default_shortcut.keycode = KEY_F + default_shortcut.shift_pressed = true + default_shortcut.alt_pressed = true + + _shortcut = Shortcut.new() + _shortcut.events.push_back(default_shortcut) + ResourceSaver.save(_shortcut, shortcur_res_file) + + _shortcut.changed.connect(update_shortcut) + + var preference_res_file = shortcur_res_file.get_base_dir().path_join("format_preference.tres") + if FileAccess.file_exists(preference_res_file): + _preference = load(preference_res_file) + if not is_instance_valid(_preference): + _preference = Resource.new() + var script = GDScript.new() + script.source_code = """@tool +extends Resource +## How many characters per line to allow. +@export var line_length := 100: + set(v): + line_length = v + emit_changed() + +## If true, will skip safety checks. +@export var fast_but_unsafe := false: + set(v): + fast_but_unsafe = v + emit_changed() + +## If true, will format on save. +@export var format_on_save := false: + set(v): + format_on_save = v + emit_changed() +""" + _preference.set_script(script) + ResourceSaver.save(_preference, preference_res_file) + + _preference.changed.connect(_on_preference_changed) + _on_preference_changed() + + +func _enter_tree() -> void: + if not _has_command("gdformat"): + print_rich( + '[color=yellow]GDScript Formatter: the command "gdformat" can\'t be found in your envrionment.[/color]' + ) + else: + _add_format_tool_item() + + if not _has_command("pip"): + print_rich( + '[color=yellow]Installs gdtoolkit is required "pip".\n\t Please install it and ensure it can be found inyour envrionment.[/color]' + ) + return + EditorInterface.get_command_palette().add_command( + "Format GDScript", + "GDScript Formatter/Format GDScript", + Callable(self, "format_script"), + _shortcut.get_as_text() + ) + add_tool_menu_item("GDScriptFormatter: Install/Update gdtoolkit", install_or_update_gdtoolkit) + update_shortcut() + + +func _exit_tree() -> void: + ( + EditorInterface + . get_command_palette() + . remove_command( + "GDScript Formatter/Format GDScript", + ) + ) + remove_tool_menu_item("GDScriptFormatter: Install/Update gdtoolkit") + if _has_format_tool_item: + remove_tool_menu_item("GDScriptFormatter: Format script") + + +func _shortcut_input(event: InputEvent) -> void: + if _shortcut.matches_event(event) and event.is_pressed() and not event.is_echo(): + if format_script(): + get_tree().root.set_input_as_handled() + + +func format_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 := [] + if not _format_code(code_edit.text, formatted): + printerr("Format GDScript failed: ", current_script.resource_path) + return false + + _reload_code_edit(code_edit, formatted.back()) + return true + + +func install_or_update_gdtoolkit() -> void: + if _install_task_id >= 0: + print_rich("Already installing or updating gdformat, please be patient.") + return + if not _has_command("pip"): + printerr( + "Install GDScript Formatter Failed: pip is required, please ensure it can be found in your environment." + ) + return + _install_task_id = WorkerThreadPool.add_task( + _install_or_update_gdtoolkit, true, "Install or update gdtoolkit." + ) + while _install_task_id >= 0: + if not WorkerThreadPool.is_task_completed(_install_task_id): + await get_tree().process_frame + else: + _install_task_id = -1 + + +func update_shortcut() -> void: + for obj in _connection_list: + obj.changed.disconnect(update_shortcut) + + _connection_list.clear() + + for event: InputEvent in _shortcut.events: + if is_instance_valid(event): + event.changed.connect(update_shortcut) + _connection_list.push_back(event) + + ( + EditorInterface + . get_command_palette() + . remove_command( + "GDScript Formatter/Format GDScript", + ) + ) + + EditorInterface.get_command_palette().add_command( + "Format GDScript", + "GDScript Formatter/Format GDScript", + Callable(self, "format_script"), + _shortcut.get_as_text() + ) + + +# + + +func _on_preference_changed() -> void: + if _preference.format_on_save and not resource_saved.is_connected(_on_resource_saved): + resource_saved.connect(_on_resource_saved) + elif not _preference.format_on_save and resource_saved.is_connected(_on_resource_saved): + resource_saved.disconnect(_on_resource_saved) + + +func _on_resource_saved(resource: Resource) -> void: + var gds := resource as GDScript + if not _has_format_tool_item or not is_instance_valid(gds): + return + + var script_editor := get_editor_interface().get_script_editor() + var open_script_editors := script_editor.get_open_script_editors() + var open_scripts := script_editor.get_open_scripts() + + if open_scripts.size() != open_script_editors.size(): + printerr("Format on save failed: due to engine bug. Please report an issue.") + return + + var formatted := [] + if not _format_code(gds.source_code, formatted): + printerr("Format GDScript failed: ", gds.resource_path) + return + + gds.source_code = formatted.back() + ResourceSaver.save(gds) + gds.reload() + + var code_edit: CodeEdit + for i in range(open_scripts.size()): + if open_scripts[i] == gds: + _reload_code_edit(open_script_editors[i].get_base_editor(), formatted.back(), true) + return + + +# +func _install_or_update_gdtoolkit(): + var has_gdformat = _has_command("gdformat") + if has_gdformat: + print("-- Begin update gdtoolkit.") + else: + print("-- Begin install gdtoolkit.") + var output := [] + var err := OS.execute("pip3", ["install", "gdtoolkit"], output) + if err == OK: + if has_gdformat: + print("-- Update gdtoolkit successfully.") + else: + print("-- Install gdtoolkit successfully.") + if not _has_format_tool_item: + _add_format_tool_item() + else: + if has_gdformat: + printerr("-- Update gdtoolkit failed, exit code: ", err) + else: + printerr("-- Install gdtoolkit failed, exit code: ", err) + printerr("\tPlease check below for more details.") + print("\n".join(output)) + + +func _add_format_tool_item() -> void: + add_tool_menu_item("GDScriptFormatter: Format script", format_script) + _has_format_tool_item = true + + +func _has_command(command: String) -> bool: + var output := [] + var err := OS.execute(command, ["--version"], output) + return err == OK + + +func _reload_code_edit(code_edit: CodeEdit, new_text: String, tag_saved: bool = false) -> void: + var column := code_edit.get_caret_column() + var line := code_edit.get_caret_line() + var scroll_hor := code_edit.scroll_horizontal + var scroll_ver := code_edit.scroll_vertical + + code_edit.text = new_text + if tag_saved: + code_edit.tag_saved_version() + + code_edit.set_caret_column(column) + code_edit.set_caret_line(line) + code_edit.scroll_horizontal = scroll_hor + code_edit.scroll_vertical = scroll_ver + + +func _format_code(code: String, formated: Array) -> bool: + const tmp_file = "res://addons/gdscript_formatter/.tmp.gd" + var f = FileAccess.open(tmp_file, FileAccess.WRITE) + if not is_instance_valid(f): + printerr("GDScript Formatter Error: can't create tmp file.") + return false + f.store_string(code) + f.close() + + var output := [] + var args := [ + ProjectSettings.globalize_path(tmp_file), "--line-length=%d" % _preference.line_length + ] + if _preference.fast_but_unsafe: + args.push_back("--fast") + var err = OS.execute("gdformat", args, output) + if err == OK: + f = FileAccess.open(tmp_file, FileAccess.READ) + formated.push_back(f.get_as_text()) + f.close() + else: + printerr("\tExit code: ", err) + + DirAccess.remove_absolute(tmp_file) + return err == OK -- 2.30.2