From cd3dbddf0520d0ced31162ac459aa4d3eb827672 Mon Sep 17 00:00:00 2001 From: Swastik Bose Date: Sun, 17 May 2026 14:06:55 +0530 Subject: [PATCH 1/2] fix(TerminalControl): invalidate hovered hyperlink on scroll When the user scrolls with the mouse wheel while the cursor is stationary, the viewport shifts but _lastHoveredCell was never invalidated. This caused stale hyperlink hover highlights to persist even when plain text was now under the cursor. Fix by clearing and re-evaluating the hovered cell immediately after UpdateScrollbar in _mouseScrollHandler. Closes #20219 --- src/cascadia/TerminalControl/ControlInteractivity.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/cascadia/TerminalControl/ControlInteractivity.cpp b/src/cascadia/TerminalControl/ControlInteractivity.cpp index bd309117971..95e4a8e7db1 100644 --- a/src/cascadia/TerminalControl/ControlInteractivity.cpp +++ b/src/cascadia/TerminalControl/ControlInteractivity.cpp @@ -635,6 +635,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation // ScrollPositionChanged event to update the scrollbar UpdateScrollbar(newValue); + // GH#20219: Re-evaluate the hovered hyperlink after scrolling. The + // viewport shift may have changed which logical buffer cell sits under + // the stationary cursor, so we must invalidate the cached hover state + // and re-query at the same pixel position. + _core->ClearHoveredCell(); + const auto terminalPosition = _getTerminalPosition(til::point{ pixelPosition }, false); + _core->SetHoveredCell(terminalPosition.to_core_point()); + if (isLeftButtonPressed) { // If user is mouse selecting and scrolls, they then point at new From 44da6609f438e718c16f67bd75d6ee535a5965b9 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Tue, 23 Jun 2026 15:24:32 -0700 Subject: [PATCH 2/2] apply broader fix --- src/cascadia/TerminalControl/ControlCore.cpp | 15 +++++++++++++++ src/cascadia/TerminalControl/ControlCore.h | 1 + .../TerminalControl/ControlInteractivity.cpp | 8 -------- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 3eaa4dc3464..b84bf94cec2 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -244,6 +244,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation [weakThis = get_weak()](const auto& update) { if (auto core{ weakThis.get() }; core && !core->_IsClosing()) { + // GH#20219: re-evaluate if we're hovering over a hyperlink after scrolling + core->_refreshHoveredCell(); core->ScrollPositionChanged.raise(*core, update); } }); @@ -746,6 +748,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation _terminal->UserScrollViewport(viewTop); } + // GH#20219: re-evaluate if we're hovering over a hyperlink after scrolling + _refreshHoveredCell(); + const auto shared = _shared.lock_shared(); if (shared->outputIdle) { @@ -837,6 +842,16 @@ namespace winrt::Microsoft::Terminal::Control::implementation _updateHoveredCell(std::nullopt); } + void ControlCore::_refreshHoveredCell() + { + if (_lastHoveredCell) + { + const auto cell = *_lastHoveredCell; + _lastHoveredCell.reset(); + _updateHoveredCell(cell); + } + } + void ControlCore::_updateHoveredCell(const std::optional terminalPosition) { if (terminalPosition == _lastHoveredCell) diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index d74473001a4..e3aa35f0cc4 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -351,6 +351,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation void _connectionOutputHandler(winrt::array_view str); void _connectionStateChangedHandler(const TerminalConnection::ITerminalConnection&, const Windows::Foundation::IInspectable&); void _updateHoveredCell(const std::optional terminalPosition); + void _refreshHoveredCell(); void _setOpacity(const float opacity, const bool focused = true); bool _isBackgroundTransparent(); diff --git a/src/cascadia/TerminalControl/ControlInteractivity.cpp b/src/cascadia/TerminalControl/ControlInteractivity.cpp index 95e4a8e7db1..bd309117971 100644 --- a/src/cascadia/TerminalControl/ControlInteractivity.cpp +++ b/src/cascadia/TerminalControl/ControlInteractivity.cpp @@ -635,14 +635,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation // ScrollPositionChanged event to update the scrollbar UpdateScrollbar(newValue); - // GH#20219: Re-evaluate the hovered hyperlink after scrolling. The - // viewport shift may have changed which logical buffer cell sits under - // the stationary cursor, so we must invalidate the cached hover state - // and re-query at the same pixel position. - _core->ClearHoveredCell(); - const auto terminalPosition = _getTerminalPosition(til::point{ pixelPosition }, false); - _core->SetHoveredCell(terminalPosition.to_core_point()); - if (isLeftButtonPressed) { // If user is mouse selecting and scrolls, they then point at new