Last active
February 26, 2023 14:13
-
-
Save Qubus0/f970ce9934aff6f3f51fe1093f2df56a to your computer and use it in GitHub Desktop.
Godot automatic brace completion for TextEdit (3.5)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
extends TextEdit | |
var last_text: String | |
var last_selection: TextSelection | |
var autobrace_pairs := { | |
"(": ")", | |
"{": "}", | |
"[": "]", | |
'"': '"', | |
"'": "'", | |
} | |
func _ready() -> void: | |
last_text = text | |
func _on_text_changed() -> void: | |
if cursor_get_column() < 1: | |
return | |
autobrace() | |
last_text = text | |
func _on_cursor_changed() -> void: | |
if get_selection_text().length() > 0: | |
last_selection = TextSelection.from_text_edit(self) | |
else: | |
last_selection = null | |
func autobrace() -> void: | |
var line := get_line(cursor_get_line()) | |
var char_before_cursor := "" | |
if cursor_get_column() > 0: | |
char_before_cursor = line[cursor_get_column()-1] | |
var char_after_cursor := "" | |
if cursor_get_column() < line.length(): | |
char_after_cursor = line[cursor_get_column()] | |
# When deleting, also delete the autobraced character | |
if Input.is_key_pressed(KEY_BACKSPACE): | |
if char_after_cursor in autobrace_pairs.values(): | |
var deleted_character := first_different_character(text, last_text) | |
if autobrace_pairs.has(deleted_character) and autobrace_pairs[deleted_character] == char_after_cursor: | |
delete_character_after_cursor() | |
# If we encounter a closing brace, "skip" over it | |
# Since the character is written already, just delete the next one | |
elif is_matching_closing_brace(char_before_cursor, char_after_cursor): | |
delete_character_after_cursor() | |
# If a character is in the autoclose dict, close it | |
elif char_before_cursor in autobrace_pairs.keys(): | |
var closing_char: String = autobrace_pairs[char_before_cursor] | |
var last_cursor_column := cursor_get_column() | |
if not last_selection: | |
insert_text_at_cursor(closing_char) | |
cursor_set_column(last_cursor_column) | |
return | |
# If there is a selection, surround that with the bracing characters | |
# Pressing the alt key moves the selection left by one character | |
if Input.is_key_pressed(KEY_ALT): | |
if last_cursor_column == last_selection.from_col +1: | |
# If selected right to left, it can be fixed by offsetting it right | |
select( | |
last_selection.from_line, last_selection.from_col +1, | |
last_selection.to_line, last_selection.to_col +1 | |
) | |
insert_text_at_cursor(last_selection.enclosed_text + closing_char) | |
cursor_set_column(last_selection.to_col +1) | |
else: | |
# If selected left to right, something else goes wrong as well, | |
# but it can be fixed by inserting the whole selection with braces | |
# and removing the leftover trailing brace behind it afterwards | |
insert_text_at_cursor(char_before_cursor + last_selection.enclosed_text + closing_char) | |
delete_character_after_cursor() | |
cursor_set_column(last_selection.to_col +1) | |
else: | |
insert_text_at_cursor(last_selection.enclosed_text + closing_char) | |
cursor_set_column(last_selection.to_col +1) | |
last_selection = null | |
func is_matching_closing_brace(new_character: String, char_after_cursor: String) -> bool: | |
if not new_character == char_after_cursor: | |
return false | |
# Opening and closing brace are the same -> "" | |
if char_after_cursor in autobrace_pairs.keys(): | |
return true | |
# Opening and closing brace are different -> () | |
if new_character in autobrace_pairs.values(): | |
return true | |
return false | |
func delete_character_after_cursor() -> void: | |
var line_text := get_line(cursor_get_line()) | |
var cursor_col := cursor_get_column() +1 | |
var text_length := len(line_text) | |
if cursor_col < 1 or cursor_col > text_length: | |
return | |
var left_text := line_text.substr(0, cursor_col - 1) | |
var right_text := line_text.substr(cursor_col, text_length - cursor_col) | |
set_line(cursor_get_line(), left_text + right_text) | |
cursor_set_column(cursor_col - 1) | |
func first_different_character(str1: String, str2: String) -> String: | |
var len1 := str1.length() | |
var len2 := str2.length() | |
if len1 == 0: | |
return str2[0] | |
if len2 == 0: | |
return str1[0] | |
for i in min(len1, len2): | |
if not str1[i] == str2[i]: | |
return str2[i] | |
return "" | |
class TextSelection: | |
var enclosed_text: String | |
var from_line: int | |
var from_col: int | |
var to_line: int | |
var to_col: int | |
func _init(p_enclosed_text: String, p_from_line: int, p_from_col: int, p_to_line: int, p_to_col: int) -> void: | |
enclosed_text = p_enclosed_text | |
from_line = p_from_line | |
from_col = p_from_col | |
to_line = p_to_line | |
to_col = p_to_col | |
static func from_text_edit(text_edit: TextEdit) -> TextSelection: | |
return TextSelection.new( | |
text_edit.get_selection_text(), | |
text_edit.get_selection_from_line(), text_edit.get_selection_from_column(), | |
text_edit.get_selection_to_line(), text_edit.get_selection_to_column() | |
) | |
func _to_string() -> String: | |
return "%s %s %s" % [ Vector2(from_line, from_col), enclosed_text, Vector2(to_line, to_col) ] | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment