diff --git a/.github/actions/spelling/allow/allow.txt b/.github/actions/spelling/allow/allow.txt index f6c03606a58..d5f5f9c2a73 100644 --- a/.github/actions/spelling/allow/allow.txt +++ b/.github/actions/spelling/allow/allow.txt @@ -80,6 +80,7 @@ mnt mru nje noreply +notwrapped ogonek ok'd overlined diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp index 10c70646e9d..2aa9176a8ca 100644 --- a/src/buffer/out/textBuffer.cpp +++ b/src/buffer/out/textBuffer.cpp @@ -1440,10 +1440,23 @@ til::point TextBuffer::_GetWordStartForSelection(const til::point target, const // expand left until we hit the left boundary or a different delimiter class while (result != bufferSize.Origin() && _GetDelimiterClassAt(result, wordDelimiters) == initialDelimiter) { - //prevent selection wrapping on whitespace selection - if (isControlChar && result.x == bufferSize.Left()) + if (result.x == bufferSize.Left()) { - break; + // Prevent wrapping to the previous line if the selection begins on whitespace + if (isControlChar) + { + break; + } + + if (result.y > 0) + { + // Prevent wrapping to the previous line if it was hard-wrapped (e.g. not forced by us to wrap) + const auto& priorRow = GetRowByOffset(result.y - 1); + if (!priorRow.WasWrapForced()) + { + break; + } + } } bufferSize.DecrementInBounds(result); } @@ -1563,10 +1576,22 @@ til::point TextBuffer::_GetWordEndForSelection(const til::point target, const st // expand right until we hit the right boundary as a ControlChar or a different delimiter class while (result != bufferSize.BottomRightInclusive() && _GetDelimiterClassAt(result, wordDelimiters) == initialDelimiter) { - if (isControlChar && result.x == bufferSize.RightInclusive()) + if (result.x == bufferSize.RightInclusive()) { - break; + // Prevent wrapping to the next line if the selection begins on whitespace + if (isControlChar) + { + break; + } + + // Prevent wrapping to the next line if this one was hard-wrapped (e.g. not forced by us to wrap) + const auto& row = GetRowByOffset(result.y); + if (!row.WasWrapForced()) + { + break; + } } + bufferSize.IncrementInBoundsCircular(result); } diff --git a/src/host/ut_host/TextBufferTests.cpp b/src/host/ut_host/TextBufferTests.cpp index 8f837985b70..f70f5c6b8ec 100644 --- a/src/host/ut_host/TextBufferTests.cpp +++ b/src/host/ut_host/TextBufferTests.cpp @@ -2336,16 +2336,32 @@ void TextBufferTests::GetWordBoundaries() } _buffer->Reset(); - _buffer->ResizeTraditional({ 10, 5 }); + _buffer->ResizeTraditional({ 10, 6 }); const std::vector secondText = { L"this wordiswrapped", + L"notwrapped" L"spaces wrapped reachEOB" }; - //Buffer looks like: - // this wordi - // swrapped - // spaces - // wrappe - // d reachEOB + WriteLinesToBuffer(secondText, *_buffer); + + //Buffer looks like: + // 0123456789 + // 0|this wordi| < wrapped + // 1|swrapped | < not wrapped + // 2|notwrapped| < not wrapped + // 3|spaces | < wrapped + // 4| wrappe| < wrapped + // 5|d reachEOB| < wrapped + + VERIFY_IS_TRUE(_buffer->GetRowByOffset(0).WasWrapForced()); + VERIFY_IS_FALSE(_buffer->GetRowByOffset(1).WasWrapForced()); + // GH#780 See the comment in WriteLinesToBuffer + // VERIFY_IS_FALSE(_buffer->GetRowByOffset(2).WasWrapForced()); + _buffer->GetMutableRowByOffset(2).SetWrapForced(false); // Ugh + VERIFY_IS_TRUE(_buffer->GetRowByOffset(3).WasWrapForced()); + VERIFY_IS_TRUE(_buffer->GetRowByOffset(4).WasWrapForced()); + VERIFY_IS_TRUE(_buffer->GetRowByOffset(5).WasWrapForced()); + + // clang-format off testData = { { { 0, 0 }, { { 0, 0 }, { 0, 0 } } }, { { 1, 0 }, { { 0, 0 }, { 0, 0 } } }, @@ -2358,15 +2374,18 @@ void TextBufferTests::GetWordBoundaries() { { 9, 1 }, { { 8, 1 }, { 5, 0 } } }, { { 0, 2 }, { { 0, 2 }, { 0, 2 } } }, - { { 7, 2 }, { { 6, 2 }, { 0, 2 } } }, - - { { 1, 3 }, { { 0, 3 }, { 0, 2 } } }, - { { 4, 3 }, { { 4, 3 }, { 4, 3 } } }, - { { 8, 3 }, { { 4, 3 }, { 4, 3 } } }, - - { { 0, 4 }, { { 4, 3 }, { 4, 3 } } }, - { { 1, 4 }, { { 1, 4 }, { 4, 3 } } }, - { { 9, 4 }, { { 2, 4 }, { 2, 4 } } }, + { { 9, 2 }, { { 0, 2 }, { 0, 2 } } }, + // v accessibility does not consider wrapping + { { 0, 3 }, { { 0, 3 }, { 0, 2 } } }, + { { 7, 3 }, { { 6, 3 }, { 0, 2 } } }, + // v accessibility does not consider wrapping + { { 1, 4 }, { { 0, 4 }, { 0, 2 } } }, + { { 4, 4 }, { { 4, 4 }, { 4, 4 } } }, + { { 8, 4 }, { { 4, 4 }, { 4, 4 } } }, + + { { 0, 5 }, { { 4, 4 }, { 4, 4 } } }, + { { 1, 5 }, { { 1, 5 }, { 4, 4 } } }, + { { 9, 5 }, { { 2, 5 }, { 2, 5 } } }, }; for (const auto& test : testData) { @@ -2377,12 +2396,15 @@ void TextBufferTests::GetWordBoundaries() } //GetWordEnd for Wrapping Text - //Buffer looks like: - // this wordi - // swrapped - // spaces - // wrappe - // d reachEOB + // Buffer: + // 0123456789 + // 0|this wordi| < wrapped + // 1|swrapped | < not wrapped + // 2|notwrapped| < not wrapped + // 3|spaces | < wrapped + // 4| wrappe| < wrapped + // 5|d reachEOB| < wrapped + // clang-format off testData = { // tests for first line of text { { 0, 0 }, { { 3, 0 }, { 5, 0 } } }, @@ -2395,17 +2417,20 @@ void TextBufferTests::GetWordBoundaries() { { 7, 1 }, { { 7, 1 }, { 0, 2 } } }, { { 9, 1 }, { { 9, 1 }, { 0, 2 } } }, - { { 0, 2 }, { { 5, 2 }, { 4, 3 } } }, - { { 7, 2 }, { { 9, 2 }, { 4, 3 } } }, + { { 0, 2 }, { { 9, 2 }, { 4, 4 } } }, + { { 9, 2 }, { { 9, 2 }, { 4, 4 } } }, + + { { 0, 3 }, { { 5, 3 }, { 4, 4 } } }, + { { 7, 3 }, { { 9, 3 }, { 4, 4 } } }, - { { 1, 3 }, { { 3, 3 }, { 4, 3 } } }, - { { 4, 3 }, { { 0, 4 }, { 2, 4 } } }, - { { 8, 3 }, { { 0, 4 }, { 2, 4 } } }, + { { 1, 4 }, { { 3, 4 }, { 4, 4 } } }, + { { 4, 4 }, { { 0, 5 }, { 2, 5 } } }, + { { 8, 4 }, { { 0, 5 }, { 2, 5 } } }, - { { 0, 4 }, { { 0, 4 }, { 2, 4 } } }, - { { 1, 4 }, { { 1, 4 }, { 2, 4 } } }, - { { 4, 4 }, { { 9, 4 }, { 0, 5 } } }, - { { 9, 4 }, { { 9, 4 }, { 0, 5 } } }, + { { 0, 5 }, { { 0, 5 }, { 2, 5 } } }, + { { 1, 5 }, { { 1, 5 }, { 2, 5 } } }, + { { 4, 5 }, { { 9, 5 }, { 0, 6 } } }, + { { 9, 5 }, { { 9, 5 }, { 0, 6 } } }, }; // clang-format on