Enable Word Navigation in UiaTextRange (#3659)

Enables support for word navigation when using an automation client (i.e.: Narrator, etc...). Specifically, adds this functionality to the UiaTextRange class. The only delimiter used is whitespace because that's how words are separated in English.

# General "Word Movement" Expectations
The resulting text range should include any word break characters that are present at the end of the word, but before the start of the next word. (Source)

If you already are on a word, getting the "next word" means you skip the word you are on, and highlight the upcoming word appropriately. (similar idea when moving backwards)


# Word Expansion
Since word selection is supposed to detect word delimiters already, I figured I'd reuse that code. I moved it from TerminalCore to the TextBuffer.

Then I built on top of it by adding an optional additional parameter that decides if you want to include...
- the delimiter run when moving forward
- the character run when moving backwards
It defaults to false so that we don't have to care when using it in selection. But we change it to true when using it in our UiaTextRange

# UiaTextRange
The code is based on character movement. This allows us to actually work with boundary conditions.

The main thing to remember here is that each text range is recorded as a MoveState. The text range is most easily defined when you think about the start Endpoint and the end Endpoint. An Endpoint is just a linear 1-dimensional indexing of the text buffer. Examples:
- Endpoint 0 --> (0,0)
- Endpoint 79 --> (79,0) (when the buffer width is 80)
- Endpoint 80 -->(0,1) (when the buffer width is 80)
- When moving forward, the strategy is to focus on moving the end Endpoint. That way, we properly get the indexing for the "next" word (this also fixes a wrapping issue). Then, we update the start Endpoint. (This is reversed for moving backwards).
- When moving a specific Endpoint, we just have a few extra if statements to properly adjust for moving start vs end.

# Hooking it up
All we really had to do is add an enum. This part was super easy :)

I originally wanted the delimiters to be able to be defined. I'm not so sure about that anymore. Either way, I hardcoded our delimiter into a variable so if we ever want to expand on it or make that customizable, we just modify that variable.

# Defining your own word delimiters

- Import a word delimiter into the constructor of the ScreenInfoUiaProvider (SIUP)
  - This defines a word delimiter for all the UiaTextRanges (UTR) created by in this context
- import a word delimiter into the UTR directly
  - this provides more control over what a "word" is
  - this can be useful if you have an idea of what text a particular UTR will encounter and you want to customize the word navigation for it (i.e consider adding / or \\ for file paths)

The default param of " " is scattered throughout because this is the word delimiter used in the English language.
This commit is contained in:
Carlos Zamora 2019-12-12 15:22:12 -08:00 committed by GitHub
parent 8d9f657d43
commit 4b48f74f5f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 1310 additions and 158 deletions

View file

@ -951,6 +951,118 @@ Microsoft::Console::Render::IRenderTarget& TextBuffer::GetRenderTarget() noexcep
return _renderTarget;
}
// Method Description:
// - Get the COORD for the beginning of the word you are on
// Arguments:
// - target - a COORD on the word you are currently on
// - wordDelimiters - what characters are we considering for the separation of words
// - includeCharacterRun - include the character run located at the beginning of the word
// Return Value:
// - The COORD for the first character on the "word" (inclusive)
const COORD TextBuffer::GetWordStart(const COORD target, const std::wstring_view wordDelimiters, bool includeCharacterRun) const
{
const auto bufferSize = GetSize();
COORD result = target;
// can't expand left
if (target.X == bufferSize.Left())
{
return result;
}
auto bufferIterator = GetTextDataAt(result);
const auto initialDelimiter = _GetDelimiterClass(*bufferIterator, wordDelimiters);
while (result.X > bufferSize.Left() && (_GetDelimiterClass(*bufferIterator, wordDelimiters) == initialDelimiter))
{
bufferSize.DecrementInBounds(result);
--bufferIterator;
}
if (includeCharacterRun)
{
// include character run for readable word
if (_GetDelimiterClass(*bufferIterator, wordDelimiters) == DelimiterClass::RegularChar)
{
result = GetWordStart(result, wordDelimiters);
}
}
else if (_GetDelimiterClass(*bufferIterator, wordDelimiters) != initialDelimiter)
{
// move off of delimiter
bufferSize.IncrementInBounds(result);
}
return result;
}
// Method Description:
// - Get the COORD for the end of the word you are on
// Arguments:
// - target - a COORD on the word you are currently on
// - wordDelimiters - what characters are we considering for the separation of words
// - includeDelimiterRun - include the delimiter runs located at the end of the word
// Return Value:
// - The COORD for the last character on the "word" (inclusive)
const COORD TextBuffer::GetWordEnd(const COORD target, const std::wstring_view wordDelimiters, bool includeDelimiterRun) const
{
const auto bufferSize = GetSize();
COORD result = target;
// can't expand right
if (target.X == bufferSize.RightInclusive())
{
return result;
}
auto bufferIterator = GetTextDataAt(result);
const auto initialDelimiter = _GetDelimiterClass(*bufferIterator, wordDelimiters);
while (result.X < bufferSize.RightInclusive() && (_GetDelimiterClass(*bufferIterator, wordDelimiters) == initialDelimiter))
{
bufferSize.IncrementInBounds(result);
++bufferIterator;
}
if (includeDelimiterRun)
{
// include delimiter run after word
if (_GetDelimiterClass(*bufferIterator, wordDelimiters) != DelimiterClass::RegularChar)
{
result = GetWordEnd(result, wordDelimiters);
}
}
else if (_GetDelimiterClass(*bufferIterator, wordDelimiters) != initialDelimiter)
{
// move off of delimiter
bufferSize.DecrementInBounds(result);
}
return result;
}
// Method Description:
// - get delimiter class for buffer cell data
// - used for double click selection and uia word navigation
// Arguments:
// - cellChar: the char saved to the buffer cell under observation
// - wordDelimiters: the delimiters defined as a part of the DelimiterClass::DelimiterChar
// Return Value:
// - the delimiter class for the given char
TextBuffer::DelimiterClass TextBuffer::_GetDelimiterClass(const std::wstring_view cellChar, const std::wstring_view wordDelimiters) const noexcept
{
if (cellChar.at(0) <= UNICODE_SPACE)
{
return DelimiterClass::ControlChar;
}
else if (wordDelimiters.find(cellChar) != std::wstring_view::npos)
{
return DelimiterClass::DelimiterChar;
}
else
{
return DelimiterClass::RegularChar;
}
}
// Routine Description:
// - Retrieves the text data from the selected region and presents it in a clipboard-ready format (given little post-processing).
// Arguments:

View file

@ -130,6 +130,9 @@ public:
Microsoft::Console::Render::IRenderTarget& GetRenderTarget() noexcept;
const COORD GetWordStart(const COORD target, const std::wstring_view wordDelimiters, bool includeCharacterRun = false) const;
const COORD GetWordEnd(const COORD target, const std::wstring_view wordDelimiters, bool includeDelimiterRun = false) const;
class TextAndColor
{
public:
@ -186,6 +189,14 @@ private:
ROW& _GetFirstRow();
ROW& _GetPrevRowNoWrap(const ROW& row);
enum class DelimiterClass
{
ControlChar,
DelimiterChar,
RegularChar
};
DelimiterClass _GetDelimiterClass(const std::wstring_view cellChar, const std::wstring_view wordDelimiters) const noexcept;
#ifdef UNIT_TESTING
friend class TextBufferTests;
friend class UiaTextRangeTests;

View file

@ -95,7 +95,7 @@ const winrt::Windows::UI::Xaml::Thickness TermControlUiaProvider::GetPadding() c
return _termControl->GetPadding();
}
HRESULT TermControlUiaProvider::GetSelectionRanges(_In_ IRawElementProviderSimple* pProvider, _Out_ std::deque<ComPtr<UiaTextRangeBase>>& result)
HRESULT TermControlUiaProvider::GetSelectionRanges(_In_ IRawElementProviderSimple* pProvider, const std::wstring_view wordDelimiters, _Out_ std::deque<ComPtr<UiaTextRangeBase>>& result)
{
try
{
@ -103,7 +103,7 @@ HRESULT TermControlUiaProvider::GetSelectionRanges(_In_ IRawElementProviderSimpl
typename std::remove_reference<decltype(result)>::type temporaryResult;
std::deque<ComPtr<UiaTextRange>> ranges;
RETURN_IF_FAILED(UiaTextRange::GetSelectionRanges(_pData, pProvider, ranges));
RETURN_IF_FAILED(UiaTextRange::GetSelectionRanges(_pData, pProvider, wordDelimiters, ranges));
while (!ranges.empty())
{
@ -117,24 +117,25 @@ HRESULT TermControlUiaProvider::GetSelectionRanges(_In_ IRawElementProviderSimpl
CATCH_RETURN();
}
HRESULT TermControlUiaProvider::CreateTextRange(_In_ IRawElementProviderSimple* const pProvider, _COM_Outptr_result_maybenull_ UiaTextRangeBase** ppUtr)
HRESULT TermControlUiaProvider::CreateTextRange(_In_ IRawElementProviderSimple* const pProvider, const std::wstring_view wordDelimiters, _COM_Outptr_result_maybenull_ UiaTextRangeBase** ppUtr)
{
RETURN_HR_IF_NULL(E_INVALIDARG, ppUtr);
*ppUtr = nullptr;
UiaTextRange* result = nullptr;
RETURN_IF_FAILED(MakeAndInitialize<UiaTextRange>(&result, _pData, pProvider));
RETURN_IF_FAILED(MakeAndInitialize<UiaTextRange>(&result, _pData, pProvider, wordDelimiters));
*ppUtr = result;
return S_OK;
}
HRESULT TermControlUiaProvider::CreateTextRange(_In_ IRawElementProviderSimple* const pProvider,
const Cursor& cursor,
const std::wstring_view wordDelimiters,
_COM_Outptr_result_maybenull_ UiaTextRangeBase** ppUtr)
{
RETURN_HR_IF_NULL(E_INVALIDARG, ppUtr);
*ppUtr = nullptr;
UiaTextRange* result = nullptr;
RETURN_IF_FAILED(MakeAndInitialize<UiaTextRange>(&result, _pData, pProvider, cursor));
RETURN_IF_FAILED(MakeAndInitialize<UiaTextRange>(&result, _pData, pProvider, cursor, wordDelimiters));
*ppUtr = result;
return S_OK;
}
@ -143,24 +144,26 @@ HRESULT TermControlUiaProvider::CreateTextRange(_In_ IRawElementProviderSimple*
const Endpoint start,
const Endpoint end,
const bool degenerate,
const std::wstring_view wordDelimiters,
_COM_Outptr_result_maybenull_ UiaTextRangeBase** ppUtr)
{
RETURN_HR_IF_NULL(E_INVALIDARG, ppUtr);
*ppUtr = nullptr;
UiaTextRange* result = nullptr;
RETURN_IF_FAILED(MakeAndInitialize<UiaTextRange>(&result, _pData, pProvider, start, end, degenerate));
RETURN_IF_FAILED(MakeAndInitialize<UiaTextRange>(&result, _pData, pProvider, start, end, degenerate, wordDelimiters));
*ppUtr = result;
return S_OK;
}
HRESULT TermControlUiaProvider::CreateTextRange(_In_ IRawElementProviderSimple* const pProvider,
const UiaPoint point,
const std::wstring_view wordDelimiters,
_COM_Outptr_result_maybenull_ UiaTextRangeBase** ppUtr)
{
RETURN_HR_IF_NULL(E_INVALIDARG, ppUtr);
*ppUtr = nullptr;
UiaTextRange* result = nullptr;
RETURN_IF_FAILED(MakeAndInitialize<UiaTextRange>(&result, _pData, pProvider, point));
RETURN_IF_FAILED(MakeAndInitialize<UiaTextRange>(&result, _pData, pProvider, point, wordDelimiters));
*ppUtr = result;
return S_OK;
}

View file

@ -47,14 +47,15 @@ namespace Microsoft::Terminal
const winrt::Windows::UI::Xaml::Thickness GetPadding() const;
protected:
HRESULT GetSelectionRanges(_In_ IRawElementProviderSimple* pProvider, _Out_ std::deque<WRL::ComPtr<Microsoft::Console::Types::UiaTextRangeBase>>& selectionRanges) override;
HRESULT GetSelectionRanges(_In_ IRawElementProviderSimple* pProvider, const std::wstring_view wordDelimiters, _Out_ std::deque<WRL::ComPtr<Microsoft::Console::Types::UiaTextRangeBase>>& selectionRanges) override;
// degenerate range
HRESULT CreateTextRange(_In_ IRawElementProviderSimple* const pProvider, _COM_Outptr_result_maybenull_ Microsoft::Console::Types::UiaTextRangeBase** ppUtr) override;
HRESULT CreateTextRange(_In_ IRawElementProviderSimple* const pProvider, const std::wstring_view wordDelimiters, _COM_Outptr_result_maybenull_ Microsoft::Console::Types::UiaTextRangeBase** ppUtr) override;
// degenerate range at cursor position
HRESULT CreateTextRange(_In_ IRawElementProviderSimple* const pProvider,
const Cursor& cursor,
const std::wstring_view wordDelimiters,
_COM_Outptr_result_maybenull_ Microsoft::Console::Types::UiaTextRangeBase** ppUtr) override;
// specific endpoint range
@ -62,11 +63,13 @@ namespace Microsoft::Terminal
const Endpoint start,
const Endpoint end,
const bool degenerate,
const std::wstring_view wordDelimiters,
_COM_Outptr_result_maybenull_ Microsoft::Console::Types::UiaTextRangeBase** ppUtr) override;
// range from a UiaPoint
HRESULT CreateTextRange(_In_ IRawElementProviderSimple* const pProvider,
const UiaPoint point,
const std::wstring_view wordDelimiters,
_COM_Outptr_result_maybenull_ Microsoft::Console::Types::UiaTextRangeBase** ppUtr) override;
private:

View file

@ -11,6 +11,7 @@ using namespace Microsoft::WRL;
HRESULT UiaTextRange::GetSelectionRanges(_In_ IUiaData* pData,
_In_ IRawElementProviderSimple* pProvider,
_In_ const std::wstring_view wordDelimiters,
_Out_ std::deque<ComPtr<UiaTextRange>>& ranges)
{
try
@ -28,7 +29,7 @@ HRESULT UiaTextRange::GetSelectionRanges(_In_ IUiaData* pData,
Endpoint end = _screenInfoRowToEndpoint(pData, currentRow) + rect.RightInclusive();
ComPtr<UiaTextRange> range;
RETURN_IF_FAILED(MakeAndInitialize<UiaTextRange>(&range, pData, pProvider, start, end, false));
RETURN_IF_FAILED(MakeAndInitialize<UiaTextRange>(&range, pData, pProvider, start, end, false, wordDelimiters));
temporaryResult.emplace_back(std::move(range));
}
std::swap(temporaryResult, ranges);
@ -38,33 +39,36 @@ HRESULT UiaTextRange::GetSelectionRanges(_In_ IUiaData* pData,
}
// degenerate range constructor.
HRESULT UiaTextRange::RuntimeClassInitialize(_In_ IUiaData* pData, _In_ IRawElementProviderSimple* const pProvider)
HRESULT UiaTextRange::RuntimeClassInitialize(_In_ IUiaData* pData, _In_ IRawElementProviderSimple* const pProvider, _In_ const std::wstring_view wordDelimiters)
{
return UiaTextRangeBase::RuntimeClassInitialize(pData, pProvider);
return UiaTextRangeBase::RuntimeClassInitialize(pData, pProvider, wordDelimiters);
}
HRESULT UiaTextRange::RuntimeClassInitialize(_In_ IUiaData* pData,
_In_ IRawElementProviderSimple* const pProvider,
const Cursor& cursor)
const Cursor& cursor,
const std::wstring_view wordDelimiters)
{
return UiaTextRangeBase::RuntimeClassInitialize(pData, pProvider, cursor);
return UiaTextRangeBase::RuntimeClassInitialize(pData, pProvider, cursor, wordDelimiters);
}
HRESULT UiaTextRange::RuntimeClassInitialize(_In_ IUiaData* pData,
_In_ IRawElementProviderSimple* const pProvider,
const Endpoint start,
const Endpoint end,
const bool degenerate)
const bool degenerate,
const std::wstring_view wordDelimiters)
{
return UiaTextRangeBase::RuntimeClassInitialize(pData, pProvider, start, end, degenerate);
return UiaTextRangeBase::RuntimeClassInitialize(pData, pProvider, start, end, degenerate, wordDelimiters);
}
// returns a degenerate text range of the start of the row closest to the y value of point
HRESULT UiaTextRange::RuntimeClassInitialize(_In_ IUiaData* pData,
_In_ IRawElementProviderSimple* const pProvider,
const UiaPoint point)
const UiaPoint point,
const std::wstring_view wordDelimiters)
{
RETURN_IF_FAILED(UiaTextRangeBase::RuntimeClassInitialize(pData, pProvider));
RETURN_IF_FAILED(UiaTextRangeBase::RuntimeClassInitialize(pData, pProvider, wordDelimiters));
Initialize(point);
return S_OK;
}

View file

@ -25,30 +25,35 @@ namespace Microsoft::Terminal
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
HRESULT RuntimeClassInitialize(_In_ Microsoft::Console::Types::IUiaData* pData,
_In_ IRawElementProviderSimple* const pProvider);
_In_ IRawElementProviderSimple* const pProvider,
_In_ const std::wstring_view wordDelimiters = DefaultWordDelimiter);
// degenerate range at cursor position
HRESULT RuntimeClassInitialize(_In_ Microsoft::Console::Types::IUiaData* pData,
_In_ IRawElementProviderSimple* const pProvider,
const Cursor& cursor);
const Cursor& cursor,
const std::wstring_view wordDelimiters = DefaultWordDelimiter);
// specific endpoint range
HRESULT RuntimeClassInitialize(_In_ Microsoft::Console::Types::IUiaData* pData,
_In_ IRawElementProviderSimple* const pProvider,
const Endpoint start,
const Endpoint end,
const bool degenerate);
const bool degenerate,
const std::wstring_view wordDelimiters = DefaultWordDelimiter);
// range from a UiaPoint
HRESULT RuntimeClassInitialize(_In_ Microsoft::Console::Types::IUiaData* pData,
_In_ IRawElementProviderSimple* const pProvider,
const UiaPoint point);
const UiaPoint point,
const std::wstring_view wordDelimiters = DefaultWordDelimiter);
HRESULT RuntimeClassInitialize(const UiaTextRange& a);

View file

@ -178,12 +178,6 @@ private:
Word,
Line
};
enum class DelimiterClass
{
ControlChar,
DelimiterChar,
RegularChar
};
COORD _selectionAnchor;
COORD _endSelectionPosition;
bool _boxSelection;
@ -241,7 +235,6 @@ private:
SHORT _ExpandWideGlyphSelectionRight(const SHORT xPos, const SHORT yPos) const;
COORD _ExpandDoubleClickSelectionLeft(const COORD position) const;
COORD _ExpandDoubleClickSelectionRight(const COORD position) const;
DelimiterClass _GetDelimiterClass(const std::wstring_view cellChar) const noexcept;
COORD _ConvertToBufferCell(const COORD viewportPos) const;
const bool _IsSingleCellSelection() const noexcept;
std::tuple<COORD, COORD> _PreprocessSelectionCoords() const;

View file

@ -360,33 +360,11 @@ const TextBuffer::TextAndColor Terminal::RetrieveSelectedTextFromBuffer(bool tri
// - updated copy of "position" to new expanded location (with vertical offset)
COORD Terminal::_ExpandDoubleClickSelectionLeft(const COORD position) const
{
const auto bufferViewport = _buffer->GetSize();
// force position to be within bounds
COORD positionWithOffsets = position;
bufferViewport.Clamp(positionWithOffsets);
_buffer->GetSize().Clamp(positionWithOffsets);
// can't expand left
if (position.X == bufferViewport.Left())
{
return positionWithOffsets;
}
auto bufferIterator = _buffer->GetTextDataAt(positionWithOffsets);
const auto startedOnDelimiter = _GetDelimiterClass(*bufferIterator);
while (positionWithOffsets.X > bufferViewport.Left() && (_GetDelimiterClass(*bufferIterator) == startedOnDelimiter))
{
bufferViewport.DecrementInBounds(positionWithOffsets);
--bufferIterator;
}
if (_GetDelimiterClass(*bufferIterator) != startedOnDelimiter)
{
// move off of delimiter to highlight properly
bufferViewport.IncrementInBounds(positionWithOffsets);
}
return positionWithOffsets;
return _buffer->GetWordStart(positionWithOffsets, _wordDelimiters);
}
// Method Description:
@ -398,56 +376,11 @@ COORD Terminal::_ExpandDoubleClickSelectionLeft(const COORD position) const
// - updated copy of "position" to new expanded location (with vertical offset)
COORD Terminal::_ExpandDoubleClickSelectionRight(const COORD position) const
{
const auto bufferViewport = _buffer->GetSize();
// force position to be within bounds
COORD positionWithOffsets = position;
bufferViewport.Clamp(positionWithOffsets);
_buffer->GetSize().Clamp(positionWithOffsets);
// can't expand right
if (position.X == bufferViewport.RightInclusive())
{
return positionWithOffsets;
}
auto bufferIterator = _buffer->GetTextDataAt(positionWithOffsets);
const auto startedOnDelimiter = _GetDelimiterClass(*bufferIterator);
while (positionWithOffsets.X < bufferViewport.RightInclusive() && (_GetDelimiterClass(*bufferIterator) == startedOnDelimiter))
{
bufferViewport.IncrementInBounds(positionWithOffsets);
++bufferIterator;
}
if (_GetDelimiterClass(*bufferIterator) != startedOnDelimiter)
{
// move off of delimiter to highlight properly
bufferViewport.DecrementInBounds(positionWithOffsets);
}
return positionWithOffsets;
}
// Method Description:
// - get delimiter class for buffer cell data
// - used for double click selection
// Arguments:
// - cellChar: the char saved to the buffer cell under observation
// Return Value:
// - the delimiter class for the given char
Terminal::DelimiterClass Terminal::_GetDelimiterClass(const std::wstring_view cellChar) const noexcept
{
if (cellChar[0] <= UNICODE_SPACE)
{
return DelimiterClass::ControlChar;
}
else if (_wordDelimiters.find(cellChar) != std::wstring_view::npos)
{
return DelimiterClass::DelimiterChar;
}
else
{
return DelimiterClass::RegularChar;
}
return _buffer->GetWordEnd(positionWithOffsets, _wordDelimiters);
}
// Method Description:

View file

@ -97,7 +97,7 @@ void ScreenInfoUiaProvider::ChangeViewport(const SMALL_RECT NewWindow)
_pUiaParent->ChangeViewport(NewWindow);
}
HRESULT ScreenInfoUiaProvider::GetSelectionRanges(_In_ IRawElementProviderSimple* pProvider, _Out_ std::deque<ComPtr<UiaTextRangeBase>>& result)
HRESULT ScreenInfoUiaProvider::GetSelectionRanges(_In_ IRawElementProviderSimple* pProvider, const std::wstring_view wordDelimiters, _Out_ std::deque<ComPtr<UiaTextRangeBase>>& result)
{
try
{
@ -105,7 +105,7 @@ HRESULT ScreenInfoUiaProvider::GetSelectionRanges(_In_ IRawElementProviderSimple
typename std::remove_reference<decltype(result)>::type temporaryResult;
std::deque<ComPtr<UiaTextRange>> ranges;
RETURN_IF_FAILED(UiaTextRange::GetSelectionRanges(_pData, pProvider, ranges));
RETURN_IF_FAILED(UiaTextRange::GetSelectionRanges(_pData, pProvider, wordDelimiters, ranges));
while (!ranges.empty())
{
@ -119,24 +119,25 @@ HRESULT ScreenInfoUiaProvider::GetSelectionRanges(_In_ IRawElementProviderSimple
CATCH_RETURN();
}
HRESULT ScreenInfoUiaProvider::CreateTextRange(_In_ IRawElementProviderSimple* const pProvider, _COM_Outptr_result_maybenull_ UiaTextRangeBase** ppUtr)
HRESULT ScreenInfoUiaProvider::CreateTextRange(_In_ IRawElementProviderSimple* const pProvider, const std::wstring_view wordDelimiters, _COM_Outptr_result_maybenull_ UiaTextRangeBase** ppUtr)
{
RETURN_HR_IF_NULL(E_INVALIDARG, ppUtr);
*ppUtr = nullptr;
UiaTextRange* result = nullptr;
RETURN_IF_FAILED(MakeAndInitialize<UiaTextRange>(&result, _pData, pProvider));
RETURN_IF_FAILED(MakeAndInitialize<UiaTextRange>(&result, _pData, pProvider, wordDelimiters));
*ppUtr = result;
return S_OK;
}
HRESULT ScreenInfoUiaProvider::CreateTextRange(_In_ IRawElementProviderSimple* const pProvider,
const Cursor& cursor,
const std::wstring_view wordDelimiters,
_COM_Outptr_result_maybenull_ UiaTextRangeBase** ppUtr)
{
RETURN_HR_IF_NULL(E_INVALIDARG, ppUtr);
*ppUtr = nullptr;
UiaTextRange* result = nullptr;
RETURN_IF_FAILED(MakeAndInitialize<UiaTextRange>(&result, _pData, pProvider, cursor));
RETURN_IF_FAILED(MakeAndInitialize<UiaTextRange>(&result, _pData, pProvider, cursor, wordDelimiters));
*ppUtr = result;
return S_OK;
}
@ -145,24 +146,26 @@ HRESULT ScreenInfoUiaProvider::CreateTextRange(_In_ IRawElementProviderSimple* c
const Endpoint start,
const Endpoint end,
const bool degenerate,
const std::wstring_view wordDelimiters,
_COM_Outptr_result_maybenull_ UiaTextRangeBase** ppUtr)
{
RETURN_HR_IF_NULL(E_INVALIDARG, ppUtr);
*ppUtr = nullptr;
UiaTextRange* result = nullptr;
RETURN_IF_FAILED(MakeAndInitialize<UiaTextRange>(&result, _pData, pProvider, start, end, degenerate));
RETURN_IF_FAILED(MakeAndInitialize<UiaTextRange>(&result, _pData, pProvider, start, end, degenerate, wordDelimiters));
*ppUtr = result;
return S_OK;
}
HRESULT ScreenInfoUiaProvider::CreateTextRange(_In_ IRawElementProviderSimple* const pProvider,
const UiaPoint point,
const std::wstring_view wordDelimiters,
_COM_Outptr_result_maybenull_ UiaTextRangeBase** ppUtr)
{
RETURN_HR_IF_NULL(E_INVALIDARG, ppUtr);
*ppUtr = nullptr;
UiaTextRange* result = nullptr;
RETURN_IF_FAILED(MakeAndInitialize<UiaTextRange>(&result, _pData, pProvider, point));
RETURN_IF_FAILED(MakeAndInitialize<UiaTextRange>(&result, _pData, pProvider, point, wordDelimiters));
*ppUtr = result;
return S_OK;
}

View file

@ -43,14 +43,15 @@ namespace Microsoft::Console::Interactivity::Win32
void ChangeViewport(const SMALL_RECT NewWindow);
protected:
HRESULT GetSelectionRanges(_In_ IRawElementProviderSimple* pProvider, _Out_ std::deque<WRL::ComPtr<Microsoft::Console::Types::UiaTextRangeBase>>& selectionRanges) override;
HRESULT GetSelectionRanges(_In_ IRawElementProviderSimple* pProvider, const std::wstring_view wordDelimiters, _Out_ std::deque<WRL::ComPtr<Microsoft::Console::Types::UiaTextRangeBase>>& selectionRanges) override;
// degenerate range
HRESULT CreateTextRange(_In_ IRawElementProviderSimple* const pProvider, _COM_Outptr_result_maybenull_ Microsoft::Console::Types::UiaTextRangeBase** ppUtr) override;
HRESULT CreateTextRange(_In_ IRawElementProviderSimple* const pProvider, const std::wstring_view wordDelimiters, _COM_Outptr_result_maybenull_ Microsoft::Console::Types::UiaTextRangeBase** ppUtr) override;
// degenerate range at cursor position
HRESULT CreateTextRange(_In_ IRawElementProviderSimple* const pProvider,
const Cursor& cursor,
const std::wstring_view wordDelimiters,
_COM_Outptr_result_maybenull_ Microsoft::Console::Types::UiaTextRangeBase** ppUtr) override;
// specific endpoint range
@ -58,11 +59,13 @@ namespace Microsoft::Console::Interactivity::Win32
const Endpoint start,
const Endpoint end,
const bool degenerate,
const std::wstring_view wordDelimiters,
_COM_Outptr_result_maybenull_ Microsoft::Console::Types::UiaTextRangeBase** ppUtr) override;
// range from a UiaPoint
HRESULT CreateTextRange(_In_ IRawElementProviderSimple* const pProvider,
const UiaPoint point,
const std::wstring_view wordDelimiters,
_COM_Outptr_result_maybenull_ Microsoft::Console::Types::UiaTextRangeBase** ppUtr) override;
private:

View file

@ -15,6 +15,7 @@ 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
@ -32,7 +33,7 @@ HRESULT UiaTextRange::GetSelectionRanges(_In_ IUiaData* pData,
Endpoint end = _screenInfoRowToEndpoint(pData, currentRow) + rect.RightInclusive();
ComPtr<UiaTextRange> range;
RETURN_IF_FAILED(MakeAndInitialize<UiaTextRange>(&range, pData, pProvider, start, end, false));
RETURN_IF_FAILED(MakeAndInitialize<UiaTextRange>(&range, pData, pProvider, start, end, false, wordDelimiters));
temporaryResult.emplace_back(std::move(range));
}
std::swap(temporaryResult, ranges);
@ -42,17 +43,18 @@ HRESULT UiaTextRange::GetSelectionRanges(_In_ IUiaData* pData,
}
// degenerate range constructor.
HRESULT UiaTextRange::RuntimeClassInitialize(_In_ IUiaData* pData, _In_ IRawElementProviderSimple* const pProvider)
HRESULT UiaTextRange::RuntimeClassInitialize(_In_ IUiaData* pData, _In_ IRawElementProviderSimple* const pProvider, _In_ const std::wstring_view wordDelimiters)
{
return UiaTextRangeBase::RuntimeClassInitialize(pData, pProvider);
return UiaTextRangeBase::RuntimeClassInitialize(pData, pProvider, wordDelimiters);
}
// degenerate range at cursor position
HRESULT UiaTextRange::RuntimeClassInitialize(_In_ IUiaData* pData,
_In_ IRawElementProviderSimple* const pProvider,
const Cursor& cursor)
const Cursor& cursor,
const std::wstring_view wordDelimiters)
{
return UiaTextRangeBase::RuntimeClassInitialize(pData, pProvider, cursor);
return UiaTextRangeBase::RuntimeClassInitialize(pData, pProvider, cursor, wordDelimiters);
}
// specific endpoint range
@ -60,17 +62,19 @@ HRESULT UiaTextRange::RuntimeClassInitialize(_In_ IUiaData* pData,
_In_ IRawElementProviderSimple* const pProvider,
const Endpoint start,
const Endpoint end,
const bool degenerate)
const bool degenerate,
const std::wstring_view wordDelimiters)
{
return UiaTextRangeBase::RuntimeClassInitialize(pData, pProvider, start, end, degenerate);
return UiaTextRangeBase::RuntimeClassInitialize(pData, pProvider, start, end, degenerate, wordDelimiters);
}
// returns a degenerate text range of the start of the row closest to the y value of point
HRESULT UiaTextRange::RuntimeClassInitialize(_In_ IUiaData* pData,
_In_ IRawElementProviderSimple* const pProvider,
const UiaPoint point)
const UiaPoint point,
const std::wstring_view wordDelimiters)
{
RETURN_IF_FAILED(UiaTextRangeBase::RuntimeClassInitialize(pData, pProvider));
RETURN_IF_FAILED(UiaTextRangeBase::RuntimeClassInitialize(pData, pProvider, wordDelimiters));
Initialize(point);
return S_OK;
}

View file

@ -26,30 +26,35 @@ namespace Microsoft::Console::Interactivity::Win32
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
HRESULT RuntimeClassInitialize(_In_ Microsoft::Console::Types::IUiaData* pData,
_In_ IRawElementProviderSimple* const pProvider);
_In_ IRawElementProviderSimple* const pProvider,
_In_ const std::wstring_view wordDelimiters = DefaultWordDelimiter);
// degenerate range at cursor position
HRESULT RuntimeClassInitialize(_In_ Microsoft::Console::Types::IUiaData* pData,
_In_ IRawElementProviderSimple* const pProvider,
const Cursor& cursor);
const Cursor& cursor,
_In_ const std::wstring_view wordDelimiters = DefaultWordDelimiter);
// specific endpoint range
HRESULT RuntimeClassInitialize(_In_ Microsoft::Console::Types::IUiaData* pData,
_In_ IRawElementProviderSimple* const pProvider,
const Endpoint start,
const Endpoint end,
const bool degenerate);
const bool degenerate,
_In_ const std::wstring_view wordDelimiters = DefaultWordDelimiter);
// range from a UiaPoint
HRESULT RuntimeClassInitialize(_In_ Microsoft::Console::Types::IUiaData* pData,
_In_ IRawElementProviderSimple* const pProvider,
const UiaPoint point);
const UiaPoint point,
_In_ const std::wstring_view wordDelimiters = DefaultWordDelimiter);
HRESULT RuntimeClassInitialize(const UiaTextRange& a);

View file

@ -473,6 +473,271 @@ class UiaTextRangeTests
}
}
TEST_METHOD(CanMoveByWord_EmptyBuffer)
{
const Column firstColumnIndex = 0;
const Column lastColumnIndex = _pScreenInfo->GetBufferSize().Width() - 1;
const ScreenInfoRow topRow = 0;
const ScreenInfoRow bottomRow = _pTextBuffer->TotalRowCount() - 1;
// clang-format off
const std::vector<std::tuple<std::wstring,
UiaTextRange::MoveState,
int, // amount to move
int, // amount actually moved
Endpoint, // start
Endpoint // end
>> testData =
{
{
L"can't move backward from (0, 0)",
{
0, 0,
0, 2,
topRow,
lastColumnIndex,
firstColumnIndex,
UiaTextRange::MovementIncrement::Backward,
UiaTextRange::MovementDirection::Backward
},
-1,
0,
0u,
lastColumnIndex
},
{
L"can move backward within a row",
{
0, 1,
0, 2,
topRow,
lastColumnIndex,
firstColumnIndex,
UiaTextRange::MovementIncrement::Backward,
UiaTextRange::MovementDirection::Backward
},
-1,
-1,
0u,
lastColumnIndex
},
{
L"can move forward in a row",
{
2, 1,
4, 5,
bottomRow,
firstColumnIndex,
lastColumnIndex,
UiaTextRange::MovementIncrement::Forward,
UiaTextRange::MovementDirection::Forward
},
5,
5,
UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, 8),
UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, 8) + lastColumnIndex
},
{
L"can't move past the last column in the last row",
{
bottomRow, lastColumnIndex,
bottomRow, lastColumnIndex,
bottomRow,
firstColumnIndex,
lastColumnIndex,
UiaTextRange::MovementIncrement::Forward,
UiaTextRange::MovementDirection::Forward
},
5,
0,
UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow),
UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex
},
{
L"can move to a new row when necessary when moving forward",
{
topRow, lastColumnIndex,
topRow, lastColumnIndex,
bottomRow,
firstColumnIndex,
lastColumnIndex,
UiaTextRange::MovementIncrement::Forward,
UiaTextRange::MovementDirection::Forward
},
5,
5,
UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow + 5),
UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow + 5) + lastColumnIndex
},
{
L"can move to a new row when necessary when moving backward",
{
topRow + 1, firstColumnIndex,
topRow + 1, lastColumnIndex,
topRow,
lastColumnIndex,
firstColumnIndex,
UiaTextRange::MovementIncrement::Backward,
UiaTextRange::MovementDirection::Backward
},
-5,
-2,
0u,
UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + lastColumnIndex
}
};
// clang-format on
for (auto data : testData)
{
Log::Comment(std::get<0>(data).c_str());
int amountMoved;
std::pair<Endpoint, Endpoint> newEndpoints = UiaTextRange::_moveByWord(_pUiaData,
std::get<2>(data),
std::get<1>(data),
L"",
&amountMoved);
VERIFY_ARE_EQUAL(std::get<3>(data), amountMoved);
VERIFY_ARE_EQUAL(std::get<4>(data), newEndpoints.first);
VERIFY_ARE_EQUAL(std::get<5>(data), newEndpoints.second);
}
}
TEST_METHOD(CanMoveByWord_NonEmptyBuffer)
{
const Column firstColumnIndex = 0;
const Column lastColumnIndex = _pScreenInfo->GetBufferSize().Width() - 1;
const ScreenInfoRow topRow = 0;
const ScreenInfoRow bottomRow = _pTextBuffer->TotalRowCount() - 1;
const std::wstring_view text[] = {
L"word1 word2 word3",
L"word4 word5 word6"
};
for (auto i = 0; i < 2; i++)
{
_pTextBuffer->WriteLine(text[i], { 0, gsl::narrow<SHORT>(i) });
}
// clang-format off
const std::vector<std::tuple<std::wstring,
UiaTextRange::MoveState,
int, // amount to move
int, // amount actually moved
Endpoint, // start
Endpoint // end
>> testData =
{
{
L"move backwards on the word by (0,0)",
{
0, 1,
0, 2,
topRow,
lastColumnIndex,
firstColumnIndex,
UiaTextRange::MovementIncrement::Backward,
UiaTextRange::MovementDirection::Backward
},
-1,
-1,
0u,
6
},
{
L"get next word while on first word",
{
0, 0,
0, 0,
bottomRow,
firstColumnIndex,
lastColumnIndex,
UiaTextRange::MovementIncrement::Forward,
UiaTextRange::MovementDirection::Forward
},
1,
1,
0,
6
},
{
L"get next word twice while on first word",
{
0, 0,
0, 0,
bottomRow,
firstColumnIndex,
lastColumnIndex,
UiaTextRange::MovementIncrement::Forward,
UiaTextRange::MovementDirection::Forward
},
2,
2,
7,
14
},
{
L"move forward to next row with word",
{
topRow, lastColumnIndex,
topRow, lastColumnIndex,
bottomRow,
firstColumnIndex,
lastColumnIndex,
UiaTextRange::MovementIncrement::Forward,
UiaTextRange::MovementDirection::Forward
},
1,
1,
UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow + 1),
UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow + 1) + 6
},
{
L"move backwards to previous row with word",
{
topRow + 1, firstColumnIndex,
topRow + 1, lastColumnIndex,
topRow,
lastColumnIndex,
firstColumnIndex,
UiaTextRange::MovementIncrement::Backward,
UiaTextRange::MovementDirection::Backward
},
-1,
-1,
UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + 15,
UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + lastColumnIndex
}
};
// clang-format on
for (auto data : testData)
{
Log::Comment(std::get<0>(data).c_str());
int amountMoved;
std::pair<Endpoint, Endpoint> newEndpoints = UiaTextRange::_moveByWord(_pUiaData,
std::get<2>(data),
std::get<1>(data),
L"",
&amountMoved);
VERIFY_ARE_EQUAL(std::get<3>(data), amountMoved);
VERIFY_ARE_EQUAL(std::get<4>(data), newEndpoints.first);
VERIFY_ARE_EQUAL(std::get<5>(data), newEndpoints.second);
}
}
TEST_METHOD(CanMoveByLine)
{
const Column firstColumnIndex = 0;
@ -798,6 +1063,207 @@ class UiaTextRangeTests
}
}
TEST_METHOD(CanMoveEndpointByUnitWord)
{
const Column firstColumnIndex = 0;
const Column lastColumnIndex = _pScreenInfo->GetBufferSize().Width() - 1;
const ScreenInfoRow topRow = 0;
const ScreenInfoRow bottomRow = _pTextBuffer->TotalRowCount() - 1;
const std::wstring_view text[] = {
L"word1 word2 word3",
L"word4 word5 word6"
};
for (auto i = 0; i < 2; i++)
{
_pTextBuffer->WriteLine(text[i], { 0, gsl::narrow<SHORT>(i) });
}
// clang-format off
const std::vector<std::tuple<std::wstring,
UiaTextRange::MoveState,
int, // amount to move
int, // amount actually moved
TextPatternRangeEndpoint, // endpoint to move
Endpoint, // start
Endpoint, // end
bool // degenerate
>> testData =
{
{
L"can't move _start past the beginning of the document when _start is positioned at the beginning",
{
topRow, firstColumnIndex,
topRow, lastColumnIndex,
topRow,
lastColumnIndex,
firstColumnIndex,
UiaTextRange::MovementIncrement::Backward,
UiaTextRange::MovementDirection::Backward
},
-1,
0,
TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start,
UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex,
UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + lastColumnIndex,
false
},
{
L"can partially move _start to the begining of the document when it is closer than the move count requested",
{
topRow, firstColumnIndex + 15,
topRow, lastColumnIndex,
topRow,
lastColumnIndex,
firstColumnIndex,
UiaTextRange::MovementIncrement::Backward,
UiaTextRange::MovementDirection::Backward
},
-5,
-2,
TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start,
UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex,
UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + lastColumnIndex,
false
},
{
L"can't move _end past the begining of the document",
{
topRow, firstColumnIndex,
topRow, firstColumnIndex + 2,
topRow,
lastColumnIndex,
firstColumnIndex,
UiaTextRange::MovementIncrement::Backward,
UiaTextRange::MovementDirection::Backward
},
-2,
-1,
TextPatternRangeEndpoint::TextPatternRangeEndpoint_End,
UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex,
UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex,
false
},
{
L"_start follows _end when passed during movement",
{
topRow + 1, firstColumnIndex + 2,
topRow + 1, firstColumnIndex + 10,
topRow,
lastColumnIndex,
firstColumnIndex,
UiaTextRange::MovementIncrement::Backward,
UiaTextRange::MovementDirection::Backward
},
-4,
-4,
TextPatternRangeEndpoint::TextPatternRangeEndpoint_End,
UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + 6,
UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + 6,
true
},
{
L"can't move _end past the beginning of the document when _end is positioned at the end",
{
bottomRow, firstColumnIndex,
bottomRow, lastColumnIndex,
bottomRow,
firstColumnIndex,
lastColumnIndex,
UiaTextRange::MovementIncrement::Forward,
UiaTextRange::MovementDirection::Forward
},
1,
0,
TextPatternRangeEndpoint::TextPatternRangeEndpoint_End,
UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + firstColumnIndex,
UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex,
false
},
{
L"can partially move _end to the end of the document when it is closer than the move count requested",
{
topRow, firstColumnIndex,
bottomRow, lastColumnIndex - 3,
bottomRow,
firstColumnIndex,
lastColumnIndex,
UiaTextRange::MovementIncrement::Forward,
UiaTextRange::MovementDirection::Forward
},
5,
1,
TextPatternRangeEndpoint::TextPatternRangeEndpoint_End,
UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex,
UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex,
false
},
{
L"can't move _start past the end of the document",
{
bottomRow, lastColumnIndex - 4,
bottomRow, lastColumnIndex,
bottomRow,
firstColumnIndex,
lastColumnIndex,
UiaTextRange::MovementIncrement::Forward,
UiaTextRange::MovementDirection::Forward
},
5,
1,
TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start,
UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex,
UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex,
false
},
{
L"_end follows _start when passed during movement",
{
topRow, firstColumnIndex,
topRow, firstColumnIndex + 3,
topRow,
lastColumnIndex,
firstColumnIndex,
UiaTextRange::MovementIncrement::Forward,
UiaTextRange::MovementDirection::Forward
},
2,
2,
TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start,
UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow)+15,
UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow)+15,
true
},
};
// clang-format on
for (auto data : testData)
{
Log::Comment(std::get<0>(data).c_str());
std::tuple<Endpoint, Endpoint, bool> result;
int amountMoved;
result = UiaTextRange::_moveEndpointByUnitWord(_pUiaData,
std::get<2>(data),
std::get<4>(data),
std::get<1>(data),
L" ",
&amountMoved);
VERIFY_ARE_EQUAL(std::get<3>(data), amountMoved);
VERIFY_ARE_EQUAL(std::get<5>(data), std::get<0>(result));
VERIFY_ARE_EQUAL(std::get<6>(data), std::get<1>(result));
VERIFY_ARE_EQUAL(std::get<7>(data), std::get<2>(result));
}
}
TEST_METHOD(CanMoveEndpointByUnitLine)
{
const Column firstColumnIndex = 0;

View file

@ -32,12 +32,15 @@ SAFEARRAY* BuildIntSafeArray(std::basic_string_view<int> data)
}
#pragma warning(suppress : 26434) // WRL RuntimeClassInitialize base is a no-op and we need this for MakeAndInitialize
HRESULT ScreenInfoUiaProviderBase::RuntimeClassInitialize(_In_ IUiaData* pData) noexcept
HRESULT ScreenInfoUiaProviderBase::RuntimeClassInitialize(_In_ IUiaData* pData, _In_ std::wstring_view wordDelimiters) noexcept
try
{
RETURN_HR_IF_NULL(E_INVALIDARG, pData);
_pData = pData;
_wordDelimiters = wordDelimiters;
return S_OK;
}
CATCH_RETURN();
[[nodiscard]] HRESULT ScreenInfoUiaProviderBase::Signal(_In_ EVENTID id)
{
@ -272,7 +275,7 @@ IFACEMETHODIMP ScreenInfoUiaProviderBase::GetSelection(_Outptr_result_maybenull_
}
WRL::ComPtr<UiaTextRangeBase> range;
hr = CreateTextRange(this, cursor, &range);
hr = CreateTextRange(this, cursor, _wordDelimiters, &range);
if (FAILED(hr))
{
SafeArrayDestroy(*ppRetVal);
@ -293,7 +296,7 @@ IFACEMETHODIMP ScreenInfoUiaProviderBase::GetSelection(_Outptr_result_maybenull_
{
// get the selection ranges
std::deque<WRL::ComPtr<UiaTextRangeBase>> ranges;
RETURN_IF_FAILED(GetSelectionRanges(this, ranges));
RETURN_IF_FAILED(GetSelectionRanges(this, _wordDelimiters, ranges));
// TODO GitHub #1914: Re-attach Tracing to UIA Tree
//apiMsg.AreaSelected = true;
@ -363,6 +366,7 @@ IFACEMETHODIMP ScreenInfoUiaProviderBase::GetVisibleRanges(_Outptr_result_mayben
start,
end,
false,
_wordDelimiters,
&range);
if (FAILED(hr))
{
@ -393,7 +397,7 @@ IFACEMETHODIMP ScreenInfoUiaProviderBase::RangeFromChild(_In_ IRawElementProvide
*ppRetVal = nullptr;
WRL::ComPtr<UiaTextRangeBase> utr;
RETURN_IF_FAILED(CreateTextRange(this, &utr));
RETURN_IF_FAILED(CreateTextRange(this, _wordDelimiters, &utr));
RETURN_IF_FAILED(utr.CopyTo(ppRetVal));
return S_OK;
}
@ -410,6 +414,7 @@ IFACEMETHODIMP ScreenInfoUiaProviderBase::RangeFromPoint(_In_ UiaPoint point,
WRL::ComPtr<UiaTextRangeBase> utr;
RETURN_IF_FAILED(CreateTextRange(this,
point,
_wordDelimiters,
&utr));
RETURN_IF_FAILED(utr.CopyTo(ppRetVal));
return S_OK;
@ -424,7 +429,7 @@ IFACEMETHODIMP ScreenInfoUiaProviderBase::get_DocumentRange(_COM_Outptr_result_m
*ppRetVal = nullptr;
WRL::ComPtr<UiaTextRangeBase> utr;
RETURN_IF_FAILED(CreateTextRange(this, &utr));
RETURN_IF_FAILED(CreateTextRange(this, _wordDelimiters, &utr));
RETURN_IF_FAILED(utr->ExpandToEnclosingUnit(TextUnit::TextUnit_Document));
RETURN_IF_FAILED(utr.CopyTo(ppRetVal));
return S_OK;

View file

@ -37,7 +37,7 @@ namespace Microsoft::Console::Types
public WRL::RuntimeClass<WRL::RuntimeClassFlags<WRL::ClassicCom | WRL::InhibitFtmBase>, IRawElementProviderSimple, IRawElementProviderFragment, ITextProvider>
{
public:
HRESULT RuntimeClassInitialize(_In_ IUiaData* pData) noexcept;
HRESULT RuntimeClassInitialize(_In_ IUiaData* pData, _In_ std::wstring_view wordDelimiters = UiaTextRangeBase::DefaultWordDelimiter) noexcept;
ScreenInfoUiaProviderBase(const ScreenInfoUiaProviderBase&) = default;
ScreenInfoUiaProviderBase(ScreenInfoUiaProviderBase&&) = default;
@ -77,14 +77,15 @@ namespace Microsoft::Console::Types
protected:
ScreenInfoUiaProviderBase() = default;
virtual HRESULT GetSelectionRanges(_In_ IRawElementProviderSimple* pProvider, _Out_ std::deque<WRL::ComPtr<UiaTextRangeBase>>& selectionRanges) = 0;
virtual HRESULT GetSelectionRanges(_In_ IRawElementProviderSimple* pProvider, const std::wstring_view wordDelimiters, _Out_ std::deque<WRL::ComPtr<UiaTextRangeBase>>& selectionRanges) = 0;
// degenerate range
virtual HRESULT CreateTextRange(_In_ IRawElementProviderSimple* const pProvider, _COM_Outptr_result_maybenull_ UiaTextRangeBase** ppUtr) = 0;
virtual HRESULT CreateTextRange(_In_ IRawElementProviderSimple* const pProvider, const std::wstring_view wordDelimiters, _COM_Outptr_result_maybenull_ UiaTextRangeBase** ppUtr) = 0;
// degenerate range at cursor position
virtual HRESULT CreateTextRange(_In_ IRawElementProviderSimple* const pProvider,
const Cursor& cursor,
const std::wstring_view wordDelimiters,
_COM_Outptr_result_maybenull_ UiaTextRangeBase** ppUtr) = 0;
// specific endpoint range
@ -92,16 +93,20 @@ namespace Microsoft::Console::Types
const Endpoint start,
const Endpoint end,
const bool degenerate,
const std::wstring_view wordDelimiters,
_COM_Outptr_result_maybenull_ UiaTextRangeBase** ppUtr) = 0;
// range from a UiaPoint
virtual HRESULT CreateTextRange(_In_ IRawElementProviderSimple* const pProvider,
const UiaPoint point,
const std::wstring_view wordDelimiters,
_COM_Outptr_result_maybenull_ UiaTextRangeBase** ppUtr) = 0;
// weak reference to IUiaData
IUiaData* _pData;
std::wstring _wordDelimiters;
private:
// this is used to prevent the object from
// signaling an event while it is already in the

View file

@ -103,7 +103,8 @@ void UiaTextRangeBase::_outputObjectState()
// degenerate range constructor.
#pragma warning(suppress : 26434) // WRL RuntimeClassInitialize base is a no-op and we need this for MakeAndInitialize
HRESULT UiaTextRangeBase::RuntimeClassInitialize(_In_ IUiaData* pData, _In_ IRawElementProviderSimple* const pProvider) noexcept
HRESULT UiaTextRangeBase::RuntimeClassInitialize(_In_ IUiaData* pData, _In_ IRawElementProviderSimple* const pProvider, _In_ std::wstring_view wordDelimiters) noexcept
try
{
RETURN_HR_IF_NULL(E_INVALIDARG, pProvider);
RETURN_HR_IF_NULL(E_INVALIDARG, pData);
@ -113,6 +114,7 @@ HRESULT UiaTextRangeBase::RuntimeClassInitialize(_In_ IUiaData* pData, _In_ IRaw
_end = 0;
_degenerate = true;
_pData = pData;
_wordDelimiters = wordDelimiters;
_id = id;
++id;
@ -125,13 +127,15 @@ HRESULT UiaTextRangeBase::RuntimeClassInitialize(_In_ IUiaData* pData, _In_ IRaw
return S_OK;
}
CATCH_RETURN();
#pragma warning(suppress : 26434) // WRL RuntimeClassInitialize base is a no-op and we need this for MakeAndInitialize
HRESULT UiaTextRangeBase::RuntimeClassInitialize(_In_ IUiaData* pData,
_In_ IRawElementProviderSimple* const pProvider,
const Cursor& cursor) noexcept
const Cursor& cursor,
_In_ std::wstring_view wordDelimiters) noexcept
{
RETURN_IF_FAILED(RuntimeClassInitialize(pData, pProvider));
RETURN_IF_FAILED(RuntimeClassInitialize(pData, pProvider, wordDelimiters));
try
{
@ -156,9 +160,10 @@ HRESULT UiaTextRangeBase::RuntimeClassInitialize(_In_ IUiaData* pData,
_In_ IRawElementProviderSimple* const pProvider,
const Endpoint start,
const Endpoint end,
const bool degenerate) noexcept
const bool degenerate,
_In_ std::wstring_view wordDelimiters) noexcept
{
RETURN_IF_FAILED(RuntimeClassInitialize(pData, pProvider));
RETURN_IF_FAILED(RuntimeClassInitialize(pData, pProvider, wordDelimiters));
RETURN_HR_IF(E_INVALIDARG, !degenerate && start > end);
_degenerate = degenerate;
@ -341,6 +346,14 @@ IFACEMETHODIMP UiaTextRangeBase::ExpandToEnclosingUnit(_In_ TextUnit unit)
{
_end = _start;
}
else if (unit <= TextUnit::TextUnit_Word)
{
// expand to word
const auto target = _start;
_start = _wordBeginEndpoint(_pData, target, _wordDelimiters);
_end = _wordEndEndpoint(_pData, target, _wordDelimiters);
FAIL_FAST_IF(!(_start <= _end));
}
else if (unit <= TextUnit::TextUnit_Line)
{
// expand to line
@ -601,11 +614,15 @@ IFACEMETHODIMP UiaTextRangeBase::Move(_In_ TextUnit unit,
_outputRowConversions();
#endif
auto moveFunc = &_moveByDocument;
std::function<std::pair<Endpoint, Endpoint>(gsl::not_null<IUiaData*>, const int, const MoveState, gsl::not_null<int*> const)> moveFunc = &_moveByDocument;
if (unit == TextUnit::TextUnit_Character)
{
moveFunc = &_moveByCharacter;
}
else if (unit <= TextUnit::TextUnit_Word)
{
moveFunc = std::bind(&_moveByWord, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, _wordDelimiters, std::placeholders::_4);
}
else if (unit <= TextUnit::TextUnit_Line)
{
moveFunc = &_moveByLine;
@ -677,11 +694,21 @@ IFACEMETHODIMP UiaTextRangeBase::MoveEndpointByUnit(_In_ TextPatternRangeEndpoin
const MovementDirection moveDirection = (count > 0) ? MovementDirection::Forward : MovementDirection::Backward;
auto moveFunc = &_moveEndpointByUnitDocument;
std::function<decltype(_moveEndpointByUnitDocument)> moveFunc = &_moveEndpointByUnitDocument;
if (unit == TextUnit::TextUnit_Character)
{
moveFunc = &_moveEndpointByUnitCharacter;
}
else if (unit <= TextUnit::TextUnit_Word)
{
// bind all params of this function, except put a _wordDelimiters in there
// NOTE: using a lambda function here because...
// - lambda is cheaper than std::bind
// - _move* functions are static, but this particular consumer needs access to a member (may be fixed by TODO GH #1993)
moveFunc = [=](auto&& pData, auto&& moveCount, auto&& endpoint, auto&& moveState, auto&& pAmountMoved) {
return _moveEndpointByUnitWord(pData, moveCount, endpoint, moveState, _wordDelimiters, pAmountMoved);
};
}
else if (unit <= TextUnit::TextUnit_Line)
{
moveFunc = &_moveEndpointByUnitLine;
@ -965,7 +992,7 @@ const COORD UiaTextRangeBase::_getScreenFontSize() const
// Routine Description:
// - Gets the number of rows in the output text buffer.
// Arguments:
// - <none>
// - pData - the UiaData for the terminal we are operating on
// Return Value:
// - The number of rows
const unsigned int UiaTextRangeBase::_getTotalRows(gsl::not_null<IUiaData*> pData) noexcept
@ -976,7 +1003,7 @@ const unsigned int UiaTextRangeBase::_getTotalRows(gsl::not_null<IUiaData*> pDat
// Routine Description:
// - Gets the width of the screen buffer rows
// Arguments:
// - <none>
// - pData - the UiaData for the terminal we are operating on
// Return Value:
// - The row width
const unsigned int UiaTextRangeBase::_getRowWidth(gsl::not_null<IUiaData*> pData)
@ -988,6 +1015,7 @@ const unsigned int UiaTextRangeBase::_getRowWidth(gsl::not_null<IUiaData*> pData
// Routine Description:
// - calculates the column refered to by the endpoint.
// Arguments:
// - pData - the UiaData for the terminal we are operating on
// - endpoint - the endpoint to translate
// Return Value:
// - the column value
@ -999,6 +1027,7 @@ const Column UiaTextRangeBase::_endpointToColumn(gsl::not_null<IUiaData*> pData,
// Routine Description:
// - converts an Endpoint into its equivalent text buffer row.
// Arguments:
// - pData - the UiaData for the terminal we are operating on
// - endpoint - the endpoint to convert
// Return Value:
// - the text buffer row value
@ -1012,7 +1041,7 @@ const TextBufferRow UiaTextRangeBase::_endpointToTextBufferRow(gsl::not_null<IUi
// - counts the number of rows that are fully or partially part of the
// range.
// Arguments:
// - <none>
// - pData - the UiaData for the terminal we are operating on
// Return Value:
// - The number of rows in the range.
const unsigned int UiaTextRangeBase::_rowCountInRange(gsl::not_null<IUiaData*> pData) const
@ -1036,6 +1065,7 @@ const unsigned int UiaTextRangeBase::_rowCountInRange(gsl::not_null<IUiaData*> p
// Routine Description:
// - Converts a TextBufferRow to a ScreenInfoRow.
// Arguments:
// - pData - the UiaData for the terminal we are operating on
// - row - the TextBufferRow to convert
// Return Value:
// - the equivalent ScreenInfoRow.
@ -1050,6 +1080,7 @@ const ScreenInfoRow UiaTextRangeBase::_textBufferRowToScreenInfoRow(gsl::not_nul
// - Converts a ScreenInfoRow to a ViewportRow. Uses the default
// viewport for the conversion.
// Arguments:
// - pData - the UiaData for the terminal we are operating on
// - row - the ScreenInfoRow to convert
// Return Value:
// - the equivalent ViewportRow.
@ -1064,7 +1095,8 @@ const ViewportRow UiaTextRangeBase::_screenInfoRowToViewportRow(gsl::not_null<IU
// buffer. The output buffer stores the text in a circular buffer so
// this method makes sure that we circle around gracefully.
// Arguments:
// - the non-normalized row index
// - pData - the UiaData for the terminal we are operating on
// - row - the non-normalized row index
// Return Value:
// - the normalized row index
const Row UiaTextRangeBase::_normalizeRow(gsl::not_null<IUiaData*> pData, const Row row) noexcept
@ -1106,6 +1138,7 @@ const unsigned int UiaTextRangeBase::_getViewportWidth(const SMALL_RECT viewport
// - checks if the row is currently visible in the viewport. Uses the
// default viewport.
// Arguments:
// - pData - the UiaData for the terminal we are operating on
// - row - the screen info row to check
// Return Value:
// - true if the row is within the bounds of the viewport
@ -1133,6 +1166,7 @@ const bool UiaTextRangeBase::_isScreenInfoRowInViewport(const ScreenInfoRow row,
// Routine Description:
// - Converts a ScreenInfoRow to a TextBufferRow.
// Arguments:
// - pData - the UiaData for the terminal we are operating on
// - row - the ScreenInfoRow to convert
// Return Value:
// - the equivalent TextBufferRow.
@ -1146,6 +1180,7 @@ const TextBufferRow UiaTextRangeBase::_screenInfoRowToTextBufferRow(gsl::not_nul
// Routine Description:
// - Converts a TextBufferRow to an Endpoint.
// Arguments:
// - pData - the UiaData for the terminal we are operating on
// - row - the TextBufferRow to convert
// Return Value:
// - the equivalent Endpoint, starting at the beginning of the TextBufferRow.
@ -1154,9 +1189,42 @@ const Endpoint UiaTextRangeBase::_textBufferRowToEndpoint(gsl::not_null<IUiaData
return _getRowWidth(pData) * row;
}
// Routine Description:
// - Finds the beginning of the word (Endpoint) for the current target (Endpoint).
// Arguments:
// - pData - the UiaData for the terminal we are operating on
// - target - the Endpoint that is on the word we want to expand to
// - wordDelimiters - characters to determine the separation between words
// Return Value:
// - the equivalent Endpoint, starting at the beginning of the word where _start is located.
const Endpoint UiaTextRangeBase::_wordBeginEndpoint(gsl::not_null<IUiaData*> pData, Endpoint target, const std::wstring_view wordDelimiters)
{
auto coord = _endpointToCoord(pData, target);
coord = pData->GetTextBuffer().GetWordStart(coord, wordDelimiters);
return _coordToEndpoint(pData, coord);
}
// Routine Description:
// - Finds the end of the word (Endpoint) for the current target (Endpoint).
// - The "end of the word" is defined to include the following:
// any word break characters that are present at the end of the "word", but before the start of the next word.
// Arguments:
// - pData - the UiaData for the terminal we are operating on
// - target - the Endpoint that is on the word we want to expand to
// - wordDelimiters - characters to determine the separation between words
// Return Value:
// - the equivalent Endpoint, starting at the end of the word where _start is located.
const Endpoint UiaTextRangeBase::_wordEndEndpoint(gsl::not_null<IUiaData*> pData, Endpoint target, const std::wstring_view wordDelimiters)
{
auto coord = _endpointToCoord(pData, target);
coord = pData->GetTextBuffer().GetWordEnd(coord, wordDelimiters, true);
return _coordToEndpoint(pData, coord);
}
// Routine Description:
// - Converts a ScreenInfoRow to an Endpoint.
// Arguments:
// - pData - the UiaData for the terminal we are operating on
// - row - the ScreenInfoRow to convert
// Return Value:
// - the equivalent Endpoint.
@ -1169,6 +1237,7 @@ const Endpoint UiaTextRangeBase::_screenInfoRowToEndpoint(gsl::not_null<IUiaData
// Routine Description:
// - Converts an Endpoint to an ScreenInfoRow.
// Arguments:
// - pData - the UiaData for the terminal we are operating on
// - endpoint - the endpoint to convert
// Return Value:
// - the equivalent ScreenInfoRow.
@ -1181,6 +1250,7 @@ const ScreenInfoRow UiaTextRangeBase::_endpointToScreenInfoRow(gsl::not_null<IUi
// Routine Description:
// - adds the relevant coordinate points from screenInfoRow to coords.
// Arguments:
// - pData - the UiaData for the terminal we are operating on
// - screenInfoRow - row to calculate coordinate positions from
// - coords - vector to add the calucated coords to
// Return Value:
@ -1252,7 +1322,7 @@ const unsigned int UiaTextRangeBase::_getFirstScreenInfoRowIndex() noexcept
// Routine Description:
// - returns the index of the last row of the screen info
// Arguments:
// - <none>
// - pData - the UiaData for the terminal we are operating on
// Return Value:
// - the index of the last row (0-indexed) of the screen info
const unsigned int UiaTextRangeBase::_getLastScreenInfoRowIndex(gsl::not_null<IUiaData*> pData) noexcept
@ -1274,7 +1344,7 @@ const Column UiaTextRangeBase::_getFirstColumnIndex() noexcept
// Routine Description:
// - returns the index of the last column of the screen info rows
// Arguments:
// - <none>
// - pData - the UiaData for the terminal we are operating on
// Return Value:
// - the index of the last column (0-indexed) of the screen info rows
const Column UiaTextRangeBase::_getLastColumnIndex(gsl::not_null<IUiaData*> pData)
@ -1285,6 +1355,7 @@ const Column UiaTextRangeBase::_getLastColumnIndex(gsl::not_null<IUiaData*> pDat
// Routine Description:
// - Compares two sets of screen info coordinates
// Arguments:
// - pData - the UiaData for the terminal we are operating on
// - rowA - the row index of the first position
// - colA - the column index of the first position
// - rowB - the row index of the second position
@ -1336,6 +1407,7 @@ const int UiaTextRangeBase::_compareScreenCoords(gsl::not_null<IUiaData*> pData,
// - calculates new Endpoints if they were to be moved moveCount times
// by character.
// Arguments:
// - pData - the UiaData for the terminal we are operating on
// - moveCount - the number of times to move
// - moveState - values indicating the state of the console for the
// move operation
@ -1363,23 +1435,23 @@ std::pair<Endpoint, Endpoint> UiaTextRangeBase::_moveByCharacterForward(gsl::not
_Out_ gsl::not_null<int*> const pAmountMoved)
{
*pAmountMoved = 0;
const int count = moveCount;
ScreenInfoRow currentScreenInfoRow = moveState.StartScreenInfoRow;
Column currentColumn = moveState.StartColumn;
for (int i = 0; i < abs(count); ++i)
for (int i = 0; i < abs(moveCount); ++i)
{
// get the current row's right
const ROW& row = pData->GetTextBuffer().GetRowByOffset(currentScreenInfoRow);
const size_t right = row.GetCharRow().MeasureRight();
const auto expectedColumn = gsl::narrow_cast<size_t>(currentColumn) + 1;
// check if we're at the edge of the screen info buffer
if (currentScreenInfoRow == moveState.LimitingRow &&
gsl::narrow_cast<size_t>(currentColumn) + 1 >= right)
expectedColumn >= right)
{
break;
}
else if (gsl::narrow_cast<size_t>(currentColumn) + 1 >= right)
else if (expectedColumn >= right)
{
// we're at the edge of a row and need to go to the next one
currentColumn = moveState.FirstColumnInRow;
@ -1409,11 +1481,10 @@ std::pair<Endpoint, Endpoint> UiaTextRangeBase::_moveByCharacterBackward(gsl::no
_Out_ gsl::not_null<int*> const pAmountMoved)
{
*pAmountMoved = 0;
const int count = moveCount;
ScreenInfoRow currentScreenInfoRow = moveState.StartScreenInfoRow;
Column currentColumn = moveState.StartColumn;
for (int i = 0; i < abs(count); ++i)
for (int i = 0; i < abs(moveCount); ++i)
{
// check if we're at the edge of the screen info buffer
if (currentScreenInfoRow == moveState.LimitingRow &&
@ -1450,10 +1521,208 @@ std::pair<Endpoint, Endpoint> UiaTextRangeBase::_moveByCharacterBackward(gsl::no
return std::make_pair<Endpoint, Endpoint>(std::move(start), std::move(end));
}
// Routine Description:
// - calculates new Endpoints if they were to be moved moveCount times
// by word.
// Arguments:
// - pData - the UiaData for the terminal we are operating on
// - moveCount - the number of times to move
// - moveState - values indicating the state of the console for the
// move operation
// - wordDelimiters - the list of characters that define a separation of different words
// - pAmountMoved - the number of times that the return values are "moved"
// Return Value:
// - a pair of endpoints of the form <start, end>
std::pair<Endpoint, Endpoint> UiaTextRangeBase::_moveByWord(gsl::not_null<IUiaData*> pData,
const int moveCount,
const MoveState moveState,
const std::wstring_view wordDelimiters,
_Out_ gsl::not_null<int*> const pAmountMoved)
{
if (moveState.Direction == MovementDirection::Forward)
{
return _moveByWordForward(pData, moveCount, moveState, wordDelimiters, pAmountMoved);
}
else
{
return _moveByWordBackward(pData, moveCount, moveState, wordDelimiters, pAmountMoved);
}
}
// Routine Description:
// - calculates new Endpoints if they were to be moved moveCount times in the forward direction
// by word.
// Arguments:
// - pData - the UiaData for the terminal we are operating on
// - moveCount - the number of times to move
// - moveState - values indicating the state of the console for the
// move operation
// - wordDelimiters - the list of characters that define a separation of different words
// - pAmountMoved - the number of times that the return values are "moved"
// Return Value:
// - a pair of endpoints of the form <start, end>
std::pair<Endpoint, Endpoint> UiaTextRangeBase::_moveByWordForward(gsl::not_null<IUiaData*> pData,
const int moveCount,
const MoveState moveState,
const std::wstring_view wordDelimiters,
_Out_ gsl::not_null<int*> const pAmountMoved)
{
// STRATEGY:
// - move the "end" Endpoint to the proper place (if need to move multiple times, do it here)
// - find the "start" Endpoint based on that
*pAmountMoved = 0;
ScreenInfoRow currentScreenInfoRow = moveState.EndScreenInfoRow;
Column currentColumn = moveState.EndColumn;
auto& buffer = pData->GetTextBuffer();
for (int i = 0; i < abs(moveCount); ++i)
{
// get the current row's right
const ROW& row = buffer.GetRowByOffset(currentScreenInfoRow);
const size_t right = row.GetCharRow().MeasureRight();
const auto expectedColumn = gsl::narrow_cast<size_t>(currentColumn) + 1;
// check if we're at the edge of the screen info buffer
if (currentScreenInfoRow == moveState.LimitingRow &&
expectedColumn >= right)
{
break;
}
else if (expectedColumn >= right)
{
// we're at the edge of a row and need to go to the next one
currentColumn = moveState.FirstColumnInRow;
currentScreenInfoRow += static_cast<int>(moveState.Increment);
const auto point = _screenInfoRowToEndpoint(pData, currentScreenInfoRow) + currentColumn;
auto target = _endpointToCoord(pData, point);
target = buffer.GetWordEnd(target, wordDelimiters, true);
currentColumn = target.X;
}
else
{
// moving somewhere away from the edges of a row
const Endpoint point = _screenInfoRowToEndpoint(pData, currentScreenInfoRow) + currentColumn;
auto target = _endpointToCoord(pData, point);
buffer.GetSize().IncrementInBounds(target);
target = (moveState.Increment == MovementIncrement::Forward) ?
buffer.GetWordEnd(target, wordDelimiters, true) :
buffer.GetWordStart(target, wordDelimiters, true);
currentColumn = target.X;
}
*pAmountMoved += static_cast<int>(moveState.Increment);
FAIL_FAST_IF(!(currentColumn >= _getFirstColumnIndex()));
FAIL_FAST_IF(!(currentColumn <= _getLastColumnIndex(pData)));
FAIL_FAST_IF(!(currentScreenInfoRow >= _getFirstScreenInfoRowIndex()));
FAIL_FAST_IF(!(currentScreenInfoRow <= _getLastScreenInfoRowIndex(pData)));
}
Endpoint end = _screenInfoRowToEndpoint(pData, currentScreenInfoRow) + currentColumn;
auto target = _endpointToCoord(pData, end);
target = buffer.GetWordStart(target, wordDelimiters, true);
Endpoint start = _coordToEndpoint(pData, target);
return std::make_pair<Endpoint, Endpoint>(std::move(start), std::move(end));
}
// Routine Description:
// - calculates new Endpoints if they were to be moved moveCount times in the backwards direction
// by word.
// Arguments:
// - pData - the UiaData for the terminal we are operating on
// - moveCount - the number of times to move
// - moveState - values indicating the state of the console for the
// move operation
// - wordDelimiters - the list of characters that define a separation of different words
// - pAmountMoved - the number of times that the return values are "moved"
// Return Value:
// - a pair of endpoints of the form <start, end>
std::pair<Endpoint, Endpoint> UiaTextRangeBase::_moveByWordBackward(gsl::not_null<IUiaData*> pData,
const int moveCount,
const MoveState moveState,
const std::wstring_view wordDelimiters,
_Out_ gsl::not_null<int*> const pAmountMoved)
{
// STRATEGY:
// - move the "start" Endpoint to the proper place (if need to move multiple times, do it here)
// - find the "end" Endpoint based on that
*pAmountMoved = 0;
ScreenInfoRow currentScreenInfoRow = moveState.StartScreenInfoRow;
Column currentColumn = moveState.StartColumn;
auto& buffer = pData->GetTextBuffer();
for (int i = 0; i < abs(moveCount); ++i)
{
// check if we're at the edge of the screen info buffer
if (currentScreenInfoRow == moveState.LimitingRow &&
currentColumn == moveState.LastColumnInRow)
{
break;
}
else if (currentColumn == moveState.LastColumnInRow)
{
// we're at the edge of a row and need to go to the
// previous one. move to the cell with the last non-whitespace charactor
currentScreenInfoRow += static_cast<int>(moveState.Increment);
// get the right-most char for the previous row
const ROW& row = buffer.GetRowByOffset(currentScreenInfoRow);
const size_t right = row.GetCharRow().MeasureRight();
currentColumn = gsl::narrow<Column>((right == 0) ? 0 : right - 1);
// get the right-most word for the previous row
const auto point = _screenInfoRowToEndpoint(pData, currentScreenInfoRow) + currentColumn;
auto target = _endpointToCoord(pData, point);
target = buffer.GetWordStart(target, wordDelimiters, true);
buffer.GetSize().IncrementInBounds(target);
currentColumn = target.X;
}
else
{
// moving somewhere away from the edges of a row
const Endpoint point = _screenInfoRowToEndpoint(pData, currentScreenInfoRow) + currentColumn;
auto target = _endpointToCoord(pData, point);
buffer.GetSize().DecrementInBounds(target);
target = (moveState.Increment == MovementIncrement::Forward) ?
buffer.GetWordEnd(target, wordDelimiters, true) :
buffer.GetWordStart(target, wordDelimiters, true);
currentColumn = target.X;
}
*pAmountMoved += static_cast<int>(moveState.Increment);
FAIL_FAST_IF(!(currentColumn >= _getFirstColumnIndex()));
FAIL_FAST_IF(!(currentColumn <= _getLastColumnIndex(pData)));
FAIL_FAST_IF(!(currentScreenInfoRow >= _getFirstScreenInfoRowIndex()));
FAIL_FAST_IF(!(currentScreenInfoRow <= _getLastScreenInfoRowIndex(pData)));
}
Endpoint start = _screenInfoRowToEndpoint(pData, currentScreenInfoRow) + currentColumn;
auto target = _endpointToCoord(pData, start);
target = buffer.GetWordEnd(target, wordDelimiters, true);
Endpoint end = _coordToEndpoint(pData, target);
return std::make_pair<Endpoint, Endpoint>(std::move(start), std::move(end));
}
// Routine Description:
// - calculates new Endpoints if they were to be moved moveCount times
// by line.
// Arguments:
// - pData - the UiaData for the terminal we are operating on
// - moveCount - the number of times to move
// - moveState - values indicating the state of the console for the
// move operation
@ -1501,6 +1770,7 @@ std::pair<Endpoint, Endpoint> UiaTextRangeBase::_moveByLine(gsl::not_null<IUiaDa
// - calculates new Endpoints if they were to be moved moveCount times
// by document.
// Arguments:
// - pData - the UiaData for the terminal we are operating on
// - moveCount - the number of times to move
// - moveState - values indicating the state of the console for the
// move operation
@ -1527,6 +1797,7 @@ std::pair<Endpoint, Endpoint> UiaTextRangeBase::_moveByDocument(gsl::not_null<IU
// - calculates new Endpoints/degenerate state if the indicated
// endpoint was moved moveCount times by character.
// Arguments:
// - pData - the UiaData for the terminal we are operating on
// - moveCount - the number of times to move
// - endpoint - the endpoint to move
// - moveState - values indicating the state of the console for the
@ -1558,7 +1829,6 @@ UiaTextRangeBase::_moveEndpointByUnitCharacterForward(gsl::not_null<IUiaData*> p
_Out_ gsl::not_null<int*> const pAmountMoved)
{
*pAmountMoved = 0;
const int count = moveCount;
ScreenInfoRow currentScreenInfoRow = 0;
Column currentColumn = 0;
@ -1574,19 +1844,20 @@ UiaTextRangeBase::_moveEndpointByUnitCharacterForward(gsl::not_null<IUiaData*> p
currentColumn = moveState.EndColumn;
}
for (int i = 0; i < abs(count); ++i)
for (int i = 0; i < abs(moveCount); ++i)
{
// get the current row's right
const ROW& row = pData->GetTextBuffer().GetRowByOffset(currentScreenInfoRow);
const size_t right = row.GetCharRow().MeasureRight();
const auto expectedColumn = gsl::narrow_cast<size_t>(currentColumn) + 1;
// check if we're at the edge of the screen info buffer
if (currentScreenInfoRow == moveState.LimitingRow &&
gsl::narrow_cast<size_t>(currentColumn) + 1 >= right)
expectedColumn >= right)
{
break;
}
else if (gsl::narrow_cast<size_t>(currentColumn) + 1 >= right)
else if (expectedColumn >= right)
{
// we're at the edge of a row and need to go to the next one
currentColumn = moveState.FirstColumnInRow;
@ -1647,7 +1918,6 @@ UiaTextRangeBase::_moveEndpointByUnitCharacterBackward(gsl::not_null<IUiaData*>
_Out_ gsl::not_null<int*> const pAmountMoved)
{
*pAmountMoved = 0;
const int count = moveCount;
ScreenInfoRow currentScreenInfoRow = 0;
Column currentColumn = 0;
@ -1663,7 +1933,7 @@ UiaTextRangeBase::_moveEndpointByUnitCharacterBackward(gsl::not_null<IUiaData*>
currentColumn = moveState.EndColumn;
}
for (int i = 0; i < abs(count); ++i)
for (int i = 0; i < abs(moveCount); ++i)
{
// check if we're at the edge of the screen info buffer
if (currentScreenInfoRow == moveState.LimitingRow &&
@ -1729,10 +1999,282 @@ UiaTextRangeBase::_moveEndpointByUnitCharacterBackward(gsl::not_null<IUiaData*>
return std::make_tuple(start, end, degenerate);
}
// Routine Description:
// - calculates new Endpoints/degenerate state if the indicated
// endpoint was moved moveCount times by word.
// Arguments:
// - pData - the UiaData for the terminal we are operating on
// - moveCount - the number of times to move
// - endpoint - the endpoint to move
// - moveState - values indicating the state of the console for the
// move operation
// - pAmountMoved - the number of times that the return values are "moved"
// Return Value:
// - A tuple of elements of the form <start, end, degenerate>
std::tuple<Endpoint, Endpoint, bool> UiaTextRangeBase::_moveEndpointByUnitWord(gsl::not_null<IUiaData*> pData,
const int moveCount,
const TextPatternRangeEndpoint endpoint,
const MoveState moveState,
const std::wstring_view wordDelimiters,
_Out_ gsl::not_null<int*> const pAmountMoved)
{
if (moveState.Direction == MovementDirection::Forward)
{
return _moveEndpointByUnitWordForward(pData, moveCount, endpoint, moveState, wordDelimiters, pAmountMoved);
}
else
{
return _moveEndpointByUnitWordBackward(pData, moveCount, endpoint, moveState, wordDelimiters, pAmountMoved);
}
}
std::tuple<Endpoint, Endpoint, bool>
UiaTextRangeBase::_moveEndpointByUnitWordForward(gsl::not_null<IUiaData*> pData,
const int moveCount,
const TextPatternRangeEndpoint endpoint,
const MoveState moveState,
const std::wstring_view wordDelimiters,
_Out_ gsl::not_null<int*> const pAmountMoved)
{
*pAmountMoved = 0;
ScreenInfoRow currentScreenInfoRow = 0;
Column currentColumn = 0;
// set current location vars
if (endpoint == TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start)
{
currentScreenInfoRow = moveState.StartScreenInfoRow;
currentColumn = moveState.StartColumn;
}
else
{
currentScreenInfoRow = moveState.EndScreenInfoRow;
currentColumn = moveState.EndColumn;
}
auto& buffer = pData->GetTextBuffer();
for (int i = 0; i < abs(moveCount); ++i)
{
// get the current row's right
const ROW& row = buffer.GetRowByOffset(currentScreenInfoRow);
const size_t right = row.GetCharRow().MeasureRight();
const auto expectedColumn = gsl::narrow_cast<size_t>(currentColumn) + 1;
// check if we're at the edge of the screen info buffer
if (currentScreenInfoRow == moveState.LimitingRow &&
expectedColumn >= right)
{
break;
}
else if (expectedColumn >= right)
{
// we're at the edge of a row and need to go to the next one
currentColumn = moveState.FirstColumnInRow;
currentScreenInfoRow += static_cast<int>(moveState.Increment);
// when moving end, we need to encompass the word
if (endpoint == TextPatternRangeEndpoint::TextPatternRangeEndpoint_End)
{
const auto point = _screenInfoRowToEndpoint(pData, currentScreenInfoRow) + currentColumn;
auto target = _endpointToCoord(pData, point);
target = buffer.GetWordEnd(target, wordDelimiters, true);
currentColumn = target.X;
}
}
else
{
// moving somewhere away from the edges of a row
const Endpoint point = _screenInfoRowToEndpoint(pData, currentScreenInfoRow) + currentColumn;
auto target = _endpointToCoord(pData, point);
buffer.GetSize().IncrementInBounds(target);
target = (moveState.Increment == MovementIncrement::Forward) ?
buffer.GetWordEnd(target, wordDelimiters, true) :
buffer.GetWordStart(target, wordDelimiters, true);
if (endpoint == TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start)
{
buffer.GetSize().IncrementInBounds(target);
if (static_cast<Column>(target.X) == moveState.FirstColumnInRow && currentScreenInfoRow != moveState.LimitingRow)
{
currentScreenInfoRow += static_cast<int>(moveState.Increment);
}
}
currentColumn = target.X;
}
*pAmountMoved += static_cast<int>(moveState.Increment);
FAIL_FAST_IF(!(currentColumn >= _getFirstColumnIndex()));
FAIL_FAST_IF(!(currentColumn <= _getLastColumnIndex(pData)));
FAIL_FAST_IF(!(currentScreenInfoRow >= _getFirstScreenInfoRowIndex()));
FAIL_FAST_IF(!(currentScreenInfoRow <= _getLastScreenInfoRowIndex(pData)));
}
// translate the row back to an endpoint and handle any crossed endpoints
const Endpoint convertedEndpoint = _screenInfoRowToEndpoint(pData, currentScreenInfoRow) + currentColumn;
Endpoint start = _screenInfoRowToEndpoint(pData, moveState.StartScreenInfoRow) + moveState.StartColumn;
Endpoint end = _screenInfoRowToEndpoint(pData, moveState.EndScreenInfoRow) + moveState.EndColumn;
bool degenerate = false;
if (endpoint == TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start)
{
start = convertedEndpoint;
if (_compareScreenCoords(pData,
currentScreenInfoRow,
currentColumn,
moveState.EndScreenInfoRow,
moveState.EndColumn) == 1)
{
end = start;
degenerate = true;
}
}
else
{
end = convertedEndpoint;
if (_compareScreenCoords(pData,
currentScreenInfoRow,
currentColumn,
moveState.StartScreenInfoRow,
moveState.StartColumn) == -1)
{
start = end;
degenerate = true;
}
}
return std::make_tuple(start, end, degenerate);
}
std::tuple<Endpoint, Endpoint, bool>
UiaTextRangeBase::_moveEndpointByUnitWordBackward(gsl::not_null<IUiaData*> pData,
const int moveCount,
const TextPatternRangeEndpoint endpoint,
const MoveState moveState,
const std::wstring_view wordDelimiters,
_Out_ gsl::not_null<int*> const pAmountMoved)
{
*pAmountMoved = 0;
ScreenInfoRow currentScreenInfoRow = 0;
Column currentColumn = 0;
// set current location vars
if (endpoint == TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start)
{
currentScreenInfoRow = moveState.StartScreenInfoRow;
currentColumn = moveState.StartColumn;
}
else
{
currentScreenInfoRow = moveState.EndScreenInfoRow;
currentColumn = moveState.EndColumn;
}
auto& buffer = pData->GetTextBuffer();
for (int i = 0; i < abs(moveCount); ++i)
{
// check if we're at the edge of the screen info buffer
if (currentScreenInfoRow == moveState.LimitingRow &&
currentColumn == moveState.LastColumnInRow)
{
break;
}
else if (currentColumn == moveState.LastColumnInRow)
{
// we're at the edge of a row and need to go to the
// next one. move to the cell with the last non-whitespace charactor
currentScreenInfoRow += static_cast<int>(moveState.Increment);
// get the right cell for the next row
const ROW& row = buffer.GetRowByOffset(currentScreenInfoRow);
const size_t right = row.GetCharRow().MeasureRight();
currentColumn = gsl::narrow<Column>((right == 0) ? 0 : right - 1);
// get the right-most word for the previous row
const auto point = _screenInfoRowToEndpoint(pData, currentScreenInfoRow) + currentColumn;
auto target = _endpointToCoord(pData, point);
target = buffer.GetWordStart(target, wordDelimiters, true);
if (endpoint == TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start)
{
buffer.GetSize().IncrementInBounds(target);
}
currentColumn = target.X;
}
else
{
// moving somewhere away from the edges of a row
const Endpoint point = _screenInfoRowToEndpoint(pData, currentScreenInfoRow) + currentColumn;
auto target = _endpointToCoord(pData, point);
buffer.GetSize().DecrementInBounds(target);
target = (moveState.Increment == MovementIncrement::Forward) ?
buffer.GetWordEnd(target, wordDelimiters, true) :
buffer.GetWordStart(target, wordDelimiters, true);
if (endpoint == TextPatternRangeEndpoint::TextPatternRangeEndpoint_End)
{
buffer.GetSize().DecrementInBounds(target);
if (static_cast<Column>(target.X) == moveState.FirstColumnInRow && currentScreenInfoRow != moveState.LimitingRow)
{
currentScreenInfoRow += static_cast<int>(moveState.Increment);
}
}
currentColumn = target.X;
}
*pAmountMoved += static_cast<int>(moveState.Increment);
FAIL_FAST_IF(!(currentColumn >= _getFirstColumnIndex()));
FAIL_FAST_IF(!(currentColumn <= _getLastColumnIndex(pData)));
FAIL_FAST_IF(!(currentScreenInfoRow >= _getFirstScreenInfoRowIndex()));
FAIL_FAST_IF(!(currentScreenInfoRow <= _getLastScreenInfoRowIndex(pData)));
}
// translate the row back to an endpoint and handle any crossed endpoints
const Endpoint convertedEndpoint = _screenInfoRowToEndpoint(pData, currentScreenInfoRow) + currentColumn;
Endpoint start = _screenInfoRowToEndpoint(pData, moveState.StartScreenInfoRow) + moveState.StartColumn;
Endpoint end = _screenInfoRowToEndpoint(pData, moveState.EndScreenInfoRow) + moveState.EndColumn;
bool degenerate = false;
if (endpoint == TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start)
{
start = convertedEndpoint;
if (_compareScreenCoords(pData,
currentScreenInfoRow,
currentColumn,
moveState.EndScreenInfoRow,
moveState.EndColumn) == 1)
{
end = start;
degenerate = true;
}
}
else
{
end = convertedEndpoint;
if (_compareScreenCoords(pData,
currentScreenInfoRow,
currentColumn,
moveState.StartScreenInfoRow,
moveState.StartColumn) == -1)
{
start = end;
degenerate = true;
}
}
return std::make_tuple(start, end, degenerate);
}
// Routine Description:
// - calculates new Endpoints/degenerate state if the indicated
// endpoint was moved moveCount times by line.
// Arguments:
// - pData - the UiaData for the terminal we are operating on
// - moveCount - the number of times to move
// - endpoint - the endpoint to move
// - moveState - values indicating the state of the console for the
@ -1892,6 +2434,7 @@ std::tuple<Endpoint, Endpoint, bool> UiaTextRangeBase::_moveEndpointByUnitLine(g
// - calculates new Endpoints/degenerate state if the indicate
// endpoint was moved moveCount times by document.
// Arguments:
// - pData - the UiaData for the terminal we are operating on
// - moveCount - the number of times to move
// - endpoint - the endpoint to move
// - moveState - values indicating the state of the console for the

View file

@ -23,6 +23,7 @@ Author(s):
#include "inc/viewport.hpp"
#include "../buffer/out/textBuffer.hpp"
#include "IUiaData.h"
#include "unicode.hpp"
#include <deque>
#include <tuple>
@ -138,21 +139,27 @@ namespace Microsoft::Console::Types
};
public:
// The default word delimiter for UiaTextRanges
static constexpr std::wstring_view DefaultWordDelimiter{ &UNICODE_SPACE, 1 };
// degenerate range
HRESULT RuntimeClassInitialize(_In_ IUiaData* pData,
_In_ IRawElementProviderSimple* const pProvider) noexcept;
_In_ IRawElementProviderSimple* const pProvider,
_In_ std::wstring_view wordDelimiters = DefaultWordDelimiter) noexcept;
// degenerate range at cursor position
HRESULT RuntimeClassInitialize(_In_ IUiaData* pData,
_In_ IRawElementProviderSimple* const pProvider,
const Cursor& cursor) noexcept;
const Cursor& cursor,
_In_ std::wstring_view wordDelimiters = DefaultWordDelimiter) noexcept;
// specific endpoint range
HRESULT RuntimeClassInitialize(_In_ IUiaData* pData,
_In_ IRawElementProviderSimple* const pProvider,
const Endpoint start,
const Endpoint end,
const bool degenerate) noexcept;
const bool degenerate,
_In_ std::wstring_view wordDelimiters = DefaultWordDelimiter) noexcept;
HRESULT RuntimeClassInitialize(const UiaTextRangeBase& a) noexcept;
@ -218,6 +225,8 @@ namespace Microsoft::Console::Types
IRawElementProviderSimple* _pProvider;
std::wstring _wordDelimiters;
virtual void _ChangeViewport(const SMALL_RECT NewWindow) = 0;
virtual void _TranslatePointToScreen(LPPOINT clientPoint) const = 0;
virtual void _TranslatePointFromScreen(LPPOINT screenPoint) const = 0;
@ -276,6 +285,9 @@ namespace Microsoft::Console::Types
const ScreenInfoRow row) noexcept;
static const Endpoint _textBufferRowToEndpoint(gsl::not_null<IUiaData*> pData, const TextBufferRow row);
static const Endpoint _wordBeginEndpoint(gsl::not_null<IUiaData*> pData, Endpoint target, const std::wstring_view wordDelimiters);
static const Endpoint _wordEndEndpoint(gsl::not_null<IUiaData*> pData, Endpoint target, const std::wstring_view wordDelimiters);
static const ScreenInfoRow _endpointToScreenInfoRow(gsl::not_null<IUiaData*> pData,
const Endpoint endpoint);
static const Endpoint _screenInfoRowToEndpoint(gsl::not_null<IUiaData*> pData,
@ -339,6 +351,24 @@ namespace Microsoft::Console::Types
const MoveState moveState,
_Out_ gsl::not_null<int*> const pAmountMoved);
static std::pair<Endpoint, Endpoint> _moveByWord(gsl::not_null<IUiaData*> pData,
const int moveCount,
const MoveState moveState,
const std::wstring_view wordDelimiters,
_Out_ gsl::not_null<int*> const pAmountMoved);
static std::pair<Endpoint, Endpoint> _moveByWordForward(gsl::not_null<IUiaData*> pData,
const int moveCount,
const MoveState moveState,
const std::wstring_view wordDelimiters,
_Out_ gsl::not_null<int*> const pAmountMoved);
static std::pair<Endpoint, Endpoint> _moveByWordBackward(gsl::not_null<IUiaData*> pData,
const int moveCount,
const MoveState moveState,
const std::wstring_view wordDelimiters,
_Out_ gsl::not_null<int*> const pAmountMoved);
static std::pair<Endpoint, Endpoint> _moveByLine(gsl::not_null<IUiaData*> pData,
const int moveCount,
const MoveState moveState,
@ -370,6 +400,30 @@ namespace Microsoft::Console::Types
const MoveState moveState,
_Out_ gsl::not_null<int*> const pAmountMoved);
static std::tuple<Endpoint, Endpoint, bool>
_moveEndpointByUnitWord(gsl::not_null<IUiaData*> pData,
const int moveCount,
const TextPatternRangeEndpoint endpoint,
const MoveState moveState,
const std::wstring_view wordDelimiters,
_Out_ gsl::not_null<int*> const pAmountMoved);
static std::tuple<Endpoint, Endpoint, bool>
_moveEndpointByUnitWordForward(gsl::not_null<IUiaData*> pData,
const int moveCount,
const TextPatternRangeEndpoint endpoint,
const MoveState moveState,
const std::wstring_view wordDelimiters,
_Out_ gsl::not_null<int*> const pAmountMoved);
static std::tuple<Endpoint, Endpoint, bool>
_moveEndpointByUnitWordBackward(gsl::not_null<IUiaData*> pData,
const int moveCount,
const TextPatternRangeEndpoint endpoint,
const MoveState moveState,
const std::wstring_view wordDelimiters,
_Out_ gsl::not_null<int*> const pAmountMoved);
static std::tuple<Endpoint, Endpoint, bool>
_moveEndpointByUnitLine(gsl::not_null<IUiaData*> pData,
const int moveCount,