Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/editor/script/script_editor_plugin.cpp
9903 views
1
/**************************************************************************/
2
/* script_editor_plugin.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 "script_editor_plugin.h"
32
33
#include "core/config/project_settings.h"
34
#include "core/input/input.h"
35
#include "core/io/config_file.h"
36
#include "core/io/file_access.h"
37
#include "core/io/json.h"
38
#include "core/io/resource_loader.h"
39
#include "core/os/keyboard.h"
40
#include "core/os/os.h"
41
#include "core/string/fuzzy_search.h"
42
#include "core/version.h"
43
#include "editor/debugger/editor_debugger_node.h"
44
#include "editor/debugger/script_editor_debugger.h"
45
#include "editor/doc/editor_help_search.h"
46
#include "editor/docks/filesystem_dock.h"
47
#include "editor/docks/inspector_dock.h"
48
#include "editor/docks/node_dock.h"
49
#include "editor/editor_interface.h"
50
#include "editor/editor_main_screen.h"
51
#include "editor/editor_node.h"
52
#include "editor/editor_string_names.h"
53
#include "editor/file_system/editor_paths.h"
54
#include "editor/gui/code_editor.h"
55
#include "editor/gui/editor_bottom_panel.h"
56
#include "editor/gui/editor_file_dialog.h"
57
#include "editor/gui/editor_toaster.h"
58
#include "editor/gui/window_wrapper.h"
59
#include "editor/inspector/editor_context_menu_plugin.h"
60
#include "editor/run/editor_run_bar.h"
61
#include "editor/script/editor_script.h"
62
#include "editor/script/find_in_files.h"
63
#include "editor/settings/editor_command_palette.h"
64
#include "editor/settings/editor_settings.h"
65
#include "editor/shader/shader_editor_plugin.h"
66
#include "editor/shader/text_shader_editor.h"
67
#include "editor/themes/editor_scale.h"
68
#include "editor/themes/editor_theme_manager.h"
69
#include "scene/gui/separator.h"
70
#include "scene/gui/tab_container.h"
71
#include "scene/gui/texture_rect.h"
72
#include "scene/main/node.h"
73
#include "scene/main/window.h"
74
#include "script_text_editor.h"
75
#include "servers/display_server.h"
76
#include "text_editor.h"
77
78
/*** SYNTAX HIGHLIGHTER ****/
79
80
String EditorSyntaxHighlighter::_get_name() const {
81
String ret = "Unnamed";
82
GDVIRTUAL_CALL(_get_name, ret);
83
return ret;
84
}
85
86
PackedStringArray EditorSyntaxHighlighter::_get_supported_languages() const {
87
PackedStringArray ret;
88
GDVIRTUAL_CALL(_get_supported_languages, ret);
89
return ret;
90
}
91
92
Ref<EditorSyntaxHighlighter> EditorSyntaxHighlighter::_create() const {
93
Ref<EditorSyntaxHighlighter> syntax_highlighter;
94
if (GDVIRTUAL_IS_OVERRIDDEN(_create)) {
95
GDVIRTUAL_CALL(_create, syntax_highlighter);
96
} else {
97
syntax_highlighter.instantiate();
98
if (get_script_instance()) {
99
syntax_highlighter->set_script(get_script_instance()->get_script());
100
}
101
}
102
return syntax_highlighter;
103
}
104
105
void EditorSyntaxHighlighter::_bind_methods() {
106
ClassDB::bind_method(D_METHOD("_get_edited_resource"), &EditorSyntaxHighlighter::_get_edited_resource);
107
108
GDVIRTUAL_BIND(_get_name)
109
GDVIRTUAL_BIND(_get_supported_languages)
110
GDVIRTUAL_BIND(_create)
111
}
112
113
////
114
115
void EditorStandardSyntaxHighlighter::_update_cache() {
116
highlighter->set_text_edit(text_edit);
117
highlighter->clear_keyword_colors();
118
highlighter->clear_member_keyword_colors();
119
highlighter->clear_color_regions();
120
121
highlighter->set_symbol_color(EDITOR_GET("text_editor/theme/highlighting/symbol_color"));
122
highlighter->set_function_color(EDITOR_GET("text_editor/theme/highlighting/function_color"));
123
highlighter->set_number_color(EDITOR_GET("text_editor/theme/highlighting/number_color"));
124
highlighter->set_member_variable_color(EDITOR_GET("text_editor/theme/highlighting/member_variable_color"));
125
126
/* Engine types. */
127
const Color type_color = EDITOR_GET("text_editor/theme/highlighting/engine_type_color");
128
List<StringName> types;
129
ClassDB::get_class_list(&types);
130
for (const StringName &E : types) {
131
highlighter->add_keyword_color(E, type_color);
132
}
133
134
/* User types. */
135
const Color usertype_color = EDITOR_GET("text_editor/theme/highlighting/user_type_color");
136
List<StringName> global_classes;
137
ScriptServer::get_global_class_list(&global_classes);
138
for (const StringName &E : global_classes) {
139
highlighter->add_keyword_color(E, usertype_color);
140
}
141
142
/* Autoloads. */
143
HashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list();
144
for (const KeyValue<StringName, ProjectSettings::AutoloadInfo> &E : autoloads) {
145
const ProjectSettings::AutoloadInfo &info = E.value;
146
if (info.is_singleton) {
147
highlighter->add_keyword_color(info.name, usertype_color);
148
}
149
}
150
151
const ScriptLanguage *scr_lang = script_language;
152
StringName instance_base;
153
154
if (scr_lang == nullptr) {
155
const Ref<Script> scr = _get_edited_resource();
156
if (scr.is_valid()) {
157
scr_lang = scr->get_language();
158
instance_base = scr->get_instance_base_type();
159
}
160
}
161
162
if (scr_lang != nullptr) {
163
/* Core types. */
164
const Color basetype_color = EDITOR_GET("text_editor/theme/highlighting/base_type_color");
165
List<String> core_types;
166
scr_lang->get_core_type_words(&core_types);
167
for (const String &E : core_types) {
168
highlighter->add_keyword_color(E, basetype_color);
169
}
170
171
/* Reserved words. */
172
const Color keyword_color = EDITOR_GET("text_editor/theme/highlighting/keyword_color");
173
const Color control_flow_keyword_color = EDITOR_GET("text_editor/theme/highlighting/control_flow_keyword_color");
174
for (const String &keyword : scr_lang->get_reserved_words()) {
175
if (scr_lang->is_control_flow_keyword(keyword)) {
176
highlighter->add_keyword_color(keyword, control_flow_keyword_color);
177
} else {
178
highlighter->add_keyword_color(keyword, keyword_color);
179
}
180
}
181
182
/* Member types. */
183
const Color member_variable_color = EDITOR_GET("text_editor/theme/highlighting/member_variable_color");
184
if (instance_base != StringName()) {
185
List<PropertyInfo> plist;
186
ClassDB::get_property_list(instance_base, &plist);
187
for (const PropertyInfo &E : plist) {
188
String prop_name = E.name;
189
if (E.usage & PROPERTY_USAGE_CATEGORY || E.usage & PROPERTY_USAGE_GROUP || E.usage & PROPERTY_USAGE_SUBGROUP) {
190
continue;
191
}
192
if (prop_name.contains_char('/')) {
193
continue;
194
}
195
highlighter->add_member_keyword_color(prop_name, member_variable_color);
196
}
197
198
List<String> clist;
199
ClassDB::get_integer_constant_list(instance_base, &clist);
200
for (const String &E : clist) {
201
highlighter->add_member_keyword_color(E, member_variable_color);
202
}
203
}
204
205
/* Comments */
206
const Color comment_color = EDITOR_GET("text_editor/theme/highlighting/comment_color");
207
for (const String &comment : scr_lang->get_comment_delimiters()) {
208
String beg = comment.get_slicec(' ', 0);
209
String end = comment.get_slice_count(" ") > 1 ? comment.get_slicec(' ', 1) : String();
210
highlighter->add_color_region(beg, end, comment_color, end.is_empty());
211
}
212
213
/* Doc comments */
214
const Color doc_comment_color = EDITOR_GET("text_editor/theme/highlighting/doc_comment_color");
215
for (const String &doc_comment : scr_lang->get_doc_comment_delimiters()) {
216
String beg = doc_comment.get_slicec(' ', 0);
217
String end = doc_comment.get_slice_count(" ") > 1 ? doc_comment.get_slicec(' ', 1) : String();
218
highlighter->add_color_region(beg, end, doc_comment_color, end.is_empty());
219
}
220
221
/* Strings */
222
const Color string_color = EDITOR_GET("text_editor/theme/highlighting/string_color");
223
for (const String &string : scr_lang->get_string_delimiters()) {
224
String beg = string.get_slicec(' ', 0);
225
String end = string.get_slice_count(" ") > 1 ? string.get_slicec(' ', 1) : String();
226
highlighter->add_color_region(beg, end, string_color, end.is_empty());
227
}
228
}
229
}
230
231
Ref<EditorSyntaxHighlighter> EditorStandardSyntaxHighlighter::_create() const {
232
Ref<EditorStandardSyntaxHighlighter> syntax_highlighter;
233
syntax_highlighter.instantiate();
234
return syntax_highlighter;
235
}
236
237
////
238
239
Ref<EditorSyntaxHighlighter> EditorPlainTextSyntaxHighlighter::_create() const {
240
Ref<EditorPlainTextSyntaxHighlighter> syntax_highlighter;
241
syntax_highlighter.instantiate();
242
return syntax_highlighter;
243
}
244
245
////
246
247
void EditorJSONSyntaxHighlighter::_update_cache() {
248
highlighter->set_text_edit(text_edit);
249
highlighter->clear_keyword_colors();
250
highlighter->clear_member_keyword_colors();
251
highlighter->clear_color_regions();
252
253
highlighter->set_symbol_color(EDITOR_GET("text_editor/theme/highlighting/symbol_color"));
254
highlighter->set_number_color(EDITOR_GET("text_editor/theme/highlighting/number_color"));
255
256
const Color string_color = EDITOR_GET("text_editor/theme/highlighting/string_color");
257
highlighter->add_color_region("\"", "\"", string_color);
258
}
259
260
Ref<EditorSyntaxHighlighter> EditorJSONSyntaxHighlighter::_create() const {
261
Ref<EditorJSONSyntaxHighlighter> syntax_highlighter;
262
syntax_highlighter.instantiate();
263
return syntax_highlighter;
264
}
265
266
////
267
268
void EditorMarkdownSyntaxHighlighter::_update_cache() {
269
highlighter->set_text_edit(text_edit);
270
highlighter->clear_keyword_colors();
271
highlighter->clear_member_keyword_colors();
272
highlighter->clear_color_regions();
273
274
// Disable automatic symbolic highlights, as these don't make sense for prose.
275
highlighter->set_symbol_color(EDITOR_GET("text_editor/theme/highlighting/text_color"));
276
highlighter->set_number_color(EDITOR_GET("text_editor/theme/highlighting/text_color"));
277
highlighter->set_member_variable_color(EDITOR_GET("text_editor/theme/highlighting/text_color"));
278
highlighter->set_function_color(EDITOR_GET("text_editor/theme/highlighting/text_color"));
279
280
// Headings (any level).
281
const Color function_color = EDITOR_GET("text_editor/theme/highlighting/function_color");
282
highlighter->add_color_region("#", "", function_color);
283
284
// Bold.
285
highlighter->add_color_region("**", "**", function_color);
286
// `__bold__` syntax is not supported as color regions must begin with a symbol,
287
// not a character that is valid in an identifier.
288
289
// Code (both inline code and triple-backticks code blocks).
290
const Color code_color = EDITOR_GET("text_editor/theme/highlighting/engine_type_color");
291
highlighter->add_color_region("`", "`", code_color);
292
293
// Link (both references and inline links with URLs). The URL is not highlighted.
294
const Color link_color = EDITOR_GET("text_editor/theme/highlighting/keyword_color");
295
highlighter->add_color_region("[", "]", link_color);
296
297
// Quote.
298
const Color quote_color = EDITOR_GET("text_editor/theme/highlighting/string_color");
299
highlighter->add_color_region(">", "", quote_color, true);
300
301
// HTML comment, which is also supported in Markdown.
302
const Color comment_color = EDITOR_GET("text_editor/theme/highlighting/comment_color");
303
highlighter->add_color_region("<!--", "-->", comment_color);
304
}
305
306
Ref<EditorSyntaxHighlighter> EditorMarkdownSyntaxHighlighter::_create() const {
307
Ref<EditorMarkdownSyntaxHighlighter> syntax_highlighter;
308
syntax_highlighter.instantiate();
309
return syntax_highlighter;
310
}
311
312
///
313
314
void EditorConfigFileSyntaxHighlighter::_update_cache() {
315
highlighter->set_text_edit(text_edit);
316
highlighter->clear_keyword_colors();
317
highlighter->clear_member_keyword_colors();
318
highlighter->clear_color_regions();
319
320
highlighter->set_symbol_color(EDITOR_GET("text_editor/theme/highlighting/symbol_color"));
321
highlighter->set_number_color(EDITOR_GET("text_editor/theme/highlighting/number_color"));
322
// Assume that all function-style syntax is for types such as `Vector2()` and `PackedStringArray()`.
323
highlighter->set_function_color(EDITOR_GET("text_editor/theme/highlighting/base_type_color"));
324
325
// Disable member variable highlighting as it's not relevant for ConfigFile.
326
highlighter->set_member_variable_color(EDITOR_GET("text_editor/theme/highlighting/text_color"));
327
328
const Color string_color = EDITOR_GET("text_editor/theme/highlighting/string_color");
329
highlighter->add_color_region("\"", "\"", string_color);
330
331
// FIXME: Sections in ConfigFile must be at the beginning of a line. Otherwise, it can be an array within a line.
332
const Color function_color = EDITOR_GET("text_editor/theme/highlighting/function_color");
333
highlighter->add_color_region("[", "]", function_color);
334
335
const Color keyword_color = EDITOR_GET("text_editor/theme/highlighting/keyword_color");
336
highlighter->add_keyword_color("true", keyword_color);
337
highlighter->add_keyword_color("false", keyword_color);
338
highlighter->add_keyword_color("null", keyword_color);
339
highlighter->add_keyword_color("ExtResource", keyword_color);
340
highlighter->add_keyword_color("SubResource", keyword_color);
341
342
const Color comment_color = EDITOR_GET("text_editor/theme/highlighting/comment_color");
343
highlighter->add_color_region(";", "", comment_color);
344
}
345
346
Ref<EditorSyntaxHighlighter> EditorConfigFileSyntaxHighlighter::_create() const {
347
Ref<EditorConfigFileSyntaxHighlighter> syntax_highlighter;
348
syntax_highlighter.instantiate();
349
return syntax_highlighter;
350
}
351
352
////////////////////////////////////////////////////////////////////////////////
353
354
/*** SCRIPT EDITOR ****/
355
356
void ScriptEditorBase::_bind_methods() {
357
ClassDB::bind_method(D_METHOD("get_base_editor"), &ScriptEditorBase::get_base_editor);
358
ClassDB::bind_method(D_METHOD("add_syntax_highlighter", "highlighter"), &ScriptEditorBase::add_syntax_highlighter);
359
360
ADD_SIGNAL(MethodInfo("name_changed"));
361
ADD_SIGNAL(MethodInfo("edited_script_changed"));
362
ADD_SIGNAL(MethodInfo("request_help", PropertyInfo(Variant::STRING, "topic")));
363
ADD_SIGNAL(MethodInfo("request_open_script_at_line", PropertyInfo(Variant::OBJECT, "script"), PropertyInfo(Variant::INT, "line")));
364
ADD_SIGNAL(MethodInfo("request_save_history"));
365
ADD_SIGNAL(MethodInfo("request_save_previous_state", PropertyInfo(Variant::DICTIONARY, "state")));
366
ADD_SIGNAL(MethodInfo("go_to_help", PropertyInfo(Variant::STRING, "what")));
367
ADD_SIGNAL(MethodInfo("search_in_files_requested", PropertyInfo(Variant::STRING, "text")));
368
ADD_SIGNAL(MethodInfo("replace_in_files_requested", PropertyInfo(Variant::STRING, "text")));
369
ADD_SIGNAL(MethodInfo("go_to_method", PropertyInfo(Variant::OBJECT, "script"), PropertyInfo(Variant::STRING, "method")));
370
}
371
372
void ScriptEditorQuickOpen::popup_dialog(const Vector<String> &p_functions, bool p_dontclear) {
373
popup_centered_ratio(0.6);
374
if (p_dontclear) {
375
search_box->select_all();
376
} else {
377
search_box->clear();
378
}
379
search_box->grab_focus();
380
functions = p_functions;
381
_update_search();
382
}
383
384
void ScriptEditorQuickOpen::_text_changed(const String &p_newtext) {
385
_update_search();
386
}
387
388
void ScriptEditorQuickOpen::_sbox_input(const Ref<InputEvent> &p_event) {
389
// Redirect navigational key events to the tree.
390
Ref<InputEventKey> key = p_event;
391
if (key.is_valid()) {
392
if (key->is_action("ui_up", true) || key->is_action("ui_down", true) || key->is_action("ui_page_up") || key->is_action("ui_page_down")) {
393
search_options->gui_input(key);
394
search_box->accept_event();
395
}
396
}
397
}
398
399
void ScriptEditorQuickOpen::_update_search() {
400
search_options->clear();
401
TreeItem *root = search_options->create_item();
402
403
for (int i = 0; i < functions.size(); i++) {
404
String file = functions[i];
405
if ((search_box->get_text().is_empty() || file.containsn(search_box->get_text()))) {
406
TreeItem *ti = search_options->create_item(root);
407
ti->set_text(0, file);
408
if (root->get_first_child() == ti) {
409
ti->select(0);
410
}
411
}
412
}
413
414
get_ok_button()->set_disabled(root->get_first_child() == nullptr);
415
}
416
417
void ScriptEditorQuickOpen::_confirmed() {
418
TreeItem *ti = search_options->get_selected();
419
if (!ti) {
420
return;
421
}
422
int line = ti->get_text(0).get_slicec(':', 1).to_int();
423
424
emit_signal(SNAME("goto_line"), line - 1);
425
hide();
426
}
427
428
void ScriptEditorQuickOpen::_notification(int p_what) {
429
switch (p_what) {
430
case NOTIFICATION_ENTER_TREE: {
431
connect(SceneStringName(confirmed), callable_mp(this, &ScriptEditorQuickOpen::_confirmed));
432
433
search_box->set_clear_button_enabled(true);
434
[[fallthrough]];
435
}
436
case NOTIFICATION_VISIBILITY_CHANGED: {
437
search_box->set_right_icon(search_options->get_editor_theme_icon(SNAME("Search")));
438
} break;
439
440
case NOTIFICATION_EXIT_TREE: {
441
disconnect(SceneStringName(confirmed), callable_mp(this, &ScriptEditorQuickOpen::_confirmed));
442
} break;
443
}
444
}
445
446
void ScriptEditorQuickOpen::_bind_methods() {
447
ADD_SIGNAL(MethodInfo("goto_line", PropertyInfo(Variant::INT, "line")));
448
}
449
450
ScriptEditorQuickOpen::ScriptEditorQuickOpen() {
451
VBoxContainer *vbc = memnew(VBoxContainer);
452
add_child(vbc);
453
search_box = memnew(LineEdit);
454
vbc->add_margin_child(TTRC("Search:"), search_box);
455
search_box->connect(SceneStringName(text_changed), callable_mp(this, &ScriptEditorQuickOpen::_text_changed));
456
search_box->connect(SceneStringName(gui_input), callable_mp(this, &ScriptEditorQuickOpen::_sbox_input));
457
search_options = memnew(Tree);
458
vbc->add_margin_child(TTRC("Matches:"), search_options, true);
459
set_ok_button_text(TTRC("Open"));
460
get_ok_button()->set_disabled(true);
461
register_text_enter(search_box);
462
set_hide_on_ok(false);
463
search_options->connect("item_activated", callable_mp(this, &ScriptEditorQuickOpen::_confirmed));
464
search_options->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
465
search_options->set_hide_root(true);
466
search_options->set_hide_folding(true);
467
search_options->add_theme_constant_override("draw_guides", 1);
468
}
469
470
/////////////////////////////////
471
472
ScriptEditor *ScriptEditor::script_editor = nullptr;
473
474
/*** SCRIPT EDITOR ******/
475
476
String ScriptEditor::_get_debug_tooltip(const String &p_text, Node *p_se) {
477
if (EDITOR_GET("text_editor/behavior/documentation/enable_tooltips")) {
478
return String();
479
}
480
481
// NOTE: See also `ScriptTextEditor::_show_symbol_tooltip()` for documentation tooltips enabled.
482
String debug_value = EditorDebuggerNode::get_singleton()->get_var_value(p_text);
483
if (!debug_value.is_empty()) {
484
constexpr int DISPLAY_LIMIT = 1024;
485
if (debug_value.size() > DISPLAY_LIMIT) {
486
debug_value = debug_value.left(DISPLAY_LIMIT) + "... " + TTR("(truncated)");
487
}
488
debug_value = TTR("Current value: ") + debug_value;
489
}
490
491
return debug_value;
492
}
493
494
void ScriptEditor::_breaked(bool p_breaked, bool p_can_debug) {
495
if (external_editor_active) {
496
return;
497
}
498
499
for (int i = 0; i < tab_container->get_tab_count(); i++) {
500
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));
501
if (!se) {
502
continue;
503
}
504
505
se->set_debugger_active(p_breaked);
506
}
507
}
508
509
void ScriptEditor::_script_created(Ref<Script> p_script) {
510
EditorNode::get_singleton()->push_item(p_script.operator->());
511
}
512
513
void ScriptEditor::_goto_script_line2(int p_line) {
514
ScriptEditorBase *current = _get_current_editor();
515
if (current) {
516
current->goto_line(p_line);
517
}
518
}
519
520
void ScriptEditor::_goto_script_line(Ref<RefCounted> p_script, int p_line) {
521
Ref<Script> scr = Object::cast_to<Script>(*p_script);
522
if (scr.is_valid() && (scr->has_source_code() || scr->get_path().is_resource_file())) {
523
if (edit(p_script, p_line, 0)) {
524
EditorNode::get_singleton()->push_item(p_script.ptr());
525
526
ScriptEditorBase *current = _get_current_editor();
527
if (ScriptTextEditor *script_text_editor = Object::cast_to<ScriptTextEditor>(current)) {
528
script_text_editor->goto_line_centered(p_line);
529
} else if (current) {
530
current->goto_line(p_line);
531
}
532
533
_save_history();
534
}
535
}
536
}
537
538
void ScriptEditor::_set_execution(Ref<RefCounted> p_script, int p_line) {
539
Ref<Script> scr = Object::cast_to<Script>(*p_script);
540
if (scr.is_valid() && (scr->has_source_code() || scr->get_path().is_resource_file())) {
541
for (int i = 0; i < tab_container->get_tab_count(); i++) {
542
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));
543
if (!se) {
544
continue;
545
}
546
547
if ((scr.is_valid() && se->get_edited_resource() == p_script) || se->get_edited_resource()->get_path() == scr->get_path()) {
548
se->set_executing_line(p_line);
549
}
550
}
551
}
552
}
553
554
void ScriptEditor::_clear_execution(Ref<RefCounted> p_script) {
555
Ref<Script> scr = Object::cast_to<Script>(*p_script);
556
if (scr.is_valid() && (scr->has_source_code() || scr->get_path().is_resource_file())) {
557
for (int i = 0; i < tab_container->get_tab_count(); i++) {
558
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));
559
if (!se) {
560
continue;
561
}
562
563
if ((scr.is_valid() && se->get_edited_resource() == p_script) || se->get_edited_resource()->get_path() == scr->get_path()) {
564
se->clear_executing_line();
565
}
566
}
567
}
568
}
569
570
void ScriptEditor::_set_breakpoint(Ref<RefCounted> p_script, int p_line, bool p_enabled) {
571
Ref<Script> scr = Object::cast_to<Script>(*p_script);
572
if (scr.is_valid() && (scr->has_source_code() || scr->get_path().is_resource_file())) {
573
// Update if open.
574
for (int i = 0; i < tab_container->get_tab_count(); i++) {
575
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));
576
if (se && se->get_edited_resource()->get_path() == scr->get_path()) {
577
se->set_breakpoint(p_line, p_enabled);
578
return;
579
}
580
}
581
582
// Handle closed.
583
Dictionary state = script_editor_cache->get_value(scr->get_path(), "state");
584
Array breakpoints;
585
if (state.has("breakpoints")) {
586
breakpoints = state["breakpoints"];
587
}
588
589
if (breakpoints.has(p_line)) {
590
if (!p_enabled) {
591
breakpoints.erase(p_line);
592
}
593
} else if (p_enabled) {
594
breakpoints.push_back(p_line);
595
}
596
state["breakpoints"] = breakpoints;
597
script_editor_cache->set_value(scr->get_path(), "state", state);
598
EditorDebuggerNode::get_singleton()->set_breakpoint(scr->get_path(), p_line + 1, p_enabled);
599
}
600
}
601
602
void ScriptEditor::_clear_breakpoints() {
603
for (int i = 0; i < tab_container->get_tab_count(); i++) {
604
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));
605
if (se) {
606
se->clear_breakpoints();
607
}
608
}
609
610
// Clear from closed scripts.
611
Vector<String> cached_editors = script_editor_cache->get_sections();
612
for (const String &E : cached_editors) {
613
Array breakpoints = _get_cached_breakpoints_for_script(E);
614
for (int breakpoint : breakpoints) {
615
EditorDebuggerNode::get_singleton()->set_breakpoint(E, (int)breakpoint + 1, false);
616
}
617
618
if (breakpoints.size() > 0) {
619
Dictionary state = script_editor_cache->get_value(E, "state");
620
state["breakpoints"] = Array();
621
script_editor_cache->set_value(E, "state", state);
622
}
623
}
624
}
625
626
Array ScriptEditor::_get_cached_breakpoints_for_script(const String &p_path) const {
627
if (!ResourceLoader::exists(p_path, "Script") || p_path.begins_with("local://") || !script_editor_cache->has_section_key(p_path, "state")) {
628
return Array();
629
}
630
631
Dictionary state = script_editor_cache->get_value(p_path, "state");
632
if (!state.has("breakpoints")) {
633
return Array();
634
}
635
return state["breakpoints"];
636
}
637
638
ScriptEditorBase *ScriptEditor::_get_current_editor() const {
639
int selected = tab_container->get_current_tab();
640
if (selected < 0 || selected >= tab_container->get_tab_count()) {
641
return nullptr;
642
}
643
644
return Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(selected));
645
}
646
647
void ScriptEditor::_update_history_arrows() {
648
script_back->set_disabled(history_pos <= 0);
649
script_forward->set_disabled(history_pos >= history.size() - 1);
650
}
651
652
void ScriptEditor::_save_history() {
653
if (history_pos >= 0 && history_pos < history.size() && history[history_pos].control == tab_container->get_current_tab_control()) {
654
Node *n = tab_container->get_current_tab_control();
655
656
if (Object::cast_to<ScriptEditorBase>(n)) {
657
history.write[history_pos].state = Object::cast_to<ScriptEditorBase>(n)->get_navigation_state();
658
}
659
if (Object::cast_to<EditorHelp>(n)) {
660
history.write[history_pos].state = Object::cast_to<EditorHelp>(n)->get_scroll();
661
}
662
}
663
664
history.resize(history_pos + 1);
665
ScriptHistory sh;
666
sh.control = tab_container->get_current_tab_control();
667
sh.state = Variant();
668
669
history.push_back(sh);
670
history_pos++;
671
672
_update_history_arrows();
673
}
674
675
void ScriptEditor::_save_previous_state(Dictionary p_state) {
676
if (lock_history) {
677
// Done as a result of a deferred call triggered by set_edit_state().
678
lock_history = false;
679
return;
680
}
681
682
if (history_pos >= 0 && history_pos < history.size() && history[history_pos].control == tab_container->get_current_tab_control()) {
683
Node *n = tab_container->get_current_tab_control();
684
685
if (Object::cast_to<ScriptTextEditor>(n)) {
686
history.write[history_pos].state = p_state;
687
}
688
}
689
690
history.resize(history_pos + 1);
691
ScriptHistory sh;
692
sh.control = tab_container->get_current_tab_control();
693
sh.state = Variant();
694
695
history.push_back(sh);
696
history_pos++;
697
698
_update_history_arrows();
699
}
700
701
void ScriptEditor::_go_to_tab(int p_idx) {
702
ScriptEditorBase *current = _get_current_editor();
703
if (current) {
704
if (current->is_unsaved()) {
705
current->apply_code();
706
}
707
}
708
709
Control *c = tab_container->get_tab_control(p_idx);
710
if (!c) {
711
return;
712
}
713
714
if (history_pos >= 0 && history_pos < history.size() && history[history_pos].control == tab_container->get_current_tab_control()) {
715
Node *n = tab_container->get_current_tab_control();
716
717
if (Object::cast_to<ScriptEditorBase>(n)) {
718
history.write[history_pos].state = Object::cast_to<ScriptEditorBase>(n)->get_navigation_state();
719
}
720
if (Object::cast_to<EditorHelp>(n)) {
721
history.write[history_pos].state = Object::cast_to<EditorHelp>(n)->get_scroll();
722
}
723
}
724
725
history.resize(history_pos + 1);
726
ScriptHistory sh;
727
sh.control = c;
728
sh.state = Variant();
729
730
if (!lock_history && (history.is_empty() || history[history.size() - 1].control != sh.control)) {
731
history.push_back(sh);
732
history_pos++;
733
}
734
735
tab_container->set_current_tab(p_idx);
736
737
c = tab_container->get_current_tab_control();
738
739
if (Object::cast_to<ScriptEditorBase>(c)) {
740
script_name_label->set_text(Object::cast_to<ScriptEditorBase>(c)->get_name());
741
script_icon->set_texture(Object::cast_to<ScriptEditorBase>(c)->get_theme_icon());
742
if (is_visible_in_tree()) {
743
Object::cast_to<ScriptEditorBase>(c)->ensure_focus();
744
}
745
746
Ref<Script> scr = Object::cast_to<ScriptEditorBase>(c)->get_edited_resource();
747
if (scr.is_valid()) {
748
notify_script_changed(scr);
749
}
750
751
Object::cast_to<ScriptEditorBase>(c)->validate();
752
}
753
if (Object::cast_to<EditorHelp>(c)) {
754
script_name_label->set_text(Object::cast_to<EditorHelp>(c)->get_class());
755
script_icon->set_texture(get_editor_theme_icon(SNAME("Help")));
756
if (is_visible_in_tree()) {
757
Object::cast_to<EditorHelp>(c)->set_focused();
758
}
759
}
760
761
c->set_meta("__editor_pass", ++edit_pass);
762
_update_history_arrows();
763
_update_script_colors();
764
_update_members_overview();
765
_update_help_overview();
766
_update_selected_editor_menu();
767
_update_online_doc();
768
_update_members_overview_visibility();
769
_update_help_overview_visibility();
770
}
771
772
void ScriptEditor::_add_recent_script(const String &p_path) {
773
if (p_path.is_empty()) {
774
return;
775
}
776
777
Array rc = EditorSettings::get_singleton()->get_project_metadata("recent_files", "scripts", Array());
778
if (rc.has(p_path)) {
779
rc.erase(p_path);
780
}
781
rc.push_front(p_path);
782
if (rc.size() > 10) {
783
rc.resize(10);
784
}
785
786
EditorSettings::get_singleton()->set_project_metadata("recent_files", "scripts", rc);
787
_update_recent_scripts();
788
}
789
790
void ScriptEditor::_update_recent_scripts() {
791
Array rc = EditorSettings::get_singleton()->get_project_metadata("recent_files", "scripts", Array());
792
recent_scripts->clear();
793
794
String path;
795
for (int i = 0; i < rc.size(); i++) {
796
path = rc[i];
797
recent_scripts->add_item(path.replace("res://", ""));
798
}
799
800
recent_scripts->add_separator();
801
recent_scripts->add_shortcut(ED_GET_SHORTCUT("script_editor/clear_recent"));
802
recent_scripts->set_item_auto_translate_mode(-1, AUTO_TRANSLATE_MODE_ALWAYS);
803
recent_scripts->set_item_disabled(-1, rc.is_empty());
804
805
recent_scripts->reset_size();
806
}
807
808
void ScriptEditor::_open_recent_script(int p_idx) {
809
// clear button
810
if (p_idx == recent_scripts->get_item_count() - 1) {
811
EditorSettings::get_singleton()->set_project_metadata("recent_files", "scripts", Array());
812
callable_mp(this, &ScriptEditor::_update_recent_scripts).call_deferred();
813
return;
814
}
815
816
Array rc = EditorSettings::get_singleton()->get_project_metadata("recent_files", "scripts", Array());
817
ERR_FAIL_INDEX(p_idx, rc.size());
818
819
String path = rc[p_idx];
820
// if its not on disk its a help file or deleted
821
if (FileAccess::exists(path)) {
822
List<String> extensions;
823
ResourceLoader::get_recognized_extensions_for_type("Script", &extensions);
824
ResourceLoader::get_recognized_extensions_for_type("JSON", &extensions);
825
826
if (extensions.find(path.get_extension())) {
827
Ref<Resource> scr = ResourceLoader::load(path);
828
if (scr.is_valid()) {
829
edit(scr, true);
830
return;
831
}
832
}
833
834
Error err;
835
Ref<TextFile> text_file = _load_text_file(path, &err);
836
if (text_file.is_valid()) {
837
edit(text_file, true);
838
return;
839
}
840
// if it's a path then it's most likely a deleted file not help
841
} else if (path.contains("::")) {
842
// built-in script
843
String res_path = path.get_slice("::", 0);
844
EditorNode::get_singleton()->load_scene_or_resource(res_path, false, false);
845
846
Ref<Script> scr = ResourceLoader::load(path);
847
if (scr.is_valid()) {
848
edit(scr, true);
849
return;
850
}
851
} else if (!path.is_resource_file()) {
852
_help_class_open(path);
853
return;
854
}
855
856
rc.remove_at(p_idx);
857
EditorSettings::get_singleton()->set_project_metadata("recent_files", "scripts", rc);
858
_update_recent_scripts();
859
_show_error_dialog(path);
860
}
861
862
void ScriptEditor::_show_error_dialog(const String &p_path) {
863
error_dialog->set_text(vformat(TTR("Can't open '%s'. The file could have been moved or deleted."), p_path));
864
error_dialog->popup_centered();
865
}
866
867
void ScriptEditor::_close_tab(int p_idx, bool p_save, bool p_history_back) {
868
int selected = p_idx;
869
if (selected < 0 || selected >= tab_container->get_tab_count()) {
870
return;
871
}
872
873
Node *tselected = tab_container->get_tab_control(selected);
874
875
ScriptEditorBase *current = Object::cast_to<ScriptEditorBase>(tselected);
876
if (current) {
877
Ref<Resource> file = current->get_edited_resource();
878
if (p_save && file.is_valid()) {
879
// Do not try to save internal scripts, but prompt to save in-memory
880
// scripts which are not saved to disk yet (have empty path).
881
if (!file->is_built_in()) {
882
save_current_script();
883
}
884
}
885
if (file.is_valid()) {
886
if (!file->get_path().is_empty()) {
887
// Only saved scripts can be restored.
888
previous_scripts.push_back(file->get_path());
889
}
890
891
Ref<Script> scr = file;
892
if (scr.is_valid()) {
893
notify_script_close(scr);
894
}
895
}
896
}
897
898
// roll back to previous tab
899
if (p_history_back) {
900
_history_back();
901
}
902
903
//remove from history
904
history.resize(history_pos + 1);
905
906
for (int i = 0; i < history.size(); i++) {
907
if (history[i].control == tselected) {
908
history.remove_at(i);
909
i--;
910
history_pos--;
911
}
912
}
913
914
if (history_pos >= history.size()) {
915
history_pos = history.size() - 1;
916
}
917
918
int idx = tab_container->get_current_tab();
919
if (current) {
920
current->clear_edit_menu();
921
_save_editor_state(current);
922
}
923
memdelete(tselected);
924
925
if (script_close_queue.is_empty()) {
926
if (idx >= tab_container->get_tab_count()) {
927
idx = tab_container->get_tab_count() - 1;
928
}
929
if (idx >= 0) {
930
if (history_pos >= 0) {
931
idx = tab_container->get_tab_idx_from_control(history[history_pos].control);
932
}
933
_go_to_tab(idx);
934
} else {
935
_update_selected_editor_menu();
936
_update_online_doc();
937
}
938
939
_update_history_arrows();
940
_update_script_names();
941
_save_layout();
942
_update_find_replace_bar();
943
}
944
}
945
946
void ScriptEditor::_close_current_tab(bool p_save, bool p_history_back) {
947
_close_tab(tab_container->get_current_tab(), p_save, p_history_back);
948
}
949
950
void ScriptEditor::_close_discard_current_tab(const String &p_str) {
951
Ref<Script> scr = _get_current_script();
952
if (scr.is_valid()) {
953
scr->reload_from_file();
954
}
955
_close_tab(tab_container->get_current_tab(), false);
956
erase_tab_confirm->hide();
957
}
958
959
void ScriptEditor::_close_docs_tab() {
960
int child_count = tab_container->get_tab_count();
961
for (int i = child_count - 1; i >= 0; i--) {
962
EditorHelp *se = Object::cast_to<EditorHelp>(tab_container->get_tab_control(i));
963
964
if (se) {
965
_close_tab(i, true, false);
966
}
967
}
968
}
969
970
void ScriptEditor::_copy_script_path() {
971
ScriptEditorBase *se = _get_current_editor();
972
if (se) {
973
Ref<Resource> scr = se->get_edited_resource();
974
DisplayServer::get_singleton()->clipboard_set(scr->get_path());
975
}
976
}
977
978
void ScriptEditor::_copy_script_uid() {
979
ScriptEditorBase *se = _get_current_editor();
980
if (se) {
981
Ref<Resource> scr = se->get_edited_resource();
982
ResourceUID::ID uid = ResourceLoader::get_resource_uid(scr->get_path());
983
DisplayServer::get_singleton()->clipboard_set(ResourceUID::get_singleton()->id_to_text(uid));
984
}
985
}
986
987
void ScriptEditor::_close_other_tabs() {
988
int current_idx = tab_container->get_current_tab();
989
for (int i = tab_container->get_tab_count() - 1; i >= 0; i--) {
990
if (i != current_idx) {
991
script_close_queue.push_back(i);
992
}
993
}
994
_queue_close_tabs();
995
}
996
997
void ScriptEditor::_close_tabs_below() {
998
int current_idx = tab_container->get_current_tab();
999
for (int i = tab_container->get_tab_count() - 1; i > current_idx; i--) {
1000
script_close_queue.push_back(i);
1001
}
1002
_go_to_tab(current_idx);
1003
_queue_close_tabs();
1004
}
1005
1006
void ScriptEditor::_close_all_tabs() {
1007
for (int i = tab_container->get_tab_count() - 1; i >= 0; i--) {
1008
script_close_queue.push_back(i);
1009
}
1010
_queue_close_tabs();
1011
}
1012
1013
void ScriptEditor::_queue_close_tabs() {
1014
while (!script_close_queue.is_empty()) {
1015
int idx = script_close_queue.front()->get();
1016
script_close_queue.pop_front();
1017
1018
tab_container->set_current_tab(idx);
1019
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(idx));
1020
if (se) {
1021
// Maybe there are unsaved changes.
1022
if (se->is_unsaved()) {
1023
_ask_close_current_unsaved_tab(se);
1024
erase_tab_confirm->connect(SceneStringName(visibility_changed), callable_mp(this, &ScriptEditor::_queue_close_tabs), CONNECT_ONE_SHOT);
1025
break;
1026
}
1027
}
1028
1029
_close_current_tab(false, false);
1030
}
1031
_update_find_replace_bar();
1032
}
1033
1034
void ScriptEditor::_ask_close_current_unsaved_tab(ScriptEditorBase *current) {
1035
erase_tab_confirm->set_text(TTR("Close and save changes?") + "\n\"" + current->get_name() + "\"");
1036
erase_tab_confirm->popup_centered();
1037
}
1038
1039
void ScriptEditor::_resave_scripts(const String &p_str) {
1040
apply_scripts();
1041
1042
for (int i = 0; i < tab_container->get_tab_count(); i++) {
1043
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));
1044
if (!se) {
1045
continue;
1046
}
1047
1048
Ref<Resource> scr = se->get_edited_resource();
1049
1050
if (scr->is_built_in()) {
1051
continue; // Internal script, who cares.
1052
}
1053
1054
if (trim_trailing_whitespace_on_save) {
1055
se->trim_trailing_whitespace();
1056
}
1057
1058
if (trim_final_newlines_on_save) {
1059
se->trim_final_newlines();
1060
}
1061
1062
if (convert_indent_on_save) {
1063
se->convert_indent();
1064
}
1065
1066
Ref<TextFile> text_file = scr;
1067
if (text_file.is_valid()) {
1068
se->apply_code();
1069
_save_text_file(text_file, text_file->get_path());
1070
break;
1071
} else {
1072
EditorNode::get_singleton()->save_resource(scr);
1073
}
1074
se->tag_saved_version();
1075
}
1076
1077
disk_changed->hide();
1078
}
1079
1080
void ScriptEditor::_res_saved_callback(const Ref<Resource> &p_res) {
1081
for (int i = 0; i < tab_container->get_tab_count(); i++) {
1082
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));
1083
if (!se) {
1084
continue;
1085
}
1086
1087
Ref<Resource> scr = se->get_edited_resource();
1088
1089
if (scr == p_res) {
1090
se->tag_saved_version();
1091
}
1092
}
1093
1094
if (p_res.is_valid()) {
1095
// In case the Resource has built-in scripts.
1096
_mark_built_in_scripts_as_saved(p_res->get_path());
1097
}
1098
1099
_update_script_names();
1100
Ref<Script> scr = p_res;
1101
if (scr.is_valid()) {
1102
trigger_live_script_reload(scr->get_path());
1103
}
1104
}
1105
1106
void ScriptEditor::_scene_saved_callback(const String &p_path) {
1107
// If scene was saved, mark all built-in scripts from that scene as saved.
1108
_mark_built_in_scripts_as_saved(p_path);
1109
}
1110
1111
void ScriptEditor::_mark_built_in_scripts_as_saved(const String &p_parent_path) {
1112
for (int i = 0; i < tab_container->get_tab_count(); i++) {
1113
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));
1114
if (!se) {
1115
continue;
1116
}
1117
1118
Ref<Resource> edited_res = se->get_edited_resource();
1119
1120
if (!edited_res->is_built_in()) {
1121
continue; // External script, who cares.
1122
}
1123
1124
if (edited_res->get_path().get_slice("::", 0) == p_parent_path) {
1125
se->tag_saved_version();
1126
}
1127
1128
Ref<Script> scr = edited_res;
1129
if (scr.is_valid()) {
1130
trigger_live_script_reload(scr->get_path());
1131
clear_docs_from_script(scr);
1132
scr->reload(true);
1133
update_docs_from_script(scr);
1134
}
1135
}
1136
}
1137
1138
void ScriptEditor::trigger_live_script_reload(const String &p_script_path) {
1139
if (!script_paths_to_reload.has(p_script_path)) {
1140
Ref<Script> reloaded_script = ResourceCache::get_ref(p_script_path);
1141
if (reloaded_script.is_null()) {
1142
reloaded_script = ResourceLoader::load(p_script_path);
1143
}
1144
if (reloaded_script.is_valid()) {
1145
if (!reloaded_script->get_language()->validate(reloaded_script->get_source_code(), p_script_path)) {
1146
// Script has errors, don't live reload.
1147
return;
1148
}
1149
}
1150
1151
script_paths_to_reload.append(p_script_path);
1152
}
1153
if (!pending_auto_reload && auto_reload_running_scripts) {
1154
callable_mp(this, &ScriptEditor::_live_auto_reload_running_scripts).call_deferred();
1155
pending_auto_reload = true;
1156
}
1157
}
1158
1159
void ScriptEditor::trigger_live_script_reload_all() {
1160
if (!pending_auto_reload && auto_reload_running_scripts) {
1161
call_deferred(SNAME("_live_auto_reload_running_scripts"));
1162
pending_auto_reload = true;
1163
reload_all_scripts = true;
1164
}
1165
}
1166
1167
void ScriptEditor::_live_auto_reload_running_scripts() {
1168
pending_auto_reload = false;
1169
if (reload_all_scripts) {
1170
EditorDebuggerNode::get_singleton()->reload_all_scripts();
1171
} else {
1172
EditorDebuggerNode::get_singleton()->reload_scripts(script_paths_to_reload);
1173
}
1174
reload_all_scripts = false;
1175
script_paths_to_reload.clear();
1176
}
1177
1178
bool ScriptEditor::_test_script_times_on_disk(Ref<Resource> p_for_script) {
1179
disk_changed_list->clear();
1180
TreeItem *r = disk_changed_list->create_item();
1181
1182
bool need_ask = false;
1183
bool need_reload = false;
1184
bool use_autoreload = EDITOR_GET("text_editor/behavior/files/auto_reload_scripts_on_external_change");
1185
1186
for (int i = 0; i < tab_container->get_tab_count(); i++) {
1187
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));
1188
if (se) {
1189
Ref<Resource> edited_res = se->get_edited_resource();
1190
if (p_for_script.is_valid() && edited_res.is_valid() && p_for_script != edited_res) {
1191
continue;
1192
}
1193
1194
if (edited_res->is_built_in()) {
1195
continue; // Internal script, who cares.
1196
}
1197
1198
uint64_t last_date = se->edited_file_data.last_modified_time;
1199
uint64_t date = FileAccess::get_modified_time(se->edited_file_data.path);
1200
1201
if (last_date != date) {
1202
TreeItem *ti = disk_changed_list->create_item(r);
1203
ti->set_text(0, se->edited_file_data.path.get_file());
1204
1205
if (!use_autoreload || se->is_unsaved()) {
1206
need_ask = true;
1207
}
1208
need_reload = true;
1209
}
1210
}
1211
}
1212
1213
if (need_reload) {
1214
if (!need_ask) {
1215
script_editor->reload_scripts();
1216
need_reload = false;
1217
} else {
1218
callable_mp((Window *)disk_changed, &Window::popup_centered_ratio).call_deferred(0.3);
1219
}
1220
}
1221
1222
return need_reload;
1223
}
1224
1225
void _import_text_editor_theme(const String &p_file) {
1226
if (p_file.get_extension() != "tet") {
1227
EditorToaster::get_singleton()->popup_str(TTR("Importing theme failed. File is not a text editor theme file (.tet)."), EditorToaster::SEVERITY_ERROR);
1228
return;
1229
}
1230
const String theme_name = p_file.get_file().get_basename();
1231
if (EditorSettings::is_default_text_editor_theme(theme_name.to_lower())) {
1232
EditorToaster::get_singleton()->popup_str(TTR("Importing theme failed. File name cannot be 'Default', 'Custom', or 'Godot 2'."), EditorToaster::SEVERITY_ERROR);
1233
return;
1234
}
1235
1236
const String theme_dir = EditorPaths::get_singleton()->get_text_editor_themes_dir();
1237
Ref<DirAccess> d = DirAccess::open(theme_dir);
1238
Error err = FAILED;
1239
if (d.is_valid()) {
1240
err = d->copy(p_file, theme_dir.path_join(p_file.get_file()));
1241
}
1242
1243
if (err != OK) {
1244
EditorToaster::get_singleton()->popup_str(TTR("Importing theme failed. Failed to copy theme file."), EditorToaster::SEVERITY_ERROR);
1245
return;
1246
}
1247
1248
// Reload themes and switch to new theme.
1249
EditorSettings::get_singleton()->update_text_editor_themes_list();
1250
EditorSettings::get_singleton()->set_manually("text_editor/theme/color_theme", theme_name, true);
1251
EditorSettings::get_singleton()->notify_changes();
1252
}
1253
1254
void _save_text_editor_theme_as(const String &p_file) {
1255
String file = p_file;
1256
if (p_file.get_extension() != "tet") {
1257
file += ".tet";
1258
}
1259
1260
const String theme_name = file.get_file().get_basename();
1261
if (EditorSettings::is_default_text_editor_theme(theme_name.to_lower())) {
1262
EditorToaster::get_singleton()->popup_str(TTR("Saving theme failed. File name cannot be 'Default', 'Custom', or 'Godot 2'."), EditorToaster::SEVERITY_ERROR);
1263
return;
1264
}
1265
1266
const String theme_section = "color_theme";
1267
const Ref<ConfigFile> cf = memnew(ConfigFile);
1268
1269
// Use the keys from the Godot 2 theme to know which settings to save.
1270
HashMap<StringName, Color> text_colors = EditorSettings::get_godot2_text_editor_theme();
1271
text_colors.sort();
1272
for (const KeyValue<StringName, Color> &text_color : text_colors) {
1273
const Color val = EditorSettings::get_singleton()->get_setting(text_color.key);
1274
const String &key = text_color.key.operator String().replace("text_editor/theme/highlighting/", "");
1275
cf->set_value(theme_section, key, val.to_html());
1276
}
1277
1278
const Error err = cf->save(file);
1279
if (err != OK) {
1280
EditorToaster::get_singleton()->popup_str(TTR("Saving theme failed."), EditorToaster::SEVERITY_ERROR);
1281
return;
1282
}
1283
1284
// Reload themes and switch to saved theme.
1285
EditorSettings::get_singleton()->update_text_editor_themes_list();
1286
if (p_file.get_base_dir() == EditorPaths::get_singleton()->get_text_editor_themes_dir()) {
1287
// Don't need to emit signal or notify changes as the colors are already set.
1288
EditorSettings::get_singleton()->set_manually("text_editor/theme/color_theme", theme_name, false);
1289
}
1290
}
1291
1292
void ScriptEditor::_file_dialog_action(const String &p_file) {
1293
switch (file_dialog_option) {
1294
case FILE_MENU_NEW_TEXTFILE: {
1295
Error err;
1296
{
1297
Ref<FileAccess> file = FileAccess::open(p_file, FileAccess::WRITE, &err);
1298
if (err) {
1299
EditorNode::get_singleton()->show_warning(TTR("Error writing TextFile:") + "\n" + p_file, TTR("Error!"));
1300
break;
1301
}
1302
}
1303
1304
if (EditorFileSystem::get_singleton()) {
1305
if (textfile_extensions.has(p_file.get_extension())) {
1306
EditorFileSystem::get_singleton()->update_file(p_file);
1307
}
1308
}
1309
1310
if (!open_textfile_after_create) {
1311
return;
1312
}
1313
[[fallthrough]];
1314
}
1315
case FILE_MENU_OPEN: {
1316
open_file(p_file);
1317
file_dialog_option = -1;
1318
} break;
1319
case FILE_MENU_SAVE_AS: {
1320
ScriptEditorBase *current = _get_current_editor();
1321
if (current) {
1322
Ref<Resource> resource = current->get_edited_resource();
1323
String path = ProjectSettings::get_singleton()->localize_path(p_file);
1324
Error err = _save_text_file(resource, path);
1325
1326
if (err != OK) {
1327
EditorNode::get_singleton()->show_accept(TTR("Error saving file!"), TTR("OK"));
1328
return;
1329
}
1330
1331
resource->set_path(path);
1332
_update_script_names();
1333
}
1334
} break;
1335
case THEME_SAVE_AS: {
1336
_save_text_editor_theme_as(p_file);
1337
} break;
1338
case THEME_IMPORT: {
1339
_import_text_editor_theme(p_file);
1340
} break;
1341
}
1342
file_dialog_option = -1;
1343
}
1344
1345
Ref<Script> ScriptEditor::_get_current_script() {
1346
ScriptEditorBase *current = _get_current_editor();
1347
1348
if (current) {
1349
Ref<Script> scr = current->get_edited_resource();
1350
return scr.is_valid() ? scr : nullptr;
1351
} else {
1352
return nullptr;
1353
}
1354
}
1355
1356
TypedArray<Script> ScriptEditor::_get_open_scripts() const {
1357
TypedArray<Script> ret;
1358
Vector<Ref<Script>> scripts = get_open_scripts();
1359
int scripts_amount = scripts.size();
1360
for (int idx_script = 0; idx_script < scripts_amount; idx_script++) {
1361
ret.push_back(scripts[idx_script]);
1362
}
1363
return ret;
1364
}
1365
1366
bool ScriptEditor::toggle_files_panel() {
1367
list_split->set_visible(!list_split->is_visible());
1368
EditorSettings::get_singleton()->set_project_metadata("files_panel", "show_files_panel", list_split->is_visible());
1369
return list_split->is_visible();
1370
}
1371
1372
bool ScriptEditor::is_files_panel_toggled() {
1373
return list_split->is_visible();
1374
}
1375
1376
void ScriptEditor::_menu_option(int p_option) {
1377
ScriptEditorBase *current = _get_current_editor();
1378
switch (p_option) {
1379
case FILE_MENU_NEW: {
1380
script_create_dialog->config("Node", "new_script", false, false);
1381
script_create_dialog->popup_centered();
1382
} break;
1383
case FILE_MENU_NEW_TEXTFILE: {
1384
file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);
1385
file_dialog->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
1386
file_dialog_option = FILE_MENU_NEW_TEXTFILE;
1387
1388
file_dialog->clear_filters();
1389
for (const String &E : textfile_extensions) {
1390
file_dialog->add_filter("*." + E, E.to_upper());
1391
}
1392
file_dialog->set_title(TTRC("New Text File..."));
1393
file_dialog->popup_file_dialog();
1394
open_textfile_after_create = true;
1395
} break;
1396
case FILE_MENU_OPEN: {
1397
file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
1398
file_dialog->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
1399
file_dialog_option = FILE_MENU_OPEN;
1400
1401
List<String> extensions;
1402
ResourceLoader::get_recognized_extensions_for_type("Script", &extensions);
1403
file_dialog->clear_filters();
1404
for (const String &extension : extensions) {
1405
file_dialog->add_filter("*." + extension, extension.to_upper());
1406
}
1407
1408
for (const String &E : textfile_extensions) {
1409
file_dialog->add_filter("*." + E, E.to_upper());
1410
}
1411
1412
file_dialog->set_title(TTRC("Open File"));
1413
file_dialog->popup_file_dialog();
1414
return;
1415
} break;
1416
case FILE_MENU_REOPEN_CLOSED: {
1417
if (previous_scripts.is_empty()) {
1418
return;
1419
}
1420
1421
String path = previous_scripts.back()->get();
1422
previous_scripts.pop_back();
1423
1424
List<String> extensions;
1425
ResourceLoader::get_recognized_extensions_for_type("Script", &extensions);
1426
ResourceLoader::get_recognized_extensions_for_type("JSON", &extensions);
1427
bool built_in = !path.is_resource_file();
1428
1429
if (extensions.find(path.get_extension()) || built_in) {
1430
if (built_in) {
1431
String res_path = path.get_slice("::", 0);
1432
EditorNode::get_singleton()->load_scene_or_resource(res_path, false, false);
1433
}
1434
1435
Ref<Resource> scr = ResourceLoader::load(path);
1436
if (scr.is_null()) {
1437
EditorNode::get_singleton()->show_warning(TTR("Could not load file at:") + "\n\n" + path, TTR("Error!"));
1438
file_dialog_option = -1;
1439
return;
1440
}
1441
1442
edit(scr);
1443
file_dialog_option = -1;
1444
} else {
1445
Error error;
1446
Ref<TextFile> text_file = _load_text_file(path, &error);
1447
if (error != OK) {
1448
EditorNode::get_singleton()->show_warning(TTR("Could not load file at:") + "\n\n" + path, TTR("Error!"));
1449
}
1450
1451
if (text_file.is_valid()) {
1452
edit(text_file);
1453
file_dialog_option = -1;
1454
}
1455
}
1456
} break;
1457
case FILE_MENU_SAVE_ALL: {
1458
if (_test_script_times_on_disk()) {
1459
return;
1460
}
1461
1462
save_all_scripts();
1463
} break;
1464
case SEARCH_IN_FILES: {
1465
open_find_in_files_dialog("");
1466
} break;
1467
case REPLACE_IN_FILES: {
1468
_on_replace_in_files_requested("");
1469
} break;
1470
case SEARCH_HELP: {
1471
help_search_dialog->popup_dialog();
1472
} break;
1473
case SEARCH_WEBSITE: {
1474
Control *tab = tab_container->get_current_tab_control();
1475
1476
EditorHelp *eh = Object::cast_to<EditorHelp>(tab);
1477
bool native_class_doc = false;
1478
if (eh) {
1479
const HashMap<String, DocData::ClassDoc>::ConstIterator E = EditorHelp::get_doc_data()->class_list.find(eh->get_class());
1480
native_class_doc = E && !E->value.is_script_doc;
1481
}
1482
if (native_class_doc) {
1483
String name = eh->get_class().to_lower();
1484
String doc_url = vformat(GODOT_VERSION_DOCS_URL "/classes/class_%s.html", name);
1485
OS::get_singleton()->shell_open(doc_url);
1486
} else {
1487
OS::get_singleton()->shell_open(GODOT_VERSION_DOCS_URL "/");
1488
}
1489
} break;
1490
case FILE_MENU_HISTORY_NEXT: {
1491
_history_forward();
1492
} break;
1493
case FILE_MENU_HISTORY_PREV: {
1494
_history_back();
1495
} break;
1496
case FILE_MENU_SORT: {
1497
_sort_list_on_update = true;
1498
_update_script_names();
1499
} break;
1500
case FILE_MENU_TOGGLE_FILES_PANEL: {
1501
toggle_files_panel();
1502
if (current) {
1503
current->update_toggle_files_button();
1504
} else {
1505
Control *tab = tab_container->get_current_tab_control();
1506
EditorHelp *editor_help = Object::cast_to<EditorHelp>(tab);
1507
if (editor_help) {
1508
editor_help->update_toggle_files_button();
1509
}
1510
}
1511
}
1512
}
1513
1514
if (p_option >= EditorContextMenuPlugin::BASE_ID) {
1515
Ref<Resource> resource;
1516
if (current) {
1517
resource = current->get_edited_resource();
1518
}
1519
EditorContextMenuPluginManager::get_singleton()->activate_custom_option(EditorContextMenuPlugin::CONTEXT_SLOT_SCRIPT_EDITOR, p_option, resource);
1520
return;
1521
}
1522
1523
if (current) {
1524
switch (p_option) {
1525
case FILE_MENU_SAVE: {
1526
save_current_script();
1527
} break;
1528
case FILE_MENU_SAVE_AS: {
1529
if (trim_trailing_whitespace_on_save) {
1530
current->trim_trailing_whitespace();
1531
}
1532
1533
if (trim_final_newlines_on_save) {
1534
current->trim_final_newlines();
1535
}
1536
1537
if (convert_indent_on_save) {
1538
current->convert_indent();
1539
}
1540
1541
Ref<Resource> resource = current->get_edited_resource();
1542
Ref<TextFile> text_file = resource;
1543
Ref<Script> scr = resource;
1544
1545
if (text_file.is_valid()) {
1546
file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);
1547
file_dialog->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
1548
file_dialog_option = FILE_MENU_SAVE_AS;
1549
1550
List<String> extensions;
1551
ResourceLoader::get_recognized_extensions_for_type("Script", &extensions);
1552
file_dialog->clear_filters();
1553
file_dialog->set_current_dir(text_file->get_path().get_base_dir());
1554
file_dialog->set_current_file(text_file->get_path().get_file());
1555
file_dialog->set_title(TTRC("Save File As..."));
1556
file_dialog->popup_file_dialog();
1557
break;
1558
}
1559
1560
if (scr.is_valid()) {
1561
clear_docs_from_script(scr);
1562
}
1563
1564
EditorNode::get_singleton()->push_item(resource.ptr());
1565
EditorNode::get_singleton()->save_resource_as(resource);
1566
1567
if (scr.is_valid()) {
1568
update_docs_from_script(scr);
1569
}
1570
} break;
1571
1572
case FILE_MENU_SOFT_RELOAD_TOOL: {
1573
Ref<Script> scr = current->get_edited_resource();
1574
if (scr.is_null()) {
1575
EditorNode::get_singleton()->show_warning(TTR("Can't obtain the script for reloading."));
1576
break;
1577
}
1578
if (!scr->is_tool()) {
1579
EditorNode::get_singleton()->show_warning(TTR("Reload only takes effect on tool scripts."));
1580
return;
1581
}
1582
scr->reload(true);
1583
1584
} break;
1585
1586
case FILE_MENU_RUN: {
1587
Ref<Script> scr = current->get_edited_resource();
1588
if (scr.is_null()) {
1589
EditorToaster::get_singleton()->popup_str(TTR("Cannot run the edited file because it's not a script."), EditorToaster::SEVERITY_WARNING);
1590
break;
1591
}
1592
1593
current->apply_code();
1594
1595
EditorNode::get_singleton()->run_editor_script(scr);
1596
} break;
1597
1598
case FILE_MENU_CLOSE: {
1599
if (current->is_unsaved()) {
1600
_ask_close_current_unsaved_tab(current);
1601
} else {
1602
_close_current_tab(false);
1603
}
1604
} break;
1605
case FILE_MENU_COPY_PATH: {
1606
_copy_script_path();
1607
} break;
1608
case FILE_MENU_COPY_UID: {
1609
_copy_script_uid();
1610
} break;
1611
case FILE_MENU_SHOW_IN_FILE_SYSTEM: {
1612
const Ref<Resource> scr = current->get_edited_resource();
1613
String path = scr->get_path();
1614
if (!path.is_empty()) {
1615
if (scr->is_built_in()) {
1616
path = path.get_slice("::", 0); // Show the scene instead.
1617
}
1618
1619
FileSystemDock::get_singleton()->navigate_to_path(path);
1620
}
1621
} break;
1622
case FILE_MENU_CLOSE_DOCS: {
1623
_close_docs_tab();
1624
} break;
1625
case FILE_MENU_CLOSE_OTHER_TABS: {
1626
_close_other_tabs();
1627
} break;
1628
case FILE_MENU_CLOSE_TABS_BELOW: {
1629
_close_tabs_below();
1630
} break;
1631
case FILE_MENU_CLOSE_ALL: {
1632
_close_all_tabs();
1633
} break;
1634
case FILE_MENU_MOVE_UP: {
1635
if (tab_container->get_current_tab() > 0) {
1636
tab_container->move_child(current, tab_container->get_current_tab() - 1);
1637
tab_container->set_current_tab(tab_container->get_current_tab());
1638
_update_script_names();
1639
}
1640
} break;
1641
case FILE_MENU_MOVE_DOWN: {
1642
if (tab_container->get_current_tab() < tab_container->get_tab_count() - 1) {
1643
tab_container->move_child(current, tab_container->get_current_tab() + 1);
1644
tab_container->set_current_tab(tab_container->get_current_tab());
1645
_update_script_names();
1646
}
1647
} break;
1648
}
1649
} else {
1650
EditorHelp *help = Object::cast_to<EditorHelp>(tab_container->get_current_tab_control());
1651
if (help) {
1652
switch (p_option) {
1653
case HELP_SEARCH_FIND: {
1654
help->popup_search();
1655
} break;
1656
case HELP_SEARCH_FIND_NEXT: {
1657
help->search_again();
1658
} break;
1659
case HELP_SEARCH_FIND_PREVIOUS: {
1660
help->search_again(true);
1661
} break;
1662
case FILE_MENU_CLOSE: {
1663
_close_current_tab();
1664
} break;
1665
case FILE_MENU_CLOSE_DOCS: {
1666
_close_docs_tab();
1667
} break;
1668
case FILE_MENU_CLOSE_OTHER_TABS: {
1669
_close_other_tabs();
1670
} break;
1671
case FILE_MENU_CLOSE_TABS_BELOW: {
1672
_close_tabs_below();
1673
} break;
1674
case FILE_MENU_CLOSE_ALL: {
1675
_close_all_tabs();
1676
} break;
1677
case FILE_MENU_MOVE_UP: {
1678
if (tab_container->get_current_tab() > 0) {
1679
tab_container->move_child(help, tab_container->get_current_tab() - 1);
1680
tab_container->set_current_tab(tab_container->get_current_tab());
1681
_update_script_names();
1682
}
1683
} break;
1684
case FILE_MENU_MOVE_DOWN: {
1685
if (tab_container->get_current_tab() < tab_container->get_tab_count() - 1) {
1686
tab_container->move_child(help, tab_container->get_current_tab() + 1);
1687
tab_container->set_current_tab(tab_container->get_current_tab());
1688
_update_script_names();
1689
}
1690
} break;
1691
}
1692
}
1693
}
1694
}
1695
1696
void ScriptEditor::_theme_option(int p_option) {
1697
switch (p_option) {
1698
case THEME_IMPORT: {
1699
file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
1700
file_dialog->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
1701
file_dialog_option = THEME_IMPORT;
1702
file_dialog->clear_filters();
1703
file_dialog->add_filter("*.tet");
1704
file_dialog->set_title(TTRC("Import Theme"));
1705
file_dialog->popup_file_dialog();
1706
} break;
1707
case THEME_RELOAD: {
1708
EditorSettings::get_singleton()->mark_setting_changed("text_editor/theme/color_theme");
1709
EditorSettings::get_singleton()->notify_changes();
1710
} break;
1711
case THEME_SAVE_AS: {
1712
ScriptEditor::_show_save_theme_as_dialog();
1713
} break;
1714
}
1715
}
1716
1717
void ScriptEditor::_show_save_theme_as_dialog() {
1718
file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);
1719
file_dialog->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
1720
file_dialog_option = THEME_SAVE_AS;
1721
file_dialog->clear_filters();
1722
file_dialog->add_filter("*.tet");
1723
file_dialog->set_current_path(EditorPaths::get_singleton()->get_text_editor_themes_dir().path_join(EDITOR_GET("text_editor/theme/color_theme")) + " New");
1724
file_dialog->set_title(TTRC("Save Theme As..."));
1725
file_dialog->popup_file_dialog();
1726
}
1727
1728
bool ScriptEditor::_has_docs_tab() const {
1729
const int child_count = tab_container->get_tab_count();
1730
for (int i = 0; i < child_count; i++) {
1731
if (Object::cast_to<EditorHelp>(tab_container->get_tab_control(i))) {
1732
return true;
1733
}
1734
}
1735
return false;
1736
}
1737
1738
bool ScriptEditor::_has_script_tab() const {
1739
const int child_count = tab_container->get_tab_count();
1740
for (int i = 0; i < child_count; i++) {
1741
if (Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i))) {
1742
return true;
1743
}
1744
}
1745
return false;
1746
}
1747
1748
void ScriptEditor::_prepare_file_menu() {
1749
PopupMenu *menu = file_menu->get_popup();
1750
ScriptEditorBase *editor = _get_current_editor();
1751
const Ref<Resource> res = editor ? editor->get_edited_resource() : Ref<Resource>();
1752
1753
menu->set_item_disabled(menu->get_item_index(FILE_MENU_REOPEN_CLOSED), previous_scripts.is_empty());
1754
1755
menu->set_item_disabled(menu->get_item_index(FILE_MENU_SAVE), res.is_null());
1756
menu->set_item_disabled(menu->get_item_index(FILE_MENU_SAVE_AS), res.is_null());
1757
menu->set_item_disabled(menu->get_item_index(FILE_MENU_SAVE_ALL), !_has_script_tab());
1758
1759
menu->set_item_disabled(menu->get_item_index(FILE_MENU_SOFT_RELOAD_TOOL), res.is_null());
1760
menu->set_item_disabled(menu->get_item_index(FILE_MENU_COPY_PATH), res.is_null() || res->get_path().is_empty());
1761
menu->set_item_disabled(menu->get_item_index(FILE_MENU_COPY_UID), res.is_null() || ResourceLoader::get_resource_uid(res->get_path()) == ResourceUID::INVALID_ID);
1762
menu->set_item_disabled(menu->get_item_index(FILE_MENU_SHOW_IN_FILE_SYSTEM), res.is_null());
1763
1764
menu->set_item_disabled(menu->get_item_index(FILE_MENU_HISTORY_PREV), history_pos <= 0);
1765
menu->set_item_disabled(menu->get_item_index(FILE_MENU_HISTORY_NEXT), history_pos >= history.size() - 1);
1766
1767
menu->set_item_disabled(menu->get_item_index(FILE_MENU_CLOSE), tab_container->get_tab_count() < 1);
1768
menu->set_item_disabled(menu->get_item_index(FILE_MENU_CLOSE_ALL), tab_container->get_tab_count() < 1);
1769
menu->set_item_disabled(menu->get_item_index(FILE_MENU_CLOSE_OTHER_TABS), tab_container->get_tab_count() <= 1);
1770
menu->set_item_disabled(menu->get_item_index(FILE_MENU_CLOSE_DOCS), !_has_docs_tab());
1771
1772
menu->set_item_disabled(menu->get_item_index(FILE_MENU_RUN), res.is_null());
1773
}
1774
1775
void ScriptEditor::_file_menu_closed() {
1776
PopupMenu *menu = file_menu->get_popup();
1777
1778
menu->set_item_disabled(menu->get_item_index(FILE_MENU_REOPEN_CLOSED), false);
1779
1780
menu->set_item_disabled(menu->get_item_index(FILE_MENU_SAVE), false);
1781
menu->set_item_disabled(menu->get_item_index(FILE_MENU_SAVE_AS), false);
1782
menu->set_item_disabled(menu->get_item_index(FILE_MENU_SAVE_ALL), false);
1783
1784
menu->set_item_disabled(menu->get_item_index(FILE_MENU_SOFT_RELOAD_TOOL), false);
1785
menu->set_item_disabled(menu->get_item_index(FILE_MENU_COPY_PATH), false);
1786
menu->set_item_disabled(menu->get_item_index(FILE_MENU_SHOW_IN_FILE_SYSTEM), false);
1787
1788
menu->set_item_disabled(menu->get_item_index(FILE_MENU_HISTORY_PREV), false);
1789
menu->set_item_disabled(menu->get_item_index(FILE_MENU_HISTORY_NEXT), false);
1790
1791
menu->set_item_disabled(menu->get_item_index(FILE_MENU_CLOSE), false);
1792
menu->set_item_disabled(menu->get_item_index(FILE_MENU_CLOSE_ALL), false);
1793
menu->set_item_disabled(menu->get_item_index(FILE_MENU_CLOSE_OTHER_TABS), false);
1794
menu->set_item_disabled(menu->get_item_index(FILE_MENU_CLOSE_DOCS), false);
1795
1796
menu->set_item_disabled(menu->get_item_index(FILE_MENU_RUN), false);
1797
}
1798
1799
void ScriptEditor::_tab_changed(int p_which) {
1800
ensure_select_current();
1801
}
1802
1803
void ScriptEditor::_notification(int p_what) {
1804
switch (p_what) {
1805
case NOTIFICATION_ENTER_TREE: {
1806
EditorRunBar::get_singleton()->connect("stop_pressed", callable_mp(this, &ScriptEditor::_editor_stop));
1807
_apply_editor_settings();
1808
[[fallthrough]];
1809
}
1810
1811
case NOTIFICATION_TRANSLATION_CHANGED: {
1812
_update_online_doc();
1813
if (!make_floating->is_disabled()) {
1814
// Override default ScreenSelect tooltip if multi-window support is available.
1815
make_floating->set_tooltip_text(TTR("Make the script editor floating.") + "\n" + TTR("Right-click to open the screen selector."));
1816
}
1817
[[fallthrough]];
1818
}
1819
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
1820
case NOTIFICATION_THEME_CHANGED: {
1821
tab_container->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("ScriptEditor"), EditorStringName(EditorStyles)));
1822
1823
help_search->set_button_icon(get_editor_theme_icon(SNAME("HelpSearch")));
1824
site_search->set_button_icon(get_editor_theme_icon(SNAME("ExternalLink")));
1825
1826
if (is_layout_rtl()) {
1827
script_forward->set_button_icon(get_editor_theme_icon(SNAME("Back")));
1828
script_back->set_button_icon(get_editor_theme_icon(SNAME("Forward")));
1829
} else {
1830
script_forward->set_button_icon(get_editor_theme_icon(SNAME("Forward")));
1831
script_back->set_button_icon(get_editor_theme_icon(SNAME("Back")));
1832
}
1833
1834
members_overview_alphabeta_sort_button->set_button_icon(get_editor_theme_icon(SNAME("Sort")));
1835
1836
filter_scripts->set_right_icon(get_editor_theme_icon(SNAME("Search")));
1837
filter_methods->set_right_icon(get_editor_theme_icon(SNAME("Search")));
1838
1839
filename->add_theme_style_override(CoreStringName(normal), get_theme_stylebox(CoreStringName(normal), SNAME("LineEdit")));
1840
1841
recent_scripts->reset_size();
1842
1843
if (is_inside_tree()) {
1844
_update_script_names();
1845
}
1846
} break;
1847
1848
case NOTIFICATION_READY: {
1849
// Can't set own styles in NOTIFICATION_THEME_CHANGED, so for now this will do.
1850
add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("ScriptEditorPanel"), EditorStringName(EditorStyles)));
1851
1852
get_tree()->connect("tree_changed", callable_mp(this, &ScriptEditor::_tree_changed));
1853
InspectorDock::get_singleton()->connect("request_help", callable_mp(this, &ScriptEditor::_help_class_open));
1854
EditorNode::get_singleton()->connect("request_help_search", callable_mp(this, &ScriptEditor::_help_search));
1855
EditorNode::get_singleton()->connect("scene_closed", callable_mp(this, &ScriptEditor::_close_builtin_scripts_from_scene));
1856
EditorNode::get_singleton()->connect("script_add_function_request", callable_mp(this, &ScriptEditor::_add_callback));
1857
EditorNode::get_singleton()->connect("resource_saved", callable_mp(this, &ScriptEditor::_res_saved_callback));
1858
EditorNode::get_singleton()->connect("scene_saved", callable_mp(this, &ScriptEditor::_scene_saved_callback));
1859
FileSystemDock::get_singleton()->connect("files_moved", callable_mp(this, &ScriptEditor::_files_moved));
1860
FileSystemDock::get_singleton()->connect("file_removed", callable_mp(this, &ScriptEditor::_file_removed));
1861
script_list->connect(SceneStringName(item_selected), callable_mp(this, &ScriptEditor::_script_selected));
1862
1863
members_overview->connect(SceneStringName(item_selected), callable_mp(this, &ScriptEditor::_members_overview_selected));
1864
help_overview->connect(SceneStringName(item_selected), callable_mp(this, &ScriptEditor::_help_overview_selected));
1865
script_split->connect("dragged", callable_mp(this, &ScriptEditor::_split_dragged));
1866
list_split->connect("dragged", callable_mp(this, &ScriptEditor::_split_dragged));
1867
1868
EditorSettings::get_singleton()->connect("settings_changed", callable_mp(this, &ScriptEditor::_editor_settings_changed));
1869
EditorFileSystem::get_singleton()->connect("filesystem_changed", callable_mp(this, &ScriptEditor::_filesystem_changed));
1870
} break;
1871
1872
case NOTIFICATION_EXIT_TREE: {
1873
EditorRunBar::get_singleton()->disconnect("stop_pressed", callable_mp(this, &ScriptEditor::_editor_stop));
1874
} break;
1875
1876
case NOTIFICATION_APPLICATION_FOCUS_IN: {
1877
_test_script_times_on_disk();
1878
_update_modified_scripts_for_external_editor();
1879
} break;
1880
}
1881
}
1882
1883
void ScriptEditor::_close_builtin_scripts_from_scene(const String &p_scene) {
1884
for (int i = 0; i < tab_container->get_tab_count(); i++) {
1885
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));
1886
1887
if (se) {
1888
Ref<Script> scr = se->get_edited_resource();
1889
if (scr.is_null()) {
1890
continue;
1891
}
1892
1893
if (scr->is_built_in() && scr->get_path().begins_with(p_scene)) { // Is an internal script and belongs to scene being closed.
1894
_close_tab(i, false);
1895
i--;
1896
}
1897
}
1898
}
1899
}
1900
1901
void ScriptEditor::edited_scene_changed() {
1902
_update_modified_scripts_for_external_editor();
1903
}
1904
1905
void ScriptEditor::notify_script_close(const Ref<Script> &p_script) {
1906
emit_signal(SNAME("script_close"), p_script);
1907
}
1908
1909
void ScriptEditor::notify_script_changed(const Ref<Script> &p_script) {
1910
emit_signal(SNAME("editor_script_changed"), p_script);
1911
}
1912
1913
Vector<String> ScriptEditor::_get_breakpoints() {
1914
Vector<String> ret;
1915
HashSet<String> loaded_scripts;
1916
for (int i = 0; i < tab_container->get_tab_count(); i++) {
1917
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));
1918
if (!se) {
1919
continue;
1920
}
1921
1922
Ref<Script> scr = se->get_edited_resource();
1923
if (scr.is_null()) {
1924
continue;
1925
}
1926
1927
String base = scr->get_path();
1928
loaded_scripts.insert(base);
1929
if (base.is_empty() || base.begins_with("local://")) {
1930
continue;
1931
}
1932
1933
PackedInt32Array bpoints = se->get_breakpoints();
1934
for (int32_t bpoint : bpoints) {
1935
ret.push_back(base + ":" + itos((int)bpoint + 1));
1936
}
1937
}
1938
1939
// Load breakpoints that are in closed scripts.
1940
Vector<String> cached_editors = script_editor_cache->get_sections();
1941
for (const String &E : cached_editors) {
1942
if (loaded_scripts.has(E)) {
1943
continue;
1944
}
1945
1946
Array breakpoints = _get_cached_breakpoints_for_script(E);
1947
for (int breakpoint : breakpoints) {
1948
ret.push_back(E + ":" + itos((int)breakpoint + 1));
1949
}
1950
}
1951
return ret;
1952
}
1953
1954
void ScriptEditor::get_breakpoints(List<String> *p_breakpoints) {
1955
HashSet<String> loaded_scripts;
1956
for (int i = 0; i < tab_container->get_tab_count(); i++) {
1957
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));
1958
if (!se) {
1959
continue;
1960
}
1961
1962
Ref<Script> scr = se->get_edited_resource();
1963
if (scr.is_null()) {
1964
continue;
1965
}
1966
1967
String base = scr->get_path();
1968
loaded_scripts.insert(base);
1969
if (base.is_empty() || base.begins_with("local://")) {
1970
continue;
1971
}
1972
1973
PackedInt32Array bpoints = se->get_breakpoints();
1974
for (int32_t bpoint : bpoints) {
1975
p_breakpoints->push_back(base + ":" + itos((int)bpoint + 1));
1976
}
1977
}
1978
1979
// Load breakpoints that are in closed scripts.
1980
Vector<String> cached_editors = script_editor_cache->get_sections();
1981
for (const String &E : cached_editors) {
1982
if (loaded_scripts.has(E)) {
1983
continue;
1984
}
1985
1986
Array breakpoints = _get_cached_breakpoints_for_script(E);
1987
for (int breakpoint : breakpoints) {
1988
p_breakpoints->push_back(E + ":" + itos((int)breakpoint + 1));
1989
}
1990
}
1991
}
1992
1993
void ScriptEditor::_members_overview_selected(int p_idx) {
1994
int line = members_overview->get_item_metadata(p_idx);
1995
ScriptEditorBase *current = _get_current_editor();
1996
if (ScriptTextEditor *script_text_editor = Object::cast_to<ScriptTextEditor>(current)) {
1997
script_text_editor->goto_line_centered(line);
1998
} else if (current) {
1999
current->goto_line(line);
2000
}
2001
}
2002
2003
void ScriptEditor::_help_overview_selected(int p_idx) {
2004
Node *current = tab_container->get_tab_control(tab_container->get_current_tab());
2005
EditorHelp *se = Object::cast_to<EditorHelp>(current);
2006
if (!se) {
2007
return;
2008
}
2009
se->scroll_to_section(help_overview->get_item_metadata(p_idx));
2010
}
2011
2012
void ScriptEditor::_script_selected(int p_idx) {
2013
grab_focus_block = !Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT); //amazing hack, simply amazing
2014
2015
_go_to_tab(script_list->get_item_metadata(p_idx));
2016
grab_focus_block = false;
2017
}
2018
2019
void ScriptEditor::ensure_select_current() {
2020
if (tab_container->get_tab_count() && tab_container->get_current_tab() >= 0) {
2021
ScriptEditorBase *se = _get_current_editor();
2022
if (se) {
2023
se->enable_editor(this);
2024
2025
if (!grab_focus_block && is_visible_in_tree()) {
2026
se->ensure_focus();
2027
}
2028
}
2029
}
2030
_update_find_replace_bar();
2031
2032
_update_selected_editor_menu();
2033
}
2034
2035
bool ScriptEditor::is_editor_floating() {
2036
return is_floating;
2037
}
2038
2039
void ScriptEditor::_find_scripts(Node *p_base, Node *p_current, HashSet<Ref<Script>> &used) {
2040
if (p_current != p_base && p_current->get_owner() != p_base) {
2041
return;
2042
}
2043
2044
if (p_current->get_script_instance()) {
2045
Ref<Script> scr = p_current->get_script();
2046
if (scr.is_valid()) {
2047
used.insert(scr);
2048
}
2049
}
2050
2051
for (int i = 0; i < p_current->get_child_count(); i++) {
2052
_find_scripts(p_base, p_current->get_child(i), used);
2053
}
2054
}
2055
2056
struct _ScriptEditorItemData {
2057
String name;
2058
String sort_key;
2059
Ref<Texture2D> icon;
2060
bool tool = false;
2061
int index = 0;
2062
String tooltip;
2063
bool used = false;
2064
int category = 0;
2065
Node *ref = nullptr;
2066
2067
bool operator<(const _ScriptEditorItemData &id) const {
2068
if (category == id.category) {
2069
if (sort_key == id.sort_key) {
2070
return index < id.index;
2071
} else {
2072
return sort_key.filenocasecmp_to(id.sort_key) < 0;
2073
}
2074
} else {
2075
return category < id.category;
2076
}
2077
}
2078
};
2079
2080
void ScriptEditor::_update_members_overview_visibility() {
2081
ScriptEditorBase *se = _get_current_editor();
2082
if (!se) {
2083
members_overview_alphabeta_sort_button->set_visible(false);
2084
members_overview->set_visible(false);
2085
2086
Node *current = tab_container->get_tab_control(tab_container->get_current_tab());
2087
EditorHelp *editor_help = Object::cast_to<EditorHelp>(current);
2088
overview_vbox->set_visible(help_overview_enabled && editor_help);
2089
return;
2090
}
2091
2092
if (members_overview_enabled && se->show_members_overview()) {
2093
members_overview_alphabeta_sort_button->set_visible(true);
2094
filter_methods->set_visible(true);
2095
members_overview->set_visible(true);
2096
overview_vbox->set_visible(true);
2097
} else {
2098
members_overview_alphabeta_sort_button->set_visible(false);
2099
filter_methods->set_visible(false);
2100
members_overview->set_visible(false);
2101
overview_vbox->set_visible(false);
2102
}
2103
}
2104
2105
void ScriptEditor::_toggle_members_overview_alpha_sort(bool p_alphabetic_sort) {
2106
EditorSettings::get_singleton()->set("text_editor/script_list/sort_members_outline_alphabetically", p_alphabetic_sort);
2107
_update_members_overview();
2108
}
2109
2110
void ScriptEditor::_update_members_overview() {
2111
members_overview->clear();
2112
2113
ScriptEditorBase *se = _get_current_editor();
2114
if (!se) {
2115
return;
2116
}
2117
2118
Vector<String> functions = se->get_functions();
2119
if (EDITOR_GET("text_editor/script_list/sort_members_outline_alphabetically")) {
2120
functions.sort();
2121
}
2122
2123
String filter = filter_methods->get_text();
2124
if (filter.is_empty()) {
2125
for (int i = 0; i < functions.size(); i++) {
2126
String name = functions[i].get_slicec(':', 0);
2127
members_overview->add_item(name);
2128
members_overview->set_item_metadata(-1, functions[i].get_slicec(':', 1).to_int() - 1);
2129
}
2130
} else {
2131
PackedStringArray search_names;
2132
for (int i = 0; i < functions.size(); i++) {
2133
search_names.append(functions[i].get_slicec(':', 0));
2134
}
2135
2136
Vector<FuzzySearchResult> results;
2137
FuzzySearch fuzzy;
2138
fuzzy.set_query(filter, false);
2139
fuzzy.search_all(search_names, results);
2140
2141
for (const FuzzySearchResult &res : results) {
2142
String name = functions[res.original_index].get_slicec(':', 0);
2143
int line = functions[res.original_index].get_slicec(':', 1).to_int() - 1;
2144
members_overview->add_item(name);
2145
members_overview->set_item_metadata(-1, line);
2146
}
2147
}
2148
2149
String path = se->get_edited_resource()->get_path();
2150
bool built_in = !path.is_resource_file();
2151
String name = built_in ? path.get_file() : se->get_name();
2152
filename->set_text(name);
2153
}
2154
2155
void ScriptEditor::_update_help_overview_visibility() {
2156
int selected = tab_container->get_current_tab();
2157
if (selected < 0 || selected >= tab_container->get_tab_count()) {
2158
help_overview->set_visible(false);
2159
return;
2160
}
2161
2162
Node *current = tab_container->get_tab_control(tab_container->get_current_tab());
2163
EditorHelp *se = Object::cast_to<EditorHelp>(current);
2164
if (!se) {
2165
help_overview->set_visible(false);
2166
return;
2167
}
2168
2169
if (help_overview_enabled) {
2170
members_overview_alphabeta_sort_button->set_visible(false);
2171
filter_methods->set_visible(false);
2172
help_overview->set_visible(true);
2173
overview_vbox->set_visible(true);
2174
filename->set_text(se->get_name());
2175
} else {
2176
help_overview->set_visible(false);
2177
overview_vbox->set_visible(false);
2178
}
2179
}
2180
2181
void ScriptEditor::_update_help_overview() {
2182
help_overview->clear();
2183
2184
int selected = tab_container->get_current_tab();
2185
if (selected < 0 || selected >= tab_container->get_tab_count()) {
2186
return;
2187
}
2188
2189
Node *current = tab_container->get_tab_control(tab_container->get_current_tab());
2190
EditorHelp *se = Object::cast_to<EditorHelp>(current);
2191
if (!se) {
2192
return;
2193
}
2194
2195
Vector<Pair<String, int>> sections = se->get_sections();
2196
for (int i = 0; i < sections.size(); i++) {
2197
help_overview->add_item(sections[i].first);
2198
help_overview->set_item_metadata(i, sections[i].second);
2199
}
2200
}
2201
2202
void ScriptEditor::_update_online_doc() {
2203
Node *current = tab_container->get_tab_control(tab_container->get_current_tab());
2204
2205
EditorHelp *eh = Object::cast_to<EditorHelp>(current);
2206
bool native_class_doc = false;
2207
if (eh) {
2208
const HashMap<String, DocData::ClassDoc>::ConstIterator E = EditorHelp::get_doc_data()->class_list.find(eh->get_class());
2209
native_class_doc = E && !E->value.is_script_doc;
2210
}
2211
if (native_class_doc) {
2212
String name = eh->get_class();
2213
String tooltip = vformat(TTR("Open '%s' in Godot online documentation."), name);
2214
site_search->set_text(TTRC("Open in Online Docs"));
2215
site_search->set_tooltip_text(tooltip);
2216
} else {
2217
site_search->set_text(TTRC("Online Docs"));
2218
site_search->set_tooltip_text(TTRC("Open Godot online documentation."));
2219
}
2220
}
2221
2222
void ScriptEditor::_update_script_colors() {
2223
bool script_temperature_enabled = EDITOR_GET("text_editor/script_list/script_temperature_enabled");
2224
2225
int hist_size = EDITOR_GET("text_editor/script_list/script_temperature_history_size");
2226
Color hot_color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
2227
hot_color.set_s(hot_color.get_s() * 0.9);
2228
Color cold_color = get_theme_color(SceneStringName(font_color), EditorStringName(Editor));
2229
2230
for (int i = 0; i < script_list->get_item_count(); i++) {
2231
int c = script_list->get_item_metadata(i);
2232
Node *n = tab_container->get_tab_control(c);
2233
if (!n) {
2234
continue;
2235
}
2236
2237
if (script_temperature_enabled) {
2238
int pass = n->get_meta("__editor_pass", -1);
2239
if (pass < 0) {
2240
continue;
2241
}
2242
2243
int h = edit_pass - pass;
2244
if (h > hist_size) {
2245
continue;
2246
}
2247
int non_zero_hist_size = (hist_size == 0) ? 1 : hist_size;
2248
float v = Math::ease((edit_pass - pass) / float(non_zero_hist_size), 0.4);
2249
2250
script_list->set_item_custom_fg_color(i, hot_color.lerp(cold_color, v));
2251
}
2252
}
2253
}
2254
2255
void ScriptEditor::_update_script_names() {
2256
if (restoring_layout) {
2257
return;
2258
}
2259
2260
HashSet<Ref<Script>> used;
2261
Node *edited = EditorNode::get_singleton()->get_edited_scene();
2262
if (edited && EDITOR_GET("text_editor/script_list/highlight_scene_scripts")) {
2263
_find_scripts(edited, edited, used);
2264
}
2265
2266
script_list->clear();
2267
bool split_script_help = EDITOR_GET("text_editor/script_list/group_help_pages");
2268
ScriptSortBy sort_by = (ScriptSortBy)(int)EDITOR_GET("text_editor/script_list/sort_scripts_by");
2269
ScriptListName display_as = (ScriptListName)(int)EDITOR_GET("text_editor/script_list/list_script_names_as");
2270
2271
Vector<_ScriptEditorItemData> sedata;
2272
2273
for (int i = 0; i < tab_container->get_tab_count(); i++) {
2274
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));
2275
if (se) {
2276
Ref<Texture2D> icon = se->get_theme_icon();
2277
String path = se->get_edited_resource()->get_path();
2278
bool saved = !path.is_empty();
2279
String name = se->get_name();
2280
Ref<Script> scr = se->get_edited_resource();
2281
2282
_ScriptEditorItemData sd;
2283
sd.icon = icon;
2284
sd.name = name;
2285
sd.tooltip = saved ? path : TTR("Unsaved file.");
2286
sd.index = i;
2287
sd.used = used.has(se->get_edited_resource());
2288
sd.category = 0;
2289
sd.ref = se;
2290
if (scr.is_valid()) {
2291
sd.tool = scr->is_tool();
2292
}
2293
2294
switch (sort_by) {
2295
case SORT_BY_NAME: {
2296
sd.sort_key = name.to_lower();
2297
} break;
2298
case SORT_BY_PATH: {
2299
sd.sort_key = path;
2300
} break;
2301
case SORT_BY_NONE: {
2302
sd.sort_key = "";
2303
} break;
2304
}
2305
2306
switch (display_as) {
2307
case DISPLAY_NAME: {
2308
sd.name = name;
2309
} break;
2310
case DISPLAY_DIR_AND_NAME: {
2311
if (!path.get_base_dir().get_file().is_empty()) {
2312
sd.name = path.get_base_dir().get_file().path_join(name);
2313
} else {
2314
sd.name = name;
2315
}
2316
} break;
2317
case DISPLAY_FULL_PATH: {
2318
sd.name = path;
2319
} break;
2320
}
2321
if (!saved) {
2322
sd.name = se->get_name();
2323
}
2324
2325
sedata.push_back(sd);
2326
}
2327
2328
EditorHelp *eh = Object::cast_to<EditorHelp>(tab_container->get_tab_control(i));
2329
if (eh && !eh->get_class().is_empty()) {
2330
String name = eh->get_class().unquote();
2331
Ref<Texture2D> icon = get_editor_theme_icon(SNAME("Help"));
2332
String tooltip = vformat(TTR("%s Class Reference"), name);
2333
2334
_ScriptEditorItemData sd;
2335
sd.icon = icon;
2336
sd.name = name;
2337
sd.sort_key = name.to_lower();
2338
sd.tooltip = tooltip;
2339
sd.index = i;
2340
sd.used = false;
2341
sd.category = split_script_help ? 1 : 0;
2342
sd.ref = eh;
2343
2344
sedata.push_back(sd);
2345
}
2346
}
2347
2348
Vector<String> disambiguated_script_names;
2349
Vector<String> full_script_paths;
2350
for (int j = 0; j < sedata.size(); j++) {
2351
String name = sedata[j].name.replace("(*)", "");
2352
ScriptListName script_display = (ScriptListName)(int)EDITOR_GET("text_editor/script_list/list_script_names_as");
2353
switch (script_display) {
2354
case DISPLAY_NAME: {
2355
name = name.get_file();
2356
} break;
2357
case DISPLAY_DIR_AND_NAME: {
2358
name = name.get_base_dir().get_file().path_join(name.get_file());
2359
} break;
2360
default:
2361
break;
2362
}
2363
2364
disambiguated_script_names.append(name);
2365
full_script_paths.append(sedata[j].tooltip);
2366
}
2367
2368
EditorNode::disambiguate_filenames(full_script_paths, disambiguated_script_names);
2369
2370
for (int j = 0; j < sedata.size(); j++) {
2371
if (sedata[j].name.ends_with("(*)")) {
2372
sedata.write[j].name = disambiguated_script_names[j] + "(*)";
2373
} else {
2374
sedata.write[j].name = disambiguated_script_names[j];
2375
}
2376
}
2377
2378
if (_sort_list_on_update && !sedata.is_empty()) {
2379
sedata.sort();
2380
2381
// change actual order of tab_container so that the order can be rearranged by user
2382
int cur_tab = tab_container->get_current_tab();
2383
int prev_tab = tab_container->get_previous_tab();
2384
int new_cur_tab = -1;
2385
int new_prev_tab = -1;
2386
for (int i = 0; i < sedata.size(); i++) {
2387
tab_container->move_child(sedata[i].ref, i);
2388
if (new_prev_tab == -1 && sedata[i].index == prev_tab) {
2389
new_prev_tab = i;
2390
}
2391
if (new_cur_tab == -1 && sedata[i].index == cur_tab) {
2392
new_cur_tab = i;
2393
}
2394
// Update index of sd entries for sorted order
2395
_ScriptEditorItemData sd = sedata[i];
2396
sd.index = i;
2397
sedata.set(i, sd);
2398
}
2399
2400
lock_history = true;
2401
_go_to_tab(new_prev_tab);
2402
_go_to_tab(new_cur_tab);
2403
lock_history = false;
2404
_sort_list_on_update = false;
2405
}
2406
2407
Vector<_ScriptEditorItemData> sedata_filtered;
2408
2409
String filter = filter_scripts->get_text();
2410
2411
if (filter.is_empty()) {
2412
sedata_filtered = sedata;
2413
} else {
2414
PackedStringArray search_names;
2415
for (int i = 0; i < sedata.size(); i++) {
2416
search_names.append(sedata[i].name);
2417
}
2418
2419
Vector<FuzzySearchResult> results;
2420
FuzzySearch fuzzy;
2421
fuzzy.set_query(filter, false);
2422
fuzzy.search_all(search_names, results);
2423
2424
for (const FuzzySearchResult &res : results) {
2425
sedata_filtered.push_back(sedata[res.original_index]);
2426
}
2427
}
2428
2429
Color tool_color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
2430
tool_color.set_s(tool_color.get_s() * 1.5);
2431
for (int i = 0; i < sedata_filtered.size(); i++) {
2432
script_list->add_item(sedata_filtered[i].name, sedata_filtered[i].icon);
2433
if (sedata_filtered[i].tool) {
2434
script_list->set_item_icon_modulate(-1, tool_color);
2435
}
2436
2437
int index = script_list->get_item_count() - 1;
2438
script_list->set_item_tooltip(index, sedata_filtered[i].tooltip);
2439
script_list->set_item_metadata(index, sedata_filtered[i].index); /* Saving as metadata the script's index in the tab container and not the filtered one */
2440
if (sedata_filtered[i].used) {
2441
script_list->set_item_custom_bg_color(index, Color(.5, .5, .5, .125));
2442
}
2443
if (tab_container->get_current_tab() == sedata_filtered[i].index) {
2444
script_list->select(index);
2445
2446
script_name_label->set_text(sedata_filtered[i].name);
2447
script_icon->set_texture(sedata_filtered[i].icon);
2448
2449
ScriptEditorBase *se = _get_current_editor();
2450
if (se) {
2451
se->enable_editor(this);
2452
_update_selected_editor_menu();
2453
}
2454
}
2455
}
2456
2457
if (!waiting_update_names) {
2458
_update_members_overview();
2459
_update_help_overview();
2460
} else {
2461
waiting_update_names = false;
2462
}
2463
_update_members_overview_visibility();
2464
_update_help_overview_visibility();
2465
_update_script_colors();
2466
}
2467
2468
Ref<TextFile> ScriptEditor::_load_text_file(const String &p_path, Error *r_error) const {
2469
if (r_error) {
2470
*r_error = ERR_FILE_CANT_OPEN;
2471
}
2472
2473
String local_path = ProjectSettings::get_singleton()->localize_path(p_path);
2474
String path = ResourceLoader::path_remap(local_path);
2475
2476
TextFile *text_file = memnew(TextFile);
2477
Ref<TextFile> text_res(text_file);
2478
Error err = text_file->load_text(path);
2479
2480
ERR_FAIL_COND_V_MSG(err != OK, Ref<Resource>(), "Cannot load text file '" + path + "'.");
2481
2482
text_file->set_file_path(local_path);
2483
text_file->set_path(local_path, true);
2484
2485
if (ResourceLoader::get_timestamp_on_load()) {
2486
text_file->set_last_modified_time(FileAccess::get_modified_time(path));
2487
}
2488
2489
if (r_error) {
2490
*r_error = OK;
2491
}
2492
2493
return text_res;
2494
}
2495
2496
Error ScriptEditor::_save_text_file(Ref<TextFile> p_text_file, const String &p_path) {
2497
Ref<TextFile> sqscr = p_text_file;
2498
ERR_FAIL_COND_V(sqscr.is_null(), ERR_INVALID_PARAMETER);
2499
2500
String source = sqscr->get_text();
2501
2502
Error err;
2503
{
2504
Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::WRITE, &err);
2505
2506
ERR_FAIL_COND_V_MSG(err, err, "Cannot save text file '" + p_path + "'.");
2507
2508
file->store_string(source);
2509
if (file->get_error() != OK && file->get_error() != ERR_FILE_EOF) {
2510
return ERR_CANT_CREATE;
2511
}
2512
}
2513
2514
if (ResourceSaver::get_timestamp_on_save()) {
2515
p_text_file->set_last_modified_time(FileAccess::get_modified_time(p_path));
2516
}
2517
2518
EditorFileSystem::get_singleton()->update_file(p_path);
2519
2520
_res_saved_callback(sqscr);
2521
return OK;
2522
}
2523
2524
bool ScriptEditor::edit(const Ref<Resource> &p_resource, int p_line, int p_col, bool p_grab_focus) {
2525
if (p_resource.is_null()) {
2526
return false;
2527
}
2528
2529
Ref<Script> scr = p_resource;
2530
2531
// Don't open dominant script if using an external editor.
2532
bool use_external_editor =
2533
external_editor_active ||
2534
(scr.is_valid() && scr->get_language()->overrides_external_editor());
2535
use_external_editor = use_external_editor && !(scr.is_valid() && scr->is_built_in()); // Ignore external editor for built-in scripts.
2536
const bool open_dominant = EDITOR_GET("text_editor/behavior/files/open_dominant_script_on_scene_change");
2537
2538
const bool should_open = (open_dominant && !use_external_editor) || !EditorNode::get_singleton()->is_changing_scene();
2539
2540
if (scr.is_valid() && scr->get_language()->overrides_external_editor()) {
2541
if (should_open) {
2542
Error err = scr->get_language()->open_in_external_editor(scr, p_line >= 0 ? p_line : 0, p_col);
2543
if (err != OK) {
2544
ERR_PRINT("Couldn't open script in the overridden external text editor");
2545
}
2546
}
2547
return false;
2548
}
2549
2550
if (use_external_editor &&
2551
(EditorDebuggerNode::get_singleton()->get_dump_stack_script() != p_resource || EditorDebuggerNode::get_singleton()->get_debug_with_external_editor()) &&
2552
p_resource->get_path().is_resource_file()) {
2553
String path = EDITOR_GET("text_editor/external/exec_path");
2554
String flags = EDITOR_GET("text_editor/external/exec_flags");
2555
2556
List<String> args;
2557
bool has_file_flag = false;
2558
String script_path = ProjectSettings::get_singleton()->globalize_path(p_resource->get_path());
2559
2560
if (flags.size()) {
2561
String project_path = ProjectSettings::get_singleton()->get_resource_path();
2562
2563
flags = flags.replacen("{line}", itos(MAX(p_line + 1, 1)));
2564
flags = flags.replacen("{col}", itos(p_col + 1));
2565
flags = flags.strip_edges().replace("\\\\", "\\");
2566
2567
int from = 0;
2568
int num_chars = 0;
2569
bool inside_quotes = false;
2570
2571
for (int i = 0; i < flags.size(); i++) {
2572
if (flags[i] == '"' && (!i || flags[i - 1] != '\\')) {
2573
if (!inside_quotes) {
2574
from++;
2575
}
2576
inside_quotes = !inside_quotes;
2577
2578
} else if (flags[i] == '\0' || (!inside_quotes && flags[i] == ' ')) {
2579
String arg = flags.substr(from, num_chars);
2580
if (arg.contains("{file}")) {
2581
has_file_flag = true;
2582
}
2583
2584
// do path replacement here, else there will be issues with spaces and quotes
2585
arg = arg.replacen("{project}", project_path);
2586
arg = arg.replacen("{file}", script_path);
2587
args.push_back(arg);
2588
2589
from = i + 1;
2590
num_chars = 0;
2591
} else {
2592
num_chars++;
2593
}
2594
}
2595
}
2596
2597
// Default to passing script path if no {file} flag is specified.
2598
if (!has_file_flag) {
2599
args.push_back(script_path);
2600
}
2601
2602
if (!path.is_empty()) {
2603
Error err = OS::get_singleton()->create_process(path, args);
2604
if (err == OK) {
2605
return false;
2606
}
2607
}
2608
2609
ERR_PRINT("Couldn't open external text editor, falling back to the internal editor. Review your `text_editor/external/` editor settings.");
2610
}
2611
2612
for (int i = 0; i < tab_container->get_tab_count(); i++) {
2613
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));
2614
if (!se) {
2615
continue;
2616
}
2617
2618
if ((scr.is_valid() && se->get_edited_resource() == p_resource) || se->get_edited_resource()->get_path() == p_resource->get_path()) {
2619
if (should_open) {
2620
se->enable_editor(this);
2621
2622
if (tab_container->get_current_tab() != i) {
2623
_go_to_tab(i);
2624
}
2625
if (is_visible_in_tree()) {
2626
se->ensure_focus();
2627
}
2628
2629
if (p_line > 0) {
2630
se->goto_line(p_line);
2631
}
2632
}
2633
_update_script_names();
2634
script_list->ensure_current_is_visible();
2635
return true;
2636
}
2637
}
2638
2639
// doesn't have it, make a new one
2640
2641
ScriptEditorBase *se = nullptr;
2642
2643
for (int i = script_editor_func_count - 1; i >= 0; i--) {
2644
se = script_editor_funcs[i](p_resource);
2645
if (se) {
2646
break;
2647
}
2648
}
2649
ERR_FAIL_NULL_V(se, false);
2650
2651
se->set_edited_resource(p_resource);
2652
2653
// Syntax highlighting.
2654
bool highlighter_set = false;
2655
for (int i = 0; i < syntax_highlighters.size(); i++) {
2656
Ref<EditorSyntaxHighlighter> highlighter = syntax_highlighters[i]->_create();
2657
if (highlighter.is_null()) {
2658
continue;
2659
}
2660
se->add_syntax_highlighter(highlighter);
2661
2662
if (highlighter_set) {
2663
continue;
2664
}
2665
2666
PackedStringArray languages = highlighter->_get_supported_languages();
2667
// If script try language, else use extension.
2668
if (scr.is_valid()) {
2669
if (languages.has(scr->get_language()->get_name())) {
2670
se->set_syntax_highlighter(highlighter);
2671
highlighter_set = true;
2672
}
2673
continue;
2674
}
2675
2676
if (languages.has(p_resource->get_path().get_extension())) {
2677
se->set_syntax_highlighter(highlighter);
2678
highlighter_set = true;
2679
}
2680
}
2681
2682
tab_container->add_child(se);
2683
2684
if (p_grab_focus) {
2685
se->enable_editor(this);
2686
}
2687
2688
// If we delete a script within the filesystem, the original resource path
2689
// is lost, so keep it as `edited_file_data` to figure out the exact tab to delete.
2690
se->edited_file_data.path = p_resource->get_path();
2691
se->edited_file_data.last_modified_time = FileAccess::get_modified_time(p_resource->get_path());
2692
2693
se->set_tooltip_request_func(callable_mp(this, &ScriptEditor::_get_debug_tooltip));
2694
2695
if (se->get_edit_menu()) {
2696
se->get_edit_menu()->hide();
2697
menu_hb->add_child(se->get_edit_menu());
2698
menu_hb->move_child(se->get_edit_menu(), 1);
2699
}
2700
2701
if (p_grab_focus) {
2702
_go_to_tab(tab_container->get_tab_count() - 1);
2703
_add_recent_script(p_resource->get_path());
2704
}
2705
2706
if (script_editor_cache->has_section(p_resource->get_path())) {
2707
se->set_edit_state(script_editor_cache->get_value(p_resource->get_path(), "state"));
2708
ScriptTextEditor *ste = Object::cast_to<ScriptTextEditor>(se);
2709
if (ste) {
2710
ste->store_previous_state();
2711
}
2712
}
2713
2714
_sort_list_on_update = true;
2715
_update_script_names();
2716
_save_layout();
2717
se->connect("name_changed", callable_mp(this, &ScriptEditor::_update_script_names));
2718
se->connect("edited_script_changed", callable_mp(this, &ScriptEditor::_script_changed));
2719
se->connect("request_help", callable_mp(this, &ScriptEditor::_help_search));
2720
se->connect("request_open_script_at_line", callable_mp(this, &ScriptEditor::_goto_script_line));
2721
se->connect("go_to_help", callable_mp(this, &ScriptEditor::_help_class_goto));
2722
se->connect("request_save_history", callable_mp(this, &ScriptEditor::_save_history));
2723
se->connect("request_save_previous_state", callable_mp(this, &ScriptEditor::_save_previous_state));
2724
se->connect("search_in_files_requested", callable_mp(this, &ScriptEditor::open_find_in_files_dialog));
2725
se->connect("replace_in_files_requested", callable_mp(this, &ScriptEditor::_on_replace_in_files_requested));
2726
se->connect("go_to_method", callable_mp(this, &ScriptEditor::script_goto_method));
2727
2728
CodeTextEditor *cte = se->get_code_editor();
2729
if (cte) {
2730
cte->set_zoom_factor(zoom_factor);
2731
cte->connect("zoomed", callable_mp(this, &ScriptEditor::_set_script_zoom_factor));
2732
cte->connect(SceneStringName(visibility_changed), callable_mp(this, &ScriptEditor::_update_code_editor_zoom_factor).bind(cte));
2733
}
2734
2735
//test for modification, maybe the script was not edited but was loaded
2736
2737
_test_script_times_on_disk(p_resource);
2738
_update_modified_scripts_for_external_editor(p_resource);
2739
2740
if (p_line >= 0) {
2741
se->goto_line(p_line);
2742
}
2743
2744
notify_script_changed(p_resource);
2745
return true;
2746
}
2747
2748
PackedStringArray ScriptEditor::get_unsaved_scripts() const {
2749
PackedStringArray unsaved_list;
2750
2751
for (int i = 0; i < tab_container->get_tab_count(); i++) {
2752
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));
2753
if (se && se->is_unsaved()) {
2754
unsaved_list.append(se->get_name());
2755
}
2756
}
2757
return unsaved_list;
2758
}
2759
2760
void ScriptEditor::save_current_script() {
2761
ScriptEditorBase *current = _get_current_editor();
2762
if (!current || _test_script_times_on_disk()) {
2763
return;
2764
}
2765
2766
if (trim_trailing_whitespace_on_save) {
2767
current->trim_trailing_whitespace();
2768
}
2769
2770
if (trim_final_newlines_on_save) {
2771
current->trim_final_newlines();
2772
}
2773
2774
if (convert_indent_on_save) {
2775
current->convert_indent();
2776
}
2777
2778
Ref<Resource> resource = current->get_edited_resource();
2779
Ref<TextFile> text_file = resource;
2780
Ref<Script> scr = resource;
2781
2782
if (text_file.is_valid()) {
2783
current->apply_code();
2784
_save_text_file(text_file, text_file->get_path());
2785
return;
2786
}
2787
2788
if (scr.is_valid()) {
2789
clear_docs_from_script(scr);
2790
}
2791
2792
EditorNode::get_singleton()->save_resource(resource);
2793
2794
if (scr.is_valid()) {
2795
update_docs_from_script(scr);
2796
}
2797
}
2798
2799
void ScriptEditor::save_all_scripts() {
2800
HashSet<String> scenes_to_save;
2801
2802
for (int i = 0; i < tab_container->get_tab_count(); i++) {
2803
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));
2804
if (!se) {
2805
continue;
2806
}
2807
2808
if (convert_indent_on_save) {
2809
se->convert_indent();
2810
}
2811
2812
if (trim_trailing_whitespace_on_save) {
2813
se->trim_trailing_whitespace();
2814
}
2815
2816
if (trim_final_newlines_on_save) {
2817
se->trim_final_newlines();
2818
}
2819
2820
if (!se->is_unsaved()) {
2821
continue;
2822
}
2823
2824
Ref<Resource> edited_res = se->get_edited_resource();
2825
if (edited_res.is_valid()) {
2826
se->apply_code();
2827
}
2828
2829
Ref<Script> scr = edited_res;
2830
2831
if (scr.is_valid()) {
2832
clear_docs_from_script(scr);
2833
}
2834
2835
if (!edited_res->is_built_in()) {
2836
Ref<TextFile> text_file = edited_res;
2837
if (text_file.is_valid()) {
2838
_save_text_file(text_file, text_file->get_path());
2839
continue;
2840
}
2841
2842
// External script, save it.
2843
EditorNode::get_singleton()->save_resource(edited_res);
2844
} else {
2845
// For built-in scripts, save their scenes instead.
2846
const String scene_path = edited_res->get_path().get_slice("::", 0);
2847
if (!scene_path.is_empty() && !scenes_to_save.has(scene_path)) {
2848
scenes_to_save.insert(scene_path);
2849
}
2850
}
2851
2852
if (scr.is_valid()) {
2853
update_docs_from_script(scr);
2854
}
2855
}
2856
2857
if (!scenes_to_save.is_empty()) {
2858
EditorNode::get_singleton()->save_scene_list(scenes_to_save);
2859
}
2860
2861
_update_script_names();
2862
}
2863
2864
void ScriptEditor::update_script_times() {
2865
for (int i = 0; i < tab_container->get_tab_count(); i++) {
2866
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));
2867
if (se) {
2868
se->edited_file_data.last_modified_time = FileAccess::get_modified_time(se->edited_file_data.path);
2869
}
2870
}
2871
}
2872
2873
void ScriptEditor::apply_scripts() const {
2874
for (int i = 0; i < tab_container->get_tab_count(); i++) {
2875
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));
2876
if (!se) {
2877
continue;
2878
}
2879
se->insert_final_newline();
2880
se->apply_code();
2881
}
2882
}
2883
2884
void ScriptEditor::reload_scripts(bool p_refresh_only) {
2885
// Call deferred to make sure it runs on the main thread.
2886
callable_mp(this, &ScriptEditor::_reload_scripts).call_deferred(p_refresh_only);
2887
}
2888
2889
void ScriptEditor::_reload_scripts(bool p_refresh_only) {
2890
for (int i = 0; i < tab_container->get_tab_count(); i++) {
2891
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));
2892
if (!se) {
2893
continue;
2894
}
2895
2896
Ref<Resource> edited_res = se->get_edited_resource();
2897
2898
if (edited_res->is_built_in()) {
2899
continue; // Internal script, who cares.
2900
}
2901
2902
if (p_refresh_only) {
2903
// Make sure the modified time is correct.
2904
se->edited_file_data.last_modified_time = FileAccess::get_modified_time(edited_res->get_path());
2905
} else {
2906
uint64_t last_date = se->edited_file_data.last_modified_time;
2907
uint64_t date = FileAccess::get_modified_time(edited_res->get_path());
2908
2909
if (last_date == date) {
2910
continue;
2911
}
2912
se->edited_file_data.last_modified_time = date;
2913
2914
Ref<Script> scr = edited_res;
2915
if (scr.is_valid()) {
2916
Ref<Script> rel_scr = ResourceLoader::load(scr->get_path(), scr->get_class(), ResourceFormatLoader::CACHE_MODE_IGNORE);
2917
ERR_CONTINUE(rel_scr.is_null());
2918
scr->set_source_code(rel_scr->get_source_code());
2919
scr->reload(true);
2920
2921
update_docs_from_script(scr);
2922
}
2923
2924
Ref<JSON> json = edited_res;
2925
if (json.is_valid()) {
2926
Ref<JSON> rel_json = ResourceLoader::load(json->get_path(), json->get_class(), ResourceFormatLoader::CACHE_MODE_IGNORE);
2927
ERR_CONTINUE(rel_json.is_null());
2928
json->parse(rel_json->get_parsed_text(), true);
2929
}
2930
2931
Ref<TextFile> text_file = edited_res;
2932
if (text_file.is_valid()) {
2933
text_file->reload_from_file();
2934
}
2935
}
2936
2937
se->reload_text();
2938
}
2939
2940
disk_changed->hide();
2941
_update_script_names();
2942
}
2943
2944
void ScriptEditor::open_find_in_files_dialog(const String &text) {
2945
find_in_files_dialog->set_find_in_files_mode(FindInFilesDialog::SEARCH_MODE);
2946
find_in_files_dialog->set_search_text(text);
2947
find_in_files_dialog->popup_centered();
2948
}
2949
2950
void ScriptEditor::open_script_create_dialog(const String &p_base_name, const String &p_base_path) {
2951
_menu_option(FILE_MENU_NEW);
2952
script_create_dialog->config(p_base_name, p_base_path);
2953
}
2954
2955
void ScriptEditor::open_text_file_create_dialog(const String &p_base_path, const String &p_base_name) {
2956
_menu_option(FILE_MENU_NEW_TEXTFILE);
2957
file_dialog->set_current_dir(p_base_path);
2958
file_dialog->set_current_file(p_base_name);
2959
open_textfile_after_create = false;
2960
}
2961
2962
Ref<Resource> ScriptEditor::open_file(const String &p_file) {
2963
List<String> extensions;
2964
ResourceLoader::get_recognized_extensions_for_type("Script", &extensions);
2965
ResourceLoader::get_recognized_extensions_for_type("JSON", &extensions);
2966
if (extensions.find(p_file.get_extension())) {
2967
Ref<Resource> scr = ResourceLoader::load(p_file);
2968
if (scr.is_null()) {
2969
EditorNode::get_singleton()->show_warning(TTR("Could not load file at:") + "\n\n" + p_file, TTR("Error!"));
2970
return Ref<Resource>();
2971
}
2972
2973
edit(scr);
2974
return scr;
2975
}
2976
2977
Error error;
2978
Ref<TextFile> text_file = _load_text_file(p_file, &error);
2979
if (error != OK) {
2980
EditorNode::get_singleton()->show_warning(TTR("Could not load file at:") + "\n\n" + p_file, TTR("Error!"));
2981
return Ref<Resource>();
2982
}
2983
2984
if (text_file.is_valid()) {
2985
edit(text_file);
2986
return text_file;
2987
}
2988
return Ref<Resource>();
2989
}
2990
2991
void ScriptEditor::_editor_stop() {
2992
for (int i = 0; i < tab_container->get_tab_count(); i++) {
2993
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));
2994
if (!se) {
2995
continue;
2996
}
2997
2998
se->set_debugger_active(false);
2999
}
3000
}
3001
3002
void ScriptEditor::_add_callback(Object *p_obj, const String &p_function, const PackedStringArray &p_args) {
3003
ERR_FAIL_NULL(p_obj);
3004
Ref<Script> scr = p_obj->get_script();
3005
ERR_FAIL_COND(scr.is_null());
3006
3007
if (!scr->get_language()->can_make_function()) {
3008
return;
3009
}
3010
3011
EditorNode::get_singleton()->push_item(scr.ptr());
3012
3013
for (int i = 0; i < tab_container->get_tab_count(); i++) {
3014
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));
3015
if (!se) {
3016
continue;
3017
}
3018
if (se->get_edited_resource() != scr) {
3019
continue;
3020
}
3021
3022
se->add_callback(p_function, p_args);
3023
3024
_go_to_tab(i);
3025
3026
script_list->select(script_list->find_metadata(i));
3027
3028
// Save the current script so the changes can be picked up by an external editor.
3029
if (!scr.ptr()->is_built_in()) { // But only if it's not built-in script.
3030
save_current_script();
3031
}
3032
3033
break;
3034
}
3035
3036
// Move back to the previously edited node to reselect it in the Inspector and the NodeDock.
3037
// We assume that the previous item is the node on which the callbacks were added.
3038
EditorNode::get_singleton()->edit_previous_item();
3039
}
3040
3041
void ScriptEditor::_save_editor_state(ScriptEditorBase *p_editor) {
3042
if (restoring_layout) {
3043
return;
3044
}
3045
3046
const String &path = p_editor->get_edited_resource()->get_path();
3047
if (!path.is_resource_file()) {
3048
return;
3049
}
3050
3051
script_editor_cache->set_value(path, "state", p_editor->get_edit_state());
3052
// This is saved later when we save the editor layout.
3053
}
3054
3055
void ScriptEditor::_save_layout() {
3056
if (restoring_layout) {
3057
return;
3058
}
3059
3060
EditorNode::get_singleton()->save_editor_layout_delayed();
3061
}
3062
3063
void ScriptEditor::_editor_settings_changed() {
3064
if (!EditorThemeManager::is_generated_theme_outdated() &&
3065
!EditorSettings::get_singleton()->check_changed_settings_in_group("interface/editor") &&
3066
!EditorSettings::get_singleton()->check_changed_settings_in_group("text_editor") &&
3067
!EditorSettings::get_singleton()->check_changed_settings_in_group("docks/filesystem")) {
3068
return;
3069
}
3070
3071
_apply_editor_settings();
3072
}
3073
3074
void ScriptEditor::_apply_editor_settings() {
3075
textfile_extensions.clear();
3076
const Vector<String> textfile_ext = ((String)(EDITOR_GET("docks/filesystem/textfile_extensions"))).split(",", false);
3077
for (const String &E : textfile_ext) {
3078
textfile_extensions.insert(E);
3079
}
3080
3081
trim_trailing_whitespace_on_save = EDITOR_GET("text_editor/behavior/files/trim_trailing_whitespace_on_save");
3082
trim_final_newlines_on_save = EDITOR_GET("text_editor/behavior/files/trim_final_newlines_on_save");
3083
convert_indent_on_save = EDITOR_GET("text_editor/behavior/files/convert_indent_on_save");
3084
3085
members_overview_enabled = EDITOR_GET("text_editor/script_list/show_members_overview");
3086
help_overview_enabled = EDITOR_GET("text_editor/help/show_help_index");
3087
external_editor_active = EDITOR_GET("text_editor/external/use_external_editor");
3088
_update_members_overview_visibility();
3089
_update_help_overview_visibility();
3090
3091
_update_autosave_timer();
3092
3093
_update_script_names();
3094
3095
ScriptServer::set_reload_scripts_on_save(EDITOR_GET("text_editor/behavior/files/auto_reload_and_parse_scripts_on_save"));
3096
3097
for (int i = 0; i < tab_container->get_tab_count(); i++) {
3098
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));
3099
if (!se) {
3100
continue;
3101
}
3102
3103
se->update_settings();
3104
}
3105
}
3106
3107
void ScriptEditor::_filesystem_changed() {
3108
_update_script_names();
3109
}
3110
3111
void ScriptEditor::_files_moved(const String &p_old_file, const String &p_new_file) {
3112
if (!script_editor_cache->has_section(p_old_file)) {
3113
return;
3114
}
3115
3116
for (int i = 0; i < tab_container->get_tab_count(); i++) {
3117
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));
3118
if (se && se->edited_file_data.path == p_old_file) {
3119
se->edited_file_data.path = p_new_file;
3120
break;
3121
}
3122
}
3123
3124
Variant state = script_editor_cache->get_value(p_old_file, "state");
3125
script_editor_cache->erase_section(p_old_file);
3126
script_editor_cache->set_value(p_new_file, "state", state);
3127
3128
// If Script, update breakpoints with debugger.
3129
Array breakpoints = _get_cached_breakpoints_for_script(p_new_file);
3130
for (int breakpoint : breakpoints) {
3131
int line = (int)breakpoint + 1;
3132
EditorDebuggerNode::get_singleton()->set_breakpoint(p_old_file, line, false);
3133
if (!p_new_file.begins_with("local://") && ResourceLoader::exists(p_new_file, "Script")) {
3134
EditorDebuggerNode::get_singleton()->set_breakpoint(p_new_file, line, true);
3135
}
3136
}
3137
// This is saved later when we save the editor layout.
3138
}
3139
3140
void ScriptEditor::_file_removed(const String &p_removed_file) {
3141
for (int i = 0; i < tab_container->get_tab_count(); i++) {
3142
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));
3143
if (!se) {
3144
continue;
3145
}
3146
if (se->edited_file_data.path == p_removed_file) {
3147
// The script is deleted with no undo, so just close the tab.
3148
_close_tab(i, false, false);
3149
}
3150
}
3151
3152
// Check closed.
3153
if (script_editor_cache->has_section(p_removed_file)) {
3154
Array breakpoints = _get_cached_breakpoints_for_script(p_removed_file);
3155
for (int breakpoint : breakpoints) {
3156
EditorDebuggerNode::get_singleton()->set_breakpoint(p_removed_file, (int)breakpoint + 1, false);
3157
}
3158
script_editor_cache->erase_section(p_removed_file);
3159
}
3160
}
3161
3162
void ScriptEditor::_update_find_replace_bar() {
3163
ScriptEditorBase *se = _get_current_editor();
3164
if (se) {
3165
se->set_find_replace_bar(find_replace_bar);
3166
} else {
3167
find_replace_bar->set_text_edit(nullptr);
3168
find_replace_bar->hide();
3169
}
3170
}
3171
3172
void ScriptEditor::_autosave_scripts() {
3173
save_all_scripts();
3174
}
3175
3176
void ScriptEditor::_update_autosave_timer() {
3177
if (!autosave_timer->is_inside_tree()) {
3178
return;
3179
}
3180
3181
float autosave_time = EDITOR_GET("text_editor/behavior/files/autosave_interval_secs");
3182
if (autosave_time > 0) {
3183
autosave_timer->set_wait_time(autosave_time);
3184
autosave_timer->start();
3185
} else {
3186
autosave_timer->stop();
3187
}
3188
}
3189
3190
void ScriptEditor::_tree_changed() {
3191
if (waiting_update_names) {
3192
return;
3193
}
3194
3195
waiting_update_names = true;
3196
callable_mp(this, &ScriptEditor::_update_script_names).call_deferred();
3197
}
3198
3199
void ScriptEditor::_split_dragged(float) {
3200
_save_layout();
3201
}
3202
3203
Variant ScriptEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) {
3204
if (tab_container->get_tab_count() == 0) {
3205
return Variant();
3206
}
3207
3208
Node *cur_node = tab_container->get_tab_control(tab_container->get_current_tab());
3209
3210
HBoxContainer *drag_preview = memnew(HBoxContainer);
3211
String preview_name = "";
3212
Ref<Texture2D> preview_icon;
3213
3214
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(cur_node);
3215
if (se) {
3216
preview_name = se->get_name();
3217
preview_icon = se->get_theme_icon();
3218
}
3219
EditorHelp *eh = Object::cast_to<EditorHelp>(cur_node);
3220
if (eh) {
3221
preview_name = eh->get_class();
3222
preview_icon = get_editor_theme_icon(SNAME("Help"));
3223
}
3224
3225
if (preview_icon.is_valid()) {
3226
TextureRect *tf = memnew(TextureRect);
3227
tf->set_texture(preview_icon);
3228
tf->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED);
3229
drag_preview->add_child(tf);
3230
}
3231
Label *label = memnew(Label(preview_name));
3232
label->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED); // Don't translate script names and class names.
3233
drag_preview->add_child(label);
3234
set_drag_preview(drag_preview);
3235
3236
Dictionary drag_data;
3237
drag_data["type"] = "script_list_element"; // using a custom type because node caused problems when dragging to scene tree
3238
drag_data["script_list_element"] = cur_node;
3239
3240
return drag_data;
3241
}
3242
3243
bool ScriptEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {
3244
Dictionary d = p_data;
3245
if (!d.has("type")) {
3246
return false;
3247
}
3248
3249
if (String(d["type"]) == "script_list_element") {
3250
Node *node = Object::cast_to<Node>(d["script_list_element"]);
3251
3252
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(node);
3253
if (se) {
3254
return true;
3255
}
3256
EditorHelp *eh = Object::cast_to<EditorHelp>(node);
3257
if (eh) {
3258
return true;
3259
}
3260
}
3261
3262
if (String(d["type"]) == "nodes") {
3263
Array nodes = d["nodes"];
3264
if (nodes.is_empty()) {
3265
return false;
3266
}
3267
Node *node = get_node((nodes[0]));
3268
3269
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(node);
3270
if (se) {
3271
return true;
3272
}
3273
EditorHelp *eh = Object::cast_to<EditorHelp>(node);
3274
if (eh) {
3275
return true;
3276
}
3277
}
3278
3279
if (String(d["type"]) == "files") {
3280
Vector<String> files = d["files"];
3281
3282
if (files.is_empty()) {
3283
return false; //weird
3284
}
3285
3286
for (int i = 0; i < files.size(); i++) {
3287
const String &file = files[i];
3288
if (file.is_empty() || !FileAccess::exists(file)) {
3289
continue;
3290
}
3291
if (ResourceLoader::exists(file, "Script") || ResourceLoader::exists(file, "JSON")) {
3292
Ref<Resource> scr = ResourceLoader::load(file);
3293
if (scr.is_valid()) {
3294
return true;
3295
}
3296
}
3297
3298
if (textfile_extensions.has(file.get_extension())) {
3299
Error err;
3300
Ref<TextFile> text_file = _load_text_file(file, &err);
3301
if (text_file.is_valid() && err == OK) {
3302
return true;
3303
}
3304
}
3305
}
3306
return false;
3307
}
3308
3309
return false;
3310
}
3311
3312
void ScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {
3313
if (!can_drop_data_fw(p_point, p_data, p_from)) {
3314
return;
3315
}
3316
3317
Dictionary d = p_data;
3318
if (!d.has("type")) {
3319
return;
3320
}
3321
3322
if (String(d["type"]) == "script_list_element") {
3323
Node *node = Object::cast_to<Node>(d["script_list_element"]);
3324
3325
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(node);
3326
EditorHelp *eh = Object::cast_to<EditorHelp>(node);
3327
if (se || eh) {
3328
int new_index = 0;
3329
if (script_list->get_item_count() > 0) {
3330
int pos = 0;
3331
if (p_point == Vector2(Math::INF, Math::INF)) {
3332
if (script_list->is_anything_selected()) {
3333
pos = script_list->get_selected_items()[0];
3334
}
3335
} else {
3336
pos = script_list->get_item_at_position(p_point);
3337
}
3338
new_index = script_list->get_item_metadata(pos);
3339
}
3340
tab_container->move_child(node, new_index);
3341
tab_container->set_current_tab(new_index);
3342
_update_script_names();
3343
}
3344
}
3345
3346
if (String(d["type"]) == "nodes") {
3347
Array nodes = d["nodes"];
3348
if (nodes.is_empty()) {
3349
return;
3350
}
3351
Node *node = get_node(nodes[0]);
3352
3353
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(node);
3354
EditorHelp *eh = Object::cast_to<EditorHelp>(node);
3355
if (se || eh) {
3356
int new_index = 0;
3357
if (script_list->get_item_count() > 0) {
3358
int pos = 0;
3359
if (p_point == Vector2(Math::INF, Math::INF)) {
3360
if (script_list->is_anything_selected()) {
3361
pos = script_list->get_selected_items()[0];
3362
}
3363
} else {
3364
pos = script_list->get_item_at_position(p_point);
3365
}
3366
new_index = script_list->get_item_metadata(pos);
3367
}
3368
tab_container->move_child(node, new_index);
3369
tab_container->set_current_tab(new_index);
3370
_update_script_names();
3371
}
3372
}
3373
3374
if (String(d["type"]) == "files") {
3375
Vector<String> files = d["files"];
3376
3377
int new_index = 0;
3378
if (script_list->get_item_count() > 0) {
3379
int pos = 0;
3380
if (p_point == Vector2(Math::INF, Math::INF)) {
3381
if (script_list->is_anything_selected()) {
3382
pos = script_list->get_selected_items()[0];
3383
}
3384
} else {
3385
pos = script_list->get_item_at_position(p_point);
3386
}
3387
new_index = script_list->get_item_metadata(pos);
3388
}
3389
int num_tabs_before = tab_container->get_tab_count();
3390
for (int i = 0; i < files.size(); i++) {
3391
const String &file = files[i];
3392
if (file.is_empty() || !FileAccess::exists(file)) {
3393
continue;
3394
}
3395
3396
if (!ResourceLoader::exists(file, "Script") && !ResourceLoader::exists(file, "JSON") && !textfile_extensions.has(file.get_extension())) {
3397
continue;
3398
}
3399
3400
Ref<Resource> res = open_file(file);
3401
if (res.is_valid()) {
3402
const int num_tabs = tab_container->get_tab_count();
3403
if (num_tabs > num_tabs_before) {
3404
tab_container->move_child(tab_container->get_tab_control(tab_container->get_tab_count() - 1), new_index);
3405
num_tabs_before = num_tabs;
3406
} else if (num_tabs > 0) { /* Maybe script was already open */
3407
tab_container->move_child(tab_container->get_tab_control(tab_container->get_current_tab()), new_index);
3408
}
3409
}
3410
}
3411
if (tab_container->get_tab_count() > 0) {
3412
tab_container->set_current_tab(new_index);
3413
}
3414
_update_script_names();
3415
}
3416
}
3417
3418
void ScriptEditor::input(const Ref<InputEvent> &p_event) {
3419
// This is implemented in `input()` rather than `unhandled_input()` to allow
3420
// the shortcut to be used regardless of the click location.
3421
// This feature can be disabled to avoid interfering with other uses of the additional
3422
// mouse buttons, such as push-to-talk in a VoIP program.
3423
if (EDITOR_GET("interface/editor/mouse_extra_buttons_navigate_history")) {
3424
const Ref<InputEventMouseButton> mb = p_event;
3425
3426
// Navigate the script history using additional mouse buttons present on some mice.
3427
// This must be hardcoded as the editor shortcuts dialog doesn't allow assigning
3428
// more than one shortcut per action.
3429
if (mb.is_valid() && mb->is_pressed() && is_visible_in_tree()) {
3430
if (mb->get_button_index() == MouseButton::MB_XBUTTON1) {
3431
_history_back();
3432
}
3433
3434
if (mb->get_button_index() == MouseButton::MB_XBUTTON2) {
3435
_history_forward();
3436
}
3437
}
3438
}
3439
}
3440
3441
void ScriptEditor::shortcut_input(const Ref<InputEvent> &p_event) {
3442
ERR_FAIL_COND(p_event.is_null());
3443
3444
if (!is_visible_in_tree() || !p_event->is_pressed() || p_event->is_echo()) {
3445
return;
3446
}
3447
if (ED_IS_SHORTCUT("script_editor/next_script", p_event)) {
3448
if (script_list->get_item_count() > 1) {
3449
int next_tab = script_list->get_current() + 1;
3450
next_tab %= script_list->get_item_count();
3451
_go_to_tab(script_list->get_item_metadata(next_tab));
3452
_update_script_names();
3453
}
3454
accept_event();
3455
}
3456
if (ED_IS_SHORTCUT("script_editor/prev_script", p_event)) {
3457
if (script_list->get_item_count() > 1) {
3458
int next_tab = script_list->get_current() - 1;
3459
next_tab = next_tab >= 0 ? next_tab : script_list->get_item_count() - 1;
3460
_go_to_tab(script_list->get_item_metadata(next_tab));
3461
_update_script_names();
3462
}
3463
accept_event();
3464
}
3465
if (ED_IS_SHORTCUT("script_editor/window_move_up", p_event)) {
3466
_menu_option(FILE_MENU_MOVE_UP);
3467
accept_event();
3468
}
3469
if (ED_IS_SHORTCUT("script_editor/window_move_down", p_event)) {
3470
_menu_option(FILE_MENU_MOVE_DOWN);
3471
accept_event();
3472
}
3473
3474
Callable custom_callback = EditorContextMenuPluginManager::get_singleton()->match_custom_shortcut(EditorContextMenuPlugin::CONTEXT_SLOT_SCRIPT_EDITOR, p_event);
3475
if (custom_callback.is_valid()) {
3476
Ref<Resource> resource;
3477
ScriptEditorBase *current = _get_current_editor();
3478
if (current) {
3479
resource = current->get_edited_resource();
3480
}
3481
EditorContextMenuPluginManager::get_singleton()->invoke_callback(custom_callback, resource);
3482
accept_event();
3483
}
3484
}
3485
3486
void ScriptEditor::_script_list_clicked(int p_item, Vector2 p_local_mouse_pos, MouseButton p_mouse_button_index) {
3487
if (p_mouse_button_index == MouseButton::MIDDLE) {
3488
script_list->select(p_item);
3489
_script_selected(p_item);
3490
_menu_option(FILE_MENU_CLOSE);
3491
}
3492
3493
if (p_mouse_button_index == MouseButton::RIGHT) {
3494
_make_script_list_context_menu();
3495
}
3496
}
3497
3498
void ScriptEditor::_make_script_list_context_menu() {
3499
context_menu->clear();
3500
3501
int selected = tab_container->get_current_tab();
3502
if (selected < 0 || selected >= tab_container->get_tab_count()) {
3503
return;
3504
}
3505
3506
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(selected));
3507
if (se) {
3508
context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/save"), FILE_MENU_SAVE);
3509
context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/save_as"), FILE_MENU_SAVE_AS);
3510
}
3511
context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/close_file"), FILE_MENU_CLOSE);
3512
context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/close_other_tabs"), FILE_MENU_CLOSE_OTHER_TABS);
3513
context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/close_tabs_below"), FILE_MENU_CLOSE_TABS_BELOW);
3514
context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/close_all"), FILE_MENU_CLOSE_ALL);
3515
context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/close_docs"), FILE_MENU_CLOSE_DOCS);
3516
context_menu->add_separator();
3517
if (se) {
3518
Ref<Script> scr = se->get_edited_resource();
3519
if (scr.is_valid() && scr->is_tool()) {
3520
context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/reload_script_soft"), FILE_MENU_SOFT_RELOAD_TOOL);
3521
context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/run_file"), FILE_MENU_RUN);
3522
context_menu->add_separator();
3523
}
3524
context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/copy_path"), FILE_MENU_COPY_PATH);
3525
context_menu->set_item_disabled(-1, se->get_edited_resource()->get_path().is_empty());
3526
context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/copy_uid"), FILE_MENU_COPY_UID);
3527
context_menu->set_item_disabled(-1, ResourceLoader::get_resource_uid(se->get_edited_resource()->get_path()) == ResourceUID::INVALID_ID);
3528
context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/show_in_file_system"), FILE_MENU_SHOW_IN_FILE_SYSTEM);
3529
context_menu->add_separator();
3530
}
3531
3532
context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/window_move_up"), FILE_MENU_MOVE_UP);
3533
context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/window_move_down"), FILE_MENU_MOVE_DOWN);
3534
context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/window_sort"), FILE_MENU_SORT);
3535
context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/toggle_files_panel"), FILE_MENU_TOGGLE_FILES_PANEL);
3536
3537
context_menu->set_item_disabled(context_menu->get_item_index(FILE_MENU_CLOSE_ALL), tab_container->get_tab_count() <= 0);
3538
context_menu->set_item_disabled(context_menu->get_item_index(FILE_MENU_CLOSE_OTHER_TABS), tab_container->get_tab_count() <= 1);
3539
context_menu->set_item_disabled(context_menu->get_item_index(FILE_MENU_CLOSE_DOCS), !_has_docs_tab());
3540
context_menu->set_item_disabled(context_menu->get_item_index(FILE_MENU_CLOSE_TABS_BELOW), tab_container->get_current_tab() >= tab_container->get_tab_count() - 1);
3541
context_menu->set_item_disabled(context_menu->get_item_index(FILE_MENU_MOVE_UP), tab_container->get_current_tab() <= 0);
3542
context_menu->set_item_disabled(context_menu->get_item_index(FILE_MENU_MOVE_DOWN), tab_container->get_current_tab() >= tab_container->get_tab_count() - 1);
3543
context_menu->set_item_disabled(context_menu->get_item_index(FILE_MENU_SORT), tab_container->get_tab_count() <= 1);
3544
3545
// Context menu plugin.
3546
Vector<String> selected_paths;
3547
if (se) {
3548
Ref<Resource> scr = se->get_edited_resource();
3549
if (scr.is_valid()) {
3550
String path = scr->get_path();
3551
selected_paths.push_back(path);
3552
}
3553
}
3554
EditorContextMenuPluginManager::get_singleton()->add_options_from_plugins(context_menu, EditorContextMenuPlugin::CONTEXT_SLOT_SCRIPT_EDITOR, selected_paths);
3555
3556
context_menu->set_position(get_screen_position() + get_local_mouse_position());
3557
context_menu->reset_size();
3558
context_menu->popup();
3559
}
3560
3561
void ScriptEditor::set_window_layout(Ref<ConfigFile> p_layout) {
3562
if (!bool(EDITOR_GET("text_editor/behavior/files/restore_scripts_on_load"))) {
3563
return;
3564
}
3565
3566
if (!p_layout->has_section_key("ScriptEditor", "open_scripts") && !p_layout->has_section_key("ScriptEditor", "open_help")) {
3567
return;
3568
}
3569
3570
Array scripts = p_layout->get_value("ScriptEditor", "open_scripts");
3571
Array helps;
3572
if (p_layout->has_section_key("ScriptEditor", "open_help")) {
3573
helps = p_layout->get_value("ScriptEditor", "open_help");
3574
}
3575
3576
restoring_layout = true;
3577
3578
HashSet<String> loaded_scripts;
3579
List<String> extensions;
3580
ResourceLoader::get_recognized_extensions_for_type("Script", &extensions);
3581
ResourceLoader::get_recognized_extensions_for_type("JSON", &extensions);
3582
3583
for (int i = 0; i < scripts.size(); i++) {
3584
String path = scripts[i];
3585
3586
Dictionary script_info = scripts[i];
3587
if (!script_info.is_empty()) {
3588
path = script_info["path"];
3589
}
3590
3591
if (!FileAccess::exists(path)) {
3592
if (script_editor_cache->has_section(path)) {
3593
script_editor_cache->erase_section(path);
3594
}
3595
continue;
3596
}
3597
loaded_scripts.insert(path);
3598
3599
if (extensions.find(path.get_extension())) {
3600
Ref<Resource> scr = ResourceLoader::load(path);
3601
if (scr.is_null()) {
3602
continue;
3603
}
3604
if (!edit(scr, false)) {
3605
continue;
3606
}
3607
} else {
3608
Error error;
3609
Ref<TextFile> text_file = _load_text_file(path, &error);
3610
if (error != OK || text_file.is_null()) {
3611
continue;
3612
}
3613
if (!edit(text_file, false)) {
3614
continue;
3615
}
3616
}
3617
3618
if (!script_info.is_empty()) {
3619
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(tab_container->get_tab_count() - 1));
3620
if (se) {
3621
se->set_edit_state(script_info["state"]);
3622
}
3623
}
3624
}
3625
3626
for (int i = 0; i < helps.size(); i++) {
3627
String path = helps[i];
3628
if (path.is_empty()) { // invalid, skip
3629
continue;
3630
}
3631
_help_class_open(path);
3632
}
3633
3634
for (int i = 0; i < tab_container->get_tab_count(); i++) {
3635
tab_container->get_tab_control(i)->set_meta("__editor_pass", Variant());
3636
}
3637
3638
if (p_layout->has_section_key("ScriptEditor", "script_split_offset")) {
3639
script_split->set_split_offset(p_layout->get_value("ScriptEditor", "script_split_offset"));
3640
}
3641
3642
if (p_layout->has_section_key("ScriptEditor", "list_split_offset")) {
3643
list_split->set_split_offset(p_layout->get_value("ScriptEditor", "list_split_offset"));
3644
}
3645
3646
// Remove any deleted editors that have been removed between launches.
3647
// and if a Script, register breakpoints with the debugger.
3648
Vector<String> cached_editors = script_editor_cache->get_sections();
3649
for (const String &E : cached_editors) {
3650
if (loaded_scripts.has(E)) {
3651
continue;
3652
}
3653
3654
if (!FileAccess::exists(E)) {
3655
script_editor_cache->erase_section(E);
3656
continue;
3657
}
3658
3659
Array breakpoints = _get_cached_breakpoints_for_script(E);
3660
for (int breakpoint : breakpoints) {
3661
EditorDebuggerNode::get_singleton()->set_breakpoint(E, (int)breakpoint + 1, true);
3662
}
3663
}
3664
3665
_set_script_zoom_factor(p_layout->get_value("ScriptEditor", "zoom_factor", 1.0f));
3666
3667
restoring_layout = false;
3668
3669
_update_script_names();
3670
3671
if (p_layout->has_section_key("ScriptEditor", "selected_script")) {
3672
String selected_script = p_layout->get_value("ScriptEditor", "selected_script");
3673
// If the selected script is not in the list of open scripts, select nothing.
3674
for (int i = 0; i < tab_container->get_tab_count(); i++) {
3675
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));
3676
if (se && se->get_edited_resource()->get_path() == selected_script) {
3677
_go_to_tab(i);
3678
break;
3679
}
3680
}
3681
}
3682
}
3683
3684
void ScriptEditor::get_window_layout(Ref<ConfigFile> p_layout) {
3685
Array scripts;
3686
Array helps;
3687
String selected_script;
3688
for (int i = 0; i < tab_container->get_tab_count(); i++) {
3689
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));
3690
if (se) {
3691
String path = se->get_edited_resource()->get_path();
3692
if (!path.is_resource_file()) {
3693
continue;
3694
}
3695
3696
if (tab_container->get_current_tab_control() == tab_container->get_tab_control(i)) {
3697
selected_script = path;
3698
}
3699
3700
_save_editor_state(se);
3701
scripts.push_back(path);
3702
}
3703
3704
EditorHelp *eh = Object::cast_to<EditorHelp>(tab_container->get_tab_control(i));
3705
3706
if (eh) {
3707
helps.push_back(eh->get_class());
3708
}
3709
}
3710
3711
p_layout->set_value("ScriptEditor", "open_scripts", scripts);
3712
p_layout->set_value("ScriptEditor", "selected_script", selected_script);
3713
p_layout->set_value("ScriptEditor", "open_help", helps);
3714
p_layout->set_value("ScriptEditor", "script_split_offset", script_split->get_split_offset());
3715
p_layout->set_value("ScriptEditor", "list_split_offset", list_split->get_split_offset());
3716
p_layout->set_value("ScriptEditor", "zoom_factor", zoom_factor);
3717
3718
// Save the cache.
3719
script_editor_cache->save(EditorPaths::get_singleton()->get_project_settings_dir().path_join("script_editor_cache.cfg"));
3720
}
3721
3722
void ScriptEditor::_help_class_open(const String &p_class) {
3723
if (p_class.is_empty()) {
3724
return;
3725
}
3726
3727
for (int i = 0; i < tab_container->get_tab_count(); i++) {
3728
EditorHelp *eh = Object::cast_to<EditorHelp>(tab_container->get_tab_control(i));
3729
3730
if (eh && eh->get_class() == p_class) {
3731
_go_to_tab(i);
3732
_update_script_names();
3733
return;
3734
}
3735
}
3736
3737
EditorHelp *eh = memnew(EditorHelp);
3738
3739
eh->set_name(p_class);
3740
tab_container->add_child(eh);
3741
_go_to_tab(tab_container->get_tab_count() - 1);
3742
eh->go_to_class(p_class);
3743
eh->connect("go_to_help", callable_mp(this, &ScriptEditor::_help_class_goto));
3744
eh->connect("request_save_history", callable_mp(this, &ScriptEditor::_save_history));
3745
_add_recent_script(p_class);
3746
_sort_list_on_update = true;
3747
_update_script_names();
3748
_save_layout();
3749
}
3750
3751
void ScriptEditor::_help_class_goto(const String &p_desc) {
3752
String cname = p_desc.get_slicec(':', 1);
3753
3754
if (_help_tab_goto(cname, p_desc)) {
3755
return;
3756
}
3757
3758
EditorHelp *eh = memnew(EditorHelp);
3759
3760
eh->set_name(cname);
3761
tab_container->add_child(eh);
3762
_go_to_tab(tab_container->get_tab_count() - 1);
3763
eh->go_to_help(p_desc);
3764
eh->connect("go_to_help", callable_mp(this, &ScriptEditor::_help_class_goto));
3765
_add_recent_script(eh->get_class());
3766
_sort_list_on_update = true;
3767
_update_script_names();
3768
_save_layout();
3769
}
3770
3771
bool ScriptEditor::_help_tab_goto(const String &p_name, const String &p_desc) {
3772
for (int i = 0; i < tab_container->get_tab_count(); i++) {
3773
EditorHelp *eh = Object::cast_to<EditorHelp>(tab_container->get_tab_control(i));
3774
3775
if (eh && eh->get_class() == p_name) {
3776
_go_to_tab(i);
3777
eh->go_to_help(p_desc);
3778
_update_script_names();
3779
return true;
3780
}
3781
}
3782
return false;
3783
}
3784
3785
void ScriptEditor::update_doc(const String &p_name) {
3786
for (int i = 0; i < tab_container->get_tab_count(); i++) {
3787
EditorHelp *eh = Object::cast_to<EditorHelp>(tab_container->get_tab_control(i));
3788
if (eh && eh->get_class() == p_name) {
3789
eh->update_doc();
3790
return;
3791
}
3792
}
3793
}
3794
3795
void ScriptEditor::clear_docs_from_script(const Ref<Script> &p_script) {
3796
ERR_FAIL_COND(p_script.is_null());
3797
3798
for (const DocData::ClassDoc &cd : p_script->get_documentation()) {
3799
if (EditorHelp::get_doc_data()->has_doc(cd.name)) {
3800
EditorHelp::get_doc_data()->remove_doc(cd.name);
3801
}
3802
}
3803
}
3804
3805
void ScriptEditor::update_docs_from_script(const Ref<Script> &p_script) {
3806
ERR_FAIL_COND(p_script.is_null());
3807
3808
for (const DocData::ClassDoc &cd : p_script->get_documentation()) {
3809
EditorHelp::get_doc_data()->add_doc(cd);
3810
update_doc(cd.name);
3811
}
3812
}
3813
3814
void ScriptEditor::_update_selected_editor_menu() {
3815
for (int i = 0; i < tab_container->get_tab_count(); i++) {
3816
bool current = tab_container->get_current_tab() == i;
3817
3818
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));
3819
if (se && se->get_edit_menu()) {
3820
if (current) {
3821
se->get_edit_menu()->show();
3822
} else {
3823
se->get_edit_menu()->hide();
3824
}
3825
}
3826
}
3827
3828
EditorHelp *eh = Object::cast_to<EditorHelp>(tab_container->get_current_tab_control());
3829
script_search_menu->get_popup()->clear();
3830
if (eh) {
3831
script_search_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/find", TTRC("Find..."), KeyModifierMask::CMD_OR_CTRL | Key::F), HELP_SEARCH_FIND);
3832
script_search_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/find_next", TTRC("Find Next"), Key::F3), HELP_SEARCH_FIND_NEXT);
3833
script_search_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/find_previous", TTRC("Find Previous"), KeyModifierMask::SHIFT | Key::F3), HELP_SEARCH_FIND_PREVIOUS);
3834
script_search_menu->get_popup()->add_separator();
3835
script_search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("editor/find_in_files"), SEARCH_IN_FILES);
3836
script_search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_editor/replace_in_files"), REPLACE_IN_FILES);
3837
script_search_menu->show();
3838
} else {
3839
if (tab_container->get_tab_count() == 0) {
3840
script_search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("editor/find_in_files"), SEARCH_IN_FILES);
3841
script_search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_editor/replace_in_files"), REPLACE_IN_FILES);
3842
script_search_menu->show();
3843
} else {
3844
script_search_menu->hide();
3845
}
3846
}
3847
}
3848
3849
void ScriptEditor::_update_history_pos(int p_new_pos) {
3850
Node *n = tab_container->get_current_tab_control();
3851
3852
if (Object::cast_to<ScriptEditorBase>(n)) {
3853
history.write[history_pos].state = Object::cast_to<ScriptEditorBase>(n)->get_navigation_state();
3854
}
3855
if (Object::cast_to<EditorHelp>(n)) {
3856
history.write[history_pos].state = Object::cast_to<EditorHelp>(n)->get_scroll();
3857
}
3858
3859
history_pos = p_new_pos;
3860
tab_container->set_current_tab(tab_container->get_tab_idx_from_control(history[history_pos].control));
3861
3862
n = history[history_pos].control;
3863
3864
ScriptEditorBase *seb = Object::cast_to<ScriptEditorBase>(n);
3865
if (seb) {
3866
lock_history = true;
3867
seb->set_edit_state(history[history_pos].state);
3868
seb->ensure_focus();
3869
3870
Ref<Script> scr = seb->get_edited_resource();
3871
if (scr.is_valid()) {
3872
notify_script_changed(scr);
3873
}
3874
}
3875
3876
EditorHelp *eh = Object::cast_to<EditorHelp>(n);
3877
if (eh) {
3878
eh->set_scroll(history[history_pos].state);
3879
eh->set_focused();
3880
}
3881
3882
n->set_meta("__editor_pass", ++edit_pass);
3883
_update_script_names();
3884
_update_history_arrows();
3885
_update_selected_editor_menu();
3886
}
3887
3888
void ScriptEditor::_history_forward() {
3889
if (history_pos < history.size() - 1) {
3890
_update_history_pos(history_pos + 1);
3891
}
3892
}
3893
3894
void ScriptEditor::_history_back() {
3895
if (history_pos > 0) {
3896
_update_history_pos(history_pos - 1);
3897
}
3898
}
3899
3900
Vector<Ref<Script>> ScriptEditor::get_open_scripts() const {
3901
Vector<Ref<Script>> out_scripts = Vector<Ref<Script>>();
3902
3903
for (int i = 0; i < tab_container->get_tab_count(); i++) {
3904
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));
3905
if (!se) {
3906
continue;
3907
}
3908
3909
Ref<Script> scr = se->get_edited_resource();
3910
if (scr.is_valid()) {
3911
out_scripts.push_back(scr);
3912
}
3913
}
3914
3915
return out_scripts;
3916
}
3917
3918
TypedArray<ScriptEditorBase> ScriptEditor::_get_open_script_editors() const {
3919
TypedArray<ScriptEditorBase> script_editors;
3920
for (int i = 0; i < tab_container->get_tab_count(); i++) {
3921
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));
3922
if (!se) {
3923
continue;
3924
}
3925
script_editors.push_back(se);
3926
}
3927
return script_editors;
3928
}
3929
3930
void ScriptEditor::set_scene_root_script(Ref<Script> p_script) {
3931
// Don't open dominant script if using an external editor.
3932
bool use_external_editor =
3933
external_editor_active ||
3934
(p_script.is_valid() && p_script->get_language()->overrides_external_editor());
3935
use_external_editor = use_external_editor && !(p_script.is_valid() && p_script->is_built_in()); // Ignore external editor for built-in scripts.
3936
const bool open_dominant = EDITOR_GET("text_editor/behavior/files/open_dominant_script_on_scene_change");
3937
3938
if (open_dominant && !use_external_editor && p_script.is_valid()) {
3939
edit(p_script);
3940
}
3941
}
3942
3943
bool ScriptEditor::script_goto_method(Ref<Script> p_script, const String &p_method) {
3944
int line = p_script->get_member_line(p_method);
3945
3946
if (line == -1) {
3947
return false;
3948
}
3949
3950
return edit(p_script, line, 0);
3951
}
3952
3953
void ScriptEditor::set_live_auto_reload_running_scripts(bool p_enabled) {
3954
auto_reload_running_scripts = p_enabled;
3955
}
3956
3957
void ScriptEditor::_help_search(const String &p_text) {
3958
help_search_dialog->popup_dialog(p_text);
3959
}
3960
3961
void ScriptEditor::_open_script_request(const String &p_path) {
3962
Ref<Script> scr = ResourceLoader::load(p_path);
3963
if (scr.is_valid()) {
3964
script_editor->edit(scr, false);
3965
return;
3966
}
3967
3968
Ref<JSON> json = ResourceLoader::load(p_path);
3969
if (json.is_valid()) {
3970
script_editor->edit(json, false);
3971
return;
3972
}
3973
3974
Error err;
3975
Ref<TextFile> text_file = script_editor->_load_text_file(p_path, &err);
3976
if (text_file.is_valid()) {
3977
script_editor->edit(text_file, false);
3978
return;
3979
}
3980
}
3981
3982
void ScriptEditor::register_syntax_highlighter(const Ref<EditorSyntaxHighlighter> &p_syntax_highlighter) {
3983
ERR_FAIL_COND(p_syntax_highlighter.is_null());
3984
3985
if (!syntax_highlighters.has(p_syntax_highlighter)) {
3986
syntax_highlighters.push_back(p_syntax_highlighter);
3987
}
3988
}
3989
3990
void ScriptEditor::unregister_syntax_highlighter(const Ref<EditorSyntaxHighlighter> &p_syntax_highlighter) {
3991
ERR_FAIL_COND(p_syntax_highlighter.is_null());
3992
3993
syntax_highlighters.erase(p_syntax_highlighter);
3994
}
3995
3996
int ScriptEditor::script_editor_func_count = 0;
3997
CreateScriptEditorFunc ScriptEditor::script_editor_funcs[ScriptEditor::SCRIPT_EDITOR_FUNC_MAX];
3998
3999
void ScriptEditor::register_create_script_editor_function(CreateScriptEditorFunc p_func) {
4000
ERR_FAIL_COND(script_editor_func_count == SCRIPT_EDITOR_FUNC_MAX);
4001
script_editor_funcs[script_editor_func_count++] = p_func;
4002
}
4003
4004
void ScriptEditor::_script_changed() {
4005
NodeDock::get_singleton()->update_lists();
4006
}
4007
4008
void ScriptEditor::_on_replace_in_files_requested(const String &text) {
4009
find_in_files_dialog->set_find_in_files_mode(FindInFilesDialog::REPLACE_MODE);
4010
find_in_files_dialog->set_search_text(text);
4011
find_in_files_dialog->set_replace_text("");
4012
find_in_files_dialog->popup_centered();
4013
}
4014
4015
void ScriptEditor::_on_find_in_files_result_selected(const String &fpath, int line_number, int begin, int end) {
4016
if (ResourceLoader::exists(fpath)) {
4017
Ref<Resource> res = ResourceLoader::load(fpath);
4018
4019
if (fpath.get_extension() == "gdshader") {
4020
ShaderEditorPlugin *shader_editor = Object::cast_to<ShaderEditorPlugin>(EditorNode::get_editor_data().get_editor_by_name("Shader"));
4021
shader_editor->edit(res.ptr());
4022
shader_editor->make_visible(true);
4023
TextShaderEditor *text_shader_editor = Object::cast_to<TextShaderEditor>(shader_editor->get_shader_editor(res));
4024
if (text_shader_editor) {
4025
text_shader_editor->goto_line_selection(line_number - 1, begin, end);
4026
}
4027
return;
4028
} else if (fpath.get_extension() == "tscn") {
4029
Ref<FileAccess> f = FileAccess::open(fpath, FileAccess::READ);
4030
bool is_script_found = false;
4031
4032
// Starting from top of the tscn file.
4033
int scr_start_line = 1;
4034
4035
String scr_header = "[sub_resource type=\"GDScript\" id=\"";
4036
String scr_id = "";
4037
String line = "";
4038
4039
int l = 0;
4040
4041
while (!f->eof_reached()) {
4042
line = f->get_line();
4043
l++;
4044
4045
if (!line.begins_with(scr_header)) {
4046
continue;
4047
}
4048
4049
// Found the end of the script.
4050
scr_id = line.get_slice(scr_header, 1);
4051
scr_id = scr_id.get_slicec('"', 0);
4052
4053
scr_start_line = l + 1;
4054
int scr_line_count = 0;
4055
4056
do {
4057
line = f->get_line();
4058
l++;
4059
String strline = line.strip_edges();
4060
4061
if (strline.ends_with("\"") && !strline.ends_with("\\\"")) {
4062
// Found the end of script.
4063
break;
4064
}
4065
scr_line_count++;
4066
4067
} while (!f->eof_reached());
4068
4069
if (line_number > scr_start_line + scr_line_count) {
4070
// Find in another built-in GDScript.
4071
continue;
4072
}
4073
4074
// Real line number of the built-in script.
4075
line_number = line_number - scr_start_line;
4076
4077
is_script_found = true;
4078
break;
4079
}
4080
4081
EditorNode::get_singleton()->load_scene(fpath);
4082
4083
if (is_script_found && !scr_id.is_empty()) {
4084
Ref<Script> scr = ResourceLoader::load(fpath + "::" + scr_id, "Script");
4085
if (scr.is_valid()) {
4086
edit(scr);
4087
ScriptTextEditor *ste = Object::cast_to<ScriptTextEditor>(_get_current_editor());
4088
4089
if (ste) {
4090
EditorInterface::get_singleton()->set_main_screen_editor("Script");
4091
ste->goto_line_selection(line_number, begin, end);
4092
}
4093
}
4094
}
4095
4096
return;
4097
} else {
4098
Ref<Script> scr = res;
4099
Ref<JSON> json = res;
4100
if (scr.is_valid() || json.is_valid()) {
4101
edit(scr);
4102
4103
ScriptTextEditor *ste = Object::cast_to<ScriptTextEditor>(_get_current_editor());
4104
if (ste) {
4105
EditorInterface::get_singleton()->set_main_screen_editor("Script");
4106
ste->goto_line_selection(line_number - 1, begin, end);
4107
}
4108
return;
4109
}
4110
}
4111
}
4112
4113
// If the file is not a valid resource/script, load it as a text file.
4114
Error err;
4115
Ref<TextFile> text_file = _load_text_file(fpath, &err);
4116
if (text_file.is_valid()) {
4117
edit(text_file);
4118
4119
TextEditor *te = Object::cast_to<TextEditor>(_get_current_editor());
4120
if (te) {
4121
te->goto_line_selection(line_number - 1, begin, end);
4122
}
4123
}
4124
}
4125
4126
void ScriptEditor::_start_find_in_files(bool with_replace) {
4127
FindInFiles *f = find_in_files->get_finder();
4128
4129
f->set_search_text(find_in_files_dialog->get_search_text());
4130
f->set_match_case(find_in_files_dialog->is_match_case());
4131
f->set_whole_words(find_in_files_dialog->is_whole_words());
4132
f->set_folder(find_in_files_dialog->get_folder());
4133
f->set_filter(find_in_files_dialog->get_filter());
4134
f->set_includes(find_in_files_dialog->get_includes());
4135
f->set_excludes(find_in_files_dialog->get_excludes());
4136
4137
find_in_files->set_with_replace(with_replace);
4138
find_in_files->set_replace_text(find_in_files_dialog->get_replace_text());
4139
find_in_files->start_search();
4140
4141
if (find_in_files_button->get_index() != find_in_files_button->get_parent()->get_child_count()) {
4142
find_in_files_button->get_parent()->move_child(find_in_files_button, -1);
4143
}
4144
if (!find_in_files_button->is_visible()) {
4145
find_in_files_button->show();
4146
}
4147
4148
EditorNode::get_bottom_panel()->make_item_visible(find_in_files);
4149
}
4150
4151
void ScriptEditor::_on_find_in_files_modified_files(const PackedStringArray &paths) {
4152
_test_script_times_on_disk();
4153
_update_modified_scripts_for_external_editor();
4154
}
4155
4156
void ScriptEditor::_set_script_zoom_factor(float p_zoom_factor) {
4157
if (zoom_factor == p_zoom_factor) {
4158
return;
4159
}
4160
4161
zoom_factor = p_zoom_factor;
4162
}
4163
4164
void ScriptEditor::_update_code_editor_zoom_factor(CodeTextEditor *p_code_text_editor) {
4165
if (p_code_text_editor && p_code_text_editor->is_visible_in_tree() && zoom_factor != p_code_text_editor->get_zoom_factor()) {
4166
p_code_text_editor->set_zoom_factor(zoom_factor);
4167
}
4168
}
4169
4170
void ScriptEditor::_on_find_in_files_close_button_clicked() {
4171
EditorNode::get_bottom_panel()->hide_bottom_panel();
4172
find_in_files_button->hide();
4173
}
4174
4175
void ScriptEditor::_window_changed(bool p_visible) {
4176
make_floating->set_visible(!p_visible);
4177
is_floating = p_visible;
4178
}
4179
4180
void ScriptEditor::_filter_scripts_text_changed(const String &p_newtext) {
4181
_update_script_names();
4182
}
4183
4184
void ScriptEditor::_filter_methods_text_changed(const String &p_newtext) {
4185
_update_members_overview();
4186
}
4187
4188
void ScriptEditor::_bind_methods() {
4189
ClassDB::bind_method("_help_tab_goto", &ScriptEditor::_help_tab_goto);
4190
ClassDB::bind_method("get_current_editor", &ScriptEditor::_get_current_editor);
4191
ClassDB::bind_method("get_open_script_editors", &ScriptEditor::_get_open_script_editors);
4192
ClassDB::bind_method("get_breakpoints", &ScriptEditor::_get_breakpoints);
4193
4194
ClassDB::bind_method(D_METHOD("register_syntax_highlighter", "syntax_highlighter"), &ScriptEditor::register_syntax_highlighter);
4195
ClassDB::bind_method(D_METHOD("unregister_syntax_highlighter", "syntax_highlighter"), &ScriptEditor::unregister_syntax_highlighter);
4196
4197
ClassDB::bind_method(D_METHOD("goto_line", "line_number"), &ScriptEditor::_goto_script_line2);
4198
ClassDB::bind_method(D_METHOD("get_current_script"), &ScriptEditor::_get_current_script);
4199
ClassDB::bind_method(D_METHOD("get_open_scripts"), &ScriptEditor::_get_open_scripts);
4200
ClassDB::bind_method(D_METHOD("open_script_create_dialog", "base_name", "base_path"), &ScriptEditor::open_script_create_dialog);
4201
4202
ClassDB::bind_method(D_METHOD("goto_help", "topic"), &ScriptEditor::goto_help);
4203
ClassDB::bind_method(D_METHOD("update_docs_from_script", "script"), &ScriptEditor::update_docs_from_script);
4204
ClassDB::bind_method(D_METHOD("clear_docs_from_script", "script"), &ScriptEditor::clear_docs_from_script);
4205
4206
ADD_SIGNAL(MethodInfo("editor_script_changed", PropertyInfo(Variant::OBJECT, "script", PROPERTY_HINT_RESOURCE_TYPE, "Script")));
4207
ADD_SIGNAL(MethodInfo("script_close", PropertyInfo(Variant::OBJECT, "script", PROPERTY_HINT_RESOURCE_TYPE, "Script")));
4208
}
4209
4210
ScriptEditor::ScriptEditor(WindowWrapper *p_wrapper) {
4211
window_wrapper = p_wrapper;
4212
4213
script_editor_cache.instantiate();
4214
script_editor_cache->load(EditorPaths::get_singleton()->get_project_settings_dir().path_join("script_editor_cache.cfg"));
4215
4216
restoring_layout = false;
4217
waiting_update_names = false;
4218
pending_auto_reload = false;
4219
auto_reload_running_scripts = true;
4220
external_editor_active = false;
4221
members_overview_enabled = EDITOR_GET("text_editor/script_list/show_members_overview");
4222
help_overview_enabled = EDITOR_GET("text_editor/help/show_help_index");
4223
4224
VBoxContainer *main_container = memnew(VBoxContainer);
4225
add_child(main_container);
4226
4227
menu_hb = memnew(HBoxContainer);
4228
main_container->add_child(menu_hb);
4229
4230
script_split = memnew(HSplitContainer);
4231
main_container->add_child(script_split);
4232
script_split->set_v_size_flags(SIZE_EXPAND_FILL);
4233
4234
list_split = memnew(VSplitContainer);
4235
script_split->add_child(list_split);
4236
list_split->set_v_size_flags(SIZE_EXPAND_FILL);
4237
4238
scripts_vbox = memnew(VBoxContainer);
4239
scripts_vbox->set_v_size_flags(SIZE_EXPAND_FILL);
4240
list_split->add_child(scripts_vbox);
4241
4242
filter_scripts = memnew(LineEdit);
4243
filter_scripts->set_placeholder(TTRC("Filter Scripts"));
4244
filter_scripts->set_accessibility_name(TTRC("Filter Scripts"));
4245
filter_scripts->set_clear_button_enabled(true);
4246
filter_scripts->connect(SceneStringName(text_changed), callable_mp(this, &ScriptEditor::_filter_scripts_text_changed));
4247
scripts_vbox->add_child(filter_scripts);
4248
4249
script_list = memnew(ItemList);
4250
script_list->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
4251
scripts_vbox->add_child(script_list);
4252
script_list->set_custom_minimum_size(Size2(100, 60) * EDSCALE); //need to give a bit of limit to avoid it from disappearing
4253
script_list->set_v_size_flags(SIZE_EXPAND_FILL);
4254
script_list->set_theme_type_variation("ItemListSecondary");
4255
script_split->set_split_offset(200 * EDSCALE);
4256
_sort_list_on_update = true;
4257
script_list->connect("item_clicked", callable_mp(this, &ScriptEditor::_script_list_clicked), CONNECT_DEFERRED);
4258
script_list->set_allow_rmb_select(true);
4259
SET_DRAG_FORWARDING_GCD(script_list, ScriptEditor);
4260
4261
context_menu = memnew(PopupMenu);
4262
add_child(context_menu);
4263
context_menu->connect(SceneStringName(id_pressed), callable_mp(this, &ScriptEditor::_menu_option));
4264
4265
overview_vbox = memnew(VBoxContainer);
4266
overview_vbox->set_custom_minimum_size(Size2(0, 90));
4267
overview_vbox->set_v_size_flags(SIZE_EXPAND_FILL);
4268
4269
list_split->add_child(overview_vbox);
4270
list_split->set_visible(EditorSettings::get_singleton()->get_project_metadata("files_panel", "show_files_panel", true));
4271
buttons_hbox = memnew(HBoxContainer);
4272
overview_vbox->add_child(buttons_hbox);
4273
4274
filename = memnew(Label);
4275
filename->set_focus_mode(FOCUS_ACCESSIBILITY);
4276
filename->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
4277
filename->set_clip_text(true);
4278
filename->set_h_size_flags(SIZE_EXPAND_FILL);
4279
filename->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
4280
filename->add_theme_style_override(CoreStringName(normal), EditorNode::get_singleton()->get_editor_theme()->get_stylebox(CoreStringName(normal), SNAME("LineEdit")));
4281
buttons_hbox->add_child(filename);
4282
4283
members_overview_alphabeta_sort_button = memnew(Button);
4284
members_overview_alphabeta_sort_button->set_theme_type_variation(SceneStringName(FlatButton));
4285
members_overview_alphabeta_sort_button->set_tooltip_text(TTRC("Toggle alphabetical sorting of the method list."));
4286
members_overview_alphabeta_sort_button->set_toggle_mode(true);
4287
members_overview_alphabeta_sort_button->set_pressed(EDITOR_GET("text_editor/script_list/sort_members_outline_alphabetically"));
4288
members_overview_alphabeta_sort_button->connect(SceneStringName(toggled), callable_mp(this, &ScriptEditor::_toggle_members_overview_alpha_sort));
4289
4290
buttons_hbox->add_child(members_overview_alphabeta_sort_button);
4291
4292
filter_methods = memnew(LineEdit);
4293
filter_methods->set_placeholder(TTRC("Filter Methods"));
4294
filter_methods->set_accessibility_name(TTRC("Filter Methods"));
4295
filter_methods->set_clear_button_enabled(true);
4296
filter_methods->connect(SceneStringName(text_changed), callable_mp(this, &ScriptEditor::_filter_methods_text_changed));
4297
overview_vbox->add_child(filter_methods);
4298
4299
members_overview = memnew(ItemList);
4300
members_overview->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
4301
members_overview->set_theme_type_variation("ItemListSecondary");
4302
overview_vbox->add_child(members_overview);
4303
4304
members_overview->set_allow_reselect(true);
4305
members_overview->set_custom_minimum_size(Size2(0, 60) * EDSCALE); //need to give a bit of limit to avoid it from disappearing
4306
members_overview->set_v_size_flags(SIZE_EXPAND_FILL);
4307
members_overview->set_allow_rmb_select(true);
4308
4309
help_overview = memnew(ItemList);
4310
help_overview->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
4311
help_overview->set_theme_type_variation("ItemListSecondary");
4312
overview_vbox->add_child(help_overview);
4313
help_overview->set_allow_reselect(true);
4314
help_overview->set_custom_minimum_size(Size2(0, 60) * EDSCALE); //need to give a bit of limit to avoid it from disappearing
4315
help_overview->set_v_size_flags(SIZE_EXPAND_FILL);
4316
4317
VBoxContainer *code_editor_container = memnew(VBoxContainer);
4318
script_split->add_child(code_editor_container);
4319
4320
tab_container = memnew(TabContainer);
4321
tab_container->set_tabs_visible(false);
4322
tab_container->set_custom_minimum_size(Size2(200, 0) * EDSCALE);
4323
code_editor_container->add_child(tab_container);
4324
tab_container->set_h_size_flags(SIZE_EXPAND_FILL);
4325
tab_container->set_v_size_flags(SIZE_EXPAND_FILL);
4326
4327
find_replace_bar = memnew(FindReplaceBar);
4328
code_editor_container->add_child(find_replace_bar);
4329
find_replace_bar->hide();
4330
4331
ED_SHORTCUT("script_editor/window_sort", TTRC("Sort"));
4332
ED_SHORTCUT("script_editor/window_move_up", TTRC("Move Up"), KeyModifierMask::SHIFT | KeyModifierMask::ALT | Key::UP);
4333
ED_SHORTCUT("script_editor/window_move_down", TTRC("Move Down"), KeyModifierMask::SHIFT | KeyModifierMask::ALT | Key::DOWN);
4334
// FIXME: These should be `Key::GREATER` and `Key::LESS` but those don't work.
4335
ED_SHORTCUT("script_editor/next_script", TTRC("Next Script"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::PERIOD);
4336
ED_SHORTCUT("script_editor/prev_script", TTRC("Previous Script"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::COMMA);
4337
set_process_input(true);
4338
set_process_shortcut_input(true);
4339
4340
file_menu = memnew(MenuButton);
4341
file_menu->set_flat(false);
4342
file_menu->set_theme_type_variation("FlatMenuButton");
4343
file_menu->set_text(TTRC("File"));
4344
file_menu->set_switch_on_hover(true);
4345
file_menu->set_shortcut_context(this);
4346
menu_hb->add_child(file_menu);
4347
4348
file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/new", TTRC("New Script..."), KeyModifierMask::CMD_OR_CTRL | Key::N), FILE_MENU_NEW);
4349
file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/new_textfile", TTRC("New Text File..."), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::N), FILE_MENU_NEW_TEXTFILE);
4350
file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/open", TTRC("Open...")), FILE_MENU_OPEN);
4351
file_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_editor/reopen_closed_script"), FILE_MENU_REOPEN_CLOSED);
4352
4353
recent_scripts = memnew(PopupMenu);
4354
recent_scripts->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
4355
file_menu->get_popup()->add_submenu_node_item(TTRC("Open Recent"), recent_scripts, FILE_MENU_OPEN_RECENT);
4356
recent_scripts->connect(SceneStringName(id_pressed), callable_mp(this, &ScriptEditor::_open_recent_script));
4357
4358
_update_recent_scripts();
4359
4360
file_menu->get_popup()->add_separator();
4361
file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/save", TTRC("Save"), KeyModifierMask::ALT | KeyModifierMask::CMD_OR_CTRL | Key::S), FILE_MENU_SAVE);
4362
file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/save_as", TTRC("Save As...")), FILE_MENU_SAVE_AS);
4363
file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/save_all", TTRC("Save All"), KeyModifierMask::SHIFT | KeyModifierMask::ALT | Key::S), FILE_MENU_SAVE_ALL);
4364
ED_SHORTCUT_OVERRIDE("script_editor/save_all", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::S);
4365
file_menu->get_popup()->add_separator();
4366
file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/reload_script_soft", TTRC("Soft Reload Tool Script"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::ALT | Key::R), FILE_MENU_SOFT_RELOAD_TOOL);
4367
file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/copy_path", TTRC("Copy Script Path")), FILE_MENU_COPY_PATH);
4368
file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/copy_uid", TTRC("Copy Script UID")), FILE_MENU_COPY_UID);
4369
file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/show_in_file_system", TTRC("Show in FileSystem")), FILE_MENU_SHOW_IN_FILE_SYSTEM);
4370
file_menu->get_popup()->add_separator();
4371
4372
file_menu->get_popup()->add_shortcut(
4373
ED_SHORTCUT_ARRAY("script_editor/history_previous", TTRC("History Previous"),
4374
{ int32_t(KeyModifierMask::ALT | Key::LEFT), int32_t(Key::BACK) }),
4375
FILE_MENU_HISTORY_PREV);
4376
file_menu->get_popup()->add_shortcut(
4377
ED_SHORTCUT_ARRAY("script_editor/history_next", TTRC("History Next"),
4378
{ int32_t(KeyModifierMask::ALT | Key::RIGHT), int32_t(Key::FORWARD) }),
4379
FILE_MENU_HISTORY_NEXT);
4380
ED_SHORTCUT_OVERRIDE("script_editor/history_previous", "macos", KeyModifierMask::ALT | KeyModifierMask::META | Key::LEFT);
4381
ED_SHORTCUT_OVERRIDE("script_editor/history_next", "macos", KeyModifierMask::ALT | KeyModifierMask::META | Key::RIGHT);
4382
4383
file_menu->get_popup()->add_separator();
4384
4385
theme_submenu = memnew(PopupMenu);
4386
theme_submenu->add_shortcut(ED_SHORTCUT("script_editor/import_theme", TTRC("Import Theme...")), THEME_IMPORT);
4387
theme_submenu->add_shortcut(ED_SHORTCUT("script_editor/reload_theme", TTRC("Reload Theme")), THEME_RELOAD);
4388
file_menu->get_popup()->add_submenu_node_item(TTRC("Theme"), theme_submenu, FILE_MENU_THEME_SUBMENU);
4389
theme_submenu->connect(SceneStringName(id_pressed), callable_mp(this, &ScriptEditor::_theme_option));
4390
4391
theme_submenu->add_separator();
4392
theme_submenu->add_shortcut(ED_SHORTCUT("script_editor/save_theme_as", TTRC("Save Theme As...")), THEME_SAVE_AS);
4393
4394
file_menu->get_popup()->add_separator();
4395
file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/close_file", TTRC("Close"), KeyModifierMask::CMD_OR_CTRL | Key::W), FILE_MENU_CLOSE);
4396
file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/close_all", TTRC("Close All")), FILE_MENU_CLOSE_ALL);
4397
file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/close_other_tabs", TTRC("Close Other Tabs")), FILE_MENU_CLOSE_OTHER_TABS);
4398
file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/close_tabs_below", TTRC("Close Tabs Below")), FILE_MENU_CLOSE_TABS_BELOW);
4399
file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/close_docs", TTRC("Close Docs")), FILE_MENU_CLOSE_DOCS);
4400
4401
file_menu->get_popup()->add_separator();
4402
file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/run_file", TTRC("Run"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::X), FILE_MENU_RUN);
4403
4404
file_menu->get_popup()->add_separator();
4405
file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/toggle_files_panel", TTRC("Toggle Files Panel"), KeyModifierMask::CMD_OR_CTRL | Key::BACKSLASH), FILE_MENU_TOGGLE_FILES_PANEL);
4406
file_menu->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &ScriptEditor::_menu_option));
4407
file_menu->get_popup()->connect("about_to_popup", callable_mp(this, &ScriptEditor::_prepare_file_menu));
4408
file_menu->get_popup()->connect("popup_hide", callable_mp(this, &ScriptEditor::_file_menu_closed));
4409
4410
script_search_menu = memnew(MenuButton);
4411
script_search_menu->set_flat(false);
4412
script_search_menu->set_theme_type_variation("FlatMenuButton");
4413
script_search_menu->set_text(TTRC("Search"));
4414
script_search_menu->set_switch_on_hover(true);
4415
script_search_menu->set_shortcut_context(this);
4416
script_search_menu->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &ScriptEditor::_menu_option));
4417
menu_hb->add_child(script_search_menu);
4418
4419
MenuButton *debug_menu_btn = memnew(MenuButton);
4420
debug_menu_btn->set_flat(false);
4421
debug_menu_btn->set_theme_type_variation("FlatMenuButton");
4422
menu_hb->add_child(debug_menu_btn);
4423
debug_menu_btn->hide(); // Handled by EditorDebuggerNode below.
4424
4425
EditorDebuggerNode *debugger = EditorDebuggerNode::get_singleton();
4426
debugger->set_script_debug_button(debug_menu_btn);
4427
debugger->connect("goto_script_line", callable_mp(this, &ScriptEditor::_goto_script_line));
4428
debugger->connect("set_execution", callable_mp(this, &ScriptEditor::_set_execution));
4429
debugger->connect("clear_execution", callable_mp(this, &ScriptEditor::_clear_execution));
4430
debugger->connect("breaked", callable_mp(this, &ScriptEditor::_breaked));
4431
debugger->connect("breakpoint_set_in_tree", callable_mp(this, &ScriptEditor::_set_breakpoint));
4432
debugger->connect("breakpoints_cleared_in_tree", callable_mp(this, &ScriptEditor::_clear_breakpoints));
4433
4434
menu_hb->add_spacer();
4435
4436
script_icon = memnew(TextureRect);
4437
menu_hb->add_child(script_icon);
4438
script_name_label = memnew(Label);
4439
script_name_label->set_focus_mode(FOCUS_ACCESSIBILITY);
4440
menu_hb->add_child(script_name_label);
4441
4442
script_icon->hide();
4443
script_name_label->hide();
4444
4445
menu_hb->add_spacer();
4446
4447
site_search = memnew(Button);
4448
site_search->set_theme_type_variation(SceneStringName(FlatButton));
4449
site_search->set_accessibility_name(TTRC("Site Search"));
4450
site_search->connect(SceneStringName(pressed), callable_mp(this, &ScriptEditor::_menu_option).bind(SEARCH_WEBSITE));
4451
menu_hb->add_child(site_search);
4452
4453
help_search = memnew(Button);
4454
help_search->set_theme_type_variation(SceneStringName(FlatButton));
4455
help_search->set_text(TTRC("Search Help"));
4456
help_search->connect(SceneStringName(pressed), callable_mp(this, &ScriptEditor::_menu_option).bind(SEARCH_HELP));
4457
menu_hb->add_child(help_search);
4458
help_search->set_tooltip_text(TTRC("Search the reference documentation."));
4459
4460
menu_hb->add_child(memnew(VSeparator));
4461
4462
script_back = memnew(Button);
4463
script_back->set_theme_type_variation(SceneStringName(FlatButton));
4464
script_back->connect(SceneStringName(pressed), callable_mp(this, &ScriptEditor::_history_back));
4465
menu_hb->add_child(script_back);
4466
script_back->set_disabled(true);
4467
script_back->set_tooltip_text(TTRC("Go to previous edited document."));
4468
4469
script_forward = memnew(Button);
4470
script_forward->set_theme_type_variation(SceneStringName(FlatButton));
4471
script_forward->connect(SceneStringName(pressed), callable_mp(this, &ScriptEditor::_history_forward));
4472
menu_hb->add_child(script_forward);
4473
script_forward->set_disabled(true);
4474
script_forward->set_tooltip_text(TTRC("Go to next edited document."));
4475
4476
menu_hb->add_child(memnew(VSeparator));
4477
4478
make_floating = memnew(ScreenSelect);
4479
make_floating->set_tooltip_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
4480
make_floating->connect("request_open_in_screen", callable_mp(window_wrapper, &WindowWrapper::enable_window_on_screen).bind(true));
4481
4482
menu_hb->add_child(make_floating);
4483
p_wrapper->connect("window_visibility_changed", callable_mp(this, &ScriptEditor::_window_changed));
4484
4485
tab_container->connect("tab_changed", callable_mp(this, &ScriptEditor::_tab_changed));
4486
4487
erase_tab_confirm = memnew(ConfirmationDialog);
4488
erase_tab_confirm->set_ok_button_text(TTRC("Save"));
4489
erase_tab_confirm->add_button(TTRC("Discard"), DisplayServer::get_singleton()->get_swap_cancel_ok(), "discard");
4490
erase_tab_confirm->connect(SceneStringName(confirmed), callable_mp(this, &ScriptEditor::_close_current_tab).bind(true, true));
4491
erase_tab_confirm->connect("custom_action", callable_mp(this, &ScriptEditor::_close_discard_current_tab));
4492
add_child(erase_tab_confirm);
4493
4494
script_create_dialog = memnew(ScriptCreateDialog);
4495
script_create_dialog->set_title(TTRC("Create Script"));
4496
add_child(script_create_dialog);
4497
script_create_dialog->connect("script_created", callable_mp(this, &ScriptEditor::_script_created));
4498
4499
file_dialog_option = -1;
4500
file_dialog = memnew(EditorFileDialog);
4501
add_child(file_dialog);
4502
file_dialog->connect("file_selected", callable_mp(this, &ScriptEditor::_file_dialog_action));
4503
4504
error_dialog = memnew(AcceptDialog);
4505
add_child(error_dialog);
4506
4507
disk_changed = memnew(ConfirmationDialog);
4508
{
4509
disk_changed->set_title(TTRC("Files have been modified outside Godot"));
4510
4511
VBoxContainer *vbc = memnew(VBoxContainer);
4512
disk_changed->add_child(vbc);
4513
4514
Label *files_are_newer_label = memnew(Label);
4515
files_are_newer_label->set_text(TTRC("The following files are newer on disk:"));
4516
vbc->add_child(files_are_newer_label);
4517
4518
disk_changed_list = memnew(Tree);
4519
disk_changed_list->set_hide_root(true);
4520
disk_changed_list->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
4521
disk_changed_list->set_accessibility_name(TTRC("The following files are newer on disk:"));
4522
disk_changed_list->set_v_size_flags(SIZE_EXPAND_FILL);
4523
vbc->add_child(disk_changed_list);
4524
4525
Label *what_action_label = memnew(Label);
4526
what_action_label->set_text(TTRC("What action should be taken?"));
4527
vbc->add_child(what_action_label);
4528
4529
disk_changed->connect(SceneStringName(confirmed), callable_mp(this, &ScriptEditor::reload_scripts).bind(false));
4530
disk_changed->set_ok_button_text(TTRC("Reload from disk"));
4531
4532
disk_changed->add_button(TTRC("Ignore external changes"), !DisplayServer::get_singleton()->get_swap_cancel_ok(), "resave");
4533
disk_changed->connect("custom_action", callable_mp(this, &ScriptEditor::_resave_scripts));
4534
}
4535
4536
add_child(disk_changed);
4537
4538
script_editor = this;
4539
4540
autosave_timer = memnew(Timer);
4541
autosave_timer->set_one_shot(false);
4542
autosave_timer->connect(SceneStringName(tree_entered), callable_mp(this, &ScriptEditor::_update_autosave_timer));
4543
autosave_timer->connect("timeout", callable_mp(this, &ScriptEditor::_autosave_scripts));
4544
add_child(autosave_timer);
4545
4546
grab_focus_block = false;
4547
4548
help_search_dialog = memnew(EditorHelpSearch);
4549
add_child(help_search_dialog);
4550
help_search_dialog->connect("go_to_help", callable_mp(this, &ScriptEditor::_help_class_goto));
4551
4552
find_in_files_dialog = memnew(FindInFilesDialog);
4553
find_in_files_dialog->connect(FindInFilesDialog::SIGNAL_FIND_REQUESTED, callable_mp(this, &ScriptEditor::_start_find_in_files).bind(false));
4554
find_in_files_dialog->connect(FindInFilesDialog::SIGNAL_REPLACE_REQUESTED, callable_mp(this, &ScriptEditor::_start_find_in_files).bind(true));
4555
add_child(find_in_files_dialog);
4556
find_in_files = memnew(FindInFilesPanel);
4557
find_in_files_button = EditorNode::get_bottom_panel()->add_item(TTRC("Search Results"), find_in_files, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_search_results_bottom_panel", TTRC("Toggle Search Results Bottom Panel")));
4558
find_in_files->set_custom_minimum_size(Size2(0, 200) * EDSCALE);
4559
find_in_files->connect(FindInFilesPanel::SIGNAL_RESULT_SELECTED, callable_mp(this, &ScriptEditor::_on_find_in_files_result_selected));
4560
find_in_files->connect(FindInFilesPanel::SIGNAL_FILES_MODIFIED, callable_mp(this, &ScriptEditor::_on_find_in_files_modified_files));
4561
find_in_files->connect(FindInFilesPanel::SIGNAL_CLOSE_BUTTON_CLICKED, callable_mp(this, &ScriptEditor::_on_find_in_files_close_button_clicked));
4562
find_in_files->hide();
4563
find_in_files_button->hide();
4564
4565
history_pos = -1;
4566
4567
edit_pass = 0;
4568
trim_trailing_whitespace_on_save = EDITOR_GET("text_editor/behavior/files/trim_trailing_whitespace_on_save");
4569
trim_final_newlines_on_save = EDITOR_GET("text_editor/behavior/files/trim_final_newlines_on_save");
4570
convert_indent_on_save = EDITOR_GET("text_editor/behavior/files/convert_indent_on_save");
4571
4572
ScriptServer::edit_request_func = _open_script_request;
4573
4574
Ref<EditorJSONSyntaxHighlighter> json_syntax_highlighter;
4575
json_syntax_highlighter.instantiate();
4576
register_syntax_highlighter(json_syntax_highlighter);
4577
4578
Ref<EditorMarkdownSyntaxHighlighter> markdown_syntax_highlighter;
4579
markdown_syntax_highlighter.instantiate();
4580
register_syntax_highlighter(markdown_syntax_highlighter);
4581
4582
Ref<EditorConfigFileSyntaxHighlighter> config_file_syntax_highlighter;
4583
config_file_syntax_highlighter.instantiate();
4584
register_syntax_highlighter(config_file_syntax_highlighter);
4585
4586
_update_online_doc();
4587
}
4588
4589
void ScriptEditorPlugin::_focus_another_editor() {
4590
if (window_wrapper->get_window_enabled()) {
4591
ERR_FAIL_COND(last_editor.is_empty());
4592
EditorInterface::get_singleton()->set_main_screen_editor(last_editor);
4593
}
4594
}
4595
4596
void ScriptEditorPlugin::_save_last_editor(const String &p_editor) {
4597
if (p_editor != get_plugin_name()) {
4598
last_editor = p_editor;
4599
}
4600
}
4601
4602
void ScriptEditorPlugin::_window_visibility_changed(bool p_visible) {
4603
_focus_another_editor();
4604
if (p_visible) {
4605
script_editor->add_theme_style_override(SceneStringName(panel), script_editor->get_theme_stylebox("ScriptEditorPanelFloating", EditorStringName(EditorStyles)));
4606
} else {
4607
script_editor->add_theme_style_override(SceneStringName(panel), script_editor->get_theme_stylebox("ScriptEditorPanel", EditorStringName(EditorStyles)));
4608
}
4609
}
4610
4611
void ScriptEditorPlugin::_notification(int p_what) {
4612
switch (p_what) {
4613
case NOTIFICATION_TRANSLATION_CHANGED: {
4614
window_wrapper->set_window_title(vformat(TTR("%s - Godot Engine"), TTR("Script Editor")));
4615
} break;
4616
case NOTIFICATION_ENTER_TREE: {
4617
connect("main_screen_changed", callable_mp(this, &ScriptEditorPlugin::_save_last_editor));
4618
} break;
4619
case NOTIFICATION_EXIT_TREE: {
4620
disconnect("main_screen_changed", callable_mp(this, &ScriptEditorPlugin::_save_last_editor));
4621
} break;
4622
}
4623
}
4624
4625
void ScriptEditorPlugin::edit(Object *p_object) {
4626
if (Object::cast_to<Script>(p_object)) {
4627
Script *p_script = Object::cast_to<Script>(p_object);
4628
String res_path = p_script->get_path().get_slice("::", 0);
4629
4630
if (p_script->is_built_in() && !res_path.is_empty()) {
4631
EditorNode::get_singleton()->load_scene_or_resource(res_path, false, false);
4632
}
4633
script_editor->edit(p_script);
4634
} else if (Object::cast_to<JSON>(p_object)) {
4635
script_editor->edit(Object::cast_to<JSON>(p_object));
4636
} else if (Object::cast_to<TextFile>(p_object)) {
4637
script_editor->edit(Object::cast_to<TextFile>(p_object));
4638
}
4639
}
4640
4641
bool ScriptEditorPlugin::handles(Object *p_object) const {
4642
if (Object::cast_to<TextFile>(p_object)) {
4643
return true;
4644
}
4645
4646
if (Object::cast_to<Script>(p_object)) {
4647
return true;
4648
}
4649
4650
if (Object::cast_to<JSON>(p_object)) {
4651
return true;
4652
}
4653
4654
return p_object->is_class("Script");
4655
}
4656
4657
void ScriptEditorPlugin::make_visible(bool p_visible) {
4658
if (p_visible) {
4659
window_wrapper->show();
4660
script_editor->ensure_select_current();
4661
} else {
4662
window_wrapper->hide();
4663
}
4664
}
4665
4666
void ScriptEditorPlugin::selected_notify() {
4667
script_editor->ensure_select_current();
4668
_focus_another_editor();
4669
}
4670
4671
String ScriptEditorPlugin::get_unsaved_status(const String &p_for_scene) const {
4672
const PackedStringArray unsaved_scripts = script_editor->get_unsaved_scripts();
4673
if (unsaved_scripts.is_empty()) {
4674
return String();
4675
}
4676
4677
PackedStringArray message;
4678
if (!p_for_scene.is_empty()) {
4679
PackedStringArray unsaved_built_in_scripts;
4680
4681
const String scene_file = p_for_scene.get_file();
4682
for (const String &E : unsaved_scripts) {
4683
if (!E.is_resource_file() && E.contains(scene_file)) {
4684
unsaved_built_in_scripts.append(E);
4685
}
4686
}
4687
4688
if (unsaved_built_in_scripts.is_empty()) {
4689
return String();
4690
} else {
4691
message.resize(unsaved_built_in_scripts.size() + 1);
4692
message.write[0] = TTR("There are unsaved changes in the following built-in script(s):");
4693
4694
int i = 1;
4695
for (const String &E : unsaved_built_in_scripts) {
4696
message.write[i] = E.trim_suffix("(*)");
4697
i++;
4698
}
4699
return String("\n").join(message);
4700
}
4701
}
4702
4703
message.resize(unsaved_scripts.size() + 1);
4704
message.write[0] = TTR("Save changes to the following script(s) before quitting?");
4705
4706
int i = 1;
4707
for (const String &E : unsaved_scripts) {
4708
message.write[i] = E.trim_suffix("(*)");
4709
i++;
4710
}
4711
return String("\n").join(message);
4712
}
4713
4714
void ScriptEditorPlugin::save_external_data() {
4715
if (!EditorNode::get_singleton()->is_exiting()) {
4716
script_editor->save_all_scripts();
4717
}
4718
}
4719
4720
void ScriptEditorPlugin::apply_changes() {
4721
script_editor->apply_scripts();
4722
}
4723
4724
void ScriptEditorPlugin::set_window_layout(Ref<ConfigFile> p_layout) {
4725
script_editor->set_window_layout(p_layout);
4726
4727
if (EDITOR_GET("interface/multi_window/restore_windows_on_load") && window_wrapper->is_window_available() && p_layout->has_section_key("ScriptEditor", "window_rect")) {
4728
window_wrapper->restore_window_from_saved_position(
4729
p_layout->get_value("ScriptEditor", "window_rect", Rect2i()),
4730
p_layout->get_value("ScriptEditor", "window_screen", -1),
4731
p_layout->get_value("ScriptEditor", "window_screen_rect", Rect2i()));
4732
} else {
4733
window_wrapper->set_window_enabled(false);
4734
}
4735
}
4736
4737
void ScriptEditorPlugin::get_window_layout(Ref<ConfigFile> p_layout) {
4738
script_editor->get_window_layout(p_layout);
4739
4740
if (window_wrapper->get_window_enabled()) {
4741
p_layout->set_value("ScriptEditor", "window_rect", window_wrapper->get_window_rect());
4742
int screen = window_wrapper->get_window_screen();
4743
p_layout->set_value("ScriptEditor", "window_screen", screen);
4744
p_layout->set_value("ScriptEditor", "window_screen_rect", DisplayServer::get_singleton()->screen_get_usable_rect(screen));
4745
4746
} else {
4747
if (p_layout->has_section_key("ScriptEditor", "window_rect")) {
4748
p_layout->erase_section_key("ScriptEditor", "window_rect");
4749
}
4750
if (p_layout->has_section_key("ScriptEditor", "window_screen")) {
4751
p_layout->erase_section_key("ScriptEditor", "window_screen");
4752
}
4753
if (p_layout->has_section_key("ScriptEditor", "window_screen_rect")) {
4754
p_layout->erase_section_key("ScriptEditor", "window_screen_rect");
4755
}
4756
}
4757
}
4758
4759
void ScriptEditorPlugin::get_breakpoints(List<String> *p_breakpoints) {
4760
script_editor->get_breakpoints(p_breakpoints);
4761
}
4762
4763
void ScriptEditorPlugin::edited_scene_changed() {
4764
script_editor->edited_scene_changed();
4765
}
4766
4767
ScriptEditorPlugin::ScriptEditorPlugin() {
4768
ED_SHORTCUT("script_editor/reopen_closed_script", TTRC("Reopen Closed Script"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::T);
4769
ED_SHORTCUT("script_editor/clear_recent", TTRC("Clear Recent Scripts"));
4770
ED_SHORTCUT("script_editor/replace_in_files", TTRC("Replace in Files"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::R);
4771
4772
ED_SHORTCUT("script_text_editor/convert_to_uppercase", TTRC("Uppercase"), KeyModifierMask::SHIFT | Key::F4);
4773
ED_SHORTCUT("script_text_editor/convert_to_lowercase", TTRC("Lowercase"), KeyModifierMask::SHIFT | Key::F5);
4774
ED_SHORTCUT("script_text_editor/capitalize", TTRC("Capitalize"), KeyModifierMask::SHIFT | Key::F6);
4775
4776
window_wrapper = memnew(WindowWrapper);
4777
window_wrapper->set_margins_enabled(true);
4778
4779
script_editor = memnew(ScriptEditor(window_wrapper));
4780
Ref<Shortcut> make_floating_shortcut = ED_SHORTCUT_AND_COMMAND("script_editor/make_floating", TTRC("Make Floating"));
4781
window_wrapper->set_wrapped_control(script_editor, make_floating_shortcut);
4782
4783
EditorNode::get_singleton()->get_editor_main_screen()->get_control()->add_child(window_wrapper);
4784
window_wrapper->set_v_size_flags(Control::SIZE_EXPAND_FILL);
4785
window_wrapper->hide();
4786
window_wrapper->connect("window_visibility_changed", callable_mp(this, &ScriptEditorPlugin::_window_visibility_changed));
4787
4788
ScriptServer::set_reload_scripts_on_save(EDITOR_GET("text_editor/behavior/files/auto_reload_and_parse_scripts_on_save"));
4789
}
4790
4791