mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-17 01:21:10 -07:00
Add setting to support OSC 52 paste (#15336)
This commit is contained in:
committed by
GitHub
parent
2cb33b2f76
commit
384fb6a465
@@ -320,6 +320,111 @@ describe('KeypressContext', () => {
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should parse valid OSC 52 response', async () => {
|
||||
const keyHandler = vi.fn();
|
||||
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
||||
|
||||
act(() => result.current.subscribe(keyHandler));
|
||||
|
||||
const base64Data = Buffer.from('Hello OSC 52').toString('base64');
|
||||
const sequence = `\x1b]52;c;${base64Data}\x07`;
|
||||
|
||||
act(() => stdin.write(sequence));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(keyHandler).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
name: 'paste',
|
||||
paste: true,
|
||||
sequence: 'Hello OSC 52',
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle split OSC 52 response', async () => {
|
||||
const keyHandler = vi.fn();
|
||||
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
||||
|
||||
act(() => result.current.subscribe(keyHandler));
|
||||
|
||||
const base64Data = Buffer.from('Split Paste').toString('base64');
|
||||
const sequence = `\x1b]52;c;${base64Data}\x07`;
|
||||
|
||||
// Split the sequence
|
||||
const part1 = sequence.slice(0, 5);
|
||||
const part2 = sequence.slice(5);
|
||||
|
||||
act(() => stdin.write(part1));
|
||||
act(() => stdin.write(part2));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(keyHandler).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
name: 'paste',
|
||||
paste: true,
|
||||
sequence: 'Split Paste',
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle OSC 52 response terminated by ESC \\', async () => {
|
||||
const keyHandler = vi.fn();
|
||||
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
||||
|
||||
act(() => result.current.subscribe(keyHandler));
|
||||
|
||||
const base64Data = Buffer.from('Terminated by ST').toString('base64');
|
||||
const sequence = `\x1b]52;c;${base64Data}\x1b\\`;
|
||||
|
||||
act(() => stdin.write(sequence));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(keyHandler).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
name: 'paste',
|
||||
paste: true,
|
||||
sequence: 'Terminated by ST',
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should ignore unknown OSC sequences', async () => {
|
||||
const keyHandler = vi.fn();
|
||||
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
||||
|
||||
act(() => result.current.subscribe(keyHandler));
|
||||
|
||||
const sequence = `\x1b]1337;File=name=Zm9vCg==\x07`;
|
||||
|
||||
act(() => stdin.write(sequence));
|
||||
|
||||
await act(async () => {
|
||||
vi.advanceTimersByTime(0);
|
||||
});
|
||||
|
||||
expect(keyHandler).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should ignore invalid OSC 52 format', async () => {
|
||||
const keyHandler = vi.fn();
|
||||
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
||||
|
||||
act(() => result.current.subscribe(keyHandler));
|
||||
|
||||
const sequence = `\x1b]52;x;notbase64\x07`;
|
||||
|
||||
act(() => stdin.write(sequence));
|
||||
|
||||
await act(async () => {
|
||||
vi.advanceTimersByTime(0);
|
||||
});
|
||||
|
||||
expect(keyHandler).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('debug keystroke logging', () => {
|
||||
|
||||
@@ -317,12 +317,56 @@ function* emitKeys(
|
||||
}
|
||||
}
|
||||
|
||||
if (escaped && (ch === 'O' || ch === '[')) {
|
||||
if (escaped && (ch === 'O' || ch === '[' || ch === ']')) {
|
||||
// ANSI escape sequence
|
||||
code = ch;
|
||||
let modifier = 0;
|
||||
|
||||
if (ch === 'O') {
|
||||
if (ch === ']') {
|
||||
// OSC sequence
|
||||
// ESC ] <params> ; <data> BEL
|
||||
// ESC ] <params> ; <data> ESC \
|
||||
let buffer = '';
|
||||
|
||||
// Read until BEL, `ESC \`, or timeout (empty string)
|
||||
while (true) {
|
||||
const next = yield;
|
||||
if (next === '' || next === '\u0007') {
|
||||
break;
|
||||
} else if (next === ESC) {
|
||||
const afterEsc = yield;
|
||||
if (afterEsc === '' || afterEsc === '\\') {
|
||||
break;
|
||||
}
|
||||
buffer += next + afterEsc;
|
||||
continue;
|
||||
}
|
||||
buffer += next;
|
||||
}
|
||||
|
||||
// Check for OSC 52 (Clipboard) response
|
||||
// Format: 52;c;<base64> or 52;p;<base64>
|
||||
const match = /^52;[cp];(.*)$/.exec(buffer);
|
||||
if (match) {
|
||||
try {
|
||||
const base64Data = match[1];
|
||||
const decoded = Buffer.from(base64Data, 'base64').toString('utf-8');
|
||||
keypressHandler({
|
||||
name: 'paste',
|
||||
ctrl: false,
|
||||
meta: false,
|
||||
shift: false,
|
||||
paste: true,
|
||||
insertable: true,
|
||||
sequence: decoded,
|
||||
});
|
||||
} catch (_e) {
|
||||
debugLogger.log('Failed to decode OSC 52 clipboard data');
|
||||
}
|
||||
}
|
||||
|
||||
continue; // resume main loop
|
||||
} else if (ch === 'O') {
|
||||
// ESC O letter
|
||||
// ESC O modifier letter
|
||||
ch = yield;
|
||||
|
||||
Reference in New Issue
Block a user