Adding FormatOnSave
This commit is contained in:
parent
f8b8370183
commit
2e7c67b0ef
12 changed files with 383 additions and 0 deletions
21
Addons/SimpleFormatOnSave/LICENSE.txt
Normal file
21
Addons/SimpleFormatOnSave/LICENSE.txt
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025-present VitSoonYoung - <vitsoonyoung+simpleformatonsave@gmail.com>
|
||||||
|
|
||||||
|
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.
|
10
Addons/SimpleFormatOnSave/formatter.gd
Normal file
10
Addons/SimpleFormatOnSave/formatter.gd
Normal file
|
@ -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
|
1
Addons/SimpleFormatOnSave/formatter.gd.uid
Normal file
1
Addons/SimpleFormatOnSave/formatter.gd.uid
Normal file
|
@ -0,0 +1 @@
|
||||||
|
uid://clqnxlippxlm4
|
BIN
Addons/SimpleFormatOnSave/icon.png
Normal file
BIN
Addons/SimpleFormatOnSave/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 31 KiB |
40
Addons/SimpleFormatOnSave/icon.png.import
Normal file
40
Addons/SimpleFormatOnSave/icon.png.import
Normal file
|
@ -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
|
7
Addons/SimpleFormatOnSave/plugin.cfg
Normal file
7
Addons/SimpleFormatOnSave/plugin.cfg
Normal file
|
@ -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"
|
64
Addons/SimpleFormatOnSave/rules/blank_lines.gd
Normal file
64
Addons/SimpleFormatOnSave/rules/blank_lines.gd
Normal file
|
@ -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)
|
1
Addons/SimpleFormatOnSave/rules/blank_lines.gd.uid
Normal file
1
Addons/SimpleFormatOnSave/rules/blank_lines.gd.uid
Normal file
|
@ -0,0 +1 @@
|
||||||
|
uid://dr266abkfhg1o
|
155
Addons/SimpleFormatOnSave/rules/spacing.gd
Normal file
155
Addons/SimpleFormatOnSave/rules/spacing.gd
Normal file
|
@ -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'(?<!#)("""|\'\'\'|"|\')((?:.|\n)*?)\1')
|
||||||
|
|
||||||
|
var string_matches = string_regex.search_all(code)
|
||||||
|
var string_map = {}
|
||||||
|
|
||||||
|
for i in range(string_matches.size()):
|
||||||
|
var match = string_matches[i]
|
||||||
|
var original = match.get_string()
|
||||||
|
var placeholder = "__STRING__%d__" % i
|
||||||
|
string_map[placeholder] = original
|
||||||
|
code = _replace(code, original, placeholder)
|
||||||
|
|
||||||
|
var comment_regex = RegEx.new()
|
||||||
|
comment_regex.compile("#.*")
|
||||||
|
var comment_matches = comment_regex.search_all(code)
|
||||||
|
var comment_map = {}
|
||||||
|
|
||||||
|
for i in range(comment_matches.size()):
|
||||||
|
var match = comment_matches[i]
|
||||||
|
var original = match.get_string()
|
||||||
|
var placeholder = "__COMMENT__%d__" % i
|
||||||
|
comment_map[placeholder] = original
|
||||||
|
code = _replace(code, original, placeholder)
|
||||||
|
|
||||||
|
var ref_regex = RegEx.new()
|
||||||
|
ref_regex.compile(r"\$[^.]*")
|
||||||
|
var ref_matches = ref_regex.search_all(code)
|
||||||
|
var ref_map = {}
|
||||||
|
|
||||||
|
for i in range(ref_matches.size()):
|
||||||
|
var match = ref_matches[i]
|
||||||
|
var original = match.get_string()
|
||||||
|
var placeholder = "__REF__%d__" % i
|
||||||
|
ref_map[placeholder] = original
|
||||||
|
code = _replace(code, original, placeholder)
|
||||||
|
|
||||||
|
code = _format_operators_and_commas(code)
|
||||||
|
|
||||||
|
for placeholder in ref_map:
|
||||||
|
code = code.replace(placeholder, ref_map[placeholder])
|
||||||
|
|
||||||
|
for placeholder in comment_map:
|
||||||
|
code = code.replace(placeholder, comment_map[placeholder])
|
||||||
|
|
||||||
|
for placeholder in string_map:
|
||||||
|
code = code.replace(placeholder, string_map[placeholder])
|
||||||
|
|
||||||
|
return code
|
||||||
|
|
||||||
|
|
||||||
|
static func _format_operators_and_commas(code: String) -> 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
|
1
Addons/SimpleFormatOnSave/rules/spacing.gd.uid
Normal file
1
Addons/SimpleFormatOnSave/rules/spacing.gd.uid
Normal file
|
@ -0,0 +1 @@
|
||||||
|
uid://wfodrj6vpfb6
|
82
Addons/SimpleFormatOnSave/simple_format_on_save.gd
Normal file
82
Addons/SimpleFormatOnSave/simple_format_on_save.gd
Normal file
|
@ -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]")
|
1
Addons/SimpleFormatOnSave/simple_format_on_save.gd.uid
Normal file
1
Addons/SimpleFormatOnSave/simple_format_on_save.gd.uid
Normal file
|
@ -0,0 +1 @@
|
||||||
|
uid://b6d7va37k5131
|
Loading…
Reference in a new issue