mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-21 19:40:40 -07:00
feat: Support Alt+key combinations (#10767)
This commit is contained in:
@@ -956,3 +956,127 @@ describe('Drag and Drop Handling', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Terminal-specific Alt+key combinations', () => {
|
||||
let stdin: MockStdin;
|
||||
const mockSetRawMode = vi.fn();
|
||||
|
||||
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<KeypressProvider kittyProtocolEnabled={true}>{children}</KeypressProvider>
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
stdin = new MockStdin();
|
||||
(useStdin as Mock).mockReturnValue({
|
||||
stdin,
|
||||
setRawMode: mockSetRawMode,
|
||||
});
|
||||
});
|
||||
|
||||
// Terminals to test
|
||||
const terminals = ['iTerm2', 'Ghostty', 'MacTerminal', 'VSCodeTerminal'];
|
||||
|
||||
// Key mappings: letter -> [keycode, accented character, shouldHaveMeta]
|
||||
// Note: µ (mu) is sent with meta:false on iTerm2/VSCode
|
||||
const keys: Record<string, [number, string, boolean]> = {
|
||||
a: [97, 'å', true],
|
||||
o: [111, 'ø', true],
|
||||
m: [109, 'µ', false],
|
||||
};
|
||||
|
||||
it.each(
|
||||
terminals.flatMap((terminal) =>
|
||||
Object.entries(keys).map(
|
||||
([key, [keycode, accentedChar, shouldHaveMeta]]) => {
|
||||
if (terminal === 'Ghostty') {
|
||||
// Ghostty uses kitty protocol sequences
|
||||
return {
|
||||
terminal,
|
||||
key,
|
||||
kittySequence: `\x1b[${keycode};3u`,
|
||||
expected: {
|
||||
name: key,
|
||||
ctrl: false,
|
||||
meta: true,
|
||||
shift: false,
|
||||
paste: false,
|
||||
kittyProtocol: true,
|
||||
},
|
||||
};
|
||||
} else if (terminal === 'MacTerminal') {
|
||||
// Mac Terminal sends ESC + letter
|
||||
return {
|
||||
terminal,
|
||||
key,
|
||||
input: {
|
||||
sequence: `\x1b${key}`,
|
||||
name: key,
|
||||
ctrl: false,
|
||||
meta: true,
|
||||
shift: false,
|
||||
paste: false,
|
||||
},
|
||||
expected: {
|
||||
sequence: `\x1b${key}`,
|
||||
name: key,
|
||||
ctrl: false,
|
||||
meta: true,
|
||||
shift: false,
|
||||
paste: false,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
// iTerm2 and VSCode send accented characters (å, ø, µ)
|
||||
// Note: µ comes with meta:false but gets converted to m with meta:true
|
||||
return {
|
||||
terminal,
|
||||
key,
|
||||
input: {
|
||||
name: key,
|
||||
ctrl: false,
|
||||
meta: shouldHaveMeta,
|
||||
shift: false,
|
||||
paste: false,
|
||||
sequence: accentedChar,
|
||||
},
|
||||
expected: {
|
||||
name: key,
|
||||
ctrl: false,
|
||||
meta: true, // Always expect meta:true after conversion
|
||||
shift: false,
|
||||
paste: false,
|
||||
sequence: accentedChar,
|
||||
},
|
||||
};
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
)(
|
||||
'should handle Alt+$key in $terminal',
|
||||
({
|
||||
kittySequence,
|
||||
input,
|
||||
expected,
|
||||
}: {
|
||||
kittySequence?: string;
|
||||
input?: Partial<Key>;
|
||||
expected: Partial<Key>;
|
||||
}) => {
|
||||
const keyHandler = vi.fn();
|
||||
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
||||
act(() => result.current.subscribe(keyHandler));
|
||||
|
||||
if (kittySequence) {
|
||||
act(() => stdin.sendKittySequence(kittySequence));
|
||||
} else if (input) {
|
||||
act(() => stdin.pressKey(input));
|
||||
}
|
||||
|
||||
expect(keyHandler).toHaveBeenCalledWith(
|
||||
expect.objectContaining(expected),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user