-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
Support color specifiers #231
Comments
Amazing idea |
ANSI escape codes do not work for Win32 consoles. This may create inconsistent behavior in the console (if anyone actually cares), unless you are handling those cases separately. |
I don't know about ANSI code in particular, but git for example does support colored output on Windows, so some way to achieve the same effect should exist. This is another reason why builtin support for this feature would be awesome. Anyway, colored output is usually used to enhance readability of output, without adding information that would be lost without colors. For this reason, not supporting colors on some platforms would not be a blocking issue for this feature, I think. |
ANSI escape codes are specifically not supported by Win32 consoles. The only way to guarantee support is to do it through the hardware, also, this will probably be a problem for any other OS with a console that doesn't have ANSI escape code support. (that is if you care about color on consoles being a problem, but if that is the case, you should not do strictly colors for readability) |
Yes, I understand that ANSI codes are not supported. Nevertheless, colored output is possible, just with a different mechanism than outputting ANSI codes. Anyway, I think a non colored output on windows is not a problem, if it is difficult to achieve. It is sufficient to say it in the docs. |
Interesting idea. There has been some work in this direction, so you can do fmt::print_colored(fmt::RED, "Elapsed time: {0:.2f} seconds", 1.23); right now. However, this only works for complete messages. I think specifying the color in the format string fmt::print(std::cout, "Colored: {:red}", "Hello world"); might slow down parsing for the common case of not using colors and requires hardcoding all the colors in the parser, so I'd go with the second option (especially since you already have it working) which can even be shortened with an appropriate alias: fmt::print(std::cout, "Colored: {}", fmt::red("Hello world")); I think the information of whether the output device is a terminal can be passed via |
To add some substance, here's how it could look like: template <typename Char>
class BasicWriter {
...
virtual bool isatty() const { return false; }
};
FMT_FUNC void fmt::print(std::FILE *f, CStringRef format_str, ArgList args) {
class FileWriter : public MemoryWriter {
private:
std::FILE *file_;
public:
explicit FileWriter(std::FILE *f) : file_(f) {}
bool isatty() const { return ::isatty(fileno(file_)); }
};
FileWriter w(f);
w.write(format_str, args);
std::fwrite(w.data(), 1, w.size(), f);
} Then you could provide an overload of the template <typename Char, typename T>
void format(BasicFormatter<Char> &f, const Char *&format_str, const colored<T, ?> &c) {
// Check if writing to a terminal with f.writer().isatty() and do the formatting.
} What do you think? |
BTW there was some work on colored output for Windows in #102. |
@vitaut I was simply pointing out ANSI escape codes are not supported by Win32 consoles, so you couldn't designate them that way. The next best way (that I can recall) is to force the writer to be colored while writing a certain section, but that probably results in a slower parse. |
On a second thought, calling |
@vitaut Yes, that seems a good solution! Anyway, is it possible to redirect a file descriptor after its creation? I don't think so. In this case, checking |
At the end, do we agree on the shape of this feature? I can contribute a patch myself but I'd like to know if it's ok for everyone. |
I can't say that I'm completely happy about the
A more general solution would be something along the lines of parameterizing the custom argument Another option that doesn't require any changes to the library is to add template <typename Char, typename T>
void format(BasicFormatter<Char> &f, const Char *&format_str, const colored<T, ?> &c) {
if (TerminalWriter *w = dynamic_cast<TerminalWriter*>(&f.writer())) {
// do colored output
} else ; // ignore color
} |
Some initial work to support custom formatters/writers: https://github.com/cppformat/cppformat/tree/custom-formatter. Once complete it will be possible to extend the current formatter for colored output or anything else. |
Here's an example of how to implement this feature using the new custom formatter functionality that I've just merged into the master branch: #include "fmt/format.h"
#include <unistd.h>
class ColoredFormatter : public fmt::BasicFormatter<char> {
private:
bool colorize_;
public:
ColoredFormatter(const fmt::ArgList &args, fmt::Writer &w, bool colorize)
: BasicFormatter<char>(args, w), colorize_(colorize) {}
bool colorize() const { return colorize_; }
};
template <typename T>
struct Colored {
const T &value;
fmt::Color color;
};
template <typename T>
Colored<T> colored(const T &value, fmt::Color color) {
Colored<T> colored = {value, color};
return colored;
}
template <typename T>
void format(ColoredFormatter &f, const char *&format_str, const Colored<T> &c) {
fmt::Writer &w = f.writer();
if (f.colorize()) {
char escape[] = "\x1b[30m";
escape[3] = static_cast<char>('0' + c.color);
w << escape;
}
f.writer() << c.value;
if (f.colorize())
w << "\x1b[0m";
}
void print_colored(std::FILE *f, const char *format_str, fmt::ArgList args) {
fmt::MemoryWriter w;
ColoredFormatter(args, w, isatty(fileno(f))).format(format_str);
std::fputs(w.c_str(), f);
}
template <typename... Args>
void print_colored(std::FILE *f, const char *format_str, const Args & ... args) {
typedef fmt::internal::ArgArray<sizeof...(Args)> ArgArray;
typename ArgArray::Type array{ArgArray::template make<ColoredFormatter>(args)...};
print_colored(f, format_str,
fmt::ArgList(fmt::internal::make_type(args...), array));
}
int main() {
print_colored(stdout, "{}", colored(42, fmt::RED));
} This can be extended to work on Windows. @nicola-gigante, will this work for you? |
@vitaut |
Thanks for the confirmation. I'm closing this but note that the part typename fmt::internal::ArgArray<sizeof...(Args)>::Type array;
print_colored(f, format_str,
fmt::internal::make_arg_list<ColoredFormatter>(array, args...)); will change a bit because it currently relies on some internal APIs. |
Ok, don't worry. Thanks for your help :) |
I've updated the example to work with the current API. |
Just a small note. In order to compile one needs to replace |
It would be great to be able to specify a colors using ANSI codes for messages meant to be printed to a terminal.
Something like:
I need this feature because I want my tool to be able to produce colored output for better readability.
I have something like the following working in my codebase:
The
colored
function returns an helper object with a suitableoperator<<
overload that outputs the argument surrounded by the correct ANSI codes.The problem with this approach is that, ideally, the tool should not produce ANSI codes if
cout
is not attached to a TTY, for example if the program has been launched with a shell redirection or in a pipe.This is easily detectable with the POSIX
isatty()
function (and something similar on Windows), but I cannot use it because my customoperator<<
is not called on the actual output stream, but rather on an intermediary string buffer (I don't know the library implementation, but I guess it is aBasicWriter
or something like that, right?). This means I cannot detect the TTY from the stream operator.Note also that the decision to turn on or off the colors cannot be done ahead for the entire program, because the output and error streams could be redirected to different devices. We may have the output stream redirected to a file and the error still connected to the terminal. In this case, messages printed on
std::cerr
should still print color codes, and this means the decision has to be taken at each invocation based on which stream is being used.All of this to say that this feature is not implementable by the user outside of the library, and a built-in support for ANSI colors with proper handling of TTYs would be great. An alternative would be to expose a way for custom stream operators to get the actual output stream passed to
fmt::print
, but I cannot think of an easy one.The text was updated successfully, but these errors were encountered: