# Program Listing for File rang.cc

↰ Return to documentation for file (`rang.cc`)

    #ifndef DOXYGEN_SHOULD_SKIP_THIS

    #include "Utils.hh"

    #if defined(UTILS_OS_LINUX) || defined(UTILS_OS_OSX)
      #include <unistd.h>
    #elif defined(UTILS_OS_WINDOWS)
      #include <windows.h>
      #include <io.h>
      #include <memory>

      // Only defined in windows 10 onwards, redefining in lower windows since it
      // doesn't gets used in lower versions
      // https://docs.microsoft.com/en-us/windows/console/getconsolemode
      #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
      #define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
      #endif
    #endif

    namespace rang {

      #ifndef DOXYGEN_SHOULD_SKIP_THIS
      using std::string;
      using std::wstring;
      using std::getenv;
      using std::any_of;
      using std::begin;
      using std::end;
      using std::strstr;
      using std::unique_ptr;
      using std::nothrow;
      using std::clog;
      using std::cerr;
      using std::cout;
      using std::ostream;
      using std::streambuf;
      #endif

      namespace rang_implementation {

        bool
        supportsColor() noexcept {
          #if defined(UTILS_OS_LINUX) || defined(UTILS_OS_OSX)
          static const bool result = [] {
            char const * Terms[] = {
              "ansi",    "color",  "console", "cygwin", "gnome",
              "konsole", "kterm",  "linux",   "msys",   "putty",
              "rxvt",    "screen", "vt100",   "xterm"
            };
            char const *env_p = getenv("TERM");
            if ( env_p == nullptr ) return false;
            return any_of(
              begin(Terms), end(Terms),
              [&](const char *term) { return strstr(env_p, term) != nullptr; }
            );
          }();
          #elif defined(UTILS_OS_WINDOWS)
          // All windows versions support colors through native console methods
          static constexpr bool result = true;
          #else
          static constexpr bool result = false;
          #endif
          return result;
        }

        #ifdef UTILS_OS_WINDOWS

        bool
        isMsysPty( int fd ) noexcept {
          // disable for mingw on MATLAB
          #if (defined(MINGW) || defined(__MINGW32__)) && defined(MATLAB_MEX_FILE )
          return false;
          #else
          // Dynamic load for binary compability with old Windows
          auto const ptrGetFileInformationByHandleEx = reinterpret_cast<decltype(&GetFileInformationByHandleEx)>(
            GetProcAddress(
              GetModuleHandle(TEXT("kernel32.dll")),
              "GetFileInformationByHandleEx"
            )
          );

          if ( !ptrGetFileInformationByHandleEx ) return false;

          HANDLE h = reinterpret_cast<HANDLE>(_get_osfhandle(fd));
          if ( h == INVALID_HANDLE_VALUE ) return false;

          // Check that it's a pipe:
          if ( GetFileType(h) != FILE_TYPE_PIPE ) return false;

          // POD type is binary compatible with FILE_NAME_INFO from WinBase.h
          // It have the same alignment and used to avoid UB in caller code
          struct MY_FILE_NAME_INFO {
            DWORD FileNameLength;
            WCHAR FileName[MAX_PATH];
          };

          auto pNameInfo = unique_ptr<MY_FILE_NAME_INFO>(
            new (nothrow) MY_FILE_NAME_INFO()
          );
          if (!pNameInfo) return false;

          // Check pipe name is template of
          // {"cygwin-","msys-"}XXXXXXXXXXXXXXX-ptyX-XX
          if ( !ptrGetFileInformationByHandleEx( h, FileNameInfo, pNameInfo.get(), sizeof(MY_FILE_NAME_INFO)) ) return false;

          wstring name(pNameInfo->FileName, pNameInfo->FileNameLength / sizeof(WCHAR));

          if ( ( name.find(L"msys-") == wstring::npos && name.find(L"cygwin-") == wstring::npos )
                || name.find(L"-pty") == wstring::npos) return false;

          return true;
          #endif
        }

        #endif

        bool
        isTerminal( streambuf const * osbuf ) noexcept {
          #if defined(UTILS_OS_LINUX) || defined(UTILS_OS_OSX)
          if ( osbuf == cout.rdbuf() ) {
            static const bool cout_term = isatty(fileno(stdout)) != 0;
            return cout_term;
          } else if ( osbuf == cerr.rdbuf() || osbuf == clog.rdbuf() ) {
            static const bool cerr_term = isatty(fileno(stderr)) != 0;
            return cerr_term;
          }
          #elif defined(UTILS_OS_WINDOWS)
          if ( osbuf == cout.rdbuf()) {
            static const bool cout_term = (_isatty(_fileno(stdout)) || isMsysPty(_fileno(stdout)));
            return cout_term;
          } else if (osbuf == cerr.rdbuf() || osbuf == clog.rdbuf()) {
            static const bool cerr_term = (_isatty(_fileno(stderr)) || isMsysPty(_fileno(stderr)));
            return cerr_term;
          }
          #endif
          return false;
        }

        #ifdef UTILS_OS_WINDOWS

        struct SGR {         // Select Graphic Rendition parameters for Windows console
          BYTE    fgColor;   // foreground color (0-15) lower 3 rgb bits + intense bit
          BYTE    bgColor;   // background color (0-15) lower 3 rgb bits + intense bit
          BYTE    bold;      // emulated as FOREGROUND_INTENSITY bit
          BYTE    underline; // emulated as BACKGROUND_INTENSITY bit
          BOOLEAN inverse;   // swap foreground/bold & background/underline
          BOOLEAN conceal;   // set foreground/bold to background/underline
        };

        enum class AttrColor : BYTE {  // Color attributes for console screen buffer
          black   = 0,
          red     = 4,
          green   = 2,
          yellow  = 6,
          blue    = 1,
          magenta = 5,
          cyan    = 3,
          gray    = 7
        };

        HANDLE
        getConsoleHandle( streambuf const * osbuf ) noexcept {
          if ( osbuf == cout.rdbuf() ) {
            static const HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
            return hStdout;
          } else if ( osbuf == cerr.rdbuf() || osbuf == clog.rdbuf() ) {
            static const HANDLE hStderr = GetStdHandle(STD_ERROR_HANDLE);
            return hStderr;
          }
          return INVALID_HANDLE_VALUE;
        }

        bool
        setWinTermAnsiColors( streambuf const *osbuf ) noexcept {
          HANDLE h = getConsoleHandle(osbuf);
          if ( h == INVALID_HANDLE_VALUE )  return false;
          DWORD dwMode = 0;
          if ( !GetConsoleMode(h, &dwMode) )  return false;
          dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
          if ( !SetConsoleMode(h, dwMode) )  return false;
          return true;
        }

        bool
        supportsAnsi( streambuf const * osbuf ) noexcept {
          if ( osbuf == cout.rdbuf() ) {
            static const bool cout_ansi = (isMsysPty(_fileno(stdout)) || setWinTermAnsiColors(osbuf));
            return cout_ansi;
          } else if ( osbuf == cerr.rdbuf() || osbuf == clog.rdbuf() ) {
            static const bool cerr_ansi = (isMsysPty(_fileno(stderr)) || setWinTermAnsiColors(osbuf));
            return cerr_ansi;
          }
          return false;
        }

        SGR const &
        defaultState() noexcept {
          static const SGR defaultSgr = []() -> SGR {
            CONSOLE_SCREEN_BUFFER_INFO info;
            WORD attrib = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
            if ( GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE),&info) ||
                 GetConsoleScreenBufferInfo(GetStdHandle(STD_ERROR_HANDLE),&info) ) {
              attrib = info.wAttributes;
            }
            SGR sgr     = { 0, 0, 0, 0, FALSE, FALSE };
            sgr.fgColor = attrib & 0x0F;
            sgr.bgColor = (attrib & 0xF0) >> 4;
            return sgr;
          }();
          return defaultSgr;
        }

        BYTE
        ansi2attr( BYTE rgb ) noexcept {
          static const AttrColor rev[8] = {
            AttrColor::black,  AttrColor::red,  AttrColor::green,
            AttrColor::yellow, AttrColor::blue, AttrColor::magenta,
            AttrColor::cyan,   AttrColor::gray
          };
          return static_cast<BYTE>(rev[rgb]);
        }

        void
        setWinSGR( rang::bg col, SGR & state ) noexcept {
          if ( col != rang::bg::reset ) {
            state.bgColor = ansi2attr(static_cast<BYTE>(col) - 40);
          } else {
            state.bgColor = defaultState().bgColor;
          }
        }

        void
        setWinSGR( rang::fg col, SGR & state ) noexcept {
          if (col != rang::fg::reset) {
            state.fgColor = ansi2attr(static_cast<BYTE>(col) - 30);
          } else {
            state.fgColor = defaultState().fgColor;
          }
        }

        void
        setWinSGR( rang::bgB col, SGR & state ) noexcept {
          state.bgColor = (BACKGROUND_INTENSITY >> 4) | ansi2attr(static_cast<BYTE>(col) - 100);
        }

        void
        setWinSGR( rang::fgB col, SGR & state ) noexcept {
          state.fgColor = FOREGROUND_INTENSITY | ansi2attr(static_cast<BYTE>(col) - 90);
        }

        void
        setWinSGR( rang::style style, SGR & state ) noexcept {
          switch (style) {
          case rang::style::reset:
            state = defaultState();
            break;
          case rang::style::bold:
            state.bold = FOREGROUND_INTENSITY;
            break;
          case rang::style::underline:
          case rang::style::blink:
            state.underline = BACKGROUND_INTENSITY;
            break;
          case rang::style::reversed:
            state.inverse = TRUE;
            break;
          case rang::style::conceal:
            state.conceal = TRUE;
            break;
          default:
            break;
          }
        }

        SGR &
        current_state() noexcept {
          static SGR state = defaultState();
          return state;
        }

        WORD
        SGR2Attr(const SGR &state) noexcept {
          WORD attrib = 0;
          if ( state.conceal ) {
            if ( state.inverse ) {
              attrib = (state.fgColor << 4) | state.fgColor;
              if ( state.bold ) attrib |= FOREGROUND_INTENSITY | BACKGROUND_INTENSITY;
            } else {
              attrib = (state.bgColor << 4) | state.bgColor;
              if ( state.underline ) attrib |= FOREGROUND_INTENSITY | BACKGROUND_INTENSITY;
            }
          } else if ( state.inverse ) {
            attrib = (state.fgColor << 4) | state.bgColor;
            if ( state.bold      ) attrib |= BACKGROUND_INTENSITY;
            if ( state.underline ) attrib |= FOREGROUND_INTENSITY;
          } else {
            attrib = state.fgColor | (state.bgColor << 4) | state.bold | state.underline;
          }
          return attrib;
        }

        template <typename T>
        inline
        void
        setWinColorNative_tmpl( ostream &os, T const value ) {
          HANDLE const h = getConsoleHandle(os.rdbuf());
          if ( h != INVALID_HANDLE_VALUE ) {
            setWinSGR(value, current_state());
            // Out all buffered text to console with previous settings:
            os.flush();
            SetConsoleTextAttribute(h, SGR2Attr(current_state()));
          }
        }

        #define DO_INSTANTIATION(A)                 \
        void                                        \
        setWinColorNative( ostream &os, A value ) { \
          setWinColorNative_tmpl<A>( os, value );   \
        }

        DO_INSTANTIATION( enum rang::style );
        DO_INSTANTIATION( enum rang::bg );
        DO_INSTANTIATION( enum rang::fg );
        DO_INSTANTIATION( enum rang::bgB );
        DO_INSTANTIATION( enum rang::fgB );

        #endif
      }  // namespace rang_implementation

    }  // namespace rang

    #endif