Make ScreenInfoUiaProvider::GetSelection() Return One Selection (#4466)

## Summary of the Pull Request
We used to return multiple text ranges to represent one selection. We only support one selection at a time, so we should only return one range.

Additionally, I moved all TriggerSelection() calls to the renderer from Terminal to TermControl for consistency. This ensures we only call it _once_ when we make a change to our selection state.

## References
#2447 - helps polish Signaling for Selection
#4465 - This is more apparent as the problem holding back Signaling for Selection

## PR Checklist
* [x] Closes #4452 

Tested using Accessibility Insights.
This commit is contained in:
Carlos Zamora 2020-02-20 15:03:50 -08:00 committed by GitHub
parent ce39b63f46
commit d0c8221c6e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 130 additions and 167 deletions

View file

@ -852,6 +852,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
if (_terminal->IsSelectionActive())
{
_terminal->ClearSelection();
_renderer->TriggerSelection();
if (vkey == VK_ESCAPE)
{
@ -1744,6 +1745,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
if (!_terminal->IsCopyOnSelectActive())
{
_terminal->ClearSelection();
_renderer->TriggerSelection();
}
// send data up for clipboard

View file

@ -100,26 +100,22 @@ void TermControlUiaProvider::ChangeViewport(const SMALL_RECT NewWindow)
_termControl->ScrollViewport(NewWindow.Top);
}
HRESULT TermControlUiaProvider::GetSelectionRanges(_In_ IRawElementProviderSimple* pProvider, const std::wstring_view wordDelimiters, _Out_ std::deque<ComPtr<UiaTextRangeBase>>& result)
HRESULT TermControlUiaProvider::GetSelectionRange(_In_ IRawElementProviderSimple* pProvider, const std::wstring_view wordDelimiters, _COM_Outptr_result_maybenull_ UiaTextRangeBase** ppUtr)
{
try
{
result.clear();
typename std::remove_reference<decltype(result)>::type temporaryResult;
RETURN_HR_IF_NULL(E_INVALIDARG, ppUtr);
*ppUtr = nullptr;
std::deque<ComPtr<UiaTextRange>> ranges;
RETURN_IF_FAILED(UiaTextRange::GetSelectionRanges(_pData, pProvider, wordDelimiters, ranges));
const auto start = _pData->GetSelectionAnchor();
while (!ranges.empty())
{
temporaryResult.emplace_back(std::move(ranges.back()));
ranges.pop_back();
}
// we need to make end exclusive
auto end = _pData->GetEndSelectionPosition();
_pData->GetTextBuffer().GetSize().IncrementInBounds(end, true);
std::swap(result, temporaryResult);
return S_OK;
}
CATCH_RETURN();
// TODO GH #4509: Box Selection is misrepresented here as a line selection.
UiaTextRange* result = nullptr;
RETURN_IF_FAILED(MakeAndInitialize<UiaTextRange>(&result, _pData, pProvider, start, end, wordDelimiters));
*ppUtr = result;
return S_OK;
}
HRESULT TermControlUiaProvider::CreateTextRange(_In_ IRawElementProviderSimple* const pProvider, const std::wstring_view wordDelimiters, _COM_Outptr_result_maybenull_ UiaTextRangeBase** ppUtr)

View file

@ -48,7 +48,7 @@ namespace Microsoft::Terminal
void ChangeViewport(const SMALL_RECT NewWindow) override;
protected:
HRESULT GetSelectionRanges(_In_ IRawElementProviderSimple* pProvider, const std::wstring_view wordDelimiters, _Out_ std::deque<WRL::ComPtr<Microsoft::Console::Types::UiaTextRangeBase>>& selectionRanges) override;
HRESULT GetSelectionRange(_In_ IRawElementProviderSimple* pProvider, const std::wstring_view wordDelimiters, _COM_Outptr_result_maybenull_ Microsoft::Console::Types::UiaTextRangeBase** ppUtr) override;
// degenerate range
HRESULT CreateTextRange(_In_ IRawElementProviderSimple* const pProvider, const std::wstring_view wordDelimiters, _COM_Outptr_result_maybenull_ Microsoft::Console::Types::UiaTextRangeBase** ppUtr) override;

View file

@ -10,34 +10,6 @@ using namespace Microsoft::Console::Types;
using namespace Microsoft::WRL;
using namespace winrt::Windows::Graphics::Display;
HRESULT UiaTextRange::GetSelectionRanges(_In_ IUiaData* pData,
_In_ IRawElementProviderSimple* pProvider,
_In_ const std::wstring_view wordDelimiters,
_Out_ std::deque<ComPtr<UiaTextRange>>& ranges)
{
try
{
typename std::remove_reference<decltype(ranges)>::type temporaryResult;
// get the selection rects
const auto rectangles = pData->GetSelectionRects();
// create a range for each row
for (const auto& rect : rectangles)
{
const auto start = rect.Origin();
const auto end = rect.EndExclusive();
ComPtr<UiaTextRange> range;
RETURN_IF_FAILED(MakeAndInitialize<UiaTextRange>(&range, pData, pProvider, start, end, wordDelimiters));
temporaryResult.emplace_back(std::move(range));
}
std::swap(temporaryResult, ranges);
return S_OK;
}
CATCH_RETURN();
}
// degenerate range constructor.
HRESULT UiaTextRange::RuntimeClassInitialize(_In_ IUiaData* pData, _In_ IRawElementProviderSimple* const pProvider, _In_ const std::wstring_view wordDelimiters)
{

View file

@ -23,11 +23,6 @@ namespace Microsoft::Terminal
class UiaTextRange final : public Microsoft::Console::Types::UiaTextRangeBase
{
public:
static HRESULT GetSelectionRanges(_In_ Microsoft::Console::Types::IUiaData* pData,
_In_ IRawElementProviderSimple* pProvider,
_In_ const std::wstring_view wordDelimiters,
_Out_ std::deque<WRL::ComPtr<UiaTextRange>>& ranges);
UiaTextRange() = default;
// degenerate range

View file

@ -141,7 +141,8 @@ public:
const bool IsSelectionActive() const noexcept override;
void ClearSelection() override;
void SelectNewRegion(const COORD coordStart, const COORD coordEnd) override;
const COORD GetSelectionAnchor() const override;
const COORD GetSelectionAnchor() const noexcept override;
const COORD GetEndSelectionPosition() const noexcept override;
const std::wstring GetConsoleTitle() const noexcept override;
void ColorSelection(const COORD coordSelectionStart, const COORD coordSelectionEnd, const TextAttribute) override;
#pragma endregion

View file

@ -113,14 +113,26 @@ SMALL_RECT Terminal::_GetSelectionRow(const SHORT row, const COORD higherCoord,
// - None
// Return Value:
// - None
const COORD Terminal::GetSelectionAnchor() const
const COORD Terminal::GetSelectionAnchor() const noexcept
{
COORD selectionAnchorPos{ _selectionAnchor };
THROW_IF_FAILED(ShortAdd(selectionAnchorPos.Y, _selectionVerticalOffset, &selectionAnchorPos.Y));
selectionAnchorPos.Y = base::ClampAdd(selectionAnchorPos.Y, _selectionVerticalOffset);
return selectionAnchorPos;
}
// Method Description:
// - Get the current end anchor position relative to the whole text buffer
// Arguments:
// - None
// Return Value:
// - None
const COORD Terminal::GetEndSelectionPosition() const noexcept
{
COORD endSelectionPos{ _endSelectionPosition };
endSelectionPos.Y = base::ClampAdd(endSelectionPos.Y, _selectionVerticalOffset);
return endSelectionPos;
}
// Method Description:
// - Expand the selection row according to selection mode and wide glyphs
// - this is particularly useful for box selections (ALT + selection)
@ -325,6 +337,7 @@ void Terminal::SetBoxSelection(const bool isEnabled) noexcept
// Method Description:
// - clear selection data and disable rendering it
#pragma warning(disable : 26440) // changing this to noexcept would require a change to ConHost's selection model
void Terminal::ClearSelection()
{
_selectionActive = false;
@ -332,8 +345,6 @@ void Terminal::ClearSelection()
_selectionAnchor = { 0, 0 };
_endSelectionPosition = { 0, 0 };
_selectionVerticalOffset = 0;
_buffer->GetRenderTarget().TriggerSelection();
}
// Method Description:

View file

@ -174,7 +174,6 @@ void Terminal::SelectNewRegion(const COORD coordStart, const COORD coordEnd)
SetSelectionAnchor(realCoordStart);
SetEndSelectionPosition(realCoordEnd);
_buffer->GetRenderTarget().TriggerSelection();
}
const std::wstring Terminal::GetConsoleTitle() const noexcept

View file

@ -389,11 +389,50 @@ void RenderData::SelectNewRegion(const COORD coordStart, const COORD coordEnd)
// - none
// Return Value:
// - current selection anchor
const COORD RenderData::GetSelectionAnchor() const
const COORD RenderData::GetSelectionAnchor() const noexcept
{
return Selection::Instance().GetSelectionAnchor();
}
// Routine Description:
// - Gets the current end selection anchor position
// Arguments:
// - none
// Return Value:
// - current selection anchor
const COORD RenderData::GetEndSelectionPosition() const noexcept
{
// The selection area in ConHost is encoded as two things...
// - SelectionAnchor: the initial position where the selection was started
// - SelectionRect: the rectangular region denoting a portion of the buffer that is selected
// The following is an exerpt from Selection::s_GetSelectionRects
// if the anchor (start of select) was in the top right or bottom left of the box,
// we need to remove rectangular overlap in the middle.
// e.g.
// For selections with the anchor in the top left (A) or bottom right (B),
// it is valid to maintain the inner rectangle (+) as part of the selection
// A+++++++================
// ==============++++++++B
// + and = are valid highlights in this scenario.
// For selections with the anchor in in the top right (A) or bottom left (B),
// we must remove a portion of the first/last line that lies within the rectangle (+)
// +++++++A=================
// ==============B+++++++
// Only = is valid for highlight in this scenario.
// This is only needed for line selection. Box selection doesn't need to account for this.
const auto selectionRect = Selection::Instance().GetSelectionRectangle();
// To extract the end anchor from this rect, we need to know which corner of the rect is the SelectionAnchor
// Then choose the opposite corner.
const auto anchor = Selection::Instance().GetSelectionAnchor();
const short x_pos = (selectionRect.Left == anchor.X) ? selectionRect.Right : selectionRect.Left;
const short y_pos = (selectionRect.Top == anchor.Y) ? selectionRect.Bottom : selectionRect.Top;
return { x_pos, y_pos };
}
// Routine Description:
// - Given two points in the buffer space, color the selection between the two with the given attribute.
// - This will create an internal selection rectangle covering the two points, assume a line selection,

View file

@ -60,7 +60,8 @@ public:
const bool IsSelectionActive() const override;
void ClearSelection() override;
void SelectNewRegion(const COORD coordStart, const COORD coordEnd) override;
const COORD GetSelectionAnchor() const;
const COORD GetSelectionAnchor() const noexcept;
const COORD GetEndSelectionPosition() const noexcept;
void ColorSelection(const COORD coordSelectionStart, const COORD coordSelectionEnd, const TextAttribute attr);
#pragma endregion
};

View file

@ -97,26 +97,22 @@ void ScreenInfoUiaProvider::ChangeViewport(const SMALL_RECT NewWindow)
_pUiaParent->ChangeViewport(NewWindow);
}
HRESULT ScreenInfoUiaProvider::GetSelectionRanges(_In_ IRawElementProviderSimple* pProvider, const std::wstring_view wordDelimiters, _Out_ std::deque<ComPtr<UiaTextRangeBase>>& result)
HRESULT ScreenInfoUiaProvider::GetSelectionRange(_In_ IRawElementProviderSimple* pProvider, const std::wstring_view wordDelimiters, _COM_Outptr_result_maybenull_ Microsoft::Console::Types::UiaTextRangeBase** ppUtr)
{
try
{
result.clear();
typename std::remove_reference<decltype(result)>::type temporaryResult;
RETURN_HR_IF_NULL(E_INVALIDARG, ppUtr);
*ppUtr = nullptr;
std::deque<ComPtr<UiaTextRange>> ranges;
RETURN_IF_FAILED(UiaTextRange::GetSelectionRanges(_pData, pProvider, wordDelimiters, ranges));
const auto start = _pData->GetSelectionAnchor();
while (!ranges.empty())
{
temporaryResult.emplace_back(std::move(ranges.back()));
ranges.pop_back();
}
// we need to make end exclusive
auto end = _pData->GetEndSelectionPosition();
_pData->GetTextBuffer().GetSize().IncrementInBounds(end, true);
std::swap(result, temporaryResult);
return S_OK;
}
CATCH_RETURN();
// TODO GH #4509: Box Selection is misrepresented here as a line selection.
UiaTextRange* result;
RETURN_IF_FAILED(MakeAndInitialize<UiaTextRange>(&result, _pData, pProvider, start, end, wordDelimiters));
*ppUtr = result;
return S_OK;
}
HRESULT ScreenInfoUiaProvider::CreateTextRange(_In_ IRawElementProviderSimple* const pProvider, const std::wstring_view wordDelimiters, _COM_Outptr_result_maybenull_ UiaTextRangeBase** ppUtr)

View file

@ -44,7 +44,7 @@ namespace Microsoft::Console::Interactivity::Win32
void ChangeViewport(const SMALL_RECT NewWindow) override;
protected:
HRESULT GetSelectionRanges(_In_ IRawElementProviderSimple* pProvider, const std::wstring_view wordDelimiters, _Out_ std::deque<WRL::ComPtr<Microsoft::Console::Types::UiaTextRangeBase>>& selectionRanges) override;
HRESULT GetSelectionRange(_In_ IRawElementProviderSimple* pProvider, const std::wstring_view wordDelimiters, _COM_Outptr_result_maybenull_ Microsoft::Console::Types::UiaTextRangeBase** ppUtr) override;
// degenerate range
HRESULT CreateTextRange(_In_ IRawElementProviderSimple* const pProvider, const std::wstring_view wordDelimiters, _COM_Outptr_result_maybenull_ Microsoft::Console::Types::UiaTextRangeBase** ppUtr) override;

View file

@ -12,34 +12,6 @@ using namespace Microsoft::Console::Interactivity::Win32;
using namespace Microsoft::WRL;
using Microsoft::Console::Interactivity::ServiceLocator;
HRESULT UiaTextRange::GetSelectionRanges(_In_ IUiaData* pData,
_In_ IRawElementProviderSimple* pProvider,
const std::wstring_view wordDelimiters,
_Out_ std::deque<ComPtr<UiaTextRange>>& ranges)
{
try
{
typename std::remove_reference<decltype(ranges)>::type temporaryResult;
// get the selection rects
const auto rectangles = pData->GetSelectionRects();
// create a range for each row
for (const auto& rect : rectangles)
{
const auto start = rect.Origin();
const auto end = rect.EndExclusive();
ComPtr<UiaTextRange> range;
RETURN_IF_FAILED(MakeAndInitialize<UiaTextRange>(&range, pData, pProvider, start, end, wordDelimiters));
temporaryResult.emplace_back(std::move(range));
}
std::swap(temporaryResult, ranges);
return S_OK;
}
CATCH_RETURN();
}
// degenerate range constructor.
HRESULT UiaTextRange::RuntimeClassInitialize(_In_ IUiaData* pData, _In_ IRawElementProviderSimple* const pProvider, _In_ const std::wstring_view wordDelimiters)
{

View file

@ -24,11 +24,6 @@ namespace Microsoft::Console::Interactivity::Win32
class UiaTextRange final : public Microsoft::Console::Types::UiaTextRangeBase
{
public:
static HRESULT GetSelectionRanges(_In_ Microsoft::Console::Types::IUiaData* pData,
_In_ IRawElementProviderSimple* pProvider,
_In_ const std::wstring_view wordDelimiters,
_Out_ std::deque<WRL::ComPtr<UiaTextRange>>& ranges);
UiaTextRange() = default;
// degenerate range

View file

@ -36,7 +36,8 @@ namespace Microsoft::Console::Types
virtual const bool IsSelectionActive() const = 0;
virtual void ClearSelection() = 0;
virtual void SelectNewRegion(const COORD coordStart, const COORD coordEnd) = 0;
virtual const COORD GetSelectionAnchor() const = 0;
virtual const COORD GetSelectionAnchor() const noexcept = 0;
virtual const COORD GetEndSelectionPosition() const noexcept = 0;
virtual void ColorSelection(const COORD coordSelectionStart, const COORD coordSelectionEnd, const TextAttribute attr) = 0;
};

View file

@ -251,75 +251,46 @@ IFACEMETHODIMP ScreenInfoUiaProviderBase::GetSelection(_Outptr_result_maybenull_
*ppRetVal = nullptr;
HRESULT hr = S_OK;
// make a safe array
*ppRetVal = SafeArrayCreateVector(VT_UNKNOWN, 0, 1);
if (*ppRetVal == nullptr)
{
return E_OUTOFMEMORY;
}
WRL::ComPtr<UiaTextRangeBase> range;
if (!_pData->IsSelectionActive())
{
// TODO GitHub #1914: Re-attach Tracing to UIA Tree
//apiMsg.AreaSelected = false;
//apiMsg.SelectionRowCount = 1;
// return a degenerate range at the cursor position
const Cursor& cursor = _getTextBuffer().GetCursor();
// make a safe array
*ppRetVal = SafeArrayCreateVector(VT_UNKNOWN, 0, 1);
if (*ppRetVal == nullptr)
{
return E_OUTOFMEMORY;
}
if (FAILED(hr))
{
SafeArrayDestroy(*ppRetVal);
*ppRetVal = nullptr;
return hr;
}
WRL::ComPtr<UiaTextRangeBase> range;
hr = CreateTextRange(this, cursor, _wordDelimiters, &range);
if (FAILED(hr))
{
SafeArrayDestroy(*ppRetVal);
*ppRetVal = nullptr;
return hr;
}
LONG currentIndex = 0;
hr = SafeArrayPutElement(*ppRetVal, &currentIndex, range.Detach());
if (FAILED(hr))
{
SafeArrayDestroy(*ppRetVal);
*ppRetVal = nullptr;
return hr;
}
}
else
{
// get the selection ranges
std::deque<WRL::ComPtr<UiaTextRangeBase>> ranges;
RETURN_IF_FAILED(GetSelectionRanges(this, _wordDelimiters, ranges));
// TODO GitHub #1914: Re-attach Tracing to UIA Tree
//apiMsg.AreaSelected = true;
//apiMsg.SelectionRowCount = static_cast<unsigned int>(ranges.size());
// make a safe array
*ppRetVal = SafeArrayCreateVector(VT_UNKNOWN, 0, gsl::narrow<ULONG>(ranges.size()));
if (*ppRetVal == nullptr)
{
return E_OUTOFMEMORY;
}
// get the selection range
hr = GetSelectionRange(this, _wordDelimiters, &range);
}
// fill the safe array
for (LONG i = 0; i < gsl::narrow<LONG>(ranges.size()); ++i)
{
hr = SafeArrayPutElement(*ppRetVal, &i, ranges.at(i).Detach());
if (FAILED(hr))
{
SafeArrayDestroy(*ppRetVal);
*ppRetVal = nullptr;
return hr;
}
}
if (FAILED(hr))
{
SafeArrayDestroy(*ppRetVal);
*ppRetVal = nullptr;
return hr;
}
LONG currentIndex = 0;
hr = SafeArrayPutElement(*ppRetVal, &currentIndex, range.Detach());
if (FAILED(hr))
{
SafeArrayDestroy(*ppRetVal);
*ppRetVal = nullptr;
return hr;
}
// TODO GitHub #1914: Re-attach Tracing to UIA Tree

View file

@ -79,7 +79,7 @@ namespace Microsoft::Console::Types
protected:
ScreenInfoUiaProviderBase() = default;
virtual HRESULT GetSelectionRanges(_In_ IRawElementProviderSimple* pProvider, const std::wstring_view wordDelimiters, _Out_ std::deque<WRL::ComPtr<UiaTextRangeBase>>& selectionRanges) = 0;
virtual HRESULT GetSelectionRange(_In_ IRawElementProviderSimple* pProvider, const std::wstring_view wordDelimiters, _COM_Outptr_result_maybenull_ UiaTextRangeBase** ppUtr) = 0;
// degenerate range
virtual HRESULT CreateTextRange(_In_ IRawElementProviderSimple* const pProvider, const std::wstring_view wordDelimiters, _COM_Outptr_result_maybenull_ UiaTextRangeBase** ppUtr) = 0;

View file

@ -91,11 +91,22 @@ HRESULT UiaTextRangeBase::RuntimeClassInitialize(_In_ IUiaData* pData,
_In_ const COORD start,
_In_ const COORD end,
_In_ std::wstring_view wordDelimiters) noexcept
try
{
RETURN_IF_FAILED(RuntimeClassInitialize(pData, pProvider, wordDelimiters));
_start = start;
_end = end;
// start is before/at end, so this is valid
if (_pData->GetTextBuffer().GetSize().CompareInBounds(start, end, true) <= 0)
{
_start = start;
_end = end;
}
else
{
// start is after end, so we need to flip our concept of start/end
_start = end;
_end = start;
}
#if defined(_DEBUG) && defined(UIATEXTRANGE_DEBUG_MSGS)
OutputDebugString(L"Constructor\n");
@ -104,6 +115,7 @@ HRESULT UiaTextRangeBase::RuntimeClassInitialize(_In_ IUiaData* pData,
return S_OK;
}
CATCH_RETURN();
void UiaTextRangeBase::Initialize(_In_ const UiaPoint point)
{