#include "diagnostics.h"
namespace empathy::diagnostics
{
const char* Renderer::Colors::Reset = "\x1b[0m";
const char* Renderer::Colors::Black = "\x1b[30m";
const char* Renderer::Colors::Red = "\x1b[31m";
const char* Renderer::Colors::Green = "\x1b[32m";
const char* Renderer::Colors::Yellow = "\x1b[33m";
const char* Renderer::Colors::Blue = "\x1b[34m";
const char* Renderer::Colors::Magenta = "\x1b[35m";
const char* Renderer::Colors::Cyan = "\x1b[36m";
const char* Renderer::Colors::White = "\x1b[37m";
const char* Renderer::Colors::BrightBlack = "\x1b[90m";
const char* Renderer::Colors::BrightRed = "\x1b[91m";
const char* Renderer::Colors::BrightGreen = "\x1b[92m";
const char* Renderer::Colors::BrightYellow = "\x1b[93m";
const char* Renderer::Colors::BrightBlue = "\x1b[94m";
const char* Renderer::Colors::BrightMagenta = "\x1b[95m";
const char* Renderer::Colors::BrightCyan = "\x1b[96m";
const char* Renderer::Colors::BrightWhite = "\x1b[97m";
void Renderer::addContext( uint32_t location, uint8_t styleFlags, const char* color )
{
addItem( { "", location, true, styleFlags, color } );
}
void Renderer::addContext( uint32_t location, uint8_t styleFlags, const string& message, const char* color )
{
addItem( { message, location, true, styleFlags, color } );
}
void Renderer::addItem( Item&& item )
{
auto loc = Location::Get( item.location );
if( !item.quoteSource )
{
// No source to quote, that's the simplest case, usually for the first line of the
// error message. Just output the location, if any, followed by the message.
// Flush whatever context infos may be pending.
flushContext();
if( loc )
{
m_output << format( "{brightblack}{}:{}:{}:{reset} ", loc->filename(), loc->line(), loc->column() );
m_lastFilename = loc->filename();
m_lastLineNumber = loc->line();
}
m_output << item.message << '\n';
return;
}
// Try to merge the context information with the pending context information, so that it is displayed
// on the same line. In case of conflict, flush what we have and started a new context line.
bool needFlush = false;
// If there is no pending filename, it means that our pending context have no location.
// So this shouldn't be a factor against merging it.
if( !m_pendingContextFilename.empty() )
{
// We can only merge context that are for the same line and file.
if( loc && m_pendingContextMessageColor && loc->line() != m_pendingContextLineNumber )
needFlush = true;
if( loc && m_pendingContextMessageColor && loc->filename() != m_pendingContextFilename )
needFlush = true;
}
// We can't have two messages on the same context line. However,
// If the existing message has no location, it is meant to override the next message.
if( !item.message.empty() && !m_pendingContextMessage.empty() )
needFlush = !m_pendingContextFilename.empty();
// We can't have two carets on the same context line.
if( ( item.styleFlags & Style::Caret ) && m_pendingCaret )
needFlush = true;
if( !needFlush && loc && !( item.styleFlags & Style::Caret ) )
{
// We can't have overlapping highlighting spans,
// with the exception of the caret, which is allowed to overlap anything.
// Find the first span vertex that is to the right of our start position.
// keep track of whether we are currently inside of a span.
bool inSpan = false;
auto it = m_pendingHighlightSpans.begin();
while( it != m_pendingHighlightSpans.end() )
{
if( !( it->styleFlags & Style::Caret ) )
inSpan = it->color ? true : false;
if( it->column > loc->column() )
break;
++it;
}
// If the starting point is inside a span, we have to flush.
if( inSpan )
needFlush = true;
{
// Now look for the next span vertex that isn't a caret vertex.
while( it != m_pendingHighlightSpans.end() )
{
if( !it->styleFlags & Style::Caret )
break;
++it;
}
// If that vertex is a starting vertex and comes before
// our own end vertex, we need to flush.
if( it != m_pendingHighlightSpans.end()
&& it->column < ( loc->column() + loc->length() ) )
{
needFlush = true;
}
}
}
if( needFlush )
flushContext();
addToPendingContext( loc, item.styleFlags, item.color, item.message );
}
// Add the provided context information to the pending context information.
void Renderer::addToPendingContext( const optional< Location >& loc, uint8_t styleFlags, const char* pColor, const string& message )
{
// Pending color is set to null to indicate that the pending context is clear.
if( !m_pendingContextMessageColor )
{
if( m_pendingContextMessage.empty() )
m_pendingContextMessage = message;
m_pendingContextMessageColor = pColor;
m_pendingCaret = false;
}
m_pendingCaret = m_pendingCaret || ( styleFlags & Style::Caret );
if( !loc )
return;
if( m_pendingContextFilename.empty() )
{
m_pendingContextFilename = loc->filename();
m_pendingContextOffset = loc->offset() - loc->column() + 1;
m_pendingContextLineNumber = loc->line();
m_pendingContextMessageXPosStart = loc->column() - 1;
m_pendingContextMessageXPosEnd = loc->column() - 1 + loc->length();
m_pendingContextMessageColor = pColor;
}
m_pendingHighlightSpans.emplace_back( SpanVertex{ loc->column(), styleFlags, pColor } );
m_pendingHighlightSpans.emplace_back( SpanVertex{ loc->column() + loc->length(), styleFlags, nullptr } );
sort( m_pendingHighlightSpans.begin(), m_pendingHighlightSpans.end(),
[]( auto&& a, auto&& b )
{
return a.column < b.column;
} );
}
// Render the pending context line and clear the pending context state.
void Renderer::flushContext()
{
if( !m_pendingContextMessageColor )
return;
// If we flush a context that had no location,
// just print it as is.
if( m_pendingContextFilename.empty() )
{
applyColor( m_pendingContextMessageColor );
m_output << m_pendingContextMessage << '\n';
m_pendingContextFilename.clear();
m_pendingContextMessage.clear();
m_pendingContextMessageColor = nullptr;
m_pendingCaret = false;
m_pendingHighlightSpans.clear();
return;
}
// Fetch the line from the file
ifstream sourcefile( m_pendingContextFilename );
if( sourcefile.good() )
{
if( m_pendingContextFilename != m_lastFilename )
m_output << format( "{brightblack}{}:{reset}\n", m_pendingContextFilename );
else if( m_pendingContextLineNumber != m_lastLineNumber && m_pendingContextLineNumber != ( m_lastLineNumber + 1 ) )
m_output << format( "{brightblack} ...\n{reset}" );
m_lastFilename = m_pendingContextFilename;
m_lastLineNumber = m_pendingContextLineNumber;
m_output << format( "{brightblack}{:>5} | {reset}", m_pendingContextLineNumber );
sourcefile.seekg( m_pendingContextOffset, ios_base::beg );
string line;
getline( sourcefile, line );
string_view sview( line );
stringstream decoration;
char decChar = ' ';
const char* pCurrentColor = Colors::Reset;
bool inCaretSpan = false;
bool atCaret = false;
uint32_t start = 0;
for( auto&& sv : m_pendingHighlightSpans )
{
// Output the span of the source string up to that highlight vertex
m_output << string( sview.substr( start, sv.column - 1 - start ) );
uint32_t count = min( sv.column - 1UL - start, sview.size() - start );
// Generate decorations
if( atCaret )
{
decoration << '^';
if( count )
--count;
atCaret = false;
}
while( count-- )
decoration << decChar;
start = sv.column - 1;
if( sv.styleFlags & Style::Squiggles )
decChar = sv.color ? '~' : ' ';
if( sv.styleFlags & Style::Caret )
{
if( sv.color )
{
inCaretSpan = true;
if( sv.styleFlags & Style::Highlight )
applyColor( sv.color );
if( m_output.has_colors() )
decoration << sv.color;
atCaret = true;
}
else
{
inCaretSpan = false;
if( sv.styleFlags & Style::Highlight )
applyColor( pCurrentColor );
if( m_output.has_colors() )
decoration << pCurrentColor;
}
continue;
}
if( !sv.color )
pCurrentColor = Colors::Reset;
else
pCurrentColor = sv.color;
if( !inCaretSpan )
{
if( sv.styleFlags & Style::Highlight )
applyColor( pCurrentColor );
if( m_output.has_colors() )
decoration << pCurrentColor;
}
}
if( start <= sview.size() )
m_output << string( sview.substr( start, sview.size() - start ) ) << '\n';
else
m_output << '\n';
auto decStr = decoration.str();
if( !decStr.empty() )
m_output << format( "{brightblack} | {reset}{}\n", decStr );
if( !m_pendingContextMessage.empty() )
{
m_output << format( "{brightblack} | {reset}" );
applyColor( m_pendingContextMessageColor );
int32_t maxpos = min( m_pendingContextMessageXPosEnd, static_cast< uint32_t >( line.size() ) );
int32_t xpos = maxpos - m_pendingContextMessageXPosStart;
xpos -= m_pendingContextMessage.size();
xpos /= 2;
uint32_t count = max( xpos + static_cast< int32_t >( m_pendingContextMessageXPosStart ), 0 );
while( count-- )
m_output << ' ';
m_output << m_pendingContextMessage << '\n';
applyColor( Colors::Reset );
}
}
m_pendingContextFilename.clear();
m_pendingContextMessage.clear();
m_pendingContextMessageColor = nullptr;
m_pendingCaret = false;
m_pendingHighlightSpans.clear();
}
void Renderer::applyColor( const char* pColor )
{
if( m_output.has_colors() )
m_output << pColor;
}
}