/*************************************************************************/ /* script_text_editor.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ /* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ /* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ /* "Software"), to deal in the Software without restriction, including */ /* without limitation the rights to use, copy, modify, merge, publish, */ /* distribute, sublicense, and/or sell copies of the Software, and to */ /* permit persons to whom the Software is furnished to do so, subject to */ /* the following conditions: */ /* */ /* The above copyright notice and this permission notice shall be */ /* included in all copies or substantial portions of the Software. */ /* */ /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ #include "script_text_editor.h" #include "core/math/expression.h" #include "core/os/keyboard.h" #include "editor/debugger/editor_debugger_node.h" #include "editor/editor_node.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" void ConnectionInfoDialog::ok_pressed() { } void ConnectionInfoDialog::popup_connections(String p_method, Vector p_nodes) { method->set_text(p_method); tree->clear(); TreeItem *root = tree->create_item(); for (int i = 0; i < p_nodes.size(); i++) { List all_connections; p_nodes[i]->get_signals_connected_to_this(&all_connections); for (List::Element *E = all_connections.front(); E; E = E->next()) { Connection connection = E->get(); if (connection.callable.get_method() != p_method) { continue; } TreeItem *node_item = tree->create_item(root); node_item->set_text(0, Object::cast_to(connection.signal.get_object())->get_name()); node_item->set_icon(0, EditorNode::get_singleton()->get_object_icon(connection.signal.get_object(), "Node")); node_item->set_selectable(0, false); node_item->set_editable(0, false); node_item->set_text(1, connection.signal.get_name()); Control *p = Object::cast_to(get_parent()); node_item->set_icon(1, p->get_theme_icon("Slot", "EditorIcons")); node_item->set_selectable(1, false); node_item->set_editable(1, false); node_item->set_text(2, Object::cast_to(connection.callable.get_object())->get_name()); node_item->set_icon(2, EditorNode::get_singleton()->get_object_icon(connection.callable.get_object(), "Node")); node_item->set_selectable(2, false); node_item->set_editable(2, false); } } popup_centered(Size2(600, 300) * EDSCALE); } ConnectionInfoDialog::ConnectionInfoDialog() { set_title(TTR("Connections to method:")); VBoxContainer *vbc = memnew(VBoxContainer); vbc->set_anchor_and_offset(SIDE_LEFT, Control::ANCHOR_BEGIN, 8 * EDSCALE); vbc->set_anchor_and_offset(SIDE_TOP, Control::ANCHOR_BEGIN, 8 * EDSCALE); vbc->set_anchor_and_offset(SIDE_RIGHT, Control::ANCHOR_END, -8 * EDSCALE); vbc->set_anchor_and_offset(SIDE_BOTTOM, Control::ANCHOR_END, -8 * EDSCALE); add_child(vbc); method = memnew(Label); method->set_align(Label::ALIGN_CENTER); vbc->add_child(method); tree = memnew(Tree); tree->set_columns(3); tree->set_hide_root(true); tree->set_column_titles_visible(true); tree->set_column_title(0, TTR("Source")); tree->set_column_title(1, TTR("Signal")); tree->set_column_title(2, TTR("Target")); vbc->add_child(tree); tree->set_v_size_flags(Control::SIZE_EXPAND_FILL); tree->set_allow_rmb_select(true); } //////////////////////////////////////////////////////////////////////////////// Vector ScriptTextEditor::get_functions() { String errortxt; int line = -1, col; CodeEdit *te = code_editor->get_text_editor(); String text = te->get_text(); List fnc; if (script->get_language()->validate(text, line, col, errortxt, script->get_path(), &fnc)) { //if valid rewrite functions to latest functions.clear(); for (List::Element *E = fnc.front(); E; E = E->next()) { functions.push_back(E->get()); } } return functions; } void ScriptTextEditor::apply_code() { if (script.is_null()) { return; } script->set_source_code(code_editor->get_text_editor()->get_text()); script->update_exports(); code_editor->get_text_editor()->get_syntax_highlighter()->update_cache(); } RES ScriptTextEditor::get_edited_resource() const { return script; } void ScriptTextEditor::set_edited_resource(const RES &p_res) { ERR_FAIL_COND(script.is_valid()); ERR_FAIL_COND(p_res.is_null()); script = p_res; code_editor->get_text_editor()->set_text(script->get_source_code()); code_editor->get_text_editor()->clear_undo_history(); code_editor->get_text_editor()->tag_saved_version(); emit_signal("name_changed"); code_editor->update_line_and_column(); } void ScriptTextEditor::enable_editor() { if (editor_enabled) { return; } editor_enabled = true; _enable_code_editor(); _set_theme_for_script(); _validate_script(); } void ScriptTextEditor::_load_theme_settings() { CodeEdit *text_edit = code_editor->get_text_editor(); text_edit->clear_keywords(); Color updated_safe_line_number_color = EDITOR_GET("text_editor/highlighting/safe_line_number_color"); if (updated_safe_line_number_color != safe_line_number_color) { safe_line_number_color = updated_safe_line_number_color; for (int i = 0; i < text_edit->get_line_count(); i++) { if (text_edit->get_line_gutter_item_color(i, line_number_gutter) != default_line_number_color) { text_edit->set_line_gutter_item_color(i, line_number_gutter, safe_line_number_color); } } } Color background_color = EDITOR_GET("text_editor/highlighting/background_color"); Color completion_background_color = EDITOR_GET("text_editor/highlighting/completion_background_color"); Color completion_selected_color = EDITOR_GET("text_editor/highlighting/completion_selected_color"); Color completion_existing_color = EDITOR_GET("text_editor/highlighting/completion_existing_color"); Color completion_scroll_color = EDITOR_GET("text_editor/highlighting/completion_scroll_color"); Color completion_font_color = EDITOR_GET("text_editor/highlighting/completion_font_color"); Color text_color = EDITOR_GET("text_editor/highlighting/text_color"); Color line_number_color = EDITOR_GET("text_editor/highlighting/line_number_color"); Color caret_color = EDITOR_GET("text_editor/highlighting/caret_color"); Color caret_background_color = EDITOR_GET("text_editor/highlighting/caret_background_color"); Color text_selected_color = EDITOR_GET("text_editor/highlighting/text_selected_color"); Color selection_color = EDITOR_GET("text_editor/highlighting/selection_color"); Color brace_mismatch_color = EDITOR_GET("text_editor/highlighting/brace_mismatch_color"); Color current_line_color = EDITOR_GET("text_editor/highlighting/current_line_color"); Color line_length_guideline_color = EDITOR_GET("text_editor/highlighting/line_length_guideline_color"); Color word_highlighted_color = EDITOR_GET("text_editor/highlighting/word_highlighted_color"); Color mark_color = EDITOR_GET("text_editor/highlighting/mark_color"); Color bookmark_color = EDITOR_GET("text_editor/highlighting/bookmark_color"); Color breakpoint_color = EDITOR_GET("text_editor/highlighting/breakpoint_color"); Color executing_line_color = EDITOR_GET("text_editor/highlighting/executing_line_color"); Color code_folding_color = EDITOR_GET("text_editor/highlighting/code_folding_color"); Color search_result_color = EDITOR_GET("text_editor/highlighting/search_result_color"); Color search_result_border_color = EDITOR_GET("text_editor/highlighting/search_result_border_color"); text_edit->add_theme_color_override("background_color", background_color); text_edit->add_theme_color_override("completion_background_color", completion_background_color); text_edit->add_theme_color_override("completion_selected_color", completion_selected_color); text_edit->add_theme_color_override("completion_existing_color", completion_existing_color); text_edit->add_theme_color_override("completion_scroll_color", completion_scroll_color); text_edit->add_theme_color_override("completion_font_color", completion_font_color); text_edit->add_theme_color_override("font_color", text_color); text_edit->add_theme_color_override("line_number_color", line_number_color); text_edit->add_theme_color_override("caret_color", caret_color); text_edit->add_theme_color_override("caret_background_color", caret_background_color); text_edit->add_theme_color_override("font_color_selected", text_selected_color); text_edit->add_theme_color_override("selection_color", selection_color); text_edit->add_theme_color_override("brace_mismatch_color", brace_mismatch_color); text_edit->add_theme_color_override("current_line_color", current_line_color); text_edit->add_theme_color_override("line_length_guideline_color", line_length_guideline_color); text_edit->add_theme_color_override("word_highlighted_color", word_highlighted_color); text_edit->add_theme_color_override("bookmark_color", bookmark_color); text_edit->add_theme_color_override("breakpoint_color", breakpoint_color); text_edit->add_theme_color_override("executing_line_color", executing_line_color); text_edit->add_theme_color_override("mark_color", mark_color); text_edit->add_theme_color_override("code_folding_color", code_folding_color); text_edit->add_theme_color_override("search_result_color", search_result_color); text_edit->add_theme_color_override("search_result_border_color", search_result_border_color); text_edit->add_theme_constant_override("line_spacing", EDITOR_DEF("text_editor/theme/line_spacing", 6)); theme_loaded = true; if (!script.is_null()) { _set_theme_for_script(); } } void ScriptTextEditor::_set_theme_for_script() { if (!theme_loaded) { return; } CodeEdit *text_edit = code_editor->get_text_editor(); text_edit->get_syntax_highlighter()->update_cache(); /* add keywords for auto completion */ // singleton autoloads (as types, just as engine singletons are) Map autoloads = ProjectSettings::get_singleton()->get_autoload_list(); for (Map::Element *E = autoloads.front(); E; E = E->next()) { const ProjectSettings::AutoloadInfo &info = E->value(); if (info.is_singleton) { text_edit->add_keyword(info.name); } } // engine types List types; ClassDB::get_class_list(&types); for (List::Element *E = types.front(); E; E = E->next()) { String n = E->get(); if (n.begins_with("_")) { n = n.substr(1, n.length()); } text_edit->add_keyword(n); } // user types List global_classes; ScriptServer::get_global_class_list(&global_classes); for (List::Element *E = global_classes.front(); E; E = E->next()) { text_edit->add_keyword(E->get()); } List keywords; script->get_language()->get_reserved_words(&keywords); for (List::Element *E = keywords.front(); E; E = E->next()) { text_edit->add_keyword(E->get()); } // core types List core_types; script->get_language()->get_core_type_words(&core_types); for (List::Element *E = core_types.front(); E; E = E->next()) { text_edit->add_keyword(E->get()); } } void ScriptTextEditor::_show_warnings_panel(bool p_show) { warnings_panel->set_visible(p_show); } void ScriptTextEditor::_warning_clicked(Variant p_line) { if (p_line.get_type() == Variant::INT) { code_editor->get_text_editor()->cursor_set_line(p_line.operator int64_t()); } else if (p_line.get_type() == Variant::DICTIONARY) { Dictionary meta = p_line.operator Dictionary(); code_editor->get_text_editor()->insert_at("# warning-ignore:" + meta["code"].operator String(), meta["line"].operator int64_t() - 1); _validate_script(); } } void ScriptTextEditor::reload_text() { ERR_FAIL_COND(script.is_null()); CodeEdit *te = code_editor->get_text_editor(); int column = te->cursor_get_column(); int row = te->cursor_get_line(); int h = te->get_h_scroll(); int v = te->get_v_scroll(); te->set_text(script->get_source_code()); te->cursor_set_line(row); te->cursor_set_column(column); te->set_h_scroll(h); te->set_v_scroll(v); te->tag_saved_version(); code_editor->update_line_and_column(); } void ScriptTextEditor::add_callback(const String &p_function, PackedStringArray p_args) { String code = code_editor->get_text_editor()->get_text(); int pos = script->get_language()->find_function(p_function, code); if (pos == -1) { //does not exist code_editor->get_text_editor()->deselect(); pos = code_editor->get_text_editor()->get_line_count() + 2; String func = script->get_language()->make_function("", p_function, p_args); //code=code+func; code_editor->get_text_editor()->cursor_set_line(pos + 1); code_editor->get_text_editor()->cursor_set_column(1000000); //none shall be that big code_editor->get_text_editor()->insert_text_at_cursor("\n\n" + func); } code_editor->get_text_editor()->cursor_set_line(pos); code_editor->get_text_editor()->cursor_set_column(1); } bool ScriptTextEditor::show_members_overview() { return true; } void ScriptTextEditor::update_settings() { code_editor->get_text_editor()->set_gutter_draw(connection_gutter, EditorSettings::get_singleton()->get("text_editor/appearance/show_info_gutter")); code_editor->update_editor_settings(); } bool ScriptTextEditor::is_unsaved() { const bool unsaved = code_editor->get_text_editor()->get_version() != code_editor->get_text_editor()->get_saved_version() || script->get_path().empty(); // In memory. return unsaved; } Variant ScriptTextEditor::get_edit_state() { return code_editor->get_edit_state(); } void ScriptTextEditor::set_edit_state(const Variant &p_state) { code_editor->set_edit_state(p_state); Dictionary state = p_state; if (state.has("syntax_highlighter")) { int idx = highlighter_menu->get_item_idx_from_text(state["syntax_highlighter"]); if (idx >= 0) { _change_syntax_highlighter(idx); } } if (editor_enabled) { ensure_focus(); } } void ScriptTextEditor::_convert_case(CodeTextEditor::CaseStyle p_case) { code_editor->convert_case(p_case); } void ScriptTextEditor::trim_trailing_whitespace() { code_editor->trim_trailing_whitespace(); } void ScriptTextEditor::insert_final_newline() { code_editor->insert_final_newline(); } void ScriptTextEditor::convert_indent_to_spaces() { code_editor->convert_indent_to_spaces(); } void ScriptTextEditor::convert_indent_to_tabs() { code_editor->convert_indent_to_tabs(); } void ScriptTextEditor::tag_saved_version() { code_editor->get_text_editor()->tag_saved_version(); } void ScriptTextEditor::goto_line(int p_line, bool p_with_error) { code_editor->goto_line(p_line); } void ScriptTextEditor::goto_line_selection(int p_line, int p_begin, int p_end) { code_editor->goto_line_selection(p_line, p_begin, p_end); } void ScriptTextEditor::goto_line_centered(int p_line) { code_editor->goto_line_centered(p_line); } void ScriptTextEditor::set_executing_line(int p_line) { code_editor->set_executing_line(p_line); } void ScriptTextEditor::clear_executing_line() { code_editor->clear_executing_line(); } void ScriptTextEditor::ensure_focus() { code_editor->get_text_editor()->grab_focus(); } String ScriptTextEditor::get_name() { String name; if (script->get_path().find("local://") == -1 && script->get_path().find("::") == -1) { name = script->get_path().get_file(); if (is_unsaved()) { if (script->get_path().empty()) { name = TTR("[unsaved]"); } name += "(*)"; } } else if (script->get_name() != "") { name = script->get_name(); } else { name = script->get_class() + "(" + itos(script->get_instance_id()) + ")"; } return name; } Ref ScriptTextEditor::get_theme_icon() { if (get_parent_control() && get_parent_control()->has_theme_icon(script->get_class(), "EditorIcons")) { return get_parent_control()->get_theme_icon(script->get_class(), "EditorIcons"); } return Ref(); } void ScriptTextEditor::_validate_script() { String errortxt; int line = -1, col; CodeEdit *te = code_editor->get_text_editor(); String text = te->get_text(); List fnc; Set safe_lines; List warnings; if (!script->get_language()->validate(text, line, col, errortxt, script->get_path(), &fnc, &warnings, &safe_lines)) { String error_text = "error(" + itos(line) + "," + itos(col) + "): " + errortxt; code_editor->set_error(error_text); code_editor->set_error_pos(line - 1, col - 1); script_is_valid = false; } else { code_editor->set_error(""); line = -1; if (!script->is_tool()) { script->set_source_code(text); script->update_exports(); te->get_syntax_highlighter()->update_cache(); } functions.clear(); for (List::Element *E = fnc.front(); E; E = E->next()) { functions.push_back(E->get()); } script_is_valid = true; } _update_connected_methods(); int warning_nb = warnings.size(); warnings_panel->clear(); // Add missing connections. if (GLOBAL_GET("debug/gdscript/warnings/enable").booleanize()) { Node *base = get_tree()->get_edited_scene_root(); if (base && missing_connections.size() > 0) { warnings_panel->push_table(1); for (List::Element *E = missing_connections.front(); E; E = E->next()) { Connection connection = E->get(); String base_path = base->get_name(); String source_path = base == connection.signal.get_object() ? base_path : base_path + "/" + base->get_path_to(Object::cast_to(connection.signal.get_object())); String target_path = base == connection.callable.get_object() ? base_path : base_path + "/" + base->get_path_to(Object::cast_to(connection.callable.get_object())); warnings_panel->push_cell(); warnings_panel->push_color(warnings_panel->get_theme_color("warning_color", "Editor")); warnings_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)); warnings_panel->pop(); // Color. warnings_panel->pop(); // Cell. } warnings_panel->pop(); // Table. warning_nb += missing_connections.size(); } } code_editor->set_warning_nb(warning_nb); // Add script warnings. warnings_panel->push_table(3); for (List::Element *E = warnings.front(); E; E = E->next()) { ScriptLanguage::Warning w = E->get(); Dictionary ignore_meta; ignore_meta["line"] = w.start_line; ignore_meta["code"] = w.string_code.to_lower(); warnings_panel->push_cell(); warnings_panel->push_meta(ignore_meta); warnings_panel->push_color( warnings_panel->get_theme_color("accent_color", "Editor").lerp(warnings_panel->get_theme_color("mono_color", "Editor"), 0.5)); warnings_panel->add_text(TTR("[Ignore]")); warnings_panel->pop(); // Color. warnings_panel->pop(); // Meta ignore. warnings_panel->pop(); // Cell. warnings_panel->push_cell(); warnings_panel->push_meta(w.start_line - 1); warnings_panel->push_color(warnings_panel->get_theme_color("warning_color", "Editor")); warnings_panel->add_text(TTR("Line") + " " + itos(w.start_line)); warnings_panel->add_text(" (" + w.string_code + "):"); warnings_panel->pop(); // Color. warnings_panel->pop(); // Meta goto. warnings_panel->pop(); // Cell. warnings_panel->push_cell(); warnings_panel->add_text(w.message); warnings_panel->pop(); // Cell. } warnings_panel->pop(); // Table. line--; bool highlight_safe = EDITOR_DEF("text_editor/highlighting/highlight_type_safe_lines", true); bool last_is_safe = false; for (int i = 0; i < te->get_line_count(); i++) { te->set_line_as_marked(i, line == i); if (highlight_safe) { if (safe_lines.has(i + 1)) { te->set_line_gutter_item_color(i, line_number_gutter, safe_line_number_color); last_is_safe = true; } else if (last_is_safe && (te->is_line_comment(i) || te->get_line(i).strip_edges().empty())) { te->set_line_gutter_item_color(i, line_number_gutter, safe_line_number_color); } else { te->set_line_gutter_item_color(i, line_number_gutter, default_line_number_color); last_is_safe = false; } } else { te->set_line_gutter_item_color(line, 1, default_line_number_color); } } emit_signal("name_changed"); emit_signal("edited_script_changed"); } void ScriptTextEditor::_update_bookmark_list() { bookmarks_menu->clear(); bookmarks_menu->set_size(Size2(1, 1)); bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_bookmark"), BOOKMARK_TOGGLE); bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/remove_all_bookmarks"), BOOKMARK_REMOVE_ALL); bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_next_bookmark"), BOOKMARK_GOTO_NEXT); bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_previous_bookmark"), BOOKMARK_GOTO_PREV); Array bookmark_list = code_editor->get_text_editor()->get_bookmarked_lines(); if (bookmark_list.size() == 0) { return; } bookmarks_menu->add_separator(); for (int i = 0; i < bookmark_list.size(); i++) { // Strip edges to remove spaces or tabs. // Also replace any tabs by spaces, since we can't print tabs in the menu. String line = code_editor->get_text_editor()->get_line(bookmark_list[i]).replace("\t", " ").strip_edges(); // Limit the size of the line if too big. if (line.length() > 50) { line = line.substr(0, 50); } bookmarks_menu->add_item(String::num((int)bookmark_list[i] + 1) + " - `" + line + "`"); bookmarks_menu->set_item_metadata(bookmarks_menu->get_item_count() - 1, bookmark_list[i]); } } void ScriptTextEditor::_bookmark_item_pressed(int p_idx) { if (p_idx < 4) { // Any item before the separator. _edit_option(bookmarks_menu->get_item_id(p_idx)); } else { code_editor->goto_line(bookmarks_menu->get_item_metadata(p_idx)); code_editor->get_text_editor()->call_deferred("center_viewport_to_cursor"); //Need to be deferred, because goto uses call_deferred(). } } static Vector _find_all_node_for_script(Node *p_base, Node *p_current, const Ref