/ externals / catch / src / catch2 / internal / catch_console_colour.cpp
catch_console_colour.cpp
  1  
  2  //              Copyright Catch2 Authors
  3  // Distributed under the Boost Software License, Version 1.0.
  4  //   (See accompanying file LICENSE.txt or copy at
  5  //        https://www.boost.org/LICENSE_1_0.txt)
  6  
  7  // SPDX-License-Identifier: BSL-1.0
  8  #if defined(__clang__)
  9  #    pragma clang diagnostic push
 10  #    pragma clang diagnostic ignored "-Wexit-time-destructors"
 11  #endif
 12  
 13  
 14  #include <catch2/internal/catch_console_colour.hpp>
 15  #include <catch2/internal/catch_enforce.hpp>
 16  #include <catch2/internal/catch_errno_guard.hpp>
 17  #include <catch2/interfaces/catch_interfaces_config.hpp>
 18  #include <catch2/internal/catch_istream.hpp>
 19  #include <catch2/internal/catch_move_and_forward.hpp>
 20  #include <catch2/internal/catch_context.hpp>
 21  #include <catch2/internal/catch_platform.hpp>
 22  #include <catch2/internal/catch_debugger.hpp>
 23  #include <catch2/internal/catch_windows_h_proxy.hpp>
 24  #include <catch2/internal/catch_compiler_capabilities.hpp>
 25  
 26  #include <cassert>
 27  #include <ostream>
 28  #include <utility>
 29  
 30  namespace Catch {
 31  
 32      ColourImpl::~ColourImpl() = default;
 33  
 34      ColourImpl::ColourGuard ColourImpl::guardColour( Colour::Code colourCode ) {
 35          return ColourGuard(colourCode, this );
 36      }
 37  
 38      void ColourImpl::ColourGuard::engageImpl( std::ostream& stream ) {
 39          assert( &stream == &m_colourImpl->m_stream->stream() &&
 40                  "Engaging colour guard for different stream than used by the "
 41                  "parent colour implementation" );
 42          static_cast<void>( stream );
 43  
 44          m_engaged = true;
 45          m_colourImpl->use( m_code );
 46      }
 47  
 48      ColourImpl::ColourGuard::ColourGuard( Colour::Code code,
 49                                            ColourImpl const* colour ):
 50          m_colourImpl( colour ), m_code( code ) {
 51      }
 52      ColourImpl::ColourGuard::ColourGuard( ColourGuard&& rhs ) noexcept:
 53          m_colourImpl( rhs.m_colourImpl ),
 54          m_code( rhs.m_code ),
 55          m_engaged( rhs.m_engaged ) {
 56          rhs.m_engaged = false;
 57      }
 58      ColourImpl::ColourGuard&
 59      ColourImpl::ColourGuard::operator=( ColourGuard&& rhs ) noexcept {
 60          using std::swap;
 61          swap( m_colourImpl, rhs.m_colourImpl );
 62          swap( m_code, rhs.m_code );
 63          swap( m_engaged, rhs.m_engaged );
 64  
 65          return *this;
 66      }
 67      ColourImpl::ColourGuard::~ColourGuard() {
 68          if ( m_engaged ) {
 69              m_colourImpl->use( Colour::None );
 70          }
 71      }
 72  
 73      ColourImpl::ColourGuard&
 74      ColourImpl::ColourGuard::engage( std::ostream& stream ) & {
 75          engageImpl( stream );
 76          return *this;
 77      }
 78  
 79      ColourImpl::ColourGuard&&
 80      ColourImpl::ColourGuard::engage( std::ostream& stream ) && {
 81          engageImpl( stream );
 82          return CATCH_MOVE(*this);
 83      }
 84  
 85      namespace {
 86          //! A do-nothing implementation of colour, used as fallback for unknown
 87          //! platforms, and when the user asks to deactivate all colours.
 88          class NoColourImpl final : public ColourImpl {
 89          public:
 90              NoColourImpl( IStream* stream ): ColourImpl( stream ) {}
 91  
 92          private:
 93              void use( Colour::Code ) const override {}
 94          };
 95      } // namespace
 96  
 97  
 98  } // namespace Catch
 99  
100  
101  #if defined ( CATCH_CONFIG_COLOUR_WIN32 ) /////////////////////////////////////////
102  
103  namespace Catch {
104  namespace {
105  
106      class Win32ColourImpl final : public ColourImpl {
107      public:
108          Win32ColourImpl(IStream* stream):
109              ColourImpl(stream) {
110              CONSOLE_SCREEN_BUFFER_INFO csbiInfo;
111              GetConsoleScreenBufferInfo( GetStdHandle( STD_OUTPUT_HANDLE ),
112                                          &csbiInfo );
113              originalForegroundAttributes = csbiInfo.wAttributes & ~( BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_INTENSITY );
114              originalBackgroundAttributes = csbiInfo.wAttributes & ~( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY );
115          }
116  
117          static bool useImplementationForStream(IStream const& stream) {
118              // Win32 text colour APIs can only be used on console streams
119              // We cannot check that the output hasn't been redirected,
120              // so we just check that the original stream is console stream.
121              return stream.isConsole();
122          }
123  
124      private:
125          void use( Colour::Code _colourCode ) const override {
126              switch( _colourCode ) {
127                  case Colour::None:      return setTextAttribute( originalForegroundAttributes );
128                  case Colour::White:     return setTextAttribute( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE );
129                  case Colour::Red:       return setTextAttribute( FOREGROUND_RED );
130                  case Colour::Green:     return setTextAttribute( FOREGROUND_GREEN );
131                  case Colour::Blue:      return setTextAttribute( FOREGROUND_BLUE );
132                  case Colour::Cyan:      return setTextAttribute( FOREGROUND_BLUE | FOREGROUND_GREEN );
133                  case Colour::Yellow:    return setTextAttribute( FOREGROUND_RED | FOREGROUND_GREEN );
134                  case Colour::Grey:      return setTextAttribute( 0 );
135  
136                  case Colour::LightGrey:     return setTextAttribute( FOREGROUND_INTENSITY );
137                  case Colour::BrightRed:     return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED );
138                  case Colour::BrightGreen:   return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN );
139                  case Colour::BrightWhite:   return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE );
140                  case Colour::BrightYellow:  return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN );
141  
142                  case Colour::Bright: CATCH_INTERNAL_ERROR( "not a colour" );
143  
144                  default:
145                      CATCH_ERROR( "Unknown colour requested" );
146              }
147          }
148  
149          void setTextAttribute( WORD _textAttribute ) const {
150              SetConsoleTextAttribute( GetStdHandle( STD_OUTPUT_HANDLE ),
151                                       _textAttribute |
152                                           originalBackgroundAttributes );
153          }
154          WORD originalForegroundAttributes;
155          WORD originalBackgroundAttributes;
156      };
157  
158  } // end anon namespace
159  } // end namespace Catch
160  
161  #endif // Windows/ ANSI/ None
162  
163  
164  #if defined( CATCH_PLATFORM_LINUX ) || defined( CATCH_PLATFORM_MAC )
165  #    define CATCH_INTERNAL_HAS_ISATTY
166  #    include <unistd.h>
167  #endif
168  
169  namespace Catch {
170  namespace {
171  
172      class ANSIColourImpl final : public ColourImpl {
173      public:
174          ANSIColourImpl( IStream* stream ): ColourImpl( stream ) {}
175  
176          static bool useImplementationForStream(IStream const& stream) {
177              // This is kinda messy due to trying to support a bunch of
178              // different platforms at once.
179              // The basic idea is that if we are asked to do autodetection (as
180              // opposed to being told to use posixy colours outright), then we
181              // only want to use the colours if we are writing to console.
182              // However, console might be redirected, so we make an attempt at
183              // checking for that on platforms where we know how to do that.
184              bool useColour = stream.isConsole();
185  #if defined( CATCH_INTERNAL_HAS_ISATTY ) && \
186      !( defined( __DJGPP__ ) && defined( __STRICT_ANSI__ ) )
187              ErrnoGuard _; // for isatty
188              useColour = useColour && isatty( STDOUT_FILENO );
189  #    endif
190  #    if defined( CATCH_PLATFORM_MAC ) || defined( CATCH_PLATFORM_IPHONE )
191              useColour = useColour && !isDebuggerActive();
192  #    endif
193  
194              return useColour;
195          }
196  
197      private:
198          void use( Colour::Code _colourCode ) const override {
199              auto setColour = [&out =
200                                    m_stream->stream()]( char const* escapeCode ) {
201                  // The escape sequence must be flushed to console, otherwise
202                  // if stdin and stderr are intermixed, we'd get accidentally
203                  // coloured output.
204                  out << '\033' << escapeCode << std::flush;
205              };
206              switch( _colourCode ) {
207                  case Colour::None:
208                  case Colour::White:     return setColour( "[0m" );
209                  case Colour::Red:       return setColour( "[0;31m" );
210                  case Colour::Green:     return setColour( "[0;32m" );
211                  case Colour::Blue:      return setColour( "[0;34m" );
212                  case Colour::Cyan:      return setColour( "[0;36m" );
213                  case Colour::Yellow:    return setColour( "[0;33m" );
214                  case Colour::Grey:      return setColour( "[1;30m" );
215  
216                  case Colour::LightGrey:     return setColour( "[0;37m" );
217                  case Colour::BrightRed:     return setColour( "[1;31m" );
218                  case Colour::BrightGreen:   return setColour( "[1;32m" );
219                  case Colour::BrightWhite:   return setColour( "[1;37m" );
220                  case Colour::BrightYellow:  return setColour( "[1;33m" );
221  
222                  case Colour::Bright: CATCH_INTERNAL_ERROR( "not a colour" );
223                  default: CATCH_INTERNAL_ERROR( "Unknown colour requested" );
224              }
225          }
226      };
227  
228  } // end anon namespace
229  } // end namespace Catch
230  
231  namespace Catch {
232  
233      Detail::unique_ptr<ColourImpl> makeColourImpl( ColourMode implSelection,
234                                                     IStream* stream ) {
235  #if defined( CATCH_CONFIG_COLOUR_WIN32 )
236          if ( implSelection == ColourMode::Win32 ) {
237              return Detail::make_unique<Win32ColourImpl>( stream );
238          }
239  #endif
240          if ( implSelection == ColourMode::ANSI ) {
241              return Detail::make_unique<ANSIColourImpl>( stream );
242          }
243          if ( implSelection == ColourMode::None ) {
244              return Detail::make_unique<NoColourImpl>( stream );
245          }
246  
247          if ( implSelection == ColourMode::PlatformDefault) {
248  #if defined( CATCH_CONFIG_COLOUR_WIN32 )
249              if ( Win32ColourImpl::useImplementationForStream( *stream ) ) {
250                  return Detail::make_unique<Win32ColourImpl>( stream );
251              }
252  #endif
253              if ( ANSIColourImpl::useImplementationForStream( *stream ) ) {
254                  return Detail::make_unique<ANSIColourImpl>( stream );
255              }
256              return Detail::make_unique<NoColourImpl>( stream );
257          }
258  
259          CATCH_ERROR( "Could not create colour impl for selection " << static_cast<int>(implSelection) );
260      }
261  
262      bool isColourImplAvailable( ColourMode colourSelection ) {
263          switch ( colourSelection ) {
264  #if defined( CATCH_CONFIG_COLOUR_WIN32 )
265          case ColourMode::Win32:
266  #endif
267          case ColourMode::ANSI:
268          case ColourMode::None:
269          case ColourMode::PlatformDefault:
270              return true;
271          default:
272              return false;
273          }
274      }
275  
276  
277  } // end namespace Catch
278  
279  #if defined(__clang__)
280  #    pragma clang diagnostic pop
281  #endif
282