Path: blob/master/editor/script/script_text_editor.cpp
9903 views
/**************************************************************************/1/* script_text_editor.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_text_editor.h"3132#include "core/config/project_settings.h"33#include "core/io/dir_access.h"34#include "core/io/json.h"35#include "core/math/expression.h"36#include "core/os/keyboard.h"37#include "editor/debugger/editor_debugger_node.h"38#include "editor/doc/editor_help.h"39#include "editor/docks/filesystem_dock.h"40#include "editor/editor_node.h"41#include "editor/editor_string_names.h"42#include "editor/gui/editor_toaster.h"43#include "editor/inspector/editor_context_menu_plugin.h"44#include "editor/settings/editor_command_palette.h"45#include "editor/settings/editor_settings.h"46#include "editor/themes/editor_scale.h"47#include "scene/gui/grid_container.h"48#include "scene/gui/menu_button.h"49#include "scene/gui/rich_text_label.h"50#include "scene/gui/slider.h"51#include "scene/gui/split_container.h"5253void ConnectionInfoDialog::ok_pressed() {54}5556void ConnectionInfoDialog::popup_connections(const String &p_method, const Vector<Node *> &p_nodes) {57method->set_text(p_method);5859tree->clear();60TreeItem *root = tree->create_item();6162for (int i = 0; i < p_nodes.size(); i++) {63List<Connection> all_connections;64p_nodes[i]->get_signals_connected_to_this(&all_connections);6566for (const Connection &connection : all_connections) {67if (connection.callable.get_method() != p_method) {68continue;69}7071TreeItem *node_item = tree->create_item(root);7273node_item->set_text(0, Object::cast_to<Node>(connection.signal.get_object())->get_name());74node_item->set_icon(0, EditorNode::get_singleton()->get_object_icon(connection.signal.get_object(), "Node"));75node_item->set_selectable(0, false);76node_item->set_editable(0, false);7778node_item->set_text(1, connection.signal.get_name());79Control *p = Object::cast_to<Control>(get_parent());80node_item->set_icon(1, p->get_editor_theme_icon(SNAME("Slot")));81node_item->set_selectable(1, false);82node_item->set_editable(1, false);8384node_item->set_text(2, Object::cast_to<Node>(connection.callable.get_object())->get_name());85node_item->set_icon(2, EditorNode::get_singleton()->get_object_icon(connection.callable.get_object(), "Node"));86node_item->set_selectable(2, false);87node_item->set_editable(2, false);88}89}9091popup_centered(Size2(600, 300) * EDSCALE);92}9394ConnectionInfoDialog::ConnectionInfoDialog() {95set_title(TTRC("Connections to method:"));9697VBoxContainer *vbc = memnew(VBoxContainer);98vbc->set_anchor_and_offset(SIDE_LEFT, Control::ANCHOR_BEGIN, 8 * EDSCALE);99vbc->set_anchor_and_offset(SIDE_TOP, Control::ANCHOR_BEGIN, 8 * EDSCALE);100vbc->set_anchor_and_offset(SIDE_RIGHT, Control::ANCHOR_END, -8 * EDSCALE);101vbc->set_anchor_and_offset(SIDE_BOTTOM, Control::ANCHOR_END, -8 * EDSCALE);102add_child(vbc);103104method = memnew(Label);105method->set_focus_mode(Control::FOCUS_ACCESSIBILITY);106method->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);107method->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);108vbc->add_child(method);109110tree = memnew(Tree);111tree->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);112tree->set_columns(3);113tree->set_hide_root(true);114tree->set_column_titles_visible(true);115tree->set_column_title(0, TTRC("Source"));116tree->set_column_title(1, TTRC("Signal"));117tree->set_column_title(2, TTRC("Target"));118vbc->add_child(tree);119tree->set_v_size_flags(Control::SIZE_EXPAND_FILL);120tree->set_allow_rmb_select(true);121}122123////////////////////////////////////////////////////////////////////////////////124125Vector<String> ScriptTextEditor::get_functions() {126CodeEdit *te = code_editor->get_text_editor();127String text = te->get_text();128List<String> fnc;129130if (script->get_language()->validate(text, script->get_path(), &fnc)) {131//if valid rewrite functions to latest132functions.clear();133for (const String &E : fnc) {134functions.push_back(E);135}136}137138return functions;139}140141void ScriptTextEditor::apply_code() {142if (script.is_null()) {143return;144}145script->set_source_code(code_editor->get_text_editor()->get_text());146script->update_exports();147code_editor->get_text_editor()->get_syntax_highlighter()->update_cache();148}149150Ref<Resource> ScriptTextEditor::get_edited_resource() const {151return script;152}153154void ScriptTextEditor::set_edited_resource(const Ref<Resource> &p_res) {155ERR_FAIL_COND(script.is_valid());156ERR_FAIL_COND(p_res.is_null());157158script = p_res;159160code_editor->get_text_editor()->set_text(script->get_source_code());161code_editor->get_text_editor()->clear_undo_history();162code_editor->get_text_editor()->tag_saved_version();163164emit_signal(SNAME("name_changed"));165code_editor->update_line_and_column();166}167168void ScriptTextEditor::enable_editor(Control *p_shortcut_context) {169if (editor_enabled) {170return;171}172173editor_enabled = true;174175_enable_code_editor();176177_validate_script();178179if (p_shortcut_context) {180for (int i = 0; i < edit_hb->get_child_count(); ++i) {181Control *c = cast_to<Control>(edit_hb->get_child(i));182if (c) {183c->set_shortcut_context(p_shortcut_context);184}185}186}187}188189void ScriptTextEditor::_load_theme_settings() {190CodeEdit *text_edit = code_editor->get_text_editor();191192Color updated_warning_line_color = EDITOR_GET("text_editor/theme/highlighting/warning_color");193Color updated_marked_line_color = EDITOR_GET("text_editor/theme/highlighting/mark_color");194Color updated_safe_line_number_color = EDITOR_GET("text_editor/theme/highlighting/safe_line_number_color");195Color updated_folded_code_region_color = EDITOR_GET("text_editor/theme/highlighting/folded_code_region_color");196197bool warning_line_color_updated = updated_warning_line_color != warning_line_color;198bool marked_line_color_updated = updated_marked_line_color != marked_line_color;199bool safe_line_number_color_updated = updated_safe_line_number_color != safe_line_number_color;200bool folded_code_region_color_updated = updated_folded_code_region_color != folded_code_region_color;201if (safe_line_number_color_updated || warning_line_color_updated || marked_line_color_updated || folded_code_region_color_updated) {202safe_line_number_color = updated_safe_line_number_color;203for (int i = 0; i < text_edit->get_line_count(); i++) {204if (warning_line_color_updated && text_edit->get_line_background_color(i) == warning_line_color) {205text_edit->set_line_background_color(i, updated_warning_line_color);206}207208if (marked_line_color_updated && text_edit->get_line_background_color(i) == marked_line_color) {209text_edit->set_line_background_color(i, updated_marked_line_color);210}211212if (safe_line_number_color_updated && text_edit->get_line_gutter_item_color(i, line_number_gutter) != default_line_number_color) {213text_edit->set_line_gutter_item_color(i, line_number_gutter, safe_line_number_color);214}215216if (folded_code_region_color_updated && text_edit->get_line_background_color(i) == folded_code_region_color) {217text_edit->set_line_background_color(i, updated_folded_code_region_color);218}219}220warning_line_color = updated_warning_line_color;221marked_line_color = updated_marked_line_color;222folded_code_region_color = updated_folded_code_region_color;223}224225theme_loaded = true;226if (script.is_valid()) {227_set_theme_for_script();228}229}230231void ScriptTextEditor::_set_theme_for_script() {232if (!theme_loaded) {233return;234}235236CodeEdit *text_edit = code_editor->get_text_editor();237text_edit->get_syntax_highlighter()->update_cache();238239Vector<String> strings = script->get_language()->get_string_delimiters();240text_edit->clear_string_delimiters();241for (const String &string : strings) {242String beg = string.get_slicec(' ', 0);243String end = string.get_slice_count(" ") > 1 ? string.get_slicec(' ', 1) : String();244if (!text_edit->has_string_delimiter(beg)) {245text_edit->add_string_delimiter(beg, end, end.is_empty());246}247248if (!end.is_empty() && !text_edit->has_auto_brace_completion_open_key(beg)) {249text_edit->add_auto_brace_completion_pair(beg, end);250}251}252253text_edit->clear_comment_delimiters();254255for (const String &comment : script->get_language()->get_comment_delimiters()) {256String beg = comment.get_slicec(' ', 0);257String end = comment.get_slice_count(" ") > 1 ? comment.get_slicec(' ', 1) : String();258text_edit->add_comment_delimiter(beg, end, end.is_empty());259260if (!end.is_empty() && !text_edit->has_auto_brace_completion_open_key(beg)) {261text_edit->add_auto_brace_completion_pair(beg, end);262}263}264265for (const String &doc_comment : script->get_language()->get_doc_comment_delimiters()) {266String beg = doc_comment.get_slicec(' ', 0);267String end = doc_comment.get_slice_count(" ") > 1 ? doc_comment.get_slicec(' ', 1) : String();268text_edit->add_comment_delimiter(beg, end, end.is_empty());269270if (!end.is_empty() && !text_edit->has_auto_brace_completion_open_key(beg)) {271text_edit->add_auto_brace_completion_pair(beg, end);272}273}274}275276void ScriptTextEditor::_show_errors_panel(bool p_show) {277errors_panel->set_visible(p_show);278}279280void ScriptTextEditor::_show_warnings_panel(bool p_show) {281warnings_panel->set_visible(p_show);282}283284void ScriptTextEditor::_warning_clicked(const Variant &p_line) {285if (p_line.get_type() == Variant::INT) {286goto_line_centered(p_line.operator int64_t());287} else if (p_line.get_type() == Variant::DICTIONARY) {288Dictionary meta = p_line.operator Dictionary();289const int line = meta["line"].operator int64_t() - 1;290const String code = meta["code"].operator String();291const String quote_style = EDITOR_GET("text_editor/completion/use_single_quotes") ? "'" : "\"";292293CodeEdit *text_editor = code_editor->get_text_editor();294String prev_line = line > 0 ? text_editor->get_line(line - 1) : "";295if (prev_line.contains("@warning_ignore")) {296const int closing_bracket_idx = prev_line.find_char(')');297const String text_to_insert = ", " + code.quote(quote_style);298text_editor->insert_text(text_to_insert, line - 1, closing_bracket_idx);299} else {300const int indent = text_editor->get_indent_level(line) / text_editor->get_indent_size();301String annotation_indent;302if (!text_editor->is_indent_using_spaces()) {303annotation_indent = String("\t").repeat(indent);304} else {305annotation_indent = String(" ").repeat(text_editor->get_indent_size() * indent);306}307text_editor->insert_line_at(line, annotation_indent + "@warning_ignore(" + code.quote(quote_style) + ")");308}309310_validate_script();311}312}313314void ScriptTextEditor::_error_clicked(const Variant &p_line) {315if (p_line.get_type() == Variant::INT) {316goto_line_centered(p_line.operator int64_t());317} else if (p_line.get_type() == Variant::DICTIONARY) {318Dictionary meta = p_line.operator Dictionary();319const String path = meta["path"].operator String();320const int line = meta["line"].operator int64_t();321const int column = meta["column"].operator int64_t();322if (path.is_empty()) {323goto_line_centered(line, column);324} else {325Ref<Resource> scr = ResourceLoader::load(path);326if (scr.is_null()) {327EditorNode::get_singleton()->show_warning(TTR("Could not load file at:") + "\n\n" + path, TTR("Error!"));328} else {329int corrected_column = column;330331const String line_text = code_editor->get_text_editor()->get_line(line);332const int indent_size = code_editor->get_text_editor()->get_indent_size();333if (indent_size > 1) {334const int tab_count = line_text.length() - line_text.lstrip("\t").length();335corrected_column -= tab_count * (indent_size - 1);336}337338ScriptEditor::get_singleton()->edit(scr, line, corrected_column);339}340}341}342}343344void ScriptTextEditor::reload_text() {345ERR_FAIL_COND(script.is_null());346347CodeEdit *te = code_editor->get_text_editor();348int column = te->get_caret_column();349int row = te->get_caret_line();350int h = te->get_h_scroll();351int v = te->get_v_scroll();352353te->set_text(script->get_source_code());354te->set_caret_line(row);355te->set_caret_column(column);356te->set_h_scroll(h);357te->set_v_scroll(v);358359te->tag_saved_version();360361code_editor->update_line_and_column();362if (editor_enabled) {363_validate_script();364}365}366367void ScriptTextEditor::add_callback(const String &p_function, const PackedStringArray &p_args) {368ScriptLanguage *language = script->get_language();369if (!language->can_make_function()) {370return;371}372code_editor->get_text_editor()->begin_complex_operation();373code_editor->get_text_editor()->remove_secondary_carets();374code_editor->get_text_editor()->deselect();375String code = code_editor->get_text_editor()->get_text();376int pos = language->find_function(p_function, code);377if (pos == -1) {378// Function does not exist, create it at the end of the file.379int last_line = code_editor->get_text_editor()->get_line_count() - 1;380String func = language->make_function("", p_function, p_args);381code_editor->get_text_editor()->insert_text("\n\n" + func, last_line, code_editor->get_text_editor()->get_line(last_line).length());382pos = last_line + 3;383}384// Put caret on the line after the function, after the indent.385int indent_column = 1;386if (EDITOR_GET("text_editor/behavior/indent/type")) {387indent_column = EDITOR_GET("text_editor/behavior/indent/size");388}389code_editor->get_text_editor()->set_caret_line(pos, true, true, -1);390code_editor->get_text_editor()->set_caret_column(indent_column);391code_editor->get_text_editor()->end_complex_operation();392}393394bool ScriptTextEditor::show_members_overview() {395return true;396}397398bool ScriptTextEditor::_is_valid_color_info(const Dictionary &p_info) {399if (p_info.get_valid("color").get_type() != Variant::COLOR) {400return false;401}402if (!p_info.get_valid("color_end").is_num() || !p_info.get_valid("color_mode").is_num()) {403return false;404}405return true;406}407408Array ScriptTextEditor::_inline_object_parse(const String &p_text) {409Array result;410int i_end_previous = 0;411int i_start = p_text.find("Color");412413while (i_start != -1) {414// Ignore words that just have "Color" in them.415if (i_start != 0 && ('_' + p_text.substr(i_start - 1, 1)).is_valid_ascii_identifier()) {416i_end_previous = MAX(i_end_previous, i_start);417i_start = p_text.find("Color", i_start + 1);418continue;419}420421const int i_par_start = p_text.find_char('(', i_start + 5);422const int i_par_end = p_text.find_char(')', i_start + 5);423if (i_par_start == -1 || i_par_end == -1) {424i_end_previous = MAX(i_end_previous, i_start);425i_start = p_text.find("Color", i_start + 1);426continue;427}428429Dictionary color_info;430color_info["column"] = i_start;431color_info["width_ratio"] = 1.0;432color_info["color_end"] = i_par_end;433434const String fn_name = p_text.substr(i_start + 5, i_par_start - i_start - 5);435const String s_params = p_text.substr(i_par_start + 1, i_par_end - i_par_start - 1);436bool has_added_color = false;437438if (fn_name.is_empty()) {439String stripped = s_params.strip_edges(true, true);440if (stripped.length() > 1 && (stripped[0] == '"' || stripped[0] == '\'')) {441// String constructor.442const char32_t string_delimiter = stripped[0];443if (stripped[stripped.length() - 1] == string_delimiter) {444const String color_string = stripped.substr(1, stripped.length() - 2);445if (!color_string.contains_char(string_delimiter)) {446color_info["color"] = Color::from_string(color_string, Color());447color_info["color_mode"] = MODE_STRING;448has_added_color = true;449}450}451} else if (stripped.length() == 10 && stripped.begins_with("0x")) {452// Hex constructor.453const String color_string = stripped.substr(2);454if (color_string.is_valid_hex_number(false)) {455color_info["color"] = Color::from_string(color_string, Color());456color_info["color_mode"] = MODE_HEX;457has_added_color = true;458}459} else if (stripped.is_empty()) {460// Empty Color() constructor.461color_info["color"] = Color();462color_info["color_mode"] = MODE_RGB;463has_added_color = true;464}465}466// Float & int parameters.467if (!has_added_color && s_params.size() > 0) {468const PackedStringArray s_params_split = s_params.split(",", false, 4);469PackedFloat64Array params;470bool valid_floats = true;471for (const String &s_param : s_params_split) {472// Only allow float literals, expressions won't be evaluated and could get replaced.473if (!s_param.strip_edges().is_valid_float()) {474valid_floats = false;475break;476}477params.push_back(s_param.to_float());478}479if (valid_floats && params.size() == 3) {480params.push_back(1.0);481}482if (valid_floats && params.size() == 4) {483has_added_color = true;484if (fn_name == ".from_ok_hsl") {485color_info["color"] = Color::from_ok_hsl(params[0], params[1], params[2], params[3]);486color_info["color_mode"] = MODE_OKHSL;487} else if (fn_name == ".from_hsv") {488color_info["color"] = Color::from_hsv(params[0], params[1], params[2], params[3]);489color_info["color_mode"] = MODE_HSV;490} else if (fn_name == ".from_rgba8") {491color_info["color"] = Color::from_rgba8(int(params[0]), int(params[1]), int(params[2]), int(params[3]));492color_info["color_mode"] = MODE_RGB8;493} else if (fn_name.is_empty()) {494color_info["color"] = Color(params[0], params[1], params[2], params[3]);495color_info["color_mode"] = MODE_RGB;496} else {497has_added_color = false;498}499}500}501502if (has_added_color) {503result.push_back(color_info);504i_end_previous = i_par_end + 1;505}506i_end_previous = MAX(i_end_previous, i_start);507i_start = p_text.find("Color", i_start + 1);508}509return result;510}511512void ScriptTextEditor::_inline_object_draw(const Dictionary &p_info, const Rect2 &p_rect) {513if (_is_valid_color_info(p_info)) {514Rect2 col_rect = p_rect.grow(-4);515if (color_alpha_texture.is_null()) {516color_alpha_texture = inline_color_picker->get_theme_icon("sample_bg", "ColorPicker");517}518code_editor->get_text_editor()->draw_texture_rect(color_alpha_texture, col_rect, false);519code_editor->get_text_editor()->draw_rect(col_rect, Color(p_info["color"]));520code_editor->get_text_editor()->draw_rect(col_rect, Color(1, 1, 1), false, 1);521}522}523524void ScriptTextEditor::_inline_object_handle_click(const Dictionary &p_info, const Rect2 &p_rect) {525if (_is_valid_color_info(p_info)) {526inline_color_picker->set_pick_color(p_info["color"]);527inline_color_line = p_info["line"];528inline_color_start = p_info["column"];529inline_color_end = p_info["color_end"];530531// Reset tooltip hover timer.532code_editor->get_text_editor()->set_symbol_tooltip_on_hover_enabled(false);533code_editor->get_text_editor()->set_symbol_tooltip_on_hover_enabled(true);534535_update_color_constructor_options();536inline_color_options->select(p_info["color_mode"]);537EditorNode::get_singleton()->setup_color_picker(inline_color_picker);538539// Move popup above the line if it's too low.540float_t view_h = get_viewport_rect().size.y;541float_t pop_h = inline_color_popup->get_contents_minimum_size().y;542float_t pop_y = p_rect.get_end().y;543float_t pop_x = p_rect.position.x;544if (pop_y + pop_h > view_h) {545pop_y = p_rect.position.y - pop_h;546}547// Move popup to the right if it's too high.548if (pop_y < 0) {549pop_x = p_rect.get_end().x;550}551552inline_color_popup->popup(Rect2(pop_x, pop_y, 0, 0));553}554}555556String ScriptTextEditor::_picker_color_stringify(const Color &p_color, COLOR_MODE p_mode) {557String result;558String fname;559Vector<String> str_params;560switch (p_mode) {561case ScriptTextEditor::MODE_STRING: {562str_params.push_back("\"" + p_color.to_html() + "\"");563} break;564case ScriptTextEditor::MODE_HEX: {565str_params.push_back("0x" + p_color.to_html());566} break;567case ScriptTextEditor::MODE_RGB: {568str_params = {569String::num(p_color.r, 3),570String::num(p_color.g, 3),571String::num(p_color.b, 3),572String::num(p_color.a, 3)573};574} break;575case ScriptTextEditor::MODE_HSV: {576str_params = {577String::num(p_color.get_h(), 3),578String::num(p_color.get_s(), 3),579String::num(p_color.get_v(), 3),580String::num(p_color.a, 3)581};582fname = ".from_hsv";583} break;584case ScriptTextEditor::MODE_OKHSL: {585str_params = {586String::num(p_color.get_ok_hsl_h(), 3),587String::num(p_color.get_ok_hsl_s(), 3),588String::num(p_color.get_ok_hsl_l(), 3),589String::num(p_color.a, 3)590};591fname = ".from_ok_hsl";592} break;593case ScriptTextEditor::MODE_RGB8: {594str_params = {595itos(p_color.get_r8()),596itos(p_color.get_g8()),597itos(p_color.get_b8()),598itos(p_color.get_a8())599};600fname = ".from_rgba8";601} break;602default: {603} break;604}605result = "Color" + fname + "(" + String(", ").join(str_params) + ")";606return result;607}608609void ScriptTextEditor::_picker_color_changed(const Color &p_color) {610_update_color_constructor_options();611_update_color_text();612}613614void ScriptTextEditor::_update_color_constructor_options() {615int item_count = inline_color_options->get_item_count();616// Update or add each constructor as an option.617for (int i = 0; i < MODE_MAX; i++) {618String option_text = _picker_color_stringify(inline_color_picker->get_pick_color(), (COLOR_MODE)i);619if (i >= item_count) {620inline_color_options->add_item(option_text);621} else {622inline_color_options->set_item_text(i, option_text);623}624}625}626627void ScriptTextEditor::_update_background_color() {628// Clear background lines.629CodeEdit *te = code_editor->get_text_editor();630for (int i = 0; i < te->get_line_count(); i++) {631bool is_folded_code_region = te->is_line_code_region_start(i) && te->is_line_folded(i);632te->set_line_background_color(i, is_folded_code_region ? folded_code_region_color : Color(0, 0, 0, 0));633}634635// Set the warning background.636if (warning_line_color.a != 0.0) {637for (const ScriptLanguage::Warning &warning : warnings) {638int warning_start_line = CLAMP(warning.start_line - 1, 0, te->get_line_count() - 1);639int warning_end_line = CLAMP(warning.end_line - 1, 0, te->get_line_count() - 1);640int folded_line_header = te->get_folded_line_header(warning_start_line);641642// If the warning highlight is too long, only highlight the start line.643const int warning_max_lines = 20;644645te->set_line_background_color(folded_line_header, warning_line_color);646if (warning_end_line - warning_start_line < warning_max_lines) {647for (int i = warning_start_line + 1; i <= warning_end_line; i++) {648te->set_line_background_color(i, warning_line_color);649}650}651}652}653654// Set the error background.655if (marked_line_color.a != 0.0) {656for (const ScriptLanguage::ScriptError &error : errors) {657int error_line = CLAMP(error.line - 1, 0, te->get_line_count() - 1);658int folded_line_header = te->get_folded_line_header(error_line);659660te->set_line_background_color(folded_line_header, marked_line_color);661}662}663}664665void ScriptTextEditor::_update_color_text() {666if (inline_color_line < 0) {667return;668}669String result = inline_color_options->get_item_text(inline_color_options->get_selected_id());670code_editor->get_text_editor()->begin_complex_operation();671code_editor->get_text_editor()->remove_text(inline_color_line, inline_color_start, inline_color_line, inline_color_end + 1);672inline_color_end = inline_color_start + result.size() - 2;673code_editor->get_text_editor()->insert_text(result, inline_color_line, inline_color_start);674code_editor->get_text_editor()->end_complex_operation();675}676677void ScriptTextEditor::update_settings() {678code_editor->get_text_editor()->set_gutter_draw(connection_gutter, EDITOR_GET("text_editor/appearance/gutters/show_info_gutter"));679if (EDITOR_GET("text_editor/appearance/enable_inline_color_picker")) {680code_editor->get_text_editor()->set_inline_object_handlers(681callable_mp(this, &ScriptTextEditor::_inline_object_parse),682callable_mp(this, &ScriptTextEditor::_inline_object_draw),683callable_mp(this, &ScriptTextEditor::_inline_object_handle_click));684} else {685code_editor->get_text_editor()->set_inline_object_handlers(Callable(), Callable(), Callable());686}687code_editor->update_editor_settings();688}689690bool ScriptTextEditor::is_unsaved() {691const bool unsaved =692code_editor->get_text_editor()->get_version() != code_editor->get_text_editor()->get_saved_version() ||693script->get_path().is_empty(); // In memory.694return unsaved;695}696697Variant ScriptTextEditor::get_edit_state() {698return code_editor->get_edit_state();699}700701void ScriptTextEditor::set_edit_state(const Variant &p_state) {702code_editor->set_edit_state(p_state);703704Dictionary state = p_state;705if (state.has("syntax_highlighter")) {706int idx = highlighter_menu->get_item_idx_from_text(state["syntax_highlighter"]);707if (idx >= 0) {708_change_syntax_highlighter(idx);709}710}711712if (editor_enabled) {713#ifndef ANDROID_ENABLED714ensure_focus();715#endif716}717}718719Variant ScriptTextEditor::get_navigation_state() {720return code_editor->get_navigation_state();721}722723Variant ScriptTextEditor::get_previous_state() {724return code_editor->get_previous_state();725}726727void ScriptTextEditor::store_previous_state() {728return code_editor->store_previous_state();729}730731void ScriptTextEditor::_convert_case(CodeTextEditor::CaseStyle p_case) {732code_editor->convert_case(p_case);733}734735void ScriptTextEditor::trim_trailing_whitespace() {736code_editor->trim_trailing_whitespace();737}738739void ScriptTextEditor::trim_final_newlines() {740code_editor->trim_final_newlines();741}742743void ScriptTextEditor::insert_final_newline() {744code_editor->insert_final_newline();745}746747void ScriptTextEditor::convert_indent() {748code_editor->get_text_editor()->convert_indent();749}750751void ScriptTextEditor::tag_saved_version() {752code_editor->get_text_editor()->tag_saved_version();753edited_file_data.last_modified_time = FileAccess::get_modified_time(edited_file_data.path);754}755756void ScriptTextEditor::goto_line(int p_line, int p_column) {757code_editor->goto_line(p_line, p_column);758}759760void ScriptTextEditor::goto_line_selection(int p_line, int p_begin, int p_end) {761code_editor->goto_line_selection(p_line, p_begin, p_end);762}763764void ScriptTextEditor::goto_line_centered(int p_line, int p_column) {765code_editor->goto_line_centered(p_line, p_column);766}767768void ScriptTextEditor::set_executing_line(int p_line) {769code_editor->set_executing_line(p_line);770}771772void ScriptTextEditor::clear_executing_line() {773code_editor->clear_executing_line();774}775776void ScriptTextEditor::ensure_focus() {777code_editor->get_text_editor()->grab_focus();778}779780String ScriptTextEditor::get_name() {781String name;782783name = script->get_path().get_file();784if (name.is_empty()) {785// This appears for newly created built-in scripts before saving the scene.786name = TTR("[unsaved]");787} else if (script->is_built_in()) {788const String &script_name = script->get_name();789if (!script_name.is_empty()) {790// If the built-in script has a custom resource name defined,791// display the built-in script name as follows: `ResourceName (scene_file.tscn)`792name = vformat("%s (%s)", script_name, name.get_slice("::", 0));793}794}795796if (is_unsaved()) {797name += "(*)";798}799800return name;801}802803Ref<Texture2D> ScriptTextEditor::get_theme_icon() {804if (get_parent_control()) {805String icon_name = script->get_class();806if (script->is_built_in()) {807icon_name += "Internal";808}809810if (get_parent_control()->has_theme_icon(icon_name, EditorStringName(EditorIcons))) {811return get_parent_control()->get_editor_theme_icon(icon_name);812} else if (get_parent_control()->has_theme_icon(script->get_class(), EditorStringName(EditorIcons))) {813return get_parent_control()->get_editor_theme_icon(script->get_class());814}815}816817return Ref<Texture2D>();818}819820void ScriptTextEditor::_validate_script() {821CodeEdit *te = code_editor->get_text_editor();822823String text = te->get_text();824List<String> fnc;825826warnings.clear();827errors.clear();828depended_errors.clear();829safe_lines.clear();830831if (!script->get_language()->validate(text, script->get_path(), &fnc, &errors, &warnings, &safe_lines)) {832List<ScriptLanguage::ScriptError>::Element *E = errors.front();833while (E) {834List<ScriptLanguage::ScriptError>::Element *next_E = E->next();835if ((E->get().path.is_empty() && !script->get_path().is_empty()) || E->get().path != script->get_path()) {836depended_errors[E->get().path].push_back(E->get());837E->erase();838}839E = next_E;840}841842if (errors.size() > 0) {843const int line = errors.front()->get().line;844const int column = errors.front()->get().column;845const String message = errors.front()->get().message.replace("[", "[lb]");846const String error_text = vformat(TTR("Error at ([hint=Line %d, column %d]%d, %d[/hint]):"), line, column, line, column) + " " + message;847code_editor->set_error(error_text);848code_editor->set_error_pos(line - 1, column - 1);849}850script_is_valid = false;851} else {852code_editor->set_error("");853if (!script->is_tool()) {854script->set_source_code(text);855script->update_exports();856te->get_syntax_highlighter()->update_cache();857}858859functions.clear();860for (const String &E : fnc) {861functions.push_back(E);862}863script_is_valid = true;864}865_update_connected_methods();866_update_warnings();867_update_errors();868_update_background_color();869870emit_signal(SNAME("name_changed"));871emit_signal(SNAME("edited_script_changed"));872}873874void ScriptTextEditor::_update_warnings() {875int warning_nb = warnings.size();876warnings_panel->clear();877878bool has_connections_table = false;879// Add missing connections.880if (GLOBAL_GET("debug/gdscript/warnings/enable")) {881Node *base = get_tree()->get_edited_scene_root();882if (base && missing_connections.size() > 0) {883has_connections_table = true;884warnings_panel->push_table(1);885for (const Connection &connection : missing_connections) {886String base_path = base->get_name();887String source_path = base == connection.signal.get_object() ? base_path : base_path + "/" + String(base->get_path_to(Object::cast_to<Node>(connection.signal.get_object())));888String target_path = base == connection.callable.get_object() ? base_path : base_path + "/" + String(base->get_path_to(Object::cast_to<Node>(connection.callable.get_object())));889890warnings_panel->push_cell();891warnings_panel->push_color(warnings_panel->get_theme_color(SNAME("warning_color"), EditorStringName(Editor)));892warnings_panel->add_text(vformat(TTR("Missing connected method '%s' for signal '%s' from node '%s' to node '%s'."), connection.callable.get_method(), connection.signal.get_name(), source_path, target_path));893warnings_panel->pop(); // Color.894warnings_panel->pop(); // Cell.895}896warnings_panel->pop(); // Table.897898warning_nb += missing_connections.size();899}900}901902code_editor->set_warning_count(warning_nb);903904if (has_connections_table) {905warnings_panel->add_newline();906}907908// Add script warnings.909warnings_panel->push_table(3);910for (const ScriptLanguage::Warning &w : warnings) {911Dictionary ignore_meta;912ignore_meta["line"] = w.start_line;913ignore_meta["code"] = w.string_code.to_lower();914warnings_panel->push_cell();915warnings_panel->push_meta(ignore_meta);916warnings_panel->push_color(917warnings_panel->get_theme_color(SNAME("accent_color"), EditorStringName(Editor)).lerp(warnings_panel->get_theme_color(SNAME("mono_color"), EditorStringName(Editor)), 0.5f));918warnings_panel->add_text(TTR("[Ignore]"));919warnings_panel->pop(); // Color.920warnings_panel->pop(); // Meta ignore.921warnings_panel->pop(); // Cell.922923warnings_panel->push_cell();924warnings_panel->push_meta(w.start_line - 1);925warnings_panel->push_color(warnings_panel->get_theme_color(SNAME("warning_color"), EditorStringName(Editor)));926warnings_panel->add_text(vformat(TTR("Line %d (%s):"), w.start_line, w.string_code));927warnings_panel->pop(); // Color.928warnings_panel->pop(); // Meta goto.929warnings_panel->pop(); // Cell.930931warnings_panel->push_cell();932warnings_panel->add_text(w.message);933warnings_panel->add_newline();934warnings_panel->pop(); // Cell.935}936warnings_panel->pop(); // Table.937}938939void ScriptTextEditor::_update_errors() {940code_editor->set_error_count(errors.size());941942errors_panel->clear();943errors_panel->push_table(2);944for (const ScriptLanguage::ScriptError &err : errors) {945Dictionary click_meta;946click_meta["line"] = err.line;947click_meta["column"] = err.column;948949errors_panel->push_cell();950errors_panel->push_meta(err.line - 1);951errors_panel->push_color(warnings_panel->get_theme_color(SNAME("error_color"), EditorStringName(Editor)));952errors_panel->add_text(vformat(TTR("Line %d:"), err.line));953errors_panel->pop(); // Color.954errors_panel->pop(); // Meta goto.955errors_panel->pop(); // Cell.956957errors_panel->push_cell();958errors_panel->add_text(err.message);959errors_panel->add_newline();960errors_panel->pop(); // Cell.961}962errors_panel->pop(); // Table963964for (const KeyValue<String, List<ScriptLanguage::ScriptError>> &KV : depended_errors) {965Dictionary click_meta;966click_meta["path"] = KV.key;967click_meta["line"] = 1;968969errors_panel->add_newline();970errors_panel->add_newline();971errors_panel->push_meta(click_meta);972errors_panel->add_text(vformat(R"(%s:)", KV.key));973errors_panel->pop(); // Meta goto.974errors_panel->add_newline();975976errors_panel->push_indent(1);977errors_panel->push_table(2);978String filename = KV.key.get_file();979for (const ScriptLanguage::ScriptError &err : KV.value) {980click_meta["line"] = err.line;981click_meta["column"] = err.column;982983errors_panel->push_cell();984errors_panel->push_meta(click_meta);985errors_panel->push_color(errors_panel->get_theme_color(SNAME("error_color"), EditorStringName(Editor)));986errors_panel->add_text(vformat(TTR("Line %d:"), err.line));987errors_panel->pop(); // Color.988errors_panel->pop(); // Meta goto.989errors_panel->pop(); // Cell.990991errors_panel->push_cell();992errors_panel->add_text(err.message);993errors_panel->pop(); // Cell.994}995errors_panel->pop(); // Table996errors_panel->pop(); // Indent.997}998999bool highlight_safe = EDITOR_GET("text_editor/appearance/gutters/highlight_type_safe_lines");1000bool last_is_safe = false;1001CodeEdit *te = code_editor->get_text_editor();10021003for (int i = 0; i < te->get_line_count(); i++) {1004if (highlight_safe) {1005if (safe_lines.has(i + 1)) {1006te->set_line_gutter_item_color(i, line_number_gutter, safe_line_number_color);1007last_is_safe = true;1008} else if (last_is_safe && (te->is_in_comment(i) != -1 || te->get_line(i).strip_edges().is_empty())) {1009te->set_line_gutter_item_color(i, line_number_gutter, safe_line_number_color);1010} else {1011te->set_line_gutter_item_color(i, line_number_gutter, default_line_number_color);1012last_is_safe = false;1013}1014} else {1015te->set_line_gutter_item_color(i, 1, default_line_number_color);1016}1017}1018}10191020void ScriptTextEditor::_update_bookmark_list() {1021bookmarks_menu->clear();1022bookmarks_menu->reset_size();10231024bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_bookmark"), BOOKMARK_TOGGLE);1025bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/remove_all_bookmarks"), BOOKMARK_REMOVE_ALL);1026bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_next_bookmark"), BOOKMARK_GOTO_NEXT);1027bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_previous_bookmark"), BOOKMARK_GOTO_PREV);10281029PackedInt32Array bookmark_list = code_editor->get_text_editor()->get_bookmarked_lines();1030if (bookmark_list.is_empty()) {1031return;1032}10331034bookmarks_menu->add_separator();10351036for (int i = 0; i < bookmark_list.size(); i++) {1037// Strip edges to remove spaces or tabs.1038// Also replace any tabs by spaces, since we can't print tabs in the menu.1039String line = code_editor->get_text_editor()->get_line(bookmark_list[i]).replace("\t", " ").strip_edges();10401041// Limit the size of the line if too big.1042if (line.length() > 50) {1043line = line.substr(0, 50);1044}10451046bookmarks_menu->add_item(String::num_int64(bookmark_list[i] + 1) + " - `" + line + "`");1047bookmarks_menu->set_item_metadata(-1, bookmark_list[i]);1048}1049}10501051void ScriptTextEditor::_bookmark_item_pressed(int p_idx) {1052if (p_idx < 4) { // Any item before the separator.1053_edit_option(bookmarks_menu->get_item_id(p_idx));1054} else {1055code_editor->goto_line_centered(bookmarks_menu->get_item_metadata(p_idx));1056}1057}10581059static Vector<Node *> _find_all_node_for_script(Node *p_base, Node *p_current, const Ref<Script> &p_script) {1060Vector<Node *> nodes;10611062if (p_current->get_owner() != p_base && p_base != p_current) {1063return nodes;1064}10651066Ref<Script> c = p_current->get_script();1067if (c == p_script) {1068nodes.push_back(p_current);1069}10701071for (int i = 0; i < p_current->get_child_count(); i++) {1072Vector<Node *> found = _find_all_node_for_script(p_base, p_current->get_child(i), p_script);1073nodes.append_array(found);1074}10751076return nodes;1077}10781079static Node *_find_node_for_script(Node *p_base, Node *p_current, const Ref<Script> &p_script) {1080if (p_current->get_owner() != p_base && p_base != p_current) {1081return nullptr;1082}1083Ref<Script> c = p_current->get_script();1084if (c == p_script) {1085return p_current;1086}1087for (int i = 0; i < p_current->get_child_count(); i++) {1088Node *found = _find_node_for_script(p_base, p_current->get_child(i), p_script);1089if (found) {1090return found;1091}1092}10931094return nullptr;1095}10961097static void _find_changed_scripts_for_external_editor(Node *p_base, Node *p_current, HashSet<Ref<Script>> &r_scripts) {1098if (p_current->get_owner() != p_base && p_base != p_current) {1099return;1100}1101Ref<Script> c = p_current->get_script();11021103if (c.is_valid()) {1104r_scripts.insert(c);1105}11061107for (int i = 0; i < p_current->get_child_count(); i++) {1108_find_changed_scripts_for_external_editor(p_base, p_current->get_child(i), r_scripts);1109}1110}11111112void ScriptEditor::_update_modified_scripts_for_external_editor(Ref<Script> p_for_script) {1113bool use_external_editor = bool(EDITOR_GET("text_editor/external/use_external_editor"));11141115ERR_FAIL_NULL(get_tree());11161117HashSet<Ref<Script>> scripts;11181119Node *base = get_tree()->get_edited_scene_root();1120if (base) {1121_find_changed_scripts_for_external_editor(base, base, scripts);1122}11231124for (const Ref<Script> &E : scripts) {1125Ref<Script> scr = E;11261127if (!use_external_editor && !scr->get_language()->overrides_external_editor()) {1128continue; // We're not using an external editor for this script.1129}11301131if (p_for_script.is_valid() && p_for_script != scr) {1132continue;1133}11341135if (scr->is_built_in()) {1136continue; //internal script, who cares, though weird1137}11381139uint64_t last_date = scr->get_last_modified_time();1140uint64_t date = FileAccess::get_modified_time(scr->get_path());11411142if (last_date != date) {1143Ref<Script> rel_scr = ResourceLoader::load(scr->get_path(), scr->get_class(), ResourceFormatLoader::CACHE_MODE_IGNORE);1144ERR_CONTINUE(rel_scr.is_null());1145scr->set_source_code(rel_scr->get_source_code());1146scr->set_last_modified_time(rel_scr->get_last_modified_time());1147scr->update_exports();11481149trigger_live_script_reload(scr->get_path());1150}1151}1152}11531154void ScriptTextEditor::_code_complete_scripts(void *p_ud, const String &p_code, List<ScriptLanguage::CodeCompletionOption> *r_options, bool &r_force) {1155ScriptTextEditor *ste = (ScriptTextEditor *)p_ud;1156ste->_code_complete_script(p_code, r_options, r_force);1157}11581159void ScriptTextEditor::_code_complete_script(const String &p_code, List<ScriptLanguage::CodeCompletionOption> *r_options, bool &r_force) {1160if (color_panel->is_visible()) {1161return;1162}1163Node *base = get_tree()->get_edited_scene_root();1164if (base) {1165base = _find_node_for_script(base, base, script);1166}1167String hint;1168Error err = script->get_language()->complete_code(p_code, script->get_path(), base, r_options, r_force, hint);11691170if (err == OK) {1171code_editor->get_text_editor()->set_code_hint(hint);1172}1173}11741175void ScriptTextEditor::_update_breakpoint_list() {1176breakpoints_menu->clear();1177breakpoints_menu->reset_size();11781179breakpoints_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_breakpoint"), DEBUG_TOGGLE_BREAKPOINT);1180breakpoints_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/remove_all_breakpoints"), DEBUG_REMOVE_ALL_BREAKPOINTS);1181breakpoints_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_next_breakpoint"), DEBUG_GOTO_NEXT_BREAKPOINT);1182breakpoints_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_previous_breakpoint"), DEBUG_GOTO_PREV_BREAKPOINT);11831184PackedInt32Array breakpoint_list = code_editor->get_text_editor()->get_breakpointed_lines();1185if (breakpoint_list.is_empty()) {1186return;1187}11881189breakpoints_menu->add_separator();11901191for (int i = 0; i < breakpoint_list.size(); i++) {1192// Strip edges to remove spaces or tabs.1193// Also replace any tabs by spaces, since we can't print tabs in the menu.1194String line = code_editor->get_text_editor()->get_line(breakpoint_list[i]).replace("\t", " ").strip_edges();11951196// Limit the size of the line if too big.1197if (line.length() > 50) {1198line = line.substr(0, 50);1199}12001201breakpoints_menu->add_item(String::num_int64(breakpoint_list[i] + 1) + " - `" + line + "`");1202breakpoints_menu->set_item_metadata(-1, breakpoint_list[i]);1203}1204}12051206void ScriptTextEditor::_breakpoint_item_pressed(int p_idx) {1207if (p_idx < 4) { // Any item before the separator.1208_edit_option(breakpoints_menu->get_item_id(p_idx));1209} else {1210code_editor->goto_line_centered(breakpoints_menu->get_item_metadata(p_idx));1211}1212}12131214void ScriptTextEditor::_breakpoint_toggled(int p_row) {1215EditorDebuggerNode::get_singleton()->set_breakpoint(script->get_path(), p_row + 1, code_editor->get_text_editor()->is_line_breakpointed(p_row));1216}12171218void ScriptTextEditor::_on_caret_moved() {1219if (code_editor->is_previewing_navigation_change()) {1220return;1221}1222int current_line = code_editor->get_text_editor()->get_caret_line();1223if (Math::abs(current_line - previous_line) >= 10) {1224Dictionary nav_state = get_navigation_state();1225nav_state["row"] = previous_line;1226nav_state["scroll_position"] = -1;1227emit_signal(SNAME("request_save_previous_state"), nav_state);1228store_previous_state();1229}1230previous_line = current_line;1231}12321233void ScriptTextEditor::_lookup_symbol(const String &p_symbol, int p_row, int p_column) {1234Node *base = get_tree()->get_edited_scene_root();1235if (base) {1236base = _find_node_for_script(base, base, script);1237}12381239ScriptLanguage::LookupResult result;1240String code_text = code_editor->get_text_editor()->get_text_with_cursor_char(p_row, p_column);1241Error lc_error = script->get_language()->lookup_code(code_text, p_symbol, script->get_path(), base, result);1242if (ScriptServer::is_global_class(p_symbol)) {1243EditorNode::get_singleton()->load_resource(ScriptServer::get_global_class_path(p_symbol));1244} else if (p_symbol.is_resource_file() || p_symbol.begins_with("uid://")) {1245if (DirAccess::dir_exists_absolute(p_symbol)) {1246FileSystemDock::get_singleton()->navigate_to_path(p_symbol);1247} else {1248EditorNode::get_singleton()->load_scene_or_resource(p_symbol);1249}1250} else if (lc_error == OK) {1251_goto_line(p_row);12521253if (!result.class_name.is_empty() && EditorHelp::get_doc_data()->class_list.has(result.class_name) && !EditorHelp::get_doc_data()->class_list[result.class_name].is_script_doc) {1254switch (result.type) {1255case ScriptLanguage::LOOKUP_RESULT_CLASS: {1256emit_signal(SNAME("go_to_help"), "class_name:" + result.class_name);1257} break;1258case ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT: {1259StringName cname = result.class_name;1260while (ClassDB::class_exists(cname)) {1261if (ClassDB::has_integer_constant(cname, result.class_member, true)) {1262result.class_name = cname;1263break;1264}1265cname = ClassDB::get_parent_class(cname);1266}1267emit_signal(SNAME("go_to_help"), "class_constant:" + result.class_name + ":" + result.class_member);1268} break;1269case ScriptLanguage::LOOKUP_RESULT_CLASS_PROPERTY: {1270StringName cname = result.class_name;1271while (ClassDB::class_exists(cname)) {1272if (ClassDB::has_property(cname, result.class_member, true)) {1273result.class_name = cname;1274break;1275}1276cname = ClassDB::get_parent_class(cname);1277}1278emit_signal(SNAME("go_to_help"), "class_property:" + result.class_name + ":" + result.class_member);1279} break;1280case ScriptLanguage::LOOKUP_RESULT_CLASS_METHOD: {1281StringName cname = result.class_name;1282while (ClassDB::class_exists(cname)) {1283if (ClassDB::has_method(cname, result.class_member, true)) {1284result.class_name = cname;1285break;1286}1287cname = ClassDB::get_parent_class(cname);1288}1289emit_signal(SNAME("go_to_help"), "class_method:" + result.class_name + ":" + result.class_member);1290} break;1291case ScriptLanguage::LOOKUP_RESULT_CLASS_SIGNAL: {1292StringName cname = result.class_name;1293while (ClassDB::class_exists(cname)) {1294if (ClassDB::has_signal(cname, result.class_member, true)) {1295result.class_name = cname;1296break;1297}1298cname = ClassDB::get_parent_class(cname);1299}1300emit_signal(SNAME("go_to_help"), "class_signal:" + result.class_name + ":" + result.class_member);1301} break;1302case ScriptLanguage::LOOKUP_RESULT_CLASS_ENUM: {1303StringName cname = result.class_name;1304while (ClassDB::class_exists(cname)) {1305if (ClassDB::has_enum(cname, result.class_member, true)) {1306result.class_name = cname;1307break;1308}1309cname = ClassDB::get_parent_class(cname);1310}1311emit_signal(SNAME("go_to_help"), "class_enum:" + result.class_name + ":" + result.class_member);1312} break;1313case ScriptLanguage::LOOKUP_RESULT_CLASS_ANNOTATION: {1314emit_signal(SNAME("go_to_help"), "class_annotation:" + result.class_name + ":" + result.class_member);1315} break;1316case ScriptLanguage::LOOKUP_RESULT_CLASS_TBD_GLOBALSCOPE: { // Deprecated.1317emit_signal(SNAME("go_to_help"), "class_global:" + result.class_name + ":" + result.class_member);1318} break;1319case ScriptLanguage::LOOKUP_RESULT_SCRIPT_LOCATION:1320case ScriptLanguage::LOOKUP_RESULT_LOCAL_CONSTANT:1321case ScriptLanguage::LOOKUP_RESULT_LOCAL_VARIABLE:1322case ScriptLanguage::LOOKUP_RESULT_MAX: {1323// Nothing to do.1324} break;1325}1326} else if (result.location >= 0) {1327if (result.script.is_valid()) {1328emit_signal(SNAME("request_open_script_at_line"), result.script, result.location - 1);1329} else {1330emit_signal(SNAME("request_save_history"));1331goto_line_centered(result.location - 1);1332}1333}1334} else if (ProjectSettings::get_singleton()->has_autoload(p_symbol)) {1335// Check for Autoload scenes.1336const ProjectSettings::AutoloadInfo &info = ProjectSettings::get_singleton()->get_autoload(p_symbol);1337if (info.is_singleton) {1338EditorNode::get_singleton()->load_scene(info.path);1339}1340} else if (p_symbol.is_relative_path()) {1341// Every symbol other than absolute path is relative path so keep this condition at last.1342String path = _get_absolute_path(p_symbol);1343if (FileAccess::exists(path)) {1344EditorNode::get_singleton()->load_scene_or_resource(path);1345}1346}1347}13481349void ScriptTextEditor::_validate_symbol(const String &p_symbol) {1350CodeEdit *text_edit = code_editor->get_text_editor();13511352Node *base = get_tree()->get_edited_scene_root();1353if (base) {1354base = _find_node_for_script(base, base, script);1355}13561357ScriptLanguage::LookupResult result;1358String lc_text = code_editor->get_text_editor()->get_text_for_symbol_lookup();1359Error lc_error = script->get_language()->lookup_code(lc_text, p_symbol, script->get_path(), base, result);1360bool is_singleton = ProjectSettings::get_singleton()->has_autoload(p_symbol) && ProjectSettings::get_singleton()->get_autoload(p_symbol).is_singleton;1361if (lc_error == OK || is_singleton || ScriptServer::is_global_class(p_symbol) || p_symbol.is_resource_file() || p_symbol.begins_with("uid://")) {1362text_edit->set_symbol_lookup_word_as_valid(true);1363} else if (p_symbol.is_relative_path()) {1364String path = _get_absolute_path(p_symbol);1365if (FileAccess::exists(path)) {1366text_edit->set_symbol_lookup_word_as_valid(true);1367} else {1368text_edit->set_symbol_lookup_word_as_valid(false);1369}1370} else {1371text_edit->set_symbol_lookup_word_as_valid(false);1372}1373}13741375void ScriptTextEditor::_show_symbol_tooltip(const String &p_symbol, int p_row, int p_column) {1376if (!EDITOR_GET("text_editor/behavior/documentation/enable_tooltips").booleanize()) {1377return;1378}13791380if (p_symbol.begins_with("res://") || p_symbol.begins_with("uid://")) {1381EditorHelpBitTooltip::show_tooltip(code_editor->get_text_editor(), "resource||" + p_symbol);1382return;1383}13841385Node *base = get_tree()->get_edited_scene_root();1386if (base) {1387base = _find_node_for_script(base, base, script);1388}13891390ScriptLanguage::LookupResult result;1391String doc_symbol;1392const String code_text = code_editor->get_text_editor()->get_text_with_cursor_char(p_row, p_column);1393const Error lc_error = script->get_language()->lookup_code(code_text, p_symbol, script->get_path(), base, result);1394if (lc_error == OK) {1395switch (result.type) {1396case ScriptLanguage::LOOKUP_RESULT_CLASS: {1397doc_symbol = "class|" + result.class_name + "|";1398} break;1399case ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT: {1400StringName cname = result.class_name;1401while (ClassDB::class_exists(cname)) {1402if (ClassDB::has_integer_constant(cname, result.class_member, true)) {1403result.class_name = cname;1404break;1405}1406cname = ClassDB::get_parent_class(cname);1407}1408doc_symbol = "constant|" + result.class_name + "|" + result.class_member;1409} break;1410case ScriptLanguage::LOOKUP_RESULT_CLASS_PROPERTY: {1411StringName cname = result.class_name;1412while (ClassDB::class_exists(cname)) {1413if (ClassDB::has_property(cname, result.class_member, true)) {1414result.class_name = cname;1415break;1416}1417cname = ClassDB::get_parent_class(cname);1418}1419doc_symbol = "property|" + result.class_name + "|" + result.class_member;1420} break;1421case ScriptLanguage::LOOKUP_RESULT_CLASS_METHOD: {1422StringName cname = result.class_name;1423while (ClassDB::class_exists(cname)) {1424if (ClassDB::has_method(cname, result.class_member, true)) {1425result.class_name = cname;1426break;1427}1428cname = ClassDB::get_parent_class(cname);1429}1430doc_symbol = "method|" + result.class_name + "|" + result.class_member;1431} break;1432case ScriptLanguage::LOOKUP_RESULT_CLASS_SIGNAL: {1433StringName cname = result.class_name;1434while (ClassDB::class_exists(cname)) {1435if (ClassDB::has_signal(cname, result.class_member, true)) {1436result.class_name = cname;1437break;1438}1439cname = ClassDB::get_parent_class(cname);1440}1441doc_symbol = "signal|" + result.class_name + "|" + result.class_member;1442} break;1443case ScriptLanguage::LOOKUP_RESULT_CLASS_ENUM: {1444StringName cname = result.class_name;1445while (ClassDB::class_exists(cname)) {1446if (ClassDB::has_enum(cname, result.class_member, true)) {1447result.class_name = cname;1448break;1449}1450cname = ClassDB::get_parent_class(cname);1451}1452doc_symbol = "enum|" + result.class_name + "|" + result.class_member;1453} break;1454case ScriptLanguage::LOOKUP_RESULT_CLASS_ANNOTATION: {1455doc_symbol = "annotation|" + result.class_name + "|" + result.class_member;1456} break;1457case ScriptLanguage::LOOKUP_RESULT_LOCAL_CONSTANT:1458case ScriptLanguage::LOOKUP_RESULT_LOCAL_VARIABLE: {1459const String item_type = (result.type == ScriptLanguage::LOOKUP_RESULT_LOCAL_CONSTANT) ? "local_constant" : "local_variable";1460Dictionary item_data;1461item_data["description"] = result.description;1462item_data["is_deprecated"] = result.is_deprecated;1463item_data["deprecated_message"] = result.deprecated_message;1464item_data["is_experimental"] = result.is_experimental;1465item_data["experimental_message"] = result.experimental_message;1466item_data["doc_type"] = result.doc_type;1467item_data["enumeration"] = result.enumeration;1468item_data["is_bitfield"] = result.is_bitfield;1469item_data["value"] = result.value;1470doc_symbol = item_type + "||" + p_symbol + "|" + JSON::stringify(item_data);1471} break;1472case ScriptLanguage::LOOKUP_RESULT_SCRIPT_LOCATION:1473case ScriptLanguage::LOOKUP_RESULT_CLASS_TBD_GLOBALSCOPE: // Deprecated.1474case ScriptLanguage::LOOKUP_RESULT_MAX: {1475// Nothing to do.1476} break;1477}1478}14791480// NOTE: See also `ScriptEditor::_get_debug_tooltip()` for documentation tooltips disabled.1481String debug_value = EditorDebuggerNode::get_singleton()->get_var_value(p_symbol);1482if (!debug_value.is_empty()) {1483constexpr int DISPLAY_LIMIT = 1024;1484if (debug_value.size() > DISPLAY_LIMIT) {1485debug_value = debug_value.left(DISPLAY_LIMIT) + "... " + TTR("(truncated)");1486}1487debug_value = TTR("Current value: ") + debug_value.replace("[", "[lb]");1488}14891490if (!doc_symbol.is_empty() || !debug_value.is_empty()) {1491EditorHelpBitTooltip::show_tooltip(code_editor->get_text_editor(), doc_symbol, debug_value, true);1492}1493}14941495String ScriptTextEditor::_get_absolute_path(const String &rel_path) {1496String base_path = script->get_path().get_base_dir();1497String path = base_path.path_join(rel_path);1498return path.replace("///", "//").simplify_path();1499}15001501void ScriptTextEditor::update_toggle_files_button() {1502code_editor->update_toggle_files_button();1503}15041505void ScriptTextEditor::_update_connected_methods() {1506CodeEdit *text_edit = code_editor->get_text_editor();1507text_edit->set_gutter_width(connection_gutter, text_edit->get_line_height());1508for (int i = 0; i < text_edit->get_line_count(); i++) {1509text_edit->set_line_gutter_metadata(i, connection_gutter, Dictionary());1510text_edit->set_line_gutter_icon(i, connection_gutter, nullptr);1511text_edit->set_line_gutter_clickable(i, connection_gutter, false);1512}1513missing_connections.clear();15141515if (!script_is_valid) {1516return;1517}15181519Node *base = get_tree()->get_edited_scene_root();1520if (!base) {1521return;1522}15231524// Add connection icons to methods.1525Vector<Node *> nodes = _find_all_node_for_script(base, base, script);1526HashSet<StringName> methods_found;1527for (int i = 0; i < nodes.size(); i++) {1528List<Connection> signal_connections;1529nodes[i]->get_signals_connected_to_this(&signal_connections);15301531for (const Connection &connection : signal_connections) {1532if (!(connection.flags & CONNECT_PERSIST)) {1533continue;1534}15351536// As deleted nodes are still accessible via the undo/redo system, check if they're still on the tree.1537Node *source = Object::cast_to<Node>(connection.signal.get_object());1538if (source && !source->is_inside_tree()) {1539continue;1540}15411542const StringName method = connection.callable.get_method();1543if (methods_found.has(method)) {1544continue;1545}15461547if (!ClassDB::has_method(script->get_instance_base_type(), method)) {1548int line = -1;15491550for (int j = 0; j < functions.size(); j++) {1551String name = functions[j].get_slicec(':', 0);1552if (name == method) {1553Dictionary line_meta;1554line_meta["type"] = "connection";1555line_meta["method"] = method;1556line = functions[j].get_slicec(':', 1).to_int() - 1;1557text_edit->set_line_gutter_metadata(line, connection_gutter, line_meta);1558text_edit->set_line_gutter_icon(line, connection_gutter, get_parent_control()->get_editor_theme_icon(SNAME("Slot")));1559text_edit->set_line_gutter_clickable(line, connection_gutter, true);1560methods_found.insert(method);1561break;1562}1563}15641565if (line >= 0) {1566continue;1567}15681569// There is a chance that the method is inherited from another script.1570bool found_inherited_function = false;1571Ref<Script> inherited_script = script->get_base_script();1572while (inherited_script.is_valid()) {1573if (inherited_script->has_method(method)) {1574found_inherited_function = true;1575break;1576}15771578inherited_script = inherited_script->get_base_script();1579}15801581if (!found_inherited_function) {1582missing_connections.push_back(connection);1583}1584}1585}1586}15871588// Add override icons to methods.1589methods_found.clear();1590for (int i = 0; i < functions.size(); i++) {1591String raw_name = functions[i].get_slicec(':', 0);1592StringName name = StringName(raw_name);1593if (methods_found.has(name)) {1594continue;1595}15961597// Account for inner classes by stripping the class names from the method,1598// starting from the right since our inner class might be inside of another inner class.1599int pos = raw_name.rfind_char('.');1600if (pos != -1) {1601name = raw_name.substr(pos + 1);1602}16031604String found_base_class;1605StringName base_class = script->get_instance_base_type();1606Ref<Script> inherited_script = script->get_base_script();1607while (inherited_script.is_valid()) {1608if (inherited_script->has_method(name)) {1609found_base_class = "script:" + inherited_script->get_path();1610break;1611}16121613base_class = inherited_script->get_instance_base_type();1614inherited_script = inherited_script->get_base_script();1615}16161617if (found_base_class.is_empty()) {1618while (base_class) {1619List<MethodInfo> methods;1620ClassDB::get_method_list(base_class, &methods, true);1621for (const MethodInfo &mi : methods) {1622if (mi.name == name) {1623found_base_class = "builtin:" + base_class;1624break;1625}1626}16271628ClassDB::ClassInfo *base_class_ptr = ClassDB::classes.getptr(base_class)->inherits_ptr;1629if (base_class_ptr == nullptr) {1630break;1631}1632base_class = base_class_ptr->name;1633}1634}16351636if (!found_base_class.is_empty()) {1637int line = functions[i].get_slicec(':', 1).to_int() - 1;16381639Dictionary line_meta = text_edit->get_line_gutter_metadata(line, connection_gutter);1640if (line_meta.is_empty()) {1641// Add override icon to gutter.1642line_meta["type"] = "inherits";1643line_meta["method"] = name;1644line_meta["base_class"] = found_base_class;1645text_edit->set_line_gutter_icon(line, connection_gutter, get_parent_control()->get_editor_theme_icon(SNAME("MethodOverride")));1646text_edit->set_line_gutter_clickable(line, connection_gutter, true);1647} else {1648// If method is also connected to signal, then merge icons and keep the click behavior of the slot.1649text_edit->set_line_gutter_icon(line, connection_gutter, get_parent_control()->get_editor_theme_icon(SNAME("MethodOverrideAndSlot")));1650}16511652methods_found.insert(StringName(raw_name));1653}1654}1655}16561657void ScriptTextEditor::_update_gutter_indexes() {1658for (int i = 0; i < code_editor->get_text_editor()->get_gutter_count(); i++) {1659if (code_editor->get_text_editor()->get_gutter_name(i) == "connection_gutter") {1660connection_gutter = i;1661continue;1662}16631664if (code_editor->get_text_editor()->get_gutter_name(i) == "line_numbers") {1665line_number_gutter = i;1666continue;1667}1668}1669}16701671void ScriptTextEditor::_gutter_clicked(int p_line, int p_gutter) {1672if (p_gutter != connection_gutter) {1673return;1674}16751676Dictionary meta = code_editor->get_text_editor()->get_line_gutter_metadata(p_line, p_gutter);1677String type = meta.get("type", "");1678if (type.is_empty()) {1679return;1680}16811682// All types currently need a method name.1683String method = meta.get("method", "");1684if (method.is_empty()) {1685return;1686}16871688if (type == "connection") {1689Node *base = get_tree()->get_edited_scene_root();1690if (!base) {1691return;1692}16931694Vector<Node *> nodes = _find_all_node_for_script(base, base, script);1695connection_info_dialog->popup_connections(method, nodes);1696} else if (type == "inherits") {1697String base_class_raw = meta["base_class"];1698PackedStringArray base_class_split = base_class_raw.split(":", true, 1);16991700if (base_class_split[0] == "script") {1701// Go to function declaration.1702Ref<Script> base_script = ResourceLoader::load(base_class_split[1]);1703ERR_FAIL_COND(base_script.is_null());1704emit_signal(SNAME("go_to_method"), base_script, method);1705} else if (base_class_split[0] == "builtin") {1706// Open method documentation.1707emit_signal(SNAME("go_to_help"), "class_method:" + base_class_split[1] + ":" + method);1708}1709}1710}17111712void ScriptTextEditor::_edit_option(int p_op) {1713CodeEdit *tx = code_editor->get_text_editor();1714tx->apply_ime();17151716switch (p_op) {1717case EDIT_UNDO: {1718tx->undo();1719callable_mp((Control *)tx, &Control::grab_focus).call_deferred();1720} break;1721case EDIT_REDO: {1722tx->redo();1723callable_mp((Control *)tx, &Control::grab_focus).call_deferred();1724} break;1725case EDIT_CUT: {1726tx->cut();1727callable_mp((Control *)tx, &Control::grab_focus).call_deferred();1728} break;1729case EDIT_COPY: {1730tx->copy();1731callable_mp((Control *)tx, &Control::grab_focus).call_deferred();1732} break;1733case EDIT_PASTE: {1734tx->paste();1735callable_mp((Control *)tx, &Control::grab_focus).call_deferred();1736} break;1737case EDIT_SELECT_ALL: {1738tx->select_all();1739callable_mp((Control *)tx, &Control::grab_focus).call_deferred();1740} break;1741case EDIT_MOVE_LINE_UP: {1742code_editor->get_text_editor()->move_lines_up();1743} break;1744case EDIT_MOVE_LINE_DOWN: {1745code_editor->get_text_editor()->move_lines_down();1746} break;1747case EDIT_INDENT: {1748Ref<Script> scr = script;1749if (scr.is_null()) {1750return;1751}1752tx->indent_lines();1753} break;1754case EDIT_UNINDENT: {1755Ref<Script> scr = script;1756if (scr.is_null()) {1757return;1758}1759tx->unindent_lines();1760} break;1761case EDIT_DELETE_LINE: {1762code_editor->get_text_editor()->delete_lines();1763} break;1764case EDIT_DUPLICATE_SELECTION: {1765code_editor->get_text_editor()->duplicate_selection();1766} break;1767case EDIT_DUPLICATE_LINES: {1768code_editor->get_text_editor()->duplicate_lines();1769} break;1770case EDIT_TOGGLE_FOLD_LINE: {1771tx->toggle_foldable_lines_at_carets();1772} break;1773case EDIT_FOLD_ALL_LINES: {1774tx->fold_all_lines();1775} break;1776case EDIT_UNFOLD_ALL_LINES: {1777tx->unfold_all_lines();1778} break;1779case EDIT_CREATE_CODE_REGION: {1780tx->create_code_region();1781} break;1782case EDIT_TOGGLE_COMMENT: {1783_edit_option_toggle_inline_comment();1784} break;1785case EDIT_COMPLETE: {1786tx->request_code_completion(true);1787} break;1788case EDIT_AUTO_INDENT: {1789String text = tx->get_text();1790Ref<Script> scr = script;1791if (scr.is_null()) {1792return;1793}17941795tx->begin_complex_operation();1796tx->begin_multicaret_edit();1797int begin = tx->get_line_count() - 1, end = 0;1798if (tx->has_selection()) {1799// Auto indent all lines that have a caret or selection on it.1800Vector<Point2i> line_ranges = tx->get_line_ranges_from_carets();1801for (Point2i line_range : line_ranges) {1802scr->get_language()->auto_indent_code(text, line_range.x, line_range.y);1803if (line_range.x < begin) {1804begin = line_range.x;1805}1806if (line_range.y > end) {1807end = line_range.y;1808}1809}1810} else {1811// Auto indent entire text.1812begin = 0;1813end = tx->get_line_count() - 1;1814scr->get_language()->auto_indent_code(text, begin, end);1815}18161817// Apply auto indented code.1818Vector<String> lines = text.split("\n");1819for (int i = begin; i <= end; ++i) {1820tx->set_line(i, lines[i]);1821}18221823tx->end_multicaret_edit();1824tx->end_complex_operation();1825} break;1826case EDIT_TRIM_TRAILING_WHITESAPCE: {1827trim_trailing_whitespace();1828} break;1829case EDIT_TRIM_FINAL_NEWLINES: {1830trim_final_newlines();1831} break;1832case EDIT_CONVERT_INDENT_TO_SPACES: {1833code_editor->set_indent_using_spaces(true);1834convert_indent();1835} break;1836case EDIT_CONVERT_INDENT_TO_TABS: {1837code_editor->set_indent_using_spaces(false);1838convert_indent();1839} break;1840case EDIT_PICK_COLOR: {1841color_panel->popup();1842} break;1843case EDIT_TO_UPPERCASE: {1844_convert_case(CodeTextEditor::UPPER);1845} break;1846case EDIT_TO_LOWERCASE: {1847_convert_case(CodeTextEditor::LOWER);1848} break;1849case EDIT_CAPITALIZE: {1850_convert_case(CodeTextEditor::CAPITALIZE);1851} break;1852case EDIT_EVALUATE: {1853Expression expression;1854tx->begin_complex_operation();1855for (int caret_idx = 0; caret_idx < tx->get_caret_count(); caret_idx++) {1856Vector<String> lines = tx->get_selected_text(caret_idx).split("\n");1857PackedStringArray results;18581859for (int i = 0; i < lines.size(); i++) {1860const String &line = lines[i];1861String whitespace = line.substr(0, line.size() - line.strip_edges(true, false).size()); // Extract the whitespace at the beginning.1862if (expression.parse(line) == OK) {1863Variant result = expression.execute(Array(), Variant(), false, true);1864if (expression.get_error_text().is_empty()) {1865results.push_back(whitespace + result.get_construct_string());1866} else {1867results.push_back(line);1868}1869} else {1870results.push_back(line);1871}1872}1873tx->insert_text_at_caret(String("\n").join(results), caret_idx);1874}1875tx->end_complex_operation();1876} break;1877case EDIT_TOGGLE_WORD_WRAP: {1878TextEdit::LineWrappingMode wrap = code_editor->get_text_editor()->get_line_wrapping_mode();1879code_editor->get_text_editor()->set_line_wrapping_mode(wrap == TextEdit::LINE_WRAPPING_BOUNDARY ? TextEdit::LINE_WRAPPING_NONE : TextEdit::LINE_WRAPPING_BOUNDARY);1880} break;1881case SEARCH_FIND: {1882code_editor->get_find_replace_bar()->popup_search();1883} break;1884case SEARCH_FIND_NEXT: {1885code_editor->get_find_replace_bar()->search_next();1886} break;1887case SEARCH_FIND_PREV: {1888code_editor->get_find_replace_bar()->search_prev();1889} break;1890case SEARCH_REPLACE: {1891code_editor->get_find_replace_bar()->popup_replace();1892} break;1893case SEARCH_IN_FILES: {1894String selected_text = tx->get_selected_text();18951896// Yep, because it doesn't make sense to instance this dialog for every single script open...1897// So this will be delegated to the ScriptEditor.1898emit_signal(SNAME("search_in_files_requested"), selected_text);1899} break;1900case REPLACE_IN_FILES: {1901String selected_text = tx->get_selected_text();19021903emit_signal(SNAME("replace_in_files_requested"), selected_text);1904} break;1905case SEARCH_LOCATE_FUNCTION: {1906quick_open->popup_dialog(get_functions());1907} break;1908case SEARCH_GOTO_LINE: {1909goto_line_popup->popup_find_line(code_editor);1910} break;1911case BOOKMARK_TOGGLE: {1912code_editor->toggle_bookmark();1913} break;1914case BOOKMARK_GOTO_NEXT: {1915code_editor->goto_next_bookmark();1916} break;1917case BOOKMARK_GOTO_PREV: {1918code_editor->goto_prev_bookmark();1919} break;1920case BOOKMARK_REMOVE_ALL: {1921code_editor->remove_all_bookmarks();1922} break;1923case DEBUG_TOGGLE_BREAKPOINT: {1924Vector<int> sorted_carets = tx->get_sorted_carets();1925int last_line = -1;1926for (const int &c : sorted_carets) {1927int from = tx->get_selection_from_line(c);1928from += from == last_line ? 1 : 0;1929int to = tx->get_selection_to_line(c);1930if (to < from) {1931continue;1932}1933// Check first if there's any lines with breakpoints in the selection.1934bool selection_has_breakpoints = false;1935for (int line = from; line <= to; line++) {1936if (tx->is_line_breakpointed(line)) {1937selection_has_breakpoints = true;1938break;1939}1940}19411942// Set breakpoint on caret or remove all bookmarks from the selection.1943if (!selection_has_breakpoints) {1944if (tx->get_caret_line(c) != last_line) {1945tx->set_line_as_breakpoint(tx->get_caret_line(c), true);1946}1947} else {1948for (int line = from; line <= to; line++) {1949tx->set_line_as_breakpoint(line, false);1950}1951}1952last_line = to;1953}1954} break;1955case DEBUG_REMOVE_ALL_BREAKPOINTS: {1956PackedInt32Array bpoints = tx->get_breakpointed_lines();19571958for (int i = 0; i < bpoints.size(); i++) {1959int line = bpoints[i];1960bool dobreak = !tx->is_line_breakpointed(line);1961tx->set_line_as_breakpoint(line, dobreak);1962EditorDebuggerNode::get_singleton()->set_breakpoint(script->get_path(), line + 1, dobreak);1963}1964} break;1965case DEBUG_GOTO_NEXT_BREAKPOINT: {1966PackedInt32Array bpoints = tx->get_breakpointed_lines();1967if (bpoints.is_empty()) {1968return;1969}19701971int current_line = tx->get_caret_line();1972int bpoint_idx = 0;1973if (current_line < (int)bpoints[bpoints.size() - 1]) {1974while (bpoint_idx < bpoints.size() && bpoints[bpoint_idx] <= current_line) {1975bpoint_idx++;1976}1977}1978code_editor->goto_line_centered(bpoints[bpoint_idx]);1979} break;1980case DEBUG_GOTO_PREV_BREAKPOINT: {1981PackedInt32Array bpoints = tx->get_breakpointed_lines();1982if (bpoints.is_empty()) {1983return;1984}19851986int current_line = tx->get_caret_line();1987int bpoint_idx = bpoints.size() - 1;1988if (current_line > (int)bpoints[0]) {1989while (bpoint_idx >= 0 && bpoints[bpoint_idx] >= current_line) {1990bpoint_idx--;1991}1992}1993code_editor->goto_line_centered(bpoints[bpoint_idx]);1994} break;1995case HELP_CONTEXTUAL: {1996String text = tx->get_selected_text(0);1997if (text.is_empty()) {1998text = tx->get_word_under_caret(0);1999}2000if (!text.is_empty()) {2001emit_signal(SNAME("request_help"), text);2002}2003} break;2004case LOOKUP_SYMBOL: {2005String text = tx->get_word_under_caret(0);2006if (text.is_empty()) {2007text = tx->get_selected_text(0);2008}2009if (!text.is_empty()) {2010_lookup_symbol(text, tx->get_caret_line(0), tx->get_caret_column(0));2011}2012} break;2013case EDIT_EMOJI_AND_SYMBOL: {2014code_editor->get_text_editor()->show_emoji_and_symbol_picker();2015} break;2016default: {2017if (p_op >= EditorContextMenuPlugin::BASE_ID) {2018EditorContextMenuPluginManager::get_singleton()->activate_custom_option(EditorContextMenuPlugin::CONTEXT_SLOT_SCRIPT_EDITOR_CODE, p_op, tx);2019}2020}2021}2022}20232024void ScriptTextEditor::_edit_option_toggle_inline_comment() {2025if (script.is_null()) {2026return;2027}20282029String delimiter = "#";20302031for (const String &script_delimiter : script->get_language()->get_comment_delimiters()) {2032if (!script_delimiter.contains_char(' ')) {2033delimiter = script_delimiter;2034break;2035}2036}20372038code_editor->toggle_inline_comment(delimiter);2039}20402041void ScriptTextEditor::add_syntax_highlighter(Ref<EditorSyntaxHighlighter> p_highlighter) {2042ERR_FAIL_COND(p_highlighter.is_null());20432044highlighters[p_highlighter->_get_name()] = p_highlighter;2045highlighter_menu->add_radio_check_item(p_highlighter->_get_name());2046}20472048void ScriptTextEditor::set_syntax_highlighter(Ref<EditorSyntaxHighlighter> p_highlighter) {2049ERR_FAIL_COND(p_highlighter.is_null());20502051HashMap<String, Ref<EditorSyntaxHighlighter>>::Iterator el = highlighters.begin();2052while (el) {2053int highlighter_index = highlighter_menu->get_item_idx_from_text(el->key);2054highlighter_menu->set_item_checked(highlighter_index, el->value == p_highlighter);2055++el;2056}20572058CodeEdit *te = code_editor->get_text_editor();2059p_highlighter->_set_edited_resource(script);2060te->set_syntax_highlighter(p_highlighter);2061}20622063void ScriptTextEditor::_change_syntax_highlighter(int p_idx) {2064set_syntax_highlighter(highlighters[highlighter_menu->get_item_text(p_idx)]);2065}20662067void ScriptTextEditor::_notification(int p_what) {2068switch (p_what) {2069case NOTIFICATION_TRANSLATION_CHANGED: {2070if (is_ready() && is_visible_in_tree()) {2071_update_errors();2072_update_warnings();2073}2074} break;20752076case NOTIFICATION_THEME_CHANGED:2077if (!editor_enabled) {2078break;2079}2080if (is_visible_in_tree()) {2081_update_warnings();2082_update_errors();2083_update_background_color();2084}2085[[fallthrough]];2086case NOTIFICATION_ENTER_TREE: {2087code_editor->get_text_editor()->set_gutter_width(connection_gutter, code_editor->get_text_editor()->get_line_height());2088Ref<Font> code_font = get_theme_font("font", "CodeEdit");2089inline_color_options->add_theme_font_override("font", code_font);2090inline_color_options->get_popup()->add_theme_font_override("font", code_font);2091} break;2092}2093}20942095Control *ScriptTextEditor::get_edit_menu() {2096return edit_hb;2097}20982099void ScriptTextEditor::clear_edit_menu() {2100if (editor_enabled) {2101memdelete(edit_hb);2102}2103}21042105void ScriptTextEditor::set_find_replace_bar(FindReplaceBar *p_bar) {2106code_editor->set_find_replace_bar(p_bar);2107}21082109void ScriptTextEditor::reload(bool p_soft) {2110CodeEdit *te = code_editor->get_text_editor();2111Ref<Script> scr = script;2112if (scr.is_null()) {2113return;2114}2115scr->set_source_code(te->get_text());2116bool soft = p_soft || ClassDB::is_parent_class(scr->get_instance_base_type(), "EditorPlugin"); // Always soft-reload editor plugins.21172118scr->get_language()->reload_tool_script(scr, soft);2119}21202121PackedInt32Array ScriptTextEditor::get_breakpoints() {2122return code_editor->get_text_editor()->get_breakpointed_lines();2123}21242125void ScriptTextEditor::set_breakpoint(int p_line, bool p_enabled) {2126code_editor->get_text_editor()->set_line_as_breakpoint(p_line, p_enabled);2127}21282129void ScriptTextEditor::clear_breakpoints() {2130code_editor->get_text_editor()->clear_breakpointed_lines();2131}21322133void ScriptTextEditor::set_tooltip_request_func(const Callable &p_toolip_callback) {2134Variant args[1] = { this };2135const Variant *argp[] = { &args[0] };2136code_editor->get_text_editor()->set_tooltip_request_func(p_toolip_callback.bindp(argp, 1));2137}21382139void ScriptTextEditor::set_debugger_active(bool p_active) {2140}21412142Control *ScriptTextEditor::get_base_editor() const {2143return code_editor->get_text_editor();2144}21452146CodeTextEditor *ScriptTextEditor::get_code_editor() const {2147return code_editor;2148}21492150Variant ScriptTextEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) {2151return Variant();2152}21532154bool ScriptTextEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {2155Dictionary d = p_data;2156if (d.has("type") &&2157(String(d["type"]) == "resource" ||2158String(d["type"]) == "files" ||2159String(d["type"]) == "nodes" ||2160String(d["type"]) == "obj_property" ||2161String(d["type"]) == "files_and_dirs")) {2162return true;2163}21642165return false;2166}21672168static Node *_find_script_node(Node *p_current_node, const Ref<Script> &script) {2169if (p_current_node->get_script() == script) {2170return p_current_node;2171}21722173for (int i = 0; i < p_current_node->get_child_count(); i++) {2174Node *n = _find_script_node(p_current_node->get_child(i), script);2175if (n) {2176return n;2177}2178}21792180return nullptr;2181}21822183static String _quote_drop_data(const String &str) {2184// This function prepares a string for being "dropped" into the script editor.2185// The string can be a resource path, node path or property name.21862187const bool using_single_quotes = EDITOR_GET("text_editor/completion/use_single_quotes");21882189String escaped = str.c_escape();21902191// If string is double quoted, there is no need to escape single quotes.2192// We can revert the extra escaping added in c_escape().2193if (!using_single_quotes) {2194escaped = escaped.replace("\\'", "\'");2195}21962197return escaped.quote(using_single_quotes ? "'" : "\"");2198}21992200static String _get_dropped_resource_line(const Ref<Resource> &p_resource, bool p_create_field, bool p_allow_uid) {2201String path = p_resource->get_path();2202if (p_allow_uid) {2203ResourceUID::ID id = ResourceLoader::get_resource_uid(path);2204if (id != ResourceUID::INVALID_ID) {2205path = ResourceUID::get_singleton()->id_to_text(id);2206}2207}2208const bool is_script = ClassDB::is_parent_class(p_resource->get_class(), "Script");22092210if (!p_create_field) {2211return vformat("preload(%s)", _quote_drop_data(path));2212}22132214String variable_name = p_resource->get_name();2215if (variable_name.is_empty()) {2216variable_name = p_resource->get_path().get_file().get_basename();2217}22182219if (is_script) {2220variable_name = variable_name.to_pascal_case().validate_unicode_identifier();2221} else {2222variable_name = variable_name.to_snake_case().to_upper().validate_unicode_identifier();2223}2224return vformat("const %s = preload(%s)", variable_name, _quote_drop_data(path));2225}22262227void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {2228Dictionary d = p_data;22292230CodeEdit *te = code_editor->get_text_editor();2231Point2i pos = (p_point == Vector2(Math::INF, Math::INF)) ? Point2i(te->get_caret_line(0), te->get_caret_column(0)) : te->get_line_column_at_pos(p_point);2232int drop_at_line = pos.y;2233int drop_at_column = pos.x;2234int selection_index = te->get_selection_at_line_column(drop_at_line, drop_at_column);22352236bool is_empty_line = false;2237if (selection_index >= 0) {2238// Dropped on a selection, it will be replaced.2239drop_at_line = te->get_selection_from_line(selection_index);2240drop_at_column = te->get_selection_from_column(selection_index);2241is_empty_line = drop_at_column <= te->get_first_non_whitespace_column(drop_at_line) && te->get_selection_to_column(selection_index) == te->get_line(te->get_selection_to_line(selection_index)).length();2242}22432244const bool drop_modifier_pressed = Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL);2245const bool allow_uid = Input::get_singleton()->is_key_pressed(Key::SHIFT) != bool(EDITOR_GET("text_editor/behavior/files/drop_preload_resources_as_uid"));2246const String &line = te->get_line(drop_at_line);22472248if (selection_index < 0) {2249is_empty_line = line.is_empty() || te->get_first_non_whitespace_column(drop_at_line) == line.length();2250}22512252String text_to_drop;2253bool add_new_line = false;22542255const String type = d.get("type", "");2256if (type == "resource") {2257Ref<Resource> resource = d["resource"];2258if (resource.is_null()) {2259return;2260}22612262const String &path = resource->get_path();2263if (path.is_empty() || path.ends_with("::")) {2264String warning = TTR("The resource does not have a valid path because it has not been saved.\nPlease save the scene or resource that contains this resource and try again.");2265EditorToaster::get_singleton()->popup_str(warning, EditorToaster::SEVERITY_ERROR);2266return;2267}22682269if (drop_modifier_pressed) {2270if (resource->is_built_in()) {2271String warning = TTR("Preloading internal resources is not supported.");2272EditorToaster::get_singleton()->popup_str(warning, EditorToaster::SEVERITY_ERROR);2273} else {2274text_to_drop = _get_dropped_resource_line(resource, is_empty_line, allow_uid);2275}2276} else {2277text_to_drop = _quote_drop_data(path);2278}2279}22802281if (type == "files" || type == "files_and_dirs") {2282const PackedStringArray files = d["files"];2283PackedStringArray parts;22842285for (const String &path : files) {2286if (drop_modifier_pressed && ResourceLoader::exists(path)) {2287Ref<Resource> resource = ResourceLoader::load(path);2288if (resource.is_null()) {2289// Resource exists, but failed to load. We need only path and name, so we can use a dummy Resource instead.2290resource.instantiate();2291resource->set_path_cache(path);2292}2293parts.append(_get_dropped_resource_line(resource, is_empty_line, allow_uid));2294} else {2295parts.append(_quote_drop_data(path));2296}2297}2298String join_string;2299if (is_empty_line) {2300int indent_level = te->get_indent_level(drop_at_line);2301if (te->is_indent_using_spaces()) {2302join_string = "\n" + String(" ").repeat(indent_level);2303} else {2304join_string = "\n" + String("\t").repeat(indent_level / te->get_tab_size());2305}2306} else {2307join_string = ", ";2308}2309text_to_drop = join_string.join(parts);2310if (is_empty_line) {2311text_to_drop += join_string;2312}2313}23142315if (type == "nodes") {2316Node *scene_root = get_tree()->get_edited_scene_root();2317if (!scene_root) {2318EditorNode::get_singleton()->show_warning(TTR("Can't drop nodes without an open scene."));2319return;2320}23212322if (!ClassDB::is_parent_class(script->get_instance_base_type(), "Node")) {2323EditorToaster::get_singleton()->popup_str(vformat(TTR("Can't drop nodes because script '%s' does not inherit Node."), get_name()), EditorToaster::SEVERITY_WARNING);2324return;2325}23262327Node *sn = _find_script_node(scene_root, script);2328if (!sn) {2329sn = scene_root;2330}23312332Array nodes = d["nodes"];23332334if (drop_modifier_pressed) {2335const bool use_type = EDITOR_GET("text_editor/completion/add_type_hints");2336add_new_line = !is_empty_line && drop_at_column != 0;23372338for (int i = 0; i < nodes.size(); i++) {2339NodePath np = nodes[i];2340Node *node = get_node(np);2341if (!node) {2342continue;2343}23442345bool is_unique = node->is_unique_name_in_owner() && (node->get_owner() == sn || node->get_owner() == sn->get_owner());2346String path = is_unique ? String(node->get_name()) : String(sn->get_path_to(node));2347for (const String &segment : path.split("/")) {2348if (!segment.is_valid_unicode_identifier()) {2349path = _quote_drop_data(path);2350break;2351}2352}23532354String variable_name = String(node->get_name()).to_snake_case().validate_unicode_identifier();2355if (use_type) {2356StringName class_name = node->get_class_name();2357Ref<Script> node_script = node->get_script();2358if (node_script.is_valid()) {2359StringName global_node_script_name = node_script->get_global_name();2360if (global_node_script_name != StringName()) {2361class_name = global_node_script_name;2362}2363}2364text_to_drop += vformat("@onready var %s: %s = %c%s", variable_name, class_name, is_unique ? '%' : '$', path);2365} else {2366text_to_drop += vformat("@onready var %s = %c%s", variable_name, is_unique ? '%' : '$', path);2367}2368if (i < nodes.size() - 1) {2369text_to_drop += "\n";2370}2371}23722373if (is_empty_line || drop_at_column == 0) {2374text_to_drop += "\n";2375}2376} else {2377for (int i = 0; i < nodes.size(); i++) {2378if (i > 0) {2379text_to_drop += ", ";2380}23812382NodePath np = nodes[i];2383Node *node = get_node(np);2384if (!node) {2385continue;2386}23872388bool is_unique = node->is_unique_name_in_owner() && (node->get_owner() == sn || node->get_owner() == sn->get_owner());2389String path = is_unique ? String(node->get_name()) : String(sn->get_path_to(node));2390for (const String &segment : path.split("/")) {2391if (!segment.is_valid_ascii_identifier()) {2392path = _quote_drop_data(path);2393break;2394}2395}2396text_to_drop += (is_unique ? "%" : "$") + path;2397}2398}2399}24002401if (type == "obj_property") {2402bool add_literal = EDITOR_GET("text_editor/completion/add_node_path_literals");2403text_to_drop = add_literal ? "^" : "";2404// It is unclear whether properties may contain single or double quotes.2405// Assume here that double-quotes may not exist. We are escaping single-quotes if necessary.2406text_to_drop += _quote_drop_data(String(d["property"]));2407}24082409if (text_to_drop.is_empty()) {2410return;2411}24122413// Remove drag caret before any actions so it is not included in undo.2414te->remove_drag_caret();2415te->begin_complex_operation();2416if (selection_index >= 0) {2417te->delete_selection(selection_index);2418}2419te->remove_secondary_carets();2420te->deselect();2421te->set_caret_line(drop_at_line);2422if (add_new_line) {2423te->set_caret_column(te->get_line(drop_at_line).length());2424text_to_drop = "\n" + text_to_drop;2425} else {2426te->set_caret_column(drop_at_column);2427}2428te->insert_text_at_caret(text_to_drop);2429te->end_complex_operation();2430te->grab_focus();2431}24322433void ScriptTextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) {2434Ref<InputEventMouseButton> mb = ev;2435Ref<InputEventKey> k = ev;2436Point2 local_pos;2437bool create_menu = false;24382439CodeEdit *tx = code_editor->get_text_editor();2440if (mb.is_valid() && mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed()) {2441local_pos = mb->get_global_position() - tx->get_global_position();2442create_menu = true;2443} else if (k.is_valid() && k->is_action("ui_menu", true)) {2444tx->adjust_viewport_to_caret(0);2445local_pos = tx->get_caret_draw_pos(0);2446create_menu = true;2447}24482449if (create_menu) {2450tx->apply_ime();24512452Point2i pos = tx->get_line_column_at_pos(local_pos);2453int mouse_line = pos.y;2454int mouse_column = pos.x;24552456tx->set_move_caret_on_right_click_enabled(EDITOR_GET("text_editor/behavior/navigation/move_caret_on_right_click"));2457int selection_clicked = -1;2458if (tx->is_move_caret_on_right_click_enabled()) {2459selection_clicked = tx->get_selection_at_line_column(mouse_line, mouse_column, true);2460if (selection_clicked < 0) {2461tx->deselect();2462tx->remove_secondary_carets();2463selection_clicked = 0;2464tx->set_caret_line(mouse_line, false, false, -1);2465tx->set_caret_column(mouse_column);2466}2467}24682469String word_at_pos = tx->get_lookup_word(mouse_line, mouse_column);2470if (word_at_pos.is_empty()) {2471word_at_pos = tx->get_word_under_caret(selection_clicked);2472}2473if (word_at_pos.is_empty()) {2474word_at_pos = tx->get_selected_text(selection_clicked);2475}24762477bool has_color = (word_at_pos == "Color");2478bool foldable = tx->can_fold_line(mouse_line) || tx->is_line_folded(mouse_line);2479bool open_docs = false;2480bool goto_definition = false;24812482if (ScriptServer::is_global_class(word_at_pos) || word_at_pos.is_resource_file()) {2483open_docs = true;2484} else {2485Node *base = get_tree()->get_edited_scene_root();2486if (base) {2487base = _find_node_for_script(base, base, script);2488}2489ScriptLanguage::LookupResult result;2490if (script->get_language()->lookup_code(tx->get_text_for_symbol_lookup(), word_at_pos, script->get_path(), base, result) == OK) {2491open_docs = true;2492}2493}24942495if (has_color) {2496String line = tx->get_line(mouse_line);2497color_position.x = mouse_line;24982499int begin = -1;2500int end = -1;2501enum EXPRESSION_PATTERNS {2502NOT_PARSED,2503RGBA_PARAMETER, // Color(float,float,float) or Color(float,float,float,float)2504COLOR_NAME, // Color.COLOR_NAME2505} expression_pattern = NOT_PARSED;25062507for (int i = mouse_column; i < line.length(); i++) {2508if (line[i] == '(') {2509if (expression_pattern == NOT_PARSED) {2510begin = i;2511expression_pattern = RGBA_PARAMETER;2512} else {2513// Method call or '(' appearing twice.2514expression_pattern = NOT_PARSED;25152516break;2517}2518} else if (expression_pattern == RGBA_PARAMETER && line[i] == ')' && end < 0) {2519end = i + 1;25202521break;2522} else if (expression_pattern == NOT_PARSED && line[i] == '.') {2523begin = i;2524expression_pattern = COLOR_NAME;2525} else if (expression_pattern == COLOR_NAME && end < 0 && (line[i] == ' ' || line[i] == '\t')) {2526// Including '.' and spaces.2527continue;2528} else if (expression_pattern == COLOR_NAME && !(line[i] == '_' || ('A' <= line[i] && line[i] <= 'Z'))) {2529end = i;25302531break;2532}2533}25342535switch (expression_pattern) {2536case RGBA_PARAMETER: {2537color_args = line.substr(begin, end - begin);2538String stripped = color_args.remove_chars(" \t()");2539PackedFloat64Array color = stripped.split_floats(",");2540if (color.size() > 2) {2541float alpha = color.size() > 3 ? color[3] : 1.0f;2542color_picker->set_pick_color(Color(color[0], color[1], color[2], alpha));2543}2544} break;2545case COLOR_NAME: {2546if (end < 0) {2547end = line.length();2548}2549color_args = line.substr(begin, end - begin);2550const String color_name = color_args.remove_chars(" \t.");2551const int color_index = Color::find_named_color(color_name);2552if (0 <= color_index) {2553const Color color_constant = Color::get_named_color(color_index);2554color_picker->set_pick_color(color_constant);2555} else {2556has_color = false;2557}2558} break;2559default:2560has_color = false;2561break;2562}2563if (has_color) {2564color_panel->set_position(get_screen_position() + local_pos);2565color_position.y = begin;2566color_position.z = end;2567}2568}2569_make_context_menu(tx->has_selection(), has_color, foldable, open_docs, goto_definition, local_pos);2570}2571}25722573void ScriptTextEditor::_color_changed(const Color &p_color) {2574String new_args;2575const int decimals = 3;2576if (p_color.a == 1.0f) {2577new_args = String("(" + String::num(p_color.r, decimals) + ", " + String::num(p_color.g, decimals) + ", " + String::num(p_color.b, decimals) + ")");2578} else {2579new_args = String("(" + String::num(p_color.r, decimals) + ", " + String::num(p_color.g, decimals) + ", " + String::num(p_color.b, decimals) + ", " + String::num(p_color.a, decimals) + ")");2580}25812582String line = code_editor->get_text_editor()->get_line(color_position.x);2583String line_with_replaced_args = line.substr(0, color_position.y) + line.substr(color_position.y, color_position.z - color_position.y).replace(color_args, new_args) + line.substr(color_position.z);25842585color_args = new_args;2586code_editor->get_text_editor()->begin_complex_operation();2587code_editor->get_text_editor()->set_line(color_position.x, line_with_replaced_args);2588code_editor->get_text_editor()->end_complex_operation();2589}25902591void ScriptTextEditor::_prepare_edit_menu() {2592const CodeEdit *tx = code_editor->get_text_editor();2593PopupMenu *popup = edit_menu->get_popup();2594popup->set_item_disabled(popup->get_item_index(EDIT_UNDO), !tx->has_undo());2595popup->set_item_disabled(popup->get_item_index(EDIT_REDO), !tx->has_redo());2596}25972598void ScriptTextEditor::_make_context_menu(bool p_selection, bool p_color, bool p_foldable, bool p_open_docs, bool p_goto_definition, Vector2 p_pos) {2599context_menu->clear();2600if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_EMOJI_AND_SYMBOL_PICKER)) {2601context_menu->add_item(TTRC("Emoji & Symbols"), EDIT_EMOJI_AND_SYMBOL);2602context_menu->add_separator();2603}2604context_menu->add_shortcut(ED_GET_SHORTCUT("ui_undo"), EDIT_UNDO);2605context_menu->add_shortcut(ED_GET_SHORTCUT("ui_redo"), EDIT_REDO);26062607context_menu->add_separator();2608context_menu->add_shortcut(ED_GET_SHORTCUT("ui_cut"), EDIT_CUT);2609context_menu->add_shortcut(ED_GET_SHORTCUT("ui_copy"), EDIT_COPY);2610context_menu->add_shortcut(ED_GET_SHORTCUT("ui_paste"), EDIT_PASTE);26112612context_menu->add_separator();2613context_menu->add_shortcut(ED_GET_SHORTCUT("ui_text_select_all"), EDIT_SELECT_ALL);26142615context_menu->add_separator();2616context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent"), EDIT_INDENT);2617context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/unindent"), EDIT_UNINDENT);2618context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_comment"), EDIT_TOGGLE_COMMENT);2619context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_bookmark"), BOOKMARK_TOGGLE);26202621if (p_selection) {2622context_menu->add_separator();2623context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_to_uppercase"), EDIT_TO_UPPERCASE);2624context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_to_lowercase"), EDIT_TO_LOWERCASE);2625context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/evaluate_selection"), EDIT_EVALUATE);2626context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/create_code_region"), EDIT_CREATE_CODE_REGION);2627}2628if (p_foldable) {2629context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_fold_line"), EDIT_TOGGLE_FOLD_LINE);2630}26312632if (p_color || p_open_docs || p_goto_definition) {2633context_menu->add_separator();2634if (p_open_docs) {2635context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_symbol"), LOOKUP_SYMBOL);2636}2637if (p_color) {2638context_menu->add_item(TTRC("Pick Color"), EDIT_PICK_COLOR);2639}2640}26412642const PackedStringArray paths = { String(code_editor->get_text_editor()->get_path()) };2643EditorContextMenuPluginManager::get_singleton()->add_options_from_plugins(context_menu, EditorContextMenuPlugin::CONTEXT_SLOT_SCRIPT_EDITOR_CODE, paths);26442645const CodeEdit *tx = code_editor->get_text_editor();2646context_menu->set_item_disabled(context_menu->get_item_index(EDIT_UNDO), !tx->has_undo());2647context_menu->set_item_disabled(context_menu->get_item_index(EDIT_REDO), !tx->has_redo());26482649context_menu->set_position(get_screen_position() + p_pos);2650context_menu->reset_size();2651context_menu->popup();2652}26532654void ScriptTextEditor::_enable_code_editor() {2655ERR_FAIL_COND(code_editor->get_parent());26562657VSplitContainer *editor_box = memnew(VSplitContainer);2658add_child(editor_box);2659editor_box->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);2660editor_box->set_v_size_flags(SIZE_EXPAND_FILL);26612662editor_box->add_child(code_editor);2663code_editor->connect("show_errors_panel", callable_mp(this, &ScriptTextEditor::_show_errors_panel));2664code_editor->connect("show_warnings_panel", callable_mp(this, &ScriptTextEditor::_show_warnings_panel));2665code_editor->connect("validate_script", callable_mp(this, &ScriptTextEditor::_validate_script));2666code_editor->connect("load_theme_settings", callable_mp(this, &ScriptTextEditor::_load_theme_settings));2667code_editor->get_text_editor()->connect("symbol_lookup", callable_mp(this, &ScriptTextEditor::_lookup_symbol));2668code_editor->get_text_editor()->connect("symbol_hovered", callable_mp(this, &ScriptTextEditor::_show_symbol_tooltip));2669code_editor->get_text_editor()->connect("symbol_validate", callable_mp(this, &ScriptTextEditor::_validate_symbol));2670code_editor->get_text_editor()->connect("gutter_added", callable_mp(this, &ScriptTextEditor::_update_gutter_indexes));2671code_editor->get_text_editor()->connect("gutter_removed", callable_mp(this, &ScriptTextEditor::_update_gutter_indexes));2672code_editor->get_text_editor()->connect("gutter_clicked", callable_mp(this, &ScriptTextEditor::_gutter_clicked));2673code_editor->get_text_editor()->connect("_fold_line_updated", callable_mp(this, &ScriptTextEditor::_update_background_color));2674code_editor->get_text_editor()->connect(SceneStringName(gui_input), callable_mp(this, &ScriptTextEditor::_text_edit_gui_input));2675code_editor->show_toggle_files_button();2676_update_gutter_indexes();26772678editor_box->add_child(warnings_panel);2679warnings_panel->add_theme_font_override(2680"normal_font", EditorNode::get_singleton()->get_editor_theme()->get_font(SNAME("main"), EditorStringName(EditorFonts)));2681warnings_panel->add_theme_font_size_override(2682"normal_font_size", EditorNode::get_singleton()->get_editor_theme()->get_font_size(SNAME("main_size"), EditorStringName(EditorFonts)));2683warnings_panel->connect("meta_clicked", callable_mp(this, &ScriptTextEditor::_warning_clicked));26842685editor_box->add_child(errors_panel);2686errors_panel->add_theme_font_override(2687"normal_font", EditorNode::get_singleton()->get_editor_theme()->get_font(SNAME("main"), EditorStringName(EditorFonts)));2688errors_panel->add_theme_font_size_override(2689"normal_font_size", EditorNode::get_singleton()->get_editor_theme()->get_font_size(SNAME("main_size"), EditorStringName(EditorFonts)));2690errors_panel->connect("meta_clicked", callable_mp(this, &ScriptTextEditor::_error_clicked));26912692add_child(context_menu);2693context_menu->connect(SceneStringName(id_pressed), callable_mp(this, &ScriptTextEditor::_edit_option));26942695add_child(color_panel);26962697color_picker = memnew(ColorPicker);2698color_picker->set_deferred_mode(true);2699color_picker->connect("color_changed", callable_mp(this, &ScriptTextEditor::_color_changed));2700color_panel->connect("about_to_popup", callable_mp(EditorNode::get_singleton(), &EditorNode::setup_color_picker).bind(color_picker));27012702color_panel->add_child(color_picker);27032704quick_open = memnew(ScriptEditorQuickOpen);2705quick_open->set_title(TTRC("Go to Function"));2706quick_open->connect("goto_line", callable_mp(this, &ScriptTextEditor::_goto_line));2707add_child(quick_open);27082709goto_line_popup = memnew(GotoLinePopup);2710add_child(goto_line_popup);27112712add_child(connection_info_dialog);27132714edit_hb->add_child(edit_menu);2715edit_menu->connect("about_to_popup", callable_mp(this, &ScriptTextEditor::_prepare_edit_menu));2716edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_undo"), EDIT_UNDO);2717edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_redo"), EDIT_REDO);2718edit_menu->get_popup()->add_separator();2719edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_cut"), EDIT_CUT);2720edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_copy"), EDIT_COPY);2721edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_paste"), EDIT_PASTE);2722edit_menu->get_popup()->add_separator();2723edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_text_select_all"), EDIT_SELECT_ALL);2724edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/duplicate_selection"), EDIT_DUPLICATE_SELECTION);2725edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/duplicate_lines"), EDIT_DUPLICATE_LINES);2726edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/evaluate_selection"), EDIT_EVALUATE);2727edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_word_wrap"), EDIT_TOGGLE_WORD_WRAP);2728edit_menu->get_popup()->add_separator();2729{2730PopupMenu *sub_menu = memnew(PopupMenu);2731sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/move_up"), EDIT_MOVE_LINE_UP);2732sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/move_down"), EDIT_MOVE_LINE_DOWN);2733sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent"), EDIT_INDENT);2734sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/unindent"), EDIT_UNINDENT);2735sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/delete_line"), EDIT_DELETE_LINE);2736sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_comment"), EDIT_TOGGLE_COMMENT);2737sub_menu->connect(SceneStringName(id_pressed), callable_mp(this, &ScriptTextEditor::_edit_option));2738edit_menu->get_popup()->add_submenu_node_item(TTRC("Line"), sub_menu);2739}2740{2741PopupMenu *sub_menu = memnew(PopupMenu);2742sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_fold_line"), EDIT_TOGGLE_FOLD_LINE);2743sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/fold_all_lines"), EDIT_FOLD_ALL_LINES);2744sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/unfold_all_lines"), EDIT_UNFOLD_ALL_LINES);2745sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/create_code_region"), EDIT_CREATE_CODE_REGION);2746sub_menu->connect(SceneStringName(id_pressed), callable_mp(this, &ScriptTextEditor::_edit_option));2747edit_menu->get_popup()->add_submenu_node_item(TTRC("Folding"), sub_menu);2748}2749edit_menu->get_popup()->add_separator();2750edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_text_completion_query"), EDIT_COMPLETE);2751edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/trim_trailing_whitespace"), EDIT_TRIM_TRAILING_WHITESAPCE);2752edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/trim_final_newlines"), EDIT_TRIM_FINAL_NEWLINES);2753{2754PopupMenu *sub_menu = memnew(PopupMenu);2755sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_indent_to_spaces"), EDIT_CONVERT_INDENT_TO_SPACES);2756sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_indent_to_tabs"), EDIT_CONVERT_INDENT_TO_TABS);2757sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/auto_indent"), EDIT_AUTO_INDENT);2758sub_menu->connect(SceneStringName(id_pressed), callable_mp(this, &ScriptTextEditor::_edit_option));2759edit_menu->get_popup()->add_submenu_node_item(TTRC("Indentation"), sub_menu);2760}2761edit_menu->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &ScriptTextEditor::_edit_option));2762edit_menu->get_popup()->add_separator();2763{2764PopupMenu *sub_menu = memnew(PopupMenu);2765sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_to_uppercase"), EDIT_TO_UPPERCASE);2766sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_to_lowercase"), EDIT_TO_LOWERCASE);2767sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/capitalize"), EDIT_CAPITALIZE);2768sub_menu->connect(SceneStringName(id_pressed), callable_mp(this, &ScriptTextEditor::_edit_option));2769edit_menu->get_popup()->add_submenu_node_item(TTRC("Convert Case"), sub_menu);2770}2771edit_menu->get_popup()->add_submenu_node_item(TTRC("Syntax Highlighter"), highlighter_menu);2772highlighter_menu->connect(SceneStringName(id_pressed), callable_mp(this, &ScriptTextEditor::_change_syntax_highlighter));27732774edit_hb->add_child(search_menu);2775search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find"), SEARCH_FIND);2776search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_next"), SEARCH_FIND_NEXT);2777search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_previous"), SEARCH_FIND_PREV);2778search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/replace"), SEARCH_REPLACE);2779search_menu->get_popup()->add_separator();2780search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("editor/find_in_files"), SEARCH_IN_FILES);2781search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/replace_in_files"), REPLACE_IN_FILES);2782search_menu->get_popup()->add_separator();2783search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/contextual_help"), HELP_CONTEXTUAL);2784search_menu->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &ScriptTextEditor::_edit_option));27852786_load_theme_settings();27872788edit_hb->add_child(goto_menu);2789goto_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_function"), SEARCH_LOCATE_FUNCTION);2790goto_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_line"), SEARCH_GOTO_LINE);2791goto_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_symbol"), LOOKUP_SYMBOL);2792goto_menu->get_popup()->add_separator();27932794goto_menu->get_popup()->add_submenu_node_item(TTRC("Bookmarks"), bookmarks_menu);2795_update_bookmark_list();2796bookmarks_menu->connect("about_to_popup", callable_mp(this, &ScriptTextEditor::_update_bookmark_list));2797bookmarks_menu->connect("index_pressed", callable_mp(this, &ScriptTextEditor::_bookmark_item_pressed));27982799goto_menu->get_popup()->add_submenu_node_item(TTRC("Breakpoints"), breakpoints_menu);2800_update_breakpoint_list();2801breakpoints_menu->connect("about_to_popup", callable_mp(this, &ScriptTextEditor::_update_breakpoint_list));2802breakpoints_menu->connect("index_pressed", callable_mp(this, &ScriptTextEditor::_breakpoint_item_pressed));28032804goto_menu->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &ScriptTextEditor::_edit_option));2805}28062807ScriptTextEditor::ScriptTextEditor() {2808code_editor = memnew(CodeTextEditor);2809code_editor->set_toggle_list_control(ScriptEditor::get_singleton()->get_left_list_split());2810code_editor->add_theme_constant_override("separation", 2);2811code_editor->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);2812code_editor->set_code_complete_func(_code_complete_scripts, this);2813code_editor->set_v_size_flags(SIZE_EXPAND_FILL);28142815code_editor->get_text_editor()->set_draw_breakpoints_gutter(true);2816code_editor->get_text_editor()->set_draw_executing_lines_gutter(true);2817code_editor->get_text_editor()->connect("breakpoint_toggled", callable_mp(this, &ScriptTextEditor::_breakpoint_toggled));2818code_editor->get_text_editor()->connect("caret_changed", callable_mp(this, &ScriptTextEditor::_on_caret_moved));2819code_editor->connect("navigation_preview_ended", callable_mp(this, &ScriptTextEditor::_on_caret_moved));28202821connection_gutter = 1;2822code_editor->get_text_editor()->add_gutter(connection_gutter);2823code_editor->get_text_editor()->set_gutter_name(connection_gutter, "connection_gutter");2824code_editor->get_text_editor()->set_gutter_draw(connection_gutter, false);2825code_editor->get_text_editor()->set_gutter_overwritable(connection_gutter, true);2826code_editor->get_text_editor()->set_gutter_type(connection_gutter, TextEdit::GUTTER_TYPE_ICON);28272828warnings_panel = memnew(RichTextLabel);2829warnings_panel->set_custom_minimum_size(Size2(0, 100 * EDSCALE));2830warnings_panel->set_h_size_flags(SIZE_EXPAND_FILL);2831warnings_panel->set_meta_underline(true);2832warnings_panel->set_selection_enabled(true);2833warnings_panel->set_context_menu_enabled(true);2834warnings_panel->set_focus_mode(FOCUS_CLICK);2835warnings_panel->hide();28362837errors_panel = memnew(RichTextLabel);2838errors_panel->set_custom_minimum_size(Size2(0, 100 * EDSCALE));2839errors_panel->set_h_size_flags(SIZE_EXPAND_FILL);2840errors_panel->set_meta_underline(true);2841errors_panel->set_selection_enabled(true);2842errors_panel->set_context_menu_enabled(true);2843errors_panel->set_focus_mode(FOCUS_CLICK);2844errors_panel->hide();28452846update_settings();28472848code_editor->get_text_editor()->set_symbol_lookup_on_click_enabled(true);2849code_editor->get_text_editor()->set_symbol_tooltip_on_hover_enabled(true);2850code_editor->get_text_editor()->set_context_menu_enabled(false);28512852context_menu = memnew(PopupMenu);28532854color_panel = memnew(PopupPanel);28552856edit_hb = memnew(HBoxContainer);28572858edit_menu = memnew(MenuButton);2859edit_menu->set_flat(false);2860edit_menu->set_theme_type_variation("FlatMenuButton");2861edit_menu->set_text(TTRC("Edit"));2862edit_menu->set_switch_on_hover(true);2863edit_menu->set_shortcut_context(this);28642865highlighter_menu = memnew(PopupMenu);28662867Ref<EditorPlainTextSyntaxHighlighter> plain_highlighter;2868plain_highlighter.instantiate();2869add_syntax_highlighter(plain_highlighter);28702871Ref<EditorStandardSyntaxHighlighter> highlighter;2872highlighter.instantiate();2873add_syntax_highlighter(highlighter);2874set_syntax_highlighter(highlighter);28752876search_menu = memnew(MenuButton);2877search_menu->set_flat(false);2878search_menu->set_theme_type_variation("FlatMenuButton");2879search_menu->set_text(TTRC("Search"));2880search_menu->set_switch_on_hover(true);2881search_menu->set_shortcut_context(this);28822883goto_menu = memnew(MenuButton);2884goto_menu->set_flat(false);2885goto_menu->set_theme_type_variation("FlatMenuButton");2886goto_menu->set_text(TTRC("Go To"));2887goto_menu->set_switch_on_hover(true);2888goto_menu->set_shortcut_context(this);28892890bookmarks_menu = memnew(PopupMenu);2891breakpoints_menu = memnew(PopupMenu);28922893inline_color_popup = memnew(PopupPanel);2894add_child(inline_color_popup);28952896inline_color_picker = memnew(ColorPicker);2897inline_color_picker->set_mouse_filter(MOUSE_FILTER_STOP);2898inline_color_picker->set_deferred_mode(true);2899inline_color_picker->set_hex_visible(false);2900inline_color_picker->connect("color_changed", callable_mp(this, &ScriptTextEditor::_picker_color_changed));2901inline_color_popup->add_child(inline_color_picker);29022903inline_color_options = memnew(OptionButton);2904inline_color_options->set_h_size_flags(SIZE_FILL);2905inline_color_options->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS);2906inline_color_options->set_fit_to_longest_item(false);2907inline_color_options->connect("item_selected", callable_mp(this, &ScriptTextEditor::_update_color_text).unbind(1));2908inline_color_picker->get_slider_container()->add_sibling(inline_color_options);29092910connection_info_dialog = memnew(ConnectionInfoDialog);29112912SET_DRAG_FORWARDING_GCD(code_editor->get_text_editor(), ScriptTextEditor);2913}29142915ScriptTextEditor::~ScriptTextEditor() {2916highlighters.clear();29172918if (!editor_enabled) {2919memdelete(code_editor);2920memdelete(warnings_panel);2921memdelete(errors_panel);2922memdelete(context_menu);2923memdelete(color_panel);2924memdelete(edit_hb);2925memdelete(edit_menu);2926memdelete(highlighter_menu);2927memdelete(search_menu);2928memdelete(goto_menu);2929memdelete(bookmarks_menu);2930memdelete(breakpoints_menu);2931memdelete(connection_info_dialog);2932}2933}29342935static ScriptEditorBase *create_editor(const Ref<Resource> &p_resource) {2936if (Object::cast_to<Script>(*p_resource)) {2937return memnew(ScriptTextEditor);2938}2939return nullptr;2940}29412942void ScriptTextEditor::register_editor() {2943ED_SHORTCUT("script_text_editor/move_up", TTRC("Move Up"), KeyModifierMask::ALT | Key::UP);2944ED_SHORTCUT("script_text_editor/move_down", TTRC("Move Down"), KeyModifierMask::ALT | Key::DOWN);2945ED_SHORTCUT("script_text_editor/delete_line", TTRC("Delete Line"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::K);29462947// Leave these at zero, same can be accomplished with tab/shift-tab, including selection.2948// The next/previous in history shortcut in this case makes a lot more sense.29492950ED_SHORTCUT("script_text_editor/indent", TTRC("Indent"), Key::NONE);2951ED_SHORTCUT("script_text_editor/unindent", TTRC("Unindent"), KeyModifierMask::SHIFT | Key::TAB);2952ED_SHORTCUT_ARRAY("script_text_editor/toggle_comment", TTRC("Toggle Comment"), { int32_t(KeyModifierMask::CMD_OR_CTRL | Key::K), int32_t(KeyModifierMask::CMD_OR_CTRL | Key::SLASH), int32_t(KeyModifierMask::CMD_OR_CTRL | Key::KP_DIVIDE), int32_t(KeyModifierMask::CMD_OR_CTRL | Key::NUMBERSIGN) });2953ED_SHORTCUT("script_text_editor/toggle_fold_line", TTRC("Fold/Unfold Line"), KeyModifierMask::ALT | Key::F);2954ED_SHORTCUT_OVERRIDE("script_text_editor/toggle_fold_line", "macos", KeyModifierMask::CTRL | KeyModifierMask::META | Key::F);2955ED_SHORTCUT("script_text_editor/fold_all_lines", TTRC("Fold All Lines"), Key::NONE);2956ED_SHORTCUT("script_text_editor/create_code_region", TTRC("Create Code Region"), KeyModifierMask::ALT | Key::R);2957ED_SHORTCUT("script_text_editor/unfold_all_lines", TTRC("Unfold All Lines"), Key::NONE);2958ED_SHORTCUT("script_text_editor/duplicate_selection", TTRC("Duplicate Selection"), KeyModifierMask::SHIFT | KeyModifierMask::CTRL | Key::D);2959ED_SHORTCUT_OVERRIDE("script_text_editor/duplicate_selection", "macos", KeyModifierMask::SHIFT | KeyModifierMask::META | Key::C);2960ED_SHORTCUT("script_text_editor/duplicate_lines", TTRC("Duplicate Lines"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::ALT | Key::DOWN);2961ED_SHORTCUT_OVERRIDE("script_text_editor/duplicate_lines", "macos", KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::DOWN);2962ED_SHORTCUT("script_text_editor/evaluate_selection", TTRC("Evaluate Selection"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::E);2963ED_SHORTCUT("script_text_editor/toggle_word_wrap", TTRC("Toggle Word Wrap"), KeyModifierMask::ALT | Key::Z);2964ED_SHORTCUT("script_text_editor/trim_trailing_whitespace", TTRC("Trim Trailing Whitespace"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::ALT | Key::T);2965ED_SHORTCUT("script_text_editor/trim_final_newlines", TTRC("Trim Final Newlines"), Key::NONE);2966ED_SHORTCUT("script_text_editor/convert_indent_to_spaces", TTRC("Convert Indent to Spaces"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::Y);2967ED_SHORTCUT("script_text_editor/convert_indent_to_tabs", TTRC("Convert Indent to Tabs"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::I);2968ED_SHORTCUT("script_text_editor/auto_indent", TTRC("Auto Indent"), KeyModifierMask::CMD_OR_CTRL | Key::I);29692970ED_SHORTCUT_AND_COMMAND("script_text_editor/find", TTRC("Find..."), KeyModifierMask::CMD_OR_CTRL | Key::F);29712972ED_SHORTCUT("script_text_editor/find_next", TTRC("Find Next"), Key::F3);2973ED_SHORTCUT_OVERRIDE("script_text_editor/find_next", "macos", KeyModifierMask::META | Key::G);29742975ED_SHORTCUT("script_text_editor/find_previous", TTRC("Find Previous"), KeyModifierMask::SHIFT | Key::F3);2976ED_SHORTCUT_OVERRIDE("script_text_editor/find_previous", "macos", KeyModifierMask::META | KeyModifierMask::SHIFT | Key::G);29772978ED_SHORTCUT_AND_COMMAND("script_text_editor/replace", TTRC("Replace..."), KeyModifierMask::CTRL | Key::R);2979ED_SHORTCUT_OVERRIDE("script_text_editor/replace", "macos", KeyModifierMask::ALT | KeyModifierMask::META | Key::F);29802981ED_SHORTCUT("script_text_editor/replace_in_files", TTRC("Replace in Files..."), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::R);29822983ED_SHORTCUT("script_text_editor/contextual_help", TTRC("Contextual Help"), KeyModifierMask::ALT | Key::F1);2984ED_SHORTCUT_OVERRIDE("script_text_editor/contextual_help", "macos", KeyModifierMask::ALT | KeyModifierMask::SHIFT | Key::SPACE);29852986ED_SHORTCUT("script_text_editor/toggle_bookmark", TTRC("Toggle Bookmark"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::ALT | Key::B);29872988ED_SHORTCUT("script_text_editor/goto_next_bookmark", TTRC("Go to Next Bookmark"), KeyModifierMask::CMD_OR_CTRL | Key::B);2989ED_SHORTCUT_OVERRIDE("script_text_editor/goto_next_bookmark", "macos", KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | KeyModifierMask::ALT | Key::B);29902991ED_SHORTCUT("script_text_editor/goto_previous_bookmark", TTRC("Go to Previous Bookmark"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::B);2992ED_SHORTCUT("script_text_editor/remove_all_bookmarks", TTRC("Remove All Bookmarks"), Key::NONE);29932994ED_SHORTCUT("script_text_editor/goto_function", TTRC("Go to Function..."), KeyModifierMask::ALT | KeyModifierMask::CTRL | Key::F);2995ED_SHORTCUT_OVERRIDE("script_text_editor/goto_function", "macos", KeyModifierMask::CTRL | KeyModifierMask::META | Key::J);29962997ED_SHORTCUT("script_text_editor/goto_line", TTRC("Go to Line..."), KeyModifierMask::CMD_OR_CTRL | Key::L);2998ED_SHORTCUT("script_text_editor/goto_symbol", TTRC("Lookup Symbol"));29993000ED_SHORTCUT("script_text_editor/toggle_breakpoint", TTRC("Toggle Breakpoint"), Key::F9);3001ED_SHORTCUT_OVERRIDE("script_text_editor/toggle_breakpoint", "macos", KeyModifierMask::META | KeyModifierMask::SHIFT | Key::B);30023003ED_SHORTCUT("script_text_editor/remove_all_breakpoints", TTRC("Remove All Breakpoints"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::F9);3004// Using Control for these shortcuts even on macOS because Command+Comma is taken for opening Editor Settings.3005ED_SHORTCUT("script_text_editor/goto_next_breakpoint", TTRC("Go to Next Breakpoint"), KeyModifierMask::CTRL | Key::PERIOD);3006ED_SHORTCUT("script_text_editor/goto_previous_breakpoint", TTRC("Go to Previous Breakpoint"), KeyModifierMask::CTRL | Key::COMMA);30073008ScriptEditor::register_create_script_editor_function(create_editor);3009}30103011void ScriptTextEditor::validate() {3012code_editor->validate_script();3013}301430153016