From 719676c30dc79a770b1f407f23c0053ce075d516 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Tue, 17 Dec 2019 23:42:56 -0800 Subject: [PATCH 01/25] utr refactor --- acc-refactor.md | 51 ++++++ src/types/UiaTextRangeBase.cpp | 315 +++++++++++---------------------- src/types/UiaTextRangeBase.hpp | 52 ++---- 3 files changed, 167 insertions(+), 251 deletions(-) create mode 100644 acc-refactor.md diff --git a/acc-refactor.md b/acc-refactor.md new file mode 100644 index 00000000000..267600110cf --- /dev/null +++ b/acc-refactor.md @@ -0,0 +1,51 @@ +# Refactor proposal + +## Goals: +1) reduce duplicate code +2) remove static functions +3) improve readability +4) improve reliability +5) improve code-coverage for testing + +## Approach: +1) reduce duplicate code + - `Move()` should rely on `MoveEndpointByUnit()` +2) remove static functions + - all helper functions should not be static +3) improve readability + - use `COORD` system internally + - import/export Endpoints +4) improve reliability + - rely more heavily on the `Viewport` and `TextBuffer` functions +5) improve code-coverage for testing + - having reusable code means that we are testing more of our code better + +## Targets (Code): +```c++ +IFACEMETHODIMP Move(_In_ TextUnit unit, + _In_ int count, + _Out_ int* pRetVal) override; +IFACEMETHODIMP MoveEndpointByUnit(_In_ TextPatternRangeEndpoint endpoint, + _In_ TextUnit unit, + _In_ int count, + _Out_ int* pRetVal) override; +IFACEMETHODIMP MoveEndpointByRange(_In_ TextPatternRangeEndpoint endpoint, + _In_ ITextRangeProvider* pTargetRange, + _In_ TextPatternRangeEndpoint targetEndpoint) override; +``` + +## Timeline +| Estimate | Task | Notes | +| -- | -- | -- | +| 3 | switch to `COORD` | remove helper functions | +| | | convert to/from COORD at f(n) boundary | +| | | update MoveState | +| 2 | remove `degenerate` | | +| X | Y | | + +## Horrible, Terrible, and Terrifying things I've found +- CompareEndpoints clamped. No documentation on why (or a need to do so) +- `GetText()` is something we shouldn't be doing manually. +- `Move()` can be reduced to `Expand...()` --> `MoveEndpointByUnit(start)` --> `Expand()` +- `MoveEndpointByRange()` just reduced to "set my endpoint to target endpoint" +- `MoveEndpointByUnitCharacter()` is literally just `Viewport::MoveInBounds()` \ No newline at end of file diff --git a/src/types/UiaTextRangeBase.cpp b/src/types/UiaTextRangeBase.cpp index d1ad0f02729..b5d91946c29 100644 --- a/src/types/UiaTextRangeBase.cpp +++ b/src/types/UiaTextRangeBase.cpp @@ -17,10 +17,8 @@ IdType UiaTextRangeBase::id = 1; UiaTextRangeBase::MoveState::MoveState(IUiaData* pData, const UiaTextRangeBase& range, const MovementDirection direction) : - StartScreenInfoRow{ UiaTextRangeBase::_endpointToScreenInfoRow(pData, range.GetStart()) }, - StartColumn{ UiaTextRangeBase::_endpointToColumn(pData, range.GetStart()) }, - EndScreenInfoRow{ UiaTextRangeBase::_endpointToScreenInfoRow(pData, range.GetEnd()) }, - EndColumn{ UiaTextRangeBase::_endpointToColumn(pData, range.GetEnd()) }, + start{ range.GetEndpoint(TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start) }, + end{ range.GetEndpoint(TextPatternRangeEndpoint::TextPatternRangeEndpoint_End) }, Direction{ direction } { if (direction == MovementDirection::Forward) @@ -39,19 +37,15 @@ UiaTextRangeBase::MoveState::MoveState(IUiaData* pData, } } -UiaTextRangeBase::MoveState::MoveState(const ScreenInfoRow startScreenInfoRow, - const Column startColumn, - const ScreenInfoRow endScreenInfoRow, - const Column endColumn, +UiaTextRangeBase::MoveState::MoveState(const COORD start, + const COORD end, const ScreenInfoRow limitingRow, const Column firstColumnInRow, const Column lastColumnInRow, const MovementIncrement increment, const MovementDirection direction) noexcept : - StartScreenInfoRow{ startScreenInfoRow }, - StartColumn{ startColumn }, - EndScreenInfoRow{ endScreenInfoRow }, - EndColumn{ endColumn }, + start{ range.GetEndpoint(TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start) }, + end{ range.GetEndpoint(TextPatternRangeEndpoint::TextPatternRangeEndpoint_End) }, LimitingRow{ limitingRow }, FirstColumnInRow{ firstColumnInRow }, LastColumnInRow{ lastColumnInRow }, @@ -186,7 +180,7 @@ void UiaTextRangeBase::Initialize(_In_ const UiaPoint point) // get row that point resides in const RECT windowRect = _getTerminalRect(); const SMALL_RECT viewport = _pData->GetViewport().ToInclusive(); - ScreenInfoRow row = 0; + SHORT row = 0; if (clientPoint.y <= windowRect.top) { row = viewport.Top; @@ -203,9 +197,9 @@ void UiaTextRangeBase::Initialize(_In_ const UiaPoint point) const COORD currentFontSize = _getScreenFontSize(); row = (clientPoint.y / currentFontSize.Y) + viewport.Top; } - _start = _screenInfoRowToEndpoint(_pData, row); + // TODO CARLOS: double check that viewport.Top returns the correct text buffer position + _start = { 0, row }; _end = _start; - _degenerate = true; } #pragma warning(suppress : 26434) // WRL RuntimeClassInitialize base is a no-op and we need this for MakeAndInitialize @@ -233,14 +227,16 @@ const IdType UiaTextRangeBase::GetId() const noexcept return _id; } -const Endpoint UiaTextRangeBase::GetStart() const noexcept -{ - return _start; -} - -const Endpoint UiaTextRangeBase::GetEnd() const noexcept +const COORD UiaTextRangeBase::GetEndpoint(TextPatternRangeEndpoint endpoint) const noexcept { - return _end; + switch(endpoint) + { + case TextPatternRangeEndpoint::TextPatternRangeEndpoint_End: + return _end; + case TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start: + default: + return _start; + } } // Routine Description: @@ -251,7 +247,7 @@ const Endpoint UiaTextRangeBase::GetEnd() const noexcept // - true if range is degenerate, false otherwise. const bool UiaTextRangeBase::IsDegenerate() const noexcept { - return _degenerate; + return _start == _end; } void UiaTextRangeBase::SetRangeValues(const Endpoint start, const Endpoint end, const bool isDegenerate) noexcept @@ -275,9 +271,9 @@ IFACEMETHODIMP UiaTextRangeBase::Compare(_In_opt_ ITextRangeProvider* pRange, _O const UiaTextRangeBase* other = static_cast(pRange); if (other) { - *pRetVal = !!(_start == other->GetStart() && - _end == other->GetEnd() && - _degenerate == other->IsDegenerate()); + *pRetVal = !!(_start == other->GetEndpoint(TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start) && + _end == other->GetEndpoint(TextPatternRangeEndpoint::TextPatternRangeEndpoint_End) && + IsDegenerate() == other->IsDegenerate()); } // TODO GitHub #1914: Re-attach Tracing to UIA Tree // tracing @@ -305,13 +301,13 @@ IFACEMETHODIMP UiaTextRangeBase::CompareEndpoints(_In_ TextPatternRangeEndpoint } // get endpoint value that we're comparing to - const Endpoint theirValue = targetEndpoint == TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start ? range->GetStart() : range->GetEnd() + 1; + const auto other = range->GetEndpoint(targetEndpoint); // get the values of our endpoint - const Endpoint ourValue = endpoint == TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start ? _start : _end + 1; + const auto mine = GetEndpoint(endpoint); // compare them - *pRetVal = std::clamp(static_cast(ourValue) - static_cast(theirValue), -1, 1); + *pRetVal = _pData->GetViewport().CompareInBounds(mine, other); // TODO GitHub #1914: Re-attach Tracing to UIA Tree // tracing @@ -339,37 +335,38 @@ IFACEMETHODIMP UiaTextRangeBase::ExpandToEnclosingUnit(_In_ TextUnit unit) try { - const ScreenInfoRow topRow = _getFirstScreenInfoRowIndex(); - const ScreenInfoRow bottomRow = _getLastScreenInfoRowIndex(_pData); + const auto buffer = _pData->GetTextBuffer(); + const auto viewport = _pData->GetTextBuffer().GetSize(); if (unit == TextUnit::TextUnit_Character) { _end = _start; + viewport.IncrementInBounds(&_end); } else if (unit <= TextUnit::TextUnit_Word) { // expand to word - const auto target = _start; - _start = _wordBeginEndpoint(_pData, target, _wordDelimiters); - _end = _wordEndEndpoint(_pData, target, _wordDelimiters); + // TODO CARLOS: verify that the end is properly exclusive here + _start = buffer.GetWordStart(_start, _wordDelimiters, true); + _end = buffer.GetWordEnd(_start, _wordDelimiters, true); FAIL_FAST_IF(!(_start <= _end)); } else if (unit <= TextUnit::TextUnit_Line) { // expand to line - _start = _textBufferRowToEndpoint(_pData, _endpointToTextBufferRow(_pData, _start)); - _end = _start + _getLastColumnIndex(_pData); + _start.X = 0; + _end = {viewport.RightInclusive(), _start.Y + 1}; + viewport.Clamp(&_end); FAIL_FAST_IF(!(_start <= _end)); } else { // expand to document - _start = _screenInfoRowToEndpoint(_pData, topRow); - _end = _screenInfoRowToEndpoint(_pData, bottomRow) + _getLastColumnIndex(_pData); + // TODO CARLOS: verify that the end is properly exclusive here + _start = viewport.Origin(); + _end = viewport.Dimensions(); } - _degenerate = false; - // TODO GitHub #1914: Re-attach Tracing to UIA Tree //Tracing::s_TraceUia(this, ApiCall::ExpandToEnclosingUnit, &apiMsg); @@ -501,11 +498,26 @@ IFACEMETHODIMP UiaTextRangeBase::GetText(_In_ int maxLength, _Out_ BSTR* pRetVal // truncated. const bool getPartialText = maxLength != -1; - if (!_degenerate) + if (!IsDegenerate()) { try { - const ScreenInfoRow startScreenInfoRow = _endpointToScreenInfoRow(_pData, _start); + // TODO CARLOS: my attempt + const auto buffer = _pData->GetTextBuffer(); + + // TODO CARLOS: I'm pretty sure this is wrong, but we can fenangle this a bit to make it work somehow + auto iter = buffer.GetTextDataAt(_start, Viewport::FromCoord(_end)); + while (iter + && getPartialText + && wstr.size() > static_cast(maxLength)) + { + wstr += iter.Chars(); + } + + // END TODO CARLOS: my attempt + + + /* const ScreenInfoRow startScreenInfoRow = _endpointToScreenInfoRow(_pData, _start); const Column startColumn = _endpointToColumn(_pData, _start); const ScreenInfoRow endScreenInfoRow = _endpointToScreenInfoRow(_pData, _end); const Column endColumn = _endpointToColumn(_pData, _end); @@ -561,7 +573,7 @@ IFACEMETHODIMP UiaTextRangeBase::GetText(_In_ int maxLength, _Out_ BSTR* pRetVal } } } - CATCH_RETURN(); + CATCH_RETURN(); */ } *pRetVal = SysAllocString(wstr.c_str()); @@ -585,11 +597,6 @@ IFACEMETHODIMP UiaTextRangeBase::Move(_In_ TextUnit unit, _In_ int count, _Out_ int* pRetVal) { - _pData->LockConsole(); - auto Unlock = wil::scope_exit([&]() noexcept { - _pData->UnlockConsole(); - }); - RETURN_HR_IF(E_INVALIDARG, pRetVal == nullptr); *pRetVal = 0; if (count == 0) @@ -614,39 +621,19 @@ IFACEMETHODIMP UiaTextRangeBase::Move(_In_ TextUnit unit, _outputRowConversions(); #endif - std::function(gsl::not_null, const int, const MoveState, gsl::not_null const)> moveFunc = &_moveByDocument; - if (unit == TextUnit::TextUnit_Character) - { - moveFunc = &_moveByCharacter; - } - else if (unit <= TextUnit::TextUnit_Word) - { - moveFunc = std::bind(&_moveByWord, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, _wordDelimiters, std::placeholders::_4); - } - else if (unit <= TextUnit::TextUnit_Line) - { - moveFunc = &_moveByLine; - } - - const MovementDirection moveDirection = (count > 0) ? MovementDirection::Forward : MovementDirection::Backward; - std::pair newEndpoints; + // Source: https://docs.microsoft.com/en-us/windows/win32/winauto/uiauto-implementingtextandtextrange + // When the ITextRangeProvider::Move method is called, the provider + // normalizes the text range by the specified text unit, + // using the same normalization logic as the ExpandToEnclosingUnit method. + ExpandToEnclosingUnit(unit); - try - { - const MoveState moveState{ _pData, *this, moveDirection }; - newEndpoints = moveFunc(_pData, - count, - moveState, - pRetVal); - } - CATCH_RETURN(); + // We can abstract this movement by moving _start... + MoveEndpointByUnit(TextPatternRangeEndpoint_Start, unit, count, pRetVal); - _start = newEndpoints.first; - _end = newEndpoints.second; + // then just expand again to get our _end + ExpandToEnclosingUnit(unit); - // a range can't be degenerate after both endpoints have been - // moved. - _degenerate = false; + // NOTE: a range can't be degenerate after both endpoints have been moved. // TODO GitHub #1914: Re-attach Tracing to UIA Tree // tracing @@ -714,18 +701,13 @@ IFACEMETHODIMP UiaTextRangeBase::MoveEndpointByUnit(_In_ TextPatternRangeEndpoin moveFunc = &_moveEndpointByUnitLine; } - std::tuple moveResults; try { const MoveState moveState{ _pData, *this, moveDirection }; - moveResults = moveFunc(_pData, count, endpoint, moveState, pRetVal); + [_start, _end] = moveFunc(count, endpoint, moveState, pRetVal); } CATCH_RETURN(); - _start = std::get<0>(moveResults); - _end = std::get<1>(moveResults); - _degenerate = std::get<2>(moveResults); - // TODO GitHub #1914: Re-attach Tracing to UIA Tree // tracing /*apiMsg.MovedCount = *pRetVal; @@ -769,69 +751,17 @@ IFACEMETHODIMP UiaTextRangeBase::MoveEndpointByRange(_In_ TextPatternRangeEndpoi _outputRowConversions(); #endif - // get the value that we're updating to - Endpoint targetEndpointValue = 0; - if (targetEndpoint == TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start) - { - targetEndpointValue = range->GetStart(); - - // If we're moving our end relative to their start, we actually have to back up one from - // their start position because this operation treats it as exclusive. - if (endpoint == TextPatternRangeEndpoint::TextPatternRangeEndpoint_End) - { - if (targetEndpointValue > 0) - { - targetEndpointValue--; - } - } - } - else - { - targetEndpointValue = range->GetEnd(); - - // If we're moving our start relative to their end, we actually have to sit one after - // their end position as it was stored inclusive and we're doing this as an exclusive operation. - if (endpoint == TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start) - { - targetEndpointValue++; - } - } - - try + // TODO CARLOS: Double check if we get the corner cases right here. Not too sure about it. + // TODO CARLOS: Make sure we get the degenerate ranges correct here too + switch(endpoint) { - // convert then endpoints to screen info rows/columns - const auto startScreenInfoRow = _endpointToScreenInfoRow(_pData, _start); - const auto startColumn = _endpointToColumn(_pData, _start); - const auto endScreenInfoRow = _endpointToScreenInfoRow(_pData, _end); - const auto endColumn = _endpointToColumn(_pData, _end); - const auto targetScreenInfoRow = _endpointToScreenInfoRow(_pData, targetEndpointValue); - const auto targetColumn = _endpointToColumn(_pData, targetEndpointValue); - - // set endpoint value and check for crossed endpoints - bool crossedEndpoints = false; - if (endpoint == TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start) - { - _start = targetEndpointValue; - if (_compareScreenCoords(_pData, endScreenInfoRow, endColumn, targetScreenInfoRow, targetColumn) == -1) - { - // endpoints were crossed - _end = _start; - crossedEndpoints = true; - } - } - else - { - _end = targetEndpointValue; - if (_compareScreenCoords(_pData, startScreenInfoRow, startColumn, targetScreenInfoRow, targetColumn) == 1) - { - // endpoints were crossed - _start = _end; - crossedEndpoints = true; - } - } - _degenerate = crossedEndpoints; + case TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start: + _start = range->GetEndpoint(targetEndpoint); + case TextPatternRangeEndpoint::TextPatternRangeEndpoint_End: + _end = range->GetEndpoint(targetEndpoint); + default: + return E_INVALIDARG; } - CATCH_RETURN(); // TODO GitHub #1914: Re-attach Tracing to UIA Tree //Tracing::s_TraceUia(this, ApiCall::MoveEndpointByRange, &apiMsg); @@ -845,18 +775,13 @@ IFACEMETHODIMP UiaTextRangeBase::Select() _pData->UnlockConsole(); }); - if (_degenerate) + if (IsDegenerate()) { // calling Select on a degenerate range should clear any current selections _pData->ClearSelection(); } else { - const COORD coordStart{ gsl::narrow(_endpointToColumn(_pData, _start)), - gsl::narrow(_endpointToScreenInfoRow(_pData, _start)) }; - const COORD coordEnd{ gsl::narrow(_endpointToColumn(_pData, _end)), - gsl::narrow(_endpointToScreenInfoRow(_pData, _end)) }; - _pData->SelectNewRegion(coordStart, coordEnd); } @@ -893,8 +818,8 @@ IFACEMETHODIMP UiaTextRangeBase::ScrollIntoView(_In_ BOOL alignToTop) const auto oldViewport = _pData->GetViewport().ToInclusive(); const auto viewportHeight = _getViewportHeight(oldViewport); // range rows - const auto startScreenInfoRow = _endpointToScreenInfoRow(_pData, _start); - const auto endScreenInfoRow = _endpointToScreenInfoRow(_pData, _end); + const auto startScreenInfoRow = _start.Y; + const auto endScreenInfoRow = _end.Y; // screen buffer rows const auto topRow = _getFirstScreenInfoRowIndex(); const auto bottomRow = _getLastScreenInfoRowIndex(_pData); @@ -1794,87 +1719,51 @@ std::pair UiaTextRangeBase::_moveByDocument(gsl::not_null -std::tuple UiaTextRangeBase::_moveEndpointByUnitCharacter(gsl::not_null pData, - const int moveCount, - const TextPatternRangeEndpoint endpoint, - const MoveState moveState, - _Out_ gsl::not_null const pAmountMoved) +// - A tuple of elements of the form +std::tuple UiaTextRangeBase::_moveEndpointByUnitCharacter(const int moveCount, + const TextPatternRangeEndpoint endpoint, + const MoveState moveState, + _Out_ gsl::not_null const pAmountMoved) { - if (moveState.Direction == MovementDirection::Forward) + switch (moveState.Direction) { - return _moveEndpointByUnitCharacterForward(pData, moveCount, endpoint, moveState, pAmountMoved); - } - else - { - return _moveEndpointByUnitCharacterBackward(pData, moveCount, endpoint, moveState, pAmountMoved); + case MovementDirection::Forward: + return _moveEndpointByUnitCharacterForward(moveCount, endpoint, moveState, pAmountMoved); + case MovementDirection::Backward: + return _moveEndpointByUnitCharacterBackward(moveCount, endpoint, moveState, pAmountMoved); + default: + *pAmountMoved = 0; + return std::make_tuple(moveState.start, moveState.end); } } -std::tuple -UiaTextRangeBase::_moveEndpointByUnitCharacterForward(gsl::not_null pData, - const int moveCount, +std::tuple +UiaTextRangeBase::_moveEndpointByUnitCharacterForward(const int moveCount, const TextPatternRangeEndpoint endpoint, const MoveState moveState, _Out_ gsl::not_null const pAmountMoved) { *pAmountMoved = 0; - ScreenInfoRow currentScreenInfoRow = 0; - Column currentColumn = 0; + auto currentPos = GetEndpoint(endpoint); - // set current location vars - if (endpoint == TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start) - { - currentScreenInfoRow = moveState.StartScreenInfoRow; - currentColumn = moveState.StartColumn; - } - else + const auto buffer = _pData->GetViewport(); + int i = 0; + while (i < abs(moveCount) && buffer.IncrementInBounds(¤tPos)) { - currentScreenInfoRow = moveState.EndScreenInfoRow; - currentColumn = moveState.EndColumn; + ++i; } + *pAmountMoved = i; - for (int i = 0; i < abs(moveCount); ++i) - { - // get the current row's right - const ROW& row = pData->GetTextBuffer().GetRowByOffset(currentScreenInfoRow); - const size_t right = row.GetCharRow().MeasureRight(); - const auto expectedColumn = gsl::narrow_cast(currentColumn) + 1; - - // check if we're at the edge of the screen info buffer - if (currentScreenInfoRow == moveState.LimitingRow && - expectedColumn >= right) - { - break; - } - else if (expectedColumn >= right) - { - // we're at the edge of a row and need to go to the next one - currentColumn = moveState.FirstColumnInRow; - currentScreenInfoRow += static_cast(moveState.Increment); - } - else - { - // moving somewhere away from the edges of a row - currentColumn += static_cast(moveState.Increment); - } - *pAmountMoved += static_cast(moveState.Increment); - - FAIL_FAST_IF(!(currentColumn >= _getFirstColumnIndex())); - FAIL_FAST_IF(!(currentColumn <= _getLastColumnIndex(pData))); - FAIL_FAST_IF(!(currentScreenInfoRow >= _getFirstScreenInfoRowIndex())); - FAIL_FAST_IF(!(currentScreenInfoRow <= _getLastScreenInfoRowIndex(pData))); - } + // TODO CARLOS STOP HERE: Stopping point. I need to simplify the code below. Shouldn't be too hard // translate the row back to an endpoint and handle any crossed endpoints const Endpoint convertedEndpoint = _screenInfoRowToEndpoint(pData, currentScreenInfoRow) + currentColumn; @@ -1907,7 +1796,7 @@ UiaTextRangeBase::_moveEndpointByUnitCharacterForward(gsl::not_null p degenerate = true; } } - return std::make_tuple(start, end, degenerate); + return std::make_tuple(start, end); } std::tuple diff --git a/src/types/UiaTextRangeBase.hpp b/src/types/UiaTextRangeBase.hpp index daae0a6aecd..5d532ea1bc4 100644 --- a/src/types/UiaTextRangeBase.hpp +++ b/src/types/UiaTextRangeBase.hpp @@ -102,11 +102,9 @@ namespace Microsoft::Console::Types struct MoveState { // screen/column position of _start - ScreenInfoRow StartScreenInfoRow; - Column StartColumn; + COORD start; // screen/column position of _end - ScreenInfoRow EndScreenInfoRow; - Column EndColumn; + COORD end // last row in the direction being moved ScreenInfoRow LimitingRow; // first column in the direction being moved @@ -123,10 +121,8 @@ namespace Microsoft::Console::Types const MovementDirection direction); private: - MoveState(const ScreenInfoRow startScreenInfoRow, - const Column startColumn, - const ScreenInfoRow endScreenInfoRow, - const Column endColumn, + MoveState(const COORD start, + const COORD end, const ScreenInfoRow limitingRow, const Column firstColumnInRow, const Column lastColumnInRow, @@ -169,8 +165,7 @@ namespace Microsoft::Console::Types ~UiaTextRangeBase() = default; const IdType GetId() const noexcept; - const Endpoint GetStart() const noexcept; - const Endpoint GetEnd() const noexcept; + const COORD GetEndpoint(TextPatternRangeEndpoint endpoint = TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start) const noexcept; const bool IsDegenerate() const noexcept; // TODO GitHub #605: @@ -249,16 +244,9 @@ namespace Microsoft::Console::Types // 0 ............... N (text buffer line indices) // ---e s----- (_start to _end) // - Endpoint _start; - Endpoint _end; - - // The msdn documentation (and hence this class) talks a bunch about a - // degenerate range. A range is degenerate if it contains - // no text (both the start and end endpoints are the same). Note that - // a degenerate range may have a position in the text. We indicate a - // degenerate range internally with a bool. If a range is degenerate - // then both endpoints will contain the same value. - bool _degenerate; + // NOTE: _start is inclusive, but _end is exclusive + COORD _start; + COORD _end; RECT _getTerminalRect() const; @@ -271,9 +259,6 @@ namespace Microsoft::Console::Types static const unsigned int _getFirstScreenInfoRowIndex() noexcept; static const unsigned int _getLastScreenInfoRowIndex(gsl::not_null pData) noexcept; - static const Column _getFirstColumnIndex() noexcept; - static const Column _getLastColumnIndex(gsl::not_null pData); - const unsigned int _rowCountInRange(gsl::not_null pData) const; static const TextBufferRow _endpointToTextBufferRow(gsl::not_null pData, @@ -330,12 +315,6 @@ namespace Microsoft::Console::Types const ScreenInfoRow screenInfoRow, _Inout_ std::vector& coords) const; - static const int _compareScreenCoords(gsl::not_null pData, - const ScreenInfoRow rowA, - const Column colA, - const ScreenInfoRow rowB, - const Column colB); - static std::pair _moveByCharacter(gsl::not_null pData, const int moveCount, const MoveState moveState, @@ -379,23 +358,20 @@ namespace Microsoft::Console::Types const MoveState moveState, _Out_ gsl::not_null const pAmountMoved); - static std::tuple - _moveEndpointByUnitCharacter(gsl::not_null pData, - const int moveCount, + std::tuple + _moveEndpointByUnitCharacter(const int moveCount, const TextPatternRangeEndpoint endpoint, const MoveState moveState, _Out_ gsl::not_null const pAmountMoved); - static std::tuple - _moveEndpointByUnitCharacterForward(gsl::not_null pData, - const int moveCount, + std::tuple + _moveEndpointByUnitCharacterForward(const int moveCount, const TextPatternRangeEndpoint endpoint, const MoveState moveState, _Out_ gsl::not_null const pAmountMoved); - static std::tuple - _moveEndpointByUnitCharacterBackward(gsl::not_null pData, - const int moveCount, + std::tuple + _moveEndpointByUnitCharacterBackward(const int moveCount, const TextPatternRangeEndpoint endpoint, const MoveState moveState, _Out_ gsl::not_null const pAmountMoved); From e7555dc1ece7dd57eb4bfd84960c377c0d649b5c Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Wed, 18 Dec 2019 18:38:25 -0800 Subject: [PATCH 02/25] Refactor UiaTextRange: - remove _degenerate - _start and _end are now COORDs - _end is always exclusive - de-static-fy functions - all COORDs are in the text buffer coordinate system --- .../TermControlUiaProvider.cpp | 7 +- .../TermControlUiaProvider.hpp | 5 +- src/cascadia/TerminalControl/UiaTextRange.cpp | 14 +- src/cascadia/TerminalControl/UiaTextRange.hpp | 5 +- .../win32/screenInfoUiaProvider.cpp | 7 +- .../win32/screenInfoUiaProvider.hpp | 5 +- src/interactivity/win32/uiaTextRange.cpp | 29 +- src/interactivity/win32/uiaTextRange.hpp | 5 +- src/types/ScreenInfoUiaProviderBase.cpp | 17 +- src/types/ScreenInfoUiaProviderBase.h | 5 +- src/types/UiaTextRangeBase.cpp | 1853 +++-------------- src/types/UiaTextRangeBase.hpp | 229 +- src/types/inc/viewport.hpp | 1 + src/types/viewport.cpp | 11 + 14 files changed, 415 insertions(+), 1778 deletions(-) diff --git a/src/cascadia/TerminalControl/TermControlUiaProvider.cpp b/src/cascadia/TerminalControl/TermControlUiaProvider.cpp index 9610feb51cd..353d30a28bf 100644 --- a/src/cascadia/TerminalControl/TermControlUiaProvider.cpp +++ b/src/cascadia/TerminalControl/TermControlUiaProvider.cpp @@ -141,16 +141,15 @@ HRESULT TermControlUiaProvider::CreateTextRange(_In_ IRawElementProviderSimple* } HRESULT TermControlUiaProvider::CreateTextRange(_In_ IRawElementProviderSimple* const pProvider, - const Endpoint start, - const Endpoint end, - const bool degenerate, + const COORD start, + const COORD end, const std::wstring_view wordDelimiters, _COM_Outptr_result_maybenull_ UiaTextRangeBase** ppUtr) { RETURN_HR_IF_NULL(E_INVALIDARG, ppUtr); *ppUtr = nullptr; UiaTextRange* result = nullptr; - RETURN_IF_FAILED(MakeAndInitialize(&result, _pData, pProvider, start, end, degenerate, wordDelimiters)); + RETURN_IF_FAILED(MakeAndInitialize(&result, _pData, pProvider, start, end, wordDelimiters)); *ppUtr = result; return S_OK; } diff --git a/src/cascadia/TerminalControl/TermControlUiaProvider.hpp b/src/cascadia/TerminalControl/TermControlUiaProvider.hpp index 914962e123e..c90d3bf6ca3 100644 --- a/src/cascadia/TerminalControl/TermControlUiaProvider.hpp +++ b/src/cascadia/TerminalControl/TermControlUiaProvider.hpp @@ -60,9 +60,8 @@ namespace Microsoft::Terminal // specific endpoint range HRESULT CreateTextRange(_In_ IRawElementProviderSimple* const pProvider, - const Endpoint start, - const Endpoint end, - const bool degenerate, + const COORD start, + const COORD end, const std::wstring_view wordDelimiters, _COM_Outptr_result_maybenull_ Microsoft::Console::Types::UiaTextRangeBase** ppUtr) override; diff --git a/src/cascadia/TerminalControl/UiaTextRange.cpp b/src/cascadia/TerminalControl/UiaTextRange.cpp index 6552acb38c9..ed355200d7a 100644 --- a/src/cascadia/TerminalControl/UiaTextRange.cpp +++ b/src/cascadia/TerminalControl/UiaTextRange.cpp @@ -24,12 +24,11 @@ HRESULT UiaTextRange::GetSelectionRanges(_In_ IUiaData* pData, // create a range for each row for (const auto& rect : rectangles) { - ScreenInfoRow currentRow = rect.Top(); - Endpoint start = _screenInfoRowToEndpoint(pData, currentRow) + rect.Left(); - Endpoint end = _screenInfoRowToEndpoint(pData, currentRow) + rect.RightInclusive(); + const auto start = rect.Origin(); + const auto end = rect.EndInclusive(); ComPtr range; - RETURN_IF_FAILED(MakeAndInitialize(&range, pData, pProvider, start, end, false, wordDelimiters)); + RETURN_IF_FAILED(MakeAndInitialize(&range, pData, pProvider, start, end, wordDelimiters)); temporaryResult.emplace_back(std::move(range)); } std::swap(temporaryResult, ranges); @@ -54,12 +53,11 @@ HRESULT UiaTextRange::RuntimeClassInitialize(_In_ IUiaData* pData, HRESULT UiaTextRange::RuntimeClassInitialize(_In_ IUiaData* pData, _In_ IRawElementProviderSimple* const pProvider, - const Endpoint start, - const Endpoint end, - const bool degenerate, + const COORD start, + const COORD end, const std::wstring_view wordDelimiters) { - return UiaTextRangeBase::RuntimeClassInitialize(pData, pProvider, start, end, degenerate, wordDelimiters); + return UiaTextRangeBase::RuntimeClassInitialize(pData, pProvider, start, end, wordDelimiters); } // returns a degenerate text range of the start of the row closest to the y value of point diff --git a/src/cascadia/TerminalControl/UiaTextRange.hpp b/src/cascadia/TerminalControl/UiaTextRange.hpp index f3e30b91e6d..0113b485e34 100644 --- a/src/cascadia/TerminalControl/UiaTextRange.hpp +++ b/src/cascadia/TerminalControl/UiaTextRange.hpp @@ -44,9 +44,8 @@ namespace Microsoft::Terminal // specific endpoint range HRESULT RuntimeClassInitialize(_In_ Microsoft::Console::Types::IUiaData* pData, _In_ IRawElementProviderSimple* const pProvider, - const Endpoint start, - const Endpoint end, - const bool degenerate, + const COORD start, + const COORD end, const std::wstring_view wordDelimiters = DefaultWordDelimiter); // range from a UiaPoint diff --git a/src/interactivity/win32/screenInfoUiaProvider.cpp b/src/interactivity/win32/screenInfoUiaProvider.cpp index d1259b8c373..b8feb5589f4 100644 --- a/src/interactivity/win32/screenInfoUiaProvider.cpp +++ b/src/interactivity/win32/screenInfoUiaProvider.cpp @@ -143,16 +143,15 @@ HRESULT ScreenInfoUiaProvider::CreateTextRange(_In_ IRawElementProviderSimple* c } HRESULT ScreenInfoUiaProvider::CreateTextRange(_In_ IRawElementProviderSimple* const pProvider, - const Endpoint start, - const Endpoint end, - const bool degenerate, + const COORD start, + const COORD end, const std::wstring_view wordDelimiters, _COM_Outptr_result_maybenull_ UiaTextRangeBase** ppUtr) { RETURN_HR_IF_NULL(E_INVALIDARG, ppUtr); *ppUtr = nullptr; UiaTextRange* result = nullptr; - RETURN_IF_FAILED(MakeAndInitialize(&result, _pData, pProvider, start, end, degenerate, wordDelimiters)); + RETURN_IF_FAILED(MakeAndInitialize(&result, _pData, pProvider, start, end, wordDelimiters)); *ppUtr = result; return S_OK; } diff --git a/src/interactivity/win32/screenInfoUiaProvider.hpp b/src/interactivity/win32/screenInfoUiaProvider.hpp index ef3fb641c56..bf53dbcd571 100644 --- a/src/interactivity/win32/screenInfoUiaProvider.hpp +++ b/src/interactivity/win32/screenInfoUiaProvider.hpp @@ -56,9 +56,8 @@ namespace Microsoft::Console::Interactivity::Win32 // specific endpoint range HRESULT CreateTextRange(_In_ IRawElementProviderSimple* const pProvider, - const Endpoint start, - const Endpoint end, - const bool degenerate, + const COORD start, + const COORD end, const std::wstring_view wordDelimiters, _COM_Outptr_result_maybenull_ Microsoft::Console::Types::UiaTextRangeBase** ppUtr) override; diff --git a/src/interactivity/win32/uiaTextRange.cpp b/src/interactivity/win32/uiaTextRange.cpp index fb43f3cf8d4..bbf65ab794f 100644 --- a/src/interactivity/win32/uiaTextRange.cpp +++ b/src/interactivity/win32/uiaTextRange.cpp @@ -28,12 +28,11 @@ HRESULT UiaTextRange::GetSelectionRanges(_In_ IUiaData* pData, // create a range for each row for (const auto& rect : rectangles) { - ScreenInfoRow currentRow = rect.Top(); - Endpoint start = _screenInfoRowToEndpoint(pData, currentRow) + rect.Left(); - Endpoint end = _screenInfoRowToEndpoint(pData, currentRow) + rect.RightInclusive(); + const auto start = rect.Origin(); + const auto end = rect.EndInclusive(); ComPtr range; - RETURN_IF_FAILED(MakeAndInitialize(&range, pData, pProvider, start, end, false, wordDelimiters)); + RETURN_IF_FAILED(MakeAndInitialize(&range, pData, pProvider, start, end, wordDelimiters)); temporaryResult.emplace_back(std::move(range)); } std::swap(temporaryResult, ranges); @@ -60,12 +59,11 @@ HRESULT UiaTextRange::RuntimeClassInitialize(_In_ IUiaData* pData, // specific endpoint range HRESULT UiaTextRange::RuntimeClassInitialize(_In_ IUiaData* pData, _In_ IRawElementProviderSimple* const pProvider, - const Endpoint start, - const Endpoint end, - const bool degenerate, + const COORD start, + const COORD end, const std::wstring_view wordDelimiters) { - return UiaTextRangeBase::RuntimeClassInitialize(pData, pProvider, start, end, degenerate, wordDelimiters); + return UiaTextRangeBase::RuntimeClassInitialize(pData, pProvider, start, end, wordDelimiters); } // returns a degenerate text range of the start of the row closest to the y value of point @@ -122,7 +120,7 @@ IFACEMETHODIMP UiaTextRange::FindText(_In_ BSTR text, const auto sensitivity = ignoreCase ? Search::Sensitivity::CaseInsensitive : Search::Sensitivity::CaseSensitive; auto searchDirection = Search::Direction::Forward; - Endpoint searchAnchor = _start; + auto searchAnchor = _start; if (searchBackward) { searchDirection = Search::Direction::Backward; @@ -131,17 +129,19 @@ IFACEMETHODIMP UiaTextRange::FindText(_In_ BSTR text, CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); THROW_HR_IF(E_POINTER, !gci.HasActiveOutputBuffer()); - Search searcher{ gci.renderData, wstr, searchDirection, sensitivity, _endpointToCoord(_pData, searchAnchor) }; + Search searcher{ gci.renderData, wstr, searchDirection, sensitivity, searchAnchor }; HRESULT hr = S_OK; if (searcher.FindNext()) { const auto foundLocation = searcher.GetFoundLocation(); - const Endpoint start = _coordToEndpoint(_pData, foundLocation.first); - const Endpoint end = _coordToEndpoint(_pData, foundLocation.second); + const auto start = foundLocation.first; + const auto end = foundLocation.second; + const auto bufferSize = _pData->GetTextBuffer().GetSize(); + // make sure what was found is within the bounds of the current range - if ((searchDirection == Search::Direction::Forward && end < _end) || - (searchDirection == Search::Direction::Backward && start > _start)) + if ((searchDirection == Search::Direction::Forward && bufferSize.CompareInBounds(end, _end) < 0) || + (searchDirection == Search::Direction::Backward && bufferSize.CompareInBounds(start, _start) > 0)) { hr = Clone(ppRetVal); if (SUCCEEDED(hr)) @@ -149,7 +149,6 @@ IFACEMETHODIMP UiaTextRange::FindText(_In_ BSTR text, UiaTextRange& range = static_cast(**ppRetVal); range._start = start; range._end = end; - range._degenerate = false; } } } diff --git a/src/interactivity/win32/uiaTextRange.hpp b/src/interactivity/win32/uiaTextRange.hpp index a97579161d9..00016d1dab2 100644 --- a/src/interactivity/win32/uiaTextRange.hpp +++ b/src/interactivity/win32/uiaTextRange.hpp @@ -45,9 +45,8 @@ namespace Microsoft::Console::Interactivity::Win32 // specific endpoint range HRESULT RuntimeClassInitialize(_In_ Microsoft::Console::Types::IUiaData* pData, _In_ IRawElementProviderSimple* const pProvider, - const Endpoint start, - const Endpoint end, - const bool degenerate, + const COORD start, + const COORD end, _In_ const std::wstring_view wordDelimiters = DefaultWordDelimiter); // range from a UiaPoint diff --git a/src/types/ScreenInfoUiaProviderBase.cpp b/src/types/ScreenInfoUiaProviderBase.cpp index 452bf3c9022..ece644ebea3 100644 --- a/src/types/ScreenInfoUiaProviderBase.cpp +++ b/src/types/ScreenInfoUiaProviderBase.cpp @@ -340,12 +340,11 @@ IFACEMETHODIMP ScreenInfoUiaProviderBase::GetVisibleRanges(_Outptr_result_mayben RETURN_HR_IF_NULL(E_INVALIDARG, ppRetVal); *ppRetVal = nullptr; - const auto viewport = _getViewport(); - const COORD screenBufferCoords = _getScreenBufferCoords(); - const int totalLines = screenBufferCoords.Y; + const auto bufferSize = _pData->GetTextBuffer().GetSize(); + const auto viewport = bufferSize.ConvertToOrigin(_getViewport()); // make a safe array - const size_t rowCount = viewport.Height(); + const auto rowCount = viewport.Height(); *ppRetVal = SafeArrayCreateVector(VT_UNKNOWN, 0, gsl::narrow(rowCount)); if (*ppRetVal == nullptr) { @@ -353,19 +352,17 @@ IFACEMETHODIMP ScreenInfoUiaProviderBase::GetVisibleRanges(_Outptr_result_mayben } // stuff each visible line in the safearray - for (size_t i = 0; i < rowCount; ++i) + for (SHORT i = 0; i < rowCount; ++i) { - const int lineNumber = (viewport.Top() + i) % totalLines; - const int start = lineNumber * screenBufferCoords.X; - // - 1 to get the last column in the row - const int end = start + screenBufferCoords.X - 1; + // end is exclusive so add 1 + const COORD start = { viewport.Left(), viewport.Top() + i }; + const COORD end = { viewport.Left(), viewport.Top() + i + 1 }; HRESULT hr = S_OK; WRL::ComPtr range; hr = CreateTextRange(this, start, end, - false, _wordDelimiters, &range); if (FAILED(hr)) diff --git a/src/types/ScreenInfoUiaProviderBase.h b/src/types/ScreenInfoUiaProviderBase.h index 3491730873d..50f4f76b508 100644 --- a/src/types/ScreenInfoUiaProviderBase.h +++ b/src/types/ScreenInfoUiaProviderBase.h @@ -90,9 +90,8 @@ namespace Microsoft::Console::Types // specific endpoint range virtual HRESULT CreateTextRange(_In_ IRawElementProviderSimple* const pProvider, - const Endpoint start, - const Endpoint end, - const bool degenerate, + const COORD start, + const COORD end, const std::wstring_view wordDelimiters, _COM_Outptr_result_maybenull_ UiaTextRangeBase** ppUtr) = 0; diff --git a/src/types/UiaTextRangeBase.cpp b/src/types/UiaTextRangeBase.cpp index b5d91946c29..b2d06ae5bb3 100644 --- a/src/types/UiaTextRangeBase.cpp +++ b/src/types/UiaTextRangeBase.cpp @@ -9,85 +9,39 @@ using namespace Microsoft::Console::Types; using namespace Microsoft::Console::Types::UiaTextRangeBaseTracing; // toggle these for additional logging in a debug build +//#define _DEBUG 1 //#define UIATEXTRANGE_DEBUG_MSGS 1 #undef UIATEXTRANGE_DEBUG_MSGS IdType UiaTextRangeBase::id = 1; -UiaTextRangeBase::MoveState::MoveState(IUiaData* pData, - const UiaTextRangeBase& range, +UiaTextRangeBase::MoveState::MoveState(const UiaTextRangeBase& range, const MovementDirection direction) : start{ range.GetEndpoint(TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start) }, end{ range.GetEndpoint(TextPatternRangeEndpoint::TextPatternRangeEndpoint_End) }, Direction{ direction } { - if (direction == MovementDirection::Forward) - { - LimitingRow = UiaTextRangeBase::_getLastScreenInfoRowIndex(pData); - FirstColumnInRow = UiaTextRangeBase::_getFirstColumnIndex(); - LastColumnInRow = UiaTextRangeBase::_getLastColumnIndex(pData); - Increment = MovementIncrement::Forward; - } - else - { - LimitingRow = UiaTextRangeBase::_getFirstScreenInfoRowIndex(); - FirstColumnInRow = UiaTextRangeBase::_getLastColumnIndex(pData); - LastColumnInRow = UiaTextRangeBase::_getFirstColumnIndex(); - Increment = MovementIncrement::Backward; - } } UiaTextRangeBase::MoveState::MoveState(const COORD start, const COORD end, - const ScreenInfoRow limitingRow, - const Column firstColumnInRow, - const Column lastColumnInRow, - const MovementIncrement increment, const MovementDirection direction) noexcept : - start{ range.GetEndpoint(TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start) }, - end{ range.GetEndpoint(TextPatternRangeEndpoint::TextPatternRangeEndpoint_End) }, - LimitingRow{ limitingRow }, - FirstColumnInRow{ firstColumnInRow }, - LastColumnInRow{ lastColumnInRow }, - Increment{ increment }, + start{ start }, + end{ end }, Direction{ direction } { } #if _DEBUG #include -// This is a debugging function that prints out the current -// relationship between screen info rows, text buffer rows, and -// endpoints. -void UiaTextRangeBase::_outputRowConversions(IUiaData* pData) -{ - try - { - unsigned int totalRows = _getTotalRows(pData); - OutputDebugString(L"screenBuffer\ttextBuffer\tendpoint\n"); - for (unsigned int i = 0; i < totalRows; ++i) - { - std::wstringstream ss; - ss << i << "\t" << _screenInfoRowToTextBufferRow(pData, i) << "\t" << _screenInfoRowToEndpoint(pData, i) << "\n"; - std::wstring str = ss.str(); - OutputDebugString(str.c_str()); - } - OutputDebugString(L"\n"); - } - catch (...) - { - LOG_HR(wil::ResultFromCaughtException()); - } -} - void UiaTextRangeBase::_outputObjectState() { std::wstringstream ss; ss << "Object State"; ss << " _id: " << _id; - ss << " _start: " << _start; - ss << " _end: " << _end; - ss << " _degenerate: " << _degenerate; + ss << " _start: { " << _start.X << ", " << _start.Y << " }"; + ss << " _end: { " << _end.X << ", " << _end.Y << " }"; + ss << " _degenerate: " << IsDegenerate(); std::wstring str = ss.str(); OutputDebugString(str.c_str()); @@ -104,10 +58,9 @@ try RETURN_HR_IF_NULL(E_INVALIDARG, pData); _pProvider = pProvider; - _start = 0; - _end = 0; - _degenerate = true; _pData = pData; + _start = pData->GetViewport().Origin(); + _end = pData->GetViewport().Origin(); _wordDelimiters = wordDelimiters; _id = id; @@ -126,15 +79,14 @@ CATCH_RETURN(); #pragma warning(suppress : 26434) // WRL RuntimeClassInitialize base is a no-op and we need this for MakeAndInitialize HRESULT UiaTextRangeBase::RuntimeClassInitialize(_In_ IUiaData* pData, _In_ IRawElementProviderSimple* const pProvider, - const Cursor& cursor, + _In_ const Cursor& cursor, _In_ std::wstring_view wordDelimiters) noexcept { RETURN_IF_FAILED(RuntimeClassInitialize(pData, pProvider, wordDelimiters)); try { - _degenerate = true; - _start = _screenInfoRowToEndpoint(_pData, cursor.GetPosition().Y) + cursor.GetPosition().X; + _start = cursor.GetPosition(); _end = _start; #if defined(_DEBUG) && defined(UIATEXTRANGE_DEBUG_MSGS) @@ -152,17 +104,14 @@ HRESULT UiaTextRangeBase::RuntimeClassInitialize(_In_ IUiaData* pData, #pragma warning(suppress : 26434) // WRL RuntimeClassInitialize base is a no-op and we need this for MakeAndInitialize HRESULT UiaTextRangeBase::RuntimeClassInitialize(_In_ IUiaData* pData, _In_ IRawElementProviderSimple* const pProvider, - const Endpoint start, - const Endpoint end, - const bool degenerate, + _In_ const COORD start, + _In_ const COORD end, _In_ std::wstring_view wordDelimiters) noexcept { RETURN_IF_FAILED(RuntimeClassInitialize(pData, pProvider, wordDelimiters)); - RETURN_HR_IF(E_INVALIDARG, !degenerate && start > end); - _degenerate = degenerate; _start = start; - _end = degenerate ? start : end; + _end = end; #if defined(_DEBUG) && defined(UIATEXTRANGE_DEBUG_MSGS) OutputDebugString(L"Constructor\n"); @@ -195,7 +144,7 @@ void UiaTextRangeBase::Initialize(_In_ const UiaPoint point) _TranslatePointFromScreen(&clientPoint); const COORD currentFontSize = _getScreenFontSize(); - row = (clientPoint.y / currentFontSize.Y) + viewport.Top; + row = gsl::narrow(clientPoint.y / static_cast(currentFontSize.Y)) + viewport.Top; } // TODO CARLOS: double check that viewport.Top returns the correct text buffer position _start = { 0, row }; @@ -208,7 +157,6 @@ HRESULT UiaTextRangeBase::RuntimeClassInitialize(const UiaTextRangeBase& a) noex _pProvider = a._pProvider; _start = a._start; _end = a._end; - _degenerate = a._degenerate; _pData = a._pData; _id = id; @@ -229,14 +177,47 @@ const IdType UiaTextRangeBase::GetId() const noexcept const COORD UiaTextRangeBase::GetEndpoint(TextPatternRangeEndpoint endpoint) const noexcept { - switch(endpoint) + switch (endpoint) { - case TextPatternRangeEndpoint::TextPatternRangeEndpoint_End: - return _end; - case TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start: - default: - return _start; + case TextPatternRangeEndpoint::TextPatternRangeEndpoint_End: + return _end; + case TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start: + default: + return _start; + } +} + +// Routine Description: +// - sets the target endpoint to the given COORD value +// - if the target endpoint crosses the other endpoint, become a degenerate range +// Arguments: +// - endpoint - the target endpoint (start or end) +// - val - the value that it will be set to +// Return Value: +// - true if range is degenerate, false otherwise. +bool UiaTextRangeBase::SetEndpoint(TextPatternRangeEndpoint endpoint, const COORD val) noexcept +{ + const auto bufferSize = _pData->GetTextBuffer().GetSize(); + switch (endpoint) + { + case TextPatternRangeEndpoint::TextPatternRangeEndpoint_End: + _end = val; + if (bufferSize.CompareInBounds(_end, _start) < 0) + { + _start = _end; + } + break; + case TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start: + _start = val; + if (bufferSize.CompareInBounds(_start, _end) > 0) + { + _end = _start; + } + break; + default: + break; } + return IsDegenerate(); } // Routine Description: @@ -250,11 +231,10 @@ const bool UiaTextRangeBase::IsDegenerate() const noexcept return _start == _end; } -void UiaTextRangeBase::SetRangeValues(const Endpoint start, const Endpoint end, const bool isDegenerate) noexcept +void UiaTextRangeBase::SetEndpoints(const COORD start, const COORD end) noexcept { _start = start; _end = end; - _degenerate = isDegenerate; } #pragma region ITextRangeProvider @@ -307,7 +287,7 @@ IFACEMETHODIMP UiaTextRangeBase::CompareEndpoints(_In_ TextPatternRangeEndpoint const auto mine = GetEndpoint(endpoint); // compare them - *pRetVal = _pData->GetViewport().CompareInBounds(mine, other); + *pRetVal = _pData->GetTextBuffer().GetSize().CompareInBounds(mine, other); // TODO GitHub #1914: Re-attach Tracing to UIA Tree // tracing @@ -335,13 +315,13 @@ IFACEMETHODIMP UiaTextRangeBase::ExpandToEnclosingUnit(_In_ TextUnit unit) try { - const auto buffer = _pData->GetTextBuffer(); - const auto viewport = _pData->GetTextBuffer().GetSize(); + const auto& buffer = _pData->GetTextBuffer(); + const auto bufferSize = buffer.GetSize(); if (unit == TextUnit::TextUnit_Character) { _end = _start; - viewport.IncrementInBounds(&_end); + bufferSize.IncrementInBounds(_end); } else if (unit <= TextUnit::TextUnit_Word) { @@ -349,27 +329,24 @@ IFACEMETHODIMP UiaTextRangeBase::ExpandToEnclosingUnit(_In_ TextUnit unit) // TODO CARLOS: verify that the end is properly exclusive here _start = buffer.GetWordStart(_start, _wordDelimiters, true); _end = buffer.GetWordEnd(_start, _wordDelimiters, true); - FAIL_FAST_IF(!(_start <= _end)); } else if (unit <= TextUnit::TextUnit_Line) { // expand to line _start.X = 0; - _end = {viewport.RightInclusive(), _start.Y + 1}; - viewport.Clamp(&_end); - FAIL_FAST_IF(!(_start <= _end)); + _end.X = 0; + RETURN_IF_FAILED(ShortAdd(_start.Y, static_cast(1), &_end.Y)); } else { // expand to document - // TODO CARLOS: verify that the end is properly exclusive here - _start = viewport.Origin(); - _end = viewport.Dimensions(); + _start = bufferSize.Origin(); + _end = { bufferSize.RightInclusive(), bufferSize.BottomInclusive() }; } // TODO GitHub #1914: Re-attach Tracing to UIA Tree //Tracing::s_TraceUia(this, ApiCall::ExpandToEnclosingUnit, &apiMsg); - + FAIL_FAST_IF(bufferSize.CompareInBounds(_start, _end) > 0); return S_OK; } CATCH_RETURN(); @@ -406,6 +383,7 @@ IFACEMETHODIMP UiaTextRangeBase::GetAttributeValue(_In_ TEXTATTRIBUTEID textAttr return S_OK; } +// TODO CARLOS: Completely rewrite this. this will be a bit of a pain :/ IFACEMETHODIMP UiaTextRangeBase::GetBoundingRectangles(_Outptr_result_maybenull_ SAFEARRAY** ppRetVal) { _pData->LockConsole(); @@ -422,23 +400,67 @@ IFACEMETHODIMP UiaTextRangeBase::GetBoundingRectangles(_Outptr_result_maybenull_ // order: left, top, width, height. each line will have its own // set of coords. std::vector coords; - const TextBufferRow startRow = _endpointToTextBufferRow(_pData, _start); - if (_degenerate && _isScreenInfoRowInViewport(_pData, startRow)) + const auto bufferSize = _pData->GetTextBuffer().GetSize(); + + // these viewport vars are converted to the buffer coordinate space + const auto viewport = bufferSize.ConvertToOrigin(_pData->GetViewport()); + const auto viewportOrigin = viewport.Origin(); + const auto viewportEnd = viewport.EndInclusive(); + + // startAnchor: the earliest COORD we will get a bounding rect for + auto startAnchor = GetEndpoint(TextPatternRangeEndpoint_Start); + if (bufferSize.CompareInBounds(startAnchor, viewportOrigin) < 0) + { + // earliest we can be is the origin + startAnchor = viewportOrigin; + } + + // endAnchor: the latest COORD we will get a bounding rect for + auto endAnchor = GetEndpoint(TextPatternRangeEndpoint_End); + if (bufferSize.CompareInBounds(endAnchor, viewportEnd) > 0) { - _addScreenInfoRowBoundaries(_pData, _textBufferRowToScreenInfoRow(_pData, startRow), coords); + // latest we can be is the viewport end + endAnchor = viewportEnd; } else { - const unsigned int totalRowsInRange = _rowCountInRange(_pData); - for (unsigned int i = 0; i < totalRowsInRange; ++i) + // this is exclusive, let's be inclusive so we don't have to think about it anymore for bounding rects + bufferSize.DecrementInBounds(endAnchor); + } + + // Remember, start cannot be past end + FAIL_FAST_IF(bufferSize.CompareInBounds(_start, _end) > 0); + if (IsDegenerate()) + { + _getBoundingRect(_start, _start, coords); + } + else if (bufferSize.CompareInBounds(_start, viewportEnd) > 0 || bufferSize.CompareInBounds(_end, viewportOrigin) < 0) + { + // start is past the viewport end, or end is past the viewport origin... + // so draw nothing + } + else + { + for (auto row = startAnchor.Y; row <= endAnchor.Y; ++row) { - const ScreenInfoRow screenInfoRow = _textBufferRowToScreenInfoRow(_pData, startRow + i); - if (!_isScreenInfoRowInViewport(_pData, screenInfoRow)) + // assume that we are going to draw the entire row + COORD startCoord = { 0, row }; + COORD endCoord = { viewport.RightInclusive(), row }; + + if (row == startAnchor.Y) { - continue; + // first row --> reduce left side + startCoord.X = startAnchor.X; } - _addScreenInfoRowBoundaries(_pData, screenInfoRow, coords); + + if (row == endAnchor.Y) + { + // last row --> reduce right side + endCoord.X = endAnchor.X; + } + + _getBoundingRect(startCoord, endCoord, coords); } } @@ -502,53 +524,37 @@ IFACEMETHODIMP UiaTextRangeBase::GetText(_In_ int maxLength, _Out_ BSTR* pRetVal { try { - // TODO CARLOS: my attempt - const auto buffer = _pData->GetTextBuffer(); - - // TODO CARLOS: I'm pretty sure this is wrong, but we can fenangle this a bit to make it work somehow - auto iter = buffer.GetTextDataAt(_start, Viewport::FromCoord(_end)); - while (iter - && getPartialText - && wstr.size() > static_cast(maxLength)) - { - wstr += iter.Chars(); - } - - // END TODO CARLOS: my attempt - - - /* const ScreenInfoRow startScreenInfoRow = _endpointToScreenInfoRow(_pData, _start); - const Column startColumn = _endpointToColumn(_pData, _start); - const ScreenInfoRow endScreenInfoRow = _endpointToScreenInfoRow(_pData, _end); - const Column endColumn = _endpointToColumn(_pData, _end); - const unsigned int totalRowsInRange = _rowCountInRange(_pData); - const TextBuffer& textBuffer = _pData->GetTextBuffer(); - #if defined(_DEBUG) && defined(UIATEXTRANGE_DEBUG_MSGS) std::wstringstream ss; - ss << L"---Initial span start=" << _start << L" and end=" << _end << L"\n"; - ss << L"----Retrieving sr:" << startScreenInfoRow << L" sc:" << startColumn << L" er:" << endScreenInfoRow << L" ec:" << endColumn << L"\n"; + ss << L"---Initial span start={" << _start.X << L", " << _start.Y << L"} and end={" << _end.X << ", " << _end.Y << L"}\n"; OutputDebugString(ss.str().c_str()); #endif + // if _end is at 0, we ignore that row because _end is exclusive + const auto& buffer = _pData->GetTextBuffer(); + const unsigned int totalRowsInRange = (_end.X == buffer.GetSize().Left()) ? + _end.Y - _start.Y : + _end.Y - _start.Y + 1; + const auto lastRowInRange = _start.Y + totalRowsInRange - 1; + ScreenInfoRow currentScreenInfoRow = 0; for (unsigned int i = 0; i < totalRowsInRange; ++i) { - currentScreenInfoRow = startScreenInfoRow + i; - const ROW& row = textBuffer.GetRowByOffset(currentScreenInfoRow); + currentScreenInfoRow = _start.Y + i; + const ROW& row = buffer.GetRowByOffset(currentScreenInfoRow); if (row.GetCharRow().ContainsText()) { const size_t rowRight = row.GetCharRow().MeasureRight(); size_t startIndex = 0; size_t endIndex = rowRight; - if (currentScreenInfoRow == startScreenInfoRow) + if (currentScreenInfoRow == static_cast(_start.Y)) { - startIndex = startColumn; + startIndex = _start.X; } - if (currentScreenInfoRow == endScreenInfoRow) + else if (currentScreenInfoRow == static_cast(_end.Y)) { // prevent the end from going past the last non-whitespace char in the row - endIndex = std::min(gsl::narrow_cast(endColumn) + 1, rowRight); + endIndex = std::min(gsl::narrow_cast(_end.X) - 1, rowRight); } // if startIndex >= endIndex then _start is @@ -561,7 +567,7 @@ IFACEMETHODIMP UiaTextRangeBase::GetText(_In_ int maxLength, _Out_ BSTR* pRetVal } } - if (currentScreenInfoRow != endScreenInfoRow) + if (currentScreenInfoRow != lastRowInRange) { wstr += L"\r\n"; } @@ -573,7 +579,7 @@ IFACEMETHODIMP UiaTextRangeBase::GetText(_In_ int maxLength, _Out_ BSTR* pRetVal } } } - CATCH_RETURN(); */ + CATCH_RETURN(); } *pRetVal = SysAllocString(wstr.c_str()); @@ -618,7 +624,6 @@ IFACEMETHODIMP UiaTextRangeBase::Move(_In_ TextUnit unit, std::wstring data = ss.str(); OutputDebugString(data.c_str()); OutputDebugString(L"\n"); - _outputRowConversions(); #endif // Source: https://docs.microsoft.com/en-us/windows/win32/winauto/uiauto-implementingtextandtextrange @@ -676,35 +681,29 @@ IFACEMETHODIMP UiaTextRangeBase::MoveEndpointByUnit(_In_ TextPatternRangeEndpoin std::wstring data = ss.str(); OutputDebugString(data.c_str()); OutputDebugString(L"\n"); - _outputRowConversions(); #endif const MovementDirection moveDirection = (count > 0) ? MovementDirection::Forward : MovementDirection::Backward; - - std::function moveFunc = &_moveEndpointByUnitDocument; - if (unit == TextUnit::TextUnit_Character) - { - moveFunc = &_moveEndpointByUnitCharacter; - } - else if (unit <= TextUnit::TextUnit_Word) - { - // bind all params of this function, except put a _wordDelimiters in there - // NOTE: using a lambda function here because... - // - lambda is cheaper than std::bind - // - _move* functions are static, but this particular consumer needs access to a member (may be fixed by TODO GH #1993) - moveFunc = [=](auto&& pData, auto&& moveCount, auto&& endpoint, auto&& moveState, auto&& pAmountMoved) { - return _moveEndpointByUnitWord(pData, moveCount, endpoint, moveState, _wordDelimiters, pAmountMoved); - }; - } - else if (unit <= TextUnit::TextUnit_Line) - { - moveFunc = &_moveEndpointByUnitLine; - } + const MoveState moveState{ *this, moveDirection }; try { - const MoveState moveState{ _pData, *this, moveDirection }; - [_start, _end] = moveFunc(count, endpoint, moveState, pRetVal); + if (unit == TextUnit::TextUnit_Character) + { + _moveEndpointByUnitCharacter(count, endpoint, moveState, pRetVal); + } + else if (unit <= TextUnit::TextUnit_Word) + { + _moveEndpointByUnitWord(count, endpoint, moveState, _wordDelimiters, pRetVal); + } + else if (unit <= TextUnit::TextUnit_Line) + { + _moveEndpointByUnitLine(count, endpoint, moveState, pRetVal); + } + else if (unit <= TextUnit::TextUnit_Document) + { + _moveEndpointByUnitDocument(count, endpoint, moveState, pRetVal); + } } CATCH_RETURN(); @@ -748,19 +747,20 @@ IFACEMETHODIMP UiaTextRangeBase::MoveEndpointByRange(_In_ TextPatternRangeEndpoi std::wstring data = ss.str(); OutputDebugString(data.c_str()); OutputDebugString(L"\n"); - _outputRowConversions(); #endif // TODO CARLOS: Double check if we get the corner cases right here. Not too sure about it. // TODO CARLOS: Make sure we get the degenerate ranges correct here too - switch(endpoint) - { - case TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start: - _start = range->GetEndpoint(targetEndpoint); - case TextPatternRangeEndpoint::TextPatternRangeEndpoint_End: - _end = range->GetEndpoint(targetEndpoint); - default: - return E_INVALIDARG; + switch (endpoint) + { + case TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start: + _start = range->GetEndpoint(targetEndpoint); + break; + case TextPatternRangeEndpoint::TextPatternRangeEndpoint_End: + _end = range->GetEndpoint(targetEndpoint); + break; + default: + return E_INVALIDARG; } // TODO GitHub #1914: Re-attach Tracing to UIA Tree @@ -782,7 +782,9 @@ IFACEMETHODIMP UiaTextRangeBase::Select() } else { - _pData->SelectNewRegion(coordStart, coordEnd); + auto temp = _end; + _pData->GetTextBuffer().GetSize().DecrementInBounds(temp); + _pData->SelectNewRegion(_start, temp); } // TODO GitHub #1914: Re-attach Tracing to UIA Tree @@ -821,8 +823,8 @@ IFACEMETHODIMP UiaTextRangeBase::ScrollIntoView(_In_ BOOL alignToTop) const auto startScreenInfoRow = _start.Y; const auto endScreenInfoRow = _end.Y; // screen buffer rows - const auto topRow = _getFirstScreenInfoRowIndex(); - const auto bottomRow = _getLastScreenInfoRowIndex(_pData); + const auto topRow = 0; + const auto bottomRow = _pData->GetTextBuffer().TotalRowCount() - 1; SMALL_RECT newViewport = oldViewport; @@ -849,7 +851,7 @@ IFACEMETHODIMP UiaTextRangeBase::ScrollIntoView(_In_ BOOL alignToTop) { // we need to align to the bottom // check if we can align to the bottom - if (endScreenInfoRow >= viewportHeight) + if (static_cast(endScreenInfoRow) >= viewportHeight) { // we can align to bottom newViewport.Bottom = gsl::narrow(endScreenInfoRow); @@ -898,11 +900,6 @@ IFACEMETHODIMP UiaTextRangeBase::GetChildren(_Outptr_result_maybenull_ SAFEARRAY #pragma endregion -const COORD UiaTextRangeBase::_getScreenBufferCoords(gsl::not_null pData) -{ - return pData->GetTextBuffer().GetSize().Dimensions(); -} - const COORD UiaTextRangeBase::_getScreenFontSize() const { COORD coordRet = _pData->GetFontInfo().GetSize(); @@ -914,122 +911,6 @@ const COORD UiaTextRangeBase::_getScreenFontSize() const return coordRet; } -// Routine Description: -// - Gets the number of rows in the output text buffer. -// Arguments: -// - pData - the UiaData for the terminal we are operating on -// Return Value: -// - The number of rows -const unsigned int UiaTextRangeBase::_getTotalRows(gsl::not_null pData) noexcept -{ - return pData->GetTextBuffer().TotalRowCount(); -} - -// Routine Description: -// - Gets the width of the screen buffer rows -// Arguments: -// - pData - the UiaData for the terminal we are operating on -// Return Value: -// - The row width -const unsigned int UiaTextRangeBase::_getRowWidth(gsl::not_null pData) -{ - // make sure that we can't leak a 0 - return std::max(static_cast(_getScreenBufferCoords(pData).X), 1u); -} - -// Routine Description: -// - calculates the column refered to by the endpoint. -// Arguments: -// - pData - the UiaData for the terminal we are operating on -// - endpoint - the endpoint to translate -// Return Value: -// - the column value -const Column UiaTextRangeBase::_endpointToColumn(gsl::not_null pData, const Endpoint endpoint) -{ - return endpoint % _getRowWidth(pData); -} - -// Routine Description: -// - converts an Endpoint into its equivalent text buffer row. -// Arguments: -// - pData - the UiaData for the terminal we are operating on -// - endpoint - the endpoint to convert -// Return Value: -// - the text buffer row value -const TextBufferRow UiaTextRangeBase::_endpointToTextBufferRow(gsl::not_null pData, - const Endpoint endpoint) -{ - return endpoint / _getRowWidth(pData); -} - -// Routine Description: -// - counts the number of rows that are fully or partially part of the -// range. -// Arguments: -// - pData - the UiaData for the terminal we are operating on -// Return Value: -// - The number of rows in the range. -const unsigned int UiaTextRangeBase::_rowCountInRange(gsl::not_null pData) const -{ - if (_degenerate) - { - return 0; - } - - const ScreenInfoRow startScreenInfoRow = _endpointToScreenInfoRow(pData, _start); - const Column startColumn = _endpointToColumn(pData, _start); - const ScreenInfoRow endScreenInfoRow = _endpointToScreenInfoRow(pData, _end); - const Column endColumn = _endpointToColumn(pData, _end); - - FAIL_FAST_IF(!(_compareScreenCoords(pData, startScreenInfoRow, startColumn, endScreenInfoRow, endColumn) <= 0)); - - // + 1 to balance subtracting ScreenInfoRows from each other - return endScreenInfoRow - startScreenInfoRow + 1; -} - -// Routine Description: -// - Converts a TextBufferRow to a ScreenInfoRow. -// Arguments: -// - pData - the UiaData for the terminal we are operating on -// - row - the TextBufferRow to convert -// Return Value: -// - the equivalent ScreenInfoRow. -const ScreenInfoRow UiaTextRangeBase::_textBufferRowToScreenInfoRow(gsl::not_null pData, - const TextBufferRow row) noexcept -{ - const int firstRowIndex = pData->GetTextBuffer().GetFirstRowIndex(); - return _normalizeRow(pData, row - firstRowIndex); -} - -// Routine Description: -// - Converts a ScreenInfoRow to a ViewportRow. Uses the default -// viewport for the conversion. -// Arguments: -// - pData - the UiaData for the terminal we are operating on -// - row - the ScreenInfoRow to convert -// Return Value: -// - the equivalent ViewportRow. -const ViewportRow UiaTextRangeBase::_screenInfoRowToViewportRow(gsl::not_null pData, const ScreenInfoRow row) noexcept -{ - const SMALL_RECT viewport = pData->GetViewport().ToInclusive(); - return _screenInfoRowToViewportRow(row, viewport); -} - -// Routine Description: -// - normalizes the row index to within the bounds of the output -// buffer. The output buffer stores the text in a circular buffer so -// this method makes sure that we circle around gracefully. -// Arguments: -// - pData - the UiaData for the terminal we are operating on -// - row - the non-normalized row index -// Return Value: -// - the normalized row index -const Row UiaTextRangeBase::_normalizeRow(gsl::not_null pData, const Row row) noexcept -{ - const unsigned int totalRows = _getTotalRows(pData); - return ((row + totalRows) % totalRows); -} - // Routine Description: // - Gets the viewport height, measured in char rows. // Arguments: @@ -1045,179 +926,44 @@ const unsigned int UiaTextRangeBase::_getViewportHeight(const SMALL_RECT viewpor } // Routine Description: -// - Gets the viewport width, measured in char columns. -// Arguments: -// - viewport - The viewport to measure -// Return Value: -// - The viewport width -const unsigned int UiaTextRangeBase::_getViewportWidth(const SMALL_RECT viewport) noexcept -{ - FAIL_FAST_IF(!(viewport.Right >= viewport.Left)); - - // + 1 because COORD is inclusive on both sides so subtracting left - // and right gets rid of 1 more then it should. - return (viewport.Right - viewport.Left + 1); -} - -// Routine Description: -// - checks if the row is currently visible in the viewport. Uses the -// default viewport. -// Arguments: -// - pData - the UiaData for the terminal we are operating on -// - row - the screen info row to check -// Return Value: -// - true if the row is within the bounds of the viewport -const bool UiaTextRangeBase::_isScreenInfoRowInViewport(gsl::not_null pData, - const ScreenInfoRow row) noexcept -{ - return _isScreenInfoRowInViewport(row, pData->GetViewport().ToInclusive()); -} - -// Routine Description: -// - checks if the row is currently visible in the viewport -// Arguments: -// - row - the row to check -// - viewport - the viewport to use for the bounds -// Return Value: -// - true if the row is within the bounds of the viewport -const bool UiaTextRangeBase::_isScreenInfoRowInViewport(const ScreenInfoRow row, - const SMALL_RECT viewport) noexcept -{ - const ViewportRow viewportRow = _screenInfoRowToViewportRow(row, viewport); - return viewportRow >= 0 && - viewportRow < gsl::narrow(_getViewportHeight(viewport)); -} - -// Routine Description: -// - Converts a ScreenInfoRow to a TextBufferRow. -// Arguments: -// - pData - the UiaData for the terminal we are operating on -// - row - the ScreenInfoRow to convert -// Return Value: -// - the equivalent TextBufferRow. -const TextBufferRow UiaTextRangeBase::_screenInfoRowToTextBufferRow(gsl::not_null pData, - const ScreenInfoRow row) noexcept -{ - const TextBufferRow firstRowIndex = pData->GetTextBuffer().GetFirstRowIndex(); - return _normalizeRow(pData, row + firstRowIndex); -} - -// Routine Description: -// - Converts a TextBufferRow to an Endpoint. -// Arguments: -// - pData - the UiaData for the terminal we are operating on -// - row - the TextBufferRow to convert -// Return Value: -// - the equivalent Endpoint, starting at the beginning of the TextBufferRow. -const Endpoint UiaTextRangeBase::_textBufferRowToEndpoint(gsl::not_null pData, const TextBufferRow row) -{ - return _getRowWidth(pData) * row; -} - -// Routine Description: -// - Finds the beginning of the word (Endpoint) for the current target (Endpoint). -// Arguments: -// - pData - the UiaData for the terminal we are operating on -// - target - the Endpoint that is on the word we want to expand to -// - wordDelimiters - characters to determine the separation between words -// Return Value: -// - the equivalent Endpoint, starting at the beginning of the word where _start is located. -const Endpoint UiaTextRangeBase::_wordBeginEndpoint(gsl::not_null pData, Endpoint target, const std::wstring_view wordDelimiters) -{ - auto coord = _endpointToCoord(pData, target); - coord = pData->GetTextBuffer().GetWordStart(coord, wordDelimiters); - return _coordToEndpoint(pData, coord); -} - -// Routine Description: -// - Finds the end of the word (Endpoint) for the current target (Endpoint). -// - The "end of the word" is defined to include the following: -// any word break characters that are present at the end of the "word", but before the start of the next word. -// Arguments: -// - pData - the UiaData for the terminal we are operating on -// - target - the Endpoint that is on the word we want to expand to -// - wordDelimiters - characters to determine the separation between words -// Return Value: -// - the equivalent Endpoint, starting at the end of the word where _start is located. -const Endpoint UiaTextRangeBase::_wordEndEndpoint(gsl::not_null pData, Endpoint target, const std::wstring_view wordDelimiters) -{ - auto coord = _endpointToCoord(pData, target); - coord = pData->GetTextBuffer().GetWordEnd(coord, wordDelimiters, true); - return _coordToEndpoint(pData, coord); -} - -// Routine Description: -// - Converts a ScreenInfoRow to an Endpoint. -// Arguments: -// - pData - the UiaData for the terminal we are operating on -// - row - the ScreenInfoRow to convert -// Return Value: -// - the equivalent Endpoint. -const Endpoint UiaTextRangeBase::_screenInfoRowToEndpoint(gsl::not_null pData, - const ScreenInfoRow row) -{ - return _textBufferRowToEndpoint(pData, _screenInfoRowToTextBufferRow(pData, row)); -} - -// Routine Description: -// - Converts an Endpoint to an ScreenInfoRow. -// Arguments: -// - pData - the UiaData for the terminal we are operating on -// - endpoint - the endpoint to convert -// Return Value: -// - the equivalent ScreenInfoRow. -const ScreenInfoRow UiaTextRangeBase::_endpointToScreenInfoRow(gsl::not_null pData, - const Endpoint endpoint) -{ - return _textBufferRowToScreenInfoRow(pData, _endpointToTextBufferRow(pData, endpoint)); -} - -// Routine Description: -// - adds the relevant coordinate points from screenInfoRow to coords. +// - adds the relevant coordinate points from the row to coords. +// - it is assumed that startAnchor and endAnchor are within the same row // Arguments: -// - pData - the UiaData for the terminal we are operating on -// - screenInfoRow - row to calculate coordinate positions from -// - coords - vector to add the calucated coords to +// - startAnchor - the start anchor of interested data within the viewport. In text buffer coordinate space. Inclusive. +// - endAnchor - the end anchor of interested data within the viewport. In text buffer coordinate space. Inclusive +// - coords - vector to add the calculated coords to // Return Value: // - -// Notes: -// - alters coords. may throw an exception. -void UiaTextRangeBase::_addScreenInfoRowBoundaries(gsl::not_null pData, - const ScreenInfoRow screenInfoRow, - _Inout_ std::vector& coords) const +void UiaTextRangeBase::_getBoundingRect(_In_ const COORD startAnchor, _In_ const COORD endAnchor, _Inout_ std::vector& coords) const { - const COORD currentFontSize = _getScreenFontSize(); + FAIL_FAST_IF(startAnchor.Y != endAnchor.Y); + + const auto viewport = _pData->GetViewport(); + const auto currentFontSize = _getScreenFontSize(); POINT topLeft{ 0 }; POINT bottomRight{ 0 }; - if (_endpointToScreenInfoRow(pData, _start) == screenInfoRow) - { - // start is somewhere in this row so we start from its position - topLeft.x = _endpointToColumn(pData, _start) * currentFontSize.X; - } - else - { - // otherwise we start from the beginning of the row - topLeft.x = 0; - } - - topLeft.y = _screenInfoRowToViewportRow(pData, screenInfoRow) * currentFontSize.Y; + // startAnchor is converted to the viewport coordinate space + auto startCoord = startAnchor; + viewport.ConvertToOrigin(&startCoord); + topLeft.x = startCoord.X * currentFontSize.X; + topLeft.y = startCoord.Y * currentFontSize.Y; - if (_endpointToScreenInfoRow(pData, _end) == screenInfoRow) + if (IsDegenerate()) { - // the endpoints are on the same row - bottomRight.x = (_endpointToColumn(pData, _end) + 1) * currentFontSize.X; + bottomRight.x = (startCoord.X) * currentFontSize.X; + bottomRight.y = (startCoord.Y + 1) * currentFontSize.Y; } else { - // _end is not on this row so span to the end of the row - bottomRight.x = _getViewportWidth(_pData->GetViewport().ToInclusive()) * currentFontSize.X; + // endAnchor is converted to the viewport coordinate space + auto endCoord = endAnchor; + viewport.ConvertToOrigin(&endCoord); + bottomRight.x = (endCoord.X + 1) * currentFontSize.X; + bottomRight.y = (endCoord.Y + 1) * currentFontSize.Y; } - // we add the font height only once here because we are adding each line individually - bottomRight.y = topLeft.y + currentFontSize.Y; - // convert the coords to be relative to the screen instead of // the client window _TranslatePointToScreen(&topLeft); @@ -1234,493 +980,130 @@ void UiaTextRangeBase::_addScreenInfoRowBoundaries(gsl::not_null pDat } // Routine Description: -// - returns the index of the first row of the screen info +// - moves the UTR's endpoint by moveCount times by character. +// - if endpoints crossed, the degenerate range is created and both endpoints are moved // Arguments: -// - +// - moveCount - the number of times to move +// - endpoint - the endpoint to move +// - moveState - values indicating the state of the console for the +// move operation +// - pAmountMoved - the number of times that the return values are "moved" // Return Value: -// - the index of the first row (0-indexed) of the screen info -const unsigned int UiaTextRangeBase::_getFirstScreenInfoRowIndex() noexcept +// - +void UiaTextRangeBase::_moveEndpointByUnitCharacter(_In_ const int moveCount, + _In_ const TextPatternRangeEndpoint endpoint, + _In_ const MoveState moveState, + _Out_ gsl::not_null const pAmountMoved) { - return 0; -} + *pAmountMoved = 0; -// Routine Description: -// - returns the index of the last row of the screen info -// Arguments: -// - pData - the UiaData for the terminal we are operating on -// Return Value: -// - the index of the last row (0-indexed) of the screen info -const unsigned int UiaTextRangeBase::_getLastScreenInfoRowIndex(gsl::not_null pData) noexcept -{ - return _getTotalRows(pData) - 1; -} + if (moveCount == 0) + { + return; + } -// Routine Description: -// - returns the index of the first column of the screen info rows -// Arguments: -// - -// Return Value: -// - the index of the first column (0-indexed) of the screen info rows -const Column UiaTextRangeBase::_getFirstColumnIndex() noexcept -{ - return 0; -} + const auto bufferSize = _pData->GetTextBuffer().GetSize(); + auto target = GetEndpoint(endpoint); + while (abs(*pAmountMoved) < abs(moveCount) && bufferSize.IsInBounds(target)) + { + switch (moveState.Direction) + { + case MovementDirection::Forward: + bufferSize.IncrementInBounds(target); + *pAmountMoved += 1; + break; + case MovementDirection::Backward: + bufferSize.DecrementInBounds(target); + *pAmountMoved -= 1; + break; + default: + break; + } + } -// Routine Description: -// - returns the index of the last column of the screen info rows -// Arguments: -// - pData - the UiaData for the terminal we are operating on -// Return Value: -// - the index of the last column (0-indexed) of the screen info rows -const Column UiaTextRangeBase::_getLastColumnIndex(gsl::not_null pData) -{ - return _getRowWidth(pData) - 1; + // if we went out of bounds, be at the corner of the buffer + if (!bufferSize.IsInBounds(target)) + { + if (moveState.Direction == MovementDirection::Forward) + { + target = bufferSize.EndInclusive(); + } + else + { + target = bufferSize.Origin(); + } + } + + SetEndpoint(endpoint, target); } // Routine Description: -// - Compares two sets of screen info coordinates +// - moves the UTR's endpoint by moveCount times by word. +// - if endpoints crossed, the degenerate range is created and both endpoints are moved // Arguments: -// - pData - the UiaData for the terminal we are operating on -// - rowA - the row index of the first position -// - colA - the column index of the first position -// - rowB - the row index of the second position -// - colB - the column index of the second position +// - moveCount - the number of times to move +// - endpoint - the endpoint to move +// - moveState - values indicating the state of the console for the +// move operation +// - pAmountMoved - the number of times that the return values are "moved" // Return Value: -// -1 if A < B -// 1 if A > B -// 0 if A == B -const int UiaTextRangeBase::_compareScreenCoords(gsl::not_null pData, - const ScreenInfoRow rowA, - const Column colA, - const ScreenInfoRow rowB, - const Column colB) -{ - FAIL_FAST_IF(!(rowA >= _getFirstScreenInfoRowIndex())); - FAIL_FAST_IF(!(rowA <= _getLastScreenInfoRowIndex(pData))); - - FAIL_FAST_IF(!(colA >= _getFirstColumnIndex())); - FAIL_FAST_IF(!(colA <= _getLastColumnIndex(pData))); - - FAIL_FAST_IF(!(rowB >= _getFirstScreenInfoRowIndex())); - FAIL_FAST_IF(!(rowB <= _getLastScreenInfoRowIndex(pData))); - - FAIL_FAST_IF(!(colB >= _getFirstColumnIndex())); - FAIL_FAST_IF(!(colB <= _getLastColumnIndex(pData))); - - if (rowA < rowB) - { - return -1; - } - else if (rowA > rowB) - { - return 1; - } - // rowA == rowB - else if (colA < colB) - { - return -1; - } - else if (colA > colB) - { - return 1; - } - // colA == colB - return 0; -} - -// Routine Description: -// - calculates new Endpoints if they were to be moved moveCount times -// by character. -// Arguments: -// - pData - the UiaData for the terminal we are operating on -// - moveCount - the number of times to move -// - moveState - values indicating the state of the console for the -// move operation -// - pAmountMoved - the number of times that the return values are "moved" -// Return Value: -// - a pair of endpoints of the form -std::pair UiaTextRangeBase::_moveByCharacter(gsl::not_null pData, - const int moveCount, - const MoveState moveState, - _Out_ gsl::not_null const pAmountMoved) -{ - if (moveState.Direction == MovementDirection::Forward) - { - return _moveByCharacterForward(pData, moveCount, moveState, pAmountMoved); - } - else - { - return _moveByCharacterBackward(pData, moveCount, moveState, pAmountMoved); - } -} - -std::pair UiaTextRangeBase::_moveByCharacterForward(gsl::not_null pData, - const int moveCount, - const MoveState moveState, - _Out_ gsl::not_null const pAmountMoved) +// - +void UiaTextRangeBase::_moveEndpointByUnitWord(_In_ const int moveCount, + _In_ const TextPatternRangeEndpoint endpoint, + _In_ const MoveState moveState, + _In_ const std::wstring_view wordDelimiters, + _Out_ gsl::not_null const pAmountMoved) { *pAmountMoved = 0; - ScreenInfoRow currentScreenInfoRow = moveState.StartScreenInfoRow; - Column currentColumn = moveState.StartColumn; - for (int i = 0; i < abs(moveCount); ++i) + if (moveCount == 0) { - // get the current row's right - const ROW& row = pData->GetTextBuffer().GetRowByOffset(currentScreenInfoRow); - const size_t right = row.GetCharRow().MeasureRight(); - const auto expectedColumn = gsl::narrow_cast(currentColumn) + 1; - - // check if we're at the edge of the screen info buffer - if (currentScreenInfoRow == moveState.LimitingRow && - expectedColumn >= right) - { - break; - } - else if (expectedColumn >= right) - { - // we're at the edge of a row and need to go to the next one - currentColumn = moveState.FirstColumnInRow; - currentScreenInfoRow += static_cast(moveState.Increment); - } - else - { - // moving somewhere away from the edges of a row - currentColumn += static_cast(moveState.Increment); - } - *pAmountMoved += static_cast(moveState.Increment); - - FAIL_FAST_IF(!(currentColumn >= _getFirstColumnIndex())); - FAIL_FAST_IF(!(currentColumn <= _getLastColumnIndex(pData))); - FAIL_FAST_IF(!(currentScreenInfoRow >= _getFirstScreenInfoRowIndex())); - FAIL_FAST_IF(!(currentScreenInfoRow <= _getLastScreenInfoRowIndex(pData))); + return; } - Endpoint start = _screenInfoRowToEndpoint(pData, currentScreenInfoRow) + currentColumn; - Endpoint end = start; - return std::make_pair(std::move(start), std::move(end)); -} - -std::pair UiaTextRangeBase::_moveByCharacterBackward(gsl::not_null pData, - const int moveCount, - const MoveState moveState, - _Out_ gsl::not_null const pAmountMoved) -{ - *pAmountMoved = 0; - ScreenInfoRow currentScreenInfoRow = moveState.StartScreenInfoRow; - Column currentColumn = moveState.StartColumn; - - for (int i = 0; i < abs(moveCount); ++i) + const auto& buffer = _pData->GetTextBuffer(); + const auto bufferSize = buffer.GetSize(); + auto target = GetEndpoint(endpoint); + while (abs(*pAmountMoved) < abs(moveCount) && bufferSize.IsInBounds(target)) { - // check if we're at the edge of the screen info buffer - if (currentScreenInfoRow == moveState.LimitingRow && - currentColumn == moveState.LastColumnInRow) + switch (moveState.Direction) { + case MovementDirection::Forward: + // Get the end of the word (including the delimiter run), + // but move one more forward to be on the next word's character + buffer.GetWordEnd(target, wordDelimiters, /*includeDelimiterRun*/ true); + bufferSize.IncrementInBounds(target); + *pAmountMoved += 1; break; - } - else if (currentColumn == moveState.LastColumnInRow) - { - // we're at the edge of a row and need to go to the - // next one. move to the cell with the last non-whitespace charactor - - currentScreenInfoRow += static_cast(moveState.Increment); - // get the right cell for the next row - const ROW& row = pData->GetTextBuffer().GetRowByOffset(currentScreenInfoRow); - const size_t right = row.GetCharRow().MeasureRight(); - currentColumn = gsl::narrow((right == 0) ? 0 : right - 1); - } - else - { - // moving somewhere away from the edges of a row - currentColumn += static_cast(moveState.Increment); - } - *pAmountMoved += static_cast(moveState.Increment); - - FAIL_FAST_IF(!(currentColumn >= _getFirstColumnIndex())); - FAIL_FAST_IF(!(currentColumn <= _getLastColumnIndex(pData))); - FAIL_FAST_IF(!(currentScreenInfoRow >= _getFirstScreenInfoRowIndex())); - FAIL_FAST_IF(!(currentScreenInfoRow <= _getLastScreenInfoRowIndex(pData))); - } - - Endpoint start = _screenInfoRowToEndpoint(pData, currentScreenInfoRow) + currentColumn; - Endpoint end = start; - return std::make_pair(std::move(start), std::move(end)); -} - -// Routine Description: -// - calculates new Endpoints if they were to be moved moveCount times -// by word. -// Arguments: -// - pData - the UiaData for the terminal we are operating on -// - moveCount - the number of times to move -// - moveState - values indicating the state of the console for the -// move operation -// - wordDelimiters - the list of characters that define a separation of different words -// - pAmountMoved - the number of times that the return values are "moved" -// Return Value: -// - a pair of endpoints of the form -std::pair UiaTextRangeBase::_moveByWord(gsl::not_null pData, - const int moveCount, - const MoveState moveState, - const std::wstring_view wordDelimiters, - _Out_ gsl::not_null const pAmountMoved) -{ - if (moveState.Direction == MovementDirection::Forward) - { - return _moveByWordForward(pData, moveCount, moveState, wordDelimiters, pAmountMoved); - } - else - { - return _moveByWordBackward(pData, moveCount, moveState, wordDelimiters, pAmountMoved); - } -} - -// Routine Description: -// - calculates new Endpoints if they were to be moved moveCount times in the forward direction -// by word. -// Arguments: -// - pData - the UiaData for the terminal we are operating on -// - moveCount - the number of times to move -// - moveState - values indicating the state of the console for the -// move operation -// - wordDelimiters - the list of characters that define a separation of different words -// - pAmountMoved - the number of times that the return values are "moved" -// Return Value: -// - a pair of endpoints of the form -std::pair UiaTextRangeBase::_moveByWordForward(gsl::not_null pData, - const int moveCount, - const MoveState moveState, - const std::wstring_view wordDelimiters, - _Out_ gsl::not_null const pAmountMoved) -{ - // STRATEGY: - // - move the "end" Endpoint to the proper place (if need to move multiple times, do it here) - // - find the "start" Endpoint based on that - - *pAmountMoved = 0; - ScreenInfoRow currentScreenInfoRow = moveState.EndScreenInfoRow; - Column currentColumn = moveState.EndColumn; - - auto& buffer = pData->GetTextBuffer(); - for (int i = 0; i < abs(moveCount); ++i) - { - // get the current row's right - const ROW& row = buffer.GetRowByOffset(currentScreenInfoRow); - const size_t right = row.GetCharRow().MeasureRight(); - const auto expectedColumn = gsl::narrow_cast(currentColumn) + 1; - - // check if we're at the edge of the screen info buffer - if (currentScreenInfoRow == moveState.LimitingRow && - expectedColumn >= right) - { + case MovementDirection::Backward: + buffer.GetWordStart(target, wordDelimiters, /*includeCharacterRun*/ true); + // NOTE: no need for bufferSize.DecrementInBounds(target) here, because we're already on the word + *pAmountMoved -= 1; + break; + default: break; } - else if (expectedColumn >= right) - { - // we're at the edge of a row and need to go to the next one - currentColumn = moveState.FirstColumnInRow; - currentScreenInfoRow += static_cast(moveState.Increment); - - const auto point = _screenInfoRowToEndpoint(pData, currentScreenInfoRow) + currentColumn; - auto target = _endpointToCoord(pData, point); - - target = buffer.GetWordEnd(target, wordDelimiters, true); - - currentColumn = target.X; - } - else - { - // moving somewhere away from the edges of a row - const Endpoint point = _screenInfoRowToEndpoint(pData, currentScreenInfoRow) + currentColumn; - auto target = _endpointToCoord(pData, point); - buffer.GetSize().IncrementInBounds(target); - - target = (moveState.Increment == MovementIncrement::Forward) ? - buffer.GetWordEnd(target, wordDelimiters, true) : - buffer.GetWordStart(target, wordDelimiters, true); - - currentColumn = target.X; - } - *pAmountMoved += static_cast(moveState.Increment); - - FAIL_FAST_IF(!(currentColumn >= _getFirstColumnIndex())); - FAIL_FAST_IF(!(currentColumn <= _getLastColumnIndex(pData))); - FAIL_FAST_IF(!(currentScreenInfoRow >= _getFirstScreenInfoRowIndex())); - FAIL_FAST_IF(!(currentScreenInfoRow <= _getLastScreenInfoRowIndex(pData))); } - Endpoint end = _screenInfoRowToEndpoint(pData, currentScreenInfoRow) + currentColumn; - - auto target = _endpointToCoord(pData, end); - target = buffer.GetWordStart(target, wordDelimiters, true); - Endpoint start = _coordToEndpoint(pData, target); - - return std::make_pair(std::move(start), std::move(end)); -} - -// Routine Description: -// - calculates new Endpoints if they were to be moved moveCount times in the backwards direction -// by word. -// Arguments: -// - pData - the UiaData for the terminal we are operating on -// - moveCount - the number of times to move -// - moveState - values indicating the state of the console for the -// move operation -// - wordDelimiters - the list of characters that define a separation of different words -// - pAmountMoved - the number of times that the return values are "moved" -// Return Value: -// - a pair of endpoints of the form -std::pair UiaTextRangeBase::_moveByWordBackward(gsl::not_null pData, - const int moveCount, - const MoveState moveState, - const std::wstring_view wordDelimiters, - _Out_ gsl::not_null const pAmountMoved) -{ - // STRATEGY: - // - move the "start" Endpoint to the proper place (if need to move multiple times, do it here) - // - find the "end" Endpoint based on that - - *pAmountMoved = 0; - ScreenInfoRow currentScreenInfoRow = moveState.StartScreenInfoRow; - Column currentColumn = moveState.StartColumn; - - auto& buffer = pData->GetTextBuffer(); - for (int i = 0; i < abs(moveCount); ++i) + // if we went out of bounds, be at the corner of the buffer + if (!bufferSize.IsInBounds(target)) { - // check if we're at the edge of the screen info buffer - if (currentScreenInfoRow == moveState.LimitingRow && - currentColumn == moveState.LastColumnInRow) - { - break; - } - else if (currentColumn == moveState.LastColumnInRow) + if (moveState.Direction == MovementDirection::Forward) { - // we're at the edge of a row and need to go to the - // previous one. move to the cell with the last non-whitespace charactor - - currentScreenInfoRow += static_cast(moveState.Increment); - - // get the right-most char for the previous row - const ROW& row = buffer.GetRowByOffset(currentScreenInfoRow); - const size_t right = row.GetCharRow().MeasureRight(); - currentColumn = gsl::narrow((right == 0) ? 0 : right - 1); - - // get the right-most word for the previous row - const auto point = _screenInfoRowToEndpoint(pData, currentScreenInfoRow) + currentColumn; - auto target = _endpointToCoord(pData, point); - - target = buffer.GetWordStart(target, wordDelimiters, true); - buffer.GetSize().IncrementInBounds(target); - - currentColumn = target.X; + target = bufferSize.EndInclusive(); } else { - // moving somewhere away from the edges of a row - const Endpoint point = _screenInfoRowToEndpoint(pData, currentScreenInfoRow) + currentColumn; - auto target = _endpointToCoord(pData, point); - buffer.GetSize().DecrementInBounds(target); - - target = (moveState.Increment == MovementIncrement::Forward) ? - buffer.GetWordEnd(target, wordDelimiters, true) : - buffer.GetWordStart(target, wordDelimiters, true); - - currentColumn = target.X; - } - *pAmountMoved += static_cast(moveState.Increment); - - FAIL_FAST_IF(!(currentColumn >= _getFirstColumnIndex())); - FAIL_FAST_IF(!(currentColumn <= _getLastColumnIndex(pData))); - FAIL_FAST_IF(!(currentScreenInfoRow >= _getFirstScreenInfoRowIndex())); - FAIL_FAST_IF(!(currentScreenInfoRow <= _getLastScreenInfoRowIndex(pData))); - } - - Endpoint start = _screenInfoRowToEndpoint(pData, currentScreenInfoRow) + currentColumn; - - auto target = _endpointToCoord(pData, start); - target = buffer.GetWordEnd(target, wordDelimiters, true); - Endpoint end = _coordToEndpoint(pData, target); - - return std::make_pair(std::move(start), std::move(end)); -} - -// Routine Description: -// - calculates new Endpoints if they were to be moved moveCount times -// by line. -// Arguments: -// - pData - the UiaData for the terminal we are operating on -// - moveCount - the number of times to move -// - moveState - values indicating the state of the console for the -// move operation -// - pAmountMoved - the number of times that the return values are "moved" -// Return Value: -// - a pair of endpoints of the form -std::pair UiaTextRangeBase::_moveByLine(gsl::not_null pData, - const int moveCount, - const MoveState moveState, - _Out_ gsl::not_null const pAmountMoved) -{ - *pAmountMoved = 0; - Endpoint start = _screenInfoRowToEndpoint(pData, moveState.StartScreenInfoRow) + moveState.StartColumn; - Endpoint end = _screenInfoRowToEndpoint(pData, moveState.EndScreenInfoRow) + moveState.EndColumn; - ScreenInfoRow currentScreenInfoRow = moveState.StartScreenInfoRow; - // we don't want to move the range if we're already in the - // limiting row and trying to move off the end of the screen buffer - const bool illegalMovement = (currentScreenInfoRow == moveState.LimitingRow && - ((moveCount < 0 && moveState.Increment == MovementIncrement::Backward) || - (moveCount > 0 && moveState.Increment == MovementIncrement::Forward))); - - if (moveCount != 0 && !illegalMovement) - { - // move the range - for (int i = 0; i < abs(moveCount); ++i) - { - if (currentScreenInfoRow == moveState.LimitingRow) - { - break; - } - currentScreenInfoRow += static_cast(moveState.Increment); - *pAmountMoved += static_cast(moveState.Increment); - - FAIL_FAST_IF(!(currentScreenInfoRow >= _getFirstScreenInfoRowIndex())); - FAIL_FAST_IF(!(currentScreenInfoRow <= _getLastScreenInfoRowIndex(pData))); + target = bufferSize.Origin(); } - start = _screenInfoRowToEndpoint(pData, currentScreenInfoRow); - end = start + _getLastColumnIndex(pData); } - return std::make_pair(std::move(start), std::move(end)); + SetEndpoint(endpoint, target); } // Routine Description: -// - calculates new Endpoints if they were to be moved moveCount times -// by document. -// Arguments: -// - pData - the UiaData for the terminal we are operating on -// - moveCount - the number of times to move -// - moveState - values indicating the state of the console for the -// move operation -// - pAmountMoved - the number of times that the return values are "moved" -// Return Value: -// - a pair of endpoints of the form -std::pair UiaTextRangeBase::_moveByDocument(gsl::not_null pData, - const int /*moveCount*/, - const MoveState moveState, - _Out_ gsl::not_null const pAmountMoved) -{ - // We can't move by anything larger than a line, so move by document will apply and will - // just report that it can't do that. - *pAmountMoved = 0; - - // We then have to return the same endpoints as what we initially had so nothing happens. - Endpoint start = _screenInfoRowToEndpoint(pData, moveState.StartScreenInfoRow) + moveState.StartColumn; - Endpoint end = _screenInfoRowToEndpoint(pData, moveState.EndScreenInfoRow) + moveState.EndColumn; - - return std::make_pair(std::move(start), std::move(end)); -} - -// Routine Description: -// - calculates new Endpoints if the indicated -// endpoint was moved moveCount times by character. +// - moves the UTR's endpoint by moveCount times by line. +// - if endpoints crossed, the degenerate range is created and both endpoints are moved // Arguments: // - moveCount - the number of times to move // - endpoint - the endpoint to move @@ -1728,685 +1111,101 @@ std::pair UiaTextRangeBase::_moveByDocument(gsl::not_null -std::tuple UiaTextRangeBase::_moveEndpointByUnitCharacter(const int moveCount, - const TextPatternRangeEndpoint endpoint, - const MoveState moveState, - _Out_ gsl::not_null const pAmountMoved) -{ - switch (moveState.Direction) - { - case MovementDirection::Forward: - return _moveEndpointByUnitCharacterForward(moveCount, endpoint, moveState, pAmountMoved); - case MovementDirection::Backward: - return _moveEndpointByUnitCharacterBackward(moveCount, endpoint, moveState, pAmountMoved); - default: - *pAmountMoved = 0; - return std::make_tuple(moveState.start, moveState.end); - } -} - -std::tuple -UiaTextRangeBase::_moveEndpointByUnitCharacterForward(const int moveCount, - const TextPatternRangeEndpoint endpoint, - const MoveState moveState, - _Out_ gsl::not_null const pAmountMoved) +// - +void UiaTextRangeBase::_moveEndpointByUnitLine(_In_ const int moveCount, + _In_ const TextPatternRangeEndpoint endpoint, + _In_ const MoveState moveState, + _Out_ gsl::not_null const pAmountMoved) { *pAmountMoved = 0; - auto currentPos = GetEndpoint(endpoint); - const auto buffer = _pData->GetViewport(); - int i = 0; - while (i < abs(moveCount) && buffer.IncrementInBounds(¤tPos)) + if (moveCount == 0) { - ++i; + return; } - *pAmountMoved = i; - - // TODO CARLOS STOP HERE: Stopping point. I need to simplify the code below. Shouldn't be too hard - // translate the row back to an endpoint and handle any crossed endpoints - const Endpoint convertedEndpoint = _screenInfoRowToEndpoint(pData, currentScreenInfoRow) + currentColumn; - Endpoint start = _screenInfoRowToEndpoint(pData, moveState.StartScreenInfoRow) + moveState.StartColumn; - Endpoint end = _screenInfoRowToEndpoint(pData, moveState.EndScreenInfoRow) + moveState.EndColumn; - bool degenerate = false; - if (endpoint == TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start) + const auto bufferSize = _pData->GetTextBuffer().GetSize(); + auto target = GetEndpoint(endpoint); + while (abs(*pAmountMoved) < abs(moveCount) && bufferSize.IsInBounds(target)) { - start = convertedEndpoint; - if (_compareScreenCoords(pData, - currentScreenInfoRow, - currentColumn, - moveState.EndScreenInfoRow, - moveState.EndColumn) == 1) + switch (moveState.Direction) { - end = start; - degenerate = true; - } - } - else - { - end = convertedEndpoint; - if (_compareScreenCoords(pData, - currentScreenInfoRow, - currentColumn, - moveState.StartScreenInfoRow, - moveState.StartColumn) == -1) - { - start = end; - degenerate = true; - } - } - return std::make_tuple(start, end); -} - -std::tuple -UiaTextRangeBase::_moveEndpointByUnitCharacterBackward(gsl::not_null pData, - const int moveCount, - const TextPatternRangeEndpoint endpoint, - const MoveState moveState, - _Out_ gsl::not_null const pAmountMoved) -{ - *pAmountMoved = 0; - ScreenInfoRow currentScreenInfoRow = 0; - Column currentColumn = 0; - - // set current location vars - if (endpoint == TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start) - { - currentScreenInfoRow = moveState.StartScreenInfoRow; - currentColumn = moveState.StartColumn; - } - else - { - currentScreenInfoRow = moveState.EndScreenInfoRow; - currentColumn = moveState.EndColumn; - } - - for (int i = 0; i < abs(moveCount); ++i) - { - // check if we're at the edge of the screen info buffer - if (currentScreenInfoRow == moveState.LimitingRow && - currentColumn == moveState.LastColumnInRow) + case MovementDirection::Forward: { + target.Y += 1; + *pAmountMoved += 1; break; } - else if (currentColumn == moveState.LastColumnInRow) - { - // we're at the edge of a row and need to go to the - // next one. move to the cell with the last non-whitespace charactor - - currentScreenInfoRow += static_cast(moveState.Increment); - // get the right cell for the next row - const ROW& row = pData->GetTextBuffer().GetRowByOffset(currentScreenInfoRow); - const size_t right = row.GetCharRow().MeasureRight(); - currentColumn = gsl::narrow((right == 0) ? 0 : right - 1); - } - else - { - // moving somewhere away from the edges of a row - currentColumn += static_cast(moveState.Increment); - } - *pAmountMoved += static_cast(moveState.Increment); - - FAIL_FAST_IF(!(currentColumn >= _getFirstColumnIndex())); - FAIL_FAST_IF(!(currentColumn <= _getLastColumnIndex(pData))); - FAIL_FAST_IF(!(currentScreenInfoRow >= _getFirstScreenInfoRowIndex())); - FAIL_FAST_IF(!(currentScreenInfoRow <= _getLastScreenInfoRowIndex(pData))); - } - - // translate the row back to an endpoint and handle any crossed endpoints - const Endpoint convertedEndpoint = _screenInfoRowToEndpoint(pData, currentScreenInfoRow) + currentColumn; - Endpoint start = _screenInfoRowToEndpoint(pData, moveState.StartScreenInfoRow) + moveState.StartColumn; - Endpoint end = _screenInfoRowToEndpoint(pData, moveState.EndScreenInfoRow) + moveState.EndColumn; - bool degenerate = false; - if (endpoint == TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start) - { - start = convertedEndpoint; - if (_compareScreenCoords(pData, - currentScreenInfoRow, - currentColumn, - moveState.EndScreenInfoRow, - moveState.EndColumn) == 1) - { - end = start; - degenerate = true; - } - } - else - { - end = convertedEndpoint; - if (_compareScreenCoords(pData, - currentScreenInfoRow, - currentColumn, - moveState.StartScreenInfoRow, - moveState.StartColumn) == -1) - { - start = end; - degenerate = true; - } - } - return std::make_tuple(start, end, degenerate); -} - -// Routine Description: -// - calculates new Endpoints/degenerate state if the indicated -// endpoint was moved moveCount times by word. -// Arguments: -// - pData - the UiaData for the terminal we are operating on -// - moveCount - the number of times to move -// - endpoint - the endpoint to move -// - moveState - values indicating the state of the console for the -// move operation -// - pAmountMoved - the number of times that the return values are "moved" -// Return Value: -// - A tuple of elements of the form -std::tuple UiaTextRangeBase::_moveEndpointByUnitWord(gsl::not_null pData, - const int moveCount, - const TextPatternRangeEndpoint endpoint, - const MoveState moveState, - const std::wstring_view wordDelimiters, - _Out_ gsl::not_null const pAmountMoved) -{ - if (moveState.Direction == MovementDirection::Forward) - { - return _moveEndpointByUnitWordForward(pData, moveCount, endpoint, moveState, wordDelimiters, pAmountMoved); - } - else - { - return _moveEndpointByUnitWordBackward(pData, moveCount, endpoint, moveState, wordDelimiters, pAmountMoved); - } -} - -std::tuple -UiaTextRangeBase::_moveEndpointByUnitWordForward(gsl::not_null pData, - const int moveCount, - const TextPatternRangeEndpoint endpoint, - const MoveState moveState, - const std::wstring_view wordDelimiters, - _Out_ gsl::not_null const pAmountMoved) -{ - *pAmountMoved = 0; - ScreenInfoRow currentScreenInfoRow = 0; - Column currentColumn = 0; - - // set current location vars - if (endpoint == TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start) - { - currentScreenInfoRow = moveState.StartScreenInfoRow; - currentColumn = moveState.StartColumn; - } - else - { - currentScreenInfoRow = moveState.EndScreenInfoRow; - currentColumn = moveState.EndColumn; - } - - auto& buffer = pData->GetTextBuffer(); - for (int i = 0; i < abs(moveCount); ++i) - { - // get the current row's right - const ROW& row = buffer.GetRowByOffset(currentScreenInfoRow); - const size_t right = row.GetCharRow().MeasureRight(); - const auto expectedColumn = gsl::narrow_cast(currentColumn) + 1; - - // check if we're at the edge of the screen info buffer - if (currentScreenInfoRow == moveState.LimitingRow && - expectedColumn >= right) + case MovementDirection::Backward: { + target.Y -= 1; + *pAmountMoved -= 1; break; } - else if (expectedColumn >= right) - { - // we're at the edge of a row and need to go to the next one - currentColumn = moveState.FirstColumnInRow; - currentScreenInfoRow += static_cast(moveState.Increment); - - // when moving end, we need to encompass the word - if (endpoint == TextPatternRangeEndpoint::TextPatternRangeEndpoint_End) - { - const auto point = _screenInfoRowToEndpoint(pData, currentScreenInfoRow) + currentColumn; - auto target = _endpointToCoord(pData, point); - - target = buffer.GetWordEnd(target, wordDelimiters, true); - - currentColumn = target.X; - } - } - else - { - // moving somewhere away from the edges of a row - const Endpoint point = _screenInfoRowToEndpoint(pData, currentScreenInfoRow) + currentColumn; - auto target = _endpointToCoord(pData, point); - buffer.GetSize().IncrementInBounds(target); - - target = (moveState.Increment == MovementIncrement::Forward) ? - buffer.GetWordEnd(target, wordDelimiters, true) : - buffer.GetWordStart(target, wordDelimiters, true); - - if (endpoint == TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start) - { - buffer.GetSize().IncrementInBounds(target); - - if (static_cast(target.X) == moveState.FirstColumnInRow && currentScreenInfoRow != moveState.LimitingRow) - { - currentScreenInfoRow += static_cast(moveState.Increment); - } - } - - currentColumn = target.X; + default: + break; } - *pAmountMoved += static_cast(moveState.Increment); - - FAIL_FAST_IF(!(currentColumn >= _getFirstColumnIndex())); - FAIL_FAST_IF(!(currentColumn <= _getLastColumnIndex(pData))); - FAIL_FAST_IF(!(currentScreenInfoRow >= _getFirstScreenInfoRowIndex())); - FAIL_FAST_IF(!(currentScreenInfoRow <= _getLastScreenInfoRowIndex(pData))); } - // translate the row back to an endpoint and handle any crossed endpoints - const Endpoint convertedEndpoint = _screenInfoRowToEndpoint(pData, currentScreenInfoRow) + currentColumn; - Endpoint start = _screenInfoRowToEndpoint(pData, moveState.StartScreenInfoRow) + moveState.StartColumn; - Endpoint end = _screenInfoRowToEndpoint(pData, moveState.EndScreenInfoRow) + moveState.EndColumn; - bool degenerate = false; - if (endpoint == TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start) + // if we went out of bounds, be at the corner of the buffer + if (!bufferSize.IsInBounds(target)) { - start = convertedEndpoint; - if (_compareScreenCoords(pData, - currentScreenInfoRow, - currentColumn, - moveState.EndScreenInfoRow, - moveState.EndColumn) == 1) + if (moveState.Direction == MovementDirection::Forward) { - end = start; - degenerate = true; - } - } - else - { - end = convertedEndpoint; - if (_compareScreenCoords(pData, - currentScreenInfoRow, - currentColumn, - moveState.StartScreenInfoRow, - moveState.StartColumn) == -1) - { - start = end; - degenerate = true; - } - } - return std::make_tuple(start, end, degenerate); -} - -std::tuple -UiaTextRangeBase::_moveEndpointByUnitWordBackward(gsl::not_null pData, - const int moveCount, - const TextPatternRangeEndpoint endpoint, - const MoveState moveState, - const std::wstring_view wordDelimiters, - _Out_ gsl::not_null const pAmountMoved) -{ - *pAmountMoved = 0; - ScreenInfoRow currentScreenInfoRow = 0; - Column currentColumn = 0; - - // set current location vars - if (endpoint == TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start) - { - currentScreenInfoRow = moveState.StartScreenInfoRow; - currentColumn = moveState.StartColumn; - } - else - { - currentScreenInfoRow = moveState.EndScreenInfoRow; - currentColumn = moveState.EndColumn; - } - - auto& buffer = pData->GetTextBuffer(); - for (int i = 0; i < abs(moveCount); ++i) - { - // check if we're at the edge of the screen info buffer - if (currentScreenInfoRow == moveState.LimitingRow && - currentColumn == moveState.LastColumnInRow) - { - break; - } - else if (currentColumn == moveState.LastColumnInRow) - { - // we're at the edge of a row and need to go to the - // next one. move to the cell with the last non-whitespace charactor - - currentScreenInfoRow += static_cast(moveState.Increment); - // get the right cell for the next row - const ROW& row = buffer.GetRowByOffset(currentScreenInfoRow); - const size_t right = row.GetCharRow().MeasureRight(); - currentColumn = gsl::narrow((right == 0) ? 0 : right - 1); - - // get the right-most word for the previous row - const auto point = _screenInfoRowToEndpoint(pData, currentScreenInfoRow) + currentColumn; - auto target = _endpointToCoord(pData, point); - - target = buffer.GetWordStart(target, wordDelimiters, true); - - if (endpoint == TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start) - { - buffer.GetSize().IncrementInBounds(target); - } - - currentColumn = target.X; + target = bufferSize.EndInclusive(); } else { - // moving somewhere away from the edges of a row - const Endpoint point = _screenInfoRowToEndpoint(pData, currentScreenInfoRow) + currentColumn; - auto target = _endpointToCoord(pData, point); - buffer.GetSize().DecrementInBounds(target); - - target = (moveState.Increment == MovementIncrement::Forward) ? - buffer.GetWordEnd(target, wordDelimiters, true) : - buffer.GetWordStart(target, wordDelimiters, true); - - if (endpoint == TextPatternRangeEndpoint::TextPatternRangeEndpoint_End) - { - buffer.GetSize().DecrementInBounds(target); - - if (static_cast(target.X) == moveState.FirstColumnInRow && currentScreenInfoRow != moveState.LimitingRow) - { - currentScreenInfoRow += static_cast(moveState.Increment); - } - } - - currentColumn = target.X; + target = bufferSize.Origin(); } - *pAmountMoved += static_cast(moveState.Increment); - - FAIL_FAST_IF(!(currentColumn >= _getFirstColumnIndex())); - FAIL_FAST_IF(!(currentColumn <= _getLastColumnIndex(pData))); - FAIL_FAST_IF(!(currentScreenInfoRow >= _getFirstScreenInfoRowIndex())); - FAIL_FAST_IF(!(currentScreenInfoRow <= _getLastScreenInfoRowIndex(pData))); } - // translate the row back to an endpoint and handle any crossed endpoints - const Endpoint convertedEndpoint = _screenInfoRowToEndpoint(pData, currentScreenInfoRow) + currentColumn; - Endpoint start = _screenInfoRowToEndpoint(pData, moveState.StartScreenInfoRow) + moveState.StartColumn; - Endpoint end = _screenInfoRowToEndpoint(pData, moveState.EndScreenInfoRow) + moveState.EndColumn; - bool degenerate = false; - if (endpoint == TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start) - { - start = convertedEndpoint; - if (_compareScreenCoords(pData, - currentScreenInfoRow, - currentColumn, - moveState.EndScreenInfoRow, - moveState.EndColumn) == 1) - { - end = start; - degenerate = true; - } - } - else - { - end = convertedEndpoint; - if (_compareScreenCoords(pData, - currentScreenInfoRow, - currentColumn, - moveState.StartScreenInfoRow, - moveState.StartColumn) == -1) - { - start = end; - degenerate = true; - } - } - return std::make_tuple(start, end, degenerate); + SetEndpoint(endpoint, target); } // Routine Description: -// - calculates new Endpoints/degenerate state if the indicated -// endpoint was moved moveCount times by line. +// - moves the UTR's endpoint by moveCount times by document. +// - if endpoints crossed, the degenerate range is created and both endpoints are moved // Arguments: -// - pData - the UiaData for the terminal we are operating on // - moveCount - the number of times to move // - endpoint - the endpoint to move // - moveState - values indicating the state of the console for the // move operation // - pAmountMoved - the number of times that the return values are "moved" // Return Value: -// - A tuple of elements of the form -std::tuple UiaTextRangeBase::_moveEndpointByUnitLine(gsl::not_null pData, - const int moveCount, - const TextPatternRangeEndpoint endpoint, - const MoveState moveState, - _Out_ gsl::not_null const pAmountMoved) +// - +void UiaTextRangeBase::_moveEndpointByUnitDocument(_In_ const int moveCount, + _In_ const TextPatternRangeEndpoint endpoint, + _In_ const MoveState moveState, + _Out_ gsl::not_null const pAmountMoved) { *pAmountMoved = 0; - int count = moveCount; - ScreenInfoRow currentScreenInfoRow = 0; - Column currentColumn = 0; - bool forceDegenerate = false; - Endpoint start = _screenInfoRowToEndpoint(pData, moveState.StartScreenInfoRow) + moveState.StartColumn; - Endpoint end = _screenInfoRowToEndpoint(pData, moveState.EndScreenInfoRow) + moveState.EndColumn; - bool degenerate = false; if (moveCount == 0) { - return std::make_tuple(start, end, degenerate); - } - - const MovementDirection moveDirection = (moveCount > 0) ? MovementDirection::Forward : MovementDirection::Backward; - - if (endpoint == TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start) - { - currentScreenInfoRow = moveState.StartScreenInfoRow; - currentColumn = moveState.StartColumn; - } - else - { - currentScreenInfoRow = moveState.EndScreenInfoRow; - currentColumn = moveState.EndColumn; - } - - // check if we can't be moved anymore - if (currentScreenInfoRow == moveState.LimitingRow && - currentColumn == moveState.LastColumnInRow) - { - return std::make_tuple(start, end, degenerate); - } - else if (endpoint == TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start && - moveDirection == MovementDirection::Forward) - { - if (moveState.StartScreenInfoRow == moveState.LimitingRow) - { - // _start is somewhere on the limiting row but not at - // the very end. move to the end of the last row - count -= static_cast(moveState.Increment); - *pAmountMoved += static_cast(moveState.Increment); - currentColumn = _getLastColumnIndex(pData); - forceDegenerate = true; - } - if (moveState.StartColumn != _getFirstColumnIndex()) - { - // _start is somewhere in the middle of a row, so do a - // partial movement to the beginning of the next row - count -= static_cast(moveState.Increment); - *pAmountMoved += static_cast(moveState.Increment); - currentScreenInfoRow += static_cast(moveState.Increment); - currentColumn = _getFirstColumnIndex(); - } - } - else if (endpoint == TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start && - moveDirection == MovementDirection::Backward) - { - if (moveState.StartColumn != _getFirstColumnIndex()) - { - // moving backwards when we weren't already at the beginning of - // the row so move there first to align with the text unit boundary - count -= static_cast(moveState.Increment); - *pAmountMoved += static_cast(moveState.Increment); - currentColumn = _getFirstColumnIndex(); - } - } - else if (endpoint == TextPatternRangeEndpoint::TextPatternRangeEndpoint_End && - moveDirection == MovementDirection::Forward) - { - if (moveState.EndColumn != _getLastColumnIndex(pData)) - { - // _end is not at the last column in a row, so we move - // forward to it with a partial movement - count -= static_cast(moveState.Increment); - *pAmountMoved += static_cast(moveState.Increment); - currentColumn = _getLastColumnIndex(pData); - } - } - else - { - // _end moving backwards - if (moveState.EndScreenInfoRow == moveState.LimitingRow) - { - // _end is somewhere on the limiting row but not at the - // front. move it there - count -= static_cast(moveState.Increment); - *pAmountMoved += static_cast(moveState.Increment); - currentColumn = _getFirstColumnIndex(); - forceDegenerate = true; - } - else if (moveState.EndColumn != _getLastColumnIndex(pData)) - { - // _end is not at the last column in a row, so we move it - // backwards to it with a partial move - count -= static_cast(moveState.Increment); - *pAmountMoved += static_cast(moveState.Increment); - currentColumn = _getLastColumnIndex(pData); - currentScreenInfoRow += static_cast(moveState.Increment); - } - } - - FAIL_FAST_IF(!(currentColumn >= _getFirstColumnIndex())); - FAIL_FAST_IF(!(currentColumn <= _getLastColumnIndex(pData))); - FAIL_FAST_IF(!(currentScreenInfoRow >= _getFirstScreenInfoRowIndex())); - FAIL_FAST_IF(!(currentScreenInfoRow <= _getLastScreenInfoRowIndex(pData))); - - // move the row that the endpoint corresponds to - while (count != 0 && currentScreenInfoRow != moveState.LimitingRow) - { - count -= static_cast(moveState.Increment); - currentScreenInfoRow += static_cast(moveState.Increment); - *pAmountMoved += static_cast(moveState.Increment); - - FAIL_FAST_IF(!(currentScreenInfoRow >= _getFirstScreenInfoRowIndex())); - FAIL_FAST_IF(!(currentScreenInfoRow <= _getLastScreenInfoRowIndex(pData))); + return; } - // translate the row back to an endpoint and handle any crossed endpoints - const Endpoint convertedEndpoint = _screenInfoRowToEndpoint(pData, currentScreenInfoRow) + currentColumn; - if (endpoint == TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start) + const auto bufferSize = _pData->GetTextBuffer().GetSize(); + switch (moveState.Direction) { - start = convertedEndpoint; - if (currentScreenInfoRow > moveState.EndScreenInfoRow || forceDegenerate) - { - degenerate = true; - end = start; - } - } - else + case MovementDirection::Forward: { - end = convertedEndpoint; - if (currentScreenInfoRow < moveState.StartScreenInfoRow || forceDegenerate) - { - degenerate = true; - start = end; - } + const COORD documentEnd = { bufferSize.RightInclusive(), bufferSize.BottomInclusive() }; + SetEndpoint(endpoint, documentEnd); + *pAmountMoved += 1; + break; } - - return std::make_tuple(start, end, degenerate); -} - -// Routine Description: -// - calculates new Endpoints/degenerate state if the indicate -// endpoint was moved moveCount times by document. -// Arguments: -// - pData - the UiaData for the terminal we are operating on -// - moveCount - the number of times to move -// - endpoint - the endpoint to move -// - moveState - values indicating the state of the console for the -// move operation -// - pAmountMoved - the number of times that the return values are "moved" -// Return Value: -// - A tuple of elements of the form -std::tuple UiaTextRangeBase::_moveEndpointByUnitDocument(gsl::not_null pData, - const int moveCount, - const TextPatternRangeEndpoint endpoint, - const MoveState moveState, - _Out_ gsl::not_null const pAmountMoved) -{ - *pAmountMoved = 0; - - Endpoint start = 0; - Endpoint end = 0; - bool degenerate = false; - if (endpoint == TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start) + case MovementDirection::Backward: { - if (moveCount < 0) - { - // moving _start backwards - start = _screenInfoRowToEndpoint(pData, _getFirstScreenInfoRowIndex()) + _getFirstColumnIndex(); - end = _screenInfoRowToEndpoint(pData, moveState.EndScreenInfoRow) + moveState.EndColumn; - if (!(moveState.StartScreenInfoRow == _getFirstScreenInfoRowIndex() && - moveState.StartColumn == _getFirstColumnIndex())) - { - *pAmountMoved += static_cast(moveState.Increment); - } - } - else - { - // moving _start forwards - start = _screenInfoRowToEndpoint(pData, _getLastScreenInfoRowIndex(pData)) + _getLastColumnIndex(pData); - end = start; - degenerate = true; - if (!(moveState.StartScreenInfoRow == _getLastScreenInfoRowIndex(pData) && - moveState.StartColumn == _getLastColumnIndex(pData))) - { - *pAmountMoved += static_cast(moveState.Increment); - } - } + const COORD documentBegin = bufferSize.Origin(); + SetEndpoint(endpoint, documentBegin); + *pAmountMoved -= 1; + break; } - else - { - if (moveCount < 0) - { - // moving _end backwards - end = _screenInfoRowToEndpoint(pData, _getFirstScreenInfoRowIndex()) + _getFirstColumnIndex(); - start = end; - degenerate = true; - if (!(moveState.EndScreenInfoRow == _getFirstScreenInfoRowIndex() && - moveState.EndColumn == _getFirstColumnIndex())) - { - *pAmountMoved += static_cast(moveState.Increment); - } - } - else - { - // moving _end forwards - end = _screenInfoRowToEndpoint(pData, _getLastScreenInfoRowIndex(pData)) + _getLastColumnIndex(pData); - start = _screenInfoRowToEndpoint(pData, moveState.StartScreenInfoRow) + moveState.StartColumn; - if (!(moveState.EndScreenInfoRow == _getLastScreenInfoRowIndex(pData) && - moveState.EndColumn == _getLastColumnIndex(pData))) - { - *pAmountMoved += static_cast(moveState.Increment); - } - } + default: + break; } - - return std::make_tuple(start, end, degenerate); -} - -COORD UiaTextRangeBase::_endpointToCoord(gsl::not_null pData, const Endpoint endpoint) -{ - return { gsl::narrow(_endpointToColumn(pData, endpoint)), gsl::narrow(_endpointToScreenInfoRow(pData, endpoint)) }; -} - -Endpoint UiaTextRangeBase::_coordToEndpoint(gsl::not_null pData, - const COORD coord) -{ - return _screenInfoRowToEndpoint(pData, coord.Y) + coord.X; } RECT UiaTextRangeBase::_getTerminalRect() const diff --git a/src/types/UiaTextRangeBase.hpp b/src/types/UiaTextRangeBase.hpp index 5d532ea1bc4..af8910a67b1 100644 --- a/src/types/UiaTextRangeBase.hpp +++ b/src/types/UiaTextRangeBase.hpp @@ -67,10 +67,6 @@ typedef unsigned long long IdType; // endpoint is equivalent to. It is 0-indexed. typedef unsigned int Column; -// an endpoint is a char location in the text buffer. endpoint 0 is -// the first char of the 0th row in the text buffer row array. -typedef unsigned int Endpoint; - constexpr IdType InvalidId = 0; namespace Microsoft::Console::Types @@ -89,14 +85,6 @@ namespace Microsoft::Console::Types Backward }; - // valid increment amounts for forward and - // backward movement - enum class MovementIncrement - { - Forward = 1, - Backward = -1 - }; - // common information used by the variety of // movement operations struct MoveState @@ -104,29 +92,16 @@ namespace Microsoft::Console::Types // screen/column position of _start COORD start; // screen/column position of _end - COORD end - // last row in the direction being moved - ScreenInfoRow LimitingRow; - // first column in the direction being moved - Column FirstColumnInRow; - // last column in the direction being moved - Column LastColumnInRow; - // increment amount - MovementIncrement Increment; + COORD end; // direction moving MovementDirection Direction; - MoveState(IUiaData* pData, - const UiaTextRangeBase& range, + MoveState(const UiaTextRangeBase& range, const MovementDirection direction); private: MoveState(const COORD start, const COORD end, - const ScreenInfoRow limitingRow, - const Column firstColumnInRow, - const Column lastColumnInRow, - const MovementIncrement increment, const MovementDirection direction) noexcept; #ifdef UNIT_TESTING @@ -146,15 +121,14 @@ namespace Microsoft::Console::Types // degenerate range at cursor position HRESULT RuntimeClassInitialize(_In_ IUiaData* pData, _In_ IRawElementProviderSimple* const pProvider, - const Cursor& cursor, + _In_ const Cursor& cursor, _In_ std::wstring_view wordDelimiters = DefaultWordDelimiter) noexcept; // specific endpoint range HRESULT RuntimeClassInitialize(_In_ IUiaData* pData, _In_ IRawElementProviderSimple* const pProvider, - const Endpoint start, - const Endpoint end, - const bool degenerate, + _In_ const COORD start, + _In_ const COORD end, _In_ std::wstring_view wordDelimiters = DefaultWordDelimiter) noexcept; HRESULT RuntimeClassInitialize(const UiaTextRangeBase& a) noexcept; @@ -166,11 +140,10 @@ namespace Microsoft::Console::Types const IdType GetId() const noexcept; const COORD GetEndpoint(TextPatternRangeEndpoint endpoint = TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start) const noexcept; + bool SetEndpoint(TextPatternRangeEndpoint endpoint, const COORD val) noexcept; const bool IsDegenerate() const noexcept; - // TODO GitHub #605: - // only used for UiaData::FindText. Remove after Search added properly - void SetRangeValues(const Endpoint start, const Endpoint end, const bool isDegenerate) noexcept; + void SetEndpoints(const COORD start, const COORD end) noexcept; // ITextRangeProvider methods virtual IFACEMETHODIMP Clone(_Outptr_result_maybenull_ ITextRangeProvider** ppRetVal) = 0; @@ -250,168 +223,34 @@ namespace Microsoft::Console::Types RECT _getTerminalRect() const; - static const COORD _getScreenBufferCoords(gsl::not_null pData); virtual const COORD _getScreenFontSize() const; + const unsigned int _getViewportHeight(const SMALL_RECT viewport) noexcept; - static const unsigned int _getTotalRows(gsl::not_null pData) noexcept; - static const unsigned int _getRowWidth(gsl::not_null pData); - - static const unsigned int _getFirstScreenInfoRowIndex() noexcept; - static const unsigned int _getLastScreenInfoRowIndex(gsl::not_null pData) noexcept; - - const unsigned int _rowCountInRange(gsl::not_null pData) const; - - static const TextBufferRow _endpointToTextBufferRow(gsl::not_null pData, - const Endpoint endpoint); - static const ScreenInfoRow _textBufferRowToScreenInfoRow(gsl::not_null pData, - const TextBufferRow row) noexcept; + void _getBoundingRect(_In_ const COORD startAnchor, _In_ const COORD endAnchor, _Inout_ std::vector& coords) const; - static const TextBufferRow _screenInfoRowToTextBufferRow(gsl::not_null pData, - const ScreenInfoRow row) noexcept; - static const Endpoint _textBufferRowToEndpoint(gsl::not_null pData, const TextBufferRow row); - - static const Endpoint _wordBeginEndpoint(gsl::not_null pData, Endpoint target, const std::wstring_view wordDelimiters); - static const Endpoint _wordEndEndpoint(gsl::not_null pData, Endpoint target, const std::wstring_view wordDelimiters); - - static const ScreenInfoRow _endpointToScreenInfoRow(gsl::not_null pData, - const Endpoint endpoint); - static const Endpoint _screenInfoRowToEndpoint(gsl::not_null pData, - const ScreenInfoRow row); - - static COORD _endpointToCoord(gsl::not_null pData, - const Endpoint endpoint); - static Endpoint _coordToEndpoint(gsl::not_null pData, - const COORD coord); - - static const Column _endpointToColumn(gsl::not_null pData, - const Endpoint endpoint); - - static const Row _normalizeRow(gsl::not_null pData, const Row row) noexcept; - - static const ViewportRow _screenInfoRowToViewportRow(gsl::not_null pData, - const ScreenInfoRow row) noexcept; - // Routine Description: - // - Converts a ScreenInfoRow to a ViewportRow. - // Arguments: - // - row - the ScreenInfoRow to convert - // - viewport - the viewport to use for the conversion - // Return Value: - // - the equivalent ViewportRow. - static constexpr const ViewportRow _screenInfoRowToViewportRow(const ScreenInfoRow row, - const SMALL_RECT viewport) noexcept - { - return row - viewport.Top; - } - - static const bool _isScreenInfoRowInViewport(gsl::not_null pData, - const ScreenInfoRow row) noexcept; - static const bool _isScreenInfoRowInViewport(const ScreenInfoRow row, - const SMALL_RECT viewport) noexcept; - - static const unsigned int _getViewportHeight(const SMALL_RECT viewport) noexcept; - static const unsigned int _getViewportWidth(const SMALL_RECT viewport) noexcept; - - void _addScreenInfoRowBoundaries(gsl::not_null pData, - const ScreenInfoRow screenInfoRow, - _Inout_ std::vector& coords) const; - - static std::pair _moveByCharacter(gsl::not_null pData, - const int moveCount, - const MoveState moveState, - _Out_ gsl::not_null const pAmountMoved); - - static std::pair _moveByCharacterForward(gsl::not_null pData, - const int moveCount, - const MoveState moveState, - _Out_ gsl::not_null const pAmountMoved); - - static std::pair _moveByCharacterBackward(gsl::not_null pData, - const int moveCount, - const MoveState moveState, - _Out_ gsl::not_null const pAmountMoved); - - static std::pair _moveByWord(gsl::not_null pData, - const int moveCount, - const MoveState moveState, - const std::wstring_view wordDelimiters, - _Out_ gsl::not_null const pAmountMoved); - - static std::pair _moveByWordForward(gsl::not_null pData, - const int moveCount, - const MoveState moveState, - const std::wstring_view wordDelimiters, - _Out_ gsl::not_null const pAmountMoved); - - static std::pair _moveByWordBackward(gsl::not_null pData, - const int moveCount, - const MoveState moveState, - const std::wstring_view wordDelimiters, - _Out_ gsl::not_null const pAmountMoved); - - static std::pair _moveByLine(gsl::not_null pData, - const int moveCount, - const MoveState moveState, - _Out_ gsl::not_null const pAmountMoved); - - static std::pair _moveByDocument(gsl::not_null pData, - const int moveCount, - const MoveState moveState, - _Out_ gsl::not_null const pAmountMoved); - - std::tuple - _moveEndpointByUnitCharacter(const int moveCount, - const TextPatternRangeEndpoint endpoint, - const MoveState moveState, + void + _moveEndpointByUnitCharacter(_In_ const int moveCount, + _In_ const TextPatternRangeEndpoint endpoint, + _In_ const MoveState moveState, _Out_ gsl::not_null const pAmountMoved); - std::tuple - _moveEndpointByUnitCharacterForward(const int moveCount, - const TextPatternRangeEndpoint endpoint, - const MoveState moveState, - _Out_ gsl::not_null const pAmountMoved); - - std::tuple - _moveEndpointByUnitCharacterBackward(const int moveCount, - const TextPatternRangeEndpoint endpoint, - const MoveState moveState, - _Out_ gsl::not_null const pAmountMoved); - - static std::tuple - _moveEndpointByUnitWord(gsl::not_null pData, - const int moveCount, - const TextPatternRangeEndpoint endpoint, - const MoveState moveState, - const std::wstring_view wordDelimiters, + void + _moveEndpointByUnitWord(_In_ const int moveCount, + _In_ const TextPatternRangeEndpoint endpoint, + _In_ const MoveState moveState, + _In_ const std::wstring_view wordDelimiters, _Out_ gsl::not_null const pAmountMoved); - static std::tuple - _moveEndpointByUnitWordForward(gsl::not_null pData, - const int moveCount, - const TextPatternRangeEndpoint endpoint, - const MoveState moveState, - const std::wstring_view wordDelimiters, - _Out_ gsl::not_null const pAmountMoved); - - static std::tuple - _moveEndpointByUnitWordBackward(gsl::not_null pData, - const int moveCount, - const TextPatternRangeEndpoint endpoint, - const MoveState moveState, - const std::wstring_view wordDelimiters, - _Out_ gsl::not_null const pAmountMoved); - - static std::tuple - _moveEndpointByUnitLine(gsl::not_null pData, - const int moveCount, - const TextPatternRangeEndpoint endpoint, - const MoveState moveState, + void + _moveEndpointByUnitLine(_In_ const int moveCount, + _In_ const TextPatternRangeEndpoint endpoint, + _In_ const MoveState moveState, _Out_ gsl::not_null const pAmountMoved); - static std::tuple - _moveEndpointByUnitDocument(gsl::not_null pData, - const int moveCount, - const TextPatternRangeEndpoint endpoint, - const MoveState moveState, + void + _moveEndpointByUnitDocument(_In_ const int moveCount, + _In_ const TextPatternRangeEndpoint endpoint, + _In_ const MoveState moveState, _Out_ gsl::not_null const pAmountMoved); #ifdef UNIT_TESTING @@ -475,8 +314,8 @@ namespace Microsoft::Console::Types struct ApiMsgExpandToEnclosingUnit : public IApiMsg { TextUnit Unit; - Endpoint OriginalStart; - Endpoint OriginalEnd; + COORD OriginalStart; + COORD OriginalEnd; }; struct ApiMsgGetText : IApiMsg @@ -486,8 +325,8 @@ namespace Microsoft::Console::Types struct ApiMsgMove : IApiMsg { - Endpoint OriginalStart; - Endpoint OriginalEnd; + COORD OriginalStart; + COORD OriginalEnd; TextUnit Unit; int RequestedCount; int MovedCount; @@ -495,8 +334,8 @@ namespace Microsoft::Console::Types struct ApiMsgMoveEndpointByUnit : IApiMsg { - Endpoint OriginalStart; - Endpoint OriginalEnd; + COORD OriginalStart; + COORD OriginalEnd; TextPatternRangeEndpoint Endpoint; TextUnit Unit; int RequestedCount; @@ -505,8 +344,8 @@ namespace Microsoft::Console::Types struct ApiMsgMoveEndpointByRange : IApiMsg { - Endpoint OriginalStart; - Endpoint OriginalEnd; + COORD OriginalStart; + COORD OriginalEnd; TextPatternRangeEndpoint Endpoint; TextPatternRangeEndpoint TargetEndpoint; IdType OtherId; diff --git a/src/types/inc/viewport.hpp b/src/types/inc/viewport.hpp index 25f5b9d7f66..ac5437a6fc9 100644 --- a/src/types/inc/viewport.hpp +++ b/src/types/inc/viewport.hpp @@ -53,6 +53,7 @@ namespace Microsoft::Console::Types SHORT Height() const noexcept; SHORT Width() const noexcept; COORD Origin() const noexcept; + COORD EndInclusive() const noexcept; COORD Dimensions() const noexcept; bool IsInBounds(const Viewport& other) const noexcept; diff --git a/src/types/viewport.cpp b/src/types/viewport.cpp index c6772bfdcc6..84528461e67 100644 --- a/src/types/viewport.cpp +++ b/src/types/viewport.cpp @@ -137,6 +137,17 @@ COORD Viewport::Origin() const noexcept return { Left(), Top() }; } +// Method Description: +// - Get a coord representing the end of this viewport in inclusive terms. +// Arguments: +// - +// Return Value: +// - the coordinates of this viewport's end. +COORD Viewport::EndInclusive() const noexcept +{ + return { RightInclusive(), BottomInclusive() }; +} + // Method Description: // - Get a coord representing the dimensions of this viewport. // Arguments: From 76a26e3f62f850270d585ee8c04f8682675b7d5d Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Wed, 18 Dec 2019 18:47:40 -0800 Subject: [PATCH 03/25] remove accidental file --- acc-refactor.md | 51 ------------------------------------------------- 1 file changed, 51 deletions(-) delete mode 100644 acc-refactor.md diff --git a/acc-refactor.md b/acc-refactor.md deleted file mode 100644 index 267600110cf..00000000000 --- a/acc-refactor.md +++ /dev/null @@ -1,51 +0,0 @@ -# Refactor proposal - -## Goals: -1) reduce duplicate code -2) remove static functions -3) improve readability -4) improve reliability -5) improve code-coverage for testing - -## Approach: -1) reduce duplicate code - - `Move()` should rely on `MoveEndpointByUnit()` -2) remove static functions - - all helper functions should not be static -3) improve readability - - use `COORD` system internally - - import/export Endpoints -4) improve reliability - - rely more heavily on the `Viewport` and `TextBuffer` functions -5) improve code-coverage for testing - - having reusable code means that we are testing more of our code better - -## Targets (Code): -```c++ -IFACEMETHODIMP Move(_In_ TextUnit unit, - _In_ int count, - _Out_ int* pRetVal) override; -IFACEMETHODIMP MoveEndpointByUnit(_In_ TextPatternRangeEndpoint endpoint, - _In_ TextUnit unit, - _In_ int count, - _Out_ int* pRetVal) override; -IFACEMETHODIMP MoveEndpointByRange(_In_ TextPatternRangeEndpoint endpoint, - _In_ ITextRangeProvider* pTargetRange, - _In_ TextPatternRangeEndpoint targetEndpoint) override; -``` - -## Timeline -| Estimate | Task | Notes | -| -- | -- | -- | -| 3 | switch to `COORD` | remove helper functions | -| | | convert to/from COORD at f(n) boundary | -| | | update MoveState | -| 2 | remove `degenerate` | | -| X | Y | | - -## Horrible, Terrible, and Terrifying things I've found -- CompareEndpoints clamped. No documentation on why (or a need to do so) -- `GetText()` is something we shouldn't be doing manually. -- `Move()` can be reduced to `Expand...()` --> `MoveEndpointByUnit(start)` --> `Expand()` -- `MoveEndpointByRange()` just reduced to "set my endpoint to target endpoint" -- `MoveEndpointByUnitCharacter()` is literally just `Viewport::MoveInBounds()` \ No newline at end of file From d4c4e29204f4fae95823cec7c471d2d3aa40c4ab Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Thu, 19 Dec 2019 16:48:41 -0800 Subject: [PATCH 04/25] Add accessibilityMode to GetWordStart/End and Viewport functions. Get some tests running again --- src/buffer/out/textBuffer.cpp | 52 +- src/buffer/out/textBuffer.hpp | 4 +- .../TerminalCore/terminalrenderdata.cpp | 1 + src/host/ut_host/TextBufferTests.cpp | 154 ++ src/interactivity/win32/uiaTextRange.hpp | 4 +- .../UiaTextRangeTests.cpp | 2044 +++++++---------- src/types/UiaTextRangeBase.cpp | 147 +- src/types/inc/viewport.hpp | 12 +- src/types/viewport.cpp | 45 +- 9 files changed, 1203 insertions(+), 1260 deletions(-) diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp index 1660438b48d..0ab7e90b164 100644 --- a/src/buffer/out/textBuffer.cpp +++ b/src/buffer/out/textBuffer.cpp @@ -956,10 +956,10 @@ Microsoft::Console::Render::IRenderTarget& TextBuffer::GetRenderTarget() noexcep // Arguments: // - target - a COORD on the word you are currently on // - wordDelimiters - what characters are we considering for the separation of words -// - includeCharacterRun - include the character run located at the beginning of the word +// - accessibilityMode - when enabled, we continue expanding left until we are at the beginning of a readable word // Return Value: // - The COORD for the first character on the "word" (inclusive) -const COORD TextBuffer::GetWordStart(const COORD target, const std::wstring_view wordDelimiters, bool includeCharacterRun) const +const COORD TextBuffer::GetWordStart(const COORD target, const std::wstring_view wordDelimiters, bool accessibilityMode) const { const auto bufferSize = GetSize(); COORD result = target; @@ -978,12 +978,22 @@ const COORD TextBuffer::GetWordStart(const COORD target, const std::wstring_view --bufferIterator; } - if (includeCharacterRun) + if (accessibilityMode) { - // include character run for readable word + // make sure we expand to the beggining of the word if (_GetDelimiterClass(*bufferIterator, wordDelimiters) == DelimiterClass::RegularChar) { - result = GetWordStart(result, wordDelimiters); + while (result.X > bufferSize.Left() && (_GetDelimiterClass(*bufferIterator, wordDelimiters) == DelimiterClass::RegularChar)) + { + bufferSize.DecrementInBounds(result); + --bufferIterator; + } + } + + // move back onto word start + if (result.X != bufferSize.Left() && _GetDelimiterClass(*bufferIterator, wordDelimiters) != DelimiterClass::RegularChar) + { + bufferSize.IncrementInBounds(result); } } else if (_GetDelimiterClass(*bufferIterator, wordDelimiters) != initialDelimiter) @@ -996,14 +1006,14 @@ const COORD TextBuffer::GetWordStart(const COORD target, const std::wstring_view } // Method Description: -// - Get the COORD for the end of the word you are on +// - Get the COORD for the beginning of the NEXT word // Arguments: // - target - a COORD on the word you are currently on // - wordDelimiters - what characters are we considering for the separation of words -// - includeDelimiterRun - include the delimiter runs located at the end of the word +// - accessibilityMode - when enabled, we continue expanding right until we are at the BEGINNING of the NEXT readable word // Return Value: // - The COORD for the last character on the "word" (inclusive) -const COORD TextBuffer::GetWordEnd(const COORD target, const std::wstring_view wordDelimiters, bool includeDelimiterRun) const +const COORD TextBuffer::GetWordEnd(const COORD target, const std::wstring_view wordDelimiters, bool accessibilityMode) const { const auto bufferSize = GetSize(); COORD result = target; @@ -1011,7 +1021,15 @@ const COORD TextBuffer::GetWordEnd(const COORD target, const std::wstring_view w // can't expand right if (target.X == bufferSize.RightInclusive()) { - return result; + if (accessibilityMode) + { + // NOTE: this may be out of bounds. We don't care in accessibility mode + return { bufferSize.Left(), target.Y + 1 }; + } + else + { + return result; + } } auto bufferIterator = GetTextDataAt(result); @@ -1022,12 +1040,22 @@ const COORD TextBuffer::GetWordEnd(const COORD target, const std::wstring_view w ++bufferIterator; } - if (includeDelimiterRun) + if (accessibilityMode) { - // include delimiter run after word + // make sure we expand to the beginning of the NEXT word if (_GetDelimiterClass(*bufferIterator, wordDelimiters) != DelimiterClass::RegularChar) { - result = GetWordEnd(result, wordDelimiters); + while (result.X < bufferSize.RightInclusive() && (_GetDelimiterClass(*bufferIterator, wordDelimiters) != DelimiterClass::RegularChar)) + { + bufferSize.IncrementInBounds(result); + ++bufferIterator; + } + } + + // handle being at the right boundary + if (result.X == bufferSize.RightInclusive()) + { + bufferSize.IncrementInBounds(result); } } else if (_GetDelimiterClass(*bufferIterator, wordDelimiters) != initialDelimiter) diff --git a/src/buffer/out/textBuffer.hpp b/src/buffer/out/textBuffer.hpp index a85bfd02bad..824b4d665dc 100644 --- a/src/buffer/out/textBuffer.hpp +++ b/src/buffer/out/textBuffer.hpp @@ -130,8 +130,8 @@ class TextBuffer final Microsoft::Console::Render::IRenderTarget& GetRenderTarget() noexcept; - const COORD GetWordStart(const COORD target, const std::wstring_view wordDelimiters, bool includeCharacterRun = false) const; - const COORD GetWordEnd(const COORD target, const std::wstring_view wordDelimiters, bool includeDelimiterRun = false) const; + const COORD GetWordStart(const COORD target, const std::wstring_view wordDelimiters, bool accessibilityMode = false) const; + const COORD GetWordEnd(const COORD target, const std::wstring_view wordDelimiters, bool accessibilityMode = false) const; class TextAndColor { diff --git a/src/cascadia/TerminalCore/terminalrenderdata.cpp b/src/cascadia/TerminalCore/terminalrenderdata.cpp index 5ec78ae9d02..95230b6faaf 100644 --- a/src/cascadia/TerminalCore/terminalrenderdata.cpp +++ b/src/cascadia/TerminalCore/terminalrenderdata.cpp @@ -159,6 +159,7 @@ void Terminal::SelectNewRegion(const COORD coordStart, const COORD coordEnd) SetSelectionAnchor(realCoordStart); SetEndSelectionPosition(realCoordEnd); + _buffer->GetRenderTarget().TriggerSelection(); } const std::wstring Terminal::GetConsoleTitle() const noexcept diff --git a/src/host/ut_host/TextBufferTests.cpp b/src/host/ut_host/TextBufferTests.cpp index 226b4ced32d..75c2cf50285 100644 --- a/src/host/ut_host/TextBufferTests.cpp +++ b/src/host/ut_host/TextBufferTests.cpp @@ -145,6 +145,9 @@ class TextBufferTests TEST_METHOD(ResizeTraditionalHighUnicodeColumnRemoval); TEST_METHOD(TestBurrito); + + TEST_METHOD(GetWordStart); + TEST_METHOD(GetWordEnd); }; void TextBufferTests::TestBufferCreate() @@ -2014,3 +2017,154 @@ void TextBufferTests::TestBurrito() _buffer->IncrementCursor(); VERIFY_IS_FALSE(afterBurritoIter); } + +void TextBufferTests::GetWordStart() +{ + COORD bufferSize{ 80, 9001 }; + UINT cursorSize = 12; + TextAttribute attr{ 0x7f }; + auto _buffer = std::make_unique(bufferSize, attr, cursorSize, _renderTarget); + + // Setup: Write lines of text to the buffer + const std::array text = { L"word other", + L" more words" }; + for (size_t row = 0; row < text.size(); ++row) + { + auto line = text[row]; + OutputCellIterator iter{ line }; + _buffer->WriteLine(iter, { 0, gsl::narrow(row) }); + } + + // Test Data: + // - COORD - starting position + // - COORD - expected result (accessibilityMode = false) + // - COORD - expected result (accessibilityMode = true) + struct ExpectedResult + { + COORD accessibilityModeDisabled; + COORD accessibilityModeEnabled; + }; + + struct Test + { + COORD startPos; + ExpectedResult expected; + }; + + // clang-format off + std::vector testData = { + // tests for first line of text + { { 0, 0 }, {{ 0, 0 }, { 0, 0 }} }, + { { 1, 0 }, {{ 0, 0 }, { 0, 0 }} }, + { { 3, 0 }, {{ 0, 0 }, { 0, 0 }} }, + { { 4, 0 }, {{ 4, 0 }, { 0, 0 }} }, + { { 5, 0 }, {{ 5, 0 }, { 5, 0 }} }, + { { 6, 0 }, {{ 5, 0 }, { 5, 0 }} }, + { { 20, 0 }, {{ 10, 0 }, { 5, 0 }} }, + { { 79, 0 }, {{ 10, 0 }, { 5, 0 }} }, + + // tests for second line of text + { { 0, 1 }, {{ 0, 1 }, { 0, 1 }} }, + { { 1, 1 }, {{ 0, 1 }, { 0, 1 }} }, + { { 2, 1 }, {{ 2, 1 }, { 2, 1 }} }, + { { 3, 1 }, {{ 2, 1 }, { 2, 1 }} }, + { { 5, 1 }, {{ 2, 1 }, { 2, 1 }} }, + { { 6, 1 }, {{ 6, 1 }, { 2, 1 }} }, + { { 7, 1 }, {{ 6, 1 }, { 2, 1 }} }, + { { 9, 1 }, {{ 9, 1 }, { 9, 1 }} }, + { { 10, 1 }, {{ 9, 1 }, { 9, 1 }} }, + { { 20, 1 }, {{14, 1 }, { 9, 1 }} }, + { { 79, 1 }, {{14, 1 }, { 9, 1 }} }, + }; + // clang-format off + + const std::wstring_view delimiters = L" "; + for (auto test : testData) + { + Log::Comment(NoThrowString().Format(L"COORD (%hd, %hd)", test.startPos.X, test.startPos.Y)); + COORD result; + + // Test with accessibilityMode = false + //result = _buffer->GetWordStart(test.startPos, delimiters, false); + //VERIFY_ARE_EQUAL(test.expected.accessibilityModeDisabled, result); + + // Test with accessibilityMode = true + result = _buffer->GetWordStart(test.startPos, delimiters, true); + VERIFY_ARE_EQUAL(test.expected.accessibilityModeEnabled, result); + } +} + +void TextBufferTests::GetWordEnd() +{ + COORD bufferSize{ 80, 9001 }; + UINT cursorSize = 12; + TextAttribute attr{ 0x7f }; + auto _buffer = std::make_unique(bufferSize, attr, cursorSize, _renderTarget); + + // Setup: Write lines of text to the buffer + const std::array text = { L"word other", + L" more words" }; + for (auto row = 0; row < text.size(); ++row) + { + auto line = text[row]; + OutputCellIterator iter{ line }; + _buffer->WriteLine(iter, { 0, gsl::narrow(row) }); + } + + // Test Data: + // - COORD - starting position + // - COORD - expected result (accessibilityMode = false) + // - COORD - expected result (accessibilityMode = true) + struct ExpectedResult + { + COORD accessibilityModeDisabled; + COORD accessibilityModeEnabled; + }; + + struct Test + { + COORD startPos; + ExpectedResult expected; + }; + + std::vector testData = { + // tests for first line of text + { { 0, 0 }, {{ 3, 0 }, { 5, 0 }} }, + { { 1, 0 }, {{ 3, 0 }, { 5, 0 }} }, + { { 3, 0 }, {{ 3, 0 }, { 5, 0 }} }, + { { 4, 0 }, {{ 4, 0 }, { 5, 0 }} }, + { { 5, 0 }, {{ 9, 0 }, { 0, 1 }} }, + { { 6, 0 }, {{ 9, 0 }, { 0, 1 }} }, + { {20, 0 }, {{ 79, 0 }, { 0, 1 }} }, + { {79, 0 }, {{ 79, 0 }, { 0, 1 }} }, + + // tests for second line of text + { { 0, 1 }, {{ 1, 1 }, { 2, 1 }} }, + { { 1, 1 }, {{ 1, 1 }, { 2, 1 }} }, + { { 2, 1 }, {{ 5, 1 }, { 9, 1 }} }, + { { 3, 1 }, {{ 5, 1 }, { 9, 1 }} }, + { { 5, 1 }, {{ 5, 1 }, { 9, 1 }} }, + { { 6, 1 }, {{ 8, 1 }, { 9, 1 }} }, + { { 7, 1 }, {{ 8, 1 }, { 9, 1 }} }, + { { 9, 1 }, {{13, 1 }, { 0, 2 }} }, + { { 10, 1 }, {{13, 1 }, { 0, 2 }} }, + { { 20, 1 }, {{79, 1 }, { 0, 2 }} }, + { { 79, 1 }, {{79, 1 }, { 0, 2 }} }, + }; + // clang-format off + + const std::wstring_view delimiters = L" "; + for (auto test : testData) + { + Log::Comment(NoThrowString().Format(L"COORD (%hd, %hd)", test.startPos.X, test.startPos.Y)); + COORD result; + + // Test with accessibilityMode = false + //result = _buffer->GetWordEnd(test.startPos, delimiters, false); + //VERIFY_ARE_EQUAL(test.expected.accessibilityModeDisabled, result); + + // Test with accessibilityMode = true + result = _buffer->GetWordEnd(test.startPos, delimiters, true); + VERIFY_ARE_EQUAL(test.expected.accessibilityModeEnabled, result); + } +} diff --git a/src/interactivity/win32/uiaTextRange.hpp b/src/interactivity/win32/uiaTextRange.hpp index 00016d1dab2..c6755499fb0 100644 --- a/src/interactivity/win32/uiaTextRange.hpp +++ b/src/interactivity/win32/uiaTextRange.hpp @@ -45,8 +45,8 @@ namespace Microsoft::Console::Interactivity::Win32 // specific endpoint range HRESULT RuntimeClassInitialize(_In_ Microsoft::Console::Types::IUiaData* pData, _In_ IRawElementProviderSimple* const pProvider, - const COORD start, - const COORD end, + _In_ const COORD start, + _In_ const COORD end, _In_ const std::wstring_view wordDelimiters = DefaultWordDelimiter); // range from a UiaPoint diff --git a/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp b/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp index 1f307d90e8b..f95f779fdc3 100644 --- a/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp +++ b/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp @@ -23,6 +23,7 @@ using namespace Microsoft::Console::Interactivity::Win32; // for unit tests so instead we'll use this one. We don't care about // it not doing anything for its implementation because it is not used // during the unit tests below. + class DummyElementProvider final : public IRawElementProviderSimple { public: @@ -105,12 +106,12 @@ class UiaTextRangeTests } // set up default range + COORD coord = { 0, 0 }; Microsoft::WRL::MakeAndInitialize(&_range, _pUiaData, &_dummyProvider, - 0, - 0, - false); + coord, + coord); return true; } @@ -129,1518 +130,1257 @@ class UiaTextRangeTests return true; } - const size_t _getRowWidth() const - { - const CharRow& charRow = _pTextBuffer->_GetFirstRow().GetCharRow(); - return charRow.MeasureRight() - charRow.MeasureLeft(); - } - TEST_METHOD(DegenerateRangesDetected) { + const auto bufferSize = _pTextBuffer->GetSize(); + // make a degenerate range and verify that it reports degenerate Microsoft::WRL::ComPtr degenerate; Microsoft::WRL::MakeAndInitialize(°enerate, _pUiaData, &_dummyProvider, - 20, - 19, - true); + bufferSize.Origin(), + bufferSize.Origin()); VERIFY_IS_TRUE(degenerate->IsDegenerate()); - VERIFY_ARE_EQUAL(0u, degenerate->_rowCountInRange(_pUiaData)); VERIFY_ARE_EQUAL(degenerate->_start, degenerate->_end); // make a non-degenerate range and verify that it reports as such - Microsoft::WRL::ComPtr notDegenerate1; - Microsoft::WRL::MakeAndInitialize(¬Degenerate1, + const COORD end = { bufferSize.Origin().X + 1, bufferSize.Origin().Y }; + Microsoft::WRL::ComPtr notDegenerate; + Microsoft::WRL::MakeAndInitialize(¬Degenerate, _pUiaData, &_dummyProvider, - 20, - 20, - false); - VERIFY_IS_FALSE(notDegenerate1->IsDegenerate()); - VERIFY_ARE_EQUAL(1u, notDegenerate1->_rowCountInRange(_pUiaData)); + bufferSize.Origin(), + end); + VERIFY_IS_FALSE(notDegenerate->IsDegenerate()); + VERIFY_ARE_NOT_EQUAL(degenerate->_start, degenerate->_end); } - TEST_METHOD(CanCheckIfScreenInfoRowIsInViewport) + TEST_METHOD(CanMoveByCharacter) { - // check a viewport that's one line tall - SMALL_RECT viewport; - viewport.Top = 0; - viewport.Bottom = 0; - - VERIFY_IS_TRUE(_range->_isScreenInfoRowInViewport(0, viewport)); - VERIFY_IS_FALSE(_range->_isScreenInfoRowInViewport(1, viewport)); + const SHORT firstColumnIndex = 0; + const SHORT lastColumnIndex = _pScreenInfo->GetBufferSize().Width() - 1; + const SHORT topRow = 0; + const SHORT bottomRow = gsl::narrow(_pTextBuffer->TotalRowCount() - 1); - // check a slightly larger viewport - viewport.Bottom = 5; - for (auto i = 0; i <= viewport.Bottom; ++i) + struct ExpectedResult { - VERIFY_IS_TRUE(_range->_isScreenInfoRowInViewport(i, viewport), - NoThrowString().Format(L"%d should be in viewport", i)); - } - VERIFY_IS_FALSE(_range->_isScreenInfoRowInViewport(viewport.Bottom + 1, viewport)); - } - - TEST_METHOD(CanTranslateScreenInfoRowToViewport) - { - const int totalRows = _pTextBuffer->TotalRowCount(); - - SMALL_RECT viewport; - viewport.Top = 0; - viewport.Bottom = 10; - - std::vector> viewportSizes = { - { 0, 10 }, // viewport at top - { 2, 10 }, // shifted viewport - { totalRows - 5, totalRows + 3 } // viewport with 0th row + int moveAmt; + COORD start; + COORD end; }; - for (auto it = viewportSizes.begin(); it != viewportSizes.end(); ++it) - { - viewport.Top = static_cast(it->first); - viewport.Bottom = static_cast(it->second); - for (int i = viewport.Top; _range->_isScreenInfoRowInViewport(i, viewport); ++i) - { - VERIFY_ARE_EQUAL(i - viewport.Top, _range->_screenInfoRowToViewportRow(i, viewport)); - } - } - - // ScreenInfoRows that are above the viewport return a - // negative value - viewport.Top = 5; - viewport.Bottom = 10; - - VERIFY_ARE_EQUAL(-1, _range->_screenInfoRowToViewportRow(4, viewport)); - VERIFY_ARE_EQUAL(-2, _range->_screenInfoRowToViewportRow(3, viewport)); - } - - TEST_METHOD(CanTranslateEndpointToTextBufferRow) - { - const auto rowWidth = _getRowWidth(); - for (auto i = 0; i < 300; ++i) - { - VERIFY_ARE_EQUAL(i / rowWidth, _range->_endpointToTextBufferRow(_pUiaData, i)); - } - } - - TEST_METHOD(CanTranslateTextBufferRowToEndpoint) - { - const auto rowWidth = _getRowWidth(); - for (unsigned int i = 0; i < 5; ++i) + struct Test { - VERIFY_ARE_EQUAL(i * rowWidth, _range->_textBufferRowToEndpoint(_pUiaData, i)); - // make sure that the translation is reversible - VERIFY_ARE_EQUAL(i, _range->_endpointToTextBufferRow(_pUiaData, _range->_textBufferRowToEndpoint(_pUiaData, i))); - } - } - - TEST_METHOD(CanTranslateTextBufferRowToScreenInfoRow) - { - const auto rowWidth = _getRowWidth(); - for (unsigned int i = 0; i < 5; ++i) - { - VERIFY_ARE_EQUAL(i, _range->_textBufferRowToScreenInfoRow(_pUiaData, _range->_screenInfoRowToTextBufferRow(_pUiaData, i))); - } - } - - TEST_METHOD(CanTranslateEndpointToColumn) - { - const auto rowWidth = _getRowWidth(); - for (auto i = 0; i < 300; ++i) - { - const auto column = i % rowWidth; - VERIFY_ARE_EQUAL(column, _range->_endpointToColumn(_pUiaData, i)); - } - } - - TEST_METHOD(CanGetTotalRows) - { - const auto totalRows = _pTextBuffer->TotalRowCount(); - VERIFY_ARE_EQUAL(totalRows, - _range->_getTotalRows(_pUiaData)); - } - - TEST_METHOD(CanGetRowWidth) - { - const auto rowWidth = _getRowWidth(); - VERIFY_ARE_EQUAL(rowWidth, _range->_getRowWidth(_pUiaData)); - } - - TEST_METHOD(CanNormalizeRow) - { - const int totalRows = _pTextBuffer->TotalRowCount(); - std::vector> rowMappings = { - { 0, 0 }, - { totalRows / 2, totalRows / 2 }, - { totalRows - 1, totalRows - 1 }, - { totalRows, 0 }, - { totalRows + 1, 1 }, - { -1, totalRows - 1 } + std::wstring comment; + UiaTextRange::MoveState moveState; + int moveAmt; + ExpectedResult expected; }; - for (auto it = rowMappings.begin(); it != rowMappings.end(); ++it) - { - VERIFY_ARE_EQUAL(static_cast(it->second), _range->_normalizeRow(_pUiaData, it->first)); - } - } - - TEST_METHOD(CanGetViewportHeight) - { - SMALL_RECT viewport; - viewport.Top = 0; - viewport.Bottom = 0; - - // Viewports are inclusive, so Top == Bottom really means 1 row - VERIFY_ARE_EQUAL(1u, _range->_getViewportHeight(viewport)); - - // make the viewport 10 rows tall - viewport.Top = 3; - viewport.Bottom = 12; - VERIFY_ARE_EQUAL(10u, _range->_getViewportHeight(viewport)); - } - - TEST_METHOD(CanGetViewportWidth) - { - SMALL_RECT viewport; - viewport.Left = 0; - viewport.Right = 0; - - // Viewports are inclusive, Left == Right is really 1 column - VERIFY_ARE_EQUAL(1u, _range->_getViewportWidth(viewport)); - - // test a more normal size - viewport.Right = 300; - VERIFY_ARE_EQUAL(viewport.Right + 1u, _range->_getViewportWidth(viewport)); - } - - TEST_METHOD(CanCompareScreenCoords) - { - const std::vector> testData = { - { 0, 0, 0, 0, 0 }, - { 5, 0, 5, 0, 0 }, - { 2, 3, 2, 3, 0 }, - { 0, 6, 0, 6, 0 }, - { 1, 5, 2, 5, -1 }, - { 5, 4, 7, 3, -1 }, - { 3, 4, 3, 5, -1 }, - { 2, 0, 1, 9, 1 }, - { 4, 5, 4, 3, 1 } - }; - - for (auto data : testData) - { - VERIFY_ARE_EQUAL(std::get<4>(data), - UiaTextRange::_compareScreenCoords(_pUiaData, - std::get<0>(data), - std::get<1>(data), - std::get<2>(data), - std::get<3>(data))); - } - } - - TEST_METHOD(CanMoveByCharacter) - { - const Column firstColumnIndex = 0; - const Column lastColumnIndex = _pScreenInfo->GetBufferSize().Width() - 1; - const ScreenInfoRow topRow = 0; - const ScreenInfoRow bottomRow = _pTextBuffer->TotalRowCount() - 1; - // clang-format off - const std::vector> testData = + const std::vector testData { - { + Test{ L"can't move backward from (0, 0)", { - 0, 0, - 0, 2, - topRow, - lastColumnIndex, - firstColumnIndex, - UiaTextRange::MovementIncrement::Backward, + { 0, 0 }, + { 0, 2 }, UiaTextRange::MovementDirection::Backward }, -1, - 0, - 0u, - 0u + { + 0, + {0,0}, + {0,0} + } }, - { + Test{ L"can move backward within a row", { - 0, 1, - 0, 2, - topRow, - lastColumnIndex, - firstColumnIndex, - UiaTextRange::MovementIncrement::Backward, + { 0, 1 }, + { 0, 2 }, UiaTextRange::MovementDirection::Backward }, -1, - -1, - 0u, - 0u + { + -1, + {0,0}, + {0,0} + } }, - { + Test{ L"can move forward in a row", { - 2, 1, - 4, 5, - bottomRow, - firstColumnIndex, - lastColumnIndex, - UiaTextRange::MovementIncrement::Forward, + { 2, 1 }, + { 4, 5 }, UiaTextRange::MovementDirection::Forward }, 5, - 5, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, 2) + 6, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, 2) + 6 + { + 5, + {2,6}, + {2,6} + } }, - { + Test{ L"can't move past the last column in the last row", { - bottomRow, lastColumnIndex, - bottomRow, lastColumnIndex, - bottomRow, - firstColumnIndex, - lastColumnIndex, - UiaTextRange::MovementIncrement::Forward, + { bottomRow, lastColumnIndex }, + { bottomRow, lastColumnIndex }, UiaTextRange::MovementDirection::Forward }, 5, - 0, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex + { + 0, + {bottomRow, lastColumnIndex}, + {bottomRow, lastColumnIndex} + } }, - { + Test{ L"can move to a new row when necessary when moving forward", { - topRow, lastColumnIndex, - topRow, lastColumnIndex, - bottomRow, - firstColumnIndex, - lastColumnIndex, - UiaTextRange::MovementIncrement::Forward, + { topRow, lastColumnIndex }, + { topRow, lastColumnIndex }, UiaTextRange::MovementDirection::Forward }, 5, - 5, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow + 1) + 4, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow + 1) + 4 + { + 5, + {topRow + 1, 4}, + {topRow + 1, 4} + } }, - { + Test{ L"can move to a new row when necessary when moving backward", { - topRow + 1, firstColumnIndex, - topRow + 1, lastColumnIndex, - topRow, - lastColumnIndex, - firstColumnIndex, - UiaTextRange::MovementIncrement::Backward, + { topRow + 1, firstColumnIndex }, + { topRow + 1, lastColumnIndex }, UiaTextRange::MovementDirection::Backward }, -5, - -5, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + (lastColumnIndex - 4), - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + (lastColumnIndex - 4) + { + -5, + {topRow, lastColumnIndex - 4}, + {topRow, lastColumnIndex - 4} + } } }; // clang-format on - for (auto data : testData) + Microsoft::WRL::ComPtr utr; + for (const auto &test : testData) { - Log::Comment(std::get<0>(data).c_str()); + Log::Comment(test.comment.data()); int amountMoved; - std::pair newEndpoints = UiaTextRange::_moveByCharacter(_pUiaData, - std::get<2>(data), - std::get<1>(data), - &amountMoved); - - VERIFY_ARE_EQUAL(std::get<3>(data), amountMoved); - VERIFY_ARE_EQUAL(std::get<4>(data), newEndpoints.first); - VERIFY_ARE_EQUAL(std::get<5>(data), newEndpoints.second); - } - } - - TEST_METHOD(CanMoveByWord_EmptyBuffer) - { - const Column firstColumnIndex = 0; - const Column lastColumnIndex = _pScreenInfo->GetBufferSize().Width() - 1; - const ScreenInfoRow topRow = 0; - const ScreenInfoRow bottomRow = _pTextBuffer->TotalRowCount() - 1; - - // clang-format off - const std::vector> testData = - { - { - L"can't move backward from (0, 0)", - { - 0, 0, - 0, 2, - topRow, - lastColumnIndex, - firstColumnIndex, - UiaTextRange::MovementIncrement::Backward, - UiaTextRange::MovementDirection::Backward - }, - -1, - 0, - 0u, - lastColumnIndex - }, - - { - L"can move backward within a row", - { - 0, 1, - 0, 2, - topRow, - lastColumnIndex, - firstColumnIndex, - UiaTextRange::MovementIncrement::Backward, - UiaTextRange::MovementDirection::Backward - }, - -1, - -1, - 0u, - lastColumnIndex - }, - - { - L"can move forward in a row", - { - 2, 1, - 4, 5, - bottomRow, - firstColumnIndex, - lastColumnIndex, - UiaTextRange::MovementIncrement::Forward, - UiaTextRange::MovementDirection::Forward - }, - 5, - 5, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, 8), - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, 8) + lastColumnIndex - }, - - { - L"can't move past the last column in the last row", - { - bottomRow, lastColumnIndex, - bottomRow, lastColumnIndex, - bottomRow, - firstColumnIndex, - lastColumnIndex, - UiaTextRange::MovementIncrement::Forward, - UiaTextRange::MovementDirection::Forward - }, - 5, - 0, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow), - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex - }, - { - L"can move to a new row when necessary when moving forward", - { - topRow, lastColumnIndex, - topRow, lastColumnIndex, - bottomRow, - firstColumnIndex, - lastColumnIndex, - UiaTextRange::MovementIncrement::Forward, - UiaTextRange::MovementDirection::Forward - }, - 5, - 5, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow + 5), - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow + 5) + lastColumnIndex - }, - - { - L"can move to a new row when necessary when moving backward", - { - topRow + 1, firstColumnIndex, - topRow + 1, lastColumnIndex, - topRow, - lastColumnIndex, - firstColumnIndex, - UiaTextRange::MovementIncrement::Backward, - UiaTextRange::MovementDirection::Backward - }, - -5, - -2, - 0u, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + lastColumnIndex - } - }; - // clang-format on + const auto moveState = test.moveState; + Microsoft::WRL::MakeAndInitialize(&utr, _pUiaData, &_dummyProvider, moveState.start, moveState.end); + utr->Move(TextUnit::TextUnit_Character, test.moveAmt, &amountMoved); - for (auto data : testData) - { - Log::Comment(std::get<0>(data).c_str()); - int amountMoved; - std::pair newEndpoints = UiaTextRange::_moveByWord(_pUiaData, - std::get<2>(data), - std::get<1>(data), - L"", - &amountMoved); - - VERIFY_ARE_EQUAL(std::get<3>(data), amountMoved); - VERIFY_ARE_EQUAL(std::get<4>(data), newEndpoints.first); - VERIFY_ARE_EQUAL(std::get<5>(data), newEndpoints.second); + VERIFY_ARE_EQUAL(test.expected.moveAmt, amountMoved); + VERIFY_ARE_EQUAL(test.expected.start, utr->_start); + VERIFY_ARE_EQUAL(test.expected.end, utr->_end); } } - TEST_METHOD(CanMoveByWord_NonEmptyBuffer) - { - const Column firstColumnIndex = 0; - const Column lastColumnIndex = _pScreenInfo->GetBufferSize().Width() - 1; - const ScreenInfoRow topRow = 0; - const ScreenInfoRow bottomRow = _pTextBuffer->TotalRowCount() - 1; - - const std::wstring_view text[] = { - L"word1 word2 word3", - L"word4 word5 word6" - }; + //TEST_METHOD(CanMoveByWord_EmptyBuffer) + //{ + // const Column firstColumnIndex = 0; + // const Column lastColumnIndex = _pScreenInfo->GetBufferSize().Width() - 1; + // const ScreenInfoRow topRow = 0; + // const ScreenInfoRow bottomRow = _pTextBuffer->TotalRowCount() - 1; + + // // clang-format off + // const std::vector> testData = + // { + // { + // L"can't move backward from (0, 0)", + // { + // 0, 0, + // 0, 2, + // topRow, + // lastColumnIndex, + // firstColumnIndex, + // UiaTextRange::MovementIncrement::Backward, + // UiaTextRange::MovementDirection::Backward + // }, + // -1, + // 0, + // 0u, + // lastColumnIndex + // }, + + // { + // L"can move backward within a row", + // { + // 0, 1, + // 0, 2, + // topRow, + // lastColumnIndex, + // firstColumnIndex, + // UiaTextRange::MovementIncrement::Backward, + // UiaTextRange::MovementDirection::Backward + // }, + // -1, + // -1, + // 0u, + // lastColumnIndex + // }, + + // { + // L"can move forward in a row", + // { + // 2, 1, + // 4, 5, + // bottomRow, + // firstColumnIndex, + // lastColumnIndex, + // UiaTextRange::MovementIncrement::Forward, + // UiaTextRange::MovementDirection::Forward + // }, + // 5, + // 5, + // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, 8), + // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, 8) + lastColumnIndex + // }, + + // { + // L"can't move past the last column in the last row", + // { + // bottomRow, lastColumnIndex, + // bottomRow, lastColumnIndex, + // bottomRow, + // firstColumnIndex, + // lastColumnIndex, + // UiaTextRange::MovementIncrement::Forward, + // UiaTextRange::MovementDirection::Forward + // }, + // 5, + // 0, + // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow), + // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex + // }, + + // { + // L"can move to a new row when necessary when moving forward", + // { + // topRow, lastColumnIndex, + // topRow, lastColumnIndex, + // bottomRow, + // firstColumnIndex, + // lastColumnIndex, + // UiaTextRange::MovementIncrement::Forward, + // UiaTextRange::MovementDirection::Forward + // }, + // 5, + // 5, + // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow + 5), + // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow + 5) + lastColumnIndex + // }, + + // { + // L"can move to a new row when necessary when moving backward", + // { + // topRow + 1, firstColumnIndex, + // topRow + 1, lastColumnIndex, + // topRow, + // lastColumnIndex, + // firstColumnIndex, + // UiaTextRange::MovementIncrement::Backward, + // UiaTextRange::MovementDirection::Backward + // }, + // -5, + // -2, + // 0u, + // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + lastColumnIndex + // } + // }; + // // clang-format on + + // for (auto data : testData) + // { + // Log::Comment(std::get<0>(data).c_str()); + // int amountMoved; + // std::pair newEndpoints = UiaTextRange::_moveByWord(_pUiaData, + // std::get<2>(data), + // std::get<1>(data), + // L"", + // &amountMoved); + + // VERIFY_ARE_EQUAL(std::get<3>(data), amountMoved); + // VERIFY_ARE_EQUAL(std::get<4>(data), newEndpoints.first); + // VERIFY_ARE_EQUAL(std::get<5>(data), newEndpoints.second); + // } + //} + + //TEST_METHOD(CanMoveByWord_NonEmptyBuffer) + //{ + // const Column firstColumnIndex = 0; + // const Column lastColumnIndex = _pScreenInfo->GetBufferSize().Width() - 1; + // const ScreenInfoRow topRow = 0; + // const ScreenInfoRow bottomRow = _pTextBuffer->TotalRowCount() - 1; + + // const std::wstring_view text[] = { + // L"word1 word2 word3", + // L"word4 word5 word6" + // }; + + // for (auto i = 0; i < 2; i++) + // { + // _pTextBuffer->WriteLine(text[i], { 0, gsl::narrow(i) }); + // } + + // // clang-format off + // const std::vector> testData = + // { + // { + // L"move backwards on the word by (0,0)", + // { + // 0, 1, + // 0, 2, + // topRow, + // lastColumnIndex, + // firstColumnIndex, + // UiaTextRange::MovementIncrement::Backward, + // UiaTextRange::MovementDirection::Backward + // }, + // -1, + // -1, + // 0u, + // 6 + // }, + + // { + // L"get next word while on first word", + // { + // 0, 0, + // 0, 0, + // bottomRow, + // firstColumnIndex, + // lastColumnIndex, + // UiaTextRange::MovementIncrement::Forward, + // UiaTextRange::MovementDirection::Forward + // }, + // 1, + // 1, + // 0, + // 6 + // }, + + // { + // L"get next word twice while on first word", + // { + // 0, 0, + // 0, 0, + // bottomRow, + // firstColumnIndex, + // lastColumnIndex, + // UiaTextRange::MovementIncrement::Forward, + // UiaTextRange::MovementDirection::Forward + // }, + // 2, + // 2, + // 7, + // 14 + // }, + + // { + // L"move forward to next row with word", + // { + // topRow, lastColumnIndex, + // topRow, lastColumnIndex, + // bottomRow, + // firstColumnIndex, + // lastColumnIndex, + // UiaTextRange::MovementIncrement::Forward, + // UiaTextRange::MovementDirection::Forward + // }, + // 1, + // 1, + // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow + 1), + // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow + 1) + 6 + // }, + + // { + // L"move backwards to previous row with word", + // { + // topRow + 1, firstColumnIndex, + // topRow + 1, lastColumnIndex, + // topRow, + // lastColumnIndex, + // firstColumnIndex, + // UiaTextRange::MovementIncrement::Backward, + // UiaTextRange::MovementDirection::Backward + // }, + // -1, + // -1, + // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + 15, + // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + lastColumnIndex + // } + // }; + // // clang-format on + + // for (auto data : testData) + // { + // Log::Comment(std::get<0>(data).c_str()); + // int amountMoved; + // std::pair newEndpoints = UiaTextRange::_moveByWord(_pUiaData, + // std::get<2>(data), + // std::get<1>(data), + // L"", + // &amountMoved); + + // VERIFY_ARE_EQUAL(std::get<3>(data), amountMoved); + // VERIFY_ARE_EQUAL(std::get<4>(data), newEndpoints.first); + // VERIFY_ARE_EQUAL(std::get<5>(data), newEndpoints.second); + // } + //} - for (auto i = 0; i < 2; i++) - { - _pTextBuffer->WriteLine(text[i], { 0, gsl::narrow(i) }); - } + TEST_METHOD(CanMoveByLine) + { + const SHORT firstColumnIndex = 0; + const SHORT lastColumnIndex = _pScreenInfo->GetBufferSize().Width() - 1; + const SHORT topRow = 0; + const SHORT bottomRow = gsl::narrow(_pTextBuffer->TotalRowCount() - 1); - // clang-format off - const std::vector> testData = + struct ExpectedResult { - { - L"move backwards on the word by (0,0)", - { - 0, 1, - 0, 2, - topRow, - lastColumnIndex, - firstColumnIndex, - UiaTextRange::MovementIncrement::Backward, - UiaTextRange::MovementDirection::Backward - }, - -1, - -1, - 0u, - 6 - }, - - { - L"get next word while on first word", - { - 0, 0, - 0, 0, - bottomRow, - firstColumnIndex, - lastColumnIndex, - UiaTextRange::MovementIncrement::Forward, - UiaTextRange::MovementDirection::Forward - }, - 1, - 1, - 0, - 6 - }, - - { - L"get next word twice while on first word", - { - 0, 0, - 0, 0, - bottomRow, - firstColumnIndex, - lastColumnIndex, - UiaTextRange::MovementIncrement::Forward, - UiaTextRange::MovementDirection::Forward - }, - 2, - 2, - 7, - 14 - }, - - { - L"move forward to next row with word", - { - topRow, lastColumnIndex, - topRow, lastColumnIndex, - bottomRow, - firstColumnIndex, - lastColumnIndex, - UiaTextRange::MovementIncrement::Forward, - UiaTextRange::MovementDirection::Forward - }, - 1, - 1, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow + 1), - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow + 1) + 6 - }, - - { - L"move backwards to previous row with word", - { - topRow + 1, firstColumnIndex, - topRow + 1, lastColumnIndex, - topRow, - lastColumnIndex, - firstColumnIndex, - UiaTextRange::MovementIncrement::Backward, - UiaTextRange::MovementDirection::Backward - }, - -1, - -1, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + 15, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + lastColumnIndex - } + int moveAmt; + COORD start; + COORD end; }; - // clang-format on - for (auto data : testData) + struct Test { - Log::Comment(std::get<0>(data).c_str()); - int amountMoved; - std::pair newEndpoints = UiaTextRange::_moveByWord(_pUiaData, - std::get<2>(data), - std::get<1>(data), - L"", - &amountMoved); - - VERIFY_ARE_EQUAL(std::get<3>(data), amountMoved); - VERIFY_ARE_EQUAL(std::get<4>(data), newEndpoints.first); - VERIFY_ARE_EQUAL(std::get<5>(data), newEndpoints.second); - } - } - - TEST_METHOD(CanMoveByLine) - { - const Column firstColumnIndex = 0; - const Column lastColumnIndex = _pScreenInfo->GetBufferSize().Width() - 1; - const ScreenInfoRow topRow = 0; - const ScreenInfoRow bottomRow = _pTextBuffer->TotalRowCount() - 1; + std::wstring comment; + UiaTextRange::MoveState moveState; + int moveAmt; + ExpectedResult expected; + }; // clang-format off - const std::vector> testData = + const std::vector testData { - { + Test{ L"can't move backward from top row", { - topRow, firstColumnIndex, - topRow, lastColumnIndex, - topRow, - lastColumnIndex, - firstColumnIndex, - UiaTextRange::MovementIncrement::Backward, + {topRow, firstColumnIndex}, + {topRow, lastColumnIndex}, UiaTextRange::MovementDirection::Backward }, -4, - 0, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + lastColumnIndex + { + 0, + {topRow, firstColumnIndex}, + {topRow, lastColumnIndex} + } }, - { + Test{ L"can move forward from top row", { - topRow, firstColumnIndex, - topRow, lastColumnIndex, - bottomRow, - firstColumnIndex, - lastColumnIndex, - UiaTextRange::MovementIncrement::Forward, + {topRow, firstColumnIndex}, + {topRow, lastColumnIndex}, UiaTextRange::MovementDirection::Forward }, 4, - 4, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow + 4) + firstColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow + 4) + lastColumnIndex + { + 4, + {topRow + 4, firstColumnIndex}, + {topRow + 4, lastColumnIndex} + } }, - { + Test{ L"can't move forward from bottom row", { - bottomRow, firstColumnIndex, - bottomRow, lastColumnIndex, - bottomRow, - firstColumnIndex, - lastColumnIndex, - UiaTextRange::MovementIncrement::Forward, + {bottomRow, firstColumnIndex}, + {bottomRow, lastColumnIndex}, UiaTextRange::MovementDirection::Forward }, 3, - 0, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + firstColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex + { + 0, + {bottomRow, firstColumnIndex}, + {bottomRow, lastColumnIndex} + } }, - { + Test{ L"can move backward from bottom row", { - bottomRow, firstColumnIndex, - bottomRow, lastColumnIndex, - topRow, - lastColumnIndex, - firstColumnIndex, - UiaTextRange::MovementIncrement::Backward, + {bottomRow, firstColumnIndex}, + {bottomRow, lastColumnIndex}, UiaTextRange::MovementDirection::Backward }, -3, - -3, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow - 3) + firstColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow - 3) + lastColumnIndex + { + -3, + {bottomRow - 3, firstColumnIndex}, + {bottomRow - 3, lastColumnIndex} + } }, - { + Test{ L"can't move backward when part of the top row is in the range", { - topRow, firstColumnIndex + 5, - topRow, lastColumnIndex, - topRow, - lastColumnIndex, - firstColumnIndex, - UiaTextRange::MovementIncrement::Backward, + {topRow, firstColumnIndex + 5}, + {topRow, lastColumnIndex}, UiaTextRange::MovementDirection::Backward }, -1, - 0, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex + 5, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + lastColumnIndex + { + 0, + {topRow, firstColumnIndex + 5}, + {topRow, lastColumnIndex} + } }, - { + Test{ L"can't move forward when part of the bottom row is in the range", { - bottomRow, firstColumnIndex, - bottomRow, firstColumnIndex, - bottomRow, - firstColumnIndex, - lastColumnIndex, - UiaTextRange::MovementIncrement::Forward, + {bottomRow, firstColumnIndex}, + {bottomRow, firstColumnIndex}, UiaTextRange::MovementDirection::Forward }, 1, - 0, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + firstColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + firstColumnIndex + { + 0, + {bottomRow, firstColumnIndex}, + {bottomRow, firstColumnIndex} + } } }; // clang-format on - for (auto data : testData) + Microsoft::WRL::ComPtr utr; + for (auto test : testData) { - Log::Comment(std::get<0>(data).c_str()); + Log::Comment(test.comment.c_str()); int amountMoved; - std::pair newEndpoints = UiaTextRange::_moveByLine(_pUiaData, - std::get<2>(data), - std::get<1>(data), - &amountMoved); - - VERIFY_ARE_EQUAL(std::get<3>(data), amountMoved); - VERIFY_ARE_EQUAL(std::get<4>(data), newEndpoints.first); - VERIFY_ARE_EQUAL(std::get<5>(data), newEndpoints.second); + + const auto moveState = test.moveState; + Microsoft::WRL::MakeAndInitialize(&utr, _pUiaData, &_dummyProvider, moveState.start, moveState.end); + utr->Move(TextUnit::TextUnit_Line, test.moveAmt, &amountMoved); + + VERIFY_ARE_EQUAL(test.expected.moveAmt, amountMoved); + VERIFY_ARE_EQUAL(test.expected.start, utr->_start); + VERIFY_ARE_EQUAL(test.expected.end, utr->_end); } } TEST_METHOD(CanMoveEndpointByUnitCharacter) { - const Column firstColumnIndex = 0; - const Column lastColumnIndex = _pScreenInfo->GetBufferSize().Width() - 1; - const ScreenInfoRow topRow = 0; - const ScreenInfoRow bottomRow = _pTextBuffer->TotalRowCount() - 1; + const SHORT firstColumnIndex = 0; + const SHORT lastColumnIndex = _pScreenInfo->GetBufferSize().Width() - 1; + const SHORT topRow = 0; + const SHORT bottomRow = static_cast(_pTextBuffer->TotalRowCount() - 1); + + struct ExpectedResult + { + int moveAmt; + COORD start; + COORD end; + }; + + struct Test + { + std::wstring comment; + UiaTextRange::MoveState moveState; + int moveAmt; + TextPatternRangeEndpoint endpoint; + ExpectedResult expected; + }; // clang-format off - const std::vector> testData = + const std::vector testData { - { + Test{ L"can't move _start past the beginning of the document when _start is positioned at the beginning", { - topRow, firstColumnIndex, - topRow, lastColumnIndex, - topRow, - lastColumnIndex, - firstColumnIndex, - UiaTextRange::MovementIncrement::Backward, + {topRow, firstColumnIndex}, + {topRow, lastColumnIndex}, UiaTextRange::MovementDirection::Backward }, -1, - 0, TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + lastColumnIndex, - false + { + 0, + {topRow, firstColumnIndex}, + {topRow, lastColumnIndex} + } }, - { + Test{ L"can partially move _start to the begining of the document when it is closer than the move count requested", { - topRow, firstColumnIndex + 3, - topRow, lastColumnIndex, - topRow, - lastColumnIndex, - firstColumnIndex, - UiaTextRange::MovementIncrement::Backward, + {topRow, firstColumnIndex + 3}, + {topRow, lastColumnIndex}, UiaTextRange::MovementDirection::Backward }, -5, - -3, TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + lastColumnIndex, - false + { + -3, + {topRow, firstColumnIndex}, + {topRow, lastColumnIndex} + } }, - { + Test{ L"can't move _end past the begining of the document", { - topRow, firstColumnIndex, - topRow, firstColumnIndex + 4, - topRow, - lastColumnIndex, - firstColumnIndex, - UiaTextRange::MovementIncrement::Backward, + {topRow, firstColumnIndex}, + {topRow, firstColumnIndex + 4}, UiaTextRange::MovementDirection::Backward }, -5, - -4, TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex, - false + { + -4, + {topRow, firstColumnIndex}, + {topRow, firstColumnIndex} + } }, - { + Test{ L"_start follows _end when passed during movement", { - topRow, firstColumnIndex + 5, - topRow, firstColumnIndex + 10, - topRow, - lastColumnIndex, - firstColumnIndex, - UiaTextRange::MovementIncrement::Backward, + {topRow, firstColumnIndex + 5}, + {topRow, firstColumnIndex + 10}, UiaTextRange::MovementDirection::Backward }, -7, - -7, TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + 3, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + 3, - true + { + -7, + {topRow, 3}, + {topRow, 3} + } }, - { + Test{ L"can't move _end past the beginning of the document when _end is positioned at the end", { - bottomRow, firstColumnIndex, - bottomRow, lastColumnIndex, - bottomRow, - firstColumnIndex, - lastColumnIndex, - UiaTextRange::MovementIncrement::Forward, + {bottomRow, firstColumnIndex}, + {bottomRow, lastColumnIndex}, UiaTextRange::MovementDirection::Forward }, 1, - 0, TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + firstColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex, - false + { + 0, + {bottomRow, firstColumnIndex}, + {bottomRow, lastColumnIndex} + } }, - { + Test{ L"can partially move _end to the end of the document when it is closer than the move count requested", { - topRow, firstColumnIndex, - bottomRow, lastColumnIndex - 3, - bottomRow, - firstColumnIndex, - lastColumnIndex, - UiaTextRange::MovementIncrement::Forward, + {topRow, firstColumnIndex}, + {bottomRow, lastColumnIndex - 3}, UiaTextRange::MovementDirection::Forward }, 5, - 3, TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex, - false + { + 3, + {topRow, firstColumnIndex}, + {bottomRow, lastColumnIndex} + } }, - { + Test{ L"can't move _start past the end of the document", { - bottomRow, lastColumnIndex - 4, - bottomRow, lastColumnIndex, - bottomRow, - firstColumnIndex, - lastColumnIndex, - UiaTextRange::MovementIncrement::Forward, + {bottomRow, lastColumnIndex - 4}, + {bottomRow, lastColumnIndex}, UiaTextRange::MovementDirection::Forward }, 5, - 4, TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex, - false + { + 4, + {bottomRow, lastColumnIndex}, + {bottomRow, lastColumnIndex} + } }, - { + Test{ L"_end follows _start when passed during movement", { - topRow, firstColumnIndex + 5, - topRow, firstColumnIndex + 10, - topRow, - lastColumnIndex, - firstColumnIndex, - UiaTextRange::MovementIncrement::Forward, + {topRow, firstColumnIndex + 5}, + {topRow, firstColumnIndex + 10}, UiaTextRange::MovementDirection::Forward }, 7, - 7, TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + 12, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + 12, - true + { + 7, + {topRow, 12}, + {topRow, 12} + } }, }; // clang-format on - for (auto data : testData) + Microsoft::WRL::ComPtr utr; + for (auto test : testData) { - Log::Comment(std::get<0>(data).c_str()); - std::tuple result; + Log::Comment(test.comment.c_str()); int amountMoved; - result = UiaTextRange::_moveEndpointByUnitCharacter(_pUiaData, - std::get<2>(data), - std::get<4>(data), - std::get<1>(data), - &amountMoved); - - VERIFY_ARE_EQUAL(std::get<3>(data), amountMoved); - VERIFY_ARE_EQUAL(std::get<5>(data), std::get<0>(result)); - VERIFY_ARE_EQUAL(std::get<6>(data), std::get<1>(result)); - VERIFY_ARE_EQUAL(std::get<7>(data), std::get<2>(result)); - } - } - TEST_METHOD(CanMoveEndpointByUnitWord) - { - const Column firstColumnIndex = 0; - const Column lastColumnIndex = _pScreenInfo->GetBufferSize().Width() - 1; - const ScreenInfoRow topRow = 0; - const ScreenInfoRow bottomRow = _pTextBuffer->TotalRowCount() - 1; - - const std::wstring_view text[] = { - L"word1 word2 word3", - L"word4 word5 word6" - }; + const auto moveState = test.moveState; + Microsoft::WRL::MakeAndInitialize(&utr, _pUiaData, &_dummyProvider, moveState.start, moveState.end); + utr->MoveEndpointByUnit(test.endpoint, TextUnit::TextUnit_Character, test.moveAmt, &amountMoved); - for (auto i = 0; i < 2; i++) - { - _pTextBuffer->WriteLine(text[i], { 0, gsl::narrow(i) }); + VERIFY_ARE_EQUAL(test.expected.moveAmt, amountMoved); + VERIFY_ARE_EQUAL(test.expected.start, utr->_start); + VERIFY_ARE_EQUAL(test.expected.end, utr->_end); } + } - // clang-format off - const std::vector> testData = - { - { - L"can't move _start past the beginning of the document when _start is positioned at the beginning", - { - topRow, firstColumnIndex, - topRow, lastColumnIndex, - topRow, - lastColumnIndex, - firstColumnIndex, - UiaTextRange::MovementIncrement::Backward, - UiaTextRange::MovementDirection::Backward - }, - -1, - 0, - TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + lastColumnIndex, - false - }, - - { - L"can partially move _start to the begining of the document when it is closer than the move count requested", - { - topRow, firstColumnIndex + 15, - topRow, lastColumnIndex, - topRow, - lastColumnIndex, - firstColumnIndex, - UiaTextRange::MovementIncrement::Backward, - UiaTextRange::MovementDirection::Backward - }, - -5, - -2, - TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + lastColumnIndex, - false - }, - - { - L"can't move _end past the begining of the document", - { - topRow, firstColumnIndex, - topRow, firstColumnIndex + 2, - topRow, - lastColumnIndex, - firstColumnIndex, - UiaTextRange::MovementIncrement::Backward, - UiaTextRange::MovementDirection::Backward - }, - -2, - -1, - TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex, - false - }, - - { - L"_start follows _end when passed during movement", - { - topRow + 1, firstColumnIndex + 2, - topRow + 1, firstColumnIndex + 10, - topRow, - lastColumnIndex, - firstColumnIndex, - UiaTextRange::MovementIncrement::Backward, - UiaTextRange::MovementDirection::Backward - }, - -4, - -4, - TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + 6, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + 6, - true - }, - - { - L"can't move _end past the beginning of the document when _end is positioned at the end", - { - bottomRow, firstColumnIndex, - bottomRow, lastColumnIndex, - bottomRow, - firstColumnIndex, - lastColumnIndex, - UiaTextRange::MovementIncrement::Forward, - UiaTextRange::MovementDirection::Forward - }, - 1, - 0, - TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + firstColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex, - false - }, - - { - L"can partially move _end to the end of the document when it is closer than the move count requested", - { - topRow, firstColumnIndex, - bottomRow, lastColumnIndex - 3, - bottomRow, - firstColumnIndex, - lastColumnIndex, - UiaTextRange::MovementIncrement::Forward, - UiaTextRange::MovementDirection::Forward - }, - 5, - 1, - TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex, - false - }, + //TEST_METHOD(CanMoveEndpointByUnitWord) + //{ + // const Column firstColumnIndex = 0; + // const Column lastColumnIndex = _pScreenInfo->GetBufferSize().Width() - 1; + // const ScreenInfoRow topRow = 0; + // const ScreenInfoRow bottomRow = _pTextBuffer->TotalRowCount() - 1; + + // const std::wstring_view text[] = { + // L"word1 word2 word3", + // L"word4 word5 word6" + // }; + + // for (auto i = 0; i < 2; i++) + // { + // _pTextBuffer->WriteLine(text[i], { 0, gsl::narrow(i) }); + // } + + // // clang-format off + // const std::vector> testData = + // { + // { + // L"can't move _start past the beginning of the document when _start is positioned at the beginning", + // { + // topRow, firstColumnIndex, + // topRow, lastColumnIndex, + // topRow, + // lastColumnIndex, + // firstColumnIndex, + // UiaTextRange::MovementIncrement::Backward, + // UiaTextRange::MovementDirection::Backward + // }, + // -1, + // 0, + // TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, + // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex, + // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + lastColumnIndex, + // false + // }, + + // { + // L"can partially move _start to the begining of the document when it is closer than the move count requested", + // { + // topRow, firstColumnIndex + 15, + // topRow, lastColumnIndex, + // topRow, + // lastColumnIndex, + // firstColumnIndex, + // UiaTextRange::MovementIncrement::Backward, + // UiaTextRange::MovementDirection::Backward + // }, + // -5, + // -2, + // TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, + // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex, + // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + lastColumnIndex, + // false + // }, + + // { + // L"can't move _end past the begining of the document", + // { + // topRow, firstColumnIndex, + // topRow, firstColumnIndex + 2, + // topRow, + // lastColumnIndex, + // firstColumnIndex, + // UiaTextRange::MovementIncrement::Backward, + // UiaTextRange::MovementDirection::Backward + // }, + // -2, + // -1, + // TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, + // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex, + // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex, + // false + // }, + + // { + // L"_start follows _end when passed during movement", + // { + // topRow + 1, firstColumnIndex + 2, + // topRow + 1, firstColumnIndex + 10, + // topRow, + // lastColumnIndex, + // firstColumnIndex, + // UiaTextRange::MovementIncrement::Backward, + // UiaTextRange::MovementDirection::Backward + // }, + // -4, + // -4, + // TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, + // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + 6, + // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + 6, + // true + // }, + + // { + // L"can't move _end past the beginning of the document when _end is positioned at the end", + // { + // bottomRow, firstColumnIndex, + // bottomRow, lastColumnIndex, + // bottomRow, + // firstColumnIndex, + // lastColumnIndex, + // UiaTextRange::MovementIncrement::Forward, + // UiaTextRange::MovementDirection::Forward + // }, + // 1, + // 0, + // TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, + // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + firstColumnIndex, + // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex, + // false + // }, + + // { + // L"can partially move _end to the end of the document when it is closer than the move count requested", + // { + // topRow, firstColumnIndex, + // bottomRow, lastColumnIndex - 3, + // bottomRow, + // firstColumnIndex, + // lastColumnIndex, + // UiaTextRange::MovementIncrement::Forward, + // UiaTextRange::MovementDirection::Forward + // }, + // 5, + // 1, + // TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, + // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex, + // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex, + // false + // }, + + // { + // L"can't move _start past the end of the document", + // { + // bottomRow, lastColumnIndex - 4, + // bottomRow, lastColumnIndex, + // bottomRow, + // firstColumnIndex, + // lastColumnIndex, + // UiaTextRange::MovementIncrement::Forward, + // UiaTextRange::MovementDirection::Forward + // }, + // 5, + // 1, + // TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, + // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex, + // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex, + // false + // }, + + // { + // L"_end follows _start when passed during movement", + // { + // topRow, firstColumnIndex, + // topRow, firstColumnIndex + 3, + // topRow, + // lastColumnIndex, + // firstColumnIndex, + // UiaTextRange::MovementIncrement::Forward, + // UiaTextRange::MovementDirection::Forward + // }, + // 2, + // 2, + // TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, + // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow)+15, + // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow)+15, + // true + // }, + // }; + // // clang-format on + + // for (auto data : testData) + // { + // Log::Comment(std::get<0>(data).c_str()); + // std::tuple result; + // int amountMoved; + // result = UiaTextRange::_moveEndpointByUnitWord(_pUiaData, + // std::get<2>(data), + // std::get<4>(data), + // std::get<1>(data), + // L" ", + // &amountMoved); + + // VERIFY_ARE_EQUAL(std::get<3>(data), amountMoved); + // VERIFY_ARE_EQUAL(std::get<5>(data), std::get<0>(result)); + // VERIFY_ARE_EQUAL(std::get<6>(data), std::get<1>(result)); + // VERIFY_ARE_EQUAL(std::get<7>(data), std::get<2>(result)); + // } + //} - { - L"can't move _start past the end of the document", - { - bottomRow, lastColumnIndex - 4, - bottomRow, lastColumnIndex, - bottomRow, - firstColumnIndex, - lastColumnIndex, - UiaTextRange::MovementIncrement::Forward, - UiaTextRange::MovementDirection::Forward - }, - 5, - 1, - TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex, - false - }, + TEST_METHOD(CanMoveEndpointByUnitLine) + { + const SHORT firstColumnIndex = 0; + const SHORT lastColumnIndex = _pScreenInfo->GetBufferSize().Width() - 1; + const SHORT topRow = 0; + const SHORT bottomRow = gsl::narrow(_pTextBuffer->TotalRowCount() - 1); - { - L"_end follows _start when passed during movement", - { - topRow, firstColumnIndex, - topRow, firstColumnIndex + 3, - topRow, - lastColumnIndex, - firstColumnIndex, - UiaTextRange::MovementIncrement::Forward, - UiaTextRange::MovementDirection::Forward - }, - 2, - 2, - TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow)+15, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow)+15, - true - }, + struct ExpectedResult + { + int moveAmt; + COORD start; + COORD end; }; - // clang-format on - for (auto data : testData) + struct Test { - Log::Comment(std::get<0>(data).c_str()); - std::tuple result; - int amountMoved; - result = UiaTextRange::_moveEndpointByUnitWord(_pUiaData, - std::get<2>(data), - std::get<4>(data), - std::get<1>(data), - L" ", - &amountMoved); - - VERIFY_ARE_EQUAL(std::get<3>(data), amountMoved); - VERIFY_ARE_EQUAL(std::get<5>(data), std::get<0>(result)); - VERIFY_ARE_EQUAL(std::get<6>(data), std::get<1>(result)); - VERIFY_ARE_EQUAL(std::get<7>(data), std::get<2>(result)); - } - } - - TEST_METHOD(CanMoveEndpointByUnitLine) - { - const Column firstColumnIndex = 0; - const Column lastColumnIndex = _pScreenInfo->GetBufferSize().Width() - 1; - const ScreenInfoRow topRow = 0; - const ScreenInfoRow bottomRow = _pTextBuffer->TotalRowCount() - 1; + std::wstring comment; + UiaTextRange::MoveState moveState; + int moveAmt; + TextPatternRangeEndpoint endpoint; + ExpectedResult expected; + }; // clang-format off - const std::vector> testData = + const std::vector testData { - { + Test{ L"can move _end forward without affecting _start", { - topRow, firstColumnIndex, - topRow, lastColumnIndex, - bottomRow, - firstColumnIndex, - lastColumnIndex, - UiaTextRange::MovementIncrement::Forward, + {topRow, firstColumnIndex}, + {topRow, lastColumnIndex}, UiaTextRange::MovementDirection::Forward }, 1, - 1, TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow + 1) + lastColumnIndex, - false + 1, + {topRow, firstColumnIndex}, + {topRow, lastColumnIndex} }, - { + Test{ L"can move _end backward without affecting _start", { - topRow + 1, firstColumnIndex, - topRow + 5, lastColumnIndex, - topRow, - lastColumnIndex, - firstColumnIndex, - UiaTextRange::MovementIncrement::Backward, + {topRow + 1, firstColumnIndex}, + {topRow + 5, lastColumnIndex}, UiaTextRange::MovementDirection::Backward }, -2, - -2, TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow + 1) + firstColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow + 3) + lastColumnIndex, - false + -2, + {topRow + 1, firstColumnIndex}, + {topRow + 3, lastColumnIndex} }, - { + Test{ L"can move _start forward without affecting _end", { - topRow + 1, firstColumnIndex, - topRow + 5, lastColumnIndex, - bottomRow, - firstColumnIndex, - lastColumnIndex, - UiaTextRange::MovementIncrement::Forward, + {topRow + 1, firstColumnIndex}, + {topRow + 5, lastColumnIndex}, UiaTextRange::MovementDirection::Forward }, 2, - 2, TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow + 3) + firstColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow + 5) + lastColumnIndex, - false + 2, + {topRow + 3, firstColumnIndex}, + {topRow + 5, lastColumnIndex} }, - { + Test{ L"can move _start backward without affecting _end", { - topRow + 2, firstColumnIndex, - topRow + 5, lastColumnIndex, - topRow, - lastColumnIndex, - firstColumnIndex, - UiaTextRange::MovementIncrement::Backward, + {topRow + 2, firstColumnIndex}, + {topRow + 5, lastColumnIndex}, UiaTextRange::MovementDirection::Backward }, -1, - -1, TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow + 1) + firstColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow + 5) + lastColumnIndex, - false + -1, + {topRow + 1, firstColumnIndex}, + {topRow + 5, lastColumnIndex} }, - { + Test{ L"can move _start backwards when it's already on the top row", { - topRow, lastColumnIndex, - topRow, lastColumnIndex, - topRow, - lastColumnIndex, - firstColumnIndex, - UiaTextRange::MovementIncrement::Backward, + {topRow, lastColumnIndex}, + {topRow, lastColumnIndex}, UiaTextRange::MovementDirection::Backward }, -1, - -1, TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + lastColumnIndex, - false + -1, + {topRow, firstColumnIndex}, + {topRow, lastColumnIndex} }, - { + Test{ L"can't move _start backwards when it's at the start of the document already", { - topRow, firstColumnIndex, - topRow, lastColumnIndex, - topRow, - lastColumnIndex, - firstColumnIndex, - UiaTextRange::MovementIncrement::Backward, + {topRow, firstColumnIndex}, + {topRow, lastColumnIndex}, UiaTextRange::MovementDirection::Backward }, -1, - 0, TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + lastColumnIndex, - false + 0, + {topRow, firstColumnIndex}, + {topRow, lastColumnIndex} }, - { + Test{ L"can move _end forwards when it's on the bottom row", { - topRow, firstColumnIndex, - bottomRow, lastColumnIndex - 3, - bottomRow, - lastColumnIndex, - firstColumnIndex, - UiaTextRange::MovementIncrement::Forward, + {topRow, firstColumnIndex}, + {bottomRow, lastColumnIndex - 3}, UiaTextRange::MovementDirection::Forward }, 1, - 1, TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex, - false + 1, + {topRow, firstColumnIndex}, + {bottomRow, lastColumnIndex} }, - { + Test{ L"can't move _end forwards when it's at the end of the document already", { - topRow, firstColumnIndex, - bottomRow, lastColumnIndex, - bottomRow, - lastColumnIndex, - firstColumnIndex, - UiaTextRange::MovementIncrement::Forward, + {topRow, firstColumnIndex}, + {bottomRow, lastColumnIndex}, UiaTextRange::MovementDirection::Forward }, 1, - 0, TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex, - false + 0, + {topRow, firstColumnIndex}, + {bottomRow, lastColumnIndex} }, - { + Test{ L"moving _start forward when it's already on the bottom row creates a degenerate range at the document end", { - bottomRow, firstColumnIndex, - bottomRow, lastColumnIndex, - bottomRow, - firstColumnIndex, - lastColumnIndex, - UiaTextRange::MovementIncrement::Forward, + {bottomRow, firstColumnIndex}, + {bottomRow, lastColumnIndex}, UiaTextRange::MovementDirection::Forward }, 1, - 1, TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex, - true + 1, + {bottomRow, lastColumnIndex}, + {bottomRow, lastColumnIndex} }, - { + Test{ L"moving _end backward when it's already on the top row creates a degenerate range at the document start", { - topRow, firstColumnIndex + 4, - topRow, lastColumnIndex - 5, - topRow, - lastColumnIndex, - firstColumnIndex, - UiaTextRange::MovementIncrement::Backward, + {topRow, firstColumnIndex + 4}, + {topRow, lastColumnIndex - 5}, UiaTextRange::MovementDirection::Backward }, -1, - -1, TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex, - true + -1, + {topRow, firstColumnIndex}, + {topRow, firstColumnIndex} } }; // clang-format on - for (auto data : testData) + Microsoft::WRL::ComPtr utr; + for (auto test : testData) { - Log::Comment(std::get<0>(data).c_str()); - std::tuple result; + Log::Comment(test.comment.c_str()); int amountMoved; - result = UiaTextRange::_moveEndpointByUnitLine(_pUiaData, - std::get<2>(data), - std::get<4>(data), - std::get<1>(data), - &amountMoved); - - VERIFY_ARE_EQUAL(std::get<3>(data), amountMoved); - VERIFY_ARE_EQUAL(std::get<5>(data), std::get<0>(result)); - VERIFY_ARE_EQUAL(std::get<6>(data), std::get<1>(result)); - VERIFY_ARE_EQUAL(std::get<7>(data), std::get<2>(result)); + + const auto moveState = test.moveState; + Microsoft::WRL::MakeAndInitialize(&utr, _pUiaData, &_dummyProvider, moveState.start, moveState.end); + utr->MoveEndpointByUnit(test.endpoint, TextUnit::TextUnit_Line, test.moveAmt, &amountMoved); + + VERIFY_ARE_EQUAL(test.expected.moveAmt, amountMoved); + VERIFY_ARE_EQUAL(test.expected.start, utr->_start); + VERIFY_ARE_EQUAL(test.expected.end, utr->_end); } } TEST_METHOD(CanMoveEndpointByUnitDocument) { - const Column firstColumnIndex = 0; - const Column lastColumnIndex = _pScreenInfo->GetBufferSize().Width() - 1; - const ScreenInfoRow topRow = 0; - const ScreenInfoRow bottomRow = _pTextBuffer->TotalRowCount() - 1; + const SHORT firstColumnIndex = 0; + const SHORT lastColumnIndex = _pScreenInfo->GetBufferSize().Width() - 1; + const SHORT topRow = 0; + const SHORT bottomRow = gsl::narrow(_pTextBuffer->TotalRowCount() - 1); + + struct ExpectedResult + { + int moveAmt; + COORD start; + COORD end; + }; + + struct Test + { + std::wstring comment; + UiaTextRange::MoveState moveState; + int moveAmt; + TextPatternRangeEndpoint endpoint; + ExpectedResult expected; + }; // clang-format off - const std::vector> testData = + const std::vector testData = { - { + Test{ L"can move _end forward to end of document without affecting _start", { - topRow, firstColumnIndex + 4, - topRow, firstColumnIndex + 4, - bottomRow, - firstColumnIndex, - lastColumnIndex, - UiaTextRange::MovementIncrement::Forward, + {topRow, firstColumnIndex + 4}, + {topRow, firstColumnIndex + 4}, UiaTextRange::MovementDirection::Forward }, 1, - 1, TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex + 4, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex, - false + { + 1, + {topRow, firstColumnIndex + 4}, + {bottomRow, lastColumnIndex} + } }, - { + Test{ L"can move _start backward to end of document without affect _end", { - topRow, firstColumnIndex + 4, - topRow, firstColumnIndex + 4, - topRow, - lastColumnIndex, - firstColumnIndex, - UiaTextRange::MovementIncrement::Backward, + {topRow, firstColumnIndex + 4}, + {topRow, firstColumnIndex + 4}, UiaTextRange::MovementDirection::Backward }, -1, - -1, TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + 4, - false + { + -1, + {topRow, firstColumnIndex}, + {topRow, 4} + } }, - { + Test{ L"can't move _end forward when it's already at the end of the document", { - topRow + 3, firstColumnIndex + 2, - bottomRow, lastColumnIndex, - bottomRow, - firstColumnIndex, - lastColumnIndex, - UiaTextRange::MovementIncrement::Forward, + {topRow + 3, firstColumnIndex + 2}, + {bottomRow, lastColumnIndex}, UiaTextRange::MovementDirection::Forward }, 1, - 0, TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow + 3) + firstColumnIndex + 2, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex, - false + { + 0, + {topRow + 3, firstColumnIndex + 2}, + {bottomRow, lastColumnIndex} + } }, - { + Test{ L"can't move _start backward when it's already at the start of the document", { - topRow, firstColumnIndex, - topRow + 5, firstColumnIndex + 6, - topRow, - lastColumnIndex, - firstColumnIndex, - UiaTextRange::MovementIncrement::Backward, + {topRow, firstColumnIndex}, + {topRow + 5, firstColumnIndex + 6}, UiaTextRange::MovementDirection::Backward }, -1, - 0, TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow + 5) + 6, - false + { + 0, + {topRow, firstColumnIndex}, + {topRow + 5, 6} + } }, - { + Test{ L"moving _end backward creates degenerate range at start of document", { - topRow + 5, firstColumnIndex + 2, - topRow + 5, firstColumnIndex + 6, - topRow, - lastColumnIndex, - firstColumnIndex, - UiaTextRange::MovementIncrement::Backward, + {topRow + 5, firstColumnIndex + 2}, + {topRow + 5, firstColumnIndex + 6}, UiaTextRange::MovementDirection::Backward }, -1, - -1, TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex, - true + { + -1, + {topRow, firstColumnIndex}, + {topRow, firstColumnIndex} + } }, - { + Test{ L"moving _start forward creates degenerate range at end of document", { - topRow + 5, firstColumnIndex + 2, - topRow + 5, firstColumnIndex + 6, - bottomRow, - firstColumnIndex, - lastColumnIndex, - UiaTextRange::MovementIncrement::Forward, + {topRow + 5, firstColumnIndex + 2}, + {topRow + 5, firstColumnIndex + 6}, UiaTextRange::MovementDirection::Forward }, 1, - 1, TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex, - true + { + 1, + {bottomRow, lastColumnIndex}, + {bottomRow, lastColumnIndex} + } } }; // clang-format on - for (auto data : testData) + Microsoft::WRL::ComPtr utr; + for (auto test : testData) { - Log::Comment(std::get<0>(data).c_str()); - std::tuple result; + Log::Comment(test.comment.c_str()); int amountMoved; - result = UiaTextRange::_moveEndpointByUnitDocument(_pUiaData, - std::get<2>(data), - std::get<4>(data), - std::get<1>(data), - &amountMoved); - - VERIFY_ARE_EQUAL(std::get<3>(data), amountMoved); - VERIFY_ARE_EQUAL(std::get<5>(data), std::get<0>(result)); - VERIFY_ARE_EQUAL(std::get<6>(data), std::get<1>(result)); - VERIFY_ARE_EQUAL(std::get<7>(data), std::get<2>(result)); + + const auto moveState = test.moveState; + Microsoft::WRL::MakeAndInitialize(&utr, _pUiaData, &_dummyProvider, moveState.start, moveState.end); + utr->MoveEndpointByUnit(test.endpoint, TextUnit::TextUnit_Document, test.moveAmt, &amountMoved); + + VERIFY_ARE_EQUAL(test.expected.moveAmt, amountMoved); + VERIFY_ARE_EQUAL(test.expected.start, utr->_start); + VERIFY_ARE_EQUAL(test.expected.end, utr->_end); } } }; diff --git a/src/types/UiaTextRangeBase.cpp b/src/types/UiaTextRangeBase.cpp index b2d06ae5bb3..91d1ccd76a2 100644 --- a/src/types/UiaTextRangeBase.cpp +++ b/src/types/UiaTextRangeBase.cpp @@ -158,6 +158,7 @@ HRESULT UiaTextRangeBase::RuntimeClassInitialize(const UiaTextRangeBase& a) noex _start = a._start; _end = a._end; _pData = a._pData; + _wordDelimiters = a._wordDelimiters.c_str(); _id = id; ++id; @@ -202,14 +203,14 @@ bool UiaTextRangeBase::SetEndpoint(TextPatternRangeEndpoint endpoint, const COOR { case TextPatternRangeEndpoint::TextPatternRangeEndpoint_End: _end = val; - if (bufferSize.CompareInBounds(_end, _start) < 0) + if (bufferSize.CompareInBounds(_end, _start, true) < 0) { _start = _end; } break; case TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start: _start = val; - if (bufferSize.CompareInBounds(_start, _end) > 0) + if (bufferSize.CompareInBounds(_start, _end, true) > 0) { _end = _start; } @@ -287,7 +288,7 @@ IFACEMETHODIMP UiaTextRangeBase::CompareEndpoints(_In_ TextPatternRangeEndpoint const auto mine = GetEndpoint(endpoint); // compare them - *pRetVal = _pData->GetTextBuffer().GetSize().CompareInBounds(mine, other); + *pRetVal = _pData->GetTextBuffer().GetSize().CompareInBounds(mine, other, true); // TODO GitHub #1914: Re-attach Tracing to UIA Tree // tracing @@ -326,9 +327,9 @@ IFACEMETHODIMP UiaTextRangeBase::ExpandToEnclosingUnit(_In_ TextUnit unit) else if (unit <= TextUnit::TextUnit_Word) { // expand to word - // TODO CARLOS: verify that the end is properly exclusive here _start = buffer.GetWordStart(_start, _wordDelimiters, true); _end = buffer.GetWordEnd(_start, _wordDelimiters, true); + bufferSize.IncrementInBounds(_end); } else if (unit <= TextUnit::TextUnit_Line) { @@ -341,12 +342,11 @@ IFACEMETHODIMP UiaTextRangeBase::ExpandToEnclosingUnit(_In_ TextUnit unit) { // expand to document _start = bufferSize.Origin(); - _end = { bufferSize.RightInclusive(), bufferSize.BottomInclusive() }; + _end = { bufferSize.RightExclusive(), bufferSize.BottomInclusive() }; } // TODO GitHub #1914: Re-attach Tracing to UIA Tree //Tracing::s_TraceUia(this, ApiCall::ExpandToEnclosingUnit, &apiMsg); - FAIL_FAST_IF(bufferSize.CompareInBounds(_start, _end) > 0); return S_OK; } CATCH_RETURN(); @@ -410,7 +410,7 @@ IFACEMETHODIMP UiaTextRangeBase::GetBoundingRectangles(_Outptr_result_maybenull_ // startAnchor: the earliest COORD we will get a bounding rect for auto startAnchor = GetEndpoint(TextPatternRangeEndpoint_Start); - if (bufferSize.CompareInBounds(startAnchor, viewportOrigin) < 0) + if (bufferSize.CompareInBounds(startAnchor, viewportOrigin, true) < 0) { // earliest we can be is the origin startAnchor = viewportOrigin; @@ -418,7 +418,7 @@ IFACEMETHODIMP UiaTextRangeBase::GetBoundingRectangles(_Outptr_result_maybenull_ // endAnchor: the latest COORD we will get a bounding rect for auto endAnchor = GetEndpoint(TextPatternRangeEndpoint_End); - if (bufferSize.CompareInBounds(endAnchor, viewportEnd) > 0) + if (bufferSize.CompareInBounds(endAnchor, viewportEnd, true) > 0) { // latest we can be is the viewport end endAnchor = viewportEnd; @@ -426,19 +426,26 @@ IFACEMETHODIMP UiaTextRangeBase::GetBoundingRectangles(_Outptr_result_maybenull_ else { // this is exclusive, let's be inclusive so we don't have to think about it anymore for bounding rects - bufferSize.DecrementInBounds(endAnchor); + if (endAnchor.Y == viewportEnd.Y + 1) + { + endAnchor.Y = viewportEnd.Y; + } + else + { + bufferSize.DecrementInBounds(endAnchor); + } } - // Remember, start cannot be past end - FAIL_FAST_IF(bufferSize.CompareInBounds(_start, _end) > 0); if (IsDegenerate()) { _getBoundingRect(_start, _start, coords); } - else if (bufferSize.CompareInBounds(_start, viewportEnd) > 0 || bufferSize.CompareInBounds(_end, viewportOrigin) < 0) + else if (bufferSize.CompareInBounds(_start, viewportEnd, true) > 0 || bufferSize.CompareInBounds(_end, viewportOrigin, true) < 0) { - // start is past the viewport end, or end is past the viewport origin... - // so draw nothing + // Remember, start cannot be past end, so + // if start is past the viewport end, + // or end is past the viewport origin + // draw nothing } else { @@ -997,6 +1004,9 @@ void UiaTextRangeBase::_moveEndpointByUnitCharacter(_In_ const int moveCount, { *pAmountMoved = 0; + // _end can be the out of bounds + bool allowBottomExclusive = (endpoint == TextPatternRangeEndpoint_End); + if (moveCount == 0) { return; @@ -1004,36 +1014,30 @@ void UiaTextRangeBase::_moveEndpointByUnitCharacter(_In_ const int moveCount, const auto bufferSize = _pData->GetTextBuffer().GetSize(); auto target = GetEndpoint(endpoint); - while (abs(*pAmountMoved) < abs(moveCount) && bufferSize.IsInBounds(target)) + bool fSuccess = true; + while (abs(*pAmountMoved) < abs(moveCount) && fSuccess) { switch (moveState.Direction) { case MovementDirection::Forward: - bufferSize.IncrementInBounds(target); - *pAmountMoved += 1; + fSuccess = bufferSize.IncrementInBounds(target, allowBottomExclusive); + if (fSuccess) + { + *pAmountMoved += 1; + } break; case MovementDirection::Backward: - bufferSize.DecrementInBounds(target); - *pAmountMoved -= 1; + fSuccess = bufferSize.DecrementInBounds(target, allowBottomExclusive); + if (fSuccess) + { + *pAmountMoved -= 1; + } break; default: break; } } - // if we went out of bounds, be at the corner of the buffer - if (!bufferSize.IsInBounds(target)) - { - if (moveState.Direction == MovementDirection::Forward) - { - target = bufferSize.EndInclusive(); - } - else - { - target = bufferSize.Origin(); - } - } - SetEndpoint(endpoint, target); } @@ -1071,12 +1075,12 @@ void UiaTextRangeBase::_moveEndpointByUnitWord(_In_ const int moveCount, case MovementDirection::Forward: // Get the end of the word (including the delimiter run), // but move one more forward to be on the next word's character - buffer.GetWordEnd(target, wordDelimiters, /*includeDelimiterRun*/ true); + target = buffer.GetWordEnd(target, wordDelimiters, /*includeDelimiterRun*/ true); bufferSize.IncrementInBounds(target); *pAmountMoved += 1; break; case MovementDirection::Backward: - buffer.GetWordStart(target, wordDelimiters, /*includeCharacterRun*/ true); + target = buffer.GetWordStart(target, wordDelimiters, /*includeCharacterRun*/ true); // NOTE: no need for bufferSize.DecrementInBounds(target) here, because we're already on the word *pAmountMoved -= 1; break; @@ -1085,19 +1089,6 @@ void UiaTextRangeBase::_moveEndpointByUnitWord(_In_ const int moveCount, } } - // if we went out of bounds, be at the corner of the buffer - if (!bufferSize.IsInBounds(target)) - { - if (moveState.Direction == MovementDirection::Forward) - { - target = bufferSize.EndInclusive(); - } - else - { - target = bufferSize.Origin(); - } - } - SetEndpoint(endpoint, target); } @@ -1119,6 +1110,9 @@ void UiaTextRangeBase::_moveEndpointByUnitLine(_In_ const int moveCount, { *pAmountMoved = 0; + // _end can be the out of bounds + bool allowBottomExclusive = (endpoint == TextPatternRangeEndpoint_End); + if (moveCount == 0) { return; @@ -1126,20 +1120,29 @@ void UiaTextRangeBase::_moveEndpointByUnitLine(_In_ const int moveCount, const auto bufferSize = _pData->GetTextBuffer().GetSize(); auto target = GetEndpoint(endpoint); - while (abs(*pAmountMoved) < abs(moveCount) && bufferSize.IsInBounds(target)) + bool fSuccess = true; + while (abs(*pAmountMoved) < abs(moveCount) && fSuccess) { switch (moveState.Direction) { case MovementDirection::Forward: { - target.Y += 1; - *pAmountMoved += 1; + target.X = bufferSize.RightInclusive(); + fSuccess = bufferSize.IncrementInBounds(target, allowBottomExclusive); + if (fSuccess) + { + *pAmountMoved += 1; + } break; } case MovementDirection::Backward: { - target.Y -= 1; - *pAmountMoved -= 1; + target.X = bufferSize.Left(); + fSuccess = bufferSize.DecrementInBounds(target, allowBottomExclusive); + if (fSuccess) + { + *pAmountMoved -= 1; + } break; } default: @@ -1147,19 +1150,6 @@ void UiaTextRangeBase::_moveEndpointByUnitLine(_In_ const int moveCount, } } - // if we went out of bounds, be at the corner of the buffer - if (!bufferSize.IsInBounds(target)) - { - if (moveState.Direction == MovementDirection::Forward) - { - target = bufferSize.EndInclusive(); - } - else - { - target = bufferSize.Origin(); - } - } - SetEndpoint(endpoint, target); } @@ -1187,20 +1177,35 @@ void UiaTextRangeBase::_moveEndpointByUnitDocument(_In_ const int moveCount, } const auto bufferSize = _pData->GetTextBuffer().GetSize(); + auto target = GetEndpoint(endpoint); switch (moveState.Direction) { case MovementDirection::Forward: { - const COORD documentEnd = { bufferSize.RightInclusive(), bufferSize.BottomInclusive() }; - SetEndpoint(endpoint, documentEnd); - *pAmountMoved += 1; + const auto documentEnd = bufferSize.EndInclusive(); + if (target == documentEnd) + { + return; + } + else + { + SetEndpoint(endpoint, documentEnd); + *pAmountMoved += 1; + } break; } case MovementDirection::Backward: { - const COORD documentBegin = bufferSize.Origin(); - SetEndpoint(endpoint, documentBegin); - *pAmountMoved -= 1; + const auto documentBegin = bufferSize.Origin(); + if (target == documentBegin) + { + return; + } + else + { + SetEndpoint(endpoint, documentBegin); + *pAmountMoved -= 1; + } break; } default: diff --git a/src/types/inc/viewport.hpp b/src/types/inc/viewport.hpp index ac5437a6fc9..dca848f5c21 100644 --- a/src/types/inc/viewport.hpp +++ b/src/types/inc/viewport.hpp @@ -57,17 +57,17 @@ namespace Microsoft::Console::Types COORD Dimensions() const noexcept; bool IsInBounds(const Viewport& other) const noexcept; - bool IsInBounds(const COORD& pos) const noexcept; + bool IsInBounds(const COORD& pos, bool allowBottomExclusive = false) const noexcept; void Clamp(COORD& pos) const; Viewport Clamp(const Viewport& other) const noexcept; bool MoveInBounds(const ptrdiff_t move, COORD& pos) const noexcept; - bool IncrementInBounds(COORD& pos) const noexcept; + bool IncrementInBounds(COORD& pos, bool allowBottomExclusive = false) const noexcept; bool IncrementInBoundsCircular(COORD& pos) const noexcept; - bool DecrementInBounds(COORD& pos) const noexcept; + bool DecrementInBounds(COORD& pos, bool allowBottomExclusive = false) const noexcept; bool DecrementInBoundsCircular(COORD& pos) const noexcept; - int CompareInBounds(const COORD& first, const COORD& second) const noexcept; + int CompareInBounds(const COORD& first, const COORD& second, bool allowBottomExclusive = false) const noexcept; enum class XWalk { @@ -87,8 +87,8 @@ namespace Microsoft::Console::Types const YWalk y; }; - bool WalkInBounds(COORD& pos, const WalkDir dir) const noexcept; - bool WalkInBoundsCircular(COORD& pos, const WalkDir dir) const noexcept; + bool WalkInBounds(COORD& pos, const WalkDir dir, bool allowBottomExclusive = false) const noexcept; + bool WalkInBoundsCircular(COORD& pos, const WalkDir dir, bool allowBottomExclusive = false) const noexcept; COORD GetWalkOrigin(const WalkDir dir) const noexcept; static WalkDir DetermineWalkDirection(const Viewport& source, const Viewport& target) noexcept; diff --git a/src/types/viewport.cpp b/src/types/viewport.cpp index 84528461e67..318a1037729 100644 --- a/src/types/viewport.cpp +++ b/src/types/viewport.cpp @@ -138,14 +138,14 @@ COORD Viewport::Origin() const noexcept } // Method Description: -// - Get a coord representing the end of this viewport in inclusive terms. +// - For Accessibility, get a coord representing the end of this viewport in inclusive terms. // Arguments: // - // Return Value: // - the coordinates of this viewport's end. COORD Viewport::EndInclusive() const noexcept { - return { RightInclusive(), BottomInclusive() }; + return { RightExclusive(), BottomInclusive() }; } // Method Description: @@ -177,10 +177,16 @@ bool Viewport::IsInBounds(const Viewport& other) const noexcept // - Determines if the given coordinate position lies within this viewport. // Arguments: // - pos - Coordinate position +// - allowBottomExclusive - if true, allow the X position to be on the BottomExclusive boundary // Return Value: // - True if it lies inside the viewport. False otherwise. -bool Viewport::IsInBounds(const COORD& pos) const noexcept +bool Viewport::IsInBounds(const COORD& pos, bool allowBottomExclusive) const noexcept { + if (allowBottomExclusive && pos.X == Left() && pos.Y == BottomExclusive()) + { + return true; + } + return pos.X >= Left() && pos.X < RightExclusive() && pos.Y >= Top() && pos.Y < BottomExclusive(); } @@ -266,11 +272,12 @@ bool Viewport::MoveInBounds(const ptrdiff_t move, COORD& pos) const noexcept // - Increments the given coordinate within the bounds of this viewport. // Arguments: // - pos - Coordinate position that will be incremented, if it can be. +// - allowBottomExclusive - if true, allow the X position to be on the BottomExclusive boundary // Return Value: // - True if it could be incremented. False if it would move outside. -bool Viewport::IncrementInBounds(COORD& pos) const noexcept +bool Viewport::IncrementInBounds(COORD& pos, bool allowBottomExclusive) const noexcept { - return WalkInBounds(pos, { XWalk::LeftToRight, YWalk::TopToBottom }); + return WalkInBounds(pos, { XWalk::LeftToRight, YWalk::TopToBottom }, allowBottomExclusive); } // Method Description: @@ -290,11 +297,12 @@ bool Viewport::IncrementInBoundsCircular(COORD& pos) const noexcept // - Decrements the given coordinate within the bounds of this viewport. // Arguments: // - pos - Coordinate position that will be incremented, if it can be. +// - allowBottomExclusive - if true, allow the X position to be on the BottomExclusive boundary // Return Value: // - True if it could be incremented. False if it would move outside. -bool Viewport::DecrementInBounds(COORD& pos) const noexcept +bool Viewport::DecrementInBounds(COORD& pos, bool allowBottomExclusive) const noexcept { - return WalkInBounds(pos, { XWalk::RightToLeft, YWalk::BottomToTop }); + return WalkInBounds(pos, { XWalk::RightToLeft, YWalk::BottomToTop }, allowBottomExclusive); } // Method Description: @@ -315,6 +323,7 @@ bool Viewport::DecrementInBoundsCircular(COORD& pos) const noexcept // Arguments: // - first- The first coordinate position // - second - The second coordinate position +// - allowBottomExclusive - if true, allow the COORD's Y value to be BottomExclusive when X = Left // Return Value: // - Negative if First is to the left of the Second. // - 0 if First and Second are the same coordinate. @@ -322,11 +331,11 @@ bool Viewport::DecrementInBoundsCircular(COORD& pos) const noexcept // - This is so you can do s_CompareCoords(first, second) <= 0 for "first is left or the same as second". // (the < looks like a left arrow :D) // - The magnitude of the result is the distance between the two coordinates when typing characters into the buffer (left to right, top to bottom) -int Viewport::CompareInBounds(const COORD& first, const COORD& second) const noexcept +int Viewport::CompareInBounds(const COORD& first, const COORD& second, bool allowBottomExclusive) const noexcept { // Assert that our coordinates are within the expected boundaries - FAIL_FAST_IF(!IsInBounds(first)); - FAIL_FAST_IF(!IsInBounds(second)); + FAIL_FAST_IF(!IsInBounds(first, allowBottomExclusive)); + FAIL_FAST_IF(!IsInBounds(second, allowBottomExclusive)); // First set the distance vertically // If first is on row 4 and second is on row 6, first will be -2 rows behind second * an 80 character row would be -160. @@ -353,12 +362,13 @@ int Viewport::CompareInBounds(const COORD& first, const COORD& second) const noe // Arguments: // - pos - Coordinate position that will be adjusted, if it can be. // - dir - Walking direction specifying which direction to go when reaching the end of a row/column +// - allowBottomExclusive - if true, allow the X position to be on the BottomExclusive boundary // Return Value: // - True if it could be adjusted as specified and remain in bounds. False if it would move outside. -bool Viewport::WalkInBounds(COORD& pos, const WalkDir dir) const noexcept +bool Viewport::WalkInBounds(COORD& pos, const WalkDir dir, bool allowBottomExclusive) const noexcept { auto copy = pos; - if (WalkInBoundsCircular(copy, dir)) + if (WalkInBoundsCircular(copy, dir, allowBottomExclusive)) { pos = copy; return true; @@ -376,14 +386,15 @@ bool Viewport::WalkInBounds(COORD& pos, const WalkDir dir) const noexcept // Arguments: // - pos - Coordinate position that will be adjusted. // - dir - Walking direction specifying which direction to go when reaching the end of a row/column +// - allowBottomExclusive - if true, allow the X position to be on the BottomExclusive boundary // Return Value: // - True if it could be adjusted inside the viewport. // - False if it rolled over from the final corner back to the initial corner // for the specified walk direction. -bool Viewport::WalkInBoundsCircular(COORD& pos, const WalkDir dir) const noexcept +bool Viewport::WalkInBoundsCircular(COORD& pos, const WalkDir dir, bool allowBottomExclusive) const noexcept { // Assert that the position given fits inside this viewport. - FAIL_FAST_IF(!IsInBounds(pos)); + FAIL_FAST_IF(!IsInBounds(pos, allowBottomExclusive)); if (dir.x == XWalk::LeftToRight) { @@ -394,7 +405,11 @@ bool Viewport::WalkInBoundsCircular(COORD& pos, const WalkDir dir) const noexcep if (dir.y == YWalk::TopToBottom) { pos.Y++; - if (pos.Y > BottomInclusive()) + if (allowBottomExclusive && pos.Y == BottomExclusive()) + { + return true; + } + else if (pos.Y > BottomInclusive()) { pos.Y = Top(); return false; From 3017aaae9a8303dd531518f726d71e4d5831f005 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Fri, 20 Dec 2019 12:50:18 -0800 Subject: [PATCH 05/25] Testing!!!! --- src/host/ut_host/TextBufferTests.cpp | 8 +- .../UiaTextRangeTests.cpp | 952 +++++++----------- src/types/UiaTextRangeBase.cpp | 261 +++-- src/types/UiaTextRangeBase.hpp | 41 +- src/types/viewport.cpp | 9 +- 5 files changed, 553 insertions(+), 718 deletions(-) diff --git a/src/host/ut_host/TextBufferTests.cpp b/src/host/ut_host/TextBufferTests.cpp index 75c2cf50285..21f14ef7eb4 100644 --- a/src/host/ut_host/TextBufferTests.cpp +++ b/src/host/ut_host/TextBufferTests.cpp @@ -2085,8 +2085,8 @@ void TextBufferTests::GetWordStart() COORD result; // Test with accessibilityMode = false - //result = _buffer->GetWordStart(test.startPos, delimiters, false); - //VERIFY_ARE_EQUAL(test.expected.accessibilityModeDisabled, result); + result = _buffer->GetWordStart(test.startPos, delimiters, false); + VERIFY_ARE_EQUAL(test.expected.accessibilityModeDisabled, result); // Test with accessibilityMode = true result = _buffer->GetWordStart(test.startPos, delimiters, true); @@ -2160,8 +2160,8 @@ void TextBufferTests::GetWordEnd() COORD result; // Test with accessibilityMode = false - //result = _buffer->GetWordEnd(test.startPos, delimiters, false); - //VERIFY_ARE_EQUAL(test.expected.accessibilityModeDisabled, result); + result = _buffer->GetWordEnd(test.startPos, delimiters, false); + VERIFY_ARE_EQUAL(test.expected.accessibilityModeDisabled, result); // Test with accessibilityMode = true result = _buffer->GetWordEnd(test.startPos, delimiters, true); diff --git a/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp b/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp index f95f779fdc3..fa7bd149c3a 100644 --- a/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp +++ b/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp @@ -133,34 +133,33 @@ class UiaTextRangeTests TEST_METHOD(DegenerateRangesDetected) { const auto bufferSize = _pTextBuffer->GetSize(); + const auto origin = bufferSize.Origin(); // make a degenerate range and verify that it reports degenerate Microsoft::WRL::ComPtr degenerate; Microsoft::WRL::MakeAndInitialize(°enerate, _pUiaData, &_dummyProvider, - bufferSize.Origin(), - bufferSize.Origin()); + origin, + origin); VERIFY_IS_TRUE(degenerate->IsDegenerate()); VERIFY_ARE_EQUAL(degenerate->_start, degenerate->_end); // make a non-degenerate range and verify that it reports as such - const COORD end = { bufferSize.Origin().X + 1, bufferSize.Origin().Y }; + const COORD end = { origin.X + 1, origin.Y }; Microsoft::WRL::ComPtr notDegenerate; Microsoft::WRL::MakeAndInitialize(¬Degenerate, _pUiaData, &_dummyProvider, - bufferSize.Origin(), + origin, end); VERIFY_IS_FALSE(notDegenerate->IsDegenerate()); - VERIFY_ARE_NOT_EQUAL(degenerate->_start, degenerate->_end); + VERIFY_ARE_NOT_EQUAL(notDegenerate->_start, notDegenerate->_end); } TEST_METHOD(CanMoveByCharacter) { - const SHORT firstColumnIndex = 0; const SHORT lastColumnIndex = _pScreenInfo->GetBufferSize().Width() - 1; - const SHORT topRow = 0; const SHORT bottomRow = gsl::narrow(_pTextBuffer->TotalRowCount() - 1); struct ExpectedResult @@ -173,7 +172,8 @@ class UiaTextRangeTests struct Test { std::wstring comment; - UiaTextRange::MoveState moveState; + COORD start; + COORD end; int moveAmt; ExpectedResult expected; }; @@ -183,104 +183,85 @@ class UiaTextRangeTests { Test{ L"can't move backward from (0, 0)", - { - { 0, 0 }, - { 0, 2 }, - UiaTextRange::MovementDirection::Backward - }, + { 0, 0 }, + { 2, 0 }, -1, { 0, {0,0}, - {0,0} + {2,0} } }, Test{ L"can move backward within a row", - { - { 0, 1 }, - { 0, 2 }, - UiaTextRange::MovementDirection::Backward - }, + { 1, 0 }, + { 2, 0 }, -1, { -1, - {0,0}, - {0,0} + {0, 0}, + {1, 0} } }, Test{ L"can move forward in a row", - { - { 2, 1 }, - { 4, 5 }, - UiaTextRange::MovementDirection::Forward - }, + { 1, 2 }, + { 5, 4 }, 5, { 5, - {2,6}, - {2,6} + {6,2}, + {7,2} } }, Test{ L"can't move past the last column in the last row", - { - { bottomRow, lastColumnIndex }, - { bottomRow, lastColumnIndex }, - UiaTextRange::MovementDirection::Forward - }, + { lastColumnIndex, bottomRow }, + { lastColumnIndex, bottomRow }, 5, { 0, - {bottomRow, lastColumnIndex}, - {bottomRow, lastColumnIndex} + { lastColumnIndex, bottomRow }, + { lastColumnIndex, bottomRow }, } }, Test{ L"can move to a new row when necessary when moving forward", - { - { topRow, lastColumnIndex }, - { topRow, lastColumnIndex }, - UiaTextRange::MovementDirection::Forward - }, + { lastColumnIndex, 0 }, + { lastColumnIndex, 0 }, 5, { 5, - {topRow + 1, 4}, - {topRow + 1, 4} + {4 , 0 + 1}, + {5 , 0 + 1} } }, Test{ L"can move to a new row when necessary when moving backward", - { - { topRow + 1, firstColumnIndex }, - { topRow + 1, lastColumnIndex }, - UiaTextRange::MovementDirection::Backward - }, + { 0, 0 + 1 }, + { lastColumnIndex, 0 + 1 }, -5, { -5, - {topRow, lastColumnIndex - 4}, - {topRow, lastColumnIndex - 4} + {lastColumnIndex - 4, 0}, + {lastColumnIndex - 3, 0} } } }; // clang-format on Microsoft::WRL::ComPtr utr; - for (const auto &test : testData) + for (const auto& test : testData) { Log::Comment(test.comment.data()); int amountMoved; - const auto moveState = test.moveState; - Microsoft::WRL::MakeAndInitialize(&utr, _pUiaData, &_dummyProvider, moveState.start, moveState.end); + Microsoft::WRL::MakeAndInitialize(&utr, _pUiaData, &_dummyProvider, test.start, test.end); utr->Move(TextUnit::TextUnit_Character, test.moveAmt, &amountMoved); VERIFY_ARE_EQUAL(test.expected.moveAmt, amountMoved); @@ -289,276 +270,190 @@ class UiaTextRangeTests } } - //TEST_METHOD(CanMoveByWord_EmptyBuffer) - //{ - // const Column firstColumnIndex = 0; - // const Column lastColumnIndex = _pScreenInfo->GetBufferSize().Width() - 1; - // const ScreenInfoRow topRow = 0; - // const ScreenInfoRow bottomRow = _pTextBuffer->TotalRowCount() - 1; + TEST_METHOD(CanMoveByWord_EmptyBuffer) + { + const SHORT lastColumnIndex = _pScreenInfo->GetBufferSize().Width() - 1; + const SHORT bottomRow = gsl::narrow(_pTextBuffer->TotalRowCount() - 1); - // // clang-format off - // const std::vector> testData = - // { - // { - // L"can't move backward from (0, 0)", - // { - // 0, 0, - // 0, 2, - // topRow, - // lastColumnIndex, - // firstColumnIndex, - // UiaTextRange::MovementIncrement::Backward, - // UiaTextRange::MovementDirection::Backward - // }, - // -1, - // 0, - // 0u, - // lastColumnIndex - // }, + struct ExpectedResult + { + int moveAmt; + COORD start; + COORD end; + }; - // { - // L"can move backward within a row", - // { - // 0, 1, - // 0, 2, - // topRow, - // lastColumnIndex, - // firstColumnIndex, - // UiaTextRange::MovementIncrement::Backward, - // UiaTextRange::MovementDirection::Backward - // }, - // -1, - // -1, - // 0u, - // lastColumnIndex - // }, + struct Test + { + std::wstring comment; + COORD start; + COORD end; + int moveAmt; + ExpectedResult expected; + }; - // { - // L"can move forward in a row", - // { - // 2, 1, - // 4, 5, - // bottomRow, - // firstColumnIndex, - // lastColumnIndex, - // UiaTextRange::MovementIncrement::Forward, - // UiaTextRange::MovementDirection::Forward - // }, - // 5, - // 5, - // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, 8), - // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, 8) + lastColumnIndex - // }, + // clang-format off + const std::vector testData + { + Test{ + L"can't move backward from (0, 0)", + { 0, 0 }, + { 0, 2 }, + -1, + { + 0, + {0, 0}, + {0, 2} + } + }, - // { - // L"can't move past the last column in the last row", - // { - // bottomRow, lastColumnIndex, - // bottomRow, lastColumnIndex, - // bottomRow, - // firstColumnIndex, - // lastColumnIndex, - // UiaTextRange::MovementIncrement::Forward, - // UiaTextRange::MovementDirection::Forward - // }, - // 5, - // 0, - // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow), - // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex - // }, + Test{ + L"can move backward within a row", + { 1, 0 }, + { 2, 0 }, + -1, + { + 0, + { 1, 0 }, + { 2, 0 }, + } + }, - // { - // L"can move to a new row when necessary when moving forward", - // { - // topRow, lastColumnIndex, - // topRow, lastColumnIndex, - // bottomRow, - // firstColumnIndex, - // lastColumnIndex, - // UiaTextRange::MovementIncrement::Forward, - // UiaTextRange::MovementDirection::Forward - // }, - // 5, - // 5, - // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow + 5), - // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow + 5) + lastColumnIndex - // }, + Test{ + L"can move forward in a row", + { 1, 0 }, + { 10, 0 }, + 5, + { + 5, + {0, 5}, + {0, 6} + } + }, - // { - // L"can move to a new row when necessary when moving backward", - // { - // topRow + 1, firstColumnIndex, - // topRow + 1, lastColumnIndex, - // topRow, - // lastColumnIndex, - // firstColumnIndex, - // UiaTextRange::MovementIncrement::Backward, - // UiaTextRange::MovementDirection::Backward - // }, - // -5, - // -2, - // 0u, - // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + lastColumnIndex - // } - // }; - // // clang-format on + Test{ + L"can't move past the last column in the last row", + { lastColumnIndex, bottomRow }, + { lastColumnIndex, bottomRow }, + 5, + { + 0, + { lastColumnIndex, bottomRow }, + { lastColumnIndex, bottomRow }, + } + }, - // for (auto data : testData) - // { - // Log::Comment(std::get<0>(data).c_str()); - // int amountMoved; - // std::pair newEndpoints = UiaTextRange::_moveByWord(_pUiaData, - // std::get<2>(data), - // std::get<1>(data), - // L"", - // &amountMoved); + Test{ + L"can move to a new row when necessary when moving forward", + { lastColumnIndex, 0 }, + { lastColumnIndex, 0 }, + 5, + { + 5, + { 0, 5 }, + { 0, 6} + } + }, - // VERIFY_ARE_EQUAL(std::get<3>(data), amountMoved); - // VERIFY_ARE_EQUAL(std::get<4>(data), newEndpoints.first); - // VERIFY_ARE_EQUAL(std::get<5>(data), newEndpoints.second); - // } - //} + Test{ + L"can move to a new row when necessary when moving backward", + { 0, 1 }, + { 5, 1 }, + -5, + { + 0, + { 0, 1 }, + { 5, 1 } + } + } + }; + // clang-format on - //TEST_METHOD(CanMoveByWord_NonEmptyBuffer) - //{ - // const Column firstColumnIndex = 0; - // const Column lastColumnIndex = _pScreenInfo->GetBufferSize().Width() - 1; - // const ScreenInfoRow topRow = 0; - // const ScreenInfoRow bottomRow = _pTextBuffer->TotalRowCount() - 1; + Microsoft::WRL::ComPtr utr; + for (const auto& test : testData) + { + Log::Comment(test.comment.data()); + int amountMoved; - // const std::wstring_view text[] = { - // L"word1 word2 word3", - // L"word4 word5 word6" - // }; + Microsoft::WRL::MakeAndInitialize(&utr, _pUiaData, &_dummyProvider, test.start, test.end); + utr->Move(TextUnit::TextUnit_Word, test.moveAmt, &amountMoved); - // for (auto i = 0; i < 2; i++) - // { - // _pTextBuffer->WriteLine(text[i], { 0, gsl::narrow(i) }); - // } + VERIFY_ARE_EQUAL(test.expected.moveAmt, amountMoved); + VERIFY_ARE_EQUAL(test.expected.start, utr->_start); + VERIFY_ARE_EQUAL(test.expected.end, utr->_end); + } + } - // // clang-format off - // const std::vector> testData = - // { - // { - // L"move backwards on the word by (0,0)", - // { - // 0, 1, - // 0, 2, - // topRow, - // lastColumnIndex, - // firstColumnIndex, - // UiaTextRange::MovementIncrement::Backward, - // UiaTextRange::MovementDirection::Backward - // }, - // -1, - // -1, - // 0u, - // 6 - // }, + TEST_METHOD(CanMoveByWord_NonEmptyBuffer) + { + const SHORT lastColumnIndex = _pScreenInfo->GetBufferSize().Width() - 1; + const SHORT bottomRow = gsl::narrow(_pTextBuffer->TotalRowCount() - 1); - // { - // L"get next word while on first word", - // { - // 0, 0, - // 0, 0, - // bottomRow, - // firstColumnIndex, - // lastColumnIndex, - // UiaTextRange::MovementIncrement::Forward, - // UiaTextRange::MovementDirection::Forward - // }, - // 1, - // 1, - // 0, - // 6 - // }, + const std::wstring_view text[] = { + L"word other", + L" more words" + }; - // { - // L"get next word twice while on first word", - // { - // 0, 0, - // 0, 0, - // bottomRow, - // firstColumnIndex, - // lastColumnIndex, - // UiaTextRange::MovementIncrement::Forward, - // UiaTextRange::MovementDirection::Forward - // }, - // 2, - // 2, - // 7, - // 14 - // }, + for (auto i = 0; i < 2; i++) + { + _pTextBuffer->WriteLine(text[i], { 0, gsl::narrow(i) }); + } - // { - // L"move forward to next row with word", - // { - // topRow, lastColumnIndex, - // topRow, lastColumnIndex, - // bottomRow, - // firstColumnIndex, - // lastColumnIndex, - // UiaTextRange::MovementIncrement::Forward, - // UiaTextRange::MovementDirection::Forward - // }, - // 1, - // 1, - // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow + 1), - // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow + 1) + 6 - // }, + struct ExpectedResult + { + int moveAmt; + COORD start; + COORD end; + }; - // { - // L"move backwards to previous row with word", - // { - // topRow + 1, firstColumnIndex, - // topRow + 1, lastColumnIndex, - // topRow, - // lastColumnIndex, - // firstColumnIndex, - // UiaTextRange::MovementIncrement::Backward, - // UiaTextRange::MovementDirection::Backward - // }, - // -1, - // -1, - // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + 15, - // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + lastColumnIndex - // } - // }; - // // clang-format on + struct Test + { + std::wstring comment; + COORD start; + COORD end; + int moveAmt; + ExpectedResult expected; + }; - // for (auto data : testData) - // { - // Log::Comment(std::get<0>(data).c_str()); - // int amountMoved; - // std::pair newEndpoints = UiaTextRange::_moveByWord(_pUiaData, - // std::get<2>(data), - // std::get<1>(data), - // L"", - // &amountMoved); + // clang-format off + const std::vector testData + { + Test{ + L"move backwards on the word by (0,0)", + { 1, 0 }, + { 2, 0 }, + -1, + { + -1, + { 0, 0 }, + { 6, 0 } + } + } - // VERIFY_ARE_EQUAL(std::get<3>(data), amountMoved); - // VERIFY_ARE_EQUAL(std::get<4>(data), newEndpoints.first); - // VERIFY_ARE_EQUAL(std::get<5>(data), newEndpoints.second); - // } - //} + //L"get next word while on first word", + //L"get next word twice while on first word", + //L"move forward to next row with word", + //L"move backwards to previous row with word", + }; + // clang-format on + + Microsoft::WRL::ComPtr utr; + for (const auto& test : testData) + { + Log::Comment(test.comment.data()); + int amountMoved; + + Microsoft::WRL::MakeAndInitialize(&utr, _pUiaData, &_dummyProvider, test.start, test.end); + utr->Move(TextUnit::TextUnit_Word, test.moveAmt, &amountMoved); + + VERIFY_ARE_EQUAL(test.expected.moveAmt, amountMoved); + VERIFY_ARE_EQUAL(test.expected.start, utr->_start); + VERIFY_ARE_EQUAL(test.expected.end, utr->_end); + } + } TEST_METHOD(CanMoveByLine) { - const SHORT firstColumnIndex = 0; const SHORT lastColumnIndex = _pScreenInfo->GetBufferSize().Width() - 1; - const SHORT topRow = 0; const SHORT bottomRow = gsl::narrow(_pTextBuffer->TotalRowCount() - 1); struct ExpectedResult @@ -571,7 +466,8 @@ class UiaTextRangeTests struct Test { std::wstring comment; - UiaTextRange::MoveState moveState; + COORD start; + COORD end; int moveAmt; ExpectedResult expected; }; @@ -581,104 +477,85 @@ class UiaTextRangeTests { Test{ L"can't move backward from top row", - { - {topRow, firstColumnIndex}, - {topRow, lastColumnIndex}, - UiaTextRange::MovementDirection::Backward - }, + {0, 0}, + {0, lastColumnIndex}, -4, { 0, - {topRow, firstColumnIndex}, - {topRow, lastColumnIndex} + {0, 0}, + {0, lastColumnIndex} } }, Test{ L"can move forward from top row", - { - {topRow, firstColumnIndex}, - {topRow, lastColumnIndex}, - UiaTextRange::MovementDirection::Forward - }, + {0, 0}, + {0, lastColumnIndex}, 4, { 4, - {topRow + 4, firstColumnIndex}, - {topRow + 4, lastColumnIndex} + {0, 4}, + {0, 5} } }, Test{ L"can't move forward from bottom row", - { - {bottomRow, firstColumnIndex}, - {bottomRow, lastColumnIndex}, - UiaTextRange::MovementDirection::Forward - }, + {0, bottomRow}, + {lastColumnIndex, bottomRow}, 3, { 0, - {bottomRow, firstColumnIndex}, - {bottomRow, lastColumnIndex} + {0, bottomRow}, + {lastColumnIndex, bottomRow}, } }, Test{ L"can move backward from bottom row", - { - {bottomRow, firstColumnIndex}, - {bottomRow, lastColumnIndex}, - UiaTextRange::MovementDirection::Backward - }, + {0, bottomRow}, + {lastColumnIndex, bottomRow}, -3, { -3, - {bottomRow - 3, firstColumnIndex}, - {bottomRow - 3, lastColumnIndex} + {0, bottomRow - 3}, + {0, bottomRow - 2} } }, Test{ L"can't move backward when part of the top row is in the range", - { - {topRow, firstColumnIndex + 5}, - {topRow, lastColumnIndex}, - UiaTextRange::MovementDirection::Backward - }, + {5, 0}, + {lastColumnIndex, 0}, -1, { 0, - {topRow, firstColumnIndex + 5}, - {topRow, lastColumnIndex} + {5, 0}, + {lastColumnIndex, 0}, } }, Test{ L"can't move forward when part of the bottom row is in the range", - { - {bottomRow, firstColumnIndex}, - {bottomRow, firstColumnIndex}, - UiaTextRange::MovementDirection::Forward - }, + {0, bottomRow}, + {0, bottomRow}, 1, { 0, - {bottomRow, firstColumnIndex}, - {bottomRow, firstColumnIndex} + {0, bottomRow}, + {0, bottomRow} } } }; // clang-format on Microsoft::WRL::ComPtr utr; - for (auto test : testData) + for (const auto& test : testData) { - Log::Comment(test.comment.c_str()); + Log::Comment(test.comment.data()); int amountMoved; - const auto moveState = test.moveState; - Microsoft::WRL::MakeAndInitialize(&utr, _pUiaData, &_dummyProvider, moveState.start, moveState.end); + Microsoft::WRL::MakeAndInitialize(&utr, _pUiaData, &_dummyProvider, test.start, test.end); utr->Move(TextUnit::TextUnit_Line, test.moveAmt, &amountMoved); VERIFY_ARE_EQUAL(test.expected.moveAmt, amountMoved); @@ -689,9 +566,7 @@ class UiaTextRangeTests TEST_METHOD(CanMoveEndpointByUnitCharacter) { - const SHORT firstColumnIndex = 0; const SHORT lastColumnIndex = _pScreenInfo->GetBufferSize().Width() - 1; - const SHORT topRow = 0; const SHORT bottomRow = static_cast(_pTextBuffer->TotalRowCount() - 1); struct ExpectedResult @@ -704,7 +579,8 @@ class UiaTextRangeTests struct Test { std::wstring comment; - UiaTextRange::MoveState moveState; + COORD start; + COORD end; int moveAmt; TextPatternRangeEndpoint endpoint; ExpectedResult expected; @@ -715,142 +591,120 @@ class UiaTextRangeTests { Test{ L"can't move _start past the beginning of the document when _start is positioned at the beginning", - { - {topRow, firstColumnIndex}, - {topRow, lastColumnIndex}, - UiaTextRange::MovementDirection::Backward - }, + {0, 0}, + {lastColumnIndex, 0}, -1, TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, { 0, - {topRow, firstColumnIndex}, - {topRow, lastColumnIndex} + {0, 0}, + {lastColumnIndex, 0} } }, Test{ L"can partially move _start to the begining of the document when it is closer than the move count requested", - { - {topRow, firstColumnIndex + 3}, - {topRow, lastColumnIndex}, - UiaTextRange::MovementDirection::Backward - }, + {3, 0}, + {lastColumnIndex, 0}, -5, TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, { -3, - {topRow, firstColumnIndex}, - {topRow, lastColumnIndex} + {0, 0}, + {lastColumnIndex, 0} } }, Test{ L"can't move _end past the begining of the document", - { - {topRow, firstColumnIndex}, - {topRow, firstColumnIndex + 4}, - UiaTextRange::MovementDirection::Backward - }, + {0, 0}, + {4, 0}, -5, TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, { -4, - {topRow, firstColumnIndex}, - {topRow, firstColumnIndex} + {0, 0}, + {0, 0} } }, Test{ L"_start follows _end when passed during movement", - { - {topRow, firstColumnIndex + 5}, - {topRow, firstColumnIndex + 10}, - UiaTextRange::MovementDirection::Backward - }, + {5, 0}, + {10, 0}, -7, TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, { -7, - {topRow, 3}, - {topRow, 3} + {3, 0}, + {3, 0} } }, Test{ L"can't move _end past the beginning of the document when _end is positioned at the end", - { - {bottomRow, firstColumnIndex}, - {bottomRow, lastColumnIndex}, - UiaTextRange::MovementDirection::Forward - }, + {0, bottomRow}, + {0, bottomRow+1}, 1, TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, { 0, - {bottomRow, firstColumnIndex}, - {bottomRow, lastColumnIndex} + {0, bottomRow}, + {0, bottomRow+1}, } }, Test{ L"can partially move _end to the end of the document when it is closer than the move count requested", - { - {topRow, firstColumnIndex}, - {bottomRow, lastColumnIndex - 3}, - UiaTextRange::MovementDirection::Forward - }, + {0, 0}, + {lastColumnIndex - 3, bottomRow}, 5, TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, { - 3, - {topRow, firstColumnIndex}, - {bottomRow, lastColumnIndex} + 4, + {0, 0}, + {0, bottomRow+1}, } }, Test{ L"can't move _start past the end of the document", - { - {bottomRow, lastColumnIndex - 4}, - {bottomRow, lastColumnIndex}, - UiaTextRange::MovementDirection::Forward - }, + {lastColumnIndex - 4, bottomRow}, + {0, bottomRow+1}, 5, TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, { 4, - {bottomRow, lastColumnIndex}, - {bottomRow, lastColumnIndex} + {lastColumnIndex, bottomRow}, + {0, bottomRow+1}, } }, Test{ L"_end follows _start when passed during movement", - { - {topRow, firstColumnIndex + 5}, - {topRow, firstColumnIndex + 10}, - UiaTextRange::MovementDirection::Forward - }, + {5, 0}, + {10, 0}, 7, TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, { 7, - {topRow, 12}, - {topRow, 12} + {12, 0}, + {12, 0} } }, }; // clang-format on Microsoft::WRL::ComPtr utr; - for (auto test : testData) + for (const auto& test : testData) { - Log::Comment(test.comment.c_str()); + Log::Comment(test.comment.data()); int amountMoved; - const auto moveState = test.moveState; - Microsoft::WRL::MakeAndInitialize(&utr, _pUiaData, &_dummyProvider, moveState.start, moveState.end); + //if (test.comment == L"can't move _end past the beginning of the document when _end is positioned at the end") + // DebugBreak(); + + Microsoft::WRL::MakeAndInitialize(&utr, _pUiaData, &_dummyProvider, test.start, test.end); utr->MoveEndpointByUnit(test.endpoint, TextUnit::TextUnit_Character, test.moveAmt, &amountMoved); VERIFY_ARE_EQUAL(test.expected.moveAmt, amountMoved); @@ -861,10 +715,8 @@ class UiaTextRangeTests //TEST_METHOD(CanMoveEndpointByUnitWord) //{ - // const Column firstColumnIndex = 0; - // const Column lastColumnIndex = _pScreenInfo->GetBufferSize().Width() - 1; - // const ScreenInfoRow topRow = 0; - // const ScreenInfoRow bottomRow = _pTextBuffer->TotalRowCount() - 1; + // const SHORT lastColumnIndex = _pScreenInfo->GetBufferSize().Width() - 1; + // const SHORT bottomRow = gsl::narrow(_pTextBuffer->TotalRowCount() - 1); // const std::wstring_view text[] = { // L"word1 word2 word3", @@ -890,86 +742,86 @@ class UiaTextRangeTests // { // L"can't move _start past the beginning of the document when _start is positioned at the beginning", // { - // topRow, firstColumnIndex, - // topRow, lastColumnIndex, - // topRow, + // 0, 0, + // 0, lastColumnIndex, + // 0, // lastColumnIndex, - // firstColumnIndex, + // 0, // UiaTextRange::MovementIncrement::Backward, // UiaTextRange::MovementDirection::Backward // }, // -1, // 0, // TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, - // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex, - // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + lastColumnIndex, + // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, 0) + 0, + // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, 0) + lastColumnIndex, // false // }, // { // L"can partially move _start to the begining of the document when it is closer than the move count requested", // { - // topRow, firstColumnIndex + 15, - // topRow, lastColumnIndex, - // topRow, + // 0, 15, + // 0, lastColumnIndex, + // 0, // lastColumnIndex, - // firstColumnIndex, + // 0, // UiaTextRange::MovementIncrement::Backward, // UiaTextRange::MovementDirection::Backward // }, // -5, // -2, // TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, - // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex, - // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + lastColumnIndex, + // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, 0) + 0, + // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, 0) + lastColumnIndex, // false // }, // { // L"can't move _end past the begining of the document", // { - // topRow, firstColumnIndex, - // topRow, firstColumnIndex + 2, - // topRow, + // 0, 0, + // 0, 2, + // 0, // lastColumnIndex, - // firstColumnIndex, + // 0, // UiaTextRange::MovementIncrement::Backward, // UiaTextRange::MovementDirection::Backward // }, // -2, // -1, // TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, - // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex, - // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex, + // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, 0) + 0, + // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, 0) + 0, // false // }, // { // L"_start follows _end when passed during movement", // { - // topRow + 1, firstColumnIndex + 2, - // topRow + 1, firstColumnIndex + 10, - // topRow, + // 0 + 1, 0 + 2, + // 0 + 1, 0 + 10, + // 0, // lastColumnIndex, - // firstColumnIndex, + // 0, // UiaTextRange::MovementIncrement::Backward, // UiaTextRange::MovementDirection::Backward // }, // -4, // -4, // TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, - // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + 6, - // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + 6, + // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, 0) + 6, + // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, 0) + 6, // true // }, // { // L"can't move _end past the beginning of the document when _end is positioned at the end", // { - // bottomRow, firstColumnIndex, + // bottomRow, 0, // bottomRow, lastColumnIndex, // bottomRow, - // firstColumnIndex, + // 0, // lastColumnIndex, // UiaTextRange::MovementIncrement::Forward, // UiaTextRange::MovementDirection::Forward @@ -977,7 +829,7 @@ class UiaTextRangeTests // 1, // 0, // TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, - // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + firstColumnIndex, + // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + 0, // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex, // false // }, @@ -985,10 +837,10 @@ class UiaTextRangeTests // { // L"can partially move _end to the end of the document when it is closer than the move count requested", // { - // topRow, firstColumnIndex, + // 0, 0, // bottomRow, lastColumnIndex - 3, // bottomRow, - // firstColumnIndex, + // 0, // lastColumnIndex, // UiaTextRange::MovementIncrement::Forward, // UiaTextRange::MovementDirection::Forward @@ -996,7 +848,7 @@ class UiaTextRangeTests // 5, // 1, // TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, - // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex, + // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, 0) + 0, // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex, // false // }, @@ -1007,7 +859,7 @@ class UiaTextRangeTests // bottomRow, lastColumnIndex - 4, // bottomRow, lastColumnIndex, // bottomRow, - // firstColumnIndex, + // 0, // lastColumnIndex, // UiaTextRange::MovementIncrement::Forward, // UiaTextRange::MovementDirection::Forward @@ -1023,19 +875,19 @@ class UiaTextRangeTests // { // L"_end follows _start when passed during movement", // { - // topRow, firstColumnIndex, - // topRow, firstColumnIndex + 3, - // topRow, + // 0, 0, + // 0, 0 + 3, + // 0, // lastColumnIndex, - // firstColumnIndex, + // 0, // UiaTextRange::MovementIncrement::Forward, // UiaTextRange::MovementDirection::Forward // }, // 2, // 2, // TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, - // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow)+15, - // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow)+15, + // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, 0)+15, + // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, 0)+15, // true // }, // }; @@ -1062,9 +914,7 @@ class UiaTextRangeTests TEST_METHOD(CanMoveEndpointByUnitLine) { - const SHORT firstColumnIndex = 0; const SHORT lastColumnIndex = _pScreenInfo->GetBufferSize().Width() - 1; - const SHORT topRow = 0; const SHORT bottomRow = gsl::narrow(_pTextBuffer->TotalRowCount() - 1); struct ExpectedResult @@ -1077,7 +927,8 @@ class UiaTextRangeTests struct Test { std::wstring comment; - UiaTextRange::MoveState moveState; + COORD start; + COORD end; int moveAmt; TextPatternRangeEndpoint endpoint; ExpectedResult expected; @@ -1088,154 +939,123 @@ class UiaTextRangeTests { Test{ L"can move _end forward without affecting _start", - { - {topRow, firstColumnIndex}, - {topRow, lastColumnIndex}, - UiaTextRange::MovementDirection::Forward - }, + {0, 0}, + {lastColumnIndex, 0}, 1, TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, 1, - {topRow, firstColumnIndex}, - {topRow, lastColumnIndex} + {0, 0}, + {0, 1} }, Test{ L"can move _end backward without affecting _start", - { - {topRow + 1, firstColumnIndex}, - {topRow + 5, lastColumnIndex}, - UiaTextRange::MovementDirection::Backward - }, + {0, 1}, + {lastColumnIndex, 5}, -2, TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, -2, - {topRow + 1, firstColumnIndex}, - {topRow + 3, lastColumnIndex} + {0, 1}, + {0, 4} }, Test{ L"can move _start forward without affecting _end", - { - {topRow + 1, firstColumnIndex}, - {topRow + 5, lastColumnIndex}, - UiaTextRange::MovementDirection::Forward - }, + {0, 1}, + {lastColumnIndex, 5}, 2, TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, 2, - {topRow + 3, firstColumnIndex}, - {topRow + 5, lastColumnIndex} + {0, 3}, + {lastColumnIndex, 5} }, Test{ L"can move _start backward without affecting _end", - { - {topRow + 2, firstColumnIndex}, - {topRow + 5, lastColumnIndex}, - UiaTextRange::MovementDirection::Backward - }, + {0, 2}, + {lastColumnIndex, 5}, -1, TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, -1, - {topRow + 1, firstColumnIndex}, - {topRow + 5, lastColumnIndex} + {0, 1}, + {lastColumnIndex, 5} }, Test{ L"can move _start backwards when it's already on the top row", - { - {topRow, lastColumnIndex}, - {topRow, lastColumnIndex}, - UiaTextRange::MovementDirection::Backward - }, + {lastColumnIndex, 0}, + {lastColumnIndex, 0}, -1, TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, -1, - {topRow, firstColumnIndex}, - {topRow, lastColumnIndex} + {0, 0}, + {lastColumnIndex, 0}, }, Test{ L"can't move _start backwards when it's at the start of the document already", - { - {topRow, firstColumnIndex}, - {topRow, lastColumnIndex}, - UiaTextRange::MovementDirection::Backward - }, + {0, 0}, + {lastColumnIndex, 0}, -1, TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, 0, - {topRow, firstColumnIndex}, - {topRow, lastColumnIndex} + {0, 0}, + {lastColumnIndex, 0} }, Test{ L"can move _end forwards when it's on the bottom row", - { - {topRow, firstColumnIndex}, - {bottomRow, lastColumnIndex - 3}, - UiaTextRange::MovementDirection::Forward - }, + {0, 0}, + {lastColumnIndex - 3, bottomRow}, 1, TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, 1, - {topRow, firstColumnIndex}, - {bottomRow, lastColumnIndex} + {0, 0}, + {0, bottomRow+1} }, Test{ L"can't move _end forwards when it's at the end of the document already", - { - {topRow, firstColumnIndex}, - {bottomRow, lastColumnIndex}, - UiaTextRange::MovementDirection::Forward - }, + {0, 0}, + {0, bottomRow+1}, 1, TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, 0, - {topRow, firstColumnIndex}, - {bottomRow, lastColumnIndex} + {0, 0}, + {0, bottomRow+1} }, Test{ L"moving _start forward when it's already on the bottom row creates a degenerate range at the document end", - { - {bottomRow, firstColumnIndex}, - {bottomRow, lastColumnIndex}, - UiaTextRange::MovementDirection::Forward - }, + {0, bottomRow}, + {lastColumnIndex, bottomRow}, 1, TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, 1, - {bottomRow, lastColumnIndex}, - {bottomRow, lastColumnIndex} + {0, bottomRow+1}, + {0, bottomRow+1} }, Test{ L"moving _end backward when it's already on the top row creates a degenerate range at the document start", - { - {topRow, firstColumnIndex + 4}, - {topRow, lastColumnIndex - 5}, - UiaTextRange::MovementDirection::Backward - }, + {4, 0}, + {lastColumnIndex - 5, 0}, -1, TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, -1, - {topRow, firstColumnIndex}, - {topRow, firstColumnIndex} + {0, 0}, + {0, 0} } }; // clang-format on Microsoft::WRL::ComPtr utr; - for (auto test : testData) + for (const auto& test : testData) { - Log::Comment(test.comment.c_str()); + Log::Comment(test.comment.data()); int amountMoved; - const auto moveState = test.moveState; - Microsoft::WRL::MakeAndInitialize(&utr, _pUiaData, &_dummyProvider, moveState.start, moveState.end); + Microsoft::WRL::MakeAndInitialize(&utr, _pUiaData, &_dummyProvider, test.start, test.end); utr->MoveEndpointByUnit(test.endpoint, TextUnit::TextUnit_Line, test.moveAmt, &amountMoved); VERIFY_ARE_EQUAL(test.expected.moveAmt, amountMoved); @@ -1246,9 +1066,7 @@ class UiaTextRangeTests TEST_METHOD(CanMoveEndpointByUnitDocument) { - const SHORT firstColumnIndex = 0; const SHORT lastColumnIndex = _pScreenInfo->GetBufferSize().Width() - 1; - const SHORT topRow = 0; const SHORT bottomRow = gsl::narrow(_pTextBuffer->TotalRowCount() - 1); struct ExpectedResult @@ -1261,7 +1079,8 @@ class UiaTextRangeTests struct Test { std::wstring comment; - UiaTextRange::MoveState moveState; + COORD start; + COORD end; int moveAmt; TextPatternRangeEndpoint endpoint; ExpectedResult expected; @@ -1272,97 +1091,79 @@ class UiaTextRangeTests { Test{ L"can move _end forward to end of document without affecting _start", - { - {topRow, firstColumnIndex + 4}, - {topRow, firstColumnIndex + 4}, - UiaTextRange::MovementDirection::Forward - }, + {0, 4}, + {0, 4}, 1, TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, { 1, - {topRow, firstColumnIndex + 4}, - {bottomRow, lastColumnIndex} + {0, 4}, + {0, bottomRow+1} } }, Test{ L"can move _start backward to end of document without affect _end", - { - {topRow, firstColumnIndex + 4}, - {topRow, firstColumnIndex + 4}, - UiaTextRange::MovementDirection::Backward - }, + {0, 4}, + {0, 4}, -1, TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, { -1, - {topRow, firstColumnIndex}, - {topRow, 4} + {0, 0}, + {0, 4} } }, Test{ L"can't move _end forward when it's already at the end of the document", - { - {topRow + 3, firstColumnIndex + 2}, - {bottomRow, lastColumnIndex}, - UiaTextRange::MovementDirection::Forward - }, + {3, 2}, + {0, bottomRow+1}, 1, TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, { 0, - {topRow + 3, firstColumnIndex + 2}, - {bottomRow, lastColumnIndex} + {3, 2}, + {0, bottomRow+1} } }, Test{ L"can't move _start backward when it's already at the start of the document", - { - {topRow, firstColumnIndex}, - {topRow + 5, firstColumnIndex + 6}, - UiaTextRange::MovementDirection::Backward - }, + {0, 0}, + {5, 6}, -1, TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, { 0, - {topRow, firstColumnIndex}, - {topRow + 5, 6} + {0, 0}, + {5, 6} } }, Test{ L"moving _end backward creates degenerate range at start of document", - { - {topRow + 5, firstColumnIndex + 2}, - {topRow + 5, firstColumnIndex + 6}, - UiaTextRange::MovementDirection::Backward - }, + {5, 2}, + {5, 6}, -1, TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, { -1, - {topRow, firstColumnIndex}, - {topRow, firstColumnIndex} + {0, 0}, + {0, 0} } }, Test{ L"moving _start forward creates degenerate range at end of document", - { - {topRow + 5, firstColumnIndex + 2}, - {topRow + 5, firstColumnIndex + 6}, - UiaTextRange::MovementDirection::Forward - }, + {5, 2}, + {5, 6}, 1, TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, { 1, - {bottomRow, lastColumnIndex}, - {bottomRow, lastColumnIndex} + {0, bottomRow+1}, + {0, bottomRow+1} } } }; @@ -1374,8 +1175,7 @@ class UiaTextRangeTests Log::Comment(test.comment.c_str()); int amountMoved; - const auto moveState = test.moveState; - Microsoft::WRL::MakeAndInitialize(&utr, _pUiaData, &_dummyProvider, moveState.start, moveState.end); + Microsoft::WRL::MakeAndInitialize(&utr, _pUiaData, &_dummyProvider, test.start, test.end); utr->MoveEndpointByUnit(test.endpoint, TextUnit::TextUnit_Document, test.moveAmt, &amountMoved); VERIFY_ARE_EQUAL(test.expected.moveAmt, amountMoved); diff --git a/src/types/UiaTextRangeBase.cpp b/src/types/UiaTextRangeBase.cpp index 91d1ccd76a2..b75d0d73bc1 100644 --- a/src/types/UiaTextRangeBase.cpp +++ b/src/types/UiaTextRangeBase.cpp @@ -15,23 +15,6 @@ using namespace Microsoft::Console::Types::UiaTextRangeBaseTracing; IdType UiaTextRangeBase::id = 1; -UiaTextRangeBase::MoveState::MoveState(const UiaTextRangeBase& range, - const MovementDirection direction) : - start{ range.GetEndpoint(TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start) }, - end{ range.GetEndpoint(TextPatternRangeEndpoint::TextPatternRangeEndpoint_End) }, - Direction{ direction } -{ -} - -UiaTextRangeBase::MoveState::MoveState(const COORD start, - const COORD end, - const MovementDirection direction) noexcept : - start{ start }, - end{ end }, - Direction{ direction } -{ -} - #if _DEBUG #include void UiaTextRangeBase::_outputObjectState() @@ -318,18 +301,18 @@ IFACEMETHODIMP UiaTextRangeBase::ExpandToEnclosingUnit(_In_ TextUnit unit) { const auto& buffer = _pData->GetTextBuffer(); const auto bufferSize = buffer.GetSize(); + const auto bufferEnd = bufferSize.EndInclusive(); if (unit == TextUnit::TextUnit_Character) { _end = _start; - bufferSize.IncrementInBounds(_end); + bufferSize.IncrementInBounds(_end, true); } else if (unit <= TextUnit::TextUnit_Word) { // expand to word _start = buffer.GetWordStart(_start, _wordDelimiters, true); _end = buffer.GetWordEnd(_start, _wordDelimiters, true); - bufferSize.IncrementInBounds(_end); } else if (unit <= TextUnit::TextUnit_Line) { @@ -342,7 +325,7 @@ IFACEMETHODIMP UiaTextRangeBase::ExpandToEnclosingUnit(_In_ TextUnit unit) { // expand to document _start = bufferSize.Origin(); - _end = { bufferSize.RightExclusive(), bufferSize.BottomInclusive() }; + _end = bufferSize.EndInclusive(); } // TODO GitHub #1914: Re-attach Tracing to UIA Tree @@ -423,18 +406,9 @@ IFACEMETHODIMP UiaTextRangeBase::GetBoundingRectangles(_Outptr_result_maybenull_ // latest we can be is the viewport end endAnchor = viewportEnd; } - else - { - // this is exclusive, let's be inclusive so we don't have to think about it anymore for bounding rects - if (endAnchor.Y == viewportEnd.Y + 1) - { - endAnchor.Y = viewportEnd.Y; - } - else - { - bufferSize.DecrementInBounds(endAnchor); - } - } + + // _end is exclusive, let's be inclusive so we don't have to think about it anymore for bounding rects + bufferSize.DecrementInBounds(endAnchor, true); if (IsDegenerate()) { @@ -558,7 +532,8 @@ IFACEMETHODIMP UiaTextRangeBase::GetText(_In_ int maxLength, _Out_ BSTR* pRetVal { startIndex = _start.X; } - else if (currentScreenInfoRow == static_cast(_end.Y)) + + if (currentScreenInfoRow == static_cast(_end.Y)) { // prevent the end from going past the last non-whitespace char in the row endIndex = std::min(gsl::narrow_cast(_end.X) - 1, rowRight); @@ -627,25 +602,48 @@ IFACEMETHODIMP UiaTextRangeBase::Move(_In_ TextUnit unit, _outputObjectState(); std::wstringstream ss; + ss << L" unit: " << unit; ss << L" count: " << count; std::wstring data = ss.str(); OutputDebugString(data.c_str()); OutputDebugString(L"\n"); #endif - // Source: https://docs.microsoft.com/en-us/windows/win32/winauto/uiauto-implementingtextandtextrange - // When the ITextRangeProvider::Move method is called, the provider - // normalizes the text range by the specified text unit, - // using the same normalization logic as the ExpandToEnclosingUnit method. - ExpandToEnclosingUnit(unit); - - // We can abstract this movement by moving _start... - MoveEndpointByUnit(TextPatternRangeEndpoint_Start, unit, count, pRetVal); + _pData->LockConsole(); + auto Unlock = wil::scope_exit([&]() noexcept { + _pData->UnlockConsole(); + }); - // then just expand again to get our _end - ExpandToEnclosingUnit(unit); + // We can abstract this movement by moving _start, but disallowing moving to the end of the buffer + constexpr auto endpoint = TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start; + constexpr auto preventBufferEnd = true; + try + { + if (unit == TextUnit::TextUnit_Character) + { + _moveEndpointByUnitCharacter(count, endpoint, pRetVal, preventBufferEnd); + } + else if (unit <= TextUnit::TextUnit_Word) + { + _moveEndpointByUnitWord(count, endpoint, _wordDelimiters, pRetVal, preventBufferEnd); + } + else if (unit <= TextUnit::TextUnit_Line) + { + _moveEndpointByUnitLine(count, endpoint, pRetVal, preventBufferEnd); + } + else if (unit <= TextUnit::TextUnit_Document) + { + _moveEndpointByUnitDocument(count, endpoint, pRetVal, preventBufferEnd); + } + } + CATCH_RETURN(); - // NOTE: a range can't be degenerate after both endpoints have been moved. + // If we actually moved... + if (*pRetVal != 0) + { + // then just expand to get our _end + ExpandToEnclosingUnit(unit); + } // TODO GitHub #1914: Re-attach Tracing to UIA Tree // tracing @@ -660,11 +658,6 @@ IFACEMETHODIMP UiaTextRangeBase::MoveEndpointByUnit(_In_ TextPatternRangeEndpoin _In_ int count, _Out_ int* pRetVal) { - _pData->LockConsole(); - auto Unlock = wil::scope_exit([&]() noexcept { - _pData->UnlockConsole(); - }); - RETURN_HR_IF(E_INVALIDARG, pRetVal == nullptr); *pRetVal = 0; if (count == 0) @@ -690,26 +683,28 @@ IFACEMETHODIMP UiaTextRangeBase::MoveEndpointByUnit(_In_ TextPatternRangeEndpoin OutputDebugString(L"\n"); #endif - const MovementDirection moveDirection = (count > 0) ? MovementDirection::Forward : MovementDirection::Backward; - const MoveState moveState{ *this, moveDirection }; + _pData->LockConsole(); + auto Unlock = wil::scope_exit([&]() noexcept { + _pData->UnlockConsole(); + }); try { if (unit == TextUnit::TextUnit_Character) { - _moveEndpointByUnitCharacter(count, endpoint, moveState, pRetVal); + _moveEndpointByUnitCharacter(count, endpoint, pRetVal); } else if (unit <= TextUnit::TextUnit_Word) { - _moveEndpointByUnitWord(count, endpoint, moveState, _wordDelimiters, pRetVal); + _moveEndpointByUnitWord(count, endpoint, _wordDelimiters, pRetVal); } else if (unit <= TextUnit::TextUnit_Line) { - _moveEndpointByUnitLine(count, endpoint, moveState, pRetVal); + _moveEndpointByUnitLine(count, endpoint, pRetVal); } else if (unit <= TextUnit::TextUnit_Document) { - _moveEndpointByUnitDocument(count, endpoint, moveState, pRetVal); + _moveEndpointByUnitDocument(count, endpoint, pRetVal); } } CATCH_RETURN(); @@ -756,8 +751,6 @@ IFACEMETHODIMP UiaTextRangeBase::MoveEndpointByRange(_In_ TextPatternRangeEndpoi OutputDebugString(L"\n"); #endif - // TODO CARLOS: Double check if we get the corner cases right here. Not too sure about it. - // TODO CARLOS: Make sure we get the degenerate ranges correct here too switch (endpoint) { case TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start: @@ -992,32 +985,33 @@ void UiaTextRangeBase::_getBoundingRect(_In_ const COORD startAnchor, _In_ const // Arguments: // - moveCount - the number of times to move // - endpoint - the endpoint to move -// - moveState - values indicating the state of the console for the -// move operation // - pAmountMoved - the number of times that the return values are "moved" +// - preventBufferEnd - when enabled, prevent endpoint from being at the end of the buffer +// This is used for general movement, where you are not allowed to +// create a degenerate range // Return Value: // - void UiaTextRangeBase::_moveEndpointByUnitCharacter(_In_ const int moveCount, _In_ const TextPatternRangeEndpoint endpoint, - _In_ const MoveState moveState, - _Out_ gsl::not_null const pAmountMoved) + _Out_ gsl::not_null const pAmountMoved, + _In_ const bool preventBufferEnd) { *pAmountMoved = 0; - // _end can be the out of bounds - bool allowBottomExclusive = (endpoint == TextPatternRangeEndpoint_End); - if (moveCount == 0) { return; } + const bool allowBottomExclusive = !preventBufferEnd; + const MovementDirection moveDirection = (moveCount > 0) ? MovementDirection::Forward : MovementDirection::Backward; const auto bufferSize = _pData->GetTextBuffer().GetSize(); - auto target = GetEndpoint(endpoint); + bool fSuccess = true; + auto target = GetEndpoint(endpoint); while (abs(*pAmountMoved) < abs(moveCount) && fSuccess) { - switch (moveState.Direction) + switch (moveDirection) { case MovementDirection::Forward: fSuccess = bufferSize.IncrementInBounds(target, allowBottomExclusive); @@ -1047,16 +1041,17 @@ void UiaTextRangeBase::_moveEndpointByUnitCharacter(_In_ const int moveCount, // Arguments: // - moveCount - the number of times to move // - endpoint - the endpoint to move -// - moveState - values indicating the state of the console for the -// move operation // - pAmountMoved - the number of times that the return values are "moved" +// - preventBufferEnd - when enabled, prevent endpoint from being at the end of the buffer +// This is used for general movement, where you are not allowed to +// create a degenerate range // Return Value: // - void UiaTextRangeBase::_moveEndpointByUnitWord(_In_ const int moveCount, _In_ const TextPatternRangeEndpoint endpoint, - _In_ const MoveState moveState, _In_ const std::wstring_view wordDelimiters, - _Out_ gsl::not_null const pAmountMoved) + _Out_ gsl::not_null const pAmountMoved, + _In_ const bool preventBufferEnd) { *pAmountMoved = 0; @@ -1065,31 +1060,65 @@ void UiaTextRangeBase::_moveEndpointByUnitWord(_In_ const int moveCount, return; } + const bool allowBottomExclusive = !preventBufferEnd; + const MovementDirection moveDirection = (moveCount > 0) ? MovementDirection::Forward : MovementDirection::Backward; const auto& buffer = _pData->GetTextBuffer(); const auto bufferSize = buffer.GetSize(); - auto target = GetEndpoint(endpoint); - while (abs(*pAmountMoved) < abs(moveCount) && bufferSize.IsInBounds(target)) + + auto resultPos = GetEndpoint(endpoint); + auto nextPos = resultPos; + + bool fSuccess = true; + while (abs(*pAmountMoved) < abs(moveCount) && fSuccess) { - switch (moveState.Direction) + switch (moveDirection) { case MovementDirection::Forward: - // Get the end of the word (including the delimiter run), - // but move one more forward to be on the next word's character - target = buffer.GetWordEnd(target, wordDelimiters, /*includeDelimiterRun*/ true); - bufferSize.IncrementInBounds(target); - *pAmountMoved += 1; + { + nextPos = buffer.GetWordEnd(nextPos, wordDelimiters, allowBottomExclusive); + + // manually check if we successfully moved + if (resultPos == nextPos || nextPos == bufferSize.EndInclusive()) + { + fSuccess = false; + } + else + { + resultPos = nextPos; + *pAmountMoved += 1; + } break; + } case MovementDirection::Backward: - target = buffer.GetWordStart(target, wordDelimiters, /*includeCharacterRun*/ true); - // NOTE: no need for bufferSize.DecrementInBounds(target) here, because we're already on the word - *pAmountMoved -= 1; + { + nextPos = resultPos; + + // first, get off of word + fSuccess = bufferSize.DecrementInBounds(nextPos); + if (fSuccess) + { + // then, expand left + nextPos = buffer.GetWordStart(nextPos, wordDelimiters, allowBottomExclusive); + + // manually check if we successfully moved + if (resultPos == nextPos || nextPos == bufferSize.Origin()) + { + fSuccess = false; + } + else + { + resultPos = nextPos; + *pAmountMoved -= 1; + } + } break; + } default: - break; + return; } } - SetEndpoint(endpoint, target); + SetEndpoint(endpoint, resultPos); } // Routine Description: @@ -1098,49 +1127,72 @@ void UiaTextRangeBase::_moveEndpointByUnitWord(_In_ const int moveCount, // Arguments: // - moveCount - the number of times to move // - endpoint - the endpoint to move -// - moveState - values indicating the state of the console for the -// move operation // - pAmountMoved - the number of times that the return values are "moved" +// - preventBufferEnd - when enabled, prevent endpoint from being at the end of the buffer +// This is used for general movement, where you are not allowed to +// create a degenerate range // Return Value: // - void UiaTextRangeBase::_moveEndpointByUnitLine(_In_ const int moveCount, _In_ const TextPatternRangeEndpoint endpoint, - _In_ const MoveState moveState, - _Out_ gsl::not_null const pAmountMoved) + _Out_ gsl::not_null const pAmountMoved, + _In_ const bool preventBufferEnd) { *pAmountMoved = 0; - // _end can be the out of bounds - bool allowBottomExclusive = (endpoint == TextPatternRangeEndpoint_End); - if (moveCount == 0) { return; } + const bool allowBottomExclusive = !preventBufferEnd; + const MovementDirection moveDirection = (moveCount > 0) ? MovementDirection::Forward : MovementDirection::Backward; const auto bufferSize = _pData->GetTextBuffer().GetSize(); - auto target = GetEndpoint(endpoint); + bool fSuccess = true; + auto resultPos = GetEndpoint(endpoint); while (abs(*pAmountMoved) < abs(moveCount) && fSuccess) { - switch (moveState.Direction) + auto nextPos = resultPos; + switch (moveDirection) { case MovementDirection::Forward: { - target.X = bufferSize.RightInclusive(); - fSuccess = bufferSize.IncrementInBounds(target, allowBottomExclusive); + // can't move past end + if (nextPos.Y >= bufferSize.BottomInclusive()) + { + if (preventBufferEnd || nextPos == bufferSize.EndInclusive()) + { + fSuccess = false; + break; + } + } + + nextPos.X = bufferSize.RightInclusive(); + fSuccess = bufferSize.IncrementInBounds(nextPos, allowBottomExclusive); if (fSuccess) { + resultPos = nextPos; *pAmountMoved += 1; } break; } case MovementDirection::Backward: { - target.X = bufferSize.Left(); - fSuccess = bufferSize.DecrementInBounds(target, allowBottomExclusive); + // can't move past top + if (!allowBottomExclusive && nextPos.Y == bufferSize.Top()) + { + fSuccess = false; + break; + } + + // NOTE: Automatically detects if we are trying to move past origin + fSuccess = bufferSize.DecrementInBounds(nextPos, allowBottomExclusive); + if (fSuccess) { + nextPos.X = bufferSize.Left(); + resultPos = nextPos; *pAmountMoved -= 1; } break; @@ -1150,7 +1202,7 @@ void UiaTextRangeBase::_moveEndpointByUnitLine(_In_ const int moveCount, } } - SetEndpoint(endpoint, target); + SetEndpoint(endpoint, resultPos); } // Routine Description: @@ -1159,15 +1211,16 @@ void UiaTextRangeBase::_moveEndpointByUnitLine(_In_ const int moveCount, // Arguments: // - moveCount - the number of times to move // - endpoint - the endpoint to move -// - moveState - values indicating the state of the console for the -// move operation // - pAmountMoved - the number of times that the return values are "moved" +// - preventBufferEnd - when enabled, prevent endpoint from being at the end of the buffer +// This is used for general movement, where you are not allowed to +// create a degenerate range // Return Value: // - void UiaTextRangeBase::_moveEndpointByUnitDocument(_In_ const int moveCount, _In_ const TextPatternRangeEndpoint endpoint, - _In_ const MoveState moveState, - _Out_ gsl::not_null const pAmountMoved) + _Out_ gsl::not_null const pAmountMoved, + _In_ const bool preventBufferEnd) { *pAmountMoved = 0; @@ -1176,14 +1229,16 @@ void UiaTextRangeBase::_moveEndpointByUnitDocument(_In_ const int moveCount, return; } + const MovementDirection moveDirection = (moveCount > 0) ? MovementDirection::Forward : MovementDirection::Backward; const auto bufferSize = _pData->GetTextBuffer().GetSize(); + auto target = GetEndpoint(endpoint); - switch (moveState.Direction) + switch (moveDirection) { case MovementDirection::Forward: { const auto documentEnd = bufferSize.EndInclusive(); - if (target == documentEnd) + if (preventBufferEnd || target == documentEnd) { return; } diff --git a/src/types/UiaTextRangeBase.hpp b/src/types/UiaTextRangeBase.hpp index af8910a67b1..a2f9c9ddfc4 100644 --- a/src/types/UiaTextRangeBase.hpp +++ b/src/types/UiaTextRangeBase.hpp @@ -85,30 +85,6 @@ namespace Microsoft::Console::Types Backward }; - // common information used by the variety of - // movement operations - struct MoveState - { - // screen/column position of _start - COORD start; - // screen/column position of _end - COORD end; - // direction moving - MovementDirection Direction; - - MoveState(const UiaTextRangeBase& range, - const MovementDirection direction); - - private: - MoveState(const COORD start, - const COORD end, - const MovementDirection direction) noexcept; - -#ifdef UNIT_TESTING - friend class ::UiaTextRangeTests; -#endif - }; - public: // The default word delimiter for UiaTextRanges static constexpr std::wstring_view DefaultWordDelimiter{ &UNICODE_SPACE, 1 }; @@ -186,7 +162,6 @@ namespace Microsoft::Console::Types protected: UiaTextRangeBase() = default; #if _DEBUG - void _outputRowConversions(IUiaData* pData); void _outputObjectState(); #endif IUiaData* _pData; @@ -231,27 +206,27 @@ namespace Microsoft::Console::Types void _moveEndpointByUnitCharacter(_In_ const int moveCount, _In_ const TextPatternRangeEndpoint endpoint, - _In_ const MoveState moveState, - _Out_ gsl::not_null const pAmountMoved); + _Out_ gsl::not_null const pAmountMoved, + _In_ const bool preventBufferEnd = false); void _moveEndpointByUnitWord(_In_ const int moveCount, _In_ const TextPatternRangeEndpoint endpoint, - _In_ const MoveState moveState, _In_ const std::wstring_view wordDelimiters, - _Out_ gsl::not_null const pAmountMoved); + _Out_ gsl::not_null const pAmountMoved, + _In_ const bool preventBufferEnd = false); void _moveEndpointByUnitLine(_In_ const int moveCount, _In_ const TextPatternRangeEndpoint endpoint, - _In_ const MoveState moveState, - _Out_ gsl::not_null const pAmountMoved); + _Out_ gsl::not_null const pAmountMoved, + _In_ const bool preventBufferEnd = false); void _moveEndpointByUnitDocument(_In_ const int moveCount, _In_ const TextPatternRangeEndpoint endpoint, - _In_ const MoveState moveState, - _Out_ gsl::not_null const pAmountMoved); + _Out_ gsl::not_null const pAmountMoved, + _In_ const bool preventBufferEnd = false); #ifdef UNIT_TESTING friend class ::UiaTextRangeTests; diff --git a/src/types/viewport.cpp b/src/types/viewport.cpp index 318a1037729..ede658cb4ca 100644 --- a/src/types/viewport.cpp +++ b/src/types/viewport.cpp @@ -145,7 +145,7 @@ COORD Viewport::Origin() const noexcept // - the coordinates of this viewport's end. COORD Viewport::EndInclusive() const noexcept { - return { RightExclusive(), BottomInclusive() }; + return { Left(), BottomExclusive() }; } // Method Description: @@ -398,7 +398,12 @@ bool Viewport::WalkInBoundsCircular(COORD& pos, const WalkDir dir, bool allowBot if (dir.x == XWalk::LeftToRight) { - if (pos.X == RightInclusive()) + if (allowBottomExclusive && pos.X == Left() && pos.Y == BottomExclusive()) + { + pos.Y = Top(); + return false; + } + else if (pos.X == RightInclusive()) { pos.X = Left(); From f6b2ea2b3fbc015a812e5784fa7f7f238a8e7bfc Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Fri, 20 Dec 2019 15:29:21 -0800 Subject: [PATCH 06/25] wtvr --- src/buffer/out/textBuffer.cpp | 34 +++++++++++++++++++ src/buffer/out/textBuffer.hpp | 3 ++ .../UiaTextRangeTests.cpp | 10 +++--- src/types/UiaTextRangeBase.cpp | 20 +++++++---- 4 files changed, 55 insertions(+), 12 deletions(-) diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp index 0ab7e90b164..b5013a60b20 100644 --- a/src/buffer/out/textBuffer.cpp +++ b/src/buffer/out/textBuffer.cpp @@ -1067,6 +1067,40 @@ const COORD TextBuffer::GetWordEnd(const COORD target, const std::wstring_view w return result; } +const bool TextBuffer::IsCharacterAtOrigin() const +{ + auto data = GetTextDataAt(GetSize().Origin()); + return _GetDelimiterClass(*data, L" ") == DelimiterClass::RegularChar; +} + +bool TextBuffer::MoveToNextWord(COORD& pos, std::wstring_view wordDelimiters) const +{ + auto copy = pos; + auto bufferSize = GetSize(); + + auto text = GetTextDataAt(copy)->data(); + while (_GetDelimiterClass(text, wordDelimiters) != DelimiterClass::RegularChar) + { + + + text = GetTextDataAt(copy)->data(); + } + bufferSize.IncrementInBounds(copy); + + // successful move, copy result out + pos = copy; + return true; +} + +bool TextBuffer::MoveToPreviousWord(COORD& pos, std::wstring_view wordDelimiters) const +{ + auto copy = pos; + + // successful move, copy result out + pos = copy; + return true; +} + // Method Description: // - get delimiter class for buffer cell data // - used for double click selection and uia word navigation diff --git a/src/buffer/out/textBuffer.hpp b/src/buffer/out/textBuffer.hpp index 824b4d665dc..cff38da3605 100644 --- a/src/buffer/out/textBuffer.hpp +++ b/src/buffer/out/textBuffer.hpp @@ -132,6 +132,9 @@ class TextBuffer final const COORD GetWordStart(const COORD target, const std::wstring_view wordDelimiters, bool accessibilityMode = false) const; const COORD GetWordEnd(const COORD target, const std::wstring_view wordDelimiters, bool accessibilityMode = false) const; + bool MoveToNextWord(COORD& pos, std::wstring_view wordDelimiters) const; + bool MoveToPreviousWord(COORD& pos, std::wstring_view wordDelimiters) const; + const bool IsCharacterAtOrigin() const; class TextAndColor { diff --git a/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp b/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp index fa7bd149c3a..8017896e967 100644 --- a/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp +++ b/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp @@ -101,7 +101,7 @@ class UiaTextRangeTests auto& charRow = row.GetCharRow(); for (auto& cell : charRow) { - cell.Char() = L'a'; + cell.Char() = L' '; } } @@ -325,8 +325,8 @@ class UiaTextRangeTests 5, { 5, - {0, 5}, - {0, 6} + {0, 4}, + {0, 5} } }, @@ -674,8 +674,8 @@ class UiaTextRangeTests 5, TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, { - 4, - {lastColumnIndex, bottomRow}, + 5, + {0, bottomRow+1}, {0, bottomRow+1}, } }, diff --git a/src/types/UiaTextRangeBase.cpp b/src/types/UiaTextRangeBase.cpp index b75d0d73bc1..1833f8dabf9 100644 --- a/src/types/UiaTextRangeBase.cpp +++ b/src/types/UiaTextRangeBase.cpp @@ -9,9 +9,9 @@ using namespace Microsoft::Console::Types; using namespace Microsoft::Console::Types::UiaTextRangeBaseTracing; // toggle these for additional logging in a debug build -//#define _DEBUG 1 -//#define UIATEXTRANGE_DEBUG_MSGS 1 -#undef UIATEXTRANGE_DEBUG_MSGS +#define _DEBUG 1 +#define UIATEXTRANGE_DEBUG_MSGS 1 +//#undef UIATEXTRANGE_DEBUG_MSGS IdType UiaTextRangeBase::id = 1; @@ -523,7 +523,7 @@ IFACEMETHODIMP UiaTextRangeBase::GetText(_In_ int maxLength, _Out_ BSTR* pRetVal { currentScreenInfoRow = _start.Y + i; const ROW& row = buffer.GetRowByOffset(currentScreenInfoRow); - if (row.GetCharRow().ContainsText()) + if (1)//row.GetCharRow().ContainsText()) { const size_t rowRight = row.GetCharRow().MeasureRight(); size_t startIndex = 0; @@ -536,7 +536,7 @@ IFACEMETHODIMP UiaTextRangeBase::GetText(_In_ int maxLength, _Out_ BSTR* pRetVal if (currentScreenInfoRow == static_cast(_end.Y)) { // prevent the end from going past the last non-whitespace char in the row - endIndex = std::min(gsl::narrow_cast(_end.X) - 1, rowRight); + endIndex = std::max(startIndex + 1, std::min(gsl::narrow_cast(_end.X), rowRight)); } // if startIndex >= endIndex then _start is @@ -1075,10 +1075,16 @@ void UiaTextRangeBase::_moveEndpointByUnitWord(_In_ const int moveCount, { case MovementDirection::Forward: { + // manual wrap to next line + if (nextPos.X == bufferSize.RightInclusive()) + { + bufferSize.IncrementInBounds(nextPos, allowBottomExclusive); + } + nextPos = buffer.GetWordEnd(nextPos, wordDelimiters, allowBottomExclusive); // manually check if we successfully moved - if (resultPos == nextPos || nextPos == bufferSize.EndInclusive()) + if (resultPos == nextPos || (preventBufferEnd && nextPos == bufferSize.EndInclusive())) { fSuccess = false; } @@ -1101,7 +1107,7 @@ void UiaTextRangeBase::_moveEndpointByUnitWord(_In_ const int moveCount, nextPos = buffer.GetWordStart(nextPos, wordDelimiters, allowBottomExclusive); // manually check if we successfully moved - if (resultPos == nextPos || nextPos == bufferSize.Origin()) + if (resultPos == nextPos || (preventBufferEnd && nextPos == bufferSize.Origin() && !buffer.IsCharacterAtOrigin())) { fSuccess = false; } From 9a40869a4b7bfc27b872d6fe9f2d3a189e529220 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Fri, 20 Dec 2019 17:57:16 -0800 Subject: [PATCH 07/25] Let's try doing word nav this way --- src/buffer/out/textBuffer.cpp | 52 +++++++++++++++++++---- src/types/UiaTextRangeBase.cpp | 76 ++++++++++++++-------------------- src/types/UiaTextRangeBase.hpp | 1 - 3 files changed, 74 insertions(+), 55 deletions(-) diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp index b5013a60b20..555242135ba 100644 --- a/src/buffer/out/textBuffer.cpp +++ b/src/buffer/out/textBuffer.cpp @@ -1067,25 +1067,34 @@ const COORD TextBuffer::GetWordEnd(const COORD target, const std::wstring_view w return result; } -const bool TextBuffer::IsCharacterAtOrigin() const -{ - auto data = GetTextDataAt(GetSize().Origin()); - return _GetDelimiterClass(*data, L" ") == DelimiterClass::RegularChar; -} - bool TextBuffer::MoveToNextWord(COORD& pos, std::wstring_view wordDelimiters) const { auto copy = pos; auto bufferSize = GetSize(); auto text = GetTextDataAt(copy)->data(); - while (_GetDelimiterClass(text, wordDelimiters) != DelimiterClass::RegularChar) + auto delimiterClass = _GetDelimiterClass(text, wordDelimiters); + // started on a word, continue until the end of the word + while (delimiterClass == DelimiterClass::RegularChar) { + if (!bufferSize.IncrementInBounds(copy)) + { + return false; + } + text = GetTextDataAt(copy)->data(); + delimiterClass = _GetDelimiterClass(text, wordDelimiters); + } - + // on whitespace, continue until the beginning of the next word + while (delimiterClass != DelimiterClass::RegularChar) + { + if (!bufferSize.IncrementInBounds(copy)) + { + return false; + } text = GetTextDataAt(copy)->data(); + delimiterClass = _GetDelimiterClass(text, wordDelimiters); } - bufferSize.IncrementInBounds(copy); // successful move, copy result out pos = copy; @@ -1095,6 +1104,31 @@ bool TextBuffer::MoveToNextWord(COORD& pos, std::wstring_view wordDelimiters) co bool TextBuffer::MoveToPreviousWord(COORD& pos, std::wstring_view wordDelimiters) const { auto copy = pos; + auto bufferSize = GetSize(); + + auto text = GetTextDataAt(copy)->data(); + auto delimiterClass = _GetDelimiterClass(text, wordDelimiters); + // started on a word, continue until the beginning of the word + while (delimiterClass == DelimiterClass::RegularChar) + { + if (!bufferSize.DecrementInBounds(copy)) + { + return false; + } + text = GetTextDataAt(copy)->data(); + delimiterClass = _GetDelimiterClass(text, wordDelimiters); + } + + // on whitespace, continue until the end of the previous word + while (delimiterClass != DelimiterClass::RegularChar) + { + if (!bufferSize.DecrementInBounds(copy)) + { + return false; + } + text = GetTextDataAt(copy)->data(); + delimiterClass = _GetDelimiterClass(text, wordDelimiters); + } // successful move, copy result out pos = copy; diff --git a/src/types/UiaTextRangeBase.cpp b/src/types/UiaTextRangeBase.cpp index 1833f8dabf9..1088902effb 100644 --- a/src/types/UiaTextRangeBase.cpp +++ b/src/types/UiaTextRangeBase.cpp @@ -523,7 +523,7 @@ IFACEMETHODIMP UiaTextRangeBase::GetText(_In_ int maxLength, _Out_ BSTR* pRetVal { currentScreenInfoRow = _start.Y + i; const ROW& row = buffer.GetRowByOffset(currentScreenInfoRow); - if (1)//row.GetCharRow().ContainsText()) + if (1) //row.GetCharRow().ContainsText()) { const size_t rowRight = row.GetCharRow().MeasureRight(); size_t startIndex = 0; @@ -625,7 +625,7 @@ IFACEMETHODIMP UiaTextRangeBase::Move(_In_ TextUnit unit, } else if (unit <= TextUnit::TextUnit_Word) { - _moveEndpointByUnitWord(count, endpoint, _wordDelimiters, pRetVal, preventBufferEnd); + _moveEndpointByUnitWord(count, endpoint, pRetVal, preventBufferEnd); } else if (unit <= TextUnit::TextUnit_Line) { @@ -696,7 +696,7 @@ IFACEMETHODIMP UiaTextRangeBase::MoveEndpointByUnit(_In_ TextPatternRangeEndpoin } else if (unit <= TextUnit::TextUnit_Word) { - _moveEndpointByUnitWord(count, endpoint, _wordDelimiters, pRetVal); + _moveEndpointByUnitWord(count, endpoint, pRetVal); } else if (unit <= TextUnit::TextUnit_Line) { @@ -1049,7 +1049,6 @@ void UiaTextRangeBase::_moveEndpointByUnitCharacter(_In_ const int moveCount, // - void UiaTextRangeBase::_moveEndpointByUnitWord(_In_ const int moveCount, _In_ const TextPatternRangeEndpoint endpoint, - _In_ const std::wstring_view wordDelimiters, _Out_ gsl::not_null const pAmountMoved, _In_ const bool preventBufferEnd) { @@ -1064,6 +1063,8 @@ void UiaTextRangeBase::_moveEndpointByUnitWord(_In_ const int moveCount, const MovementDirection moveDirection = (moveCount > 0) ? MovementDirection::Forward : MovementDirection::Backward; const auto& buffer = _pData->GetTextBuffer(); const auto bufferSize = buffer.GetSize(); + const auto bufferOrigin = bufferSize.Origin(); + const auto bufferEnd = bufferSize.EndInclusive(); auto resultPos = GetEndpoint(endpoint); auto nextPos = resultPos; @@ -1071,51 +1072,40 @@ void UiaTextRangeBase::_moveEndpointByUnitWord(_In_ const int moveCount, bool fSuccess = true; while (abs(*pAmountMoved) < abs(moveCount) && fSuccess) { + nextPos = resultPos; switch (moveDirection) { - case MovementDirection::Forward: - { - // manual wrap to next line - if (nextPos.X == bufferSize.RightInclusive()) - { - bufferSize.IncrementInBounds(nextPos, allowBottomExclusive); - } - - nextPos = buffer.GetWordEnd(nextPos, wordDelimiters, allowBottomExclusive); - - // manually check if we successfully moved - if (resultPos == nextPos || (preventBufferEnd && nextPos == bufferSize.EndInclusive())) + case MovementDirection::Forward: { + if (nextPos == bufferEnd) { fSuccess = false; } - else + else if (buffer.MoveToNextWord(nextPos, _wordDelimiters)) { resultPos = nextPos; *pAmountMoved += 1; } + else if (allowBottomExclusive) + { + resultPos = bufferEnd; + *pAmountMoved += 1; + } break; } - case MovementDirection::Backward: - { - nextPos = resultPos; - - // first, get off of word - fSuccess = bufferSize.DecrementInBounds(nextPos); - if (fSuccess) + case MovementDirection::Backward: { + if (nextPos == bufferOrigin) { - // then, expand left - nextPos = buffer.GetWordStart(nextPos, wordDelimiters, allowBottomExclusive); - - // manually check if we successfully moved - if (resultPos == nextPos || (preventBufferEnd && nextPos == bufferSize.Origin() && !buffer.IsCharacterAtOrigin())) - { - fSuccess = false; - } - else - { - resultPos = nextPos; - *pAmountMoved -= 1; - } + fSuccess = false; + } + else if (buffer.MoveToPreviousWord(nextPos, _wordDelimiters)) + { + resultPos = nextPos; + *pAmountMoved -= 1; + } + else + { + resultPos = bufferOrigin; + *pAmountMoved -= 1; } break; } @@ -1162,8 +1152,7 @@ void UiaTextRangeBase::_moveEndpointByUnitLine(_In_ const int moveCount, auto nextPos = resultPos; switch (moveDirection) { - case MovementDirection::Forward: - { + case MovementDirection::Forward: { // can't move past end if (nextPos.Y >= bufferSize.BottomInclusive()) { @@ -1183,8 +1172,7 @@ void UiaTextRangeBase::_moveEndpointByUnitLine(_In_ const int moveCount, } break; } - case MovementDirection::Backward: - { + case MovementDirection::Backward: { // can't move past top if (!allowBottomExclusive && nextPos.Y == bufferSize.Top()) { @@ -1241,8 +1229,7 @@ void UiaTextRangeBase::_moveEndpointByUnitDocument(_In_ const int moveCount, auto target = GetEndpoint(endpoint); switch (moveDirection) { - case MovementDirection::Forward: - { + case MovementDirection::Forward: { const auto documentEnd = bufferSize.EndInclusive(); if (preventBufferEnd || target == documentEnd) { @@ -1255,8 +1242,7 @@ void UiaTextRangeBase::_moveEndpointByUnitDocument(_In_ const int moveCount, } break; } - case MovementDirection::Backward: - { + case MovementDirection::Backward: { const auto documentBegin = bufferSize.Origin(); if (target == documentBegin) { diff --git a/src/types/UiaTextRangeBase.hpp b/src/types/UiaTextRangeBase.hpp index a2f9c9ddfc4..cbfdc8b7ca5 100644 --- a/src/types/UiaTextRangeBase.hpp +++ b/src/types/UiaTextRangeBase.hpp @@ -212,7 +212,6 @@ namespace Microsoft::Console::Types void _moveEndpointByUnitWord(_In_ const int moveCount, _In_ const TextPatternRangeEndpoint endpoint, - _In_ const std::wstring_view wordDelimiters, _Out_ gsl::not_null const pAmountMoved, _In_ const bool preventBufferEnd = false); From ea22c8f360009889f893110c257d846615cbdbd3 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Wed, 15 Jan 2020 23:29:36 -0800 Subject: [PATCH 08/25] Add testing for Compare, CompareEndpoint, Expand, and MoveEndpointByRange Missing tests for... - GetText - Move - MoveEndpointByUnit Still need to think of how to approach Move and MoveEndpointByUnit since these should be very similar and should reuse a lot of code. --- src/buffer/out/textBuffer.cpp | 40 +- .../UiaTextRangeTests.cpp | 449 +++++++++++++++--- src/types/UiaTextRangeBase.cpp | 12 +- 3 files changed, 412 insertions(+), 89 deletions(-) diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp index 555242135ba..552b1f56d95 100644 --- a/src/buffer/out/textBuffer.cpp +++ b/src/buffer/out/textBuffer.cpp @@ -959,7 +959,7 @@ Microsoft::Console::Render::IRenderTarget& TextBuffer::GetRenderTarget() noexcep // - accessibilityMode - when enabled, we continue expanding left until we are at the beginning of a readable word // Return Value: // - The COORD for the first character on the "word" (inclusive) -const COORD TextBuffer::GetWordStart(const COORD target, const std::wstring_view wordDelimiters, bool accessibilityMode) const +const COORD TextBuffer::GetWordStart(const COORD target, const std::wstring_view wordDelimiters, bool /*accessibilityMode*/) const { const auto bufferSize = GetSize(); COORD result = target; @@ -978,25 +978,25 @@ const COORD TextBuffer::GetWordStart(const COORD target, const std::wstring_view --bufferIterator; } - if (accessibilityMode) - { - // make sure we expand to the beggining of the word - if (_GetDelimiterClass(*bufferIterator, wordDelimiters) == DelimiterClass::RegularChar) - { - while (result.X > bufferSize.Left() && (_GetDelimiterClass(*bufferIterator, wordDelimiters) == DelimiterClass::RegularChar)) - { - bufferSize.DecrementInBounds(result); - --bufferIterator; - } - } - - // move back onto word start - if (result.X != bufferSize.Left() && _GetDelimiterClass(*bufferIterator, wordDelimiters) != DelimiterClass::RegularChar) - { - bufferSize.IncrementInBounds(result); - } - } - else if (_GetDelimiterClass(*bufferIterator, wordDelimiters) != initialDelimiter) + //if (accessibilityMode) + //{ + // // make sure we expand to the beggining of the word + // if (_GetDelimiterClass(*bufferIterator, wordDelimiters) == DelimiterClass::RegularChar) + // { + // while (result.X > bufferSize.Left() && (_GetDelimiterClass(*bufferIterator, wordDelimiters) == DelimiterClass::RegularChar)) + // { + // bufferSize.DecrementInBounds(result); + // --bufferIterator; + // } + // } + + // // move back onto word start + // if (result.X != bufferSize.Left() && _GetDelimiterClass(*bufferIterator, wordDelimiters) != DelimiterClass::RegularChar) + // { + // bufferSize.IncrementInBounds(result); + // } + //} + if (_GetDelimiterClass(*bufferIterator, wordDelimiters) != initialDelimiter) { // move off of delimiter bufferSize.IncrementInBounds(result); diff --git a/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp b/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp index 8017896e967..ac0e375174e 100644 --- a/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp +++ b/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp @@ -105,14 +105,6 @@ class UiaTextRangeTests } } - // set up default range - COORD coord = { 0, 0 }; - Microsoft::WRL::MakeAndInitialize(&_range, - _pUiaData, - &_dummyProvider, - coord, - coord); - return true; } @@ -137,26 +129,370 @@ class UiaTextRangeTests // make a degenerate range and verify that it reports degenerate Microsoft::WRL::ComPtr degenerate; - Microsoft::WRL::MakeAndInitialize(°enerate, - _pUiaData, - &_dummyProvider, - origin, - origin); + THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize(°enerate, + _pUiaData, + &_dummyProvider, + origin, + origin)); VERIFY_IS_TRUE(degenerate->IsDegenerate()); VERIFY_ARE_EQUAL(degenerate->_start, degenerate->_end); // make a non-degenerate range and verify that it reports as such const COORD end = { origin.X + 1, origin.Y }; Microsoft::WRL::ComPtr notDegenerate; - Microsoft::WRL::MakeAndInitialize(¬Degenerate, - _pUiaData, - &_dummyProvider, - origin, - end); + THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize(¬Degenerate, + _pUiaData, + &_dummyProvider, + origin, + end)); VERIFY_IS_FALSE(notDegenerate->IsDegenerate()); VERIFY_ARE_NOT_EQUAL(notDegenerate->_start, notDegenerate->_end); } + TEST_METHOD(CompareRange) + { + const auto bufferSize = _pTextBuffer->GetSize(); + const auto origin = bufferSize.Origin(); + + Microsoft::WRL::ComPtr utr1; + THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize(&utr1, + _pUiaData, + &_dummyProvider, + origin, + origin)); + + // utr2 initialized to have the same start/end as utr1 + Microsoft::WRL::ComPtr utr2; + THROW_IF_FAILED(utr1->Clone(&utr2)); + + BOOL comparison; + Log::Comment(L"_start and _end should match"); + THROW_IF_FAILED(utr1->Compare(utr2.Get(), &comparison)); + VERIFY_IS_TRUE(comparison); + + // utr2 redefined to have different end from utr1 + const COORD end = { origin.X + 2, origin.Y }; + THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize(&utr2, + _pUiaData, + &_dummyProvider, + origin, + end)); + + Log::Comment(L"_end is different"); + THROW_IF_FAILED(utr1->Compare(utr2.Get(), &comparison)); + VERIFY_IS_FALSE(comparison); + } + + TEST_METHOD(CompareEndpoints) + { + const auto bufferSize = _pTextBuffer->GetSize(); + const auto origin = bufferSize.Origin(); + + Microsoft::WRL::ComPtr utr1; + THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize(&utr1, + _pUiaData, + &_dummyProvider, + origin, + origin)); + + Microsoft::WRL::ComPtr utr2; + THROW_IF_FAILED(utr1->Clone(&utr2)); + + int comparison; + Log::Comment(L"For a degenerate range, comparing _start and _end should return 0"); + VERIFY_IS_TRUE(utr1->IsDegenerate()); + THROW_IF_FAILED(utr1->CompareEndpoints(TextPatternRangeEndpoint_Start, utr1.Get(), TextPatternRangeEndpoint_End, &comparison)); + + Log::Comment(L"_start and _end should match"); + THROW_IF_FAILED(utr1->CompareEndpoints(TextPatternRangeEndpoint_Start, utr2.Get(), TextPatternRangeEndpoint_Start, &comparison)); + VERIFY_IS_TRUE(comparison == 0); + THROW_IF_FAILED(utr1->CompareEndpoints(TextPatternRangeEndpoint_End, utr2.Get(), TextPatternRangeEndpoint_End, &comparison)); + VERIFY_IS_TRUE(comparison == 0); + + // utr2 redefined to have different end from utr1 + const COORD end = { origin.X + 2, origin.Y }; + THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize(&utr2, + _pUiaData, + &_dummyProvider, + origin, + end)); + + Log::Comment(L"_start should match"); + THROW_IF_FAILED(utr1->CompareEndpoints(TextPatternRangeEndpoint_Start, utr2.Get(), TextPatternRangeEndpoint_Start, &comparison)); + VERIFY_IS_TRUE(comparison == 0); + + Log::Comment(L"_start and end should be 2 units apart. Sign depends on order of comparison."); + THROW_IF_FAILED(utr1->CompareEndpoints(TextPatternRangeEndpoint_End, utr2.Get(), TextPatternRangeEndpoint_End, &comparison)); + VERIFY_IS_TRUE(comparison == -2); + THROW_IF_FAILED(utr2->CompareEndpoints(TextPatternRangeEndpoint_End, utr1.Get(), TextPatternRangeEndpoint_End, &comparison)); + VERIFY_IS_TRUE(comparison == 2); + } + + TEST_METHOD(ExpandToEnclosingUnit) + { + // Let's start by filling the text buffer with something useful: + for (UINT i = 0; i < _pTextBuffer->TotalRowCount(); ++i) + { + ROW& row = _pTextBuffer->GetRowByOffset(i); + auto& charRow = row.GetCharRow(); + for (size_t j = 0; j < charRow.size(); ++j) + { + // every 5th cell is a space, otherwise a letter + // this is used to simulate words + CharRowCellReference cell = charRow.GlyphAt(j); + if (j % 5 == 0) + { + cell = L" "; + } + else + { + cell = L"x"; + } + } + } + + // According to https://docs.microsoft.com/en-us/windows/win32/winauto/uiauto-implementingtextandtextrange#manipulating-a-text-range-by-text-unit + // there are 9 examples of how ExpandToEnclosingUnit should behave. See the diagram there for reference. + // Some of the relevant text has been copied below... + // 1-2) If the text range starts at the beginning of a text unit + // and ends at the beginning of, or before, the next text unit + // boundary, the ending endpoint is moved to the next text unit boundary + // 3-4) If the text range starts at the beginning of a text unit + // and ends at, or after, the next unit boundary, + // the ending endpoint stays or is moved backward to + // the next unit boundary after the starting endpoint + // NOTE: If there is more than one text unit boundary between + // the starting and ending endpoints, the ending endpoint + // is moved backward to the next unit boundary after + // the starting endpoint, resulting in a text range that is + // one text unit in length. + // 5-8) If the text range starts in a middle of the text unit, + // the starting endpoint is moved backward to the beginning + // of the text unit, and the ending endpoint is moved forward + // or backward, as necessary, to the next unit boundary + // after the starting endpoint + // 9) (same as 1) If the text range starts and ends at the beginning of + // a text unit boundary, the ending endpoint is moved to the next text unit boundary + + // We will abstract these tests so that we can define the beginning and end of a text unit boundary, + // based on the text unit we are testing + constexpr TextUnit supportedUnits[] = { TextUnit_Character, TextUnit_Word, TextUnit_Line, TextUnit_Document }; + + auto toString = [&](TextUnit unit) { + // if a format is not supported, it goes to the next largest text unit + switch (unit) + { + case TextUnit_Character: + return L"Character"; + case TextUnit_Format: + case TextUnit_Word: + return L"Word"; + case TextUnit_Line: + return L"Line"; + case TextUnit_Paragraph: + case TextUnit_Page: + case TextUnit_Document: + return L"Document"; + default: + throw E_INVALIDARG; + } + }; + + struct TextUnitBoundaries + { + COORD start; + COORD end; + }; + + const std::map textUnitBoundaries = { + { TextUnit_Character, + TextUnitBoundaries{ + { 0, 0 }, + { 1, 0 } } }, + { TextUnit_Word, + TextUnitBoundaries{ + { 1, 0 }, + { 6, 0 } } }, + { TextUnit_Line, + TextUnitBoundaries{ + { 0, 0 }, + { 0, 1 } } }, + { TextUnit_Document, + TextUnitBoundaries{ + { 0, 0 }, + _pTextBuffer->GetSize().EndInclusive() } } + }; + + Microsoft::WRL::ComPtr utr; + auto verifyExpansion = [&](TextUnit textUnit, COORD utrStart, COORD utrEnd) { + THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize(&utr, + _pUiaData, + &_dummyProvider, + utrStart, + utrEnd)); + THROW_IF_FAILED(utr->ExpandToEnclosingUnit(textUnit)); + + const auto boundaries = textUnitBoundaries.at(textUnit); + VERIFY_ARE_EQUAL(utr->GetEndpoint(TextPatternRangeEndpoint_Start), boundaries.start); + VERIFY_ARE_EQUAL(utr->GetEndpoint(TextPatternRangeEndpoint_End), boundaries.end); + }; + + for (auto textUnit : supportedUnits) + { + const auto boundaries = textUnitBoundaries.at(textUnit); + + // Test 1 + Log::Comment(NoThrowString().Format(L"%s - Test 1", toString(textUnit))); + verifyExpansion(textUnit, boundaries.start, boundaries.start); + + // Test 2 (impossible for TextUnit_Character) + if (textUnit != TextUnit_Character) + { + Log::Comment(NoThrowString().Format(L"%s - Test 2", toString(textUnit))); + const COORD end = { boundaries.start.X + 1, boundaries.start.Y }; + verifyExpansion(textUnit, boundaries.start, end); + } + + // Test 3 + Log::Comment(NoThrowString().Format(L"%s - Test 3", toString(textUnit))); + verifyExpansion(textUnit, boundaries.start, boundaries.end); + + // Test 4 (impossible for TextUnit_Character and TextUnit_Document) + if (textUnit != TextUnit_Character && textUnit != TextUnit_Document) + { + Log::Comment(NoThrowString().Format(L"%s - Test 4", toString(textUnit))); + const COORD end = { boundaries.end.X + 1, boundaries.end.Y }; + verifyExpansion(textUnit, boundaries.start, end); + } + + // Test 5 (impossible for TextUnit_Character) + if (textUnit != TextUnit_Character) + { + Log::Comment(NoThrowString().Format(L"%s - Test 5", toString(textUnit))); + const COORD start = { boundaries.start.X + 1, boundaries.start.Y }; + verifyExpansion(textUnit, start, start); + } + + // Test 6 (impossible for TextUnit_Character) + if (textUnit != TextUnit_Character) + { + Log::Comment(NoThrowString().Format(L"%s - Test 6", toString(textUnit))); + const COORD start = { boundaries.start.X + 1, boundaries.start.Y }; + const COORD end = { start.X + 1, start.Y }; + verifyExpansion(textUnit, start, end); + } + + // Test 7 (impossible for TextUnit_Character) + if (textUnit != TextUnit_Character) + { + Log::Comment(NoThrowString().Format(L"%s - Test 7", toString(textUnit))); + const COORD start = { boundaries.start.X + 1, boundaries.start.Y }; + verifyExpansion(textUnit, start, boundaries.end); + } + + // Test 8 (impossible for TextUnit_Character and TextUnit_Document) + if (textUnit != TextUnit_Character && textUnit != TextUnit_Document) + { + Log::Comment(NoThrowString().Format(L"%s - Test 8", toString(textUnit))); + const COORD start = { boundaries.start.X + 1, boundaries.start.Y }; + const COORD end = { boundaries.end.X + 1, boundaries.end.Y }; + verifyExpansion(textUnit, start, end); + } + } + } + + TEST_METHOD(MoveEndpointByRange) + { + const COORD start{ 0, 1 }; + const COORD end{ 1, 2 }; + Microsoft::WRL::ComPtr utr; + THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize(&utr, + _pUiaData, + &_dummyProvider, + start, + end)); + + const auto bufferSize = _pTextBuffer->GetSize(); + const auto origin = bufferSize.Origin(); + Microsoft::WRL::ComPtr target; + + auto resetTargetUTR = [&]() { + THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize(&target, + _pUiaData, + &_dummyProvider, + origin, + origin)); + }; + + Log::Comment(L"Move target's end to utr1's start"); + { + resetTargetUTR(); + THROW_IF_FAILED(target->MoveEndpointByRange(TextPatternRangeEndpoint_End, + utr.Get(), + TextPatternRangeEndpoint_Start)); + VERIFY_ARE_EQUAL(target->GetEndpoint(TextPatternRangeEndpoint_Start), origin); + VERIFY_ARE_EQUAL(target->GetEndpoint(TextPatternRangeEndpoint_End), utr->GetEndpoint(TextPatternRangeEndpoint_Start)); + } + + Log::Comment(L"Move target's start/end to utr1's start/end respectively"); + { + resetTargetUTR(); + THROW_IF_FAILED(target->MoveEndpointByRange(TextPatternRangeEndpoint_End, + utr.Get(), + TextPatternRangeEndpoint_End)); + VERIFY_ARE_EQUAL(target->GetEndpoint(TextPatternRangeEndpoint_Start), origin); + VERIFY_ARE_EQUAL(target->GetEndpoint(TextPatternRangeEndpoint_End), utr->GetEndpoint(TextPatternRangeEndpoint_End)); + + THROW_IF_FAILED(target->MoveEndpointByRange(TextPatternRangeEndpoint_Start, + utr.Get(), + TextPatternRangeEndpoint_Start)); + VERIFY_ARE_EQUAL(target->GetEndpoint(TextPatternRangeEndpoint_Start), utr->GetEndpoint(TextPatternRangeEndpoint_Start)); + VERIFY_ARE_EQUAL(target->GetEndpoint(TextPatternRangeEndpoint_End), utr->GetEndpoint(TextPatternRangeEndpoint_End)); + } + + Log::Comment(L"(Clone utr1) Collapse onto itself"); + { + // Move start to end + ComPtr temp; + THROW_IF_FAILED(utr->Clone(&temp)); + target = static_cast(temp.Get()); + THROW_IF_FAILED(target->MoveEndpointByRange(TextPatternRangeEndpoint_Start, + target.Get(), + TextPatternRangeEndpoint_End)); + VERIFY_ARE_EQUAL(target->GetEndpoint(TextPatternRangeEndpoint_Start), utr->GetEndpoint(TextPatternRangeEndpoint_End)); + VERIFY_ARE_EQUAL(target->GetEndpoint(TextPatternRangeEndpoint_End), utr->GetEndpoint(TextPatternRangeEndpoint_End)); + + // Move end to start + THROW_IF_FAILED(utr->Clone(&temp)); + target = static_cast(temp.Get()); + THROW_IF_FAILED(target->MoveEndpointByRange(TextPatternRangeEndpoint_End, + target.Get(), + TextPatternRangeEndpoint_Start)); + VERIFY_ARE_EQUAL(target->GetEndpoint(TextPatternRangeEndpoint_Start), utr->GetEndpoint(TextPatternRangeEndpoint_Start)); + VERIFY_ARE_EQUAL(target->GetEndpoint(TextPatternRangeEndpoint_End), utr->GetEndpoint(TextPatternRangeEndpoint_Start)); + } + + Log::Comment(L"Cross endpoints (force degenerate range)"); + { + // move start past end + resetTargetUTR(); + THROW_IF_FAILED(target->MoveEndpointByRange(TextPatternRangeEndpoint_Start, + utr.Get(), + TextPatternRangeEndpoint_End)); + VERIFY_ARE_EQUAL(target->GetEndpoint(TextPatternRangeEndpoint_Start), utr->GetEndpoint(TextPatternRangeEndpoint_End)); + VERIFY_ARE_EQUAL(target->GetEndpoint(TextPatternRangeEndpoint_End), utr->GetEndpoint(TextPatternRangeEndpoint_End)); + VERIFY_IS_TRUE(target->IsDegenerate()); + + // move end past start + THROW_IF_FAILED(target->MoveEndpointByRange(TextPatternRangeEndpoint_End, + utr.Get(), + TextPatternRangeEndpoint_Start)); + VERIFY_ARE_EQUAL(target->GetEndpoint(TextPatternRangeEndpoint_Start), utr->GetEndpoint(TextPatternRangeEndpoint_Start)); + VERIFY_ARE_EQUAL(target->GetEndpoint(TextPatternRangeEndpoint_End), utr->GetEndpoint(TextPatternRangeEndpoint_Start)); + VERIFY_IS_TRUE(target->IsDegenerate()); + } + } + TEST_METHOD(CanMoveByCharacter) { const SHORT lastColumnIndex = _pScreenInfo->GetBufferSize().Width() - 1; @@ -261,7 +597,7 @@ class UiaTextRangeTests Log::Comment(test.comment.data()); int amountMoved; - Microsoft::WRL::MakeAndInitialize(&utr, _pUiaData, &_dummyProvider, test.start, test.end); + THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize(&utr, _pUiaData, &_dummyProvider, test.start, test.end)); utr->Move(TextUnit::TextUnit_Character, test.moveAmt, &amountMoved); VERIFY_ARE_EQUAL(test.expected.moveAmt, amountMoved); @@ -374,8 +710,8 @@ class UiaTextRangeTests Log::Comment(test.comment.data()); int amountMoved; - Microsoft::WRL::MakeAndInitialize(&utr, _pUiaData, &_dummyProvider, test.start, test.end); - utr->Move(TextUnit::TextUnit_Word, test.moveAmt, &amountMoved); + THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize(&utr, _pUiaData, &_dummyProvider, test.start, test.end)); + THROW_IF_FAILED(utr->Move(TextUnit::TextUnit_Word, test.moveAmt, &amountMoved)); VERIFY_ARE_EQUAL(test.expected.moveAmt, amountMoved); VERIFY_ARE_EQUAL(test.expected.start, utr->_start); @@ -442,8 +778,8 @@ class UiaTextRangeTests Log::Comment(test.comment.data()); int amountMoved; - Microsoft::WRL::MakeAndInitialize(&utr, _pUiaData, &_dummyProvider, test.start, test.end); - utr->Move(TextUnit::TextUnit_Word, test.moveAmt, &amountMoved); + THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize(&utr, _pUiaData, &_dummyProvider, test.start, test.end)); + THROW_IF_FAILED(utr->Move(TextUnit::TextUnit_Word, test.moveAmt, &amountMoved)); VERIFY_ARE_EQUAL(test.expected.moveAmt, amountMoved); VERIFY_ARE_EQUAL(test.expected.start, utr->_start); @@ -555,8 +891,8 @@ class UiaTextRangeTests Log::Comment(test.comment.data()); int amountMoved; - Microsoft::WRL::MakeAndInitialize(&utr, _pUiaData, &_dummyProvider, test.start, test.end); - utr->Move(TextUnit::TextUnit_Line, test.moveAmt, &amountMoved); + THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize(&utr, _pUiaData, &_dummyProvider, test.start, test.end)); + THROW_IF_FAILED(utr->Move(TextUnit::TextUnit_Line, test.moveAmt, &amountMoved)); VERIFY_ARE_EQUAL(test.expected.moveAmt, amountMoved); VERIFY_ARE_EQUAL(test.expected.start, utr->_start); @@ -594,7 +930,7 @@ class UiaTextRangeTests {0, 0}, {lastColumnIndex, 0}, -1, - TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, + TextPatternRangeEndpoint_Start, { 0, {0, 0}, @@ -607,7 +943,7 @@ class UiaTextRangeTests {3, 0}, {lastColumnIndex, 0}, -5, - TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, + TextPatternRangeEndpoint_Start, { -3, {0, 0}, @@ -620,7 +956,7 @@ class UiaTextRangeTests {0, 0}, {4, 0}, -5, - TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, + TextPatternRangeEndpoint_End, { -4, {0, 0}, @@ -633,7 +969,7 @@ class UiaTextRangeTests {5, 0}, {10, 0}, -7, - TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, + TextPatternRangeEndpoint_End, { -7, {3, 0}, @@ -646,7 +982,7 @@ class UiaTextRangeTests {0, bottomRow}, {0, bottomRow+1}, 1, - TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, + TextPatternRangeEndpoint_End, { 0, {0, bottomRow}, @@ -659,7 +995,7 @@ class UiaTextRangeTests {0, 0}, {lastColumnIndex - 3, bottomRow}, 5, - TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, + TextPatternRangeEndpoint_End, { 4, {0, 0}, @@ -672,7 +1008,7 @@ class UiaTextRangeTests {lastColumnIndex - 4, bottomRow}, {0, bottomRow+1}, 5, - TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, + TextPatternRangeEndpoint_Start, { 5, {0, bottomRow+1}, @@ -685,7 +1021,7 @@ class UiaTextRangeTests {5, 0}, {10, 0}, 7, - TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, + TextPatternRangeEndpoint_Start, { 7, {12, 0}, @@ -701,11 +1037,8 @@ class UiaTextRangeTests Log::Comment(test.comment.data()); int amountMoved; - //if (test.comment == L"can't move _end past the beginning of the document when _end is positioned at the end") - // DebugBreak(); - - Microsoft::WRL::MakeAndInitialize(&utr, _pUiaData, &_dummyProvider, test.start, test.end); - utr->MoveEndpointByUnit(test.endpoint, TextUnit::TextUnit_Character, test.moveAmt, &amountMoved); + THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize(&utr, _pUiaData, &_dummyProvider, test.start, test.end)); + THROW_IF_FAILED(utr->MoveEndpointByUnit(test.endpoint, TextUnit::TextUnit_Character, test.moveAmt, &amountMoved)); VERIFY_ARE_EQUAL(test.expected.moveAmt, amountMoved); VERIFY_ARE_EQUAL(test.expected.start, utr->_start); @@ -942,7 +1275,7 @@ class UiaTextRangeTests {0, 0}, {lastColumnIndex, 0}, 1, - TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, + TextPatternRangeEndpoint_End, 1, {0, 0}, {0, 1} @@ -953,7 +1286,7 @@ class UiaTextRangeTests {0, 1}, {lastColumnIndex, 5}, -2, - TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, + TextPatternRangeEndpoint_End, -2, {0, 1}, {0, 4} @@ -964,7 +1297,7 @@ class UiaTextRangeTests {0, 1}, {lastColumnIndex, 5}, 2, - TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, + TextPatternRangeEndpoint_Start, 2, {0, 3}, {lastColumnIndex, 5} @@ -975,7 +1308,7 @@ class UiaTextRangeTests {0, 2}, {lastColumnIndex, 5}, -1, - TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, + TextPatternRangeEndpoint_Start, -1, {0, 1}, {lastColumnIndex, 5} @@ -986,7 +1319,7 @@ class UiaTextRangeTests {lastColumnIndex, 0}, {lastColumnIndex, 0}, -1, - TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, + TextPatternRangeEndpoint_Start, -1, {0, 0}, {lastColumnIndex, 0}, @@ -997,7 +1330,7 @@ class UiaTextRangeTests {0, 0}, {lastColumnIndex, 0}, -1, - TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, + TextPatternRangeEndpoint_Start, 0, {0, 0}, {lastColumnIndex, 0} @@ -1008,7 +1341,7 @@ class UiaTextRangeTests {0, 0}, {lastColumnIndex - 3, bottomRow}, 1, - TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, + TextPatternRangeEndpoint_End, 1, {0, 0}, {0, bottomRow+1} @@ -1019,7 +1352,7 @@ class UiaTextRangeTests {0, 0}, {0, bottomRow+1}, 1, - TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, + TextPatternRangeEndpoint_End, 0, {0, 0}, {0, bottomRow+1} @@ -1030,7 +1363,7 @@ class UiaTextRangeTests {0, bottomRow}, {lastColumnIndex, bottomRow}, 1, - TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, + TextPatternRangeEndpoint_Start, 1, {0, bottomRow+1}, {0, bottomRow+1} @@ -1041,7 +1374,7 @@ class UiaTextRangeTests {4, 0}, {lastColumnIndex - 5, 0}, -1, - TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, + TextPatternRangeEndpoint_End, -1, {0, 0}, {0, 0} @@ -1055,8 +1388,8 @@ class UiaTextRangeTests Log::Comment(test.comment.data()); int amountMoved; - Microsoft::WRL::MakeAndInitialize(&utr, _pUiaData, &_dummyProvider, test.start, test.end); - utr->MoveEndpointByUnit(test.endpoint, TextUnit::TextUnit_Line, test.moveAmt, &amountMoved); + THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize(&utr, _pUiaData, &_dummyProvider, test.start, test.end)); + THROW_IF_FAILED(utr->MoveEndpointByUnit(test.endpoint, TextUnit::TextUnit_Line, test.moveAmt, &amountMoved)); VERIFY_ARE_EQUAL(test.expected.moveAmt, amountMoved); VERIFY_ARE_EQUAL(test.expected.start, utr->_start); @@ -1094,7 +1427,7 @@ class UiaTextRangeTests {0, 4}, {0, 4}, 1, - TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, + TextPatternRangeEndpoint_End, { 1, {0, 4}, @@ -1107,7 +1440,7 @@ class UiaTextRangeTests {0, 4}, {0, 4}, -1, - TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, + TextPatternRangeEndpoint_Start, { -1, {0, 0}, @@ -1120,7 +1453,7 @@ class UiaTextRangeTests {3, 2}, {0, bottomRow+1}, 1, - TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, + TextPatternRangeEndpoint_End, { 0, {3, 2}, @@ -1133,7 +1466,7 @@ class UiaTextRangeTests {0, 0}, {5, 6}, -1, - TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, + TextPatternRangeEndpoint_Start, { 0, {0, 0}, @@ -1146,7 +1479,7 @@ class UiaTextRangeTests {5, 2}, {5, 6}, -1, - TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, + TextPatternRangeEndpoint_End, { -1, {0, 0}, @@ -1159,7 +1492,7 @@ class UiaTextRangeTests {5, 2}, {5, 6}, 1, - TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, + TextPatternRangeEndpoint_Start, { 1, {0, bottomRow+1}, @@ -1175,8 +1508,8 @@ class UiaTextRangeTests Log::Comment(test.comment.c_str()); int amountMoved; - Microsoft::WRL::MakeAndInitialize(&utr, _pUiaData, &_dummyProvider, test.start, test.end); - utr->MoveEndpointByUnit(test.endpoint, TextUnit::TextUnit_Document, test.moveAmt, &amountMoved); + THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize(&utr, _pUiaData, &_dummyProvider, test.start, test.end)); + THROW_IF_FAILED(utr->MoveEndpointByUnit(test.endpoint, TextUnit::TextUnit_Document, test.moveAmt, &amountMoved)); VERIFY_ARE_EQUAL(test.expected.moveAmt, amountMoved); VERIFY_ARE_EQUAL(test.expected.start, utr->_start); diff --git a/src/types/UiaTextRangeBase.cpp b/src/types/UiaTextRangeBase.cpp index 1088902effb..6b8dd10586c 100644 --- a/src/types/UiaTextRangeBase.cpp +++ b/src/types/UiaTextRangeBase.cpp @@ -751,17 +751,7 @@ IFACEMETHODIMP UiaTextRangeBase::MoveEndpointByRange(_In_ TextPatternRangeEndpoi OutputDebugString(L"\n"); #endif - switch (endpoint) - { - case TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start: - _start = range->GetEndpoint(targetEndpoint); - break; - case TextPatternRangeEndpoint::TextPatternRangeEndpoint_End: - _end = range->GetEndpoint(targetEndpoint); - break; - default: - return E_INVALIDARG; - } + SetEndpoint(endpoint, range->GetEndpoint(targetEndpoint)); // TODO GitHub #1914: Re-attach Tracing to UIA Tree //Tracing::s_TraceUia(this, ApiCall::MoveEndpointByRange, &apiMsg); From bcfc52245b91e67974d6ad3d91fe910a7347165e Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Wed, 22 Jan 2020 10:35:48 -0800 Subject: [PATCH 09/25] Turns out, the bug is in our wrapper class. Not supposed to return nullptr when attribute not found. Don't know why this is an issue _now_ --- .../TerminalControl/XamlUiaTextRange.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/cascadia/TerminalControl/XamlUiaTextRange.cpp b/src/cascadia/TerminalControl/XamlUiaTextRange.cpp index 80a9b623d40..966e0c79068 100644 --- a/src/cascadia/TerminalControl/XamlUiaTextRange.cpp +++ b/src/cascadia/TerminalControl/XamlUiaTextRange.cpp @@ -24,6 +24,18 @@ namespace XamlAutomation namespace winrt::Microsoft::Terminal::TerminalControl::implementation { + // EmptyObject is our equivalent of UIAutomationCore::UiaGetReservedNotSupportedValue() + // This retrieves a value that is interpreted as "not supported". + class EmptyObject : public winrt::implements + { + public: + static IInspectable GetInstance() + { + static auto eo = make_self(); + return eo.as(); + } + }; + XamlAutomation::ITextRangeProvider XamlUiaTextRange::Clone() const { UIA::ITextRangeProvider* pReturn; @@ -88,7 +100,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation } else { - return nullptr; + // We _need_ to return an empty object here. + // Returning nullptr is an improper implementation of it being unsupported. + return EmptyObject::GetInstance();//*EmptyObject::GetInstance(); } } From 71d50cb936995725e6372dd94417153b7c80a75c Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Wed, 22 Jan 2020 10:42:52 -0800 Subject: [PATCH 10/25] - Code format - disable debug log --- .../TerminalControl/XamlUiaTextRange.cpp | 2 +- src/interactivity/win32/uiaTextRange.cpp | 2 +- .../UiaTextRangeTests.cpp | 2 +- src/types/UiaTextRangeBase.cpp | 24 ++++++++++++------- 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/cascadia/TerminalControl/XamlUiaTextRange.cpp b/src/cascadia/TerminalControl/XamlUiaTextRange.cpp index 966e0c79068..25289bc92c6 100644 --- a/src/cascadia/TerminalControl/XamlUiaTextRange.cpp +++ b/src/cascadia/TerminalControl/XamlUiaTextRange.cpp @@ -102,7 +102,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation { // We _need_ to return an empty object here. // Returning nullptr is an improper implementation of it being unsupported. - return EmptyObject::GetInstance();//*EmptyObject::GetInstance(); + return EmptyObject::GetInstance(); } } diff --git a/src/interactivity/win32/uiaTextRange.cpp b/src/interactivity/win32/uiaTextRange.cpp index bbf65ab794f..54ade18fb13 100644 --- a/src/interactivity/win32/uiaTextRange.cpp +++ b/src/interactivity/win32/uiaTextRange.cpp @@ -138,7 +138,7 @@ IFACEMETHODIMP UiaTextRange::FindText(_In_ BSTR text, const auto start = foundLocation.first; const auto end = foundLocation.second; const auto bufferSize = _pData->GetTextBuffer().GetSize(); - + // make sure what was found is within the bounds of the current range if ((searchDirection == Search::Direction::Forward && bufferSize.CompareInBounds(end, _end) < 0) || (searchDirection == Search::Direction::Backward && bufferSize.CompareInBounds(start, _start) > 0)) diff --git a/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp b/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp index ac0e375174e..59880d9707d 100644 --- a/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp +++ b/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp @@ -202,7 +202,7 @@ class UiaTextRangeTests Log::Comment(L"For a degenerate range, comparing _start and _end should return 0"); VERIFY_IS_TRUE(utr1->IsDegenerate()); THROW_IF_FAILED(utr1->CompareEndpoints(TextPatternRangeEndpoint_Start, utr1.Get(), TextPatternRangeEndpoint_End, &comparison)); - + Log::Comment(L"_start and _end should match"); THROW_IF_FAILED(utr1->CompareEndpoints(TextPatternRangeEndpoint_Start, utr2.Get(), TextPatternRangeEndpoint_Start, &comparison)); VERIFY_IS_TRUE(comparison == 0); diff --git a/src/types/UiaTextRangeBase.cpp b/src/types/UiaTextRangeBase.cpp index 6b8dd10586c..1bcf669a35b 100644 --- a/src/types/UiaTextRangeBase.cpp +++ b/src/types/UiaTextRangeBase.cpp @@ -9,9 +9,9 @@ using namespace Microsoft::Console::Types; using namespace Microsoft::Console::Types::UiaTextRangeBaseTracing; // toggle these for additional logging in a debug build -#define _DEBUG 1 -#define UIATEXTRANGE_DEBUG_MSGS 1 -//#undef UIATEXTRANGE_DEBUG_MSGS +//#define _DEBUG 1 +//#define UIATEXTRANGE_DEBUG_MSGS 1 +#undef UIATEXTRANGE_DEBUG_MSGS IdType UiaTextRangeBase::id = 1; @@ -1065,7 +1065,8 @@ void UiaTextRangeBase::_moveEndpointByUnitWord(_In_ const int moveCount, nextPos = resultPos; switch (moveDirection) { - case MovementDirection::Forward: { + case MovementDirection::Forward: + { if (nextPos == bufferEnd) { fSuccess = false; @@ -1082,7 +1083,8 @@ void UiaTextRangeBase::_moveEndpointByUnitWord(_In_ const int moveCount, } break; } - case MovementDirection::Backward: { + case MovementDirection::Backward: + { if (nextPos == bufferOrigin) { fSuccess = false; @@ -1142,7 +1144,8 @@ void UiaTextRangeBase::_moveEndpointByUnitLine(_In_ const int moveCount, auto nextPos = resultPos; switch (moveDirection) { - case MovementDirection::Forward: { + case MovementDirection::Forward: + { // can't move past end if (nextPos.Y >= bufferSize.BottomInclusive()) { @@ -1162,7 +1165,8 @@ void UiaTextRangeBase::_moveEndpointByUnitLine(_In_ const int moveCount, } break; } - case MovementDirection::Backward: { + case MovementDirection::Backward: + { // can't move past top if (!allowBottomExclusive && nextPos.Y == bufferSize.Top()) { @@ -1219,7 +1223,8 @@ void UiaTextRangeBase::_moveEndpointByUnitDocument(_In_ const int moveCount, auto target = GetEndpoint(endpoint); switch (moveDirection) { - case MovementDirection::Forward: { + case MovementDirection::Forward: + { const auto documentEnd = bufferSize.EndInclusive(); if (preventBufferEnd || target == documentEnd) { @@ -1232,7 +1237,8 @@ void UiaTextRangeBase::_moveEndpointByUnitDocument(_In_ const int moveCount, } break; } - case MovementDirection::Backward: { + case MovementDirection::Backward: + { const auto documentBegin = bufferSize.Origin(); if (target == documentBegin) { From c075ab2faf1c68dd894e5c988a98b5dfddc4dcdd Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Mon, 27 Jan 2020 13:39:25 -0800 Subject: [PATCH 11/25] Finish fixing NVDA issues with UiaTextRange (scoped for this PR) Narrator's word navigation has regressed. This PR will not be ready until that is fixed. --- src/buffer/out/textBuffer.cpp | 213 ++++++++++++------ src/buffer/out/textBuffer.hpp | 5 +- .../TerminalControl/XamlUiaTextRange.cpp | 6 +- src/host/ut_host/TextBufferTests.cpp | 26 +-- src/types/UiaTextRangeBase.cpp | 9 +- 5 files changed, 172 insertions(+), 87 deletions(-) diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp index 552b1f56d95..5ce4badc387 100644 --- a/src/buffer/out/textBuffer.cpp +++ b/src/buffer/out/textBuffer.cpp @@ -958,8 +958,8 @@ Microsoft::Console::Render::IRenderTarget& TextBuffer::GetRenderTarget() noexcep // - wordDelimiters - what characters are we considering for the separation of words // - accessibilityMode - when enabled, we continue expanding left until we are at the beginning of a readable word // Return Value: -// - The COORD for the first character on the "word" (inclusive) -const COORD TextBuffer::GetWordStart(const COORD target, const std::wstring_view wordDelimiters, bool /*accessibilityMode*/) const +// - The COORD for the first character on the "word" (inclusive) +const COORD TextBuffer::GetWordStart(const COORD target, const std::wstring_view wordDelimiters, bool accessibilityMode) const { const auto bufferSize = GetSize(); COORD result = target; @@ -971,35 +971,64 @@ const COORD TextBuffer::GetWordStart(const COORD target, const std::wstring_view } auto bufferIterator = GetTextDataAt(result); - const auto initialDelimiter = _GetDelimiterClass(*bufferIterator, wordDelimiters); - while (result.X > bufferSize.Left() && (_GetDelimiterClass(*bufferIterator, wordDelimiters) == initialDelimiter)) + + if (accessibilityMode) { - bufferSize.DecrementInBounds(result); - --bufferIterator; - } + bool stayAtOrigin = false; - //if (accessibilityMode) - //{ - // // make sure we expand to the beggining of the word - // if (_GetDelimiterClass(*bufferIterator, wordDelimiters) == DelimiterClass::RegularChar) - // { - // while (result.X > bufferSize.Left() && (_GetDelimiterClass(*bufferIterator, wordDelimiters) == DelimiterClass::RegularChar)) - // { - // bufferSize.DecrementInBounds(result); - // --bufferIterator; - // } - // } - - // // move back onto word start - // if (result.X != bufferSize.Left() && _GetDelimiterClass(*bufferIterator, wordDelimiters) != DelimiterClass::RegularChar) - // { - // bufferSize.IncrementInBounds(result); - // } - //} - if (_GetDelimiterClass(*bufferIterator, wordDelimiters) != initialDelimiter) + // ignore left boundary. Continue until readable text found + while (_GetDelimiterClass(*bufferIterator, wordDelimiters) != DelimiterClass::RegularChar) + { + if (bufferSize.DecrementInBounds(result)) + { + --bufferIterator; + } + else + { + // first char in buffer is a DelimiterChar or ControlChar + // we can't move any further back + stayAtOrigin = true; + break; + } + } + + // make sure we expand to the left boundary or the beginning of the word + while (_GetDelimiterClass(*bufferIterator, wordDelimiters) == DelimiterClass::RegularChar) + { + if (bufferSize.DecrementInBounds(result)) + { + --bufferIterator; + } + else + { + // first char in buffer is a RegularChar + // we can't move any further back + break; + } + } + + // move off of delimiter and onto word start + if (!stayAtOrigin && _GetDelimiterClass(*bufferIterator, wordDelimiters) != DelimiterClass::RegularChar) + { + bufferSize.IncrementInBounds(result); + } + } + else { - // move off of delimiter - bufferSize.IncrementInBounds(result); + const auto initialDelimiter = _GetDelimiterClass(*bufferIterator, wordDelimiters); + + // expand left until we hit the left boundary or a different delimiter class + while (result.X > bufferSize.Left() && (_GetDelimiterClass(*bufferIterator, wordDelimiters) == initialDelimiter)) + { + bufferSize.DecrementInBounds(result); + --bufferIterator; + } + + if (_GetDelimiterClass(*bufferIterator, wordDelimiters) != initialDelimiter) + { + // move off of delimiter + bufferSize.IncrementInBounds(result); + } } return result; @@ -1015,81 +1044,124 @@ const COORD TextBuffer::GetWordStart(const COORD target, const std::wstring_view // - The COORD for the last character on the "word" (inclusive) const COORD TextBuffer::GetWordEnd(const COORD target, const std::wstring_view wordDelimiters, bool accessibilityMode) const { + // Consider a buffer with this text in it: + // " word other " + // In selection (accessibilityMode = false), + // a "word" is defined as the range between two delimiters + // so the words in the example include [" ", "word", " ", "other", " "] + // In accessibility (accessibilityMode = true), + // a "word" includes the delimiters after a range of readable characters + // so the words in the example include ["word ", "other "] + // NOTE: the end anchor (this one) is exclusive + const auto bufferSize = GetSize(); COORD result = target; + auto bufferIterator = GetTextDataAt(result); - // can't expand right - if (target.X == bufferSize.RightInclusive()) + if (accessibilityMode) { - if (accessibilityMode) - { - // NOTE: this may be out of bounds. We don't care in accessibility mode - return { bufferSize.Left(), target.Y + 1 }; - } - else + // ignore right boundary. Continue through readable text found + while (_GetDelimiterClass(*bufferIterator, wordDelimiters) == DelimiterClass::RegularChar) { - return result; + if (bufferSize.IncrementInBounds(result, true)) + { + ++bufferIterator; + } + else + { + // last char in buffer is a RegularChar + // we can't move any further forward + break; + } } - } - auto bufferIterator = GetTextDataAt(result); - const auto initialDelimiter = _GetDelimiterClass(*bufferIterator, wordDelimiters); - while (result.X < bufferSize.RightInclusive() && (_GetDelimiterClass(*bufferIterator, wordDelimiters) == initialDelimiter)) - { - bufferSize.IncrementInBounds(result); - ++bufferIterator; - } - - if (accessibilityMode) - { // make sure we expand to the beginning of the NEXT word - if (_GetDelimiterClass(*bufferIterator, wordDelimiters) != DelimiterClass::RegularChar) + while (_GetDelimiterClass(*bufferIterator, wordDelimiters) != DelimiterClass::RegularChar) { - while (result.X < bufferSize.RightInclusive() && (_GetDelimiterClass(*bufferIterator, wordDelimiters) != DelimiterClass::RegularChar)) + if (bufferSize.IncrementInBounds(result, true)) { - bufferSize.IncrementInBounds(result); ++bufferIterator; } + else + { + // we are at the EndInclusive COORD + // this signifies that we must include the last char in the buffer + // but the position of the COORD points to nothing + break; + } + } + } + else + { + // can't expand right + if (target.X == bufferSize.RightInclusive()) + { + return result; } - // handle being at the right boundary - if (result.X == bufferSize.RightInclusive()) + const auto initialDelimiter = _GetDelimiterClass(*bufferIterator, wordDelimiters); + + // expand right until we hit the right boundary or a different delimiter class + while (result.X < bufferSize.RightInclusive() && (_GetDelimiterClass(*bufferIterator, wordDelimiters) == initialDelimiter)) { bufferSize.IncrementInBounds(result); + ++bufferIterator; + } + + if (_GetDelimiterClass(*bufferIterator, wordDelimiters) != initialDelimiter) + { + // move off of delimiter + bufferSize.DecrementInBounds(result); } - } - else if (_GetDelimiterClass(*bufferIterator, wordDelimiters) != initialDelimiter) - { - // move off of delimiter - bufferSize.DecrementInBounds(result); } return result; } -bool TextBuffer::MoveToNextWord(COORD& pos, std::wstring_view wordDelimiters) const +// Method Description: +// - Update pos to be the position of the first character of the next word. This is used for accessibility +// Arguments: +// - pos - a COORD on the word you are currently on +// - wordDelimiters - what characters are we considering for the separation of words +// - lastCharPos - the position of the last nonspace character in the text buffer (to improve performance) +// Return Value: +// - true, if successfully updated pos. False, if we are unable to move (usually due to a buffer boundary) +// - pos - The COORD for the first character on the "word" (inclusive) +bool TextBuffer::MoveToNextWord(COORD& pos, const std::wstring_view wordDelimiters, COORD lastCharPos) const { auto copy = pos; - auto bufferSize = GetSize(); + const auto bufferSize = GetSize(); auto text = GetTextDataAt(copy)->data(); auto delimiterClass = _GetDelimiterClass(text, wordDelimiters); + // started on a word, continue until the end of the word while (delimiterClass == DelimiterClass::RegularChar) { if (!bufferSize.IncrementInBounds(copy)) { + // last char in buffer is a RegularChar + // thus there is no next word return false; } text = GetTextDataAt(copy)->data(); delimiterClass = _GetDelimiterClass(text, wordDelimiters); } + // we are already on/past the last RegularChar + //const auto lastCharPos = GetLastNonSpaceCharacter(); + if (bufferSize.CompareInBounds(copy, lastCharPos) >= 0) + { + return false; + } + // on whitespace, continue until the beginning of the next word while (delimiterClass != DelimiterClass::RegularChar) { if (!bufferSize.IncrementInBounds(copy)) { + // last char in buffer is a DelimiterChar or ControlChar + // there is no next word return false; } text = GetTextDataAt(copy)->data(); @@ -1101,6 +1173,14 @@ bool TextBuffer::MoveToNextWord(COORD& pos, std::wstring_view wordDelimiters) co return true; } +// Method Description: +// - Update pos to be the position of the first character of the previous word. This is used for accessibility +// Arguments: +// - pos - a COORD on the word you are currently on +// - wordDelimiters - what characters are we considering for the separation of words +// Return Value: +// - true, if successfully updated pos. False, if we are unable to move (usually due to a buffer boundary) +// - pos - The COORD for the first character on the "word" (inclusive) bool TextBuffer::MoveToPreviousWord(COORD& pos, std::wstring_view wordDelimiters) const { auto copy = pos; @@ -1108,22 +1188,27 @@ bool TextBuffer::MoveToPreviousWord(COORD& pos, std::wstring_view wordDelimiters auto text = GetTextDataAt(copy)->data(); auto delimiterClass = _GetDelimiterClass(text, wordDelimiters); - // started on a word, continue until the beginning of the word - while (delimiterClass == DelimiterClass::RegularChar) + + // started on whitespace/delimiter, continue until the end of the previous word + while (delimiterClass != DelimiterClass::RegularChar) { if (!bufferSize.DecrementInBounds(copy)) { + // first char in buffer is a DelimiterChar or ControlChar + // there is no previous word return false; } text = GetTextDataAt(copy)->data(); delimiterClass = _GetDelimiterClass(text, wordDelimiters); } - // on whitespace, continue until the end of the previous word - while (delimiterClass != DelimiterClass::RegularChar) + // on a word, continue until the beginning of the word + while (delimiterClass == DelimiterClass::RegularChar) { if (!bufferSize.DecrementInBounds(copy)) { + // first char in buffer is a RegularChar + // there is no previous word return false; } text = GetTextDataAt(copy)->data(); diff --git a/src/buffer/out/textBuffer.hpp b/src/buffer/out/textBuffer.hpp index cff38da3605..1f42a5b3cd4 100644 --- a/src/buffer/out/textBuffer.hpp +++ b/src/buffer/out/textBuffer.hpp @@ -132,9 +132,8 @@ class TextBuffer final const COORD GetWordStart(const COORD target, const std::wstring_view wordDelimiters, bool accessibilityMode = false) const; const COORD GetWordEnd(const COORD target, const std::wstring_view wordDelimiters, bool accessibilityMode = false) const; - bool MoveToNextWord(COORD& pos, std::wstring_view wordDelimiters) const; - bool MoveToPreviousWord(COORD& pos, std::wstring_view wordDelimiters) const; - const bool IsCharacterAtOrigin() const; + bool MoveToNextWord(COORD& pos, const std::wstring_view wordDelimiters, COORD lastCharPos) const; + bool MoveToPreviousWord(COORD& pos, const std::wstring_view wordDelimiters) const; class TextAndColor { diff --git a/src/cascadia/TerminalControl/XamlUiaTextRange.cpp b/src/cascadia/TerminalControl/XamlUiaTextRange.cpp index 25289bc92c6..a2bf8e592a6 100644 --- a/src/cascadia/TerminalControl/XamlUiaTextRange.cpp +++ b/src/cascadia/TerminalControl/XamlUiaTextRange.cpp @@ -31,8 +31,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation public: static IInspectable GetInstance() { - static auto eo = make_self(); - return eo.as(); + static auto empty = make_self(); + return *empty; } }; @@ -88,7 +88,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // TODO GitHub #605: Search functionality // we need to wrap this around the UiaTextRange FindText() function // but right now it returns E_NOTIMPL, so let's just return nullptr for now. - throw winrt::hresult_not_implemented(); + return nullptr; } winrt::Windows::Foundation::IInspectable XamlUiaTextRange::GetAttributeValue(int32_t textAttributeId) const diff --git a/src/host/ut_host/TextBufferTests.cpp b/src/host/ut_host/TextBufferTests.cpp index 21f14ef7eb4..69030e197f5 100644 --- a/src/host/ut_host/TextBufferTests.cpp +++ b/src/host/ut_host/TextBufferTests.cpp @@ -2065,7 +2065,7 @@ void TextBufferTests::GetWordStart() // tests for second line of text { { 0, 1 }, {{ 0, 1 }, { 0, 1 }} }, - { { 1, 1 }, {{ 0, 1 }, { 0, 1 }} }, + { { 1, 1 }, {{ 0, 1 }, { 5, 0 }} }, { { 2, 1 }, {{ 2, 1 }, { 2, 1 }} }, { { 3, 1 }, {{ 2, 1 }, { 2, 1 }} }, { { 5, 1 }, {{ 2, 1 }, { 2, 1 }} }, @@ -2085,11 +2085,11 @@ void TextBufferTests::GetWordStart() COORD result; // Test with accessibilityMode = false - result = _buffer->GetWordStart(test.startPos, delimiters, false); + result = _buffer->GetWordStart(test.startPos, delimiters, /*accessibilityMode*/ false); VERIFY_ARE_EQUAL(test.expected.accessibilityModeDisabled, result); // Test with accessibilityMode = true - result = _buffer->GetWordStart(test.startPos, delimiters, true); + result = _buffer->GetWordStart(test.startPos, delimiters, /*accessibilityMode*/ true); VERIFY_ARE_EQUAL(test.expected.accessibilityModeEnabled, result); } } @@ -2133,10 +2133,10 @@ void TextBufferTests::GetWordEnd() { { 1, 0 }, {{ 3, 0 }, { 5, 0 }} }, { { 3, 0 }, {{ 3, 0 }, { 5, 0 }} }, { { 4, 0 }, {{ 4, 0 }, { 5, 0 }} }, - { { 5, 0 }, {{ 9, 0 }, { 0, 1 }} }, - { { 6, 0 }, {{ 9, 0 }, { 0, 1 }} }, - { {20, 0 }, {{ 79, 0 }, { 0, 1 }} }, - { {79, 0 }, {{ 79, 0 }, { 0, 1 }} }, + { { 5, 0 }, {{ 9, 0 }, { 2, 1 }} }, + { { 6, 0 }, {{ 9, 0 }, { 2, 1 }} }, + { {20, 0 }, {{ 79, 0 }, { 2, 1 }} }, + { {79, 0 }, {{ 79, 0 }, { 2, 1 }} }, // tests for second line of text { { 0, 1 }, {{ 1, 1 }, { 2, 1 }} }, @@ -2146,10 +2146,10 @@ void TextBufferTests::GetWordEnd() { { 5, 1 }, {{ 5, 1 }, { 9, 1 }} }, { { 6, 1 }, {{ 8, 1 }, { 9, 1 }} }, { { 7, 1 }, {{ 8, 1 }, { 9, 1 }} }, - { { 9, 1 }, {{13, 1 }, { 0, 2 }} }, - { { 10, 1 }, {{13, 1 }, { 0, 2 }} }, - { { 20, 1 }, {{79, 1 }, { 0, 2 }} }, - { { 79, 1 }, {{79, 1 }, { 0, 2 }} }, + { { 9, 1 }, {{13, 1 }, { 0, 9001 }} }, + { { 10, 1 }, {{13, 1 }, { 0, 9001 }} }, + { { 20, 1 }, {{79, 1 }, { 0, 9001 }} }, + { { 79, 1 }, {{79, 1 }, { 0, 9001 }} }, }; // clang-format off @@ -2160,11 +2160,11 @@ void TextBufferTests::GetWordEnd() COORD result; // Test with accessibilityMode = false - result = _buffer->GetWordEnd(test.startPos, delimiters, false); + result = _buffer->GetWordEnd(test.startPos, delimiters, /*accessibilityMode*/ false); VERIFY_ARE_EQUAL(test.expected.accessibilityModeDisabled, result); // Test with accessibilityMode = true - result = _buffer->GetWordEnd(test.startPos, delimiters, true); + result = _buffer->GetWordEnd(test.startPos, delimiters, /*accessibilityMode*/ true); VERIFY_ARE_EQUAL(test.expected.accessibilityModeEnabled, result); } } diff --git a/src/types/UiaTextRangeBase.cpp b/src/types/UiaTextRangeBase.cpp index 1bcf669a35b..989a968ada6 100644 --- a/src/types/UiaTextRangeBase.cpp +++ b/src/types/UiaTextRangeBase.cpp @@ -311,8 +311,8 @@ IFACEMETHODIMP UiaTextRangeBase::ExpandToEnclosingUnit(_In_ TextUnit unit) else if (unit <= TextUnit::TextUnit_Word) { // expand to word - _start = buffer.GetWordStart(_start, _wordDelimiters, true); - _end = buffer.GetWordEnd(_start, _wordDelimiters, true); + _start = buffer.GetWordStart(_start, _wordDelimiters, /*accessibilityMode*/ true); + _end = buffer.GetWordEnd(_start, _wordDelimiters, /*accessibilityMode*/ true); } else if (unit <= TextUnit::TextUnit_Line) { @@ -523,7 +523,7 @@ IFACEMETHODIMP UiaTextRangeBase::GetText(_In_ int maxLength, _Out_ BSTR* pRetVal { currentScreenInfoRow = _start.Y + i; const ROW& row = buffer.GetRowByOffset(currentScreenInfoRow); - if (1) //row.GetCharRow().ContainsText()) + if (row.GetCharRow().ContainsText()) { const size_t rowRight = row.GetCharRow().MeasureRight(); size_t startIndex = 0; @@ -1055,6 +1055,7 @@ void UiaTextRangeBase::_moveEndpointByUnitWord(_In_ const int moveCount, const auto bufferSize = buffer.GetSize(); const auto bufferOrigin = bufferSize.Origin(); const auto bufferEnd = bufferSize.EndInclusive(); + const auto lastCharPos = buffer.GetLastNonSpaceCharacter(); auto resultPos = GetEndpoint(endpoint); auto nextPos = resultPos; @@ -1071,7 +1072,7 @@ void UiaTextRangeBase::_moveEndpointByUnitWord(_In_ const int moveCount, { fSuccess = false; } - else if (buffer.MoveToNextWord(nextPos, _wordDelimiters)) + else if (buffer.MoveToNextWord(nextPos, _wordDelimiters, lastCharPos)) { resultPos = nextPos; *pAmountMoved += 1; From 9ee0d72fd14766132a5f2b44235d9e5e9f42fa55 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Wed, 29 Jan 2020 21:32:12 -0800 Subject: [PATCH 12/25] Good amount of PR Changes --- src/buffer/out/textBuffer.cpp | 261 +++++++++++++++--------- src/buffer/out/textBuffer.hpp | 4 + src/types/ScreenInfoUiaProviderBase.cpp | 4 +- src/types/UiaTextRangeBase.cpp | 79 +++---- src/types/UiaTextRangeBase.hpp | 18 +- src/types/inc/viewport.hpp | 2 +- src/types/viewport.cpp | 6 +- 7 files changed, 221 insertions(+), 153 deletions(-) diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp index dede516d919..63df86e0abd 100644 --- a/src/buffer/out/textBuffer.cpp +++ b/src/buffer/out/textBuffer.cpp @@ -956,81 +956,120 @@ Microsoft::Console::Render::IRenderTarget& TextBuffer::GetRenderTarget() noexcep // Arguments: // - target - a COORD on the word you are currently on // - wordDelimiters - what characters are we considering for the separation of words -// - accessibilityMode - when enabled, we continue expanding left until we are at the beginning of a readable word +// - accessibilityMode - when enabled, we continue expanding left until we are at the beginning of a readable word. +// Otherwise, expand left until a character of a new delimiter class is found +// (or a row boundary is encountered) // Return Value: // - The COORD for the first character on the "word" (inclusive) const COORD TextBuffer::GetWordStart(const COORD target, const std::wstring_view wordDelimiters, bool accessibilityMode) const { - const auto bufferSize = GetSize(); - COORD result = target; + // Consider a buffer with this text in it: + // " word other " + // In selection (accessibilityMode = false), + // a "word" is defined as the range between two delimiters + // so the words in the example include [" ", "word", " ", "other", " "] + // In accessibility (accessibilityMode = true), + // a "word" includes the delimiters after a range of readable characters + // so the words in the example include ["word ", "other "] + // NOTE: the start anchor (this one) is inclusive, whereas the end anchor (GetWordEnd) is exclusive // can't expand left - if (target.X == bufferSize.Left()) + if (target.X == GetSize().Left()) { - return result; + return target; } - auto bufferIterator = GetTextDataAt(result); - if (accessibilityMode) { - bool stayAtOrigin = false; + return _GetWordStartForAccessibility(target, wordDelimiters); + } + else + { + return _GetWordStartForSelection(target, wordDelimiters); + } +} - // ignore left boundary. Continue until readable text found - while (_GetDelimiterClass(*bufferIterator, wordDelimiters) != DelimiterClass::RegularChar) - { - if (bufferSize.DecrementInBounds(result)) - { - --bufferIterator; - } - else - { - // first char in buffer is a DelimiterChar or ControlChar - // we can't move any further back - stayAtOrigin = true; - break; - } - } +// Method Description: +// - Helper method for GetWordStart(). Get the COORD for the beginning of the word (accessibility definition) you are on +// Arguments: +// - target - a COORD on the word you are currently on +// - wordDelimiters - what characters are we considering for the separation of words +// Return Value: +// - The COORD for the first character on the current/previous READABLE "word" (inclusive) +const COORD TextBuffer::_GetWordStartForAccessibility(const COORD target, const std::wstring_view wordDelimiters) const +{ + COORD result = target; + const auto bufferSize = GetSize(); + bool stayAtOrigin = false; + auto bufferIterator = GetTextDataAt(result); - // make sure we expand to the left boundary or the beginning of the word - while (_GetDelimiterClass(*bufferIterator, wordDelimiters) == DelimiterClass::RegularChar) + // ignore left boundary. Continue until readable text found + while (_GetDelimiterClass(*bufferIterator, wordDelimiters) != DelimiterClass::RegularChar) + { + if (bufferSize.DecrementInBounds(result)) { - if (bufferSize.DecrementInBounds(result)) - { - --bufferIterator; - } - else - { - // first char in buffer is a RegularChar - // we can't move any further back - break; - } + --bufferIterator; } - - // move off of delimiter and onto word start - if (!stayAtOrigin && _GetDelimiterClass(*bufferIterator, wordDelimiters) != DelimiterClass::RegularChar) + else { - bufferSize.IncrementInBounds(result); + // first char in buffer is a DelimiterChar or ControlChar + // we can't move any further back + stayAtOrigin = true; + break; } } - else - { - const auto initialDelimiter = _GetDelimiterClass(*bufferIterator, wordDelimiters); - // expand left until we hit the left boundary or a different delimiter class - while (result.X > bufferSize.Left() && (_GetDelimiterClass(*bufferIterator, wordDelimiters) == initialDelimiter)) + // make sure we expand to the left boundary or the beginning of the word + while (_GetDelimiterClass(*bufferIterator, wordDelimiters) == DelimiterClass::RegularChar) + { + if (bufferSize.DecrementInBounds(result)) { - bufferSize.DecrementInBounds(result); --bufferIterator; } - - if (_GetDelimiterClass(*bufferIterator, wordDelimiters) != initialDelimiter) + else { - // move off of delimiter - bufferSize.IncrementInBounds(result); + // first char in buffer is a RegularChar + // we can't move any further back + break; } } + // move off of delimiter and onto word start + if (!stayAtOrigin && _GetDelimiterClass(*bufferIterator, wordDelimiters) != DelimiterClass::RegularChar) + { + bufferSize.IncrementInBounds(result); + } + + return result; +} + +// Method Description: +// - Helper method for GetWordStart(). Get the COORD for the beginning of the word (selection definition) you are on +// Arguments: +// - target - a COORD on the word you are currently on +// - wordDelimiters - what characters are we considering for the separation of words +// Return Value: +// - The COORD for the first character on the current word or delimiter run (stopped by the left margin) +const COORD TextBuffer::_GetWordStartForSelection(const COORD target, const std::wstring_view wordDelimiters) const +{ + COORD result = target; + const auto bufferSize = GetSize(); + auto bufferIterator = GetTextDataAt(result); + const auto initialDelimiter = _GetDelimiterClass(*bufferIterator, wordDelimiters); + + // expand left until we hit the left boundary or a different delimiter class + while (result.X > bufferSize.Left() && (_GetDelimiterClass(*bufferIterator, wordDelimiters) == initialDelimiter)) + { + bufferSize.DecrementInBounds(result); + --bufferIterator; + } + + if (_GetDelimiterClass(*bufferIterator, wordDelimiters) != initialDelimiter) + { + // move off of delimiter + bufferSize.IncrementInBounds(result); + } + return result; } @@ -1039,7 +1078,9 @@ const COORD TextBuffer::GetWordStart(const COORD target, const std::wstring_view // Arguments: // - target - a COORD on the word you are currently on // - wordDelimiters - what characters are we considering for the separation of words -// - accessibilityMode - when enabled, we continue expanding right until we are at the BEGINNING of the NEXT readable word +// - accessibilityMode - when enabled, we continue expanding right until we are at the beginning of the next READABLE word +// Otherwise, expand right until a character of a new delimiter class is found +// (or a row boundary is encountered) // Return Value: // - The COORD for the last character on the "word" (inclusive) const COORD TextBuffer::GetWordEnd(const COORD target, const std::wstring_view wordDelimiters, bool accessibilityMode) const @@ -1052,72 +1093,103 @@ const COORD TextBuffer::GetWordEnd(const COORD target, const std::wstring_view w // In accessibility (accessibilityMode = true), // a "word" includes the delimiters after a range of readable characters // so the words in the example include ["word ", "other "] - // NOTE: the end anchor (this one) is exclusive + // NOTE: the end anchor (this one) is exclusive, whereas the start anchor (GetWordStart) is inclusive + if (accessibilityMode) + { + return _GetWordEndForAccessibility(target, wordDelimiters); + } + else + { + return _GetWordEndForSelection(target, wordDelimiters); + } +} + + +// Method Description: +// - Helper method for GetWordEnd(). Get the COORD for the beginning of the next READABLE word +// Arguments: +// - target - a COORD on the word you are currently on +// - wordDelimiters - what characters are we considering for the separation of words +// Return Value: +// - The COORD for the first character of the next readable "word". If no next word, return one past the end of the buffer +const COORD TextBuffer::_GetWordEndForAccessibility(const COORD target, const std::wstring_view wordDelimiters) const +{ const auto bufferSize = GetSize(); COORD result = target; auto bufferIterator = GetTextDataAt(result); - if (accessibilityMode) + // ignore right boundary. Continue through readable text found + while (_GetDelimiterClass(*bufferIterator, wordDelimiters) == DelimiterClass::RegularChar) { - // ignore right boundary. Continue through readable text found - while (_GetDelimiterClass(*bufferIterator, wordDelimiters) == DelimiterClass::RegularChar) + if (bufferSize.IncrementInBounds(result, true)) { - if (bufferSize.IncrementInBounds(result, true)) - { - ++bufferIterator; - } - else - { - // last char in buffer is a RegularChar - // we can't move any further forward - break; - } + ++bufferIterator; } - - // make sure we expand to the beginning of the NEXT word - while (_GetDelimiterClass(*bufferIterator, wordDelimiters) != DelimiterClass::RegularChar) + else { - if (bufferSize.IncrementInBounds(result, true)) - { - ++bufferIterator; - } - else - { - // we are at the EndInclusive COORD - // this signifies that we must include the last char in the buffer - // but the position of the COORD points to nothing - break; - } + // last char in buffer is a RegularChar + // we can't move any further forward + break; } } - else - { - // can't expand right - if (target.X == bufferSize.RightInclusive()) - { - return result; - } - const auto initialDelimiter = _GetDelimiterClass(*bufferIterator, wordDelimiters); - - // expand right until we hit the right boundary or a different delimiter class - while (result.X < bufferSize.RightInclusive() && (_GetDelimiterClass(*bufferIterator, wordDelimiters) == initialDelimiter)) + // make sure we expand to the beginning of the NEXT word + while (_GetDelimiterClass(*bufferIterator, wordDelimiters) != DelimiterClass::RegularChar) + { + if (bufferSize.IncrementInBounds(result, true)) { - bufferSize.IncrementInBounds(result); ++bufferIterator; } - - if (_GetDelimiterClass(*bufferIterator, wordDelimiters) != initialDelimiter) + else { - // move off of delimiter - bufferSize.DecrementInBounds(result); + // we are at the EndInclusive COORD + // this signifies that we must include the last char in the buffer + // but the position of the COORD points to nothing + break; } } return result; } +// Method Description: +// - Helper method for GetWordEnd(). Get the COORD for the beginning of the NEXT word +// Arguments: +// - target - a COORD on the word you are currently on +// - wordDelimiters - what characters are we considering for the separation of words +// Return Value: +// - The COORD for the last character of the current word or delimiter run (stopped by right margin) +const COORD TextBuffer::_GetWordEndForSelection(const COORD target, const std::wstring_view wordDelimiters) const +{ + const auto bufferSize = GetSize(); + COORD result = target; + auto bufferIterator = GetTextDataAt(result); + + // can't expand right + if (target.X == bufferSize.RightInclusive()) + { + return result; + } + + const auto initialDelimiter = _GetDelimiterClass(*bufferIterator, wordDelimiters); + + // expand right until we hit the right boundary or a different delimiter class + while (result.X < bufferSize.RightInclusive() && (_GetDelimiterClass(*bufferIterator, wordDelimiters) == initialDelimiter)) + { + bufferSize.IncrementInBounds(result); + ++bufferIterator; + } + + if (_GetDelimiterClass(*bufferIterator, wordDelimiters) != initialDelimiter) + { + // move off of delimiter + bufferSize.DecrementInBounds(result); + } + + return result; +} + // Method Description: // - Update pos to be the position of the first character of the next word. This is used for accessibility // Arguments: @@ -1149,7 +1221,6 @@ bool TextBuffer::MoveToNextWord(COORD& pos, const std::wstring_view wordDelimite } // we are already on/past the last RegularChar - //const auto lastCharPos = GetLastNonSpaceCharacter(); if (bufferSize.CompareInBounds(copy, lastCharPos) >= 0) { return false; diff --git a/src/buffer/out/textBuffer.hpp b/src/buffer/out/textBuffer.hpp index 21da2b6036f..7400efb353a 100644 --- a/src/buffer/out/textBuffer.hpp +++ b/src/buffer/out/textBuffer.hpp @@ -200,6 +200,10 @@ class TextBuffer final RegularChar }; DelimiterClass _GetDelimiterClass(const std::wstring_view cellChar, const std::wstring_view wordDelimiters) const noexcept; + const COORD _GetWordStartForAccessibility(const COORD target, const std::wstring_view wordDelimiters) const; + const COORD _GetWordStartForSelection(const COORD target, const std::wstring_view wordDelimiters) const; + const COORD _GetWordEndForAccessibility(const COORD target, const std::wstring_view wordDelimiters) const; + const COORD _GetWordEndForSelection(const COORD target, const std::wstring_view wordDelimiters) const; #ifdef UNIT_TESTING friend class TextBufferTests; diff --git a/src/types/ScreenInfoUiaProviderBase.cpp b/src/types/ScreenInfoUiaProviderBase.cpp index ece644ebea3..42d43636f59 100644 --- a/src/types/ScreenInfoUiaProviderBase.cpp +++ b/src/types/ScreenInfoUiaProviderBase.cpp @@ -355,8 +355,8 @@ IFACEMETHODIMP ScreenInfoUiaProviderBase::GetVisibleRanges(_Outptr_result_mayben for (SHORT i = 0; i < rowCount; ++i) { // end is exclusive so add 1 - const COORD start = { viewport.Left(), viewport.Top() + i }; - const COORD end = { viewport.Left(), viewport.Top() + i + 1 }; + const COORD start { viewport.Left(), viewport.Top() + i }; + const COORD end { start.X, start.Y + 1 }; HRESULT hr = S_OK; WRL::ComPtr range; diff --git a/src/types/UiaTextRangeBase.cpp b/src/types/UiaTextRangeBase.cpp index 989a968ada6..aaac594c8b6 100644 --- a/src/types/UiaTextRangeBase.cpp +++ b/src/types/UiaTextRangeBase.cpp @@ -112,7 +112,7 @@ void UiaTextRangeBase::Initialize(_In_ const UiaPoint point) // get row that point resides in const RECT windowRect = _getTerminalRect(); const SMALL_RECT viewport = _pData->GetViewport().ToInclusive(); - SHORT row = 0; + short row = 0; if (clientPoint.y <= windowRect.top) { row = viewport.Top; @@ -129,7 +129,6 @@ void UiaTextRangeBase::Initialize(_In_ const UiaPoint point) const COORD currentFontSize = _getScreenFontSize(); row = gsl::narrow(clientPoint.y / static_cast(currentFontSize.Y)) + viewport.Top; } - // TODO CARLOS: double check that viewport.Top returns the correct text buffer position _start = { 0, row }; _end = _start; } @@ -141,7 +140,7 @@ HRESULT UiaTextRangeBase::RuntimeClassInitialize(const UiaTextRangeBase& a) noex _start = a._start; _end = a._end; _pData = a._pData; - _wordDelimiters = a._wordDelimiters.c_str(); + _wordDelimiters = a._wordDelimiters; _id = id; ++id; @@ -186,15 +185,19 @@ bool UiaTextRangeBase::SetEndpoint(TextPatternRangeEndpoint endpoint, const COOR { case TextPatternRangeEndpoint::TextPatternRangeEndpoint_End: _end = val; + // if end is before start... if (bufferSize.CompareInBounds(_end, _start, true) < 0) { + // make this range degenerate at end _start = _end; } break; case TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start: _start = val; + // if start is after end... if (bufferSize.CompareInBounds(_start, _end, true) > 0) { + // make this range degenerate at start _end = _start; } break; @@ -235,8 +238,8 @@ IFACEMETHODIMP UiaTextRangeBase::Compare(_In_opt_ ITextRangeProvider* pRange, _O const UiaTextRangeBase* other = static_cast(pRange); if (other) { - *pRetVal = !!(_start == other->GetEndpoint(TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start) && - _end == other->GetEndpoint(TextPatternRangeEndpoint::TextPatternRangeEndpoint_End) && + *pRetVal = (_start == other->GetEndpoint(TextPatternRangeEndpoint_Start) && + _end == other->GetEndpoint(TextPatternRangeEndpoint_End) && IsDegenerate() == other->IsDegenerate()); } // TODO GitHub #1914: Re-attach Tracing to UIA Tree @@ -301,7 +304,7 @@ IFACEMETHODIMP UiaTextRangeBase::ExpandToEnclosingUnit(_In_ TextUnit unit) { const auto& buffer = _pData->GetTextBuffer(); const auto bufferSize = buffer.GetSize(); - const auto bufferEnd = bufferSize.EndInclusive(); + const auto bufferEnd = bufferSize.EndExclusive(); if (unit == TextUnit::TextUnit_Character) { @@ -318,14 +321,13 @@ IFACEMETHODIMP UiaTextRangeBase::ExpandToEnclosingUnit(_In_ TextUnit unit) { // expand to line _start.X = 0; - _end.X = 0; - RETURN_IF_FAILED(ShortAdd(_start.Y, static_cast(1), &_end.Y)); + _end.X = base::ClampAdd(_start.Y, 1); } else { // expand to document _start = bufferSize.Origin(); - _end = bufferSize.EndInclusive(); + _end = bufferSize.EndExclusive(); } // TODO GitHub #1914: Re-attach Tracing to UIA Tree @@ -366,7 +368,6 @@ IFACEMETHODIMP UiaTextRangeBase::GetAttributeValue(_In_ TEXTATTRIBUTEID textAttr return S_OK; } -// TODO CARLOS: Completely rewrite this. this will be a bit of a pain :/ IFACEMETHODIMP UiaTextRangeBase::GetBoundingRectangles(_Outptr_result_maybenull_ SAFEARRAY** ppRetVal) { _pData->LockConsole(); @@ -389,7 +390,7 @@ IFACEMETHODIMP UiaTextRangeBase::GetBoundingRectangles(_Outptr_result_maybenull_ // these viewport vars are converted to the buffer coordinate space const auto viewport = bufferSize.ConvertToOrigin(_pData->GetViewport()); const auto viewportOrigin = viewport.Origin(); - const auto viewportEnd = viewport.EndInclusive(); + const auto viewportEnd = viewport.EndExclusive(); // startAnchor: the earliest COORD we will get a bounding rect for auto startAnchor = GetEndpoint(TextPatternRangeEndpoint_Start); @@ -513,13 +514,13 @@ IFACEMETHODIMP UiaTextRangeBase::GetText(_In_ int maxLength, _Out_ BSTR* pRetVal // if _end is at 0, we ignore that row because _end is exclusive const auto& buffer = _pData->GetTextBuffer(); - const unsigned int totalRowsInRange = (_end.X == buffer.GetSize().Left()) ? + const SHORT totalRowsInRange = (_end.X == buffer.GetSize().Left()) ? _end.Y - _start.Y : - _end.Y - _start.Y + 1; - const auto lastRowInRange = _start.Y + totalRowsInRange - 1; + _end.Y - _start.Y + static_cast(1); + const SHORT lastRowInRange = _start.Y + totalRowsInRange - static_cast(1); ScreenInfoRow currentScreenInfoRow = 0; - for (unsigned int i = 0; i < totalRowsInRange; ++i) + for (SHORT i = 0; i < totalRowsInRange; ++i) { currentScreenInfoRow = _start.Y + i; const ROW& row = buffer.GetRowByOffset(currentScreenInfoRow); @@ -907,7 +908,7 @@ const COORD UiaTextRangeBase::_getScreenFontSize() const // - viewport - The viewport to measure // Return Value: // - The viewport height -const unsigned int UiaTextRangeBase::_getViewportHeight(const SMALL_RECT viewport) noexcept +const unsigned int UiaTextRangeBase::_getViewportHeight(const SMALL_RECT viewport) const noexcept { FAIL_FAST_IF(!(viewport.Bottom >= viewport.Top)); // + 1 because COORD is inclusive on both sides so subtracting top @@ -997,24 +998,24 @@ void UiaTextRangeBase::_moveEndpointByUnitCharacter(_In_ const int moveCount, const MovementDirection moveDirection = (moveCount > 0) ? MovementDirection::Forward : MovementDirection::Backward; const auto bufferSize = _pData->GetTextBuffer().GetSize(); - bool fSuccess = true; + bool success = true; auto target = GetEndpoint(endpoint); - while (abs(*pAmountMoved) < abs(moveCount) && fSuccess) + while (abs(*pAmountMoved) < abs(moveCount) && success) { switch (moveDirection) { case MovementDirection::Forward: - fSuccess = bufferSize.IncrementInBounds(target, allowBottomExclusive); - if (fSuccess) + success = bufferSize.IncrementInBounds(target, allowBottomExclusive); + if (success) { - *pAmountMoved += 1; + (*pAmountMoved)++; } break; case MovementDirection::Backward: - fSuccess = bufferSize.DecrementInBounds(target, allowBottomExclusive); - if (fSuccess) + success = bufferSize.DecrementInBounds(target, allowBottomExclusive); + if (success) { - *pAmountMoved -= 1; + (*pAmountMoved)--; } break; default: @@ -1054,14 +1055,14 @@ void UiaTextRangeBase::_moveEndpointByUnitWord(_In_ const int moveCount, const auto& buffer = _pData->GetTextBuffer(); const auto bufferSize = buffer.GetSize(); const auto bufferOrigin = bufferSize.Origin(); - const auto bufferEnd = bufferSize.EndInclusive(); + const auto bufferEnd = bufferSize.EndExclusive(); const auto lastCharPos = buffer.GetLastNonSpaceCharacter(); auto resultPos = GetEndpoint(endpoint); auto nextPos = resultPos; - bool fSuccess = true; - while (abs(*pAmountMoved) < abs(moveCount) && fSuccess) + bool success = true; + while (std::abs(*pAmountMoved) < std::abs(moveCount) && success) { nextPos = resultPos; switch (moveDirection) @@ -1070,17 +1071,17 @@ void UiaTextRangeBase::_moveEndpointByUnitWord(_In_ const int moveCount, { if (nextPos == bufferEnd) { - fSuccess = false; + success = false; } else if (buffer.MoveToNextWord(nextPos, _wordDelimiters, lastCharPos)) { resultPos = nextPos; - *pAmountMoved += 1; + (*pAmountMoved)++; } else if (allowBottomExclusive) { resultPos = bufferEnd; - *pAmountMoved += 1; + (*pAmountMoved)++; } break; } @@ -1088,17 +1089,17 @@ void UiaTextRangeBase::_moveEndpointByUnitWord(_In_ const int moveCount, { if (nextPos == bufferOrigin) { - fSuccess = false; + success = false; } else if (buffer.MoveToPreviousWord(nextPos, _wordDelimiters)) { resultPos = nextPos; - *pAmountMoved -= 1; + (*pAmountMoved)--; } else { resultPos = bufferOrigin; - *pAmountMoved -= 1; + (*pAmountMoved)--; } break; } @@ -1150,7 +1151,7 @@ void UiaTextRangeBase::_moveEndpointByUnitLine(_In_ const int moveCount, // can't move past end if (nextPos.Y >= bufferSize.BottomInclusive()) { - if (preventBufferEnd || nextPos == bufferSize.EndInclusive()) + if (preventBufferEnd || nextPos == bufferSize.EndExclusive()) { fSuccess = false; break; @@ -1162,7 +1163,7 @@ void UiaTextRangeBase::_moveEndpointByUnitLine(_In_ const int moveCount, if (fSuccess) { resultPos = nextPos; - *pAmountMoved += 1; + (*pAmountMoved)++; } break; } @@ -1182,7 +1183,7 @@ void UiaTextRangeBase::_moveEndpointByUnitLine(_In_ const int moveCount, { nextPos.X = bufferSize.Left(); resultPos = nextPos; - *pAmountMoved -= 1; + (*pAmountMoved)--; } break; } @@ -1226,7 +1227,7 @@ void UiaTextRangeBase::_moveEndpointByUnitDocument(_In_ const int moveCount, { case MovementDirection::Forward: { - const auto documentEnd = bufferSize.EndInclusive(); + const auto documentEnd = bufferSize.EndExclusive(); if (preventBufferEnd || target == documentEnd) { return; @@ -1234,7 +1235,7 @@ void UiaTextRangeBase::_moveEndpointByUnitDocument(_In_ const int moveCount, else { SetEndpoint(endpoint, documentEnd); - *pAmountMoved += 1; + (*pAmountMoved)++; } break; } @@ -1248,7 +1249,7 @@ void UiaTextRangeBase::_moveEndpointByUnitDocument(_In_ const int moveCount, else { SetEndpoint(endpoint, documentBegin); - *pAmountMoved -= 1; + (*pAmountMoved)--; } break; } diff --git a/src/types/UiaTextRangeBase.hpp b/src/types/UiaTextRangeBase.hpp index cbfdc8b7ca5..1ad413d105c 100644 --- a/src/types/UiaTextRangeBase.hpp +++ b/src/types/UiaTextRangeBase.hpp @@ -115,7 +115,7 @@ namespace Microsoft::Console::Types ~UiaTextRangeBase() = default; const IdType GetId() const noexcept; - const COORD GetEndpoint(TextPatternRangeEndpoint endpoint = TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start) const noexcept; + const COORD GetEndpoint(TextPatternRangeEndpoint endpoint) const noexcept; bool SetEndpoint(TextPatternRangeEndpoint endpoint, const COORD val) noexcept; const bool IsDegenerate() const noexcept; @@ -180,18 +180,8 @@ namespace Microsoft::Console::Types // between the provider and the client IdType _id; - // measure units in the form [_start, _end]. _start - // may be a bigger number than _end if the range - // wraps around the end of the text buffer. - // - // In this scenario, _start <= _end - // 0 ............... N (text buffer line indices) - // s-----e (_start to _end) - // - // In this scenario, _start >= end - // 0 ............... N (text buffer line indices) - // ---e s----- (_start to _end) - // + // measure units in the form [_start, _end). + // These are in the TextBuffer coordinate space. // NOTE: _start is inclusive, but _end is exclusive COORD _start; COORD _end; @@ -199,7 +189,7 @@ namespace Microsoft::Console::Types RECT _getTerminalRect() const; virtual const COORD _getScreenFontSize() const; - const unsigned int _getViewportHeight(const SMALL_RECT viewport) noexcept; + const unsigned int _getViewportHeight(const SMALL_RECT viewport) const noexcept; void _getBoundingRect(_In_ const COORD startAnchor, _In_ const COORD endAnchor, _Inout_ std::vector& coords) const; diff --git a/src/types/inc/viewport.hpp b/src/types/inc/viewport.hpp index 6ab559cdb08..4467df05a2a 100644 --- a/src/types/inc/viewport.hpp +++ b/src/types/inc/viewport.hpp @@ -57,7 +57,7 @@ namespace Microsoft::Console::Types SHORT Height() const noexcept; SHORT Width() const noexcept; COORD Origin() const noexcept; - COORD EndInclusive() const noexcept; + COORD EndExclusive() const noexcept; COORD Dimensions() const noexcept; bool IsInBounds(const Viewport& other) const noexcept; diff --git a/src/types/viewport.cpp b/src/types/viewport.cpp index 663a804e2dd..3ff51a19113 100644 --- a/src/types/viewport.cpp +++ b/src/types/viewport.cpp @@ -138,12 +138,14 @@ COORD Viewport::Origin() const noexcept } // Method Description: -// - For Accessibility, get a coord representing the end of this viewport in inclusive terms. +// - For Accessibility, get a COORD representing the end of this viewport in exclusive terms. +// - This is needed to represent an exclusive endpoint in UiaTextRange that includes the last +// COORD's text in the buffer at (RightInclusive(), BottomInclusive()) // Arguments: // - // Return Value: // - the coordinates of this viewport's end. -COORD Viewport::EndInclusive() const noexcept +COORD Viewport::EndExclusive() const noexcept { return { Left(), BottomExclusive() }; } From e44db5489eb17430fcf58c78b1be99763fe768c5 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Wed, 29 Jan 2020 22:55:33 -0800 Subject: [PATCH 13/25] A few dumb errors on my part --- src/cascadia/TerminalControl/UiaTextRange.cpp | 2 +- src/interactivity/win32/uiaTextRange.cpp | 2 +- .../win32/ut_interactivity_win32/UiaTextRangeTests.cpp | 2 +- src/types/UiaTextRangeBase.cpp | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/cascadia/TerminalControl/UiaTextRange.cpp b/src/cascadia/TerminalControl/UiaTextRange.cpp index ed355200d7a..9b82aa1e8ed 100644 --- a/src/cascadia/TerminalControl/UiaTextRange.cpp +++ b/src/cascadia/TerminalControl/UiaTextRange.cpp @@ -25,7 +25,7 @@ HRESULT UiaTextRange::GetSelectionRanges(_In_ IUiaData* pData, for (const auto& rect : rectangles) { const auto start = rect.Origin(); - const auto end = rect.EndInclusive(); + const auto end = rect.EndExclusive(); ComPtr range; RETURN_IF_FAILED(MakeAndInitialize(&range, pData, pProvider, start, end, wordDelimiters)); diff --git a/src/interactivity/win32/uiaTextRange.cpp b/src/interactivity/win32/uiaTextRange.cpp index 54ade18fb13..dcbabcf7130 100644 --- a/src/interactivity/win32/uiaTextRange.cpp +++ b/src/interactivity/win32/uiaTextRange.cpp @@ -29,7 +29,7 @@ HRESULT UiaTextRange::GetSelectionRanges(_In_ IUiaData* pData, for (const auto& rect : rectangles) { const auto start = rect.Origin(); - const auto end = rect.EndInclusive(); + const auto end = rect.EndExclusive(); ComPtr range; RETURN_IF_FAILED(MakeAndInitialize(&range, pData, pProvider, start, end, wordDelimiters)); diff --git a/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp b/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp index 59880d9707d..c914a4dc69a 100644 --- a/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp +++ b/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp @@ -320,7 +320,7 @@ class UiaTextRangeTests { TextUnit_Document, TextUnitBoundaries{ { 0, 0 }, - _pTextBuffer->GetSize().EndInclusive() } } + _pTextBuffer->GetSize().EndExclusive() } } }; Microsoft::WRL::ComPtr utr; diff --git a/src/types/UiaTextRangeBase.cpp b/src/types/UiaTextRangeBase.cpp index aaac594c8b6..51975843035 100644 --- a/src/types/UiaTextRangeBase.cpp +++ b/src/types/UiaTextRangeBase.cpp @@ -519,7 +519,7 @@ IFACEMETHODIMP UiaTextRangeBase::GetText(_In_ int maxLength, _Out_ BSTR* pRetVal _end.Y - _start.Y + static_cast(1); const SHORT lastRowInRange = _start.Y + totalRowsInRange - static_cast(1); - ScreenInfoRow currentScreenInfoRow = 0; + SHORT currentScreenInfoRow = 0; for (SHORT i = 0; i < totalRowsInRange; ++i) { currentScreenInfoRow = _start.Y + i; @@ -529,12 +529,12 @@ IFACEMETHODIMP UiaTextRangeBase::GetText(_In_ int maxLength, _Out_ BSTR* pRetVal const size_t rowRight = row.GetCharRow().MeasureRight(); size_t startIndex = 0; size_t endIndex = rowRight; - if (currentScreenInfoRow == static_cast(_start.Y)) + if (currentScreenInfoRow == _start.Y) { startIndex = _start.X; } - if (currentScreenInfoRow == static_cast(_end.Y)) + if (currentScreenInfoRow == _end.Y) { // prevent the end from going past the last non-whitespace char in the row endIndex = std::max(startIndex + 1, std::min(gsl::narrow_cast(_end.X), rowRight)); From 4571d42ba55e1af36543f4f05f23d2768b8be50c Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Thu, 30 Jan 2020 13:58:44 -0800 Subject: [PATCH 14/25] Polish non-testing area. Up next: testing polish --- src/buffer/out/textBuffer.cpp | 1 - .../TerminalControl/XamlUiaTextRange.cpp | 20 ++---- src/inc/LibraryIncludes.h | 3 + src/types/ScreenInfoUiaProviderBase.cpp | 6 +- src/types/UiaTextRangeBase.cpp | 69 ++++++++++--------- src/types/viewport.cpp | 54 +++++++++------ 6 files changed, 80 insertions(+), 73 deletions(-) diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp index 63df86e0abd..4fb59de5d3f 100644 --- a/src/buffer/out/textBuffer.cpp +++ b/src/buffer/out/textBuffer.cpp @@ -1105,7 +1105,6 @@ const COORD TextBuffer::GetWordEnd(const COORD target, const std::wstring_view w } } - // Method Description: // - Helper method for GetWordEnd(). Get the COORD for the beginning of the next READABLE word // Arguments: diff --git a/src/cascadia/TerminalControl/XamlUiaTextRange.cpp b/src/cascadia/TerminalControl/XamlUiaTextRange.cpp index a2bf8e592a6..1fec5f49c30 100644 --- a/src/cascadia/TerminalControl/XamlUiaTextRange.cpp +++ b/src/cascadia/TerminalControl/XamlUiaTextRange.cpp @@ -5,6 +5,10 @@ #include "XamlUiaTextRange.h" #include "UiaTextRange.hpp" +// the same as COR_E_NOTSUPPORTED +// we don't want to import the CLR headers to get it +#define XAML_E_NOT_SUPPORTED 0x80131515L + namespace UIA { using ::ITextRangeProvider; @@ -24,18 +28,6 @@ namespace XamlAutomation namespace winrt::Microsoft::Terminal::TerminalControl::implementation { - // EmptyObject is our equivalent of UIAutomationCore::UiaGetReservedNotSupportedValue() - // This retrieves a value that is interpreted as "not supported". - class EmptyObject : public winrt::implements - { - public: - static IInspectable GetInstance() - { - static auto empty = make_self(); - return *empty; - } - }; - XamlAutomation::ITextRangeProvider XamlUiaTextRange::Clone() const { UIA::ITextRangeProvider* pReturn; @@ -100,9 +92,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation } else { - // We _need_ to return an empty object here. + // We _need_ to return XAML_E_NOT_SUPPORTED here. // Returning nullptr is an improper implementation of it being unsupported. - return EmptyObject::GetInstance(); + winrt::throw_hresult(XAML_E_NOT_SUPPORTED); } } diff --git a/src/inc/LibraryIncludes.h b/src/inc/LibraryIncludes.h index ef0331689ba..61399cde05d 100644 --- a/src/inc/LibraryIncludes.h +++ b/src/inc/LibraryIncludes.h @@ -70,7 +70,10 @@ #include // Chromium Numerics (safe math) +#pragma warning(push) +#pragma warning(disable:4100) // unreferenced parameter #include +#pragma warning(pop) // IntSafe #define ENABLE_INTSAFE_SIGNED_FUNCTIONS diff --git a/src/types/ScreenInfoUiaProviderBase.cpp b/src/types/ScreenInfoUiaProviderBase.cpp index 42d43636f59..bc95e3ba972 100644 --- a/src/types/ScreenInfoUiaProviderBase.cpp +++ b/src/types/ScreenInfoUiaProviderBase.cpp @@ -352,11 +352,11 @@ IFACEMETHODIMP ScreenInfoUiaProviderBase::GetVisibleRanges(_Outptr_result_mayben } // stuff each visible line in the safearray - for (SHORT i = 0; i < rowCount; ++i) + for (short i = 0; i < rowCount; ++i) { // end is exclusive so add 1 - const COORD start { viewport.Left(), viewport.Top() + i }; - const COORD end { start.X, start.Y + 1 }; + const COORD start{ viewport.Left(), viewport.Top() + i }; + const COORD end{ start.X, start.Y + 1 }; HRESULT hr = S_OK; WRL::ComPtr range; diff --git a/src/types/UiaTextRangeBase.cpp b/src/types/UiaTextRangeBase.cpp index 51975843035..8689437176b 100644 --- a/src/types/UiaTextRangeBase.cpp +++ b/src/types/UiaTextRangeBase.cpp @@ -239,8 +239,8 @@ IFACEMETHODIMP UiaTextRangeBase::Compare(_In_opt_ ITextRangeProvider* pRange, _O if (other) { *pRetVal = (_start == other->GetEndpoint(TextPatternRangeEndpoint_Start) && - _end == other->GetEndpoint(TextPatternRangeEndpoint_End) && - IsDegenerate() == other->IsDegenerate()); + _end == other->GetEndpoint(TextPatternRangeEndpoint_End) && + IsDegenerate() == other->IsDegenerate()); } // TODO GitHub #1914: Re-attach Tracing to UIA Tree // tracing @@ -314,14 +314,15 @@ IFACEMETHODIMP UiaTextRangeBase::ExpandToEnclosingUnit(_In_ TextUnit unit) else if (unit <= TextUnit::TextUnit_Word) { // expand to word - _start = buffer.GetWordStart(_start, _wordDelimiters, /*accessibilityMode*/ true); - _end = buffer.GetWordEnd(_start, _wordDelimiters, /*accessibilityMode*/ true); + _start = buffer.GetWordStart(_start, _wordDelimiters, true); + _end = buffer.GetWordEnd(_start, _wordDelimiters, true); } else if (unit <= TextUnit::TextUnit_Line) { // expand to line _start.X = 0; - _end.X = base::ClampAdd(_start.Y, 1); + _end.X = 0; + _end.Y = base::ClampAdd(_start.Y, 1); } else { @@ -411,12 +412,11 @@ IFACEMETHODIMP UiaTextRangeBase::GetBoundingRectangles(_Outptr_result_maybenull_ // _end is exclusive, let's be inclusive so we don't have to think about it anymore for bounding rects bufferSize.DecrementInBounds(endAnchor, true); - if (IsDegenerate()) - { - _getBoundingRect(_start, _start, coords); - } - else if (bufferSize.CompareInBounds(_start, viewportEnd, true) > 0 || bufferSize.CompareInBounds(_end, viewportOrigin, true) < 0) + if (IsDegenerate() || bufferSize.CompareInBounds(_start, viewportEnd, true) > 0 || bufferSize.CompareInBounds(_end, viewportOrigin, true) < 0) { + // An empty array is returned for a degenerate (empty) text range or for a text range + // reference: https://docs.microsoft.com/en-us/windows/win32/api/uiautomationclient/nf-uiautomationclient-iuiautomationtextrange-getboundingrectangles + // Remember, start cannot be past end, so // if start is past the viewport end, // or end is past the viewport origin @@ -515,8 +515,8 @@ IFACEMETHODIMP UiaTextRangeBase::GetText(_In_ int maxLength, _Out_ BSTR* pRetVal // if _end is at 0, we ignore that row because _end is exclusive const auto& buffer = _pData->GetTextBuffer(); const SHORT totalRowsInRange = (_end.X == buffer.GetSize().Left()) ? - _end.Y - _start.Y : - _end.Y - _start.Y + static_cast(1); + _end.Y - _start.Y : + _end.Y - _start.Y + static_cast(1); const SHORT lastRowInRange = _start.Y + totalRowsInRange - static_cast(1); SHORT currentScreenInfoRow = 0; @@ -811,11 +811,11 @@ IFACEMETHODIMP UiaTextRangeBase::ScrollIntoView(_In_ BOOL alignToTop) const auto oldViewport = _pData->GetViewport().ToInclusive(); const auto viewportHeight = _getViewportHeight(oldViewport); // range rows - const auto startScreenInfoRow = _start.Y; - const auto endScreenInfoRow = _end.Y; + const base::ClampedNumeric startScreenInfoRow = _start.Y; + const base::ClampedNumeric endScreenInfoRow = _end.Y; // screen buffer rows - const auto topRow = 0; - const auto bottomRow = _pData->GetTextBuffer().TotalRowCount() - 1; + const base::ClampedNumeric topRow = 0; + const base::ClampedNumeric bottomRow = _pData->GetTextBuffer().TotalRowCount() - 1; SMALL_RECT newViewport = oldViewport; @@ -827,15 +827,15 @@ IFACEMETHODIMP UiaTextRangeBase::ScrollIntoView(_In_ BOOL alignToTop) if (startScreenInfoRow + viewportHeight <= bottomRow) { // we can align to the top - newViewport.Top = gsl::narrow(startScreenInfoRow); - newViewport.Bottom = gsl::narrow(startScreenInfoRow + viewportHeight - 1); + newViewport.Top = startScreenInfoRow; + newViewport.Bottom = startScreenInfoRow + viewportHeight - 1; } else { // we can align to the top so we'll just move the viewport // to the bottom of the screen buffer - newViewport.Bottom = gsl::narrow(bottomRow); - newViewport.Top = gsl::narrow(bottomRow - viewportHeight + 1); + newViewport.Bottom = bottomRow; + newViewport.Top = bottomRow - viewportHeight + 1; } } else @@ -845,20 +845,20 @@ IFACEMETHODIMP UiaTextRangeBase::ScrollIntoView(_In_ BOOL alignToTop) if (static_cast(endScreenInfoRow) >= viewportHeight) { // we can align to bottom - newViewport.Bottom = gsl::narrow(endScreenInfoRow); - newViewport.Top = gsl::narrow(endScreenInfoRow - viewportHeight + 1); + newViewport.Bottom = endScreenInfoRow; + newViewport.Top = endScreenInfoRow - viewportHeight + 1; } else { // we can't align to bottom so we'll move the viewport to // the top of the screen buffer - newViewport.Top = gsl::narrow(topRow); - newViewport.Bottom = gsl::narrow(topRow + viewportHeight - 1); + newViewport.Top = topRow; + newViewport.Bottom = topRow + viewportHeight - 1; } } - FAIL_FAST_IF(!(newViewport.Top >= gsl::narrow(topRow))); - FAIL_FAST_IF(!(newViewport.Bottom <= gsl::narrow(bottomRow))); + FAIL_FAST_IF(!(newViewport.Top >= topRow)); + FAIL_FAST_IF(!(newViewport.Bottom <= bottomRow)); FAIL_FAST_IF(!(_getViewportHeight(oldViewport) == _getViewportHeight(newViewport))); _ChangeViewport(newViewport); @@ -938,21 +938,22 @@ void UiaTextRangeBase::_getBoundingRect(_In_ const COORD startAnchor, _In_ const // startAnchor is converted to the viewport coordinate space auto startCoord = startAnchor; viewport.ConvertToOrigin(&startCoord); - topLeft.x = startCoord.X * currentFontSize.X; - topLeft.y = startCoord.Y * currentFontSize.Y; + topLeft.x = base::ClampMul(startCoord.X, currentFontSize.X); + topLeft.y = base::ClampMul(startCoord.Y, currentFontSize.Y); if (IsDegenerate()) { - bottomRight.x = (startCoord.X) * currentFontSize.X; - bottomRight.y = (startCoord.Y + 1) * currentFontSize.Y; + // An empty array is returned for a degenerate (empty) text range or for a text range + // reference: https://docs.microsoft.com/en-us/windows/win32/api/uiautomationclient/nf-uiautomationclient-iuiautomationtextrange-getboundingrectangles + return; } else { // endAnchor is converted to the viewport coordinate space auto endCoord = endAnchor; viewport.ConvertToOrigin(&endCoord); - bottomRight.x = (endCoord.X + 1) * currentFontSize.X; - bottomRight.y = (endCoord.Y + 1) * currentFontSize.Y; + bottomRight.x = base::ClampMul(endCoord.X, currentFontSize.X); + bottomRight.y = base::ClampMul(base::ClampAdd(endCoord.Y, 1), currentFontSize.Y); } // convert the coords to be relative to the screen instead of @@ -960,8 +961,8 @@ void UiaTextRangeBase::_getBoundingRect(_In_ const COORD startAnchor, _In_ const _TranslatePointToScreen(&topLeft); _TranslatePointToScreen(&bottomRight); - const LONG width = bottomRight.x - topLeft.x; - const LONG height = bottomRight.y - topLeft.y; + const long width = base::ClampSub(bottomRight.x, topLeft.x); + const long height = base::ClampSub(bottomRight.y, topLeft.y); // insert the coords coords.push_back(topLeft.x); diff --git a/src/types/viewport.cpp b/src/types/viewport.cpp index 3ff51a19113..daa8e7e4e19 100644 --- a/src/types/viewport.cpp +++ b/src/types/viewport.cpp @@ -179,12 +179,14 @@ bool Viewport::IsInBounds(const Viewport& other) const noexcept // - Determines if the given coordinate position lies within this viewport. // Arguments: // - pos - Coordinate position -// - allowBottomExclusive - if true, allow the X position to be on the BottomExclusive boundary +// - allowEndExclusive - if true, allow the EndExclusive COORD as a valid position. +// Used in accessibility to signify that the exclusive end +// includes the last COORD in a given viewport. // Return Value: // - True if it lies inside the viewport. False otherwise. -bool Viewport::IsInBounds(const COORD& pos, bool allowBottomExclusive) const noexcept +bool Viewport::IsInBounds(const COORD& pos, bool allowEndExclusive) const noexcept { - if (allowBottomExclusive && pos.X == Left() && pos.Y == BottomExclusive()) + if (allowEndExclusive && pos == EndExclusive()) { return true; } @@ -274,12 +276,14 @@ bool Viewport::MoveInBounds(const ptrdiff_t move, COORD& pos) const noexcept // - Increments the given coordinate within the bounds of this viewport. // Arguments: // - pos - Coordinate position that will be incremented, if it can be. -// - allowBottomExclusive - if true, allow the X position to be on the BottomExclusive boundary +// - allowEndExclusive - if true, allow the EndExclusive COORD as a valid position. +// Used in accessibility to signify that the exclusive end +// includes the last COORD in a given viewport. // Return Value: // - True if it could be incremented. False if it would move outside. -bool Viewport::IncrementInBounds(COORD& pos, bool allowBottomExclusive) const noexcept +bool Viewport::IncrementInBounds(COORD& pos, bool allowEndExclusive) const noexcept { - return WalkInBounds(pos, { XWalk::LeftToRight, YWalk::TopToBottom }, allowBottomExclusive); + return WalkInBounds(pos, { XWalk::LeftToRight, YWalk::TopToBottom }, allowEndExclusive); } // Method Description: @@ -299,12 +303,14 @@ bool Viewport::IncrementInBoundsCircular(COORD& pos) const noexcept // - Decrements the given coordinate within the bounds of this viewport. // Arguments: // - pos - Coordinate position that will be incremented, if it can be. -// - allowBottomExclusive - if true, allow the X position to be on the BottomExclusive boundary +// - allowEndExclusive - if true, allow the EndExclusive COORD as a valid position. +// Used in accessibility to signify that the exclusive end +// includes the last COORD in a given viewport. // Return Value: // - True if it could be incremented. False if it would move outside. -bool Viewport::DecrementInBounds(COORD& pos, bool allowBottomExclusive) const noexcept +bool Viewport::DecrementInBounds(COORD& pos, bool allowEndExclusive) const noexcept { - return WalkInBounds(pos, { XWalk::RightToLeft, YWalk::BottomToTop }, allowBottomExclusive); + return WalkInBounds(pos, { XWalk::RightToLeft, YWalk::BottomToTop }, allowEndExclusive); } // Method Description: @@ -325,7 +331,9 @@ bool Viewport::DecrementInBoundsCircular(COORD& pos) const noexcept // Arguments: // - first- The first coordinate position // - second - The second coordinate position -// - allowBottomExclusive - if true, allow the COORD's Y value to be BottomExclusive when X = Left +// - allowEndExclusive - if true, allow the EndExclusive COORD as a valid position. +// Used in accessibility to signify that the exclusive end +// includes the last COORD in a given viewport. // Return Value: // - Negative if First is to the left of the Second. // - 0 if First and Second are the same coordinate. @@ -333,11 +341,11 @@ bool Viewport::DecrementInBoundsCircular(COORD& pos) const noexcept // - This is so you can do s_CompareCoords(first, second) <= 0 for "first is left or the same as second". // (the < looks like a left arrow :D) // - The magnitude of the result is the distance between the two coordinates when typing characters into the buffer (left to right, top to bottom) -int Viewport::CompareInBounds(const COORD& first, const COORD& second, bool allowBottomExclusive) const noexcept +int Viewport::CompareInBounds(const COORD& first, const COORD& second, bool allowEndExclusive) const noexcept { // Assert that our coordinates are within the expected boundaries - FAIL_FAST_IF(!IsInBounds(first, allowBottomExclusive)); - FAIL_FAST_IF(!IsInBounds(second, allowBottomExclusive)); + FAIL_FAST_IF(!IsInBounds(first, allowEndExclusive)); + FAIL_FAST_IF(!IsInBounds(second, allowEndExclusive)); // First set the distance vertically // If first is on row 4 and second is on row 6, first will be -2 rows behind second * an 80 character row would be -160. @@ -364,13 +372,15 @@ int Viewport::CompareInBounds(const COORD& first, const COORD& second, bool allo // Arguments: // - pos - Coordinate position that will be adjusted, if it can be. // - dir - Walking direction specifying which direction to go when reaching the end of a row/column -// - allowBottomExclusive - if true, allow the X position to be on the BottomExclusive boundary +// - allowEndExclusive - if true, allow the EndExclusive COORD as a valid position. +// Used in accessibility to signify that the exclusive end +// includes the last COORD in a given viewport. // Return Value: // - True if it could be adjusted as specified and remain in bounds. False if it would move outside. -bool Viewport::WalkInBounds(COORD& pos, const WalkDir dir, bool allowBottomExclusive) const noexcept +bool Viewport::WalkInBounds(COORD& pos, const WalkDir dir, bool allowEndExclusive) const noexcept { auto copy = pos; - if (WalkInBoundsCircular(copy, dir, allowBottomExclusive)) + if (WalkInBoundsCircular(copy, dir, allowEndExclusive)) { pos = copy; return true; @@ -388,19 +398,21 @@ bool Viewport::WalkInBounds(COORD& pos, const WalkDir dir, bool allowBottomExclu // Arguments: // - pos - Coordinate position that will be adjusted. // - dir - Walking direction specifying which direction to go when reaching the end of a row/column -// - allowBottomExclusive - if true, allow the X position to be on the BottomExclusive boundary +// - allowEndExclusive - if true, allow the EndExclusive COORD as a valid position. +// Used in accessibility to signify that the exclusive end +// includes the last COORD in a given viewport. // Return Value: // - True if it could be adjusted inside the viewport. // - False if it rolled over from the final corner back to the initial corner // for the specified walk direction. -bool Viewport::WalkInBoundsCircular(COORD& pos, const WalkDir dir, bool allowBottomExclusive) const noexcept +bool Viewport::WalkInBoundsCircular(COORD& pos, const WalkDir dir, bool allowEndExclusive) const noexcept { // Assert that the position given fits inside this viewport. - FAIL_FAST_IF(!IsInBounds(pos, allowBottomExclusive)); + FAIL_FAST_IF(!IsInBounds(pos, allowEndExclusive)); if (dir.x == XWalk::LeftToRight) { - if (allowBottomExclusive && pos.X == Left() && pos.Y == BottomExclusive()) + if (allowEndExclusive && pos.X == Left() && pos.Y == BottomExclusive()) { pos.Y = Top(); return false; @@ -412,7 +424,7 @@ bool Viewport::WalkInBoundsCircular(COORD& pos, const WalkDir dir, bool allowBot if (dir.y == YWalk::TopToBottom) { pos.Y++; - if (allowBottomExclusive && pos.Y == BottomExclusive()) + if (allowEndExclusive && pos.Y == BottomExclusive()) { return true; } From 1e9cf7560de635d2741c63e6ed5784d1598247db Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Thu, 30 Jan 2020 15:52:42 -0800 Subject: [PATCH 15/25] Polish tests --- src/host/ut_host/TextBufferTests.cpp | 144 +++---- .../UiaTextRangeTests.cpp | 380 ------------------ src/types/UiaTextRangeBase.cpp | 1 - 3 files changed, 56 insertions(+), 469 deletions(-) diff --git a/src/host/ut_host/TextBufferTests.cpp b/src/host/ut_host/TextBufferTests.cpp index 18ec9c70994..93f4c4c9426 100644 --- a/src/host/ut_host/TextBufferTests.cpp +++ b/src/host/ut_host/TextBufferTests.cpp @@ -146,8 +146,8 @@ class TextBufferTests TEST_METHOD(TestBurrito); - TEST_METHOD(GetWordStart); - TEST_METHOD(GetWordEnd); + void WriteLinesToBuffer(std::vector text, TextBuffer& buffer); + TEST_METHOD(GetWordBoundaries); }; void TextBufferTests::TestBufferCreate() @@ -2018,7 +2018,17 @@ void TextBufferTests::TestBurrito() VERIFY_IS_FALSE(afterBurritoIter); } -void TextBufferTests::GetWordStart() +void TextBufferTests::WriteLinesToBuffer(std::vector text, TextBuffer& buffer) +{ + for (auto row = 0; row < text.size(); ++row) + { + auto line = text[row]; + OutputCellIterator iter{ line }; + buffer.WriteLine(iter, { 0, gsl::narrow(row) }); + } +} + +void TextBufferTests::GetWordBoundaries() { COORD bufferSize{ 80, 9001 }; UINT cursorSize = 12; @@ -2026,14 +2036,9 @@ void TextBufferTests::GetWordStart() auto _buffer = std::make_unique(bufferSize, attr, cursorSize, _renderTarget); // Setup: Write lines of text to the buffer - const std::array text = { L"word other", - L" more words" }; - for (size_t row = 0; row < text.size(); ++row) - { - auto line = text[row]; - OutputCellIterator iter{ line }; - _buffer->WriteLine(iter, { 0, gsl::narrow(row) }); - } + const std::vector text = { L"word other", + L" more words" }; + WriteLinesToBuffer(text, *_buffer); // Test Data: // - COORD - starting position @@ -2051,6 +2056,7 @@ void TextBufferTests::GetWordStart() ExpectedResult expected; }; + // Set testData for GetWordStart tests // clang-format off std::vector testData = { // tests for first line of text @@ -2076,95 +2082,57 @@ void TextBufferTests::GetWordStart() { { 20, 1 }, {{14, 1 }, { 9, 1 }} }, { { 79, 1 }, {{14, 1 }, { 9, 1 }} }, }; - // clang-format off - - const std::wstring_view delimiters = L" "; - for (auto test : testData) - { - Log::Comment(NoThrowString().Format(L"COORD (%hd, %hd)", test.startPos.X, test.startPos.Y)); - COORD result; - - // Test with accessibilityMode = false - result = _buffer->GetWordStart(test.startPos, delimiters, /*accessibilityMode*/ false); - VERIFY_ARE_EQUAL(test.expected.accessibilityModeDisabled, result); + // clang-format on - // Test with accessibilityMode = true - result = _buffer->GetWordStart(test.startPos, delimiters, /*accessibilityMode*/ true); - VERIFY_ARE_EQUAL(test.expected.accessibilityModeEnabled, result); - } -} + BEGIN_TEST_METHOD_PROPERTIES() + TEST_METHOD_PROPERTY(L"Data:accessibilityMode", L"{false, true}") + END_TEST_METHOD_PROPERTIES(); -void TextBufferTests::GetWordEnd() -{ - COORD bufferSize{ 80, 9001 }; - UINT cursorSize = 12; - TextAttribute attr{ 0x7f }; - auto _buffer = std::make_unique(bufferSize, attr, cursorSize, _renderTarget); + bool accessibilityMode; + VERIFY_SUCCEEDED(TestData::TryGetValue(L"accessibilityMode", accessibilityMode), L"Get accessibility mode variant"); - // Setup: Write lines of text to the buffer - const std::array text = { L"word other", - L" more words" }; - for (auto row = 0; row < text.size(); ++row) + const std::wstring_view delimiters = L" "; + for (const auto& test : testData) { - auto line = text[row]; - OutputCellIterator iter{ line }; - _buffer->WriteLine(iter, { 0, gsl::narrow(row) }); + Log::Comment(NoThrowString().Format(L"COORD (%hd, %hd)", test.startPos.X, test.startPos.Y)); + const auto result = _buffer->GetWordStart(test.startPos, delimiters, accessibilityMode); + const auto expected = accessibilityMode ? test.expected.accessibilityModeEnabled : test.expected.accessibilityModeDisabled; + VERIFY_ARE_EQUAL(expected, result); } - // Test Data: - // - COORD - starting position - // - COORD - expected result (accessibilityMode = false) - // - COORD - expected result (accessibilityMode = true) - struct ExpectedResult - { - COORD accessibilityModeDisabled; - COORD accessibilityModeEnabled; - }; - - struct Test - { - COORD startPos; - ExpectedResult expected; - }; - - std::vector testData = { + // Update testData for GetWordEnd tests + // clang-format off + testData = { // tests for first line of text - { { 0, 0 }, {{ 3, 0 }, { 5, 0 }} }, - { { 1, 0 }, {{ 3, 0 }, { 5, 0 }} }, - { { 3, 0 }, {{ 3, 0 }, { 5, 0 }} }, - { { 4, 0 }, {{ 4, 0 }, { 5, 0 }} }, - { { 5, 0 }, {{ 9, 0 }, { 2, 1 }} }, - { { 6, 0 }, {{ 9, 0 }, { 2, 1 }} }, - { {20, 0 }, {{ 79, 0 }, { 2, 1 }} }, - { {79, 0 }, {{ 79, 0 }, { 2, 1 }} }, + { { 0, 0 }, { { 3, 0 }, { 5, 0 } } }, + { { 1, 0 }, { { 3, 0 }, { 5, 0 } } }, + { { 3, 0 }, { { 3, 0 }, { 5, 0 } } }, + { { 4, 0 }, { { 4, 0 }, { 5, 0 } } }, + { { 5, 0 }, { { 9, 0 }, { 2, 1 } } }, + { { 6, 0 }, { { 9, 0 }, { 2, 1 } } }, + { { 20, 0 }, { { 79, 0 }, { 2, 1 } } }, + { { 79, 0 }, { { 79, 0 }, { 2, 1 } } }, // tests for second line of text - { { 0, 1 }, {{ 1, 1 }, { 2, 1 }} }, - { { 1, 1 }, {{ 1, 1 }, { 2, 1 }} }, - { { 2, 1 }, {{ 5, 1 }, { 9, 1 }} }, - { { 3, 1 }, {{ 5, 1 }, { 9, 1 }} }, - { { 5, 1 }, {{ 5, 1 }, { 9, 1 }} }, - { { 6, 1 }, {{ 8, 1 }, { 9, 1 }} }, - { { 7, 1 }, {{ 8, 1 }, { 9, 1 }} }, - { { 9, 1 }, {{13, 1 }, { 0, 9001 }} }, - { { 10, 1 }, {{13, 1 }, { 0, 9001 }} }, - { { 20, 1 }, {{79, 1 }, { 0, 9001 }} }, - { { 79, 1 }, {{79, 1 }, { 0, 9001 }} }, + { { 0, 1 }, { { 1, 1 }, { 2, 1 } } }, + { { 1, 1 }, { { 1, 1 }, { 2, 1 } } }, + { { 2, 1 }, { { 5, 1 }, { 9, 1 } } }, + { { 3, 1 }, { { 5, 1 }, { 9, 1 } } }, + { { 5, 1 }, { { 5, 1 }, { 9, 1 } } }, + { { 6, 1 }, { { 8, 1 }, { 9, 1 } } }, + { { 7, 1 }, { { 8, 1 }, { 9, 1 } } }, + { { 9, 1 }, { { 13, 1 }, { 0, 9001 } } }, + { { 10, 1 }, { { 13, 1 }, { 0, 9001 } } }, + { { 20, 1 }, { { 79, 1 }, { 0, 9001 } } }, + { { 79, 1 }, { { 79, 1 }, { 0, 9001 } } }, }; - // clang-format off + // clang-format on - const std::wstring_view delimiters = L" "; - for (auto test : testData) + for (const auto& test : testData) { Log::Comment(NoThrowString().Format(L"COORD (%hd, %hd)", test.startPos.X, test.startPos.Y)); - COORD result; - - // Test with accessibilityMode = false - result = _buffer->GetWordEnd(test.startPos, delimiters, /*accessibilityMode*/ false); - VERIFY_ARE_EQUAL(test.expected.accessibilityModeDisabled, result); - - // Test with accessibilityMode = true - result = _buffer->GetWordEnd(test.startPos, delimiters, /*accessibilityMode*/ true); - VERIFY_ARE_EQUAL(test.expected.accessibilityModeEnabled, result); + COORD result = _buffer->GetWordEnd(test.startPos, delimiters, accessibilityMode); + const auto expected = accessibilityMode ? test.expected.accessibilityModeEnabled : test.expected.accessibilityModeDisabled; + VERIFY_ARE_EQUAL(expected, result); } } diff --git a/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp b/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp index c914a4dc69a..3a4ce70f409 100644 --- a/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp +++ b/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp @@ -606,187 +606,6 @@ class UiaTextRangeTests } } - TEST_METHOD(CanMoveByWord_EmptyBuffer) - { - const SHORT lastColumnIndex = _pScreenInfo->GetBufferSize().Width() - 1; - const SHORT bottomRow = gsl::narrow(_pTextBuffer->TotalRowCount() - 1); - - struct ExpectedResult - { - int moveAmt; - COORD start; - COORD end; - }; - - struct Test - { - std::wstring comment; - COORD start; - COORD end; - int moveAmt; - ExpectedResult expected; - }; - - // clang-format off - const std::vector testData - { - Test{ - L"can't move backward from (0, 0)", - { 0, 0 }, - { 0, 2 }, - -1, - { - 0, - {0, 0}, - {0, 2} - } - }, - - Test{ - L"can move backward within a row", - { 1, 0 }, - { 2, 0 }, - -1, - { - 0, - { 1, 0 }, - { 2, 0 }, - } - }, - - Test{ - L"can move forward in a row", - { 1, 0 }, - { 10, 0 }, - 5, - { - 5, - {0, 4}, - {0, 5} - } - }, - - Test{ - L"can't move past the last column in the last row", - { lastColumnIndex, bottomRow }, - { lastColumnIndex, bottomRow }, - 5, - { - 0, - { lastColumnIndex, bottomRow }, - { lastColumnIndex, bottomRow }, - } - }, - - Test{ - L"can move to a new row when necessary when moving forward", - { lastColumnIndex, 0 }, - { lastColumnIndex, 0 }, - 5, - { - 5, - { 0, 5 }, - { 0, 6} - } - }, - - Test{ - L"can move to a new row when necessary when moving backward", - { 0, 1 }, - { 5, 1 }, - -5, - { - 0, - { 0, 1 }, - { 5, 1 } - } - } - }; - // clang-format on - - Microsoft::WRL::ComPtr utr; - for (const auto& test : testData) - { - Log::Comment(test.comment.data()); - int amountMoved; - - THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize(&utr, _pUiaData, &_dummyProvider, test.start, test.end)); - THROW_IF_FAILED(utr->Move(TextUnit::TextUnit_Word, test.moveAmt, &amountMoved)); - - VERIFY_ARE_EQUAL(test.expected.moveAmt, amountMoved); - VERIFY_ARE_EQUAL(test.expected.start, utr->_start); - VERIFY_ARE_EQUAL(test.expected.end, utr->_end); - } - } - - TEST_METHOD(CanMoveByWord_NonEmptyBuffer) - { - const SHORT lastColumnIndex = _pScreenInfo->GetBufferSize().Width() - 1; - const SHORT bottomRow = gsl::narrow(_pTextBuffer->TotalRowCount() - 1); - - const std::wstring_view text[] = { - L"word other", - L" more words" - }; - - for (auto i = 0; i < 2; i++) - { - _pTextBuffer->WriteLine(text[i], { 0, gsl::narrow(i) }); - } - - struct ExpectedResult - { - int moveAmt; - COORD start; - COORD end; - }; - - struct Test - { - std::wstring comment; - COORD start; - COORD end; - int moveAmt; - ExpectedResult expected; - }; - - // clang-format off - const std::vector testData - { - Test{ - L"move backwards on the word by (0,0)", - { 1, 0 }, - { 2, 0 }, - -1, - { - -1, - { 0, 0 }, - { 6, 0 } - } - } - - //L"get next word while on first word", - //L"get next word twice while on first word", - //L"move forward to next row with word", - //L"move backwards to previous row with word", - }; - // clang-format on - - Microsoft::WRL::ComPtr utr; - for (const auto& test : testData) - { - Log::Comment(test.comment.data()); - int amountMoved; - - THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize(&utr, _pUiaData, &_dummyProvider, test.start, test.end)); - THROW_IF_FAILED(utr->Move(TextUnit::TextUnit_Word, test.moveAmt, &amountMoved)); - - VERIFY_ARE_EQUAL(test.expected.moveAmt, amountMoved); - VERIFY_ARE_EQUAL(test.expected.start, utr->_start); - VERIFY_ARE_EQUAL(test.expected.end, utr->_end); - } - } - TEST_METHOD(CanMoveByLine) { const SHORT lastColumnIndex = _pScreenInfo->GetBufferSize().Width() - 1; @@ -1046,205 +865,6 @@ class UiaTextRangeTests } } - //TEST_METHOD(CanMoveEndpointByUnitWord) - //{ - // const SHORT lastColumnIndex = _pScreenInfo->GetBufferSize().Width() - 1; - // const SHORT bottomRow = gsl::narrow(_pTextBuffer->TotalRowCount() - 1); - - // const std::wstring_view text[] = { - // L"word1 word2 word3", - // L"word4 word5 word6" - // }; - - // for (auto i = 0; i < 2; i++) - // { - // _pTextBuffer->WriteLine(text[i], { 0, gsl::narrow(i) }); - // } - - // // clang-format off - // const std::vector> testData = - // { - // { - // L"can't move _start past the beginning of the document when _start is positioned at the beginning", - // { - // 0, 0, - // 0, lastColumnIndex, - // 0, - // lastColumnIndex, - // 0, - // UiaTextRange::MovementIncrement::Backward, - // UiaTextRange::MovementDirection::Backward - // }, - // -1, - // 0, - // TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, - // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, 0) + 0, - // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, 0) + lastColumnIndex, - // false - // }, - - // { - // L"can partially move _start to the begining of the document when it is closer than the move count requested", - // { - // 0, 15, - // 0, lastColumnIndex, - // 0, - // lastColumnIndex, - // 0, - // UiaTextRange::MovementIncrement::Backward, - // UiaTextRange::MovementDirection::Backward - // }, - // -5, - // -2, - // TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, - // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, 0) + 0, - // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, 0) + lastColumnIndex, - // false - // }, - - // { - // L"can't move _end past the begining of the document", - // { - // 0, 0, - // 0, 2, - // 0, - // lastColumnIndex, - // 0, - // UiaTextRange::MovementIncrement::Backward, - // UiaTextRange::MovementDirection::Backward - // }, - // -2, - // -1, - // TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, - // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, 0) + 0, - // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, 0) + 0, - // false - // }, - - // { - // L"_start follows _end when passed during movement", - // { - // 0 + 1, 0 + 2, - // 0 + 1, 0 + 10, - // 0, - // lastColumnIndex, - // 0, - // UiaTextRange::MovementIncrement::Backward, - // UiaTextRange::MovementDirection::Backward - // }, - // -4, - // -4, - // TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, - // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, 0) + 6, - // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, 0) + 6, - // true - // }, - - // { - // L"can't move _end past the beginning of the document when _end is positioned at the end", - // { - // bottomRow, 0, - // bottomRow, lastColumnIndex, - // bottomRow, - // 0, - // lastColumnIndex, - // UiaTextRange::MovementIncrement::Forward, - // UiaTextRange::MovementDirection::Forward - // }, - // 1, - // 0, - // TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, - // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + 0, - // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex, - // false - // }, - - // { - // L"can partially move _end to the end of the document when it is closer than the move count requested", - // { - // 0, 0, - // bottomRow, lastColumnIndex - 3, - // bottomRow, - // 0, - // lastColumnIndex, - // UiaTextRange::MovementIncrement::Forward, - // UiaTextRange::MovementDirection::Forward - // }, - // 5, - // 1, - // TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, - // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, 0) + 0, - // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex, - // false - // }, - - // { - // L"can't move _start past the end of the document", - // { - // bottomRow, lastColumnIndex - 4, - // bottomRow, lastColumnIndex, - // bottomRow, - // 0, - // lastColumnIndex, - // UiaTextRange::MovementIncrement::Forward, - // UiaTextRange::MovementDirection::Forward - // }, - // 5, - // 1, - // TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, - // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex, - // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex, - // false - // }, - - // { - // L"_end follows _start when passed during movement", - // { - // 0, 0, - // 0, 0 + 3, - // 0, - // lastColumnIndex, - // 0, - // UiaTextRange::MovementIncrement::Forward, - // UiaTextRange::MovementDirection::Forward - // }, - // 2, - // 2, - // TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, - // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, 0)+15, - // UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, 0)+15, - // true - // }, - // }; - // // clang-format on - - // for (auto data : testData) - // { - // Log::Comment(std::get<0>(data).c_str()); - // std::tuple result; - // int amountMoved; - // result = UiaTextRange::_moveEndpointByUnitWord(_pUiaData, - // std::get<2>(data), - // std::get<4>(data), - // std::get<1>(data), - // L" ", - // &amountMoved); - - // VERIFY_ARE_EQUAL(std::get<3>(data), amountMoved); - // VERIFY_ARE_EQUAL(std::get<5>(data), std::get<0>(result)); - // VERIFY_ARE_EQUAL(std::get<6>(data), std::get<1>(result)); - // VERIFY_ARE_EQUAL(std::get<7>(data), std::get<2>(result)); - // } - //} - TEST_METHOD(CanMoveEndpointByUnitLine) { const SHORT lastColumnIndex = _pScreenInfo->GetBufferSize().Width() - 1; diff --git a/src/types/UiaTextRangeBase.cpp b/src/types/UiaTextRangeBase.cpp index 8689437176b..2468ead6f3d 100644 --- a/src/types/UiaTextRangeBase.cpp +++ b/src/types/UiaTextRangeBase.cpp @@ -1100,7 +1100,6 @@ void UiaTextRangeBase::_moveEndpointByUnitWord(_In_ const int moveCount, else { resultPos = bufferOrigin; - (*pAmountMoved)--; } break; } From 7bcfa728c036a36d2705a7dad5b1e7fc49b186c3 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Thu, 30 Jan 2020 16:04:25 -0800 Subject: [PATCH 16/25] static analysis fixes --- src/types/UiaTextRangeBase.cpp | 18 ++++++++++-------- src/types/UiaTextRangeBase.hpp | 4 ++-- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/types/UiaTextRangeBase.cpp b/src/types/UiaTextRangeBase.cpp index 2468ead6f3d..5440fc2c863 100644 --- a/src/types/UiaTextRangeBase.cpp +++ b/src/types/UiaTextRangeBase.cpp @@ -140,7 +140,9 @@ HRESULT UiaTextRangeBase::RuntimeClassInitialize(const UiaTextRangeBase& a) noex _start = a._start; _end = a._end; _pData = a._pData; - _wordDelimiters = a._wordDelimiters; + + // c_str needed to suppress C26447 + _wordDelimiters = a._wordDelimiters.c_str(); _id = id; ++id; @@ -178,7 +180,7 @@ const COORD UiaTextRangeBase::GetEndpoint(TextPatternRangeEndpoint endpoint) con // - val - the value that it will be set to // Return Value: // - true if range is degenerate, false otherwise. -bool UiaTextRangeBase::SetEndpoint(TextPatternRangeEndpoint endpoint, const COORD val) noexcept +bool UiaTextRangeBase::SetEndpoint(TextPatternRangeEndpoint endpoint, const COORD val) { const auto bufferSize = _pData->GetTextBuffer().GetSize(); switch (endpoint) @@ -255,7 +257,7 @@ IFACEMETHODIMP UiaTextRangeBase::Compare(_In_opt_ ITextRangeProvider* pRange, _O IFACEMETHODIMP UiaTextRangeBase::CompareEndpoints(_In_ TextPatternRangeEndpoint endpoint, _In_ ITextRangeProvider* pTargetRange, _In_ TextPatternRangeEndpoint targetEndpoint, - _Out_ int* pRetVal) noexcept + _Out_ int* pRetVal) { RETURN_HR_IF(E_INVALIDARG, pRetVal == nullptr); *pRetVal = 0; @@ -514,13 +516,13 @@ IFACEMETHODIMP UiaTextRangeBase::GetText(_In_ int maxLength, _Out_ BSTR* pRetVal // if _end is at 0, we ignore that row because _end is exclusive const auto& buffer = _pData->GetTextBuffer(); - const SHORT totalRowsInRange = (_end.X == buffer.GetSize().Left()) ? + const short totalRowsInRange = (_end.X == buffer.GetSize().Left()) ? _end.Y - _start.Y : - _end.Y - _start.Y + static_cast(1); - const SHORT lastRowInRange = _start.Y + totalRowsInRange - static_cast(1); + _end.Y - _start.Y + base::ClampedNumeric(1); + const short lastRowInRange = _start.Y + totalRowsInRange - base::ClampedNumeric(1); - SHORT currentScreenInfoRow = 0; - for (SHORT i = 0; i < totalRowsInRange; ++i) + short currentScreenInfoRow = 0; + for (short i = 0; i < totalRowsInRange; ++i) { currentScreenInfoRow = _start.Y + i; const ROW& row = buffer.GetRowByOffset(currentScreenInfoRow); diff --git a/src/types/UiaTextRangeBase.hpp b/src/types/UiaTextRangeBase.hpp index 1ad413d105c..102277fb5e6 100644 --- a/src/types/UiaTextRangeBase.hpp +++ b/src/types/UiaTextRangeBase.hpp @@ -116,7 +116,7 @@ namespace Microsoft::Console::Types const IdType GetId() const noexcept; const COORD GetEndpoint(TextPatternRangeEndpoint endpoint) const noexcept; - bool SetEndpoint(TextPatternRangeEndpoint endpoint, const COORD val) noexcept; + bool SetEndpoint(TextPatternRangeEndpoint endpoint, const COORD val); const bool IsDegenerate() const noexcept; void SetEndpoints(const COORD start, const COORD end) noexcept; @@ -127,7 +127,7 @@ namespace Microsoft::Console::Types IFACEMETHODIMP CompareEndpoints(_In_ TextPatternRangeEndpoint endpoint, _In_ ITextRangeProvider* pTargetRange, _In_ TextPatternRangeEndpoint targetEndpoint, - _Out_ int* pRetVal) noexcept override; + _Out_ int* pRetVal) override; IFACEMETHODIMP ExpandToEnclosingUnit(_In_ TextUnit unit) override; IFACEMETHODIMP FindAttribute(_In_ TEXTATTRIBUTEID textAttributeId, _In_ VARIANT val, From 706620c422749efa54b3d51bc3cfa3d4d1942f6d Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Thu, 30 Jan 2020 16:05:36 -0800 Subject: [PATCH 17/25] whoops! missed a const! --- src/host/ut_host/TextBufferTests.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/host/ut_host/TextBufferTests.cpp b/src/host/ut_host/TextBufferTests.cpp index 93f4c4c9426..1fa9e731868 100644 --- a/src/host/ut_host/TextBufferTests.cpp +++ b/src/host/ut_host/TextBufferTests.cpp @@ -2018,7 +2018,7 @@ void TextBufferTests::TestBurrito() VERIFY_IS_FALSE(afterBurritoIter); } -void TextBufferTests::WriteLinesToBuffer(std::vector text, TextBuffer& buffer) +void TextBufferTests::WriteLinesToBuffer(const std::vector text, TextBuffer& buffer) { for (auto row = 0; row < text.size(); ++row) { From f676af3402fbda4ab63a70b318719f7c30763375 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Thu, 30 Jan 2020 16:39:33 -0800 Subject: [PATCH 18/25] pass SA --- src/types/UiaTextRangeBase.cpp | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/types/UiaTextRangeBase.cpp b/src/types/UiaTextRangeBase.cpp index 5440fc2c863..522c41955bb 100644 --- a/src/types/UiaTextRangeBase.cpp +++ b/src/types/UiaTextRangeBase.cpp @@ -135,14 +135,13 @@ void UiaTextRangeBase::Initialize(_In_ const UiaPoint point) #pragma warning(suppress : 26434) // WRL RuntimeClassInitialize base is a no-op and we need this for MakeAndInitialize HRESULT UiaTextRangeBase::RuntimeClassInitialize(const UiaTextRangeBase& a) noexcept +try { _pProvider = a._pProvider; _start = a._start; _end = a._end; _pData = a._pData; - - // c_str needed to suppress C26447 - _wordDelimiters = a._wordDelimiters.c_str(); + _wordDelimiters = a._wordDelimiters; _id = id; ++id; @@ -154,6 +153,7 @@ HRESULT UiaTextRangeBase::RuntimeClassInitialize(const UiaTextRangeBase& a) noex return S_OK; } +CATCH_RETURN(); const IdType UiaTextRangeBase::GetId() const noexcept { @@ -241,8 +241,7 @@ IFACEMETHODIMP UiaTextRangeBase::Compare(_In_opt_ ITextRangeProvider* pRange, _O if (other) { *pRetVal = (_start == other->GetEndpoint(TextPatternRangeEndpoint_Start) && - _end == other->GetEndpoint(TextPatternRangeEndpoint_End) && - IsDegenerate() == other->IsDegenerate()); + _end == other->GetEndpoint(TextPatternRangeEndpoint_End)); } // TODO GitHub #1914: Re-attach Tracing to UIA Tree // tracing @@ -416,7 +415,7 @@ IFACEMETHODIMP UiaTextRangeBase::GetBoundingRectangles(_Outptr_result_maybenull_ if (IsDegenerate() || bufferSize.CompareInBounds(_start, viewportEnd, true) > 0 || bufferSize.CompareInBounds(_end, viewportOrigin, true) < 0) { - // An empty array is returned for a degenerate (empty) text range or for a text range + // An empty array is returned for a degenerate (empty) text range. // reference: https://docs.microsoft.com/en-us/windows/win32/api/uiautomationclient/nf-uiautomationclient-iuiautomationtextrange-getboundingrectangles // Remember, start cannot be past end, so @@ -517,9 +516,9 @@ IFACEMETHODIMP UiaTextRangeBase::GetText(_In_ int maxLength, _Out_ BSTR* pRetVal // if _end is at 0, we ignore that row because _end is exclusive const auto& buffer = _pData->GetTextBuffer(); const short totalRowsInRange = (_end.X == buffer.GetSize().Left()) ? - _end.Y - _start.Y : - _end.Y - _start.Y + base::ClampedNumeric(1); - const short lastRowInRange = _start.Y + totalRowsInRange - base::ClampedNumeric(1); + base::ClampSub(_end.Y, _start.Y) : + base::ClampAdd(base::ClampSub(_end.Y, _start.Y), base::ClampedNumeric(1)); + const short lastRowInRange = _start.Y + totalRowsInRange - 1; short currentScreenInfoRow = 0; for (short i = 0; i < totalRowsInRange; ++i) @@ -938,8 +937,12 @@ void UiaTextRangeBase::_getBoundingRect(_In_ const COORD startAnchor, _In_ const POINT bottomRight{ 0 }; // startAnchor is converted to the viewport coordinate space +#pragma warning(suppress:26496) // analysis can't see this, TODO GH: 4015 to improve Viewport to be less bad because it'd go away if ConvertToOrigin returned instead of inout'd. auto startCoord = startAnchor; viewport.ConvertToOrigin(&startCoord); + + // we want to clamp to a long (output type), not a short (input type) + // so we need to explicitly say topLeft.x = base::ClampMul(startCoord.X, currentFontSize.X); topLeft.y = base::ClampMul(startCoord.Y, currentFontSize.Y); @@ -952,6 +955,7 @@ void UiaTextRangeBase::_getBoundingRect(_In_ const COORD startAnchor, _In_ const else { // endAnchor is converted to the viewport coordinate space +#pragma warning(suppress : 26496) // analysis can't see this, TODO GH: 4015 to improve Viewport to be less bad because it'd go away if ConvertToOrigin returned instead of inout'd. auto endCoord = endAnchor; viewport.ConvertToOrigin(&endCoord); bottomRight.x = base::ClampMul(endCoord.X, currentFontSize.X); @@ -1224,7 +1228,7 @@ void UiaTextRangeBase::_moveEndpointByUnitDocument(_In_ const int moveCount, const MovementDirection moveDirection = (moveCount > 0) ? MovementDirection::Forward : MovementDirection::Backward; const auto bufferSize = _pData->GetTextBuffer().GetSize(); - auto target = GetEndpoint(endpoint); + const auto target = GetEndpoint(endpoint); switch (moveDirection) { case MovementDirection::Forward: From d21fbdff8906bc28061f2d74e48272876d2bce31 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Thu, 30 Jan 2020 16:43:24 -0800 Subject: [PATCH 19/25] dead code --- src/types/UiaTextRangeBase.hpp | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/src/types/UiaTextRangeBase.hpp b/src/types/UiaTextRangeBase.hpp index 102277fb5e6..8c02ef13e6d 100644 --- a/src/types/UiaTextRangeBase.hpp +++ b/src/types/UiaTextRangeBase.hpp @@ -33,34 +33,6 @@ Author(s): class UiaTextRangeTests; #endif -// The UiaTextRangeBase deals with several data structures that have -// similar semantics. In order to keep the information from these data -// structures separated, each structure has its own naming for a -// row. -// -// There is the generic Row, which does not know which data structure -// the row came from. -// -// There is the ViewportRow, which is a 0-indexed row value from the -// viewport. The top row of the viewport is at 0, rows below the top -// row increase in value and rows above the top row get increasingly -// negative. -// -// ScreenInfoRow is a row from the screen info data structure. They -// start at 0 at the top of screen info buffer. Their positions do not -// change but their associated row in the text buffer does change each -// time a new line is written. -// -// TextBufferRow is a row from the text buffer. It is not a ROW -// struct, but rather the index of a row. This is also 0-indexed. A -// TextBufferRow with a value of 0 does not necessarily refer to the -// top row of the console. - -typedef int Row; -typedef int ViewportRow; -typedef unsigned int ScreenInfoRow; -typedef unsigned int TextBufferRow; - typedef unsigned long long IdType; // A Column is a row agnostic value that refers to the column an From 1caf2ca9418c90367b3572e142314210e52703c5 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Thu, 30 Jan 2020 17:24:58 -0800 Subject: [PATCH 20/25] fix the build!! --- src/host/ut_host/TextBufferTests.cpp | 2 +- src/types/UiaTextRangeBase.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/host/ut_host/TextBufferTests.cpp b/src/host/ut_host/TextBufferTests.cpp index 1fa9e731868..09118db1cea 100644 --- a/src/host/ut_host/TextBufferTests.cpp +++ b/src/host/ut_host/TextBufferTests.cpp @@ -2020,7 +2020,7 @@ void TextBufferTests::TestBurrito() void TextBufferTests::WriteLinesToBuffer(const std::vector text, TextBuffer& buffer) { - for (auto row = 0; row < text.size(); ++row) + for (size_t row = 0; row < text.size(); ++row) { auto line = text[row]; OutputCellIterator iter{ line }; diff --git a/src/types/UiaTextRangeBase.cpp b/src/types/UiaTextRangeBase.cpp index 522c41955bb..db98aca8921 100644 --- a/src/types/UiaTextRangeBase.cpp +++ b/src/types/UiaTextRangeBase.cpp @@ -937,7 +937,7 @@ void UiaTextRangeBase::_getBoundingRect(_In_ const COORD startAnchor, _In_ const POINT bottomRight{ 0 }; // startAnchor is converted to the viewport coordinate space -#pragma warning(suppress:26496) // analysis can't see this, TODO GH: 4015 to improve Viewport to be less bad because it'd go away if ConvertToOrigin returned instead of inout'd. +#pragma warning(suppress : 26496) // analysis can't see this, TODO GH: 4015 to improve Viewport to be less bad because it'd go away if ConvertToOrigin returned instead of inout'd. auto startCoord = startAnchor; viewport.ConvertToOrigin(&startCoord); From a60cfea6a4c1a5157f58e41410b00990a459b1ce Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Mon, 27 Jan 2020 14:55:52 -0800 Subject: [PATCH 21/25] move FindText() to UiaTextRangeBase --- src/cascadia/TerminalControl/UiaTextRange.cpp | 9 ---- src/cascadia/TerminalControl/UiaTextRange.hpp | 4 -- .../TerminalControl/XamlUiaTextRange.cpp | 19 ++++--- src/interactivity/win32/uiaTextRange.cpp | 53 ------------------- src/interactivity/win32/uiaTextRange.hpp | 4 -- src/types/UiaTextRangeBase.cpp | 51 ++++++++++++++++++ src/types/UiaTextRangeBase.hpp | 8 +-- 7 files changed, 66 insertions(+), 82 deletions(-) diff --git a/src/cascadia/TerminalControl/UiaTextRange.cpp b/src/cascadia/TerminalControl/UiaTextRange.cpp index 9b82aa1e8ed..45e907bd856 100644 --- a/src/cascadia/TerminalControl/UiaTextRange.cpp +++ b/src/cascadia/TerminalControl/UiaTextRange.cpp @@ -105,15 +105,6 @@ IFACEMETHODIMP UiaTextRange::Clone(_Outptr_result_maybenull_ ITextRangeProvider* return S_OK; } -IFACEMETHODIMP UiaTextRange::FindText(_In_ BSTR /*text*/, - _In_ BOOL /*searchBackward*/, - _In_ BOOL /*ignoreCase*/, - _Outptr_result_maybenull_ ITextRangeProvider** /*ppRetVal*/) -{ - // TODO GitHub #605: Search functionality - return E_NOTIMPL; -} - void UiaTextRange::_ChangeViewport(const SMALL_RECT /*NewWindow*/) { // TODO GitHub #2361: Update viewport when calling UiaTextRangeBase::ScrollIntoView() diff --git a/src/cascadia/TerminalControl/UiaTextRange.hpp b/src/cascadia/TerminalControl/UiaTextRange.hpp index 0113b485e34..8f70b24114c 100644 --- a/src/cascadia/TerminalControl/UiaTextRange.hpp +++ b/src/cascadia/TerminalControl/UiaTextRange.hpp @@ -57,10 +57,6 @@ namespace Microsoft::Terminal HRESULT RuntimeClassInitialize(const UiaTextRange& a); IFACEMETHODIMP Clone(_Outptr_result_maybenull_ ITextRangeProvider** ppRetVal) override; - IFACEMETHODIMP FindText(_In_ BSTR text, - _In_ BOOL searchBackward, - _In_ BOOL ignoreCase, - _Outptr_result_maybenull_ ITextRangeProvider** ppRetVal) override; protected: void _ChangeViewport(const SMALL_RECT NewWindow) override; diff --git a/src/cascadia/TerminalControl/XamlUiaTextRange.cpp b/src/cascadia/TerminalControl/XamlUiaTextRange.cpp index 1fec5f49c30..b40a299c532 100644 --- a/src/cascadia/TerminalControl/XamlUiaTextRange.cpp +++ b/src/cascadia/TerminalControl/XamlUiaTextRange.cpp @@ -73,14 +73,17 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation throw winrt::hresult_not_implemented(); } - XamlAutomation::ITextRangeProvider XamlUiaTextRange::FindText(winrt::hstring /*text*/, - bool /*searchBackward*/, - bool /*ignoreCase*/) - { - // TODO GitHub #605: Search functionality - // we need to wrap this around the UiaTextRange FindText() function - // but right now it returns E_NOTIMPL, so let's just return nullptr for now. - return nullptr; + XamlAutomation::ITextRangeProvider XamlUiaTextRange::FindText(winrt::hstring text, + bool searchBackward, + bool ignoreCase) + { + UIA::ITextRangeProvider* pReturn; + BSTR bs = SysAllocStringLen(text.data(), text.size()); + + THROW_IF_FAILED(_uiaProvider->FindText(bs, searchBackward, ignoreCase, &pReturn)); + + auto xutr = winrt::make_self(pReturn, _parentProvider); + return xutr.as(); } winrt::Windows::Foundation::IInspectable XamlUiaTextRange::GetAttributeValue(int32_t textAttributeId) const diff --git a/src/interactivity/win32/uiaTextRange.cpp b/src/interactivity/win32/uiaTextRange.cpp index dcbabcf7130..c5b7f645885 100644 --- a/src/interactivity/win32/uiaTextRange.cpp +++ b/src/interactivity/win32/uiaTextRange.cpp @@ -5,7 +5,6 @@ #include "uiaTextRange.hpp" #include "screenInfoUiaProvider.hpp" -#include "..\buffer\out\search.h" #include "..\interactivity\inc\ServiceLocator.hpp" using namespace Microsoft::Console::Types; @@ -105,58 +104,6 @@ IFACEMETHODIMP UiaTextRange::Clone(_Outptr_result_maybenull_ ITextRangeProvider* return S_OK; } -IFACEMETHODIMP UiaTextRange::FindText(_In_ BSTR text, - _In_ BOOL searchBackward, - _In_ BOOL ignoreCase, - _Outptr_result_maybenull_ ITextRangeProvider** ppRetVal) -{ - // TODO GitHub #1914: Re-attach Tracing to UIA Tree - //Tracing::s_TraceUia(this, ApiCall::FindText, nullptr); - RETURN_HR_IF(E_INVALIDARG, ppRetVal == nullptr); - *ppRetVal = nullptr; - try - { - const std::wstring wstr{ text, SysStringLen(text) }; - const auto sensitivity = ignoreCase ? Search::Sensitivity::CaseInsensitive : Search::Sensitivity::CaseSensitive; - - auto searchDirection = Search::Direction::Forward; - auto searchAnchor = _start; - if (searchBackward) - { - searchDirection = Search::Direction::Backward; - searchAnchor = _end; - } - - CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - THROW_HR_IF(E_POINTER, !gci.HasActiveOutputBuffer()); - Search searcher{ gci.renderData, wstr, searchDirection, sensitivity, searchAnchor }; - - HRESULT hr = S_OK; - if (searcher.FindNext()) - { - const auto foundLocation = searcher.GetFoundLocation(); - const auto start = foundLocation.first; - const auto end = foundLocation.second; - const auto bufferSize = _pData->GetTextBuffer().GetSize(); - - // make sure what was found is within the bounds of the current range - if ((searchDirection == Search::Direction::Forward && bufferSize.CompareInBounds(end, _end) < 0) || - (searchDirection == Search::Direction::Backward && bufferSize.CompareInBounds(start, _start) > 0)) - { - hr = Clone(ppRetVal); - if (SUCCEEDED(hr)) - { - UiaTextRange& range = static_cast(**ppRetVal); - range._start = start; - range._end = end; - } - } - } - return hr; - } - CATCH_RETURN(); -} - void UiaTextRange::_ChangeViewport(const SMALL_RECT NewWindow) { auto provider = static_cast(_pProvider); diff --git a/src/interactivity/win32/uiaTextRange.hpp b/src/interactivity/win32/uiaTextRange.hpp index c6755499fb0..660746230be 100644 --- a/src/interactivity/win32/uiaTextRange.hpp +++ b/src/interactivity/win32/uiaTextRange.hpp @@ -58,10 +58,6 @@ namespace Microsoft::Console::Interactivity::Win32 HRESULT RuntimeClassInitialize(const UiaTextRange& a); IFACEMETHODIMP Clone(_Outptr_result_maybenull_ ITextRangeProvider** ppRetVal) override; - IFACEMETHODIMP FindText(_In_ BSTR text, - _In_ BOOL searchBackward, - _In_ BOOL ignoreCase, - _Outptr_result_maybenull_ ITextRangeProvider** ppRetVal) override; protected: void _ChangeViewport(const SMALL_RECT NewWindow) override; diff --git a/src/types/UiaTextRangeBase.cpp b/src/types/UiaTextRangeBase.cpp index db98aca8921..5eb3bc88e20 100644 --- a/src/types/UiaTextRangeBase.cpp +++ b/src/types/UiaTextRangeBase.cpp @@ -4,6 +4,7 @@ #include "precomp.h" #include "UiaTextRangeBase.hpp" #include "ScreenInfoUiaProviderBase.h" +#include "..\buffer\out\search.h" using namespace Microsoft::Console::Types; using namespace Microsoft::Console::Types::UiaTextRangeBaseTracing; @@ -350,6 +351,56 @@ IFACEMETHODIMP UiaTextRangeBase::FindAttribute(_In_ TEXTATTRIBUTEID /*textAttrib return E_NOTIMPL; } +IFACEMETHODIMP UiaTextRangeBase::FindText(_In_ BSTR text, + _In_ BOOL searchBackward, + _In_ BOOL ignoreCase, + _Outptr_result_maybenull_ ITextRangeProvider** ppRetVal) noexcept +{ + // TODO GitHub #1914: Re-attach Tracing to UIA Tree + //Tracing::s_TraceUia(this, ApiCall::FindText, nullptr); + RETURN_HR_IF(E_INVALIDARG, ppRetVal == nullptr); + *ppRetVal = nullptr; + try + { + const std::wstring wstr{ text, SysStringLen(text) }; + const auto sensitivity = ignoreCase ? Search::Sensitivity::CaseInsensitive : Search::Sensitivity::CaseSensitive; + + auto searchDirection = Search::Direction::Forward; + auto searchAnchor = _start; + if (searchBackward) + { + searchDirection = Search::Direction::Backward; + searchAnchor = _end; + } + + Search searcher{ *_pData, wstr, searchDirection, sensitivity, searchAnchor }; + + HRESULT hr = S_OK; + if (searcher.FindNext()) + { + const auto foundLocation = searcher.GetFoundLocation(); + const auto start = foundLocation.first; + const auto end = foundLocation.second; + const auto bufferSize = _pData->GetTextBuffer().GetSize(); + + // make sure what was found is within the bounds of the current range + if ((searchDirection == Search::Direction::Forward && bufferSize.CompareInBounds(end, _end) < 0) || + (searchDirection == Search::Direction::Backward && bufferSize.CompareInBounds(start, _start) > 0)) + { + hr = Clone(ppRetVal); + if (SUCCEEDED(hr)) + { + UiaTextRangeBase& range = static_cast(**ppRetVal); + range._start = start; + range._end = end; + } + } + } + return hr; + } + CATCH_RETURN(); +} + IFACEMETHODIMP UiaTextRangeBase::GetAttributeValue(_In_ TEXTATTRIBUTEID textAttributeId, _Out_ VARIANT* pRetVal) noexcept { diff --git a/src/types/UiaTextRangeBase.hpp b/src/types/UiaTextRangeBase.hpp index 8c02ef13e6d..cb5dedba8e9 100644 --- a/src/types/UiaTextRangeBase.hpp +++ b/src/types/UiaTextRangeBase.hpp @@ -105,10 +105,10 @@ namespace Microsoft::Console::Types _In_ VARIANT val, _In_ BOOL searchBackward, _Outptr_result_maybenull_ ITextRangeProvider** ppRetVal) noexcept override; - virtual IFACEMETHODIMP FindText(_In_ BSTR text, - _In_ BOOL searchBackward, - _In_ BOOL ignoreCase, - _Outptr_result_maybenull_ ITextRangeProvider** ppRetVal) = 0; + IFACEMETHODIMP FindText(_In_ BSTR text, + _In_ BOOL searchBackward, + _In_ BOOL ignoreCase, + _Outptr_result_maybenull_ ITextRangeProvider** ppRetVal) noexcept override; IFACEMETHODIMP GetAttributeValue(_In_ TEXTATTRIBUTEID textAttributeId, _Out_ VARIANT* pRetVal) noexcept override; IFACEMETHODIMP GetBoundingRectangles(_Outptr_result_maybenull_ SAFEARRAY** ppRetVal) override; From d0ac9f6ae7b5eb53b64078ce7afa1d8bae49f698 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Mon, 27 Jan 2020 15:13:44 -0800 Subject: [PATCH 22/25] handle exclusive/inclusive end conversion --- src/types/UiaTextRangeBase.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/types/UiaTextRangeBase.cpp b/src/types/UiaTextRangeBase.cpp index 5eb3bc88e20..1684fefa6e8 100644 --- a/src/types/UiaTextRangeBase.cpp +++ b/src/types/UiaTextRangeBase.cpp @@ -378,10 +378,13 @@ IFACEMETHODIMP UiaTextRangeBase::FindText(_In_ BSTR text, HRESULT hr = S_OK; if (searcher.FindNext()) { + const auto bufferSize = _pData->GetTextBuffer().GetSize(); const auto foundLocation = searcher.GetFoundLocation(); const auto start = foundLocation.first; - const auto end = foundLocation.second; - const auto bufferSize = _pData->GetTextBuffer().GetSize(); + + // we need to increment the position of end because it's exclusive + auto end = foundLocation.second; + bufferSize.IncrementInBounds(end, true); // make sure what was found is within the bounds of the current range if ((searchDirection == Search::Direction::Forward && bufferSize.CompareInBounds(end, _end) < 0) || From c41b8fb4268ce918ee75db68fd188045d6486667 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Mon, 27 Jan 2020 16:29:55 -0800 Subject: [PATCH 23/25] Fix FindText issue and BSTR lifetime (Thanks Michael) --- src/cascadia/TerminalControl/XamlUiaTextRange.cpp | 5 +++-- src/inc/HostAndPropsheetIncludes.h | 4 ++++ src/interactivity/win32/screenInfoUiaProvider.hpp | 3 ++- src/types/ScreenInfoUiaProviderBase.h | 3 ++- src/types/UiaTextRangeBase.hpp | 3 +-- src/types/WindowUiaProviderBase.hpp | 2 +- 6 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/cascadia/TerminalControl/XamlUiaTextRange.cpp b/src/cascadia/TerminalControl/XamlUiaTextRange.cpp index b40a299c532..ab7f59bb0e9 100644 --- a/src/cascadia/TerminalControl/XamlUiaTextRange.cpp +++ b/src/cascadia/TerminalControl/XamlUiaTextRange.cpp @@ -4,6 +4,7 @@ #include "pch.h" #include "XamlUiaTextRange.h" #include "UiaTextRange.hpp" +#include // the same as COR_E_NOTSUPPORTED // we don't want to import the CLR headers to get it @@ -78,9 +79,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation bool ignoreCase) { UIA::ITextRangeProvider* pReturn; - BSTR bs = SysAllocStringLen(text.data(), text.size()); + const auto bs = wil::make_bstr(text.c_str()); - THROW_IF_FAILED(_uiaProvider->FindText(bs, searchBackward, ignoreCase, &pReturn)); + THROW_IF_FAILED(_uiaProvider->FindText(bs.get(), searchBackward, ignoreCase, &pReturn)); auto xutr = winrt::make_self(pReturn, _parentProvider); return xutr.as(); diff --git a/src/inc/HostAndPropsheetIncludes.h b/src/inc/HostAndPropsheetIncludes.h index 9aaf241c773..d0ede2a3a38 100644 --- a/src/inc/HostAndPropsheetIncludes.h +++ b/src/inc/HostAndPropsheetIncludes.h @@ -4,6 +4,10 @@ // clang-format off +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN // If this is not defined, windows.h includes commdlg.h which defines FindText globally and conflicts with UIAutomation ITextRangeProvider. +#endif + // Define and then undefine WIN32_NO_STATUS because windows.h has no guard to prevent it from double defing certain statuses // when included with ntstatus.h #define WIN32_NO_STATUS diff --git a/src/interactivity/win32/screenInfoUiaProvider.hpp b/src/interactivity/win32/screenInfoUiaProvider.hpp index bf53dbcd571..3b39a32eec8 100644 --- a/src/interactivity/win32/screenInfoUiaProvider.hpp +++ b/src/interactivity/win32/screenInfoUiaProvider.hpp @@ -19,7 +19,8 @@ Author(s): #pragma once -#include "precomp.h" +#include + #include "..\types\ScreenInfoUiaProviderBase.h" #include "..\types\UiaTextRangeBase.hpp" #include "uiaTextRange.hpp" diff --git a/src/types/ScreenInfoUiaProviderBase.h b/src/types/ScreenInfoUiaProviderBase.h index 50f4f76b508..82bdd31fcb0 100644 --- a/src/types/ScreenInfoUiaProviderBase.h +++ b/src/types/ScreenInfoUiaProviderBase.h @@ -21,11 +21,12 @@ Author(s): #pragma once -#include "precomp.h" #include "../buffer/out/textBuffer.hpp" #include "UiaTextRangeBase.hpp" #include "IUiaData.h" +#include + #include namespace Microsoft::Console::Types diff --git a/src/types/UiaTextRangeBase.hpp b/src/types/UiaTextRangeBase.hpp index cb5dedba8e9..b5b7dbebc97 100644 --- a/src/types/UiaTextRangeBase.hpp +++ b/src/types/UiaTextRangeBase.hpp @@ -18,13 +18,12 @@ Author(s): #pragma once -#include "precomp.h" - #include "inc/viewport.hpp" #include "../buffer/out/textBuffer.hpp" #include "IUiaData.h" #include "unicode.hpp" +#include #include #include #include diff --git a/src/types/WindowUiaProviderBase.hpp b/src/types/WindowUiaProviderBase.hpp index 606ed60856d..d14a9e0db44 100644 --- a/src/types/WindowUiaProviderBase.hpp +++ b/src/types/WindowUiaProviderBase.hpp @@ -20,7 +20,7 @@ Author(s): #pragma once -#include "precomp.h" +#include #include From 623dc41a0fc076dd5fa3453faca5342f5997d446 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Thu, 30 Jan 2020 17:59:06 -0800 Subject: [PATCH 24/25] PR comments --- .../TerminalControl/XamlUiaTextRange.cpp | 6 ++-- src/types/UiaTextRangeBase.cpp | 28 +++++++++---------- src/types/UiaTextRangeBase.hpp | 2 +- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/cascadia/TerminalControl/XamlUiaTextRange.cpp b/src/cascadia/TerminalControl/XamlUiaTextRange.cpp index ab7f59bb0e9..ea57becbd5b 100644 --- a/src/cascadia/TerminalControl/XamlUiaTextRange.cpp +++ b/src/cascadia/TerminalControl/XamlUiaTextRange.cpp @@ -79,12 +79,12 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation bool ignoreCase) { UIA::ITextRangeProvider* pReturn; - const auto bs = wil::make_bstr(text.c_str()); + const auto queryText = wil::make_bstr(text.c_str()); - THROW_IF_FAILED(_uiaProvider->FindText(bs.get(), searchBackward, ignoreCase, &pReturn)); + THROW_IF_FAILED(_uiaProvider->FindText(queryText.get(), searchBackward, ignoreCase, &pReturn)); auto xutr = winrt::make_self(pReturn, _parentProvider); - return xutr.as(); + return *xutr; } winrt::Windows::Foundation::IInspectable XamlUiaTextRange::GetAttributeValue(int32_t textAttributeId) const diff --git a/src/types/UiaTextRangeBase.cpp b/src/types/UiaTextRangeBase.cpp index 1684fefa6e8..b879ed7cc23 100644 --- a/src/types/UiaTextRangeBase.cpp +++ b/src/types/UiaTextRangeBase.cpp @@ -354,7 +354,7 @@ IFACEMETHODIMP UiaTextRangeBase::FindAttribute(_In_ TEXTATTRIBUTEID /*textAttrib IFACEMETHODIMP UiaTextRangeBase::FindText(_In_ BSTR text, _In_ BOOL searchBackward, _In_ BOOL ignoreCase, - _Outptr_result_maybenull_ ITextRangeProvider** ppRetVal) noexcept + _Outptr_result_maybenull_ ITextRangeProvider** ppRetVal) { // TODO GitHub #1914: Re-attach Tracing to UIA Tree //Tracing::s_TraceUia(this, ApiCall::FindText, nullptr); @@ -362,7 +362,8 @@ IFACEMETHODIMP UiaTextRangeBase::FindText(_In_ BSTR text, *ppRetVal = nullptr; try { - const std::wstring wstr{ text, SysStringLen(text) }; + const std::wstring queryText{ text }; + const auto bufferSize = _pData->GetTextBuffer().GetSize(); const auto sensitivity = ignoreCase ? Search::Sensitivity::CaseInsensitive : Search::Sensitivity::CaseSensitive; auto searchDirection = Search::Direction::Forward; @@ -370,15 +371,17 @@ IFACEMETHODIMP UiaTextRangeBase::FindText(_In_ BSTR text, if (searchBackward) { searchDirection = Search::Direction::Backward; + + // we need to convert the end to inclusive + // because Search operates with an inclusive COORD searchAnchor = _end; + bufferSize.DecrementInBounds(searchAnchor, true); } - Search searcher{ *_pData, wstr, searchDirection, sensitivity, searchAnchor }; + Search searcher{ *_pData, queryText, searchDirection, sensitivity, searchAnchor }; - HRESULT hr = S_OK; if (searcher.FindNext()) { - const auto bufferSize = _pData->GetTextBuffer().GetSize(); const auto foundLocation = searcher.GetFoundLocation(); const auto start = foundLocation.first; @@ -387,19 +390,16 @@ IFACEMETHODIMP UiaTextRangeBase::FindText(_In_ BSTR text, bufferSize.IncrementInBounds(end, true); // make sure what was found is within the bounds of the current range - if ((searchDirection == Search::Direction::Forward && bufferSize.CompareInBounds(end, _end) < 0) || + if ((searchDirection == Search::Direction::Forward && bufferSize.CompareInBounds(end, _end, true) < 0) || (searchDirection == Search::Direction::Backward && bufferSize.CompareInBounds(start, _start) > 0)) { - hr = Clone(ppRetVal); - if (SUCCEEDED(hr)) - { - UiaTextRangeBase& range = static_cast(**ppRetVal); - range._start = start; - range._end = end; - } + RETURN_IF_FAILED(Clone(ppRetVal)); + UiaTextRangeBase& range = static_cast(**ppRetVal); + range._start = start; + range._end = end; } } - return hr; + return S_OK; } CATCH_RETURN(); } diff --git a/src/types/UiaTextRangeBase.hpp b/src/types/UiaTextRangeBase.hpp index b5b7dbebc97..10fbc2f5607 100644 --- a/src/types/UiaTextRangeBase.hpp +++ b/src/types/UiaTextRangeBase.hpp @@ -107,7 +107,7 @@ namespace Microsoft::Console::Types IFACEMETHODIMP FindText(_In_ BSTR text, _In_ BOOL searchBackward, _In_ BOOL ignoreCase, - _Outptr_result_maybenull_ ITextRangeProvider** ppRetVal) noexcept override; + _Outptr_result_maybenull_ ITextRangeProvider** ppRetVal) override; IFACEMETHODIMP GetAttributeValue(_In_ TEXTATTRIBUTEID textAttributeId, _Out_ VARIANT* pRetVal) noexcept override; IFACEMETHODIMP GetBoundingRectangles(_Outptr_result_maybenull_ SAFEARRAY** ppRetVal) override; From a9cafb897a871def0ea5439d0248f5e3dc3f46f0 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Fri, 31 Jan 2020 12:59:50 -0800 Subject: [PATCH 25/25] PR comments --- src/types/UiaTextRangeBase.cpp | 69 +++++++++++++++++----------------- src/types/UiaTextRangeBase.hpp | 2 +- 2 files changed, 35 insertions(+), 36 deletions(-) diff --git a/src/types/UiaTextRangeBase.cpp b/src/types/UiaTextRangeBase.cpp index b879ed7cc23..77a8087d88c 100644 --- a/src/types/UiaTextRangeBase.cpp +++ b/src/types/UiaTextRangeBase.cpp @@ -354,55 +354,54 @@ IFACEMETHODIMP UiaTextRangeBase::FindAttribute(_In_ TEXTATTRIBUTEID /*textAttrib IFACEMETHODIMP UiaTextRangeBase::FindText(_In_ BSTR text, _In_ BOOL searchBackward, _In_ BOOL ignoreCase, - _Outptr_result_maybenull_ ITextRangeProvider** ppRetVal) + _Outptr_result_maybenull_ ITextRangeProvider** ppRetVal) noexcept +try { // TODO GitHub #1914: Re-attach Tracing to UIA Tree //Tracing::s_TraceUia(this, ApiCall::FindText, nullptr); RETURN_HR_IF(E_INVALIDARG, ppRetVal == nullptr); *ppRetVal = nullptr; - try - { - const std::wstring queryText{ text }; - const auto bufferSize = _pData->GetTextBuffer().GetSize(); - const auto sensitivity = ignoreCase ? Search::Sensitivity::CaseInsensitive : Search::Sensitivity::CaseSensitive; - auto searchDirection = Search::Direction::Forward; - auto searchAnchor = _start; - if (searchBackward) - { - searchDirection = Search::Direction::Backward; + const std::wstring queryText{ text, SysStringLen(text) }; + const auto bufferSize = _pData->GetTextBuffer().GetSize(); + const auto sensitivity = ignoreCase ? Search::Sensitivity::CaseInsensitive : Search::Sensitivity::CaseSensitive; - // we need to convert the end to inclusive - // because Search operates with an inclusive COORD - searchAnchor = _end; - bufferSize.DecrementInBounds(searchAnchor, true); - } + auto searchDirection = Search::Direction::Forward; + auto searchAnchor = _start; + if (searchBackward) + { + searchDirection = Search::Direction::Backward; - Search searcher{ *_pData, queryText, searchDirection, sensitivity, searchAnchor }; + // we need to convert the end to inclusive + // because Search operates with an inclusive COORD + searchAnchor = _end; + bufferSize.DecrementInBounds(searchAnchor, true); + } - if (searcher.FindNext()) - { - const auto foundLocation = searcher.GetFoundLocation(); - const auto start = foundLocation.first; + Search searcher{ *_pData, queryText, searchDirection, sensitivity, searchAnchor }; - // we need to increment the position of end because it's exclusive - auto end = foundLocation.second; - bufferSize.IncrementInBounds(end, true); + if (searcher.FindNext()) + { + const auto foundLocation = searcher.GetFoundLocation(); + const auto start = foundLocation.first; - // make sure what was found is within the bounds of the current range - if ((searchDirection == Search::Direction::Forward && bufferSize.CompareInBounds(end, _end, true) < 0) || - (searchDirection == Search::Direction::Backward && bufferSize.CompareInBounds(start, _start) > 0)) - { - RETURN_IF_FAILED(Clone(ppRetVal)); - UiaTextRangeBase& range = static_cast(**ppRetVal); - range._start = start; - range._end = end; - } + // we need to increment the position of end because it's exclusive + auto end = foundLocation.second; + bufferSize.IncrementInBounds(end, true); + + // make sure what was found is within the bounds of the current range + if ((searchDirection == Search::Direction::Forward && bufferSize.CompareInBounds(end, _end, true) < 0) || + (searchDirection == Search::Direction::Backward && bufferSize.CompareInBounds(start, _start) > 0)) + { + RETURN_IF_FAILED(Clone(ppRetVal)); + UiaTextRangeBase& range = static_cast(**ppRetVal); + range._start = start; + range._end = end; } - return S_OK; } - CATCH_RETURN(); + return S_OK; } +CATCH_RETURN(); IFACEMETHODIMP UiaTextRangeBase::GetAttributeValue(_In_ TEXTATTRIBUTEID textAttributeId, _Out_ VARIANT* pRetVal) noexcept diff --git a/src/types/UiaTextRangeBase.hpp b/src/types/UiaTextRangeBase.hpp index 10fbc2f5607..b5b7dbebc97 100644 --- a/src/types/UiaTextRangeBase.hpp +++ b/src/types/UiaTextRangeBase.hpp @@ -107,7 +107,7 @@ namespace Microsoft::Console::Types IFACEMETHODIMP FindText(_In_ BSTR text, _In_ BOOL searchBackward, _In_ BOOL ignoreCase, - _Outptr_result_maybenull_ ITextRangeProvider** ppRetVal) override; + _Outptr_result_maybenull_ ITextRangeProvider** ppRetVal) noexcept override; IFACEMETHODIMP GetAttributeValue(_In_ TEXTATTRIBUTEID textAttributeId, _Out_ VARIANT* pRetVal) noexcept override; IFACEMETHODIMP GetBoundingRectangles(_Outptr_result_maybenull_ SAFEARRAY** ppRetVal) override;