Skip to content

Commit 39014fc

Browse files
DinahK-2SOCopilot
andcommitted
Fix agent pane Ctrl+wheel zoom
Prioritize vertical Ctrl+wheel zoom for agent TermControls while preserving plain and horizontal VT mouse input. Add focused unit coverage and physical packaged E2E coverage with release checklist mapping. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent f67c04e commit 39014fc

12 files changed

Lines changed: 333 additions & 15 deletions

File tree

doc/release-check-list.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ Net effect: UT shrinks the manual matrix to "did the wiring and UI connect", not
167167
- [ ] `C263` `[new]` `[UT✓]` `[E2E]` **Keyboard selection keeps focused completed turns visible:** Tab and Up/Down navigation scrolls the chat only as needed to keep the focused completed turn visible, without overriding later manual scrolling. _(UT: `render_chat_keeps_keyboard_selected_completed_turn_visible`; E2E: `Feature.CompletedTurnSelection`.)_
168168
- [ ] `C264` `[new]` `[UT✓]` `[E2E]` **Completed turns toggle and select from rendered prompt targets:** A left-button Down/Up anywhere on the same visible prompt row, including its triangle, prefix, text, explicit blank row, or unused row-end space, collapses or expands only that turn and reuses the keyboard selection, highlight, navigation, Esc, and Enter behavior. Hover uses only a hand cursor: action metadata must not add hyperlink underline or tooltip text. Details, separators, drags, overlays, tab changes, scrolling, and stale/offscreen rows do not toggle; clicking the live input dialog clears the history selection and restores draft input. Multiline, wrapped, wide-cell, and double/triple-click paths preserve their expected geometry and text-selection behavior. _(UT: `clicking_multiline_completed_turn_prompt_selects_and_reuses_enter_toggle`, `completed_turn_user_input_hit_spans_full_row_with_wide_cells`, `completed_turn_prompt_rows_expose_state_aware_action_links`, `overlay_preserves_full_rows_and_cell_styles`, `CompletedTurnActionHyperlinksSuppressUnderlines`, `clicking_input_dialog_restores_input_navigation_after_mouse_turn_selection`, `completed_turn_user_input_multi_click_preserves_turn_state_and_text_selection`, `completed_turn_mouse_selection_continues_with_keyboard_navigation`, `completed_turn_triangle_click_ignores_text_drag_and_hidden_chat`, `completed_turn_triangle_hits_follow_visible_scrolled_turns`, `completed_turn_prompt_hits_survive_a_clipped_header_row`; E2E: `Feature.AgentMouse` `CompletedTurnMouse` / `CompletedTurnPromptMouse`; hand cursor and absence of action underline manually verified on the exact deployed Dev build.)_
169169
- [ ] `C242` `[new]` `[UT✓]` `[E2E]` **Mouse wheel scrolls chat without changing the draft:** Wheel input scrolls the chat viewport while Up/Down remain prompt-history controls and the unsent draft stays intact. _(UT: `mouse_wheel_scrolls_chat_without_changing_input_history`; #506; E2E: `Feature.AgentMouse`.)_
170+
- [ ] `C295` `[new]` `[UT✓]` `[E2E]` **Ctrl+wheel zooms the agent pane while plain wheel scrolls chat:** Physical Ctrl+wheel changes the agent pane font size before VT mouse tracking can consume the event, while physical plain wheel still scrolls chat and both paths preserve the unsent draft. _(UT: `AgentPaneCtrlWheelZoomsBeforeVtMouse`; #790; E2E: `Feature.AgentMouse`.)_
170171
- [ ] `C243` `[new]` `[UT✓]` `[E2E]` **Mouse selection copies text and clears after copy:** Double-click selection survives release, `Ctrl+C` copies the exact selected text with confirmation instead of canceling/closing the pane, and the selection is then cleared. _(UT: `mouse_release_does_not_return_text_for_automatic_copy`, `clearing_selection_resets_multi_click_sequence`; #506; E2E: `Feature.AgentMouse`.)_
171172
- [ ] `C287` `[new]` `[UT✓]` `[E2E]` **Ctrl+A selects and copies the current agent frame:** Plain `Ctrl+A` selects the current rendered WTA frame through the existing text-selection path; `Ctrl+C` copies it with the existing confirmation and clears selection so a later `Ctrl+C` cannot replay stale text. _(UT: `select_all_extracts_and_highlights_the_current_frame`, `select_all_tracks_the_latest_frame_snapshot`, `ctrl_a_selects_current_rendered_frame_without_altering_input`; E2E: `Feature.AgentSelectAll`.)_
172173
- [ ] `C265` `[new]` `[UT✓]` `[E2E]` **Right-click copies the agent text selection:** A physical right-click copies the exact WTA-owned selection through the OS clipboard, clears it, shows the existing copied confirmation, and never also pastes; the next right-click follows the no-selection Default Paste path without replaying selected text. _(UT: `right_click_copy_event_is_forwarded`, `right_click_copies_and_clears_text_selection`; E2E: `Feature.AgentMouse` `RightClickCopy`.)_
@@ -465,4 +466,4 @@ Net effect: UT shrinks the manual matrix to "did the wiring and UI connect", not
465466
- Slash commands: `tools\wta\src\commands.rs`.
466467
- Session state model: `tools\wta\src\agent_sessions.rs`, `tools\wta\AGENTS.md`.
467468
- Multi-window agent pane architecture: `doc\specs\Multi-window-agent-pane.md`.
468-
- Autofix flow, logging, and runtime layout: `AGENTS.md`.
469+
- Autofix flow, logging, and runtime layout: `AGENTS.md`.

src/cascadia/TerminalApp/AgentPaneContent.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,7 @@ namespace winrt::TerminalApp::implementation
419419
{
420420
impl->UpdateSettings(settings);
421421
}
422+
GetTermControl().EnableAgentMouseWheelZoom(true);
422423

423424
const winrt::Microsoft::Terminal::Control::KeyChord ctrlV{ Windows::System::VirtualKeyModifiers::Control, 'V', 0 };
424425
if (const auto actionMap = settings.ActionMap())

src/cascadia/TerminalControl/ControlInteractivity.cpp

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -528,8 +528,29 @@ namespace winrt::Microsoft::Terminal::Control::implementation
528528
const Core::Point delta,
529529
const Core::Point pixelPosition,
530530
const Control::MouseButtonState buttonState)
531+
{
532+
return MouseWheel(modifiers, delta, pixelPosition, buttonState, false);
533+
}
534+
535+
bool ControlInteractivity::MouseWheel(const ::Microsoft::Terminal::Core::ControlKeyStates modifiers,
536+
const Core::Point delta,
537+
const Core::Point pixelPosition,
538+
const Control::MouseButtonState buttonState,
539+
const bool prioritizeZoom)
531540
{
532541
const auto terminalPosition = _getTerminalPosition(til::point{ pixelPosition }, false);
542+
const auto ctrlPressed = modifiers.IsCtrlPressed();
543+
const auto shiftPressed = modifiers.IsShiftPressed();
544+
545+
if (prioritizeZoom &&
546+
ctrlPressed &&
547+
!shiftPressed &&
548+
delta.Y != 0 &&
549+
_core->Settings().ScrollToZoom())
550+
{
551+
_mouseZoomHandler(delta.Y);
552+
return false;
553+
}
533554

534555
// Short-circuit isReadOnly check to avoid warning dialog.
535556
//
@@ -552,9 +573,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
552573
buttonState);
553574
}
554575

555-
const auto ctrlPressed = modifiers.IsCtrlPressed();
556-
const auto shiftPressed = modifiers.IsShiftPressed();
557-
558576
if (ctrlPressed && shiftPressed && _core->Settings().ScrollToChangeOpacity())
559577
{
560578
_mouseTransparencyHandler(delta.Y);

src/cascadia/TerminalControl/ControlInteractivity.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
7777
const Core::Point delta,
7878
const Core::Point pixelPosition,
7979
const Control::MouseButtonState state);
80+
bool MouseWheel(const ::Microsoft::Terminal::Core::ControlKeyStates modifiers,
81+
const Core::Point delta,
82+
const Core::Point pixelPosition,
83+
const Control::MouseButtonState state,
84+
bool prioritizeZoom);
8085

8186
void UpdateScrollbar(const float newValue);
8287

src/cascadia/TerminalControl/TermControl.cpp

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2171,12 +2171,16 @@ namespace winrt::Microsoft::Terminal::Control::implementation
21712171

21722172
const auto point = args.GetCurrentPoint(*this);
21732173
auto delta = point.Properties().MouseWheelDelta();
2174-
auto result = _interactivity.MouseWheel(ControlKeyStates{ args.KeyModifiers() },
2175-
point.Properties().IsHorizontalMouseWheel() ?
2176-
Core::Point{ delta, 0 } :
2177-
Core::Point{ 0, delta },
2178-
_toTerminalOrigin(point.Position()),
2179-
TermControl::GetPressedMouseButtons(point));
2174+
const auto modifiers = ControlKeyStates{ args.KeyModifiers() };
2175+
const auto wheelDelta = point.Properties().IsHorizontalMouseWheel() ?
2176+
Core::Point{ delta, 0 } :
2177+
Core::Point{ 0, delta };
2178+
const auto interactivity = winrt::get_self<implementation::ControlInteractivity>(_interactivity);
2179+
const auto result = interactivity->MouseWheel(modifiers,
2180+
wheelDelta,
2181+
_toTerminalOrigin(point.Position()),
2182+
TermControl::GetPressedMouseButtons(point),
2183+
_agentMouseWheelZoomEnabled);
21802184
if (result)
21812185
{
21822186
args.Handled(true);
@@ -2206,7 +2210,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
22062210
WI_SetFlagIf(state, Control::MouseButtonState::IsMiddleButtonDown, midButtonDown);
22072211
WI_SetFlagIf(state, Control::MouseButtonState::IsRightButtonDown, rightButtonDown);
22082212

2209-
return _interactivity.MouseWheel(modifiers, delta, _toTerminalOrigin(location), state);
2213+
const auto interactivity = winrt::get_self<implementation::ControlInteractivity>(_interactivity);
2214+
return interactivity->MouseWheel(modifiers, delta, _toTerminalOrigin(location), state, _agentMouseWheelZoomEnabled);
22102215
}
22112216

22122217
// Method Description:

src/cascadia/TerminalControl/TermControl.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
6464
bool CopySelectionToClipboard(bool dismissSelection, bool singleLine, bool withControlSequences, const CopyFormat formats);
6565
void PasteTextFromClipboard();
6666
void EnableAgentPasteShortcutFallback(bool enabled) noexcept { _agentPasteShortcutFallbackEnabled = enabled; }
67+
void EnableAgentMouseWheelZoom(bool enabled) noexcept { _agentMouseWheelZoomEnabled = enabled; }
6768
void SelectAll();
6869
bool ToggleBlockSelection();
6970
void ToggleMarkMode();
@@ -257,6 +258,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
257258
Control::ControlInteractivity _interactivity{ nullptr };
258259
Control::ControlCore _core{ nullptr };
259260
Control::IKeyBindings _keyBindings{ nullptr };
261+
bool _agentMouseWheelZoomEnabled{ false };
260262
TsfDataProvider _tsfDataProvider{ this };
261263
winrt::com_ptr<SearchBoxControl> _searchBox;
262264

src/cascadia/TerminalControl/TermControl.idl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,5 +170,6 @@ namespace Microsoft.Terminal.Control
170170
void ClearQuickFix();
171171

172172
void Detach();
173+
void EnableAgentMouseWheelZoom(Boolean enabled);
173174
}
174175
}

src/cascadia/UnitTests_Control/ControlInteractivityTests.cpp

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ namespace ControlUnitTests
4242
TEST_METHOD(IncrementCircularBufferWithSelection);
4343

4444
TEST_METHOD(GetMouseEventsInTest);
45+
TEST_METHOD(AgentPaneCtrlWheelZoomsBeforeVtMouse);
4546
TEST_METHOD(AltBufferClampMouse);
4647
TEST_METHOD(ParseCompletedTurnActionHyperlinks);
4748
TEST_METHOD(CompletedTurnActionHyperlinksSuppressUnderlines);
@@ -1009,6 +1010,58 @@ namespace ControlUnitTests
10091010
cursorPosition0.to_core_point());
10101011
}
10111012

1013+
void ControlInteractivityTests::AgentPaneCtrlWheelZoomsBeforeVtMouse()
1014+
{
1015+
WEX::TestExecution::DisableVerifyExceptions disableVerifyExceptions{};
1016+
1017+
auto [settings, conn] = _createSettingsAndConnection();
1018+
settings->ScrollToZoom(true);
1019+
auto [core, interactivity] = _createCoreAndInteractivity(*settings, *conn);
1020+
_standardInit(core, interactivity);
1021+
1022+
std::deque<std::wstring> expectedOutput{};
1023+
auto validateDrained = _addInputCallback(conn, expectedOutput);
1024+
1025+
auto& term{ *core->_terminal };
1026+
term.Write(L"\x1b[?1000h\x1b[?1006h");
1027+
1028+
const auto modifiers = ControlKeyStates{ CTRL_PRESSED };
1029+
interactivity->MouseWheel(modifiers,
1030+
Core::Point{ 0, WHEEL_DELTA },
1031+
Core::Point{ 0, 0 },
1032+
{},
1033+
true);
1034+
1035+
VERIFY_ARE_EQUAL(1.0f, core->_accumulatedFontSizeDelta);
1036+
VERIFY_ARE_EQUAL(0u, expectedOutput.size());
1037+
1038+
expectedOutput.push_back(L"\x1b[<64;1;1M");
1039+
interactivity->MouseWheel({},
1040+
Core::Point{ 0, WHEEL_DELTA },
1041+
Core::Point{ 0, 0 },
1042+
{},
1043+
true);
1044+
VERIFY_ARE_EQUAL(0u, expectedOutput.size());
1045+
VERIFY_ARE_EQUAL(1.0f, core->_accumulatedFontSizeDelta);
1046+
1047+
expectedOutput.push_back(L"\x1b[<83;1;1M");
1048+
interactivity->MouseWheel(modifiers,
1049+
Core::Point{ WHEEL_DELTA, 0 },
1050+
Core::Point{ 0, 0 },
1051+
{},
1052+
true);
1053+
VERIFY_ARE_EQUAL(0u, expectedOutput.size());
1054+
VERIFY_ARE_EQUAL(1.0f, core->_accumulatedFontSizeDelta);
1055+
1056+
expectedOutput.push_back(L"\x1b[<80;1;1M");
1057+
interactivity->MouseWheel(modifiers,
1058+
Core::Point{ 0, WHEEL_DELTA },
1059+
Core::Point{ 0, 0 },
1060+
{});
1061+
VERIFY_ARE_EQUAL(0u, expectedOutput.size());
1062+
VERIFY_ARE_EQUAL(1.0f, core->_accumulatedFontSizeDelta);
1063+
}
1064+
10121065
void ControlInteractivityTests::AltBufferClampMouse()
10131066
{
10141067
// This is a test for

test/e2e/ItE2E/ItE2E.psm1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ $publicFns = @(
3636
# Ui
3737
'Get-UiTree', 'Find-UiElement', 'Get-UiElement', 'Test-UiElementEnabled', 'Invoke-UiElement', 'Invoke-UiClick', 'Get-UiTextBounds', 'Invoke-UiMouseDrag', 'Set-UiValue', 'Get-UiValue',
3838
'Wait-UiElement', 'Test-UiElementExists', 'Save-UiScreenshot', 'Get-WtWindowHwnds', 'Test-WinAppAvailable',
39-
'Send-WtWindowKey', 'Set-WtWindowForeground', 'Test-WtWindowKeyFocusable', 'Open-WtSettings', 'Test-CommandPaletteOpen', 'Invoke-SettingsNav',
39+
'Send-WtWindowKey', 'Invoke-WtWindowWheel', 'Set-WtWindowForeground', 'Test-WtWindowKeyFocusable', 'Open-WtSettings', 'Test-CommandPaletteOpen', 'Invoke-SettingsNav',
4040
# Observe
4141
'Get-ItLogDir', 'Initialize-LogOffsets', 'Get-ItLogText', 'Start-WtEventListener', 'Get-WtEvents',
4242
'Wait-WtEvent', 'Stop-WtEventListener', 'Get-ContextBundle', 'ConvertTo-ContextText',

0 commit comments

Comments
 (0)