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

Code before:
    // This if condition serves 2 purposes:
    // * By setting top/bottom to the full height we ensure that we call Present() without
    //   any dirty rects and not Present1() on the first frame after the settings change.
    // * If the scrollOffset is so large that it scrolls the entire viewport, invalidatedRows will span
    //   the entire viewport as well. We need to set scrollOffset to 0 then, not just because scrolling
    //   the contents of the entire swap chain is redundant, but more importantly because the scroll rect
    //   is the subset of the contents that are being scrolled into. If you scroll the entire viewport
    //   then the scroll rect is empty, which Present1() will loudly complain about.
    if (_api.invalidatedRows == range<u16>{ 0, _p.s->cellCount.y })
    {
        _p.MarkAllAsDirty();
    }

    if (const auto offset = _api.scrollOffset)
    {
        if (offset < 0)
        {
            // scrollOffset/offset = -1
            // +----------+    +----------+
            // |          |    | xxxxxxxxx|
            // | xxxxxxxxx| -> |xxxxxxx   |
            // |xxxxxxx   |    |          |
            // +----------+    +----------+
            const auto dst = std::copy_n(_p.rows.begin() - offset, _p.rows.size() + offset, _p.rowsScratch.begin());
            std::copy_n(_p.rows.begin(), -offset, dst);
        }
        else
        {
            // scrollOffset/offset = 1
            // +----------+    +----------+
            // | xxxxxxxxx|    |          |
            // |xxxxxxx   | -> | xxxxxxxxx|
            // |          |    |xxxxxxx   |
            // +----------+    +----------+
            const auto dst = std::copy_n(_p.rows.end() - offset, offset, _p.rowsScratch.begin());
            std::copy_n(_p.rows.begin(), _p.rows.size() - offset, dst);
        }

        std::swap(_p.rows, _p.rowsScratch);

        // Now that the rows have scrolled, their cached dirty rects, naturally also need to do the same.
        // It doesn't really matter that some of these will end up being out of bounds,
        // because we'll call ShapedRow::Clear() later on which resets them.
        {
            const auto deltaPx = offset * _p.s->font->cellSize.y;
            for (const auto r : _p.rows)
            {
                r->dirtyTop += deltaPx;
                r->dirtyBottom += deltaPx;
            }
        }

Review comment (bug):
I sometimes get write access violation here if `-offset` is greater than `_p.rows.size()`.

![Just before the crash](https://user-images.githubusercontent.com/12915102/236096418-8e23e01b-eb77-48a6-8fa5-d7804475922e.png)
![Access violation in memcpy](https://user-images.githubusercontent.com/12915102/236096632-bd33c2bb-15c9-4d99-bcba-054de5071729.png)

Just wanted to check if https://github.com/microsoft/terminal/issues/14082#issuecomment-1533946883 is fixed by this PR, but running `oh-my-posh debug` a few times crashes Terminal with these changes.


Code after:
    _p.scrollOffset = _api.scrollOffset;

    // This if condition serves 2 purposes:
    // * By setting top/bottom to the full height we ensure that we call Present() without
    //   any dirty rects and not Present1() on the first frame after the settings change.
    // * If the scrollOffset is so large that it scrolls the entire viewport, invalidatedRows will span
    //   the entire viewport as well. We need to set scrollOffset to 0 then, not just because scrolling
    //   the contents of the entire swap chain is redundant, but more importantly because the scroll rect
    //   is the subset of the contents that are being scrolled into. If you scroll the entire viewport
    //   then the scroll rect is empty, which Present1() will loudly complain about.
    if (_api.invalidatedRows == range<u16>{ 0, _p.s->cellCount.y })
    {
        _p.MarkAllAsDirty();
    }

    if (const auto offset = _api.scrollOffset)
    {
        if (offset < 0)
        {
            // scrollOffset/offset = -1
            // +----------+    +----------+
            // |          |    | xxxxxxxxx|
            // | xxxxxxxxx| -> |xxxxxxx   |
            // |xxxxxxx   |    |          |
            // +----------+    +----------+
            const auto dst = std::copy_n(_p.rows.begin() - offset, _p.rows.size() + offset, _p.rowsScratch.begin());
            std::copy_n(_p.rows.begin(), -offset, dst);
        }
        else
        {
            // scrollOffset/offset = 1
            // +----------+    +----------+
            // | xxxxxxxxx|    |          |
            // |xxxxxxx   | -> | xxxxxxxxx|
            // |          |    |xxxxxxx   |
            // +----------+    +----------+
            const auto dst = std::copy_n(_p.rows.end() - offset, offset, _p.rowsScratch.begin());
            std::copy_n(_p.rows.begin(), _p.rows.size() - offset, dst);
        }

        std::swap(_p.rows, _p.rowsScratch);

        // Now that the rows have scrolled, their cached dirty rects, naturally also need to do the same.
        // It doesn't really matter that some of these will end up being out of bounds,
        // because we'll call ShapedRow::Clear() later on which resets them.
        {
            const auto deltaPx = offset * _p.s->font->cellSize.y;
            for (const auto r : _p.rows)
            {
                r->dirtyTop += deltaPx;
                r->dirtyBottom += deltaPx;