0

I started using fmt for printing recently. I really like the lib, fast, easy to use. But when I completed my conversion, there are ways that my program can run that will render with a bunch of additional newlines. It's not every case, so this will get a bit deep.

What I have is a compiler and a build manager. The build manager (picture Ninja, although this is a custom tool) launches compile processes, buffers the output, and prints it all at once. Both programs have been converted to use fmt. The key function being called is fmt::vprint(stream, format, args). When the build manager prints directly, things are fine. But when I'm reading the child process output, any \n in the data has been prefixed with \r. Windows Terminal will render that fine, but some shells (such as the Visual Studio output window) do not, and will show a bunch of extra newlines.

fmt is open source so I was able to hack on it a bunch and see what is different between what it did and what my program was doing originally. The crux is this:

namespace detail {
FMT_FUNC void print(std::FILE* f, string_view text) {
#ifdef _WIN32
  auto fd = _fileno(f);
  if (_isatty(fd)) {
    detail::utf8_to_utf16 u16(string_view(text.data(), text.size()));
    auto written = detail::dword();
    if (detail::WriteConsoleW(reinterpret_cast<void*>(_get_osfhandle(fd)),
                              u16.c_str(), static_cast<uint32_t>(u16.size()),
                              &written, nullptr)) {
      return;
    }
    // Fallback to fwrite on failure. It can happen if the output has been
    // redirected to NUL.
  }
#endif
  detail::fwrite_fully(text.data(), 1, text.size(), f);
}
}  // namespace detail

As a child process, the _isatty() function will come back with false, so we fall back to the fwrite() function, and that triggers the \r escaping. In my original program, I have an fwrite() fallback as well, but it only picks up if GetStdHandle(STD_OUTPUT_HANDLE) returns nullptr. In the child process case, there is still a console we can WriteFile() to.

The other side-effect I see happening is if I use the fmt way of injecting color, eg:

fmt::print(fmt::emphasis::bold | fg(fmt::color::red), "Elapsed time: {0:.2f} seconds", 1.23);

Again Windows Terminal renders it correctly, but in Visual Studio's output window this turns into a soup of garbage. The native way of doing it -- SetConsoleTextAttribute(console, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY);-- does not trigger that problem.

I tried hacking up the fmt source to be more like my original console printing code. The key difference was the _isatty() function. I suspect that's too broad of a question for the cases where console printing might fail.

1 Answers1

0

\r is added because the file is opened in text mode. You could try (re)opening in binary mode or ignore \r on the read side.

vitaut
  • 49,672
  • 25
  • 199
  • 336