mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-13 05:12:55 -07:00
fix(cli): disable auto-completion on Shift+Tab to preserve mode cycling (#19451)
This commit is contained in:
@@ -61,7 +61,7 @@ available combinations.
|
|||||||
| Show the next entry in history. | `Ctrl + N (no Shift)` |
|
| Show the next entry in history. | `Ctrl + N (no Shift)` |
|
||||||
| Start reverse search through history. | `Ctrl + R` |
|
| Start reverse search through history. | `Ctrl + R` |
|
||||||
| Submit the selected reverse-search match. | `Enter (no Ctrl)` |
|
| Submit the selected reverse-search match. | `Enter (no Ctrl)` |
|
||||||
| Accept a suggestion while reverse searching. | `Tab` |
|
| Accept a suggestion while reverse searching. | `Tab (no Shift)` |
|
||||||
| Browse and rewind previous interactions. | `Double Esc` |
|
| Browse and rewind previous interactions. | `Double Esc` |
|
||||||
|
|
||||||
#### Navigation
|
#### Navigation
|
||||||
@@ -79,7 +79,7 @@ available combinations.
|
|||||||
|
|
||||||
| Action | Keys |
|
| Action | Keys |
|
||||||
| --------------------------------------- | -------------------------------------------------- |
|
| --------------------------------------- | -------------------------------------------------- |
|
||||||
| Accept the inline suggestion. | `Tab`<br />`Enter (no Ctrl)` |
|
| Accept the inline suggestion. | `Tab (no Shift)`<br />`Enter (no Ctrl)` |
|
||||||
| Move to the previous completion option. | `Up Arrow (no Shift)`<br />`Ctrl + P (no Shift)` |
|
| Move to the previous completion option. | `Up Arrow (no Shift)`<br />`Ctrl + P (no Shift)` |
|
||||||
| Move to the next completion option. | `Down Arrow (no Shift)`<br />`Ctrl + N (no Shift)` |
|
| Move to the next completion option. | `Down Arrow (no Shift)`<br />`Ctrl + N (no Shift)` |
|
||||||
| Expand an inline suggestion. | `Right Arrow` |
|
| Expand an inline suggestion. | `Right Arrow` |
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ export const defaultKeyBindings: KeyBindingConfig = {
|
|||||||
[Command.REVERSE_SEARCH]: [{ key: 'r', ctrl: true }],
|
[Command.REVERSE_SEARCH]: [{ key: 'r', ctrl: true }],
|
||||||
[Command.REWIND]: [{ key: 'double escape' }],
|
[Command.REWIND]: [{ key: 'double escape' }],
|
||||||
[Command.SUBMIT_REVERSE_SEARCH]: [{ key: 'return', ctrl: false }],
|
[Command.SUBMIT_REVERSE_SEARCH]: [{ key: 'return', ctrl: false }],
|
||||||
[Command.ACCEPT_SUGGESTION_REVERSE_SEARCH]: [{ key: 'tab' }],
|
[Command.ACCEPT_SUGGESTION_REVERSE_SEARCH]: [{ key: 'tab', shift: false }],
|
||||||
|
|
||||||
// Navigation
|
// Navigation
|
||||||
[Command.NAVIGATION_UP]: [{ key: 'up', shift: false }],
|
[Command.NAVIGATION_UP]: [{ key: 'up', shift: false }],
|
||||||
@@ -231,7 +231,10 @@ export const defaultKeyBindings: KeyBindingConfig = {
|
|||||||
[Command.DIALOG_PREV]: [{ key: 'tab', shift: true }],
|
[Command.DIALOG_PREV]: [{ key: 'tab', shift: true }],
|
||||||
|
|
||||||
// Suggestions & Completions
|
// Suggestions & Completions
|
||||||
[Command.ACCEPT_SUGGESTION]: [{ key: 'tab' }, { key: 'return', ctrl: false }],
|
[Command.ACCEPT_SUGGESTION]: [
|
||||||
|
{ key: 'tab', shift: false },
|
||||||
|
{ key: 'return', ctrl: false },
|
||||||
|
],
|
||||||
[Command.COMPLETION_UP]: [
|
[Command.COMPLETION_UP]: [
|
||||||
{ key: 'up', shift: false },
|
{ key: 'up', shift: false },
|
||||||
{ key: 'p', shift: false, ctrl: true },
|
{ key: 'p', shift: false, ctrl: true },
|
||||||
|
|||||||
@@ -1221,6 +1221,36 @@ describe('InputPrompt', () => {
|
|||||||
unmount();
|
unmount();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should NOT autocomplete on Shift+Tab', async () => {
|
||||||
|
const suggestion = { label: 'about', value: 'about' };
|
||||||
|
|
||||||
|
mockedUseCommandCompletion.mockReturnValue({
|
||||||
|
...mockCommandCompletion,
|
||||||
|
showSuggestions: true,
|
||||||
|
suggestions: [suggestion],
|
||||||
|
activeSuggestionIndex: 0,
|
||||||
|
getCompletedText: vi.fn().mockReturnValue('/about'),
|
||||||
|
});
|
||||||
|
|
||||||
|
props.buffer.setText('/ab');
|
||||||
|
props.buffer.lines = ['/ab'];
|
||||||
|
props.buffer.cursor = [0, 3];
|
||||||
|
|
||||||
|
const { stdin, unmount } = renderWithProviders(<InputPrompt {...props} />, {
|
||||||
|
uiActions,
|
||||||
|
});
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
stdin.write('\x1b[Z'); // Shift+Tab
|
||||||
|
});
|
||||||
|
|
||||||
|
// We need to wait a bit to ensure handleAutocomplete was NOT called
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||||
|
|
||||||
|
expect(mockCommandCompletion.handleAutocomplete).not.toHaveBeenCalled();
|
||||||
|
unmount();
|
||||||
|
});
|
||||||
|
|
||||||
it('should autocomplete custom commands from .toml files on Enter', async () => {
|
it('should autocomplete custom commands from .toml files on Enter', async () => {
|
||||||
const customCommand: SlashCommand = {
|
const customCommand: SlashCommand = {
|
||||||
name: 'find-capital',
|
name: 'find-capital',
|
||||||
@@ -2659,6 +2689,38 @@ describe('InputPrompt', () => {
|
|||||||
unmount();
|
unmount();
|
||||||
}, 15000);
|
}, 15000);
|
||||||
|
|
||||||
|
it('should NOT autocomplete on Shift+Tab in reverse search', async () => {
|
||||||
|
const mockHandleAutocomplete = vi.fn();
|
||||||
|
|
||||||
|
mockedUseReverseSearchCompletion.mockReturnValue({
|
||||||
|
...mockReverseSearchCompletion,
|
||||||
|
suggestions: [{ label: 'echo hello', value: 'echo hello' }],
|
||||||
|
showSuggestions: true,
|
||||||
|
activeSuggestionIndex: 0,
|
||||||
|
handleAutocomplete: mockHandleAutocomplete,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { stdin, unmount } = renderWithProviders(
|
||||||
|
<InputPrompt {...props} />,
|
||||||
|
{
|
||||||
|
uiActions,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
stdin.write('\x12'); // Ctrl+R
|
||||||
|
});
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
stdin.write('\x1b[Z'); // Shift+Tab
|
||||||
|
});
|
||||||
|
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||||
|
|
||||||
|
expect(mockHandleAutocomplete).not.toHaveBeenCalled();
|
||||||
|
unmount();
|
||||||
|
});
|
||||||
|
|
||||||
it('submits the highlighted entry on Enter and exits reverse-search', async () => {
|
it('submits the highlighted entry on Enter and exits reverse-search', async () => {
|
||||||
// Mock the reverse search completion to return suggestions
|
// Mock the reverse search completion to return suggestions
|
||||||
mockedUseReverseSearchCompletion.mockReturnValue({
|
mockedUseReverseSearchCompletion.mockReturnValue({
|
||||||
@@ -3035,6 +3097,39 @@ describe('InputPrompt', () => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
it('should NOT accept ghost text on Shift+Tab', async () => {
|
||||||
|
const mockAccept = vi.fn();
|
||||||
|
mockedUseCommandCompletion.mockReturnValue({
|
||||||
|
...mockCommandCompletion,
|
||||||
|
showSuggestions: false,
|
||||||
|
suggestions: [],
|
||||||
|
promptCompletion: {
|
||||||
|
text: 'ghost text',
|
||||||
|
accept: mockAccept,
|
||||||
|
clear: vi.fn(),
|
||||||
|
isLoading: false,
|
||||||
|
isActive: true,
|
||||||
|
markSelected: vi.fn(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { stdin, unmount } = renderWithProviders(
|
||||||
|
<InputPrompt {...props} />,
|
||||||
|
{
|
||||||
|
uiActions,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
stdin.write('\x1b[Z'); // Shift+Tab
|
||||||
|
});
|
||||||
|
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||||
|
|
||||||
|
expect(mockAccept).not.toHaveBeenCalled();
|
||||||
|
unmount();
|
||||||
|
});
|
||||||
|
|
||||||
it('should not reveal clean UI details on Shift+Tab when hidden', async () => {
|
it('should not reveal clean UI details on Shift+Tab when hidden', async () => {
|
||||||
mockedUseCommandCompletion.mockReturnValue({
|
mockedUseCommandCompletion.mockReturnValue({
|
||||||
...mockCommandCompletion,
|
...mockCommandCompletion,
|
||||||
|
|||||||
@@ -975,6 +975,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
|||||||
// Handle Tab key for ghost text acceptance
|
// Handle Tab key for ghost text acceptance
|
||||||
if (
|
if (
|
||||||
key.name === 'tab' &&
|
key.name === 'tab' &&
|
||||||
|
!key.shift &&
|
||||||
!completion.showSuggestions &&
|
!completion.showSuggestions &&
|
||||||
completion.promptCompletion.text
|
completion.promptCompletion.text
|
||||||
) {
|
) {
|
||||||
|
|||||||
Reference in New Issue
Block a user