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