diff --git a/packages/cli/src/gemini.test.tsx b/packages/cli/src/gemini.test.tsx index 41f9978d7c1..2e55c9b25d1 100644 --- a/packages/cli/src/gemini.test.tsx +++ b/packages/cli/src/gemini.test.tsx @@ -238,18 +238,15 @@ vi.mock('./validateNonInterActiveAuth.js', () => ({ })); describe('gemini.tsx main function', () => { - let originalEnvGeminiSandbox: string | undefined; - let originalEnvSandbox: string | undefined; let originalIsTTY: boolean | undefined; let initialUnhandledRejectionListeners: NodeJS.UnhandledRejectionListener[] = []; beforeEach(() => { // Store and clear sandbox-related env variables to ensure a consistent test environment - originalEnvGeminiSandbox = process.env['GEMINI_SANDBOX']; - originalEnvSandbox = process.env['SANDBOX']; - delete process.env['GEMINI_SANDBOX']; - delete process.env['SANDBOX']; + vi.stubEnv('GEMINI_SANDBOX', ''); + vi.stubEnv('SANDBOX', ''); + vi.stubEnv('SHPOOL_SESSION_NAME', ''); initialUnhandledRejectionListeners = process.listeners('unhandledRejection'); @@ -260,18 +257,6 @@ describe('gemini.tsx main function', () => { }); afterEach(() => { - // Restore original env variables - if (originalEnvGeminiSandbox !== undefined) { - process.env['GEMINI_SANDBOX'] = originalEnvGeminiSandbox; - } else { - delete process.env['GEMINI_SANDBOX']; - } - if (originalEnvSandbox !== undefined) { - process.env['SANDBOX'] = originalEnvSandbox; - } else { - delete process.env['SANDBOX']; - } - const currentListeners = process.listeners('unhandledRejection'); currentListeners.forEach((listener) => { if (!initialUnhandledRejectionListeners.includes(listener)) { @@ -282,6 +267,7 @@ describe('gemini.tsx main function', () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any (process.stdin as any).isTTY = originalIsTTY; + vi.unstubAllEnvs(); vi.restoreAllMocks(); }); @@ -1209,7 +1195,12 @@ describe('startInteractiveUI', () => { registerTelemetryConfig: vi.fn(), })); + beforeEach(() => { + vi.stubEnv('SHPOOL_SESSION_NAME', ''); + }); + afterEach(() => { + vi.unstubAllEnvs(); vi.restoreAllMocks(); }); @@ -1308,7 +1299,7 @@ describe('startInteractiveUI', () => { // Verify all startup tasks were called expect(getVersion).toHaveBeenCalledTimes(1); - expect(registerCleanup).toHaveBeenCalledTimes(3); + expect(registerCleanup).toHaveBeenCalledTimes(4); // Verify cleanup handler is registered with unmount function const cleanupFn = vi.mocked(registerCleanup).mock.calls[0][0]; diff --git a/packages/cli/src/gemini.tsx b/packages/cli/src/gemini.tsx index 68ce4c99b64..a18f3ace378 100644 --- a/packages/cli/src/gemini.tsx +++ b/packages/cli/src/gemini.tsx @@ -57,8 +57,8 @@ import { writeToStderr, disableMouseEvents, enableMouseEvents, - enterAlternateScreen, disableLineWrapping, + enableLineWrapping, shouldEnterAlternateScreen, startupProfiler, ExitCodes, @@ -89,6 +89,7 @@ import { SessionStatsProvider } from './ui/contexts/SessionContext.js'; import { VimModeProvider } from './ui/contexts/VimModeContext.js'; import { KeypressProvider } from './ui/contexts/KeypressContext.js'; import { useKittyKeyboardProtocol } from './ui/hooks/useKittyKeyboardProtocol.js'; +import { useTerminalSize } from './ui/hooks/useTerminalSize.js'; import { relaunchAppInChildProcess, relaunchOnExitCode, @@ -214,9 +215,13 @@ export async function startInteractiveUI( const { stdout: inkStdout, stderr: inkStderr } = createWorkingStdio(); + const isShpool = !!process.env['SHPOOL_SESSION_NAME']; + // Create wrapper component to use hooks inside render const AppWrapper = () => { useKittyKeyboardProtocol(); + const { columns, rows } = useTerminalSize(); + return ( setTimeout(resolve, 100)); + } + const instance = render( process.env['DEBUG'] ? ( @@ -273,10 +290,19 @@ export async function startInteractiveUI( patchConsole: false, alternateBuffer: useAlternateBuffer, incrementalRendering: - settings.merged.ui.incrementalRendering !== false && useAlternateBuffer, + settings.merged.ui.incrementalRendering !== false && + useAlternateBuffer && + !isShpool, }, ); + if (useAlternateBuffer) { + disableLineWrapping(); + registerCleanup(() => { + enableLineWrapping(); + }); + } + checkForUpdates(settings) .then((info) => { handleAutoUpdate(info, settings, config.getProjectRoot()); @@ -590,26 +616,13 @@ export async function main() { // input showing up in the output. process.stdin.setRawMode(true); - if ( - shouldEnterAlternateScreen( - isAlternateBufferEnabled(settings), - config.getScreenReader(), - ) - ) { - enterAlternateScreen(); - disableLineWrapping(); - - // Ink will cleanup so there is no need for us to manually cleanup. - } - // This cleanup isn't strictly needed but may help in certain situations. - const restoreRawMode = () => { + process.on('SIGTERM', () => { process.stdin.setRawMode(wasRaw); - }; - process.off('SIGTERM', restoreRawMode); - process.on('SIGTERM', restoreRawMode); - process.off('SIGINT', restoreRawMode); - process.on('SIGINT', restoreRawMode); + }); + process.on('SIGINT', () => { + process.stdin.setRawMode(wasRaw); + }); } await setupTerminalAndTheme(config, settings); diff --git a/packages/cli/src/ui/AppContainer.tsx b/packages/cli/src/ui/AppContainer.tsx index 7d863a638fe..97e1cec2b7d 100644 --- a/packages/cli/src/ui/AppContainer.tsx +++ b/packages/cli/src/ui/AppContainer.tsx @@ -363,7 +363,9 @@ export const AppContainer = (props: AppContainerProps) => { (async () => { // Note: the program will not work if this fails so let errors be // handled by the global catch. - await config.initialize(); + if (!config.isInitialized()) { + await config.initialize(); + } setConfigInitialized(true); startupProfiler.flush(config); diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index 1570339010f..6d811799bc6 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -905,6 +905,10 @@ export class Config { ); } + isInitialized(): boolean { + return this.initialized; + } + /** * Must only be called once, throws if called again. */