Path: blob/master/editor/script/script_editor_plugin.cpp
9903 views
/**************************************************************************/1/* script_editor_plugin.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "script_editor_plugin.h"3132#include "core/config/project_settings.h"33#include "core/input/input.h"34#include "core/io/config_file.h"35#include "core/io/file_access.h"36#include "core/io/json.h"37#include "core/io/resource_loader.h"38#include "core/os/keyboard.h"39#include "core/os/os.h"40#include "core/string/fuzzy_search.h"41#include "core/version.h"42#include "editor/debugger/editor_debugger_node.h"43#include "editor/debugger/script_editor_debugger.h"44#include "editor/doc/editor_help_search.h"45#include "editor/docks/filesystem_dock.h"46#include "editor/docks/inspector_dock.h"47#include "editor/docks/node_dock.h"48#include "editor/editor_interface.h"49#include "editor/editor_main_screen.h"50#include "editor/editor_node.h"51#include "editor/editor_string_names.h"52#include "editor/file_system/editor_paths.h"53#include "editor/gui/code_editor.h"54#include "editor/gui/editor_bottom_panel.h"55#include "editor/gui/editor_file_dialog.h"56#include "editor/gui/editor_toaster.h"57#include "editor/gui/window_wrapper.h"58#include "editor/inspector/editor_context_menu_plugin.h"59#include "editor/run/editor_run_bar.h"60#include "editor/script/editor_script.h"61#include "editor/script/find_in_files.h"62#include "editor/settings/editor_command_palette.h"63#include "editor/settings/editor_settings.h"64#include "editor/shader/shader_editor_plugin.h"65#include "editor/shader/text_shader_editor.h"66#include "editor/themes/editor_scale.h"67#include "editor/themes/editor_theme_manager.h"68#include "scene/gui/separator.h"69#include "scene/gui/tab_container.h"70#include "scene/gui/texture_rect.h"71#include "scene/main/node.h"72#include "scene/main/window.h"73#include "script_text_editor.h"74#include "servers/display_server.h"75#include "text_editor.h"7677/*** SYNTAX HIGHLIGHTER ****/7879String EditorSyntaxHighlighter::_get_name() const {80String ret = "Unnamed";81GDVIRTUAL_CALL(_get_name, ret);82return ret;83}8485PackedStringArray EditorSyntaxHighlighter::_get_supported_languages() const {86PackedStringArray ret;87GDVIRTUAL_CALL(_get_supported_languages, ret);88return ret;89}9091Ref<EditorSyntaxHighlighter> EditorSyntaxHighlighter::_create() const {92Ref<EditorSyntaxHighlighter> syntax_highlighter;93if (GDVIRTUAL_IS_OVERRIDDEN(_create)) {94GDVIRTUAL_CALL(_create, syntax_highlighter);95} else {96syntax_highlighter.instantiate();97if (get_script_instance()) {98syntax_highlighter->set_script(get_script_instance()->get_script());99}100}101return syntax_highlighter;102}103104void EditorSyntaxHighlighter::_bind_methods() {105ClassDB::bind_method(D_METHOD("_get_edited_resource"), &EditorSyntaxHighlighter::_get_edited_resource);106107GDVIRTUAL_BIND(_get_name)108GDVIRTUAL_BIND(_get_supported_languages)109GDVIRTUAL_BIND(_create)110}111112////113114void EditorStandardSyntaxHighlighter::_update_cache() {115highlighter->set_text_edit(text_edit);116highlighter->clear_keyword_colors();117highlighter->clear_member_keyword_colors();118highlighter->clear_color_regions();119120highlighter->set_symbol_color(EDITOR_GET("text_editor/theme/highlighting/symbol_color"));121highlighter->set_function_color(EDITOR_GET("text_editor/theme/highlighting/function_color"));122highlighter->set_number_color(EDITOR_GET("text_editor/theme/highlighting/number_color"));123highlighter->set_member_variable_color(EDITOR_GET("text_editor/theme/highlighting/member_variable_color"));124125/* Engine types. */126const Color type_color = EDITOR_GET("text_editor/theme/highlighting/engine_type_color");127List<StringName> types;128ClassDB::get_class_list(&types);129for (const StringName &E : types) {130highlighter->add_keyword_color(E, type_color);131}132133/* User types. */134const Color usertype_color = EDITOR_GET("text_editor/theme/highlighting/user_type_color");135List<StringName> global_classes;136ScriptServer::get_global_class_list(&global_classes);137for (const StringName &E : global_classes) {138highlighter->add_keyword_color(E, usertype_color);139}140141/* Autoloads. */142HashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list();143for (const KeyValue<StringName, ProjectSettings::AutoloadInfo> &E : autoloads) {144const ProjectSettings::AutoloadInfo &info = E.value;145if (info.is_singleton) {146highlighter->add_keyword_color(info.name, usertype_color);147}148}149150const ScriptLanguage *scr_lang = script_language;151StringName instance_base;152153if (scr_lang == nullptr) {154const Ref<Script> scr = _get_edited_resource();155if (scr.is_valid()) {156scr_lang = scr->get_language();157instance_base = scr->get_instance_base_type();158}159}160161if (scr_lang != nullptr) {162/* Core types. */163const Color basetype_color = EDITOR_GET("text_editor/theme/highlighting/base_type_color");164List<String> core_types;165scr_lang->get_core_type_words(&core_types);166for (const String &E : core_types) {167highlighter->add_keyword_color(E, basetype_color);168}169170/* Reserved words. */171const Color keyword_color = EDITOR_GET("text_editor/theme/highlighting/keyword_color");172const Color control_flow_keyword_color = EDITOR_GET("text_editor/theme/highlighting/control_flow_keyword_color");173for (const String &keyword : scr_lang->get_reserved_words()) {174if (scr_lang->is_control_flow_keyword(keyword)) {175highlighter->add_keyword_color(keyword, control_flow_keyword_color);176} else {177highlighter->add_keyword_color(keyword, keyword_color);178}179}180181/* Member types. */182const Color member_variable_color = EDITOR_GET("text_editor/theme/highlighting/member_variable_color");183if (instance_base != StringName()) {184List<PropertyInfo> plist;185ClassDB::get_property_list(instance_base, &plist);186for (const PropertyInfo &E : plist) {187String prop_name = E.name;188if (E.usage & PROPERTY_USAGE_CATEGORY || E.usage & PROPERTY_USAGE_GROUP || E.usage & PROPERTY_USAGE_SUBGROUP) {189continue;190}191if (prop_name.contains_char('/')) {192continue;193}194highlighter->add_member_keyword_color(prop_name, member_variable_color);195}196197List<String> clist;198ClassDB::get_integer_constant_list(instance_base, &clist);199for (const String &E : clist) {200highlighter->add_member_keyword_color(E, member_variable_color);201}202}203204/* Comments */205const Color comment_color = EDITOR_GET("text_editor/theme/highlighting/comment_color");206for (const String &comment : scr_lang->get_comment_delimiters()) {207String beg = comment.get_slicec(' ', 0);208String end = comment.get_slice_count(" ") > 1 ? comment.get_slicec(' ', 1) : String();209highlighter->add_color_region(beg, end, comment_color, end.is_empty());210}211212/* Doc comments */213const Color doc_comment_color = EDITOR_GET("text_editor/theme/highlighting/doc_comment_color");214for (const String &doc_comment : scr_lang->get_doc_comment_delimiters()) {215String beg = doc_comment.get_slicec(' ', 0);216String end = doc_comment.get_slice_count(" ") > 1 ? doc_comment.get_slicec(' ', 1) : String();217highlighter->add_color_region(beg, end, doc_comment_color, end.is_empty());218}219220/* Strings */221const Color string_color = EDITOR_GET("text_editor/theme/highlighting/string_color");222for (const String &string : scr_lang->get_string_delimiters()) {223String beg = string.get_slicec(' ', 0);224String end = string.get_slice_count(" ") > 1 ? string.get_slicec(' ', 1) : String();225highlighter->add_color_region(beg, end, string_color, end.is_empty());226}227}228}229230Ref<EditorSyntaxHighlighter> EditorStandardSyntaxHighlighter::_create() const {231Ref<EditorStandardSyntaxHighlighter> syntax_highlighter;232syntax_highlighter.instantiate();233return syntax_highlighter;234}235236////237238Ref<EditorSyntaxHighlighter> EditorPlainTextSyntaxHighlighter::_create() const {239Ref<EditorPlainTextSyntaxHighlighter> syntax_highlighter;240syntax_highlighter.instantiate();241return syntax_highlighter;242}243244////245246void EditorJSONSyntaxHighlighter::_update_cache() {247highlighter->set_text_edit(text_edit);248highlighter->clear_keyword_colors();249highlighter->clear_member_keyword_colors();250highlighter->clear_color_regions();251252highlighter->set_symbol_color(EDITOR_GET("text_editor/theme/highlighting/symbol_color"));253highlighter->set_number_color(EDITOR_GET("text_editor/theme/highlighting/number_color"));254255const Color string_color = EDITOR_GET("text_editor/theme/highlighting/string_color");256highlighter->add_color_region("\"", "\"", string_color);257}258259Ref<EditorSyntaxHighlighter> EditorJSONSyntaxHighlighter::_create() const {260Ref<EditorJSONSyntaxHighlighter> syntax_highlighter;261syntax_highlighter.instantiate();262return syntax_highlighter;263}264265////266267void EditorMarkdownSyntaxHighlighter::_update_cache() {268highlighter->set_text_edit(text_edit);269highlighter->clear_keyword_colors();270highlighter->clear_member_keyword_colors();271highlighter->clear_color_regions();272273// Disable automatic symbolic highlights, as these don't make sense for prose.274highlighter->set_symbol_color(EDITOR_GET("text_editor/theme/highlighting/text_color"));275highlighter->set_number_color(EDITOR_GET("text_editor/theme/highlighting/text_color"));276highlighter->set_member_variable_color(EDITOR_GET("text_editor/theme/highlighting/text_color"));277highlighter->set_function_color(EDITOR_GET("text_editor/theme/highlighting/text_color"));278279// Headings (any level).280const Color function_color = EDITOR_GET("text_editor/theme/highlighting/function_color");281highlighter->add_color_region("#", "", function_color);282283// Bold.284highlighter->add_color_region("**", "**", function_color);285// `__bold__` syntax is not supported as color regions must begin with a symbol,286// not a character that is valid in an identifier.287288// Code (both inline code and triple-backticks code blocks).289const Color code_color = EDITOR_GET("text_editor/theme/highlighting/engine_type_color");290highlighter->add_color_region("`", "`", code_color);291292// Link (both references and inline links with URLs). The URL is not highlighted.293const Color link_color = EDITOR_GET("text_editor/theme/highlighting/keyword_color");294highlighter->add_color_region("[", "]", link_color);295296// Quote.297const Color quote_color = EDITOR_GET("text_editor/theme/highlighting/string_color");298highlighter->add_color_region(">", "", quote_color, true);299300// HTML comment, which is also supported in Markdown.301const Color comment_color = EDITOR_GET("text_editor/theme/highlighting/comment_color");302highlighter->add_color_region("<!--", "-->", comment_color);303}304305Ref<EditorSyntaxHighlighter> EditorMarkdownSyntaxHighlighter::_create() const {306Ref<EditorMarkdownSyntaxHighlighter> syntax_highlighter;307syntax_highlighter.instantiate();308return syntax_highlighter;309}310311///312313void EditorConfigFileSyntaxHighlighter::_update_cache() {314highlighter->set_text_edit(text_edit);315highlighter->clear_keyword_colors();316highlighter->clear_member_keyword_colors();317highlighter->clear_color_regions();318319highlighter->set_symbol_color(EDITOR_GET("text_editor/theme/highlighting/symbol_color"));320highlighter->set_number_color(EDITOR_GET("text_editor/theme/highlighting/number_color"));321// Assume that all function-style syntax is for types such as `Vector2()` and `PackedStringArray()`.322highlighter->set_function_color(EDITOR_GET("text_editor/theme/highlighting/base_type_color"));323324// Disable member variable highlighting as it's not relevant for ConfigFile.325highlighter->set_member_variable_color(EDITOR_GET("text_editor/theme/highlighting/text_color"));326327const Color string_color = EDITOR_GET("text_editor/theme/highlighting/string_color");328highlighter->add_color_region("\"", "\"", string_color);329330// FIXME: Sections in ConfigFile must be at the beginning of a line. Otherwise, it can be an array within a line.331const Color function_color = EDITOR_GET("text_editor/theme/highlighting/function_color");332highlighter->add_color_region("[", "]", function_color);333334const Color keyword_color = EDITOR_GET("text_editor/theme/highlighting/keyword_color");335highlighter->add_keyword_color("true", keyword_color);336highlighter->add_keyword_color("false", keyword_color);337highlighter->add_keyword_color("null", keyword_color);338highlighter->add_keyword_color("ExtResource", keyword_color);339highlighter->add_keyword_color("SubResource", keyword_color);340341const Color comment_color = EDITOR_GET("text_editor/theme/highlighting/comment_color");342highlighter->add_color_region(";", "", comment_color);343}344345Ref<EditorSyntaxHighlighter> EditorConfigFileSyntaxHighlighter::_create() const {346Ref<EditorConfigFileSyntaxHighlighter> syntax_highlighter;347syntax_highlighter.instantiate();348return syntax_highlighter;349}350351////////////////////////////////////////////////////////////////////////////////352353/*** SCRIPT EDITOR ****/354355void ScriptEditorBase::_bind_methods() {356ClassDB::bind_method(D_METHOD("get_base_editor"), &ScriptEditorBase::get_base_editor);357ClassDB::bind_method(D_METHOD("add_syntax_highlighter", "highlighter"), &ScriptEditorBase::add_syntax_highlighter);358359ADD_SIGNAL(MethodInfo("name_changed"));360ADD_SIGNAL(MethodInfo("edited_script_changed"));361ADD_SIGNAL(MethodInfo("request_help", PropertyInfo(Variant::STRING, "topic")));362ADD_SIGNAL(MethodInfo("request_open_script_at_line", PropertyInfo(Variant::OBJECT, "script"), PropertyInfo(Variant::INT, "line")));363ADD_SIGNAL(MethodInfo("request_save_history"));364ADD_SIGNAL(MethodInfo("request_save_previous_state", PropertyInfo(Variant::DICTIONARY, "state")));365ADD_SIGNAL(MethodInfo("go_to_help", PropertyInfo(Variant::STRING, "what")));366ADD_SIGNAL(MethodInfo("search_in_files_requested", PropertyInfo(Variant::STRING, "text")));367ADD_SIGNAL(MethodInfo("replace_in_files_requested", PropertyInfo(Variant::STRING, "text")));368ADD_SIGNAL(MethodInfo("go_to_method", PropertyInfo(Variant::OBJECT, "script"), PropertyInfo(Variant::STRING, "method")));369}370371void ScriptEditorQuickOpen::popup_dialog(const Vector<String> &p_functions, bool p_dontclear) {372popup_centered_ratio(0.6);373if (p_dontclear) {374search_box->select_all();375} else {376search_box->clear();377}378search_box->grab_focus();379functions = p_functions;380_update_search();381}382383void ScriptEditorQuickOpen::_text_changed(const String &p_newtext) {384_update_search();385}386387void ScriptEditorQuickOpen::_sbox_input(const Ref<InputEvent> &p_event) {388// Redirect navigational key events to the tree.389Ref<InputEventKey> key = p_event;390if (key.is_valid()) {391if (key->is_action("ui_up", true) || key->is_action("ui_down", true) || key->is_action("ui_page_up") || key->is_action("ui_page_down")) {392search_options->gui_input(key);393search_box->accept_event();394}395}396}397398void ScriptEditorQuickOpen::_update_search() {399search_options->clear();400TreeItem *root = search_options->create_item();401402for (int i = 0; i < functions.size(); i++) {403String file = functions[i];404if ((search_box->get_text().is_empty() || file.containsn(search_box->get_text()))) {405TreeItem *ti = search_options->create_item(root);406ti->set_text(0, file);407if (root->get_first_child() == ti) {408ti->select(0);409}410}411}412413get_ok_button()->set_disabled(root->get_first_child() == nullptr);414}415416void ScriptEditorQuickOpen::_confirmed() {417TreeItem *ti = search_options->get_selected();418if (!ti) {419return;420}421int line = ti->get_text(0).get_slicec(':', 1).to_int();422423emit_signal(SNAME("goto_line"), line - 1);424hide();425}426427void ScriptEditorQuickOpen::_notification(int p_what) {428switch (p_what) {429case NOTIFICATION_ENTER_TREE: {430connect(SceneStringName(confirmed), callable_mp(this, &ScriptEditorQuickOpen::_confirmed));431432search_box->set_clear_button_enabled(true);433[[fallthrough]];434}435case NOTIFICATION_VISIBILITY_CHANGED: {436search_box->set_right_icon(search_options->get_editor_theme_icon(SNAME("Search")));437} break;438439case NOTIFICATION_EXIT_TREE: {440disconnect(SceneStringName(confirmed), callable_mp(this, &ScriptEditorQuickOpen::_confirmed));441} break;442}443}444445void ScriptEditorQuickOpen::_bind_methods() {446ADD_SIGNAL(MethodInfo("goto_line", PropertyInfo(Variant::INT, "line")));447}448449ScriptEditorQuickOpen::ScriptEditorQuickOpen() {450VBoxContainer *vbc = memnew(VBoxContainer);451add_child(vbc);452search_box = memnew(LineEdit);453vbc->add_margin_child(TTRC("Search:"), search_box);454search_box->connect(SceneStringName(text_changed), callable_mp(this, &ScriptEditorQuickOpen::_text_changed));455search_box->connect(SceneStringName(gui_input), callable_mp(this, &ScriptEditorQuickOpen::_sbox_input));456search_options = memnew(Tree);457vbc->add_margin_child(TTRC("Matches:"), search_options, true);458set_ok_button_text(TTRC("Open"));459get_ok_button()->set_disabled(true);460register_text_enter(search_box);461set_hide_on_ok(false);462search_options->connect("item_activated", callable_mp(this, &ScriptEditorQuickOpen::_confirmed));463search_options->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);464search_options->set_hide_root(true);465search_options->set_hide_folding(true);466search_options->add_theme_constant_override("draw_guides", 1);467}468469/////////////////////////////////470471ScriptEditor *ScriptEditor::script_editor = nullptr;472473/*** SCRIPT EDITOR ******/474475String ScriptEditor::_get_debug_tooltip(const String &p_text, Node *p_se) {476if (EDITOR_GET("text_editor/behavior/documentation/enable_tooltips")) {477return String();478}479480// NOTE: See also `ScriptTextEditor::_show_symbol_tooltip()` for documentation tooltips enabled.481String debug_value = EditorDebuggerNode::get_singleton()->get_var_value(p_text);482if (!debug_value.is_empty()) {483constexpr int DISPLAY_LIMIT = 1024;484if (debug_value.size() > DISPLAY_LIMIT) {485debug_value = debug_value.left(DISPLAY_LIMIT) + "... " + TTR("(truncated)");486}487debug_value = TTR("Current value: ") + debug_value;488}489490return debug_value;491}492493void ScriptEditor::_breaked(bool p_breaked, bool p_can_debug) {494if (external_editor_active) {495return;496}497498for (int i = 0; i < tab_container->get_tab_count(); i++) {499ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));500if (!se) {501continue;502}503504se->set_debugger_active(p_breaked);505}506}507508void ScriptEditor::_script_created(Ref<Script> p_script) {509EditorNode::get_singleton()->push_item(p_script.operator->());510}511512void ScriptEditor::_goto_script_line2(int p_line) {513ScriptEditorBase *current = _get_current_editor();514if (current) {515current->goto_line(p_line);516}517}518519void ScriptEditor::_goto_script_line(Ref<RefCounted> p_script, int p_line) {520Ref<Script> scr = Object::cast_to<Script>(*p_script);521if (scr.is_valid() && (scr->has_source_code() || scr->get_path().is_resource_file())) {522if (edit(p_script, p_line, 0)) {523EditorNode::get_singleton()->push_item(p_script.ptr());524525ScriptEditorBase *current = _get_current_editor();526if (ScriptTextEditor *script_text_editor = Object::cast_to<ScriptTextEditor>(current)) {527script_text_editor->goto_line_centered(p_line);528} else if (current) {529current->goto_line(p_line);530}531532_save_history();533}534}535}536537void ScriptEditor::_set_execution(Ref<RefCounted> p_script, int p_line) {538Ref<Script> scr = Object::cast_to<Script>(*p_script);539if (scr.is_valid() && (scr->has_source_code() || scr->get_path().is_resource_file())) {540for (int i = 0; i < tab_container->get_tab_count(); i++) {541ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));542if (!se) {543continue;544}545546if ((scr.is_valid() && se->get_edited_resource() == p_script) || se->get_edited_resource()->get_path() == scr->get_path()) {547se->set_executing_line(p_line);548}549}550}551}552553void ScriptEditor::_clear_execution(Ref<RefCounted> p_script) {554Ref<Script> scr = Object::cast_to<Script>(*p_script);555if (scr.is_valid() && (scr->has_source_code() || scr->get_path().is_resource_file())) {556for (int i = 0; i < tab_container->get_tab_count(); i++) {557ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));558if (!se) {559continue;560}561562if ((scr.is_valid() && se->get_edited_resource() == p_script) || se->get_edited_resource()->get_path() == scr->get_path()) {563se->clear_executing_line();564}565}566}567}568569void ScriptEditor::_set_breakpoint(Ref<RefCounted> p_script, int p_line, bool p_enabled) {570Ref<Script> scr = Object::cast_to<Script>(*p_script);571if (scr.is_valid() && (scr->has_source_code() || scr->get_path().is_resource_file())) {572// Update if open.573for (int i = 0; i < tab_container->get_tab_count(); i++) {574ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));575if (se && se->get_edited_resource()->get_path() == scr->get_path()) {576se->set_breakpoint(p_line, p_enabled);577return;578}579}580581// Handle closed.582Dictionary state = script_editor_cache->get_value(scr->get_path(), "state");583Array breakpoints;584if (state.has("breakpoints")) {585breakpoints = state["breakpoints"];586}587588if (breakpoints.has(p_line)) {589if (!p_enabled) {590breakpoints.erase(p_line);591}592} else if (p_enabled) {593breakpoints.push_back(p_line);594}595state["breakpoints"] = breakpoints;596script_editor_cache->set_value(scr->get_path(), "state", state);597EditorDebuggerNode::get_singleton()->set_breakpoint(scr->get_path(), p_line + 1, p_enabled);598}599}600601void ScriptEditor::_clear_breakpoints() {602for (int i = 0; i < tab_container->get_tab_count(); i++) {603ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));604if (se) {605se->clear_breakpoints();606}607}608609// Clear from closed scripts.610Vector<String> cached_editors = script_editor_cache->get_sections();611for (const String &E : cached_editors) {612Array breakpoints = _get_cached_breakpoints_for_script(E);613for (int breakpoint : breakpoints) {614EditorDebuggerNode::get_singleton()->set_breakpoint(E, (int)breakpoint + 1, false);615}616617if (breakpoints.size() > 0) {618Dictionary state = script_editor_cache->get_value(E, "state");619state["breakpoints"] = Array();620script_editor_cache->set_value(E, "state", state);621}622}623}624625Array ScriptEditor::_get_cached_breakpoints_for_script(const String &p_path) const {626if (!ResourceLoader::exists(p_path, "Script") || p_path.begins_with("local://") || !script_editor_cache->has_section_key(p_path, "state")) {627return Array();628}629630Dictionary state = script_editor_cache->get_value(p_path, "state");631if (!state.has("breakpoints")) {632return Array();633}634return state["breakpoints"];635}636637ScriptEditorBase *ScriptEditor::_get_current_editor() const {638int selected = tab_container->get_current_tab();639if (selected < 0 || selected >= tab_container->get_tab_count()) {640return nullptr;641}642643return Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(selected));644}645646void ScriptEditor::_update_history_arrows() {647script_back->set_disabled(history_pos <= 0);648script_forward->set_disabled(history_pos >= history.size() - 1);649}650651void ScriptEditor::_save_history() {652if (history_pos >= 0 && history_pos < history.size() && history[history_pos].control == tab_container->get_current_tab_control()) {653Node *n = tab_container->get_current_tab_control();654655if (Object::cast_to<ScriptEditorBase>(n)) {656history.write[history_pos].state = Object::cast_to<ScriptEditorBase>(n)->get_navigation_state();657}658if (Object::cast_to<EditorHelp>(n)) {659history.write[history_pos].state = Object::cast_to<EditorHelp>(n)->get_scroll();660}661}662663history.resize(history_pos + 1);664ScriptHistory sh;665sh.control = tab_container->get_current_tab_control();666sh.state = Variant();667668history.push_back(sh);669history_pos++;670671_update_history_arrows();672}673674void ScriptEditor::_save_previous_state(Dictionary p_state) {675if (lock_history) {676// Done as a result of a deferred call triggered by set_edit_state().677lock_history = false;678return;679}680681if (history_pos >= 0 && history_pos < history.size() && history[history_pos].control == tab_container->get_current_tab_control()) {682Node *n = tab_container->get_current_tab_control();683684if (Object::cast_to<ScriptTextEditor>(n)) {685history.write[history_pos].state = p_state;686}687}688689history.resize(history_pos + 1);690ScriptHistory sh;691sh.control = tab_container->get_current_tab_control();692sh.state = Variant();693694history.push_back(sh);695history_pos++;696697_update_history_arrows();698}699700void ScriptEditor::_go_to_tab(int p_idx) {701ScriptEditorBase *current = _get_current_editor();702if (current) {703if (current->is_unsaved()) {704current->apply_code();705}706}707708Control *c = tab_container->get_tab_control(p_idx);709if (!c) {710return;711}712713if (history_pos >= 0 && history_pos < history.size() && history[history_pos].control == tab_container->get_current_tab_control()) {714Node *n = tab_container->get_current_tab_control();715716if (Object::cast_to<ScriptEditorBase>(n)) {717history.write[history_pos].state = Object::cast_to<ScriptEditorBase>(n)->get_navigation_state();718}719if (Object::cast_to<EditorHelp>(n)) {720history.write[history_pos].state = Object::cast_to<EditorHelp>(n)->get_scroll();721}722}723724history.resize(history_pos + 1);725ScriptHistory sh;726sh.control = c;727sh.state = Variant();728729if (!lock_history && (history.is_empty() || history[history.size() - 1].control != sh.control)) {730history.push_back(sh);731history_pos++;732}733734tab_container->set_current_tab(p_idx);735736c = tab_container->get_current_tab_control();737738if (Object::cast_to<ScriptEditorBase>(c)) {739script_name_label->set_text(Object::cast_to<ScriptEditorBase>(c)->get_name());740script_icon->set_texture(Object::cast_to<ScriptEditorBase>(c)->get_theme_icon());741if (is_visible_in_tree()) {742Object::cast_to<ScriptEditorBase>(c)->ensure_focus();743}744745Ref<Script> scr = Object::cast_to<ScriptEditorBase>(c)->get_edited_resource();746if (scr.is_valid()) {747notify_script_changed(scr);748}749750Object::cast_to<ScriptEditorBase>(c)->validate();751}752if (Object::cast_to<EditorHelp>(c)) {753script_name_label->set_text(Object::cast_to<EditorHelp>(c)->get_class());754script_icon->set_texture(get_editor_theme_icon(SNAME("Help")));755if (is_visible_in_tree()) {756Object::cast_to<EditorHelp>(c)->set_focused();757}758}759760c->set_meta("__editor_pass", ++edit_pass);761_update_history_arrows();762_update_script_colors();763_update_members_overview();764_update_help_overview();765_update_selected_editor_menu();766_update_online_doc();767_update_members_overview_visibility();768_update_help_overview_visibility();769}770771void ScriptEditor::_add_recent_script(const String &p_path) {772if (p_path.is_empty()) {773return;774}775776Array rc = EditorSettings::get_singleton()->get_project_metadata("recent_files", "scripts", Array());777if (rc.has(p_path)) {778rc.erase(p_path);779}780rc.push_front(p_path);781if (rc.size() > 10) {782rc.resize(10);783}784785EditorSettings::get_singleton()->set_project_metadata("recent_files", "scripts", rc);786_update_recent_scripts();787}788789void ScriptEditor::_update_recent_scripts() {790Array rc = EditorSettings::get_singleton()->get_project_metadata("recent_files", "scripts", Array());791recent_scripts->clear();792793String path;794for (int i = 0; i < rc.size(); i++) {795path = rc[i];796recent_scripts->add_item(path.replace("res://", ""));797}798799recent_scripts->add_separator();800recent_scripts->add_shortcut(ED_GET_SHORTCUT("script_editor/clear_recent"));801recent_scripts->set_item_auto_translate_mode(-1, AUTO_TRANSLATE_MODE_ALWAYS);802recent_scripts->set_item_disabled(-1, rc.is_empty());803804recent_scripts->reset_size();805}806807void ScriptEditor::_open_recent_script(int p_idx) {808// clear button809if (p_idx == recent_scripts->get_item_count() - 1) {810EditorSettings::get_singleton()->set_project_metadata("recent_files", "scripts", Array());811callable_mp(this, &ScriptEditor::_update_recent_scripts).call_deferred();812return;813}814815Array rc = EditorSettings::get_singleton()->get_project_metadata("recent_files", "scripts", Array());816ERR_FAIL_INDEX(p_idx, rc.size());817818String path = rc[p_idx];819// if its not on disk its a help file or deleted820if (FileAccess::exists(path)) {821List<String> extensions;822ResourceLoader::get_recognized_extensions_for_type("Script", &extensions);823ResourceLoader::get_recognized_extensions_for_type("JSON", &extensions);824825if (extensions.find(path.get_extension())) {826Ref<Resource> scr = ResourceLoader::load(path);827if (scr.is_valid()) {828edit(scr, true);829return;830}831}832833Error err;834Ref<TextFile> text_file = _load_text_file(path, &err);835if (text_file.is_valid()) {836edit(text_file, true);837return;838}839// if it's a path then it's most likely a deleted file not help840} else if (path.contains("::")) {841// built-in script842String res_path = path.get_slice("::", 0);843EditorNode::get_singleton()->load_scene_or_resource(res_path, false, false);844845Ref<Script> scr = ResourceLoader::load(path);846if (scr.is_valid()) {847edit(scr, true);848return;849}850} else if (!path.is_resource_file()) {851_help_class_open(path);852return;853}854855rc.remove_at(p_idx);856EditorSettings::get_singleton()->set_project_metadata("recent_files", "scripts", rc);857_update_recent_scripts();858_show_error_dialog(path);859}860861void ScriptEditor::_show_error_dialog(const String &p_path) {862error_dialog->set_text(vformat(TTR("Can't open '%s'. The file could have been moved or deleted."), p_path));863error_dialog->popup_centered();864}865866void ScriptEditor::_close_tab(int p_idx, bool p_save, bool p_history_back) {867int selected = p_idx;868if (selected < 0 || selected >= tab_container->get_tab_count()) {869return;870}871872Node *tselected = tab_container->get_tab_control(selected);873874ScriptEditorBase *current = Object::cast_to<ScriptEditorBase>(tselected);875if (current) {876Ref<Resource> file = current->get_edited_resource();877if (p_save && file.is_valid()) {878// Do not try to save internal scripts, but prompt to save in-memory879// scripts which are not saved to disk yet (have empty path).880if (!file->is_built_in()) {881save_current_script();882}883}884if (file.is_valid()) {885if (!file->get_path().is_empty()) {886// Only saved scripts can be restored.887previous_scripts.push_back(file->get_path());888}889890Ref<Script> scr = file;891if (scr.is_valid()) {892notify_script_close(scr);893}894}895}896897// roll back to previous tab898if (p_history_back) {899_history_back();900}901902//remove from history903history.resize(history_pos + 1);904905for (int i = 0; i < history.size(); i++) {906if (history[i].control == tselected) {907history.remove_at(i);908i--;909history_pos--;910}911}912913if (history_pos >= history.size()) {914history_pos = history.size() - 1;915}916917int idx = tab_container->get_current_tab();918if (current) {919current->clear_edit_menu();920_save_editor_state(current);921}922memdelete(tselected);923924if (script_close_queue.is_empty()) {925if (idx >= tab_container->get_tab_count()) {926idx = tab_container->get_tab_count() - 1;927}928if (idx >= 0) {929if (history_pos >= 0) {930idx = tab_container->get_tab_idx_from_control(history[history_pos].control);931}932_go_to_tab(idx);933} else {934_update_selected_editor_menu();935_update_online_doc();936}937938_update_history_arrows();939_update_script_names();940_save_layout();941_update_find_replace_bar();942}943}944945void ScriptEditor::_close_current_tab(bool p_save, bool p_history_back) {946_close_tab(tab_container->get_current_tab(), p_save, p_history_back);947}948949void ScriptEditor::_close_discard_current_tab(const String &p_str) {950Ref<Script> scr = _get_current_script();951if (scr.is_valid()) {952scr->reload_from_file();953}954_close_tab(tab_container->get_current_tab(), false);955erase_tab_confirm->hide();956}957958void ScriptEditor::_close_docs_tab() {959int child_count = tab_container->get_tab_count();960for (int i = child_count - 1; i >= 0; i--) {961EditorHelp *se = Object::cast_to<EditorHelp>(tab_container->get_tab_control(i));962963if (se) {964_close_tab(i, true, false);965}966}967}968969void ScriptEditor::_copy_script_path() {970ScriptEditorBase *se = _get_current_editor();971if (se) {972Ref<Resource> scr = se->get_edited_resource();973DisplayServer::get_singleton()->clipboard_set(scr->get_path());974}975}976977void ScriptEditor::_copy_script_uid() {978ScriptEditorBase *se = _get_current_editor();979if (se) {980Ref<Resource> scr = se->get_edited_resource();981ResourceUID::ID uid = ResourceLoader::get_resource_uid(scr->get_path());982DisplayServer::get_singleton()->clipboard_set(ResourceUID::get_singleton()->id_to_text(uid));983}984}985986void ScriptEditor::_close_other_tabs() {987int current_idx = tab_container->get_current_tab();988for (int i = tab_container->get_tab_count() - 1; i >= 0; i--) {989if (i != current_idx) {990script_close_queue.push_back(i);991}992}993_queue_close_tabs();994}995996void ScriptEditor::_close_tabs_below() {997int current_idx = tab_container->get_current_tab();998for (int i = tab_container->get_tab_count() - 1; i > current_idx; i--) {999script_close_queue.push_back(i);1000}1001_go_to_tab(current_idx);1002_queue_close_tabs();1003}10041005void ScriptEditor::_close_all_tabs() {1006for (int i = tab_container->get_tab_count() - 1; i >= 0; i--) {1007script_close_queue.push_back(i);1008}1009_queue_close_tabs();1010}10111012void ScriptEditor::_queue_close_tabs() {1013while (!script_close_queue.is_empty()) {1014int idx = script_close_queue.front()->get();1015script_close_queue.pop_front();10161017tab_container->set_current_tab(idx);1018ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(idx));1019if (se) {1020// Maybe there are unsaved changes.1021if (se->is_unsaved()) {1022_ask_close_current_unsaved_tab(se);1023erase_tab_confirm->connect(SceneStringName(visibility_changed), callable_mp(this, &ScriptEditor::_queue_close_tabs), CONNECT_ONE_SHOT);1024break;1025}1026}10271028_close_current_tab(false, false);1029}1030_update_find_replace_bar();1031}10321033void ScriptEditor::_ask_close_current_unsaved_tab(ScriptEditorBase *current) {1034erase_tab_confirm->set_text(TTR("Close and save changes?") + "\n\"" + current->get_name() + "\"");1035erase_tab_confirm->popup_centered();1036}10371038void ScriptEditor::_resave_scripts(const String &p_str) {1039apply_scripts();10401041for (int i = 0; i < tab_container->get_tab_count(); i++) {1042ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));1043if (!se) {1044continue;1045}10461047Ref<Resource> scr = se->get_edited_resource();10481049if (scr->is_built_in()) {1050continue; // Internal script, who cares.1051}10521053if (trim_trailing_whitespace_on_save) {1054se->trim_trailing_whitespace();1055}10561057if (trim_final_newlines_on_save) {1058se->trim_final_newlines();1059}10601061if (convert_indent_on_save) {1062se->convert_indent();1063}10641065Ref<TextFile> text_file = scr;1066if (text_file.is_valid()) {1067se->apply_code();1068_save_text_file(text_file, text_file->get_path());1069break;1070} else {1071EditorNode::get_singleton()->save_resource(scr);1072}1073se->tag_saved_version();1074}10751076disk_changed->hide();1077}10781079void ScriptEditor::_res_saved_callback(const Ref<Resource> &p_res) {1080for (int i = 0; i < tab_container->get_tab_count(); i++) {1081ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));1082if (!se) {1083continue;1084}10851086Ref<Resource> scr = se->get_edited_resource();10871088if (scr == p_res) {1089se->tag_saved_version();1090}1091}10921093if (p_res.is_valid()) {1094// In case the Resource has built-in scripts.1095_mark_built_in_scripts_as_saved(p_res->get_path());1096}10971098_update_script_names();1099Ref<Script> scr = p_res;1100if (scr.is_valid()) {1101trigger_live_script_reload(scr->get_path());1102}1103}11041105void ScriptEditor::_scene_saved_callback(const String &p_path) {1106// If scene was saved, mark all built-in scripts from that scene as saved.1107_mark_built_in_scripts_as_saved(p_path);1108}11091110void ScriptEditor::_mark_built_in_scripts_as_saved(const String &p_parent_path) {1111for (int i = 0; i < tab_container->get_tab_count(); i++) {1112ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));1113if (!se) {1114continue;1115}11161117Ref<Resource> edited_res = se->get_edited_resource();11181119if (!edited_res->is_built_in()) {1120continue; // External script, who cares.1121}11221123if (edited_res->get_path().get_slice("::", 0) == p_parent_path) {1124se->tag_saved_version();1125}11261127Ref<Script> scr = edited_res;1128if (scr.is_valid()) {1129trigger_live_script_reload(scr->get_path());1130clear_docs_from_script(scr);1131scr->reload(true);1132update_docs_from_script(scr);1133}1134}1135}11361137void ScriptEditor::trigger_live_script_reload(const String &p_script_path) {1138if (!script_paths_to_reload.has(p_script_path)) {1139Ref<Script> reloaded_script = ResourceCache::get_ref(p_script_path);1140if (reloaded_script.is_null()) {1141reloaded_script = ResourceLoader::load(p_script_path);1142}1143if (reloaded_script.is_valid()) {1144if (!reloaded_script->get_language()->validate(reloaded_script->get_source_code(), p_script_path)) {1145// Script has errors, don't live reload.1146return;1147}1148}11491150script_paths_to_reload.append(p_script_path);1151}1152if (!pending_auto_reload && auto_reload_running_scripts) {1153callable_mp(this, &ScriptEditor::_live_auto_reload_running_scripts).call_deferred();1154pending_auto_reload = true;1155}1156}11571158void ScriptEditor::trigger_live_script_reload_all() {1159if (!pending_auto_reload && auto_reload_running_scripts) {1160call_deferred(SNAME("_live_auto_reload_running_scripts"));1161pending_auto_reload = true;1162reload_all_scripts = true;1163}1164}11651166void ScriptEditor::_live_auto_reload_running_scripts() {1167pending_auto_reload = false;1168if (reload_all_scripts) {1169EditorDebuggerNode::get_singleton()->reload_all_scripts();1170} else {1171EditorDebuggerNode::get_singleton()->reload_scripts(script_paths_to_reload);1172}1173reload_all_scripts = false;1174script_paths_to_reload.clear();1175}11761177bool ScriptEditor::_test_script_times_on_disk(Ref<Resource> p_for_script) {1178disk_changed_list->clear();1179TreeItem *r = disk_changed_list->create_item();11801181bool need_ask = false;1182bool need_reload = false;1183bool use_autoreload = EDITOR_GET("text_editor/behavior/files/auto_reload_scripts_on_external_change");11841185for (int i = 0; i < tab_container->get_tab_count(); i++) {1186ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));1187if (se) {1188Ref<Resource> edited_res = se->get_edited_resource();1189if (p_for_script.is_valid() && edited_res.is_valid() && p_for_script != edited_res) {1190continue;1191}11921193if (edited_res->is_built_in()) {1194continue; // Internal script, who cares.1195}11961197uint64_t last_date = se->edited_file_data.last_modified_time;1198uint64_t date = FileAccess::get_modified_time(se->edited_file_data.path);11991200if (last_date != date) {1201TreeItem *ti = disk_changed_list->create_item(r);1202ti->set_text(0, se->edited_file_data.path.get_file());12031204if (!use_autoreload || se->is_unsaved()) {1205need_ask = true;1206}1207need_reload = true;1208}1209}1210}12111212if (need_reload) {1213if (!need_ask) {1214script_editor->reload_scripts();1215need_reload = false;1216} else {1217callable_mp((Window *)disk_changed, &Window::popup_centered_ratio).call_deferred(0.3);1218}1219}12201221return need_reload;1222}12231224void _import_text_editor_theme(const String &p_file) {1225if (p_file.get_extension() != "tet") {1226EditorToaster::get_singleton()->popup_str(TTR("Importing theme failed. File is not a text editor theme file (.tet)."), EditorToaster::SEVERITY_ERROR);1227return;1228}1229const String theme_name = p_file.get_file().get_basename();1230if (EditorSettings::is_default_text_editor_theme(theme_name.to_lower())) {1231EditorToaster::get_singleton()->popup_str(TTR("Importing theme failed. File name cannot be 'Default', 'Custom', or 'Godot 2'."), EditorToaster::SEVERITY_ERROR);1232return;1233}12341235const String theme_dir = EditorPaths::get_singleton()->get_text_editor_themes_dir();1236Ref<DirAccess> d = DirAccess::open(theme_dir);1237Error err = FAILED;1238if (d.is_valid()) {1239err = d->copy(p_file, theme_dir.path_join(p_file.get_file()));1240}12411242if (err != OK) {1243EditorToaster::get_singleton()->popup_str(TTR("Importing theme failed. Failed to copy theme file."), EditorToaster::SEVERITY_ERROR);1244return;1245}12461247// Reload themes and switch to new theme.1248EditorSettings::get_singleton()->update_text_editor_themes_list();1249EditorSettings::get_singleton()->set_manually("text_editor/theme/color_theme", theme_name, true);1250EditorSettings::get_singleton()->notify_changes();1251}12521253void _save_text_editor_theme_as(const String &p_file) {1254String file = p_file;1255if (p_file.get_extension() != "tet") {1256file += ".tet";1257}12581259const String theme_name = file.get_file().get_basename();1260if (EditorSettings::is_default_text_editor_theme(theme_name.to_lower())) {1261EditorToaster::get_singleton()->popup_str(TTR("Saving theme failed. File name cannot be 'Default', 'Custom', or 'Godot 2'."), EditorToaster::SEVERITY_ERROR);1262return;1263}12641265const String theme_section = "color_theme";1266const Ref<ConfigFile> cf = memnew(ConfigFile);12671268// Use the keys from the Godot 2 theme to know which settings to save.1269HashMap<StringName, Color> text_colors = EditorSettings::get_godot2_text_editor_theme();1270text_colors.sort();1271for (const KeyValue<StringName, Color> &text_color : text_colors) {1272const Color val = EditorSettings::get_singleton()->get_setting(text_color.key);1273const String &key = text_color.key.operator String().replace("text_editor/theme/highlighting/", "");1274cf->set_value(theme_section, key, val.to_html());1275}12761277const Error err = cf->save(file);1278if (err != OK) {1279EditorToaster::get_singleton()->popup_str(TTR("Saving theme failed."), EditorToaster::SEVERITY_ERROR);1280return;1281}12821283// Reload themes and switch to saved theme.1284EditorSettings::get_singleton()->update_text_editor_themes_list();1285if (p_file.get_base_dir() == EditorPaths::get_singleton()->get_text_editor_themes_dir()) {1286// Don't need to emit signal or notify changes as the colors are already set.1287EditorSettings::get_singleton()->set_manually("text_editor/theme/color_theme", theme_name, false);1288}1289}12901291void ScriptEditor::_file_dialog_action(const String &p_file) {1292switch (file_dialog_option) {1293case FILE_MENU_NEW_TEXTFILE: {1294Error err;1295{1296Ref<FileAccess> file = FileAccess::open(p_file, FileAccess::WRITE, &err);1297if (err) {1298EditorNode::get_singleton()->show_warning(TTR("Error writing TextFile:") + "\n" + p_file, TTR("Error!"));1299break;1300}1301}13021303if (EditorFileSystem::get_singleton()) {1304if (textfile_extensions.has(p_file.get_extension())) {1305EditorFileSystem::get_singleton()->update_file(p_file);1306}1307}13081309if (!open_textfile_after_create) {1310return;1311}1312[[fallthrough]];1313}1314case FILE_MENU_OPEN: {1315open_file(p_file);1316file_dialog_option = -1;1317} break;1318case FILE_MENU_SAVE_AS: {1319ScriptEditorBase *current = _get_current_editor();1320if (current) {1321Ref<Resource> resource = current->get_edited_resource();1322String path = ProjectSettings::get_singleton()->localize_path(p_file);1323Error err = _save_text_file(resource, path);13241325if (err != OK) {1326EditorNode::get_singleton()->show_accept(TTR("Error saving file!"), TTR("OK"));1327return;1328}13291330resource->set_path(path);1331_update_script_names();1332}1333} break;1334case THEME_SAVE_AS: {1335_save_text_editor_theme_as(p_file);1336} break;1337case THEME_IMPORT: {1338_import_text_editor_theme(p_file);1339} break;1340}1341file_dialog_option = -1;1342}13431344Ref<Script> ScriptEditor::_get_current_script() {1345ScriptEditorBase *current = _get_current_editor();13461347if (current) {1348Ref<Script> scr = current->get_edited_resource();1349return scr.is_valid() ? scr : nullptr;1350} else {1351return nullptr;1352}1353}13541355TypedArray<Script> ScriptEditor::_get_open_scripts() const {1356TypedArray<Script> ret;1357Vector<Ref<Script>> scripts = get_open_scripts();1358int scripts_amount = scripts.size();1359for (int idx_script = 0; idx_script < scripts_amount; idx_script++) {1360ret.push_back(scripts[idx_script]);1361}1362return ret;1363}13641365bool ScriptEditor::toggle_files_panel() {1366list_split->set_visible(!list_split->is_visible());1367EditorSettings::get_singleton()->set_project_metadata("files_panel", "show_files_panel", list_split->is_visible());1368return list_split->is_visible();1369}13701371bool ScriptEditor::is_files_panel_toggled() {1372return list_split->is_visible();1373}13741375void ScriptEditor::_menu_option(int p_option) {1376ScriptEditorBase *current = _get_current_editor();1377switch (p_option) {1378case FILE_MENU_NEW: {1379script_create_dialog->config("Node", "new_script", false, false);1380script_create_dialog->popup_centered();1381} break;1382case FILE_MENU_NEW_TEXTFILE: {1383file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);1384file_dialog->set_access(EditorFileDialog::ACCESS_FILESYSTEM);1385file_dialog_option = FILE_MENU_NEW_TEXTFILE;13861387file_dialog->clear_filters();1388for (const String &E : textfile_extensions) {1389file_dialog->add_filter("*." + E, E.to_upper());1390}1391file_dialog->set_title(TTRC("New Text File..."));1392file_dialog->popup_file_dialog();1393open_textfile_after_create = true;1394} break;1395case FILE_MENU_OPEN: {1396file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);1397file_dialog->set_access(EditorFileDialog::ACCESS_FILESYSTEM);1398file_dialog_option = FILE_MENU_OPEN;13991400List<String> extensions;1401ResourceLoader::get_recognized_extensions_for_type("Script", &extensions);1402file_dialog->clear_filters();1403for (const String &extension : extensions) {1404file_dialog->add_filter("*." + extension, extension.to_upper());1405}14061407for (const String &E : textfile_extensions) {1408file_dialog->add_filter("*." + E, E.to_upper());1409}14101411file_dialog->set_title(TTRC("Open File"));1412file_dialog->popup_file_dialog();1413return;1414} break;1415case FILE_MENU_REOPEN_CLOSED: {1416if (previous_scripts.is_empty()) {1417return;1418}14191420String path = previous_scripts.back()->get();1421previous_scripts.pop_back();14221423List<String> extensions;1424ResourceLoader::get_recognized_extensions_for_type("Script", &extensions);1425ResourceLoader::get_recognized_extensions_for_type("JSON", &extensions);1426bool built_in = !path.is_resource_file();14271428if (extensions.find(path.get_extension()) || built_in) {1429if (built_in) {1430String res_path = path.get_slice("::", 0);1431EditorNode::get_singleton()->load_scene_or_resource(res_path, false, false);1432}14331434Ref<Resource> scr = ResourceLoader::load(path);1435if (scr.is_null()) {1436EditorNode::get_singleton()->show_warning(TTR("Could not load file at:") + "\n\n" + path, TTR("Error!"));1437file_dialog_option = -1;1438return;1439}14401441edit(scr);1442file_dialog_option = -1;1443} else {1444Error error;1445Ref<TextFile> text_file = _load_text_file(path, &error);1446if (error != OK) {1447EditorNode::get_singleton()->show_warning(TTR("Could not load file at:") + "\n\n" + path, TTR("Error!"));1448}14491450if (text_file.is_valid()) {1451edit(text_file);1452file_dialog_option = -1;1453}1454}1455} break;1456case FILE_MENU_SAVE_ALL: {1457if (_test_script_times_on_disk()) {1458return;1459}14601461save_all_scripts();1462} break;1463case SEARCH_IN_FILES: {1464open_find_in_files_dialog("");1465} break;1466case REPLACE_IN_FILES: {1467_on_replace_in_files_requested("");1468} break;1469case SEARCH_HELP: {1470help_search_dialog->popup_dialog();1471} break;1472case SEARCH_WEBSITE: {1473Control *tab = tab_container->get_current_tab_control();14741475EditorHelp *eh = Object::cast_to<EditorHelp>(tab);1476bool native_class_doc = false;1477if (eh) {1478const HashMap<String, DocData::ClassDoc>::ConstIterator E = EditorHelp::get_doc_data()->class_list.find(eh->get_class());1479native_class_doc = E && !E->value.is_script_doc;1480}1481if (native_class_doc) {1482String name = eh->get_class().to_lower();1483String doc_url = vformat(GODOT_VERSION_DOCS_URL "/classes/class_%s.html", name);1484OS::get_singleton()->shell_open(doc_url);1485} else {1486OS::get_singleton()->shell_open(GODOT_VERSION_DOCS_URL "/");1487}1488} break;1489case FILE_MENU_HISTORY_NEXT: {1490_history_forward();1491} break;1492case FILE_MENU_HISTORY_PREV: {1493_history_back();1494} break;1495case FILE_MENU_SORT: {1496_sort_list_on_update = true;1497_update_script_names();1498} break;1499case FILE_MENU_TOGGLE_FILES_PANEL: {1500toggle_files_panel();1501if (current) {1502current->update_toggle_files_button();1503} else {1504Control *tab = tab_container->get_current_tab_control();1505EditorHelp *editor_help = Object::cast_to<EditorHelp>(tab);1506if (editor_help) {1507editor_help->update_toggle_files_button();1508}1509}1510}1511}15121513if (p_option >= EditorContextMenuPlugin::BASE_ID) {1514Ref<Resource> resource;1515if (current) {1516resource = current->get_edited_resource();1517}1518EditorContextMenuPluginManager::get_singleton()->activate_custom_option(EditorContextMenuPlugin::CONTEXT_SLOT_SCRIPT_EDITOR, p_option, resource);1519return;1520}15211522if (current) {1523switch (p_option) {1524case FILE_MENU_SAVE: {1525save_current_script();1526} break;1527case FILE_MENU_SAVE_AS: {1528if (trim_trailing_whitespace_on_save) {1529current->trim_trailing_whitespace();1530}15311532if (trim_final_newlines_on_save) {1533current->trim_final_newlines();1534}15351536if (convert_indent_on_save) {1537current->convert_indent();1538}15391540Ref<Resource> resource = current->get_edited_resource();1541Ref<TextFile> text_file = resource;1542Ref<Script> scr = resource;15431544if (text_file.is_valid()) {1545file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);1546file_dialog->set_access(EditorFileDialog::ACCESS_FILESYSTEM);1547file_dialog_option = FILE_MENU_SAVE_AS;15481549List<String> extensions;1550ResourceLoader::get_recognized_extensions_for_type("Script", &extensions);1551file_dialog->clear_filters();1552file_dialog->set_current_dir(text_file->get_path().get_base_dir());1553file_dialog->set_current_file(text_file->get_path().get_file());1554file_dialog->set_title(TTRC("Save File As..."));1555file_dialog->popup_file_dialog();1556break;1557}15581559if (scr.is_valid()) {1560clear_docs_from_script(scr);1561}15621563EditorNode::get_singleton()->push_item(resource.ptr());1564EditorNode::get_singleton()->save_resource_as(resource);15651566if (scr.is_valid()) {1567update_docs_from_script(scr);1568}1569} break;15701571case FILE_MENU_SOFT_RELOAD_TOOL: {1572Ref<Script> scr = current->get_edited_resource();1573if (scr.is_null()) {1574EditorNode::get_singleton()->show_warning(TTR("Can't obtain the script for reloading."));1575break;1576}1577if (!scr->is_tool()) {1578EditorNode::get_singleton()->show_warning(TTR("Reload only takes effect on tool scripts."));1579return;1580}1581scr->reload(true);15821583} break;15841585case FILE_MENU_RUN: {1586Ref<Script> scr = current->get_edited_resource();1587if (scr.is_null()) {1588EditorToaster::get_singleton()->popup_str(TTR("Cannot run the edited file because it's not a script."), EditorToaster::SEVERITY_WARNING);1589break;1590}15911592current->apply_code();15931594EditorNode::get_singleton()->run_editor_script(scr);1595} break;15961597case FILE_MENU_CLOSE: {1598if (current->is_unsaved()) {1599_ask_close_current_unsaved_tab(current);1600} else {1601_close_current_tab(false);1602}1603} break;1604case FILE_MENU_COPY_PATH: {1605_copy_script_path();1606} break;1607case FILE_MENU_COPY_UID: {1608_copy_script_uid();1609} break;1610case FILE_MENU_SHOW_IN_FILE_SYSTEM: {1611const Ref<Resource> scr = current->get_edited_resource();1612String path = scr->get_path();1613if (!path.is_empty()) {1614if (scr->is_built_in()) {1615path = path.get_slice("::", 0); // Show the scene instead.1616}16171618FileSystemDock::get_singleton()->navigate_to_path(path);1619}1620} break;1621case FILE_MENU_CLOSE_DOCS: {1622_close_docs_tab();1623} break;1624case FILE_MENU_CLOSE_OTHER_TABS: {1625_close_other_tabs();1626} break;1627case FILE_MENU_CLOSE_TABS_BELOW: {1628_close_tabs_below();1629} break;1630case FILE_MENU_CLOSE_ALL: {1631_close_all_tabs();1632} break;1633case FILE_MENU_MOVE_UP: {1634if (tab_container->get_current_tab() > 0) {1635tab_container->move_child(current, tab_container->get_current_tab() - 1);1636tab_container->set_current_tab(tab_container->get_current_tab());1637_update_script_names();1638}1639} break;1640case FILE_MENU_MOVE_DOWN: {1641if (tab_container->get_current_tab() < tab_container->get_tab_count() - 1) {1642tab_container->move_child(current, tab_container->get_current_tab() + 1);1643tab_container->set_current_tab(tab_container->get_current_tab());1644_update_script_names();1645}1646} break;1647}1648} else {1649EditorHelp *help = Object::cast_to<EditorHelp>(tab_container->get_current_tab_control());1650if (help) {1651switch (p_option) {1652case HELP_SEARCH_FIND: {1653help->popup_search();1654} break;1655case HELP_SEARCH_FIND_NEXT: {1656help->search_again();1657} break;1658case HELP_SEARCH_FIND_PREVIOUS: {1659help->search_again(true);1660} break;1661case FILE_MENU_CLOSE: {1662_close_current_tab();1663} break;1664case FILE_MENU_CLOSE_DOCS: {1665_close_docs_tab();1666} break;1667case FILE_MENU_CLOSE_OTHER_TABS: {1668_close_other_tabs();1669} break;1670case FILE_MENU_CLOSE_TABS_BELOW: {1671_close_tabs_below();1672} break;1673case FILE_MENU_CLOSE_ALL: {1674_close_all_tabs();1675} break;1676case FILE_MENU_MOVE_UP: {1677if (tab_container->get_current_tab() > 0) {1678tab_container->move_child(help, tab_container->get_current_tab() - 1);1679tab_container->set_current_tab(tab_container->get_current_tab());1680_update_script_names();1681}1682} break;1683case FILE_MENU_MOVE_DOWN: {1684if (tab_container->get_current_tab() < tab_container->get_tab_count() - 1) {1685tab_container->move_child(help, tab_container->get_current_tab() + 1);1686tab_container->set_current_tab(tab_container->get_current_tab());1687_update_script_names();1688}1689} break;1690}1691}1692}1693}16941695void ScriptEditor::_theme_option(int p_option) {1696switch (p_option) {1697case THEME_IMPORT: {1698file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);1699file_dialog->set_access(EditorFileDialog::ACCESS_FILESYSTEM);1700file_dialog_option = THEME_IMPORT;1701file_dialog->clear_filters();1702file_dialog->add_filter("*.tet");1703file_dialog->set_title(TTRC("Import Theme"));1704file_dialog->popup_file_dialog();1705} break;1706case THEME_RELOAD: {1707EditorSettings::get_singleton()->mark_setting_changed("text_editor/theme/color_theme");1708EditorSettings::get_singleton()->notify_changes();1709} break;1710case THEME_SAVE_AS: {1711ScriptEditor::_show_save_theme_as_dialog();1712} break;1713}1714}17151716void ScriptEditor::_show_save_theme_as_dialog() {1717file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);1718file_dialog->set_access(EditorFileDialog::ACCESS_FILESYSTEM);1719file_dialog_option = THEME_SAVE_AS;1720file_dialog->clear_filters();1721file_dialog->add_filter("*.tet");1722file_dialog->set_current_path(EditorPaths::get_singleton()->get_text_editor_themes_dir().path_join(EDITOR_GET("text_editor/theme/color_theme")) + " New");1723file_dialog->set_title(TTRC("Save Theme As..."));1724file_dialog->popup_file_dialog();1725}17261727bool ScriptEditor::_has_docs_tab() const {1728const int child_count = tab_container->get_tab_count();1729for (int i = 0; i < child_count; i++) {1730if (Object::cast_to<EditorHelp>(tab_container->get_tab_control(i))) {1731return true;1732}1733}1734return false;1735}17361737bool ScriptEditor::_has_script_tab() const {1738const int child_count = tab_container->get_tab_count();1739for (int i = 0; i < child_count; i++) {1740if (Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i))) {1741return true;1742}1743}1744return false;1745}17461747void ScriptEditor::_prepare_file_menu() {1748PopupMenu *menu = file_menu->get_popup();1749ScriptEditorBase *editor = _get_current_editor();1750const Ref<Resource> res = editor ? editor->get_edited_resource() : Ref<Resource>();17511752menu->set_item_disabled(menu->get_item_index(FILE_MENU_REOPEN_CLOSED), previous_scripts.is_empty());17531754menu->set_item_disabled(menu->get_item_index(FILE_MENU_SAVE), res.is_null());1755menu->set_item_disabled(menu->get_item_index(FILE_MENU_SAVE_AS), res.is_null());1756menu->set_item_disabled(menu->get_item_index(FILE_MENU_SAVE_ALL), !_has_script_tab());17571758menu->set_item_disabled(menu->get_item_index(FILE_MENU_SOFT_RELOAD_TOOL), res.is_null());1759menu->set_item_disabled(menu->get_item_index(FILE_MENU_COPY_PATH), res.is_null() || res->get_path().is_empty());1760menu->set_item_disabled(menu->get_item_index(FILE_MENU_COPY_UID), res.is_null() || ResourceLoader::get_resource_uid(res->get_path()) == ResourceUID::INVALID_ID);1761menu->set_item_disabled(menu->get_item_index(FILE_MENU_SHOW_IN_FILE_SYSTEM), res.is_null());17621763menu->set_item_disabled(menu->get_item_index(FILE_MENU_HISTORY_PREV), history_pos <= 0);1764menu->set_item_disabled(menu->get_item_index(FILE_MENU_HISTORY_NEXT), history_pos >= history.size() - 1);17651766menu->set_item_disabled(menu->get_item_index(FILE_MENU_CLOSE), tab_container->get_tab_count() < 1);1767menu->set_item_disabled(menu->get_item_index(FILE_MENU_CLOSE_ALL), tab_container->get_tab_count() < 1);1768menu->set_item_disabled(menu->get_item_index(FILE_MENU_CLOSE_OTHER_TABS), tab_container->get_tab_count() <= 1);1769menu->set_item_disabled(menu->get_item_index(FILE_MENU_CLOSE_DOCS), !_has_docs_tab());17701771menu->set_item_disabled(menu->get_item_index(FILE_MENU_RUN), res.is_null());1772}17731774void ScriptEditor::_file_menu_closed() {1775PopupMenu *menu = file_menu->get_popup();17761777menu->set_item_disabled(menu->get_item_index(FILE_MENU_REOPEN_CLOSED), false);17781779menu->set_item_disabled(menu->get_item_index(FILE_MENU_SAVE), false);1780menu->set_item_disabled(menu->get_item_index(FILE_MENU_SAVE_AS), false);1781menu->set_item_disabled(menu->get_item_index(FILE_MENU_SAVE_ALL), false);17821783menu->set_item_disabled(menu->get_item_index(FILE_MENU_SOFT_RELOAD_TOOL), false);1784menu->set_item_disabled(menu->get_item_index(FILE_MENU_COPY_PATH), false);1785menu->set_item_disabled(menu->get_item_index(FILE_MENU_SHOW_IN_FILE_SYSTEM), false);17861787menu->set_item_disabled(menu->get_item_index(FILE_MENU_HISTORY_PREV), false);1788menu->set_item_disabled(menu->get_item_index(FILE_MENU_HISTORY_NEXT), false);17891790menu->set_item_disabled(menu->get_item_index(FILE_MENU_CLOSE), false);1791menu->set_item_disabled(menu->get_item_index(FILE_MENU_CLOSE_ALL), false);1792menu->set_item_disabled(menu->get_item_index(FILE_MENU_CLOSE_OTHER_TABS), false);1793menu->set_item_disabled(menu->get_item_index(FILE_MENU_CLOSE_DOCS), false);17941795menu->set_item_disabled(menu->get_item_index(FILE_MENU_RUN), false);1796}17971798void ScriptEditor::_tab_changed(int p_which) {1799ensure_select_current();1800}18011802void ScriptEditor::_notification(int p_what) {1803switch (p_what) {1804case NOTIFICATION_ENTER_TREE: {1805EditorRunBar::get_singleton()->connect("stop_pressed", callable_mp(this, &ScriptEditor::_editor_stop));1806_apply_editor_settings();1807[[fallthrough]];1808}18091810case NOTIFICATION_TRANSLATION_CHANGED: {1811_update_online_doc();1812if (!make_floating->is_disabled()) {1813// Override default ScreenSelect tooltip if multi-window support is available.1814make_floating->set_tooltip_text(TTR("Make the script editor floating.") + "\n" + TTR("Right-click to open the screen selector."));1815}1816[[fallthrough]];1817}1818case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:1819case NOTIFICATION_THEME_CHANGED: {1820tab_container->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("ScriptEditor"), EditorStringName(EditorStyles)));18211822help_search->set_button_icon(get_editor_theme_icon(SNAME("HelpSearch")));1823site_search->set_button_icon(get_editor_theme_icon(SNAME("ExternalLink")));18241825if (is_layout_rtl()) {1826script_forward->set_button_icon(get_editor_theme_icon(SNAME("Back")));1827script_back->set_button_icon(get_editor_theme_icon(SNAME("Forward")));1828} else {1829script_forward->set_button_icon(get_editor_theme_icon(SNAME("Forward")));1830script_back->set_button_icon(get_editor_theme_icon(SNAME("Back")));1831}18321833members_overview_alphabeta_sort_button->set_button_icon(get_editor_theme_icon(SNAME("Sort")));18341835filter_scripts->set_right_icon(get_editor_theme_icon(SNAME("Search")));1836filter_methods->set_right_icon(get_editor_theme_icon(SNAME("Search")));18371838filename->add_theme_style_override(CoreStringName(normal), get_theme_stylebox(CoreStringName(normal), SNAME("LineEdit")));18391840recent_scripts->reset_size();18411842if (is_inside_tree()) {1843_update_script_names();1844}1845} break;18461847case NOTIFICATION_READY: {1848// Can't set own styles in NOTIFICATION_THEME_CHANGED, so for now this will do.1849add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("ScriptEditorPanel"), EditorStringName(EditorStyles)));18501851get_tree()->connect("tree_changed", callable_mp(this, &ScriptEditor::_tree_changed));1852InspectorDock::get_singleton()->connect("request_help", callable_mp(this, &ScriptEditor::_help_class_open));1853EditorNode::get_singleton()->connect("request_help_search", callable_mp(this, &ScriptEditor::_help_search));1854EditorNode::get_singleton()->connect("scene_closed", callable_mp(this, &ScriptEditor::_close_builtin_scripts_from_scene));1855EditorNode::get_singleton()->connect("script_add_function_request", callable_mp(this, &ScriptEditor::_add_callback));1856EditorNode::get_singleton()->connect("resource_saved", callable_mp(this, &ScriptEditor::_res_saved_callback));1857EditorNode::get_singleton()->connect("scene_saved", callable_mp(this, &ScriptEditor::_scene_saved_callback));1858FileSystemDock::get_singleton()->connect("files_moved", callable_mp(this, &ScriptEditor::_files_moved));1859FileSystemDock::get_singleton()->connect("file_removed", callable_mp(this, &ScriptEditor::_file_removed));1860script_list->connect(SceneStringName(item_selected), callable_mp(this, &ScriptEditor::_script_selected));18611862members_overview->connect(SceneStringName(item_selected), callable_mp(this, &ScriptEditor::_members_overview_selected));1863help_overview->connect(SceneStringName(item_selected), callable_mp(this, &ScriptEditor::_help_overview_selected));1864script_split->connect("dragged", callable_mp(this, &ScriptEditor::_split_dragged));1865list_split->connect("dragged", callable_mp(this, &ScriptEditor::_split_dragged));18661867EditorSettings::get_singleton()->connect("settings_changed", callable_mp(this, &ScriptEditor::_editor_settings_changed));1868EditorFileSystem::get_singleton()->connect("filesystem_changed", callable_mp(this, &ScriptEditor::_filesystem_changed));1869} break;18701871case NOTIFICATION_EXIT_TREE: {1872EditorRunBar::get_singleton()->disconnect("stop_pressed", callable_mp(this, &ScriptEditor::_editor_stop));1873} break;18741875case NOTIFICATION_APPLICATION_FOCUS_IN: {1876_test_script_times_on_disk();1877_update_modified_scripts_for_external_editor();1878} break;1879}1880}18811882void ScriptEditor::_close_builtin_scripts_from_scene(const String &p_scene) {1883for (int i = 0; i < tab_container->get_tab_count(); i++) {1884ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));18851886if (se) {1887Ref<Script> scr = se->get_edited_resource();1888if (scr.is_null()) {1889continue;1890}18911892if (scr->is_built_in() && scr->get_path().begins_with(p_scene)) { // Is an internal script and belongs to scene being closed.1893_close_tab(i, false);1894i--;1895}1896}1897}1898}18991900void ScriptEditor::edited_scene_changed() {1901_update_modified_scripts_for_external_editor();1902}19031904void ScriptEditor::notify_script_close(const Ref<Script> &p_script) {1905emit_signal(SNAME("script_close"), p_script);1906}19071908void ScriptEditor::notify_script_changed(const Ref<Script> &p_script) {1909emit_signal(SNAME("editor_script_changed"), p_script);1910}19111912Vector<String> ScriptEditor::_get_breakpoints() {1913Vector<String> ret;1914HashSet<String> loaded_scripts;1915for (int i = 0; i < tab_container->get_tab_count(); i++) {1916ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));1917if (!se) {1918continue;1919}19201921Ref<Script> scr = se->get_edited_resource();1922if (scr.is_null()) {1923continue;1924}19251926String base = scr->get_path();1927loaded_scripts.insert(base);1928if (base.is_empty() || base.begins_with("local://")) {1929continue;1930}19311932PackedInt32Array bpoints = se->get_breakpoints();1933for (int32_t bpoint : bpoints) {1934ret.push_back(base + ":" + itos((int)bpoint + 1));1935}1936}19371938// Load breakpoints that are in closed scripts.1939Vector<String> cached_editors = script_editor_cache->get_sections();1940for (const String &E : cached_editors) {1941if (loaded_scripts.has(E)) {1942continue;1943}19441945Array breakpoints = _get_cached_breakpoints_for_script(E);1946for (int breakpoint : breakpoints) {1947ret.push_back(E + ":" + itos((int)breakpoint + 1));1948}1949}1950return ret;1951}19521953void ScriptEditor::get_breakpoints(List<String> *p_breakpoints) {1954HashSet<String> loaded_scripts;1955for (int i = 0; i < tab_container->get_tab_count(); i++) {1956ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));1957if (!se) {1958continue;1959}19601961Ref<Script> scr = se->get_edited_resource();1962if (scr.is_null()) {1963continue;1964}19651966String base = scr->get_path();1967loaded_scripts.insert(base);1968if (base.is_empty() || base.begins_with("local://")) {1969continue;1970}19711972PackedInt32Array bpoints = se->get_breakpoints();1973for (int32_t bpoint : bpoints) {1974p_breakpoints->push_back(base + ":" + itos((int)bpoint + 1));1975}1976}19771978// Load breakpoints that are in closed scripts.1979Vector<String> cached_editors = script_editor_cache->get_sections();1980for (const String &E : cached_editors) {1981if (loaded_scripts.has(E)) {1982continue;1983}19841985Array breakpoints = _get_cached_breakpoints_for_script(E);1986for (int breakpoint : breakpoints) {1987p_breakpoints->push_back(E + ":" + itos((int)breakpoint + 1));1988}1989}1990}19911992void ScriptEditor::_members_overview_selected(int p_idx) {1993int line = members_overview->get_item_metadata(p_idx);1994ScriptEditorBase *current = _get_current_editor();1995if (ScriptTextEditor *script_text_editor = Object::cast_to<ScriptTextEditor>(current)) {1996script_text_editor->goto_line_centered(line);1997} else if (current) {1998current->goto_line(line);1999}2000}20012002void ScriptEditor::_help_overview_selected(int p_idx) {2003Node *current = tab_container->get_tab_control(tab_container->get_current_tab());2004EditorHelp *se = Object::cast_to<EditorHelp>(current);2005if (!se) {2006return;2007}2008se->scroll_to_section(help_overview->get_item_metadata(p_idx));2009}20102011void ScriptEditor::_script_selected(int p_idx) {2012grab_focus_block = !Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT); //amazing hack, simply amazing20132014_go_to_tab(script_list->get_item_metadata(p_idx));2015grab_focus_block = false;2016}20172018void ScriptEditor::ensure_select_current() {2019if (tab_container->get_tab_count() && tab_container->get_current_tab() >= 0) {2020ScriptEditorBase *se = _get_current_editor();2021if (se) {2022se->enable_editor(this);20232024if (!grab_focus_block && is_visible_in_tree()) {2025se->ensure_focus();2026}2027}2028}2029_update_find_replace_bar();20302031_update_selected_editor_menu();2032}20332034bool ScriptEditor::is_editor_floating() {2035return is_floating;2036}20372038void ScriptEditor::_find_scripts(Node *p_base, Node *p_current, HashSet<Ref<Script>> &used) {2039if (p_current != p_base && p_current->get_owner() != p_base) {2040return;2041}20422043if (p_current->get_script_instance()) {2044Ref<Script> scr = p_current->get_script();2045if (scr.is_valid()) {2046used.insert(scr);2047}2048}20492050for (int i = 0; i < p_current->get_child_count(); i++) {2051_find_scripts(p_base, p_current->get_child(i), used);2052}2053}20542055struct _ScriptEditorItemData {2056String name;2057String sort_key;2058Ref<Texture2D> icon;2059bool tool = false;2060int index = 0;2061String tooltip;2062bool used = false;2063int category = 0;2064Node *ref = nullptr;20652066bool operator<(const _ScriptEditorItemData &id) const {2067if (category == id.category) {2068if (sort_key == id.sort_key) {2069return index < id.index;2070} else {2071return sort_key.filenocasecmp_to(id.sort_key) < 0;2072}2073} else {2074return category < id.category;2075}2076}2077};20782079void ScriptEditor::_update_members_overview_visibility() {2080ScriptEditorBase *se = _get_current_editor();2081if (!se) {2082members_overview_alphabeta_sort_button->set_visible(false);2083members_overview->set_visible(false);20842085Node *current = tab_container->get_tab_control(tab_container->get_current_tab());2086EditorHelp *editor_help = Object::cast_to<EditorHelp>(current);2087overview_vbox->set_visible(help_overview_enabled && editor_help);2088return;2089}20902091if (members_overview_enabled && se->show_members_overview()) {2092members_overview_alphabeta_sort_button->set_visible(true);2093filter_methods->set_visible(true);2094members_overview->set_visible(true);2095overview_vbox->set_visible(true);2096} else {2097members_overview_alphabeta_sort_button->set_visible(false);2098filter_methods->set_visible(false);2099members_overview->set_visible(false);2100overview_vbox->set_visible(false);2101}2102}21032104void ScriptEditor::_toggle_members_overview_alpha_sort(bool p_alphabetic_sort) {2105EditorSettings::get_singleton()->set("text_editor/script_list/sort_members_outline_alphabetically", p_alphabetic_sort);2106_update_members_overview();2107}21082109void ScriptEditor::_update_members_overview() {2110members_overview->clear();21112112ScriptEditorBase *se = _get_current_editor();2113if (!se) {2114return;2115}21162117Vector<String> functions = se->get_functions();2118if (EDITOR_GET("text_editor/script_list/sort_members_outline_alphabetically")) {2119functions.sort();2120}21212122String filter = filter_methods->get_text();2123if (filter.is_empty()) {2124for (int i = 0; i < functions.size(); i++) {2125String name = functions[i].get_slicec(':', 0);2126members_overview->add_item(name);2127members_overview->set_item_metadata(-1, functions[i].get_slicec(':', 1).to_int() - 1);2128}2129} else {2130PackedStringArray search_names;2131for (int i = 0; i < functions.size(); i++) {2132search_names.append(functions[i].get_slicec(':', 0));2133}21342135Vector<FuzzySearchResult> results;2136FuzzySearch fuzzy;2137fuzzy.set_query(filter, false);2138fuzzy.search_all(search_names, results);21392140for (const FuzzySearchResult &res : results) {2141String name = functions[res.original_index].get_slicec(':', 0);2142int line = functions[res.original_index].get_slicec(':', 1).to_int() - 1;2143members_overview->add_item(name);2144members_overview->set_item_metadata(-1, line);2145}2146}21472148String path = se->get_edited_resource()->get_path();2149bool built_in = !path.is_resource_file();2150String name = built_in ? path.get_file() : se->get_name();2151filename->set_text(name);2152}21532154void ScriptEditor::_update_help_overview_visibility() {2155int selected = tab_container->get_current_tab();2156if (selected < 0 || selected >= tab_container->get_tab_count()) {2157help_overview->set_visible(false);2158return;2159}21602161Node *current = tab_container->get_tab_control(tab_container->get_current_tab());2162EditorHelp *se = Object::cast_to<EditorHelp>(current);2163if (!se) {2164help_overview->set_visible(false);2165return;2166}21672168if (help_overview_enabled) {2169members_overview_alphabeta_sort_button->set_visible(false);2170filter_methods->set_visible(false);2171help_overview->set_visible(true);2172overview_vbox->set_visible(true);2173filename->set_text(se->get_name());2174} else {2175help_overview->set_visible(false);2176overview_vbox->set_visible(false);2177}2178}21792180void ScriptEditor::_update_help_overview() {2181help_overview->clear();21822183int selected = tab_container->get_current_tab();2184if (selected < 0 || selected >= tab_container->get_tab_count()) {2185return;2186}21872188Node *current = tab_container->get_tab_control(tab_container->get_current_tab());2189EditorHelp *se = Object::cast_to<EditorHelp>(current);2190if (!se) {2191return;2192}21932194Vector<Pair<String, int>> sections = se->get_sections();2195for (int i = 0; i < sections.size(); i++) {2196help_overview->add_item(sections[i].first);2197help_overview->set_item_metadata(i, sections[i].second);2198}2199}22002201void ScriptEditor::_update_online_doc() {2202Node *current = tab_container->get_tab_control(tab_container->get_current_tab());22032204EditorHelp *eh = Object::cast_to<EditorHelp>(current);2205bool native_class_doc = false;2206if (eh) {2207const HashMap<String, DocData::ClassDoc>::ConstIterator E = EditorHelp::get_doc_data()->class_list.find(eh->get_class());2208native_class_doc = E && !E->value.is_script_doc;2209}2210if (native_class_doc) {2211String name = eh->get_class();2212String tooltip = vformat(TTR("Open '%s' in Godot online documentation."), name);2213site_search->set_text(TTRC("Open in Online Docs"));2214site_search->set_tooltip_text(tooltip);2215} else {2216site_search->set_text(TTRC("Online Docs"));2217site_search->set_tooltip_text(TTRC("Open Godot online documentation."));2218}2219}22202221void ScriptEditor::_update_script_colors() {2222bool script_temperature_enabled = EDITOR_GET("text_editor/script_list/script_temperature_enabled");22232224int hist_size = EDITOR_GET("text_editor/script_list/script_temperature_history_size");2225Color hot_color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));2226hot_color.set_s(hot_color.get_s() * 0.9);2227Color cold_color = get_theme_color(SceneStringName(font_color), EditorStringName(Editor));22282229for (int i = 0; i < script_list->get_item_count(); i++) {2230int c = script_list->get_item_metadata(i);2231Node *n = tab_container->get_tab_control(c);2232if (!n) {2233continue;2234}22352236if (script_temperature_enabled) {2237int pass = n->get_meta("__editor_pass", -1);2238if (pass < 0) {2239continue;2240}22412242int h = edit_pass - pass;2243if (h > hist_size) {2244continue;2245}2246int non_zero_hist_size = (hist_size == 0) ? 1 : hist_size;2247float v = Math::ease((edit_pass - pass) / float(non_zero_hist_size), 0.4);22482249script_list->set_item_custom_fg_color(i, hot_color.lerp(cold_color, v));2250}2251}2252}22532254void ScriptEditor::_update_script_names() {2255if (restoring_layout) {2256return;2257}22582259HashSet<Ref<Script>> used;2260Node *edited = EditorNode::get_singleton()->get_edited_scene();2261if (edited && EDITOR_GET("text_editor/script_list/highlight_scene_scripts")) {2262_find_scripts(edited, edited, used);2263}22642265script_list->clear();2266bool split_script_help = EDITOR_GET("text_editor/script_list/group_help_pages");2267ScriptSortBy sort_by = (ScriptSortBy)(int)EDITOR_GET("text_editor/script_list/sort_scripts_by");2268ScriptListName display_as = (ScriptListName)(int)EDITOR_GET("text_editor/script_list/list_script_names_as");22692270Vector<_ScriptEditorItemData> sedata;22712272for (int i = 0; i < tab_container->get_tab_count(); i++) {2273ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));2274if (se) {2275Ref<Texture2D> icon = se->get_theme_icon();2276String path = se->get_edited_resource()->get_path();2277bool saved = !path.is_empty();2278String name = se->get_name();2279Ref<Script> scr = se->get_edited_resource();22802281_ScriptEditorItemData sd;2282sd.icon = icon;2283sd.name = name;2284sd.tooltip = saved ? path : TTR("Unsaved file.");2285sd.index = i;2286sd.used = used.has(se->get_edited_resource());2287sd.category = 0;2288sd.ref = se;2289if (scr.is_valid()) {2290sd.tool = scr->is_tool();2291}22922293switch (sort_by) {2294case SORT_BY_NAME: {2295sd.sort_key = name.to_lower();2296} break;2297case SORT_BY_PATH: {2298sd.sort_key = path;2299} break;2300case SORT_BY_NONE: {2301sd.sort_key = "";2302} break;2303}23042305switch (display_as) {2306case DISPLAY_NAME: {2307sd.name = name;2308} break;2309case DISPLAY_DIR_AND_NAME: {2310if (!path.get_base_dir().get_file().is_empty()) {2311sd.name = path.get_base_dir().get_file().path_join(name);2312} else {2313sd.name = name;2314}2315} break;2316case DISPLAY_FULL_PATH: {2317sd.name = path;2318} break;2319}2320if (!saved) {2321sd.name = se->get_name();2322}23232324sedata.push_back(sd);2325}23262327EditorHelp *eh = Object::cast_to<EditorHelp>(tab_container->get_tab_control(i));2328if (eh && !eh->get_class().is_empty()) {2329String name = eh->get_class().unquote();2330Ref<Texture2D> icon = get_editor_theme_icon(SNAME("Help"));2331String tooltip = vformat(TTR("%s Class Reference"), name);23322333_ScriptEditorItemData sd;2334sd.icon = icon;2335sd.name = name;2336sd.sort_key = name.to_lower();2337sd.tooltip = tooltip;2338sd.index = i;2339sd.used = false;2340sd.category = split_script_help ? 1 : 0;2341sd.ref = eh;23422343sedata.push_back(sd);2344}2345}23462347Vector<String> disambiguated_script_names;2348Vector<String> full_script_paths;2349for (int j = 0; j < sedata.size(); j++) {2350String name = sedata[j].name.replace("(*)", "");2351ScriptListName script_display = (ScriptListName)(int)EDITOR_GET("text_editor/script_list/list_script_names_as");2352switch (script_display) {2353case DISPLAY_NAME: {2354name = name.get_file();2355} break;2356case DISPLAY_DIR_AND_NAME: {2357name = name.get_base_dir().get_file().path_join(name.get_file());2358} break;2359default:2360break;2361}23622363disambiguated_script_names.append(name);2364full_script_paths.append(sedata[j].tooltip);2365}23662367EditorNode::disambiguate_filenames(full_script_paths, disambiguated_script_names);23682369for (int j = 0; j < sedata.size(); j++) {2370if (sedata[j].name.ends_with("(*)")) {2371sedata.write[j].name = disambiguated_script_names[j] + "(*)";2372} else {2373sedata.write[j].name = disambiguated_script_names[j];2374}2375}23762377if (_sort_list_on_update && !sedata.is_empty()) {2378sedata.sort();23792380// change actual order of tab_container so that the order can be rearranged by user2381int cur_tab = tab_container->get_current_tab();2382int prev_tab = tab_container->get_previous_tab();2383int new_cur_tab = -1;2384int new_prev_tab = -1;2385for (int i = 0; i < sedata.size(); i++) {2386tab_container->move_child(sedata[i].ref, i);2387if (new_prev_tab == -1 && sedata[i].index == prev_tab) {2388new_prev_tab = i;2389}2390if (new_cur_tab == -1 && sedata[i].index == cur_tab) {2391new_cur_tab = i;2392}2393// Update index of sd entries for sorted order2394_ScriptEditorItemData sd = sedata[i];2395sd.index = i;2396sedata.set(i, sd);2397}23982399lock_history = true;2400_go_to_tab(new_prev_tab);2401_go_to_tab(new_cur_tab);2402lock_history = false;2403_sort_list_on_update = false;2404}24052406Vector<_ScriptEditorItemData> sedata_filtered;24072408String filter = filter_scripts->get_text();24092410if (filter.is_empty()) {2411sedata_filtered = sedata;2412} else {2413PackedStringArray search_names;2414for (int i = 0; i < sedata.size(); i++) {2415search_names.append(sedata[i].name);2416}24172418Vector<FuzzySearchResult> results;2419FuzzySearch fuzzy;2420fuzzy.set_query(filter, false);2421fuzzy.search_all(search_names, results);24222423for (const FuzzySearchResult &res : results) {2424sedata_filtered.push_back(sedata[res.original_index]);2425}2426}24272428Color tool_color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));2429tool_color.set_s(tool_color.get_s() * 1.5);2430for (int i = 0; i < sedata_filtered.size(); i++) {2431script_list->add_item(sedata_filtered[i].name, sedata_filtered[i].icon);2432if (sedata_filtered[i].tool) {2433script_list->set_item_icon_modulate(-1, tool_color);2434}24352436int index = script_list->get_item_count() - 1;2437script_list->set_item_tooltip(index, sedata_filtered[i].tooltip);2438script_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 */2439if (sedata_filtered[i].used) {2440script_list->set_item_custom_bg_color(index, Color(.5, .5, .5, .125));2441}2442if (tab_container->get_current_tab() == sedata_filtered[i].index) {2443script_list->select(index);24442445script_name_label->set_text(sedata_filtered[i].name);2446script_icon->set_texture(sedata_filtered[i].icon);24472448ScriptEditorBase *se = _get_current_editor();2449if (se) {2450se->enable_editor(this);2451_update_selected_editor_menu();2452}2453}2454}24552456if (!waiting_update_names) {2457_update_members_overview();2458_update_help_overview();2459} else {2460waiting_update_names = false;2461}2462_update_members_overview_visibility();2463_update_help_overview_visibility();2464_update_script_colors();2465}24662467Ref<TextFile> ScriptEditor::_load_text_file(const String &p_path, Error *r_error) const {2468if (r_error) {2469*r_error = ERR_FILE_CANT_OPEN;2470}24712472String local_path = ProjectSettings::get_singleton()->localize_path(p_path);2473String path = ResourceLoader::path_remap(local_path);24742475TextFile *text_file = memnew(TextFile);2476Ref<TextFile> text_res(text_file);2477Error err = text_file->load_text(path);24782479ERR_FAIL_COND_V_MSG(err != OK, Ref<Resource>(), "Cannot load text file '" + path + "'.");24802481text_file->set_file_path(local_path);2482text_file->set_path(local_path, true);24832484if (ResourceLoader::get_timestamp_on_load()) {2485text_file->set_last_modified_time(FileAccess::get_modified_time(path));2486}24872488if (r_error) {2489*r_error = OK;2490}24912492return text_res;2493}24942495Error ScriptEditor::_save_text_file(Ref<TextFile> p_text_file, const String &p_path) {2496Ref<TextFile> sqscr = p_text_file;2497ERR_FAIL_COND_V(sqscr.is_null(), ERR_INVALID_PARAMETER);24982499String source = sqscr->get_text();25002501Error err;2502{2503Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::WRITE, &err);25042505ERR_FAIL_COND_V_MSG(err, err, "Cannot save text file '" + p_path + "'.");25062507file->store_string(source);2508if (file->get_error() != OK && file->get_error() != ERR_FILE_EOF) {2509return ERR_CANT_CREATE;2510}2511}25122513if (ResourceSaver::get_timestamp_on_save()) {2514p_text_file->set_last_modified_time(FileAccess::get_modified_time(p_path));2515}25162517EditorFileSystem::get_singleton()->update_file(p_path);25182519_res_saved_callback(sqscr);2520return OK;2521}25222523bool ScriptEditor::edit(const Ref<Resource> &p_resource, int p_line, int p_col, bool p_grab_focus) {2524if (p_resource.is_null()) {2525return false;2526}25272528Ref<Script> scr = p_resource;25292530// Don't open dominant script if using an external editor.2531bool use_external_editor =2532external_editor_active ||2533(scr.is_valid() && scr->get_language()->overrides_external_editor());2534use_external_editor = use_external_editor && !(scr.is_valid() && scr->is_built_in()); // Ignore external editor for built-in scripts.2535const bool open_dominant = EDITOR_GET("text_editor/behavior/files/open_dominant_script_on_scene_change");25362537const bool should_open = (open_dominant && !use_external_editor) || !EditorNode::get_singleton()->is_changing_scene();25382539if (scr.is_valid() && scr->get_language()->overrides_external_editor()) {2540if (should_open) {2541Error err = scr->get_language()->open_in_external_editor(scr, p_line >= 0 ? p_line : 0, p_col);2542if (err != OK) {2543ERR_PRINT("Couldn't open script in the overridden external text editor");2544}2545}2546return false;2547}25482549if (use_external_editor &&2550(EditorDebuggerNode::get_singleton()->get_dump_stack_script() != p_resource || EditorDebuggerNode::get_singleton()->get_debug_with_external_editor()) &&2551p_resource->get_path().is_resource_file()) {2552String path = EDITOR_GET("text_editor/external/exec_path");2553String flags = EDITOR_GET("text_editor/external/exec_flags");25542555List<String> args;2556bool has_file_flag = false;2557String script_path = ProjectSettings::get_singleton()->globalize_path(p_resource->get_path());25582559if (flags.size()) {2560String project_path = ProjectSettings::get_singleton()->get_resource_path();25612562flags = flags.replacen("{line}", itos(MAX(p_line + 1, 1)));2563flags = flags.replacen("{col}", itos(p_col + 1));2564flags = flags.strip_edges().replace("\\\\", "\\");25652566int from = 0;2567int num_chars = 0;2568bool inside_quotes = false;25692570for (int i = 0; i < flags.size(); i++) {2571if (flags[i] == '"' && (!i || flags[i - 1] != '\\')) {2572if (!inside_quotes) {2573from++;2574}2575inside_quotes = !inside_quotes;25762577} else if (flags[i] == '\0' || (!inside_quotes && flags[i] == ' ')) {2578String arg = flags.substr(from, num_chars);2579if (arg.contains("{file}")) {2580has_file_flag = true;2581}25822583// do path replacement here, else there will be issues with spaces and quotes2584arg = arg.replacen("{project}", project_path);2585arg = arg.replacen("{file}", script_path);2586args.push_back(arg);25872588from = i + 1;2589num_chars = 0;2590} else {2591num_chars++;2592}2593}2594}25952596// Default to passing script path if no {file} flag is specified.2597if (!has_file_flag) {2598args.push_back(script_path);2599}26002601if (!path.is_empty()) {2602Error err = OS::get_singleton()->create_process(path, args);2603if (err == OK) {2604return false;2605}2606}26072608ERR_PRINT("Couldn't open external text editor, falling back to the internal editor. Review your `text_editor/external/` editor settings.");2609}26102611for (int i = 0; i < tab_container->get_tab_count(); i++) {2612ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));2613if (!se) {2614continue;2615}26162617if ((scr.is_valid() && se->get_edited_resource() == p_resource) || se->get_edited_resource()->get_path() == p_resource->get_path()) {2618if (should_open) {2619se->enable_editor(this);26202621if (tab_container->get_current_tab() != i) {2622_go_to_tab(i);2623}2624if (is_visible_in_tree()) {2625se->ensure_focus();2626}26272628if (p_line > 0) {2629se->goto_line(p_line);2630}2631}2632_update_script_names();2633script_list->ensure_current_is_visible();2634return true;2635}2636}26372638// doesn't have it, make a new one26392640ScriptEditorBase *se = nullptr;26412642for (int i = script_editor_func_count - 1; i >= 0; i--) {2643se = script_editor_funcs[i](p_resource);2644if (se) {2645break;2646}2647}2648ERR_FAIL_NULL_V(se, false);26492650se->set_edited_resource(p_resource);26512652// Syntax highlighting.2653bool highlighter_set = false;2654for (int i = 0; i < syntax_highlighters.size(); i++) {2655Ref<EditorSyntaxHighlighter> highlighter = syntax_highlighters[i]->_create();2656if (highlighter.is_null()) {2657continue;2658}2659se->add_syntax_highlighter(highlighter);26602661if (highlighter_set) {2662continue;2663}26642665PackedStringArray languages = highlighter->_get_supported_languages();2666// If script try language, else use extension.2667if (scr.is_valid()) {2668if (languages.has(scr->get_language()->get_name())) {2669se->set_syntax_highlighter(highlighter);2670highlighter_set = true;2671}2672continue;2673}26742675if (languages.has(p_resource->get_path().get_extension())) {2676se->set_syntax_highlighter(highlighter);2677highlighter_set = true;2678}2679}26802681tab_container->add_child(se);26822683if (p_grab_focus) {2684se->enable_editor(this);2685}26862687// If we delete a script within the filesystem, the original resource path2688// is lost, so keep it as `edited_file_data` to figure out the exact tab to delete.2689se->edited_file_data.path = p_resource->get_path();2690se->edited_file_data.last_modified_time = FileAccess::get_modified_time(p_resource->get_path());26912692se->set_tooltip_request_func(callable_mp(this, &ScriptEditor::_get_debug_tooltip));26932694if (se->get_edit_menu()) {2695se->get_edit_menu()->hide();2696menu_hb->add_child(se->get_edit_menu());2697menu_hb->move_child(se->get_edit_menu(), 1);2698}26992700if (p_grab_focus) {2701_go_to_tab(tab_container->get_tab_count() - 1);2702_add_recent_script(p_resource->get_path());2703}27042705if (script_editor_cache->has_section(p_resource->get_path())) {2706se->set_edit_state(script_editor_cache->get_value(p_resource->get_path(), "state"));2707ScriptTextEditor *ste = Object::cast_to<ScriptTextEditor>(se);2708if (ste) {2709ste->store_previous_state();2710}2711}27122713_sort_list_on_update = true;2714_update_script_names();2715_save_layout();2716se->connect("name_changed", callable_mp(this, &ScriptEditor::_update_script_names));2717se->connect("edited_script_changed", callable_mp(this, &ScriptEditor::_script_changed));2718se->connect("request_help", callable_mp(this, &ScriptEditor::_help_search));2719se->connect("request_open_script_at_line", callable_mp(this, &ScriptEditor::_goto_script_line));2720se->connect("go_to_help", callable_mp(this, &ScriptEditor::_help_class_goto));2721se->connect("request_save_history", callable_mp(this, &ScriptEditor::_save_history));2722se->connect("request_save_previous_state", callable_mp(this, &ScriptEditor::_save_previous_state));2723se->connect("search_in_files_requested", callable_mp(this, &ScriptEditor::open_find_in_files_dialog));2724se->connect("replace_in_files_requested", callable_mp(this, &ScriptEditor::_on_replace_in_files_requested));2725se->connect("go_to_method", callable_mp(this, &ScriptEditor::script_goto_method));27262727CodeTextEditor *cte = se->get_code_editor();2728if (cte) {2729cte->set_zoom_factor(zoom_factor);2730cte->connect("zoomed", callable_mp(this, &ScriptEditor::_set_script_zoom_factor));2731cte->connect(SceneStringName(visibility_changed), callable_mp(this, &ScriptEditor::_update_code_editor_zoom_factor).bind(cte));2732}27332734//test for modification, maybe the script was not edited but was loaded27352736_test_script_times_on_disk(p_resource);2737_update_modified_scripts_for_external_editor(p_resource);27382739if (p_line >= 0) {2740se->goto_line(p_line);2741}27422743notify_script_changed(p_resource);2744return true;2745}27462747PackedStringArray ScriptEditor::get_unsaved_scripts() const {2748PackedStringArray unsaved_list;27492750for (int i = 0; i < tab_container->get_tab_count(); i++) {2751ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));2752if (se && se->is_unsaved()) {2753unsaved_list.append(se->get_name());2754}2755}2756return unsaved_list;2757}27582759void ScriptEditor::save_current_script() {2760ScriptEditorBase *current = _get_current_editor();2761if (!current || _test_script_times_on_disk()) {2762return;2763}27642765if (trim_trailing_whitespace_on_save) {2766current->trim_trailing_whitespace();2767}27682769if (trim_final_newlines_on_save) {2770current->trim_final_newlines();2771}27722773if (convert_indent_on_save) {2774current->convert_indent();2775}27762777Ref<Resource> resource = current->get_edited_resource();2778Ref<TextFile> text_file = resource;2779Ref<Script> scr = resource;27802781if (text_file.is_valid()) {2782current->apply_code();2783_save_text_file(text_file, text_file->get_path());2784return;2785}27862787if (scr.is_valid()) {2788clear_docs_from_script(scr);2789}27902791EditorNode::get_singleton()->save_resource(resource);27922793if (scr.is_valid()) {2794update_docs_from_script(scr);2795}2796}27972798void ScriptEditor::save_all_scripts() {2799HashSet<String> scenes_to_save;28002801for (int i = 0; i < tab_container->get_tab_count(); i++) {2802ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));2803if (!se) {2804continue;2805}28062807if (convert_indent_on_save) {2808se->convert_indent();2809}28102811if (trim_trailing_whitespace_on_save) {2812se->trim_trailing_whitespace();2813}28142815if (trim_final_newlines_on_save) {2816se->trim_final_newlines();2817}28182819if (!se->is_unsaved()) {2820continue;2821}28222823Ref<Resource> edited_res = se->get_edited_resource();2824if (edited_res.is_valid()) {2825se->apply_code();2826}28272828Ref<Script> scr = edited_res;28292830if (scr.is_valid()) {2831clear_docs_from_script(scr);2832}28332834if (!edited_res->is_built_in()) {2835Ref<TextFile> text_file = edited_res;2836if (text_file.is_valid()) {2837_save_text_file(text_file, text_file->get_path());2838continue;2839}28402841// External script, save it.2842EditorNode::get_singleton()->save_resource(edited_res);2843} else {2844// For built-in scripts, save their scenes instead.2845const String scene_path = edited_res->get_path().get_slice("::", 0);2846if (!scene_path.is_empty() && !scenes_to_save.has(scene_path)) {2847scenes_to_save.insert(scene_path);2848}2849}28502851if (scr.is_valid()) {2852update_docs_from_script(scr);2853}2854}28552856if (!scenes_to_save.is_empty()) {2857EditorNode::get_singleton()->save_scene_list(scenes_to_save);2858}28592860_update_script_names();2861}28622863void ScriptEditor::update_script_times() {2864for (int i = 0; i < tab_container->get_tab_count(); i++) {2865ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));2866if (se) {2867se->edited_file_data.last_modified_time = FileAccess::get_modified_time(se->edited_file_data.path);2868}2869}2870}28712872void ScriptEditor::apply_scripts() const {2873for (int i = 0; i < tab_container->get_tab_count(); i++) {2874ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));2875if (!se) {2876continue;2877}2878se->insert_final_newline();2879se->apply_code();2880}2881}28822883void ScriptEditor::reload_scripts(bool p_refresh_only) {2884// Call deferred to make sure it runs on the main thread.2885callable_mp(this, &ScriptEditor::_reload_scripts).call_deferred(p_refresh_only);2886}28872888void ScriptEditor::_reload_scripts(bool p_refresh_only) {2889for (int i = 0; i < tab_container->get_tab_count(); i++) {2890ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));2891if (!se) {2892continue;2893}28942895Ref<Resource> edited_res = se->get_edited_resource();28962897if (edited_res->is_built_in()) {2898continue; // Internal script, who cares.2899}29002901if (p_refresh_only) {2902// Make sure the modified time is correct.2903se->edited_file_data.last_modified_time = FileAccess::get_modified_time(edited_res->get_path());2904} else {2905uint64_t last_date = se->edited_file_data.last_modified_time;2906uint64_t date = FileAccess::get_modified_time(edited_res->get_path());29072908if (last_date == date) {2909continue;2910}2911se->edited_file_data.last_modified_time = date;29122913Ref<Script> scr = edited_res;2914if (scr.is_valid()) {2915Ref<Script> rel_scr = ResourceLoader::load(scr->get_path(), scr->get_class(), ResourceFormatLoader::CACHE_MODE_IGNORE);2916ERR_CONTINUE(rel_scr.is_null());2917scr->set_source_code(rel_scr->get_source_code());2918scr->reload(true);29192920update_docs_from_script(scr);2921}29222923Ref<JSON> json = edited_res;2924if (json.is_valid()) {2925Ref<JSON> rel_json = ResourceLoader::load(json->get_path(), json->get_class(), ResourceFormatLoader::CACHE_MODE_IGNORE);2926ERR_CONTINUE(rel_json.is_null());2927json->parse(rel_json->get_parsed_text(), true);2928}29292930Ref<TextFile> text_file = edited_res;2931if (text_file.is_valid()) {2932text_file->reload_from_file();2933}2934}29352936se->reload_text();2937}29382939disk_changed->hide();2940_update_script_names();2941}29422943void ScriptEditor::open_find_in_files_dialog(const String &text) {2944find_in_files_dialog->set_find_in_files_mode(FindInFilesDialog::SEARCH_MODE);2945find_in_files_dialog->set_search_text(text);2946find_in_files_dialog->popup_centered();2947}29482949void ScriptEditor::open_script_create_dialog(const String &p_base_name, const String &p_base_path) {2950_menu_option(FILE_MENU_NEW);2951script_create_dialog->config(p_base_name, p_base_path);2952}29532954void ScriptEditor::open_text_file_create_dialog(const String &p_base_path, const String &p_base_name) {2955_menu_option(FILE_MENU_NEW_TEXTFILE);2956file_dialog->set_current_dir(p_base_path);2957file_dialog->set_current_file(p_base_name);2958open_textfile_after_create = false;2959}29602961Ref<Resource> ScriptEditor::open_file(const String &p_file) {2962List<String> extensions;2963ResourceLoader::get_recognized_extensions_for_type("Script", &extensions);2964ResourceLoader::get_recognized_extensions_for_type("JSON", &extensions);2965if (extensions.find(p_file.get_extension())) {2966Ref<Resource> scr = ResourceLoader::load(p_file);2967if (scr.is_null()) {2968EditorNode::get_singleton()->show_warning(TTR("Could not load file at:") + "\n\n" + p_file, TTR("Error!"));2969return Ref<Resource>();2970}29712972edit(scr);2973return scr;2974}29752976Error error;2977Ref<TextFile> text_file = _load_text_file(p_file, &error);2978if (error != OK) {2979EditorNode::get_singleton()->show_warning(TTR("Could not load file at:") + "\n\n" + p_file, TTR("Error!"));2980return Ref<Resource>();2981}29822983if (text_file.is_valid()) {2984edit(text_file);2985return text_file;2986}2987return Ref<Resource>();2988}29892990void ScriptEditor::_editor_stop() {2991for (int i = 0; i < tab_container->get_tab_count(); i++) {2992ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));2993if (!se) {2994continue;2995}29962997se->set_debugger_active(false);2998}2999}30003001void ScriptEditor::_add_callback(Object *p_obj, const String &p_function, const PackedStringArray &p_args) {3002ERR_FAIL_NULL(p_obj);3003Ref<Script> scr = p_obj->get_script();3004ERR_FAIL_COND(scr.is_null());30053006if (!scr->get_language()->can_make_function()) {3007return;3008}30093010EditorNode::get_singleton()->push_item(scr.ptr());30113012for (int i = 0; i < tab_container->get_tab_count(); i++) {3013ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));3014if (!se) {3015continue;3016}3017if (se->get_edited_resource() != scr) {3018continue;3019}30203021se->add_callback(p_function, p_args);30223023_go_to_tab(i);30243025script_list->select(script_list->find_metadata(i));30263027// Save the current script so the changes can be picked up by an external editor.3028if (!scr.ptr()->is_built_in()) { // But only if it's not built-in script.3029save_current_script();3030}30313032break;3033}30343035// Move back to the previously edited node to reselect it in the Inspector and the NodeDock.3036// We assume that the previous item is the node on which the callbacks were added.3037EditorNode::get_singleton()->edit_previous_item();3038}30393040void ScriptEditor::_save_editor_state(ScriptEditorBase *p_editor) {3041if (restoring_layout) {3042return;3043}30443045const String &path = p_editor->get_edited_resource()->get_path();3046if (!path.is_resource_file()) {3047return;3048}30493050script_editor_cache->set_value(path, "state", p_editor->get_edit_state());3051// This is saved later when we save the editor layout.3052}30533054void ScriptEditor::_save_layout() {3055if (restoring_layout) {3056return;3057}30583059EditorNode::get_singleton()->save_editor_layout_delayed();3060}30613062void ScriptEditor::_editor_settings_changed() {3063if (!EditorThemeManager::is_generated_theme_outdated() &&3064!EditorSettings::get_singleton()->check_changed_settings_in_group("interface/editor") &&3065!EditorSettings::get_singleton()->check_changed_settings_in_group("text_editor") &&3066!EditorSettings::get_singleton()->check_changed_settings_in_group("docks/filesystem")) {3067return;3068}30693070_apply_editor_settings();3071}30723073void ScriptEditor::_apply_editor_settings() {3074textfile_extensions.clear();3075const Vector<String> textfile_ext = ((String)(EDITOR_GET("docks/filesystem/textfile_extensions"))).split(",", false);3076for (const String &E : textfile_ext) {3077textfile_extensions.insert(E);3078}30793080trim_trailing_whitespace_on_save = EDITOR_GET("text_editor/behavior/files/trim_trailing_whitespace_on_save");3081trim_final_newlines_on_save = EDITOR_GET("text_editor/behavior/files/trim_final_newlines_on_save");3082convert_indent_on_save = EDITOR_GET("text_editor/behavior/files/convert_indent_on_save");30833084members_overview_enabled = EDITOR_GET("text_editor/script_list/show_members_overview");3085help_overview_enabled = EDITOR_GET("text_editor/help/show_help_index");3086external_editor_active = EDITOR_GET("text_editor/external/use_external_editor");3087_update_members_overview_visibility();3088_update_help_overview_visibility();30893090_update_autosave_timer();30913092_update_script_names();30933094ScriptServer::set_reload_scripts_on_save(EDITOR_GET("text_editor/behavior/files/auto_reload_and_parse_scripts_on_save"));30953096for (int i = 0; i < tab_container->get_tab_count(); i++) {3097ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));3098if (!se) {3099continue;3100}31013102se->update_settings();3103}3104}31053106void ScriptEditor::_filesystem_changed() {3107_update_script_names();3108}31093110void ScriptEditor::_files_moved(const String &p_old_file, const String &p_new_file) {3111if (!script_editor_cache->has_section(p_old_file)) {3112return;3113}31143115for (int i = 0; i < tab_container->get_tab_count(); i++) {3116ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));3117if (se && se->edited_file_data.path == p_old_file) {3118se->edited_file_data.path = p_new_file;3119break;3120}3121}31223123Variant state = script_editor_cache->get_value(p_old_file, "state");3124script_editor_cache->erase_section(p_old_file);3125script_editor_cache->set_value(p_new_file, "state", state);31263127// If Script, update breakpoints with debugger.3128Array breakpoints = _get_cached_breakpoints_for_script(p_new_file);3129for (int breakpoint : breakpoints) {3130int line = (int)breakpoint + 1;3131EditorDebuggerNode::get_singleton()->set_breakpoint(p_old_file, line, false);3132if (!p_new_file.begins_with("local://") && ResourceLoader::exists(p_new_file, "Script")) {3133EditorDebuggerNode::get_singleton()->set_breakpoint(p_new_file, line, true);3134}3135}3136// This is saved later when we save the editor layout.3137}31383139void ScriptEditor::_file_removed(const String &p_removed_file) {3140for (int i = 0; i < tab_container->get_tab_count(); i++) {3141ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));3142if (!se) {3143continue;3144}3145if (se->edited_file_data.path == p_removed_file) {3146// The script is deleted with no undo, so just close the tab.3147_close_tab(i, false, false);3148}3149}31503151// Check closed.3152if (script_editor_cache->has_section(p_removed_file)) {3153Array breakpoints = _get_cached_breakpoints_for_script(p_removed_file);3154for (int breakpoint : breakpoints) {3155EditorDebuggerNode::get_singleton()->set_breakpoint(p_removed_file, (int)breakpoint + 1, false);3156}3157script_editor_cache->erase_section(p_removed_file);3158}3159}31603161void ScriptEditor::_update_find_replace_bar() {3162ScriptEditorBase *se = _get_current_editor();3163if (se) {3164se->set_find_replace_bar(find_replace_bar);3165} else {3166find_replace_bar->set_text_edit(nullptr);3167find_replace_bar->hide();3168}3169}31703171void ScriptEditor::_autosave_scripts() {3172save_all_scripts();3173}31743175void ScriptEditor::_update_autosave_timer() {3176if (!autosave_timer->is_inside_tree()) {3177return;3178}31793180float autosave_time = EDITOR_GET("text_editor/behavior/files/autosave_interval_secs");3181if (autosave_time > 0) {3182autosave_timer->set_wait_time(autosave_time);3183autosave_timer->start();3184} else {3185autosave_timer->stop();3186}3187}31883189void ScriptEditor::_tree_changed() {3190if (waiting_update_names) {3191return;3192}31933194waiting_update_names = true;3195callable_mp(this, &ScriptEditor::_update_script_names).call_deferred();3196}31973198void ScriptEditor::_split_dragged(float) {3199_save_layout();3200}32013202Variant ScriptEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) {3203if (tab_container->get_tab_count() == 0) {3204return Variant();3205}32063207Node *cur_node = tab_container->get_tab_control(tab_container->get_current_tab());32083209HBoxContainer *drag_preview = memnew(HBoxContainer);3210String preview_name = "";3211Ref<Texture2D> preview_icon;32123213ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(cur_node);3214if (se) {3215preview_name = se->get_name();3216preview_icon = se->get_theme_icon();3217}3218EditorHelp *eh = Object::cast_to<EditorHelp>(cur_node);3219if (eh) {3220preview_name = eh->get_class();3221preview_icon = get_editor_theme_icon(SNAME("Help"));3222}32233224if (preview_icon.is_valid()) {3225TextureRect *tf = memnew(TextureRect);3226tf->set_texture(preview_icon);3227tf->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED);3228drag_preview->add_child(tf);3229}3230Label *label = memnew(Label(preview_name));3231label->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED); // Don't translate script names and class names.3232drag_preview->add_child(label);3233set_drag_preview(drag_preview);32343235Dictionary drag_data;3236drag_data["type"] = "script_list_element"; // using a custom type because node caused problems when dragging to scene tree3237drag_data["script_list_element"] = cur_node;32383239return drag_data;3240}32413242bool ScriptEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {3243Dictionary d = p_data;3244if (!d.has("type")) {3245return false;3246}32473248if (String(d["type"]) == "script_list_element") {3249Node *node = Object::cast_to<Node>(d["script_list_element"]);32503251ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(node);3252if (se) {3253return true;3254}3255EditorHelp *eh = Object::cast_to<EditorHelp>(node);3256if (eh) {3257return true;3258}3259}32603261if (String(d["type"]) == "nodes") {3262Array nodes = d["nodes"];3263if (nodes.is_empty()) {3264return false;3265}3266Node *node = get_node((nodes[0]));32673268ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(node);3269if (se) {3270return true;3271}3272EditorHelp *eh = Object::cast_to<EditorHelp>(node);3273if (eh) {3274return true;3275}3276}32773278if (String(d["type"]) == "files") {3279Vector<String> files = d["files"];32803281if (files.is_empty()) {3282return false; //weird3283}32843285for (int i = 0; i < files.size(); i++) {3286const String &file = files[i];3287if (file.is_empty() || !FileAccess::exists(file)) {3288continue;3289}3290if (ResourceLoader::exists(file, "Script") || ResourceLoader::exists(file, "JSON")) {3291Ref<Resource> scr = ResourceLoader::load(file);3292if (scr.is_valid()) {3293return true;3294}3295}32963297if (textfile_extensions.has(file.get_extension())) {3298Error err;3299Ref<TextFile> text_file = _load_text_file(file, &err);3300if (text_file.is_valid() && err == OK) {3301return true;3302}3303}3304}3305return false;3306}33073308return false;3309}33103311void ScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {3312if (!can_drop_data_fw(p_point, p_data, p_from)) {3313return;3314}33153316Dictionary d = p_data;3317if (!d.has("type")) {3318return;3319}33203321if (String(d["type"]) == "script_list_element") {3322Node *node = Object::cast_to<Node>(d["script_list_element"]);33233324ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(node);3325EditorHelp *eh = Object::cast_to<EditorHelp>(node);3326if (se || eh) {3327int new_index = 0;3328if (script_list->get_item_count() > 0) {3329int pos = 0;3330if (p_point == Vector2(Math::INF, Math::INF)) {3331if (script_list->is_anything_selected()) {3332pos = script_list->get_selected_items()[0];3333}3334} else {3335pos = script_list->get_item_at_position(p_point);3336}3337new_index = script_list->get_item_metadata(pos);3338}3339tab_container->move_child(node, new_index);3340tab_container->set_current_tab(new_index);3341_update_script_names();3342}3343}33443345if (String(d["type"]) == "nodes") {3346Array nodes = d["nodes"];3347if (nodes.is_empty()) {3348return;3349}3350Node *node = get_node(nodes[0]);33513352ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(node);3353EditorHelp *eh = Object::cast_to<EditorHelp>(node);3354if (se || eh) {3355int new_index = 0;3356if (script_list->get_item_count() > 0) {3357int pos = 0;3358if (p_point == Vector2(Math::INF, Math::INF)) {3359if (script_list->is_anything_selected()) {3360pos = script_list->get_selected_items()[0];3361}3362} else {3363pos = script_list->get_item_at_position(p_point);3364}3365new_index = script_list->get_item_metadata(pos);3366}3367tab_container->move_child(node, new_index);3368tab_container->set_current_tab(new_index);3369_update_script_names();3370}3371}33723373if (String(d["type"]) == "files") {3374Vector<String> files = d["files"];33753376int new_index = 0;3377if (script_list->get_item_count() > 0) {3378int pos = 0;3379if (p_point == Vector2(Math::INF, Math::INF)) {3380if (script_list->is_anything_selected()) {3381pos = script_list->get_selected_items()[0];3382}3383} else {3384pos = script_list->get_item_at_position(p_point);3385}3386new_index = script_list->get_item_metadata(pos);3387}3388int num_tabs_before = tab_container->get_tab_count();3389for (int i = 0; i < files.size(); i++) {3390const String &file = files[i];3391if (file.is_empty() || !FileAccess::exists(file)) {3392continue;3393}33943395if (!ResourceLoader::exists(file, "Script") && !ResourceLoader::exists(file, "JSON") && !textfile_extensions.has(file.get_extension())) {3396continue;3397}33983399Ref<Resource> res = open_file(file);3400if (res.is_valid()) {3401const int num_tabs = tab_container->get_tab_count();3402if (num_tabs > num_tabs_before) {3403tab_container->move_child(tab_container->get_tab_control(tab_container->get_tab_count() - 1), new_index);3404num_tabs_before = num_tabs;3405} else if (num_tabs > 0) { /* Maybe script was already open */3406tab_container->move_child(tab_container->get_tab_control(tab_container->get_current_tab()), new_index);3407}3408}3409}3410if (tab_container->get_tab_count() > 0) {3411tab_container->set_current_tab(new_index);3412}3413_update_script_names();3414}3415}34163417void ScriptEditor::input(const Ref<InputEvent> &p_event) {3418// This is implemented in `input()` rather than `unhandled_input()` to allow3419// the shortcut to be used regardless of the click location.3420// This feature can be disabled to avoid interfering with other uses of the additional3421// mouse buttons, such as push-to-talk in a VoIP program.3422if (EDITOR_GET("interface/editor/mouse_extra_buttons_navigate_history")) {3423const Ref<InputEventMouseButton> mb = p_event;34243425// Navigate the script history using additional mouse buttons present on some mice.3426// This must be hardcoded as the editor shortcuts dialog doesn't allow assigning3427// more than one shortcut per action.3428if (mb.is_valid() && mb->is_pressed() && is_visible_in_tree()) {3429if (mb->get_button_index() == MouseButton::MB_XBUTTON1) {3430_history_back();3431}34323433if (mb->get_button_index() == MouseButton::MB_XBUTTON2) {3434_history_forward();3435}3436}3437}3438}34393440void ScriptEditor::shortcut_input(const Ref<InputEvent> &p_event) {3441ERR_FAIL_COND(p_event.is_null());34423443if (!is_visible_in_tree() || !p_event->is_pressed() || p_event->is_echo()) {3444return;3445}3446if (ED_IS_SHORTCUT("script_editor/next_script", p_event)) {3447if (script_list->get_item_count() > 1) {3448int next_tab = script_list->get_current() + 1;3449next_tab %= script_list->get_item_count();3450_go_to_tab(script_list->get_item_metadata(next_tab));3451_update_script_names();3452}3453accept_event();3454}3455if (ED_IS_SHORTCUT("script_editor/prev_script", p_event)) {3456if (script_list->get_item_count() > 1) {3457int next_tab = script_list->get_current() - 1;3458next_tab = next_tab >= 0 ? next_tab : script_list->get_item_count() - 1;3459_go_to_tab(script_list->get_item_metadata(next_tab));3460_update_script_names();3461}3462accept_event();3463}3464if (ED_IS_SHORTCUT("script_editor/window_move_up", p_event)) {3465_menu_option(FILE_MENU_MOVE_UP);3466accept_event();3467}3468if (ED_IS_SHORTCUT("script_editor/window_move_down", p_event)) {3469_menu_option(FILE_MENU_MOVE_DOWN);3470accept_event();3471}34723473Callable custom_callback = EditorContextMenuPluginManager::get_singleton()->match_custom_shortcut(EditorContextMenuPlugin::CONTEXT_SLOT_SCRIPT_EDITOR, p_event);3474if (custom_callback.is_valid()) {3475Ref<Resource> resource;3476ScriptEditorBase *current = _get_current_editor();3477if (current) {3478resource = current->get_edited_resource();3479}3480EditorContextMenuPluginManager::get_singleton()->invoke_callback(custom_callback, resource);3481accept_event();3482}3483}34843485void ScriptEditor::_script_list_clicked(int p_item, Vector2 p_local_mouse_pos, MouseButton p_mouse_button_index) {3486if (p_mouse_button_index == MouseButton::MIDDLE) {3487script_list->select(p_item);3488_script_selected(p_item);3489_menu_option(FILE_MENU_CLOSE);3490}34913492if (p_mouse_button_index == MouseButton::RIGHT) {3493_make_script_list_context_menu();3494}3495}34963497void ScriptEditor::_make_script_list_context_menu() {3498context_menu->clear();34993500int selected = tab_container->get_current_tab();3501if (selected < 0 || selected >= tab_container->get_tab_count()) {3502return;3503}35043505ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(selected));3506if (se) {3507context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/save"), FILE_MENU_SAVE);3508context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/save_as"), FILE_MENU_SAVE_AS);3509}3510context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/close_file"), FILE_MENU_CLOSE);3511context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/close_other_tabs"), FILE_MENU_CLOSE_OTHER_TABS);3512context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/close_tabs_below"), FILE_MENU_CLOSE_TABS_BELOW);3513context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/close_all"), FILE_MENU_CLOSE_ALL);3514context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/close_docs"), FILE_MENU_CLOSE_DOCS);3515context_menu->add_separator();3516if (se) {3517Ref<Script> scr = se->get_edited_resource();3518if (scr.is_valid() && scr->is_tool()) {3519context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/reload_script_soft"), FILE_MENU_SOFT_RELOAD_TOOL);3520context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/run_file"), FILE_MENU_RUN);3521context_menu->add_separator();3522}3523context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/copy_path"), FILE_MENU_COPY_PATH);3524context_menu->set_item_disabled(-1, se->get_edited_resource()->get_path().is_empty());3525context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/copy_uid"), FILE_MENU_COPY_UID);3526context_menu->set_item_disabled(-1, ResourceLoader::get_resource_uid(se->get_edited_resource()->get_path()) == ResourceUID::INVALID_ID);3527context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/show_in_file_system"), FILE_MENU_SHOW_IN_FILE_SYSTEM);3528context_menu->add_separator();3529}35303531context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/window_move_up"), FILE_MENU_MOVE_UP);3532context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/window_move_down"), FILE_MENU_MOVE_DOWN);3533context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/window_sort"), FILE_MENU_SORT);3534context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/toggle_files_panel"), FILE_MENU_TOGGLE_FILES_PANEL);35353536context_menu->set_item_disabled(context_menu->get_item_index(FILE_MENU_CLOSE_ALL), tab_container->get_tab_count() <= 0);3537context_menu->set_item_disabled(context_menu->get_item_index(FILE_MENU_CLOSE_OTHER_TABS), tab_container->get_tab_count() <= 1);3538context_menu->set_item_disabled(context_menu->get_item_index(FILE_MENU_CLOSE_DOCS), !_has_docs_tab());3539context_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);3540context_menu->set_item_disabled(context_menu->get_item_index(FILE_MENU_MOVE_UP), tab_container->get_current_tab() <= 0);3541context_menu->set_item_disabled(context_menu->get_item_index(FILE_MENU_MOVE_DOWN), tab_container->get_current_tab() >= tab_container->get_tab_count() - 1);3542context_menu->set_item_disabled(context_menu->get_item_index(FILE_MENU_SORT), tab_container->get_tab_count() <= 1);35433544// Context menu plugin.3545Vector<String> selected_paths;3546if (se) {3547Ref<Resource> scr = se->get_edited_resource();3548if (scr.is_valid()) {3549String path = scr->get_path();3550selected_paths.push_back(path);3551}3552}3553EditorContextMenuPluginManager::get_singleton()->add_options_from_plugins(context_menu, EditorContextMenuPlugin::CONTEXT_SLOT_SCRIPT_EDITOR, selected_paths);35543555context_menu->set_position(get_screen_position() + get_local_mouse_position());3556context_menu->reset_size();3557context_menu->popup();3558}35593560void ScriptEditor::set_window_layout(Ref<ConfigFile> p_layout) {3561if (!bool(EDITOR_GET("text_editor/behavior/files/restore_scripts_on_load"))) {3562return;3563}35643565if (!p_layout->has_section_key("ScriptEditor", "open_scripts") && !p_layout->has_section_key("ScriptEditor", "open_help")) {3566return;3567}35683569Array scripts = p_layout->get_value("ScriptEditor", "open_scripts");3570Array helps;3571if (p_layout->has_section_key("ScriptEditor", "open_help")) {3572helps = p_layout->get_value("ScriptEditor", "open_help");3573}35743575restoring_layout = true;35763577HashSet<String> loaded_scripts;3578List<String> extensions;3579ResourceLoader::get_recognized_extensions_for_type("Script", &extensions);3580ResourceLoader::get_recognized_extensions_for_type("JSON", &extensions);35813582for (int i = 0; i < scripts.size(); i++) {3583String path = scripts[i];35843585Dictionary script_info = scripts[i];3586if (!script_info.is_empty()) {3587path = script_info["path"];3588}35893590if (!FileAccess::exists(path)) {3591if (script_editor_cache->has_section(path)) {3592script_editor_cache->erase_section(path);3593}3594continue;3595}3596loaded_scripts.insert(path);35973598if (extensions.find(path.get_extension())) {3599Ref<Resource> scr = ResourceLoader::load(path);3600if (scr.is_null()) {3601continue;3602}3603if (!edit(scr, false)) {3604continue;3605}3606} else {3607Error error;3608Ref<TextFile> text_file = _load_text_file(path, &error);3609if (error != OK || text_file.is_null()) {3610continue;3611}3612if (!edit(text_file, false)) {3613continue;3614}3615}36163617if (!script_info.is_empty()) {3618ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(tab_container->get_tab_count() - 1));3619if (se) {3620se->set_edit_state(script_info["state"]);3621}3622}3623}36243625for (int i = 0; i < helps.size(); i++) {3626String path = helps[i];3627if (path.is_empty()) { // invalid, skip3628continue;3629}3630_help_class_open(path);3631}36323633for (int i = 0; i < tab_container->get_tab_count(); i++) {3634tab_container->get_tab_control(i)->set_meta("__editor_pass", Variant());3635}36363637if (p_layout->has_section_key("ScriptEditor", "script_split_offset")) {3638script_split->set_split_offset(p_layout->get_value("ScriptEditor", "script_split_offset"));3639}36403641if (p_layout->has_section_key("ScriptEditor", "list_split_offset")) {3642list_split->set_split_offset(p_layout->get_value("ScriptEditor", "list_split_offset"));3643}36443645// Remove any deleted editors that have been removed between launches.3646// and if a Script, register breakpoints with the debugger.3647Vector<String> cached_editors = script_editor_cache->get_sections();3648for (const String &E : cached_editors) {3649if (loaded_scripts.has(E)) {3650continue;3651}36523653if (!FileAccess::exists(E)) {3654script_editor_cache->erase_section(E);3655continue;3656}36573658Array breakpoints = _get_cached_breakpoints_for_script(E);3659for (int breakpoint : breakpoints) {3660EditorDebuggerNode::get_singleton()->set_breakpoint(E, (int)breakpoint + 1, true);3661}3662}36633664_set_script_zoom_factor(p_layout->get_value("ScriptEditor", "zoom_factor", 1.0f));36653666restoring_layout = false;36673668_update_script_names();36693670if (p_layout->has_section_key("ScriptEditor", "selected_script")) {3671String selected_script = p_layout->get_value("ScriptEditor", "selected_script");3672// If the selected script is not in the list of open scripts, select nothing.3673for (int i = 0; i < tab_container->get_tab_count(); i++) {3674ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));3675if (se && se->get_edited_resource()->get_path() == selected_script) {3676_go_to_tab(i);3677break;3678}3679}3680}3681}36823683void ScriptEditor::get_window_layout(Ref<ConfigFile> p_layout) {3684Array scripts;3685Array helps;3686String selected_script;3687for (int i = 0; i < tab_container->get_tab_count(); i++) {3688ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));3689if (se) {3690String path = se->get_edited_resource()->get_path();3691if (!path.is_resource_file()) {3692continue;3693}36943695if (tab_container->get_current_tab_control() == tab_container->get_tab_control(i)) {3696selected_script = path;3697}36983699_save_editor_state(se);3700scripts.push_back(path);3701}37023703EditorHelp *eh = Object::cast_to<EditorHelp>(tab_container->get_tab_control(i));37043705if (eh) {3706helps.push_back(eh->get_class());3707}3708}37093710p_layout->set_value("ScriptEditor", "open_scripts", scripts);3711p_layout->set_value("ScriptEditor", "selected_script", selected_script);3712p_layout->set_value("ScriptEditor", "open_help", helps);3713p_layout->set_value("ScriptEditor", "script_split_offset", script_split->get_split_offset());3714p_layout->set_value("ScriptEditor", "list_split_offset", list_split->get_split_offset());3715p_layout->set_value("ScriptEditor", "zoom_factor", zoom_factor);37163717// Save the cache.3718script_editor_cache->save(EditorPaths::get_singleton()->get_project_settings_dir().path_join("script_editor_cache.cfg"));3719}37203721void ScriptEditor::_help_class_open(const String &p_class) {3722if (p_class.is_empty()) {3723return;3724}37253726for (int i = 0; i < tab_container->get_tab_count(); i++) {3727EditorHelp *eh = Object::cast_to<EditorHelp>(tab_container->get_tab_control(i));37283729if (eh && eh->get_class() == p_class) {3730_go_to_tab(i);3731_update_script_names();3732return;3733}3734}37353736EditorHelp *eh = memnew(EditorHelp);37373738eh->set_name(p_class);3739tab_container->add_child(eh);3740_go_to_tab(tab_container->get_tab_count() - 1);3741eh->go_to_class(p_class);3742eh->connect("go_to_help", callable_mp(this, &ScriptEditor::_help_class_goto));3743eh->connect("request_save_history", callable_mp(this, &ScriptEditor::_save_history));3744_add_recent_script(p_class);3745_sort_list_on_update = true;3746_update_script_names();3747_save_layout();3748}37493750void ScriptEditor::_help_class_goto(const String &p_desc) {3751String cname = p_desc.get_slicec(':', 1);37523753if (_help_tab_goto(cname, p_desc)) {3754return;3755}37563757EditorHelp *eh = memnew(EditorHelp);37583759eh->set_name(cname);3760tab_container->add_child(eh);3761_go_to_tab(tab_container->get_tab_count() - 1);3762eh->go_to_help(p_desc);3763eh->connect("go_to_help", callable_mp(this, &ScriptEditor::_help_class_goto));3764_add_recent_script(eh->get_class());3765_sort_list_on_update = true;3766_update_script_names();3767_save_layout();3768}37693770bool ScriptEditor::_help_tab_goto(const String &p_name, const String &p_desc) {3771for (int i = 0; i < tab_container->get_tab_count(); i++) {3772EditorHelp *eh = Object::cast_to<EditorHelp>(tab_container->get_tab_control(i));37733774if (eh && eh->get_class() == p_name) {3775_go_to_tab(i);3776eh->go_to_help(p_desc);3777_update_script_names();3778return true;3779}3780}3781return false;3782}37833784void ScriptEditor::update_doc(const String &p_name) {3785for (int i = 0; i < tab_container->get_tab_count(); i++) {3786EditorHelp *eh = Object::cast_to<EditorHelp>(tab_container->get_tab_control(i));3787if (eh && eh->get_class() == p_name) {3788eh->update_doc();3789return;3790}3791}3792}37933794void ScriptEditor::clear_docs_from_script(const Ref<Script> &p_script) {3795ERR_FAIL_COND(p_script.is_null());37963797for (const DocData::ClassDoc &cd : p_script->get_documentation()) {3798if (EditorHelp::get_doc_data()->has_doc(cd.name)) {3799EditorHelp::get_doc_data()->remove_doc(cd.name);3800}3801}3802}38033804void ScriptEditor::update_docs_from_script(const Ref<Script> &p_script) {3805ERR_FAIL_COND(p_script.is_null());38063807for (const DocData::ClassDoc &cd : p_script->get_documentation()) {3808EditorHelp::get_doc_data()->add_doc(cd);3809update_doc(cd.name);3810}3811}38123813void ScriptEditor::_update_selected_editor_menu() {3814for (int i = 0; i < tab_container->get_tab_count(); i++) {3815bool current = tab_container->get_current_tab() == i;38163817ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));3818if (se && se->get_edit_menu()) {3819if (current) {3820se->get_edit_menu()->show();3821} else {3822se->get_edit_menu()->hide();3823}3824}3825}38263827EditorHelp *eh = Object::cast_to<EditorHelp>(tab_container->get_current_tab_control());3828script_search_menu->get_popup()->clear();3829if (eh) {3830script_search_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/find", TTRC("Find..."), KeyModifierMask::CMD_OR_CTRL | Key::F), HELP_SEARCH_FIND);3831script_search_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/find_next", TTRC("Find Next"), Key::F3), HELP_SEARCH_FIND_NEXT);3832script_search_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/find_previous", TTRC("Find Previous"), KeyModifierMask::SHIFT | Key::F3), HELP_SEARCH_FIND_PREVIOUS);3833script_search_menu->get_popup()->add_separator();3834script_search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("editor/find_in_files"), SEARCH_IN_FILES);3835script_search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_editor/replace_in_files"), REPLACE_IN_FILES);3836script_search_menu->show();3837} else {3838if (tab_container->get_tab_count() == 0) {3839script_search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("editor/find_in_files"), SEARCH_IN_FILES);3840script_search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_editor/replace_in_files"), REPLACE_IN_FILES);3841script_search_menu->show();3842} else {3843script_search_menu->hide();3844}3845}3846}38473848void ScriptEditor::_update_history_pos(int p_new_pos) {3849Node *n = tab_container->get_current_tab_control();38503851if (Object::cast_to<ScriptEditorBase>(n)) {3852history.write[history_pos].state = Object::cast_to<ScriptEditorBase>(n)->get_navigation_state();3853}3854if (Object::cast_to<EditorHelp>(n)) {3855history.write[history_pos].state = Object::cast_to<EditorHelp>(n)->get_scroll();3856}38573858history_pos = p_new_pos;3859tab_container->set_current_tab(tab_container->get_tab_idx_from_control(history[history_pos].control));38603861n = history[history_pos].control;38623863ScriptEditorBase *seb = Object::cast_to<ScriptEditorBase>(n);3864if (seb) {3865lock_history = true;3866seb->set_edit_state(history[history_pos].state);3867seb->ensure_focus();38683869Ref<Script> scr = seb->get_edited_resource();3870if (scr.is_valid()) {3871notify_script_changed(scr);3872}3873}38743875EditorHelp *eh = Object::cast_to<EditorHelp>(n);3876if (eh) {3877eh->set_scroll(history[history_pos].state);3878eh->set_focused();3879}38803881n->set_meta("__editor_pass", ++edit_pass);3882_update_script_names();3883_update_history_arrows();3884_update_selected_editor_menu();3885}38863887void ScriptEditor::_history_forward() {3888if (history_pos < history.size() - 1) {3889_update_history_pos(history_pos + 1);3890}3891}38923893void ScriptEditor::_history_back() {3894if (history_pos > 0) {3895_update_history_pos(history_pos - 1);3896}3897}38983899Vector<Ref<Script>> ScriptEditor::get_open_scripts() const {3900Vector<Ref<Script>> out_scripts = Vector<Ref<Script>>();39013902for (int i = 0; i < tab_container->get_tab_count(); i++) {3903ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));3904if (!se) {3905continue;3906}39073908Ref<Script> scr = se->get_edited_resource();3909if (scr.is_valid()) {3910out_scripts.push_back(scr);3911}3912}39133914return out_scripts;3915}39163917TypedArray<ScriptEditorBase> ScriptEditor::_get_open_script_editors() const {3918TypedArray<ScriptEditorBase> script_editors;3919for (int i = 0; i < tab_container->get_tab_count(); i++) {3920ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));3921if (!se) {3922continue;3923}3924script_editors.push_back(se);3925}3926return script_editors;3927}39283929void ScriptEditor::set_scene_root_script(Ref<Script> p_script) {3930// Don't open dominant script if using an external editor.3931bool use_external_editor =3932external_editor_active ||3933(p_script.is_valid() && p_script->get_language()->overrides_external_editor());3934use_external_editor = use_external_editor && !(p_script.is_valid() && p_script->is_built_in()); // Ignore external editor for built-in scripts.3935const bool open_dominant = EDITOR_GET("text_editor/behavior/files/open_dominant_script_on_scene_change");39363937if (open_dominant && !use_external_editor && p_script.is_valid()) {3938edit(p_script);3939}3940}39413942bool ScriptEditor::script_goto_method(Ref<Script> p_script, const String &p_method) {3943int line = p_script->get_member_line(p_method);39443945if (line == -1) {3946return false;3947}39483949return edit(p_script, line, 0);3950}39513952void ScriptEditor::set_live_auto_reload_running_scripts(bool p_enabled) {3953auto_reload_running_scripts = p_enabled;3954}39553956void ScriptEditor::_help_search(const String &p_text) {3957help_search_dialog->popup_dialog(p_text);3958}39593960void ScriptEditor::_open_script_request(const String &p_path) {3961Ref<Script> scr = ResourceLoader::load(p_path);3962if (scr.is_valid()) {3963script_editor->edit(scr, false);3964return;3965}39663967Ref<JSON> json = ResourceLoader::load(p_path);3968if (json.is_valid()) {3969script_editor->edit(json, false);3970return;3971}39723973Error err;3974Ref<TextFile> text_file = script_editor->_load_text_file(p_path, &err);3975if (text_file.is_valid()) {3976script_editor->edit(text_file, false);3977return;3978}3979}39803981void ScriptEditor::register_syntax_highlighter(const Ref<EditorSyntaxHighlighter> &p_syntax_highlighter) {3982ERR_FAIL_COND(p_syntax_highlighter.is_null());39833984if (!syntax_highlighters.has(p_syntax_highlighter)) {3985syntax_highlighters.push_back(p_syntax_highlighter);3986}3987}39883989void ScriptEditor::unregister_syntax_highlighter(const Ref<EditorSyntaxHighlighter> &p_syntax_highlighter) {3990ERR_FAIL_COND(p_syntax_highlighter.is_null());39913992syntax_highlighters.erase(p_syntax_highlighter);3993}39943995int ScriptEditor::script_editor_func_count = 0;3996CreateScriptEditorFunc ScriptEditor::script_editor_funcs[ScriptEditor::SCRIPT_EDITOR_FUNC_MAX];39973998void ScriptEditor::register_create_script_editor_function(CreateScriptEditorFunc p_func) {3999ERR_FAIL_COND(script_editor_func_count == SCRIPT_EDITOR_FUNC_MAX);4000script_editor_funcs[script_editor_func_count++] = p_func;4001}40024003void ScriptEditor::_script_changed() {4004NodeDock::get_singleton()->update_lists();4005}40064007void ScriptEditor::_on_replace_in_files_requested(const String &text) {4008find_in_files_dialog->set_find_in_files_mode(FindInFilesDialog::REPLACE_MODE);4009find_in_files_dialog->set_search_text(text);4010find_in_files_dialog->set_replace_text("");4011find_in_files_dialog->popup_centered();4012}40134014void ScriptEditor::_on_find_in_files_result_selected(const String &fpath, int line_number, int begin, int end) {4015if (ResourceLoader::exists(fpath)) {4016Ref<Resource> res = ResourceLoader::load(fpath);40174018if (fpath.get_extension() == "gdshader") {4019ShaderEditorPlugin *shader_editor = Object::cast_to<ShaderEditorPlugin>(EditorNode::get_editor_data().get_editor_by_name("Shader"));4020shader_editor->edit(res.ptr());4021shader_editor->make_visible(true);4022TextShaderEditor *text_shader_editor = Object::cast_to<TextShaderEditor>(shader_editor->get_shader_editor(res));4023if (text_shader_editor) {4024text_shader_editor->goto_line_selection(line_number - 1, begin, end);4025}4026return;4027} else if (fpath.get_extension() == "tscn") {4028Ref<FileAccess> f = FileAccess::open(fpath, FileAccess::READ);4029bool is_script_found = false;40304031// Starting from top of the tscn file.4032int scr_start_line = 1;40334034String scr_header = "[sub_resource type=\"GDScript\" id=\"";4035String scr_id = "";4036String line = "";40374038int l = 0;40394040while (!f->eof_reached()) {4041line = f->get_line();4042l++;40434044if (!line.begins_with(scr_header)) {4045continue;4046}40474048// Found the end of the script.4049scr_id = line.get_slice(scr_header, 1);4050scr_id = scr_id.get_slicec('"', 0);40514052scr_start_line = l + 1;4053int scr_line_count = 0;40544055do {4056line = f->get_line();4057l++;4058String strline = line.strip_edges();40594060if (strline.ends_with("\"") && !strline.ends_with("\\\"")) {4061// Found the end of script.4062break;4063}4064scr_line_count++;40654066} while (!f->eof_reached());40674068if (line_number > scr_start_line + scr_line_count) {4069// Find in another built-in GDScript.4070continue;4071}40724073// Real line number of the built-in script.4074line_number = line_number - scr_start_line;40754076is_script_found = true;4077break;4078}40794080EditorNode::get_singleton()->load_scene(fpath);40814082if (is_script_found && !scr_id.is_empty()) {4083Ref<Script> scr = ResourceLoader::load(fpath + "::" + scr_id, "Script");4084if (scr.is_valid()) {4085edit(scr);4086ScriptTextEditor *ste = Object::cast_to<ScriptTextEditor>(_get_current_editor());40874088if (ste) {4089EditorInterface::get_singleton()->set_main_screen_editor("Script");4090ste->goto_line_selection(line_number, begin, end);4091}4092}4093}40944095return;4096} else {4097Ref<Script> scr = res;4098Ref<JSON> json = res;4099if (scr.is_valid() || json.is_valid()) {4100edit(scr);41014102ScriptTextEditor *ste = Object::cast_to<ScriptTextEditor>(_get_current_editor());4103if (ste) {4104EditorInterface::get_singleton()->set_main_screen_editor("Script");4105ste->goto_line_selection(line_number - 1, begin, end);4106}4107return;4108}4109}4110}41114112// If the file is not a valid resource/script, load it as a text file.4113Error err;4114Ref<TextFile> text_file = _load_text_file(fpath, &err);4115if (text_file.is_valid()) {4116edit(text_file);41174118TextEditor *te = Object::cast_to<TextEditor>(_get_current_editor());4119if (te) {4120te->goto_line_selection(line_number - 1, begin, end);4121}4122}4123}41244125void ScriptEditor::_start_find_in_files(bool with_replace) {4126FindInFiles *f = find_in_files->get_finder();41274128f->set_search_text(find_in_files_dialog->get_search_text());4129f->set_match_case(find_in_files_dialog->is_match_case());4130f->set_whole_words(find_in_files_dialog->is_whole_words());4131f->set_folder(find_in_files_dialog->get_folder());4132f->set_filter(find_in_files_dialog->get_filter());4133f->set_includes(find_in_files_dialog->get_includes());4134f->set_excludes(find_in_files_dialog->get_excludes());41354136find_in_files->set_with_replace(with_replace);4137find_in_files->set_replace_text(find_in_files_dialog->get_replace_text());4138find_in_files->start_search();41394140if (find_in_files_button->get_index() != find_in_files_button->get_parent()->get_child_count()) {4141find_in_files_button->get_parent()->move_child(find_in_files_button, -1);4142}4143if (!find_in_files_button->is_visible()) {4144find_in_files_button->show();4145}41464147EditorNode::get_bottom_panel()->make_item_visible(find_in_files);4148}41494150void ScriptEditor::_on_find_in_files_modified_files(const PackedStringArray &paths) {4151_test_script_times_on_disk();4152_update_modified_scripts_for_external_editor();4153}41544155void ScriptEditor::_set_script_zoom_factor(float p_zoom_factor) {4156if (zoom_factor == p_zoom_factor) {4157return;4158}41594160zoom_factor = p_zoom_factor;4161}41624163void ScriptEditor::_update_code_editor_zoom_factor(CodeTextEditor *p_code_text_editor) {4164if (p_code_text_editor && p_code_text_editor->is_visible_in_tree() && zoom_factor != p_code_text_editor->get_zoom_factor()) {4165p_code_text_editor->set_zoom_factor(zoom_factor);4166}4167}41684169void ScriptEditor::_on_find_in_files_close_button_clicked() {4170EditorNode::get_bottom_panel()->hide_bottom_panel();4171find_in_files_button->hide();4172}41734174void ScriptEditor::_window_changed(bool p_visible) {4175make_floating->set_visible(!p_visible);4176is_floating = p_visible;4177}41784179void ScriptEditor::_filter_scripts_text_changed(const String &p_newtext) {4180_update_script_names();4181}41824183void ScriptEditor::_filter_methods_text_changed(const String &p_newtext) {4184_update_members_overview();4185}41864187void ScriptEditor::_bind_methods() {4188ClassDB::bind_method("_help_tab_goto", &ScriptEditor::_help_tab_goto);4189ClassDB::bind_method("get_current_editor", &ScriptEditor::_get_current_editor);4190ClassDB::bind_method("get_open_script_editors", &ScriptEditor::_get_open_script_editors);4191ClassDB::bind_method("get_breakpoints", &ScriptEditor::_get_breakpoints);41924193ClassDB::bind_method(D_METHOD("register_syntax_highlighter", "syntax_highlighter"), &ScriptEditor::register_syntax_highlighter);4194ClassDB::bind_method(D_METHOD("unregister_syntax_highlighter", "syntax_highlighter"), &ScriptEditor::unregister_syntax_highlighter);41954196ClassDB::bind_method(D_METHOD("goto_line", "line_number"), &ScriptEditor::_goto_script_line2);4197ClassDB::bind_method(D_METHOD("get_current_script"), &ScriptEditor::_get_current_script);4198ClassDB::bind_method(D_METHOD("get_open_scripts"), &ScriptEditor::_get_open_scripts);4199ClassDB::bind_method(D_METHOD("open_script_create_dialog", "base_name", "base_path"), &ScriptEditor::open_script_create_dialog);42004201ClassDB::bind_method(D_METHOD("goto_help", "topic"), &ScriptEditor::goto_help);4202ClassDB::bind_method(D_METHOD("update_docs_from_script", "script"), &ScriptEditor::update_docs_from_script);4203ClassDB::bind_method(D_METHOD("clear_docs_from_script", "script"), &ScriptEditor::clear_docs_from_script);42044205ADD_SIGNAL(MethodInfo("editor_script_changed", PropertyInfo(Variant::OBJECT, "script", PROPERTY_HINT_RESOURCE_TYPE, "Script")));4206ADD_SIGNAL(MethodInfo("script_close", PropertyInfo(Variant::OBJECT, "script", PROPERTY_HINT_RESOURCE_TYPE, "Script")));4207}42084209ScriptEditor::ScriptEditor(WindowWrapper *p_wrapper) {4210window_wrapper = p_wrapper;42114212script_editor_cache.instantiate();4213script_editor_cache->load(EditorPaths::get_singleton()->get_project_settings_dir().path_join("script_editor_cache.cfg"));42144215restoring_layout = false;4216waiting_update_names = false;4217pending_auto_reload = false;4218auto_reload_running_scripts = true;4219external_editor_active = false;4220members_overview_enabled = EDITOR_GET("text_editor/script_list/show_members_overview");4221help_overview_enabled = EDITOR_GET("text_editor/help/show_help_index");42224223VBoxContainer *main_container = memnew(VBoxContainer);4224add_child(main_container);42254226menu_hb = memnew(HBoxContainer);4227main_container->add_child(menu_hb);42284229script_split = memnew(HSplitContainer);4230main_container->add_child(script_split);4231script_split->set_v_size_flags(SIZE_EXPAND_FILL);42324233list_split = memnew(VSplitContainer);4234script_split->add_child(list_split);4235list_split->set_v_size_flags(SIZE_EXPAND_FILL);42364237scripts_vbox = memnew(VBoxContainer);4238scripts_vbox->set_v_size_flags(SIZE_EXPAND_FILL);4239list_split->add_child(scripts_vbox);42404241filter_scripts = memnew(LineEdit);4242filter_scripts->set_placeholder(TTRC("Filter Scripts"));4243filter_scripts->set_accessibility_name(TTRC("Filter Scripts"));4244filter_scripts->set_clear_button_enabled(true);4245filter_scripts->connect(SceneStringName(text_changed), callable_mp(this, &ScriptEditor::_filter_scripts_text_changed));4246scripts_vbox->add_child(filter_scripts);42474248script_list = memnew(ItemList);4249script_list->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);4250scripts_vbox->add_child(script_list);4251script_list->set_custom_minimum_size(Size2(100, 60) * EDSCALE); //need to give a bit of limit to avoid it from disappearing4252script_list->set_v_size_flags(SIZE_EXPAND_FILL);4253script_list->set_theme_type_variation("ItemListSecondary");4254script_split->set_split_offset(200 * EDSCALE);4255_sort_list_on_update = true;4256script_list->connect("item_clicked", callable_mp(this, &ScriptEditor::_script_list_clicked), CONNECT_DEFERRED);4257script_list->set_allow_rmb_select(true);4258SET_DRAG_FORWARDING_GCD(script_list, ScriptEditor);42594260context_menu = memnew(PopupMenu);4261add_child(context_menu);4262context_menu->connect(SceneStringName(id_pressed), callable_mp(this, &ScriptEditor::_menu_option));42634264overview_vbox = memnew(VBoxContainer);4265overview_vbox->set_custom_minimum_size(Size2(0, 90));4266overview_vbox->set_v_size_flags(SIZE_EXPAND_FILL);42674268list_split->add_child(overview_vbox);4269list_split->set_visible(EditorSettings::get_singleton()->get_project_metadata("files_panel", "show_files_panel", true));4270buttons_hbox = memnew(HBoxContainer);4271overview_vbox->add_child(buttons_hbox);42724273filename = memnew(Label);4274filename->set_focus_mode(FOCUS_ACCESSIBILITY);4275filename->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);4276filename->set_clip_text(true);4277filename->set_h_size_flags(SIZE_EXPAND_FILL);4278filename->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);4279filename->add_theme_style_override(CoreStringName(normal), EditorNode::get_singleton()->get_editor_theme()->get_stylebox(CoreStringName(normal), SNAME("LineEdit")));4280buttons_hbox->add_child(filename);42814282members_overview_alphabeta_sort_button = memnew(Button);4283members_overview_alphabeta_sort_button->set_theme_type_variation(SceneStringName(FlatButton));4284members_overview_alphabeta_sort_button->set_tooltip_text(TTRC("Toggle alphabetical sorting of the method list."));4285members_overview_alphabeta_sort_button->set_toggle_mode(true);4286members_overview_alphabeta_sort_button->set_pressed(EDITOR_GET("text_editor/script_list/sort_members_outline_alphabetically"));4287members_overview_alphabeta_sort_button->connect(SceneStringName(toggled), callable_mp(this, &ScriptEditor::_toggle_members_overview_alpha_sort));42884289buttons_hbox->add_child(members_overview_alphabeta_sort_button);42904291filter_methods = memnew(LineEdit);4292filter_methods->set_placeholder(TTRC("Filter Methods"));4293filter_methods->set_accessibility_name(TTRC("Filter Methods"));4294filter_methods->set_clear_button_enabled(true);4295filter_methods->connect(SceneStringName(text_changed), callable_mp(this, &ScriptEditor::_filter_methods_text_changed));4296overview_vbox->add_child(filter_methods);42974298members_overview = memnew(ItemList);4299members_overview->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);4300members_overview->set_theme_type_variation("ItemListSecondary");4301overview_vbox->add_child(members_overview);43024303members_overview->set_allow_reselect(true);4304members_overview->set_custom_minimum_size(Size2(0, 60) * EDSCALE); //need to give a bit of limit to avoid it from disappearing4305members_overview->set_v_size_flags(SIZE_EXPAND_FILL);4306members_overview->set_allow_rmb_select(true);43074308help_overview = memnew(ItemList);4309help_overview->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);4310help_overview->set_theme_type_variation("ItemListSecondary");4311overview_vbox->add_child(help_overview);4312help_overview->set_allow_reselect(true);4313help_overview->set_custom_minimum_size(Size2(0, 60) * EDSCALE); //need to give a bit of limit to avoid it from disappearing4314help_overview->set_v_size_flags(SIZE_EXPAND_FILL);43154316VBoxContainer *code_editor_container = memnew(VBoxContainer);4317script_split->add_child(code_editor_container);43184319tab_container = memnew(TabContainer);4320tab_container->set_tabs_visible(false);4321tab_container->set_custom_minimum_size(Size2(200, 0) * EDSCALE);4322code_editor_container->add_child(tab_container);4323tab_container->set_h_size_flags(SIZE_EXPAND_FILL);4324tab_container->set_v_size_flags(SIZE_EXPAND_FILL);43254326find_replace_bar = memnew(FindReplaceBar);4327code_editor_container->add_child(find_replace_bar);4328find_replace_bar->hide();43294330ED_SHORTCUT("script_editor/window_sort", TTRC("Sort"));4331ED_SHORTCUT("script_editor/window_move_up", TTRC("Move Up"), KeyModifierMask::SHIFT | KeyModifierMask::ALT | Key::UP);4332ED_SHORTCUT("script_editor/window_move_down", TTRC("Move Down"), KeyModifierMask::SHIFT | KeyModifierMask::ALT | Key::DOWN);4333// FIXME: These should be `Key::GREATER` and `Key::LESS` but those don't work.4334ED_SHORTCUT("script_editor/next_script", TTRC("Next Script"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::PERIOD);4335ED_SHORTCUT("script_editor/prev_script", TTRC("Previous Script"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::COMMA);4336set_process_input(true);4337set_process_shortcut_input(true);43384339file_menu = memnew(MenuButton);4340file_menu->set_flat(false);4341file_menu->set_theme_type_variation("FlatMenuButton");4342file_menu->set_text(TTRC("File"));4343file_menu->set_switch_on_hover(true);4344file_menu->set_shortcut_context(this);4345menu_hb->add_child(file_menu);43464347file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/new", TTRC("New Script..."), KeyModifierMask::CMD_OR_CTRL | Key::N), FILE_MENU_NEW);4348file_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);4349file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/open", TTRC("Open...")), FILE_MENU_OPEN);4350file_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_editor/reopen_closed_script"), FILE_MENU_REOPEN_CLOSED);43514352recent_scripts = memnew(PopupMenu);4353recent_scripts->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);4354file_menu->get_popup()->add_submenu_node_item(TTRC("Open Recent"), recent_scripts, FILE_MENU_OPEN_RECENT);4355recent_scripts->connect(SceneStringName(id_pressed), callable_mp(this, &ScriptEditor::_open_recent_script));43564357_update_recent_scripts();43584359file_menu->get_popup()->add_separator();4360file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/save", TTRC("Save"), KeyModifierMask::ALT | KeyModifierMask::CMD_OR_CTRL | Key::S), FILE_MENU_SAVE);4361file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/save_as", TTRC("Save As...")), FILE_MENU_SAVE_AS);4362file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/save_all", TTRC("Save All"), KeyModifierMask::SHIFT | KeyModifierMask::ALT | Key::S), FILE_MENU_SAVE_ALL);4363ED_SHORTCUT_OVERRIDE("script_editor/save_all", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::S);4364file_menu->get_popup()->add_separator();4365file_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);4366file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/copy_path", TTRC("Copy Script Path")), FILE_MENU_COPY_PATH);4367file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/copy_uid", TTRC("Copy Script UID")), FILE_MENU_COPY_UID);4368file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/show_in_file_system", TTRC("Show in FileSystem")), FILE_MENU_SHOW_IN_FILE_SYSTEM);4369file_menu->get_popup()->add_separator();43704371file_menu->get_popup()->add_shortcut(4372ED_SHORTCUT_ARRAY("script_editor/history_previous", TTRC("History Previous"),4373{ int32_t(KeyModifierMask::ALT | Key::LEFT), int32_t(Key::BACK) }),4374FILE_MENU_HISTORY_PREV);4375file_menu->get_popup()->add_shortcut(4376ED_SHORTCUT_ARRAY("script_editor/history_next", TTRC("History Next"),4377{ int32_t(KeyModifierMask::ALT | Key::RIGHT), int32_t(Key::FORWARD) }),4378FILE_MENU_HISTORY_NEXT);4379ED_SHORTCUT_OVERRIDE("script_editor/history_previous", "macos", KeyModifierMask::ALT | KeyModifierMask::META | Key::LEFT);4380ED_SHORTCUT_OVERRIDE("script_editor/history_next", "macos", KeyModifierMask::ALT | KeyModifierMask::META | Key::RIGHT);43814382file_menu->get_popup()->add_separator();43834384theme_submenu = memnew(PopupMenu);4385theme_submenu->add_shortcut(ED_SHORTCUT("script_editor/import_theme", TTRC("Import Theme...")), THEME_IMPORT);4386theme_submenu->add_shortcut(ED_SHORTCUT("script_editor/reload_theme", TTRC("Reload Theme")), THEME_RELOAD);4387file_menu->get_popup()->add_submenu_node_item(TTRC("Theme"), theme_submenu, FILE_MENU_THEME_SUBMENU);4388theme_submenu->connect(SceneStringName(id_pressed), callable_mp(this, &ScriptEditor::_theme_option));43894390theme_submenu->add_separator();4391theme_submenu->add_shortcut(ED_SHORTCUT("script_editor/save_theme_as", TTRC("Save Theme As...")), THEME_SAVE_AS);43924393file_menu->get_popup()->add_separator();4394file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/close_file", TTRC("Close"), KeyModifierMask::CMD_OR_CTRL | Key::W), FILE_MENU_CLOSE);4395file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/close_all", TTRC("Close All")), FILE_MENU_CLOSE_ALL);4396file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/close_other_tabs", TTRC("Close Other Tabs")), FILE_MENU_CLOSE_OTHER_TABS);4397file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/close_tabs_below", TTRC("Close Tabs Below")), FILE_MENU_CLOSE_TABS_BELOW);4398file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/close_docs", TTRC("Close Docs")), FILE_MENU_CLOSE_DOCS);43994400file_menu->get_popup()->add_separator();4401file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/run_file", TTRC("Run"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::X), FILE_MENU_RUN);44024403file_menu->get_popup()->add_separator();4404file_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);4405file_menu->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &ScriptEditor::_menu_option));4406file_menu->get_popup()->connect("about_to_popup", callable_mp(this, &ScriptEditor::_prepare_file_menu));4407file_menu->get_popup()->connect("popup_hide", callable_mp(this, &ScriptEditor::_file_menu_closed));44084409script_search_menu = memnew(MenuButton);4410script_search_menu->set_flat(false);4411script_search_menu->set_theme_type_variation("FlatMenuButton");4412script_search_menu->set_text(TTRC("Search"));4413script_search_menu->set_switch_on_hover(true);4414script_search_menu->set_shortcut_context(this);4415script_search_menu->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &ScriptEditor::_menu_option));4416menu_hb->add_child(script_search_menu);44174418MenuButton *debug_menu_btn = memnew(MenuButton);4419debug_menu_btn->set_flat(false);4420debug_menu_btn->set_theme_type_variation("FlatMenuButton");4421menu_hb->add_child(debug_menu_btn);4422debug_menu_btn->hide(); // Handled by EditorDebuggerNode below.44234424EditorDebuggerNode *debugger = EditorDebuggerNode::get_singleton();4425debugger->set_script_debug_button(debug_menu_btn);4426debugger->connect("goto_script_line", callable_mp(this, &ScriptEditor::_goto_script_line));4427debugger->connect("set_execution", callable_mp(this, &ScriptEditor::_set_execution));4428debugger->connect("clear_execution", callable_mp(this, &ScriptEditor::_clear_execution));4429debugger->connect("breaked", callable_mp(this, &ScriptEditor::_breaked));4430debugger->connect("breakpoint_set_in_tree", callable_mp(this, &ScriptEditor::_set_breakpoint));4431debugger->connect("breakpoints_cleared_in_tree", callable_mp(this, &ScriptEditor::_clear_breakpoints));44324433menu_hb->add_spacer();44344435script_icon = memnew(TextureRect);4436menu_hb->add_child(script_icon);4437script_name_label = memnew(Label);4438script_name_label->set_focus_mode(FOCUS_ACCESSIBILITY);4439menu_hb->add_child(script_name_label);44404441script_icon->hide();4442script_name_label->hide();44434444menu_hb->add_spacer();44454446site_search = memnew(Button);4447site_search->set_theme_type_variation(SceneStringName(FlatButton));4448site_search->set_accessibility_name(TTRC("Site Search"));4449site_search->connect(SceneStringName(pressed), callable_mp(this, &ScriptEditor::_menu_option).bind(SEARCH_WEBSITE));4450menu_hb->add_child(site_search);44514452help_search = memnew(Button);4453help_search->set_theme_type_variation(SceneStringName(FlatButton));4454help_search->set_text(TTRC("Search Help"));4455help_search->connect(SceneStringName(pressed), callable_mp(this, &ScriptEditor::_menu_option).bind(SEARCH_HELP));4456menu_hb->add_child(help_search);4457help_search->set_tooltip_text(TTRC("Search the reference documentation."));44584459menu_hb->add_child(memnew(VSeparator));44604461script_back = memnew(Button);4462script_back->set_theme_type_variation(SceneStringName(FlatButton));4463script_back->connect(SceneStringName(pressed), callable_mp(this, &ScriptEditor::_history_back));4464menu_hb->add_child(script_back);4465script_back->set_disabled(true);4466script_back->set_tooltip_text(TTRC("Go to previous edited document."));44674468script_forward = memnew(Button);4469script_forward->set_theme_type_variation(SceneStringName(FlatButton));4470script_forward->connect(SceneStringName(pressed), callable_mp(this, &ScriptEditor::_history_forward));4471menu_hb->add_child(script_forward);4472script_forward->set_disabled(true);4473script_forward->set_tooltip_text(TTRC("Go to next edited document."));44744475menu_hb->add_child(memnew(VSeparator));44764477make_floating = memnew(ScreenSelect);4478make_floating->set_tooltip_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);4479make_floating->connect("request_open_in_screen", callable_mp(window_wrapper, &WindowWrapper::enable_window_on_screen).bind(true));44804481menu_hb->add_child(make_floating);4482p_wrapper->connect("window_visibility_changed", callable_mp(this, &ScriptEditor::_window_changed));44834484tab_container->connect("tab_changed", callable_mp(this, &ScriptEditor::_tab_changed));44854486erase_tab_confirm = memnew(ConfirmationDialog);4487erase_tab_confirm->set_ok_button_text(TTRC("Save"));4488erase_tab_confirm->add_button(TTRC("Discard"), DisplayServer::get_singleton()->get_swap_cancel_ok(), "discard");4489erase_tab_confirm->connect(SceneStringName(confirmed), callable_mp(this, &ScriptEditor::_close_current_tab).bind(true, true));4490erase_tab_confirm->connect("custom_action", callable_mp(this, &ScriptEditor::_close_discard_current_tab));4491add_child(erase_tab_confirm);44924493script_create_dialog = memnew(ScriptCreateDialog);4494script_create_dialog->set_title(TTRC("Create Script"));4495add_child(script_create_dialog);4496script_create_dialog->connect("script_created", callable_mp(this, &ScriptEditor::_script_created));44974498file_dialog_option = -1;4499file_dialog = memnew(EditorFileDialog);4500add_child(file_dialog);4501file_dialog->connect("file_selected", callable_mp(this, &ScriptEditor::_file_dialog_action));45024503error_dialog = memnew(AcceptDialog);4504add_child(error_dialog);45054506disk_changed = memnew(ConfirmationDialog);4507{4508disk_changed->set_title(TTRC("Files have been modified outside Godot"));45094510VBoxContainer *vbc = memnew(VBoxContainer);4511disk_changed->add_child(vbc);45124513Label *files_are_newer_label = memnew(Label);4514files_are_newer_label->set_text(TTRC("The following files are newer on disk:"));4515vbc->add_child(files_are_newer_label);45164517disk_changed_list = memnew(Tree);4518disk_changed_list->set_hide_root(true);4519disk_changed_list->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);4520disk_changed_list->set_accessibility_name(TTRC("The following files are newer on disk:"));4521disk_changed_list->set_v_size_flags(SIZE_EXPAND_FILL);4522vbc->add_child(disk_changed_list);45234524Label *what_action_label = memnew(Label);4525what_action_label->set_text(TTRC("What action should be taken?"));4526vbc->add_child(what_action_label);45274528disk_changed->connect(SceneStringName(confirmed), callable_mp(this, &ScriptEditor::reload_scripts).bind(false));4529disk_changed->set_ok_button_text(TTRC("Reload from disk"));45304531disk_changed->add_button(TTRC("Ignore external changes"), !DisplayServer::get_singleton()->get_swap_cancel_ok(), "resave");4532disk_changed->connect("custom_action", callable_mp(this, &ScriptEditor::_resave_scripts));4533}45344535add_child(disk_changed);45364537script_editor = this;45384539autosave_timer = memnew(Timer);4540autosave_timer->set_one_shot(false);4541autosave_timer->connect(SceneStringName(tree_entered), callable_mp(this, &ScriptEditor::_update_autosave_timer));4542autosave_timer->connect("timeout", callable_mp(this, &ScriptEditor::_autosave_scripts));4543add_child(autosave_timer);45444545grab_focus_block = false;45464547help_search_dialog = memnew(EditorHelpSearch);4548add_child(help_search_dialog);4549help_search_dialog->connect("go_to_help", callable_mp(this, &ScriptEditor::_help_class_goto));45504551find_in_files_dialog = memnew(FindInFilesDialog);4552find_in_files_dialog->connect(FindInFilesDialog::SIGNAL_FIND_REQUESTED, callable_mp(this, &ScriptEditor::_start_find_in_files).bind(false));4553find_in_files_dialog->connect(FindInFilesDialog::SIGNAL_REPLACE_REQUESTED, callable_mp(this, &ScriptEditor::_start_find_in_files).bind(true));4554add_child(find_in_files_dialog);4555find_in_files = memnew(FindInFilesPanel);4556find_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")));4557find_in_files->set_custom_minimum_size(Size2(0, 200) * EDSCALE);4558find_in_files->connect(FindInFilesPanel::SIGNAL_RESULT_SELECTED, callable_mp(this, &ScriptEditor::_on_find_in_files_result_selected));4559find_in_files->connect(FindInFilesPanel::SIGNAL_FILES_MODIFIED, callable_mp(this, &ScriptEditor::_on_find_in_files_modified_files));4560find_in_files->connect(FindInFilesPanel::SIGNAL_CLOSE_BUTTON_CLICKED, callable_mp(this, &ScriptEditor::_on_find_in_files_close_button_clicked));4561find_in_files->hide();4562find_in_files_button->hide();45634564history_pos = -1;45654566edit_pass = 0;4567trim_trailing_whitespace_on_save = EDITOR_GET("text_editor/behavior/files/trim_trailing_whitespace_on_save");4568trim_final_newlines_on_save = EDITOR_GET("text_editor/behavior/files/trim_final_newlines_on_save");4569convert_indent_on_save = EDITOR_GET("text_editor/behavior/files/convert_indent_on_save");45704571ScriptServer::edit_request_func = _open_script_request;45724573Ref<EditorJSONSyntaxHighlighter> json_syntax_highlighter;4574json_syntax_highlighter.instantiate();4575register_syntax_highlighter(json_syntax_highlighter);45764577Ref<EditorMarkdownSyntaxHighlighter> markdown_syntax_highlighter;4578markdown_syntax_highlighter.instantiate();4579register_syntax_highlighter(markdown_syntax_highlighter);45804581Ref<EditorConfigFileSyntaxHighlighter> config_file_syntax_highlighter;4582config_file_syntax_highlighter.instantiate();4583register_syntax_highlighter(config_file_syntax_highlighter);45844585_update_online_doc();4586}45874588void ScriptEditorPlugin::_focus_another_editor() {4589if (window_wrapper->get_window_enabled()) {4590ERR_FAIL_COND(last_editor.is_empty());4591EditorInterface::get_singleton()->set_main_screen_editor(last_editor);4592}4593}45944595void ScriptEditorPlugin::_save_last_editor(const String &p_editor) {4596if (p_editor != get_plugin_name()) {4597last_editor = p_editor;4598}4599}46004601void ScriptEditorPlugin::_window_visibility_changed(bool p_visible) {4602_focus_another_editor();4603if (p_visible) {4604script_editor->add_theme_style_override(SceneStringName(panel), script_editor->get_theme_stylebox("ScriptEditorPanelFloating", EditorStringName(EditorStyles)));4605} else {4606script_editor->add_theme_style_override(SceneStringName(panel), script_editor->get_theme_stylebox("ScriptEditorPanel", EditorStringName(EditorStyles)));4607}4608}46094610void ScriptEditorPlugin::_notification(int p_what) {4611switch (p_what) {4612case NOTIFICATION_TRANSLATION_CHANGED: {4613window_wrapper->set_window_title(vformat(TTR("%s - Godot Engine"), TTR("Script Editor")));4614} break;4615case NOTIFICATION_ENTER_TREE: {4616connect("main_screen_changed", callable_mp(this, &ScriptEditorPlugin::_save_last_editor));4617} break;4618case NOTIFICATION_EXIT_TREE: {4619disconnect("main_screen_changed", callable_mp(this, &ScriptEditorPlugin::_save_last_editor));4620} break;4621}4622}46234624void ScriptEditorPlugin::edit(Object *p_object) {4625if (Object::cast_to<Script>(p_object)) {4626Script *p_script = Object::cast_to<Script>(p_object);4627String res_path = p_script->get_path().get_slice("::", 0);46284629if (p_script->is_built_in() && !res_path.is_empty()) {4630EditorNode::get_singleton()->load_scene_or_resource(res_path, false, false);4631}4632script_editor->edit(p_script);4633} else if (Object::cast_to<JSON>(p_object)) {4634script_editor->edit(Object::cast_to<JSON>(p_object));4635} else if (Object::cast_to<TextFile>(p_object)) {4636script_editor->edit(Object::cast_to<TextFile>(p_object));4637}4638}46394640bool ScriptEditorPlugin::handles(Object *p_object) const {4641if (Object::cast_to<TextFile>(p_object)) {4642return true;4643}46444645if (Object::cast_to<Script>(p_object)) {4646return true;4647}46484649if (Object::cast_to<JSON>(p_object)) {4650return true;4651}46524653return p_object->is_class("Script");4654}46554656void ScriptEditorPlugin::make_visible(bool p_visible) {4657if (p_visible) {4658window_wrapper->show();4659script_editor->ensure_select_current();4660} else {4661window_wrapper->hide();4662}4663}46644665void ScriptEditorPlugin::selected_notify() {4666script_editor->ensure_select_current();4667_focus_another_editor();4668}46694670String ScriptEditorPlugin::get_unsaved_status(const String &p_for_scene) const {4671const PackedStringArray unsaved_scripts = script_editor->get_unsaved_scripts();4672if (unsaved_scripts.is_empty()) {4673return String();4674}46754676PackedStringArray message;4677if (!p_for_scene.is_empty()) {4678PackedStringArray unsaved_built_in_scripts;46794680const String scene_file = p_for_scene.get_file();4681for (const String &E : unsaved_scripts) {4682if (!E.is_resource_file() && E.contains(scene_file)) {4683unsaved_built_in_scripts.append(E);4684}4685}46864687if (unsaved_built_in_scripts.is_empty()) {4688return String();4689} else {4690message.resize(unsaved_built_in_scripts.size() + 1);4691message.write[0] = TTR("There are unsaved changes in the following built-in script(s):");46924693int i = 1;4694for (const String &E : unsaved_built_in_scripts) {4695message.write[i] = E.trim_suffix("(*)");4696i++;4697}4698return String("\n").join(message);4699}4700}47014702message.resize(unsaved_scripts.size() + 1);4703message.write[0] = TTR("Save changes to the following script(s) before quitting?");47044705int i = 1;4706for (const String &E : unsaved_scripts) {4707message.write[i] = E.trim_suffix("(*)");4708i++;4709}4710return String("\n").join(message);4711}47124713void ScriptEditorPlugin::save_external_data() {4714if (!EditorNode::get_singleton()->is_exiting()) {4715script_editor->save_all_scripts();4716}4717}47184719void ScriptEditorPlugin::apply_changes() {4720script_editor->apply_scripts();4721}47224723void ScriptEditorPlugin::set_window_layout(Ref<ConfigFile> p_layout) {4724script_editor->set_window_layout(p_layout);47254726if (EDITOR_GET("interface/multi_window/restore_windows_on_load") && window_wrapper->is_window_available() && p_layout->has_section_key("ScriptEditor", "window_rect")) {4727window_wrapper->restore_window_from_saved_position(4728p_layout->get_value("ScriptEditor", "window_rect", Rect2i()),4729p_layout->get_value("ScriptEditor", "window_screen", -1),4730p_layout->get_value("ScriptEditor", "window_screen_rect", Rect2i()));4731} else {4732window_wrapper->set_window_enabled(false);4733}4734}47354736void ScriptEditorPlugin::get_window_layout(Ref<ConfigFile> p_layout) {4737script_editor->get_window_layout(p_layout);47384739if (window_wrapper->get_window_enabled()) {4740p_layout->set_value("ScriptEditor", "window_rect", window_wrapper->get_window_rect());4741int screen = window_wrapper->get_window_screen();4742p_layout->set_value("ScriptEditor", "window_screen", screen);4743p_layout->set_value("ScriptEditor", "window_screen_rect", DisplayServer::get_singleton()->screen_get_usable_rect(screen));47444745} else {4746if (p_layout->has_section_key("ScriptEditor", "window_rect")) {4747p_layout->erase_section_key("ScriptEditor", "window_rect");4748}4749if (p_layout->has_section_key("ScriptEditor", "window_screen")) {4750p_layout->erase_section_key("ScriptEditor", "window_screen");4751}4752if (p_layout->has_section_key("ScriptEditor", "window_screen_rect")) {4753p_layout->erase_section_key("ScriptEditor", "window_screen_rect");4754}4755}4756}47574758void ScriptEditorPlugin::get_breakpoints(List<String> *p_breakpoints) {4759script_editor->get_breakpoints(p_breakpoints);4760}47614762void ScriptEditorPlugin::edited_scene_changed() {4763script_editor->edited_scene_changed();4764}47654766ScriptEditorPlugin::ScriptEditorPlugin() {4767ED_SHORTCUT("script_editor/reopen_closed_script", TTRC("Reopen Closed Script"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::T);4768ED_SHORTCUT("script_editor/clear_recent", TTRC("Clear Recent Scripts"));4769ED_SHORTCUT("script_editor/replace_in_files", TTRC("Replace in Files"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::R);47704771ED_SHORTCUT("script_text_editor/convert_to_uppercase", TTRC("Uppercase"), KeyModifierMask::SHIFT | Key::F4);4772ED_SHORTCUT("script_text_editor/convert_to_lowercase", TTRC("Lowercase"), KeyModifierMask::SHIFT | Key::F5);4773ED_SHORTCUT("script_text_editor/capitalize", TTRC("Capitalize"), KeyModifierMask::SHIFT | Key::F6);47744775window_wrapper = memnew(WindowWrapper);4776window_wrapper->set_margins_enabled(true);47774778script_editor = memnew(ScriptEditor(window_wrapper));4779Ref<Shortcut> make_floating_shortcut = ED_SHORTCUT_AND_COMMAND("script_editor/make_floating", TTRC("Make Floating"));4780window_wrapper->set_wrapped_control(script_editor, make_floating_shortcut);47814782EditorNode::get_singleton()->get_editor_main_screen()->get_control()->add_child(window_wrapper);4783window_wrapper->set_v_size_flags(Control::SIZE_EXPAND_FILL);4784window_wrapper->hide();4785window_wrapper->connect("window_visibility_changed", callable_mp(this, &ScriptEditorPlugin::_window_visibility_changed));47864787ScriptServer::set_reload_scripts_on_save(EDITOR_GET("text_editor/behavior/files/auto_reload_and_parse_scripts_on_save"));4788}478947904791