diff --git a/core/debugger/remote_debugger.cpp b/core/debugger/remote_debugger.cpp index 5ee4e2c3687..508a71ece9f 100644 --- a/core/debugger/remote_debugger.cpp +++ b/core/debugger/remote_debugger.cpp @@ -208,7 +208,7 @@ void RemoteDebugger::_err_handler(void *p_this, const char *p_func, const char * rd->script_debugger->send_error(String::utf8(p_func), String::utf8(p_file), p_line, String::utf8(p_err), String::utf8(p_descr), p_editor_notify, p_type, si); } -void RemoteDebugger::_print_handler(void *p_this, const String &p_string, bool p_error) { +void RemoteDebugger::_print_handler(void *p_this, const String &p_string, bool p_error, bool p_rich) { RemoteDebugger *rd = static_cast(p_this); if (rd->flushing && Thread::get_caller_id() == rd->flush_thread) { // Can't handle recursive prints during flush. @@ -237,7 +237,13 @@ void RemoteDebugger::_print_handler(void *p_this, const String &p_string, bool p OutputString output_string; output_string.message = s; - output_string.type = p_error ? MESSAGE_TYPE_ERROR : MESSAGE_TYPE_LOG; + if (p_error) { + output_string.type = MESSAGE_TYPE_ERROR; + } else if (p_rich) { + output_string.type = MESSAGE_TYPE_LOG_RICH; + } else { + output_string.type = MESSAGE_TYPE_LOG; + } rd->output_strings.push_back(output_string); if (overflowed) { diff --git a/core/debugger/remote_debugger.h b/core/debugger/remote_debugger.h index fdb312ae680..fe4bbe86ea8 100644 --- a/core/debugger/remote_debugger.h +++ b/core/debugger/remote_debugger.h @@ -44,6 +44,7 @@ public: enum MessageType { MESSAGE_TYPE_LOG, MESSAGE_TYPE_ERROR, + MESSAGE_TYPE_LOG_RICH, }; private: @@ -82,7 +83,7 @@ private: Thread::ID flush_thread = 0; PrintHandlerList phl; - static void _print_handler(void *p_this, const String &p_string, bool p_error); + static void _print_handler(void *p_this, const String &p_string, bool p_error, bool p_rich); ErrorHandlerList eh; static void _err_handler(void *p_this, const char *p_func, const char *p_file, int p_line, const char *p_err, const char *p_descr, bool p_editor_notify, ErrorHandlerType p_type); diff --git a/core/os/os.cpp b/core/os/os.cpp index 93477f4288f..b9daf6fa537 100644 --- a/core/os/os.cpp +++ b/core/os/os.cpp @@ -100,6 +100,21 @@ void OS::print(const char *p_format, ...) { va_end(argp); } +void OS::print_rich(const char *p_format, ...) { + if (!_stdout_enabled) { + return; + } + + va_list argp; + va_start(argp, p_format); + + if (_logger) { + _logger->logv(p_format, argp, false); + } + + va_end(argp); +} + void OS::printerr(const char *p_format, ...) { if (!_stderr_enabled) { return; diff --git a/core/os/os.h b/core/os/os.h index c6ea9d869a7..af6c38cbe03 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -120,6 +120,7 @@ public: void print_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify = false, Logger::ErrorType p_type = Logger::ERR_ERROR); void print(const char *p_format, ...) _PRINTF_FORMAT_ATTRIBUTE_2_3; + void print_rich(const char *p_format, ...) _PRINTF_FORMAT_ATTRIBUTE_2_3; void printerr(const char *p_format, ...) _PRINTF_FORMAT_ATTRIBUTE_2_3; virtual String get_stdin_string(bool p_block = true) = 0; diff --git a/core/string/print_string.cpp b/core/string/print_string.cpp index 919c9e08e3c..f58486e0a52 100644 --- a/core/string/print_string.cpp +++ b/core/string/print_string.cpp @@ -79,7 +79,98 @@ void __print_line(String p_string) { _global_lock(); PrintHandlerList *l = print_handler_list; while (l) { - l->printfunc(l->userdata, p_string, false); + l->printfunc(l->userdata, p_string, false, false); + l = l->next; + } + + _global_unlock(); +} + +void __print_line_rich(String p_string) { + if (!_print_line_enabled) { + return; + } + + // Convert a subset of BBCode tags to ANSI escape codes for correct display in the terminal. + // Support of those ANSI escape codes varies across terminal emulators, + // especially for italic and strikethrough. + String p_string_ansi = p_string; + + p_string_ansi = p_string_ansi.replace("[b]", "\u001b[1m"); + p_string_ansi = p_string_ansi.replace("[/b]", "\u001b[22m"); + p_string_ansi = p_string_ansi.replace("[i]", "\u001b[3m"); + p_string_ansi = p_string_ansi.replace("[/i]", "\u001b[23m"); + p_string_ansi = p_string_ansi.replace("[u]", "\u001b[4m"); + p_string_ansi = p_string_ansi.replace("[/u]", "\u001b[24m"); + p_string_ansi = p_string_ansi.replace("[s]", "\u001b[9m"); + p_string_ansi = p_string_ansi.replace("[/s]", "\u001b[29m"); + + p_string_ansi = p_string_ansi.replace("[indent]", " "); + p_string_ansi = p_string_ansi.replace("[/indent]", ""); + p_string_ansi = p_string_ansi.replace("[code]", "\u001b[2m"); + p_string_ansi = p_string_ansi.replace("[/code]", "\u001b[22m"); + p_string_ansi = p_string_ansi.replace("[url]", ""); + p_string_ansi = p_string_ansi.replace("[/url]", ""); + p_string_ansi = p_string_ansi.replace("[center]", "\n\t\t\t"); + p_string_ansi = p_string_ansi.replace("[/center]", ""); + p_string_ansi = p_string_ansi.replace("[right]", "\n\t\t\t\t\t\t"); + p_string_ansi = p_string_ansi.replace("[/right]", ""); + + if (p_string_ansi.contains("[color")) { + p_string_ansi = p_string_ansi.replace("[color=black]", "\u001b[30m"); + p_string_ansi = p_string_ansi.replace("[color=red]", "\u001b[91m"); + p_string_ansi = p_string_ansi.replace("[color=green]", "\u001b[92m"); + p_string_ansi = p_string_ansi.replace("[color=lime]", "\u001b[92m"); + p_string_ansi = p_string_ansi.replace("[color=yellow]", "\u001b[93m"); + p_string_ansi = p_string_ansi.replace("[color=blue]", "\u001b[94m"); + p_string_ansi = p_string_ansi.replace("[color=magenta]", "\u001b[95m"); + p_string_ansi = p_string_ansi.replace("[color=pink]", "\u001b[38;5;218m"); + p_string_ansi = p_string_ansi.replace("[color=purple]", "\u001b[38;5;98m"); + p_string_ansi = p_string_ansi.replace("[color=cyan]", "\u001b[96m"); + p_string_ansi = p_string_ansi.replace("[color=white]", "\u001b[97m"); + p_string_ansi = p_string_ansi.replace("[color=orange]", "\u001b[38;5;208m"); + p_string_ansi = p_string_ansi.replace("[color=gray]", "\u001b[90m"); + p_string_ansi = p_string_ansi.replace("[/color]", "\u001b[39m"); + } + if (p_string_ansi.contains("[bgcolor")) { + p_string_ansi = p_string_ansi.replace("[bgcolor=black]", "\u001b[40m"); + p_string_ansi = p_string_ansi.replace("[bgcolor=red]", "\u001b[101m"); + p_string_ansi = p_string_ansi.replace("[bgcolor=green]", "\u001b[102m"); + p_string_ansi = p_string_ansi.replace("[bgcolor=lime]", "\u001b[102m"); + p_string_ansi = p_string_ansi.replace("[bgcolor=yellow]", "\u001b[103m"); + p_string_ansi = p_string_ansi.replace("[bgcolor=blue]", "\u001b[104m"); + p_string_ansi = p_string_ansi.replace("[bgcolor=magenta]", "\u001b[105m"); + p_string_ansi = p_string_ansi.replace("[bgcolor=pink]", "\u001b[48;5;218m"); + p_string_ansi = p_string_ansi.replace("[bgcolor=purple]", "\u001b[48;5;98m"); + p_string_ansi = p_string_ansi.replace("[bgcolor=cyan]", "\u001b[106m"); + p_string_ansi = p_string_ansi.replace("[bgcolor=white]", "\u001b[107m"); + p_string_ansi = p_string_ansi.replace("[bgcolor=orange]", "\u001b[48;5;208m"); + p_string_ansi = p_string_ansi.replace("[bgcolor=gray]", "\u001b[100m"); + p_string_ansi = p_string_ansi.replace("[/bgcolor]", "\u001b[49m"); + } + if (p_string_ansi.contains("[fgcolor")) { + p_string_ansi = p_string_ansi.replace("[fgcolor=black]", "\u001b[30;40m"); + p_string_ansi = p_string_ansi.replace("[fgcolor=red]", "\u001b[91;101m"); + p_string_ansi = p_string_ansi.replace("[fgcolor=green]", "\u001b[92;102m"); + p_string_ansi = p_string_ansi.replace("[fgcolor=lime]", "\u001b[92;102m"); + p_string_ansi = p_string_ansi.replace("[fgcolor=yellow]", "\u001b[93;103m"); + p_string_ansi = p_string_ansi.replace("[fgcolor=blue]", "\u001b[94;104m"); + p_string_ansi = p_string_ansi.replace("[fgcolor=magenta]", "\u001b[95;105m"); + p_string_ansi = p_string_ansi.replace("[fgcolor=pink]", "\u001b[38;5;218;48;5;218m"); + p_string_ansi = p_string_ansi.replace("[fgcolor=purple]", "\u001b[38;5;98;48;5;98m"); + p_string_ansi = p_string_ansi.replace("[fgcolor=cyan]", "\u001b[96;106m"); + p_string_ansi = p_string_ansi.replace("[fgcolor=white]", "\u001b[97;107m"); + p_string_ansi = p_string_ansi.replace("[fgcolor=orange]", "\u001b[38;5;208;48;5;208m"); + p_string_ansi = p_string_ansi.replace("[fgcolor=gray]", "\u001b[90;100m"); + p_string_ansi = p_string_ansi.replace("[/fgcolor]", "\u001b[39;49m"); + } + + OS::get_singleton()->print_rich("%s\n", p_string_ansi.utf8().get_data()); + + _global_lock(); + PrintHandlerList *l = print_handler_list; + while (l) { + l->printfunc(l->userdata, p_string, false, true); l = l->next; } @@ -96,7 +187,7 @@ void print_error(String p_string) { _global_lock(); PrintHandlerList *l = print_handler_list; while (l) { - l->printfunc(l->userdata, p_string, true); + l->printfunc(l->userdata, p_string, true, false); l = l->next; } diff --git a/core/string/print_string.h b/core/string/print_string.h index f7d0f250308..823e2c29e8f 100644 --- a/core/string/print_string.h +++ b/core/string/print_string.h @@ -35,7 +35,7 @@ extern void (*_print_func)(String); -typedef void (*PrintHandlerFunc)(void *, const String &p_string, bool p_error); +typedef void (*PrintHandlerFunc)(void *, const String &p_string, bool p_error, bool p_rich); struct PrintHandlerList { PrintHandlerFunc printfunc = nullptr; @@ -59,6 +59,7 @@ void remove_print_handler(const PrintHandlerList *p_handler); extern bool _print_line_enabled; extern bool _print_error_enabled; extern void __print_line(String p_string); +extern void __print_line_rich(String p_string); extern void print_error(String p_string); extern void print_verbose(String p_string); @@ -66,9 +67,18 @@ inline void print_line(Variant v) { __print_line(stringify_variants(v)); } +inline void print_line_rich(Variant v) { + __print_line_rich(stringify_variants(v)); +} + template void print_line(Variant p_var, Args... p_args) { __print_line(stringify_variants(p_var, p_args...)); } +template +void print_line_rich(Variant p_var, Args... p_args) { + __print_line_rich(stringify_variants(p_var, p_args...)); +} + #endif // PRINT_STRING_H diff --git a/core/variant/variant_utility.cpp b/core/variant/variant_utility.cpp index d1b5e285d25..2bca5f82845 100644 --- a/core/variant/variant_utility.cpp +++ b/core/variant/variant_utility.cpp @@ -560,6 +560,22 @@ struct VariantUtilityFunctions { r_error.error = Callable::CallError::CALL_OK; } + static inline void print_rich(const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { + String s; + for (int i = 0; i < p_arg_count; i++) { + String os = p_args[i]->operator String(); + + if (i == 0) { + s = os; + } else { + s += os; + } + } + + print_line_rich(s); + r_error.error = Callable::CallError::CALL_OK; + } + static inline void print_verbose(const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { if (OS::get_singleton()->is_stdout_verbose()) { String s; @@ -1306,6 +1322,7 @@ void Variant::_register_variant_utility_functions() { FUNCBINDVARARGS(str, sarray(), Variant::UTILITY_FUNC_TYPE_GENERAL); FUNCBINDR(error_string, sarray("error"), Variant::UTILITY_FUNC_TYPE_GENERAL); FUNCBINDVARARGV(print, sarray(), Variant::UTILITY_FUNC_TYPE_GENERAL); + FUNCBINDVARARGV(print_rich, sarray(), Variant::UTILITY_FUNC_TYPE_GENERAL); FUNCBINDVARARGV(printerr, sarray(), Variant::UTILITY_FUNC_TYPE_GENERAL); FUNCBINDVARARGV(printt, sarray(), Variant::UTILITY_FUNC_TYPE_GENERAL); FUNCBINDVARARGV(prints, sarray(), Variant::UTILITY_FUNC_TYPE_GENERAL); diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml index 1a0253a28b6..19432213090 100644 --- a/doc/classes/@GlobalScope.xml +++ b/doc/classes/@GlobalScope.xml @@ -657,6 +657,16 @@ [b]Note:[/b] Consider using [method push_error] and [method push_warning] to print error and warning messages instead of [method print]. This distinguishes them from print messages used for debugging purposes, while also displaying a stack trace when an error or warning is printed. + + + Converts one or more arguments of any type to string in the best way possible and prints them to the console. The following BBCode tags are supported: b, i, u, s, indent, code, url, center, right, color, bgcolor, fgcolor. Color tags only support named colors such as [code]red[/code], [i]not[/i] hexadecimal color codes. Unsupported tags will be left as-is in standard output. + When printing to standard output, the supported subset of BBCode is converted to ANSI escape codes for the terminal emulator to display. Displaying ANSI escape codes is currently only supported on Linux and macOS. Support for ANSI escape codes may vary across terminal emulators, especially for italic and strikethrough. + [codeblock] + print_rich("[code][b]Hello world![/b][/code]") # Prints out: [b]Hello world![/b] + [/codeblock] + [b]Note:[/b] Consider using [method push_error] and [method push_warning] to print error and warning messages instead of [method print] or [method print_rich]. This distinguishes them from print messages used for debugging purposes, while also displaying a stack trace when an error or warning is printed. + + If verbose mode is enabled ([method OS.is_stdout_verbose] returning [code]true[/code]), converts one or more arguments of any type to string in the best way possible and prints them to the console. diff --git a/editor/editor_log.cpp b/editor/editor_log.cpp index dbe44aee1b8..f26f47dbc7c 100644 --- a/editor/editor_log.cpp +++ b/editor/editor_log.cpp @@ -181,7 +181,7 @@ void EditorLog::clear() { } void EditorLog::_process_message(const String &p_msg, MessageType p_type) { - if (messages.size() > 0 && messages[messages.size() - 1].text == p_msg) { + if (messages.size() > 0 && messages[messages.size() - 1].text == p_msg && messages[messages.size() - 1].type == p_type) { // If previous message is the same as the new one, increase previous count rather than adding another // instance to the messages list. LogMessage &previous = messages.write[messages.size() - 1]; @@ -258,6 +258,8 @@ void EditorLog::_add_log_line(LogMessage &p_message, bool p_replace_previous) { switch (p_message.type) { case MSG_TYPE_STD: { } break; + case MSG_TYPE_STD_RICH: { + } break; case MSG_TYPE_ERROR: { log->push_color(get_theme_color(SNAME("error_color"), SNAME("Editor"))); Ref icon = get_theme_icon(SNAME("Error"), SNAME("EditorIcons")); @@ -285,11 +287,15 @@ void EditorLog::_add_log_line(LogMessage &p_message, bool p_replace_previous) { log->pop(); } - log->add_text(p_message.text); + if (p_message.type == MSG_TYPE_STD_RICH) { + log->append_text(p_message.text); + } else { + log->add_text(p_message.text); + } // Need to use pop() to exit out of the RichTextLabels current "push" stack. - // We only "push" in the above switch when message type != STD, so only pop when that is the case. - if (p_message.type != MSG_TYPE_STD) { + // We only "push" in the above switch when message type != STD and RICH, so only pop when that is the case. + if (p_message.type != MSG_TYPE_STD && p_message.type != MSG_TYPE_STD_RICH) { log->pop(); } @@ -342,6 +348,7 @@ EditorLog::EditorLog() { // Log - Rich Text Label. log = memnew(RichTextLabel); + log->set_use_bbcode(true); log->set_scroll_follow(true); log->set_selection_enabled(true); log->set_focus_mode(FOCUS_CLICK); @@ -418,6 +425,7 @@ EditorLog::EditorLog() { std_filter->initialize_button(TTR("Toggle visibility of standard output messages."), callable_mp(this, &EditorLog::_set_filter_active)); vb_right->add_child(std_filter->toggle_button); type_filter_map.insert(MSG_TYPE_STD, std_filter); + type_filter_map.insert(MSG_TYPE_STD_RICH, std_filter); LogFilter *error_filter = memnew(LogFilter(MSG_TYPE_ERROR)); error_filter->initialize_button(TTR("Toggle visibility of errors."), callable_mp(this, &EditorLog::_set_filter_active)); @@ -451,6 +459,10 @@ void EditorLog::deinit() { EditorLog::~EditorLog() { for (const KeyValue &E : type_filter_map) { - memdelete(E.value); + // MSG_TYPE_STD_RICH is connected to the std_filter button, so we do this + // to avoid it from being deleted twice, causing a crash on closing. + if (E.key != MSG_TYPE_STD_RICH) { + memdelete(E.value); + } } } diff --git a/editor/editor_log.h b/editor/editor_log.h index de0368501c1..653fba9524b 100644 --- a/editor/editor_log.h +++ b/editor/editor_log.h @@ -48,6 +48,7 @@ public: enum MessageType { MSG_TYPE_STD, MSG_TYPE_ERROR, + MSG_TYPE_STD_RICH, MSG_TYPE_WARNING, MSG_TYPE_EDITOR, }; diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index b4b82b1edfa..b196cadcb1f 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -5829,9 +5829,15 @@ static Node *_resource_get_edited_scene() { return EditorNode::get_singleton()->get_edited_scene(); } -void EditorNode::_print_handler(void *p_this, const String &p_string, bool p_error) { +void EditorNode::_print_handler(void *p_this, const String &p_string, bool p_error, bool p_rich) { EditorNode *en = static_cast(p_this); - en->log->add_message(p_string, p_error ? EditorLog::MSG_TYPE_ERROR : EditorLog::MSG_TYPE_STD); + if (p_error) { + en->log->add_message(p_string, EditorLog::MSG_TYPE_ERROR); + } else if (p_rich) { + en->log->add_message(p_string, EditorLog::MSG_TYPE_STD_RICH); + } else { + en->log->add_message(p_string, EditorLog::MSG_TYPE_STD); + } } static void _execute_thread(void *p_ud) { diff --git a/editor/editor_node.h b/editor/editor_node.h index 89f80baeb9c..c327a73ce93 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -505,7 +505,7 @@ private: static void _load_error_notify(void *p_ud, const String &p_text); static void _file_access_close_error_notify(const String &p_str); - static void _print_handler(void *p_this, const String &p_string, bool p_error); + static void _print_handler(void *p_this, const String &p_string, bool p_error, bool p_rich); static void _resource_saved(Ref p_resource, const String &p_path); static void _resource_loaded(Ref p_resource, const String &p_path); diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp index de5cd10e7c9..ff4832bde05 100644 --- a/modules/gdscript/tests/gdscript_test_runner.cpp +++ b/modules/gdscript/tests/gdscript_test_runner.cpp @@ -363,7 +363,7 @@ void GDScriptTest::disable_stdout() { OS::get_singleton()->set_stderr_enabled(false); } -void GDScriptTest::print_handler(void *p_this, const String &p_message, bool p_error) { +void GDScriptTest::print_handler(void *p_this, const String &p_message, bool p_error, bool p_rich) { TestResult *result = (TestResult *)p_this; result->output += p_message + "\n"; } diff --git a/modules/gdscript/tests/gdscript_test_runner.h b/modules/gdscript/tests/gdscript_test_runner.h index d6c6419e217..ee21afd9c9a 100644 --- a/modules/gdscript/tests/gdscript_test_runner.h +++ b/modules/gdscript/tests/gdscript_test_runner.h @@ -86,7 +86,7 @@ private: TestResult execute_test_code(bool p_is_generating); public: - static void print_handler(void *p_this, const String &p_message, bool p_error); + static void print_handler(void *p_this, const String &p_message, bool p_error, bool p_rich); static void error_handler(void *p_this, const char *p_function, const char *p_file, int p_line, const char *p_error, const char *p_explanation, bool p_editor_notify, ErrorHandlerType p_type); TestResult run_test(); bool generate_output(); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs index 74aa38386f4..bb076a96338 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs @@ -238,6 +238,27 @@ namespace Godot godot_icall_GD_print(GetPrintParams(what)); } + /// + /// Converts one or more arguments of any type to string in the best way possible and prints them to the console. The following BBCode tags are supported: b, i, u, s, indent, code, url, center, right, color, bgcolor, fgcolor. Color tags only support named colors such as [code]red[/code], [i]not[/i] hexadecimal color codes. Unsupported tags will be left as-is in standard output. + /// When printing to standard output, the supported subset of BBCode is converted to ANSI escape codes for the terminal emulator to display. Displaying ANSI escape codes is currently only supported on Linux and macOS. Support for ANSI escape codes may vary across terminal emulators, especially for italic and strikethrough. + /// + /// Note: Consider using and + /// to print error and warning messages instead of or . + /// This distinguishes them from print messages used for debugging purposes, + /// while also displaying a stack trace when an error or warning is printed. + /// + /// + /// + /// GD.PrintRich("[b]Hello world![/b]"); // Prints out "Hello world!" in bold. + /// + /// + /// Arguments that will be printed. + /// + public static void PrintRich(params object[] what) + { + godot_icall_GD_print_rich(GetPrintParams(what)); + } + /// /// Prints the current stack trace information to the console. /// @@ -561,6 +582,9 @@ namespace Godot [MethodImpl(MethodImplOptions.InternalCall)] internal static extern void godot_icall_GD_print(object[] what); + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern void godot_icall_GD_print_rich(object[] what); + [MethodImpl(MethodImplOptions.InternalCall)] internal static extern void godot_icall_GD_printerr(object[] what); diff --git a/modules/mono/glue/gd_glue.cpp b/modules/mono/glue/gd_glue.cpp index 8aead217cf6..8b1c2b729ea 100644 --- a/modules/mono/glue/gd_glue.cpp +++ b/modules/mono/glue/gd_glue.cpp @@ -90,6 +90,27 @@ void godot_icall_GD_print(MonoArray *p_what) { print_line(str); } +void godot_icall_GD_print_rich(MonoArray *p_what) { + String str; + int length = mono_array_length(p_what); + + for (int i = 0; i < length; i++) { + MonoObject *elem = mono_array_get(p_what, MonoObject *, i); + + MonoException *exc = nullptr; + String elem_str = GDMonoMarshal::mono_object_to_variant_string(elem, &exc); + + if (exc) { + GDMonoUtils::set_pending_exception(exc); + return; + } + + str += elem_str; + } + + print_line_rich(str); +} + void godot_icall_GD_printerr(MonoArray *p_what) { String str; int length = mono_array_length(p_what); @@ -300,6 +321,7 @@ void godot_register_gd_icalls() { GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_pusherror", godot_icall_GD_pusherror); GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_pushwarning", godot_icall_GD_pushwarning); GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_print", godot_icall_GD_print); + GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_print_rich", godot_icall_GD_print_rich); GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_printerr", godot_icall_GD_printerr); GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_printraw", godot_icall_GD_printraw); GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_prints", godot_icall_GD_prints);