Skip to content

Commit 35bd607

Browse files
Add support for tabbing to embedded hyperlinks (#18347)
## Summary of the Pull Request There's already logic to tab to a hyperlink when we're in mark mode. We do this by looking at the automatically detected hyperlinks and finding the next one of interest. This adds an extra step afterwards to find any embedded hyperlinks and tab to them too. Since embedded hyperlinks are stored as text attributes, we need to iterate through the buffer to find the hyperlink and it's buffer boundaries. This PR tries to reduce the workload of that by first finding the automatically detected hyperlinks (since that's a fairly quick process), then using the reduced search area to find the embedded hyperlink (if one exists). ## Validation Steps Performed In PowerShell, add an embedded hyperlink as such: ```powershell ${ESC}=[char]27 Write-Host "${ESC}]8;;https://github.com/microsoft/terminal${ESC}\This is a link!${ESC}]8;;${ESC}\" ``` Enter mark mode (ctrl+shift+m) then shift+tab to it. ✅ The "This is a link!" is selected ✅ Verified that this works when searching forwards and backwards Closes #18310 Closes #15194 Follow-up from #13405 OSC 8 support added in #7251
1 parent 62e7f4b commit 35bd607

File tree

1 file changed

+82
-17
lines changed

1 file changed

+82
-17
lines changed

src/cascadia/TerminalCore/TerminalSelection.cpp

Lines changed: 82 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -437,7 +437,8 @@ void Terminal::SelectHyperlink(const SearchDirection dir)
437437
}
438438

439439
// 0. Useful tools/vars
440-
const auto bufferSize = _activeBuffer().GetSize();
440+
const auto& buffer = _activeBuffer();
441+
const auto bufferSize = buffer.GetSize();
441442
const auto viewportHeight = _GetMutableViewport().Height();
442443

443444
// The patterns are stored relative to the "search area". Initially, this search area will be the viewport,
@@ -504,8 +505,18 @@ void Terminal::SelectHyperlink(const SearchDirection dir)
504505
};
505506

506507
// 1. Look for the hyperlink
507-
til::point searchStart = dir == SearchDirection::Forward ? _selection->start : til::point{ bufferSize.Left(), _VisibleStartIndex() };
508-
til::point searchEnd = dir == SearchDirection::Forward ? til::point{ bufferSize.RightInclusive(), _VisibleEndIndex() } : _selection->start;
508+
til::point searchStart;
509+
til::point searchEnd;
510+
if (dir == SearchDirection::Forward)
511+
{
512+
searchStart = _selection->start;
513+
searchEnd = til::point{ bufferSize.RightInclusive(), _VisibleEndIndex() };
514+
}
515+
else
516+
{
517+
searchStart = til::point{ bufferSize.Left(), _VisibleStartIndex() };
518+
searchEnd = _selection->start;
519+
}
509520

510521
// 1.A) Try searching the current viewport (no scrolling required)
511522
auto resultList = _patternIntervalTree.findContained(convertToSearchArea(searchStart), convertToSearchArea(searchEnd));
@@ -547,27 +558,81 @@ void Terminal::SelectHyperlink(const SearchDirection dir)
547558
searchArea = Viewport::FromDimensions(searchStart, { searchEnd.x + 1, searchEnd.y + 1 });
548559
}
549560
}
561+
}
550562

551-
// 1.C) Nothing was found. Bail!
552-
if (!result.has_value())
563+
// 2. We found a hyperlink from the pattern tree. Look for embedded hyperlinks too!
564+
// Use the result (if one was found) to narrow down the search.
565+
if (dir == SearchDirection::Forward)
566+
{
567+
searchStart = _selection->start;
568+
searchEnd = (result ? result->first : buffer.GetLastNonSpaceCharacter());
569+
}
570+
else
571+
{
572+
searchStart = (result ? result->second : bufferSize.Origin());
573+
searchEnd = _selection->start;
574+
}
575+
576+
// Careful! Selection can point to RightExclusive(), which doesn't contain data!
577+
// Clamp to be safe.
578+
auto initialPos = dir == SearchDirection::Forward ? searchStart : searchEnd;
579+
bufferSize.Clamp(initialPos);
580+
auto iter = buffer.GetCellDataAt(initialPos);
581+
while (dir == SearchDirection::Forward ? iter.Pos() < searchEnd : iter.Pos() > searchStart)
582+
{
583+
// Don't let us select the same hyperlink again
584+
if (iter.Pos() < _selection->start || iter.Pos() > _selection->end)
553585
{
554-
return;
586+
if (auto attr = iter->TextAttr(); attr.IsHyperlink())
587+
{
588+
// Found an embedded hyperlink!
589+
const auto hyperlinkId = attr.GetHyperlinkId();
590+
591+
// Expand the start to include the entire hyperlink
592+
TextBufferCellIterator hyperlinkStartIter{ buffer, iter.Pos() };
593+
while (hyperlinkStartIter.Pos() > searchStart && attr.IsHyperlink() && attr.GetHyperlinkId() == hyperlinkId)
594+
{
595+
--hyperlinkStartIter;
596+
attr = hyperlinkStartIter->TextAttr();
597+
}
598+
if (hyperlinkStartIter.Pos() != bufferSize.Origin())
599+
{
600+
// undo a move to be inclusive
601+
++hyperlinkStartIter;
602+
}
603+
604+
// Expand the end to include the entire hyperlink
605+
// No need to undo a move! We'll decrement in the next step anyways.
606+
TextBufferCellIterator hyperlinkEndIter{ buffer, iter.Pos() };
607+
attr = hyperlinkEndIter->TextAttr();
608+
while (hyperlinkEndIter.Pos() < searchEnd && attr.IsHyperlink() && attr.GetHyperlinkId() == hyperlinkId)
609+
{
610+
++hyperlinkEndIter;
611+
attr = hyperlinkEndIter->TextAttr();
612+
}
613+
614+
result = { hyperlinkStartIter.Pos(), hyperlinkEndIter.Pos() };
615+
break;
616+
}
555617
}
618+
iter += dir == SearchDirection::Forward ? 1 : -1;
556619
}
557620

558-
// 2. Select the hyperlink
621+
// 3. Select the hyperlink, if one exists
622+
if (!result.has_value())
559623
{
560-
auto selection{ _selection.write() };
561-
wil::hide_name _selection;
562-
selection->start = result->first;
563-
selection->pivot = result->first;
564-
selection->end = result->second;
565-
_selectionIsTargetingUrl = true;
566-
_selectionEndpoint = SelectionEndpoint::End;
624+
return;
567625
}
568-
569-
// 3. Scroll to the selected area (if necessary)
570-
_ScrollToPoint(_selection->end);
626+
auto selection{ _selection.write() };
627+
wil::hide_name _selection;
628+
selection->start = result->first;
629+
selection->pivot = result->first;
630+
selection->end = result->second;
631+
_selectionIsTargetingUrl = true;
632+
_selectionEndpoint = SelectionEndpoint::End;
633+
634+
// 4. Scroll to the selected area (if necessary)
635+
_ScrollToPoint(selection->end);
571636
}
572637

573638
Terminal::UpdateSelectionParams Terminal::ConvertKeyEventToUpdateSelectionParams(const ControlKeyStates mods, const WORD vkey) const noexcept

0 commit comments

Comments
 (0)