From 345d2663503d975d6f475da84fa3daa74682d574 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Thu, 2 May 2024 11:14:20 -0500 Subject: [PATCH] Take wrapping into account when expanding wordwise selections (#17170) Closes #17165 (cherry picked from commit 6cda6797f8289cb727ba9fe95f4649a5eec417f0) Service-Card-Id: 92471634 Service-Version: 1.20 --- .github/actions/spelling/allow/allow.txt | 1 + src/buffer/out/textBuffer.cpp | 35 ++++++++-- src/host/ut_host/TextBufferTests.cpp | 87 +++++++++++++++--------- 3 files changed, 87 insertions(+), 36 deletions(-) diff --git a/.github/actions/spelling/allow/allow.txt b/.github/actions/spelling/allow/allow.txt index da32cb6242a..1b8ab62304a 100644 --- a/.github/actions/spelling/allow/allow.txt +++ b/.github/actions/spelling/allow/allow.txt @@ -78,6 +78,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 5c71dd1d398..a9e307f0847 100644 --- a/src/buffer/out/textBuffer.cpp +++ b/src/buffer/out/textBuffer.cpp @@ -1414,10 +1414,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); } @@ -1537,10 +1550,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 648b054778b..08386cc4eb6 100644 --- a/src/host/ut_host/TextBufferTests.cpp +++ b/src/host/ut_host/TextBufferTests.cpp @@ -2253,16 +2253,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 } } }, @@ -2275,15 +2291,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) { @@ -2294,12 +2313,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 } } }, @@ -2312,17 +2334,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