mirror of
https://github.com/microsoft/terminal.git
synced 2025-12-10 00:48:23 -06:00
Make selection an exclusive range (#18106)
Selection is generally stored as an inclusive start and end. This PR makes the end exclusive which now allows degenerate selections, namely in mark mode. This also modifies mouse selection to round to the nearest cell boundary (see #5099) and improves word boundaries to be a bit more modern and make sense for degenerate selections (similar to #15787). Closes #5099 Closes #13447 Closes #17892 ## Detailed Description of the Pull Request / Additional comments - Buffer, Viewport, and Point - Introduced a few new functions here to find word boundaries, delimiter class runs, and glyph boundaries. - 📝These new functions should be able to replace a few other functions (i.e. `GetWordStart` --> `GetWordStart2`). That migration is going to be a part of #4423 to reduce the risk of breaking UIA. - Viewport: added a few functions to handle navigating the _exclusive_ bounds (namely allowing RightExclusive as a position for buffer coordinates). This is important for selection to be able to highlight the entire line. - 📝`BottomInclusiveRightExclusive()` will replace `EndExclusive` in the UIA code - Point: `iterate_rows_exclusive` is similar to `iterate_rows`, except it has handling for RightExclusive - Renderer - Use `iterate_rows_exclusive` for proper handling (this actually fixed a lot of our issues) - Remove some workarounds in `_drawHighlighted` (this is a boundary where we got inclusive coords and made them exclusive, but now we don't need that!) - Terminal - fix selection marker rendering - `_ConvertToBufferCell()`: add a param to allow for RightExclusive or clamp it to RightInclusive (original behavior). Both are useful! - Use new `GetWordStart2` and `GetWordEnd2` to improve word boundaries and make them feel right now that the selection an exclusive range. - Convert a few `IsInBounds` --> `IsInExclusiveBounds` for safety and correctness - Add `TriggerSelection` to `SelectNewRegion` - 📝 We normally called `TriggerSelection` in a different layer, but it turns out, UIA's `Select` function wouldn't actually update the renderer. Whoops! This fixes that. - TermControl - `_getTerminalPosition` now has a new param to round to the nearest cell (see #5099) - UIA - `TermControlUIAProvider::GetSelectionRange` no need to convert from inclusive range to exclusive range anymore! - `TextBuffer::GetPlainText` now works on an exclusive range, so no need to convert the range anymore! ## Validation Steps Performed This fundamental change impacts a lot of scenarios: - ✅Rendering selections - ✅Selection markers - ✅Copy text - ✅Session restore - ✅Mark mode navigation (i.e. character, word, line, buffer) - ✅Mouse selection (i.e. click+drag, shift+click, multi-click, alt+click) - ✅Hyperlinks (interaction and rendering) - ✅Accessibility (i.e. get selection, movement, text extraction, selecting text) - [ ] Prev/Next Command/Output (untested) - ✅Unit tests ## Follow-ups - Refs #4423 - Now that selection and UIA are both exclusive ranges, it should be a lot easier to deduplicate code between selection and UIA. We should be able to remove `EndExclusive` as well when we do that. This'll also be an opportunity to modernize that code and use more `til` classes.
This commit is contained in:
parent
8e4da6e938
commit
64d4fbab17
2
.github/actions/spelling/expect/expect.txt
vendored
2
.github/actions/spelling/expect/expect.txt
vendored
@ -163,6 +163,7 @@ CBN
|
||||
cbt
|
||||
Ccc
|
||||
CCCBB
|
||||
CCCDDD
|
||||
cch
|
||||
CCHAR
|
||||
CCmd
|
||||
@ -369,6 +370,7 @@ DColor
|
||||
dcommon
|
||||
DComposition
|
||||
dde
|
||||
DDDCCC
|
||||
DDESHARE
|
||||
DDevice
|
||||
DEADCHAR
|
||||
|
||||
@ -80,11 +80,12 @@ constexpr OutIt copy_n_small(InIt first, Diff count, OutIt dest)
|
||||
return dest;
|
||||
}
|
||||
|
||||
CharToColumnMapper::CharToColumnMapper(const wchar_t* chars, const uint16_t* charOffsets, ptrdiff_t lastCharOffset, til::CoordType currentColumn) noexcept :
|
||||
CharToColumnMapper::CharToColumnMapper(const wchar_t* chars, const uint16_t* charOffsets, ptrdiff_t charsLength, til::CoordType currentColumn, til::CoordType columnCount) noexcept :
|
||||
_chars{ chars },
|
||||
_charOffsets{ charOffsets },
|
||||
_lastCharOffset{ lastCharOffset },
|
||||
_currentColumn{ currentColumn }
|
||||
_charsLength{ charsLength },
|
||||
_currentColumn{ currentColumn },
|
||||
_columnCount{ columnCount }
|
||||
{
|
||||
}
|
||||
|
||||
@ -92,7 +93,7 @@ CharToColumnMapper::CharToColumnMapper(const wchar_t* chars, const uint16_t* cha
|
||||
// This function in particular returns the glyph's first column.
|
||||
til::CoordType CharToColumnMapper::GetLeadingColumnAt(ptrdiff_t targetOffset) noexcept
|
||||
{
|
||||
targetOffset = clamp(targetOffset, 0, _lastCharOffset);
|
||||
targetOffset = clamp(targetOffset, 0, _charsLength);
|
||||
|
||||
// This code needs to fulfill two conditions on top of the obvious (a forward/backward search):
|
||||
// A: We never want to stop on a column that is marked with CharOffsetsTrailer (= "GetLeadingColumn").
|
||||
@ -130,10 +131,14 @@ til::CoordType CharToColumnMapper::GetLeadingColumnAt(ptrdiff_t targetOffset) no
|
||||
til::CoordType CharToColumnMapper::GetTrailingColumnAt(ptrdiff_t offset) noexcept
|
||||
{
|
||||
auto col = GetLeadingColumnAt(offset);
|
||||
// This loop is a little redundant with the forward search loop in GetLeadingColumnAt()
|
||||
// but it's realistically not worth caring about this. This code is not a bottleneck.
|
||||
for (; WI_IsFlagSet(_charOffsets[col + 1], CharOffsetsTrailer); ++col)
|
||||
|
||||
if (col < _columnCount)
|
||||
{
|
||||
// This loop is a little redundant with the forward search loop in GetLeadingColumnAt()
|
||||
// but it's realistically not worth caring about this. This code is not a bottleneck.
|
||||
for (; WI_IsFlagSet(_charOffsets[col + 1], CharOffsetsTrailer); ++col)
|
||||
{
|
||||
}
|
||||
}
|
||||
return col;
|
||||
}
|
||||
@ -1114,6 +1119,9 @@ std::wstring_view ROW::GetText() const noexcept
|
||||
return { _chars.data(), width };
|
||||
}
|
||||
|
||||
// Arguments:
|
||||
// - columnBegin: inclusive
|
||||
// - columnEnd: exclusive
|
||||
std::wstring_view ROW::GetText(til::CoordType columnBegin, til::CoordType columnEnd) const noexcept
|
||||
{
|
||||
const auto columns = GetReadableColumnCount();
|
||||
@ -1219,15 +1227,15 @@ T ROW::_adjustForward(T column) const noexcept
|
||||
}
|
||||
|
||||
// Creates a CharToColumnMapper given an offset into _chars.data().
|
||||
// In other words, for a 120 column ROW with just ASCII text, the offset should be [0,120).
|
||||
// In other words, for a 120 column ROW with just ASCII text, the offset should be [0,120].
|
||||
CharToColumnMapper ROW::_createCharToColumnMapper(ptrdiff_t offset) const noexcept
|
||||
{
|
||||
const auto charsSize = _charSize();
|
||||
const auto lastChar = gsl::narrow_cast<ptrdiff_t>(charsSize - 1);
|
||||
const auto lastChar = gsl::narrow_cast<ptrdiff_t>(charsSize);
|
||||
// We can sort of guess what column belongs to what offset because BMP glyphs are very common and
|
||||
// UTF-16 stores them in 1 char. In other words, usually a ROW will have N chars for N columns.
|
||||
const auto guessedColumn = gsl::narrow_cast<til::CoordType>(clamp(offset, 0, _columnCount));
|
||||
return CharToColumnMapper{ _chars.data(), _charOffsets.data(), lastChar, guessedColumn };
|
||||
return CharToColumnMapper{ _chars.data(), _charOffsets.data(), lastChar, guessedColumn, _columnCount };
|
||||
}
|
||||
|
||||
const std::optional<ScrollbarData>& ROW::GetScrollbarData() const noexcept
|
||||
|
||||
@ -71,7 +71,7 @@ struct RowCopyTextFromState
|
||||
// into a ROW's text this class can tell you what cell that pointer belongs to.
|
||||
struct CharToColumnMapper
|
||||
{
|
||||
CharToColumnMapper(const wchar_t* chars, const uint16_t* charOffsets, ptrdiff_t lastCharOffset, til::CoordType currentColumn) noexcept;
|
||||
CharToColumnMapper(const wchar_t* chars, const uint16_t* charOffsets, ptrdiff_t lastCharOffset, til::CoordType currentColumn, til::CoordType columnCount) noexcept;
|
||||
|
||||
til::CoordType GetLeadingColumnAt(ptrdiff_t targetOffset) noexcept;
|
||||
til::CoordType GetTrailingColumnAt(ptrdiff_t offset) noexcept;
|
||||
@ -85,8 +85,9 @@ private:
|
||||
|
||||
const wchar_t* _chars;
|
||||
const uint16_t* _charOffsets;
|
||||
ptrdiff_t _lastCharOffset;
|
||||
ptrdiff_t _charsLength;
|
||||
til::CoordType _currentColumn;
|
||||
til::CoordType _columnCount;
|
||||
};
|
||||
|
||||
class ROW final
|
||||
|
||||
@ -411,16 +411,13 @@ Microsoft::Console::ICU::unique_uregex Microsoft::Console::ICU::CreateRegex(cons
|
||||
return unique_uregex{ re };
|
||||
}
|
||||
|
||||
// Returns an inclusive point range given a text start and end position.
|
||||
// Returns a half-open [beg,end) range given a text start and end position.
|
||||
// This function is designed to be used with uregex_start64/uregex_end64.
|
||||
til::point_span Microsoft::Console::ICU::BufferRangeFromMatch(UText* ut, URegularExpression* re)
|
||||
{
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
const auto nativeIndexBeg = uregex_start64(re, 0, &status);
|
||||
auto nativeIndexEnd = uregex_end64(re, 0, &status);
|
||||
|
||||
// The parameters are given as a half-open [beg,end) range, but the point_span we return in closed [beg,end].
|
||||
nativeIndexEnd--;
|
||||
const auto nativeIndexEnd = uregex_end64(re, 0, &status);
|
||||
|
||||
const auto& textBuffer = *static_cast<const TextBuffer*>(ut->context);
|
||||
til::point_span ret;
|
||||
@ -439,7 +436,7 @@ til::point_span Microsoft::Console::ICU::BufferRangeFromMatch(UText* ut, URegula
|
||||
if (utextAccess(ut, nativeIndexEnd, true))
|
||||
{
|
||||
const auto y = accessCurrentRow(ut);
|
||||
ret.end.x = textBuffer.GetRowByOffset(y).GetTrailingColumnAtCharOffset(ut->chunkOffset);
|
||||
ret.end.x = textBuffer.GetRowByOffset(y).GetLeadingColumnAtCharOffset(ut->chunkOffset);
|
||||
ret.end.y = y;
|
||||
}
|
||||
else
|
||||
|
||||
@ -1118,6 +1118,14 @@ void TextBuffer::TriggerNewTextNotification(const std::wstring_view newText)
|
||||
}
|
||||
}
|
||||
|
||||
void TextBuffer::TriggerSelection()
|
||||
{
|
||||
if (_isActiveBuffer && _renderer)
|
||||
{
|
||||
_renderer->TriggerSelection();
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - get delimiter class for buffer cell position
|
||||
// - used for double click selection and uia word navigation
|
||||
@ -1132,6 +1140,213 @@ DelimiterClass TextBuffer::_GetDelimiterClassAt(const til::point pos, const std:
|
||||
return GetRowByOffset(realPos.y).DelimiterClassAt(realPos.x, wordDelimiters);
|
||||
}
|
||||
|
||||
til::point TextBuffer::GetWordStart2(til::point pos, const std::wstring_view wordDelimiters, bool includeWhitespace, std::optional<til::point> limitOptional) const
|
||||
{
|
||||
const auto bufferSize{ GetSize() };
|
||||
const auto limit{ limitOptional.value_or(bufferSize.BottomInclusiveRightExclusive()) };
|
||||
|
||||
if (pos < bufferSize.Origin())
|
||||
{
|
||||
// can't move further back, so return early at origin
|
||||
return bufferSize.Origin();
|
||||
}
|
||||
else if (pos >= limit)
|
||||
{
|
||||
// clamp to limit,
|
||||
// but still do movement
|
||||
pos = limit;
|
||||
}
|
||||
|
||||
// Consider the delimiter classes represented as these chars:
|
||||
// - ControlChar: "_"
|
||||
// - DelimiterChar: "D"
|
||||
// - RegularChar: "C"
|
||||
// Expected results ("|" is the position):
|
||||
// includeWhitespace: true false
|
||||
// CCC___| --> |CCC___ CCC|___
|
||||
// DDD___| --> |DDD___ DDD|___
|
||||
// ___CCC| --> ___|CCC ___|CCC
|
||||
// DDDCCC| --> DDD|CCC DDD|CCC
|
||||
// ___DDD| --> ___|DDD ___|DDD
|
||||
// CCCDDD| --> CCC|DDD CCC|DDD
|
||||
// So the heuristic we use is:
|
||||
// 1. move to the beginning of the delimiter class run
|
||||
// 2. (includeWhitespace) if we were on a ControlChar, go back one more delimiter class run
|
||||
const auto initialDelimiter = bufferSize.IsInBounds(pos) ? _GetDelimiterClassAt(pos, wordDelimiters) : DelimiterClass::ControlChar;
|
||||
pos = _GetDelimiterClassRunStart(pos, wordDelimiters);
|
||||
if (!includeWhitespace || pos.x == bufferSize.Left())
|
||||
{
|
||||
// Special case:
|
||||
// we're at the left boundary (and end of a delimiter class run),
|
||||
// we already know we can't wrap, so return early
|
||||
return pos;
|
||||
}
|
||||
else if (initialDelimiter == DelimiterClass::ControlChar)
|
||||
{
|
||||
bufferSize.DecrementInExclusiveBounds(pos);
|
||||
pos = _GetDelimiterClassRunStart(pos, wordDelimiters);
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
til::point TextBuffer::GetWordEnd2(til::point pos, const std::wstring_view wordDelimiters, bool includeWhitespace, std::optional<til::point> limitOptional) const
|
||||
{
|
||||
const auto bufferSize{ GetSize() };
|
||||
const auto limit{ limitOptional.value_or(bufferSize.BottomInclusiveRightExclusive()) };
|
||||
|
||||
if (pos >= limit)
|
||||
{
|
||||
// can't move further forward,
|
||||
// so return early at limit
|
||||
return limit;
|
||||
}
|
||||
else if (const auto origin{ bufferSize.Origin() }; pos < origin)
|
||||
{
|
||||
// clamp to origin,
|
||||
// but still do movement
|
||||
pos = origin;
|
||||
}
|
||||
|
||||
// Consider the delimiter classes represented as these chars:
|
||||
// - ControlChar: "_"
|
||||
// - DelimiterChar: "D"
|
||||
// - RegularChar: "C"
|
||||
// Expected results ("|" is the position):
|
||||
// includeWhitespace: true false
|
||||
// |CCC___ --> CCC___| CCC|___
|
||||
// |DDD___ --> DDD___| DDD|___
|
||||
// |___CCC --> ___|CCC ___|CCC
|
||||
// |DDDCCC --> DDD|CCC DDD|CCC
|
||||
// |___DDD --> ___|DDD ___|DDD
|
||||
// |CCCDDD --> CCC|DDD CCC|DDD
|
||||
// So the heuristic we use is:
|
||||
// 1. move to the end of the delimiter class run
|
||||
// 2. (includeWhitespace) if the next delimiter class run is a ControlChar, go forward one more delimiter class run
|
||||
pos = _GetDelimiterClassRunEnd(pos, wordDelimiters);
|
||||
if (!includeWhitespace || pos.x == bufferSize.RightExclusive())
|
||||
{
|
||||
// Special case:
|
||||
// we're at the right boundary (and end of a delimiter class run),
|
||||
// we already know we can't wrap, so return early
|
||||
return pos;
|
||||
}
|
||||
|
||||
if (const auto nextDelimClass = bufferSize.IsInBounds(pos) ? _GetDelimiterClassAt(pos, wordDelimiters) : DelimiterClass::ControlChar;
|
||||
nextDelimClass == DelimiterClass::ControlChar)
|
||||
{
|
||||
return _GetDelimiterClassRunEnd(pos, wordDelimiters);
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
bool TextBuffer::IsWordBoundary(const til::point pos, const std::wstring_view wordDelimiters) const
|
||||
{
|
||||
const auto bufferSize = GetSize();
|
||||
if (!bufferSize.IsInExclusiveBounds(pos))
|
||||
{
|
||||
// not in bounds
|
||||
return false;
|
||||
}
|
||||
|
||||
// buffer boundaries are always word boundaries
|
||||
if (pos == bufferSize.Origin() || pos == bufferSize.BottomInclusiveRightExclusive())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// at beginning of the row, but we didn't wrap
|
||||
if (pos.x == bufferSize.Left())
|
||||
{
|
||||
const auto& row = GetRowByOffset(pos.y - 1);
|
||||
if (!row.WasWrapForced())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// at end of the row, but we didn't wrap
|
||||
if (pos.x == bufferSize.RightExclusive())
|
||||
{
|
||||
const auto& row = GetRowByOffset(pos.y);
|
||||
if (!row.WasWrapForced())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// we can treat text as contiguous,
|
||||
// use DecrementInBounds (not exclusive) here
|
||||
auto prevPos = pos;
|
||||
bufferSize.DecrementInBounds(prevPos);
|
||||
const auto prevDelimiterClass = _GetDelimiterClassAt(prevPos, wordDelimiters);
|
||||
|
||||
// if we changed delimiter class
|
||||
// and the current delimiter class is not a control char,
|
||||
// we're at a word boundary
|
||||
const auto currentDelimiterClass = _GetDelimiterClassAt(pos, wordDelimiters);
|
||||
return prevDelimiterClass != currentDelimiterClass && currentDelimiterClass != DelimiterClass::ControlChar;
|
||||
}
|
||||
|
||||
til::point TextBuffer::_GetDelimiterClassRunStart(til::point pos, const std::wstring_view wordDelimiters) const
|
||||
{
|
||||
const auto bufferSize = GetSize();
|
||||
const auto initialDelimClass = bufferSize.IsInBounds(pos) ? _GetDelimiterClassAt(pos, wordDelimiters) : DelimiterClass::ControlChar;
|
||||
for (auto nextPos = pos; nextPos != bufferSize.Origin(); pos = nextPos)
|
||||
{
|
||||
bufferSize.DecrementInExclusiveBounds(nextPos);
|
||||
|
||||
if (nextPos.x == bufferSize.RightExclusive())
|
||||
{
|
||||
// wrapped onto previous line,
|
||||
// check if it was forced to wrap
|
||||
const auto& row = GetRowByOffset(nextPos.y);
|
||||
if (!row.WasWrapForced())
|
||||
{
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
else if (_GetDelimiterClassAt(nextPos, wordDelimiters) != initialDelimClass)
|
||||
{
|
||||
// if we changed delim class, we're done (don't apply move)
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Get the exclusive position for the end of the current delimiter class run
|
||||
// Arguments:
|
||||
// - pos - the buffer position being within the current delimiter class
|
||||
// - wordDelimiters - what characters are we considering for the separation of words
|
||||
til::point TextBuffer::_GetDelimiterClassRunEnd(til::point pos, const std::wstring_view wordDelimiters) const
|
||||
{
|
||||
const auto bufferSize = GetSize();
|
||||
const auto initialDelimClass = bufferSize.IsInBounds(pos) ? _GetDelimiterClassAt(pos, wordDelimiters) : DelimiterClass::ControlChar;
|
||||
for (auto nextPos = pos; nextPos != bufferSize.BottomInclusiveRightExclusive(); pos = nextPos)
|
||||
{
|
||||
bufferSize.IncrementInExclusiveBounds(nextPos);
|
||||
|
||||
if (nextPos.x == bufferSize.Left())
|
||||
{
|
||||
// wrapped onto next line,
|
||||
// check if it was forced to wrap or switched delimiter class
|
||||
const auto& row = GetRowByOffset(pos.y);
|
||||
if (!row.WasWrapForced() || _GetDelimiterClassAt(nextPos, wordDelimiters) != initialDelimClass)
|
||||
{
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
else if (bufferSize.IsInBounds(nextPos) && _GetDelimiterClassAt(nextPos, wordDelimiters) != initialDelimClass)
|
||||
{
|
||||
// if we changed delim class,
|
||||
// apply the move and return
|
||||
return nextPos;
|
||||
}
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Get the til::point for the beginning of the word you are on
|
||||
// Arguments:
|
||||
@ -1520,13 +1735,14 @@ til::point TextBuffer::GetGlyphStart(const til::point pos, std::optional<til::po
|
||||
const auto limit{ limitOptional.value_or(bufferSize.EndExclusive()) };
|
||||
|
||||
// Clamp pos to limit
|
||||
if (bufferSize.CompareInBounds(resultPos, limit, true) > 0)
|
||||
if (resultPos > limit)
|
||||
{
|
||||
resultPos = limit;
|
||||
return limit;
|
||||
}
|
||||
|
||||
// limit is exclusive, so we need to move back to be within valid bounds
|
||||
if (resultPos != limit && GetCellDataAt(resultPos)->DbcsAttr() == DbcsAttribute::Trailing)
|
||||
// if we're on a trailing byte, move to the leading byte
|
||||
if (bufferSize.IsInBounds(resultPos) &&
|
||||
GetCellDataAt(resultPos)->DbcsAttr() == DbcsAttribute::Trailing)
|
||||
{
|
||||
bufferSize.DecrementInBounds(resultPos, true);
|
||||
}
|
||||
@ -1548,12 +1764,13 @@ til::point TextBuffer::GetGlyphEnd(const til::point pos, bool accessibilityMode,
|
||||
const auto limit{ limitOptional.value_or(bufferSize.EndExclusive()) };
|
||||
|
||||
// Clamp pos to limit
|
||||
if (bufferSize.CompareInBounds(resultPos, limit, true) > 0)
|
||||
if (resultPos > limit)
|
||||
{
|
||||
resultPos = limit;
|
||||
return limit;
|
||||
}
|
||||
|
||||
if (resultPos != limit && GetCellDataAt(resultPos)->DbcsAttr() == DbcsAttribute::Leading)
|
||||
if (bufferSize.IsInBounds(resultPos) &&
|
||||
GetCellDataAt(resultPos)->DbcsAttr() == DbcsAttribute::Leading)
|
||||
{
|
||||
bufferSize.IncrementInBounds(resultPos, true);
|
||||
}
|
||||
@ -1610,6 +1827,31 @@ bool TextBuffer::MoveToNextGlyph(til::point& pos, bool allowExclusiveEnd, std::o
|
||||
return success;
|
||||
}
|
||||
|
||||
bool TextBuffer::MoveToNextGlyph2(til::point& pos, std::optional<til::point> limitOptional) const
|
||||
{
|
||||
const auto bufferSize = GetSize();
|
||||
const auto limit{ limitOptional.value_or(bufferSize.BottomInclusiveRightExclusive()) };
|
||||
|
||||
if (pos >= limit)
|
||||
{
|
||||
// Corner Case: we're on/past the limit
|
||||
// Clamp us to the limit
|
||||
pos = limit;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try to move forward, but if we hit the buffer boundary, we fail to move.
|
||||
const bool success = bufferSize.IncrementInExclusiveBounds(pos);
|
||||
if (success &&
|
||||
bufferSize.IsInBounds(pos) &&
|
||||
GetCellDataAt(pos)->DbcsAttr() == DbcsAttribute::Trailing)
|
||||
{
|
||||
// Move again if we're on a wide glyph
|
||||
bufferSize.IncrementInExclusiveBounds(pos);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Update pos to be the beginning of the previous glyph/character. This is used for accessibility
|
||||
// Arguments:
|
||||
@ -1642,6 +1884,31 @@ bool TextBuffer::MoveToPreviousGlyph(til::point& pos, std::optional<til::point>
|
||||
return success;
|
||||
}
|
||||
|
||||
bool TextBuffer::MoveToPreviousGlyph2(til::point& pos, std::optional<til::point> limitOptional) const
|
||||
{
|
||||
const auto bufferSize = GetSize();
|
||||
const auto limit{ limitOptional.value_or(bufferSize.BottomInclusiveRightExclusive()) };
|
||||
|
||||
if (pos >= limit)
|
||||
{
|
||||
// Corner Case: we're on/past the limit
|
||||
// Clamp us to the limit
|
||||
pos = limit;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try to move backward, but if we hit the buffer boundary, we fail to move.
|
||||
const bool success = bufferSize.DecrementInExclusiveBounds(pos);
|
||||
if (success &&
|
||||
bufferSize.IsInBounds(pos) &&
|
||||
GetCellDataAt(pos)->DbcsAttr() == DbcsAttribute::Trailing)
|
||||
{
|
||||
// Move again if we're on a wide glyph
|
||||
bufferSize.DecrementInExclusiveBounds(pos);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Determines the line-by-line rectangles based on two COORDs
|
||||
// - expands the rectangles to support wide glyphs
|
||||
@ -1660,12 +1927,10 @@ const std::vector<til::inclusive_rect> TextBuffer::GetTextRects(til::point start
|
||||
{
|
||||
std::vector<til::inclusive_rect> textRects;
|
||||
|
||||
const auto bufferSize = GetSize();
|
||||
|
||||
// (0,0) is the top-left of the screen
|
||||
// the physically "higher" coordinate is closer to the top-left
|
||||
// the physically "lower" coordinate is closer to the bottom-right
|
||||
const auto [higherCoord, lowerCoord] = bufferSize.CompareInBounds(start, end) <= 0 ?
|
||||
const auto [higherCoord, lowerCoord] = start <= end ?
|
||||
std::make_tuple(start, end) :
|
||||
std::make_tuple(end, start);
|
||||
|
||||
@ -1686,6 +1951,7 @@ const std::vector<til::inclusive_rect> TextBuffer::GetTextRects(til::point start
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto bufferSize = GetSize();
|
||||
textRow.left = (row == higherCoord.y) ? higherCoord.x : bufferSize.Left();
|
||||
textRow.right = (row == lowerCoord.y) ? lowerCoord.x : bufferSize.RightInclusive();
|
||||
}
|
||||
@ -1710,7 +1976,7 @@ const std::vector<til::inclusive_rect> TextBuffer::GetTextRects(til::point start
|
||||
// - Else if a blockSelection, returns spans corresponding to each line in the block selection
|
||||
// Arguments:
|
||||
// - start: beginning of the text region of interest (inclusive)
|
||||
// - end: the other end of the text region of interest (inclusive)
|
||||
// - end: the other end of the text region of interest (exclusive)
|
||||
// - blockSelection: when enabled, get spans for each line covered by the block
|
||||
// - bufferCoordinates: when enabled, treat the coordinates as relative to
|
||||
// the buffer rather than the screen.
|
||||
@ -1780,31 +2046,17 @@ void TextBuffer::_ExpandTextRow(til::inclusive_rect& textRow) const
|
||||
|
||||
// expand left side of rect
|
||||
til::point targetPoint{ textRow.left, textRow.top };
|
||||
if (GetCellDataAt(targetPoint)->DbcsAttr() == DbcsAttribute::Trailing)
|
||||
if (bufferSize.IsInBounds(targetPoint) && GetCellDataAt(targetPoint)->DbcsAttr() == DbcsAttribute::Trailing)
|
||||
{
|
||||
if (targetPoint.x == bufferSize.Left())
|
||||
{
|
||||
bufferSize.IncrementInBounds(targetPoint);
|
||||
}
|
||||
else
|
||||
{
|
||||
bufferSize.DecrementInBounds(targetPoint);
|
||||
}
|
||||
bufferSize.DecrementInExclusiveBounds(targetPoint);
|
||||
textRow.left = targetPoint.x;
|
||||
}
|
||||
|
||||
// expand right side of rect
|
||||
targetPoint = { textRow.right, textRow.bottom };
|
||||
if (GetCellDataAt(targetPoint)->DbcsAttr() == DbcsAttribute::Leading)
|
||||
if (bufferSize.IsInBounds(targetPoint) && GetCellDataAt(targetPoint)->DbcsAttr() == DbcsAttribute::Trailing)
|
||||
{
|
||||
if (targetPoint.x == bufferSize.RightInclusive())
|
||||
{
|
||||
bufferSize.DecrementInBounds(targetPoint);
|
||||
}
|
||||
else
|
||||
{
|
||||
bufferSize.IncrementInBounds(targetPoint);
|
||||
}
|
||||
bufferSize.IncrementInExclusiveBounds(targetPoint);
|
||||
textRow.right = targetPoint.x;
|
||||
}
|
||||
}
|
||||
@ -1821,8 +2073,8 @@ size_t TextBuffer::SpanLength(const til::point coordStart, const til::point coor
|
||||
// - Retrieves the plain text data between the specified coordinates.
|
||||
// Arguments:
|
||||
// - trimTrailingWhitespace - remove the trailing whitespace at the end of the result.
|
||||
// - start - where to start getting text (should be at or prior to "end")
|
||||
// - end - where to end getting text
|
||||
// - start - where to start getting text (should be at or prior to "end") (inclusive)
|
||||
// - end - where to end getting text (exclusive)
|
||||
// Return Value:
|
||||
// - Just the text.
|
||||
std::wstring TextBuffer::GetPlainText(const til::point start, const til::point end) const
|
||||
@ -1851,7 +2103,7 @@ std::tuple<til::CoordType, til::CoordType, bool> TextBuffer::_RowCopyHelper(cons
|
||||
const auto maxX = req.bufferCoordinates ? req.maxX : ScreenToBufferLineInclusive(til::point{ req.maxX, iRow }, lineRendition).x;
|
||||
|
||||
rowBeg = minX;
|
||||
rowEnd = maxX + 1; // +1 to get an exclusive end
|
||||
rowEnd = maxX;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1860,7 +2112,7 @@ std::tuple<til::CoordType, til::CoordType, bool> TextBuffer::_RowCopyHelper(cons
|
||||
const auto end = req.bufferCoordinates ? req.end : ScreenToBufferLineInclusive(req.end, lineRendition);
|
||||
|
||||
rowBeg = iRow != beg.y ? 0 : beg.x;
|
||||
rowEnd = iRow != end.y ? row.GetReadableColumnCount() : end.x + 1; // +1 to get an exclusive end
|
||||
rowEnd = iRow != end.y ? row.GetReadableColumnCount() : end.x;
|
||||
}
|
||||
|
||||
// Our selection mechanism doesn't stick to glyph boundaries at the moment.
|
||||
@ -1905,7 +2157,7 @@ std::wstring TextBuffer::GetPlainText(const CopyRequest& req) const
|
||||
const auto& row = GetRowByOffset(iRow);
|
||||
const auto& [rowBeg, rowEnd, addLineBreak] = _RowCopyHelper(req, iRow, row);
|
||||
|
||||
// save selected text
|
||||
// save selected text (exclusive end)
|
||||
selectedText += row.GetText(rowBeg, rowEnd);
|
||||
|
||||
if (addLineBreak && iRow != req.end.y)
|
||||
|
||||
@ -170,9 +170,15 @@ public:
|
||||
void TriggerScroll();
|
||||
void TriggerScroll(const til::point delta);
|
||||
void TriggerNewTextNotification(const std::wstring_view newText);
|
||||
void TriggerSelection();
|
||||
|
||||
til::point GetWordStart(const til::point target, const std::wstring_view wordDelimiters, bool accessibilityMode = false, std::optional<til::point> limitOptional = std::nullopt) const;
|
||||
til::point GetWordEnd(const til::point target, const std::wstring_view wordDelimiters, bool accessibilityMode = false, std::optional<til::point> limitOptional = std::nullopt) const;
|
||||
|
||||
til::point GetWordStart2(til::point pos, const std::wstring_view wordDelimiters, bool includeWhitespace, std::optional<til::point> limitOptional = std::nullopt) const;
|
||||
til::point GetWordEnd2(til::point pos, const std::wstring_view wordDelimiters, bool includeWhitespace, std::optional<til::point> limitOptional = std::nullopt) const;
|
||||
|
||||
bool IsWordBoundary(const til::point pos, const std::wstring_view wordDelimiters) const;
|
||||
bool MoveToNextWord(til::point& pos, const std::wstring_view wordDelimiters, std::optional<til::point> limitOptional = std::nullopt) const;
|
||||
bool MoveToPreviousWord(til::point& pos, const std::wstring_view wordDelimiters) const;
|
||||
|
||||
@ -180,6 +186,8 @@ public:
|
||||
til::point GetGlyphEnd(const til::point pos, bool accessibilityMode = false, std::optional<til::point> limitOptional = std::nullopt) const;
|
||||
bool MoveToNextGlyph(til::point& pos, bool allowBottomExclusive = false, std::optional<til::point> limitOptional = std::nullopt) const;
|
||||
bool MoveToPreviousGlyph(til::point& pos, std::optional<til::point> limitOptional = std::nullopt) const;
|
||||
bool MoveToNextGlyph2(til::point& pos, std::optional<til::point> limitOptional = std::nullopt) const;
|
||||
bool MoveToPreviousGlyph2(til::point& pos, std::optional<til::point> limitOptional = std::nullopt) const;
|
||||
|
||||
const std::vector<til::inclusive_rect> GetTextRects(til::point start, til::point end, bool blockSelection, bool bufferCoordinates) const;
|
||||
std::vector<til::point_span> GetTextSpans(til::point start, til::point end, bool blockSelection, bool bufferCoordinates) const;
|
||||
@ -322,6 +330,8 @@ private:
|
||||
void _SetFirstRowIndex(const til::CoordType FirstRowIndex) noexcept;
|
||||
void _ExpandTextRow(til::inclusive_rect& selectionRow) const;
|
||||
DelimiterClass _GetDelimiterClassAt(const til::point pos, const std::wstring_view wordDelimiters) const;
|
||||
til::point _GetDelimiterClassRunStart(til::point pos, const std::wstring_view wordDelimiters) const;
|
||||
til::point _GetDelimiterClassRunEnd(til::point pos, const std::wstring_view wordDelimiters) const;
|
||||
til::point _GetWordStartForAccessibility(const til::point target, const std::wstring_view wordDelimiters) const;
|
||||
til::point _GetWordStartForSelection(const til::point target, const std::wstring_view wordDelimiters) const;
|
||||
til::point _GetWordEndForAccessibility(const til::point target, const std::wstring_view wordDelimiters, const til::point limit) const;
|
||||
|
||||
@ -49,15 +49,15 @@ class UTextAdapterTests
|
||||
return { { beg, 0 }, { end, 0 } };
|
||||
};
|
||||
|
||||
auto expected = std::vector{ s(0, 2), s(8, 10) };
|
||||
auto expected = std::vector{ s(0, 3), s(8, 11) };
|
||||
auto actual = buffer.SearchText(L"abc", SearchFlag::None);
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
|
||||
expected = std::vector{ s(5, 5) };
|
||||
expected = std::vector{ s(5, 6) };
|
||||
actual = buffer.SearchText(L"𝒷", SearchFlag::None);
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
|
||||
expected = std::vector{ s(12, 15) };
|
||||
expected = std::vector{ s(12, 16) };
|
||||
actual = buffer.SearchText(L"ネコ", SearchFlag::None);
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
}
|
||||
|
||||
@ -1200,7 +1200,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
|
||||
const auto bufferSize{ _terminal->GetTextBuffer().GetSize() };
|
||||
info.StartAtLeftBoundary = _terminal->GetSelectionAnchor().x == bufferSize.Left();
|
||||
info.EndAtRightBoundary = _terminal->GetSelectionEnd().x == bufferSize.RightInclusive();
|
||||
info.EndAtRightBoundary = _terminal->GetSelectionEnd().x == bufferSize.RightExclusive();
|
||||
return info;
|
||||
}
|
||||
|
||||
@ -1217,8 +1217,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
return;
|
||||
}
|
||||
|
||||
// clamp the converted position to be within the viewport bounds
|
||||
// x: allow range of [0, RightExclusive]
|
||||
// GH #18106: right exclusive needed for selection to support exclusive end
|
||||
til::point terminalPosition{
|
||||
std::clamp(position.x, 0, _terminal->GetViewport().Width() - 1),
|
||||
std::clamp(position.x, 0, _terminal->GetViewport().Width()),
|
||||
std::clamp(position.y, 0, _terminal->GetViewport().Height() - 1)
|
||||
};
|
||||
|
||||
@ -2722,7 +2725,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
bufferSize.DecrementInBounds(inclusiveEnd);
|
||||
|
||||
_terminal->SelectNewRegion(s.start, inclusiveEnd);
|
||||
_renderer->TriggerSelection();
|
||||
}
|
||||
|
||||
void ControlCore::SelectCommand(const bool goUp)
|
||||
|
||||
@ -241,7 +241,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
const ::Microsoft::Terminal::Core::ControlKeyStates modifiers,
|
||||
const Core::Point pixelPosition)
|
||||
{
|
||||
const auto terminalPosition = _getTerminalPosition(til::point{ pixelPosition });
|
||||
const auto terminalPosition = _getTerminalPosition(til::point{ pixelPosition }, true);
|
||||
|
||||
const auto altEnabled = modifiers.IsAltPressed();
|
||||
const auto shiftEnabled = modifiers.IsShiftPressed();
|
||||
@ -338,7 +338,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
const Core::Point pixelPosition,
|
||||
const bool pointerPressedInBounds)
|
||||
{
|
||||
const auto terminalPosition = _getTerminalPosition(til::point{ pixelPosition });
|
||||
const auto terminalPosition = _getTerminalPosition(til::point{ pixelPosition }, true);
|
||||
// Returning true from this function indicates that the caller should do no further processing of this movement.
|
||||
bool handledCompletely = false;
|
||||
|
||||
@ -372,7 +372,19 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// _touchdown_ point here. We want to start the selection
|
||||
// from where the user initially clicked, not where they are
|
||||
// now.
|
||||
_core->SetSelectionAnchor(_getTerminalPosition(til::point{ touchdownPoint }));
|
||||
auto termPos = _getTerminalPosition(til::point{ touchdownPoint }, false);
|
||||
if (dx < 0)
|
||||
{
|
||||
// _getTerminalPosition(_, false) will floor the x-value,
|
||||
// meaning that the selection will start on the left-side
|
||||
// of the current cell. This is great if the use is dragging
|
||||
// towards the right.
|
||||
// If the user is dragging towards the left (dx < 0),
|
||||
// we want to select the current cell, so place the anchor on the right
|
||||
// side of the current cell.
|
||||
termPos.x++;
|
||||
}
|
||||
_core->SetSelectionAnchor(termPos);
|
||||
|
||||
// stop tracking the touchdown point
|
||||
_singleClickTouchdownPos = std::nullopt;
|
||||
@ -428,7 +440,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
const ::Microsoft::Terminal::Core::ControlKeyStates modifiers,
|
||||
const Core::Point pixelPosition)
|
||||
{
|
||||
const auto terminalPosition = _getTerminalPosition(til::point{ pixelPosition });
|
||||
const auto terminalPosition = _getTerminalPosition(til::point{ pixelPosition }, false);
|
||||
// Short-circuit isReadOnly check to avoid warning dialog
|
||||
if (!_core->IsInReadOnlyMode() && _canSendVTMouseInput(modifiers))
|
||||
{
|
||||
@ -475,7 +487,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
const Core::Point pixelPosition,
|
||||
const Control::MouseButtonState buttonState)
|
||||
{
|
||||
const auto terminalPosition = _getTerminalPosition(til::point{ pixelPosition });
|
||||
const auto terminalPosition = _getTerminalPosition(til::point{ pixelPosition }, true);
|
||||
|
||||
// Short-circuit isReadOnly check to avoid warning dialog.
|
||||
//
|
||||
@ -662,7 +674,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// - cursorPosition: in pixels, relative to the origin of the control
|
||||
void ControlInteractivity::SetEndSelectionPoint(const Core::Point pixelPosition)
|
||||
{
|
||||
_core->SetEndSelectionPoint(_getTerminalPosition(til::point{ pixelPosition }));
|
||||
_core->SetEndSelectionPoint(_getTerminalPosition(til::point{ pixelPosition }, true));
|
||||
_selectionNeedsToBeCopied = true;
|
||||
}
|
||||
|
||||
@ -672,12 +684,22 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// Arguments:
|
||||
// - pixelPosition: the (x,y) position of a given point (i.e.: mouse cursor).
|
||||
// NOTE: origin (0,0) is top-left.
|
||||
// - roundToNearestCell: if true, round the x-value. Otherwise, floor it (standard int division)
|
||||
// Return Value:
|
||||
// - the corresponding viewport terminal position for the given Point parameter
|
||||
til::point ControlInteractivity::_getTerminalPosition(const til::point pixelPosition)
|
||||
til::point ControlInteractivity::_getTerminalPosition(const til::point pixelPosition, bool roundToNearestCell)
|
||||
{
|
||||
// Get the size of the font, which is in pixels
|
||||
const til::size fontSize{ _core->GetFont().GetSize() };
|
||||
const auto fontSize{ _core->GetFont().GetSize() };
|
||||
|
||||
if (roundToNearestCell)
|
||||
{
|
||||
// GH#5099: round the x-value to the nearest cell
|
||||
til::point result;
|
||||
result.x = gsl::narrow_cast<til::CoordType>(std::round(gsl::narrow_cast<double>(pixelPosition.x) / fontSize.width));
|
||||
result.y = pixelPosition.y / fontSize.height;
|
||||
return result;
|
||||
}
|
||||
// Convert the location in pixels to characters within the current viewport.
|
||||
return pixelPosition / fontSize;
|
||||
}
|
||||
|
||||
@ -155,7 +155,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
bool _canSendVTMouseInput(const ::Microsoft::Terminal::Core::ControlKeyStates modifiers);
|
||||
bool _shouldSendAlternateScroll(const ::Microsoft::Terminal::Core::ControlKeyStates modifiers, const int32_t delta);
|
||||
|
||||
til::point _getTerminalPosition(const til::point pixelPosition);
|
||||
til::point _getTerminalPosition(const til::point pixelPosition, bool roundToNearestCell);
|
||||
|
||||
bool _sendMouseEventHelper(const til::point terminalPosition,
|
||||
const unsigned int pointerUpdateKind,
|
||||
|
||||
@ -478,7 +478,7 @@ bool Terminal::ShouldSendAlternateScroll(const unsigned int uiButton,
|
||||
// - The position relative to the viewport
|
||||
std::wstring Terminal::GetHyperlinkAtViewportPosition(const til::point viewportPos)
|
||||
{
|
||||
return GetHyperlinkAtBufferPosition(_ConvertToBufferCell(viewportPos));
|
||||
return GetHyperlinkAtBufferPosition(_ConvertToBufferCell(viewportPos, false));
|
||||
}
|
||||
|
||||
std::wstring Terminal::GetHyperlinkAtBufferPosition(const til::point bufferPos)
|
||||
@ -502,12 +502,8 @@ std::wstring Terminal::GetHyperlinkAtBufferPosition(const til::point bufferPos)
|
||||
result = GetHyperlinkIntervalFromViewportPosition(viewportPos);
|
||||
if (result.has_value())
|
||||
{
|
||||
// GetPlainText and _ConvertToBufferCell work with inclusive coordinates, but interval's
|
||||
// stop point is (horizontally) exclusive, so let's just update it.
|
||||
result->stop.x--;
|
||||
|
||||
result->start = _ConvertToBufferCell(result->start);
|
||||
result->stop = _ConvertToBufferCell(result->stop);
|
||||
result->start = _ConvertToBufferCell(result->start, false);
|
||||
result->stop = _ConvertToBufferCell(result->stop, true);
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -544,7 +540,7 @@ std::wstring Terminal::GetHyperlinkAtBufferPosition(const til::point bufferPos)
|
||||
// - The hyperlink ID
|
||||
uint16_t Terminal::GetHyperlinkIdAtViewportPosition(const til::point viewportPos)
|
||||
{
|
||||
return _activeBuffer().GetCellDataAt(_ConvertToBufferCell(viewportPos))->TextAttr().GetHyperlinkId();
|
||||
return _activeBuffer().GetCellDataAt(_ConvertToBufferCell(viewportPos, false))->TextAttr().GetHyperlinkId();
|
||||
}
|
||||
|
||||
// Method description:
|
||||
@ -1466,7 +1462,6 @@ PointTree Terminal::_getPatterns(til::CoordType beg, til::CoordType end) const
|
||||
// PointTree uses half-open ranges and viewport-relative coordinates.
|
||||
range.start.y -= beg;
|
||||
range.end.y -= beg;
|
||||
range.end.x++;
|
||||
intervals.push_back(PointTree::interval(range.start, range.end, 0));
|
||||
} while (uregex_findNext(re.get(), &status));
|
||||
}
|
||||
|
||||
@ -478,7 +478,7 @@ private:
|
||||
std::vector<til::point_span> _GetSelectionSpans() const noexcept;
|
||||
std::pair<til::point, til::point> _PivotSelection(const til::point targetPos, bool& targetStart) const noexcept;
|
||||
std::pair<til::point, til::point> _ExpandSelectionAnchors(std::pair<til::point, til::point> anchors) const;
|
||||
til::point _ConvertToBufferCell(const til::point viewportPos) const;
|
||||
til::point _ConvertToBufferCell(const til::point viewportPos, bool allowRightExclusive) const;
|
||||
void _ScrollToPoint(const til::point pos);
|
||||
void _MoveByChar(SelectionDirection direction, til::point& pos);
|
||||
void _MoveByWord(SelectionDirection direction, til::point& pos);
|
||||
|
||||
@ -93,14 +93,21 @@ const til::point Terminal::GetSelectionEnd() const noexcept
|
||||
til::point Terminal::SelectionStartForRendering() const
|
||||
{
|
||||
auto pos{ _selection->start };
|
||||
const auto bufferSize{ GetTextBuffer().GetSize() };
|
||||
const auto& buffer = GetTextBuffer();
|
||||
const auto bufferSize{ buffer.GetSize() };
|
||||
if (bufferSize.IsInBounds(pos) && buffer.GetCellDataAt(pos)->DbcsAttr() == DbcsAttribute::Trailing)
|
||||
{
|
||||
// if we're on a trailing byte, move off of it to include it
|
||||
bufferSize.DecrementInExclusiveBounds(pos);
|
||||
}
|
||||
|
||||
if (pos.x != bufferSize.Left())
|
||||
{
|
||||
// In general, we need to draw the marker one before the
|
||||
// beginning of the selection.
|
||||
// When we're at the left boundary, we want to
|
||||
// flip the marker, so we skip this step.
|
||||
bufferSize.DecrementInBounds(pos);
|
||||
bufferSize.DecrementInExclusiveBounds(pos);
|
||||
}
|
||||
pos.y = base::ClampSub(pos.y, _VisibleStartIndex());
|
||||
return til::point{ pos };
|
||||
@ -112,14 +119,21 @@ til::point Terminal::SelectionStartForRendering() const
|
||||
til::point Terminal::SelectionEndForRendering() const
|
||||
{
|
||||
auto pos{ _selection->end };
|
||||
const auto bufferSize{ GetTextBuffer().GetSize() };
|
||||
if (pos.x != bufferSize.RightInclusive())
|
||||
const auto& buffer = GetTextBuffer();
|
||||
const auto bufferSize{ buffer.GetSize() };
|
||||
if (bufferSize.IsInBounds(pos) && buffer.GetCellDataAt(pos)->DbcsAttr() == DbcsAttribute::Trailing)
|
||||
{
|
||||
// In general, we need to draw the marker one after the
|
||||
// end of the selection.
|
||||
// if we're on a trailing byte, move off of it to include it
|
||||
bufferSize.IncrementInExclusiveBounds(pos);
|
||||
}
|
||||
|
||||
if (pos.x == bufferSize.RightExclusive())
|
||||
{
|
||||
// sln->end is exclusive
|
||||
// In general, we need to draw the marker on the same cell.
|
||||
// When we're at the right boundary, we want to
|
||||
// flip the marker, so we skip this step.
|
||||
bufferSize.IncrementInBounds(pos);
|
||||
// flip the marker, so we move one cell to the left.
|
||||
bufferSize.DecrementInExclusiveBounds(pos);
|
||||
}
|
||||
pos.y = base::ClampSub(pos.y, _VisibleStartIndex());
|
||||
return til::point{ pos };
|
||||
@ -157,7 +171,7 @@ void Terminal::MultiClickSelection(const til::point viewportPos, SelectionExpans
|
||||
auto selection{ _selection.write() };
|
||||
wil::hide_name _selection;
|
||||
|
||||
selection->pivot = _ConvertToBufferCell(viewportPos);
|
||||
selection->pivot = _ConvertToBufferCell(viewportPos, true);
|
||||
selection->active = true;
|
||||
|
||||
_multiClickSelectionMode = expansionMode;
|
||||
@ -179,7 +193,7 @@ void Terminal::SetSelectionAnchor(const til::point viewportPos)
|
||||
auto selection{ _selection.write() };
|
||||
wil::hide_name _selection;
|
||||
|
||||
selection->pivot = _ConvertToBufferCell(viewportPos);
|
||||
selection->pivot = _ConvertToBufferCell(viewportPos, true);
|
||||
selection->active = true;
|
||||
|
||||
_multiClickSelectionMode = SelectionExpansion::Char;
|
||||
@ -198,7 +212,7 @@ void Terminal::SetSelectionEnd(const til::point viewportPos, std::optional<Selec
|
||||
// - based on the selection expansion mode
|
||||
// Arguments:
|
||||
// - viewportPos: the (x,y) coordinate on the visible viewport
|
||||
// - newExpansionMode: overwrites the _multiClickSelectionMode for this function call. Used for ShiftClick
|
||||
// - newExpansionMode: overwrites the _multiClickSelectionMode for this function call. Used for Shift+Click
|
||||
void Terminal::_SetSelectionEnd(SelectionInfo* selection, const til::point viewportPos, std::optional<SelectionExpansion> newExpansionMode)
|
||||
{
|
||||
wil::hide_name _selection;
|
||||
@ -209,7 +223,12 @@ void Terminal::_SetSelectionEnd(SelectionInfo* selection, const til::point viewp
|
||||
return;
|
||||
}
|
||||
|
||||
const auto textBufferPos = _ConvertToBufferCell(viewportPos);
|
||||
auto textBufferPos = _ConvertToBufferCell(viewportPos, true);
|
||||
if (newExpansionMode && *newExpansionMode == SelectionExpansion::Char && textBufferPos >= selection->pivot)
|
||||
{
|
||||
// Shift+Click forwards should highlight the clicked space
|
||||
_activeBuffer().GetSize().IncrementInExclusiveBounds(textBufferPos);
|
||||
}
|
||||
|
||||
// if this is a shiftClick action, we need to overwrite the _multiClickSelectionMode value (even if it's the same)
|
||||
// Otherwise, we may accidentally expand during other selection-based actions
|
||||
@ -248,7 +267,7 @@ void Terminal::_SetSelectionEnd(SelectionInfo* selection, const til::point viewp
|
||||
// - the new start/end for a selection
|
||||
std::pair<til::point, til::point> Terminal::_PivotSelection(const til::point targetPos, bool& targetStart) const noexcept
|
||||
{
|
||||
if (targetStart = _activeBuffer().GetSize().CompareInBounds(targetPos, _selection->pivot) <= 0)
|
||||
if (targetStart = targetPos <= _selection->pivot)
|
||||
{
|
||||
// target is before pivot
|
||||
// treat target as start
|
||||
@ -273,17 +292,31 @@ std::pair<til::point, til::point> Terminal::_ExpandSelectionAnchors(std::pair<ti
|
||||
auto start = anchors.first;
|
||||
auto end = anchors.second;
|
||||
|
||||
const auto bufferSize = _activeBuffer().GetSize();
|
||||
const auto& buffer = _activeBuffer();
|
||||
const auto bufferSize = buffer.GetSize();
|
||||
switch (_multiClickSelectionMode)
|
||||
{
|
||||
case SelectionExpansion::Line:
|
||||
start = { bufferSize.Left(), start.y };
|
||||
end = { bufferSize.RightInclusive(), end.y };
|
||||
end = { bufferSize.RightExclusive(), end.y };
|
||||
break;
|
||||
case SelectionExpansion::Word:
|
||||
start = _activeBuffer().GetWordStart(start, _wordDelimiters);
|
||||
end = _activeBuffer().GetWordEnd(end, _wordDelimiters);
|
||||
{
|
||||
start = buffer.GetWordStart2(start, _wordDelimiters, false);
|
||||
|
||||
// GH#5099: We round to the nearest cell boundary,
|
||||
// so we would normally prematurely expand to the next word
|
||||
// as we approach it during a 2x-click+drag.
|
||||
// To remedy this, decrement the end's position by 1.
|
||||
// However, only do this when expanding right (it's correct
|
||||
// as is when expanding left).
|
||||
if (end > _selection->pivot)
|
||||
{
|
||||
bufferSize.DecrementInExclusiveBounds(end);
|
||||
}
|
||||
end = buffer.GetWordEnd2(end, _wordDelimiters, false);
|
||||
break;
|
||||
}
|
||||
case SelectionExpansion::Char:
|
||||
default:
|
||||
// no expansion is necessary
|
||||
@ -376,9 +409,9 @@ void Terminal::ExpandSelectionToWord()
|
||||
const auto& buffer = _activeBuffer();
|
||||
auto selection{ _selection.write() };
|
||||
wil::hide_name _selection;
|
||||
selection->start = buffer.GetWordStart(selection->start, _wordDelimiters);
|
||||
selection->start = buffer.GetWordStart2(selection->start, _wordDelimiters, false);
|
||||
selection->pivot = selection->start;
|
||||
selection->end = buffer.GetWordEnd(selection->end, _wordDelimiters);
|
||||
selection->end = buffer.GetWordEnd2(selection->end, _wordDelimiters, false);
|
||||
|
||||
// if we're targeting both endpoints, instead just target "end"
|
||||
if (WI_IsFlagSet(_selectionEndpoint, SelectionEndpoint::Start) && WI_IsFlagSet(_selectionEndpoint, SelectionEndpoint::End))
|
||||
@ -529,7 +562,6 @@ void Terminal::SelectHyperlink(const SearchDirection dir)
|
||||
selection->start = result->first;
|
||||
selection->pivot = result->first;
|
||||
selection->end = result->second;
|
||||
bufferSize.DecrementInBounds(selection->end);
|
||||
_selectionIsTargetingUrl = true;
|
||||
_selectionEndpoint = SelectionEndpoint::End;
|
||||
}
|
||||
@ -625,7 +657,7 @@ void Terminal::UpdateSelection(SelectionDirection direction, SelectionExpansion
|
||||
}
|
||||
auto targetPos{ WI_IsFlagSet(_selectionEndpoint, SelectionEndpoint::Start) ? _selection->start : _selection->end };
|
||||
|
||||
// 2 Perform the movement
|
||||
// 2. Perform the movement
|
||||
switch (mode)
|
||||
{
|
||||
case SelectionExpansion::Char:
|
||||
@ -695,12 +727,12 @@ void Terminal::_MoveByChar(SelectionDirection direction, til::point& pos)
|
||||
switch (direction)
|
||||
{
|
||||
case SelectionDirection::Left:
|
||||
_activeBuffer().GetSize().DecrementInBounds(pos);
|
||||
pos = _activeBuffer().GetGlyphStart(pos);
|
||||
_activeBuffer().MoveToPreviousGlyph2(pos);
|
||||
break;
|
||||
case SelectionDirection::Right:
|
||||
_activeBuffer().GetSize().IncrementInBounds(pos);
|
||||
pos = _activeBuffer().GetGlyphEnd(pos);
|
||||
// We need the limit to be the mutable viewport here,
|
||||
// otherwise we're allowed to navigate by character past the mutable bottom
|
||||
_activeBuffer().MoveToNextGlyph2(pos, _GetMutableViewport().BottomInclusiveRightExclusive());
|
||||
break;
|
||||
case SelectionDirection::Up:
|
||||
{
|
||||
@ -714,7 +746,7 @@ void Terminal::_MoveByChar(SelectionDirection direction, til::point& pos)
|
||||
const auto bufferSize{ _activeBuffer().GetSize() };
|
||||
const auto mutableBottom{ _GetMutableViewport().BottomInclusive() };
|
||||
const auto newY{ pos.y + 1 };
|
||||
pos = newY > mutableBottom ? til::point{ bufferSize.RightInclusive(), mutableBottom } : til::point{ pos.x, newY };
|
||||
pos = newY > mutableBottom ? til::point{ bufferSize.RightExclusive(), mutableBottom } : til::point{ pos.x, newY };
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -722,61 +754,45 @@ void Terminal::_MoveByChar(SelectionDirection direction, til::point& pos)
|
||||
|
||||
void Terminal::_MoveByWord(SelectionDirection direction, til::point& pos)
|
||||
{
|
||||
const auto& buffer = _activeBuffer();
|
||||
switch (direction)
|
||||
{
|
||||
case SelectionDirection::Left:
|
||||
{
|
||||
const auto wordStartPos{ _activeBuffer().GetWordStart(pos, _wordDelimiters) };
|
||||
if (_activeBuffer().GetSize().CompareInBounds(_selection->pivot, pos) < 0)
|
||||
auto nextPos = pos;
|
||||
nextPos = buffer.GetWordStart2(nextPos, _wordDelimiters, true);
|
||||
if (nextPos == pos)
|
||||
{
|
||||
// If we're moving towards the pivot, move one more cell
|
||||
pos = wordStartPos;
|
||||
_activeBuffer().GetSize().DecrementInBounds(pos);
|
||||
}
|
||||
else if (wordStartPos == pos)
|
||||
{
|
||||
// already at the beginning of the current word,
|
||||
// move to the beginning of the previous word
|
||||
_activeBuffer().GetSize().DecrementInBounds(pos);
|
||||
pos = _activeBuffer().GetWordStart(pos, _wordDelimiters);
|
||||
}
|
||||
else
|
||||
{
|
||||
// move to the beginning of the current word
|
||||
pos = wordStartPos;
|
||||
// didn't move because we're already at the beginning of a word,
|
||||
// so move to the beginning of the previous word
|
||||
buffer.GetSize().DecrementInExclusiveBounds(nextPos);
|
||||
nextPos = buffer.GetWordStart2(nextPos, _wordDelimiters, true);
|
||||
}
|
||||
pos = nextPos;
|
||||
break;
|
||||
}
|
||||
case SelectionDirection::Right:
|
||||
{
|
||||
const auto wordEndPos{ _activeBuffer().GetWordEnd(pos, _wordDelimiters) };
|
||||
if (_activeBuffer().GetSize().CompareInBounds(pos, _selection->pivot) < 0)
|
||||
const auto mutableViewportEndExclusive = _GetMutableViewport().BottomInclusiveRightExclusive();
|
||||
auto nextPos = pos;
|
||||
nextPos = buffer.GetWordEnd2(nextPos, _wordDelimiters, true, mutableViewportEndExclusive);
|
||||
if (nextPos == pos)
|
||||
{
|
||||
// If we're moving towards the pivot, move one more cell
|
||||
pos = _activeBuffer().GetWordEnd(pos, _wordDelimiters);
|
||||
_activeBuffer().GetSize().IncrementInBounds(pos);
|
||||
}
|
||||
else if (wordEndPos == pos)
|
||||
{
|
||||
// already at the end of the current word,
|
||||
// move to the end of the next word
|
||||
_activeBuffer().GetSize().IncrementInBounds(pos);
|
||||
pos = _activeBuffer().GetWordEnd(pos, _wordDelimiters);
|
||||
}
|
||||
else
|
||||
{
|
||||
// move to the end of the current word
|
||||
pos = wordEndPos;
|
||||
// didn't move because we're already at the end of a word,
|
||||
// so move to the end of the next word
|
||||
buffer.GetSize().IncrementInExclusiveBounds(nextPos);
|
||||
nextPos = buffer.GetWordEnd2(nextPos, _wordDelimiters, true, mutableViewportEndExclusive);
|
||||
}
|
||||
pos = nextPos;
|
||||
break;
|
||||
}
|
||||
case SelectionDirection::Up:
|
||||
_MoveByChar(direction, pos);
|
||||
pos = _activeBuffer().GetWordStart(pos, _wordDelimiters);
|
||||
pos = buffer.GetWordStart2(pos, _wordDelimiters, true);
|
||||
break;
|
||||
case SelectionDirection::Down:
|
||||
_MoveByChar(direction, pos);
|
||||
pos = _activeBuffer().GetWordEnd(pos, _wordDelimiters);
|
||||
pos = buffer.GetWordEnd2(pos, _wordDelimiters, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -790,7 +806,7 @@ void Terminal::_MoveByViewport(SelectionDirection direction, til::point& pos) no
|
||||
pos = { bufferSize.Left(), pos.y };
|
||||
break;
|
||||
case SelectionDirection::Right:
|
||||
pos = { bufferSize.RightInclusive(), pos.y };
|
||||
pos = { bufferSize.RightExclusive(), pos.y };
|
||||
break;
|
||||
case SelectionDirection::Up:
|
||||
{
|
||||
@ -804,7 +820,7 @@ void Terminal::_MoveByViewport(SelectionDirection direction, til::point& pos) no
|
||||
const auto viewportHeight{ _GetMutableViewport().Height() };
|
||||
const auto mutableBottom{ _GetMutableViewport().BottomInclusive() };
|
||||
const auto newY{ pos.y + viewportHeight };
|
||||
pos = newY > mutableBottom ? til::point{ bufferSize.RightInclusive(), mutableBottom } : til::point{ pos.x, newY };
|
||||
pos = newY > mutableBottom ? til::point{ bufferSize.RightExclusive(), mutableBottom } : til::point{ pos.x, newY };
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -821,7 +837,7 @@ void Terminal::_MoveByBuffer(SelectionDirection direction, til::point& pos) noex
|
||||
break;
|
||||
case SelectionDirection::Right:
|
||||
case SelectionDirection::Down:
|
||||
pos = { bufferSize.RightInclusive(), _GetMutableViewport().BottomInclusive() };
|
||||
pos = { bufferSize.RightExclusive(), _GetMutableViewport().BottomInclusive() };
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -901,13 +917,17 @@ Terminal::TextCopyData Terminal::RetrieveSelectedTextFromBuffer(const bool singl
|
||||
// - convert viewport position to the corresponding location on the buffer
|
||||
// Arguments:
|
||||
// - viewportPos: a coordinate on the viewport
|
||||
// - allowRightExclusive: if true, clamp to the right exclusive boundary of the buffer.
|
||||
// Careful! This position doesn't point to any data in the buffer!
|
||||
// Return Value:
|
||||
// - the corresponding location on the buffer
|
||||
til::point Terminal::_ConvertToBufferCell(const til::point viewportPos) const
|
||||
til::point Terminal::_ConvertToBufferCell(const til::point viewportPos, bool allowRightExclusive) const
|
||||
{
|
||||
const auto yPos = _VisibleStartIndex() + viewportPos.y;
|
||||
til::point bufferPos = { viewportPos.x, yPos };
|
||||
_activeBuffer().GetSize().Clamp(bufferPos);
|
||||
const auto bufferSize = _activeBuffer().GetSize();
|
||||
bufferPos.x = std::clamp(bufferPos.x, bufferSize.Left(), allowRightExclusive ? bufferSize.RightExclusive() : bufferSize.RightInclusive());
|
||||
bufferPos.y = std::clamp(bufferPos.y, bufferSize.Top(), bufferSize.BottomInclusive());
|
||||
return bufferPos;
|
||||
}
|
||||
|
||||
@ -917,7 +937,7 @@ til::point Terminal::_ConvertToBufferCell(const til::point viewportPos) const
|
||||
// - pos: a coordinate relative to the buffer (not viewport)
|
||||
void Terminal::_ScrollToPoint(const til::point pos)
|
||||
{
|
||||
if (const auto visibleViewport = _GetVisibleViewport(); !visibleViewport.IsInBounds(pos))
|
||||
if (const auto visibleViewport = _GetVisibleViewport(); !visibleViewport.IsInExclusiveBounds(pos))
|
||||
{
|
||||
if (const auto amtAboveView = visibleViewport.Top() - pos.y; amtAboveView > 0)
|
||||
{
|
||||
|
||||
@ -201,6 +201,11 @@ til::CoordType Terminal::_ScrollToPoints(const til::point coordStart, const til:
|
||||
return _VisibleStartIndex();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - selects the region from coordStart to coordEnd
|
||||
// Arguments:
|
||||
// - coordStart - The start point (inclusive)
|
||||
// - coordEnd - The end point (inclusive)
|
||||
void Terminal::SelectNewRegion(const til::point coordStart, const til::point coordEnd)
|
||||
{
|
||||
const auto newScrollOffset = _ScrollToPoints(coordStart, coordEnd);
|
||||
@ -210,6 +215,7 @@ void Terminal::SelectNewRegion(const til::point coordStart, const til::point coo
|
||||
const auto newCoordEnd = til::point{ coordEnd.x, coordEnd.y - newScrollOffset };
|
||||
SetSelectionAnchor(newCoordStart);
|
||||
SetSelectionEnd(newCoordEnd, SelectionExpansion::Char);
|
||||
_activeBuffer().TriggerSelection();
|
||||
}
|
||||
|
||||
const std::wstring_view Terminal::GetConsoleTitle() const noexcept
|
||||
|
||||
@ -419,7 +419,7 @@ namespace ControlUnitTests
|
||||
const auto& start = core->_terminal->GetSelectionAnchor();
|
||||
const auto& end = core->_terminal->GetSelectionEnd();
|
||||
const til::point expectedStart{ 17, 0 };
|
||||
const til::point expectedEnd{ 23, 0 };
|
||||
const til::point expectedEnd{ 24, 0 };
|
||||
VERIFY_ARE_EQUAL(expectedStart, start);
|
||||
VERIFY_ARE_EQUAL(expectedEnd, end);
|
||||
}
|
||||
@ -440,7 +440,7 @@ namespace ControlUnitTests
|
||||
const auto& start = core->_terminal->GetSelectionAnchor();
|
||||
const auto& end = core->_terminal->GetSelectionEnd();
|
||||
const til::point expectedStart{ 17, 4 };
|
||||
const til::point expectedEnd{ 23, 4 };
|
||||
const til::point expectedEnd{ 24, 4 };
|
||||
VERIFY_ARE_EQUAL(expectedStart, start);
|
||||
VERIFY_ARE_EQUAL(expectedEnd, end);
|
||||
}
|
||||
@ -450,7 +450,7 @@ namespace ControlUnitTests
|
||||
const auto& start = core->_terminal->GetSelectionAnchor();
|
||||
const auto& end = core->_terminal->GetSelectionEnd();
|
||||
const til::point expectedStart{ 17, 0 };
|
||||
const til::point expectedEnd{ 23, 0 };
|
||||
const til::point expectedEnd{ 24, 0 };
|
||||
VERIFY_ARE_EQUAL(expectedStart, start);
|
||||
VERIFY_ARE_EQUAL(expectedEnd, end);
|
||||
}
|
||||
@ -460,7 +460,7 @@ namespace ControlUnitTests
|
||||
const auto& start = core->_terminal->GetSelectionAnchor();
|
||||
const auto& end = core->_terminal->GetSelectionEnd();
|
||||
const til::point expectedStart{ 17, 4 };
|
||||
const til::point expectedEnd{ 23, 4 };
|
||||
const til::point expectedEnd{ 24, 4 };
|
||||
VERIFY_ARE_EQUAL(expectedStart, start);
|
||||
VERIFY_ARE_EQUAL(expectedEnd, end);
|
||||
}
|
||||
@ -502,7 +502,7 @@ namespace ControlUnitTests
|
||||
const auto& start = core->_terminal->GetSelectionAnchor();
|
||||
const auto& end = core->_terminal->GetSelectionEnd();
|
||||
const til::point expectedStart{ 24, 0 }; // The character after the prompt
|
||||
const til::point expectedEnd{ 21, 3 }; // x = the end of the text
|
||||
const til::point expectedEnd{ 22, 3 }; // x = the end of the text + 1 (exclusive end)
|
||||
VERIFY_ARE_EQUAL(expectedStart, start);
|
||||
VERIFY_ARE_EQUAL(expectedEnd, end);
|
||||
}
|
||||
@ -663,7 +663,7 @@ namespace ControlUnitTests
|
||||
const auto& start = core->_terminal->GetSelectionAnchor();
|
||||
const auto& end = core->_terminal->GetSelectionEnd();
|
||||
const til::point expectedStart{ 20, 4 }; // The character after the prompt
|
||||
const til::point expectedEnd{ 26, 34 }; // x = the end of the text
|
||||
const til::point expectedEnd{ 27, 34 }; // x = the end of the text + 1 (exclusive end)
|
||||
VERIFY_ARE_EQUAL(expectedStart, start);
|
||||
VERIFY_ARE_EQUAL(expectedEnd, end);
|
||||
}
|
||||
@ -673,7 +673,7 @@ namespace ControlUnitTests
|
||||
const auto& start = core->_terminal->GetSelectionAnchor();
|
||||
const auto& end = core->_terminal->GetSelectionEnd();
|
||||
const til::point expectedStart{ 24, 0 }; // The character after the prompt
|
||||
const til::point expectedEnd{ 21, 3 }; // x = the end of the text
|
||||
const til::point expectedEnd{ 22, 3 }; // x = the end of the text + 1 (exclusive end)
|
||||
VERIFY_ARE_EQUAL(expectedStart, start);
|
||||
VERIFY_ARE_EQUAL(expectedEnd, end);
|
||||
}
|
||||
@ -731,7 +731,7 @@ namespace ControlUnitTests
|
||||
const auto& start = core->_terminal->GetSelectionAnchor();
|
||||
const auto& end = core->_terminal->GetSelectionEnd();
|
||||
const til::point expectedStart{ 20, 4 }; // The character after the prompt
|
||||
const til::point expectedEnd{ 29, 34 }; // x = the end of the text
|
||||
const til::point expectedEnd{ 30, 34 }; // x = the end of the text + 1 (exclusive end)
|
||||
VERIFY_ARE_EQUAL(expectedStart, start);
|
||||
VERIFY_ARE_EQUAL(expectedEnd, end);
|
||||
}
|
||||
@ -741,7 +741,7 @@ namespace ControlUnitTests
|
||||
const auto& start = core->_terminal->GetSelectionAnchor();
|
||||
const auto& end = core->_terminal->GetSelectionEnd();
|
||||
const til::point expectedStart{ 24, 0 }; // The character after the prompt
|
||||
const til::point expectedEnd{ 21, 3 }; // x = the end of the text
|
||||
const til::point expectedEnd{ 22, 3 }; // x = the end of the text + 1 (exclusive end)
|
||||
VERIFY_ARE_EQUAL(expectedStart, start);
|
||||
VERIFY_ARE_EQUAL(expectedEnd, end);
|
||||
}
|
||||
@ -804,7 +804,7 @@ namespace ControlUnitTests
|
||||
const auto& start = core->_terminal->GetSelectionAnchor();
|
||||
const auto& end = core->_terminal->GetSelectionEnd();
|
||||
const til::point expectedStart{ 1, 1 };
|
||||
const til::point expectedEnd{ 1, 2 };
|
||||
const til::point expectedEnd{ 2, 2 };
|
||||
VERIFY_ARE_EQUAL(expectedStart, start);
|
||||
VERIFY_ARE_EQUAL(expectedEnd, end);
|
||||
}
|
||||
|
||||
@ -438,8 +438,9 @@ namespace ControlUnitTests
|
||||
// The viewport is on row 21, so the selection will be on:
|
||||
// {(5, 5)+(0, 21)} to {(5, 5)+(0, 21)}
|
||||
til::point expectedAnchor{ 5, 26 };
|
||||
til::point expectedEnd{ 6, 26 }; // add 1 to x-coordinate because end is exclusive
|
||||
VERIFY_ARE_EQUAL(expectedAnchor, core->_terminal->GetSelectionAnchor());
|
||||
VERIFY_ARE_EQUAL(expectedAnchor, core->_terminal->GetSelectionEnd());
|
||||
VERIFY_ARE_EQUAL(expectedEnd, core->_terminal->GetSelectionEnd());
|
||||
|
||||
Log::Comment(L"Scroll up a line, with the left mouse button selected");
|
||||
interactivity->MouseWheel(modifiers,
|
||||
@ -449,10 +450,11 @@ namespace ControlUnitTests
|
||||
|
||||
Log::Comment(L"Verify the location of the selection");
|
||||
// The viewport is now on row 20, so the selection will be on:
|
||||
// {(5, 5)+(0, 20)} to {(5, 5)+(0, 21)}
|
||||
til::point newExpectedAnchor{ 5, 25 };
|
||||
// {(5 + 1, 5)+(0, 20)} to {(5, 5)+(0, 21)}
|
||||
// NOTE: newExpectedAnchor should be expectedEnd moved up one row
|
||||
til::point newExpectedAnchor{ 6, 25 };
|
||||
// Remember, the anchor is always before the end in the buffer. So yes,
|
||||
// se started the selection on 5,26, but now that's the end.
|
||||
// we started the selection on 5,26, but now that's the end.
|
||||
VERIFY_ARE_EQUAL(newExpectedAnchor, core->_terminal->GetSelectionAnchor());
|
||||
VERIFY_ARE_EQUAL(expectedAnchor, core->_terminal->GetSelectionEnd());
|
||||
}
|
||||
@ -628,7 +630,7 @@ namespace ControlUnitTests
|
||||
Log::Comment(L"Verify that it started on the first cell we clicked on, not the one we dragged to");
|
||||
til::point expectedAnchor{ 0, 0 };
|
||||
VERIFY_ARE_EQUAL(expectedAnchor, core->_terminal->GetSelectionAnchor());
|
||||
til::point expectedEnd{ 2, 0 };
|
||||
til::point expectedEnd{ 3, 0 }; // add 1 to x-coordinate because end is exclusive
|
||||
VERIFY_ARE_EQUAL(expectedEnd, core->_terminal->GetSelectionEnd());
|
||||
|
||||
interactivity->PointerReleased(noMouseDown,
|
||||
@ -798,8 +800,9 @@ namespace ControlUnitTests
|
||||
// The viewport is on row (historySize + 5), so the selection will be on:
|
||||
// {(5, (historySize+5))+(0, 21)} to {(5, (historySize+5))+(0, 21)}
|
||||
til::point expectedAnchor{ 5, settings->HistorySize() + 5 };
|
||||
til::point expectedEnd{ 6, expectedAnchor.y }; // add 1 to x-coordinate because end is exclusive
|
||||
VERIFY_ARE_EQUAL(expectedAnchor, core->_terminal->GetSelectionAnchor());
|
||||
VERIFY_ARE_EQUAL(expectedAnchor, core->_terminal->GetSelectionEnd());
|
||||
VERIFY_ARE_EQUAL(expectedEnd, core->_terminal->GetSelectionEnd());
|
||||
|
||||
Log::Comment(L"Output a line of text");
|
||||
conn->WriteInput(winrt_wstring_to_array_view(L"Foo\r\n"));
|
||||
@ -807,6 +810,7 @@ namespace ControlUnitTests
|
||||
Log::Comment(L"Verify the location of the selection");
|
||||
// The selection should now be 1 row lower
|
||||
expectedAnchor.y -= 1;
|
||||
expectedEnd.y -= 1;
|
||||
{
|
||||
const auto anchor{ core->_terminal->GetSelectionAnchor() };
|
||||
const auto end{ core->_terminal->GetSelectionEnd() };
|
||||
@ -815,7 +819,7 @@ namespace ControlUnitTests
|
||||
Log::Comment(fmt::format(L"end:({},{})", end.x, end.y).c_str());
|
||||
|
||||
VERIFY_ARE_EQUAL(expectedAnchor, anchor);
|
||||
VERIFY_ARE_EQUAL(expectedAnchor, end);
|
||||
VERIFY_ARE_EQUAL(expectedEnd, end);
|
||||
}
|
||||
VERIFY_ARE_EQUAL(scrollbackLength - 1, core->_terminal->GetScrollOffset());
|
||||
|
||||
@ -825,6 +829,7 @@ namespace ControlUnitTests
|
||||
Log::Comment(L"Verify the location of the selection");
|
||||
// The selection should now be 1 row lower
|
||||
expectedAnchor.y -= 1;
|
||||
expectedEnd.y -= 1;
|
||||
{
|
||||
const auto anchor{ core->_terminal->GetSelectionAnchor() };
|
||||
const auto end{ core->_terminal->GetSelectionEnd() };
|
||||
@ -833,7 +838,7 @@ namespace ControlUnitTests
|
||||
Log::Comment(fmt::format(L"end:({},{})", end.x, end.y).c_str());
|
||||
|
||||
VERIFY_ARE_EQUAL(expectedAnchor, anchor);
|
||||
VERIFY_ARE_EQUAL(expectedAnchor, end);
|
||||
VERIFY_ARE_EQUAL(expectedEnd, end);
|
||||
}
|
||||
VERIFY_ARE_EQUAL(scrollbackLength - 2, core->_terminal->GetScrollOffset());
|
||||
|
||||
@ -858,15 +863,17 @@ namespace ControlUnitTests
|
||||
Log::Comment(fmt::format(L"anchor:({},{})", anchor.x, anchor.y).c_str());
|
||||
Log::Comment(fmt::format(L"end:({},{})", end.x, end.y).c_str());
|
||||
|
||||
// Selection was updated, but we didn't highlight a full cell
|
||||
VERIFY_ARE_EQUAL(expectedAnchor, anchor);
|
||||
VERIFY_ARE_EQUAL(expectedAnchor, end);
|
||||
}
|
||||
|
||||
Log::Comment(L"Output a line ant move the mouse a little to update the selection, all at once");
|
||||
Log::Comment(L"Output a line and move the mouse a little to update the selection, all at once");
|
||||
// Same as above. The viewport has moved, so the mouse is still over the
|
||||
// same character, even though it's at a new offset.
|
||||
conn->WriteInput(winrt_wstring_to_array_view(L"Foo\r\n"));
|
||||
expectedAnchor.y -= 1;
|
||||
expectedEnd.y -= 1;
|
||||
VERIFY_ARE_EQUAL(scrollbackLength - 3, core->_terminal->GetScrollOffset());
|
||||
interactivity->PointerMoved(leftMouseDown,
|
||||
WM_LBUTTONDOWN, //pointerUpdateKind
|
||||
@ -883,7 +890,7 @@ namespace ControlUnitTests
|
||||
Log::Comment(fmt::format(L"end:({},{})", end.x, end.y).c_str());
|
||||
|
||||
VERIFY_ARE_EQUAL(expectedAnchor, anchor);
|
||||
VERIFY_ARE_EQUAL(expectedAnchor, end);
|
||||
VERIFY_ARE_EQUAL(expectedEnd, end);
|
||||
}
|
||||
|
||||
// Output enough text for the selection to get pushed off the buffer
|
||||
|
||||
@ -126,20 +126,20 @@ namespace TerminalCoreUnitTests
|
||||
|
||||
// Test with no scrollback
|
||||
Log::Comment(L"Single click selection with NO scrollback value");
|
||||
ValidateSingleClickSelection(0, { 9, 9 }, { 9, 9 });
|
||||
ValidateSingleClickSelection(0, { 10, 9 }, { 10, 9 });
|
||||
Log::Comment(L"Double click selection with NO scrollback value");
|
||||
ValidateDoubleClickSelection(0, { 0, 9 }, { 9, 9 });
|
||||
ValidateDoubleClickSelection(0, { 0, 9 }, { 10, 9 });
|
||||
Log::Comment(L"Triple click selection with NO scrollback value");
|
||||
ValidateTripleClickSelection(0, { 0, 9 }, { 9, 9 });
|
||||
ValidateTripleClickSelection(0, { 0, 9 }, { 10, 9 });
|
||||
|
||||
// Test with max scrollback
|
||||
const til::CoordType expected_row = SHRT_MAX - 1;
|
||||
Log::Comment(L"Single click selection with MAXIMUM scrollback value");
|
||||
ValidateSingleClickSelection(SHRT_MAX, { 9, expected_row }, { 9, expected_row });
|
||||
ValidateSingleClickSelection(SHRT_MAX, { 10, expected_row }, { 10, expected_row });
|
||||
Log::Comment(L"Double click selection with MAXIMUM scrollback value");
|
||||
ValidateDoubleClickSelection(SHRT_MAX, { 0, expected_row }, { 9, expected_row });
|
||||
ValidateDoubleClickSelection(SHRT_MAX, { 0, expected_row }, { 10, expected_row });
|
||||
Log::Comment(L"Triple click selection with MAXIMUM scrollback value");
|
||||
ValidateTripleClickSelection(SHRT_MAX, { 0, expected_row }, { 9, expected_row });
|
||||
ValidateTripleClickSelection(SHRT_MAX, { 0, expected_row }, { 10, expected_row });
|
||||
}
|
||||
|
||||
TEST_METHOD(SelectFromOutofBounds)
|
||||
@ -155,7 +155,7 @@ namespace TerminalCoreUnitTests
|
||||
|
||||
auto viewport = term.GetViewport();
|
||||
const auto leftBoundary = viewport.Left();
|
||||
const auto rightBoundary = viewport.RightInclusive();
|
||||
const auto rightExclusiveBoundary = viewport.RightExclusive();
|
||||
const auto topBoundary = viewport.Top();
|
||||
const auto bottomBoundary = viewport.BottomInclusive();
|
||||
|
||||
@ -163,7 +163,7 @@ namespace TerminalCoreUnitTests
|
||||
// should clamp to right boundary
|
||||
term.SetSelectionAnchor({ 20, 5 });
|
||||
Log::Comment(L"Out of bounds: X-value too large");
|
||||
ValidateLinearSelection(term, { rightBoundary, 5 }, { rightBoundary, 5 });
|
||||
ValidateLinearSelection(term, { rightExclusiveBoundary, 5 }, { rightExclusiveBoundary, 5 });
|
||||
|
||||
// Case 2: Simulate click past left (x,y) = (-20,5)
|
||||
// should clamp to left boundary
|
||||
@ -197,7 +197,7 @@ namespace TerminalCoreUnitTests
|
||||
|
||||
auto viewport = term.GetViewport();
|
||||
const til::CoordType leftBoundary = 0;
|
||||
const auto rightBoundary = viewport.RightInclusive();
|
||||
const auto rightExclusiveBoundary = viewport.RightExclusive();
|
||||
|
||||
// Simulate click at (x,y) = (5,5)
|
||||
term.SetSelectionAnchor({ 5, 5 });
|
||||
@ -205,7 +205,7 @@ namespace TerminalCoreUnitTests
|
||||
// Case 1: Move out of right boundary
|
||||
Log::Comment(L"Out of bounds: X-value too large");
|
||||
term.SetSelectionEnd({ 20, 5 });
|
||||
ValidateLinearSelection(term, { 5, 5 }, { rightBoundary, 5 });
|
||||
ValidateLinearSelection(term, { 5, 5 }, { rightExclusiveBoundary, 5 });
|
||||
|
||||
// Case 2: Move out of left boundary
|
||||
Log::Comment(L"Out of bounds: X-value negative");
|
||||
@ -312,7 +312,7 @@ namespace TerminalCoreUnitTests
|
||||
|
||||
// Validate selection area
|
||||
// Selection should expand one to the left to get the leading half of the wide glyph
|
||||
ValidateLinearSelection(term, { 4, 10 }, { 5, 10 });
|
||||
ValidateLinearSelection(term, { 4, 10 }, { 6, 10 });
|
||||
}
|
||||
|
||||
TEST_METHOD(SelectWideGlyph_Leading)
|
||||
@ -334,8 +334,8 @@ namespace TerminalCoreUnitTests
|
||||
term.SetSelectionAnchor(clickPos);
|
||||
|
||||
// Validate selection area
|
||||
// Selection should expand one to the left to get the leading half of the wide glyph
|
||||
ValidateLinearSelection(term, { 4, 10 }, { 5, 10 });
|
||||
// Selection should clamp to the left side of the glyph and stay degenerate
|
||||
ValidateLinearSelection(term, { 4, 10 }, { 4, 10 });
|
||||
}
|
||||
|
||||
TEST_METHOD(SelectWideGlyphsInBoxSelection)
|
||||
@ -356,12 +356,26 @@ namespace TerminalCoreUnitTests
|
||||
GetTextBuffer(term).GetCursor().SetPosition({ 7, 11 });
|
||||
term.Write(burrito);
|
||||
|
||||
// Text buffer should look like this:
|
||||
// -------------
|
||||
// | A |
|
||||
// | |
|
||||
// | 🌯 |
|
||||
// | 🌯 |
|
||||
// | B |
|
||||
// -------------
|
||||
// A: selection anchor
|
||||
// B: selection end
|
||||
// The boundaries of the selection should cut through
|
||||
// the middle of the burritos, but the selection
|
||||
// should expand to encompass each burrito entirely.
|
||||
|
||||
// Simulate ALT + click at (x,y) = (5,8)
|
||||
term.SetSelectionAnchor({ 5, 8 });
|
||||
term.SetBlockSelection(true);
|
||||
|
||||
// Simulate move to (x,y) = (7,12)
|
||||
term.SetSelectionEnd({ 7, 12 });
|
||||
// Simulate move to (x,y) = (8,12)
|
||||
term.SetSelectionEnd({ 8, 12 });
|
||||
|
||||
// Simulate renderer calling TriggerSelection and acquiring selection area
|
||||
auto selectionSpans = term.GetSelectionSpans();
|
||||
@ -376,18 +390,18 @@ namespace TerminalCoreUnitTests
|
||||
if (rowValue == 10)
|
||||
{
|
||||
VERIFY_ARE_EQUAL((til::point{ 4, rowValue }), sp.start);
|
||||
VERIFY_ARE_EQUAL((til::point{ 7, rowValue }), sp.end);
|
||||
VERIFY_ARE_EQUAL((til::point{ 8, rowValue }), sp.end);
|
||||
}
|
||||
else if (rowValue == 11)
|
||||
{
|
||||
VERIFY_ARE_EQUAL((til::point{ 5, rowValue }), sp.start);
|
||||
VERIFY_ARE_EQUAL((til::point{ 8, rowValue }), sp.end);
|
||||
VERIFY_ARE_EQUAL((til::point{ 9, rowValue }), sp.end);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Verify all lines
|
||||
VERIFY_ARE_EQUAL((til::point{ 5, rowValue }), sp.start);
|
||||
VERIFY_ARE_EQUAL((til::point{ 7, rowValue }), sp.end);
|
||||
VERIFY_ARE_EQUAL((til::point{ 8, rowValue }), sp.end);
|
||||
}
|
||||
|
||||
rowValue++;
|
||||
@ -414,7 +428,7 @@ namespace TerminalCoreUnitTests
|
||||
term.MultiClickSelection(clickPos, Terminal::SelectionExpansion::Word);
|
||||
|
||||
// Validate selection area
|
||||
ValidateLinearSelection(term, { 4, 10 }, { gsl::narrow<til::CoordType>(4 + text.size() - 1), 10 });
|
||||
ValidateLinearSelection(term, { 4, 10 }, { gsl::narrow<til::CoordType>(4 + text.size()), 10 });
|
||||
}
|
||||
|
||||
TEST_METHOD(DoubleClick_Delimiter)
|
||||
@ -432,7 +446,7 @@ namespace TerminalCoreUnitTests
|
||||
term.MultiClickSelection(clickPos, Terminal::SelectionExpansion::Word);
|
||||
|
||||
// Validate selection area
|
||||
ValidateLinearSelection(term, { 0, 10 }, { 99, 10 });
|
||||
ValidateLinearSelection(term, { 0, 10 }, { term.GetViewport().RightExclusive(), 10 });
|
||||
}
|
||||
|
||||
TEST_METHOD(DoubleClick_DelimiterClass)
|
||||
@ -457,10 +471,10 @@ namespace TerminalCoreUnitTests
|
||||
|
||||
// ---Validate selection area---
|
||||
// "Terminal" is in class 2
|
||||
// ">" is in class 1
|
||||
// ":" and ">" are in class 1
|
||||
// the white space to the right of the ">" is in class 0
|
||||
// Double-clicking the ">" should only highlight that cell
|
||||
ValidateLinearSelection(term, { 15, 10 }, { 15, 10 });
|
||||
ValidateLinearSelection(term, { 15, 10 }, { 16, 10 });
|
||||
}
|
||||
|
||||
TEST_METHOD(DoubleClickDrag_Right)
|
||||
@ -489,7 +503,7 @@ namespace TerminalCoreUnitTests
|
||||
term.SetSelectionEnd({ 21, 10 });
|
||||
|
||||
// Validate selection area
|
||||
ValidateLinearSelection(term, { 4, 10 }, { 32, 10 });
|
||||
ValidateLinearSelection(term, { 4, 10 }, { 33, 10 });
|
||||
}
|
||||
|
||||
TEST_METHOD(DoubleClickDrag_Left)
|
||||
@ -502,7 +516,7 @@ namespace TerminalCoreUnitTests
|
||||
auto settings = winrt::make<MockTermSettings>(0, 100, 100);
|
||||
term.UpdateSettings(settings);
|
||||
|
||||
// Insert text at position (21,10)
|
||||
// Insert text at position (4,10)
|
||||
const std::wstring_view text = L"doubleClickMe dragThroughHere";
|
||||
GetTextBuffer(term).GetCursor().SetPosition({ 4, 10 });
|
||||
term.Write(text);
|
||||
@ -513,12 +527,12 @@ namespace TerminalCoreUnitTests
|
||||
// Simulate move to (x,y) = (5,10)
|
||||
//
|
||||
// buffer: doubleClickMe dragThroughHere
|
||||
// ^ ^
|
||||
// finish start
|
||||
// ^ ^
|
||||
// finish start
|
||||
term.SetSelectionEnd({ 5, 10 });
|
||||
|
||||
// Validate selection area
|
||||
ValidateLinearSelection(term, { 4, 10 }, { 32, 10 });
|
||||
ValidateLinearSelection(term, { 4, 10 }, { 33, 10 });
|
||||
}
|
||||
|
||||
TEST_METHOD(TripleClick_GeneralCase)
|
||||
@ -532,7 +546,7 @@ namespace TerminalCoreUnitTests
|
||||
term.MultiClickSelection(clickPos, Terminal::SelectionExpansion::Line);
|
||||
|
||||
// Validate selection area
|
||||
ValidateLinearSelection(term, { 0, 10 }, { 99, 10 });
|
||||
ValidateLinearSelection(term, { 0, 10 }, { term.GetViewport().RightExclusive(), 10 });
|
||||
}
|
||||
|
||||
TEST_METHOD(TripleClickDrag_Horizontal)
|
||||
@ -549,7 +563,7 @@ namespace TerminalCoreUnitTests
|
||||
term.SetSelectionEnd({ 7, 10 });
|
||||
|
||||
// Validate selection area
|
||||
ValidateLinearSelection(term, { 0, 10 }, { 99, 10 });
|
||||
ValidateLinearSelection(term, { 0, 10 }, { term.GetViewport().RightExclusive(), 10 });
|
||||
}
|
||||
|
||||
TEST_METHOD(TripleClickDrag_Vertical)
|
||||
@ -565,7 +579,7 @@ namespace TerminalCoreUnitTests
|
||||
// Simulate move to (x,y) = (5,11)
|
||||
term.SetSelectionEnd({ 5, 11 });
|
||||
|
||||
ValidateLinearSelection(term, { 0, 10 }, { 99, 11 });
|
||||
ValidateLinearSelection(term, { 0, 10 }, { term.GetViewport().RightExclusive(), 11 });
|
||||
}
|
||||
|
||||
TEST_METHOD(ShiftClick)
|
||||
@ -579,20 +593,20 @@ namespace TerminalCoreUnitTests
|
||||
term.UpdateSettings(settings);
|
||||
|
||||
// Insert text at position (4,10)
|
||||
const std::wstring_view text = L"doubleClickMe dragThroughHere";
|
||||
const std::wstring_view text = L"doubleClickMe dragThroughHere anotherWord";
|
||||
GetTextBuffer(term).GetCursor().SetPosition({ 4, 10 });
|
||||
term.Write(text);
|
||||
|
||||
// Step 1: Create a selection on "doubleClickMe"
|
||||
Log::Comment(L"Step 1 : Create a selection on \"doubleClickMe\"");
|
||||
{
|
||||
// Simulate double click at (x,y) = (5,10)
|
||||
term.MultiClickSelection({ 5, 10 }, Terminal::SelectionExpansion::Word);
|
||||
|
||||
// Validate selection area: "doubleClickMe" selected
|
||||
ValidateLinearSelection(term, { 4, 10 }, { 16, 10 });
|
||||
ValidateLinearSelection(term, { 4, 10 }, { 17, 10 });
|
||||
}
|
||||
|
||||
// Step 2: Shift+Click to "dragThroughHere"
|
||||
Log::Comment(L"Step 2: Shift+Click to \"dragThroughHere\"");
|
||||
{
|
||||
// Simulate Shift+Click at (x,y) = (21,10)
|
||||
//
|
||||
@ -602,10 +616,10 @@ namespace TerminalCoreUnitTests
|
||||
term.SetSelectionEnd({ 21, 10 }, Terminal::SelectionExpansion::Char);
|
||||
|
||||
// Validate selection area: "doubleClickMe drag" selected
|
||||
ValidateLinearSelection(term, { 4, 10 }, { 21, 10 });
|
||||
ValidateLinearSelection(term, { 4, 10 }, { 22, 10 });
|
||||
}
|
||||
|
||||
// Step 3: Shift+Double-Click at "dragThroughHere"
|
||||
Log::Comment(L"Step 3: Shift+Double-Click at \"dragThroughHere\"");
|
||||
{
|
||||
// Simulate Shift+DoubleClick at (x,y) = (21,10)
|
||||
//
|
||||
@ -615,10 +629,10 @@ namespace TerminalCoreUnitTests
|
||||
term.SetSelectionEnd({ 21, 10 }, Terminal::SelectionExpansion::Word);
|
||||
|
||||
// Validate selection area: "doubleClickMe dragThroughHere" selected
|
||||
ValidateLinearSelection(term, { 4, 10 }, { 32, 10 });
|
||||
ValidateLinearSelection(term, { 4, 10 }, { 33, 10 });
|
||||
}
|
||||
|
||||
// Step 4: Shift+Triple-Click at "dragThroughHere"
|
||||
Log::Comment(L"Step 4: Shift+Triple-Click at \"dragThroughHere\"");
|
||||
{
|
||||
// Simulate Shift+TripleClick at (x,y) = (21,10)
|
||||
//
|
||||
@ -628,60 +642,62 @@ namespace TerminalCoreUnitTests
|
||||
term.SetSelectionEnd({ 21, 10 }, Terminal::SelectionExpansion::Line);
|
||||
|
||||
// Validate selection area: "doubleClickMe dragThroughHere..." selected
|
||||
ValidateLinearSelection(term, { 4, 10 }, { 99, 10 });
|
||||
ValidateLinearSelection(term, { 4, 10 }, { 100, 10 });
|
||||
}
|
||||
|
||||
// Step 5: Shift+Double-Click at "dragThroughHere"
|
||||
Log::Comment(L"Step 5: Shift+Double-Click at \"dragThroughHere\"");
|
||||
{
|
||||
// Simulate Shift+DoubleClick at (x,y) = (21,10)
|
||||
//
|
||||
// buffer: doubleClickMe dragThroughHere
|
||||
// ^ ^ ^
|
||||
// start click finish
|
||||
// buffer: doubleClickMe dragThroughHere anotherWord
|
||||
// ^ ^ ^
|
||||
// start click finish
|
||||
// NOTE: end is exclusive, so finish should point to the spot AFTER "dragThroughHere"
|
||||
term.SetSelectionEnd({ 21, 10 }, Terminal::SelectionExpansion::Word);
|
||||
|
||||
// Validate selection area: "doubleClickMe dragThroughHere" selected
|
||||
ValidateLinearSelection(term, { 4, 10 }, { 32, 10 });
|
||||
ValidateLinearSelection(term, { 4, 10 }, { 33, 10 });
|
||||
}
|
||||
|
||||
// Step 6: Drag past "dragThroughHere"
|
||||
Log::Comment(L"Step 6: Drag past \"dragThroughHere\"");
|
||||
{
|
||||
// Simulate drag to (x,y) = (35,10)
|
||||
// Since we were preceded by a double-click, we're in "word" expansion mode
|
||||
//
|
||||
// buffer: doubleClickMe dragThroughHere |
|
||||
// ^ ^
|
||||
// start finish (boundary)
|
||||
// buffer: doubleClickMe dragThroughHere anotherWord
|
||||
// ^ ^
|
||||
// start finish
|
||||
term.SetSelectionEnd({ 35, 10 });
|
||||
|
||||
// Validate selection area: "doubleClickMe dragThroughHere..." selected
|
||||
ValidateLinearSelection(term, { 4, 10 }, { 99, 10 });
|
||||
// Validate selection area: "doubleClickMe dragThroughHere anotherWord" selected
|
||||
ValidateLinearSelection(term, { 4, 10 }, { 45, 10 });
|
||||
}
|
||||
|
||||
// Step 6: Drag back to "dragThroughHere"
|
||||
Log::Comment(L"Step 7: Drag back to \"dragThroughHere\"");
|
||||
{
|
||||
// Simulate drag to (x,y) = (21,10)
|
||||
// Should still be in "word" expansion mode!
|
||||
//
|
||||
// buffer: doubleClickMe dragThroughHere
|
||||
// ^ ^ ^
|
||||
// start drag finish
|
||||
// buffer: doubleClickMe dragThroughHere anotherWord
|
||||
// ^ ^
|
||||
// start finish
|
||||
term.SetSelectionEnd({ 21, 10 });
|
||||
|
||||
// Validate selection area: "doubleClickMe dragThroughHere" selected
|
||||
ValidateLinearSelection(term, { 4, 10 }, { 32, 10 });
|
||||
ValidateLinearSelection(term, { 4, 10 }, { 33, 10 });
|
||||
}
|
||||
|
||||
// Step 7: Drag within "dragThroughHere"
|
||||
Log::Comment(L"Step 8: Drag within \"dragThroughHere\"");
|
||||
{
|
||||
// Simulate drag to (x,y) = (25,10)
|
||||
//
|
||||
// buffer: doubleClickMe dragThroughHere
|
||||
// ^ ^ ^
|
||||
// start drag finish
|
||||
// buffer: doubleClickMe dragThroughHere anotherWord
|
||||
// ^ ^
|
||||
// start finish
|
||||
term.SetSelectionEnd({ 25, 10 });
|
||||
|
||||
// Validate selection area: "doubleClickMe dragThroughHere" still selected
|
||||
ValidateLinearSelection(term, { 4, 10 }, { 32, 10 });
|
||||
ValidateLinearSelection(term, { 4, 10 }, { 33, 10 });
|
||||
}
|
||||
}
|
||||
|
||||
@ -691,25 +707,27 @@ namespace TerminalCoreUnitTests
|
||||
DummyRenderer renderer{ &term };
|
||||
term.Create({ 100, 100 }, 0, renderer);
|
||||
|
||||
// Step 1: Create a selection
|
||||
Log::Comment(L"Step 1: Create a selection");
|
||||
{
|
||||
// (10,10) to (20, 10)
|
||||
// (10,10) to (20, 10) (inclusive)
|
||||
term.SelectNewRegion({ 10, 10 }, { 20, 10 });
|
||||
|
||||
// Validate selection area
|
||||
ValidateLinearSelection(term, { 10, 10 }, { 20, 10 });
|
||||
ValidateLinearSelection(term, { 10, 10 }, { 21, 10 });
|
||||
}
|
||||
|
||||
// Step 2: Drag to (5,10)
|
||||
Log::Comment(L"Step 2: Drag to (5,10)");
|
||||
{
|
||||
term.SetSelectionEnd({ 5, 10 });
|
||||
|
||||
// Validate selection area
|
||||
// NOTE: Pivot should be (10, 10)
|
||||
// NOTES:
|
||||
// - Pivot should be (10, 10)
|
||||
// - though end is generally exclusive, since we moved behind the pivot, end is actually inclusive
|
||||
ValidateLinearSelection(term, { 5, 10 }, { 10, 10 });
|
||||
}
|
||||
|
||||
// Step 3: Drag back to (20,10)
|
||||
Log::Comment(L"Step 3: Drag back to (20,10)");
|
||||
{
|
||||
term.SetSelectionEnd({ 20, 10 });
|
||||
|
||||
@ -718,7 +736,7 @@ namespace TerminalCoreUnitTests
|
||||
ValidateLinearSelection(term, { 10, 10 }, { 20, 10 });
|
||||
}
|
||||
|
||||
// Step 4: Shift+Click at (5,10)
|
||||
Log::Comment(L"Step 4: Shift+Click at (5,10)");
|
||||
{
|
||||
term.SetSelectionEnd({ 5, 10 }, Terminal::SelectionExpansion::Char);
|
||||
|
||||
@ -727,13 +745,14 @@ namespace TerminalCoreUnitTests
|
||||
ValidateLinearSelection(term, { 5, 10 }, { 10, 10 });
|
||||
}
|
||||
|
||||
// Step 5: Shift+Click back at (20,10)
|
||||
Log::Comment(L"Step 5: Shift+Click back at (20,10)");
|
||||
{
|
||||
term.SetSelectionEnd({ 20, 10 }, Terminal::SelectionExpansion::Char);
|
||||
|
||||
// Validate selection area
|
||||
// NOTE: Pivot should still be (10, 10)
|
||||
ValidateLinearSelection(term, { 10, 10 }, { 20, 10 });
|
||||
// Pivot should still be (10, 10)
|
||||
// Shift+Click makes end inclusive (so add 1)
|
||||
ValidateLinearSelection(term, { 10, 10 }, { 21, 10 });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -43,11 +43,32 @@ void Selection::_RegenerateSelectionSpans() const
|
||||
endSelectionAnchor.x = (_d->coordSelectionAnchor.x == _d->srSelectionRect.left) ? _d->srSelectionRect.right : _d->srSelectionRect.left;
|
||||
endSelectionAnchor.y = (_d->coordSelectionAnchor.y == _d->srSelectionRect.top) ? _d->srSelectionRect.bottom : _d->srSelectionRect.top;
|
||||
|
||||
// GH #18106: Conhost and Terminal share most of the selection code.
|
||||
// Both now store the selection data as a half-open range [start, end),
|
||||
// where "end" is the bottom-right-most point.
|
||||
// Note that Conhost defines start/end as "start was set in time before end",
|
||||
// whereas above (and in Terminal) we're treating start/end as "start is physically before end".
|
||||
// We want Conhost to still operate as an inclusive range.
|
||||
// To make it "feel" inclusive, we need to adjust the "end" endpoint
|
||||
// by incrementing it by one, so that the "end" endpoint is rendered
|
||||
// and handled as selected.
|
||||
const auto blockSelection = !IsLineSelection();
|
||||
_lastSelectionSpans = screenInfo.GetTextBuffer().GetTextSpans(_d->coordSelectionAnchor,
|
||||
endSelectionAnchor,
|
||||
blockSelection,
|
||||
false);
|
||||
const auto& buffer = screenInfo.GetTextBuffer();
|
||||
auto startSelectionAnchor = _d->coordSelectionAnchor;
|
||||
if (blockSelection)
|
||||
{
|
||||
// Compare x-values when we're in block selection!
|
||||
buffer.GetSize().IncrementInExclusiveBounds(startSelectionAnchor.x <= endSelectionAnchor.x ? endSelectionAnchor : startSelectionAnchor);
|
||||
}
|
||||
else
|
||||
{
|
||||
// General comparison for line selection.
|
||||
buffer.GetSize().IncrementInExclusiveBounds(startSelectionAnchor <= endSelectionAnchor ? endSelectionAnchor : startSelectionAnchor);
|
||||
}
|
||||
_lastSelectionSpans = buffer.GetTextSpans(startSelectionAnchor,
|
||||
endSelectionAnchor,
|
||||
blockSelection,
|
||||
false);
|
||||
_lastSelectionGeneration = _d.generation();
|
||||
}
|
||||
|
||||
|
||||
@ -84,7 +84,7 @@ class ClipboardTests
|
||||
const auto& screenInfo = gci.GetActiveOutputBuffer();
|
||||
const auto& buffer = screenInfo.GetTextBuffer();
|
||||
|
||||
constexpr til::point_span selection = { { 0, 0 }, { 14, 3 } };
|
||||
constexpr til::point_span selection = { { 0, 0 }, { 15, 3 } };
|
||||
const auto req = TextBuffer::CopyRequest::FromConfig(buffer, selection.start, selection.end, false, !fLineSelection, false);
|
||||
return buffer.GetPlainText(req);
|
||||
}
|
||||
|
||||
@ -60,7 +60,7 @@ class SearchTests
|
||||
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
|
||||
auto coordEndExpected = coordStartExpected;
|
||||
coordEndExpected.x += 1;
|
||||
coordEndExpected.x += 2;
|
||||
|
||||
VERIFY_IS_TRUE(s.SelectCurrent());
|
||||
VERIFY_ARE_EQUAL(coordStartExpected, gci.renderData.GetSelectionAnchor());
|
||||
|
||||
@ -74,7 +74,7 @@ class SelectionTests
|
||||
VERIFY_ARE_EQUAL(span.end.y, sRectangleLineNumber);
|
||||
|
||||
VERIFY_ARE_EQUAL(span.start.x, m_pSelection->_d->srSelectionRect.left);
|
||||
VERIFY_ARE_EQUAL(span.end.x, m_pSelection->_d->srSelectionRect.right);
|
||||
VERIFY_ARE_EQUAL(span.end.x, m_pSelection->_d->srSelectionRect.right + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -141,15 +141,17 @@ class SelectionTests
|
||||
}
|
||||
}
|
||||
|
||||
void VerifyGetSelectionSpans_LineMode(const til::point start, const til::point end)
|
||||
void VerifyGetSelectionSpans_LineMode(const til::point inclusiveStart, const til::point inclusiveEnd)
|
||||
{
|
||||
const auto selectionSpans = m_pSelection->GetSelectionSpans();
|
||||
|
||||
if (VERIFY_ARE_EQUAL(1u, selectionSpans.size()))
|
||||
{
|
||||
auto& span{ selectionSpans[0] };
|
||||
VERIFY_ARE_EQUAL(start, span.start, L"start");
|
||||
VERIFY_ARE_EQUAL(end, span.end, L"end");
|
||||
VERIFY_ARE_EQUAL(inclusiveStart, span.start, L"start");
|
||||
|
||||
const til::point exclusiveEnd{ inclusiveEnd.x + 1, inclusiveEnd.y };
|
||||
VERIFY_ARE_EQUAL(exclusiveEnd, span.end, L"end");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -2432,11 +2432,11 @@ void TextBufferTests::GetTextRects()
|
||||
std::vector<til::inclusive_rect> expected{};
|
||||
if (blockSelection)
|
||||
{
|
||||
expected.push_back({ 1, 0, 7, 0 });
|
||||
expected.push_back({ 1, 1, 8, 1 }); // expand right
|
||||
expected.push_back({ 1, 2, 7, 2 });
|
||||
expected.push_back({ 0, 3, 7, 3 }); // expand left
|
||||
expected.push_back({ 1, 4, 7, 4 });
|
||||
expected.push_back({ 1, 0, 8, 0 });
|
||||
expected.push_back({ 1, 1, 9, 1 }); // expand right
|
||||
expected.push_back({ 1, 2, 8, 2 });
|
||||
expected.push_back({ 0, 3, 8, 3 }); // do not expand
|
||||
expected.push_back({ 1, 4, 8, 4 });
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -2444,11 +2444,11 @@ void TextBufferTests::GetTextRects()
|
||||
expected.push_back({ 0, 1, 19, 1 });
|
||||
expected.push_back({ 0, 2, 19, 2 });
|
||||
expected.push_back({ 0, 3, 19, 3 });
|
||||
expected.push_back({ 0, 4, 7, 4 });
|
||||
expected.push_back({ 0, 4, 8, 4 });
|
||||
}
|
||||
|
||||
til::point start{ 1, 0 };
|
||||
til::point end{ 7, 4 };
|
||||
til::point end{ 8, 4 };
|
||||
const auto result = _buffer->GetTextRects(start, end, blockSelection, false);
|
||||
VERIFY_ARE_EQUAL(expected.size(), result.size());
|
||||
for (size_t i = 0; i < expected.size(); ++i)
|
||||
@ -2494,8 +2494,9 @@ void TextBufferTests::GetPlainText()
|
||||
L" 3 " };
|
||||
WriteLinesToBuffer(bufferText, *_buffer);
|
||||
|
||||
// simulate a selection from origin to {4,4}
|
||||
constexpr til::point_span selection = { { 0, 0 }, { 4, 4 } };
|
||||
// simulate a selection from origin to {5,4}
|
||||
// Remember! End is exclusive!
|
||||
constexpr til::point_span selection = { { 0, 0 }, { 5, 4 } };
|
||||
|
||||
const auto req = TextBuffer::CopyRequest{ *_buffer, selection.start, selection.end, blockSelection, includeCRLF, trimTrailingWhitespace, false };
|
||||
const auto result = _buffer->GetPlainText(req);
|
||||
@ -2589,8 +2590,9 @@ void TextBufferTests::GetPlainText()
|
||||
// | |
|
||||
// |_____|
|
||||
|
||||
// simulate a selection from origin to {4,5}
|
||||
constexpr til::point_span selection = { { 0, 0 }, { 4, 5 } };
|
||||
// simulate a selection from origin to {5,5}
|
||||
// Remember! End is exclusive!
|
||||
constexpr til::point_span selection = { { 0, 0 }, { 5, 5 } };
|
||||
|
||||
const auto formatWrappedRows = blockSelection;
|
||||
const auto req = TextBuffer::CopyRequest{ *_buffer, selection.start, selection.end, blockSelection, includeCRLF, trimTrailingWhitespace, formatWrappedRows };
|
||||
|
||||
@ -322,6 +322,42 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
||||
func(y, x1, x2);
|
||||
}
|
||||
}
|
||||
|
||||
// Similar to iterate_rows above, but the "end" point is exclusive
|
||||
// and RightExclusive (aka "width") is an allowable coordinate
|
||||
constexpr void iterate_rows_exclusive(til::CoordType width, auto&& func) const
|
||||
{
|
||||
// Copy the members so that the compiler knows it doesn't
|
||||
// need to re-read them on every loop iteration.
|
||||
const auto w = width;
|
||||
auto ax = std::clamp(start.x, 0, w);
|
||||
auto ay = start.y;
|
||||
auto bx = std::clamp(end.x, 0, w);
|
||||
auto by = end.y;
|
||||
|
||||
// if start is at RightExclusive,
|
||||
// treat it as (0, y+1) (left-most point on next line)
|
||||
if (ax == w)
|
||||
{
|
||||
ay++;
|
||||
ax = 0;
|
||||
}
|
||||
|
||||
// if end is on left boundary,
|
||||
// treat it as (w, y-1) (RightExclusive on previous line)
|
||||
if (bx == 0)
|
||||
{
|
||||
by--;
|
||||
bx = w;
|
||||
}
|
||||
|
||||
for (auto y = ay; y <= by; ++y)
|
||||
{
|
||||
const auto x1 = y != ay ? 0 : ax;
|
||||
const auto x2 = y != by ? w : bx;
|
||||
func(y, x1, x2);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -89,11 +89,11 @@ void AtlasEngine::_invalidateSpans(std::span<const til::point_span> spans, const
|
||||
const auto viewport = til::rect{ 0, 0, _api.s->viewportCellCount.x, _api.s->viewportCellCount.y };
|
||||
for (auto&& sp : spans)
|
||||
{
|
||||
sp.iterate_rows(til::CoordTypeMax, [&](til::CoordType row, til::CoordType beg, til::CoordType end) {
|
||||
sp.iterate_rows_exclusive(til::CoordTypeMax, [&](til::CoordType row, til::CoordType beg, til::CoordType end) {
|
||||
const auto shift = buffer.GetLineRendition(row) != LineRendition::SingleWidth ? 1 : 0;
|
||||
beg <<= shift;
|
||||
end <<= shift;
|
||||
til::rect rect{ beg, row, end + 1, row + 1 };
|
||||
til::rect rect{ beg, row, end, row + 1 };
|
||||
rect = rect.to_origin(viewportOrigin);
|
||||
rect &= viewport;
|
||||
_api.invalidatedRows.start = gsl::narrow_cast<u16>(std::min<int>(_api.invalidatedRows.start, std::max<int>(0, rect.top)));
|
||||
|
||||
@ -427,13 +427,13 @@ try
|
||||
if (y > hiStart.y)
|
||||
{
|
||||
const auto isFinalRow = y == hiEnd.y;
|
||||
const auto end = isFinalRow ? std::min(hiEnd.x + 1, x2) : x2;
|
||||
const auto end = isFinalRow ? std::min(hiEnd.x, x2) : x2;
|
||||
_fillColorBitmap(row, x1, end, fgColor, bgColor);
|
||||
|
||||
// Return early if we couldn't paint the whole region (either this was not the last row, or
|
||||
// it was the last row but the highlight ends outside of our x range.)
|
||||
// We will resume from here in the next call.
|
||||
if (!isFinalRow || hiEnd.x /*inclusive*/ >= x2 /*exclusive*/)
|
||||
if (!isFinalRow || hiEnd.x > x2)
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
@ -448,10 +448,10 @@ try
|
||||
hiEnd = it->end - offset;
|
||||
|
||||
const auto isStartInside = y == hiStart.y && hiStart.x < x2;
|
||||
const auto isEndInside = y == hiEnd.y && hiEnd.x < x2;
|
||||
const auto isEndInside = y == hiEnd.y && hiEnd.x <= x2;
|
||||
if (isStartInside && isEndInside)
|
||||
{
|
||||
_fillColorBitmap(row, hiStart.x, static_cast<size_t>(hiEnd.x) + 1, fgColor, bgColor);
|
||||
_fillColorBitmap(row, hiStart.x, static_cast<size_t>(hiEnd.x), fgColor, bgColor);
|
||||
++it;
|
||||
}
|
||||
else
|
||||
|
||||
@ -342,9 +342,8 @@ try
|
||||
const til::rect vp{ _viewport.ToExclusive() };
|
||||
for (auto&& sp : spans)
|
||||
{
|
||||
sp.iterate_rows(bufferWidth, [&](til::CoordType row, til::CoordType min, til::CoordType max) {
|
||||
sp.iterate_rows_exclusive(bufferWidth, [&](til::CoordType row, til::CoordType min, til::CoordType max) {
|
||||
const auto shift = buffer.GetLineRendition(row) != LineRendition::SingleWidth ? 1 : 0;
|
||||
max += 1; // Selection spans are inclusive (still)
|
||||
min <<= shift;
|
||||
max <<= shift;
|
||||
til::rect r{ min, row, max, row + 1 };
|
||||
|
||||
@ -157,14 +157,8 @@ HRESULT TermControlUiaProvider::GetSelectionRange(_In_ IRawElementProviderSimple
|
||||
RETURN_HR_IF_NULL(E_INVALIDARG, ppUtr);
|
||||
*ppUtr = nullptr;
|
||||
|
||||
const auto start = _pData->GetSelectionAnchor();
|
||||
|
||||
// we need to make end exclusive
|
||||
auto end = _pData->GetSelectionEnd();
|
||||
_pData->GetTextBuffer().GetSize().IncrementInBounds(end, true);
|
||||
|
||||
TermControlUiaTextRange* result = nullptr;
|
||||
RETURN_IF_FAILED(MakeAndInitialize<TermControlUiaTextRange>(&result, _pData, pProvider, start, end, _pData->IsBlockSelection(), wordDelimiters));
|
||||
RETURN_IF_FAILED(MakeAndInitialize<TermControlUiaTextRange>(&result, _pData, pProvider, _pData->GetSelectionAnchor(), _pData->GetSelectionEnd(), _pData->IsBlockSelection(), wordDelimiters));
|
||||
*ppUtr = result;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
@ -972,7 +972,7 @@ CATCH_RETURN();
|
||||
// Return Value:
|
||||
// - the text that the UiaTextRange encompasses
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 26447) // compiler isn't filtering throws inside the try/catch
|
||||
#pragma warning(disable : 26440) // The function ... can be declared as noexcept.
|
||||
std::wstring UiaTextRangeBase::_getTextValue(til::CoordType maxLength) const
|
||||
{
|
||||
std::wstring textData{};
|
||||
@ -987,13 +987,12 @@ std::wstring UiaTextRangeBase::_getTextValue(til::CoordType maxLength) const
|
||||
|
||||
// TODO GH#5406: create a different UIA parent object for each TextBuffer
|
||||
// nvaccess/nvda#11428: Ensure our endpoints are in bounds
|
||||
THROW_HR_IF(E_FAIL, !bufferSize.IsInBounds(_start, true) || !bufferSize.IsInBounds(_end, true));
|
||||
auto isValid = [&](const til::point& point) {
|
||||
return bufferSize.IsInExclusiveBounds(point) || point == bufferSize.EndExclusive();
|
||||
};
|
||||
THROW_HR_IF(E_FAIL, !isValid(_start) || !isValid(_end));
|
||||
|
||||
// convert _end to be inclusive
|
||||
auto inclusiveEnd = _end;
|
||||
bufferSize.DecrementInBounds(inclusiveEnd, true);
|
||||
|
||||
const auto req = TextBuffer::CopyRequest{ buffer, _start, inclusiveEnd, _blockRange, true, false, false, true };
|
||||
const auto req = TextBuffer::CopyRequest{ buffer, _start, _end, _blockRange, true, false, false, true };
|
||||
auto plainText = buffer.GetPlainText(req);
|
||||
|
||||
if (plainText.size() > maxLengthAsSize)
|
||||
|
||||
@ -41,20 +41,26 @@ namespace Microsoft::Console::Types
|
||||
til::point Origin() const noexcept;
|
||||
til::point BottomRightInclusive() const noexcept;
|
||||
til::point BottomRightExclusive() const noexcept;
|
||||
til::point BottomInclusiveRightExclusive() const noexcept;
|
||||
til::point EndExclusive() const noexcept;
|
||||
til::size Dimensions() const noexcept;
|
||||
|
||||
bool IsInBounds(const Viewport& other) const noexcept;
|
||||
bool IsInBounds(const til::point pos, bool allowEndExclusive = false) const noexcept;
|
||||
bool IsInExclusiveBounds(const til::point pos) const noexcept;
|
||||
|
||||
void Clamp(til::point& pos) const;
|
||||
Viewport Clamp(const Viewport& other) const noexcept;
|
||||
|
||||
bool IncrementInBounds(til::point& pos, bool allowEndExclusive = false) const noexcept;
|
||||
bool DecrementInBounds(til::point& pos, bool allowEndExclusive = false) const noexcept;
|
||||
bool IncrementInExclusiveBounds(til::point& pos) const noexcept;
|
||||
bool DecrementInExclusiveBounds(til::point& pos) const noexcept;
|
||||
int CompareInBounds(const til::point first, const til::point second, bool allowEndExclusive = false) const noexcept;
|
||||
int CompareInExclusiveBounds(const til::point first, const til::point second) const noexcept;
|
||||
|
||||
bool WalkInBounds(til::point& pos, const til::CoordType delta, bool allowEndExclusive = false) const noexcept;
|
||||
bool WalkInExclusiveBounds(til::point& pos, const til::CoordType delta) const noexcept;
|
||||
til::point GetWalkOrigin(const til::CoordType delta) const noexcept;
|
||||
static til::CoordType DetermineWalkDirection(const Viewport& source, const Viewport& target) noexcept;
|
||||
|
||||
|
||||
@ -118,6 +118,11 @@ til::point Viewport::BottomRightExclusive() const noexcept
|
||||
return { RightExclusive(), BottomExclusive() };
|
||||
}
|
||||
|
||||
til::point Viewport::BottomInclusiveRightExclusive() const noexcept
|
||||
{
|
||||
return { RightExclusive(), BottomInclusive() };
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - For Accessibility, get a til::point representing the end of this viewport in exclusive terms.
|
||||
// - This is needed to represent an exclusive endpoint in UiaTextRange that includes the last
|
||||
@ -295,6 +300,61 @@ bool Viewport::WalkInBounds(til::point& pos, const til::CoordType delta, bool al
|
||||
return off == offClamped;
|
||||
}
|
||||
|
||||
bool Viewport::IncrementInExclusiveBounds(til::point& pos) const noexcept
|
||||
{
|
||||
return WalkInExclusiveBounds(pos, 1);
|
||||
}
|
||||
|
||||
bool Viewport::DecrementInExclusiveBounds(til::point& pos) const noexcept
|
||||
{
|
||||
return WalkInExclusiveBounds(pos, -1);
|
||||
}
|
||||
|
||||
bool Viewport::IsInExclusiveBounds(const til::point pos) const noexcept
|
||||
{
|
||||
return pos.x >= Left() && pos.x <= RightExclusive() &&
|
||||
pos.y >= Top() && pos.y <= BottomInclusive();
|
||||
}
|
||||
|
||||
int Viewport::CompareInExclusiveBounds(const til::point first, const til::point second) const noexcept
|
||||
{
|
||||
// Assert that our coordinates are within the expected boundaries
|
||||
assert(IsInExclusiveBounds(first));
|
||||
assert(IsInExclusiveBounds(second));
|
||||
|
||||
// First set the distance vertically
|
||||
// If first is on row 4 and second is on row 6, first will be -2 rows behind second * an 80 character row would be -160.
|
||||
// For the same row, it'll be 0 rows * 80 character width = 0 difference.
|
||||
auto retVal = (first.y - second.y) * Width();
|
||||
|
||||
// Now adjust for horizontal differences
|
||||
// If first is in position 15 and second is in position 30, first is -15 left in relation to 30.
|
||||
retVal += (first.x - second.x);
|
||||
|
||||
// Further notes:
|
||||
// If we already moved behind one row, this will help correct for when first is right of second.
|
||||
// For example, with row 4, col 79 and row 5, col 0 as first and second respectively, the distance is -1.
|
||||
// Assume the row width is 80.
|
||||
// Step one will set the retVal as -80 as first is one row behind the second.
|
||||
// Step two will then see that first is 79 - 0 = +79 right of second and add 79
|
||||
// The total is -80 + 79 = -1.
|
||||
return retVal;
|
||||
}
|
||||
|
||||
bool Viewport::WalkInExclusiveBounds(til::point& pos, const til::CoordType delta) const noexcept
|
||||
{
|
||||
const auto l = static_cast<ptrdiff_t>(_sr.left);
|
||||
const auto t = static_cast<ptrdiff_t>(_sr.top);
|
||||
const auto w = static_cast<ptrdiff_t>(std::max(0, _sr.right - _sr.left + 2));
|
||||
const auto h = static_cast<ptrdiff_t>(std::max(0, _sr.bottom - _sr.top + 1));
|
||||
const auto max = w * h;
|
||||
const auto off = w * (pos.y - t) + (pos.x - l) + delta;
|
||||
const auto offClamped = std::clamp(off, ptrdiff_t{ 0 }, max);
|
||||
pos.x = gsl::narrow_cast<til::CoordType>(offClamped % w + l);
|
||||
pos.y = gsl::narrow_cast<til::CoordType>(offClamped / w + t);
|
||||
return off == offClamped;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - If walking through a viewport, one might want to know the origin
|
||||
// for the direction walking.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user