Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/modules/gdscript/editor/gdscript_highlighter.cpp
10278 views
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 "core/core_constants.h"
38
#include "editor/settings/editor_settings.h"
39
#include "editor/themes/editor_theme_manager.h"
40
#include "scene/gui/text_edit.h"
41
42
Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_line) {
43
Dictionary color_map;
44
45
Type next_type = NONE;
46
Type current_type = NONE;
47
Type prev_type = NONE;
48
49
String prev_text = "";
50
int prev_column = 0;
51
bool prev_is_char = false;
52
bool prev_is_digit = false;
53
bool prev_is_binary_op = false;
54
55
bool in_keyword = false;
56
bool in_word = false;
57
bool in_number = false;
58
bool in_node_path = false;
59
bool in_node_ref = false;
60
bool in_annotation = false;
61
bool in_string_name = false;
62
bool is_hex_notation = false;
63
bool is_bin_notation = false;
64
bool in_member_variable = false;
65
bool in_lambda = false;
66
67
bool in_function_name = false; // Any call.
68
bool in_function_declaration = false; // Only declaration.
69
bool in_signal_declaration = false;
70
bool is_after_func_signal_declaration = false;
71
bool in_var_const_declaration = false;
72
bool is_after_var_const_declaration = false;
73
bool expect_type = false;
74
75
int in_declaration_params = 0; // The number of opened `(` after func/signal name.
76
int in_declaration_param_dicts = 0; // The number of opened `{` inside func params.
77
int in_type_params = 0; // The number of opened `[` after type name.
78
79
Color keyword_color;
80
Color color;
81
82
color_region_cache[p_line] = -1;
83
int in_region = -1;
84
if (p_line != 0) {
85
int prev_region_line = p_line - 1;
86
while (prev_region_line > 0 && !color_region_cache.has(prev_region_line)) {
87
prev_region_line--;
88
}
89
for (int i = prev_region_line; i < p_line - 1; i++) {
90
get_line_syntax_highlighting(i);
91
}
92
if (!color_region_cache.has(p_line - 1)) {
93
get_line_syntax_highlighting(p_line - 1);
94
}
95
in_region = color_region_cache[p_line - 1];
96
}
97
98
const String &str = text_edit->get_line_with_ime(p_line);
99
const int line_length = str.length();
100
Color prev_color;
101
102
if (in_region != -1 && line_length == 0) {
103
color_region_cache[p_line] = in_region;
104
}
105
for (int j = 0; j < line_length; j++) {
106
Dictionary highlighter_info;
107
108
color = font_color;
109
bool is_char = !is_symbol(str[j]);
110
bool is_a_symbol = is_symbol(str[j]);
111
bool is_a_digit = is_digit(str[j]);
112
bool is_binary_op = false;
113
114
/* color regions */
115
if (is_a_symbol || in_region != -1) {
116
int from = j;
117
118
if (in_region == -1) {
119
for (; from < line_length; from++) {
120
if (str[from] == '\\') {
121
from++;
122
continue;
123
}
124
break;
125
}
126
}
127
128
if (from != line_length) {
129
// Check if we are in entering a region.
130
if (in_region == -1) {
131
const bool r_prefix = from > 0 && str[from - 1] == 'r';
132
for (int c = 0; c < color_regions.size(); c++) {
133
// Check there is enough room.
134
int chars_left = line_length - from;
135
int start_key_length = color_regions[c].start_key.length();
136
int end_key_length = color_regions[c].end_key.length();
137
if (chars_left < start_key_length) {
138
continue;
139
}
140
141
if (color_regions[c].is_string && color_regions[c].r_prefix != r_prefix) {
142
continue;
143
}
144
145
// Search the line.
146
bool match = true;
147
const char32_t *start_key = color_regions[c].start_key.get_data();
148
for (int k = 0; k < start_key_length; k++) {
149
if (start_key[k] != str[from + k]) {
150
match = false;
151
break;
152
}
153
}
154
// "#region" and "#endregion" only highlighted if they're the first region on the line.
155
if (color_regions[c].type == ColorRegion::TYPE_CODE_REGION) {
156
Vector<String> str_stripped_split = str.strip_edges().split_spaces(1);
157
if (!str_stripped_split.is_empty() &&
158
str_stripped_split[0] != "#region" &&
159
str_stripped_split[0] != "#endregion") {
160
match = false;
161
}
162
}
163
if (!match) {
164
continue;
165
}
166
in_region = c;
167
from += start_key_length;
168
169
// Check if it's the whole line.
170
if (end_key_length == 0 || color_regions[c].line_only || from + end_key_length > line_length) {
171
// Don't skip comments, for highlighting markers.
172
if (color_regions[in_region].is_comment) {
173
break;
174
}
175
if (from + end_key_length > line_length) {
176
// If it's key length and there is a '\', dont skip to highlight esc chars.
177
if (str.find_char('\\', from) >= 0) {
178
break;
179
}
180
}
181
prev_color = color_regions[in_region].color;
182
highlighter_info["color"] = color_regions[c].color;
183
color_map[j] = highlighter_info;
184
185
j = line_length;
186
if (!color_regions[c].line_only) {
187
color_region_cache[p_line] = c;
188
}
189
}
190
break;
191
}
192
193
// Don't skip comments, for highlighting markers.
194
if (j == line_length && !color_regions[in_region].is_comment) {
195
continue;
196
}
197
}
198
199
// If we are in one, find the end key.
200
if (in_region != -1) {
201
Color region_color = color_regions[in_region].color;
202
if (in_node_path && color_regions[in_region].type == ColorRegion::TYPE_STRING) {
203
region_color = node_path_color;
204
}
205
if (in_node_ref && color_regions[in_region].type == ColorRegion::TYPE_STRING) {
206
region_color = node_ref_color;
207
}
208
if (in_string_name && color_regions[in_region].type == ColorRegion::TYPE_STRING) {
209
region_color = string_name_color;
210
}
211
212
prev_color = region_color;
213
highlighter_info["color"] = region_color;
214
color_map[j] = highlighter_info;
215
216
if (color_regions[in_region].is_comment) {
217
int marker_start_pos = from;
218
int marker_len = 0;
219
while (from <= line_length) {
220
if (from < line_length && is_unicode_identifier_continue(str[from])) {
221
marker_len++;
222
} else {
223
if (marker_len > 0) {
224
HashMap<String, CommentMarkerLevel>::ConstIterator E = comment_markers.find(str.substr(marker_start_pos, marker_len));
225
if (E) {
226
Dictionary marker_highlighter_info;
227
marker_highlighter_info["color"] = comment_marker_colors[E->value];
228
color_map[marker_start_pos] = marker_highlighter_info;
229
230
Dictionary marker_continue_highlighter_info;
231
marker_continue_highlighter_info["color"] = region_color;
232
color_map[from] = marker_continue_highlighter_info;
233
}
234
}
235
marker_start_pos = from + 1;
236
marker_len = 0;
237
}
238
from++;
239
}
240
from = line_length - 1;
241
j = from;
242
} else {
243
// Search the line.
244
int region_end_index = -1;
245
int end_key_length = color_regions[in_region].end_key.length();
246
const char32_t *end_key = color_regions[in_region].end_key.get_data();
247
for (; from < line_length; from++) {
248
if (line_length - from < end_key_length) {
249
// Don't break if '\' to highlight esc chars.
250
if (str.find_char('\\', from) < 0) {
251
break;
252
}
253
}
254
255
if (!is_symbol(str[from])) {
256
continue;
257
}
258
259
if (str[from] == '\\') {
260
if (!color_regions[in_region].r_prefix) {
261
Dictionary escape_char_highlighter_info;
262
escape_char_highlighter_info["color"] = symbol_color;
263
color_map[from] = escape_char_highlighter_info;
264
}
265
266
from++;
267
268
if (!color_regions[in_region].r_prefix) {
269
int esc_len = 0;
270
if (str[from] == 'u') {
271
esc_len = 4;
272
} else if (str[from] == 'U') {
273
esc_len = 6;
274
}
275
for (int k = 0; k < esc_len && from < line_length - 1; k++) {
276
if (!is_hex_digit(str[from + 1])) {
277
break;
278
}
279
from++;
280
}
281
282
Dictionary region_continue_highlighter_info;
283
region_continue_highlighter_info["color"] = region_color;
284
color_map[from + 1] = region_continue_highlighter_info;
285
}
286
287
continue;
288
}
289
290
region_end_index = from;
291
for (int k = 0; k < end_key_length; k++) {
292
if (end_key[k] != str[from + k]) {
293
region_end_index = -1;
294
break;
295
}
296
}
297
298
if (region_end_index != -1) {
299
break;
300
}
301
}
302
j = from + (end_key_length - 1);
303
if (region_end_index == -1) {
304
color_region_cache[p_line] = in_region;
305
}
306
}
307
308
prev_type = REGION;
309
prev_text = "";
310
prev_column = j;
311
312
in_region = -1;
313
prev_is_char = false;
314
prev_is_digit = false;
315
prev_is_binary_op = false;
316
continue;
317
}
318
}
319
}
320
321
// VERY hacky... but couldn't come up with anything better.
322
if (j > 0 && (str[j] == '&' || str[j] == '^' || str[j] == '%' || str[j] == '+' || str[j] == '-' || str[j] == '~' || str[j] == '.')) {
323
int to = j - 1;
324
// Find what the last text was (prev_text won't work if there's no whitespace, so we need to do it manually).
325
while (to > 0 && is_whitespace(str[to])) {
326
to--;
327
}
328
int from = to;
329
while (from > 0 && !is_symbol(str[from])) {
330
from--;
331
}
332
String word = str.substr(from + 1, to - from);
333
// Keywords need to be exceptions, except for keywords that represent a value.
334
if (word == "true" || word == "false" || word == "null" || word == "PI" || word == "TAU" || word == "INF" || word == "NAN" || word == "self" || word == "super" || !reserved_keywords.has(word)) {
335
if (!is_symbol(str[to]) || str[to] == '"' || str[to] == '\'' || str[to] == ')' || str[to] == ']' || str[to] == '}') {
336
is_binary_op = true;
337
}
338
}
339
}
340
341
if (!is_char) {
342
in_keyword = false;
343
}
344
345
// Allow ABCDEF in hex notation.
346
if (is_hex_notation && (is_hex_digit(str[j]) || is_a_digit)) {
347
is_a_digit = true;
348
} else if (str[j] != '_') {
349
is_hex_notation = false;
350
}
351
352
// Disallow anything not a 0 or 1 in binary notation.
353
if (is_bin_notation && !is_binary_digit(str[j])) {
354
is_a_digit = false;
355
is_bin_notation = false;
356
}
357
358
if (!in_number && !in_word && is_a_digit) {
359
in_number = true;
360
}
361
362
// Special cases for numbers.
363
if (in_number && !is_a_digit) {
364
if ((str[j] == 'b' || str[j] == 'B') && str[j - 1] == '0') {
365
is_bin_notation = true;
366
} else if ((str[j] == 'x' || str[j] == 'X') && str[j - 1] == '0') {
367
is_hex_notation = true;
368
} else if (!((str[j] == '-' || str[j] == '+') && (str[j - 1] == 'e' || str[j - 1] == 'E') && !prev_is_digit) &&
369
!(str[j] == '_' && (prev_is_digit || str[j - 1] == 'b' || str[j - 1] == 'B' || str[j - 1] == 'x' || str[j - 1] == 'X' || str[j - 1] == '.')) &&
370
!((str[j] == 'e' || str[j] == 'E') && (prev_is_digit || str[j - 1] == '_')) &&
371
!(str[j] == '.' && (prev_is_digit || (!prev_is_binary_op && (j > 0 && (str[j - 1] == '_' || str[j - 1] == '-' || str[j - 1] == '+' || str[j - 1] == '~'))))) &&
372
!((str[j] == '-' || str[j] == '+' || str[j] == '~') && !is_binary_op && !prev_is_binary_op && str[j - 1] != 'e' && str[j - 1] != 'E')) {
373
/* This condition continues number highlighting in special cases.
374
1st row: '+' or '-' after scientific notation (like 3e-4);
375
2nd row: '_' as a numeric separator;
376
3rd row: Scientific notation 'e' and floating points;
377
4th row: Floating points inside the number, or leading if after a unary mathematical operator;
378
5th row: Multiple unary mathematical operators (like ~-7) */
379
in_number = false;
380
}
381
} else if (str[j] == '.' && !is_binary_op && is_digit(str[j + 1]) && (j == 0 || (j > 0 && str[j - 1] != '.'))) {
382
// Start number highlighting from leading decimal points (like .42)
383
in_number = true;
384
} else if ((str[j] == '-' || str[j] == '+' || str[j] == '~') && !is_binary_op) {
385
// Only start number highlighting on unary operators if a digit follows them.
386
int non_op = j + 1;
387
while (str[non_op] == '-' || str[non_op] == '+' || str[non_op] == '~') {
388
non_op++;
389
}
390
if (is_digit(str[non_op]) || (str[non_op] == '.' && non_op < line_length && is_digit(str[non_op + 1]))) {
391
in_number = true;
392
}
393
}
394
395
if (!in_word && is_unicode_identifier_start(str[j]) && !in_number) {
396
in_word = true;
397
}
398
399
if (is_a_symbol && str[j] != '.' && in_word) {
400
in_word = false;
401
}
402
403
if (!in_keyword && is_char && !prev_is_char) {
404
int to = j;
405
while (to < line_length && !is_symbol(str[to])) {
406
to++;
407
}
408
409
String word = str.substr(j, to - j);
410
Color col;
411
if (global_functions.has(word)) {
412
// "assert" and "preload" are reserved, so highlight even if not followed by a bracket.
413
if (word == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::ASSERT) || word == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::PRELOAD)) {
414
col = global_function_color;
415
} else {
416
// For other global functions, check if followed by bracket.
417
int k = to;
418
while (k < line_length && is_whitespace(str[k])) {
419
k++;
420
}
421
422
if (str[k] == '(') {
423
col = global_function_color;
424
}
425
}
426
} else if (class_names.has(word)) {
427
col = class_names[word];
428
} else if (reserved_keywords.has(word)) {
429
col = reserved_keywords[word];
430
// Don't highlight `list` as a type in `for elem: Type in list`.
431
expect_type = false;
432
} else if (member_keywords.has(word)) {
433
col = member_keywords[word];
434
in_member_variable = true;
435
}
436
437
if (col != Color()) {
438
for (int k = j - 1; k >= 0; k--) {
439
if (str[k] == '.') {
440
col = Color(); // Keyword, member & global func indexing not allowed.
441
break;
442
} else if (str[k] > 32) {
443
break;
444
}
445
}
446
447
if (!in_member_variable && col != Color()) {
448
in_keyword = true;
449
keyword_color = col;
450
}
451
}
452
}
453
454
if (!in_function_name && in_word && !in_keyword) {
455
if (prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::SIGNAL)) {
456
in_signal_declaration = true;
457
} else {
458
int k = j;
459
while (k < line_length && !is_symbol(str[k]) && !is_whitespace(str[k])) {
460
k++;
461
}
462
463
// Check for space between name and bracket.
464
while (k < line_length && is_whitespace(str[k])) {
465
k++;
466
}
467
468
if (str[k] == '(') {
469
in_function_name = true;
470
if (prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::FUNC)) {
471
in_function_declaration = true;
472
}
473
} else if (prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::VAR) || prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::FOR) || prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::TK_CONST)) {
474
in_var_const_declaration = true;
475
}
476
477
// Check for lambda.
478
if (in_function_declaration) {
479
k = j - 1;
480
while (k > 0 && is_whitespace(str[k])) {
481
k--;
482
}
483
484
if (str[k] == ':') {
485
in_lambda = true;
486
}
487
}
488
}
489
}
490
491
if (!in_function_name && !in_member_variable && !in_keyword && !in_number && in_word) {
492
int k = j;
493
while (k > 0 && !is_symbol(str[k]) && !is_whitespace(str[k])) {
494
k--;
495
}
496
497
if (str[k] == '.' && (k < 1 || str[k - 1] != '.')) {
498
in_member_variable = true;
499
}
500
}
501
502
if (is_a_symbol) {
503
if (in_function_declaration || in_signal_declaration) {
504
is_after_func_signal_declaration = true;
505
}
506
if (in_var_const_declaration) {
507
is_after_var_const_declaration = true;
508
}
509
510
if (in_declaration_params > 0) {
511
switch (str[j]) {
512
case '(':
513
in_declaration_params += 1;
514
break;
515
case ')':
516
in_declaration_params -= 1;
517
break;
518
case '{':
519
in_declaration_param_dicts += 1;
520
break;
521
case '}':
522
in_declaration_param_dicts -= 1;
523
break;
524
}
525
} else if ((is_after_func_signal_declaration || prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::FUNC)) && str[j] == '(') {
526
in_declaration_params = 1;
527
in_declaration_param_dicts = 0;
528
}
529
530
if (expect_type) {
531
switch (str[j]) {
532
case '[':
533
in_type_params += 1;
534
break;
535
case ']':
536
in_type_params -= 1;
537
break;
538
case ',':
539
if (in_type_params <= 0) {
540
expect_type = false;
541
}
542
break;
543
case ' ':
544
case '\t':
545
case '.':
546
break;
547
default:
548
expect_type = false;
549
break;
550
}
551
} else {
552
if (j > 0 && str[j - 1] == '-' && str[j] == '>') {
553
expect_type = true;
554
in_type_params = 0;
555
}
556
if ((is_after_var_const_declaration || (in_declaration_params == 1 && in_declaration_param_dicts == 0)) && str[j] == ':') {
557
expect_type = true;
558
in_type_params = 0;
559
}
560
}
561
562
in_function_name = false;
563
in_function_declaration = false;
564
in_signal_declaration = false;
565
in_var_const_declaration = false;
566
in_lambda = false;
567
in_member_variable = false;
568
569
if (!is_whitespace(str[j])) {
570
is_after_func_signal_declaration = false;
571
is_after_var_const_declaration = false;
572
}
573
}
574
575
// Set color of StringName, keeping symbol color for binary '&&' and '&'.
576
if (!in_string_name && in_region == -1 && str[j] == '&' && !is_binary_op) {
577
if (j + 1 <= line_length - 1 && (str[j + 1] == '\'' || str[j + 1] == '"')) {
578
in_string_name = true;
579
// Cover edge cases of i.e. '+&""' and '&&&""', so the StringName is properly colored.
580
if (prev_is_binary_op && j >= 2 && str[j - 1] == '&' && str[j - 2] != '&') {
581
in_string_name = false;
582
is_binary_op = true;
583
}
584
} else {
585
is_binary_op = true;
586
}
587
} else if (in_region != -1 || is_a_symbol) {
588
in_string_name = false;
589
}
590
591
// '^^' has no special meaning, so unlike StringName, when binary, use NodePath color for the last caret.
592
if (!in_node_path && in_region == -1 && str[j] == '^' && !is_binary_op && (j == 0 || (j > 0 && str[j - 1] != '^') || prev_is_binary_op)) {
593
in_node_path = true;
594
} else if (in_region != -1 || is_a_symbol) {
595
in_node_path = false;
596
}
597
598
if (!in_node_ref && in_region == -1 && (str[j] == '$' || (str[j] == '%' && !is_binary_op))) {
599
in_node_ref = true;
600
} 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] == '%'))) {
601
// NodeRefs can't start with digits, so point out wrong syntax immediately.
602
in_node_ref = false;
603
}
604
605
if (!in_annotation && in_region == -1 && str[j] == '@') {
606
in_annotation = true;
607
} else if (in_region != -1 || is_a_symbol) {
608
in_annotation = false;
609
}
610
611
const bool in_raw_string_prefix = in_region == -1 && str[j] == 'r' && j + 1 < line_length && (str[j + 1] == '"' || str[j + 1] == '\'');
612
613
if (in_raw_string_prefix) {
614
color = string_color;
615
} else if (in_node_ref) {
616
next_type = NODE_REF;
617
color = node_ref_color;
618
} else if (in_annotation) {
619
next_type = ANNOTATION;
620
color = annotation_color;
621
} else if (in_string_name) {
622
next_type = STRING_NAME;
623
color = string_name_color;
624
} else if (in_node_path) {
625
next_type = NODE_PATH;
626
color = node_path_color;
627
} else if (in_keyword) {
628
next_type = KEYWORD;
629
color = keyword_color;
630
} else if (in_signal_declaration) {
631
next_type = SIGNAL;
632
color = member_variable_color;
633
} else if (in_function_name) {
634
next_type = FUNCTION;
635
if (!in_lambda && in_function_declaration) {
636
color = function_definition_color;
637
} else {
638
color = function_color;
639
}
640
} else if (in_number) {
641
next_type = NUMBER;
642
color = number_color;
643
} else if (is_a_symbol) {
644
next_type = SYMBOL;
645
color = symbol_color;
646
} else if (expect_type) {
647
next_type = TYPE;
648
color = type_color;
649
} else if (in_member_variable) {
650
next_type = MEMBER;
651
color = member_variable_color;
652
} else {
653
next_type = IDENTIFIER;
654
}
655
656
if (next_type != current_type) {
657
if (current_type == NONE) {
658
current_type = next_type;
659
} else {
660
prev_type = current_type;
661
current_type = next_type;
662
663
// No need to store regions...
664
if (prev_type == REGION) {
665
prev_text = "";
666
prev_column = j;
667
} else {
668
String text = str.substr(prev_column, j - prev_column).strip_edges();
669
prev_column = j;
670
671
// Ignore if just whitespace.
672
if (!text.is_empty()) {
673
prev_text = text;
674
}
675
}
676
}
677
}
678
679
prev_is_char = is_char;
680
prev_is_digit = is_a_digit;
681
prev_is_binary_op = is_binary_op;
682
683
if (color != prev_color) {
684
prev_color = color;
685
highlighter_info["color"] = color;
686
color_map[j] = highlighter_info;
687
}
688
}
689
return color_map;
690
}
691
692
String GDScriptSyntaxHighlighter::_get_name() const {
693
return "GDScript";
694
}
695
696
PackedStringArray GDScriptSyntaxHighlighter::_get_supported_languages() const {
697
PackedStringArray languages;
698
languages.push_back("GDScript");
699
return languages;
700
}
701
702
void GDScriptSyntaxHighlighter::_update_cache() {
703
class_names.clear();
704
reserved_keywords.clear();
705
member_keywords.clear();
706
global_functions.clear();
707
color_regions.clear();
708
color_region_cache.clear();
709
710
font_color = text_edit->get_theme_color(SceneStringName(font_color));
711
symbol_color = EDITOR_GET("text_editor/theme/highlighting/symbol_color");
712
function_color = EDITOR_GET("text_editor/theme/highlighting/function_color");
713
number_color = EDITOR_GET("text_editor/theme/highlighting/number_color");
714
member_variable_color = EDITOR_GET("text_editor/theme/highlighting/member_variable_color");
715
716
/* Engine types. */
717
const Color types_color = EDITOR_GET("text_editor/theme/highlighting/engine_type_color");
718
List<StringName> types;
719
ClassDB::get_class_list(&types);
720
for (const StringName &E : types) {
721
if (ClassDB::is_class_exposed(E)) {
722
class_names[E] = types_color;
723
}
724
}
725
726
/* Global enums. */
727
List<StringName> global_enums;
728
CoreConstants::get_global_enums(&global_enums);
729
for (const StringName &enum_name : global_enums) {
730
class_names[enum_name] = types_color;
731
}
732
733
/* User types. */
734
const Color usertype_color = EDITOR_GET("text_editor/theme/highlighting/user_type_color");
735
List<StringName> global_classes;
736
ScriptServer::get_global_class_list(&global_classes);
737
for (const StringName &E : global_classes) {
738
class_names[E] = usertype_color;
739
}
740
741
/* Autoloads. */
742
for (const KeyValue<StringName, ProjectSettings::AutoloadInfo> &E : ProjectSettings::get_singleton()->get_autoload_list()) {
743
const ProjectSettings::AutoloadInfo &info = E.value;
744
if (info.is_singleton) {
745
class_names[info.name] = usertype_color;
746
}
747
}
748
749
const GDScriptLanguage *gdscript = GDScriptLanguage::get_singleton();
750
751
/* Core types. */
752
const Color basetype_color = EDITOR_GET("text_editor/theme/highlighting/base_type_color");
753
List<String> core_types;
754
gdscript->get_core_type_words(&core_types);
755
for (const String &E : core_types) {
756
class_names[StringName(E)] = basetype_color;
757
}
758
class_names[SNAME("Variant")] = basetype_color;
759
class_names[SNAME("void")] = basetype_color;
760
// `get_core_type_words()` doesn't return primitive types.
761
class_names[SNAME("bool")] = basetype_color;
762
class_names[SNAME("int")] = basetype_color;
763
class_names[SNAME("float")] = basetype_color;
764
765
/* Reserved words. */
766
const Color keyword_color = EDITOR_GET("text_editor/theme/highlighting/keyword_color");
767
const Color control_flow_keyword_color = EDITOR_GET("text_editor/theme/highlighting/control_flow_keyword_color");
768
for (const String &keyword : gdscript->get_reserved_words()) {
769
if (gdscript->is_control_flow_keyword(keyword)) {
770
reserved_keywords[StringName(keyword)] = control_flow_keyword_color;
771
} else {
772
reserved_keywords[StringName(keyword)] = keyword_color;
773
}
774
}
775
776
// Highlight `set` and `get` as "keywords" with the function color to avoid conflicts with method calls.
777
reserved_keywords[SNAME("set")] = function_color;
778
reserved_keywords[SNAME("get")] = function_color;
779
780
/* Global functions. */
781
List<StringName> global_function_list;
782
GDScriptUtilityFunctions::get_function_list(&global_function_list);
783
Variant::get_utility_function_list(&global_function_list);
784
// "assert" and "preload" are not utility functions, but are global nonetheless, so insert them.
785
global_functions.insert(SNAME("assert"));
786
global_functions.insert(SNAME("preload"));
787
for (const StringName &E : global_function_list) {
788
global_functions.insert(E);
789
}
790
791
/* Comments. */
792
const Color comment_color = EDITOR_GET("text_editor/theme/highlighting/comment_color");
793
for (const String &comment : gdscript->get_comment_delimiters()) {
794
String beg = comment.get_slicec(' ', 0);
795
String end = comment.get_slice_count(" ") > 1 ? comment.get_slicec(' ', 1) : String();
796
add_color_region(ColorRegion::TYPE_COMMENT, beg, end, comment_color, end.is_empty());
797
}
798
799
/* Doc comments */
800
const Color doc_comment_color = EDITOR_GET("text_editor/theme/highlighting/doc_comment_color");
801
for (const String &doc_comment : gdscript->get_doc_comment_delimiters()) {
802
String beg = doc_comment.get_slicec(' ', 0);
803
String end = doc_comment.get_slice_count(" ") > 1 ? doc_comment.get_slicec(' ', 1) : String();
804
add_color_region(ColorRegion::TYPE_COMMENT, beg, end, doc_comment_color, end.is_empty());
805
}
806
807
/* Code regions */
808
const Color code_region_color = Color(EDITOR_GET("text_editor/theme/highlighting/folded_code_region_color").operator Color(), 1.0);
809
add_color_region(ColorRegion::TYPE_CODE_REGION, "#region", "", code_region_color, true);
810
add_color_region(ColorRegion::TYPE_CODE_REGION, "#endregion", "", code_region_color, true);
811
812
/* Strings */
813
string_color = EDITOR_GET("text_editor/theme/highlighting/string_color");
814
add_color_region(ColorRegion::TYPE_STRING, "\"", "\"", string_color);
815
add_color_region(ColorRegion::TYPE_STRING, "'", "'", string_color);
816
add_color_region(ColorRegion::TYPE_MULTILINE_STRING, "\"\"\"", "\"\"\"", string_color);
817
add_color_region(ColorRegion::TYPE_MULTILINE_STRING, "'''", "'''", string_color);
818
add_color_region(ColorRegion::TYPE_STRING, "\"", "\"", string_color, false, true);
819
add_color_region(ColorRegion::TYPE_STRING, "'", "'", string_color, false, true);
820
add_color_region(ColorRegion::TYPE_MULTILINE_STRING, "\"\"\"", "\"\"\"", string_color, false, true);
821
add_color_region(ColorRegion::TYPE_MULTILINE_STRING, "'''", "'''", string_color, false, true);
822
823
/* Members. */
824
Ref<Script> scr = _get_edited_resource();
825
if (scr.is_valid()) {
826
StringName instance_base = scr->get_instance_base_type();
827
if (instance_base != StringName()) {
828
List<PropertyInfo> property_list;
829
ClassDB::get_property_list(instance_base, &property_list);
830
for (const PropertyInfo &E : property_list) {
831
String prop_name = E.name;
832
if (E.usage & PROPERTY_USAGE_CATEGORY || E.usage & PROPERTY_USAGE_GROUP || E.usage & PROPERTY_USAGE_SUBGROUP) {
833
continue;
834
}
835
if (prop_name.contains_char('/')) {
836
continue;
837
}
838
member_keywords[prop_name] = member_variable_color;
839
}
840
841
List<MethodInfo> signal_list;
842
ClassDB::get_signal_list(instance_base, &signal_list);
843
for (const MethodInfo &E : signal_list) {
844
member_keywords[E.name] = member_variable_color;
845
}
846
847
// For callables.
848
List<MethodInfo> method_list;
849
ClassDB::get_method_list(instance_base, &method_list);
850
for (const MethodInfo &E : method_list) {
851
member_keywords[E.name] = member_variable_color;
852
}
853
854
List<String> constant_list;
855
ClassDB::get_integer_constant_list(instance_base, &constant_list);
856
for (const String &E : constant_list) {
857
member_keywords[E] = member_variable_color;
858
}
859
860
List<StringName> builtin_enums;
861
ClassDB::get_enum_list(instance_base, &builtin_enums);
862
for (const StringName &E : builtin_enums) {
863
member_keywords[E] = types_color;
864
}
865
}
866
867
List<PropertyInfo> scr_property_list;
868
scr->get_script_property_list(&scr_property_list);
869
for (const PropertyInfo &E : scr_property_list) {
870
String prop_name = E.name;
871
if (E.usage & PROPERTY_USAGE_CATEGORY || E.usage & PROPERTY_USAGE_GROUP || E.usage & PROPERTY_USAGE_SUBGROUP) {
872
continue;
873
}
874
if (prop_name.contains_char('/')) {
875
continue;
876
}
877
member_keywords[prop_name] = member_variable_color;
878
}
879
880
List<MethodInfo> scr_signal_list;
881
scr->get_script_signal_list(&scr_signal_list);
882
for (const MethodInfo &E : scr_signal_list) {
883
member_keywords[E.name] = member_variable_color;
884
}
885
886
// For callables.
887
List<MethodInfo> scr_method_list;
888
scr->get_script_method_list(&scr_method_list);
889
for (const MethodInfo &E : scr_method_list) {
890
member_keywords[E.name] = member_variable_color;
891
}
892
893
Ref<Script> scr_class = scr;
894
while (scr_class.is_valid()) {
895
HashMap<StringName, Variant> scr_constant_list;
896
scr_class->get_constants(&scr_constant_list);
897
for (const KeyValue<StringName, Variant> &E : scr_constant_list) {
898
member_keywords[E.key.operator String()] = member_variable_color;
899
}
900
scr_class = scr_class->get_base_script();
901
}
902
}
903
904
function_definition_color = EDITOR_GET("text_editor/theme/highlighting/gdscript/function_definition_color");
905
global_function_color = EDITOR_GET("text_editor/theme/highlighting/gdscript/global_function_color");
906
node_path_color = EDITOR_GET("text_editor/theme/highlighting/gdscript/node_path_color");
907
node_ref_color = EDITOR_GET("text_editor/theme/highlighting/gdscript/node_reference_color");
908
annotation_color = EDITOR_GET("text_editor/theme/highlighting/gdscript/annotation_color");
909
string_name_color = EDITOR_GET("text_editor/theme/highlighting/gdscript/string_name_color");
910
type_color = EDITOR_GET("text_editor/theme/highlighting/base_type_color");
911
comment_marker_colors[COMMENT_MARKER_CRITICAL] = EDITOR_GET("text_editor/theme/highlighting/comment_markers/critical_color");
912
comment_marker_colors[COMMENT_MARKER_WARNING] = EDITOR_GET("text_editor/theme/highlighting/comment_markers/warning_color");
913
comment_marker_colors[COMMENT_MARKER_NOTICE] = EDITOR_GET("text_editor/theme/highlighting/comment_markers/notice_color");
914
915
comment_markers.clear();
916
Vector<String> critical_list = EDITOR_GET("text_editor/theme/highlighting/comment_markers/critical_list").operator String().split(",", false);
917
for (int i = 0; i < critical_list.size(); i++) {
918
comment_markers[critical_list[i]] = COMMENT_MARKER_CRITICAL;
919
}
920
Vector<String> warning_list = EDITOR_GET("text_editor/theme/highlighting/comment_markers/warning_list").operator String().split(",", false);
921
for (int i = 0; i < warning_list.size(); i++) {
922
comment_markers[warning_list[i]] = COMMENT_MARKER_WARNING;
923
}
924
Vector<String> notice_list = EDITOR_GET("text_editor/theme/highlighting/comment_markers/notice_list").operator String().split(",", false);
925
for (int i = 0; i < notice_list.size(); i++) {
926
comment_markers[notice_list[i]] = COMMENT_MARKER_NOTICE;
927
}
928
}
929
930
void GDScriptSyntaxHighlighter::add_color_region(ColorRegion::Type p_type, const String &p_start_key, const String &p_end_key, const Color &p_color, bool p_line_only, bool p_r_prefix) {
931
ERR_FAIL_COND_MSG(p_start_key.is_empty(), "Color region start key cannot be empty.");
932
ERR_FAIL_COND_MSG(!is_symbol(p_start_key[0]), "Color region start key must start with a symbol.");
933
934
if (!p_end_key.is_empty()) {
935
ERR_FAIL_COND_MSG(!is_symbol(p_end_key[0]), "Color region end key must start with a symbol.");
936
}
937
938
int at = 0;
939
for (const ColorRegion &region : color_regions) {
940
ERR_FAIL_COND_MSG(region.start_key == p_start_key && region.r_prefix == p_r_prefix, "Color region with start key '" + p_start_key + "' already exists.");
941
if (p_start_key.length() < region.start_key.length()) {
942
at++;
943
} else {
944
break;
945
}
946
}
947
948
ColorRegion color_region;
949
color_region.type = p_type;
950
color_region.color = p_color;
951
color_region.start_key = p_start_key;
952
color_region.end_key = p_end_key;
953
color_region.line_only = p_line_only;
954
color_region.r_prefix = p_r_prefix;
955
color_region.is_string = p_type == ColorRegion::TYPE_STRING || p_type == ColorRegion::TYPE_MULTILINE_STRING;
956
color_region.is_comment = p_type == ColorRegion::TYPE_COMMENT || p_type == ColorRegion::TYPE_CODE_REGION;
957
color_regions.insert(at, color_region);
958
clear_highlighting_cache();
959
}
960
961
Ref<EditorSyntaxHighlighter> GDScriptSyntaxHighlighter::_create() const {
962
Ref<GDScriptSyntaxHighlighter> syntax_highlighter;
963
syntax_highlighter.instantiate();
964
return syntax_highlighter;
965
}
966
967