Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add print_rich() for printing with BBCode #60675

Merged
merged 1 commit into from
Jun 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions core/debugger/remote_debugger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<RemoteDebugger *>(p_this);

if (rd->flushing && Thread::get_caller_id() == rd->flush_thread) { // Can't handle recursive prints during flush.
Expand Down Expand Up @@ -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) {
Expand Down
3 changes: 2 additions & 1 deletion core/debugger/remote_debugger.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class RemoteDebugger : public EngineDebugger {
enum MessageType {
MESSAGE_TYPE_LOG,
MESSAGE_TYPE_ERROR,
MESSAGE_TYPE_LOG_RICH,
};

private:
Expand Down Expand Up @@ -82,7 +83,7 @@ class RemoteDebugger : public EngineDebugger {
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);

Expand Down
15 changes: 15 additions & 0 deletions core/os/os.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions core/os/os.h
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ class OS {

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;
Expand Down
95 changes: 93 additions & 2 deletions core/string/print_string.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
voylin marked this conversation as resolved.
Show resolved Hide resolved
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;
}

Expand All @@ -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;
}

Expand Down
12 changes: 11 additions & 1 deletion core/string/print_string.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -59,16 +59,26 @@ 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);

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 <typename... Args>
void print_line(Variant p_var, Args... p_args) {
__print_line(stringify_variants(p_var, p_args...));
}

template <typename... Args>
void print_line_rich(Variant p_var, Args... p_args) {
__print_line_rich(stringify_variants(p_var, p_args...));
}

#endif // PRINT_STRING_H
17 changes: 17 additions & 0 deletions core/variant/variant_utility.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
10 changes: 10 additions & 0 deletions doc/classes/@GlobalScope.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
</description>
</method>
<method name="print_rich" qualifiers="vararg">
<description>
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.
</description>
</method>
<method name="print_verbose" qualifiers="vararg">
<description>
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.
Expand Down
22 changes: 17 additions & 5 deletions editor/editor_log.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down Expand Up @@ -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<Texture2D> icon = get_theme_icon(SNAME("Error"), SNAME("EditorIcons"));
Expand Down Expand Up @@ -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();
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -451,6 +459,10 @@ void EditorLog::deinit() {

EditorLog::~EditorLog() {
for (const KeyValue<MessageType, LogFilter *> &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);
}
}
}
1 change: 1 addition & 0 deletions editor/editor_log.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class EditorLog : public HBoxContainer {
enum MessageType {
MSG_TYPE_STD,
MSG_TYPE_ERROR,
MSG_TYPE_STD_RICH,
MSG_TYPE_WARNING,
MSG_TYPE_EDITOR,
};
Expand Down
10 changes: 8 additions & 2 deletions editor/editor_node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<EditorNode *>(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) {
Expand Down
2 changes: 1 addition & 1 deletion editor/editor_node.h
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,7 @@ class EditorNode : public Node {
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<Resource> p_resource, const String &p_path);
static void _resource_loaded(Ref<Resource> p_resource, const String &p_path);

Expand Down
2 changes: 1 addition & 1 deletion modules/gdscript/tests/gdscript_test_runner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
Expand Down
2 changes: 1 addition & 1 deletion modules/gdscript/tests/gdscript_test_runner.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ class GDScriptTest {
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();
Expand Down
Loading