1/**************************************************************************/
2/* text_shader_editor.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 "text_shader_editor.h"
32
33#include "core/version_generated.gen.h"
34#include "editor/editor_node.h"
35#include "editor/editor_scale.h"
36#include "editor/editor_settings.h"
37#include "editor/editor_string_names.h"
38#include "editor/filesystem_dock.h"
39#include "editor/project_settings_editor.h"
40#include "scene/gui/split_container.h"
41#include "servers/rendering/shader_preprocessor.h"
42#include "servers/rendering/shader_types.h"
43
44/*** SHADER SYNTAX HIGHLIGHTER ****/
45
46Dictionary GDShaderSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_line) {
47 Dictionary color_map;
48
49 for (const Point2i &region : disabled_branch_regions) {
50 if (p_line >= region.x && p_line <= region.y) {
51 Dictionary highlighter_info;
52 highlighter_info["color"] = disabled_branch_color;
53
54 color_map[0] = highlighter_info;
55 return color_map;
56 }
57 }
58
59 return CodeHighlighter::_get_line_syntax_highlighting_impl(p_line);
60}
61
62void GDShaderSyntaxHighlighter::add_disabled_branch_region(const Point2i &p_region) {
63 ERR_FAIL_COND(p_region.x < 0);
64 ERR_FAIL_COND(p_region.y < 0);
65
66 for (int i = 0; i < disabled_branch_regions.size(); i++) {
67 ERR_FAIL_COND_MSG(disabled_branch_regions[i].x == p_region.x, "Branch region with a start line '" + itos(p_region.x) + "' already exists.");
68 }
69
70 Point2i disabled_branch_region;
71 disabled_branch_region.x = p_region.x;
72 disabled_branch_region.y = p_region.y;
73 disabled_branch_regions.push_back(disabled_branch_region);
74
75 clear_highlighting_cache();
76}
77
78void GDShaderSyntaxHighlighter::clear_disabled_branch_regions() {
79 disabled_branch_regions.clear();
80 clear_highlighting_cache();
81}
82
83void GDShaderSyntaxHighlighter::set_disabled_branch_color(const Color &p_color) {
84 disabled_branch_color = p_color;
85 clear_highlighting_cache();
86}
87
88/*** SHADER SCRIPT EDITOR ****/
89
90static bool saved_warnings_enabled = false;
91static bool saved_treat_warning_as_errors = false;
92static HashMap<ShaderWarning::Code, bool> saved_warnings;
93static uint32_t saved_warning_flags = 0U;
94
95void ShaderTextEditor::_notification(int p_what) {
96 switch (p_what) {
97 case NOTIFICATION_THEME_CHANGED: {
98 if (is_visible_in_tree()) {
99 _load_theme_settings();
100 if (warnings.size() > 0 && last_compile_result == OK) {
101 warnings_panel->clear();
102 _update_warning_panel();
103 }
104 }
105 } break;
106 }
107}
108
109Ref<Shader> ShaderTextEditor::get_edited_shader() const {
110 return shader;
111}
112
113Ref<ShaderInclude> ShaderTextEditor::get_edited_shader_include() const {
114 return shader_inc;
115}
116
117void ShaderTextEditor::set_edited_shader(const Ref<Shader> &p_shader) {
118 set_edited_shader(p_shader, p_shader->get_code());
119}
120
121void ShaderTextEditor::set_edited_shader(const Ref<Shader> &p_shader, const String &p_code) {
122 if (shader == p_shader) {
123 return;
124 }
125 if (shader.is_valid()) {
126 shader->disconnect_changed(callable_mp(this, &ShaderTextEditor::_shader_changed));
127 }
128 shader = p_shader;
129 shader_inc = Ref<ShaderInclude>();
130
131 set_edited_code(p_code);
132
133 if (shader.is_valid()) {
134 shader->connect_changed(callable_mp(this, &ShaderTextEditor::_shader_changed));
135 }
136}
137
138void ShaderTextEditor::set_edited_shader_include(const Ref<ShaderInclude> &p_shader_inc) {
139 set_edited_shader_include(p_shader_inc, p_shader_inc->get_code());
140}
141
142void ShaderTextEditor::_shader_changed() {
143 // This function is used for dependencies (include changing changes main shader and forces it to revalidate)
144 if (block_shader_changed) {
145 return;
146 }
147 dependencies_version++;
148 _validate_script();
149}
150
151void ShaderTextEditor::set_edited_shader_include(const Ref<ShaderInclude> &p_shader_inc, const String &p_code) {
152 if (shader_inc == p_shader_inc) {
153 return;
154 }
155 if (shader_inc.is_valid()) {
156 shader_inc->disconnect_changed(callable_mp(this, &ShaderTextEditor::_shader_changed));
157 }
158 shader_inc = p_shader_inc;
159 shader = Ref<Shader>();
160
161 set_edited_code(p_code);
162
163 if (shader_inc.is_valid()) {
164 shader_inc->connect_changed(callable_mp(this, &ShaderTextEditor::_shader_changed));
165 }
166}
167
168void ShaderTextEditor::set_edited_code(const String &p_code) {
169 _load_theme_settings();
170
171 get_text_editor()->set_text(p_code);
172 get_text_editor()->clear_undo_history();
173 get_text_editor()->call_deferred(SNAME("set_h_scroll"), 0);
174 get_text_editor()->call_deferred(SNAME("set_v_scroll"), 0);
175 get_text_editor()->tag_saved_version();
176
177 _validate_script();
178 _line_col_changed();
179}
180
181void ShaderTextEditor::reload_text() {
182 ERR_FAIL_COND(shader.is_null() && shader_inc.is_null());
183
184 String code;
185 if (shader.is_valid()) {
186 code = shader->get_code();
187 } else {
188 code = shader_inc->get_code();
189 }
190
191 CodeEdit *te = get_text_editor();
192 int column = te->get_caret_column();
193 int row = te->get_caret_line();
194 int h = te->get_h_scroll();
195 int v = te->get_v_scroll();
196
197 te->set_text(code);
198 te->set_caret_line(row);
199 te->set_caret_column(column);
200 te->set_h_scroll(h);
201 te->set_v_scroll(v);
202
203 te->tag_saved_version();
204
205 update_line_and_column();
206}
207
208void ShaderTextEditor::set_warnings_panel(RichTextLabel *p_warnings_panel) {
209 warnings_panel = p_warnings_panel;
210}
211
212void ShaderTextEditor::_load_theme_settings() {
213 CodeEdit *te = get_text_editor();
214 Color updated_marked_line_color = EDITOR_GET("text_editor/theme/highlighting/mark_color");
215 if (updated_marked_line_color != marked_line_color) {
216 for (int i = 0; i < te->get_line_count(); i++) {
217 if (te->get_line_background_color(i) == marked_line_color) {
218 te->set_line_background_color(i, updated_marked_line_color);
219 }
220 }
221 marked_line_color = updated_marked_line_color;
222 }
223
224 syntax_highlighter->set_number_color(EDITOR_GET("text_editor/theme/highlighting/number_color"));
225 syntax_highlighter->set_symbol_color(EDITOR_GET("text_editor/theme/highlighting/symbol_color"));
226 syntax_highlighter->set_function_color(EDITOR_GET("text_editor/theme/highlighting/function_color"));
227 syntax_highlighter->set_member_variable_color(EDITOR_GET("text_editor/theme/highlighting/member_variable_color"));
228
229 syntax_highlighter->clear_keyword_colors();
230
231 const Color keyword_color = EDITOR_GET("text_editor/theme/highlighting/keyword_color");
232 const Color control_flow_keyword_color = EDITOR_GET("text_editor/theme/highlighting/control_flow_keyword_color");
233
234 List<String> keywords;
235 ShaderLanguage::get_keyword_list(&keywords);
236
237 for (const String &E : keywords) {
238 if (ShaderLanguage::is_control_flow_keyword(E)) {
239 syntax_highlighter->add_keyword_color(E, control_flow_keyword_color);
240 } else {
241 syntax_highlighter->add_keyword_color(E, keyword_color);
242 }
243 }
244
245 List<String> pp_keywords;
246 ShaderPreprocessor::get_keyword_list(&pp_keywords, false);
247
248 for (const String &E : pp_keywords) {
249 syntax_highlighter->add_keyword_color(E, control_flow_keyword_color);
250 }
251
252 // Colorize built-ins like `COLOR` differently to make them easier
253 // to distinguish from keywords at a quick glance.
254
255 List<String> built_ins;
256
257 if (shader_inc.is_valid()) {
258 for (int i = 0; i < RenderingServer::SHADER_MAX; i++) {
259 for (const KeyValue<StringName, ShaderLanguage::FunctionInfo> &E : ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(i))) {
260 for (const KeyValue<StringName, ShaderLanguage::BuiltInInfo> &F : E.value.built_ins) {
261 built_ins.push_back(F.key);
262 }
263 }
264
265 const Vector<ShaderLanguage::ModeInfo> &modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(i));
266
267 for (int j = 0; j < modes.size(); j++) {
268 const ShaderLanguage::ModeInfo &mode_info = modes[j];
269
270 if (!mode_info.options.is_empty()) {
271 for (int k = 0; k < mode_info.options.size(); k++) {
272 built_ins.push_back(String(mode_info.name) + "_" + String(mode_info.options[k]));
273 }
274 } else {
275 built_ins.push_back(String(mode_info.name));
276 }
277 }
278 }
279 } else if (shader.is_valid()) {
280 for (const KeyValue<StringName, ShaderLanguage::FunctionInfo> &E : ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(shader->get_mode()))) {
281 for (const KeyValue<StringName, ShaderLanguage::BuiltInInfo> &F : E.value.built_ins) {
282 built_ins.push_back(F.key);
283 }
284 }
285
286 const Vector<ShaderLanguage::ModeInfo> &modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(shader->get_mode()));
287
288 for (int i = 0; i < modes.size(); i++) {
289 const ShaderLanguage::ModeInfo &mode_info = modes[i];
290
291 if (!mode_info.options.is_empty()) {
292 for (int j = 0; j < mode_info.options.size(); j++) {
293 built_ins.push_back(String(mode_info.name) + "_" + String(mode_info.options[j]));
294 }
295 } else {
296 built_ins.push_back(String(mode_info.name));
297 }
298 }
299 }
300
301 const Color user_type_color = EDITOR_GET("text_editor/theme/highlighting/user_type_color");
302
303 for (const String &E : built_ins) {
304 syntax_highlighter->add_keyword_color(E, user_type_color);
305 }
306
307 // Colorize comments.
308 const Color comment_color = EDITOR_GET("text_editor/theme/highlighting/comment_color");
309 syntax_highlighter->clear_color_regions();
310 syntax_highlighter->add_color_region("/*", "*/", comment_color, false);
311 syntax_highlighter->add_color_region("//", "", comment_color, true);
312
313 // Disabled preprocessor branches use translucent text color to be easier to distinguish from comments.
314 syntax_highlighter->set_disabled_branch_color(Color(EDITOR_GET("text_editor/theme/highlighting/text_color")) * Color(1, 1, 1, 0.5));
315
316 te->clear_comment_delimiters();
317 te->add_comment_delimiter("/*", "*/", false);
318 te->add_comment_delimiter("//", "", true);
319
320 if (!te->has_auto_brace_completion_open_key("/*")) {
321 te->add_auto_brace_completion_pair("/*", "*/");
322 }
323
324 // Colorize preprocessor include strings.
325 const Color string_color = EDITOR_GET("text_editor/theme/highlighting/string_color");
326 syntax_highlighter->add_color_region("\"", "\"", string_color, false);
327
328 if (warnings_panel) {
329 // Warnings panel.
330 warnings_panel->add_theme_font_override("normal_font", EditorNode::get_singleton()->get_editor_theme()->get_font(SNAME("main"), EditorStringName(EditorFonts)));
331 warnings_panel->add_theme_font_size_override("normal_font_size", EditorNode::get_singleton()->get_editor_theme()->get_font_size(SNAME("main_size"), EditorStringName(EditorFonts)));
332 }
333}
334
335void ShaderTextEditor::_check_shader_mode() {
336 String type = ShaderLanguage::get_shader_type(get_text_editor()->get_text());
337
338 Shader::Mode mode;
339
340 if (type == "canvas_item") {
341 mode = Shader::MODE_CANVAS_ITEM;
342 } else if (type == "particles") {
343 mode = Shader::MODE_PARTICLES;
344 } else if (type == "sky") {
345 mode = Shader::MODE_SKY;
346 } else if (type == "fog") {
347 mode = Shader::MODE_FOG;
348 } else {
349 mode = Shader::MODE_SPATIAL;
350 }
351
352 if (shader->get_mode() != mode) {
353 set_block_shader_changed(true);
354 shader->set_code(get_text_editor()->get_text());
355 set_block_shader_changed(false);
356 _load_theme_settings();
357 }
358}
359
360static ShaderLanguage::DataType _get_global_shader_uniform_type(const StringName &p_variable) {
361 RS::GlobalShaderParameterType gvt = RS::get_singleton()->global_shader_parameter_get_type(p_variable);
362 return (ShaderLanguage::DataType)RS::global_shader_uniform_type_get_shader_datatype(gvt);
363}
364
365static String complete_from_path;
366
367static void _complete_include_paths_search(EditorFileSystemDirectory *p_efsd, List<ScriptLanguage::CodeCompletionOption> *r_options) {
368 if (!p_efsd) {
369 return;
370 }
371 for (int i = 0; i < p_efsd->get_file_count(); i++) {
372 if (p_efsd->get_file_type(i) == SNAME("ShaderInclude")) {
373 String path = p_efsd->get_file_path(i);
374 if (path.begins_with(complete_from_path)) {
375 path = path.replace_first(complete_from_path, "");
376 }
377 r_options->push_back(ScriptLanguage::CodeCompletionOption(path, ScriptLanguage::CODE_COMPLETION_KIND_FILE_PATH));
378 }
379 }
380 for (int j = 0; j < p_efsd->get_subdir_count(); j++) {
381 _complete_include_paths_search(p_efsd->get_subdir(j), r_options);
382 }
383}
384
385static void _complete_include_paths(List<ScriptLanguage::CodeCompletionOption> *r_options) {
386 _complete_include_paths_search(EditorFileSystem::get_singleton()->get_filesystem(), r_options);
387}
388
389void ShaderTextEditor::_code_complete_script(const String &p_code, List<ScriptLanguage::CodeCompletionOption> *r_options) {
390 List<ScriptLanguage::CodeCompletionOption> pp_options;
391 List<ScriptLanguage::CodeCompletionOption> pp_defines;
392 ShaderPreprocessor preprocessor;
393 String code;
394 String resource_path = (shader.is_valid() ? shader->get_path() : shader_inc->get_path());
395 complete_from_path = resource_path.get_base_dir();
396 if (!complete_from_path.ends_with("/")) {
397 complete_from_path += "/";
398 }
399 preprocessor.preprocess(p_code, resource_path, code, nullptr, nullptr, nullptr, nullptr, &pp_options, &pp_defines, _complete_include_paths);
400 complete_from_path = String();
401 if (pp_options.size()) {
402 for (const ScriptLanguage::CodeCompletionOption &E : pp_options) {
403 r_options->push_back(E);
404 }
405 return;
406 }
407 for (const ScriptLanguage::CodeCompletionOption &E : pp_defines) {
408 r_options->push_back(E);
409 }
410
411 ShaderLanguage sl;
412 String calltip;
413 ShaderLanguage::ShaderCompileInfo comp_info;
414 comp_info.global_shader_uniform_type_func = _get_global_shader_uniform_type;
415
416 if (shader.is_null()) {
417 comp_info.is_include = true;
418
419 sl.complete(code, comp_info, r_options, calltip);
420 get_text_editor()->set_code_hint(calltip);
421 return;
422 }
423 _check_shader_mode();
424 comp_info.functions = ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(shader->get_mode()));
425 comp_info.render_modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(shader->get_mode()));
426 comp_info.shader_types = ShaderTypes::get_singleton()->get_types();
427
428 sl.complete(code, comp_info, r_options, calltip);
429 get_text_editor()->set_code_hint(calltip);
430}
431
432void ShaderTextEditor::_validate_script() {
433 emit_signal(SNAME("script_changed")); // Ensure to notify that it changed, so it is applied
434
435 String code;
436
437 if (shader.is_valid()) {
438 _check_shader_mode();
439 code = shader->get_code();
440 } else {
441 code = shader_inc->get_code();
442 }
443
444 ShaderPreprocessor preprocessor;
445 String code_pp;
446 String error_pp;
447 List<ShaderPreprocessor::FilePosition> err_positions;
448 List<ShaderPreprocessor::Region> regions;
449 String filename;
450 if (shader.is_valid()) {
451 filename = shader->get_path();
452 } else if (shader_inc.is_valid()) {
453 filename = shader_inc->get_path();
454 }
455 last_compile_result = preprocessor.preprocess(code, filename, code_pp, &error_pp, &err_positions, &regions);
456
457 for (int i = 0; i < get_text_editor()->get_line_count(); i++) {
458 get_text_editor()->set_line_background_color(i, Color(0, 0, 0, 0));
459 }
460
461 syntax_highlighter->clear_disabled_branch_regions();
462 for (const ShaderPreprocessor::Region &region : regions) {
463 if (!region.enabled) {
464 if (filename != region.file) {
465 continue;
466 }
467 syntax_highlighter->add_disabled_branch_region(Point2i(region.from_line, region.to_line));
468 }
469 }
470
471 set_error("");
472 set_error_count(0);
473
474 if (last_compile_result != OK) {
475 //preprocessor error
476 ERR_FAIL_COND(err_positions.size() == 0);
477
478 String err_text = error_pp;
479 int err_line = err_positions.front()->get().line;
480 if (err_positions.size() == 1) {
481 // Error in main file
482 err_text = "error(" + itos(err_line) + "): " + err_text;
483 } else {
484 err_text = "error(" + itos(err_line) + ") in include " + err_positions.back()->get().file.get_file() + ":" + itos(err_positions.back()->get().line) + ": " + err_text;
485 set_error_count(err_positions.size() - 1);
486 }
487
488 set_error(err_text);
489 set_error_pos(err_line - 1, 0);
490 for (int i = 0; i < get_text_editor()->get_line_count(); i++) {
491 get_text_editor()->set_line_background_color(i, Color(0, 0, 0, 0));
492 }
493 get_text_editor()->set_line_background_color(err_line - 1, marked_line_color);
494
495 set_warning_count(0);
496
497 } else {
498 ShaderLanguage sl;
499
500 sl.enable_warning_checking(saved_warnings_enabled);
501 uint32_t flags = saved_warning_flags;
502 if (shader.is_null()) {
503 if (flags & ShaderWarning::UNUSED_CONSTANT) {
504 flags &= ~(ShaderWarning::UNUSED_CONSTANT);
505 }
506 if (flags & ShaderWarning::UNUSED_FUNCTION) {
507 flags &= ~(ShaderWarning::UNUSED_FUNCTION);
508 }
509 if (flags & ShaderWarning::UNUSED_STRUCT) {
510 flags &= ~(ShaderWarning::UNUSED_STRUCT);
511 }
512 if (flags & ShaderWarning::UNUSED_UNIFORM) {
513 flags &= ~(ShaderWarning::UNUSED_UNIFORM);
514 }
515 if (flags & ShaderWarning::UNUSED_VARYING) {
516 flags &= ~(ShaderWarning::UNUSED_VARYING);
517 }
518 }
519 sl.set_warning_flags(flags);
520
521 ShaderLanguage::ShaderCompileInfo comp_info;
522 comp_info.global_shader_uniform_type_func = _get_global_shader_uniform_type;
523
524 if (shader.is_null()) {
525 comp_info.is_include = true;
526 } else {
527 Shader::Mode mode = shader->get_mode();
528 comp_info.functions = ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(mode));
529 comp_info.render_modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(mode));
530 comp_info.shader_types = ShaderTypes::get_singleton()->get_types();
531 }
532
533 code = code_pp;
534 //compiler error
535 last_compile_result = sl.compile(code, comp_info);
536
537 if (last_compile_result != OK) {
538 String err_text;
539 int err_line;
540 Vector<ShaderLanguage::FilePosition> include_positions = sl.get_include_positions();
541 if (include_positions.size() > 1) {
542 //error is in an include
543 err_line = include_positions[0].line;
544 err_text = "error(" + itos(err_line) + ") in include " + include_positions[include_positions.size() - 1].file + ":" + itos(include_positions[include_positions.size() - 1].line) + ": " + sl.get_error_text();
545 set_error_count(include_positions.size() - 1);
546 } else {
547 err_line = sl.get_error_line();
548 err_text = "error(" + itos(err_line) + "): " + sl.get_error_text();
549 set_error_count(0);
550 }
551 set_error(err_text);
552 set_error_pos(err_line - 1, 0);
553 get_text_editor()->set_line_background_color(err_line - 1, marked_line_color);
554 } else {
555 set_error("");
556 }
557
558 if (warnings.size() > 0 || last_compile_result != OK) {
559 warnings_panel->clear();
560 }
561 warnings.clear();
562 for (List<ShaderWarning>::Element *E = sl.get_warnings_ptr(); E; E = E->next()) {
563 warnings.push_back(E->get());
564 }
565 if (warnings.size() > 0 && last_compile_result == OK) {
566 warnings.sort_custom<WarningsComparator>();
567 _update_warning_panel();
568 } else {
569 set_warning_count(0);
570 }
571 }
572
573 emit_signal(SNAME("script_validated"), last_compile_result == OK); // Notify that validation finished, to update the list of scripts
574}
575
576void ShaderTextEditor::_update_warning_panel() {
577 int warning_count = 0;
578
579 warnings_panel->push_table(2);
580 for (int i = 0; i < warnings.size(); i++) {
581 ShaderWarning &w = warnings[i];
582
583 if (warning_count == 0) {
584 if (saved_treat_warning_as_errors) {
585 String error_text = "error(" + itos(w.get_line()) + "): " + w.get_message() + " " + TTR("Warnings should be fixed to prevent errors.");
586 set_error_pos(w.get_line() - 1, 0);
587 set_error(error_text);
588 get_text_editor()->set_line_background_color(w.get_line() - 1, marked_line_color);
589 }
590 }
591
592 warning_count++;
593 int line = w.get_line();
594
595 // First cell.
596 warnings_panel->push_cell();
597 warnings_panel->push_color(warnings_panel->get_theme_color(SNAME("warning_color"), EditorStringName(Editor)));
598 if (line != -1) {
599 warnings_panel->push_meta(line - 1);
600 warnings_panel->add_text(TTR("Line") + " " + itos(line));
601 warnings_panel->add_text(" (" + w.get_name() + "):");
602 warnings_panel->pop(); // Meta goto.
603 } else {
604 warnings_panel->add_text(w.get_name() + ":");
605 }
606 warnings_panel->pop(); // Color.
607 warnings_panel->pop(); // Cell.
608
609 // Second cell.
610 warnings_panel->push_cell();
611 warnings_panel->add_text(w.get_message());
612 warnings_panel->pop(); // Cell.
613 }
614 warnings_panel->pop(); // Table.
615
616 set_warning_count(warning_count);
617}
618
619void ShaderTextEditor::_bind_methods() {
620 ADD_SIGNAL(MethodInfo("script_validated", PropertyInfo(Variant::BOOL, "valid")));
621}
622
623ShaderTextEditor::ShaderTextEditor() {
624 syntax_highlighter.instantiate();
625 get_text_editor()->set_syntax_highlighter(syntax_highlighter);
626}
627
628/*** SCRIPT EDITOR ******/
629
630void TextShaderEditor::_menu_option(int p_option) {
631 switch (p_option) {
632 case EDIT_UNDO: {
633 shader_editor->get_text_editor()->undo();
634 } break;
635 case EDIT_REDO: {
636 shader_editor->get_text_editor()->redo();
637 } break;
638 case EDIT_CUT: {
639 shader_editor->get_text_editor()->cut();
640 } break;
641 case EDIT_COPY: {
642 shader_editor->get_text_editor()->copy();
643 } break;
644 case EDIT_PASTE: {
645 shader_editor->get_text_editor()->paste();
646 } break;
647 case EDIT_SELECT_ALL: {
648 shader_editor->get_text_editor()->select_all();
649 } break;
650 case EDIT_MOVE_LINE_UP: {
651 shader_editor->move_lines_up();
652 } break;
653 case EDIT_MOVE_LINE_DOWN: {
654 shader_editor->move_lines_down();
655 } break;
656 case EDIT_INDENT: {
657 if (shader.is_null() && shader_inc.is_null()) {
658 return;
659 }
660 shader_editor->get_text_editor()->indent_lines();
661 } break;
662 case EDIT_UNINDENT: {
663 if (shader.is_null() && shader_inc.is_null()) {
664 return;
665 }
666 shader_editor->get_text_editor()->unindent_lines();
667 } break;
668 case EDIT_DELETE_LINE: {
669 shader_editor->delete_lines();
670 } break;
671 case EDIT_DUPLICATE_SELECTION: {
672 shader_editor->duplicate_selection();
673 } break;
674 case EDIT_TOGGLE_WORD_WRAP: {
675 TextEdit::LineWrappingMode wrap = shader_editor->get_text_editor()->get_line_wrapping_mode();
676 shader_editor->get_text_editor()->set_line_wrapping_mode(wrap == TextEdit::LINE_WRAPPING_BOUNDARY ? TextEdit::LINE_WRAPPING_NONE : TextEdit::LINE_WRAPPING_BOUNDARY);
677 } break;
678 case EDIT_TOGGLE_COMMENT: {
679 if (shader.is_null() && shader_inc.is_null()) {
680 return;
681 }
682 shader_editor->toggle_inline_comment("//");
683 } break;
684 case EDIT_COMPLETE: {
685 shader_editor->get_text_editor()->request_code_completion();
686 } break;
687 case SEARCH_FIND: {
688 shader_editor->get_find_replace_bar()->popup_search();
689 } break;
690 case SEARCH_FIND_NEXT: {
691 shader_editor->get_find_replace_bar()->search_next();
692 } break;
693 case SEARCH_FIND_PREV: {
694 shader_editor->get_find_replace_bar()->search_prev();
695 } break;
696 case SEARCH_REPLACE: {
697 shader_editor->get_find_replace_bar()->popup_replace();
698 } break;
699 case SEARCH_GOTO_LINE: {
700 goto_line_dialog->popup_find_line(shader_editor->get_text_editor());
701 } break;
702 case BOOKMARK_TOGGLE: {
703 shader_editor->toggle_bookmark();
704 } break;
705 case BOOKMARK_GOTO_NEXT: {
706 shader_editor->goto_next_bookmark();
707 } break;
708 case BOOKMARK_GOTO_PREV: {
709 shader_editor->goto_prev_bookmark();
710 } break;
711 case BOOKMARK_REMOVE_ALL: {
712 shader_editor->remove_all_bookmarks();
713 } break;
714 case HELP_DOCS: {
715 OS::get_singleton()->shell_open(vformat("%s/tutorials/shaders/shader_reference/index.html", VERSION_DOCS_URL));
716 } break;
717 }
718 if (p_option != SEARCH_FIND && p_option != SEARCH_REPLACE && p_option != SEARCH_GOTO_LINE) {
719 shader_editor->get_text_editor()->call_deferred(SNAME("grab_focus"));
720 }
721}
722
723void TextShaderEditor::_notification(int p_what) {
724 switch (p_what) {
725 case NOTIFICATION_ENTER_TREE:
726 case NOTIFICATION_THEME_CHANGED: {
727 PopupMenu *popup = help_menu->get_popup();
728 popup->set_item_icon(popup->get_item_index(HELP_DOCS), get_editor_theme_icon(SNAME("ExternalLink")));
729 } break;
730
731 case NOTIFICATION_APPLICATION_FOCUS_IN: {
732 _check_for_external_edit();
733 } break;
734 }
735}
736
737void TextShaderEditor::_editor_settings_changed() {
738 shader_editor->update_editor_settings();
739
740 shader_editor->get_text_editor()->add_theme_constant_override("line_spacing", EDITOR_GET("text_editor/appearance/whitespace/line_spacing"));
741 shader_editor->get_text_editor()->set_draw_breakpoints_gutter(false);
742 shader_editor->get_text_editor()->set_draw_executing_lines_gutter(false);
743
744 trim_trailing_whitespace_on_save = EDITOR_GET("text_editor/behavior/files/trim_trailing_whitespace_on_save");
745}
746
747void TextShaderEditor::_show_warnings_panel(bool p_show) {
748 warnings_panel->set_visible(p_show);
749}
750
751void TextShaderEditor::_warning_clicked(Variant p_line) {
752 if (p_line.get_type() == Variant::INT) {
753 shader_editor->get_text_editor()->set_caret_line(p_line.operator int64_t());
754 }
755}
756
757void TextShaderEditor::_bind_methods() {
758 ClassDB::bind_method("_show_warnings_panel", &TextShaderEditor::_show_warnings_panel);
759 ClassDB::bind_method("_warning_clicked", &TextShaderEditor::_warning_clicked);
760
761 ADD_SIGNAL(MethodInfo("validation_changed"));
762}
763
764void TextShaderEditor::ensure_select_current() {
765}
766
767void TextShaderEditor::goto_line_selection(int p_line, int p_begin, int p_end) {
768 shader_editor->goto_line_selection(p_line, p_begin, p_end);
769}
770
771void TextShaderEditor::_project_settings_changed() {
772 _update_warnings(true);
773}
774
775void TextShaderEditor::_update_warnings(bool p_validate) {
776 bool changed = false;
777
778 bool warnings_enabled = GLOBAL_GET("debug/shader_language/warnings/enable").booleanize();
779 if (warnings_enabled != saved_warnings_enabled) {
780 saved_warnings_enabled = warnings_enabled;
781 changed = true;
782 }
783
784 bool treat_warning_as_errors = GLOBAL_GET("debug/shader_language/warnings/treat_warnings_as_errors").booleanize();
785 if (treat_warning_as_errors != saved_treat_warning_as_errors) {
786 saved_treat_warning_as_errors = treat_warning_as_errors;
787 changed = true;
788 }
789
790 bool update_flags = false;
791
792 for (int i = 0; i < ShaderWarning::WARNING_MAX; i++) {
793 ShaderWarning::Code code = (ShaderWarning::Code)i;
794 bool value = GLOBAL_GET("debug/shader_language/warnings/" + ShaderWarning::get_name_from_code(code).to_lower());
795
796 if (saved_warnings[code] != value) {
797 saved_warnings[code] = value;
798 update_flags = true;
799 changed = true;
800 }
801 }
802
803 if (update_flags) {
804 saved_warning_flags = (uint32_t)ShaderWarning::get_flags_from_codemap(saved_warnings);
805 }
806
807 if (p_validate && changed && shader_editor && shader_editor->get_edited_shader().is_valid()) {
808 shader_editor->validate_script();
809 }
810}
811
812void TextShaderEditor::_check_for_external_edit() {
813 bool use_autoreload = bool(EDITOR_GET("text_editor/behavior/files/auto_reload_scripts_on_external_change"));
814
815 if (shader_inc.is_valid()) {
816 if (shader_inc->get_last_modified_time() != FileAccess::get_modified_time(shader_inc->get_path())) {
817 if (use_autoreload) {
818 _reload_shader_include_from_disk();
819 } else {
820 disk_changed->call_deferred(SNAME("popup_centered"));
821 }
822 }
823 return;
824 }
825
826 if (shader.is_null() || shader->is_built_in()) {
827 return;
828 }
829
830 if (shader->get_last_modified_time() != FileAccess::get_modified_time(shader->get_path())) {
831 if (use_autoreload) {
832 _reload_shader_from_disk();
833 } else {
834 disk_changed->call_deferred(SNAME("popup_centered"));
835 }
836 }
837}
838
839void TextShaderEditor::_reload_shader_from_disk() {
840 Ref<Shader> rel_shader = ResourceLoader::load(shader->get_path(), shader->get_class(), ResourceFormatLoader::CACHE_MODE_IGNORE);
841 ERR_FAIL_COND(!rel_shader.is_valid());
842
843 shader_editor->set_block_shader_changed(true);
844 shader->set_code(rel_shader->get_code());
845 shader_editor->set_block_shader_changed(false);
846 shader->set_last_modified_time(rel_shader->get_last_modified_time());
847 shader_editor->reload_text();
848}
849
850void TextShaderEditor::_reload_shader_include_from_disk() {
851 Ref<ShaderInclude> rel_shader_include = ResourceLoader::load(shader_inc->get_path(), shader_inc->get_class(), ResourceFormatLoader::CACHE_MODE_IGNORE);
852 ERR_FAIL_COND(!rel_shader_include.is_valid());
853
854 shader_editor->set_block_shader_changed(true);
855 shader_inc->set_code(rel_shader_include->get_code());
856 shader_editor->set_block_shader_changed(false);
857 shader_inc->set_last_modified_time(rel_shader_include->get_last_modified_time());
858 shader_editor->reload_text();
859}
860
861void TextShaderEditor::_reload() {
862 if (shader.is_valid()) {
863 _reload_shader_from_disk();
864 } else if (shader_inc.is_valid()) {
865 _reload_shader_include_from_disk();
866 }
867}
868
869void TextShaderEditor::edit(const Ref<Shader> &p_shader) {
870 if (p_shader.is_null() || !p_shader->is_text_shader()) {
871 return;
872 }
873
874 if (shader == p_shader) {
875 return;
876 }
877
878 shader = p_shader;
879 shader_inc = Ref<ShaderInclude>();
880
881 shader_editor->set_edited_shader(shader);
882}
883
884void TextShaderEditor::edit(const Ref<ShaderInclude> &p_shader_inc) {
885 if (p_shader_inc.is_null()) {
886 return;
887 }
888
889 if (shader_inc == p_shader_inc) {
890 return;
891 }
892
893 shader_inc = p_shader_inc;
894 shader = Ref<Shader>();
895
896 shader_editor->set_edited_shader_include(p_shader_inc);
897}
898
899void TextShaderEditor::save_external_data(const String &p_str) {
900 if (shader.is_null() && shader_inc.is_null()) {
901 disk_changed->hide();
902 return;
903 }
904
905 if (trim_trailing_whitespace_on_save) {
906 trim_trailing_whitespace();
907 }
908
909 apply_shaders();
910
911 Ref<Shader> edited_shader = shader_editor->get_edited_shader();
912 if (edited_shader.is_valid()) {
913 ResourceSaver::save(edited_shader);
914 }
915 if (shader.is_valid() && shader != edited_shader) {
916 ResourceSaver::save(shader);
917 }
918
919 Ref<ShaderInclude> edited_shader_inc = shader_editor->get_edited_shader_include();
920 if (edited_shader_inc.is_valid()) {
921 ResourceSaver::save(edited_shader_inc);
922 }
923 if (shader_inc.is_valid() && shader_inc != edited_shader_inc) {
924 ResourceSaver::save(shader_inc);
925 }
926 shader_editor->get_text_editor()->tag_saved_version();
927
928 disk_changed->hide();
929}
930
931void TextShaderEditor::trim_trailing_whitespace() {
932 shader_editor->trim_trailing_whitespace();
933}
934
935void TextShaderEditor::validate_script() {
936 shader_editor->_validate_script();
937}
938
939bool TextShaderEditor::is_unsaved() const {
940 return shader_editor->get_text_editor()->get_saved_version() != shader_editor->get_text_editor()->get_version();
941}
942
943void TextShaderEditor::tag_saved_version() {
944 shader_editor->get_text_editor()->tag_saved_version();
945}
946
947void TextShaderEditor::apply_shaders() {
948 String editor_code = shader_editor->get_text_editor()->get_text();
949 if (shader.is_valid()) {
950 String shader_code = shader->get_code();
951 if (shader_code != editor_code || dependencies_version != shader_editor->get_dependencies_version()) {
952 shader_editor->set_block_shader_changed(true);
953 shader->set_code(editor_code);
954 shader_editor->set_block_shader_changed(false);
955 shader->set_edited(true);
956 }
957 }
958 if (shader_inc.is_valid()) {
959 String shader_inc_code = shader_inc->get_code();
960 if (shader_inc_code != editor_code || dependencies_version != shader_editor->get_dependencies_version()) {
961 shader_editor->set_block_shader_changed(true);
962 shader_inc->set_code(editor_code);
963 shader_editor->set_block_shader_changed(false);
964 shader_inc->set_edited(true);
965 }
966 }
967
968 dependencies_version = shader_editor->get_dependencies_version();
969}
970
971void TextShaderEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) {
972 Ref<InputEventMouseButton> mb = ev;
973
974 if (mb.is_valid()) {
975 if (mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed()) {
976 CodeEdit *tx = shader_editor->get_text_editor();
977
978 Point2i pos = tx->get_line_column_at_pos(mb->get_global_position() - tx->get_global_position());
979 int row = pos.y;
980 int col = pos.x;
981 tx->set_move_caret_on_right_click_enabled(EDITOR_GET("text_editor/behavior/navigation/move_caret_on_right_click"));
982
983 if (tx->is_move_caret_on_right_click_enabled()) {
984 tx->remove_secondary_carets();
985 if (tx->has_selection()) {
986 int from_line = tx->get_selection_from_line();
987 int to_line = tx->get_selection_to_line();
988 int from_column = tx->get_selection_from_column();
989 int to_column = tx->get_selection_to_column();
990
991 if (row < from_line || row > to_line || (row == from_line && col < from_column) || (row == to_line && col > to_column)) {
992 // Right click is outside the selected text
993 tx->deselect();
994 }
995 }
996 if (!tx->has_selection()) {
997 tx->set_caret_line(row, true, false);
998 tx->set_caret_column(col);
999 }
1000 }
1001 _make_context_menu(tx->has_selection(), get_local_mouse_position());
1002 }
1003 }
1004
1005 Ref<InputEventKey> k = ev;
1006 if (k.is_valid() && k->is_pressed() && k->is_action("ui_menu", true)) {
1007 CodeEdit *tx = shader_editor->get_text_editor();
1008 tx->adjust_viewport_to_caret();
1009 _make_context_menu(tx->has_selection(), (get_global_transform().inverse() * tx->get_global_transform()).xform(tx->get_caret_draw_pos()));
1010 context_menu->grab_focus();
1011 }
1012}
1013
1014void TextShaderEditor::_update_bookmark_list() {
1015 bookmarks_menu->clear();
1016
1017 bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_bookmark"), BOOKMARK_TOGGLE);
1018 bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/remove_all_bookmarks"), BOOKMARK_REMOVE_ALL);
1019 bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_next_bookmark"), BOOKMARK_GOTO_NEXT);
1020 bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_previous_bookmark"), BOOKMARK_GOTO_PREV);
1021
1022 PackedInt32Array bookmark_list = shader_editor->get_text_editor()->get_bookmarked_lines();
1023 if (bookmark_list.size() == 0) {
1024 return;
1025 }
1026
1027 bookmarks_menu->add_separator();
1028
1029 for (int i = 0; i < bookmark_list.size(); i++) {
1030 String line = shader_editor->get_text_editor()->get_line(bookmark_list[i]).strip_edges();
1031 // Limit the size of the line if too big.
1032 if (line.length() > 50) {
1033 line = line.substr(0, 50);
1034 }
1035
1036 bookmarks_menu->add_item(String::num((int)bookmark_list[i] + 1) + " - \"" + line + "\"");
1037 bookmarks_menu->set_item_metadata(-1, bookmark_list[i]);
1038 }
1039}
1040
1041void TextShaderEditor::_bookmark_item_pressed(int p_idx) {
1042 if (p_idx < 4) { // Any item before the separator.
1043 _menu_option(bookmarks_menu->get_item_id(p_idx));
1044 } else {
1045 shader_editor->goto_line(bookmarks_menu->get_item_metadata(p_idx));
1046 }
1047}
1048
1049void TextShaderEditor::_make_context_menu(bool p_selection, Vector2 p_position) {
1050 context_menu->clear();
1051 if (p_selection) {
1052 context_menu->add_shortcut(ED_GET_SHORTCUT("ui_cut"), EDIT_CUT);
1053 context_menu->add_shortcut(ED_GET_SHORTCUT("ui_copy"), EDIT_COPY);
1054 }
1055
1056 context_menu->add_shortcut(ED_GET_SHORTCUT("ui_paste"), EDIT_PASTE);
1057 context_menu->add_separator();
1058 context_menu->add_shortcut(ED_GET_SHORTCUT("ui_text_select_all"), EDIT_SELECT_ALL);
1059 context_menu->add_shortcut(ED_GET_SHORTCUT("ui_undo"), EDIT_UNDO);
1060 context_menu->add_shortcut(ED_GET_SHORTCUT("ui_redo"), EDIT_REDO);
1061
1062 context_menu->add_separator();
1063 context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent"), EDIT_INDENT);
1064 context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/unindent"), EDIT_UNINDENT);
1065 context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_comment"), EDIT_TOGGLE_COMMENT);
1066 context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_bookmark"), BOOKMARK_TOGGLE);
1067
1068 context_menu->set_position(get_screen_position() + p_position);
1069 context_menu->reset_size();
1070 context_menu->popup();
1071}
1072
1073TextShaderEditor::TextShaderEditor() {
1074 _update_warnings(false);
1075
1076 shader_editor = memnew(ShaderTextEditor);
1077
1078 shader_editor->connect("script_validated", callable_mp(this, &TextShaderEditor::_script_validated));
1079
1080 shader_editor->set_v_size_flags(SIZE_EXPAND_FILL);
1081 shader_editor->add_theme_constant_override("separation", 0);
1082 shader_editor->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
1083
1084 shader_editor->connect("show_warnings_panel", callable_mp(this, &TextShaderEditor::_show_warnings_panel));
1085 shader_editor->connect("script_changed", callable_mp(this, &TextShaderEditor::apply_shaders));
1086 EditorSettings::get_singleton()->connect("settings_changed", callable_mp(this, &TextShaderEditor::_editor_settings_changed));
1087 ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &TextShaderEditor::_project_settings_changed));
1088
1089 shader_editor->get_text_editor()->set_code_hint_draw_below(EDITOR_GET("text_editor/completion/put_callhint_tooltip_below_current_line"));
1090
1091 shader_editor->get_text_editor()->set_symbol_lookup_on_click_enabled(true);
1092 shader_editor->get_text_editor()->set_context_menu_enabled(false);
1093 shader_editor->get_text_editor()->connect("gui_input", callable_mp(this, &TextShaderEditor::_text_edit_gui_input));
1094
1095 shader_editor->update_editor_settings();
1096
1097 context_menu = memnew(PopupMenu);
1098 add_child(context_menu);
1099 context_menu->connect("id_pressed", callable_mp(this, &TextShaderEditor::_menu_option));
1100
1101 VBoxContainer *main_container = memnew(VBoxContainer);
1102 HBoxContainer *hbc = memnew(HBoxContainer);
1103
1104 edit_menu = memnew(MenuButton);
1105 edit_menu->set_shortcut_context(this);
1106 edit_menu->set_text(TTR("Edit"));
1107 edit_menu->set_switch_on_hover(true);
1108
1109 edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_undo"), EDIT_UNDO);
1110 edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_redo"), EDIT_REDO);
1111 edit_menu->get_popup()->add_separator();
1112 edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_cut"), EDIT_CUT);
1113 edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_copy"), EDIT_COPY);
1114 edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_paste"), EDIT_PASTE);
1115 edit_menu->get_popup()->add_separator();
1116 edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_text_select_all"), EDIT_SELECT_ALL);
1117 edit_menu->get_popup()->add_separator();
1118 edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/move_up"), EDIT_MOVE_LINE_UP);
1119 edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/move_down"), EDIT_MOVE_LINE_DOWN);
1120 edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent"), EDIT_INDENT);
1121 edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/unindent"), EDIT_UNINDENT);
1122 edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/delete_line"), EDIT_DELETE_LINE);
1123 edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_comment"), EDIT_TOGGLE_COMMENT);
1124 edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/duplicate_selection"), EDIT_DUPLICATE_SELECTION);
1125 edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_word_wrap"), EDIT_TOGGLE_WORD_WRAP);
1126 edit_menu->get_popup()->add_separator();
1127 edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_text_completion_query"), EDIT_COMPLETE);
1128 edit_menu->get_popup()->connect("id_pressed", callable_mp(this, &TextShaderEditor::_menu_option));
1129
1130 search_menu = memnew(MenuButton);
1131 search_menu->set_shortcut_context(this);
1132 search_menu->set_text(TTR("Search"));
1133 search_menu->set_switch_on_hover(true);
1134
1135 search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find"), SEARCH_FIND);
1136 search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_next"), SEARCH_FIND_NEXT);
1137 search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_previous"), SEARCH_FIND_PREV);
1138 search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/replace"), SEARCH_REPLACE);
1139 search_menu->get_popup()->connect("id_pressed", callable_mp(this, &TextShaderEditor::_menu_option));
1140
1141 MenuButton *goto_menu = memnew(MenuButton);
1142 goto_menu->set_shortcut_context(this);
1143 goto_menu->set_text(TTR("Go To"));
1144 goto_menu->set_switch_on_hover(true);
1145 goto_menu->get_popup()->connect("id_pressed", callable_mp(this, &TextShaderEditor::_menu_option));
1146
1147 goto_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_line"), SEARCH_GOTO_LINE);
1148 goto_menu->get_popup()->add_separator();
1149
1150 bookmarks_menu = memnew(PopupMenu);
1151 bookmarks_menu->set_name("Bookmarks");
1152 goto_menu->get_popup()->add_child(bookmarks_menu);
1153 goto_menu->get_popup()->add_submenu_item(TTR("Bookmarks"), "Bookmarks");
1154 _update_bookmark_list();
1155 bookmarks_menu->connect("about_to_popup", callable_mp(this, &TextShaderEditor::_update_bookmark_list));
1156 bookmarks_menu->connect("index_pressed", callable_mp(this, &TextShaderEditor::_bookmark_item_pressed));
1157
1158 help_menu = memnew(MenuButton);
1159 help_menu->set_text(TTR("Help"));
1160 help_menu->set_switch_on_hover(true);
1161 help_menu->get_popup()->add_item(TTR("Online Docs"), HELP_DOCS);
1162 help_menu->get_popup()->connect("id_pressed", callable_mp(this, &TextShaderEditor::_menu_option));
1163
1164 add_child(main_container);
1165 main_container->add_child(hbc);
1166 hbc->add_child(search_menu);
1167 hbc->add_child(edit_menu);
1168 hbc->add_child(goto_menu);
1169 hbc->add_child(help_menu);
1170 hbc->add_theme_style_override("panel", EditorNode::get_singleton()->get_editor_theme()->get_stylebox(SNAME("ScriptEditorPanel"), EditorStringName(EditorStyles)));
1171
1172 VSplitContainer *editor_box = memnew(VSplitContainer);
1173 main_container->add_child(editor_box);
1174 editor_box->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
1175 editor_box->set_v_size_flags(SIZE_EXPAND_FILL);
1176 editor_box->add_child(shader_editor);
1177
1178 FindReplaceBar *bar = memnew(FindReplaceBar);
1179 main_container->add_child(bar);
1180 bar->hide();
1181 shader_editor->set_find_replace_bar(bar);
1182
1183 warnings_panel = memnew(RichTextLabel);
1184 warnings_panel->set_custom_minimum_size(Size2(0, 100 * EDSCALE));
1185 warnings_panel->set_h_size_flags(SIZE_EXPAND_FILL);
1186 warnings_panel->set_meta_underline(true);
1187 warnings_panel->set_selection_enabled(true);
1188 warnings_panel->set_context_menu_enabled(true);
1189 warnings_panel->set_focus_mode(FOCUS_CLICK);
1190 warnings_panel->hide();
1191 warnings_panel->connect("meta_clicked", callable_mp(this, &TextShaderEditor::_warning_clicked));
1192 editor_box->add_child(warnings_panel);
1193 shader_editor->set_warnings_panel(warnings_panel);
1194
1195 goto_line_dialog = memnew(GotoLineDialog);
1196 add_child(goto_line_dialog);
1197
1198 disk_changed = memnew(ConfirmationDialog);
1199
1200 VBoxContainer *vbc = memnew(VBoxContainer);
1201 disk_changed->add_child(vbc);
1202
1203 Label *dl = memnew(Label);
1204 dl->set_text(TTR("This shader has been modified on disk.\nWhat action should be taken?"));
1205 vbc->add_child(dl);
1206
1207 disk_changed->connect("confirmed", callable_mp(this, &TextShaderEditor::_reload));
1208 disk_changed->set_ok_button_text(TTR("Reload"));
1209
1210 disk_changed->add_button(TTR("Resave"), !DisplayServer::get_singleton()->get_swap_cancel_ok(), "resave");
1211 disk_changed->connect("custom_action", callable_mp(this, &TextShaderEditor::save_external_data));
1212
1213 trim_trailing_whitespace_on_save = EDITOR_GET("text_editor/behavior/files/trim_trailing_whitespace_on_save");
1214
1215 add_child(disk_changed);
1216
1217 _editor_settings_changed();
1218}
1219