Prechádzať zdrojové kódy

CodeEditor: Make possible to select and copy error text

Danil Alexeev 2 mesiacov pred
rodič
commit
db9b8ff003

+ 61 - 45
editor/code_editor.cpp

@@ -39,9 +39,13 @@
 #include "editor/plugins/script_editor_plugin.h"
 #include "editor/themes/editor_scale.h"
 #include "editor/themes/editor_theme_manager.h"
+#include "scene/gui/check_box.h"
+#include "scene/gui/label.h"
 #include "scene/gui/line_edit.h"
 #include "scene/gui/menu_button.h"
+#include "scene/gui/rich_text_label.h"
 #include "scene/gui/separator.h"
+#include "scene/main/timer.h"
 #include "scene/resources/font.h"
 
 void GotoLinePopup::popup_find_line(CodeTextEditor *p_text_editor) {
@@ -716,10 +720,6 @@ bool FindReplaceBar::is_selection_only() const {
 	return selection_only->is_pressed();
 }
 
-void FindReplaceBar::set_error(const String &p_label) {
-	emit_signal(SNAME("error"), p_label);
-}
-
 void FindReplaceBar::set_text_edit(CodeTextEditor *p_text_editor) {
 	if (p_text_editor == base_text_editor) {
 		return;
@@ -749,8 +749,6 @@ void FindReplaceBar::set_text_edit(CodeTextEditor *p_text_editor) {
 
 void FindReplaceBar::_bind_methods() {
 	ClassDB::bind_method("_search_current", &FindReplaceBar::search_current);
-
-	ADD_SIGNAL(MethodInfo("error"));
 }
 
 FindReplaceBar::FindReplaceBar() {
@@ -1187,7 +1185,6 @@ void CodeTextEditor::set_find_replace_bar(FindReplaceBar *p_bar) {
 
 	find_replace_bar = p_bar;
 	find_replace_bar->set_text_edit(this);
-	find_replace_bar->connect("error", callable_mp(error, &Label::set_text));
 }
 
 void CodeTextEditor::remove_find_replace_bar() {
@@ -1195,7 +1192,6 @@ void CodeTextEditor::remove_find_replace_bar() {
 		return;
 	}
 
-	find_replace_bar->disconnect("error", callable_mp(error, &Label::set_text));
 	find_replace_bar = nullptr;
 }
 
@@ -1516,20 +1512,35 @@ Variant CodeTextEditor::get_navigation_state() {
 }
 
 void CodeTextEditor::set_error(const String &p_error) {
-	// Trim the error message if it is more than 2 lines long.
-	if (p_error.count("\n") >= 2) {
-		Vector<String> splits = p_error.split("\n");
-		String trimmed_error = String("\n").join(splits.slice(0, 2));
-		error->set_text(trimmed_error + "...");
+	error->set_text(p_error);
+
+	_update_error_content_height();
+
+	if (p_error.is_empty()) {
+		error->set_default_cursor_shape(CURSOR_ARROW);
 	} else {
-		error->set_text(p_error);
+		error->set_default_cursor_shape(CURSOR_POINTING_HAND);
+	}
+}
+
+void CodeTextEditor::_update_error_content_height() {
+	float margin_height = 0;
+	const Ref<StyleBox> style = error->get_theme_stylebox(CoreStringName(normal));
+	if (style.is_valid()) {
+		margin_height += style->get_content_margin(SIDE_TOP) + style->get_content_margin(SIDE_BOTTOM);
 	}
 
-	if (!p_error.is_empty()) {
-		error->set_default_cursor_shape(CURSOR_POINTING_HAND);
-	} else {
-		error->set_default_cursor_shape(CURSOR_ARROW);
+	const float content_height = margin_height + error->get_content_height();
+
+	float content_max_height = margin_height;
+	for (int i = 0; i < 3; i++) {
+		if (i >= error->get_line_count()) {
+			break;
+		}
+		content_max_height += error->get_line_height(i);
 	}
+
+	error->set_custom_minimum_size(Size2(0, CLAMP(content_height, 0, content_max_height)));
 }
 
 void CodeTextEditor::set_error_pos(int p_line, int p_column) {
@@ -1559,28 +1570,36 @@ void CodeTextEditor::goto_error() {
 void CodeTextEditor::_update_text_editor_theme() {
 	emit_signal(SNAME("load_theme_settings"));
 
-	error_button->set_button_icon(get_editor_theme_icon(SNAME("StatusError")));
-	warning_button->set_button_icon(get_editor_theme_icon(SNAME("NodeWarning")));
-
-	Ref<Font> status_bar_font = get_theme_font(SNAME("status_source"), EditorStringName(EditorFonts));
-	int status_bar_font_size = get_theme_font_size(SNAME("status_source_size"), EditorStringName(EditorFonts));
-
-	int count = status_bar->get_child_count();
-	for (int i = 0; i < count; i++) {
-		Control *n = Object::cast_to<Control>(status_bar->get_child(i));
-		if (n) {
-			n->add_theme_font_override(SceneStringName(font), status_bar_font);
-			n->add_theme_font_size_override(SceneStringName(font_size), status_bar_font_size);
-		}
-	}
-
+	const Ref<Font> status_bar_font = get_theme_font(SNAME("status_source"), EditorStringName(EditorFonts));
+	const int status_bar_font_size = get_theme_font_size(SNAME("status_source_size"), EditorStringName(EditorFonts));
 	const Color &error_color = get_theme_color(SNAME("error_color"), EditorStringName(Editor));
 	const Color &warning_color = get_theme_color(SNAME("warning_color"), EditorStringName(Editor));
+	const Ref<StyleBox> label_stylebox = get_theme_stylebox(SNAME("normal"), SNAME("Label")); // Empty stylebox.
 
-	error->add_theme_color_override(SceneStringName(font_color), error_color);
+	error->begin_bulk_theme_override();
+	error->add_theme_font_override(SNAME("normal_font"), status_bar_font);
+	error->add_theme_font_size_override(SNAME("normal_font_size"), status_bar_font_size);
+	error->add_theme_color_override(SNAME("default_color"), error_color);
+	error->add_theme_style_override(SNAME("normal"), label_stylebox);
+	error->end_bulk_theme_override();
+
+	error_button->set_button_icon(get_editor_theme_icon(SNAME("StatusError")));
 	error_button->add_theme_color_override(SceneStringName(font_color), error_color);
+
+	warning_button->set_button_icon(get_editor_theme_icon(SNAME("NodeWarning")));
 	warning_button->add_theme_color_override(SceneStringName(font_color), warning_color);
 
+	const int child_count = status_bar->get_child_count();
+	for (int i = 0; i < child_count; i++) {
+		Control *child = Object::cast_to<Control>(status_bar->get_child(i));
+		if (child) {
+			child->begin_bulk_theme_override();
+			child->add_theme_font_override(SceneStringName(font), status_bar_font);
+			child->add_theme_font_size_override(SceneStringName(font_size), status_bar_font_size);
+			child->end_bulk_theme_override();
+		}
+	}
+
 	_update_font_ligatures();
 }
 
@@ -1909,19 +1928,16 @@ CodeTextEditor::CodeTextEditor() {
 	toggle_files_button->hide();
 
 	// Error
-	ScrollContainer *scroll = memnew(ScrollContainer);
-	scroll->set_h_size_flags(SIZE_EXPAND_FILL);
-	scroll->set_v_size_flags(SIZE_EXPAND_FILL);
-	scroll->set_vertical_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED);
-	status_bar->add_child(scroll);
-
-	error = memnew(Label);
-	error->set_focus_mode(FOCUS_ACCESSIBILITY);
+	error = memnew(RichTextLabel);
+	error->set_use_bbcode(true);
+	error->set_selection_enabled(true);
+	error->set_context_menu_enabled(true);
 	error->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
-	error->set_v_size_flags(SIZE_EXPAND | SIZE_SHRINK_CENTER);
-	error->set_mouse_filter(MOUSE_FILTER_STOP);
-	scroll->add_child(error);
+	error->set_h_size_flags(SIZE_EXPAND_FILL);
+	error->set_v_size_flags(SIZE_SHRINK_CENTER);
 	error->connect(SceneStringName(gui_input), callable_mp(this, &CodeTextEditor::_error_pressed));
+	error->connect(SceneStringName(resized), callable_mp(this, &CodeTextEditor::_update_error_content_height));
+	status_bar->add_child(error);
 
 	// Errors
 	error_button = memnew(Button);

+ 9 - 8
editor/code_editor.h

@@ -31,17 +31,17 @@
 #pragma once
 
 #include "scene/gui/box_container.h"
-#include "scene/gui/button.h"
-#include "scene/gui/check_box.h"
 #include "scene/gui/code_edit.h"
 #include "scene/gui/dialogs.h"
-#include "scene/gui/label.h"
-#include "scene/gui/popup.h"
-#include "scene/main/timer.h"
 
-class MenuButton;
+class Button;
+class CheckBox;
 class CodeTextEditor;
+class Label;
 class LineEdit;
+class MenuButton;
+class RichTextLabel;
+class Timer;
 
 class GotoLinePopup : public PopupPanel {
 	GDCLASS(GotoLinePopup, PopupPanel);
@@ -138,7 +138,6 @@ public:
 	bool is_case_sensitive() const;
 	bool is_whole_words() const;
 	bool is_selection_only() const;
-	void set_error(const String &p_label);
 
 	void set_text_edit(CodeTextEditor *p_text_editor);
 
@@ -183,7 +182,7 @@ class CodeTextEditor : public VBoxContainer {
 
 	float zoom_factor = 1.0f;
 
-	Label *error = nullptr;
+	RichTextLabel *error = nullptr;
 	int error_line;
 	int error_column;
 
@@ -211,6 +210,8 @@ class CodeTextEditor : public VBoxContainer {
 	void _zoom_out();
 	void _zoom_to(float p_zoom_factor);
 
+	void _update_error_content_height();
+
 	void _error_button_pressed();
 	void _warning_button_pressed();
 	void _set_show_errors_panel(bool p_show);

+ 37 - 10
editor/debugger/script_editor_debugger.cpp

@@ -643,6 +643,7 @@ void ScriptEditorDebugger::_msg_error(uint64_t p_thread_id, const Array &p_data)
 	// item with the original error condition.
 	error_title += oe.error_descr.is_empty() ? oe.error : oe.error_descr;
 	error->set_text(1, error_title);
+	error->set_autowrap_mode(1, TextServer::AUTOWRAP_WORD_SMART);
 	tooltip += " " + error_title + "\n";
 
 	// Find the language of the error's source file.
@@ -980,16 +981,20 @@ void ScriptEditorDebugger::_init_parse_message_handlers() {
 void ScriptEditorDebugger::_set_reason_text(const String &p_reason, MessageType p_type) {
 	switch (p_type) {
 		case MESSAGE_ERROR:
-			reason->add_theme_color_override(SceneStringName(font_color), get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
+			reason->add_theme_color_override(SNAME("default_color"), get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
 			break;
 		case MESSAGE_WARNING:
-			reason->add_theme_color_override(SceneStringName(font_color), get_theme_color(SNAME("warning_color"), EditorStringName(Editor)));
+			reason->add_theme_color_override(SNAME("default_color"), get_theme_color(SNAME("warning_color"), EditorStringName(Editor)));
 			break;
 		default:
-			reason->add_theme_color_override(SceneStringName(font_color), get_theme_color(SNAME("success_color"), EditorStringName(Editor)));
+			reason->add_theme_color_override(SNAME("default_color"), get_theme_color(SNAME("success_color"), EditorStringName(Editor)));
+			break;
 	}
+
 	reason->set_text(p_reason);
 
+	_update_reason_content_height();
+
 	const PackedInt32Array boundaries = TS->string_get_word_breaks(p_reason, "", 80);
 	PackedStringArray lines;
 	for (int i = 0; i < boundaries.size(); i += 2) {
@@ -1001,6 +1006,26 @@ void ScriptEditorDebugger::_set_reason_text(const String &p_reason, MessageType
 	reason->set_tooltip_text(String("\n").join(lines));
 }
 
+void ScriptEditorDebugger::_update_reason_content_height() {
+	float margin_height = 0;
+	const Ref<StyleBox> style = reason->get_theme_stylebox(CoreStringName(normal));
+	if (style.is_valid()) {
+		margin_height += style->get_content_margin(SIDE_TOP) + style->get_content_margin(SIDE_BOTTOM);
+	}
+
+	const float content_height = margin_height + reason->get_content_height();
+
+	float content_max_height = margin_height;
+	for (int i = 0; i < 3; i++) {
+		if (i >= reason->get_line_count()) {
+			break;
+		}
+		content_max_height += reason->get_line_height(i);
+	}
+
+	reason->set_custom_minimum_size(Size2(0, CLAMP(content_height, 0, content_max_height)));
+}
+
 void ScriptEditorDebugger::_notification(int p_what) {
 	switch (p_what) {
 		case NOTIFICATION_ENTER_TREE: {
@@ -1031,7 +1056,8 @@ void ScriptEditorDebugger::_notification(int p_what) {
 			vmem_export->set_button_icon(get_editor_theme_icon(SNAME("Save")));
 			search->set_right_icon(get_editor_theme_icon(SNAME("Search")));
 
-			reason->add_theme_color_override(SceneStringName(font_color), get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
+			reason->add_theme_color_override(SNAME("default_color"), get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
+			reason->add_theme_style_override(SNAME("normal"), get_theme_stylebox(SNAME("normal"), SNAME("Label"))); // Empty stylebox.
 
 			TreeItem *error_root = error_tree->get_root();
 			if (error_root) {
@@ -1245,6 +1271,7 @@ void ScriptEditorDebugger::stop() {
 		peer.unref();
 		reason->set_text("");
 		reason->set_tooltip_text("");
+		reason->set_custom_minimum_size(Size2(0, 0));
 	}
 
 	node_path_cache.clear();
@@ -1966,14 +1993,14 @@ ScriptEditorDebugger::ScriptEditorDebugger() {
 		HBoxContainer *hbc = memnew(HBoxContainer);
 		vbc->add_child(hbc);
 
-		reason = memnew(Label);
+		reason = memnew(RichTextLabel);
 		reason->set_focus_mode(FOCUS_ACCESSIBILITY);
-		reason->set_text("");
-		hbc->add_child(reason);
+		reason->set_selection_enabled(true);
+		reason->set_context_menu_enabled(true);
 		reason->set_h_size_flags(SIZE_EXPAND_FILL);
-		reason->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART);
-		reason->set_max_lines_visible(3);
-		reason->set_mouse_filter(Control::MOUSE_FILTER_PASS);
+		reason->set_v_size_flags(SIZE_SHRINK_CENTER);
+		reason->connect(SceneStringName(resized), callable_mp(this, &ScriptEditorDebugger::_update_reason_content_height));
+		hbc->add_child(reason);
 
 		hbc->add_child(memnew(VSeparator));
 

+ 2 - 1
editor/debugger/script_editor_debugger.h

@@ -118,7 +118,7 @@ private:
 
 	TabContainer *tabs = nullptr;
 
-	Label *reason = nullptr;
+	RichTextLabel *reason = nullptr;
 
 	Button *skip_breakpoints = nullptr;
 	Button *ignore_error_breaks = nullptr;
@@ -231,6 +231,7 @@ private:
 
 	void _parse_message(const String &p_msg, uint64_t p_thread_id, const Array &p_data);
 	void _set_reason_text(const String &p_reason, MessageType p_type);
+	void _update_reason_content_height();
 	void _update_buttons_state();
 	void _remote_object_selected(ObjectID p_object);
 	void _remote_objects_edited(const String &p_prop, const TypedDictionary<uint64_t, Variant> &p_values, const String &p_field);

+ 5 - 3
editor/plugins/script_text_editor.cpp

@@ -819,10 +819,12 @@ void ScriptTextEditor::_validate_script() {
 		}
 
 		if (errors.size() > 0) {
-			// TRANSLATORS: Script error pointing to a line and column number.
-			String error_text = vformat(TTR("Error at (%d, %d):"), errors.front()->get().line, errors.front()->get().column) + " " + errors.front()->get().message;
+			const int line = errors.front()->get().line;
+			const int column = errors.front()->get().column;
+			const String message = errors.front()->get().message.replace("[", "[lb]");
+			const String error_text = vformat(TTR("Error at ([hint=Line %d, column %d]%d, %d[/hint]):"), line, column, line, column) + " " + message;
 			code_editor->set_error(error_text);
-			code_editor->set_error_pos(errors.front()->get().line - 1, errors.front()->get().column - 1);
+			code_editor->set_error_pos(line - 1, column - 1);
 		}
 		script_is_valid = false;
 	} else {

+ 1 - 1
editor/plugins/text_editor.cpp

@@ -200,7 +200,7 @@ void TextEditor::_validate_script() {
 		code_editor->set_error("");
 
 		if (json_file->parse(te->get_text(), true) != OK) {
-			code_editor->set_error(json_file->get_error_message());
+			code_editor->set_error(json_file->get_error_message().replace("[", "[lb]"));
 			code_editor->set_error_pos(json_file->get_error_line(), 0);
 			te->set_line_background_color(code_editor->get_error_pos().x, EDITOR_GET("text_editor/theme/highlighting/mark_color"));
 		}

+ 35 - 13
editor/plugins/text_shader_editor.cpp

@@ -515,28 +515,35 @@ void ShaderTextEditor::_validate_script() {
 	set_error_count(0);
 
 	if (last_compile_result != OK) {
-		//preprocessor error
+		// Preprocessor error.
 		ERR_FAIL_COND(err_positions.is_empty());
 
-		String err_text = error_pp;
-		int err_line = err_positions.front()->get().line;
+		String err_text;
+		const int err_line = err_positions.front()->get().line;
 		if (err_positions.size() == 1) {
-			// Error in main file
-			err_text = "error(" + itos(err_line) + "): " + err_text;
+			// Error in the main file.
+			const String message = error_pp.replace("[", "[lb]");
+
+			err_text = vformat(TTR("Error at line %d:"), err_line) + " " + message;
 		} else {
-			err_text = "error(" + itos(err_line) + ") in include " + err_positions.back()->get().file.get_file() + ":" + itos(err_positions.back()->get().line) + ": " + err_text;
+			// Error in an included file.
+			const String inc_file = err_positions.back()->get().file.get_file();
+			const int inc_line = err_positions.back()->get().line;
+			const String message = error_pp.replace("[", "[lb]");
+
+			err_text = vformat(TTR("Error at line %d in include %s:%d:"), err_line, inc_file, inc_line) + " " + message;
 			set_error_count(err_positions.size() - 1);
 		}
 
 		set_error(err_text);
 		set_error_pos(err_line - 1, 0);
+
 		for (int i = 0; i < get_text_editor()->get_line_count(); i++) {
 			get_text_editor()->set_line_background_color(i, Color(0, 0, 0, 0));
 		}
 		get_text_editor()->set_line_background_color(err_line - 1, marked_line_color);
 
 		set_warning_count(0);
-
 	} else {
 		ShaderLanguage sl;
 
@@ -579,21 +586,33 @@ void ShaderTextEditor::_validate_script() {
 		last_compile_result = sl.compile(code, comp_info);
 
 		if (last_compile_result != OK) {
+			Vector<ShaderLanguage::FilePosition> include_positions = sl.get_include_positions();
+
 			String err_text;
 			int err_line;
-			Vector<ShaderLanguage::FilePosition> include_positions = sl.get_include_positions();
 			if (include_positions.size() > 1) {
-				//error is in an include
+				// Error in an included file.
 				err_line = include_positions[0].line;
-				err_text = "error(" + itos(err_line) + ") in include " + include_positions[include_positions.size() - 1].file + ":" + itos(include_positions[include_positions.size() - 1].line) + ": " + sl.get_error_text();
+
+				const String inc_file = include_positions[include_positions.size() - 1].file;
+				const int inc_line = include_positions[include_positions.size() - 1].line;
+				const String message = sl.get_error_text().replace("[", "[lb]");
+
+				err_text = vformat(TTR("Error at line %d in include %s:%d:"), err_line, inc_file, inc_line) + " " + message;
 				set_error_count(include_positions.size() - 1);
 			} else {
+				// Error in the main file.
 				err_line = sl.get_error_line();
-				err_text = "error(" + itos(err_line) + "): " + sl.get_error_text();
+
+				const String message = sl.get_error_text().replace("[", "[lb]");
+
+				err_text = vformat(TTR("Error at line %d:"), err_line) + " " + message;
 				set_error_count(0);
 			}
+
 			set_error(err_text);
 			set_error_pos(err_line - 1, 0);
+
 			get_text_editor()->set_line_background_color(err_line - 1, marked_line_color);
 		} else {
 			set_error("");
@@ -624,9 +643,12 @@ void ShaderTextEditor::_update_warning_panel() {
 	for (const ShaderWarning &w : warnings) {
 		if (warning_count == 0) {
 			if (saved_treat_warning_as_errors) {
-				String error_text = "error(" + itos(w.get_line()) + "): " + w.get_message() + " " + TTR("Warnings should be fixed to prevent errors.");
-				set_error_pos(w.get_line() - 1, 0);
+				const String message = (w.get_message() + " " + TTR("Warnings should be fixed to prevent errors.")).replace("[", "[lb]");
+				const String error_text = vformat(TTR("Error at line %d:"), w.get_line()) + " " + message;
+
 				set_error(error_text);
+				set_error_pos(w.get_line() - 1, 0);
+
 				get_text_editor()->set_line_background_color(w.get_line() - 1, marked_line_color);
 			}
 		}