Compare commits

...

2 Commits

Author SHA1 Message Date
Taylor Mullen 911a9cf9de docs(cli): update keyboard shortcut documentation for unfocus shell input 2026-02-04 22:25:27 -08:00
Taylor Mullen 568444133a 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.
2026-02-04 22:10:54 -08:00
6 changed files with 50 additions and 4 deletions
+1 -1
View File
@@ -115,7 +115,7 @@ available combinations.
| Tab | `Tab (no Shift)` |
| Tab | `Tab (no Shift)` |
| Focus the shell input from the gemini input. | `Tab (no Shift)` |
| Focus the Gemini input from the shell input. | `Tab` |
| Focus the Gemini input from the shell input. | `Shift + Tab` |
| Clear the terminal screen and redraw the UI. | `Ctrl + L` |
| Restart the application. | `R` |
| Suspend the application (not yet implemented). | `Ctrl + Z` |
+1 -1
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 }],
+22
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)', () => {
+3 -2
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;
@@ -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();
});
});
@@ -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;