xiandie/addons/dialogue_manager/components/code_edit_syntax_highlighter.gd
2025-06-16 16:40:11 +08:00

232 lines
9.5 KiB
GDScript

@tool
class_name DMSyntaxHighlighter extends SyntaxHighlighter
var regex: DMCompilerRegEx = DMCompilerRegEx.new()
var compilation: DMCompilation = DMCompilation.new()
var expression_parser = DMExpressionParser.new()
var cache: Dictionary = {}
func _clear_highlighting_cache() -> void:
cache.clear()
func _get_line_syntax_highlighting(line: int) -> Dictionary:
expression_parser.include_comments = true
var colors: Dictionary = {}
var text_edit: TextEdit = get_text_edit()
var text: String = text_edit.get_line(line)
# Prevent an error from popping up while developing
if not is_instance_valid(text_edit) or text_edit.theme_overrides.is_empty():
return colors
# Disable this, as well as the line at the bottom of this function to remove the cache.
if text in cache:
return cache[text]
var theme: Dictionary = text_edit.theme_overrides
var index: int = 0
match DMCompiler.get_line_type(text):
DMConstants.TYPE_USING:
colors[index] = { color = theme.conditions_color }
colors[index + "using ".length()] = { color = theme.text_color }
DMConstants.TYPE_IMPORT:
colors[index] = { color = theme.conditions_color }
var import: RegExMatch = regex.IMPORT_REGEX.search(text)
if import:
colors[index + import.get_start("path") - 1] = { color = theme.strings_color }
colors[index + import.get_end("path") + 1] = { color = theme.conditions_color }
colors[index + import.get_start("prefix")] = { color = theme.text_color }
DMConstants.TYPE_COMMENT:
colors[index] = { color = theme.comments_color }
DMConstants.TYPE_TITLE:
colors[index] = { color = theme.titles_color }
DMConstants.TYPE_CONDITION, DMConstants.TYPE_WHILE, DMConstants.TYPE_MATCH, DMConstants.TYPE_WHEN:
colors[0] = { color = theme.conditions_color }
index = text.find(" ")
if index > -1:
var expression: Array = expression_parser.tokenise(text.substr(index), DMConstants.TYPE_CONDITION, 0)
if expression.size() == 0:
colors[index] = { color = theme.critical_color }
else:
_highlight_expression(expression, colors, index)
DMConstants.TYPE_MUTATION:
colors[0] = { color = theme.mutations_color }
index = text.find(" ")
var expression: Array = expression_parser.tokenise(text.substr(index), DMConstants.TYPE_MUTATION, 0)
if expression.size() == 0:
colors[index] = { color = theme.critical_color }
else:
_highlight_expression(expression, colors, index)
DMConstants.TYPE_GOTO:
if text.strip_edges().begins_with("%"):
colors[index] = { color = theme.symbols_color }
index = text.find(" ")
_highlight_goto(text, colors, index)
DMConstants.TYPE_RANDOM:
colors[index] = { color = theme.symbols_color }
DMConstants.TYPE_DIALOGUE, DMConstants.TYPE_RESPONSE:
if text.strip_edges().begins_with("%"):
colors[index] = { color = theme.symbols_color }
index = text.find(" ", text.find("%"))
colors[index] = { color = theme.text_color.lerp(theme.symbols_color, 0.5) }
var dialogue_text: String = text.substr(index, text.find("=>"))
# Highlight character name (but ignore ":" within line ID reference)
var split_index: int = dialogue_text.replace("\\:", "??").find(":")
if text.substr(split_index - 3, 3) != "[ID":
colors[index + split_index + 1] = { color = theme.text_color }
else:
# If there's no character name then just highlight the text as dialogue.
colors[index] = { color = theme.text_color }
# Interpolation
var replacements: Array[RegExMatch] = regex.REPLACEMENTS_REGEX.search_all(dialogue_text)
for replacement: RegExMatch in replacements:
var expression_text: String = replacement.get_string().substr(0, replacement.get_string().length() - 2).substr(2)
var expression: Array = expression_parser.tokenise(expression_text, DMConstants.TYPE_MUTATION, replacement.get_start())
var expression_index: int = index + replacement.get_start()
colors[expression_index] = { color = theme.symbols_color }
if expression.size() == 0 or expression[0].type == DMConstants.TYPE_ERROR:
colors[expression_index] = { color = theme.critical_color }
else:
_highlight_expression(expression, colors, index + 2)
colors[expression_index + expression_text.length() + 2] = { color = theme.symbols_color }
colors[expression_index + expression_text.length() + 4] = { color = theme.text_color }
# Tags (and inline mutations)
var resolved_line_data: DMResolvedLineData = DMResolvedLineData.new("")
var bbcodes: Array[Dictionary] = resolved_line_data.find_bbcode_positions_in_string(dialogue_text, true, true)
for bbcode: Dictionary in bbcodes:
var tag: String = bbcode.code
var code: String = bbcode.raw_args
if code.begins_with("["):
colors[index + bbcode.start] = { color = theme.symbols_color }
colors[index + bbcode.start + 2] = { color = theme.text_color }
var pipe_cursor: int = code.find("|")
while pipe_cursor > -1:
colors[index + bbcode.start + pipe_cursor + 1] = { color = theme.symbols_color }
colors[index + bbcode.start + pipe_cursor + 2] = { color = theme.text_color }
pipe_cursor = code.find("|", pipe_cursor + 1)
colors[index + bbcode.end - 1] = { color = theme.symbols_color }
colors[index + bbcode.end + 1] = { color = theme.text_color }
else:
colors[index + bbcode.start] = { color = theme.symbols_color }
if tag.begins_with("do") or tag.begins_with("set") or tag.begins_with("if"):
if tag.begins_with("if"):
colors[index + bbcode.start + 1] = { color = theme.conditions_color }
else:
colors[index + bbcode.start + 1] = { color = theme.mutations_color }
var expression: Array = expression_parser.tokenise(code, DMConstants.TYPE_MUTATION, bbcode.start + bbcode.code.length())
if expression.size() == 0 or expression[0].type == DMConstants.TYPE_ERROR:
colors[index + bbcode.start + tag.length() + 1] = { color = theme.critical_color }
else:
_highlight_expression(expression, colors, index + 2)
# else and closing if have no expression
elif tag.begins_with("else") or tag.begins_with("/if"):
colors[index + bbcode.start + 1] = { color = theme.conditions_color }
colors[index + bbcode.end] = { color = theme.symbols_color }
colors[index + bbcode.end + 1] = { color = theme.text_color }
# Jumps
if "=> " in text or "=>< " in text:
_highlight_goto(text, colors, index)
# Order the dictionary keys to prevent CodeEdit from having issues
var ordered_colors: Dictionary = {}
var ordered_keys: Array = colors.keys()
ordered_keys.sort()
for key_index: int in ordered_keys:
ordered_colors[key_index] = colors[key_index]
cache[text] = ordered_colors
return ordered_colors
func _highlight_expression(tokens: Array, colors: Dictionary, index: int) -> int:
var theme: Dictionary = get_text_edit().theme_overrides
var last_index: int = index
for token: Dictionary in tokens:
last_index = token.i
match token.type:
DMConstants.TOKEN_COMMENT:
colors[index + token.i] = { color = theme.comments_color }
DMConstants.TOKEN_CONDITION, DMConstants.TOKEN_AND_OR:
colors[index + token.i] = { color = theme.conditions_color }
DMConstants.TOKEN_VARIABLE:
if token.value in ["true", "false"]:
colors[index + token.i] = { color = theme.conditions_color }
else:
colors[index + token.i] = { color = theme.members_color }
DMConstants.TOKEN_OPERATOR, DMConstants.TOKEN_COLON, \
DMConstants.TOKEN_COMMA, DMConstants.TOKEN_DOT, DMConstants.TOKEN_NULL_COALESCE, \
DMConstants.TOKEN_NUMBER, DMConstants.TOKEN_ASSIGNMENT:
colors[index + token.i] = { color = theme.symbols_color }
DMConstants.TOKEN_STRING:
colors[index + token.i] = { color = theme.strings_color }
DMConstants.TOKEN_FUNCTION:
colors[index + token.i] = { color = theme.mutations_color }
colors[index + token.i + token.function.length()] = { color = theme.symbols_color }
for parameter: Array in token.value:
last_index = _highlight_expression(parameter, colors, index)
DMConstants.TOKEN_PARENS_CLOSE:
colors[index + token.i] = { color = theme.symbols_color }
DMConstants.TOKEN_DICTIONARY_REFERENCE:
colors[index + token.i] = { color = theme.members_color }
colors[index + token.i + token.variable.length()] = { color = theme.symbols_color }
last_index = _highlight_expression(token.value, colors, index)
DMConstants.TOKEN_ARRAY:
colors[index + token.i] = { color = theme.symbols_color }
for item: Array in token.value:
last_index = _highlight_expression(item, colors, index)
DMConstants.TOKEN_BRACKET_CLOSE:
colors[index + token.i] = { color = theme.symbols_color }
DMConstants.TOKEN_DICTIONARY:
colors[index + token.i] = { color = theme.symbols_color }
last_index = _highlight_expression(token.value.keys() + token.value.values(), colors, index)
DMConstants.TOKEN_BRACE_CLOSE:
colors[index + token.i] = { color = theme.symbols_color }
last_index += 1
DMConstants.TOKEN_GROUP:
last_index = _highlight_expression(token.value, colors, index)
return last_index
func _highlight_goto(text: String, colors: Dictionary, index: int) -> int:
var theme: Dictionary = get_text_edit().theme_overrides
var goto_data: DMResolvedGotoData = DMResolvedGotoData.new(text, {})
colors[goto_data.index] = { color = theme.jumps_color }
if "{{" in text:
index = text.find("{{", goto_data.index)
var last_index: int = 0
if goto_data.error:
colors[index + 2] = { color = theme.critical_color }
else:
last_index = _highlight_expression(goto_data.expression, colors, index)
index = text.find("}}", index + last_index)
colors[index] = { color = theme.jumps_color }
return index