mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-26 04:54:25 -07:00
Support command/ctrl/alt backspace correctly (#17175)
This commit is contained in:
committed by
GitHub
parent
e894871afc
commit
f190b87223
@@ -851,8 +851,9 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
completion.promptCompletion.text &&
|
||||
key.sequence &&
|
||||
key.sequence.length === 1 &&
|
||||
!key.alt &&
|
||||
!key.ctrl &&
|
||||
!key.meta
|
||||
!key.cmd
|
||||
) {
|
||||
completion.promptCompletion.clear();
|
||||
setExpandedSuggestionIndex(-1);
|
||||
|
||||
@@ -91,9 +91,10 @@ describe('MultiFolderTrustDialog', () => {
|
||||
await act(async () => {
|
||||
keypressCallback({
|
||||
name: 'escape',
|
||||
ctrl: false,
|
||||
meta: false,
|
||||
shift: false,
|
||||
alt: false,
|
||||
ctrl: false,
|
||||
cmd: false,
|
||||
sequence: '',
|
||||
insertable: false,
|
||||
});
|
||||
|
||||
@@ -93,10 +93,10 @@ const createMockConfig = (overrides: Partial<Config> = {}): Config =>
|
||||
const triggerKey = (
|
||||
partialKey: Partial<{
|
||||
name: string;
|
||||
ctrl: boolean;
|
||||
meta: boolean;
|
||||
shift: boolean;
|
||||
paste: boolean;
|
||||
alt: boolean;
|
||||
ctrl: boolean;
|
||||
cmd: boolean;
|
||||
insertable: boolean;
|
||||
sequence: string;
|
||||
}>,
|
||||
@@ -108,9 +108,10 @@ const triggerKey = (
|
||||
|
||||
const key = {
|
||||
name: '',
|
||||
ctrl: false,
|
||||
meta: false,
|
||||
shift: false,
|
||||
alt: false,
|
||||
ctrl: false,
|
||||
cmd: false,
|
||||
insertable: false,
|
||||
sequence: '',
|
||||
...partialKey,
|
||||
@@ -263,7 +264,13 @@ describe('SessionBrowser component', () => {
|
||||
|
||||
// Type the query "query".
|
||||
for (const ch of ['q', 'u', 'e', 'r', 'y']) {
|
||||
triggerKey({ sequence: ch, name: ch, ctrl: false, meta: false });
|
||||
triggerKey({
|
||||
sequence: ch,
|
||||
name: ch,
|
||||
alt: false,
|
||||
ctrl: false,
|
||||
cmd: false,
|
||||
});
|
||||
}
|
||||
|
||||
await waitFor(() => {
|
||||
|
||||
@@ -781,9 +781,10 @@ export const useSessionBrowserInput = (
|
||||
state.setScrollOffset(0);
|
||||
} else if (
|
||||
key.sequence &&
|
||||
key.sequence.length === 1 &&
|
||||
!key.alt &&
|
||||
!key.ctrl &&
|
||||
!key.meta &&
|
||||
key.sequence.length === 1
|
||||
!key.cmd
|
||||
) {
|
||||
state.setSearchQuery((prev) => prev + key.sequence);
|
||||
state.setActiveIndex(0);
|
||||
|
||||
@@ -53,7 +53,14 @@ describe('ShellInputPrompt', () => {
|
||||
const handler = mockUseKeypress.mock.calls[0][0];
|
||||
|
||||
// Simulate keypress
|
||||
handler({ name, sequence, ctrl: false, shift: false, meta: false });
|
||||
handler({
|
||||
name,
|
||||
shift: false,
|
||||
alt: false,
|
||||
ctrl: false,
|
||||
cmd: false,
|
||||
sequence,
|
||||
});
|
||||
|
||||
expect(mockWriteToPty).toHaveBeenCalledWith(1, sequence);
|
||||
});
|
||||
@@ -66,7 +73,7 @@ describe('ShellInputPrompt', () => {
|
||||
|
||||
const handler = mockUseKeypress.mock.calls[0][0];
|
||||
|
||||
handler({ name: key, ctrl: true, shift: true, meta: false });
|
||||
handler({ name: key, shift: true, alt: false, ctrl: true, cmd: false });
|
||||
|
||||
expect(mockScrollPty).toHaveBeenCalledWith(1, direction);
|
||||
});
|
||||
@@ -78,10 +85,11 @@ describe('ShellInputPrompt', () => {
|
||||
|
||||
handler({
|
||||
name: 'a',
|
||||
sequence: 'a',
|
||||
ctrl: false,
|
||||
shift: false,
|
||||
meta: false,
|
||||
alt: false,
|
||||
ctrl: false,
|
||||
cmd: false,
|
||||
sequence: 'a',
|
||||
});
|
||||
|
||||
expect(mockWriteToPty).not.toHaveBeenCalled();
|
||||
@@ -94,10 +102,11 @@ describe('ShellInputPrompt', () => {
|
||||
|
||||
handler({
|
||||
name: 'a',
|
||||
sequence: 'a',
|
||||
ctrl: false,
|
||||
shift: false,
|
||||
meta: false,
|
||||
alt: false,
|
||||
ctrl: false,
|
||||
cmd: false,
|
||||
sequence: 'a',
|
||||
});
|
||||
|
||||
expect(mockWriteToPty).not.toHaveBeenCalled();
|
||||
|
||||
@@ -151,18 +151,20 @@ describe('TextInput', () => {
|
||||
|
||||
keypressHandler({
|
||||
name: 'a',
|
||||
sequence: 'a',
|
||||
ctrl: false,
|
||||
meta: false,
|
||||
shift: false,
|
||||
alt: false,
|
||||
ctrl: false,
|
||||
cmd: false,
|
||||
sequence: 'a',
|
||||
});
|
||||
|
||||
expect(mockBuffer.handleInput).toHaveBeenCalledWith({
|
||||
name: 'a',
|
||||
sequence: 'a',
|
||||
ctrl: false,
|
||||
meta: false,
|
||||
shift: false,
|
||||
alt: false,
|
||||
ctrl: false,
|
||||
cmd: false,
|
||||
sequence: 'a',
|
||||
});
|
||||
expect(mockBuffer.text).toBe('a');
|
||||
});
|
||||
@@ -176,18 +178,20 @@ describe('TextInput', () => {
|
||||
|
||||
keypressHandler({
|
||||
name: 'backspace',
|
||||
sequence: '',
|
||||
ctrl: false,
|
||||
meta: false,
|
||||
shift: false,
|
||||
alt: false,
|
||||
ctrl: false,
|
||||
cmd: false,
|
||||
sequence: '',
|
||||
});
|
||||
|
||||
expect(mockBuffer.handleInput).toHaveBeenCalledWith({
|
||||
name: 'backspace',
|
||||
sequence: '',
|
||||
ctrl: false,
|
||||
meta: false,
|
||||
shift: false,
|
||||
alt: false,
|
||||
ctrl: false,
|
||||
cmd: false,
|
||||
sequence: '',
|
||||
});
|
||||
expect(mockBuffer.text).toBe('tes');
|
||||
});
|
||||
@@ -201,10 +205,11 @@ describe('TextInput', () => {
|
||||
|
||||
keypressHandler({
|
||||
name: 'left',
|
||||
sequence: '',
|
||||
ctrl: false,
|
||||
meta: false,
|
||||
shift: false,
|
||||
alt: false,
|
||||
ctrl: false,
|
||||
cmd: false,
|
||||
sequence: '',
|
||||
});
|
||||
|
||||
// Cursor moves from end to before 't'
|
||||
@@ -221,10 +226,11 @@ describe('TextInput', () => {
|
||||
|
||||
keypressHandler({
|
||||
name: 'right',
|
||||
sequence: '',
|
||||
ctrl: false,
|
||||
meta: false,
|
||||
shift: false,
|
||||
alt: false,
|
||||
ctrl: false,
|
||||
cmd: false,
|
||||
sequence: '',
|
||||
});
|
||||
|
||||
expect(mockBuffer.visualCursor[1]).toBe(3);
|
||||
@@ -239,10 +245,11 @@ describe('TextInput', () => {
|
||||
|
||||
keypressHandler({
|
||||
name: 'return',
|
||||
sequence: '',
|
||||
ctrl: false,
|
||||
meta: false,
|
||||
shift: false,
|
||||
alt: false,
|
||||
ctrl: false,
|
||||
cmd: false,
|
||||
sequence: '',
|
||||
});
|
||||
|
||||
expect(onSubmit).toHaveBeenCalledWith('test');
|
||||
@@ -257,10 +264,11 @@ describe('TextInput', () => {
|
||||
|
||||
keypressHandler({
|
||||
name: 'escape',
|
||||
sequence: '',
|
||||
ctrl: false,
|
||||
meta: false,
|
||||
shift: false,
|
||||
alt: false,
|
||||
ctrl: false,
|
||||
cmd: false,
|
||||
sequence: '',
|
||||
});
|
||||
await vi.runAllTimersAsync();
|
||||
|
||||
|
||||
@@ -1059,9 +1059,10 @@ describe('useTextBuffer', () => {
|
||||
act(() =>
|
||||
result.current.handleInput({
|
||||
name: 'h',
|
||||
ctrl: false,
|
||||
meta: false,
|
||||
shift: false,
|
||||
alt: false,
|
||||
ctrl: false,
|
||||
cmd: false,
|
||||
insertable: true,
|
||||
sequence: 'h',
|
||||
}),
|
||||
@@ -1069,9 +1070,10 @@ describe('useTextBuffer', () => {
|
||||
act(() =>
|
||||
result.current.handleInput({
|
||||
name: 'i',
|
||||
ctrl: false,
|
||||
meta: false,
|
||||
shift: false,
|
||||
alt: false,
|
||||
ctrl: false,
|
||||
cmd: false,
|
||||
insertable: true,
|
||||
sequence: 'i',
|
||||
}),
|
||||
@@ -1086,9 +1088,10 @@ describe('useTextBuffer', () => {
|
||||
act(() =>
|
||||
result.current.handleInput({
|
||||
name: 'return',
|
||||
ctrl: false,
|
||||
meta: false,
|
||||
shift: false,
|
||||
alt: false,
|
||||
ctrl: false,
|
||||
cmd: false,
|
||||
insertable: true,
|
||||
sequence: '\r',
|
||||
}),
|
||||
@@ -1103,9 +1106,10 @@ describe('useTextBuffer', () => {
|
||||
act(() =>
|
||||
result.current.handleInput({
|
||||
name: 'j',
|
||||
ctrl: true,
|
||||
meta: false,
|
||||
shift: false,
|
||||
alt: false,
|
||||
ctrl: true,
|
||||
cmd: false,
|
||||
insertable: false,
|
||||
sequence: '\n',
|
||||
}),
|
||||
@@ -1120,9 +1124,10 @@ describe('useTextBuffer', () => {
|
||||
act(() =>
|
||||
result.current.handleInput({
|
||||
name: 'tab',
|
||||
ctrl: false,
|
||||
meta: false,
|
||||
shift: false,
|
||||
alt: false,
|
||||
ctrl: false,
|
||||
cmd: false,
|
||||
insertable: false,
|
||||
sequence: '\t',
|
||||
}),
|
||||
@@ -1137,9 +1142,10 @@ describe('useTextBuffer', () => {
|
||||
act(() =>
|
||||
result.current.handleInput({
|
||||
name: 'tab',
|
||||
ctrl: false,
|
||||
meta: false,
|
||||
shift: true,
|
||||
alt: false,
|
||||
ctrl: false,
|
||||
cmd: false,
|
||||
insertable: false,
|
||||
sequence: '\u001b[9;2u',
|
||||
}),
|
||||
@@ -1159,9 +1165,10 @@ describe('useTextBuffer', () => {
|
||||
act(() =>
|
||||
result.current.handleInput({
|
||||
name: 'backspace',
|
||||
ctrl: false,
|
||||
meta: false,
|
||||
shift: false,
|
||||
alt: false,
|
||||
ctrl: false,
|
||||
cmd: false,
|
||||
insertable: false,
|
||||
sequence: '\x7f',
|
||||
}),
|
||||
@@ -1183,25 +1190,28 @@ describe('useTextBuffer', () => {
|
||||
act(() => {
|
||||
result.current.handleInput({
|
||||
name: 'backspace',
|
||||
ctrl: false,
|
||||
meta: false,
|
||||
shift: false,
|
||||
alt: false,
|
||||
ctrl: false,
|
||||
cmd: false,
|
||||
insertable: false,
|
||||
sequence: '\x7f',
|
||||
});
|
||||
result.current.handleInput({
|
||||
name: 'backspace',
|
||||
ctrl: false,
|
||||
meta: false,
|
||||
shift: false,
|
||||
alt: false,
|
||||
ctrl: false,
|
||||
cmd: false,
|
||||
insertable: false,
|
||||
sequence: '\x7f',
|
||||
});
|
||||
result.current.handleInput({
|
||||
name: 'backspace',
|
||||
ctrl: false,
|
||||
meta: false,
|
||||
shift: false,
|
||||
alt: false,
|
||||
ctrl: false,
|
||||
cmd: false,
|
||||
insertable: false,
|
||||
sequence: '\x7f',
|
||||
});
|
||||
@@ -1258,24 +1268,26 @@ describe('useTextBuffer', () => {
|
||||
act(() =>
|
||||
result.current.handleInput({
|
||||
name: 'left',
|
||||
ctrl: false,
|
||||
meta: false,
|
||||
shift: false,
|
||||
alt: false,
|
||||
ctrl: false,
|
||||
cmd: false,
|
||||
insertable: false,
|
||||
sequence: '\x1b[D',
|
||||
}),
|
||||
); // cursor [0,1]
|
||||
);
|
||||
expect(getBufferState(result).cursor).toEqual([0, 1]);
|
||||
act(() =>
|
||||
result.current.handleInput({
|
||||
name: 'right',
|
||||
ctrl: false,
|
||||
meta: false,
|
||||
shift: false,
|
||||
alt: false,
|
||||
ctrl: false,
|
||||
cmd: false,
|
||||
insertable: false,
|
||||
sequence: '\x1b[C',
|
||||
}),
|
||||
); // cursor [0,2]
|
||||
);
|
||||
expect(getBufferState(result).cursor).toEqual([0, 2]);
|
||||
});
|
||||
|
||||
@@ -1288,9 +1300,10 @@ describe('useTextBuffer', () => {
|
||||
act(() =>
|
||||
result.current.handleInput({
|
||||
name: '',
|
||||
ctrl: false,
|
||||
meta: false,
|
||||
shift: false,
|
||||
alt: false,
|
||||
ctrl: false,
|
||||
cmd: false,
|
||||
insertable: true,
|
||||
sequence: textWithAnsi,
|
||||
}),
|
||||
@@ -1305,9 +1318,10 @@ describe('useTextBuffer', () => {
|
||||
act(() =>
|
||||
result.current.handleInput({
|
||||
name: 'return',
|
||||
ctrl: false,
|
||||
meta: false,
|
||||
shift: true,
|
||||
alt: false,
|
||||
ctrl: false,
|
||||
cmd: false,
|
||||
insertable: true,
|
||||
sequence: '\r',
|
||||
}),
|
||||
@@ -1509,13 +1523,13 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
||||
describe('Input Sanitization', () => {
|
||||
const createInput = (sequence: string) => ({
|
||||
name: '',
|
||||
ctrl: false,
|
||||
meta: false,
|
||||
shift: false,
|
||||
alt: false,
|
||||
ctrl: false,
|
||||
cmd: false,
|
||||
insertable: true,
|
||||
sequence,
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
input: '\x1B[31mHello\x1B[0m \x1B[32mWorld\x1B[0m',
|
||||
@@ -1567,9 +1581,10 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
||||
act(() =>
|
||||
result.current.handleInput({
|
||||
name: '',
|
||||
ctrl: false,
|
||||
meta: false,
|
||||
shift: false,
|
||||
alt: false,
|
||||
ctrl: false,
|
||||
cmd: false,
|
||||
insertable: true,
|
||||
sequence: largeTextWithUnsafe,
|
||||
}),
|
||||
@@ -1601,9 +1616,10 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
||||
act(() =>
|
||||
result.current.handleInput({
|
||||
name: '',
|
||||
ctrl: false,
|
||||
meta: false,
|
||||
shift: false,
|
||||
alt: false,
|
||||
ctrl: false,
|
||||
cmd: false,
|
||||
insertable: true,
|
||||
sequence: largeTextWithAnsi,
|
||||
}),
|
||||
@@ -1625,9 +1641,10 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
||||
act(() =>
|
||||
result.current.handleInput({
|
||||
name: '',
|
||||
ctrl: false,
|
||||
meta: false,
|
||||
shift: false,
|
||||
alt: false,
|
||||
ctrl: false,
|
||||
cmd: false,
|
||||
insertable: true,
|
||||
sequence: emojis,
|
||||
}),
|
||||
@@ -1816,9 +1833,10 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
||||
act(() =>
|
||||
result.current.handleInput({
|
||||
name: 'return',
|
||||
ctrl: false,
|
||||
meta: false,
|
||||
shift: false,
|
||||
alt: false,
|
||||
ctrl: false,
|
||||
cmd: false,
|
||||
insertable: true,
|
||||
sequence: '\r',
|
||||
}),
|
||||
@@ -1837,9 +1855,10 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
||||
act(() =>
|
||||
result.current.handleInput({
|
||||
name: 'f1',
|
||||
ctrl: false,
|
||||
meta: false,
|
||||
shift: false,
|
||||
alt: false,
|
||||
ctrl: false,
|
||||
cmd: false,
|
||||
insertable: false,
|
||||
sequence: '\u001bOP',
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user