forward mouse wheel as xterm wheel escapes when TUI asks for it (T-74)
test / unit + widget + golden + a11y (push) Failing after 31s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 1m0s

Previously every PointerScrollEvent fell straight to PgUp/PgDown
keyInput as a "universal scroll" workaround. That kept plain shells
scrolling but starved vim mouse=a / htop / less of the wheel events
they expect.

Now `_onPointerSignal` checks `terminal.mouseMode.reportScroll`
first (the cascade of mouse handlers cares about this flag). If
the inner program declared ?1000h / ?1002h / ?1003h (optionally
+?1006h SGR), the wheel forwards as `wheelUp` / `wheelDown` button
events through the existing `renderTerminal.mouseEvent` path. Plain
shells stay on PgUp/PgDown because their mouse mode is `none` —
the existing test for that path keeps passing unchanged.

Click + drag forwarding through the gesture handler was already
wired (renderTerminal.mouseEvent for taps), so T-74's acceptance
list is met by this scroll fix alone.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-05-18 13:51:55 +02:00
co-authored by Claude
parent 74f9a45539
commit ae8d774529
5 changed files with 62 additions and 7 deletions
+27
View File
@@ -2,6 +2,7 @@
/// gesture / keyboard / scroll / render plumbing around a `Terminal`.
library;
import 'package:clide/src/terminal/src/core/mouse/mode.dart';
import 'package:clide/src/terminal/src/terminal.dart';
import 'package:clide/src/terminal/src/terminal_view.dart';
import 'package:clide/src/terminal/src/ui/controller.dart';
@@ -97,6 +98,32 @@ void main() {
expect(r.outputs, isNotEmpty);
});
testWidgets('PointerScrollEvent forwards as xterm wheel escapes when mouseMode.reportScroll (T-74)', (tester) async {
final r = _OutputRecorder();
final t = r.build();
// Have the inner program declare ?1000h (upDownScroll) so
// mouseMode.reportScroll becomes true.
t.setMouseMode(MouseMode.upDownScroll);
await tester.pumpWidget(_host(TerminalView(t)));
final viewCenter = tester.getCenter(find.byType(TerminalView));
final testGesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await testGesture.addPointer(location: viewCenter);
await tester.sendEventToBinding(PointerScrollEvent(
position: viewCenter,
scrollDelta: const Offset(0, 100),
));
await tester.pump();
expect(r.outputs, isNotEmpty);
// Wheel-down id is 64+5=69; normal-mode reporter encodes button
// bytes as 32+id, but the reporter chunks across modes — the
// load-bearing assertion is "no PgDn key escape was emitted".
// PgDn under default keyboard sends ESC[6~. Scrolls in mouse
// mode must NOT contain that — they contain CSI M / SGR mouse
// sequences instead.
final combined = r.outputs.join();
expect(combined.contains('\x1b[6~'), isFalse, reason: 'expected wheel forwarded as mouse, not PgDn');
});
testWidgets('non-scroll PointerSignalEvent is a no-op', (tester) async {
final r = _OutputRecorder();
final t = r.build();