mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-06-12 20:37:08 -07:00
fix(cli): prevent Termux relaunch and resize remount loops (#27110)
Co-authored-by: Spencer <spencertang@google.com>
This commit is contained in:
committed by
GitHub
parent
6afb109953
commit
ba04e99bea
@@ -126,7 +126,7 @@ async function run() {
|
||||
while (true) {
|
||||
try {
|
||||
const exitCode = await runner();
|
||||
if (exitCode !== RELAUNCH_EXIT_CODE) {
|
||||
if (process.platform === 'android' || exitCode !== RELAUNCH_EXIT_CODE) {
|
||||
process.exit(exitCode);
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
|
||||
@@ -254,7 +254,6 @@ export const AppContainer = (props: AppContainerProps) => {
|
||||
}, [mouseMode, setOptions]);
|
||||
|
||||
const [corgiMode, setCorgiMode] = useState(false);
|
||||
const [forceRerenderKey, setForceRerenderKey] = useState(0);
|
||||
const [debugMessage, setDebugMessage] = useState<string>('');
|
||||
const [quittingMessages, setQuittingMessages] = useState<
|
||||
HistoryItem[] | null
|
||||
@@ -1687,8 +1686,6 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
needsRestart: ideNeedsRestart,
|
||||
restartReason: ideTrustRestartReason,
|
||||
} = useIdeTrustListener();
|
||||
const isInitialMount = useRef(true);
|
||||
|
||||
useIncludeDirsTrust(config, isTrustedFolder, historyManager, setCustomDialog);
|
||||
|
||||
const tabFocusTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
@@ -1741,8 +1738,6 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
const { handleSuspend } = useSuspend({
|
||||
handleWarning,
|
||||
setRawMode,
|
||||
refreshStatic,
|
||||
setForceRerenderKey,
|
||||
shouldUseAlternateScreen,
|
||||
});
|
||||
|
||||
@@ -1753,21 +1748,6 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
}
|
||||
}, [ideNeedsRestart]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isInitialMount.current) {
|
||||
isInitialMount.current = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const handler = setTimeout(() => {
|
||||
refreshStatic();
|
||||
}, 300);
|
||||
|
||||
return () => {
|
||||
clearTimeout(handler);
|
||||
};
|
||||
}, [terminalWidth, refreshStatic]);
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribe = ideContextStore.subscribe(setIdeContextState);
|
||||
setIdeContextState(ideContextStore.get());
|
||||
@@ -2872,7 +2852,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
<ShellFocusContext.Provider value={isFocused}>
|
||||
<MouseProvider mouseEventsEnabled={mouseMode}>
|
||||
<ScrollProvider>
|
||||
<App key={`app-${forceRerenderKey}`} />
|
||||
<App />
|
||||
</ScrollProvider>
|
||||
</MouseProvider>
|
||||
</ShellFocusContext.Provider>
|
||||
|
||||
@@ -83,8 +83,6 @@ describe('useSuspend', () => {
|
||||
it('cleans terminal state on suspend and restores/repaints on resume in alternate screen mode', async () => {
|
||||
const handleWarning = vi.fn();
|
||||
const setRawMode = vi.fn();
|
||||
const refreshStatic = vi.fn();
|
||||
const setForceRerenderKey = vi.fn();
|
||||
const enableSupportedModes =
|
||||
terminalCapabilityManager.enableSupportedModes as unknown as Mock;
|
||||
|
||||
@@ -92,8 +90,6 @@ describe('useSuspend', () => {
|
||||
useSuspend({
|
||||
handleWarning,
|
||||
setRawMode,
|
||||
refreshStatic,
|
||||
setForceRerenderKey,
|
||||
shouldUseAlternateScreen: true,
|
||||
}),
|
||||
);
|
||||
@@ -131,8 +127,6 @@ describe('useSuspend', () => {
|
||||
expect(enableSupportedModes).toHaveBeenCalledTimes(1);
|
||||
expect(enableMouseEvents).toHaveBeenCalledTimes(1);
|
||||
expect(setRawMode).toHaveBeenCalledWith(true);
|
||||
expect(refreshStatic).toHaveBeenCalledTimes(1);
|
||||
expect(setForceRerenderKey).toHaveBeenCalledTimes(1);
|
||||
|
||||
unmount();
|
||||
});
|
||||
@@ -140,15 +134,11 @@ describe('useSuspend', () => {
|
||||
it('does not toggle alternate screen or mouse restore when alternate screen mode is disabled', async () => {
|
||||
const handleWarning = vi.fn();
|
||||
const setRawMode = vi.fn();
|
||||
const refreshStatic = vi.fn();
|
||||
const setForceRerenderKey = vi.fn();
|
||||
|
||||
const { result, unmount } = await renderHook(() =>
|
||||
useSuspend({
|
||||
handleWarning,
|
||||
setRawMode,
|
||||
refreshStatic,
|
||||
setForceRerenderKey,
|
||||
shouldUseAlternateScreen: false,
|
||||
}),
|
||||
);
|
||||
@@ -174,15 +164,11 @@ describe('useSuspend', () => {
|
||||
|
||||
const handleWarning = vi.fn();
|
||||
const setRawMode = vi.fn();
|
||||
const refreshStatic = vi.fn();
|
||||
const setForceRerenderKey = vi.fn();
|
||||
|
||||
const { result, unmount } = await renderHook(() =>
|
||||
useSuspend({
|
||||
handleWarning,
|
||||
setRawMode,
|
||||
refreshStatic,
|
||||
setForceRerenderKey,
|
||||
shouldUseAlternateScreen: true,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -26,16 +26,12 @@ import { Command } from '../key/keyBindings.js';
|
||||
interface UseSuspendProps {
|
||||
handleWarning: (message: string) => void;
|
||||
setRawMode: (mode: boolean) => void;
|
||||
refreshStatic: () => void;
|
||||
setForceRerenderKey: (updater: (prev: number) => number) => void;
|
||||
shouldUseAlternateScreen: boolean;
|
||||
}
|
||||
|
||||
export function useSuspend({
|
||||
handleWarning,
|
||||
setRawMode,
|
||||
refreshStatic,
|
||||
setForceRerenderKey,
|
||||
shouldUseAlternateScreen,
|
||||
}: UseSuspendProps) {
|
||||
const [ctrlZPressCount, setCtrlZPressCount] = useState(0);
|
||||
@@ -108,16 +104,8 @@ export function useSuspend({
|
||||
enableMouseEvents();
|
||||
}
|
||||
|
||||
// Force Ink to do a complete repaint by:
|
||||
// 1. Emitting a resize event (tricks Ink into full redraw)
|
||||
// 2. Remounting components via state changes
|
||||
// Force Ink to do a complete repaint without remounting the app.
|
||||
process.stdout.emit('resize');
|
||||
|
||||
// Give a tick for resize to process, then trigger remount
|
||||
setImmediate(() => {
|
||||
refreshStatic();
|
||||
setForceRerenderKey((prev) => prev + 1);
|
||||
});
|
||||
} finally {
|
||||
if (onResumeHandlerRef.current === onResume) {
|
||||
onResumeHandlerRef.current = null;
|
||||
@@ -142,14 +130,7 @@ export function useSuspend({
|
||||
ctrlZTimerRef.current = null;
|
||||
}, WARNING_PROMPT_DURATION_MS);
|
||||
}
|
||||
}, [
|
||||
ctrlZPressCount,
|
||||
handleWarning,
|
||||
setRawMode,
|
||||
refreshStatic,
|
||||
setForceRerenderKey,
|
||||
shouldUseAlternateScreen,
|
||||
]);
|
||||
}, [ctrlZPressCount, handleWarning, setRawMode, shouldUseAlternateScreen]);
|
||||
|
||||
const handleSuspend = useCallback(() => {
|
||||
setCtrlZPressCount((prev) => prev + 1);
|
||||
|
||||
@@ -46,6 +46,14 @@ import { relaunchAppInChildProcess, relaunchOnExitCode } from './relaunch.js';
|
||||
describe('relaunchOnExitCode', () => {
|
||||
let processExitSpy: MockInstance;
|
||||
let stdinResumeSpy: MockInstance;
|
||||
const originalPlatform = process.platform;
|
||||
|
||||
const setPlatform = (platform: NodeJS.Platform) => {
|
||||
Object.defineProperty(process, 'platform', {
|
||||
value: platform,
|
||||
configurable: true,
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
processExitSpy = vi.spyOn(process, 'exit').mockImplementation(() => {
|
||||
@@ -60,6 +68,7 @@ describe('relaunchOnExitCode', () => {
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs();
|
||||
setPlatform(originalPlatform);
|
||||
processExitSpy.mockRestore();
|
||||
stdinResumeSpy.mockRestore();
|
||||
});
|
||||
@@ -92,6 +101,18 @@ describe('relaunchOnExitCode', () => {
|
||||
expect(processExitSpy).toHaveBeenCalledWith(0);
|
||||
});
|
||||
|
||||
it('should not relaunch on Android when RELAUNCH_EXIT_CODE is returned', async () => {
|
||||
setPlatform('android');
|
||||
const runner = vi.fn().mockResolvedValue(RELAUNCH_EXIT_CODE);
|
||||
|
||||
await expect(relaunchOnExitCode(runner)).rejects.toThrow(
|
||||
'PROCESS_EXIT_CALLED',
|
||||
);
|
||||
|
||||
expect(runner).toHaveBeenCalledTimes(1);
|
||||
expect(processExitSpy).toHaveBeenCalledWith(RELAUNCH_EXIT_CODE);
|
||||
});
|
||||
|
||||
it('should handle runner errors', async () => {
|
||||
const error = new Error('Runner failed');
|
||||
const runner = vi.fn().mockRejectedValue(error);
|
||||
|
||||
@@ -20,7 +20,7 @@ export async function relaunchOnExitCode(runner: () => Promise<number>) {
|
||||
try {
|
||||
const exitCode = await runner();
|
||||
|
||||
if (exitCode !== RELAUNCH_EXIT_CODE) {
|
||||
if (process.platform === 'android' || exitCode !== RELAUNCH_EXIT_CODE) {
|
||||
process.exit(exitCode);
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user