Repository: microsoft/terminal
File: src/renderer/base/renderer.cpp (C++)

Code before:
            // Reset our flag to know when we're in the special circumstance
            // of attempting to draw only the right-half of a two-column character
            // as the first item in our run.
            auto trimLeft = false;

            // Run contains wide character (>1 columns)
            auto containsWideCharacter = false;

            // This inner loop will accumulate clusters until the color changes.
            // When the color changes, it will save the new color off and break.
            // We also accumulate clusters according to regex patterns
            do
            {
                til::point thisPoint{ screenPoint.x + cols, screenPoint.y };
                const auto thisPointPatterns = _pData->GetPatternId(thisPoint);
                const auto thisUsingSoftFont = s_IsSoftFontChar(it->Chars(), _firstSoftFontChar, _lastSoftFontChar);
                const auto changedPatternOrFont = patternIds != thisPointPatterns || usingSoftFont != thisUsingSoftFont;
                auto origAttr = it->TextAttr();
                for (auto highlight : highlights)
                {
                    if (thisPoint.y == highlight.top && thisPoint.x < highlight.right && thisPoint.x >= highlight.left)
                    {
                        origAttr.SetBackground(0x003296ff);
                        origAttr.SetForeground(0x000000);
                    }
                }

                if (color != origAttr || changedPatternOrFont)
                {
                    auto newAttr{ origAttr };
                    // foreground doesn't matter for runs of spaces (!)
                    // if we trick it . . . we call Paint far fewer times for cmatrix
                    if (!_IsAllSpaces(it->Chars()) || !newAttr.HasIdenticalVisualRepresentationForBlankSpace(color, globalInvert) || changedPatternOrFont)
                    {
                        color = newAttr;
                        patternIds = thisPointPatterns;
                        usingSoftFont = thisUsingSoftFont;
                        break; // vend this run
                    }
                }

                // Walk through the text data and turn it into rendering clusters.
                // Keep the columnCount as we go to improve performance over digging it out of the vector at the end.
                auto columnCount = it->Columns();

                // If we're on the first cluster to be added and it's marked as "trailing"
                // (a.k.a. the right half of a two column character), then we need some special handling.
                if (_clusterBuffer.empty() && it->DbcsAttr() == DbcsAttribute::Trailing)
                {
                    // Move left to the one so the whole character can be struck correctly.
                    --screenPoint.x;

Review comment (performance):
Going through the highlights for each line of text seems fairly expensive. When I wrote this:
> Should we perhaps do the same? Get rid of` _drawSearchSelections` and just manipulate the background/foreground colors of the highlighted text (see second half of `AtlasEngine::PaintBufferLine`)?

I meant that we could simply change `AtlasEngine::PaintSelections` to look like this (untested pseudocode):
```cpp
[[nodiscard]] HRESULT AtlasEngine::PaintSelections(const std::vector<til::rect>& rects) noexcept
try
{
    // Unfortunately there's no step after Renderer::_PaintBufferOutput that
    // would inform us that it's done with the last AtlasEngine::PaintBufferLine.
    // As such we got to call _flushBufferLine() here just to be sure.
    _flushBufferLine();

    for (const auto& rect : rects)
    {
        const auto y = gsl::narrow_cast<u16>(clamp<til::CoordType>(rect.top, 0, _p.s->viewportCellCount.y));
        const auto from = gsl::narrow_cast<u16>(clamp<til::CoordType>(rect.left, 0, _p.s->viewportCellCount.x - 1));
        const auto to = gsl::narrow_cast<u16>(clamp<til::CoordType>(rect.right, from, _p.s->viewportCellCount.x));

        if (rect.bottom <= 0 || rect.top >= _p.s->viewportCellCount.y)
        {
            continue;
        }

        const auto bg = &_p.backgroundBitmap[_p.colorBitmapRowStride * y];
        const auto fg = &_p.foregroundBitmap[_p.colorBitmapRowStride * y];
        std::fill(bg + from, bg + to, 0xff3296ff);
        std::fill(fg + from, fg + to, 0xff000000);
    }
    return S_OK;
}
CATCH_RETURN()
```

and remove all other modifications to `AtlasEngine`. The benefit of this approach is that it directly "decompresses" your selections into the background/foreground color bitmaps.

What do you think about this?

Code after:

            // This inner loop will accumulate clusters until the color changes.
            // When the color changes, it will save the new color off and break.
            // We also accumulate clusters according to regex patterns
            do
            {
                til::point thisPoint{ screenPoint.x + cols, screenPoint.y };
                const auto thisPointPatterns = _pData->GetPatternId(thisPoint);
                const auto thisUsingSoftFont = s_IsSoftFontChar(it->Chars(), _firstSoftFontChar, _lastSoftFontChar);
                const auto changedPatternOrFont = patternIds != thisPointPatterns || usingSoftFont != thisUsingSoftFont;
                if (color != it->TextAttr() || changedPatternOrFont)
                {
                    auto newAttr{ it->TextAttr() };
                    // foreground doesn't matter for runs of spaces (!)
                    // if we trick it . . . we call Paint far fewer times for cmatrix
                    if (!_IsAllSpaces(it->Chars()) || !newAttr.HasIdenticalVisualRepresentationForBlankSpace(color, globalInvert) || changedPatternOrFont)
                    {
                        color = newAttr;
                        patternIds = thisPointPatterns;
                        usingSoftFont = thisUsingSoftFont;
                        break; // vend this run
                    }
                }

                // Walk through the text data and turn it into rendering clusters.
                // Keep the columnCount as we go to improve performance over digging it out of the vector at the end.
                auto columnCount = it->Columns();

                // If we're on the first cluster to be added and it's marked as "trailing"
                // (a.k.a. the right half of a two column character), then we need some special handling.
                if (_clusterBuffer.empty() && it->DbcsAttr() == DbcsAttribute::Trailing)
                {
                    // Move left to the one so the whole character can be struck correctly.
                    --screenPoint.x;
                    // And tell the next function to trim off the left half of it.
                    trimLeft = true;
                    // And add one to the number of columns we expect it to take as we insert it.
                    ++columnCount;
                }

                if (columnCount > 1)
                {
                    containsWideCharacter = true;
                }

                // Advance the cluster and column counts.
                _clusterBuffer.emplace_back(it->Chars(), columnCount);
                it += std::max(it->Columns(), 1); // prevent infinite loop for no visible columns
                cols += columnCount;

            } while (it);