Repository: microsoft/terminal (language: cpp)

Issue:
Incorrect DECCRA behavior when copying from a double-width line
### Windows Terminal version

1.17.1023

### Windows build number

10.0.19044.2364

### Other Software

_No response_

### Steps to reproduce

1. Open a WSL bash shell in Windows Terminal.
2. Make sure the viewport width is around 80 columns.
3. Execute the following command:
    
        printf "\e#8\e[8H\e[J\e[4H\e[41m\e#6\e[m\e[H\e[3;20;5;60;1;10;20;1\$v"

### Expected Behavior

That test case is copying a block of text spanning a double-width row from the top to the bottom half of the screen. The copied block should look something like this (the gap is from the section of the double-width row that is off screen).

![image](https://user-images.githubusercontent.com/4181424/226184467-91e4efb4-29be-4faf-9c75-26f398d44f22.png)

### Actual Behavior

The copied block ends up looking like this:

![image](https://user-images.githubusercontent.com/4181424/226184696-f162812b-91b1-4164-b1a0-022947276e65.png)

This is a regression that was introduced in PR #14650 when I was trying to fix horizontal scrolling of DBCS characters. The copy operation reads two characters in advance, and the code that checks whether the source is off screen ends up using the cursor position from the wrong character.

This can be fixed by saving the current `srcPos` at the same time as we save the current `OutputCell` in the loop here:

https://github.com/microsoft/terminal/blob/b9248fa903eaa8d187c72bf08f8aa17b25f9d6bb/src/terminal/adapter/adaptDispatch.cpp#L1155-L1163


Discussion / hints:

Fix patch:
diff --git a/src/host/ut_host/ScreenBufferTests.cpp b/src/host/ut_host/ScreenBufferTests.cpp
index 92593b013f2..668a82fcc6e 100644
--- a/src/host/ut_host/ScreenBufferTests.cpp
+++ b/src/host/ut_host/ScreenBufferTests.cpp
@@ -252,6 +252,7 @@ class ScreenBufferTests
     TEST_METHOD(TestDeferredMainBufferResize);
 
     TEST_METHOD(RectangularAreaOperations);
+    TEST_METHOD(CopyDoubleWidthRectangularArea);
 
     TEST_METHOD(DelayedWrapReset);
 };
@@ -7548,6 +7549,57 @@ void ScreenBufferTests::RectangularAreaOperations()
     VERIFY_IS_TRUE(_ValidateLinesContain(targetArea.right, targetArea.top, targetArea.bottom, bufferChar, bufferAttr));
 }
 
+void ScreenBufferTests::CopyDoubleWidthRectangularArea()
+{
+    auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
+    auto& si = gci.GetActiveOutputBuffer().GetActiveBuffer();
+    auto& stateMachine = si.GetStateMachine();
+    auto& textBuffer = si.GetTextBuffer();
+    WI_SetFlag(si.OutputMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING);
+
+    // Fill the entire buffer with Zs. Blue on Green and Underlined.
+    const auto bufferChar = L'Z';
+    const auto bufferHeight = si.GetBufferSize().Height();
+    auto bufferAttr = TextAttribute{ FOREGROUND_BLUE | BACKGROUND_GREEN };
+    bufferAttr.SetUnderlined(true);
+    _FillLines(0, bufferHeight, bufferChar, bufferAttr);
+
+    // Fill the first three lines with Cs. Green on Red and Intense.
+    const auto copyChar = L'C';
+    auto copyAttr = TextAttribute{ FOREGROUND_GREEN | BACKGROUND_RED };
+    copyAttr.SetIntense(true);
+    _FillLines(0, 3, copyChar, copyAttr);
+
+    // Set the active attributes to Red on Blue;
+    auto activeAttr = TextAttribute{ FOREGROUND_RED | BACKGROUND_BLUE };
+    si.SetAttributes(activeAttr);
+
+    // Make the second line (offset 1) double width.
+    textBuffer.GetCursor().SetPosition({ 0, 1 });
+    textBuffer.SetCurrentLineRendition(LineRendition::DoubleWidth);
+
+    // Copy a segment of the top three lines with DECCRA.
+    stateMachine.ProcessString(L"\033[1;31;3;50;1;4;31;1$v");
+
+    Log::Comment(L"The first 30 columns of the target area should remain unchanged");
+    const auto thirtyBufferChars = std::wstring(30, bufferChar);
+    VERIFY_IS_TRUE(_ValidateLinesContain(3, 6, thirtyBufferChars, bufferAttr));
+
+    Log::Comment(L"The next 20 columns should contain the copied content");
+    const auto twentyCopyChars = std::wstring(20, copyChar);
+    VERIFY_IS_TRUE(_ValidateLineContains({ 30, 3 }, twentyCopyChars, copyAttr));
+    VERIFY_IS_TRUE(_ValidateLineContains({ 30, 5 }, twentyCopyChars, copyAttr));
+
+    Log::Comment(L"But the second target row ends after 10, because of the double-width source");
+    const auto tenCopyChars = std::wstring(10, copyChar);
+    VERIFY_IS_TRUE(_ValidateLineContains({ 30, 4 }, tenCopyChars, copyAttr));
+
+    Log::Comment(L"The subsequent columns in each row should remain unchanged");
+    VERIFY_IS_TRUE(_ValidateLineContains({ 50, 3 }, bufferChar, bufferAttr));
+    VERIFY_IS_TRUE(_ValidateLineContains({ 40, 4 }, bufferChar, bufferAttr));
+    VERIFY_IS_TRUE(_ValidateLineContains({ 50, 5 }, bufferChar, bufferAttr));
+}
+
 void ScreenBufferTests::DelayedWrapReset()
 {
     BEGIN_TEST_METHOD_PROPERTIES()
diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp
index b3937d9d433..78a27e25f09 100644
--- a/src/terminal/adapter/adaptDispatch.cpp
+++ b/src/terminal/adapter/adaptDispatch.cpp
@@ -1153,11 +1153,12 @@ bool AdaptDispatch::CopyRectangularArea(const VTInt top, const VTInt left, const
         do
         {
             const auto current = next;
+            const auto currentSrcPos = srcPos;
             srcView.WalkInBounds(srcPos, walkDirection);
             next = OutputCell(*textBuffer.GetCellDataAt(srcPos));
             // If the source position is offscreen (which can occur on double
             // width lines), then we shouldn't copy anything to the destination.
-            if (srcPos.x < textBuffer.GetLineWidth(srcPos.y))
+            if (currentSrcPos.x < textBuffer.GetLineWidth(currentSrcPos.y))
             {
                 textBuffer.WriteLine(OutputCellIterator({ &current, 1 }), dstPos);
             }


Test patch: