diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp index 76d12708639..d322c871a36 100644 --- a/src/buffer/out/textBuffer.cpp +++ b/src/buffer/out/textBuffer.cpp @@ -184,6 +184,20 @@ TextBufferCellIterator TextBuffer::GetCellDataAt(const COORD at, const Viewport return TextBufferCellIterator(*this, at, limit); } +// Routine Description: +// - Retrieves read-only cell iterator at the given buffer location +// 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) +// Return Value: +// - Read-only iterator of cell data. +TextBufferCellIterator TextBuffer::GetCellDataAt(const COORD at, const Viewport limit, const COORD until) const +{ + return TextBufferCellIterator(*this, at, limit, until); +} + //Routine Description: // - Corrects and enforces consistent double byte character state (KAttrs line) within a row of the text buffer. // - This will take the given double byte information and check that it will be consistent when inserted into the buffer diff --git a/src/buffer/out/textBuffer.hpp b/src/buffer/out/textBuffer.hpp index 468a9fc4f34..702a57d493b 100644 --- a/src/buffer/out/textBuffer.hpp +++ b/src/buffer/out/textBuffer.hpp @@ -81,6 +81,7 @@ 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; TextBufferTextIterator GetTextDataAt(const COORD at) const; TextBufferTextIterator GetTextLineDataAt(const COORD at) const; TextBufferTextIterator GetTextDataAt(const COORD at, const Microsoft::Console::Types::Viewport limit) const; diff --git a/src/buffer/out/textBufferCellIterator.cpp b/src/buffer/out/textBufferCellIterator.cpp index 7ab512c72cd..a8402f7b8a8 100644 --- a/src/buffer/out/textBufferCellIterator.cpp +++ b/src/buffer/out/textBufferCellIterator.cpp @@ -50,6 +50,25 @@ TextBufferCellIterator::TextBufferCellIterator(const TextBuffer& buffer, COORD p _GenerateView(); } +// Routine Description: +// - Creates a new read-only iterator to seek through cell data stored within a screen buffer +// 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) +{ + // Throw if the coordinate is not limited to the inside of the given buffer. + THROW_HR_IF(E_INVALIDARG, !_bounds.IsInBounds(endPosInclusive)); + + // Throw if pos is past endPos + THROW_HR_IF(E_INVALIDARG, _bounds.CompareInBounds(pos, endPosInclusive) > 0); + + _endPosInclusive = endPosInclusive; +} + // Routine Description: // - Tells if the iterator is still valid (hasn't exceeded boundaries of underlying text buffer) // Return Value: @@ -72,7 +91,8 @@ bool TextBufferCellIterator::operator==(const TextBufferCellIterator& it) const _exceeded == it._exceeded && _bounds == it._bounds && _pRow == it._pRow && - _attrIter == it._attrIter; + _attrIter == it._attrIter && + _endPosInclusive == _endPosInclusive; } // Routine Description: @@ -98,12 +118,26 @@ TextBufferCellIterator& TextBufferCellIterator::operator+=(const ptrdiff_t& move auto newPos = _pos; while (move > 0 && !_exceeded) { - _exceeded = !_bounds.IncrementInBounds(newPos); + // If we have an endPos, check if we've exceeded it + if (_endPosInclusive.has_value()) + { + _exceeded = _bounds.CompareInBounds(newPos, *_endPosInclusive) > 0; + } + + // If we already exceeded from endPos, we'll short-circuit and _not_ increment + _exceeded |= !_bounds.IncrementInBounds(newPos); move--; } while (move < 0 && !_exceeded) { - _exceeded = !_bounds.DecrementInBounds(newPos); + // If we have an endPos, check if we've exceeded it + if (_endPosInclusive.has_value()) + { + _exceeded = _bounds.CompareInBounds(newPos, *_endPosInclusive) < 0; + } + + // If we already exceeded from endPos, we'll short-circuit and _not_ decrement + _exceeded |= !_bounds.DecrementInBounds(newPos); move++; } _SetPos(newPos); @@ -265,3 +299,8 @@ const OutputCellView* TextBufferCellIterator::operator->() const noexcept { return &_view; } + +COORD TextBufferCellIterator::Pos() const noexcept +{ + return _pos; +} diff --git a/src/buffer/out/textBufferCellIterator.hpp b/src/buffer/out/textBufferCellIterator.hpp index 8b7604bb6e4..c5fe69142e8 100644 --- a/src/buffer/out/textBufferCellIterator.hpp +++ b/src/buffer/out/textBufferCellIterator.hpp @@ -27,6 +27,7 @@ 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); operator bool() const noexcept; @@ -47,6 +48,8 @@ class TextBufferCellIterator const OutputCellView& operator*() const noexcept; const OutputCellView* operator->() const noexcept; + COORD Pos() const noexcept; + protected: void _SetPos(const COORD newPos); void _GenerateView(); @@ -60,6 +63,7 @@ class TextBufferCellIterator const Microsoft::Console::Types::Viewport _bounds; bool _exceeded; COORD _pos; + std::optional _endPosInclusive; #if UNIT_TESTING friend class TextBufferIteratorTests; diff --git a/src/cascadia/TerminalControl/XamlUiaTextRange.cpp b/src/cascadia/TerminalControl/XamlUiaTextRange.cpp index 7fb8d3b3d74..73b447d0a30 100644 --- a/src/cascadia/TerminalControl/XamlUiaTextRange.cpp +++ b/src/cascadia/TerminalControl/XamlUiaTextRange.cpp @@ -5,6 +5,7 @@ #include "XamlUiaTextRange.h" #include "../types/TermControlUiaTextRange.hpp" #include +#include // the same as COR_E_NOTSUPPORTED // we don't want to import the CLR headers to get it @@ -89,12 +90,56 @@ namespace winrt::Microsoft::Terminal::Control::implementation winrt::Windows::Foundation::IInspectable XamlUiaTextRange::GetAttributeValue(int32_t textAttributeId) const { - // Copied functionality from Types::UiaTextRange.cpp - if (textAttributeId == UIA_IsReadOnlyAttributeId) + // Call the function off of the underlying UiaTextRange. + VARIANT result; + THROW_IF_FAILED(_uiaProvider->GetAttributeValue(textAttributeId, &result)); + + // Convert the resulting VARIANT into a format that is consumable by XAML. + switch (result.vt) + { + case VT_BSTR: + { + return box_value(result.bstrVal); + } + case VT_I4: + { + return box_value(result.iVal); + } + case VT_R8: + { + return box_value(result.dblVal); + } + case VT_BOOL: + { + return box_value(result.boolVal); + } + case VT_UNKNOWN: { - return winrt::box_value(false); + // This one is particularly special. + // We might return a special value like UiaGetReservedMixedAttributeValue + // or UiaGetReservedNotSupportedValue. + // Some text attributes may return a real value, however, none of those + // 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); + } + + IUnknown* mixedAttributeVal; + UiaGetReservedMixedAttributeValue(&mixedAttributeVal); + if (result.punkVal == mixedAttributeVal) + { + return Windows::UI::Xaml::DependencyProperty::UnsetValue(); + } + + __fallthrough; } - else + default: { // We _need_ to return XAML_E_NOT_SUPPORTED here. // Returning nullptr is an improper implementation of it being unsupported. @@ -103,6 +148,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation // Magically, this doesn't affect other forms of navigation... winrt::throw_hresult(XAML_E_NOT_SUPPORTED); } + } } void XamlUiaTextRange::GetBoundingRectangles(com_array& returnValue) const diff --git a/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp b/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp index 17c771830fd..f5ed538ab0a 100644 --- a/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp +++ b/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp @@ -9,6 +9,7 @@ #include "uiaTextRange.hpp" #include "../types/ScreenInfoUiaProviderBase.h" #include "../../../buffer/out/textBuffer.hpp" +#include "../types/UiaTracing.h" using namespace WEX::Common; using namespace WEX::Logging; @@ -132,6 +133,20 @@ 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 @@ -1346,4 +1361,312 @@ class UiaTextRangeTests VERIFY_SUCCEEDED(utr->ScrollIntoView(alignToTop)); } } + + TEST_METHOD(GetAttributeValue) + { + Log::Comment(L"Check supported attributes"); + IUnknown* notSupportedVal; + UiaGetReservedNotSupportedValue(¬SupportedVal); + for (long uiaAttributeId = 40000; uiaAttributeId <= 40042; ++uiaAttributeId) + { + Microsoft::WRL::ComPtr utr; + THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize(&utr, _pUiaData, &_dummyProvider)); + + Log::Comment(UiaTracing::convertAttributeId(uiaAttributeId).c_str()); + VARIANT result; + VERIFY_SUCCEEDED(utr->GetAttributeValue(uiaAttributeId, &result)); + + switch (uiaAttributeId) + { + case FontName: + { + 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: + { + VERIFY_ARE_EQUAL(VT_I4, result.vt); + break; + } + case Italic: + case IsReadOnly: + { + VERIFY_ARE_EQUAL(VT_BOOL, result.vt); + break; + } + default: + { + // Expected: not supported + VERIFY_ARE_EQUAL(VT_UNKNOWN, result.vt); + VERIFY_ARE_EQUAL(notSupportedVal, result.punkVal); + break; + } + } + } + + // This is the text attribute we'll use to update the text buffer. + // We'll modify it, then test if the UiaTextRange can extract/interpret the data properly. + // updateBuffer() will write that text attribute to the first cell in the buffer. + TextAttribute attr; + auto updateBuffer = [&](TextAttribute outputAttr) { + _pTextBuffer->Write({ outputAttr }, { 0, 0 }); + }; + + Microsoft::WRL::ComPtr utr; + THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize(&utr, _pUiaData, &_dummyProvider)); + { + 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); + + 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); + } + { + Log::Comment(L"Test Foreground"); + const auto foregroundColor{ RGB(255, 0, 0) }; + attr.SetForeground(foregroundColor); + updateBuffer(attr); + VARIANT result; + VERIFY_SUCCEEDED(utr->GetAttributeValue(ForegroundColor, &result)); + VERIFY_ARE_EQUAL(foregroundColor, static_cast(result.iVal)); + } + { + Log::Comment(L"Test Italic"); + attr.SetItalic(true); + updateBuffer(attr); + VARIANT result; + VERIFY_SUCCEEDED(utr->GetAttributeValue(Italic, &result)); + VERIFY_IS_TRUE(result.boolVal); + + attr.SetItalic(false); + updateBuffer(attr); + VERIFY_SUCCEEDED(utr->GetAttributeValue(Italic, &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); + + 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); + } + { + Log::Comment(L"Test Underline"); + const auto foregroundColor{ attr.GetForeground().GetRGB() }; + 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); + + 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); + } + { + Log::Comment(L"Test Font Name (special)"); + VARIANT result; + VERIFY_SUCCEEDED(utr->GetAttributeValue(FontName, &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_IS_FALSE(result.boolVal); + } + } + + TEST_METHOD(FindAttribute) + { + Microsoft::WRL::ComPtr utr; + const COORD startPos{ 0, 0 }; + const COORD endPos{ 0, 2 }; + THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize(&utr, _pUiaData, &_dummyProvider, startPos, endPos)); + { + Log::Comment(L"Test Font Name (special)"); + + // Populate query with font name currently in use. + const auto fontName{ _pUiaData->GetFontInfo().GetFaceName() }; + VARIANT var{}; + var.vt = VT_BSTR; + var.bstrVal = SysAllocString(fontName.data()); + + Microsoft::WRL::ComPtr result; + VERIFY_SUCCEEDED(utr->FindAttribute(FontName, 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 Name (special) - Backwards"); + Microsoft::WRL::ComPtr resultBackwards; + VERIFY_SUCCEEDED(utr->FindAttribute(FontName, 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)"); + + VARIANT var{}; + var.vt = VT_BOOL; + var.boolVal = false; + + Microsoft::WRL::ComPtr result; + VERIFY_SUCCEEDED(utr->FindAttribute(IsReadOnly, 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 Read Only (special) - Backwards"); + Microsoft::WRL::ComPtr resultBackwards; + VERIFY_SUCCEEDED(utr->FindAttribute(IsReadOnly, 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 IsItalic (standard attribute)"); + + // Since all of the other attributes operate very similarly, + // we're just going to pick one of them and test that. + // The "GetAttribute" tests provide code coverage for + // retrieving an attribute verification function. + // This test is intended to provide code coverage for + // finding a text range with the desired attribute. + + // Set up the buffer's attributes. + TextAttribute italicAttr; + italicAttr.SetItalic(true); + auto iter{ _pUiaData->GetTextBuffer().GetCellDataAt(startPos) }; + for (auto i = 0; i < 5; ++i) + { + _pTextBuffer->Write({ italicAttr }, iter.Pos()); + ++iter; + } + + // set the expected end (exclusive) + auto expectedEndPos{ iter.Pos() }; + _pUiaData->GetTextBuffer().GetSize().IncrementInBounds(expectedEndPos); + + 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); + + // 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())); + + // Expecting the same text range endpoints + BOOL isEqual; + THROW_IF_FAILED(result->Compare(resultBackwards.Get(), &isEqual)); + VERIFY_IS_TRUE(isEqual); + } + } }; diff --git a/src/types/UiaTextRangeBase.cpp b/src/types/UiaTextRangeBase.cpp index 3d61d23f314..c77cbef2630 100644 --- a/src/types/UiaTextRangeBase.cpp +++ b/src/types/UiaTextRangeBase.cpp @@ -310,14 +310,319 @@ IFACEMETHODIMP UiaTextRangeBase::ExpandToEnclosingUnit(_In_ TextUnit unit) noexc CATCH_RETURN(); } -// we don't support this currently -IFACEMETHODIMP UiaTextRangeBase::FindAttribute(_In_ TEXTATTRIBUTEID /*textAttributeId*/, - _In_ VARIANT /*val*/, - _In_ BOOL /*searchBackward*/, - _Outptr_result_maybenull_ ITextRangeProvider** /*ppRetVal*/) noexcept +// Method Description: +// - Generate an attribute verification function for that attributeId and sub-type +// Arguments: +// - attributeId - the UIA text attribute identifier we're looking for +// - val - the attributeId's sub-type we're looking for +// Return Value: +// - a function that can be used to verify if a given TextAttribute meets the attributeId's sub-type +std::function UiaTextRangeBase::_getAttrVerificationFn(TEXTATTRIBUTEID attributeId, VARIANT val) const { - UiaTracing::TextRange::FindAttribute(*this); - return E_NOTIMPL; + // Most of the attributes we're looking for just require us to check TextAttribute. + // So if we support it, we'll return a function to verify if the TextAttribute + // has the desired attribute. + switch (attributeId) + { + case UIA_FontWeightAttributeId: + { + // Expected type: VT_I4 + THROW_HR_IF(E_INVALIDARG, val.vt != VT_I4); + + // 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 }; + + if (queryFontWeight > FW_NORMAL) + { + // we're looking for a bold font weight + return [](const TextAttribute& attr) { + 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(); + }; + } + } + case UIA_ForegroundColorAttributeId: + { + // Expected type: VT_I4 + 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; + }; + } + case UIA_IsItalicAttributeId: + { + // Expected type: VT_I4 + THROW_HR_IF(E_INVALIDARG, val.vt != VT_BOOL); + + // The text is either italic or it isn't. + const auto queryIsItalic{ val.boolVal }; + if (queryIsItalic) + { + return [](const TextAttribute& attr) { + return attr.IsItalic(); + }; + } + else + { + return [](const TextAttribute& attr) { + return !attr.IsItalic(); + }; + } + } + 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 + THROW_HR_IF(E_INVALIDARG, val.vt != VT_I4); + + // 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) + { + return [](const TextAttribute& attr) { + return !attr.IsCrossedOut(); + }; + } + else + { + 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(); + }; + } + } + case UIA_UnderlineStyleAttributeId: + { + // Expected type: VT_I4 + 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) + { + return [](const TextAttribute& attr) { + return !attr.IsUnderlined(); + }; + } + else + { + return [](const TextAttribute& attr) { + return attr.IsUnderlined(); + }; + } + } + default: + return nullptr; + } +} + +IFACEMETHODIMP UiaTextRangeBase::FindAttribute(_In_ TEXTATTRIBUTEID attributeId, + _In_ VARIANT val, + _In_ BOOL searchBackwards, + _Outptr_result_maybenull_ ITextRangeProvider** ppRetVal) noexcept +{ + RETURN_HR_IF(E_INVALIDARG, ppRetVal == nullptr); + *ppRetVal = nullptr; + + // AttributeIDs that require special handling + switch (attributeId) + { + case UIA_FontNameAttributeId: + { + RETURN_HR_IF(E_INVALIDARG, val.vt != VT_BSTR); + const std::wstring queryFontName{ val.bstrVal }; + if (queryFontName == _pData->GetFontInfo().GetFaceName()) + { + Clone(ppRetVal); + } + 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); + if (!val.boolVal) + { + Clone(ppRetVal); + } + UiaTracing::TextRange::FindAttribute(*this, attributeId, val, searchBackwards, static_cast(**ppRetVal)); + return S_OK; + } + default: + __fallthrough; + } + + // AttributeIDs that are exposed via TextAttribute + std::function checkIfAttrFound; + try + { + checkIfAttrFound = _getAttrVerificationFn(attributeId, val); + if (!checkIfAttrFound) + { + // The AttributeID is not supported. + UiaTracing::TextRange::FindAttribute(*this, attributeId, val, searchBackwards, static_cast(**ppRetVal), UiaTracing::AttributeType::Unsupported); + return E_NOTIMPL; + } + } + catch (...) + { + LOG_HR(wil::ResultFromCaughtException()); + UiaTracing::TextRange::FindAttribute(*this, attributeId, val, searchBackwards, static_cast(**ppRetVal), UiaTracing::AttributeType::Error); + return E_INVALIDARG; + } + + // Get some useful variables + const auto& buffer{ _pData->GetTextBuffer() }; + const auto bufferSize{ buffer.GetSize() }; + const auto inclusiveEnd{ _getInclusiveEnd() }; + + // Start/End for the resulting range. + // NOTE: we store these as "first" and "second" anchor because, + // we just want to know what the inclusive range is. + // We'll do some post-processing to fix this on the way out. + std::optional resultFirstAnchor; + std::optional resultSecondAnchor; + + // Start/End for the direction to perform the search in + const auto searchStart{ searchBackwards ? inclusiveEnd : _start }; + const auto searchEnd{ searchBackwards ? _start : inclusiveEnd }; + + // Iterate from searchStart to searchEnd in the buffer. + // If we find the attribute we're looking for, we update resultFirstAnchor/SecondAnchor appropriately. + const Viewport viewportRange{ _blockRange ? Viewport::FromDimensions(_start, inclusiveEnd.X - _start.X + 1, inclusiveEnd.Y - _start.Y + 1) : bufferSize }; + auto iter{ buffer.GetCellDataAt(searchStart, viewportRange, searchEnd) }; + for (; iter; searchBackwards ? --iter : ++iter) + { + const auto& attr{ iter->TextAttr() }; + if (checkIfAttrFound(attr)) + { + // populate the first anchor if it's not populated. + // otherwise, populate the second anchor. + if (!resultFirstAnchor.has_value()) + { + resultFirstAnchor = iter.Pos(); + } + else + { + resultSecondAnchor = iter.Pos(); + } + } + else if (resultFirstAnchor.has_value() && resultSecondAnchor.has_value()) + { + // Exit the loop early if... + // - the cell we're looking at doesn't have the attr we're looking for + // - the anchors have been populated + // This means that we've found a contiguous range where the text attribute was found. + // No point in searching through the rest of the search space. + break; + } + } + + // If a result was found, populate ppRetVal with the UiaTextRange + // representing the found selection anchors. + if (resultFirstAnchor.has_value() && resultSecondAnchor.has_value()) + { + RETURN_IF_FAILED(Clone(ppRetVal)); + UiaTextRangeBase& range = static_cast(**ppRetVal); + + // IMPORTANT: resultFirstAnchor and resultSecondAnchor make up an inclusive range. + range._start = searchBackwards ? *resultSecondAnchor : *resultFirstAnchor; + range._end = searchBackwards ? *resultFirstAnchor : *resultSecondAnchor; + + // We need to make the end exclusive! + // But be careful here, we might be a block range + auto exclusiveIter{ buffer.GetCellDataAt(range._end, viewportRange) }; + exclusiveIter++; + range._end = exclusiveIter.Pos(); + } + + UiaTracing::TextRange::FindAttribute(*this, attributeId, val, searchBackwards, static_cast(**ppRetVal)); + return S_OK; } IFACEMETHODIMP UiaTextRangeBase::FindText(_In_ BSTR text, @@ -372,22 +677,168 @@ try } CATCH_RETURN(); -IFACEMETHODIMP UiaTextRangeBase::GetAttributeValue(_In_ TEXTATTRIBUTEID textAttributeId, +// Method Description: +// - (1) Checks the current range for the attributeId's sub-type +// - (2) Record the attributeId's sub-type +// - (3) Generate an attribute verification function for that attributeId sub-type +// Arguments: +// - attributeId - the UIA text attribute identifier we're looking for +// - pRetVal - the attributeId's sub-type for the first cell in the range (i.e. foreground color) +// Return Value: +// - a function that can be used to verify if a given TextAttribute meets the attributeId's sub-type +std::function UiaTextRangeBase::_getAttrVerificationFnForFirstAttr(TEXTATTRIBUTEID attributeId, VARIANT* pRetVal) const +{ + THROW_HR_IF(E_INVALIDARG, pRetVal == nullptr); + + const auto attr{ _pData->GetTextBuffer().GetCellDataAt(_start)->TextAttr() }; + switch (attributeId) + { + case UIA_FontWeightAttributeId: + { + // 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". + // Source: https://docs.microsoft.com/en-us/windows/win32/winauto/uiauto-textattribute-ids + pRetVal->vt = VT_I4; + if (attr.IsBold()) + { + pRetVal->iVal = FW_BOLD; + } + else if (attr.IsFaint()) + { + pRetVal->iVal = FW_LIGHT; + } + else + { + pRetVal->iVal = FW_NORMAL; + } + return _getAttrVerificationFn(attributeId, *pRetVal); + } + case UIA_ForegroundColorAttributeId: + { + pRetVal->vt = VT_I4; + pRetVal->iVal = base::ClampedNumeric(attr.GetForeground().GetRGB()); + 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); + 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; + return _getAttrVerificationFn(attributeId, *pRetVal); + } + case UIA_UnderlineStyleAttributeId: + { + pRetVal->vt = VT_I4; + pRetVal->iVal = static_cast(attr.IsUnderlined() ? TextDecorationLineStyle_Single : TextDecorationLineStyle_None); + return _getAttrVerificationFn(attributeId, *pRetVal); + } + default: + // This attribute is not supported. + pRetVal->vt = VT_UNKNOWN; + UiaGetReservedNotSupportedValue(&pRetVal->punkVal); + return nullptr; + } +} + +IFACEMETHODIMP UiaTextRangeBase::GetAttributeValue(_In_ TEXTATTRIBUTEID attributeId, _Out_ VARIANT* pRetVal) noexcept { RETURN_HR_IF(E_INVALIDARG, pRetVal == nullptr); - if (textAttributeId == UIA_IsReadOnlyAttributeId) + // AttributeIDs that require special handling + switch (attributeId) + { + case UIA_FontNameAttributeId: + { + pRetVal->vt = VT_BSTR; + pRetVal->bstrVal = SysAllocString(_pData->GetFontInfo().GetFaceName().data()); + 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; pRetVal->boolVal = VARIANT_FALSE; + UiaTracing::TextRange::GetAttributeValue(*this, attributeId, *pRetVal); + return S_OK; } - else + default: + __fallthrough; + } + + // AttributeIDs that are exposed via TextAttribute + std::function checkIfAttrFound; + try { - pRetVal->vt = VT_UNKNOWN; - UiaGetReservedNotSupportedValue(&pRetVal->punkVal); + checkIfAttrFound = _getAttrVerificationFnForFirstAttr(attributeId, pRetVal); + if (!checkIfAttrFound) + { + // The AttributeID is not supported. + pRetVal->vt = VT_UNKNOWN; + UiaTracing::TextRange::GetAttributeValue(*this, attributeId, *pRetVal, UiaTracing::AttributeType::Unsupported); + return UiaGetReservedNotSupportedValue(&pRetVal->punkVal); + } + } + catch (...) + { + LOG_HR(wil::ResultFromCaughtException()); + UiaTracing::TextRange::GetAttributeValue(*this, attributeId, *pRetVal, UiaTracing::AttributeType::Error); + return E_INVALIDARG; } - UiaTracing::TextRange::GetAttributeValue(*this, textAttributeId, *pRetVal); + + // Get some useful variables + const auto& buffer{ _pData->GetTextBuffer() }; + const auto bufferSize{ buffer.GetSize() }; + const auto inclusiveEnd{ _getInclusiveEnd() }; + + // Check if the entire text range has that text attribute + const Viewport viewportRange{ _blockRange ? Viewport::FromDimensions(_start, inclusiveEnd.X - _start.X + 1, inclusiveEnd.Y - _start.Y + 1) : bufferSize }; + auto iter{ buffer.GetCellDataAt(_start, viewportRange, inclusiveEnd) }; + for (; iter; ++iter) + { + const auto& attr{ iter->TextAttr() }; + if (!checkIfAttrFound(attr)) + { + // The value of the specified attribute varies over the text range + // 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); + } + } + + UiaTracing::TextRange::GetAttributeValue(*this, attributeId, *pRetVal); return S_OK; } @@ -1244,3 +1695,10 @@ RECT UiaTextRangeBase::_getTerminalRect() const gsl::narrow(result.top + result.height) }; } + +COORD UiaTextRangeBase::_getInclusiveEnd() +{ + auto result{ _end }; + _pData->GetTextBuffer().GetSize().DecrementInBounds(result, true); + return result; +} diff --git a/src/types/UiaTextRangeBase.hpp b/src/types/UiaTextRangeBase.hpp index 7122cefcf44..19a3c130c16 100644 --- a/src/types/UiaTextRangeBase.hpp +++ b/src/types/UiaTextRangeBase.hpp @@ -177,6 +177,11 @@ namespace Microsoft::Console::Types gsl::not_null const pAmountMoved, _In_ const bool preventBufferEnd = false) noexcept; + std::function _getAttrVerificationFn(TEXTATTRIBUTEID attributeId, VARIANT val) const; + std::function _getAttrVerificationFnForFirstAttr(TEXTATTRIBUTEID attributeId, VARIANT* pRetVal) const; + + COORD _getInclusiveEnd(); + #ifdef UNIT_TESTING friend class ::UiaTextRangeTests; #endif diff --git a/src/types/UiaTracing.cpp b/src/types/UiaTracing.cpp index efee217d0b9..461fccb6906 100644 --- a/src/types/UiaTracing.cpp +++ b/src/types/UiaTracing.cpp @@ -126,6 +126,140 @@ inline std::wstring UiaTracing::_getValue(const TextUnit unit) noexcept } } +std::wstring UiaTracing::convertAttributeId(const TEXTATTRIBUTEID attrId) noexcept +{ + // Source: https://docs.microsoft.com/en-us/windows/win32/winauto/uiauto-textattribute-ids + switch (attrId) + { + case UIA_AfterParagraphSpacingAttributeId: + return L"AfterParagraphSpacing"; + case UIA_AnimationStyleAttributeId: + return L"AnimationStyle"; + case UIA_AnnotationObjectsAttributeId: + return L"AnnotationObjects"; + case UIA_AnnotationTypesAttributeId: + return L"AnnotationTypes"; + case UIA_BackgroundColorAttributeId: + return L"BackgroundColor"; + case UIA_BeforeParagraphSpacingAttributeId: + return L"BeforeParagraphSpacing"; + case UIA_BulletStyleAttributeId: + return L"BulletStyle"; + case UIA_CapStyleAttributeId: + return L"CapStyle"; + case UIA_CaretBidiModeAttributeId: + return L"CaretBidiMode"; + case UIA_CaretPositionAttributeId: + return L"CaretPosition"; + case UIA_CultureAttributeId: + return L"Culture"; + case UIA_FontNameAttributeId: + return L"FontName"; + case UIA_FontSizeAttributeId: + return L"FontSize"; + case UIA_FontWeightAttributeId: + return L"FontWeight"; + case UIA_ForegroundColorAttributeId: + return L"ForegroundColor"; + case UIA_HorizontalTextAlignmentAttributeId: + return L"HorizontalTextAlignment"; + case UIA_IndentationFirstLineAttributeId: + return L"IndentationFirstLine"; + case UIA_IndentationLeadingAttributeId: + return L"IndentationLeading"; + case UIA_IndentationTrailingAttributeId: + return L"IndentationTrailing"; + case UIA_IsActiveAttributeId: + return L"IsActive"; + case UIA_IsHiddenAttributeId: + return L"IsHidden"; + case UIA_IsItalicAttributeId: + return L"IsItalic"; + case UIA_IsReadOnlyAttributeId: + return L"IsReadOnly"; + case UIA_IsSubscriptAttributeId: + return L"IsSubscript"; + case UIA_IsSuperscriptAttributeId: + return L"IsSuperscript"; + case UIA_LineSpacingAttributeId: + return L"LineSpacing"; + case UIA_LinkAttributeId: + return L"Link"; + case UIA_MarginBottomAttributeId: + return L"MarginBottom"; + case UIA_MarginLeadingAttributeId: + return L"MarginLeading"; + case UIA_MarginTopAttributeId: + return L"MarginTop"; + case UIA_MarginTrailingAttributeId: + return L"MarginTrailing"; + case UIA_OutlineStylesAttributeId: + return L"OutlineStyles"; + case UIA_OverlineColorAttributeId: + return L"OverlineColor"; + case UIA_OverlineStyleAttributeId: + return L"OverlineStyle"; + case UIA_SelectionActiveEndAttributeId: + return L"SelectionActiveEnd"; + case UIA_StrikethroughColorAttributeId: + return L"StrikethroughColor"; + case UIA_StrikethroughStyleAttributeId: + return L"StrikethroughStyle"; + case UIA_StyleIdAttributeId: + return L"StyleId"; + case UIA_StyleNameAttributeId: + return L"StyleName"; + case UIA_TabsAttributeId: + return L"Tabs"; + case UIA_TextFlowDirectionsAttributeId: + return L"TextFlowDirections"; + case UIA_UnderlineColorAttributeId: + return L"UnderlineColor"; + case UIA_UnderlineStyleAttributeId: + return L"UnderlineStyle"; + default: + return L"Unknown attribute"; + } +} + +inline std::wstring UiaTracing::_getValue(const VARIANT val) noexcept +{ + // This is not a comprehensive conversion of VARIANT result to string + // We're only including the one's we need at this time. + switch (val.vt) + { + case VT_BSTR: + return val.bstrVal; + case VT_R8: + return std::to_wstring(val.dblVal); + case VT_BOOL: + return std::to_wstring(val.boolVal); + case VT_I4: + return std::to_wstring(val.iVal); + case VT_UNKNOWN: + default: + { + return L"unknown"; + } + } +} + +inline std::wstring UiaTracing::_getValue(const AttributeType attrType) noexcept +{ + switch (attrType) + { + case AttributeType::Mixed: + return L"Mixed"; + case AttributeType::Unsupported: + return L"Unsupported"; + case AttributeType::Error: + return L"Error"; + case AttributeType::Standard: + default: + return L"Standard"; + } +} + void UiaTracing::TextRange::Constructor(UiaTextRangeBase& result) noexcept { EnsureRegistration(); @@ -206,15 +340,20 @@ void UiaTracing::TextRange::ExpandToEnclosingUnit(TextUnit unit, const UiaTextRa } } -void UiaTracing::TextRange::FindAttribute(const UiaTextRangeBase& utr) noexcept +void UiaTracing::TextRange::FindAttribute(const UiaTextRangeBase& utr, TEXTATTRIBUTEID id, VARIANT val, BOOL searchBackwards, const UiaTextRangeBase& result, AttributeType attrType) noexcept { EnsureRegistration(); if (TraceLoggingProviderEnabled(g_UiaProviderTraceProvider, WINEVENT_LEVEL_VERBOSE, TIL_KEYWORD_TRACE)) { TraceLoggingWrite( g_UiaProviderTraceProvider, - "UiaTextRange::FindAttribute (UNSUPPORTED)", + "UiaTextRange::FindAttribute", TraceLoggingValue(_getValue(utr).c_str(), "base"), + TraceLoggingValue(convertAttributeId(id).c_str(), "text attribute ID"), + TraceLoggingValue(_getValue(val).c_str(), "text attribute sub-data"), + TraceLoggingValue(searchBackwards ? L"true" : L"false", "search backwards"), + TraceLoggingValue(_getValue(attrType).c_str(), "attribute type"), + TraceLoggingValue(_getValue(result).c_str(), "result"), TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), TraceLoggingKeyword(TIL_KEYWORD_TRACE)); } @@ -238,7 +377,7 @@ void UiaTracing::TextRange::FindText(const UiaTextRangeBase& base, std::wstring } } -void UiaTracing::TextRange::GetAttributeValue(const UiaTextRangeBase& base, TEXTATTRIBUTEID id, VARIANT result) noexcept +void UiaTracing::TextRange::GetAttributeValue(const UiaTextRangeBase& base, TEXTATTRIBUTEID id, VARIANT result, AttributeType attrType) noexcept { EnsureRegistration(); if (TraceLoggingProviderEnabled(g_UiaProviderTraceProvider, WINEVENT_LEVEL_VERBOSE, TIL_KEYWORD_TRACE)) @@ -247,8 +386,9 @@ void UiaTracing::TextRange::GetAttributeValue(const UiaTextRangeBase& base, TEXT g_UiaProviderTraceProvider, "UiaTextRange::GetAttributeValue", TraceLoggingValue(_getValue(base).c_str(), "base"), - TraceLoggingValue(id, "textAttributeId"), - TraceLoggingValue(result.vt, "result (type)"), + TraceLoggingValue(convertAttributeId(id).c_str(), "text attribute ID"), + TraceLoggingValue(_getValue(result).c_str(), "result"), + TraceLoggingValue(_getValue(attrType).c_str(), "attribute type"), TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), TraceLoggingKeyword(TIL_KEYWORD_TRACE)); } diff --git a/src/types/UiaTracing.h b/src/types/UiaTracing.h index 6a8c0a00885..a1bd20a04a2 100644 --- a/src/types/UiaTracing.h +++ b/src/types/UiaTracing.h @@ -29,6 +29,16 @@ namespace Microsoft::Console::Types class UiaTracing final { public: + static std::wstring convertAttributeId(const TEXTATTRIBUTEID attrId) noexcept; + + enum class AttributeType + { + Standard, + Mixed, + Unsupported, + Error + }; + class TextRange final { public: @@ -37,9 +47,9 @@ namespace Microsoft::Console::Types static void Compare(const UiaTextRangeBase& base, const UiaTextRangeBase& other, bool result) noexcept; static void CompareEndpoints(const UiaTextRangeBase& base, const TextPatternRangeEndpoint endpoint, const UiaTextRangeBase& other, TextPatternRangeEndpoint otherEndpoint, int result) noexcept; static void ExpandToEnclosingUnit(TextUnit unit, const UiaTextRangeBase& result) noexcept; - static void FindAttribute(const UiaTextRangeBase& base) noexcept; + static void FindAttribute(const UiaTextRangeBase& base, TEXTATTRIBUTEID attributeId, VARIANT val, BOOL searchBackwards, const UiaTextRangeBase& result, AttributeType attrType = AttributeType::Standard) noexcept; static void FindText(const UiaTextRangeBase& base, std::wstring text, bool searchBackward, bool ignoreCase, const UiaTextRangeBase& result) noexcept; - static void GetAttributeValue(const UiaTextRangeBase& base, TEXTATTRIBUTEID id, VARIANT result) noexcept; + static void GetAttributeValue(const UiaTextRangeBase& base, TEXTATTRIBUTEID id, VARIANT result, AttributeType attrType = AttributeType::Standard) noexcept; static void GetBoundingRectangles(const UiaTextRangeBase& base) noexcept; static void GetEnclosingElement(const UiaTextRangeBase& base) noexcept; static void GetText(const UiaTextRangeBase& base, int maxLength, std::wstring result) noexcept; @@ -101,6 +111,9 @@ namespace Microsoft::Console::Types static inline std::wstring _getValue(const TextPatternRangeEndpoint endpoint) noexcept; static inline std::wstring _getValue(const TextUnit unit) noexcept; + static inline std::wstring _getValue(const AttributeType attrType) noexcept; + static inline std::wstring _getValue(const VARIANT val) noexcept; + // these are used to assign IDs to new UiaTextRanges and ScreenInfoUiaProviders respectively static IdType _utrId; static IdType _siupId;