terminal/src/buffer/out/textBufferCellIterator.cpp
Carlos Zamora a0e5085b49
Expose Text Attributes to UI Automation (#10336)
## Summary of the Pull Request
This implements `GetAttributeValue` and `FindAttribute` for `UiaTextRangeBase` (the shared `ITextRangeProvider` for Conhost and Windows Terminal). This also updates `UiaTracing` to collect more useful information on these function calls. 

## References
#7000 - Epic
[Text Attribute Identifiers](https://docs.microsoft.com/en-us/windows/win32/winauto/uiauto-textattribute-ids)
[ITextRangeProvider::GetAttributeValue](https://docs.microsoft.com/en-us/windows/win32/api/uiautomationcore/nf-uiautomationcore-itextrangeprovider-getattributevalue)
[ITextRangeProvider::FindAttribute](https://docs.microsoft.com/en-us/windows/win32/api/uiautomationcore/nf-uiautomationcore-itextrangeprovider-findattribute)

## PR Checklist
* [X] Closes #2161 
* [X] Tests added/passed

## Detailed Description of the Pull Request / Additional comments
- `TextBuffer`:
   - Exposes a new `TextBufferCellIterator` that takes in an end position. This simplifies the logic drastically as we can now use this iterator to navigate through the text buffer. The iterator can also expose the position in the buffer.
- `UiaTextRangeBase`:
   - Shared logic & helper functions:
      - Most of the text attributes are stored as `TextAttribute`s in the text buffer. To extract them, we generate an attribute verification function via `_getAttrVerificationFn()`, then use that to verify if a given cell has the desired attribute.
      - A few attributes are special (i.e. font name, font size, and "is read only"), in that they are (1) acquired differently and (2) consistent across the entire text buffer. These are handled separate from the attribute verification function.
   - `GetAttributeValue`: Retrieve the attribute verification of the first cell in the range. Then, verify that the entire range has that attribute by iterating through the text range. If a cell does not have that attribute, return the "reserved mixed attribute value".
   - `FindAttribute`: Iterate through the text range and leverage the attribute verification function to find the first contiguous range with that attribute. Then, make the end exclusive and output a `UiaTextRangeBase`. This function must be able to perform a search backwards, so we abstract the "start" and "end" into `resultFirstAnchor` and `resultSecondAnchor`, then perform post processing to output a valid `UiaTextRangeBase`.
- `UiaTracing`:
   - `GetAttributeValue`: Log uia text range, desired attribute, resulting attribute metadata, and the type of the result.
   - `FindAttribute`: Log uia text range, desired attribute and attribute metadata, if we were searching backwards, the type of the result, and the resulting text range.
   - `AttributeType` is a nice way to understand/record if the result was either of the reserved UIA values, a normal result, or an error.
- `UiaTextRangeTests`:
   - `GetAttributeValue`:
      - verify that we know which attributes we support
      - test each of the known text attributes (expecting 100% code coverage for `_getAttrVerificationFn()`)
   - `FindAttribute`: 
      - test each of the known _special_ text attributes
      - test `IsItalic`. NOTE: I'm explicitly only testing one of the standard text attributes because the logic is largely the same between all of them and they leverage `_getAttrVerificationFn()`.

## Validation Steps Performed
- @codeofdusk has been testing this Conhost build
- Tests added for Conhost and shared implementation
- Windows Terminal changes were manually verified using accessibility insights and NVDA
2021-07-09 23:21:35 +00:00

273 lines
9.1 KiB
C++

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "textBufferCellIterator.hpp"
#include "CharRow.hpp"
#include "textBuffer.hpp"
#include "../types/inc/convert.hpp"
#include "../types/inc/viewport.hpp"
#pragma hdrstop
using namespace Microsoft::Console::Types;
// Routine Description:
// - Creates a new read-only iterator to seek through cell data stored within a screen buffer
// Arguments:
// - buffer - Text buffer to seek through
// - pos - Starting position to retrieve text data from (within screen buffer bounds)
TextBufferCellIterator::TextBufferCellIterator(const TextBuffer& buffer, COORD pos) :
TextBufferCellIterator(buffer, pos, buffer.GetSize())
{
}
// Routine Description:
// - Creates a new read-only iterator to seek through cell data stored within a screen buffer
// Arguments:
// - buffer - Pointer to screen buffer to seek through
// - pos - Starting position to retrieve text data from (within screen buffer bounds)
// - limits - Viewport limits to restrict the iterator within the buffer bounds (smaller than the buffer itself)
TextBufferCellIterator::TextBufferCellIterator(const TextBuffer& buffer, COORD pos, const Viewport limits) :
_buffer(buffer),
_pos(pos),
_pRow(s_GetRow(buffer, pos)),
_bounds(limits),
_exceeded(false),
_view({}, {}, {}, TextAttributeBehavior::Stored),
_attrIter(s_GetRow(buffer, pos)->GetAttrRow().cbegin())
{
// Throw if the bounds rectangle is not limited to the inside of the given buffer.
THROW_HR_IF(E_INVALIDARG, !buffer.GetSize().IsInBounds(limits));
// Throw if the coordinate is not limited to the inside of the given buffer.
THROW_HR_IF(E_INVALIDARG, !limits.IsInBounds(pos));
_attrIter += pos.X;
_GenerateView();
}
// Routine Description:
// - Tells if the iterator is still valid (hasn't exceeded boundaries of underlying text buffer)
// Return Value:
// - True if this iterator can still be dereferenced for data. False if we've passed the end and are out of data.
TextBufferCellIterator::operator bool() const noexcept
{
return !_exceeded && _bounds.IsInBounds(_pos);
}
// Routine Description:
// - Compares two iterators to see if they're pointing to the same position in the same buffer
// Arguments:
// - it - The other iterator to compare to this one.
// Return Value:
// - True if it's the same text buffer and same cell position. False otherwise.
bool TextBufferCellIterator::operator==(const TextBufferCellIterator& it) const noexcept
{
return _pos == it._pos &&
&_buffer == &it._buffer &&
_exceeded == it._exceeded &&
_bounds == it._bounds &&
_pRow == it._pRow &&
_attrIter == it._attrIter;
}
// Routine Description:
// - Compares two iterators to see if they're pointing to the different positions in the same buffer or different buffers entirely.
// Arguments:
// - it - The other iterator to compare to this one.
// Return Value:
// - True if it's the same text buffer and different cell position or if they're different buffers. False otherwise.
bool TextBufferCellIterator::operator!=(const TextBufferCellIterator& it) const noexcept
{
return !(*this == it);
}
// Routine Description:
// - Advances the iterator forward relative to the underlying text buffer by the specified movement
// Arguments:
// - movement - Magnitude and direction of movement.
// Return Value:
// - Reference to self after movement.
TextBufferCellIterator& TextBufferCellIterator::operator+=(const ptrdiff_t& movement)
{
ptrdiff_t move = movement;
auto newPos = _pos;
while (move > 0 && !_exceeded)
{
_exceeded = !_bounds.IncrementInBounds(newPos);
move--;
}
while (move < 0 && !_exceeded)
{
_exceeded = !_bounds.DecrementInBounds(newPos);
move++;
}
_SetPos(newPos);
return (*this);
}
// Routine Description:
// - Advances the iterator backward relative to the underlying text buffer by the specified movement
// Arguments:
// - movement - Magnitude and direction of movement.
// Return Value:
// - Reference to self after movement.
TextBufferCellIterator& TextBufferCellIterator::operator-=(const ptrdiff_t& movement)
{
return this->operator+=(-movement);
}
// Routine Description:
// - Advances the iterator forward relative to the underlying text buffer by exactly 1
// Return Value:
// - Reference to self after movement.
TextBufferCellIterator& TextBufferCellIterator::operator++()
{
return this->operator+=(1);
}
// Routine Description:
// - Advances the iterator backward relative to the underlying text buffer by exactly 1
// Return Value:
// - Reference to self after movement.
TextBufferCellIterator& TextBufferCellIterator::operator--()
{
return this->operator-=(1);
}
// Routine Description:
// - Advances the iterator forward relative to the underlying text buffer by exactly 1
// Return Value:
// - Value with previous position prior to movement.
TextBufferCellIterator TextBufferCellIterator::operator++(int)
{
auto temp(*this);
operator++();
return temp;
}
// Routine Description:
// - Advances the iterator backward relative to the underlying text buffer by exactly 1
// Return Value:
// - Value with previous position prior to movement.
TextBufferCellIterator TextBufferCellIterator::operator--(int)
{
auto temp(*this);
operator--();
return temp;
}
// Routine Description:
// - Advances the iterator forward relative to the underlying text buffer by the specified movement
// Arguments:
// - movement - Magnitude and direction of movement.
// Return Value:
// - Value with previous position prior to movement.
TextBufferCellIterator TextBufferCellIterator::operator+(const ptrdiff_t& movement)
{
auto temp(*this);
temp += movement;
return temp;
}
// Routine Description:
// - Advances the iterator negative relative to the underlying text buffer by the specified movement
// Arguments:
// - movement - Magnitude and direction of movement.
// Return Value:
// - Value with previous position prior to movement.
TextBufferCellIterator TextBufferCellIterator::operator-(const ptrdiff_t& movement)
{
auto temp(*this);
temp -= movement;
return temp;
}
// Routine Description:
// - Provides the difference in position between two iterators.
// Arguments:
// - it - The other iterator to compare to this one.
ptrdiff_t TextBufferCellIterator::operator-(const TextBufferCellIterator& it)
{
THROW_HR_IF(E_NOT_VALID_STATE, &_buffer != &it._buffer); // It's not valid to compare this for iterators pointing at different buffers.
return _bounds.CompareInBounds(_pos, it._pos);
}
// Routine Description:
// - Sets the coordinate position that this iterator will inspect within the text buffer on dereference.
// Arguments:
// - newPos - The new coordinate position.
void TextBufferCellIterator::_SetPos(const COORD newPos)
{
if (newPos.Y != _pos.Y)
{
_pRow = s_GetRow(_buffer, newPos);
_attrIter = _pRow->GetAttrRow().cbegin();
_pos.X = 0;
}
if (newPos.X != _pos.X)
{
const auto diff = gsl::narrow_cast<ptrdiff_t>(newPos.X) - gsl::narrow_cast<ptrdiff_t>(_pos.X);
_attrIter += diff;
}
_pos = newPos;
_GenerateView();
}
// Routine Description:
// - Shortcut for pulling the row out of the text buffer embedded in the screen information.
// We'll hold and cache this to improve performance over looking it up every time.
// Arguments:
// - buffer - Screen information pointer to pull text buffer data from
// - pos - Position inside screen buffer bounds to retrieve row
// Return Value:
// - Pointer to the underlying CharRow structure
const ROW* TextBufferCellIterator::s_GetRow(const TextBuffer& buffer, const COORD pos)
{
return &buffer.GetRowByOffset(pos.Y);
}
// Routine Description:
// - Updates the internal view. Call after updating row, attribute, or positions.
void TextBufferCellIterator::_GenerateView()
{
_view = OutputCellView(_pRow->GetCharRow().GlyphAt(_pos.X),
_pRow->GetCharRow().DbcsAttrAt(_pos.X),
*_attrIter,
TextAttributeBehavior::Stored);
}
// Routine Description:
// - Provides full fidelity view of the cell data in the underlying buffer.
// Arguments:
// - <none> - Uses current position
// Return Value:
// - OutputCellView representation that provides a read-only view into the underlying text buffer data.
const OutputCellView& TextBufferCellIterator::operator*() const noexcept
{
return _view;
}
// Routine Description:
// - Provides full fidelity view of the cell data in the underlying buffer.
// Arguments:
// - <none> - Uses current position
// Return Value:
// - OutputCellView representation that provides a read-only view into the underlying text buffer data.
const OutputCellView* TextBufferCellIterator::operator->() const noexcept
{
return &_view;
}
COORD TextBufferCellIterator::Pos() const noexcept
{
return _pos;
}