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