Skip to content

Commit 0199ca3

Browse files
authored
Port selection in conhost and Terminal to use til::generational (#17676)
In #17638, I am moving selection to an earlier phase of rendering (so that further phases can take it into account). Since I am drafting off the design of search highlights, one of the required changes is moving to passing `span`s of `point_span`s around to make selection effectively zero-copy. We can't easily have zero-copy selection propagation without caching, and we can't have caching without mandatory cache invalidation. This pull request moves both conhost and Terminal to use `til::generational` for all selection members that impact the ranges that would be produced from `GetSelectionRects`. This required a move from `std::optional<>` to a boolean to determine whether a selection was active in Terminal. We will no longer regenerate the selection rects from the selection anchors plus the text buffer *every single frame*. Apart from being annoying to read, there is one downside. If you begin a selection on a narrow character, _and that narrow character later turns into a wide character_, we will show it as half-selected. This should be a rare-enough case that we can accept it as a regression.
1 parent 7c0d6d9 commit 0199ca3

File tree

8 files changed

+369
-264
lines changed

8 files changed

+369
-264
lines changed

src/cascadia/TerminalCore/Terminal.hpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "../../types/inc/GlyphWidth.hpp"
1616
#include "../../cascadia/terminalcore/ITerminalInput.hpp"
1717

18+
#include <til/generational.h>
1819
#include <til/ticket_lock.h>
1920
#include <til/winrt.h>
2021

@@ -382,14 +383,15 @@ class Microsoft::Terminal::Core::Terminal final :
382383
// the pivot is the til::point that remains selected when you extend a selection in any direction
383384
// this is particularly useful when a word selection is extended over its starting point
384385
// see TerminalSelection.cpp for more information
385-
struct SelectionAnchors
386+
struct SelectionInfo
386387
{
387388
til::point start;
388389
til::point end;
389390
til::point pivot;
391+
bool blockSelection = false;
392+
bool active = false;
390393
};
391-
std::optional<SelectionAnchors> _selection;
392-
bool _blockSelection = false;
394+
til::generational<SelectionInfo> _selection{};
393395
std::wstring _wordDelimiters;
394396
SelectionExpansion _multiClickSelectionMode = SelectionExpansion::Char;
395397
SelectionInteractionMode _selectionMode = SelectionInteractionMode::None;
@@ -474,6 +476,7 @@ class Microsoft::Terminal::Core::Terminal final :
474476
void _MoveByWord(SelectionDirection direction, til::point& pos);
475477
void _MoveByViewport(SelectionDirection direction, til::point& pos) noexcept;
476478
void _MoveByBuffer(SelectionDirection direction, til::point& pos) noexcept;
479+
void _SetSelectionEnd(SelectionInfo* selection, const til::point position, std::optional<SelectionExpansion> newExpansionMode = std::nullopt);
477480
#pragma endregion
478481

479482
#ifdef UNIT_TESTING

src/cascadia/TerminalCore/TerminalApi.cpp

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -339,23 +339,25 @@ void Terminal::SearchMissingCommand(const std::wstring_view command)
339339
void Terminal::NotifyBufferRotation(const int delta)
340340
{
341341
// Update our selection, so it doesn't move as the buffer is cycled
342-
if (_selection)
342+
if (_selection->active)
343343
{
344+
auto selection{ _selection.write() };
345+
wil::hide_name _selection;
344346
// If the end of the selection will be out of range after the move, we just
345347
// clear the selection. Otherwise we move both the start and end points up
346348
// by the given delta and clamp to the first row.
347-
if (_selection->end.y < delta)
349+
if (selection->end.y < delta)
348350
{
349-
_selection.reset();
351+
selection->active = false;
350352
}
351353
else
352354
{
353355
// Stash this, so we can make sure to update the pivot to match later.
354-
const auto pivotWasStart = _selection->start == _selection->pivot;
355-
_selection->start.y = std::max(_selection->start.y - delta, 0);
356-
_selection->end.y = std::max(_selection->end.y - delta, 0);
356+
const auto pivotWasStart = selection->start == selection->pivot;
357+
selection->start.y = std::max(selection->start.y - delta, 0);
358+
selection->end.y = std::max(selection->end.y - delta, 0);
357359
// Make sure to sync the pivot with whichever value is the right one.
358-
_selection->pivot = pivotWasStart ? _selection->start : _selection->end;
360+
selection->pivot = pivotWasStart ? selection->start : selection->end;
359361
}
360362
}
361363

src/cascadia/TerminalCore/TerminalSelection.cpp

Lines changed: 72 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ std::vector<til::inclusive_rect> Terminal::_GetSelectionRects() const noexcept
5757

5858
try
5959
{
60-
return _activeBuffer().GetTextRects(_selection->start, _selection->end, _blockSelection, false);
60+
return _activeBuffer().GetTextRects(_selection->start, _selection->end, _selection->blockSelection, false);
6161
}
6262
CATCH_LOG();
6363
return result;
@@ -78,7 +78,7 @@ std::vector<til::point_span> Terminal::_GetSelectionSpans() const noexcept
7878

7979
try
8080
{
81-
return _activeBuffer().GetTextSpans(_selection->start, _selection->end, _blockSelection, false);
81+
return _activeBuffer().GetTextSpans(_selection->start, _selection->end, _selection->blockSelection, false);
8282
}
8383
CATCH_LOG();
8484
return result;
@@ -158,13 +158,13 @@ const Terminal::SelectionEndpoint Terminal::SelectionEndpointTarget() const noex
158158
const bool Terminal::IsSelectionActive() const noexcept
159159
{
160160
_assertLocked();
161-
return _selection.has_value();
161+
return _selection->active;
162162
}
163163

164164
const bool Terminal::IsBlockSelection() const noexcept
165165
{
166166
_assertLocked();
167-
return _blockSelection;
167+
return _selection->blockSelection;
168168
}
169169

170170
// Method Description:
@@ -175,15 +175,18 @@ const bool Terminal::IsBlockSelection() const noexcept
175175
void Terminal::MultiClickSelection(const til::point viewportPos, SelectionExpansion expansionMode)
176176
{
177177
// set the selection pivot to expand the selection using SetSelectionEnd()
178-
_selection = SelectionAnchors{};
179-
_selection->pivot = _ConvertToBufferCell(viewportPos);
178+
auto selection{ _selection.write() };
179+
wil::hide_name _selection;
180+
181+
selection->pivot = _ConvertToBufferCell(viewportPos);
182+
selection->active = true;
180183

181184
_multiClickSelectionMode = expansionMode;
182185
SetSelectionEnd(viewportPos);
183186

184187
// we need to set the _selectionPivot again
185188
// for future shift+clicks
186-
_selection->pivot = _selection->start;
189+
selection->pivot = selection->start;
187190
}
188191

189192
// Method Description:
@@ -194,13 +197,21 @@ void Terminal::SetSelectionAnchor(const til::point viewportPos)
194197
{
195198
_assertLocked();
196199

197-
_selection = SelectionAnchors{};
198-
_selection->pivot = _ConvertToBufferCell(viewportPos);
200+
auto selection{ _selection.write() };
201+
wil::hide_name _selection;
202+
203+
selection->pivot = _ConvertToBufferCell(viewportPos);
204+
selection->active = true;
199205

200206
_multiClickSelectionMode = SelectionExpansion::Char;
201207
SetSelectionEnd(viewportPos);
202208

203-
_selection->start = _selection->pivot;
209+
selection->start = selection->pivot;
210+
}
211+
212+
void Terminal::SetSelectionEnd(const til::point viewportPos, std::optional<SelectionExpansion> newExpansionMode)
213+
{
214+
_SetSelectionEnd(_selection.write(), viewportPos, newExpansionMode);
204215
}
205216

206217
// Method Description:
@@ -209,9 +220,10 @@ void Terminal::SetSelectionAnchor(const til::point viewportPos)
209220
// Arguments:
210221
// - viewportPos: the (x,y) coordinate on the visible viewport
211222
// - newExpansionMode: overwrites the _multiClickSelectionMode for this function call. Used for ShiftClick
212-
void Terminal::SetSelectionEnd(const til::point viewportPos, std::optional<SelectionExpansion> newExpansionMode)
223+
void Terminal::_SetSelectionEnd(SelectionInfo* selection, const til::point viewportPos, std::optional<SelectionExpansion> newExpansionMode)
213224
{
214-
if (!IsSelectionActive())
225+
wil::hide_name _selection;
226+
if (!selection->active)
215227
{
216228
// capture a log for spurious endpoint sets without an active selection
217229
LOG_HR(E_ILLEGAL_STATE_CHANGE);
@@ -231,17 +243,17 @@ void Terminal::SetSelectionEnd(const til::point viewportPos, std::optional<Selec
231243
if (newExpansionMode.has_value())
232244
{
233245
// shift-click operations only expand the target side
234-
auto& anchorToExpand = targetStart ? _selection->start : _selection->end;
246+
auto& anchorToExpand = targetStart ? selection->start : selection->end;
235247
anchorToExpand = targetStart ? expandedAnchors.first : expandedAnchors.second;
236248

237249
// the other anchor should then become the pivot (we don't expand it)
238-
auto& anchorToPivot = targetStart ? _selection->end : _selection->start;
239-
anchorToPivot = _selection->pivot;
250+
auto& anchorToPivot = targetStart ? selection->end : selection->start;
251+
anchorToPivot = selection->pivot;
240252
}
241253
else
242254
{
243255
// expand both anchors
244-
std::tie(_selection->start, _selection->end) = expandedAnchors;
256+
std::tie(selection->start, selection->end) = expandedAnchors;
245257
}
246258
_selectionMode = SelectionInteractionMode::Mouse;
247259
_selectionIsTargetingUrl = false;
@@ -307,7 +319,7 @@ std::pair<til::point, til::point> Terminal::_ExpandSelectionAnchors(std::pair<ti
307319
// - isEnabled: new value for _blockSelection
308320
void Terminal::SetBlockSelection(const bool isEnabled) noexcept
309321
{
310-
_blockSelection = isEnabled;
322+
_selection.write()->blockSelection = isEnabled;
311323
}
312324

313325
Terminal::SelectionInteractionMode Terminal::SelectionMode() const noexcept
@@ -331,11 +343,13 @@ void Terminal::ToggleMarkMode()
331343
{
332344
// No selection --> start one at the cursor
333345
const auto cursorPos{ _activeBuffer().GetCursor().GetPosition() };
334-
_selection = SelectionAnchors{};
335-
_selection->start = cursorPos;
336-
_selection->end = cursorPos;
337-
_selection->pivot = cursorPos;
338-
_blockSelection = false;
346+
*_selection.write() = SelectionInfo{
347+
.start = cursorPos,
348+
.end = cursorPos,
349+
.pivot = cursorPos,
350+
.blockSelection = false,
351+
.active = true,
352+
};
339353
WI_SetAllFlags(_selectionEndpoint, SelectionEndpoint::Start | SelectionEndpoint::End);
340354
}
341355
else if (WI_AreAllFlagsClear(_selectionEndpoint, SelectionEndpoint::Start | SelectionEndpoint::End))
@@ -365,13 +379,13 @@ void Terminal::SwitchSelectionEndpoint() noexcept
365379
{
366380
// moving end --> now we're moving start
367381
_selectionEndpoint = SelectionEndpoint::Start;
368-
_selection->pivot = _selection->end;
382+
_selection.write()->pivot = _selection->end;
369383
}
370384
else if (WI_IsFlagSet(_selectionEndpoint, SelectionEndpoint::Start))
371385
{
372386
// moving start --> now we're moving end
373387
_selectionEndpoint = SelectionEndpoint::End;
374-
_selection->pivot = _selection->start;
388+
_selection.write()->pivot = _selection->start;
375389
}
376390
}
377391
}
@@ -381,9 +395,11 @@ void Terminal::ExpandSelectionToWord()
381395
if (IsSelectionActive())
382396
{
383397
const auto& buffer = _activeBuffer();
384-
_selection->start = buffer.GetWordStart(_selection->start, _wordDelimiters);
385-
_selection->pivot = _selection->start;
386-
_selection->end = buffer.GetWordEnd(_selection->end, _wordDelimiters);
398+
auto selection{ _selection.write() };
399+
wil::hide_name _selection;
400+
selection->start = buffer.GetWordStart(selection->start, _wordDelimiters);
401+
selection->pivot = selection->start;
402+
selection->end = buffer.GetWordEnd(selection->end, _wordDelimiters);
387403

388404
// if we're targeting both endpoints, instead just target "end"
389405
if (WI_IsFlagSet(_selectionEndpoint, SelectionEndpoint::Start) && WI_IsFlagSet(_selectionEndpoint, SelectionEndpoint::End))
@@ -528,12 +544,16 @@ void Terminal::SelectHyperlink(const SearchDirection dir)
528544
}
529545

530546
// 2. Select the hyperlink
531-
_selection->start = result->first;
532-
_selection->pivot = result->first;
533-
_selection->end = result->second;
534-
bufferSize.DecrementInBounds(_selection->end);
535-
_selectionIsTargetingUrl = true;
536-
_selectionEndpoint = SelectionEndpoint::End;
547+
{
548+
auto selection{ _selection.write() };
549+
wil::hide_name _selection;
550+
selection->start = result->first;
551+
selection->pivot = result->first;
552+
selection->end = result->second;
553+
bufferSize.DecrementInBounds(selection->end);
554+
_selectionIsTargetingUrl = true;
555+
_selectionEndpoint = SelectionEndpoint::End;
556+
}
537557

538558
// 3. Scroll to the selected area (if necessary)
539559
_ScrollToPoint(_selection->end);
@@ -646,19 +666,23 @@ void Terminal::UpdateSelection(SelectionDirection direction, SelectionExpansion
646666
// 3. Actually modify the selection state
647667
_selectionIsTargetingUrl = false;
648668
_selectionMode = std::max(_selectionMode, SelectionInteractionMode::Keyboard);
669+
670+
auto selection{ _selection.write() };
671+
wil::hide_name _selection;
672+
649673
if (shouldMoveBothEndpoints)
650674
{
651675
// [Mark Mode] + shift unpressed --> move all three (i.e. just use arrow keys)
652-
_selection->start = targetPos;
653-
_selection->end = targetPos;
654-
_selection->pivot = targetPos;
676+
selection->start = targetPos;
677+
selection->end = targetPos;
678+
selection->pivot = targetPos;
655679
}
656680
else
657681
{
658682
// [Mark Mode] + shift --> updating a standard selection
659683
// This is also standard quick-edit modification
660684
bool targetStart = false;
661-
std::tie(_selection->start, _selection->end) = _PivotSelection(targetPos, targetStart);
685+
std::tie(selection->start, selection->end) = _PivotSelection(targetPos, targetStart);
662686

663687
// IMPORTANT! Pivoting the selection here might have changed which endpoint we're targeting.
664688
// So let's set the targeted endpoint again.
@@ -673,10 +697,15 @@ void Terminal::UpdateSelection(SelectionDirection direction, SelectionExpansion
673697
void Terminal::SelectAll()
674698
{
675699
const auto bufferSize{ _activeBuffer().GetSize() };
676-
_selection = SelectionAnchors{};
677-
_selection->start = bufferSize.Origin();
678-
_selection->end = { bufferSize.RightInclusive(), _GetMutableViewport().BottomInclusive() };
679-
_selection->pivot = _selection->end;
700+
const til::point end{ bufferSize.RightInclusive(), _GetMutableViewport().BottomInclusive() };
701+
*_selection.write() = SelectionInfo{
702+
.start = bufferSize.Origin(),
703+
.end = end,
704+
.pivot = end,
705+
.blockSelection = false,
706+
.active = true,
707+
};
708+
680709
_selectionMode = SelectionInteractionMode::Keyboard;
681710
_selectionIsTargetingUrl = false;
682711
_ScrollToPoint(_selection->start);
@@ -824,7 +853,7 @@ void Terminal::_MoveByBuffer(SelectionDirection direction, til::point& pos) noex
824853
void Terminal::ClearSelection()
825854
{
826855
_assertLocked();
827-
_selection = std::nullopt;
856+
_selection.write()->active = false;
828857
_selectionMode = SelectionInteractionMode::None;
829858
_selectionIsTargetingUrl = false;
830859
_selectionEndpoint = static_cast<SelectionEndpoint>(0);
@@ -858,7 +887,7 @@ Terminal::TextCopyData Terminal::RetrieveSelectedTextFromBuffer(const bool singl
858887

859888
const auto& textBuffer = _activeBuffer();
860889

861-
const auto req = TextBuffer::CopyRequest::FromConfig(textBuffer, _selection->start, _selection->end, singleLine, _blockSelection, _trimBlockSelection);
890+
const auto req = TextBuffer::CopyRequest::FromConfig(textBuffer, _selection->start, _selection->end, singleLine, _selection->blockSelection, _trimBlockSelection);
862891
data.plainText = textBuffer.GetPlainText(req);
863892

864893
if (html || rtf)

0 commit comments

Comments
 (0)