mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-15 02:49:33 +01:00
Merge pull request #664: fix(terminal): forward touch scrolling for native SGR mouse tracking
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
commit
04faa0d3f1
6 files changed
+171
-15
No files matched your search
@@ -876,4 +876,60 @@ describe('scroll routing diagnostic (issue #205 round 2)', () => {
|
||||
app._logScrollRouting('page-keys');
|
||||
expect(logs[0]).toContain('cliVersion=unknown');
|
||||
});
|
||||
|
||||
it("reports xterm's mouse encoding, the input touch forwarding to native tracking reads", () => {
|
||||
const { app, logs } = hollowApp({ mode: 'shell' });
|
||||
app._logScrollRouting('local-scrollback'); // no readable xterm private
|
||||
expect(logs[0]).toContain('mouseEncoding=unknown');
|
||||
|
||||
// An app switching to SGR changes what touch does, so it prints again.
|
||||
app.terminal.modes.mouseTrackingMode = 'any';
|
||||
app.terminal._core = { coreMouseService: { activeEncoding: 'SGR' } };
|
||||
app._logScrollRouting('local-scrollback');
|
||||
expect(logs).toHaveLength(2);
|
||||
expect(logs[1]).toContain('mouseTracking=any');
|
||||
expect(logs[1]).toContain('mouseEncoding=SGR');
|
||||
});
|
||||
});
|
||||
|
||||
describe('touch scrolling when xterm owns native mouse tracking', () => {
|
||||
it.each(['vt200', 'drag', 'any'])('forwards touch for %s tracking even for a shell session', (mode) => {
|
||||
const { app } = hollowApp({ mode: 'shell' });
|
||||
app.terminal.modes.mouseTrackingMode = mode;
|
||||
app.terminal._core = { coreMouseService: { activeEncoding: 'SGR' } };
|
||||
app.terminal.buffer.active.type = 'alternate';
|
||||
expect(app._shouldForwardWheelToApp({ shiftKey: false })).toBe(false);
|
||||
expect(app._shouldForwardTouchToApp()).toBe(true);
|
||||
});
|
||||
|
||||
it('does not send SGR to a legacy-protocol or X10 application', () => {
|
||||
const { app } = hollowApp({ mode: 'shell' });
|
||||
app.terminal.modes.mouseTrackingMode = 'vt200';
|
||||
app.terminal._core = { coreMouseService: { activeEncoding: 'DEFAULT' } };
|
||||
expect(app._shouldForwardTouchToApp()).toBe(false);
|
||||
app.terminal._core.coreMouseService.activeEncoding = 'SGR';
|
||||
app.terminal.modes.mouseTrackingMode = 'x10';
|
||||
expect(app._shouldForwardTouchToApp()).toBe(false);
|
||||
});
|
||||
|
||||
it('does not send cell-based SGR reports to an app that asked for SGR pixel coordinates', () => {
|
||||
const { app } = hollowApp({ mode: 'shell' });
|
||||
app.terminal.modes.mouseTrackingMode = 'any';
|
||||
app.terminal._core = { coreMouseService: { activeEncoding: 'SGR_PIXELS' } };
|
||||
expect(app._shouldForwardTouchToApp()).toBe(false);
|
||||
});
|
||||
|
||||
it('keeps the explicit local-history preference for touch', () => {
|
||||
const { app } = hollowApp({ mode: 'shell' });
|
||||
app.terminal.modes.mouseTrackingMode = 'any';
|
||||
app.loadAppSettingsFromStorage = () => ({ terminalWheelLocalScrollback: true });
|
||||
expect(app._shouldForwardTouchToApp()).toBe(false);
|
||||
});
|
||||
|
||||
it('uses normal CLI routing when xterm mouse tracking is off', () => {
|
||||
const { app } = hollowApp({ cliVersion: '2.1.187', cliMouseTracking: true });
|
||||
expect(app._shouldForwardTouchToApp()).toBe(true);
|
||||
app.sessions.get('sess-1').cliMouseTracking = false;
|
||||
expect(app._shouldForwardTouchToApp()).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -11,6 +11,10 @@
|
||||
* the contact's id (one per touchstart, carried by its momentum frames), and a
|
||||
* contact is one gesture.
|
||||
*
|
||||
* Its own describe covers the other touch route in the same listeners (#663):
|
||||
* an app that turned on xterm's own SGR mouse tracking gets a drag and its
|
||||
* fling momentum as SGR wheel reports, not a local scroll.
|
||||
*
|
||||
* What runs for real: the shipped app (page served by a WebServer on port 0),
|
||||
* its xterm, the touch listeners, the momentum loop on requestAnimationFrame,
|
||||
* the gates in terminal-ui.js and the 40 ms key coalescer. What is stubbed:
|
||||
@@ -144,4 +148,65 @@ describe('touch paging of a hollow Codex pane in a real browser', () => {
|
||||
expect(second.keys).toBe(UP + UP);
|
||||
await touch('touchend', x, y0 + Math.ceil(cell));
|
||||
}, 60000);
|
||||
|
||||
// Not the page-key fallback: a shell app (htop, vim) that turned on xterm's
|
||||
// own SGR mouse tracking gets touch drags and fling momentum as SGR wheel
|
||||
// reports (_shouldForwardTouchToApp), at phone and tablet widths. Shares this
|
||||
// file's page and touch dispatcher.
|
||||
describe('touch forwarding when xterm owns native SGR mouse tracking', () => {
|
||||
it.each([390, 820])('forwards drag and momentum with native mouse tracking at %ipx', async (width) => {
|
||||
await page.setViewportSize({ width, height: 900 });
|
||||
await page.evaluate(async (id) => {
|
||||
const app = (window as any).app;
|
||||
app.sessions.set(id, { id, name: id, mode: 'shell', status: 'idle' });
|
||||
app.activeSessionId = id;
|
||||
app.loadAppSettingsFromStorage = () => ({ terminalWheelLocalScrollback: false });
|
||||
app._wheelSgrQueue = '';
|
||||
const sent: string[] = [];
|
||||
app._sendInputEphemeral = (_id: string, data: string) => sent.push(data);
|
||||
(window as any).__mouseTouchProbe = { sent };
|
||||
await new Promise<void>((resolve) => app.terminal.write('\x1b[?1049h\x1b[?1003h\x1b[?1006h', resolve));
|
||||
}, PROBE_ID);
|
||||
const point = await page.evaluate(() => {
|
||||
const app = (window as any).app;
|
||||
const r = document.getElementById('terminalContainer')!.getBoundingClientRect();
|
||||
return {
|
||||
x: r.left + 30,
|
||||
y: r.top + 200,
|
||||
mode: app.terminal.modes.mouseTrackingMode,
|
||||
buffer: app.terminal.buffer.active.type,
|
||||
};
|
||||
});
|
||||
expect(point.mode).toBe('any');
|
||||
expect(point.buffer).toBe('alternate');
|
||||
await touch('touchstart', point.x, point.y);
|
||||
await touch('touchmove', point.x, point.y - 60);
|
||||
const drag = await page.evaluate(() => {
|
||||
(window as any).app._flushWheelSgrQueue();
|
||||
return (window as any).__mouseTouchProbe.sent.join('');
|
||||
});
|
||||
expect(drag).toMatch(/\x1b\[<65;\d+;\d+M/);
|
||||
await touch('touchend', point.x, point.y - 60);
|
||||
await page.waitForTimeout(150);
|
||||
const momentum = await page.evaluate(() => {
|
||||
(window as any).app._flushWheelSgrQueue();
|
||||
return (window as any).__mouseTouchProbe.sent.join('');
|
||||
});
|
||||
expect(momentum.length).toBeGreaterThan(drag.length);
|
||||
// Stop momentum before changing the requested mouse encoding.
|
||||
await touch('touchstart', point.x, point.y);
|
||||
await touch('touchcancel', point.x, point.y);
|
||||
await page.evaluate(async () => {
|
||||
const app = (window as any).app;
|
||||
app._wheelSgrQueue = '';
|
||||
(window as any).__mouseTouchProbe.sent.length = 0;
|
||||
await new Promise<void>((resolve) => app.terminal.write('\x1b[?1006l', resolve));
|
||||
});
|
||||
await touch('touchstart', point.x, point.y);
|
||||
await touch('touchmove', point.x, point.y - 60);
|
||||
await page.waitForTimeout(60);
|
||||
expect(await page.evaluate(() => (window as any).__mouseTouchProbe.sent.join(''))).toBe('');
|
||||
await touch('touchcancel', point.x, point.y);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -49,8 +49,10 @@ describe('xterm private-API dependency guard', () => {
|
||||
'xterm moved off the verified version — re-verify _kickRenderer in a real browser ' +
|
||||
'(terminal-ui.js: _core._renderService._renderDebouncer._animationFrame) AND the ' +
|
||||
'CompositionHelper fields installEditSync() uses (terminal-keycode229-recovery.js: ' +
|
||||
'_handleAnyTextareaChanges, _coreService, _isComposing, _isSendingComposition, _dataAlreadySent), ' +
|
||||
'then update VERIFIED_XTERM_VERSION here. The accessor is optional-chained, so a renamed field ' +
|
||||
'_handleAnyTextareaChanges, _coreService, _isComposing, _isSendingComposition, _dataAlreadySent) ' +
|
||||
'and the mouse-encoding read _shouldForwardTouchToApp makes (terminal-ui.js: ' +
|
||||
'_core.coreMouseService.activeEncoding), then update VERIFIED_XTERM_VERSION here. ' +
|
||||
'The accessor is optional-chained, so a renamed field ' +
|
||||
'degrades to a silent no-op and the freeze it heals comes back unnoticed.'
|
||||
).toBe(VERIFIED_XTERM_VERSION);
|
||||
});
|
||||
@@ -60,9 +62,12 @@ describe('xterm private-API dependency guard', () => {
|
||||
// append-only diff duplicates). It reaches `_compositionHelper`, `_coreService`, `_isComposing`,
|
||||
// `_isSendingComposition` and `_dataAlreadySent`; if xterm renames any of them the install quietly
|
||||
// falls back to xterm's own handler and the duplication returns (or, for `_isSendingComposition`, a
|
||||
// composition xterm finalizes at an Enter keydown is sent twice). Property names survive
|
||||
// composition xterm finalizes at an Enter keydown is sent twice). terminal-ui.js's
|
||||
// _shouldForwardTouchToApp reads `_core.coreMouseService.activeEncoding` to forward a touch drag
|
||||
// only to an app that asked for SGR mouse reports; if either name changes the read is undefined
|
||||
// and touch quietly stops forwarding to native SGR tracking apps. Property names survive
|
||||
// minification, so a string check on the shipped bundle catches a rename on upgrade.
|
||||
it('still ships the composition-helper fields the edit-based 229 sync depends on', () => {
|
||||
it('still ships the composition-helper and mouse-service fields terminal input depends on', () => {
|
||||
const bundle = readFileSync(resolve(root, 'node_modules/@xterm/xterm/lib/xterm.js'), 'utf8');
|
||||
for (const name of [
|
||||
'_handleAnyTextareaChanges',
|
||||
@@ -71,11 +76,14 @@ describe('xterm private-API dependency guard', () => {
|
||||
'_isComposing',
|
||||
'_isSendingComposition',
|
||||
'_dataAlreadySent',
|
||||
'coreMouseService',
|
||||
'activeEncoding',
|
||||
]) {
|
||||
expect(
|
||||
bundle,
|
||||
`xterm no longer mentions ${name}: re-verify terminal-keycode229-recovery.js installEditSync() ` +
|
||||
'(src/web/public) against the new CompositionHelper before bumping VERIFIED_XTERM_VERSION'
|
||||
'against the new CompositionHelper, and terminal-ui.js _shouldForwardTouchToApp against the ' +
|
||||
'new CoreMouseService (both in src/web/public), before bumping VERIFIED_XTERM_VERSION'
|
||||
).toContain(name);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user