Files
locode/src/ui/ink/mouseInput.ts
T
kim 60c6021390 feat: split system prompt by local/cloud, improve mouse tracking, increase retries & iterations
- Separate system prompt for local vs cloud models via isLocal flag
  (isLocalBackendURL, buildSystemPrompt(..., isLocal), propagate through
  createSession/createSessionFromRecord/compactSession/spawnSubAgent)
- Increase MAX_EMPTY_RESPONSE_RETRIES from 1 to 3 for cloud model resilience
- Upgrade mouse input: full SGR-1006 parsing with col/row/pressed/modifiers,
  logicalButton() helper, and copyToClipboard() via OSC 52
- Add temporary mouse debug logging in App.tsx
- Increase DEFAULT_MAX_ITERATIONS from 100 to 300
- Update README mouse/scrollback docs, tweak diff-remove color
2026-08-21 18:01:29 +09:00

89 lines
3.4 KiB
TypeScript

// xterm SGR-1006 mouse sequences (enabled in App.tsx via `\x1b[?1002h\x1b[?1006h`)
// arrive on stdin as a single ink `useInput` event with the leading ESC stripped,
// e.g. "[<0;12;5M" (left button press at col 12, row 5) or "[<64;12;5M" (wheel up).
//
// Mode 1002 (button-event tracking) reports: button press, button release, and
// drag (motion while a button is held). Every active `useInput` hook in the tree
// receives the same raw event — ink has no concept of one handler "consuming" it
// before others see it — so any text-input component that doesn't recognize the
// sequence falls through to its regular-character handling and types the raw
// escape text into the field. Shared so every `useInput` consumer can ignore it
// the same way App.tsx's own handler already does.
//
// SGR-1006 format: [<button;col;rowM (press/drag) or [<button;col;rowm (release)
// button encodes button number + modifier keys:
// bit 0: shift
// bit 1: meta (alt)
// bit 2: ctrl
// bits 4-5: 00=left, 01=middle, 10=right
// Special: 64=wheel up, 65=wheel down
// Drag reports button 3 (all buttons released) during motion.
const MOUSE_SEQUENCE_RE = /^\[<(\d+);(\d+);(\d+)([Mm])$/;
export interface MouseEvent {
/** Raw button code (includes modifier bits in bits 0-2, button in bits 4-5). */
button: number;
/** 1-based column. */
col: number;
/** 1-based row. */
row: number;
/** True on press/drag (trailing 'M'), false on release (trailing 'm'). */
pressed: boolean;
/** True if shift was held. */
shift: boolean;
/** True if meta (alt) was held. */
meta: boolean;
/** True if ctrl was held. */
ctrl: boolean;
}
/** Which logical button is pressed (ignoring modifiers). */
export function logicalButton(e: MouseEvent): "left" | "middle" | "right" | "wheel-up" | "wheel-down" | "release" | "other" {
const raw = e.button & ~0x07; // strip modifier bits
if (raw === 0) return "left";
if (raw === 1) return "middle";
if (raw === 2) return "right";
if (raw === 64) return "wheel-up";
if (raw === 65) return "wheel-down";
if (raw === 3 || (!e.pressed && raw === 0)) return "release";
return "other";
}
export function matchMouseSequence(input: string): MouseEvent | null {
const match = MOUSE_SEQUENCE_RE.exec(input);
if (!match) return null;
const button = Number(match[1]);
const col = Number(match[2]);
const row = Number(match[3]);
const pressed = match[4] === "M";
return {
button,
col,
row,
pressed,
shift: !!(button & 4),
meta: !!(button & 8),
ctrl: !!(button & 16),
};
}
/** Write text to the system clipboard using OSC 52 (works over SSH, tmux, etc.).
* Falls back to platform-specific commands if OSC 52 doesn't work. */
export function copyToClipboard(text: string, stdout: NodeJS.WriteStream): void {
// OSC 52: \x1b]52;c;<base64>\x07 (target 'c' = system clipboard)
const base64 = Buffer.from(text, "utf-8").toString("base64");
// Write in chunks to avoid overflowing terminal input buffers (some terminals
// have limits around 4096 bytes for a single escape sequence).
const CHUNK = 300;
for (let i = 0; i < base64.length; i += CHUNK) {
const chunk = base64.slice(i, i + CHUNK);
// For the first chunk, start the sequence; for subsequent chunks, just append.
if (i === 0) {
stdout.write(`\x1b]52;c;${chunk}`);
} else {
stdout.write(chunk);
}
}
stdout.write("\x07"); // BEL terminates the sequence
}