From a99fa3cd645ad667c56ed0f18c08bf49baf16c79 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Tue, 22 Jun 2021 16:29:41 -0700 Subject: [PATCH] fix/add tests; remove invalid attributes --- src/buffer/out/textBuffer.cpp | 34 +-- src/buffer/out/textBuffer.hpp | 6 +- src/buffer/out/textBufferCellIterator.cpp | 39 ++-- src/buffer/out/textBufferCellIterator.hpp | 6 +- src/cascadia/TerminalControl/ControlCore.cpp | 2 + .../TerminalControl/XamlUiaTextRange.cpp | 24 +- src/cascadia/TerminalCore/Terminal.hpp | 4 +- .../TerminalCore/terminalrenderdata.cpp | 10 + src/host/renderData.hpp | 3 +- src/host/ut_host/TextBufferIteratorTests.cpp | 138 ++++++++++-- .../UiaTextRangeTests.cpp | 207 +++++++----------- src/renderer/inc/IRenderData.hpp | 2 - src/types/IBaseData.h | 1 + src/types/UiaTextRangeBase.cpp | 195 ++++++----------- 14 files changed, 346 insertions(+), 325 deletions(-) diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp index d322c871a36..e56d4f7c630 100644 --- a/src/buffer/out/textBuffer.cpp +++ b/src/buffer/out/textBuffer.cpp @@ -149,13 +149,13 @@ TextBufferTextIterator TextBuffer::GetTextLineDataAt(const COORD at) const // - Read-only iterator of cell data. TextBufferCellIterator TextBuffer::GetCellLineDataAt(const COORD at) const { - SMALL_RECT limit; - limit.Top = at.Y; - limit.Bottom = at.Y; - limit.Left = 0; - limit.Right = GetSize().RightInclusive(); + SMALL_RECT bounds; + bounds.Top = at.Y; + bounds.Bottom = at.Y; + bounds.Left = 0; + bounds.Right = GetSize().RightInclusive(); - return TextBufferCellIterator(*this, at, Viewport::FromInclusive(limit)); + return TextBufferCellIterator(*this, at, Viewport::FromInclusive(bounds)); } // Routine Description: @@ -163,12 +163,12 @@ TextBufferCellIterator TextBuffer::GetCellLineDataAt(const COORD at) const // but restricted to operate only inside the given viewport. // Arguments: // - at - X,Y position in buffer for iterator start position -// - limit - boundaries for the iterator to operate within +// - bounds - boundaries for the iterator to operate within // Return Value: // - Read-only iterator of text data only. -TextBufferTextIterator TextBuffer::GetTextDataAt(const COORD at, const Viewport limit) const +TextBufferTextIterator TextBuffer::GetTextDataAt(const COORD at, const Viewport bounds) const { - return TextBufferTextIterator(GetCellDataAt(at, limit)); + return TextBufferTextIterator(GetCellDataAt(at, bounds)); } // Routine Description: @@ -176,12 +176,12 @@ TextBufferTextIterator TextBuffer::GetTextDataAt(const COORD at, const Viewport // but restricted to operate only inside the given viewport. // Arguments: // - at - X,Y position in buffer for iterator start position -// - limit - boundaries for the iterator to operate within +// - bounds - boundaries for the iterator to operate within // Return Value: // - Read-only iterator of cell data. -TextBufferCellIterator TextBuffer::GetCellDataAt(const COORD at, const Viewport limit) const +TextBufferCellIterator TextBuffer::GetCellDataAt(const COORD at, const Viewport bounds) const { - return TextBufferCellIterator(*this, at, limit); + return TextBufferCellIterator(*this, at, bounds); } // Routine Description: @@ -189,13 +189,15 @@ TextBufferCellIterator TextBuffer::GetCellDataAt(const COORD at, const Viewport // but restricted to operate only inside the given viewport. // Arguments: // - at - X,Y position in buffer for iterator start position -// - limit - boundaries for the iterator to operate within -// - until - X,Y position in buffer for last position for the iterator to read (inclusive) +// - bounds - viewport boundaries for the iterator to operate within. +// Allows for us to iterate over a sub-grid of the buffer. +// - limit - X,Y position in buffer for the iterator end position (inclusive). +// Allows for us to iterate through "bounds" until we hit the end of "bounds" or the limit. // Return Value: // - Read-only iterator of cell data. -TextBufferCellIterator TextBuffer::GetCellDataAt(const COORD at, const Viewport limit, const COORD until) const +TextBufferCellIterator TextBuffer::GetCellDataAt(const COORD at, const Viewport bounds, const COORD limit) const { - return TextBufferCellIterator(*this, at, limit, until); + return TextBufferCellIterator(*this, at, bounds, limit); } //Routine Description: diff --git a/src/buffer/out/textBuffer.hpp b/src/buffer/out/textBuffer.hpp index 702a57d493b..d84b8d6c5d0 100644 --- a/src/buffer/out/textBuffer.hpp +++ b/src/buffer/out/textBuffer.hpp @@ -80,11 +80,11 @@ class TextBuffer final TextBufferCellIterator GetCellDataAt(const COORD at) const; TextBufferCellIterator GetCellLineDataAt(const COORD at) const; - TextBufferCellIterator GetCellDataAt(const COORD at, const Microsoft::Console::Types::Viewport limit) const; - TextBufferCellIterator GetCellDataAt(const COORD at, const Microsoft::Console::Types::Viewport limit, const COORD until) const; + TextBufferCellIterator GetCellDataAt(const COORD at, const Microsoft::Console::Types::Viewport bounds) const; + TextBufferCellIterator GetCellDataAt(const COORD at, const Microsoft::Console::Types::Viewport bounds, const COORD limit) const; TextBufferTextIterator GetTextDataAt(const COORD at) const; TextBufferTextIterator GetTextLineDataAt(const COORD at) const; - TextBufferTextIterator GetTextDataAt(const COORD at, const Microsoft::Console::Types::Viewport limit) const; + TextBufferTextIterator GetTextDataAt(const COORD at, const Microsoft::Console::Types::Viewport bounds) const; // Text insertion functions OutputCellIterator Write(const OutputCellIterator givenIt); diff --git a/src/buffer/out/textBufferCellIterator.cpp b/src/buffer/out/textBufferCellIterator.cpp index a8402f7b8a8..ba6b04ebc0b 100644 --- a/src/buffer/out/textBufferCellIterator.cpp +++ b/src/buffer/out/textBufferCellIterator.cpp @@ -29,21 +29,21 @@ TextBufferCellIterator::TextBufferCellIterator(const TextBuffer& buffer, COORD p // Arguments: // - buffer - Pointer to screen buffer to seek through // - pos - Starting position to retrieve text data from (within screen buffer bounds) -// - limits - Viewport limits to restrict the iterator within the buffer bounds (smaller than the buffer itself) -TextBufferCellIterator::TextBufferCellIterator(const TextBuffer& buffer, COORD pos, const Viewport limits) : +// - bounds - Viewport boundaries to restrict the iterator within the buffer bounds (smaller than the buffer itself) +TextBufferCellIterator::TextBufferCellIterator(const TextBuffer& buffer, COORD pos, const Viewport bounds) : _buffer(buffer), _pos(pos), _pRow(s_GetRow(buffer, pos)), - _bounds(limits), + _bounds(bounds), _exceeded(false), _view({}, {}, {}, TextAttributeBehavior::Stored), _attrIter(s_GetRow(buffer, pos)->GetAttrRow().cbegin()) { // Throw if the bounds rectangle is not limited to the inside of the given buffer. - THROW_HR_IF(E_INVALIDARG, !buffer.GetSize().IsInBounds(limits)); + THROW_HR_IF(E_INVALIDARG, !buffer.GetSize().IsInBounds(bounds)); // Throw if the coordinate is not limited to the inside of the given buffer. - THROW_HR_IF(E_INVALIDARG, !limits.IsInBounds(pos)); + THROW_HR_IF(E_INVALIDARG, !bounds.IsInBounds(pos)); _attrIter += pos.X; @@ -55,18 +55,15 @@ TextBufferCellIterator::TextBufferCellIterator(const TextBuffer& buffer, COORD p // Arguments: // - buffer - Text buffer to seek through // - pos - Starting position to retrieve text data from (within screen buffer bounds) -// - limits - Viewport limits to restrict the iterator within the buffer bounds (smaller than the buffer itself) -// - endPosInclusive - last position to iterate through (inclusive) -TextBufferCellIterator::TextBufferCellIterator(const TextBuffer& buffer, COORD pos, const Viewport limits, const COORD endPosInclusive) : - TextBufferCellIterator(buffer, pos, limits) +// - bounds - Viewport boundaries to restrict the iterator within the buffer bounds (smaller than the buffer itself) +// - limit - last position to iterate through (inclusive) +TextBufferCellIterator::TextBufferCellIterator(const TextBuffer& buffer, COORD pos, const Viewport bounds, const COORD limit) : + TextBufferCellIterator(buffer, pos, bounds) { // Throw if the coordinate is not limited to the inside of the given buffer. - THROW_HR_IF(E_INVALIDARG, !_bounds.IsInBounds(endPosInclusive)); + THROW_HR_IF(E_INVALIDARG, !_bounds.IsInBounds(limit)); - // Throw if pos is past endPos - THROW_HR_IF(E_INVALIDARG, _bounds.CompareInBounds(pos, endPosInclusive) > 0); - - _endPosInclusive = endPosInclusive; + _limit = limit; } // Routine Description: @@ -92,7 +89,7 @@ bool TextBufferCellIterator::operator==(const TextBufferCellIterator& it) const _bounds == it._bounds && _pRow == it._pRow && _attrIter == it._attrIter && - _endPosInclusive == _endPosInclusive; + _limit == _limit; } // Routine Description: @@ -118,22 +115,22 @@ TextBufferCellIterator& TextBufferCellIterator::operator+=(const ptrdiff_t& move auto newPos = _pos; while (move > 0 && !_exceeded) { - // If we have an endPos, check if we've exceeded it - if (_endPosInclusive.has_value()) + // If we have a limit, check if we've exceeded it + if (_limit.has_value()) { - _exceeded = _bounds.CompareInBounds(newPos, *_endPosInclusive) > 0; + _exceeded |= (newPos == _limit); } - // If we already exceeded from endPos, we'll short-circuit and _not_ increment + // If we already exceeded limit, we'll short-circuit and _not_ increment _exceeded |= !_bounds.IncrementInBounds(newPos); move--; } while (move < 0 && !_exceeded) { // If we have an endPos, check if we've exceeded it - if (_endPosInclusive.has_value()) + if (_limit.has_value()) { - _exceeded = _bounds.CompareInBounds(newPos, *_endPosInclusive) < 0; + _exceeded |= (newPos == _limit); } // If we already exceeded from endPos, we'll short-circuit and _not_ decrement diff --git a/src/buffer/out/textBufferCellIterator.hpp b/src/buffer/out/textBufferCellIterator.hpp index c5fe69142e8..1877834412d 100644 --- a/src/buffer/out/textBufferCellIterator.hpp +++ b/src/buffer/out/textBufferCellIterator.hpp @@ -26,8 +26,8 @@ class TextBufferCellIterator { public: TextBufferCellIterator(const TextBuffer& buffer, COORD pos); - TextBufferCellIterator(const TextBuffer& buffer, COORD pos, const Microsoft::Console::Types::Viewport limits); - TextBufferCellIterator(const TextBuffer& buffer, COORD pos, const Microsoft::Console::Types::Viewport limits, const COORD endPosInclusive); + TextBufferCellIterator(const TextBuffer& buffer, COORD pos, const Microsoft::Console::Types::Viewport bounds); + TextBufferCellIterator(const TextBuffer& buffer, COORD pos, const Microsoft::Console::Types::Viewport bounds, const COORD limit); operator bool() const noexcept; @@ -63,7 +63,7 @@ class TextBufferCellIterator const Microsoft::Console::Types::Viewport _bounds; bool _exceeded; COORD _pos; - std::optional _endPosInclusive; + std::optional _limit; #if UNIT_TESTING friend class TextBufferIteratorTests; diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 7e9e9c7d78c..932042a8b1f 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -592,6 +592,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation const int newDpi = static_cast(static_cast(USER_DEFAULT_SCREEN_DPI) * _compositionScale); + _terminal->SetFontInfo(_actualFont); + // TODO: MSFT:20895307 If the font doesn't exist, this doesn't // actually fail. We need a way to gracefully fallback. _renderer->TriggerFontChange(newDpi, _desiredFont, _actualFont); diff --git a/src/cascadia/TerminalControl/XamlUiaTextRange.cpp b/src/cascadia/TerminalControl/XamlUiaTextRange.cpp index 73b447d0a30..6ecb35c44e1 100644 --- a/src/cascadia/TerminalControl/XamlUiaTextRange.cpp +++ b/src/cascadia/TerminalControl/XamlUiaTextRange.cpp @@ -103,7 +103,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation } case VT_I4: { - return box_value(result.iVal); + // Surprisingly, `long` is _not_ a WinRT type. + // So we have to use `int32_t` to make sure this is output properly. + // Otherwise, you'll get "Attribute does not exist" out the other end. + return box_value(result.lVal); } case VT_R8: { @@ -122,17 +125,16 @@ namespace winrt::Microsoft::Terminal::Control::implementation // are supported at this time. // So we need to figure out what was actually intended to be returned. - IUnknown* notSupportedVal; - UiaGetReservedNotSupportedValue(¬SupportedVal); - if (result.punkVal == notSupportedVal) - { - // See below for why we need to throw this special value. - winrt::throw_hresult(XAML_E_NOT_SUPPORTED); - } + // use C++11 magic statics to make sure we only do this once. + static const auto mixedAttributeVal = []() { + IUnknown* resultRaw; + com_ptr result; + UiaGetReservedMixedAttributeValue(&resultRaw); + result.attach(resultRaw); + return result; + }(); - IUnknown* mixedAttributeVal; - UiaGetReservedMixedAttributeValue(&mixedAttributeVal); - if (result.punkVal == mixedAttributeVal) + if (result.punkVal == mixedAttributeVal.get()) { return Windows::UI::Xaml::DependencyProperty::UnsetValue(); } diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index df9b1bc71ae..f4d173649d6 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -68,6 +68,7 @@ class Microsoft::Terminal::Core::Terminal final : void UpdateSettings(winrt::Microsoft::Terminal::Core::ICoreSettings settings); void UpdateAppearance(const winrt::Microsoft::Terminal::Core::ICoreAppearance& appearance); + void SetFontInfo(const FontInfo& fontInfo); // Write goes through the parser void Write(std::wstring_view stringView); @@ -160,6 +161,7 @@ class Microsoft::Terminal::Core::Terminal final : COORD GetTextBufferEndPosition() const noexcept override; const TextBuffer& GetTextBuffer() noexcept override; const FontInfo& GetFontInfo() noexcept override; + std::pair GetAttributeColors(const TextAttribute& attr) const noexcept override; void LockConsole() noexcept override; void UnlockConsole() noexcept override; @@ -168,7 +170,6 @@ class Microsoft::Terminal::Core::Terminal final : #pragma region IRenderData // These methods are defined in TerminalRenderData.cpp const TextAttribute GetDefaultBrushColors() noexcept override; - std::pair GetAttributeColors(const TextAttribute& attr) const noexcept override; COORD GetCursorPosition() const noexcept override; bool IsCursorVisible() const noexcept override; bool IsCursorOn() const noexcept override; @@ -276,6 +277,7 @@ class Microsoft::Terminal::Core::Terminal final : size_t _hyperlinkPatternId; std::wstring _workingDirectory; + std::optional _fontInfo; #pragma region Text Selection // a selection is represented as a range between two COORDs (start and end) // the pivot is the COORD that remains selected when you extend a selection in any direction diff --git a/src/cascadia/TerminalCore/terminalrenderdata.cpp b/src/cascadia/TerminalCore/terminalrenderdata.cpp index 17b711db748..47e2a28022f 100644 --- a/src/cascadia/TerminalCore/terminalrenderdata.cpp +++ b/src/cascadia/TerminalCore/terminalrenderdata.cpp @@ -34,6 +34,11 @@ const TextBuffer& Terminal::GetTextBuffer() noexcept #pragma warning(disable : 26447) const FontInfo& Terminal::GetFontInfo() noexcept { + if (_fontInfo) + { + return *_fontInfo; + } + // TODO: This font value is only used to check if the font is a raster font. // Otherwise, the font is changed with the renderer via TriggerFontChange. // The renderer never uses any of the other members from the value returned @@ -45,6 +50,11 @@ const FontInfo& Terminal::GetFontInfo() noexcept } #pragma warning(pop) +void Terminal::SetFontInfo(const FontInfo& fontInfo) +{ + _fontInfo = fontInfo; +} + const TextAttribute Terminal::GetDefaultBrushColors() noexcept { return TextAttribute{}; diff --git a/src/host/renderData.hpp b/src/host/renderData.hpp index 9e1e79b0b56..8b858c541e5 100644 --- a/src/host/renderData.hpp +++ b/src/host/renderData.hpp @@ -27,6 +27,7 @@ class RenderData final : COORD GetTextBufferEndPosition() const noexcept override; const TextBuffer& GetTextBuffer() noexcept override; const FontInfo& GetFontInfo() noexcept override; + std::pair GetAttributeColors(const TextAttribute& attr) const noexcept override; std::vector GetSelectionRects() noexcept override; @@ -37,8 +38,6 @@ class RenderData final : #pragma region IRenderData const TextAttribute GetDefaultBrushColors() noexcept override; - std::pair GetAttributeColors(const TextAttribute& attr) const noexcept override; - COORD GetCursorPosition() const noexcept override; bool IsCursorVisible() const noexcept override; bool IsCursorOn() const noexcept override; diff --git a/src/host/ut_host/TextBufferIteratorTests.cpp b/src/host/ut_host/TextBufferIteratorTests.cpp index 197deffa143..278ebaaf13d 100644 --- a/src/host/ut_host/TextBufferIteratorTests.cpp +++ b/src/host/ut_host/TextBufferIteratorTests.cpp @@ -264,8 +264,11 @@ class TextBufferIteratorTests TEST_METHOD(DereferenceOperatorText); TEST_METHOD(DereferenceOperatorCell); - TEST_METHOD(ConstructedNoLimit); - TEST_METHOD(ConstructedLimits); + TEST_METHOD(ConstructedNoBounds); + TEST_METHOD(ConstructedBounds); + TEST_METHOD(ConstructedLimit); + TEST_METHOD(ConstructedLimitBackwards); + TEST_METHOD(ConstructedBoundsAndLimit); }; template @@ -510,7 +513,7 @@ void TextBufferIteratorTests::DereferenceOperatorCell() VERIFY_ARE_EQUAL(attrExpected, attrActual); } -void TextBufferIteratorTests::ConstructedNoLimit() +void TextBufferIteratorTests::ConstructedNoBounds() { m_state->FillTextBuffer(); @@ -523,6 +526,7 @@ void TextBufferIteratorTests::ConstructedNoLimit() VERIFY_IS_TRUE(it, L"Iterator is valid."); VERIFY_ARE_EQUAL(bufferSize, it._bounds, L"Bounds match the bounds of the text buffer."); + VERIFY_IS_FALSE(it._limit.has_value(), L"Positional limit is not defined."); const auto totalBufferDistance = bufferSize.Width() * bufferSize.Height(); @@ -538,7 +542,7 @@ void TextBufferIteratorTests::ConstructedNoLimit() VERIFY_THROWS_SPECIFIC(TextBufferCellIterator(textBuffer, { -1, -1 }), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_INVALIDARG; }); } -void TextBufferIteratorTests::ConstructedLimits() +void TextBufferIteratorTests::ConstructedBounds() { m_state->FillTextBuffer(); @@ -546,21 +550,22 @@ void TextBufferIteratorTests::ConstructedLimits() const auto& outputBuffer = gci.GetActiveOutputBuffer(); const auto& textBuffer = outputBuffer.GetTextBuffer(); - SMALL_RECT limits; - limits.Top = 1; - limits.Bottom = 1; - limits.Left = 3; - limits.Right = 5; - const auto viewport = Microsoft::Console::Types::Viewport::FromInclusive(limits); + SMALL_RECT bounds; + bounds.Top = 1; + bounds.Bottom = 1; + bounds.Left = 3; + bounds.Right = 5; + const auto viewport = Microsoft::Console::Types::Viewport::FromInclusive(bounds); COORD pos; - pos.X = limits.Left; - pos.Y = limits.Top; + pos.X = bounds.Left; + pos.Y = bounds.Top; TextBufferCellIterator it(textBuffer, pos, viewport); VERIFY_IS_TRUE(it, L"Iterator is valid."); VERIFY_ARE_EQUAL(viewport, it._bounds, L"Bounds match the bounds given."); + VERIFY_IS_FALSE(it._limit.has_value(), L"Positional limit is not defined."); const auto totalBufferDistance = viewport.Width() * viewport.Height(); @@ -579,7 +584,7 @@ void TextBufferIteratorTests::ConstructedLimits() wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_INVALIDARG; }); - // Verify throws for limit not inside buffer + // Verify throws for bounds not inside buffer const auto bufferSize = textBuffer.GetSize(); VERIFY_THROWS_SPECIFIC(TextBufferCellIterator(textBuffer, pos, @@ -587,3 +592,110 @@ void TextBufferIteratorTests::ConstructedLimits() wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_INVALIDARG; }); } + +void TextBufferIteratorTests::ConstructedLimit() +{ + m_state->FillTextBuffer(); + + const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + const auto& outputBuffer = gci.GetActiveOutputBuffer(); + const auto& textBuffer = outputBuffer.GetTextBuffer(); + const auto bufferSize = textBuffer.GetSize(); + + const COORD pos{ bufferSize.Origin() }; + const COORD limitPos{ 5, 1 }; + + TextBufferCellIterator it(textBuffer, pos, bufferSize, limitPos); + + VERIFY_IS_TRUE(it, L"Iterator is valid."); + VERIFY_ARE_EQUAL(bufferSize, it._bounds, L"Bounds match the bounds given."); + VERIFY_IS_TRUE(it._limit.has_value(), L"Positional limit is defined."); + VERIFY_ARE_EQUAL(limitPos, it._limit.value(), L"Positional limit matches the one given."); + + const auto totalBufferDistance = bufferSize.Width() + 5; + + // Advance buffer to one before the positional limit. + it += totalBufferDistance; + VERIFY_IS_TRUE(it, L"Iterator is still valid."); + + // Advance over the positional limit. + it++; + VERIFY_IS_FALSE(it, L"Iterator invalid now."); + + // Verify throws for positional limit not inside buffer + VERIFY_THROWS_SPECIFIC(TextBufferCellIterator(textBuffer, + pos, + bufferSize, + bufferSize.BottomRightExclusive()), + wil::ResultException, + [](wil::ResultException& e) { return e.GetErrorCode() == E_INVALIDARG; }); +} + +void TextBufferIteratorTests::ConstructedLimitBackwards() +{ + m_state->FillTextBuffer(); + + const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + const auto& outputBuffer = gci.GetActiveOutputBuffer(); + const auto& textBuffer = outputBuffer.GetTextBuffer(); + const auto bufferSize = textBuffer.GetSize(); + + const COORD pos{ 5, 1 }; + const COORD limitPos{ 2, 0 }; + + TextBufferCellIterator it(textBuffer, pos, bufferSize, limitPos); + + VERIFY_IS_TRUE(it, L"Iterator is valid."); + VERIFY_ARE_EQUAL(bufferSize, it._bounds, L"Bounds match the bounds given."); + VERIFY_IS_TRUE(it._limit.has_value(), L"Positional limit is defined."); + VERIFY_ARE_EQUAL(limitPos, it._limit.value(), L"Positional limit matches the one given."); + + const auto totalBufferDistance = bufferSize.Width() + 5 - 2; + + // Advance buffer to one before the positional limit. + it -= totalBufferDistance; + VERIFY_IS_TRUE(it, L"Iterator is still valid."); + + // Advance over the positional limit. + it--; + VERIFY_IS_FALSE(it, L"Iterator invalid now."); +} + +void TextBufferIteratorTests::ConstructedBoundsAndLimit() +{ + m_state->FillTextBuffer(); + + const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + const auto& outputBuffer = gci.GetActiveOutputBuffer(); + const auto& textBuffer = outputBuffer.GetTextBuffer(); + + SMALL_RECT bounds; + bounds.Top = 1; + bounds.Bottom = 2; + bounds.Left = 2; + bounds.Right = 5; + const auto viewport = Microsoft::Console::Types::Viewport::FromInclusive(bounds); + + COORD pos; + pos.X = bounds.Left; + pos.Y = bounds.Top; + + const COORD limitPos{ 4, 1 }; + + TextBufferCellIterator it(textBuffer, pos, viewport, limitPos); + + VERIFY_IS_TRUE(it, L"Iterator is valid."); + VERIFY_ARE_EQUAL(viewport, it._bounds, L"Bounds match the bounds given."); + VERIFY_IS_TRUE(it._limit.has_value(), L"Positional limit is defined."); + VERIFY_ARE_EQUAL(limitPos, it._limit.value(), L"Positional limit matches the one given."); + + const auto totalBufferDistance = 2; + + // Advance buffer to one before the positional limit. + it += totalBufferDistance; + VERIFY_IS_TRUE(it, L"Iterator is still valid."); + + // Advance over the positional limit. + it++; + VERIFY_IS_FALSE(it, L"Iterator invalid now."); +} diff --git a/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp b/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp index f5ed538ab0a..4b8a6d2bf10 100644 --- a/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp +++ b/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp @@ -133,20 +133,6 @@ class UiaTextRangeTests short yPos; }; - enum SupportedTextAttributes - { - FontName = UIA_FontNameAttributeId, // special handling - FontSize = UIA_FontSizeAttributeId, // special handling - FontWeight = UIA_FontWeightAttributeId, - ForegroundColor = UIA_ForegroundColorAttributeId, - Italic = UIA_IsItalicAttributeId, - IsReadOnly = UIA_IsReadOnlyAttributeId, // special handling (always false) - StrikethroughColor = UIA_StrikethroughColorAttributeId, - StrikethroughStyle = UIA_StrikethroughStyleAttributeId, - UnderlineColor = UIA_UnderlineColorAttributeId, - UnderlineStyle = UIA_UnderlineStyleAttributeId - }; - static constexpr wchar_t* toString(TextUnit unit) noexcept { // if a format is not supported, it goes to the next largest text unit @@ -1365,12 +1351,17 @@ class UiaTextRangeTests TEST_METHOD(GetAttributeValue) { Log::Comment(L"Check supported attributes"); - IUnknown* notSupportedVal; + Microsoft::WRL::ComPtr notSupportedVal; UiaGetReservedNotSupportedValue(¬SupportedVal); + + // Iterate over UIA's Text Attribute Identifiers + // Validate that we know which ones are (not) supported + // source: https://docs.microsoft.com/en-us/windows/win32/winauto/uiauto-textattribute-ids for (long uiaAttributeId = 40000; uiaAttributeId <= 40042; ++uiaAttributeId) { Microsoft::WRL::ComPtr utr; THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize(&utr, _pUiaData, &_dummyProvider)); + THROW_IF_FAILED(utr->ExpandToEnclosingUnit(TextUnit_Character)); Log::Comment(UiaTracing::convertAttributeId(uiaAttributeId).c_str()); VARIANT result; @@ -1378,28 +1369,22 @@ class UiaTextRangeTests switch (uiaAttributeId) { - case FontName: + case UIA_FontNameAttributeId: { VERIFY_ARE_EQUAL(VT_BSTR, result.vt); break; } - case FontSize: - { - VERIFY_ARE_EQUAL(VT_R8, result.vt); - break; - } - case FontWeight: - case ForegroundColor: - case StrikethroughColor: - case StrikethroughStyle: - case UnderlineColor: - case UnderlineStyle: + case UIA_BackgroundColorAttributeId: + case UIA_FontWeightAttributeId: + case UIA_ForegroundColorAttributeId: + case UIA_StrikethroughStyleAttributeId: + case UIA_UnderlineStyleAttributeId: { VERIFY_ARE_EQUAL(VT_I4, result.vt); break; } - case Italic: - case IsReadOnly: + case UIA_IsItalicAttributeId: + case UIA_IsReadOnlyAttributeId: { VERIFY_ARE_EQUAL(VT_BOOL, result.vt); break; @@ -1408,7 +1393,7 @@ class UiaTextRangeTests { // Expected: not supported VERIFY_ARE_EQUAL(VT_UNKNOWN, result.vt); - VERIFY_ARE_EQUAL(notSupportedVal, result.punkVal); + VERIFY_ARE_EQUAL(notSupportedVal.Get(), result.punkVal); break; } } @@ -1424,110 +1409,109 @@ class UiaTextRangeTests Microsoft::WRL::ComPtr utr; THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize(&utr, _pUiaData, &_dummyProvider)); + THROW_IF_FAILED(utr->ExpandToEnclosingUnit(TextUnit_Character)); + { + Log::Comment(L"Test Background"); + const auto rawBackgroundColor{ RGB(255, 0, 0) }; + attr.SetBackground(rawBackgroundColor); + updateBuffer(attr); + VARIANT result; + VERIFY_SUCCEEDED(utr->GetAttributeValue(UIA_BackgroundColorAttributeId, &result)); + + const COLORREF realBackgroundColor{ _pUiaData->GetAttributeColors(attr).second & 0x00ffffff }; + VERIFY_ARE_EQUAL(realBackgroundColor, static_cast(result.lVal)); + } { Log::Comment(L"Test Font Weight"); attr.SetBold(true); updateBuffer(attr); VARIANT result; - VERIFY_SUCCEEDED(utr->GetAttributeValue(FontWeight, &result)); - VERIFY_ARE_EQUAL(FW_BOLD, result.iVal); + VERIFY_SUCCEEDED(utr->GetAttributeValue(UIA_FontWeightAttributeId, &result)); + VERIFY_ARE_EQUAL(FW_BOLD, result.lVal); attr.SetBold(false); updateBuffer(attr); - VERIFY_SUCCEEDED(utr->GetAttributeValue(FontWeight, &result)); - VERIFY_ARE_EQUAL(FW_NORMAL, result.iVal); - - attr.SetFaint(true); - updateBuffer(attr); - VERIFY_SUCCEEDED(utr->GetAttributeValue(FontWeight, &result)); - VERIFY_ARE_EQUAL(FW_LIGHT, result.iVal); - - attr.SetFaint(false); - updateBuffer(attr); - VERIFY_SUCCEEDED(utr->GetAttributeValue(FontWeight, &result)); - VERIFY_ARE_EQUAL(FW_NORMAL, result.iVal); + VERIFY_SUCCEEDED(utr->GetAttributeValue(UIA_FontWeightAttributeId, &result)); + VERIFY_ARE_EQUAL(FW_NORMAL, result.lVal); + ; } { Log::Comment(L"Test Foreground"); - const auto foregroundColor{ RGB(255, 0, 0) }; - attr.SetForeground(foregroundColor); + const auto rawForegroundColor{ RGB(255, 0, 0) }; + attr.SetForeground(rawForegroundColor); updateBuffer(attr); VARIANT result; - VERIFY_SUCCEEDED(utr->GetAttributeValue(ForegroundColor, &result)); - VERIFY_ARE_EQUAL(foregroundColor, static_cast(result.iVal)); + VERIFY_SUCCEEDED(utr->GetAttributeValue(UIA_ForegroundColorAttributeId, &result)); + + const auto realForegroundColor{ _pUiaData->GetAttributeColors(attr).first & 0x00ffffff }; + VERIFY_ARE_EQUAL(realForegroundColor, static_cast(result.lVal)); } { Log::Comment(L"Test Italic"); attr.SetItalic(true); updateBuffer(attr); VARIANT result; - VERIFY_SUCCEEDED(utr->GetAttributeValue(Italic, &result)); + VERIFY_SUCCEEDED(utr->GetAttributeValue(UIA_IsItalicAttributeId, &result)); VERIFY_IS_TRUE(result.boolVal); attr.SetItalic(false); updateBuffer(attr); - VERIFY_SUCCEEDED(utr->GetAttributeValue(Italic, &result)); + VERIFY_SUCCEEDED(utr->GetAttributeValue(UIA_IsItalicAttributeId, &result)); VERIFY_IS_FALSE(result.boolVal); } { Log::Comment(L"Test Strikethrough"); - const auto foregroundColor{ attr.GetForeground().GetRGB() }; attr.SetCrossedOut(true); updateBuffer(attr); VARIANT result; - VERIFY_SUCCEEDED(utr->GetAttributeValue(StrikethroughColor, &result)); - VERIFY_ARE_EQUAL(foregroundColor, static_cast(result.iVal)); - VERIFY_SUCCEEDED(utr->GetAttributeValue(StrikethroughStyle, &result)); - VERIFY_ARE_EQUAL(TextDecorationLineStyle_Single, result.iVal); + VERIFY_SUCCEEDED(utr->GetAttributeValue(UIA_StrikethroughStyleAttributeId, &result)); + VERIFY_ARE_EQUAL(TextDecorationLineStyle_Single, result.lVal); attr.SetCrossedOut(false); updateBuffer(attr); - VERIFY_SUCCEEDED(utr->GetAttributeValue(StrikethroughColor, &result)); - VERIFY_ARE_EQUAL(0, result.iVal); - VERIFY_SUCCEEDED(utr->GetAttributeValue(StrikethroughStyle, &result)); - VERIFY_ARE_EQUAL(TextDecorationLineStyle_None, result.iVal); + VERIFY_SUCCEEDED(utr->GetAttributeValue(UIA_StrikethroughStyleAttributeId, &result)); + VERIFY_ARE_EQUAL(TextDecorationLineStyle_None, result.lVal); } { Log::Comment(L"Test Underline"); - const auto foregroundColor{ attr.GetForeground().GetRGB() }; + + // Single underline attr.SetUnderlined(true); updateBuffer(attr); VARIANT result; - VERIFY_SUCCEEDED(utr->GetAttributeValue(UnderlineColor, &result)); - VERIFY_ARE_EQUAL(foregroundColor, static_cast(result.iVal)); - VERIFY_SUCCEEDED(utr->GetAttributeValue(UnderlineStyle, &result)); - VERIFY_ARE_EQUAL(TextDecorationLineStyle_Single, result.iVal); + VERIFY_SUCCEEDED(utr->GetAttributeValue(UIA_UnderlineStyleAttributeId, &result)); + VERIFY_ARE_EQUAL(TextDecorationLineStyle_Single, result.lVal); + // Double underline + attr.SetDoublyUnderlined(true); + updateBuffer(attr); + VERIFY_SUCCEEDED(utr->GetAttributeValue(UIA_UnderlineStyleAttributeId, &result)); + VERIFY_ARE_EQUAL(TextDecorationLineStyle_Double, result.lVal); + + // Double underline attr.SetUnderlined(false); updateBuffer(attr); - VERIFY_SUCCEEDED(utr->GetAttributeValue(UnderlineColor, &result)); - VERIFY_ARE_EQUAL(0, result.iVal); - VERIFY_SUCCEEDED(utr->GetAttributeValue(UnderlineStyle, &result)); - VERIFY_ARE_EQUAL(TextDecorationLineStyle_None, result.iVal); + VERIFY_SUCCEEDED(utr->GetAttributeValue(UIA_UnderlineStyleAttributeId, &result)); + VERIFY_ARE_EQUAL(TextDecorationLineStyle_Double, result.lVal); + + // No underline + attr.SetDoublyUnderlined(false); + updateBuffer(attr); + VERIFY_SUCCEEDED(utr->GetAttributeValue(UIA_UnderlineStyleAttributeId, &result)); + VERIFY_ARE_EQUAL(TextDecorationLineStyle_None, result.lVal); } { Log::Comment(L"Test Font Name (special)"); VARIANT result; - VERIFY_SUCCEEDED(utr->GetAttributeValue(FontName, &result)); + VERIFY_SUCCEEDED(utr->GetAttributeValue(UIA_FontNameAttributeId, &result)); const std::wstring actualFontName{ result.bstrVal }; const auto expectedFontName{ _pUiaData->GetFontInfo().GetFaceName() }; VERIFY_ARE_EQUAL(expectedFontName, actualFontName); } - { - Log::Comment(L"Test Font Size (special)"); - VARIANT result; - VERIFY_SUCCEEDED(utr->GetAttributeValue(FontSize, &result)); - const auto actualFontSize{ result.dblVal }; - const auto expectedFontSize{ 0 }; //_pUiaData->GetFontInfo().GetUnscaledSize() }; - VERIFY_ARE_EQUAL(expectedFontSize, actualFontSize); - - // TODO CARLOS: Fix this test when we actually know how to convert the font size into points - VERIFY_IS_TRUE(false); - } { Log::Comment(L"Test Read Only (special)"); VARIANT result; - VERIFY_SUCCEEDED(utr->GetAttributeValue(IsReadOnly, &result)); + VERIFY_SUCCEEDED(utr->GetAttributeValue(UIA_IsReadOnlyAttributeId, &result)); VERIFY_IS_FALSE(result.boolVal); } } @@ -1548,7 +1532,7 @@ class UiaTextRangeTests var.bstrVal = SysAllocString(fontName.data()); Microsoft::WRL::ComPtr result; - VERIFY_SUCCEEDED(utr->FindAttribute(FontName, var, false, result.GetAddressOf())); + VERIFY_SUCCEEDED(utr->FindAttribute(UIA_FontNameAttributeId, var, false, result.GetAddressOf())); // Expecting the same text range endpoints BOOL isEqual; @@ -1558,41 +1542,12 @@ class UiaTextRangeTests // Now perform the same test, but searching backwards Log::Comment(L"Test Font Name (special) - Backwards"); Microsoft::WRL::ComPtr resultBackwards; - VERIFY_SUCCEEDED(utr->FindAttribute(FontName, var, true, resultBackwards.GetAddressOf())); + VERIFY_SUCCEEDED(utr->FindAttribute(UIA_FontNameAttributeId, var, true, resultBackwards.GetAddressOf())); // Expecting the same text range endpoints THROW_IF_FAILED(result->Compare(resultBackwards.Get(), &isEqual)); VERIFY_IS_TRUE(isEqual); } - { - Log::Comment(L"Test Font Size (special)"); - - // Populate query with font size currently in use. - const auto fontSize{ 0.0 }; //{ _pUiaData->GetFontInfo().GetUnscaledSize() }; - VARIANT var{}; - var.vt = VT_R8; - var.dblVal = 0; - - Microsoft::WRL::ComPtr result; - VERIFY_SUCCEEDED(utr->FindAttribute(FontSize, var, false, result.GetAddressOf())); - - // Expecting the same text range endpoints - BOOL isEqual; - THROW_IF_FAILED(utr->Compare(result.Get(), &isEqual)); - VERIFY_IS_TRUE(isEqual); - - // Now perform the same test, but searching backwards - Log::Comment(L"Test Font Size (special) - Backwards"); - Microsoft::WRL::ComPtr resultBackwards; - VERIFY_SUCCEEDED(utr->FindAttribute(FontSize, var, true, resultBackwards.GetAddressOf())); - - // Expecting the same text range endpoints - THROW_IF_FAILED(result->Compare(resultBackwards.Get(), &isEqual)); - VERIFY_IS_TRUE(isEqual); - - // TODO CARLOS: Fix this test when we actually know how to convert the font size into points - VERIFY_IS_TRUE(false); - } { Log::Comment(L"Test Read Only (special)"); @@ -1601,7 +1556,7 @@ class UiaTextRangeTests var.boolVal = false; Microsoft::WRL::ComPtr result; - VERIFY_SUCCEEDED(utr->FindAttribute(IsReadOnly, var, false, result.GetAddressOf())); + VERIFY_SUCCEEDED(utr->FindAttribute(UIA_IsReadOnlyAttributeId, var, false, result.GetAddressOf())); // Expecting the same text range endpoints BOOL isEqual; @@ -1611,7 +1566,7 @@ class UiaTextRangeTests // Now perform the same test, but searching backwards Log::Comment(L"Test Read Only (special) - Backwards"); Microsoft::WRL::ComPtr resultBackwards; - VERIFY_SUCCEEDED(utr->FindAttribute(IsReadOnly, var, true, resultBackwards.GetAddressOf())); + VERIFY_SUCCEEDED(utr->FindAttribute(UIA_IsReadOnlyAttributeId, var, true, resultBackwards.GetAddressOf())); // Expecting the same text range endpoints THROW_IF_FAILED(result->Compare(resultBackwards.Get(), &isEqual)); @@ -1633,35 +1588,29 @@ class UiaTextRangeTests auto iter{ _pUiaData->GetTextBuffer().GetCellDataAt(startPos) }; for (auto i = 0; i < 5; ++i) { - _pTextBuffer->Write({ italicAttr }, iter.Pos()); + _pTextBuffer->Write({ L"X", italicAttr }, iter.Pos()); ++iter; } // set the expected end (exclusive) - auto expectedEndPos{ iter.Pos() }; - _pUiaData->GetTextBuffer().GetSize().IncrementInBounds(expectedEndPos); + const auto expectedEndPos{ iter.Pos() }; VARIANT var{}; var.vt = VT_BOOL; var.boolVal = true; Microsoft::WRL::ComPtr result; - VERIFY_SUCCEEDED(utr->FindAttribute(Italic, var, false, result.GetAddressOf())); - - // TODO CARLOS: The "end" verification is failing. Here's a trace of what's going on: - // - UiaTextRangeBase: - // - checkIfAttrFound is properly set and used - // - the search loop properly sets the "start" - // - the search loop iterates through the entire utr because checkIfAttrFound - // continues to return true for the whole range - // I have no clue why this is happening. A fresh pair of eyes would be appreciated here :) - VERIFY_ARE_EQUAL(startPos, utr->_start); - VERIFY_ARE_EQUAL(expectedEndPos, utr->_end); + THROW_IF_FAILED(utr->ExpandToEnclosingUnit(TextUnit_Document)); + VERIFY_SUCCEEDED(utr->FindAttribute(UIA_IsItalicAttributeId, var, false, result.GetAddressOf())); + + Microsoft::WRL::ComPtr resultUtr{ static_cast(result.Get()) }; + VERIFY_ARE_EQUAL(startPos, resultUtr->_start); + VERIFY_ARE_EQUAL(expectedEndPos, resultUtr->_end); // Now perform the same test, but searching backwards Log::Comment(L"Test IsItalic (standard attribute) - Backwards"); Microsoft::WRL::ComPtr resultBackwards; - VERIFY_SUCCEEDED(utr->FindAttribute(Italic, var, true, resultBackwards.GetAddressOf())); + VERIFY_SUCCEEDED(utr->FindAttribute(UIA_IsItalicAttributeId, var, true, resultBackwards.GetAddressOf())); // Expecting the same text range endpoints BOOL isEqual; diff --git a/src/renderer/inc/IRenderData.hpp b/src/renderer/inc/IRenderData.hpp index 56ab872e217..a8c96be268a 100644 --- a/src/renderer/inc/IRenderData.hpp +++ b/src/renderer/inc/IRenderData.hpp @@ -48,8 +48,6 @@ namespace Microsoft::Console::Render virtual const TextAttribute GetDefaultBrushColors() noexcept = 0; - virtual std::pair GetAttributeColors(const TextAttribute& attr) const noexcept = 0; - virtual COORD GetCursorPosition() const noexcept = 0; virtual bool IsCursorVisible() const noexcept = 0; virtual bool IsCursorOn() const noexcept = 0; diff --git a/src/types/IBaseData.h b/src/types/IBaseData.h index 58804c38269..6b9f8b722f7 100644 --- a/src/types/IBaseData.h +++ b/src/types/IBaseData.h @@ -37,6 +37,7 @@ namespace Microsoft::Console::Types virtual COORD GetTextBufferEndPosition() const noexcept = 0; virtual const TextBuffer& GetTextBuffer() noexcept = 0; virtual const FontInfo& GetFontInfo() noexcept = 0; + virtual std::pair GetAttributeColors(const TextAttribute& attr) const noexcept = 0; virtual std::vector GetSelectionRects() noexcept = 0; diff --git a/src/types/UiaTextRangeBase.cpp b/src/types/UiaTextRangeBase.cpp index c77cbef2630..4a7e4b23883 100644 --- a/src/types/UiaTextRangeBase.cpp +++ b/src/types/UiaTextRangeBase.cpp @@ -324,6 +324,17 @@ std::function UiaTextRangeBase::_getAttrVerification // has the desired attribute. switch (attributeId) { + case UIA_BackgroundColorAttributeId: + { + // Expected type: VT_I4 + THROW_HR_IF(E_INVALIDARG, val.vt != VT_I4); + + // The foreground color is stored as a COLORREF. + const auto queryBackgroundColor{ base::ClampedNumeric(val.lVal) }; + return [this, queryBackgroundColor](const TextAttribute& attr) { + return (_pData->GetAttributeColors(attr).second & 0x00ffffff) == queryBackgroundColor; + }; + } case UIA_FontWeightAttributeId: { // Expected type: VT_I4 @@ -332,7 +343,7 @@ std::function UiaTextRangeBase::_getAttrVerification // The font weight can be any value from 0 to 900. // The text buffer doesn't store the actual value, // we just store "IsBold" and "IsFaint". - const auto queryFontWeight{ val.iVal }; + const auto queryFontWeight{ val.lVal }; if (queryFontWeight > FW_NORMAL) { @@ -341,18 +352,11 @@ std::function UiaTextRangeBase::_getAttrVerification return attr.IsBold(); }; } - else if (queryFontWeight < FW_NORMAL) - { - // we're looking for a faint font weight - return [](const TextAttribute& attr) { - return attr.IsFaint(); - }; - } else { // we're looking for "normal" font weight return [](const TextAttribute& attr) { - return !attr.IsBold() && !attr.IsFaint(); + return !attr.IsBold(); }; } } @@ -362,9 +366,9 @@ std::function UiaTextRangeBase::_getAttrVerification THROW_HR_IF(E_INVALIDARG, val.vt != VT_I4); // The foreground color is stored as a COLORREF. - const COLORREF queryForegroundColor{ base::ClampedNumeric(val.iVal) }; - return [queryForegroundColor](const TextAttribute& attr) { - return attr.GetForeground().GetRGB() == queryForegroundColor; + const auto queryForegroundColor{ base::ClampedNumeric(val.lVal) }; + return [this, queryForegroundColor](const TextAttribute& attr) { + return (_pData->GetAttributeColors(attr).first & 0x00ffffff) == queryForegroundColor; }; } case UIA_IsItalicAttributeId: @@ -387,30 +391,6 @@ std::function UiaTextRangeBase::_getAttrVerification }; } } - case UIA_StrikethroughColorAttributeId: - { - // Expected type: VT_I4 - THROW_HR_IF(E_INVALIDARG, val.vt != VT_I4); - - // The strikethrough color is stored as a COLORREF. - // However, the text buffer doesn't actually store the color. - // Instead, we just store whether or not the text is crossed out. - // So we'll assume that _any_ query here (except 0) is interpreted as "is the - // text crossed out?" - - if (val.iVal == 0) - { - return [](const TextAttribute& attr) { - return !attr.IsCrossedOut(); - }; - } - else - { - return [](const TextAttribute& attr) { - return attr.IsCrossedOut(); - }; - } - } case UIA_StrikethroughStyleAttributeId: { // Expected type: VT_I4 @@ -419,44 +399,18 @@ std::function UiaTextRangeBase::_getAttrVerification // The strikethrough style is stored as a TextDecorationLineStyle. // However, The text buffer doesn't have different styles for being crossed out. // Instead, we just store whether or not the text is crossed out. - // So we'll assume that _any_ query here is interpreted as "is the - // text crossed out?" UNLESS we are asked for TextDecorationLineStyle_None - const TextDecorationLineStyle queryStrikethroughStyle{ static_cast(val.iVal) }; - if (queryStrikethroughStyle == TextDecorationLineStyle_None) + switch (val.lVal) { + case TextDecorationLineStyle_None: return [](const TextAttribute& attr) { return !attr.IsCrossedOut(); }; - } - else - { + case TextDecorationLineStyle_Single: return [](const TextAttribute& attr) { return attr.IsCrossedOut(); }; - } - } - case UIA_UnderlineColorAttributeId: - { - // Expected type: VT_I4 - THROW_HR_IF(E_INVALIDARG, val.vt != VT_I4); - - // The underline color is stored as a COLORREF. - // However, the text buffer doesn't actually store the color. - // Instead, we just store whether or not the text is underlined. - // So we'll assume that _any_ query here (except 0) is interpreted as "is the - // text underlined?" - - if (val.iVal == 0) - { - return [](const TextAttribute& attr) { - return !attr.IsUnderlined(); - }; - } - else - { - return [](const TextAttribute& attr) { - return attr.IsUnderlined(); - }; + default: + return nullptr; } } case UIA_UnderlineStyleAttributeId: @@ -465,22 +419,24 @@ std::function UiaTextRangeBase::_getAttrVerification THROW_HR_IF(E_INVALIDARG, val.vt != VT_I4); // The underline style is stored as a TextDecorationLineStyle. - // However, The text buffer doesn't have different styles for being underlined. - // Instead, we just store whether or not the text is underlined. - // So we'll assume that _any_ query here is interpreted as "is the - // text underlined?" UNLESS we are asked for TextDecorationLineStyle_None - const TextDecorationLineStyle queryUnderlineStyle{ static_cast(val.iVal) }; - if (queryUnderlineStyle == TextDecorationLineStyle_None) + // However, The text buffer doesn't have that many different styles for being underlined. + // Instead, we only have single and double underlined. + switch (val.lVal) { + case TextDecorationLineStyle_None: return [](const TextAttribute& attr) { - return !attr.IsUnderlined(); + return !attr.IsUnderlined() && !attr.IsDoublyUnderlined(); }; - } - else - { + case TextDecorationLineStyle_Double: + return [](const TextAttribute& attr) { + return attr.IsDoublyUnderlined(); + }; + case TextDecorationLineStyle_Single: return [](const TextAttribute& attr) { return attr.IsUnderlined(); }; + default: + return nullptr; } } default: @@ -510,19 +466,6 @@ IFACEMETHODIMP UiaTextRangeBase::FindAttribute(_In_ TEXTATTRIBUTEID attributeId, UiaTracing::TextRange::FindAttribute(*this, attributeId, val, searchBackwards, static_cast(**ppRetVal)); return S_OK; } - case UIA_FontSizeAttributeId: - { - RETURN_HR_IF(E_INVALIDARG, val.vt != VT_R8); - const auto queryFontSize{ val.dblVal }; - - // TODO CARLOS: how do I get the font size in points? - if (queryFontSize == 0) //_pData->GetFontInfo().GetUnscaledSize()) - { - Clone(ppRetVal); - } - UiaTracing::TextRange::FindAttribute(*this, attributeId, val, searchBackwards, static_cast(**ppRetVal)); - return S_OK; - } case UIA_IsReadOnlyAttributeId: { RETURN_HR_IF(E_INVALIDARG, val.vt != VT_BOOL); @@ -693,6 +636,12 @@ std::function UiaTextRangeBase::_getAttrVerification const auto attr{ _pData->GetTextBuffer().GetCellDataAt(_start)->TextAttr() }; switch (attributeId) { + case UIA_BackgroundColorAttributeId: + { + pRetVal->vt = VT_I4; + pRetVal->lVal = base::ClampedNumeric(_pData->GetAttributeColors(attr).second & 0x00ffffff); + return _getAttrVerificationFn(attributeId, *pRetVal); + } case UIA_FontWeightAttributeId: { // The font weight can be any value from 0 to 900. @@ -702,56 +651,50 @@ std::function UiaTextRangeBase::_getAttrVerification pRetVal->vt = VT_I4; if (attr.IsBold()) { - pRetVal->iVal = FW_BOLD; - } - else if (attr.IsFaint()) - { - pRetVal->iVal = FW_LIGHT; + pRetVal->lVal = FW_BOLD; } else { - pRetVal->iVal = FW_NORMAL; + pRetVal->lVal = FW_NORMAL; } return _getAttrVerificationFn(attributeId, *pRetVal); } case UIA_ForegroundColorAttributeId: { pRetVal->vt = VT_I4; - pRetVal->iVal = base::ClampedNumeric(attr.GetForeground().GetRGB()); + const auto x{ _pData->GetAttributeColors(attr).first }; + const auto y{ x & 0x00ffffff }; + pRetVal->lVal = y; + //pRetVal->lVal = base::ClampedNumeric(_pData->GetAttributeColors(attr).first & 0x00ffffff); return _getAttrVerificationFn(attributeId, *pRetVal); } case UIA_IsItalicAttributeId: { pRetVal->vt = VT_BOOL; - pRetVal->iVal = attr.IsItalic(); - return _getAttrVerificationFn(attributeId, *pRetVal); - } - case UIA_StrikethroughColorAttributeId: - { - // Default Value: 0 - // Source: https://docs.microsoft.com/en-us/windows/win32/winauto/uiauto-textattribute-ids - pRetVal->vt = VT_I4; - pRetVal->iVal = base::ClampedNumeric(attr.IsCrossedOut() ? attr.GetForeground().GetRGB() : 0); + pRetVal->lVal = attr.IsItalic(); return _getAttrVerificationFn(attributeId, *pRetVal); } case UIA_StrikethroughStyleAttributeId: { pRetVal->vt = VT_I4; - pRetVal->iVal = static_cast(attr.IsCrossedOut() ? TextDecorationLineStyle_Single : TextDecorationLineStyle_None); - return _getAttrVerificationFn(attributeId, *pRetVal); - } - case UIA_UnderlineColorAttributeId: - { - // Default Value: 0 - // Source: https://docs.microsoft.com/en-us/windows/win32/winauto/uiauto-textattribute-ids - pRetVal->vt = VT_I4; - pRetVal->iVal = attr.IsUnderlined() ? base::ClampedNumeric(attr.GetForeground().GetRGB()) : 0; + pRetVal->lVal = static_cast(attr.IsCrossedOut() ? TextDecorationLineStyle_Single : TextDecorationLineStyle_None); return _getAttrVerificationFn(attributeId, *pRetVal); } case UIA_UnderlineStyleAttributeId: { pRetVal->vt = VT_I4; - pRetVal->iVal = static_cast(attr.IsUnderlined() ? TextDecorationLineStyle_Single : TextDecorationLineStyle_None); + if (attr.IsDoublyUnderlined()) + { + pRetVal->lVal = static_cast(TextDecorationLineStyle_Double); + } + else if (attr.IsUnderlined()) + { + pRetVal->lVal = static_cast(TextDecorationLineStyle_Single); + } + else + { + pRetVal->lVal = static_cast(TextDecorationLineStyle_None); + } return _getAttrVerificationFn(attributeId, *pRetVal); } default: @@ -766,6 +709,7 @@ IFACEMETHODIMP UiaTextRangeBase::GetAttributeValue(_In_ TEXTATTRIBUTEID attribut _Out_ VARIANT* pRetVal) noexcept { RETURN_HR_IF(E_INVALIDARG, pRetVal == nullptr); + VariantInit(pRetVal); // AttributeIDs that require special handling switch (attributeId) @@ -777,14 +721,6 @@ IFACEMETHODIMP UiaTextRangeBase::GetAttributeValue(_In_ TEXTATTRIBUTEID attribut UiaTracing::TextRange::GetAttributeValue(*this, attributeId, *pRetVal); return S_OK; } - case UIA_FontSizeAttributeId: - { - pRetVal->vt = VT_R8; - // TODO CARLOS: how do I get the font size in points? - pRetVal->dblVal = 0; //_pData->GetFontInfo().GetUnscaledSize()) - UiaTracing::TextRange::GetAttributeValue(*this, attributeId, *pRetVal); - return S_OK; - } case UIA_IsReadOnlyAttributeId: { pRetVal->vt = VT_BOOL; @@ -816,6 +752,17 @@ IFACEMETHODIMP UiaTextRangeBase::GetAttributeValue(_In_ TEXTATTRIBUTEID attribut return E_INVALIDARG; } + if (IsDegenerate()) + { + // Unlike a normal text editor, which applies formatting at the caret, + // we don't know what attributes are written at a degenerate range. + // So let's return UiaGetReservedMixedAttributeValue. + // Source: https://docs.microsoft.com/en-us/windows/win32/api/uiautomationcore/nf-uiautomationcore-itextrangeprovider-getattributevalue + pRetVal->vt = VT_UNKNOWN; + UiaTracing::TextRange::GetAttributeValue(*this, attributeId, *pRetVal, UiaTracing::AttributeType::Mixed); + return UiaGetReservedMixedAttributeValue(&pRetVal->punkVal); + } + // Get some useful variables const auto& buffer{ _pData->GetTextBuffer() }; const auto bufferSize{ buffer.GetSize() };