-
Notifications
You must be signed in to change notification settings - Fork 8.4k
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.
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. 🙂
Feedback from Bug Bash (11/19)
|
FYI - the tests failed on all 3 platforms, which indicates a real produce code failure! |
// 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). |
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'm not 100% sure if I understand this code, but to me it feels like the calling code should contain the fix for this and not this function. Is this perhaps just a case of floor
VS. ceil
in the calling code?
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.
Yeah, I'm not happy about this part either. Let me explain what's going on better. The code flow is:
ControlInteractivity::SetEndSelectionPoint()
(link) - called on mouse dragControlInteractivity::_getTerminalPosition()
(link): round to the nearest cell, that way, we have to drag past a cell to "select" it
ControlCore::SetEndSelectionPoint()
(link): really just clamps the position to the viewport boundsTerminal::SetSelectionEnd()
(link) -->Terminal::_SetSelectionEnd()
(link):- This is the important part of the code, really. It's where we update our selection state for shift+click, pivoting, and multi-clicks (+ dragging).
- The issue really only arises when we're multi-clicking+dragging (think "double-click" then drag, as it should expand by word as we drag). So the issue has to do with the expansion part. Anything before that in this function wouldn't make sense to alter, imo.
Terminal::_ExpandSelectionAnchors()
(link): this is where we actually expand our selection endpoints if we're multi-clicking.
Thankfully, _ExpandSelectionAnchors()
is only used in this spot. I'm not sure where else the change would make sense to go though. Open to suggestions.
All the tests are good now. The only one that failed was:
which seems unrelated, so I'm running it again. /azp run |
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.
18/30 i have a lot of questions after reading a few tests so i'm checkpointing here
@@ -709,7 +709,7 @@ namespace TerminalCoreUnitTests | |||
ValidateLinearSelection(term, { 5, 10 }, { 10, 10 }); |
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 don't understand why these coordinates did not become exclusive
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.
Good catch! Looked into it more. Since we're moving behind the pivot, end is actually inclusive! (which, admittedly, is pretty confusing)
Leaving this thread unresolved in case we want to make any changes because of this
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.