Repository: microsoft/terminal
File: src/buffer/out/textBuffer.cpp (C++)

Code before:
    TriggerRedraw(Viewport::FromExclusive({ state.columnBeginDirty, row, state.columnEndDirty, row + 1 }));
}

void TextBuffer::Insert(til::CoordType row, const TextAttribute& attributes, RowWriteState& state)
{
    auto& r = GetMutableRowByOffset(row);
    auto& scratch = GetScratchpadRow();

    scratch.CopyFrom(r);

    r.ReplaceText(state);
    r.ReplaceAttributes(state.columnBegin, state.columnEnd, attributes);

    // Restore trailing text from our backup in scratch.
    RowWriteState restoreState{
        .text = scratch.GetText(state.columnBegin, state.columnLimit),
        .columnBegin = state.columnEnd,
        .columnLimit = state.columnLimit,
    };
    r.ReplaceText(restoreState);

    // Restore trailing attributes as well.
    auto& rowAttr = r.Attributes();
    const auto& scratchAttr = scratch.Attributes();
    const auto restoreAttr = scratchAttr.slice(gsl::narrow<uint16_t>(state.columnBegin), gsl::narrow<uint16_t>(state.columnLimit));
    rowAttr.replace(gsl::narrow<uint16_t>(state.columnEnd), gsl::narrow<uint16_t>(state.columnEnd + restoreAttr.size()), restoreAttr);

    TriggerRedraw(Viewport::FromExclusive({ state.columnBeginDirty, row, restoreState.columnEndDirty, row + 1 }));
}

// Fills an area of the buffer with a given fill character(s) and attributes.
void TextBuffer::FillRect(const til::rect& rect, const std::wstring_view& fill, const TextAttribute& attributes)
{
    if (!rect || fill.empty())
    {
        return;
    }

    auto& scratchpad = GetScratchpadRow(attributes);

    // The scratchpad row gets reset to whitespace by default, so there's no need to
    // initialize it again. Filling with whitespace is the most common operation by far.
    if (fill != L" ")
    {
        RowWriteState state{
            .columnLimit = rect.right,
            .columnEnd = rect.left,
        };

        // Fill the scratchpad row with consecutive copies of "fill" up to the amount we need.
        //

Review comment (suggestion):
@DHowett Ah no Dustin, this isn't quite correct. You see...

If you had a 6 column row and the contents were
```
woooo
012356
```

and you add "lol" at 2, like
```
wololo
0123456
```

![image](https://github.com/microsoft/terminal/assets/2256941/6b0f95bf-f49a-42c1-b25a-38c16e770451)

it should copy attributes `[5,6)` ("o"), but I pass I pass `[columnBegin,columnLimit)` which is `[2,6)` ("ooo"). Before I tried adding support for margins this worked, because I finished it up with a `resize_trailing_extent` which fixed this up, but now we can't do this anymore.

What I should do is copy `restoreState.columnBegin,restoreState.columnEnd)` which should be immediately obvious because that's literally the exact same thing we pass to `ReplaceAttributes` above!

I'll write tests, as I should've always done...

Code after:
}

void TextBuffer::Insert(til::CoordType row, const TextAttribute& attributes, RowWriteState& state)
{
    auto& r = GetMutableRowByOffset(row);
    auto& scratch = GetScratchpadRow();

    scratch.CopyFrom(r);

    r.ReplaceText(state);
    r.ReplaceAttributes(state.columnBegin, state.columnEnd, attributes);

    // Restore trailing text from our backup in scratch.
    RowWriteState restoreState{
        .text = scratch.GetText(state.columnBegin, state.columnLimit),
        .columnBegin = state.columnEnd,
        .columnLimit = state.columnLimit,
    };
    r.ReplaceText(restoreState);

    // Restore trailing attributes as well.
    if (const auto copyAmount = restoreState.columnEnd - restoreState.columnBegin; copyAmount > 0)
    {
        auto& rowAttr = r.Attributes();
        const auto& scratchAttr = scratch.Attributes();
        const auto restoreAttr = scratchAttr.slice(gsl::narrow<uint16_t>(state.columnBegin), gsl::narrow<uint16_t>(state.columnBegin + copyAmount));
        rowAttr.replace(gsl::narrow<uint16_t>(restoreState.columnBegin), gsl::narrow<uint16_t>(restoreState.columnEnd), restoreAttr);
    }

    TriggerRedraw(Viewport::FromExclusive({ state.columnBeginDirty, row, restoreState.columnEndDirty, row + 1 }));
}

// Fills an area of the buffer with a given fill character(s) and attributes.
void TextBuffer::FillRect(const til::rect& rect, const std::wstring_view& fill, const TextAttribute& attributes)
{
    if (!rect || fill.empty())
    {
        return;
    }

    auto& scratchpad = GetScratchpadRow(attributes);

    // The scratchpad row gets reset to whitespace by default, so there's no need to
    // initialize it again. Filling with whitespace is the most common operation by far.
    if (fill != L" ")
    {
        RowWriteState state{
            .columnLimit = rect.right,
            .columnEnd = rect.left,
        };