Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/modules/gdscript/editor/gdscript_highlighter.cpp
20843 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
int placeholder_end = from;
248
for (; from < line_length; from++) {
249
if (line_length - from < end_key_length) {
250
// Don't break if '\' to highlight escape sequences,
251
// and '%' and '{' to highlight placeholders.
252
if (str.find_char('\\', from) == -1 && str.find_char('%', from) == -1 && str.find_char('{', from) == -1) {
253
break;
254
}
255
}
256
257
if (!is_symbol(str[from]) && str[from] != '%') {
258
continue;
259
}
260
261
if (str[from] == '%' || str[from] == '{') {
262
int placeholder_start = from;
263
bool is_percent = str[from] == '%';
264
265
from++;
266
267
if (is_percent) {
268
if (str[from] == '%') {
269
placeholder_end = from + 1;
270
} else {
271
const String allowed_chars = "+.-*0123456789";
272
const String placeholder_types = "cdfosvxX";
273
for (int i = 0; i < line_length - from; i++) {
274
if (allowed_chars.contains_char(str[from + i]) &&
275
!placeholder_types.contains_char(str[from + i]) &&
276
(str[from + i] != end_key[0] || (str[from + i] == end_key[0] && str[from + i - 1] == '\\'))) {
277
continue;
278
}
279
if (placeholder_types.contains_char(str[from + i])) {
280
placeholder_end = from + i + 1;
281
}
282
break;
283
}
284
}
285
} else {
286
for (int i = 0; i < line_length - from; i++) {
287
if (str[from + i] != '}' && (str[from + i] != end_key[0] || (str[from + i] == end_key[0] && str[from + i - 1] == '\\'))) {
288
continue;
289
}
290
if (str[from + i] == '}') {
291
placeholder_end = from + i + 1;
292
}
293
break;
294
}
295
}
296
297
if (placeholder_end > placeholder_start) {
298
Dictionary placeholder_highlighter_info;
299
placeholder_highlighter_info["color"] = placeholder_color;
300
color_map[placeholder_start] = placeholder_highlighter_info;
301
Dictionary region_continue_highlighter_info;
302
region_continue_highlighter_info["color"] = region_color;
303
color_map[placeholder_end] = region_continue_highlighter_info;
304
}
305
}
306
307
if (str[from] == '\\') {
308
if (!color_regions[in_region].r_prefix) {
309
Dictionary escape_char_highlighter_info;
310
escape_char_highlighter_info["color"] = symbol_color;
311
color_map[from] = escape_char_highlighter_info;
312
}
313
314
from++;
315
316
if (!color_regions[in_region].r_prefix) {
317
int esc_len = 0;
318
if (str[from] == 'u') {
319
esc_len = 4;
320
} else if (str[from] == 'U') {
321
esc_len = 6;
322
}
323
for (int k = 0; k < esc_len && from < line_length - 1; k++) {
324
if (!is_hex_digit(str[from + 1])) {
325
break;
326
}
327
from++;
328
}
329
330
Dictionary region_continue_highlighter_info;
331
region_continue_highlighter_info["color"] = from < placeholder_end ? placeholder_color : region_color;
332
color_map[from + 1] = region_continue_highlighter_info;
333
}
334
335
continue;
336
}
337
338
region_end_index = from;
339
for (int k = 0; k < end_key_length; k++) {
340
if (end_key[k] != str[from + k]) {
341
region_end_index = -1;
342
break;
343
}
344
}
345
346
if (region_end_index != -1) {
347
break;
348
}
349
}
350
j = from + (end_key_length - 1);
351
if (region_end_index == -1) {
352
color_region_cache[p_line] = in_region;
353
}
354
}
355
356
prev_type = REGION;
357
prev_text = "";
358
prev_column = j;
359
360
in_region = -1;
361
prev_is_char = false;
362
prev_is_digit = false;
363
prev_is_binary_op = false;
364
continue;
365
}
366
color_map.sort(); // Prevents e.g. escape sequences from being overridden by string placeholders.
367
}
368
}
369
370
// VERY hacky... but couldn't come up with anything better.
371
if (j > 0 && (str[j] == '&' || str[j] == '^' || str[j] == '%' || str[j] == '+' || str[j] == '-' || str[j] == '~' || str[j] == '.')) {
372
int to = j - 1;
373
// Find what the last text was (prev_text won't work if there's no whitespace, so we need to do it manually).
374
while (to > 0 && is_whitespace(str[to])) {
375
to--;
376
}
377
int from = to;
378
while (from > 0 && !is_symbol(str[from])) {
379
from--;
380
}
381
String word = str.substr(from + 1, to - from);
382
// Keywords need to be exceptions, except for keywords that represent a value.
383
if (word == "true" || word == "false" || word == "null" || word == "PI" || word == "TAU" || word == "INF" || word == "NAN" || word == "self" || word == "super" || !reserved_keywords.has(word)) {
384
if (!is_symbol(str[to]) || str[to] == '"' || str[to] == '\'' || str[to] == ')' || str[to] == ']' || str[to] == '}') {
385
is_binary_op = true;
386
}
387
}
388
}
389
390
if (!is_char) {
391
in_keyword = false;
392
}
393
394
// Allow ABCDEF in hex notation.
395
if (is_hex_notation && (is_hex_digit(str[j]) || is_a_digit)) {
396
is_a_digit = true;
397
} else if (str[j] != '_') {
398
is_hex_notation = false;
399
}
400
401
// Disallow anything not a 0 or 1 in binary notation.
402
if (is_bin_notation && !is_binary_digit(str[j])) {
403
is_a_digit = false;
404
is_bin_notation = false;
405
}
406
407
if (!in_number && !in_word && is_a_digit) {
408
in_number = true;
409
}
410
411
// Special cases for numbers.
412
if (in_number && !is_a_digit) {
413
if ((str[j] == 'b' || str[j] == 'B') && str[j - 1] == '0') {
414
is_bin_notation = true;
415
} else if ((str[j] == 'x' || str[j] == 'X') && str[j - 1] == '0') {
416
is_hex_notation = true;
417
} else if (!((str[j] == '-' || str[j] == '+') && (str[j - 1] == 'e' || str[j - 1] == 'E') && !prev_is_digit) &&
418
!(str[j] == '_' && (prev_is_digit || str[j - 1] == 'b' || str[j - 1] == 'B' || str[j - 1] == 'x' || str[j - 1] == 'X' || str[j - 1] == '.')) &&
419
!((str[j] == 'e' || str[j] == 'E') && (prev_is_digit || str[j - 1] == '_')) &&
420
!(str[j] == '.' && (prev_is_digit || (!prev_is_binary_op && (j > 0 && (str[j - 1] == '_' || str[j - 1] == '-' || str[j - 1] == '+' || str[j - 1] == '~'))))) &&
421
!((str[j] == '-' || str[j] == '+' || str[j] == '~') && !is_binary_op && !prev_is_binary_op && str[j - 1] != 'e' && str[j - 1] != 'E')) {
422
/* This condition continues number highlighting in special cases.
423
1st row: '+' or '-' after scientific notation (like 3e-4);
424
2nd row: '_' as a numeric separator;
425
3rd row: Scientific notation 'e' and floating points;
426
4th row: Floating points inside the number, or leading if after a unary mathematical operator;
427
5th row: Multiple unary mathematical operators (like ~-7) */
428
in_number = false;
429
}
430
} else if (str[j] == '.' && !is_binary_op && is_digit(str[j + 1]) && (j == 0 || (j > 0 && str[j - 1] != '.'))) {
431
// Start number highlighting from leading decimal points (like .42)
432
in_number = true;
433
} else if ((str[j] == '-' || str[j] == '+' || str[j] == '~') && !is_binary_op) {
434
// Only start number highlighting on unary operators if a digit follows them.
435
int non_op = j + 1;
436
while (str[non_op] == '-' || str[non_op] == '+' || str[non_op] == '~') {
437
non_op++;
438
}
439
if (is_digit(str[non_op]) || (str[non_op] == '.' && non_op < line_length && is_digit(str[non_op + 1]))) {
440
in_number = true;
441
}
442
}
443
444
if (!in_word && is_unicode_identifier_start(str[j]) && !in_number) {
445
in_word = true;
446
}
447
448
if (is_a_symbol && str[j] != '.' && in_word) {
449
in_word = false;
450
}
451
452
if (!in_keyword && is_char && !prev_is_char) {
453
int to = j;
454
while (to < line_length && !is_symbol(str[to])) {
455
to++;
456
}
457
458
String word = str.substr(j, to - j);
459
Color col;
460
if (global_functions.has(word)) {
461
// "assert" and "preload" are reserved, so highlight even if not followed by a bracket.
462
if (word == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::ASSERT) || word == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::PRELOAD)) {
463
col = global_function_color;
464
} else {
465
// For other global functions, check if followed by bracket.
466
int k = to;
467
while (k < line_length && is_whitespace(str[k])) {
468
k++;
469
}
470
471
if (str[k] == '(') {
472
col = global_function_color;
473
}
474
}
475
} else if (class_names.has(word)) {
476
col = class_names[word];
477
} else if (reserved_keywords.has(word)) {
478
col = reserved_keywords[word];
479
// Don't highlight `list` as a type in `for elem: Type in list`.
480
expect_type = false;
481
} else if (member_keywords.has(word)) {
482
col = member_keywords[word];
483
in_member_variable = true;
484
}
485
486
if (col != Color()) {
487
for (int k = j - 1; k >= 0; k--) {
488
if (str[k] == '.') {
489
// Keyword, member, & global func indexing not allowed,
490
// but don't reset color if prev was a number like "5."
491
col = (prev_type == NUMBER) ? col : Color();
492
break;
493
} else if (str[k] > 32) {
494
break;
495
}
496
}
497
498
if (!in_member_variable && col != Color()) {
499
in_keyword = true;
500
keyword_color = col;
501
}
502
}
503
}
504
505
if (!in_function_name && in_word && !in_keyword) {
506
if (prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::SIGNAL)) {
507
in_signal_declaration = true;
508
} else {
509
int k = j;
510
while (k < line_length && !is_symbol(str[k]) && !is_whitespace(str[k])) {
511
k++;
512
}
513
514
// Check for space between name and bracket.
515
while (k < line_length && is_whitespace(str[k])) {
516
k++;
517
}
518
519
if (str[k] == '(') {
520
in_function_name = true;
521
if (prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::FUNC)) {
522
in_function_declaration = true;
523
}
524
} 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)) {
525
in_var_const_declaration = true;
526
}
527
528
// Check for lambda.
529
if (in_function_declaration) {
530
k = j - 1;
531
while (k > 0 && is_whitespace(str[k])) {
532
k--;
533
}
534
535
if (str[k] == ':') {
536
in_lambda = true;
537
}
538
}
539
}
540
}
541
542
if (!in_function_name && !in_member_variable && !in_keyword && !in_number && in_word) {
543
int k = j;
544
while (k > 0 && !is_symbol(str[k]) && !is_whitespace(str[k])) {
545
k--;
546
}
547
548
if (str[k] == '.' && (k < 1 || str[k - 1] != '.')) {
549
in_member_variable = true;
550
}
551
}
552
553
if (is_a_symbol) {
554
if (in_function_declaration || in_signal_declaration) {
555
is_after_func_signal_declaration = true;
556
}
557
if (in_var_const_declaration) {
558
is_after_var_const_declaration = true;
559
}
560
561
if (in_declaration_params > 0) {
562
switch (str[j]) {
563
case '(':
564
in_declaration_params += 1;
565
break;
566
case ')':
567
in_declaration_params -= 1;
568
break;
569
case '{':
570
in_declaration_param_dicts += 1;
571
break;
572
case '}':
573
in_declaration_param_dicts -= 1;
574
break;
575
}
576
} else if ((is_after_func_signal_declaration || prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::FUNC)) && str[j] == '(') {
577
in_declaration_params = 1;
578
in_declaration_param_dicts = 0;
579
}
580
581
if (expect_type) {
582
switch (str[j]) {
583
case '[':
584
in_type_params += 1;
585
break;
586
case ']':
587
in_type_params -= 1;
588
break;
589
case ',':
590
if (in_type_params <= 0) {
591
expect_type = false;
592
}
593
break;
594
case ' ':
595
case '\t':
596
case '.':
597
break;
598
default:
599
expect_type = false;
600
break;
601
}
602
} else {
603
if (j > 0 && str[j - 1] == '-' && str[j] == '>') {
604
expect_type = true;
605
in_type_params = 0;
606
}
607
if ((is_after_var_const_declaration || (in_declaration_params == 1 && in_declaration_param_dicts == 0)) && str[j] == ':') {
608
expect_type = true;
609
in_type_params = 0;
610
}
611
}
612
613
in_function_name = false;
614
in_function_declaration = false;
615
in_signal_declaration = false;
616
in_var_const_declaration = false;
617
in_lambda = false;
618
in_member_variable = false;
619
620
if (!is_whitespace(str[j])) {
621
is_after_func_signal_declaration = false;
622
is_after_var_const_declaration = false;
623
}
624
}
625
626
// Set color of StringName, keeping symbol color for binary '&&' and '&'.
627
if (!in_string_name && in_region == -1 && str[j] == '&' && !is_binary_op) {
628
if (j + 1 <= line_length - 1 && (str[j + 1] == '\'' || str[j + 1] == '"')) {
629
in_string_name = true;
630
// Cover edge cases of i.e. '+&""' and '&&&""', so the StringName is properly colored.
631
if (prev_is_binary_op && j >= 2 && str[j - 1] == '&' && str[j - 2] != '&') {
632
in_string_name = false;
633
is_binary_op = true;
634
}
635
} else {
636
is_binary_op = true;
637
}
638
} else if (in_region != -1 || is_a_symbol) {
639
in_string_name = false;
640
}
641
642
// '^^' has no special meaning, so unlike StringName, when binary, use NodePath color for the last caret.
643
if (!in_node_path && in_region == -1 && str[j] == '^' && !is_binary_op && (j == 0 || (j > 0 && str[j - 1] != '^') || prev_is_binary_op)) {
644
in_node_path = true;
645
} else if (in_region != -1 || is_a_symbol) {
646
in_node_path = false;
647
}
648
649
if (!in_node_ref && in_region == -1 && (str[j] == '$' || (str[j] == '%' && !is_binary_op))) {
650
in_node_ref = true;
651
} 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] == '%'))) {
652
// NodeRefs can't start with digits, so point out wrong syntax immediately.
653
in_node_ref = false;
654
}
655
656
if (!in_annotation && in_region == -1 && str[j] == '@') {
657
in_annotation = true;
658
} else if (in_region != -1 || is_a_symbol) {
659
in_annotation = false;
660
}
661
662
const bool in_raw_string_prefix = in_region == -1 && str[j] == 'r' && j + 1 < line_length && (str[j + 1] == '"' || str[j + 1] == '\'');
663
664
if (in_raw_string_prefix) {
665
color = string_color;
666
} else if (in_node_ref) {
667
next_type = NODE_REF;
668
color = node_ref_color;
669
} else if (in_annotation) {
670
next_type = ANNOTATION;
671
color = annotation_color;
672
} else if (in_string_name) {
673
next_type = STRING_NAME;
674
color = string_name_color;
675
} else if (in_node_path) {
676
next_type = NODE_PATH;
677
color = node_path_color;
678
} else if (in_keyword) {
679
next_type = KEYWORD;
680
color = keyword_color;
681
} else if (in_signal_declaration) {
682
next_type = SIGNAL;
683
color = member_variable_color;
684
} else if (in_function_name) {
685
next_type = FUNCTION;
686
if (!in_lambda && in_function_declaration) {
687
color = function_definition_color;
688
} else {
689
color = function_color;
690
}
691
} else if (in_number) {
692
next_type = NUMBER;
693
color = number_color;
694
} else if (is_a_symbol) {
695
next_type = SYMBOL;
696
color = symbol_color;
697
} else if (expect_type) {
698
next_type = TYPE;
699
color = type_color;
700
} else if (in_member_variable) {
701
next_type = MEMBER;
702
color = member_variable_color;
703
} else {
704
next_type = IDENTIFIER;
705
}
706
707
if (next_type != current_type) {
708
if (current_type == NONE) {
709
current_type = next_type;
710
} else {
711
prev_type = current_type;
712
current_type = next_type;
713
714
// No need to store regions...
715
if (prev_type == REGION) {
716
prev_text = "";
717
prev_column = j;
718
} else {
719
String text = str.substr(prev_column, j - prev_column).strip_edges();
720
prev_column = j;
721
722
// Ignore if just whitespace.
723
if (!text.is_empty()) {
724
prev_text = text;
725
}
726
}
727
}
728
}
729
730
prev_is_char = is_char;
731
prev_is_digit = is_a_digit;
732
prev_is_binary_op = is_binary_op;
733
734
if (color != prev_color) {
735
prev_color = color;
736
highlighter_info["color"] = color;
737
color_map[j] = highlighter_info;
738
}
739
}
740
return color_map;
741
}
742
743
String GDScriptSyntaxHighlighter::_get_name() const {
744
return "GDScript";
745
}
746
747
PackedStringArray GDScriptSyntaxHighlighter::_get_supported_languages() const {
748
PackedStringArray languages;
749
languages.push_back("GDScript");
750
return languages;
751
}
752
753
void GDScriptSyntaxHighlighter::_update_cache() {
754
class_names.clear();
755
reserved_keywords.clear();
756
member_keywords.clear();
757
global_functions.clear();
758
color_regions.clear();
759
color_region_cache.clear();
760
761
font_color = text_edit->get_theme_color(SceneStringName(font_color));
762
symbol_color = EDITOR_GET("text_editor/theme/highlighting/symbol_color");
763
function_color = EDITOR_GET("text_editor/theme/highlighting/function_color");
764
number_color = EDITOR_GET("text_editor/theme/highlighting/number_color");
765
member_variable_color = EDITOR_GET("text_editor/theme/highlighting/member_variable_color");
766
767
/* Engine types. */
768
const Color types_color = EDITOR_GET("text_editor/theme/highlighting/engine_type_color");
769
LocalVector<StringName> types;
770
ClassDB::get_class_list(types);
771
for (const StringName &type : types) {
772
if (ClassDB::is_class_exposed(type)) {
773
class_names[type] = types_color;
774
}
775
}
776
777
/* Global enums. */
778
List<StringName> global_enums;
779
CoreConstants::get_global_enums(&global_enums);
780
for (const StringName &enum_name : global_enums) {
781
class_names[enum_name] = types_color;
782
}
783
784
/* User types. */
785
const Color usertype_color = EDITOR_GET("text_editor/theme/highlighting/user_type_color");
786
LocalVector<StringName> global_classes;
787
ScriptServer::get_global_class_list(global_classes);
788
for (const StringName &class_name : global_classes) {
789
class_names[class_name] = usertype_color;
790
}
791
792
/* Autoloads. */
793
for (const KeyValue<StringName, ProjectSettings::AutoloadInfo> &E : ProjectSettings::get_singleton()->get_autoload_list()) {
794
const ProjectSettings::AutoloadInfo &info = E.value;
795
if (info.is_singleton) {
796
class_names[info.name] = usertype_color;
797
}
798
}
799
800
const GDScriptLanguage *gdscript = GDScriptLanguage::get_singleton();
801
802
/* Core types. */
803
const Color basetype_color = EDITOR_GET("text_editor/theme/highlighting/base_type_color");
804
List<String> core_types;
805
gdscript->get_core_type_words(&core_types);
806
for (const String &E : core_types) {
807
class_names[StringName(E)] = basetype_color;
808
}
809
class_names[SNAME("Variant")] = basetype_color;
810
class_names[SNAME("void")] = basetype_color;
811
// `get_core_type_words()` doesn't return primitive types.
812
class_names[SNAME("bool")] = basetype_color;
813
class_names[SNAME("int")] = basetype_color;
814
class_names[SNAME("float")] = basetype_color;
815
816
/* Reserved words. */
817
const Color keyword_color = EDITOR_GET("text_editor/theme/highlighting/keyword_color");
818
const Color control_flow_keyword_color = EDITOR_GET("text_editor/theme/highlighting/control_flow_keyword_color");
819
for (const String &keyword : gdscript->get_reserved_words()) {
820
if (gdscript->is_control_flow_keyword(keyword)) {
821
reserved_keywords[StringName(keyword)] = control_flow_keyword_color;
822
} else {
823
reserved_keywords[StringName(keyword)] = keyword_color;
824
}
825
}
826
827
// Highlight `set` and `get` as "keywords" with the function color to avoid conflicts with method calls.
828
reserved_keywords[SNAME("set")] = function_color;
829
reserved_keywords[SNAME("get")] = function_color;
830
831
/* Global functions. */
832
List<StringName> global_function_list;
833
GDScriptUtilityFunctions::get_function_list(&global_function_list);
834
Variant::get_utility_function_list(&global_function_list);
835
// "assert" and "preload" are not utility functions, but are global nonetheless, so insert them.
836
global_functions.insert(SNAME("assert"));
837
global_functions.insert(SNAME("preload"));
838
for (const StringName &E : global_function_list) {
839
global_functions.insert(E);
840
}
841
842
/* Comments. */
843
const Color comment_color = EDITOR_GET("text_editor/theme/highlighting/comment_color");
844
for (const String &comment : gdscript->get_comment_delimiters()) {
845
String beg = comment.get_slicec(' ', 0);
846
String end = comment.get_slice_count(" ") > 1 ? comment.get_slicec(' ', 1) : String();
847
add_color_region(ColorRegion::TYPE_COMMENT, beg, end, comment_color, end.is_empty());
848
}
849
850
/* Doc comments */
851
const Color doc_comment_color = EDITOR_GET("text_editor/theme/highlighting/doc_comment_color");
852
for (const String &doc_comment : gdscript->get_doc_comment_delimiters()) {
853
String beg = doc_comment.get_slicec(' ', 0);
854
String end = doc_comment.get_slice_count(" ") > 1 ? doc_comment.get_slicec(' ', 1) : String();
855
add_color_region(ColorRegion::TYPE_COMMENT, beg, end, doc_comment_color, end.is_empty());
856
}
857
858
/* Code regions */
859
const Color code_region_color = Color(EDITOR_GET("text_editor/theme/highlighting/folded_code_region_color").operator Color(), 1.0);
860
add_color_region(ColorRegion::TYPE_CODE_REGION, "#region", "", code_region_color, true);
861
add_color_region(ColorRegion::TYPE_CODE_REGION, "#endregion", "", code_region_color, true);
862
863
/* Strings */
864
string_color = EDITOR_GET("text_editor/theme/highlighting/string_color");
865
placeholder_color = EDITOR_GET("text_editor/theme/highlighting/string_placeholder_color");
866
add_color_region(ColorRegion::TYPE_STRING, "\"", "\"", string_color);
867
add_color_region(ColorRegion::TYPE_STRING, "'", "'", string_color);
868
add_color_region(ColorRegion::TYPE_MULTILINE_STRING, "\"\"\"", "\"\"\"", string_color);
869
add_color_region(ColorRegion::TYPE_MULTILINE_STRING, "'''", "'''", string_color);
870
add_color_region(ColorRegion::TYPE_STRING, "\"", "\"", string_color, false, true);
871
add_color_region(ColorRegion::TYPE_STRING, "'", "'", string_color, false, true);
872
add_color_region(ColorRegion::TYPE_MULTILINE_STRING, "\"\"\"", "\"\"\"", string_color, false, true);
873
add_color_region(ColorRegion::TYPE_MULTILINE_STRING, "'''", "'''", string_color, false, true);
874
875
/* Members. */
876
Ref<Script> scr = _get_edited_resource();
877
if (scr.is_valid()) {
878
StringName instance_base = scr->get_instance_base_type();
879
if (instance_base != StringName()) {
880
List<PropertyInfo> property_list;
881
ClassDB::get_property_list(instance_base, &property_list);
882
for (const PropertyInfo &E : property_list) {
883
String prop_name = E.name;
884
if (E.usage & PROPERTY_USAGE_CATEGORY || E.usage & PROPERTY_USAGE_GROUP || E.usage & PROPERTY_USAGE_SUBGROUP) {
885
continue;
886
}
887
if (prop_name.contains_char('/')) {
888
continue;
889
}
890
member_keywords[prop_name] = member_variable_color;
891
}
892
893
List<MethodInfo> signal_list;
894
ClassDB::get_signal_list(instance_base, &signal_list);
895
for (const MethodInfo &E : signal_list) {
896
member_keywords[E.name] = member_variable_color;
897
}
898
899
// For callables.
900
List<MethodInfo> method_list;
901
ClassDB::get_method_list(instance_base, &method_list);
902
for (const MethodInfo &E : method_list) {
903
member_keywords[E.name] = member_variable_color;
904
}
905
906
List<String> constant_list;
907
ClassDB::get_integer_constant_list(instance_base, &constant_list);
908
for (const String &E : constant_list) {
909
member_keywords[E] = member_variable_color;
910
}
911
912
List<StringName> builtin_enums;
913
ClassDB::get_enum_list(instance_base, &builtin_enums);
914
for (const StringName &E : builtin_enums) {
915
member_keywords[E] = types_color;
916
}
917
}
918
919
List<PropertyInfo> scr_property_list;
920
scr->get_script_property_list(&scr_property_list);
921
for (const PropertyInfo &E : scr_property_list) {
922
String prop_name = E.name;
923
if (E.usage & PROPERTY_USAGE_CATEGORY || E.usage & PROPERTY_USAGE_GROUP || E.usage & PROPERTY_USAGE_SUBGROUP) {
924
continue;
925
}
926
if (prop_name.contains_char('/')) {
927
continue;
928
}
929
member_keywords[prop_name] = member_variable_color;
930
}
931
932
List<MethodInfo> scr_signal_list;
933
scr->get_script_signal_list(&scr_signal_list);
934
for (const MethodInfo &E : scr_signal_list) {
935
member_keywords[E.name] = member_variable_color;
936
}
937
938
// For callables.
939
List<MethodInfo> scr_method_list;
940
scr->get_script_method_list(&scr_method_list);
941
for (const MethodInfo &E : scr_method_list) {
942
member_keywords[E.name] = member_variable_color;
943
}
944
945
Ref<Script> scr_class = scr;
946
while (scr_class.is_valid()) {
947
HashMap<StringName, Variant> scr_constant_list;
948
scr_class->get_constants(&scr_constant_list);
949
for (const KeyValue<StringName, Variant> &E : scr_constant_list) {
950
member_keywords[E.key.operator String()] = member_variable_color;
951
}
952
scr_class = scr_class->get_base_script();
953
}
954
}
955
956
function_definition_color = EDITOR_GET("text_editor/theme/highlighting/gdscript/function_definition_color");
957
global_function_color = EDITOR_GET("text_editor/theme/highlighting/gdscript/global_function_color");
958
node_path_color = EDITOR_GET("text_editor/theme/highlighting/gdscript/node_path_color");
959
node_ref_color = EDITOR_GET("text_editor/theme/highlighting/gdscript/node_reference_color");
960
annotation_color = EDITOR_GET("text_editor/theme/highlighting/gdscript/annotation_color");
961
string_name_color = EDITOR_GET("text_editor/theme/highlighting/gdscript/string_name_color");
962
type_color = EDITOR_GET("text_editor/theme/highlighting/base_type_color");
963
comment_marker_colors[COMMENT_MARKER_CRITICAL] = EDITOR_GET("text_editor/theme/highlighting/comment_markers/critical_color");
964
comment_marker_colors[COMMENT_MARKER_WARNING] = EDITOR_GET("text_editor/theme/highlighting/comment_markers/warning_color");
965
comment_marker_colors[COMMENT_MARKER_NOTICE] = EDITOR_GET("text_editor/theme/highlighting/comment_markers/notice_color");
966
967
comment_markers.clear();
968
Vector<String> critical_list = EDITOR_GET("text_editor/theme/highlighting/comment_markers/critical_list").operator String().split(",", false);
969
for (int i = 0; i < critical_list.size(); i++) {
970
comment_markers[critical_list[i]] = COMMENT_MARKER_CRITICAL;
971
}
972
Vector<String> warning_list = EDITOR_GET("text_editor/theme/highlighting/comment_markers/warning_list").operator String().split(",", false);
973
for (int i = 0; i < warning_list.size(); i++) {
974
comment_markers[warning_list[i]] = COMMENT_MARKER_WARNING;
975
}
976
Vector<String> notice_list = EDITOR_GET("text_editor/theme/highlighting/comment_markers/notice_list").operator String().split(",", false);
977
for (int i = 0; i < notice_list.size(); i++) {
978
comment_markers[notice_list[i]] = COMMENT_MARKER_NOTICE;
979
}
980
}
981
982
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) {
983
ERR_FAIL_COND_MSG(p_start_key.is_empty(), "Color region start key cannot be empty.");
984
ERR_FAIL_COND_MSG(!is_symbol(p_start_key[0]), "Color region start key must start with a symbol.");
985
986
if (!p_end_key.is_empty()) {
987
ERR_FAIL_COND_MSG(!is_symbol(p_end_key[0]), "Color region end key must start with a symbol.");
988
}
989
990
int at = 0;
991
for (const ColorRegion &region : color_regions) {
992
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.");
993
if (p_start_key.length() < region.start_key.length()) {
994
at++;
995
} else {
996
break;
997
}
998
}
999
1000
ColorRegion color_region;
1001
color_region.type = p_type;
1002
color_region.color = p_color;
1003
color_region.start_key = p_start_key;
1004
color_region.end_key = p_end_key;
1005
color_region.line_only = p_line_only;
1006
color_region.r_prefix = p_r_prefix;
1007
color_region.is_string = p_type == ColorRegion::TYPE_STRING || p_type == ColorRegion::TYPE_MULTILINE_STRING;
1008
color_region.is_comment = p_type == ColorRegion::TYPE_COMMENT || p_type == ColorRegion::TYPE_CODE_REGION;
1009
color_regions.insert(at, color_region);
1010
clear_highlighting_cache();
1011
}
1012
1013
Ref<EditorSyntaxHighlighter> GDScriptSyntaxHighlighter::_create() const {
1014
Ref<GDScriptSyntaxHighlighter> syntax_highlighter;
1015
syntax_highlighter.instantiate();
1016
return syntax_highlighter;
1017
}
1018
1019