@tool class_name LuaSyntaxHighlighter extends SyntaxHighlighter @export_group("Colors") @export var font_color: Color = Color("#d4d4d4", Color.WHITE) @export var keyword_color: Color = Color.from_string("#c586c0", Color.WHITE) @export var gurt_globals_color: Color = Color.from_string("#569cd6", Color.WHITE) @export var function_color: Color = Color.from_string("#dcdcaa", Color.WHITE) @export var member_color: Color = Color.from_string("#9cdcfe", Color.WHITE) @export var number_color: Color = Color.from_string("#b5cea8", Color.WHITE) @export var string_color: Color = Color.from_string("#ce9178", Color.WHITE) @export var comment_color: Color = Color.from_string("#6a9955", Color.WHITE) @export var symbol_color: Color = Color.from_string("#c586c0", Color.WHITE) enum State { DEFAULT, IN_MULTILINE_COMMENT, IN_MULTILINE_STRING } var _state_cache: Dictionary = {} var _keywords: Dictionary = {} var _built_in_functions: Dictionary = {} var _gurt_globals: Dictionary = {} var _member_keywords: Dictionary = {} func _init() -> void: for k in ["and", "break", "do", "else", "elseif", "end", "false", "for", "function", "if", "in", "local", "nil", "not", "or", "repeat", "return", "then", "true", "until", "while"]: _keywords[k] = true for f in ["assert", "collectgarbage", "dofile", "error", "getmetatable", "ipairs", "load", "loadfile", "next", "pairs", "pcall", "print", "rawequal", "rawget", "rawlen", "rawset", "require", "select", "setmetatable", "tonumber", "tostring", "type", "xpcall"]: _built_in_functions[f] = true for g in ["gurt", "trace", "JSON", "Time", "WebSocket", "Clipboard", "Regex", "setTimeout", "setInterval", "clearTimeout", "clearInterval", "fetch", "urlEncode", "urlDecode"]: _gurt_globals[g] = true var members = [ "select", "selectAll", "create", "body", "location", "href", "reload", "goto", "query", "get", "has", "getAll", "log", "warn", "error", "text", "value", "visible", "children", "parent", "nextSibling", "previousSibling", "firstChild", "lastChild", "classList", "on", "append", "remove", "insertBefore", "insertAfter", "replace", "clone", "getAttribute", "setAttribute", "show", "hide", "focus", "unfocus", "createTween", "unsubscribe", "add", "remove", "toggle", "contains", "item", "length", "to", "duration", "easing", "transition", "play", "pause", "stop", "currentTime", "volume", "loop", "src", "playing", "paused", "withContext", "fillRect", "strokeRect", "clearRect", "drawCircle", "drawText", "setFont", "measureText", "beginPath", "moveTo", "lineTo", "closePath", "stroke", "fill", "arc", "quadraticCurveTo", "bezierCurveTo", "setStrokeStyle", "setFillStyle", "setLineWidth", "save", "translate", "rotate", "scale", "restore", "source", "ok", "json", "status", "statusText", "headers", "stringify", "parse", "now", "format", "date", "sleep", "benchmark", "timer", "delay", "elapsed", "reset", "complete", "remaining", "new", "send", "close", "readyState", "test", "match", "tostring", "replace", "replaceAll", "trim", ] for m in members: _member_keywords[m] = true func _is_whitespace(char: String) -> bool: return char == " " or char == "\t" func _clear_highlighting_cache(): _state_cache.clear() func _get_initial_state() -> int: return State.DEFAULT func _get_line_state(p_line: int, p_state: int) -> int: var current_state: int = p_state var line_text: String = get_text_edit().get_line(p_line) var line_len: int = line_text.length() var i := 0 while i < line_len: if current_state == State.DEFAULT: if i + 3 < line_len and line_text.substr(i, 4) == "--[[": current_state = State.IN_MULTILINE_COMMENT i += 4 continue if i + 1 < line_len and line_text.substr(i, 2) == "[[": current_state = State.IN_MULTILINE_STRING i += 2 continue if line_text[i] == "'" or line_text[i] == "\"": var quote = line_text[i] i += 1 while i < line_len: if line_text[i] == "\\": i += 2; continue if line_text[i] == quote: break i += 1 else: var end_idx = line_text.find("]]", i) if end_idx != -1: current_state = State.DEFAULT i = end_idx + 2 continue else: i = line_len i += 1 _state_cache[p_line] = current_state return current_state func _get_line_syntax_highlighting(p_line: int) -> Dictionary: var color_map := {} var start_state: int = _state_cache.get(p_line - 1, _get_initial_state()) var line_text: String = get_text_edit().get_line(p_line) var line_len: int = line_text.length() color_map[0] = {"color": font_color} var i := 0 if start_state != State.DEFAULT: var end_idx = line_text.find("]]") var region_color = comment_color if start_state == State.IN_MULTILINE_COMMENT else string_color color_map[0] = {"color": region_color} if end_idx == -1: return color_map else: i = end_idx + 2 if i < line_len: color_map[i] = {"color": font_color} start_state = State.DEFAULT while i < line_len: var start_col: int = i var current_char: String = line_text[i] if current_char == "-" and i + 1 < line_len and line_text[i+1] == "-": if not (i + 3 < line_len and line_text.substr(i, 4) == "--[["): color_map[i] = {"color": comment_color} return color_map if current_char == "\"" or current_char == "'": var quote = current_char var string_start = i i += 1 while i < line_len: if line_text[i] == "\\": i += 2; continue if line_text[i] == quote: i += 1; break i += 1 var string_end = i color_map[string_start] = {"color": string_color} if string_end < line_len: color_map[string_end] = {"color": font_color} continue if current_char.is_valid_int() or (current_char == "." and i + 1 < line_len and line_text[i+1].is_valid_int()): var is_hex = false if current_char == "0" and i + 1 < line_len and line_text[i+1].to_lower() == "x": i += 2; is_hex = true while i < line_len: var char = line_text[i] if (is_hex and char.is_valid_hex_number(false)) or \ (not is_hex and (char.is_valid_int() or char in "Ee.-+")): i += 1 else: break var number_end = i color_map[start_col] = {"color": number_color} if number_end < line_len: color_map[number_end] = {"color": font_color} continue if current_char.is_valid_identifier() and not current_char.is_valid_int(): while i < line_len and line_text[i].is_valid_identifier(): i += 1 var word = line_text.substr(start_col, i - start_col) var color = font_color if _keywords.has(word): color = keyword_color elif _gurt_globals.has(word): color = gurt_globals_color elif _built_in_functions.has(word): color = function_color else: var prev_char_idx = start_col - 1 while prev_char_idx >= 0 and _is_whitespace(line_text[prev_char_idx]): prev_char_idx -= 1 var next_char_idx = i while next_char_idx < line_len and _is_whitespace(line_text[next_char_idx]): next_char_idx += 1 var is_member = prev_char_idx >= 0 and line_text[prev_char_idx] in [".", ":"] var is_function_call = next_char_idx < line_len and line_text[next_char_idx] == "(" if is_member and _member_keywords.has(word): color = member_color elif is_function_call: color = function_color if color != font_color: color_map[start_col] = {"color": color} continue if not _is_whitespace(current_char): color_map[i] = {"color": symbol_color} if i + 1 < line_len: color_map[i + 1] = {"color": font_color} i += 1 return color_map