mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-23 04:21:31 -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)` |
|
||||
| Start reverse search through history. | `Ctrl + R` |
|
||||
| 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` |
|
||||
|
||||
#### Navigation
|
||||
@@ -79,7 +79,7 @@ available combinations.
|
||||
|
||||
| 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 next completion option. | `Down Arrow (no Shift)`<br />`Ctrl + N (no Shift)` |
|
||||
| Expand an inline suggestion. | `Right Arrow` |
|
||||
|
||||
@@ -212,7 +212,7 @@ export const defaultKeyBindings: KeyBindingConfig = {
|
||||
[Command.REVERSE_SEARCH]: [{ key: 'r', ctrl: true }],
|
||||
[Command.REWIND]: [{ key: 'double escape' }],
|
||||
[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
|
||||
[Command.NAVIGATION_UP]: [{ key: 'up', shift: false }],
|
||||
@@ -231,7 +231,10 @@ export const defaultKeyBindings: KeyBindingConfig = {
|
||||
[Command.DIALOG_PREV]: [{ key: 'tab', shift: true }],
|
||||
|
||||
// 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]: [
|
||||
{ key: 'up', shift: false },
|
||||
{ key: 'p', shift: false, ctrl: true },
|
||||
|
||||
@@ -1221,6 +1221,36 @@ describe('InputPrompt', () => {
|
||||
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 () => {
|
||||
const customCommand: SlashCommand = {
|
||||
name: 'find-capital',
|
||||
@@ -2659,6 +2689,38 @@ describe('InputPrompt', () => {
|
||||
unmount();
|
||||
}, 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 () => {
|
||||
// Mock the reverse search completion to return suggestions
|
||||
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 () => {
|
||||
mockedUseCommandCompletion.mockReturnValue({
|
||||
...mockCommandCompletion,
|
||||
|
||||
@@ -975,6 +975,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
// Handle Tab key for ghost text acceptance
|
||||
if (
|
||||
key.name === 'tab' &&
|
||||
!key.shift &&
|
||||
!completion.showSuggestions &&
|
||||
completion.promptCompletion.text
|
||||
) {
|
||||
|
||||
Reference in New Issue
Block a user