fix(cli): disable auto-completion on Shift+Tab to preserve mode cycling (#19451)

This commit is contained in:
N. Taylor Mullen
2026-02-18 14:08:38 -08:00
committed by GitHub
parent 87f5dd15d6
commit 8910b2720f
4 changed files with 103 additions and 4 deletions

View File

@@ -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,

View File

@@ -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
) {