mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-14 02:29:33 +01:00
Merge #771 fix(pi): keep the mouse wheel working after a tab switch or reload
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
4d20b9c898
17 files changed
+172
-49
No files matched your search
@@ -102,7 +102,7 @@ describe('isMuxAltScreenOnlyStripMode', () => {
|
||||
expect(isMuxAltScreenOnlyStripMode(mode, false)).toBe(false);
|
||||
}
|
||||
// The full strip and the mouse strip already own their modes; never double-gate.
|
||||
for (const mode of ['claude', 'codex', 'gemini', 'opencode', 'copilot'] as const) {
|
||||
for (const mode of ['claude', 'codex', 'gemini', 'opencode', 'copilot', 'pi'] as const) {
|
||||
expect(isMuxAltScreenOnlyStripMode(mode, true)).toBe(false);
|
||||
}
|
||||
});
|
||||
@@ -372,6 +372,44 @@ describe('strip decision table: live stream = replay, for every stock CLI', () =
|
||||
* on itself. Left in, xterm reports a plain drag to the TUI instead of selecting, so Auto Copy and
|
||||
* copy-on-select did nothing (only Shift+drag selected). Same strip, same reason.
|
||||
*/
|
||||
describe('pi: alt-screen + mouse DECSETs stripped, 3J kept (#758)', () => {
|
||||
// What fullscreen pi really emits on entering its TUI, read off a pane on 1.1.0, 1.4.0 and 1.5.0.
|
||||
// Tracking comes on once, here, so a replay that drops it leaves the wheel dead unless the
|
||||
// server remembers it. ?1004h (focus reporting) is not a mouse mode and stays.
|
||||
const ENTER = '\x1b[?1049h\x1b[?7l\x1b[?1000h\x1b[?1002h\x1b[?1004h\x1b[?1006h';
|
||||
|
||||
it('is a mouse-strip mode under tmux, and only there', () => {
|
||||
expect(isMuxMouseStripMode('pi', true)).toBe(true);
|
||||
expect(isMuxMouseStripMode('pi', false)).toBe(false);
|
||||
});
|
||||
|
||||
it('drops the alt-screen and mouse enables, and keeps focus reporting and 3J', () => {
|
||||
const session = new Session({ workingDir: '/tmp', mode: 'pi' as never, useMux: true });
|
||||
handleOutput(session, `${ENTER}\x1b[3JTUI`);
|
||||
expect(session.terminalBuffer).toBe('\x1b[?7l\x1b[?1004h\x1b[3JTUI');
|
||||
});
|
||||
|
||||
it('publishes cliMouseTracking so the wheel survives a replay', () => {
|
||||
const session = new Session({ workingDir: '/tmp', mode: 'pi' as never, useMux: true });
|
||||
handleOutput(session, `${ENTER}TUI`);
|
||||
expect(session.toState().cliMouseTracking).toBe(true);
|
||||
handleOutput(session, '\x1b[?1006l\x1b[?1004l\x1b[?1003l\x1b[?1002l\x1b[?1000l');
|
||||
expect(session.toState().cliMouseTracking).toBeFalsy();
|
||||
});
|
||||
|
||||
it('never sets the flag for an inline pi, which emits no mouse enable', () => {
|
||||
const session = new Session({ workingDir: '/tmp', mode: 'pi' as never, useMux: true });
|
||||
handleOutput(session, '\x1b[?2004hpi');
|
||||
expect(session.toState().cliMouseTracking).toBeFalsy();
|
||||
});
|
||||
|
||||
it('leaves a direct-PTY pi pane untouched', () => {
|
||||
const session = new Session({ workingDir: '/tmp', mode: 'pi' as never, useMux: false });
|
||||
handleOutput(session, '\x1b[?1049h\x1b[?1002hTUI');
|
||||
expect(session.terminalBuffer).toBe('\x1b[?1049h\x1b[?1002hTUI');
|
||||
});
|
||||
});
|
||||
|
||||
describe('copilot: alt-screen + mouse DECSETs stripped, 3J kept', () => {
|
||||
it('is a mouse-strip mode under tmux, and only there', () => {
|
||||
expect(isMuxMouseStripMode('copilot', true)).toBe(true);
|
||||
|
||||
@@ -633,20 +633,22 @@ describe('capability shapes', () => {
|
||||
|
||||
it('declares which CLIs get the wheel forwarded, and whether a version gates it', () => {
|
||||
// Claude forwards from 2.1.187 (fullscreen only); tracked Codex and Copilot
|
||||
// forward without a version gate. Unmeasured entries stay 'never'.
|
||||
// forward without a version gate, and so does pi. Unmeasured entries stay 'never'.
|
||||
const mode = (id: string) => STOCK_CLIS.find((e) => (e.id as string) === id)?.capabilities.wheelForward;
|
||||
expect(mode('copilot')).toEqual({ mode: 'always' });
|
||||
expect(mode('claude')).toEqual({ mode: 'version-gated', minVersion: '2.1.187' });
|
||||
const forwarding = STOCK_CLIS.filter((e) => e.capabilities.wheelForward.mode !== 'never').map(
|
||||
(e) => e.id as string
|
||||
);
|
||||
expect(forwarding.sort()).toEqual(['claude', 'codex', 'copilot']);
|
||||
expect(forwarding.sort()).toEqual(['claude', 'codex', 'copilot', 'pi']);
|
||||
expect(CliEntrySchema.safeParse(baseEntry('copilot')).success).toBe(true);
|
||||
});
|
||||
|
||||
it('declares transcriptPageKeys for exactly the CLIs measured to page on PageUp/PageDown', () => {
|
||||
// claude (repaint mode), codex, opencode and copilot (all on the alternate screen) were
|
||||
// measured paging their own transcript on PageUp/PageDown; no other stock entry has been.
|
||||
// pi pages too when fullscreen, and stays out on purpose: in `tuiMode: regular` the same keys
|
||||
// move the caret of a multi-line draft.
|
||||
const paging = STOCK_CLIS.filter((e) => e.capabilities.transcriptPageKeys === true).map((e) => e.id as string);
|
||||
expect(paging.sort()).toEqual(['claude', 'codex', 'copilot', 'opencode']);
|
||||
});
|
||||
|
||||
@@ -898,6 +898,35 @@ describe('GitHub Copilot wheel forwarding', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('pi wheel forwarding (fullscreen by default since 1.0.0)', () => {
|
||||
// pi enables mouse tracking once at start. Codeman's replay (tab switch, reload) drops that
|
||||
// mode in xterm (`mouseTrackingMode` none, baseY 0), so the wheel hit an empty local buffer
|
||||
// and went dead until a redraw. Like Copilot, the server strips the DECSETs and publishes
|
||||
// `cliMouseTracking`; the gate forwards from that flag, which survives the replay.
|
||||
it('forwards the wheel while pi tracks the mouse', () => {
|
||||
const { app } = hollowApp({ mode: 'pi', cliMouseTracking: true });
|
||||
expect(app._shouldForwardWheelToApp({ shiftKey: false })).toBe(true);
|
||||
});
|
||||
|
||||
it('keeps Shift+wheel local', () => {
|
||||
expect(hollowApp({ mode: 'pi', cliMouseTracking: true }).app._shouldForwardWheelToApp({ shiftKey: true })).toBe(
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
it('sends nothing to an inline pi: page keys there move the caret of a multi-line draft', () => {
|
||||
// `tuiMode: regular`, and every pi below 1.0.0, emits no mouse enable, so the flag stays false
|
||||
// over a buffer that is hollow on a fresh pane. pi binds PageUp to its editor in that mode:
|
||||
// measured on real pi, the wheel moved the caret and the next X landed on the first line.
|
||||
const { app, sent } = hollowApp({ mode: 'pi', cliMouseTracking: false });
|
||||
|
||||
expect(app._shouldForwardWheelToApp({ shiftKey: false })).toBe(false);
|
||||
expect(app._maybePageCliTranscript({ shiftKey: false }, -18)).toBe(false);
|
||||
app._flushWheelSgrQueue();
|
||||
expect(sent).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('the paging gates asked for another pane (a TerminalTile)', () => {
|
||||
it('exports the paging math, and the primary pane runs on it', () => {
|
||||
const { app, sent, windowRef } = hollowApp();
|
||||
|
||||
Reference in new issue
Block a user