1/**************************************************************************/
2/* gdscript_highlighter.cpp */
3/**************************************************************************/
4/* This file is part of: */
5/* GODOT ENGINE */
6/* https://godotengine.org */
7/**************************************************************************/
8/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10/* */
11/* Permission is hereby granted, free of charge, to any person obtaining */
12/* a copy of this software and associated documentation files (the */
13/* "Software"), to deal in the Software without restriction, including */
14/* without limitation the rights to use, copy, modify, merge, publish, */
15/* distribute, sublicense, and/or sell copies of the Software, and to */
16/* permit persons to whom the Software is furnished to do so, subject to */
17/* the following conditions: */
18/* */
19/* The above copyright notice and this permission notice shall be */
20/* included in all copies or substantial portions of the Software. */
21/* */
22/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29/**************************************************************************/
30
31#include "gdscript_highlighter.h"
32
33#include "../gdscript.h"
34#include "../gdscript_tokenizer.h"
35
36#include "core/config/project_settings.h"
37#include "editor/editor_settings.h"
38
39Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_line) {
40 Dictionary color_map;
41
42 Type next_type = NONE;
43 Type current_type = NONE;
44 Type prev_type = NONE;
45
46 String prev_text = "";
47 int prev_column = 0;
48 bool prev_is_char = false;
49 bool prev_is_digit = false;
50 bool prev_is_binary_op = false;
51
52 bool in_keyword = false;
53 bool in_word = false;
54 bool in_number = false;
55 bool in_node_path = false;
56 bool in_node_ref = false;
57 bool in_annotation = false;
58 bool in_string_name = false;
59 bool is_hex_notation = false;
60 bool is_bin_notation = false;
61 bool in_member_variable = false;
62 bool in_lambda = false;
63
64 bool in_function_name = false;
65 bool in_function_args = false;
66 bool in_variable_declaration = false;
67 bool in_signal_declaration = false;
68 bool expect_type = false;
69
70 Color keyword_color;
71 Color color;
72
73 color_region_cache[p_line] = -1;
74 int in_region = -1;
75 if (p_line != 0) {
76 int prev_region_line = p_line - 1;
77 while (prev_region_line > 0 && !color_region_cache.has(prev_region_line)) {
78 prev_region_line--;
79 }
80 for (int i = prev_region_line; i < p_line - 1; i++) {
81 get_line_syntax_highlighting(i);
82 }
83 if (!color_region_cache.has(p_line - 1)) {
84 get_line_syntax_highlighting(p_line - 1);
85 }
86 in_region = color_region_cache[p_line - 1];
87 }
88
89 const String &str = text_edit->get_line(p_line);
90 const int line_length = str.length();
91 Color prev_color;
92
93 if (in_region != -1 && line_length == 0) {
94 color_region_cache[p_line] = in_region;
95 }
96 for (int j = 0; j < line_length; j++) {
97 Dictionary highlighter_info;
98
99 color = font_color;
100 bool is_char = !is_symbol(str[j]);
101 bool is_a_symbol = is_symbol(str[j]);
102 bool is_a_digit = is_digit(str[j]);
103 bool is_binary_op = false;
104
105 /* color regions */
106 if (is_a_symbol || in_region != -1) {
107 int from = j;
108
109 if (in_region == -1) {
110 for (; from < line_length; from++) {
111 if (str[from] == '\\') {
112 from++;
113 continue;
114 }
115 break;
116 }
117 }
118
119 if (from != line_length) {
120 // Check if we are in entering a region.
121 if (in_region == -1) {
122 for (int c = 0; c < color_regions.size(); c++) {
123 // Check there is enough room.
124 int chars_left = line_length - from;
125 int start_key_length = color_regions[c].start_key.length();
126 int end_key_length = color_regions[c].end_key.length();
127 if (chars_left < start_key_length) {
128 continue;
129 }
130
131 // Search the line.
132 bool match = true;
133 const char32_t *start_key = color_regions[c].start_key.get_data();
134 for (int k = 0; k < start_key_length; k++) {
135 if (start_key[k] != str[from + k]) {
136 match = false;
137 break;
138 }
139 }
140 if (!match) {
141 continue;
142 }
143 in_region = c;
144 from += start_key_length;
145
146 // Check if it's the whole line.
147 if (end_key_length == 0 || color_regions[c].line_only || from + end_key_length > line_length) {
148 // Don't skip comments, for highlighting markers.
149 if (color_regions[in_region].start_key == "#") {
150 break;
151 }
152 if (from + end_key_length > line_length) {
153 // If it's key length and there is a '\', dont skip to highlight esc chars.
154 if (str.find("\\", from) >= 0) {
155 break;
156 }
157 }
158 prev_color = color_regions[in_region].color;
159 highlighter_info["color"] = color_regions[c].color;
160 color_map[j] = highlighter_info;
161
162 j = line_length;
163 if (!color_regions[c].line_only) {
164 color_region_cache[p_line] = c;
165 }
166 }
167 break;
168 }
169
170 // Don't skip comments, for highlighting markers.
171 if (j == line_length && color_regions[in_region].start_key != "#") {
172 continue;
173 }
174 }
175
176 // If we are in one, find the end key.
177 if (in_region != -1) {
178 Color region_color = color_regions[in_region].color;
179 if (in_node_path && (color_regions[in_region].start_key == "\"" || color_regions[in_region].start_key == "\'")) {
180 region_color = node_path_color;
181 }
182 if (in_node_ref && (color_regions[in_region].start_key == "\"" || color_regions[in_region].start_key == "\'")) {
183 region_color = node_ref_color;
184 }
185 if (in_string_name && (color_regions[in_region].start_key == "\"" || color_regions[in_region].start_key == "\'")) {
186 region_color = string_name_color;
187 }
188
189 prev_color = region_color;
190 highlighter_info["color"] = region_color;
191 color_map[j] = highlighter_info;
192
193 if (color_regions[in_region].start_key == "#") {
194 int marker_start_pos = from;
195 int marker_len = 0;
196 while (from <= line_length) {
197 if (from < line_length && is_unicode_identifier_continue(str[from])) {
198 marker_len++;
199 } else {
200 if (marker_len > 0) {
201 HashMap<String, CommentMarkerLevel>::ConstIterator E = comment_markers.find(str.substr(marker_start_pos, marker_len));
202 if (E) {
203 Dictionary marker_highlighter_info;
204 marker_highlighter_info["color"] = comment_marker_colors[E->value];
205 color_map[marker_start_pos] = marker_highlighter_info;
206
207 Dictionary marker_continue_highlighter_info;
208 marker_continue_highlighter_info["color"] = region_color;
209 color_map[from] = marker_continue_highlighter_info;
210 }
211 }
212 marker_start_pos = from + 1;
213 marker_len = 0;
214 }
215 from++;
216 }
217 from = line_length - 1;
218 j = from;
219 } else {
220 // Search the line.
221 int region_end_index = -1;
222 int end_key_length = color_regions[in_region].end_key.length();
223 const char32_t *end_key = color_regions[in_region].end_key.get_data();
224 for (; from < line_length; from++) {
225 if (line_length - from < end_key_length) {
226 // Don't break if '\' to highlight esc chars.
227 if (str.find("\\", from) < 0) {
228 break;
229 }
230 }
231
232 if (!is_symbol(str[from])) {
233 continue;
234 }
235
236 if (str[from] == '\\') {
237 Dictionary escape_char_highlighter_info;
238 escape_char_highlighter_info["color"] = symbol_color;
239 color_map[from] = escape_char_highlighter_info;
240
241 from++;
242
243 Dictionary region_continue_highlighter_info;
244 region_continue_highlighter_info["color"] = region_color;
245 color_map[from + 1] = region_continue_highlighter_info;
246 continue;
247 }
248
249 region_end_index = from;
250 for (int k = 0; k < end_key_length; k++) {
251 if (end_key[k] != str[from + k]) {
252 region_end_index = -1;
253 break;
254 }
255 }
256
257 if (region_end_index != -1) {
258 break;
259 }
260 }
261 j = from + (end_key_length - 1);
262 if (region_end_index == -1) {
263 color_region_cache[p_line] = in_region;
264 }
265 }
266
267 prev_type = REGION;
268 prev_text = "";
269 prev_column = j;
270
271 in_region = -1;
272 prev_is_char = false;
273 prev_is_digit = false;
274 prev_is_binary_op = false;
275 continue;
276 }
277 }
278 }
279
280 // VERY hacky... but couldn't come up with anything better.
281 if (j > 0 && (str[j] == '&' || str[j] == '^' || str[j] == '%' || str[j] == '+' || str[j] == '-' || str[j] == '~' || str[j] == '.')) {
282 int to = j - 1;
283 // Find what the last text was (prev_text won't work if there's no whitespace, so we need to do it manually).
284 while (to > 0 && is_whitespace(str[to])) {
285 to--;
286 }
287 int from = to;
288 while (from > 0 && !is_symbol(str[from])) {
289 from--;
290 }
291 String word = str.substr(from + 1, to - from);
292 // Keywords need to be exceptions, except for keywords that represent a value.
293 if (word == "true" || word == "false" || word == "null" || word == "PI" || word == "TAU" || word == "INF" || word == "NAN" || word == "self" || word == "super" || !reserved_keywords.has(word)) {
294 if (!is_symbol(str[to]) || str[to] == '"' || str[to] == '\'' || str[to] == ')' || str[to] == ']' || str[to] == '}') {
295 is_binary_op = true;
296 }
297 }
298 }
299
300 if (!is_char) {
301 in_keyword = false;
302 }
303
304 // Allow ABCDEF in hex notation.
305 if (is_hex_notation && (is_hex_digit(str[j]) || is_a_digit)) {
306 is_a_digit = true;
307 } else if (str[j] != '_') {
308 is_hex_notation = false;
309 }
310
311 // Disallow anything not a 0 or 1 in binary notation.
312 if (is_bin_notation && !is_binary_digit(str[j])) {
313 is_a_digit = false;
314 is_bin_notation = false;
315 }
316
317 if (!in_number && !in_word && is_a_digit) {
318 in_number = true;
319 }
320
321 // Special cases for numbers.
322 if (in_number && !is_a_digit) {
323 if (str[j] == 'b' && str[j - 1] == '0') {
324 is_bin_notation = true;
325 } else if (str[j] == 'x' && str[j - 1] == '0') {
326 is_hex_notation = true;
327 } else if (!((str[j] == '-' || str[j] == '+') && str[j - 1] == 'e' && !prev_is_digit) &&
328 !(str[j] == '_' && (prev_is_digit || str[j - 1] == 'b' || str[j - 1] == 'x' || str[j - 1] == '.')) &&
329 !(str[j] == 'e' && (prev_is_digit || str[j - 1] == '_')) &&
330 !(str[j] == '.' && (prev_is_digit || (!prev_is_binary_op && (j > 0 && (str[j - 1] == '_' || str[j - 1] == '-' || str[j - 1] == '+' || str[j - 1] == '~'))))) &&
331 !((str[j] == '-' || str[j] == '+' || str[j] == '~') && !is_binary_op && !prev_is_binary_op && str[j - 1] != 'e')) {
332 /* This condition continues number highlighting in special cases.
333 1st row: '+' or '-' after scientific notation (like 3e-4);
334 2nd row: '_' as a numeric separator;
335 3rd row: Scientific notation 'e' and floating points;
336 4th row: Floating points inside the number, or leading if after a unary mathematical operator;
337 5th row: Multiple unary mathematical operators (like ~-7) */
338 in_number = false;
339 }
340 } else if (str[j] == '.' && !is_binary_op && is_digit(str[j + 1]) && (j == 0 || (j > 0 && str[j - 1] != '.'))) {
341 // Start number highlighting from leading decimal points (like .42)
342 in_number = true;
343 } else if ((str[j] == '-' || str[j] == '+' || str[j] == '~') && !is_binary_op) {
344 // Only start number highlighting on unary operators if a digit follows them.
345 int non_op = j + 1;
346 while (str[non_op] == '-' || str[non_op] == '+' || str[non_op] == '~') {
347 non_op++;
348 }
349 if (is_digit(str[non_op]) || (str[non_op] == '.' && non_op < line_length && is_digit(str[non_op + 1]))) {
350 in_number = true;
351 }
352 }
353
354 if (!in_word && is_unicode_identifier_start(str[j]) && !in_number) {
355 in_word = true;
356 }
357
358 if (is_a_symbol && str[j] != '.' && in_word) {
359 in_word = false;
360 }
361
362 if (!in_keyword && is_char && !prev_is_char) {
363 int to = j;
364 while (to < line_length && !is_symbol(str[to])) {
365 to++;
366 }
367
368 String word = str.substr(j, to - j);
369 Color col;
370 if (global_functions.has(word)) {
371 // "assert" and "preload" are reserved, so highlight even if not followed by a bracket.
372 if (word == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::ASSERT) || word == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::PRELOAD)) {
373 col = global_function_color;
374 } else {
375 // For other global functions, check if followed by bracket.
376 int k = to;
377 while (k < line_length && is_whitespace(str[k])) {
378 k++;
379 }
380
381 if (str[k] == '(') {
382 col = global_function_color;
383 }
384 }
385 } else if (class_names.has(word)) {
386 col = class_names[word];
387 } else if (reserved_keywords.has(word)) {
388 col = reserved_keywords[word];
389 } else if (member_keywords.has(word)) {
390 col = member_keywords[word];
391 }
392
393 if (col != Color()) {
394 for (int k = j - 1; k >= 0; k--) {
395 if (str[k] == '.') {
396 col = Color(); // Keyword, member & global func indexing not allowed.
397 break;
398 } else if (str[k] > 32) {
399 break;
400 }
401 }
402
403 if (col != Color()) {
404 in_keyword = true;
405 keyword_color = col;
406 }
407 }
408 }
409
410 if (!in_function_name && in_word && !in_keyword) {
411 if (prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::SIGNAL)) {
412 in_signal_declaration = true;
413 } else {
414 int k = j;
415 while (k < line_length && !is_symbol(str[k]) && !is_whitespace(str[k])) {
416 k++;
417 }
418
419 // Check for space between name and bracket.
420 while (k < line_length && is_whitespace(str[k])) {
421 k++;
422 }
423
424 if (str[k] == '(') {
425 in_function_name = true;
426 } else if (prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::VAR)) {
427 in_variable_declaration = true;
428 }
429
430 // Check for lambda.
431 if (in_function_name && prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::FUNC)) {
432 k = j - 1;
433 while (k > 0 && is_whitespace(str[k])) {
434 k--;
435 }
436
437 if (str[k] == ':') {
438 in_lambda = true;
439 }
440 }
441 }
442 }
443
444 if (!in_function_name && !in_member_variable && !in_keyword && !in_number && in_word) {
445 int k = j;
446 while (k > 0 && !is_symbol(str[k]) && !is_whitespace(str[k])) {
447 k--;
448 }
449
450 if (str[k] == '.') {
451 in_member_variable = true;
452 }
453 }
454
455 if (is_a_symbol) {
456 if (in_function_name) {
457 in_function_args = true;
458 }
459
460 if (in_function_args && str[j] == ')') {
461 in_function_args = false;
462 }
463
464 if (expect_type && (prev_is_char || str[j] == '=') && str[j] != '[') {
465 expect_type = false;
466 }
467
468 if (j > 0 && str[j - 1] == '-' && str[j] == '>') {
469 expect_type = true;
470 }
471
472 if (in_variable_declaration || in_function_args) {
473 int k = j;
474 // Skip space
475 while (k < line_length && is_whitespace(str[k])) {
476 k++;
477 }
478
479 if (str[k] == ':') {
480 // has type hint
481 expect_type = true;
482 }
483 }
484
485 in_variable_declaration = false;
486 in_signal_declaration = false;
487 in_function_name = false;
488 in_lambda = false;
489 in_member_variable = false;
490 }
491
492 // Keep symbol color for binary '&&'. In the case of '&&&' use StringName color for the last ampersand.
493 if (!in_string_name && in_region == -1 && str[j] == '&' && !is_binary_op) {
494 if (j >= 2 && str[j - 1] == '&' && str[j - 2] != '&' && prev_is_binary_op) {
495 is_binary_op = true;
496 } else if (j == 0 || (j > 0 && str[j - 1] != '&') || prev_is_binary_op) {
497 in_string_name = true;
498 }
499 } else if (in_region != -1 || is_a_symbol) {
500 in_string_name = false;
501 }
502
503 // '^^' has no special meaning, so unlike StringName, when binary, use NodePath color for the last caret.
504 if (!in_node_path && in_region == -1 && str[j] == '^' && !is_binary_op && (j == 0 || (j > 0 && str[j - 1] != '^') || prev_is_binary_op)) {
505 in_node_path = true;
506 } else if (in_region != -1 || is_a_symbol) {
507 in_node_path = false;
508 }
509
510 if (!in_node_ref && in_region == -1 && (str[j] == '$' || (str[j] == '%' && !is_binary_op))) {
511 in_node_ref = true;
512 } else if (in_region != -1 || (is_a_symbol && str[j] != '/' && str[j] != '%') || (is_a_digit && j > 0 && (str[j - 1] == '$' || str[j - 1] == '/' || str[j - 1] == '%'))) {
513 // NodeRefs can't start with digits, so point out wrong syntax immediately.
514 in_node_ref = false;
515 }
516
517 if (!in_annotation && in_region == -1 && str[j] == '@') {
518 in_annotation = true;
519 } else if (in_region != -1 || is_a_symbol) {
520 in_annotation = false;
521 }
522
523 if (in_node_ref) {
524 next_type = NODE_REF;
525 color = node_ref_color;
526 } else if (in_annotation) {
527 next_type = ANNOTATION;
528 color = annotation_color;
529 } else if (in_string_name) {
530 next_type = STRING_NAME;
531 color = string_name_color;
532 } else if (in_node_path) {
533 next_type = NODE_PATH;
534 color = node_path_color;
535 } else if (in_keyword) {
536 next_type = KEYWORD;
537 color = keyword_color;
538 } else if (in_member_variable) {
539 next_type = MEMBER;
540 color = member_color;
541 } else if (in_signal_declaration) {
542 next_type = SIGNAL;
543
544 color = member_color;
545 } else if (in_function_name) {
546 next_type = FUNCTION;
547
548 if (!in_lambda && prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::FUNC)) {
549 color = function_definition_color;
550 } else {
551 color = function_color;
552 }
553 } else if (in_number) {
554 next_type = NUMBER;
555 color = number_color;
556 } else if (is_a_symbol) {
557 next_type = SYMBOL;
558 color = symbol_color;
559 } else if (expect_type) {
560 next_type = TYPE;
561 color = type_color;
562 } else {
563 next_type = IDENTIFIER;
564 }
565
566 if (next_type != current_type) {
567 if (current_type == NONE) {
568 current_type = next_type;
569 } else {
570 prev_type = current_type;
571 current_type = next_type;
572
573 // No need to store regions...
574 if (prev_type == REGION) {
575 prev_text = "";
576 prev_column = j;
577 } else {
578 String text = str.substr(prev_column, j - prev_column).strip_edges();
579 prev_column = j;
580
581 // Ignore if just whitespace.
582 if (!text.is_empty()) {
583 prev_text = text;
584 }
585 }
586 }
587 }
588
589 prev_is_char = is_char;
590 prev_is_digit = is_a_digit;
591 prev_is_binary_op = is_binary_op;
592
593 if (color != prev_color) {
594 prev_color = color;
595 highlighter_info["color"] = color;
596 color_map[j] = highlighter_info;
597 }
598 }
599 return color_map;
600}
601
602String GDScriptSyntaxHighlighter::_get_name() const {
603 return "GDScript";
604}
605
606PackedStringArray GDScriptSyntaxHighlighter::_get_supported_languages() const {
607 PackedStringArray languages;
608 languages.push_back("GDScript");
609 return languages;
610}
611
612void GDScriptSyntaxHighlighter::_update_cache() {
613 class_names.clear();
614 reserved_keywords.clear();
615 member_keywords.clear();
616 global_functions.clear();
617 color_regions.clear();
618 color_region_cache.clear();
619
620 font_color = text_edit->get_theme_color(SNAME("font_color"));
621 symbol_color = EDITOR_GET("text_editor/theme/highlighting/symbol_color");
622 function_color = EDITOR_GET("text_editor/theme/highlighting/function_color");
623 number_color = EDITOR_GET("text_editor/theme/highlighting/number_color");
624 member_color = EDITOR_GET("text_editor/theme/highlighting/member_variable_color");
625
626 /* Engine types. */
627 const Color types_color = EDITOR_GET("text_editor/theme/highlighting/engine_type_color");
628 List<StringName> types;
629 ClassDB::get_class_list(&types);
630 for (const StringName &E : types) {
631 class_names[E] = types_color;
632 }
633
634 /* User types. */
635 const Color usertype_color = EDITOR_GET("text_editor/theme/highlighting/user_type_color");
636 List<StringName> global_classes;
637 ScriptServer::get_global_class_list(&global_classes);
638 for (const StringName &E : global_classes) {
639 class_names[E] = usertype_color;
640 }
641
642 /* Autoloads. */
643 for (const KeyValue<StringName, ProjectSettings::AutoloadInfo> &E : ProjectSettings::get_singleton()->get_autoload_list()) {
644 const ProjectSettings::AutoloadInfo &info = E.value;
645 if (info.is_singleton) {
646 class_names[info.name] = usertype_color;
647 }
648 }
649
650 const GDScriptLanguage *gdscript = GDScriptLanguage::get_singleton();
651
652 /* Core types. */
653 const Color basetype_color = EDITOR_GET("text_editor/theme/highlighting/base_type_color");
654 List<String> core_types;
655 gdscript->get_core_type_words(&core_types);
656 for (const String &E : core_types) {
657 class_names[StringName(E)] = basetype_color;
658 }
659
660 /* Reserved words. */
661 const Color keyword_color = EDITOR_GET("text_editor/theme/highlighting/keyword_color");
662 const Color control_flow_keyword_color = EDITOR_GET("text_editor/theme/highlighting/control_flow_keyword_color");
663 List<String> keyword_list;
664 gdscript->get_reserved_words(&keyword_list);
665 for (const String &E : keyword_list) {
666 if (gdscript->is_control_flow_keyword(E)) {
667 reserved_keywords[StringName(E)] = control_flow_keyword_color;
668 } else {
669 reserved_keywords[StringName(E)] = keyword_color;
670 }
671 }
672
673 /* Global functions. */
674 List<StringName> global_function_list;
675 GDScriptUtilityFunctions::get_function_list(&global_function_list);
676 Variant::get_utility_function_list(&global_function_list);
677 // "assert" and "preload" are not utility functions, but are global nonetheless, so insert them.
678 global_functions.insert(SNAME("assert"));
679 global_functions.insert(SNAME("preload"));
680 for (const StringName &E : global_function_list) {
681 global_functions.insert(E);
682 }
683
684 /* Comments */
685 const Color comment_color = EDITOR_GET("text_editor/theme/highlighting/comment_color");
686 List<String> comments;
687 gdscript->get_comment_delimiters(&comments);
688 for (const String &comment : comments) {
689 String beg = comment.get_slice(" ", 0);
690 String end = comment.get_slice_count(" ") > 1 ? comment.get_slice(" ", 1) : String();
691 add_color_region(beg, end, comment_color, end.is_empty());
692 }
693
694 /* Strings */
695 const Color string_color = EDITOR_GET("text_editor/theme/highlighting/string_color");
696 List<String> strings;
697 gdscript->get_string_delimiters(&strings);
698 for (const String &string : strings) {
699 String beg = string.get_slice(" ", 0);
700 String end = string.get_slice_count(" ") > 1 ? string.get_slice(" ", 1) : String();
701 add_color_region(beg, end, string_color, end.is_empty());
702 }
703
704 const Ref<Script> scr = _get_edited_resource();
705 if (scr.is_valid()) {
706 /* Member types. */
707 const Color member_variable_color = EDITOR_GET("text_editor/theme/highlighting/member_variable_color");
708 StringName instance_base = scr->get_instance_base_type();
709 if (instance_base != StringName()) {
710 List<PropertyInfo> plist;
711 ClassDB::get_property_list(instance_base, &plist);
712 for (const PropertyInfo &E : plist) {
713 String prop_name = E.name;
714 if (E.usage & PROPERTY_USAGE_CATEGORY || E.usage & PROPERTY_USAGE_GROUP || E.usage & PROPERTY_USAGE_SUBGROUP) {
715 continue;
716 }
717 if (prop_name.contains("/")) {
718 continue;
719 }
720 member_keywords[prop_name] = member_variable_color;
721 }
722
723 List<String> clist;
724 ClassDB::get_integer_constant_list(instance_base, &clist);
725 for (const String &E : clist) {
726 member_keywords[E] = member_variable_color;
727 }
728 }
729 }
730
731 const String text_edit_color_theme = EDITOR_GET("text_editor/theme/color_theme");
732 const bool godot_2_theme = text_edit_color_theme == "Godot 2";
733
734 if (godot_2_theme || EditorSettings::get_singleton()->is_dark_theme()) {
735 function_definition_color = Color(0.4, 0.9, 1.0);
736 global_function_color = Color(0.64, 0.64, 0.96);
737 node_path_color = Color(0.72, 0.77, 0.49);
738 node_ref_color = Color(0.39, 0.76, 0.35);
739 annotation_color = Color(1.0, 0.7, 0.45);
740 string_name_color = Color(1.0, 0.76, 0.65);
741 comment_marker_colors[COMMENT_MARKER_CRITICAL] = Color(0.77, 0.35, 0.35);
742 comment_marker_colors[COMMENT_MARKER_WARNING] = Color(0.72, 0.61, 0.48);
743 comment_marker_colors[COMMENT_MARKER_NOTICE] = Color(0.56, 0.67, 0.51);
744 } else {
745 function_definition_color = Color(0, 0.6, 0.6);
746 global_function_color = Color(0.36, 0.18, 0.72);
747 node_path_color = Color(0.18, 0.55, 0);
748 node_ref_color = Color(0.0, 0.5, 0);
749 annotation_color = Color(0.8, 0.37, 0);
750 string_name_color = Color(0.8, 0.56, 0.45);
751 comment_marker_colors[COMMENT_MARKER_CRITICAL] = Color(0.8, 0.14, 0.14);
752 comment_marker_colors[COMMENT_MARKER_WARNING] = Color(0.75, 0.39, 0.03);
753 comment_marker_colors[COMMENT_MARKER_NOTICE] = Color(0.24, 0.54, 0.09);
754 }
755
756 EDITOR_DEF("text_editor/theme/highlighting/gdscript/function_definition_color", function_definition_color);
757 EDITOR_DEF("text_editor/theme/highlighting/gdscript/global_function_color", global_function_color);
758 EDITOR_DEF("text_editor/theme/highlighting/gdscript/node_path_color", node_path_color);
759 EDITOR_DEF("text_editor/theme/highlighting/gdscript/node_reference_color", node_ref_color);
760 EDITOR_DEF("text_editor/theme/highlighting/gdscript/annotation_color", annotation_color);
761 EDITOR_DEF("text_editor/theme/highlighting/gdscript/string_name_color", string_name_color);
762 EDITOR_DEF("text_editor/theme/highlighting/comment_markers/critical_color", comment_marker_colors[COMMENT_MARKER_CRITICAL]);
763 EDITOR_DEF("text_editor/theme/highlighting/comment_markers/warning_color", comment_marker_colors[COMMENT_MARKER_WARNING]);
764 EDITOR_DEF("text_editor/theme/highlighting/comment_markers/notice_color", comment_marker_colors[COMMENT_MARKER_NOTICE]);
765 // The list is based on <https://github.com/KDE/syntax-highlighting/blob/master/data/syntax/alert.xml>.
766 EDITOR_DEF("text_editor/theme/highlighting/comment_markers/critical_list", "ALERT,ATTENTION,CAUTION,CRITICAL,DANGER,SECURITY");
767 EDITOR_DEF("text_editor/theme/highlighting/comment_markers/warning_list", "BUG,DEPRECATED,FIXME,HACK,TASK,TBD,TODO,WARNING");
768 EDITOR_DEF("text_editor/theme/highlighting/comment_markers/notice_list", "INFO,NOTE,NOTICE,TEST,TESTING");
769
770 if (text_edit_color_theme == "Default" || godot_2_theme) {
771 EditorSettings::get_singleton()->set_initial_value(
772 "text_editor/theme/highlighting/gdscript/function_definition_color",
773 function_definition_color,
774 true);
775 EditorSettings::get_singleton()->set_initial_value(
776 "text_editor/theme/highlighting/gdscript/global_function_color",
777 global_function_color,
778 true);
779 EditorSettings::get_singleton()->set_initial_value(
780 "text_editor/theme/highlighting/gdscript/node_path_color",
781 node_path_color,
782 true);
783 EditorSettings::get_singleton()->set_initial_value(
784 "text_editor/theme/highlighting/gdscript/node_reference_color",
785 node_ref_color,
786 true);
787 EditorSettings::get_singleton()->set_initial_value(
788 "text_editor/theme/highlighting/gdscript/annotation_color",
789 annotation_color,
790 true);
791 EditorSettings::get_singleton()->set_initial_value(
792 "text_editor/theme/highlighting/gdscript/string_name_color",
793 string_name_color,
794 true);
795 EditorSettings::get_singleton()->set_initial_value(
796 "text_editor/theme/highlighting/comment_markers/critical_color",
797 comment_marker_colors[COMMENT_MARKER_CRITICAL],
798 true);
799 EditorSettings::get_singleton()->set_initial_value(
800 "text_editor/theme/highlighting/comment_markers/warning_color",
801 comment_marker_colors[COMMENT_MARKER_WARNING],
802 true);
803 EditorSettings::get_singleton()->set_initial_value(
804 "text_editor/theme/highlighting/comment_markers/notice_color",
805 comment_marker_colors[COMMENT_MARKER_NOTICE],
806 true);
807 }
808
809 function_definition_color = EDITOR_GET("text_editor/theme/highlighting/gdscript/function_definition_color");
810 global_function_color = EDITOR_GET("text_editor/theme/highlighting/gdscript/global_function_color");
811 node_path_color = EDITOR_GET("text_editor/theme/highlighting/gdscript/node_path_color");
812 node_ref_color = EDITOR_GET("text_editor/theme/highlighting/gdscript/node_reference_color");
813 annotation_color = EDITOR_GET("text_editor/theme/highlighting/gdscript/annotation_color");
814 string_name_color = EDITOR_GET("text_editor/theme/highlighting/gdscript/string_name_color");
815 type_color = EDITOR_GET("text_editor/theme/highlighting/base_type_color");
816 comment_marker_colors[COMMENT_MARKER_CRITICAL] = EDITOR_GET("text_editor/theme/highlighting/comment_markers/critical_color");
817 comment_marker_colors[COMMENT_MARKER_WARNING] = EDITOR_GET("text_editor/theme/highlighting/comment_markers/warning_color");
818 comment_marker_colors[COMMENT_MARKER_NOTICE] = EDITOR_GET("text_editor/theme/highlighting/comment_markers/notice_color");
819
820 comment_markers.clear();
821 Vector<String> critical_list = EDITOR_GET("text_editor/theme/highlighting/comment_markers/critical_list").operator String().split(",", false);
822 for (int i = 0; i < critical_list.size(); i++) {
823 comment_markers[critical_list[i]] = COMMENT_MARKER_CRITICAL;
824 }
825 Vector<String> warning_list = EDITOR_GET("text_editor/theme/highlighting/comment_markers/warning_list").operator String().split(",", false);
826 for (int i = 0; i < warning_list.size(); i++) {
827 comment_markers[warning_list[i]] = COMMENT_MARKER_WARNING;
828 }
829 Vector<String> notice_list = EDITOR_GET("text_editor/theme/highlighting/comment_markers/notice_list").operator String().split(",", false);
830 for (int i = 0; i < notice_list.size(); i++) {
831 comment_markers[notice_list[i]] = COMMENT_MARKER_NOTICE;
832 }
833}
834
835void GDScriptSyntaxHighlighter::add_color_region(const String &p_start_key, const String &p_end_key, const Color &p_color, bool p_line_only) {
836 for (int i = 0; i < p_start_key.length(); i++) {
837 ERR_FAIL_COND_MSG(!is_symbol(p_start_key[i]), "color regions must start with a symbol");
838 }
839
840 if (p_end_key.length() > 0) {
841 for (int i = 0; i < p_end_key.length(); i++) {
842 ERR_FAIL_COND_MSG(!is_symbol(p_end_key[i]), "color regions must end with a symbol");
843 }
844 }
845
846 int at = 0;
847 for (int i = 0; i < color_regions.size(); i++) {
848 ERR_FAIL_COND_MSG(color_regions[i].start_key == p_start_key, "color region with start key '" + p_start_key + "' already exists.");
849 if (p_start_key.length() < color_regions[i].start_key.length()) {
850 at++;
851 }
852 }
853
854 ColorRegion color_region;
855 color_region.color = p_color;
856 color_region.start_key = p_start_key;
857 color_region.end_key = p_end_key;
858 color_region.line_only = p_line_only;
859 color_regions.insert(at, color_region);
860 clear_highlighting_cache();
861}
862
863Ref<EditorSyntaxHighlighter> GDScriptSyntaxHighlighter::_create() const {
864 Ref<GDScriptSyntaxHighlighter> syntax_highlighter;
865 syntax_highlighter.instantiate();
866 return syntax_highlighter;
867}
868