mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-02 07:54:48 -07:00
fix(cli): improve focus navigation for interactive and background shells (#18343)
This commit is contained in:
@@ -1291,24 +1291,26 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
}, WARNING_PROMPT_DURATION_MS);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const handleSelectionWarning = () => {
|
||||
handleWarning('Press Ctrl-S to enter selection mode to copy text.');
|
||||
};
|
||||
const handlePasteTimeout = () => {
|
||||
handleWarning('Paste Timed out. Possibly due to slow connection.');
|
||||
};
|
||||
appEvents.on(AppEvent.SelectionWarning, handleSelectionWarning);
|
||||
appEvents.on(AppEvent.PasteTimeout, handlePasteTimeout);
|
||||
return () => {
|
||||
appEvents.off(AppEvent.SelectionWarning, handleSelectionWarning);
|
||||
appEvents.off(AppEvent.PasteTimeout, handlePasteTimeout);
|
||||
// Handle timeout cleanup on unmount
|
||||
useEffect(
|
||||
() => () => {
|
||||
if (warningTimeoutRef.current) {
|
||||
clearTimeout(warningTimeoutRef.current);
|
||||
}
|
||||
if (tabFocusTimeoutRef.current) {
|
||||
clearTimeout(tabFocusTimeoutRef.current);
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const handlePasteTimeout = () => {
|
||||
handleWarning('Paste Timed out. Possibly due to slow connection.');
|
||||
};
|
||||
appEvents.on(AppEvent.PasteTimeout, handlePasteTimeout);
|
||||
return () => {
|
||||
appEvents.off(AppEvent.PasteTimeout, handlePasteTimeout);
|
||||
};
|
||||
}, [handleWarning]);
|
||||
|
||||
@@ -1506,71 +1508,60 @@ 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_BACKGROUND_SHELL_LIST](key)) &&
|
||||
(activePtyId || (isBackgroundShellVisible && backgroundShells.size > 0))
|
||||
) {
|
||||
if (key.name === 'tab' && key.shift) {
|
||||
// Always change focus
|
||||
if (embeddedShellFocused) {
|
||||
const capturedTime = lastOutputTimeRef.current;
|
||||
if (tabFocusTimeoutRef.current)
|
||||
clearTimeout(tabFocusTimeoutRef.current);
|
||||
tabFocusTimeoutRef.current = setTimeout(() => {
|
||||
if (lastOutputTimeRef.current === capturedTime) {
|
||||
setEmbeddedShellFocused(false);
|
||||
} else {
|
||||
handleWarning('Use Shift+Tab to unfocus');
|
||||
}
|
||||
}, 150);
|
||||
return false;
|
||||
}
|
||||
|
||||
const isIdle = Date.now() - lastOutputTimeRef.current >= 100;
|
||||
|
||||
if (isIdle && !activePtyId && !isBackgroundShellVisible) {
|
||||
if (tabFocusTimeoutRef.current)
|
||||
clearTimeout(tabFocusTimeoutRef.current);
|
||||
toggleBackgroundShell();
|
||||
setEmbeddedShellFocused(true);
|
||||
if (backgroundShells.size > 1) setIsBackgroundShellListOpen(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
setEmbeddedShellFocused(true);
|
||||
return true;
|
||||
} else if (
|
||||
keyMatchers[Command.UNFOCUS_SHELL_INPUT](key) ||
|
||||
keyMatchers[Command.UNFOCUS_BACKGROUND_SHELL](key)
|
||||
) {
|
||||
if (embeddedShellFocused) {
|
||||
setEmbeddedShellFocused(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (embeddedShellFocused) {
|
||||
handleWarning('Press Shift+Tab to focus out.');
|
||||
return true;
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
// If the shell hasn't produced output in the last 100ms, it's considered idle.
|
||||
const isIdle = now - lastOutputTimeRef.current >= 100;
|
||||
if (isIdle && !activePtyId) {
|
||||
if (tabFocusTimeoutRef.current) {
|
||||
clearTimeout(tabFocusTimeoutRef.current);
|
||||
}
|
||||
toggleBackgroundShell();
|
||||
if (!isBackgroundShellVisible) {
|
||||
// We are about to show it, so focus it
|
||||
setEmbeddedShellFocused(true);
|
||||
if (backgroundShells.size > 1) {
|
||||
setIsBackgroundShellListOpen(true);
|
||||
}
|
||||
} else {
|
||||
// We are about to hide it
|
||||
tabFocusTimeoutRef.current = setTimeout(() => {
|
||||
tabFocusTimeoutRef.current = null;
|
||||
// If the shell produced output since the tab press, we assume it handled the tab
|
||||
// (e.g. autocomplete) so we should not toggle focus.
|
||||
if (lastOutputTimeRef.current > now) {
|
||||
handleWarning('Press Shift+Tab to focus out.');
|
||||
return;
|
||||
}
|
||||
setEmbeddedShellFocused(false);
|
||||
}, 100);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Not idle, just focus it
|
||||
setEmbeddedShellFocused(true);
|
||||
return true;
|
||||
return false;
|
||||
} else if (keyMatchers[Command.TOGGLE_BACKGROUND_SHELL](key)) {
|
||||
if (activePtyId) {
|
||||
backgroundCurrentShell();
|
||||
// After backgrounding, we explicitly do NOT show or focus the background UI.
|
||||
} else {
|
||||
if (isBackgroundShellVisible && !embeddedShellFocused) {
|
||||
toggleBackgroundShell();
|
||||
// Toggle focus based on intent: if we were hiding, unfocus; if showing, focus.
|
||||
if (!isBackgroundShellVisible && backgroundShells.size > 0) {
|
||||
setEmbeddedShellFocused(true);
|
||||
} else {
|
||||
toggleBackgroundShell();
|
||||
// Toggle focus based on intent: if we were hiding, unfocus; if showing, focus.
|
||||
if (!isBackgroundShellVisible && backgroundShells.size > 0) {
|
||||
setEmbeddedShellFocused(true);
|
||||
if (backgroundShells.size > 1) {
|
||||
setIsBackgroundShellListOpen(true);
|
||||
}
|
||||
} else {
|
||||
setEmbeddedShellFocused(false);
|
||||
if (backgroundShells.size > 1) {
|
||||
setIsBackgroundShellListOpen(true);
|
||||
}
|
||||
} else {
|
||||
setEmbeddedShellFocused(false);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -1613,7 +1604,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
],
|
||||
);
|
||||
|
||||
useKeypress(handleGlobalKeypress, { isActive: true });
|
||||
useKeypress(handleGlobalKeypress, { isActive: true, priority: true });
|
||||
|
||||
useEffect(() => {
|
||||
// Respect hideWindowTitle settings
|
||||
|
||||
Reference in New Issue
Block a user