Goose  Artifact [8ba0fe5b6d]

Artifact 8ba0fe5b6d3c18a5bac8e7b93eddb872f6cae4fb73493feaa573706a71d62f99:

  • File bs/diagnostics/renderer.cpp — part of check-in [48a020a1fa] at 2019-08-17 14:49:29 on branch trunk —
    • Intrinsics automatically set their domain depending on the domain restrictions of the builtin types that they use.
    • Declaring a local variable of a compile-time only type is now properly rejected during codegen.
    • Improved error messages for operators and extension point invocations.
    (user: achavasse size: 12034)

#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;
    }
}