fix(cli): ensure Shift+Tab unfocuses interactive shell

- Update Command.UNFOCUS_SHELL_INPUT to explicitly require shift: true.
- Allow UNFOCUS_SHELL_INPUT to bubble up from ShellInputPrompt.
- Update AppContainer to handle UNFOCUS_SHELL_INPUT for unfocusing the shell.
- Add unit tests for ShellInputPrompt and AppContainer.
This commit is contained in:
Taylor Mullen
2026-02-04 22:10:54 -08:00
parent d29383a132
commit 568444133a
5 changed files with 49 additions and 3 deletions

View File

@@ -288,7 +288,7 @@ export const defaultKeyBindings: KeyBindingConfig = {
{ key: 's', ctrl: true },
],
[Command.FOCUS_SHELL_INPUT]: [{ key: 'tab', shift: false }],
[Command.UNFOCUS_SHELL_INPUT]: [{ key: 'tab' }],
[Command.UNFOCUS_SHELL_INPUT]: [{ key: 'tab', shift: true }],
[Command.CLEAR_SCREEN]: [{ key: 'l', ctrl: true }],
[Command.RESTART_APP]: [{ key: 'r' }],
[Command.SUSPEND_APP]: [{ key: 'z', ctrl: true }],

View File

@@ -1940,6 +1940,28 @@ describe('AppContainer State Management', () => {
unmount();
});
});
describe('Shell Focus', () => {
it('should unfocus shell when Shift+Tab is pressed', async () => {
mockedUseGeminiStream.mockReturnValue({
...DEFAULT_GEMINI_STREAM_MOCK,
activePtyId: 1,
});
await setupKeypressTest();
act(() => {
capturedUIActions.setEmbeddedShellFocused(true);
});
rerender();
expect(capturedUIState.embeddedShellFocused).toBe(true);
pressKey({ name: 'tab', shift: true });
expect(capturedUIState.embeddedShellFocused).toBe(false);
unmount();
});
});
});
describe('Copy Mode (CTRL+S)', () => {

View File

@@ -1500,10 +1500,11 @@ Logging in with Google... Restarting Gemini CLI to continue.
setConstrainHeight(false);
return true;
} else if (
keyMatchers[Command.FOCUS_SHELL_INPUT](key) &&
(keyMatchers[Command.FOCUS_SHELL_INPUT](key) ||
keyMatchers[Command.UNFOCUS_SHELL_INPUT](key)) &&
(activePtyId || (isBackgroundShellVisible && backgroundShells.size > 0))
) {
if (key.name === 'tab' && key.shift) {
if (keyMatchers[Command.UNFOCUS_SHELL_INPUT](key)) {
// Always change focus
setEmbeddedShellFocused(false);
return true;

View File

@@ -111,4 +111,22 @@ describe('ShellInputPrompt', () => {
expect(mockWriteToPty).not.toHaveBeenCalled();
});
it('bubbles up Shift+Tab for unfocusing', () => {
render(<ShellInputPrompt activeShellPtyId={1} focus={true} />);
const handler = mockUseKeypress.mock.calls[0][0];
const result = handler({
name: 'tab',
shift: true,
alt: false,
ctrl: false,
cmd: false,
sequence: '\x1b[Z',
});
expect(result).toBe(false);
expect(mockWriteToPty).not.toHaveBeenCalled();
});
});

View File

@@ -40,6 +40,11 @@ export const ShellInputPrompt: React.FC<ShellInputPromptProps> = ({
return false;
}
// Allow unfocus to bubble up
if (keyMatchers[Command.UNFOCUS_SHELL_INPUT](key)) {
return false;
}
if (key.ctrl && key.shift && key.name === 'up') {
ShellExecutionService.scrollPty(activeShellPtyId, -1);
return true;