diff --git a/docs/cli/keyboard-shortcuts.md b/docs/cli/keyboard-shortcuts.md
index 0117a652a2..1402422c6b 100644
--- a/docs/cli/keyboard-shortcuts.md
+++ b/docs/cli/keyboard-shortcuts.md
@@ -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`
`Enter (no Ctrl)` |
+| Accept the inline suggestion. | `Tab (no Shift)`
`Enter (no Ctrl)` |
| Move to the previous completion option. | `Up Arrow (no Shift)`
`Ctrl + P (no Shift)` |
| Move to the next completion option. | `Down Arrow (no Shift)`
`Ctrl + N (no Shift)` |
| Expand an inline suggestion. | `Right Arrow` |
diff --git a/packages/cli/src/config/keyBindings.ts b/packages/cli/src/config/keyBindings.ts
index b6b8cb9534..4813abd368 100644
--- a/packages/cli/src/config/keyBindings.ts
+++ b/packages/cli/src/config/keyBindings.ts
@@ -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 },
diff --git a/packages/cli/src/ui/components/InputPrompt.test.tsx b/packages/cli/src/ui/components/InputPrompt.test.tsx
index 8257cd8acc..6a6424c3c7 100644
--- a/packages/cli/src/ui/components/InputPrompt.test.tsx
+++ b/packages/cli/src/ui/components/InputPrompt.test.tsx
@@ -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(, {
+ 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(
+ ,
+ {
+ 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(
+ ,
+ {
+ 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,
diff --git a/packages/cli/src/ui/components/InputPrompt.tsx b/packages/cli/src/ui/components/InputPrompt.tsx
index 7e9ec74ac4..8a4f068df1 100644
--- a/packages/cli/src/ui/components/InputPrompt.tsx
+++ b/packages/cli/src/ui/components/InputPrompt.tsx
@@ -975,6 +975,7 @@ export const InputPrompt: React.FC = ({
// Handle Tab key for ghost text acceptance
if (
key.name === 'tab' &&
+ !key.shift &&
!completion.showSuggestions &&
completion.promptCompletion.text
) {