Skip to content
Open
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
8 changes: 8 additions & 0 deletions core/config/engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,14 @@ String Engine::get_shader_cache_path() const {
return shader_cache_path;
}

void Engine::set_color_standard_output(bool p_enable) {
color_standard_output = p_enable;
}

bool Engine::is_coloring_standard_output() const {
return color_standard_output;
}

Engine *Engine::get_singleton() {
return singleton;
}
Expand Down
5 changes: 5 additions & 0 deletions core/config/engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ class Engine {

bool freeze_time_scale = false;

bool color_standard_output = true;

public:
static Engine *get_singleton();

Expand Down Expand Up @@ -194,6 +196,9 @@ class Engine {
void set_shader_cache_path(const String &p_path);
String get_shader_cache_path() const;

void set_color_standard_output(bool p_enable);
bool is_coloring_standard_output() const;

bool is_abort_on_gpu_errors_enabled() const;
bool is_validation_layers_enabled() const;
bool is_generate_spirv_debug_info_enabled() const;
Expand Down
118 changes: 60 additions & 58 deletions core/string/print_string.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -131,145 +131,147 @@ void __print_line_rich(const String &p_string) {
pos = brk_end + 1;
output += txt;

const bool should_color = Engine::get_singleton()->is_coloring_standard_output();

String tag = p_string.substr(brk_pos + 1, brk_end - brk_pos - 1);
if (tag == "b") {
output += "\u001b[1m";
output += should_color ? "\u001b[1m" : "";
} else if (tag == "/b") {
output += "\u001b[22m";
output += should_color ? "\u001b[22m" : "";
} else if (tag == "i") {
output += "\u001b[3m";
output += should_color ? "\u001b[3m" : "";
} else if (tag == "/i") {
output += "\u001b[23m";
output += should_color ? "\u001b[23m" : "";
} else if (tag == "u") {
output += "\u001b[4m";
output += should_color ? "\u001b[4m" : "";
} else if (tag == "/u") {
output += "\u001b[24m";
output += should_color ? "\u001b[24m" : "";
} else if (tag == "s") {
output += "\u001b[9m";
output += should_color ? "\u001b[9m" : "";
} else if (tag == "/s") {
output += "\u001b[29m";
output += should_color ? "\u001b[29m" : "";
} else if (tag == "indent") {
output += " ";
output += should_color ? " " : "";
} else if (tag == "/indent") {
output += "";
} else if (tag == "code") {
output += "\u001b[2m";
output += should_color ? "\u001b[2m" : "";
} else if (tag == "/code") {
output += "\u001b[22m";
output += should_color ? "\u001b[22m" : "";
} else if (tag == "url") {
output += "";
} else if (tag == "/url") {
output += "";
} else if (tag == "center") {
output += "\n\t\t\t";
output += should_color ? "\n\t\t\t" : "";
} else if (tag == "/center") {
output += "";
} else if (tag == "right") {
output += "\n\t\t\t\t\t\t";
output += should_color ? "\n\t\t\t\t\t\t" : "";
} else if (tag == "/right") {
output += "";
} else if (tag.begins_with("color=")) {
String color_name = tag.trim_prefix("color=");
if (color_name == "black") {
output += "\u001b[30m";
output += should_color ? "\u001b[30m" : "";
} else if (color_name == "red") {
output += "\u001b[91m";
output += should_color ? "\u001b[91m" : "";
} else if (color_name == "green") {
output += "\u001b[92m";
output += should_color ? "\u001b[92m" : "";
} else if (color_name == "lime") {
output += "\u001b[92m";
output += should_color ? "\u001b[92m" : "";
} else if (color_name == "yellow") {
output += "\u001b[93m";
output += should_color ? "\u001b[93m" : "";
} else if (color_name == "blue") {
output += "\u001b[94m";
output += should_color ? "\u001b[94m" : "";
} else if (color_name == "magenta") {
output += "\u001b[95m";
output += should_color ? "\u001b[95m" : "";
} else if (color_name == "pink") {
output += "\u001b[38;5;218m";
output += should_color ? "\u001b[38;5;218m" : "";
} else if (color_name == "purple") {
output += "\u001b[38;5;98m";
output += should_color ? "\u001b[38;5;98m" : "";
} else if (color_name == "cyan") {
output += "\u001b[96m";
output += should_color ? "\u001b[96m" : "";
} else if (color_name == "white") {
output += "\u001b[97m";
output += should_color ? "\u001b[97m" : "";
} else if (color_name == "orange") {
output += "\u001b[38;5;208m";
output += should_color ? "\u001b[38;5;208m" : "";
} else if (color_name == "gray") {
output += "\u001b[90m";
output += should_color ? "\u001b[90m" : "";
} else {
Color c = Color::from_string(color_name, Color());
output += vformat("\u001b[38;2;%d;%d;%dm", c.r * 255, c.g * 255, c.b * 255);
output += should_color ? vformat("\u001b[38;2;%d;%d;%dm", c.r * 255, c.g * 255, c.b * 255) : "";
}
} else if (tag == "/color") {
output += "\u001b[39m";
output += should_color ? "\u001b[39m" : "";
} else if (tag.begins_with("bgcolor=")) {
String color_name = tag.trim_prefix("bgcolor=");
if (color_name == "black") {
output += "\u001b[40m";
output += should_color ? "\u001b[40m" : "";
} else if (color_name == "red") {
output += "\u001b[101m";
output += should_color ? "\u001b[101m" : "";
} else if (color_name == "green") {
output += "\u001b[102m";
output += should_color ? "\u001b[102m" : "";
} else if (color_name == "lime") {
output += "\u001b[102m";
output += should_color ? "\u001b[102m" : "";
} else if (color_name == "yellow") {
output += "\u001b[103m";
output += should_color ? "\u001b[103m" : "";
} else if (color_name == "blue") {
output += "\u001b[104m";
output += should_color ? "\u001b[104m" : "";
} else if (color_name == "magenta") {
output += "\u001b[105m";
output += should_color ? "\u001b[105m" : "";
} else if (color_name == "pink") {
output += "\u001b[48;5;218m";
output += should_color ? "\u001b[48;5;218m" : "";
} else if (color_name == "purple") {
output += "\u001b[48;5;98m";
output += should_color ? "\u001b[48;5;98m" : "";
} else if (color_name == "cyan") {
output += "\u001b[106m";
output += should_color ? "\u001b[106m" : "";
} else if (color_name == "white") {
output += "\u001b[107m";
output += should_color ? "\u001b[107m" : "";
} else if (color_name == "orange") {
output += "\u001b[48;5;208m";
output += should_color ? "\u001b[48;5;208m" : "";
} else if (color_name == "gray") {
output += "\u001b[100m";
output += should_color ? "\u001b[100m" : "";
} else {
Color c = Color::from_string(color_name, Color());
output += vformat("\u001b[48;2;%d;%d;%dm", c.r * 255, c.g * 255, c.b * 255);
output += should_color ? vformat("\u001b[48;2;%d;%d;%dm", c.r * 255, c.g * 255, c.b * 255) : "";
}
} else if (tag == "/bgcolor") {
output += "\u001b[49m";
output += should_color ? "\u001b[49m" : "";
} else if (tag.begins_with("fgcolor=")) {
String color_name = tag.trim_prefix("fgcolor=");
if (color_name == "black") {
output += "\u001b[30;40m";
output += should_color ? "\u001b[30;40m" : "";
} else if (color_name == "red") {
output += "\u001b[91;101m";
output += should_color ? "\u001b[91;101m" : "";
} else if (color_name == "green") {
output += "\u001b[92;102m";
output += should_color ? "\u001b[92;102m" : "";
} else if (color_name == "lime") {
output += "\u001b[92;102m";
output += should_color ? "\u001b[92;102m" : "";
} else if (color_name == "yellow") {
output += "\u001b[93;103m";
output += should_color ? "\u001b[93;103m" : "";
} else if (color_name == "blue") {
output += "\u001b[94;104m";
output += should_color ? "\u001b[94;104m" : "";
} else if (color_name == "magenta") {
output += "\u001b[95;105m";
output += should_color ? "\u001b[95;105m" : "";
} else if (color_name == "pink") {
output += "\u001b[38;5;218;48;5;218m";
output += should_color ? "\u001b[38;5;218;48;5;218m" : "";
} else if (color_name == "purple") {
output += "\u001b[38;5;98;48;5;98m";
output += should_color ? "\u001b[38;5;98;48;5;98m" : "";
} else if (color_name == "cyan") {
output += "\u001b[96;106m";
output += should_color ? "\u001b[96;106m" : "";
} else if (color_name == "white") {
output += "\u001b[97;107m";
output += should_color ? "\u001b[97;107m" : "";
} else if (color_name == "orange") {
output += "\u001b[38;5;208;48;5;208m";
output += should_color ? "\u001b[38;5;208;48;5;208m" : "";
} else if (color_name == "gray") {
output += "\u001b[90;100m";
output += should_color ? "\u001b[90;100m" : "";
} else {
Color c = Color::from_string(color_name, Color());
output += vformat("\u001b[38;2;%d;%d;%d;48;2;%d;%d;%dm", c.r * 255, c.g * 255, c.b * 255, c.r * 255, c.g * 255, c.b * 255);
output += should_color ? vformat("\u001b[38;2;%d;%d;%d;48;2;%d;%d;%dm", c.r * 255, c.g * 255, c.b * 255, c.r * 255, c.g * 255, c.b * 255) : "";
}
} else if (tag == "/fgcolor") {
output += "\u001b[39;49m";
output += should_color ? "\u001b[39;49m" : "";
} else {
output += "[";
pos = brk_pos + 1;
Expand Down
2 changes: 2 additions & 0 deletions doc/classes/@GlobalScope.xml
Original file line number Diff line number Diff line change
Expand Up @@ -877,6 +877,8 @@
[/codeblocks]
[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.
[b]Note:[/b] Output displayed in the editor supports clickable [code skip-lint][url=address]text[/url][/code] tags. The [code skip-lint][url][/code] tag's [code]address[/code] value is handled by [method OS.shell_open] when clicked.
[b]Note:[/b] On Windows, only Windows 10 and later correctly displays ANSI escape codes in standard output.
[b]Note:[/b] Color/formatting tags are only effective if the engine has colored console output enabled. This is the case by default, but it can be disabled if stdout is not a TTY (e.g. if writing to a file or under a continuous integration setup) or if the [code]NO_COLOR[/code] environment variable is set to a non-empty string. The [code]--color auto|always|never[/code] command line argument can be used to override this behavior, with [code]auto[/code] being the default.
</description>
</method>
<method name="print_verbose" qualifiers="vararg">
Expand Down
25 changes: 11 additions & 14 deletions drivers/unix/os_unix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1206,20 +1206,17 @@ void UnixTerminalLogger::log_error(const char *p_function, const char *p_file, i
err_details = p_code;
}

// Disable color codes if stdout is not a TTY.
// This prevents Godot from writing ANSI escape codes when redirecting
// stdout and stderr to a file.
const bool tty = isatty(fileno(stdout));
const char *gray = tty ? "\E[0;90m" : "";
const char *red = tty ? "\E[0;91m" : "";
const char *red_bold = tty ? "\E[1;31m" : "";
const char *yellow = tty ? "\E[0;93m" : "";
const char *yellow_bold = tty ? "\E[1;33m" : "";
const char *magenta = tty ? "\E[0;95m" : "";
const char *magenta_bold = tty ? "\E[1;35m" : "";
const char *cyan = tty ? "\E[0;96m" : "";
const char *cyan_bold = tty ? "\E[1;36m" : "";
const char *reset = tty ? "\E[0m" : "";
const bool should_color = Engine::get_singleton() ? Engine::get_singleton()->is_coloring_standard_output() : false;
const char *gray = should_color ? "\E[0;90m" : "";
const char *red = should_color ? "\E[0;91m" : "";
const char *red_bold = should_color ? "\E[1;31m" : "";
const char *yellow = should_color ? "\E[0;93m" : "";
const char *yellow_bold = should_color ? "\E[1;33m" : "";
const char *magenta = should_color ? "\E[0;95m" : "";
const char *magenta_bold = should_color ? "\E[1;35m" : "";
const char *cyan = should_color ? "\E[0;96m" : "";
const char *cyan_bold = should_color ? "\E[1;36m" : "";
const char *reset = should_color ? "\E[0m" : "";

const char *bold_color;
const char *normal_color;
Expand Down
36 changes: 36 additions & 0 deletions main/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,15 @@
#endif // TOOLS_ENABLED && !GDSCRIPT_NO_LSP
#endif // MODULE_GDSCRIPT_ENABLED

#ifdef WINDOWS_ENABLED
#include <io.h>
#include <stdio.h>
#define isatty _isatty
#define fileno _fileno
#else
#include <unistd.h>
#endif

/* Static members */

// Singletons
Expand Down Expand Up @@ -539,6 +548,7 @@ void Main::print_help(const char *p_binary) {
print_help_option("-v, --verbose", "Use verbose stdout mode.\n");
print_help_option("--quiet", "Quiet mode, silences stdout messages. Errors are still displayed.\n");
print_help_option("--no-header", "Do not print engine version and rendering method header on startup.\n");
print_help_option("--color", "Use colors for console output (\"auto\", \"always\", \"never\").\n");

print_help_title("Run options");
print_help_option("--, ++", "Separator for user-provided arguments. Following arguments are not used by the engine, but can be read from `OS.get_cmdline_user_args()`.\n");
Expand Down Expand Up @@ -1049,6 +1059,16 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
packed_data = memnew(PackedData);
}

// Disable color codes if stdout is not a TTY, or if the `NO_COLOR` environment variable
// is set to a non-empty string (https://no-color.org/).
// This prevents Godot from writing ANSI escape codes when redirecting stdout and stderr to a file.
// If we are running in a CI environment or `CLICOLOR_FORCE` is set to `1`, force colored output.
//
// These options are overridden by the `--color` command line argument.
const bool no_color = !OS::get_singleton()->get_environment("NO_COLOR").is_empty();
const bool force_color = bool(OS::get_singleton()->get_environment("CLICOLOR_FORCE").to_int()) || bool(OS::get_singleton()->get_environment("CI").to_int());
Engine::get_singleton()->set_color_standard_output(no_color ? false : (force_color || isatty(fileno(stdout))));

#ifdef MINIZIP_ENABLED

//XXX: always get_singleton() == 0x0
Expand Down Expand Up @@ -1134,7 +1154,23 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph

} else if (arg == "--no-header") {
Engine::get_singleton()->_print_header = false;
} else if (arg == "--color") { // Color console output.
if (N) {
// The "auto" case is already handled above.
if (N->get() == "always") {
Engine::get_singleton()->set_color_standard_output(true);
} else if (N->get() == "never") {
Engine::get_singleton()->set_color_standard_output(false);
} else if (N->get() != "auto") {
OS::get_singleton()->print("Unknown color argument '%s', aborting.\nValid options are 'auto', 'always' and 'never'.\n", N->get().utf8().get_data());
goto error;
}

N = N->next();
} else {
OS::get_singleton()->print("Missing color argument, aborting.\nValid options are 'auto', 'always' and 'never'.\n");
goto error;
}
} else if (arg == "--audio-driver") { // audio driver

if (N) {
Expand Down
Loading