Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
22 changes: 1 addition & 21 deletions packages/cli/src/ui/AppContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -1741,8 +1738,6 @@ Logging in with Google... Restarting Gemini CLI to continue.
const { handleSuspend } = useSuspend({
handleWarning,
setRawMode,
refreshStatic,
setForceRerenderKey,
shouldUseAlternateScreen,
});

Expand All @@ -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());
Expand Down Expand Up @@ -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>
Expand Down
14 changes: 0 additions & 14 deletions packages/cli/src/ui/hooks/useSuspend.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,17 +83,13 @@ 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;

const { result, unmount } = await renderHook(() =>
useSuspend({
handleWarning,
setRawMode,
refreshStatic,
setForceRerenderKey,
shouldUseAlternateScreen: true,
}),
);
Expand Down Expand Up @@ -131,24 +127,18 @@ describe('useSuspend', () => {
expect(enableSupportedModes).toHaveBeenCalledTimes(1);
expect(enableMouseEvents).toHaveBeenCalledTimes(1);
expect(setRawMode).toHaveBeenCalledWith(true);
expect(refreshStatic).toHaveBeenCalledTimes(1);
expect(setForceRerenderKey).toHaveBeenCalledTimes(1);

unmount();
});

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,
}),
);
Expand All @@ -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,
}),
);
Expand Down
23 changes: 2 additions & 21 deletions packages/cli/src/ui/hooks/useSuspend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand Down
21 changes: 21 additions & 0 deletions packages/cli/src/utils/relaunch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(() => {
Expand All @@ -60,6 +68,7 @@ describe('relaunchOnExitCode', () => {

afterEach(() => {
vi.unstubAllEnvs();
setPlatform(originalPlatform);
processExitSpy.mockRestore();
stdinResumeSpy.mockRestore();
});
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/utils/relaunch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Loading