]> Untitled Git - wolf-seeking-sheep.git/blob - addons/dialogic/Editor/TimelineEditor/TextEditor/syntax_highlighter.gd
Initial Godot project with Dialogic 2.0-Alpha-17
[wolf-seeking-sheep.git] / addons / dialogic / Editor / TimelineEditor / TextEditor / syntax_highlighter.gd
1 @tool
2 extends SyntaxHighlighter
3
4 ## Syntax highlighter for the dialogic text timeline editor and text events in the visual editor.
5
6 enum Modes {TEXT_EVENT_ONLY, FULL_HIGHLIGHTING}
7 var mode := Modes.FULL_HIGHLIGHTING
8
9
10 ## RegEx's
11 var word_regex := RegEx.new()
12 var region_regex := RegEx.new()
13 var number_regex := RegEx.create_from_string(r"(\d|\.)+")
14 var shortcode_regex := RegEx.create_from_string(r"\W*\[(?<id>\w*)(?<args>[^\]]*)?")
15 var shortcode_param_regex := RegEx.create_from_string(r'((?<parameter>[^\s=]*)\s*=\s*"(?<value>([^=]|\\=)*)(?<!\\)")')
16
17 ## Colors
18 var normal_color: Color
19 var translation_id_color: Color
20
21 var code_flow_color: Color
22 var boolean_operator_color: Color
23 var variable_color: Color
24 var string_color: Color
25 var character_name_color: Color
26 var character_portrait_color: Color
27
28 var shortcode_events := {}
29 var custom_syntax_events := []
30 var text_event: DialogicTextEvent = null
31
32
33 func _init() -> void:
34         update_colors()
35         DialogicUtil.get_dialogic_plugin().get_editor_interface().get_base_control().theme_changed.connect(update_colors)
36
37
38 func update_colors() -> void:
39         if not DialogicUtil.get_dialogic_plugin():
40                 return
41         var editor_settings: EditorSettings = DialogicUtil.get_dialogic_plugin().get_editor_interface().get_editor_settings()
42         normal_color = editor_settings.get('text_editor/theme/highlighting/text_color')
43         translation_id_color = editor_settings.get('text_editor/theme/highlighting/comment_color')
44
45         code_flow_color = editor_settings.get("text_editor/theme/highlighting/control_flow_keyword_color")
46         boolean_operator_color = code_flow_color.lightened(0.5)
47         variable_color = editor_settings.get('text_editor/theme/highlighting/engine_type_color')
48         string_color = editor_settings.get('text_editor/theme/highlighting/string_color')
49         character_name_color = editor_settings.get('text_editor/theme/highlighting/symbol_color').lerp(normal_color, 0.3)
50         character_portrait_color = character_name_color.lerp(normal_color, 0.5)
51
52
53 func _get_line_syntax_highlighting(line:int) -> Dictionary:
54         var str_line := get_text_edit().get_line(line)
55
56         if shortcode_events.is_empty():
57                 for event in DialogicResourceUtil.get_event_cache():
58                         if event.get_shortcode() != 'default_shortcode':
59                                 shortcode_events[event.get_shortcode()] = event
60                         else:
61                                 custom_syntax_events.append(event)
62                         if event is DialogicTextEvent:
63                                 text_event = event
64                                 text_event.load_text_effects()
65
66         var dict := {}
67         dict[0] = {'color':normal_color}
68
69         dict = color_translation_id(dict, str_line)
70
71         if mode == Modes.FULL_HIGHLIGHTING:
72                 if line_is_shortcode_event(line):
73                         var full_event := get_full_event(line)
74                         var result := shortcode_regex.search(full_event)
75                         if result:
76                                 if result.get_string('id') in shortcode_events:
77                                         if full_event.begins_with(str_line):
78                                                 dict[result.get_start('id')] = {"color":shortcode_events[result.get_string('id')].event_color.lerp(normal_color, 0.4)}
79                                                 dict[result.get_end('id')] = {"color":normal_color}
80
81                                                 if result.get_string('args'):
82                                                         color_shortcode_content(dict, str_line, result.get_start('args'), result.get_end('args'), shortcode_events[result.get_string('id')].event_color)
83                                         else:
84                                                 color_shortcode_content(dict, str_line, 0, 0, shortcode_events[result.get_string('id')].event_color)
85                         return fix_dict(dict)
86
87                 else:
88                         for event in custom_syntax_events:
89                                 if event.is_valid_event(str_line.strip_edges()):
90                                         dict = event._get_syntax_highlighting(self, dict, str_line)
91                                         return fix_dict(dict)
92
93         else:
94                 dict = text_event._get_syntax_highlighting(self, dict, str_line)
95         return fix_dict(dict)
96
97
98 func line_is_shortcode_event(line_idx:int) -> bool:
99         var str_line := get_text_edit().get_line(line_idx)
100         if text_event.text_effects_regex.search(str_line.get_slice(' ', 0)):
101                 return false
102
103         if str_line.strip_edges().begins_with("["):
104                 return true
105
106         if line_idx > 0 and get_text_edit().get_line(line_idx-1).ends_with('\\'):
107                 return line_is_shortcode_event(line_idx-1)
108
109         return false
110
111
112 func get_full_event(line_idx:int) -> String:
113         var str_line := get_text_edit().get_line(line_idx)
114         var offset := 1
115         # Add previous lines
116         while get_text_edit().get_line(line_idx-offset).ends_with('\\'):
117                 str_line = get_text_edit().get_line(line_idx-offset).trim_suffix('\\')+"\n"+str_line
118                 offset += 1
119
120         # This is commented out, as it is not needed right now.
121         # However without it, this isn't actually the full event.
122         # Might need to be included some day.
123         #offset = 0
124         ## Add following lines
125         #while get_text_edit().get_line(line_idx+offset).ends_with('\\'):
126                 #str_line = str_line.trim_suffix('\\')+"\n"+get_text_edit().get_line(line_idx+offset)
127                 #offset += 1
128
129         return str_line
130
131 func fix_dict(dict:Dictionary) -> Dictionary:
132         var d := {}
133         var k := dict.keys()
134         k.sort()
135         for i in k:
136                 d[i] = dict[i]
137         return d
138
139
140 func color_condition(dict:Dictionary, line:String, from:int = 0, to:int = 0) -> Dictionary:
141         dict = color_word(dict, code_flow_color, line, 'or',  from, to)
142         dict = color_word(dict, code_flow_color, line, 'and', from, to)
143         dict = color_word(dict, code_flow_color, line, '==',  from, to)
144         dict = color_word(dict, code_flow_color, line, '!=',  from, to)
145         if !">=" in line:
146                 dict = color_word(dict, code_flow_color, line, '>',   from, to)
147         else:
148                 dict = color_word(dict, code_flow_color, line, '>=',  from, to)
149         if !"<=" in line:
150                 dict = color_word(dict, code_flow_color, line, '<',   from, to)
151         else:
152                 dict = color_word(dict, code_flow_color, line, '<=',  from, to)
153         dict = color_region(dict, variable_color, line, '{', '}', from, to)
154         dict = color_region(dict, string_color, line, '"', '"', from, to)
155
156
157         return dict
158
159
160 func color_translation_id(dict:Dictionary, line:String) -> Dictionary:
161         dict = color_region(dict, translation_id_color, line, '#id:', '')
162         return dict
163
164
165 func color_word(dict:Dictionary, color:Color, line:String, word:String, from:int= 0, to:int = 0) -> Dictionary:
166         word_regex.compile("\\W(?<word>"+word+")\\W")
167         if to <= from:
168                 to = len(line)-1
169         for i in word_regex.search_all(line.substr(from, to-from+2)):
170                 dict[i.get_start('word')+from] = {'color':color}
171                 dict[i.get_end('word')+from] = {'color':normal_color}
172         return dict
173
174
175 func color_region(dict:Dictionary, color:Color, line:String, start:String, end:String, from:int = 0, to:int = 0, base_color:Color=normal_color) -> Dictionary:
176         if start in "()[].":
177                 start = "\\"+start
178         if end in "()[].":
179                 end = "\\"+end
180
181         if end.is_empty():
182                 region_regex.compile("(?<!\\\\)"+start+".*")
183         else:
184                 region_regex.compile("(?<!\\\\)"+start+"((?!"+end+").)*"+end)
185         if to <= from:
186                 to = len(line)-1
187         for region in region_regex.search_all(line.substr(from, to-from+2)):
188                 dict[region.get_start()+from] = {'color':color}
189                 dict[region.get_end()+from] = {'color':base_color}
190         return dict
191
192
193 func color_shortcode_content(dict:Dictionary, line:String, from:int = 0, to:int = 0, base_color:=normal_color) -> Dictionary:
194         if to <= from:
195                 to = len(line)-1
196         var args_result := shortcode_param_regex.search_all(line.substr(from, to-from+2))
197         for x in args_result:
198                 dict[x.get_start()+from] = {"color":base_color.lerp(normal_color, 0.5)}
199                 dict[x.get_start('value')+from-1] = {"color":base_color.lerp(normal_color, 0.7)}
200                 dict[x.get_end()+from] = {"color":normal_color}
201         return dict