diff --git a/Addons/SimpleFormatOnSave/LICENSE.txt b/Addons/SimpleFormatOnSave/LICENSE.txt new file mode 100644 index 0000000..933cadc --- /dev/null +++ b/Addons/SimpleFormatOnSave/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025-present VitSoonYoung - + +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. diff --git a/Addons/SimpleFormatOnSave/formatter.gd b/Addons/SimpleFormatOnSave/formatter.gd new file mode 100644 index 0000000..dbde166 --- /dev/null +++ b/Addons/SimpleFormatOnSave/formatter.gd @@ -0,0 +1,10 @@ +class_name Formatter + +const RuleSpacing = preload("./rules/spacing.gd") +const RuleBlankLines = preload("./rules/blank_lines.gd") + + +func format_code(code: String) -> String: + code = RuleSpacing.apply(code) + code = RuleBlankLines.apply(code) + return code diff --git a/Addons/SimpleFormatOnSave/formatter.gd.uid b/Addons/SimpleFormatOnSave/formatter.gd.uid new file mode 100644 index 0000000..b666797 --- /dev/null +++ b/Addons/SimpleFormatOnSave/formatter.gd.uid @@ -0,0 +1 @@ +uid://clqnxlippxlm4 diff --git a/Addons/SimpleFormatOnSave/icon.png b/Addons/SimpleFormatOnSave/icon.png new file mode 100644 index 0000000..909a177 Binary files /dev/null and b/Addons/SimpleFormatOnSave/icon.png differ diff --git a/Addons/SimpleFormatOnSave/icon.png.import b/Addons/SimpleFormatOnSave/icon.png.import new file mode 100644 index 0000000..0249f50 --- /dev/null +++ b/Addons/SimpleFormatOnSave/icon.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cv42njjkdhl2l" +path="res://.godot/imported/icon.png-c0076118964b028971c5407cf9f25f2c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Addons/SimpleFormatOnSave/icon.png" +dest_files=["res://.godot/imported/icon.png-c0076118964b028971c5407cf9f25f2c.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 diff --git a/Addons/SimpleFormatOnSave/plugin.cfg b/Addons/SimpleFormatOnSave/plugin.cfg new file mode 100644 index 0000000..d7d61a9 --- /dev/null +++ b/Addons/SimpleFormatOnSave/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="Simple Format On Save" +description="Combination of simple gdscript formatter & Format On Save with extra rules for beatiful formatting...maybe" +author="VitSoonYoung" +version="0.1" +script="simple_format_on_save.gd" diff --git a/Addons/SimpleFormatOnSave/rules/blank_lines.gd b/Addons/SimpleFormatOnSave/rules/blank_lines.gd new file mode 100644 index 0000000..4107400 --- /dev/null +++ b/Addons/SimpleFormatOnSave/rules/blank_lines.gd @@ -0,0 +1,64 @@ +class_name RuleBlankLines + +var FORMAT_ACTION := "simple_format_on_save/format_code" +var format_key: InputEventKey + + +static func apply(code: String) -> String: + var trim1_regex = RegEx.create_from_string("\n{2,}") + code = trim1_regex.sub(code, "\n\n", true) + code = _blank_for_func_class(code) + var trim2_regex = RegEx.create_from_string("\n{3,}") + code = trim2_regex.sub(code, "\n\n\n", true) + return code + + +static func _blank_for_func_class(code: String) -> String: + var assignment_regex = RegEx.create_from_string(r".*=.*") + var statement_regex = RegEx.create_from_string(r"\s+(if|for|while|match)[\s|\(].*") + var misc_statement_regex = RegEx.create_from_string(r"\s+(else|elif|\}|\]).*") + var func_class_regex = RegEx.create_from_string(r".*(func|class) .*") + var comment_line_regex = RegEx.create_from_string(r"^\s*#") + var empty_line_regex = RegEx.create_from_string(r"^\s+$") + var lines := code.split('\n') + var modified_lines: Array[String] = [] + + for line: String in lines: + # Spaces between functions & classes + if func_class_regex.search(line): + if modified_lines.size() > 0: + var i := modified_lines.size() - 1 + + while i > 0 and comment_line_regex.search(modified_lines[i]): + i -= 1 + + if i == 0: + modified_lines.append(line) + continue + + modified_lines.insert(i + 1, "") + modified_lines.insert(i + 1, "") + + # 1 space between assignment & statement + if statement_regex.search(line): + if modified_lines.size() > 0: + var i := modified_lines.size() - 1 + + if assignment_regex.search(modified_lines[i]) and not statement_regex.search(modified_lines[i]) and not statement_regex.search(line): + modified_lines.insert(i + 1, "") + else: + pass + + # Space after a code block (Doesn't work with spaces for now) + var indent_count := line.count("\t") + + if indent_count and not misc_statement_regex.search(line) and not statement_regex.search(line): + if modified_lines.size() > 0: + var i := modified_lines.size() - 1 + + if modified_lines[i].count("\t") > indent_count: + modified_lines.insert(i + 1, "") + + modified_lines.append(line) + + return "\n".join(modified_lines) diff --git a/Addons/SimpleFormatOnSave/rules/blank_lines.gd.uid b/Addons/SimpleFormatOnSave/rules/blank_lines.gd.uid new file mode 100644 index 0000000..452523a --- /dev/null +++ b/Addons/SimpleFormatOnSave/rules/blank_lines.gd.uid @@ -0,0 +1 @@ +uid://dr266abkfhg1o diff --git a/Addons/SimpleFormatOnSave/rules/spacing.gd b/Addons/SimpleFormatOnSave/rules/spacing.gd new file mode 100644 index 0000000..e0326f2 --- /dev/null +++ b/Addons/SimpleFormatOnSave/rules/spacing.gd @@ -0,0 +1,155 @@ +class_name RuleSpacing + +const SYMBOLS = [ + r"\*\*=", + r"\*\*", + "<<=", + ">>=", + "<<", + ">>", + "==", + "!=", + ">=", + "<=", + "&&", + r"\|\|", + r"\+=", + "-=", + r"\*=", + "/=", + "%=", + "&=", + r"\^=", + r"\|=", + "~=", + ":=", + "->", + r"&", + r"\|", + r"\^", + "-", + r"\+", + "/", + r"\*", + ">", + "<", + "-", + "%", + "=", + ":", + ",", +]; +const KEYWORDS = [ + "and", + "is", + "or", + "not", +] + + +static func apply(code: String) -> String: + var string_regex = RegEx.new() + string_regex.compile(r'(? String: + var indent_regex = RegEx.create_from_string(r"^\s{4}") + var new_code = indent_regex.sub(code, "\t", true) + + while (code != new_code): + code = new_code + new_code = indent_regex.sub(code, "\t", true) + + var symbols_regex = "(" + ") | (".join(SYMBOLS) + ")" + symbols_regex = " * ?(" + symbols_regex + ") * " + var symbols_operator_regex = RegEx.create_from_string(symbols_regex) + code = symbols_operator_regex.sub(code, " $1 ", true) + + # ": =" => ":=" + code = RegEx.create_from_string(r": *=").sub(code, ":=", true) + + # "a(" => "a (" + code = RegEx.create_from_string(r"(?<=[\w\)\]]) *([\(:,])(?!=)").sub(code, "$1", true) + + # "( a" => "(a" + code = RegEx.create_from_string(r"([\(\{}]) *").sub(code, "$1", true) + + # "a )" => "a)" + code = RegEx.create_from_string(r" *([\)\}])").sub(code, "$1", true) + + # "if(" => "if (" + code = RegEx.create_from_string(r"\b(if|for|while|switch|match)\(").sub(code, "$1 (", true) + + var keywoisrd_regex = r"|".join(KEYWORDS) + var keyword_operator_regex = RegEx.create_from_string(r"(?<=[ \)\]])(" + keywoisrd_regex + r")(?=[ \(\[])") + code = keyword_operator_regex.sub(code, " $1 ", true) + + # tab "a\t=" => "a =" + code = RegEx.create_from_string(r"(\t*. * ?)\t * ").sub(code, "$1", true) + + #trim + code = RegEx.create_from_string("[ \t]*\n").sub(code, "\n", true) + + # " " => " " + code = RegEx.create_from_string(" +").sub(code, " ", true) + + # "= -a" => "= -a" + code = RegEx.create_from_string(r"([=,(] ?)- ").sub(code, "$1-", true) + + return code + + +static func _replace(text: String, what: String, forwhat: String) -> String: + var index := text.find(what) + + if index != -1: + text = text.substr(0, index) + forwhat + text.substr(index + what.length()) + + return text diff --git a/Addons/SimpleFormatOnSave/rules/spacing.gd.uid b/Addons/SimpleFormatOnSave/rules/spacing.gd.uid new file mode 100644 index 0000000..5b6c307 --- /dev/null +++ b/Addons/SimpleFormatOnSave/rules/spacing.gd.uid @@ -0,0 +1 @@ +uid://wfodrj6vpfb6 diff --git a/Addons/SimpleFormatOnSave/simple_format_on_save.gd b/Addons/SimpleFormatOnSave/simple_format_on_save.gd new file mode 100644 index 0000000..2164754 --- /dev/null +++ b/Addons/SimpleFormatOnSave/simple_format_on_save.gd @@ -0,0 +1,82 @@ +@tool +extends EditorPlugin + +var FORMAT_ACTION := "simple_format_on_save/format_code" +var format_key: InputEventKey +var formatter: Formatter + + +func _enter_tree(): + add_tool_menu_item("Format (Ctrl+Alt+L)", _on_format_code) + + if InputMap.has_action(FORMAT_ACTION): + InputMap.erase_action(FORMAT_ACTION) + + InputMap.add_action(FORMAT_ACTION) + format_key = InputEventKey.new() + format_key.keycode = KEY_L + format_key.ctrl_pressed = true + format_key.alt_pressed = true + InputMap.action_add_event(FORMAT_ACTION, format_key) + resource_saved.connect(on_resource_saved) + + +func _exit_tree(): + remove_tool_menu_item("Format (Ctrl+Alt+L)") + InputMap.erase_action(FORMAT_ACTION) + resource_saved.disconnect(on_resource_saved) + + +# Return true if formatted code != original code +func _on_format_code() -> bool: + var current_editor := EditorInterface.get_script_editor().get_current_editor() + + if not (current_editor and current_editor.is_class("ScriptTextEditor")): + return false + + var text_edit = current_editor.get_base_editor() + var code = text_edit.text + + if not formatter: + formatter = Formatter.new() + + var formatted_code = formatter.format_code(code) + + if not formatted_code: + return false + + if code.length() == formatted_code.length() and code == formatted_code: + return false + + var scroll_horizontal = text_edit.scroll_horizontal + var scroll_vertical = text_edit.scroll_vertical + var caret_column = text_edit.get_caret_column(0) + var caret_line = text_edit.get_caret_line(0) + + text_edit.text = formatted_code + text_edit.set_caret_line(caret_line) + text_edit.set_caret_column(caret_column) + text_edit.scroll_horizontal = scroll_horizontal + text_edit.scroll_vertical = scroll_vertical + + return true + + +func _shortcut_input(event: InputEvent) -> void: + if event is InputEventKey and event.is_pressed(): + if Input.is_action_pressed(FORMAT_ACTION): + _on_format_code() + + +# CALLED WHEN A SCRIPT IS SAVED +func on_resource_saved(resource: Resource): + if resource is Script: + var script: Script = resource + var current_script = get_editor_interface().get_script_editor().get_current_script() + + # Prevents other unsaved scripts from overwriting the active one + if current_script == script: + var is_modified: bool = _on_format_code() + + #if is_modified: + #print_rich("[color=#636363]Auto formatted code[/color]") diff --git a/Addons/SimpleFormatOnSave/simple_format_on_save.gd.uid b/Addons/SimpleFormatOnSave/simple_format_on_save.gd.uid new file mode 100644 index 0000000..a8e28ce --- /dev/null +++ b/Addons/SimpleFormatOnSave/simple_format_on_save.gd.uid @@ -0,0 +1 @@ +uid://b6d7va37k5131