2022-05-13 13:12:05 +00:00
// Copyright (C) 2019 The Qt Company Ltd.
2023-12-08 15:16:30 +00:00
// SPDX-License-Identifier: LicenseRef-Qt-Commercial
2019-06-14 12:21:25 +00:00
2021-03-25 14:34:42 +00:00
# include "qcoloroutput_p.h"
2019-11-11 17:18:04 +00:00
# include <QtCore/qfile.h>
# include <QtCore/qhash.h>
2019-06-14 12:21:25 +00:00
# ifndef Q_OS_WIN
# include <unistd.h>
# endif
2021-11-22 12:41:14 +00:00
QT_BEGIN_NAMESPACE
2021-03-25 14:34:42 +00:00
class QColorOutputPrivate
2019-06-14 12:21:25 +00:00
{
public :
2021-11-18 13:26:29 +00:00
QColorOutputPrivate ( )
2019-06-14 12:21:25 +00:00
{
2019-11-11 17:18:04 +00:00
/* - QIODevice::Unbuffered because we want it to appear when the user actually calls,
* performance is considered of lower priority .
2019-06-14 12:21:25 +00:00
*/
m_out . open ( stderr , QIODevice : : WriteOnly | QIODevice : : Unbuffered ) ;
2019-11-11 17:18:04 +00:00
m_coloringEnabled = isColoringPossible ( ) ;
2019-06-14 12:21:25 +00:00
}
static const char * const foregrounds [ ] ;
static const char * const backgrounds [ ] ;
2019-11-11 17:18:04 +00:00
inline void write ( const QString & msg ) { m_out . write ( msg . toLocal8Bit ( ) ) ; }
2019-06-14 12:21:25 +00:00
static QString escapeCode ( const QString & in )
{
2019-11-11 17:18:04 +00:00
const ushort escapeChar = 0x1B ;
2019-06-14 12:21:25 +00:00
QString result ;
2019-11-11 17:18:04 +00:00
result . append ( QChar ( escapeChar ) ) ;
2019-06-14 12:21:25 +00:00
result . append ( QLatin1Char ( ' [ ' ) ) ;
result . append ( in ) ;
result . append ( QLatin1Char ( ' m ' ) ) ;
return result ;
}
2021-03-25 14:34:42 +00:00
void insertColor ( int id , QColorOutput : : ColorCode code ) { m_colorMapping . insert ( id , code ) ; }
QColorOutput : : ColorCode color ( int id ) const { return m_colorMapping . value ( id ) ; }
2019-11-11 17:18:04 +00:00
bool containsColor ( int id ) const { return m_colorMapping . contains ( id ) ; }
2021-11-18 13:26:29 +00:00
void setSilent ( bool silent ) { m_silent = silent ; }
2019-11-11 17:18:04 +00:00
bool isSilent ( ) const { return m_silent ; }
2021-11-18 13:26:29 +00:00
2019-11-11 17:18:04 +00:00
void setCurrentColorID ( int colorId ) { m_currentColorID = colorId ; }
bool coloringEnabled ( ) const { return m_coloringEnabled ; }
2019-06-14 12:21:25 +00:00
private :
QFile m_out ;
2021-03-25 14:34:42 +00:00
QColorOutput : : ColorMapping m_colorMapping ;
2021-11-18 13:26:29 +00:00
int m_currentColorID = - 1 ;
bool m_coloringEnabled = false ;
bool m_silent = false ;
2019-06-14 12:21:25 +00:00
/*!
Returns true if it ' s suitable to send colored output to \ c stderr .
*/
inline bool isColoringPossible ( ) const
{
2019-11-11 17:18:04 +00:00
# if defined(Q_OS_WIN)
/* Windows doesn't at all support ANSI escape codes, unless
* the user install a " device driver " . See the Wikipedia links in the
* class documentation for details . */
return false ;
# else
/* We use QFile::handle() to get the file descriptor. It's a bit unsure
* whether it ' s 2 on all platforms and in all cases , so hopefully this layer
* of abstraction helps handle such cases . */
return isatty ( m_out . handle ( ) ) ;
# endif
2019-06-14 12:21:25 +00:00
}
} ;
2021-03-25 14:34:42 +00:00
const char * const QColorOutputPrivate : : foregrounds [ ] =
2019-06-14 12:21:25 +00:00
{
" 0;30 " ,
" 0;34 " ,
" 0;32 " ,
" 0;36 " ,
" 0;31 " ,
" 0;35 " ,
" 0;33 " ,
" 0;37 " ,
" 1;30 " ,
" 1;34 " ,
" 1;32 " ,
" 1;36 " ,
" 1;31 " ,
" 1;35 " ,
" 1;33 " ,
" 1;37 "
} ;
2021-03-25 14:34:42 +00:00
const char * const QColorOutputPrivate : : backgrounds [ ] =
2019-06-14 12:21:25 +00:00
{
" 0;40 " ,
" 0;44 " ,
" 0;42 " ,
" 0;46 " ,
" 0;41 " ,
" 0;45 " ,
" 0;43 "
} ;
/*!
\ class ColorOutput
\ nonreentrant
\ brief Outputs colored messages to \ c stderr .
\ internal
ColorOutput is a convenience class for outputting messages to \ c
stderr using color escape codes , as mandated in ECMA - 48. ColorOutput
will only color output when it is detected to be suitable . For
instance , if \ c stderr is detected to be attached to a file instead
of a TTY , no coloring will be done .
ColorOutput does its best attempt . but it is generally undefined
what coloring or effect the various coloring flags has . It depends
strongly on what terminal software that is being used .
When using ` echo - e ' my escape sequence ' ` , \ c { \ 033 } works as an
initiator but not when printing from a C + + program , despite having
escaped the backslash . That ' s why we below use characters with
value 0x1B .
It can be convenient to subclass ColorOutput with a private scope ,
such that the functions are directly available in the class using
it .
\ section1 Usage
To output messages , call write ( ) or writeUncolored ( ) . write ( ) takes
as second argument an integer , which ColorOutput uses as a lookup
key to find the color it should color the text in . The mapping from
keys to colors is done using insertMapping ( ) . Typically this is used
by having enums for the various kinds of messages , which
subsequently are registered .
\ code
enum MyMessage
{
Error ,
Important
} ;
ColorOutput output ;
output . insertMapping ( Error , ColorOutput : : RedForeground ) ;
output . insertMapping ( Import , ColorOutput : : BlueForeground ) ;
output . write ( " This is important " , Important ) ;
output . write ( " Jack, I'm only the selected official! " , Error ) ;
\ endcode
\ sa { http : //tldp.org/HOWTO/Bash-Prompt-HOWTO/x329.html}{Bash Prompt HOWTO, 6.1. Colors},
{ http : //linuxgazette.net/issue51/livingston-blade.html}{Linux Gazette, Tweaking Eterm, Edward Livingston-Blade},
{ http : //www.ecma-international.org/publications/standards/Ecma-048.htm}{Standard ECMA-48, Control Functions for Coded Character Sets, ECMA International},
{ http : //en.wikipedia.org/wiki/ANSI_escape_code}{Wikipedia, ANSI escape code},
{ http : //linuxgazette.net/issue65/padala.html}{Linux Gazette, So You Like Color!, Pradeep Padala}
*/
/*!
\ enum ColorOutput : : ColorCodeComponent
\ value BlackForeground
\ value BlueForeground
\ value GreenForeground
\ value CyanForeground
\ value RedForeground
\ value PurpleForeground
\ value BrownForeground
\ value LightGrayForeground
\ value DarkGrayForeground
\ value LightBlueForeground
\ value LightGreenForeground
\ value LightCyanForeground
\ value LightRedForeground
\ value LightPurpleForeground
\ value YellowForeground
\ value WhiteForeground
\ value BlackBackground
\ value BlueBackground
\ value GreenBackground
\ value CyanBackground
\ value RedBackground
\ value PurpleBackground
\ value BrownBackground
\ value DefaultColor ColorOutput performs no coloring . This typically
means black on white or white on black , depending
on the settings of the user ' s terminal .
*/
/*!
Constructs a ColorOutput instance , ready for use .
*/
2021-11-18 13:26:29 +00:00
QColorOutput : : QColorOutput ( ) : d ( new QColorOutputPrivate ) { }
2019-06-14 12:21:25 +00:00
2019-11-11 17:18:04 +00:00
// must be here so that QScopedPointer has access to the complete type
2021-03-25 14:34:42 +00:00
QColorOutput : : ~ QColorOutput ( ) = default ;
2019-06-14 12:21:25 +00:00
2021-11-18 13:26:29 +00:00
bool QColorOutput : : isSilent ( ) const { return d - > isSilent ( ) ; }
void QColorOutput : : setSilent ( bool silent ) { d - > setSilent ( silent ) ; }
2019-06-14 12:21:25 +00:00
/*!
2019-11-11 17:18:04 +00:00
Sends \ a message to \ c stderr , using the color looked up in the color mapping using \ a colorID .
2019-06-14 12:21:25 +00:00
2019-11-11 17:18:04 +00:00
If \ a color isn ' t available in the color mapping , result and behavior is undefined .
2019-06-14 12:21:25 +00:00
If \ a colorID is 0 , which is the default value , the previously used coloring is used . ColorOutput
is initialized to not color at all .
If \ a message is empty , effects are undefined .
\ a message will be printed as is . For instance , no line endings will be inserted .
*/
2021-03-25 14:34:42 +00:00
void QColorOutput : : write ( QStringView message , int colorID )
2019-06-14 12:21:25 +00:00
{
2019-11-11 17:18:04 +00:00
if ( ! d - > isSilent ( ) )
2019-09-19 09:15:47 +00:00
d - > write ( colorify ( message , colorID ) ) ;
2019-06-14 12:21:25 +00:00
}
2021-03-25 14:34:42 +00:00
void QColorOutput : : writePrefixedMessage ( const QString & message , QtMsgType type ,
2020-10-06 10:52:42 +00:00
const QString & prefix )
{
2021-03-25 14:34:42 +00:00
static const QHash < QtMsgType , QString > prefixes = {
{ QtMsgType : : QtCriticalMsg , QStringLiteral ( " Error " ) } ,
{ QtMsgType : : QtWarningMsg , QStringLiteral ( " Warning " ) } ,
{ QtMsgType : : QtInfoMsg , QStringLiteral ( " Info " ) } ,
{ QtMsgType : : QtDebugMsg , QStringLiteral ( " Hint " ) }
2020-10-06 10:52:42 +00:00
} ;
2021-03-25 14:34:42 +00:00
Q_ASSERT ( prefixes . contains ( type ) ) ;
2020-10-06 10:52:42 +00:00
Q_ASSERT ( prefix . isEmpty ( ) | | prefix . front ( ) . isUpper ( ) ) ;
write ( ( prefix . isEmpty ( ) ? prefixes [ type ] : prefix ) + QStringLiteral ( " : " ) , type ) ;
writeUncolored ( message ) ;
}
2019-06-14 12:21:25 +00:00
/*!
Writes \ a message to \ c stderr as if for instance
QTextStream would have been used , and adds a line ending at the end .
This function can be practical to use such that one can use ColorOutput for all forms of writing .
*/
2021-03-25 14:34:42 +00:00
void QColorOutput : : writeUncolored ( const QString & message )
2019-06-14 12:21:25 +00:00
{
2019-11-11 17:18:04 +00:00
if ( ! d - > isSilent ( ) )
2019-09-19 09:15:47 +00:00
d - > write ( message + QLatin1Char ( ' \n ' ) ) ;
2019-06-14 12:21:25 +00:00
}
/*!
Treats \ a message and \ a colorID identically to write ( ) , but instead of writing
\ a message to \ c stderr , it is prepared for being written to \ c stderr , but is then
returned .
This is useful when the colored string is inserted into a translated string ( dividing
the string into several small strings prevents proper translation ) .
*/
2021-03-25 14:34:42 +00:00
QString QColorOutput : : colorify ( const QStringView message , int colorID ) const
2019-06-14 12:21:25 +00:00
{
2019-11-11 17:18:04 +00:00
Q_ASSERT_X ( colorID = = - 1 | | d - > containsColor ( colorID ) , Q_FUNC_INFO ,
qPrintable ( QString : : fromLatin1 ( " There is no color registered by id %1 " )
. arg ( colorID ) ) ) ;
Q_ASSERT_X ( ! message . isEmpty ( ) , Q_FUNC_INFO ,
" It makes no sense to attempt to print an empty string. " ) ;
2019-06-14 12:21:25 +00:00
if ( colorID ! = - 1 )
2019-11-11 17:18:04 +00:00
d - > setCurrentColorID ( colorID ) ;
2019-06-14 12:21:25 +00:00
2019-11-11 17:18:04 +00:00
if ( d - > coloringEnabled ( ) & & colorID ! = - 1 ) {
const int color = d - > color ( colorID ) ;
2019-06-14 12:21:25 +00:00
/* If DefaultColor is set, we don't want to color it. */
if ( color & DefaultColor )
2021-03-25 16:24:24 +00:00
return message . toString ( ) ;
2019-06-14 12:21:25 +00:00
2019-11-11 17:18:04 +00:00
const int foregroundCode = ( color & ForegroundMask ) > > ForegroundShift ;
const int backgroundCode = ( color & BackgroundMask ) > > BackgroundShift ;
2019-06-14 12:21:25 +00:00
QString finalMessage ;
bool closureNeeded = false ;
2019-11-11 17:18:04 +00:00
if ( foregroundCode > 0 ) {
finalMessage . append (
2021-03-25 14:34:42 +00:00
QColorOutputPrivate : : escapeCode (
QLatin1String ( QColorOutputPrivate : : foregrounds [ foregroundCode - 1 ] ) ) ) ;
2019-06-14 12:21:25 +00:00
closureNeeded = true ;
}
2019-11-11 17:18:04 +00:00
if ( backgroundCode > 0 ) {
finalMessage . append (
2021-03-25 14:34:42 +00:00
QColorOutputPrivate : : escapeCode (
QLatin1String ( QColorOutputPrivate : : backgrounds [ backgroundCode - 1 ] ) ) ) ;
2019-06-14 12:21:25 +00:00
closureNeeded = true ;
}
finalMessage . append ( message ) ;
if ( closureNeeded )
2021-03-25 14:34:42 +00:00
finalMessage . append ( QColorOutputPrivate : : escapeCode ( QLatin1String ( " 0 " ) ) ) ;
2019-06-14 12:21:25 +00:00
return finalMessage ;
}
2019-11-11 17:18:04 +00:00
2021-03-25 16:24:24 +00:00
return message . toString ( ) ;
2019-06-14 12:21:25 +00:00
}
/*!
Adds a color mapping from \ a colorID to \ a colorCode , for this ColorOutput instance .
*/
2021-03-25 14:34:42 +00:00
void QColorOutput : : insertMapping ( int colorID , const ColorCode colorCode )
2019-06-14 12:21:25 +00:00
{
2019-11-11 17:18:04 +00:00
d - > insertColor ( colorID , colorCode ) ;
2019-06-14 12:21:25 +00:00
}
2021-11-22 12:41:14 +00:00
QT_END_NAMESPACE