-
Notifications
You must be signed in to change notification settings - Fork 8.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make selection an exclusive range #18106
base: main
Are you sure you want to change the base?
Conversation
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
_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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW I think we may have made a mistake here due to my poor understanding of this code. Probably needs to be mulled over once more...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, be careful - this is used in TWO places. search highlights and selection rendering. they're both inclusive today. this PR only says it changes one of them.
{ | ||
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)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be +1. BTW is this function really needed? We already have WalkInBounds
with the allowEndExclusive
parameter that we could set to false
no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, so allowEndExclusive
is slightly different. EndExclusive = (Left, BottomExclusive)
. allowEndExclusive
was designed to make WalkInBounds(true)
still walk from Left
--> RightInclusive
, but when we get to the last cell of the bottom inclusive row, we wrap onto the next line (EndExclusive). This made it so that we're able to represent the UIA start/end as an exclusive range, and we'd be able to include the last cell of the buffer by making end = EndExclusive.
In hindsight, I think the right move should've been to make EndExclusive = (RightExclusive, BottomInclusive)
. This PR makes selection do that and adds these extra APIs. I think it's much cleaner. But yeah, I wouldn't want to port UIA over to this system until it gets reviewed and merged. So I'm making that work a part of #4423 as a follow-up (where we should be able to remove EndExclusive
altogether).
As for the +1
, I honestly don't remember why I made it +2
... you're probably right. I'll make that change and validate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IOU a review here - thanks for doing it!
However, all of the tests exploded so... I may wait. :P |
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have you tested...
- detecting URls
- selecting URLs
- search highlights (with and without regexes)
@@ -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) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
careful! this touches conhost code too. ... !
this may make selections not work properly in conhost
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Testing out Host.exe, this PR does change selection to be exclusive there too. Pivoting the selection is a bit weird now as shift+clicking before the pivot highlights the selected cell, whereas doing so after the pivot highlights up to the cell the pointer is on.
I'm able to polish Conhost to work more nicely too, but is that something we want? Or should I explore how to keep Conhost the same?
This comment was marked as resolved.
This comment was marked as resolved.
Was this fixed? Just wanna know if I should be making my rounds here too |
Currently working on it. My goal is to get the following fixed so we can test it in the bug bash on Tuesday:
|
These are fixed now! Verified ConHost using GDI and Atlas. Verified hyperlink detection with cases I mentioned earlier. |
@@ -436,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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lhecker ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The offsets are exclusive now. This means the chunkOffset now refers to the first character after the matched range. In turn, this means that if there's a wide glyph after the matched range, we want its leading half, because the exclusive end should be exactly 1 past the end (and not 2).
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 } | ||
{ | ||
} | ||
|
||
// If given a position (`offset`) inside the ROW's text, this function will return the corresponding column. | ||
// 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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what's all this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These changes are necessary so that both accessor functions work reliably with an exclusive target char offset. I suggested to rename the member to make its usage a bit easier to understand, now that it refers to the length and not length-1 anymore. I did this in collaboration with Carlos. 🙂
Summary of the Pull Request
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).
References and Relevant Issues
Closes #5099
Closes #13447
Closes #17892
Detailed Description of the Pull Request / Additional comments
GetWordStart
-->GetWordStart2
). That migration is going to be a part of Refactor Word Expansion in TextBuffer #4423 to reduce the risk of breaking UIA.BottomInclusiveRightExclusive()
will replaceEndExclusive
in the UIA codeiterate_rows_exclusive
is similar toiterate_rows
, except it has handling for RightExclusiveiterate_rows_exclusive
for proper handling (this actually fixed a lot of our issues)_drawHighlighted
(this is a boundary where we got inclusive coords and made them exclusive, but now we don't need that!)_ConvertToBufferCell()
: add a param to allow for RightExclusive or clamp it to RightInclusive (original behavior). Both are useful!GetWordStart2
andGetWordEnd2
to improve word boundaries and make them feel right now that the selection an exclusive range.IsInBounds
-->IsInExclusiveBounds
for safety and correctnessTriggerSelection
toSelectNewRegion
TriggerSelection
in a different layer, but it turns out, UIA'sSelect
function wouldn't actually update the renderer. Whoops! This fixes that._getTerminalPosition
now has a new param to round to the nearest cell (see Mouse selection should begin and end at nearest cell boundary #5099)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:
We should definitely bug bash this to get good coverage.
Follow-ups
EndExclusive
as well when we do that. This'll also be an opportunity to modernize that code and use moretil
classes.