fix(run): restore wheel scrolling in the terminal, suppress only arrow emulation
The previous `attachCustomWheelEventHandler(() => false)` killed scrolling outright. xterm's `bindMouse` consults that handler in BOTH wheel paths — the one that emits a real SGR mouse report and the fallback that turns a notch on an alt-screen buffer into ESC[A/ESC[B — so a blanket false also blocked tmux's own wheel scrolling (tmux runs with `mouse on`, i.e. tracking is active). Now the handler returns false only for the exact bad case: no wheel tracking AND an alternate buffer. `x10` counts as untracked because that protocol reports button presses only, never the wheel, so xterm takes the emulation path for it too.
This commit is contained in:
@@ -16,9 +16,20 @@ const openMock = vi.fn();
|
||||
const disposeMock = vi.fn();
|
||||
const wheelHandlerMock = vi.fn();
|
||||
const keyHandlerMock = vi.fn();
|
||||
// Mutable stand-ins for the two live terminal facts the wheel handler reads.
|
||||
const termState = {
|
||||
modes: { mouseTrackingMode: "none" as "none" | "x10" | "vt200" | "drag" | "any" },
|
||||
buffer: { active: { type: "alternate" as "normal" | "alternate" } },
|
||||
};
|
||||
|
||||
vi.mock("@xterm/xterm", () => ({
|
||||
Terminal: vi.fn().mockImplementation(() => ({
|
||||
get modes() {
|
||||
return termState.modes;
|
||||
},
|
||||
get buffer() {
|
||||
return termState.buffer;
|
||||
},
|
||||
open: openMock,
|
||||
write: writeMock,
|
||||
onData: (fn: (d: string) => void) => {
|
||||
@@ -58,6 +69,8 @@ global.WebSocket = MockWebSocket;
|
||||
|
||||
describe("TerminalView", () => {
|
||||
beforeEach(() => {
|
||||
termState.modes.mouseTrackingMode = "none";
|
||||
termState.buffer.active.type = "alternate";
|
||||
MockWebSocket.instances = [];
|
||||
onDataHandlers.length = 0;
|
||||
writeMock.mockClear();
|
||||
@@ -99,6 +112,8 @@ describe("TerminalView", () => {
|
||||
});
|
||||
|
||||
it("swallows wheel events xterm would otherwise turn into arrow keys", () => {
|
||||
termState.modes.mouseTrackingMode = "none";
|
||||
termState.buffer.active.type = "alternate";
|
||||
render(<TerminalView runId="ccam-lane-1" wsBaseUrl="ws://localhost:4820" />);
|
||||
expect(wheelHandlerMock).toHaveBeenCalledTimes(1);
|
||||
// false = xterm skips its alt-screen wheel→cursor-key emulation, which in a
|
||||
@@ -106,6 +121,29 @@ describe("TerminalView", () => {
|
||||
expect(wheelHandlerMock.mock.calls[0]![0]!(new Event("wheel"))).toBe(false);
|
||||
});
|
||||
|
||||
it("lets the wheel through once the pane's program tracks the mouse", () => {
|
||||
// tmux with `mouse on` sets this; the wheel must reach it as a real SGR
|
||||
// report, so returning false here would kill scrolling entirely.
|
||||
termState.modes.mouseTrackingMode = "any";
|
||||
termState.buffer.active.type = "alternate";
|
||||
render(<TerminalView runId="ccam-lane-1" wsBaseUrl="ws://localhost:4820" />);
|
||||
expect(wheelHandlerMock.mock.calls[0]![0]!(new Event("wheel"))).toBe(true);
|
||||
});
|
||||
|
||||
it("lets the wheel through on a normal buffer, where xterm scrolls scrollback", () => {
|
||||
termState.modes.mouseTrackingMode = "none";
|
||||
termState.buffer.active.type = "normal";
|
||||
render(<TerminalView runId="ccam-lane-1" wsBaseUrl="ws://localhost:4820" />);
|
||||
expect(wheelHandlerMock.mock.calls[0]![0]!(new Event("wheel"))).toBe(true);
|
||||
});
|
||||
|
||||
it("treats x10 tracking as no wheel tracking (x10 never reports the wheel)", () => {
|
||||
termState.modes.mouseTrackingMode = "x10";
|
||||
termState.buffer.active.type = "alternate";
|
||||
render(<TerminalView runId="ccam-lane-1" wsBaseUrl="ws://localhost:4820" />);
|
||||
expect(wheelHandlerMock.mock.calls[0]![0]!(new Event("wheel"))).toBe(false);
|
||||
});
|
||||
|
||||
it("keeps Shift+Tab in the terminal instead of letting focus escape", () => {
|
||||
render(<TerminalView runId="ccam-lane-1" wsBaseUrl="ws://localhost:4820" />);
|
||||
const handler = keyHandlerMock.mock.calls[0]![0]! as (e: KeyboardEvent) => boolean;
|
||||
|
||||
Reference in New Issue
Block a user